I denna lektion skall vi bekanta oss med en 7-segments LED, de finns i alla storlekar och utförande med allt från en siffra till några stycken och de är användbara för att visa värden, men inte text då varje siffra har endast 7 segment plus en decimalpunkt. Den vi skall arbeta med här i denna lektion är röd har 4 siffror, kommer från SparkFun och kan beställas i vår webbutik här och finns i ett antal olika färger. Den röda ingick även i vårt “Add On Kit” Starterkit” (säljs inte längre).
Ovan är en bild på den display vi använder, den består av 4 siffror med decimalpunkter för varje siffra, kolon i mitten (användbart om man gör en klocka) samt en extra lysdiod L3 som kan t.ex. indikera grader etc. På bilden syns även vad man kallar de olika segmenten (A-G samt DP).
Ovan visar hur denna display med 4 siffror och 7 segment (plus decimalpunkt och extra lysdioder) är inkopplad internt. Det skulle bli en ganska komplicerad inkoppling (och massa pinnar) om varje lysdiodselement skulle vara tillgängligt externt, så denna typen av displayer är kopplade i en matris, där varje siffra har en gemensam anod och sedan är alla katoder på segmenten parallellkopplade (8 segment med decimaltecken), sedan har kolontecknet en inkoppling och den lysdiod (L3) en egen. I vårt exempel kommer vi inte att använda dessa extra lysdioder (L1-L3). För att visa ett värde på en display av denna typ tar man en siffra i taget genom att sätta anoden för den aktuella siffran hög (5VDC) sedan sätter man de segment som man vill lysa låg (0VDC), de andra som inte skall lysa sätter man hög (5VDC), när man visat den aktuella sifran, låt oss säga 2mS, så tar man nästa siffra och gör samma sak. De siffror som är inaktiva sätter man låg (0VDC). Sedan snurrar man runt på detta viset hela tiden och gör man detta tillräckligt snabbt (mer än 24-30ggr/sekund) så upplever ögat att alla siffror syns samtidigt. Så antalet I/O som går åt är 8st (en för varje segment) samt en I/O per siffra, i vårt fall 4st I/O, dvs. totalt 12st I/O. Har man 2 displayer som är likadana, ja då räcker det med att vi parallellkopplar alla segment enskilt och tar ytterligare 4 I/O för de nya siffrorna, dvs. 16 I/O.
Uppkopplingen är ganska enkel, det krävs 8 resistorer (vi använder 330ohm eftersom de kommer med vårt Starterkit) för att strömbegränsa varje segment. Dock är det inte lätt att visa inkopplingen på ett foto.
Därför har vi gjort en enkel bild på inkopplingen ovan där siffrorna överst och underst är den I/O vi ansluter dem till på vårt utvecklingskort samt även de 8 resistorerna och hur de kopplas in.
Koden till lektionen kan laddas ner här. Programmet är mycket enkelt uppbyggt och kan inte användas i mer avancerade projekt, då uppdateringen måste ske kontinuerligt för att inte displayen skall slockna eller blinka till, men för en enkel test för att demonstrera hur man skriver kod för denna typen av displayer duger koden väl (och enklare program där man inte måste hantera tidskrävande uppgifter som gör att displayen inte kan uppdateras tillräckligt ofta).
const int dig1 = 10; const int dig2 = 11; const int dig3 = 12; const int dig4 = 13; const int segA = 4; const int segB = 2; const int segC = 5; const int segD = 7; const int segE = 8; const int segF = 6; const int segG = 3; const int segDP = 9; int tmp = 0;
Först definierar vi upp var de olika segmenten (katoderna) och siffrorna (anoderna) är anslutna, genom att göra detta kan vi lätt ändra detta om vi vill ha en annan inkoppling. Notera att vi här introducerar en ny sak som vi tidigare inte använt, nämligen “const” framför en variabel. Det betyder Konstant, dsv. en “variabel” vi inte har för avseende att ändra i hela programmet. Fördelen med att använda “const” är att “variabeln” ligger kvar i Flashminnet och tar inte upp plats i RAM-minnet (vi har mer Flash än RAM i vår microkontroller som är en ATMega328P). Använder man inte “const”, så kommer programmet att skapa plats i RAM-minnet och kopiera över värdet från Flash, men eftersom vi inte har för avseende att ändra inkopplingen under programmets gång, så spar vi både RAM och Flash (ja, vi sparar Flashminne med, då koden för att kopiera över från Flash till RAM sparas in, prova att ta bort “const” framför varie variabel och se på programstorleken). Slutligen har vi en variabel tmp, men den är inte en konstant, då den ändras under programmets gång, så den deklarerar vi på vanligt vis.
void showDigit(int digit, int decPoint) { switch (digit) { case 0: digitalWrite(segA, LOW); digitalWrite(segB, LOW); digitalWrite(segC, LOW); digitalWrite(segD, LOW); digitalWrite(segE, LOW); digitalWrite(segF, LOW); digitalWrite(segG, HIGH); break; case 1: digitalWrite(segA, HIGH); digitalWrite(segB, LOW); digitalWrite(segC, LOW); digitalWrite(segD, HIGH); digitalWrite(segE, HIGH); digitalWrite(segF, HIGH); digitalWrite(segG, HIGH); break; . . . } if (decPoint) { digitalWrite(segDP, LOW); } else { digitalWrite(segDP, HIGH); }
}
Vi har sedan en rutin showDigit() som skriver ut en siffra, dvs aktiverar de aktuella segmenten som skall lysa och de som skall vara släckta, det gör vi med switch() satsen, vi visar endast hur det fungerar för siffran 0 och 1, resten är utelämnat och se den fulla källkoden för mer information. Men vi har definierat upp alla segment för siffrorna 0-9 samt en för blank, dvs. ingen siffra. Dessutom tar denna rutin en andra parameter, nämligen om vi vill visa decimalpunkten eller inte för den aktuella siffran. Varje segment drivs låg “LOW” om vi skall segmentet skall lysa och hög “HIGH” om det skall vara släckt. Vi kan även utöka denna rutin att visa vissa bokstäver som kan tolkas med 7 segment, typ C, E, F osv. och även minus “-” för att visa negativa tal, men då vi håller detta program på enklast möjliga nivå så utelämnar vi detta.
void showNumber(int value, int decPoint) { int digit; // // Loop through all 4 digits, starting at the LSD (Least Significant Digit) // for (digit = 4; digit > 0; digit--) { // // Turn OFF all 4 digits // digitalWrite (dig1, LOW); digitalWrite (dig2, LOW); digitalWrite (dig3, LOW); digitalWrite (dig4, LOW); // // Get out one digit (from right) from the value // if (decPoint == digit) { showDigit((value % 10), 1); // Show the Decimal Point } else { showDigit((value % 10), 0); // Hide the Decimal Point } value = value / 10; // // Turn ON the active digit for a short time // switch(digit) { case 1: digitalWrite(dig1, HIGH); break; case 2: digitalWrite(dig2, HIGH); break; case 3: digitalWrite(dig3, HIGH); break; case 4: digitalWrite(dig4, HIGH); break; } delay(2); } }
Vår andra rutin som vi anropar från huvudloppen är showNumber() som sköter hela displayen och anropar i sin tur showDigit(), vår rutin tar in ett heltal samt var kommatecknet skall vara placerat. Det första som vi gör i denna rutin är att stänga av alla siffror genom att sätta alla 4 anoder till 0VDC (så slipper vi komma ihåg vilken som var tänd sist), sedan uppdaterar vi alla siffror genom att ta vårt värde vi skall visa och här använder vi något nytt, nämligen %, den operationen heter modulo (eller modulus) och ni kan läsa mer hur den fungerar här om ni glömt hur “rest” fungerar vid heltalsdivision. Notera att vi börjar från med siffra 4 (DIG.4 på schemat) den som är längst till höger och kallas LSD (Least Significant Digit)
Men för att ta ett enkelt exempel: Vi anropar denna rutin med talet 177. Vi vill ju då visa siffran 0, 1, 7 och 7. Genom att dela 177 med 10 så går det 17ggr och vi får en rest på 7 (17 * 10 = 170, rest = 7). Det är just denna rest vi är intresserade av för att visa en siffra i taget, vi uppdaterar den 4 siffran (längst till höger) med 7, sedan utför vi divisionen 177 med 10 och vi får då kvar 17 i heltal. Nästa runda delar vi 17 med 10 (17 / 10 = 1, rest = 7) för att få fram en rest på 7 igen, vi uppdaterar den tredje siffran (andra från höger) med 7. Den tredje rundan delar vi 1 med 10 (1 / 10 = 0, rest = 1) och där har vi vår etta på den andra siffran (tredje från från höger). Sista rundan delar vi 0 med 10 (0 / 10 = 0, rest 0) och vi får en nolla som den första siffran från vänster, dvs. MSD (Most Significant Digit).
I vår rutin sätter vi den aktuella siffrans anod hög för att driva ström lågt genom alla segment som är aktivt låga. Slutligen pausar vi 2mS innan vi uppdaterar nästa siffra, detta så de inte skall blinka allt för mycket.
void setup() { pinMode(segA, OUTPUT); pinMode(segB, OUTPUT); pinMode(segC, OUTPUT); pinMode(segD, OUTPUT); pinMode(segE, OUTPUT); pinMode(segF, OUTPUT); pinMode(segG, OUTPUT); pinMode(segDP, OUTPUT); pinMode(dig1, OUTPUT); pinMode(dig2, OUTPUT); pinMode(dig3, OUTPUT); pinMode(dig4, OUTPUT); }
I vår setup() rutin sätter vi alla de I/O vi använder till utgångar, inget konstigt med det.
void loop() { showNumber((millis() / 100), 3); }
Slutligen i vår huvudloop gör vi endast en sak, nämligen anropar millis() och delar värdet med 100, då får vi nämligen ut ett värde som indikerar hur många tiondelars sekunder programmet var varit igång. Vi anropar showNumber() med detta värde samt indikerar att vi vill visa vårt decimaltecken på den tredje siffran från vänster. Har du kopplat in rätt och laddat ner koden så kommer displayen börja på 000.0 och räkna upp till 999.9 och vi har gjort oss en enkel timer som visar sekunderna med tiondelar.
Vi kommer senare att göra detta enkla exempel till en mer generellt användbar rutin som är helt oberoende på vad vi gör, den sköts i bakgrunden och vi kommer då att göra den till ett bibliotek (LIB) som vi lätt kan använda i andra projekt.