8051 Microcontroller Embedded Systemen Een gids voor XC888 toepassingen. Versie: 1.0 01/10/2010 2.0 11/07/2011 3.0 05/07/2014 Auteurs: Pauwels D. Roggemans M.
Inhoudsopgave: Inhoudsopgave: .......................................................................................................... 2 Inleiding ...................................................................................................................... 9 Hoofdstuk 1 Historiek ............................................................................................ 10 1.1 Inleiding ...................................................................................................... 10 1.2 Historiek ..................................................................................................... 10 Hoofdstuk 2 Embedded systemen ........................................................................ 15 2.1 Inleiding ...................................................................................................... 15 2.2 Wat is er uniek aan het ontwerp van embedded systemen? ...................... 17 2.3 Hoe krachtig zijn embedded processoren? ................................................ 17 2.4 Embedded systemen werken meestal "REAL-TIME" ................................. 20 2.5 Een typisch microprocessorsysteem .......................................................... 20 2.5.1 MTBF ................................................................................................... 21 2.5.2 Ontwerp van een PCB ......................................................................... 23 2.5.3 Overige nadelen................................................................................... 23 2.6 Een microcontroller systeem ...................................................................... 23 2.7 Wat is er op de markt? ............................................................................... 25 2.8 Een voorbeeld uit elke categorie ................................................................ 27 2.8.1 4-Bit controller: EM6626....................................................................... 27 2.8.2 8-Bit controller: XC888 ......................................................................... 29 2.8.3 16-bit controller: XC167 van Infineon .................................................. 30 2.8.4 32-Bit controller: De MB91F361 van Fujitsu........................................ 31 Hoofdstuk 3 8051 hardware .................................................................................. 33 3.1 Inleiding ...................................................................................................... 33 3.2 De oorspronkelijke 8051 architectuur ......................................................... 34 3.3 Memory map van de 8051 CPU ................................................................. 35 3.3.1 De inwendige geheugenruimte ............................................................ 36 3.3.2 De externe systeemarchitectuur .......................................................... 39 3.3.2.1 De externe geheugenruimte .......................................................... 39 3.3.2.2 De bus architectuur ....................................................................... 43 3.3.2.3 De externe timing .......................................................................... 44 3.4 I/O systemen .............................................................................................. 48 3.4.1 Mapping/Paging van SFR’s (XC888) ................................................... 49 HOOFDSTUK 4 De 8051 instructieset ................................................................... 52 4.1 Inleiding ......................................................................................................... 52 4.2 De CPU en zijn basisregisters. ................................................................... 54 4.2.1 De PSW .................................................................................................... 55 4.3 Adresseringsmethoden .............................................................................. 57 4.3.1 Algemeen ............................................................................................. 57 4.3.2 Voorbeelden......................................................................................... 59 MOV A,#03FH ............................................................................................ 59 MOV A,03FH .............................................................................................. 60 MOV A,Rn................................................................................................... 61 MOV A,@Ri ................................................................................................ 62 MOVX A,@DPTR...................................................................................... 63 MOVX @DPTR,A...................................................................................... 63 MOVC A,@A+DPTR .................................................................................. 63 MOVX @Ri,A ............................................................................................ 64 MOVX A,@Ri ............................................................................................ 64
Voorbeeldprogramma’s algemene opmerkingen en eerste voorbeeld: .......... 65 LJMP 16bit_address ................................................................................ 66 naam EQU getal ..................................................................................... 67 naam DATA getal ................................................................................... 67 naam BIT getal ....................................................................................... 67 Voorbeeldprogramma EQU: .......................................................................... 67 4.4 Wiskundige instructies................................................................................ 68 4.4.1 Inleiding................................................................................................ 68 Overzicht talstelsels: ...................................................................................... 69 4.4.2 ADD(C) ................................................................................................ 71 Voorbeeld van een 8 bit optelling ................................................................... 71 Voorbeeld van een 16 bit optelling ................................................................. 72 4.4.3 SUBB ................................................................................................... 73 Voorbeeld van een 8 bit verschil .................................................................... 73 Voorbeeld van een 16 bit verschil .................................................................. 74 4.4.4 INC/DEC .............................................................................................. 75 Voorbeeld inc en dec instructies .................................................................... 75 4.4.5 INC DPTR ............................................................................................ 76 4.4.6 MUL AB................................................................................................ 76 Voorbeeld mul instructie ................................................................................ 76 4.4.7 DIV AB ................................................................................................. 77 4.4.8 DA A .................................................................................................... 78 4.5 Logische instructies .................................................................................... 79 4.5.1 Inleiding................................................................................................ 79 4.5.2 ANL/ORL/XRL...................................................................................... 79 Voorbeelden orl,anl,xrl ................................................................................... 79 4.5.3 Specifieke accumulator instructies ....................................................... 80 CLR A ............................................................................................................ 80 CPL A ............................................................................................................ 80 Voorbeelden .................................................................................................. 80 Rotate ............................................................................................................ 80 Voorbeelden RL, RR, RLC en RRC ............................................................... 80 SWAP A ......................................................................................................... 81 Voorbeeld SWAP A........................................................................................ 81 4.6 Verplaats instructies ................................................................................... 82 4.6.1 De exchange instructie ........................................................................ 82 4.6.2 De systeemstack, stackpointer en de PUSH / POP instructies. ........... 82 4.6.2.1 De systeemstack en stackpointer (call instructies) ......................... 82 4.6.2.2 Push en POP instructies................................................................. 84 PUSH register ............................................................................................... 85 POP register ................................................................................................. 85 Voorbeelden push en pop .............................................................................. 86 Voorbeeld gebruik stack ................................................................................ 89 4.7 Bit manipulaties .......................................................................................... 90 4.7.1 Inleiding................................................................................................ 90 4.7.2 Berekeningen op bit variabelen............................................................ 91 Voorbeelden van bewerkingen op bitvariabelen ............................................ 92 4.8 Programma- en machinecontrole ............................................................... 93 4.8.1 Inleiding................................................................................................ 93 4.8.2 Jump instructies ................................................................................... 95
3
LJMP.............................................................................................................. 95 Voorbeeld van een ljmp ................................................................................. 95 AJMP ............................................................................................................. 95 4.8.3 Relatieve sprong instructies ................................................................. 95 SJMP ............................................................................................................. 96 Voorbeeld sjmp instructie ............................................................................... 96 4.8.4 Relatieve voorwaardelijke sprong instructies ....................................... 96 JC .................................................................................................................. 96 JNC ................................................................................................................ 96 JB................................................................................................................... 96 JNB ................................................................................................................ 96 JBC ................................................................................................................ 96 JZ ................................................................................................................... 96 JNZ ................................................................................................................ 96 CJNE ............................................................................................................. 96 Extra mogelijkheid CNJE instructie ................................................................ 97 DJNZ.............................................................................................................. 97 Voorbeelden van spronginstructies ................................................................ 98 4.8.5 De (L)CALL instructie ........................................................................... 98 4.8.6 De NOP instructie .............................................................................. 101 4.9 Uitvoeringstijd van een instructie ............................................................. 101 Hoofdstuk 5 Toepassingen ................................................................................. 104 5.1 Inleiding .................................................................................................... 104 5.2 I/O driver xcez1.inc................................................................................... 104 5.2.1 Inleiding.............................................................................................. 104 5.2.2 LED’s en dipswitch ............................................................................. 105 Voorbeeld initialisatie LED’s en schakelaars (onvolledig) ............................ 109 Voorbeeld initialisatie LED’s en schakelaars (volledig) ................................ 111 Testprogramma kopieer schakelaars naar de LED’s ................................... 111 Testprogramma met xcez1.inc driver ........................................................... 112 5.2.3 Functie toetsen .................................................................................. 112 5.2.3.1 Van schakelaar tot keyboard, algemeenheden… ......................... 114 5.2.3.2 De keyboard hardware basics. ..................................................... 114 5.2.3.3 Een matrix (keyboard) scanning algoritme. .................................. 115 5.2.4 LCD scherm ....................................................................................... 118 Voorbeelden gebruik LCD display, backlight en buzzer: ............................. 121 db ........................................................................................................... 123 In/uit schakelen backlight/buzzer ................................................................. 123 5.2.5 I2C bus............................................................................................... 124 Voorbeeldprogramma 1 uitlezen LM75 ........................................................ 125 Voorbeeldprogramma 2 uitlezen LM75 ........................................................ 126 5.2.6 ADC ................................................................................................... 127 Voorbeeldprogramma inlezen potentiometer ............................................... 130 5.2.7 SIO ..................................................................................................... 130 Voorbeeldprogramma SIO/UART ................................................................ 132 5.2.8 Wiskundige berekeningen .................................................................. 133 Voorbeeldprogramma wiskundige routines .................................................. 134 5.2.9 Diverse ............................................................................................... 135 5.3 Tips en trucs ............................................................................................. 136 5.3.1 Inleiding.............................................................................................. 136
4
5.3.2 Software tijdsvertraging ..................................................................... 136 Voorbeeldprogramma looplicht met software tijdsvertraging ....................... 136 5.3.3 Aanmaken en uitlezen van tabellen ................................................... 138 Voorbeeldprogramma: looplicht met tabel (tellen aantal bytes in tabel) ...... 138 Voorbeeldprogramma: looplicht met tabel (verboden code) ....................... 140 Voorbeeldprogramma: afdrukken tekst uit tabel (PC of LCD) ..................... 141 5.4 Een tabel gebruiken om te vertalen voor linearisering .............................. 142 5.5 HEX, BCD, BIN inlezen en uitschrijven .................................................... 145 5.6 Omrekening HEX naar BCD ..................................................................... 148 5.7 Omrekening BCD naar HEX ..................................................................... 152 5.8 Vermenigvuldigen en delen van grote getallen ........................................ 154 5.9 Vierkantswortel berekenen ....................................................................... 159 5.11 Referenties naar de ftp site ...................................................................... 162 Hoofdstuk 6 XC888 hardware units .................................................................... 163 6.1 Inleiding .................................................................................................... 163 6.2 Interrupt .................................................................................................... 164 6.2.1 Inleiding.............................................................................................. 164 6.2.2 Interrupt bronnen ............................................................................... 166 6.2.3 Interrupt vectoren ............................................................................... 166 6.2.4 Interrupt prioriteit ................................................................................ 168 6.2.5 Interrupt initialisatie ............................................................................ 170 6.2.6 Vormvereisten interrupt routines ........................................................ 172 Voorbeeld van programma met een timer 0 interrupt................................... 172 Voorbeeldprogramma uurwerk .................................................................... 175 6.3 Timer0 en timer1 ...................................................................................... 177 6.3.1 Inleiding.............................................................................................. 177 6.3.2 16 bit timer/counter ............................................................................ 178 6.3.2.1 16 bit timers: periodemeting ......................................................... 181 6.3.2.2 16 bit genereren van periodische timeroverflows of 'timeticks' .... 183 6.3.3 8 bit auto reload mode ....................................................................... 185 6.3.3.1 8 bit TIMER met autoreload voor het genereren van timeticks ..... 186 6.3.4 Controleregisters................................................................................ 187 6.4 Timer2 en timer 21 ................................................................................... 190 6.4.1 Inleiding.............................................................................................. 190 6.4.2 Basis counter T2 ................................................................................ 190 6.4.3 Auto reload mode en toepassingen ................................................... 191 6.4.4 Capture mode .................................................................................... 195 6.4.5 Controleregisters................................................................................ 197 6.5 Capture/Compare Unit 6 (CCU6) ............................................................. 197 6.5.1 Inleiding.............................................................................................. 197 6.5.2 PWM sturing ...................................................................................... 198 6.6 UART en UART1 ...................................................................................... 201 6.6.1 Inleiding.............................................................................................. 201 6.6.2 Baud rate generator ........................................................................... 204 6.6.3 SFR registers UART .......................................................................... 206 Voorbeeldprogramma initialisatie subroutine UART 9600 baud .................. 209 Voorbeeldprogramma zenden 1 byte ........................................................... 210 Voorbeeldprogramma ontvangen 1 byte ...................................................... 210 6.6.4 LIN interface....................................................................................... 210 6.7 SSC .......................................................................................................... 211
5
6.7.1 Inleiding.............................................................................................. 211 6.7.2 Controleregisters................................................................................ 212 6.8 CAN.......................................................................................................... 213 6.8.1 Inleiding.............................................................................................. 213 6.8.2 CAN SFR’s......................................................................................... 214 6.9 Analoog naar digitaal omvormer (ADC) .................................................... 214 6.9.1 Inleiding.............................................................................................. 214 6.9.2 ADC instellingen en gebruik ............................................................... 218 Voorbeeldprogramma ADC .......................................................................... 219 6.10 MDU ......................................................................................................... 220 6.10.1 Inleiding .......................................................................................... 220 6.10.2 Werking en gebruik van de MDU .................................................... 220 6.10.3 XCEZ1 driver en MDU .................................................................... 222 6.11 CORDIC ................................................................................................... 223 6.11.1 Inleiding .......................................................................................... 223 6.11.2 CORDIC werking in de “rotation mode”. .......................................... 224 A) Vectorrotatie over een hoek ............................................................ 224 B) Rotatie van de eenheidsvector over een hoek ................................ 224 C) Opdeling van de hoek in en aantal (getabuleerde) subhoeken ....... 225 D) De iteratie procedure .......................................................................... 225 E) Vereenvoudigingen voor de hardware-implementatie ......................... 226 6.11.3 CORDIC werking in “vector mode” ................................................. 230 A) Bepaling van ARCSIN via CORDIC .................................................... 232 B) Bepaling van ARCCOS via CORDIC .................................................. 233 6.11.4 Uitbreiding van CORDIC naar lineaire en hyperbolische functies. .. 233 A) Berekening van lineaire functies met CORDIC (m=0) ......................... 234 B) Berekening van hyperbolische functies met CORDIC (m=-1) ............. 235 6.12 WDT ......................................................................................................... 239 6.12.1 Inleiding .......................................................................................... 239 6.12.2 SFR’s en instellingen watchdog ...................................................... 239 6.13 OCDS ....................................................................................................... 241 6.13.1 inleiding .......................................................................................... 241 6.14 ROM floating point routines ...................................................................... 241 6.14.1 Inleiding .......................................................................................... 241 6.14.2 Rom routines en hun gebruik .......................................................... 241 6.14.3 FP-notatie ....................................................................................... 249 6.15 Low power modes .................................................................................... 251 6.15.1 Inleiding .......................................................................................... 251 6.15.2 Uitschakelen onderdelen microcontroller ........................................ 251 6.15.3 Algemene low power modes ........................................................... 251 De ACTIVE mode ........................................................................................ 252 De IDLE mode ............................................................................................. 252 De POWER-DOWN mode ........................................................................... 252 De SLOW-DOWN mode .............................................................................. 253 Stroomverbruik XC888 ................................................................................. 254 6.16 Flash als data opslag ............................................................................... 255 6.16.1 Inleiding .......................................................................................... 255 6.16.2 IAP (in application programming).................................................... 257 6.17 Systeem klok ............................................................................................ 259 6.17.1 Inleiding .......................................................................................... 259 6
6.17.2 Blokdiagram klokgenerator ............................................................. 260 6.17.3 Klokgenerator na reset ................................................................... 261 6.17.4 Omschakelen tussen RC en kristal ................................................. 262 Voorbeeldprogramma omschakelen RC naar kristal 12MHz ....................... 262 Besluit & bedenkingen ............................................................................................ 264 Appendix A XC888 instructionset (snel geheugen) ............................................ 265 Appendix B XC888 instructionset (reële core) ................................................... 270 Appendix C SFR pages (XC888) ....................................................................... 275 Appendix D xcez0.inc functies beknopt .............................................................. 277 Appendix E ASCII code tabel ............................................................................. 279 Bronvermelding: ..................................................................................................... 280
7
8
Inleiding Deze cursus kan gebruikt worden als zelfstudiemateriaal of als gids bij het volgen van de lessen. In beide gevallen is de aangereikte informatie slechts indicatief en niet algemeen toepasbaar. De auteur kan ook niet aansprakelijk gesteld worden voor eventuele onjuistheden die in het cursus materiaal zijn geslopen. Het is dus noodzakelijk voor elke specifieke toepassing het nodige opzoekwerk en berekeningen uit te voeren. Het WWW kan hierbij een nuttige bron van bijkomende informatie zijn. Gelet op het algemene karakter van de aangeboden informatie zal de cursist de nodige kritische ingesteldheid moeten tonen bij het verwerken ervan. Dit is ook zo bij het verwerken van informatie die via andere bronnen bekomen wordt. De cursus bevat informatie die op verschillende controllers/computers toepasbaar is. De familie van 8051 microcontrollers staat centraal. Meer specifiek wordt de XC888 microcontroller van Infineon als voorbeeld gebruikt. De beoordeling van het vak verloopt via een praktijkexamen. De student moet een professionele oplossing kunnen aanreiken voor gerelateerde problemen. Dit houdt in (indien van toepassing) een schema opstellen, verklaren van de principiële werking en verduidelijken van gebruikte begrippen, het opstellen van een flowchart, schrijven en debuggen van een programma. Het is duidelijk dat een combinatie van verschillende technieken noodzakelijk kan zijn. De wijze van evaluatie is voor alle types van studiecontracten, en voor alle evaluatieperiodes dezelfde. Door de uitvoerige bespreking van die voorbeelden tijdens de hoorcolleges willen we een kritische ingesteldheid aanmoedigen. Een actieve participatie aan de lessen is dan ook aanbevolen. De cursus is bedoeld, tijdens en na de studies, als naslagwerk. Eventuele constructieve kritiek en voorstellen tot aanvulling/aanpassing zijn steeds welkom. De voorbeelden in de cursus gebruiken directieven en include files die afhankelijk zijn van de gebruikte software op de PC (IDE), of de beschikbare versie van de betreffende bestanden (include files). In de lessen wordt de uitleg gegeven i.f.v. de op dat ogenblik gebruikte IDE en versie van bestanden.
9
Hoofdstuk 1 1.1
Historiek
Inleiding
Alvorens dieper in te gaan op de 8051 familie van microcontrollers, en meer specifiek de XC888 van Infineon, willen we kort een overzicht geven van de evolutie op gebied van computers. Deze technologie is (zoals de meeste technologieën) vrij recent en in continue evolutie. Vooral de miniaturisatie en het gebruik van halfgeleiders heeft de weg geëffend voor het algemeen gebruik ervan.
1.2
Historiek
Computers vinden hun oorsprong is de noodzaak om berekeningen sneller te kunnen maken, maar evenzeer uit de behoefte om productieprocessen te automatiseren. In figuur 1.2.1 wordt een telraam weergegeven. Het is een mechanisch hulpmiddel dat vandaag de dag nog steeds in gebruik is.
Figuur 1.2.1 Telraam De eerste “geautomatiseerde” rekenmachines dateren van de 17e eeuw. In 1642 doet Blaise Pascal een poging met de niet erg betrouwbare Pascaline (figuur 1.2.2). Beide apparaten zijn bedoeld om getallen te manipuleren. De sturing van een weefgetouw (figuur 1.2.3), ontworpen door Basil Bouchon kon met ponskaarten bediend worden en was ontworpen om een productieproces te automatiseren. Door beide systemen te combineren ontstaan de eerste computers die de elementaire onderdelen bevatten die we tot op vandaag in moderne computers terugvinden. 10
Figuur 1.2.2 De Pascaline
Figuur 1.2.3 Weefgetouw In het begin van de 20e eeuw ontstaan computers die elektromechanisch zijn opgebouwd, en tegen WWII worden elektronenbuizen gebruikt als schakelelementen 11
Figuur 1.2.4 laat de architectuur en praktische uitwerking zien van de computer die door Konrad Zuse werd gebouwd.
Figuur 1.2.4 Konrad Zuse (WWII)
12
Het blokdiagram van de architectuur van de Z3 is principieel nog steeds van toepassingen op alle moderne computersystemen. In de Verenigde Staten (1944) wordt de Harvard Mark 1 ontworpen door IBM (figuur 1.2.5). Het toestel weegt 5 ton en bevat ongeveer 750000 componenten. Hij is in staat om in 0.3 seconden een optelling uit te voeren. Voor een vermenigvuldiging zijn er 6 seconden nodig. De computer bestaat hoofdzakelijk uit (elektro)mechanische onderdelen. De computer bleef in gebruik tot 1959. Hij was gemiddeld 1 keer per week defect.
Figuur 1.2.5 Harvard Mark 1 Door het vervangen van elektronenbuizen door geïntegreerde schakelingen (IC’s) werden de systemen compacter, daalde het stroomverbruik en verhoogde de rekensnelheid. Door de hogere MTBF van halfgeleiderschakelingen t.o.v. elektronenbuizen nam ook de betrouwbaarheid enorm toe. Vanaf ongeveer 1975 komen er computers op de markt die betaalbaar zijn voor het brede publiek (figuur 1.2.6). Vanaf dan zal de steeds verder doorgedreven miniaturisatie er voor zorgen dat de systemen evolueren naar de mogelijkheden die we vandaag kennen. Hierbij maken we een belangrijk onderscheid tussen “computersystemen” en “embedded systemen”. In figuur 1.2.7 is het algemeen blokschema van een computer weergeven. Dit is van toepassing op beide systemen. In het volgende 13
hoofdstuk gaan we verder in op het onderscheid tussen beide benaderingen van het begrip “computer”.
Figuur 1.2.6 1977:Tandy TRS80 (kostprijs 600$)
X-tal Osc.
POR
ADRES DECODER
ADRESBUS
CPU
ROM (FLASH) (EPROM) (EEPROM)
SRAM
PAR. POORT
TIMER SER. COUNTER POORT
DATABUS
Figuur 1.2.7 Algemeen blokschema van een computer
14
Hoofdstuk 2
2.1
Embedded systemen
Inleiding
Embedded systemen zijn elektronische systemen waarin “computers” zijn verwerkt, zonder dat de gebruiker hiervan kennis moet hebben. Het embedded systeem is de elektronische schakeling die in het apparaat is ingebouwd, en verantwoordelijk is voor de besturing. Het is best mogelijk dat er in 1 toestel meerdere embedded systemen zijn ingebouwd. Een voorbeeld hiervan is een auto (figuur 2.1.1)
Figuur 2.1.1 Embedded systemen in een auto
We worden omringd door een groot aantal embedded systemen die we onbewust gebruiken, en die een grote impact hebben op ons dagelijks leven. Je kan nauwelijks nog een toestel bedenken waar geen computer bijdraagt tot de werking of de bediening. Keukentoestellen, audio- en video applicaties, voertuigen, communicatieapparatuur, werktuigen en machines,… Vaak zijn we afhankelijk van het correct functioneren van deze schakelingen. 15
Door computers in te bouwen in applicaties worden een aantal streefdoelen bereikt: vereenvoudigen van het systeemontwerp flexibiliteit in bediening en werking het drukken van de kostprijs per systeem verhogen van het bedieningscomfort verhogen van de functionaliteit optimalisatie van rendement … Door de besturing van het apparaat via een computer te laten verlopen volstaat een eenvoudige aanpassing van de software (firmware = software die instaat voor de besturing van het computersysteem, en door de gebruiker niet aangepast wordt) om: tekortkomingen te verbeteren mogelijkheden van het toestel aan te passen De hedendaagse embedded systemen zijn zo goedkoop geworden dat ze bijna in elk toestel zitten dat we gebruiken. Onze desktop PC is ontworpen om verschillende toepassingen uit te voeren zoals email versturen, tekstverwerking doen, spreadsheets en databases raadplegen. De gebruiker kan zelf de functionaliteit van de PC aanpassen door extra software te installeren en eventueel bijkomende hardware toe te voegen. Zo kan je met een PC, een DVB-T ontvanger (USB stick van enkele euro’s) en wat software TV kijken en DAB radio beluisteren. Een embedded systeem is ontworpen om slechts een beperkt aantal taken uit te voeren zoals het instellen van de kookcyclus in de microgolfoven. De gebruiker kan zelf (meestal) geen aanpassingen aan software (in dit geval firmware) of hardware aanbrengen. De desktop PC bestaat gek genoeg ook uit een aantal embedded systemen. Hieronder staat een niet limitatieve lijst van subsystemen die een eigen “computer” bevatten: klavier HD DVD USB-controller grafische kaart beeldscherm muis … In een optische muis zit een embedded systeem (bestaat uit 2 computers) die er voor zorgen dat: de bedieningsknoppen ingelezen worden het beeld van de camera omgezet wordt in bewegingsinformatie de gegevens doorgezonden worden naar de PC
16
2.2
Wat is er uniek aan het ontwerp van embedded systemen?
Het ontwerp van een embedded systeem vormt een uitdaging op het gebied van betrouwbaarheid, performantie en systeemkost. Omdat embedded systemen o.a. verantwoordelijk kunnen zijn voor de veiligheid van de gebruiker (ABS systemen, pacemakers, sturing van liften,…) is hun betrouwbaarheid essentieel. Ontwerpers moeten rekening houden met het feit dat hun systemen niet eenvoudig even opnieuw opgestart kunnen worden (reboot) als het misgaat. In vele gevallen zijn ze (zonder uitschakelen) jaar in, jaar uit actief. Een kookplaat wordt enkel nog in “stand by” gezet. De software en hardware zullen zodanig ontworpen worden dat een “fail safe” werking gegarandeerd wordt. Daarom zijn solide ontwerpmethoden en een grondige testfase heel belangrijk voor elk embedded systeem, zowel wat de hardware- als de software implementatie betreft. Door de behoefte om te communiceren met sensors, actuatoren, toetsenborden, displays enz. moet de programmeur een goede kennis hebben over hoe men op alternatieve manieren aan I/O kan doen, zodat de vereisten qua snelheid, complexiteit en systeemkost op elkaar afgestemd kunnen worden. Alhoewel we meestal (afhankelijk van de gebruikte processor of controller) programmeren in een hogere programmeertaal voor een betere productiviteit, zal de intense interactie tussen hardware en software aanleiding geven om af te dalen tot op het hardwareniveau van het systeem en te programmeren in machinetaal (assembler).
2.3
Hoe krachtig zijn embedded processoren?
De embedded systemen die we vinden in de meeste consumer producten bevatten een microcontroller (single chip computersysteem figuur 2.3.1). De eigenschappen van elk computersysteem (en dus ook van een microcontroller) zijn terug te brengen tot volgende kernbegrippen: hoeveelheid geheugen (ROM en/of RAM) rekenkracht (aantal instructies per seconde, grootte van de variabelen, bewerkingen die de processor kan uitvoeren (floating point?) ) beschikbare I/O (poorten, AD, DA, PWM, communicatie, …) stroomverbruik De figuur 2.3.2 geeft een typisch embedded systeem weer.
17
Figuur 2.3.1 Blokdiagram 8051 microcontroller
18
Figuur 2.3.2 Blokdiagram embedded systeem met microcontroller
Omdat microcontrollers single chip computers zijn, bevatten ze minder transistoren dan een PC, die opgebouwd is uit meerdere geïntegreerde schakelingen. De prestaties van beide kunnen dan ook niet zomaar vergeleken worden. Microcontrollers zijn meestal geoptimaliseerd naar een bepaald type toepassing. Zo zal de microcontroller die op een identiteitskaart zit niet onmiddellijk geschikt zijn om een GSM te besturen. Het aanbod van verschillende types en fabrikanten is dan ook overweldigend. Het maken van de juiste keuze is geen eenvoudige taak. Embedded systemen (microcontrollers) kunnen gebaseerd zijn op 4-bit (vb. uurwerken), 8-bit (het merendeel van de toepassingen), 16-bit (vb. motion control) en 32-bit (o.a. PDA, GSM, gaming) processoren. Het aantal bit is bepalend voor het grootste getal dat de CPU in een operatie kan verwerken. Een 8 bit CPU kan in een keer getallen manipuleren tussen 0 en 255. Grotere getallen zullen in meerdere stappen verwerkt moeten worden. De stelling “hoe meer bit hoe krachtiger” gaat niet steeds op. Zo zal een PLC vooral bit variabelen manipuleren. Voor dit type toepassing is het belangrijk dat de CPU individuele bits eenvoudig en snel kan manipuleren of testen.
19
2.4
Embedded systemen werken meestal "REAL-TIME"
Real-time systemen verwerken 'events'. Events zijn gebeurtenissen die voorkomen aan systeem inputs en die andere gebeurtenissen veroorzaken aan systeem outputs. Een voorbeeld van een event is het blokkeren van een wiel van een wagen bij het remmen. De reactie van het ABS systeem (embedded systeem) hierop is het gepulseerd bekrachtigen van de rem zodat het wiel weer gaat draaien en de wagen opnieuw bestuurbaar wordt. De reactietijd van een real-time embedded systeem moet dan ook minimaal zijn. Het ABS systeem moet het blokkeren van het wiel detecteren binnen enkele milliseconden. Een vertraging van enkele seconden zou hier ontoelaatbaar en zelfs potentieel dodelijk zijn. Wat real-time betekent voor een embedded systeem is afhankelijk van de toepassing. Zo zal de besturing van een TV minder tijdskritisch zijn en zullen we tevreden zijn als de microcontroller zijn reactie zo snel mogelijk berekent. Er is immers geen echte deadline, de gebruiker of de installatie is niet in gevaar als de response iets langer duurt. We spreken hier van soft real-time. Wanneer er wel een deadline is opgelegd aan het systeem, zoals in het geval van het ABS systeem spreken we van hard real-time systemen.
2.5
Een typisch microprocessorsysteem
Om de voordelen van een microcontroller te duiden, gaan we eerst even het blokschema van een typisch embedded microprocessor systeem van nabij bekijken (figuur 2.5.1). De CPU (een single chip CPU wordt een microprocessor genoemd) is verbonden met de nodige geheugenchips (ROM en RAM) en met een aantal I/O chips, in dit geval parallelle poorten, seriële poorten en timers. De verbinding van de CPU met de andere componenten verloopt via een aantal bussen. Een bus is een verzameling van geleiders die signalen van een zelfde aard transporteren. De draden die de gegevens van en naar het geheugen vervoeren, worden de DATABUS genoemd. De geleiders die het adres (locatienummer) vervoeren dat aangeeft waar er informatie gelezen of geschreven wordt, noemen we de ADRESBUS.
20
X-tal Osc.
POR
ADRES DECODER
ADRESBUS
CPU
ROM (FLASH) (EPROM) (EEPROM)
SRAM
PAR. POORT
TIMER SER. COUNTER POORT
DATABUS
Figuur 2.5.1 Blokdiagram van een microprocessor systeem
Omdat we moeten kunnen lezen en of schrijven naar het geheugen, zijn er ook controlesignalen nodig, die we de CONTROLEBUS noemen. Aangezien het de processor is die de controlebus stuurt, hebben alle namen van de signalen betrekking op de functie die ze vanuit de processor krijgen. Zo wordt de 'read' lijn actief als de processor wil lezen, en de 'write' lijn als de processor wil schrijven. De controlebus is niet opgenomen in figuur 2.5.1. Het gaat over een beperkt aantal verbindingen tussen de CPU en de andere bouwstenen in het systeem. Het klassieke microprocessor systeem heeft voor embedded applications een aantal nadelen (zie volgende paragrafen).
2.5.1
MTBF
Alles gaat stuk! Niets is, helaas, voor de eeuwigheid. Een ontwerper zal trachten de levensduur van zijn design af te stemmen op verwachtingen van de klant. Een systeem bestaat meestal uit verschillende componenten die allemaal hun eigen "Failure rate" hebben. Deze FIT-rate ( FIT of Failures In Time) is de statistische waarde die voor een component aangeeft hoeveel failures of defecten de component zal hebben per miljard werkingsuren (1.000000000 uur =114155 jaar). De FIT rate van een component is een gegeven dat door de fabrikant ter beschikking kan worden gesteld voor elke component, of gegevens waaruit je de FIT rate kan berekenen.. Aan de hand van deze cijfers kan voor een bepaald systeem de MTBF worden bepaald. MTBF staat voor Mean Time Between Failure. 21
De MTBF is geen exact gegeven, het is een voorspelling, die voor een systeem met een aantal componenten, elk met een bepaalde FIT-rate, een gemiddelde werkingsduur wordt berekend. MTBF
1 10 9 n
q i 1
Waarin:
n qi ri
i
ri
het aantal verschillende componenten hoeveelheid van de i de component de FIT rate van de i de component
Hieruit blijkt dat voor een bepaalde FIT rate de betrouwbaarheid van het systeem afneemt als het aantal componenten toeneemt, iets wat je met wat boerenwijsheid ook al wel kon inschatten. In de figuur 2.5.2 staat de FIT-rate van een PowerPC 603r processor van Freescale weergegeven in functie van de junctietemperatuur met de voedingsspanning als parameter.
Figuur 2.5.2 FIT-rate van een PowerPC 603r
22
2.5.2
Ontwerp van een PCB
Voor een embedded processorsysteem moet er ook een PCB (Printed Circuit Board) worden ontworpen. Met de steeds hogere klokfrequenties en pincount van de processoren is dit eveneens een niet zo voor de hand liggende- en vaak tijdrovende zaak. Sommige ontwerpers zullen, niet gehinderd door enige kennis ter zake, aanvoeren dat het tekenen van een PCB louter en alleen het omzetten is van schema verbindingen naar echte bedrading. Niets is minder waar! Het tekenen van een PCB is het, met kennis van zaken, creëren van een nieuwe component met eigen elektrische eigenschappen en met een aanzienlijk aandeel in het al dan niet slagen van een project! Zelfs bij de meest ervaren ontwerper, en bij het gebruik van de meest geavanceerde CAD pakketten, zullen er waarschijnlijk een aantal designfouten in de eerste prototype PCB's van een complex systeem zitten. Dikwijls liggen deze problemen op het vlak van overspraak en wederzijdse koppelingen of signaalintegriteit. Ook op het vlak van EMC (Electro Magnetische Compatibiliteit) kunnen er een aantal problemen aan het licht komen bij de eerste metingen op de prototypes. Het systeem kan immers te stoorgevoelig zijn, of kan zelf teveel storing veroorzaken. Dit heeft als gevolg dat er een aantal aanpassingen of zelfs een redesign van de PCB moet worden gedaan. Dit is een kostelijke zaak. Je kan dus maar beter deze complexe en grote PCB's vermijden.
2.5.3
Overige nadelen
In een standaard computer zitten tal van verschillende onderdelen. De fabrikant zal dit moeten voorzien in zijn stockbeheer. Het ontbreken van 1 component kan de totale productie stilleggen. Ook naar onderhoud is het aantal verschillende componenten een nadeel.
2.6
Een microcontroller systeem
Wanneer we het blokschema van een microcontroller systeem (figuur 2.6.1) dat uit 1 IC bestaat, vergelijken met het blokschema van een processor systeem (figuur 2.5.1) dat is opgebouwd uit discrete componenten, dan merken we dat deze praktisch gelijkaardig zijn.
23
Figuur 2.6.1 Microcontroller systeem
Het grote verschil is echter dat bij een microcontroller het hele systeem op één chip is geïntegreerd, wat grote voordelen heeft tegenover een diskreet opgebouwd systeem. Omdat het hier gaat om een single-chip oplossing is de MTBF van het eindproduct meestal groter. Het ontwerp van de PCB is minder complex. Een groot deel van de verbindingen tussen de functionele onderdelen is immers op de chip uitgevoerd. Het systeem kan ook veel compacter worden gemaakt met een microcontroller dan met de discrete oplossing. Dit heeft samen met het vorige argument als gevolg dat het eindproduct tegen een lagere kostprijs op de markt gebracht kan worden. Op technisch vlak hebben de testingenieurs het ook makkelijker. Omdat de digitale bussen, waar de snelle signalen over getransporteerd worden, on-chip blijven zal het systeem veel sneller voldoen aan de gangbare EMC normen. In het 8-bit segment is de meest populaire en meest gebruikte architectuur die van de Intel 8051. Die populariteit is in belangrijke mate het gevolg van een unieke economische zet van het bedrijf. Het ontwerp van de controller (IP) is voor iedereen (mits vergoeding) toegankelijk. Op die manier kan elke fabrikant snel en goedkoop een microcontroller produceren. 24
Doordat deze architectuur gebruikt wordt door een groot aantal gebruikers ontwikkelen verschillende fabrikanten nog steeds controllers met nieuwe features, gebaseerd op deze architectuur (kip en ei verhaal). Er zijn ongeveer een 500-tal verschillende derivaten van een oorspronkelijke 8051 op de markt, en na 30 jaar komen er nog regelmatig bij. Men schat dat de helft van alle gebruikte microcontrollers gebaseerd zijn op deze architectuur.
2.7
Wat is er op de markt?
In figuur 2.7.1 wordt de verhouding van microcontrollers in het marktaandeel weergegeven i.f.v. het aantal bit van de CPU. Gelet op de lagere kostprijs van 8 bit controllers is hun numeriek aandeel verhoudingsgewijs nog groter. Recent zit het gebruik van 32 bit controllers in de lift. Een belangrijke oorzaak is de opkomst van de ARM microcontrollers (ARM7, ARM9, CORTEX,…). ARM heeft zelf geen silicon productie, maar verkoopt IP aan andere fabrikanten. Hierdoor komt er en groot aantal compatibele controllers op de markt. De ARMx en CORTEX families zijn speciaal ontworpen (RISC) zodat ze een laag stroomverbruik combineren met een hoge verwerkingssnelheid. Als bijkomend voordeel is er de geringe silicium oppervlakte die door de core gebruikt wordt. Hierdoor is de kostprijs van deze 32 bit controller vergelijkbaar of lager dan die van 8 bit microcontrollers. Net zoals bij de 8051 is een toolchain bruikbaar voor componenten van verschillende fabrikanten. Het komt er in de praktijk op neer dat voor een bepaalde toepassing steeds de meest geschikte controller moet gekozen worden uit het globale aanbod. Hierbij zal de ontwerper rekening moeten houden met: beschikbare tools voor de ontwikkeling (compilers, simulatoren, debuggers, enz…) bestaande software bibliotheken opgedane ervaring met bepaalde controllerreeksen beschikbaarheid van componenten economische gegevens …
25
Figuur 2.7.1 Microcontroller marktaandeel i.f.v. aantal bit CPU
26
2.8
Een voorbeeld uit elke categorie
2.8.1
4-Bit controller: EM6626
Figuur 2.8.1.1 bevat een onderdeel van de datasheet van een 4 bit microcontroller (producent maakt deel uit van de Swatch group!!).
27
Figuur 2.8.1.1 4 Bit microcontroller
Deze familie van controllers worden gebruikt voor 'low-end' toepassingen, zoals calculators, telefoons (geen GSM's of DECT toestellen…), uurwerken (in 'DIE' te verkrijgen), spelletjes, speelgoed, fietskilometertellers, thermostaat, enz… Een eigenschap van deze reeks controllers is dat ze low power devices zijn. Ze kunnen werken op een zeer lage voedingsspanning van 1,2V tot 3,6V en dit bij een stroomverbruik van 1,8μA in actieve toestand! (batterijgevoede toepassingen). 28
Een gevolg van dit zeer laag stroomverbruik is dat ze met een lage klok frequentie werken, in dit geval 32KHz tot enkele honderden KHz. Bij 32KHz geeft dit een uitvoeringstijd van 61μS per instructie, zeker iets waar rekening moet mee gehouden worden. Een eigenschap van de meeste 4-bit controllers is dat ze slechts in mask ROM uitvoering of in OTP (One Time Programmable) ROM uitvoering beschikbaar zijn.
2.8.2
8-Bit controller: XC888
De groep van 8-bit controllers worden gebruikt in medium end toepassingen zoals: harddisk controle, keyboards, PLC's (vb. LOGO van SIEMENS), sensoren, stappenmotorsturingen, subsystemen (bv. display units), enz. De hier weergegeven XC888 van Infineon (vroeger SIEMENS) is een erg veelzijdige controller die een heleboel verschillende I/O mogelijkheden heeft meegekregen, vandaar zijn toepassing in voornamelijk industriële systemen (figuur 2.8.2.1).
Figuur 2.8.2.1 Infineon XC888 blokschema
Deze controller is geen low power type, actief (@24MHz en 5V) verbruikt hij 30mA stroom, maar hij beschikt over een idle mode waar hij 15mA verbruikt en een power down mode waar hij maar 10μA verbruikt. De systeemklok moet tussen 4 en 12MHz liggen (externe klok), het is dus geen statisch ontwerp. De XC888 beschikt over FLASH geheugen, maar er is ook een ROM uitvoering beschikbaar voor grote aantallen. Het FLASH geheugen is ISP (In 29
System Programmable) . De XC888 is een controller die is afgeleid van de Intel 8051, waar heel wat nieuwe periferie-elementen werden aan toegevoegd. Het grote voordeel van deze familie is dat er reeds immense hoeveelheden software zijn geschreven die herbruikbaar is. Naast deze periferie valt ook de hardware Division/Multiplication unit op, die 32bit/16-bit bewerkingen en 16-bit x16-bit bewerkingen kan maken los van de CPU, wat een enorme tijdswinst kan opleveren bij wiskundige bewerkingen. Bijkomend is de controller uitgerust met een CORDIC rekeneenheid (sin, cos, log). Door de controller uit te rusten met een CAN bus en communicatiepoorten met seriële mogelijkheden kan de controller eenvoudig communiceren met andere systemen. De kostprijs voor dit type controller zit rond €8 voor grote aantallen. 2.8.3
16-bit controller: XC167 van Infineon
In figuur 2.8.3.1 is het blokdiagram van de XC167 microcontroller van Infineon opgenomen.
Figuur 2.8.3.1 Infineon XC167 microcontroller 30
16-bit controllers worden gebruikt in high-end toepassingen, dit zijn meestal allemaal rekenintensieve toepassingen zoals: PID motor snelheidsregelingen (servocontrollers), PLC's (S7 reeks van SIEMENS), industriële controlesystemen, management van verbrandingsmotoren, ABS systemen, enz. Industriële systemen moeten alsmaar meer in staat zijn om te communiceren met elkaar of met een subsysteem, hier vormt de CAN bus (industrieel- en automotive communicatie netwerk) een uitstekende oplossing. Deze chip bezit: een klokgenerator met PLL en verschillende types intern geheugen zoals 128Kbytes programma Flash, programma RAM en data RAM.
2.8.4
32-Bit controller: De MB91F361 van Fujitsu
Dit is een voorbeeld uit een reeks van controllers voor zeer rekenintensieve taken zoals: laser- en high end inktjet printers, communicatie producten (routers, bridges…), high end gaming producten, muziek instrumenten, GPS systemen, next generation dashboards… Zijn kloksnelheid ligt tussen 16 en 64MHz en is onder programmacontrole instelbaar, wat nuttig is om het stroomverbruik te beperken wanneer dit nodig is. Verder valt de massale hoeveelheid intern geheugen op (512 Kbytes Flash en 16Kbytes RAM) wat nuttig is voor het stockeren van een (real time) OS, wat bij 32-bit controllers meestal het geval is. Merk ook op dat deze controller beschikt over een volledige 32-bit externe bus interface zodat hij een groot extern geheugen kan aanspreken (max. 4Gbyte). Hij is dan ook ruim behuisd, met zijn 208 pins TQFP verpakking. De externe bus interface maakt de grens tussen een microcontroller en een embedded microprocessor zeer vaag. De microcontroller heeft hier in tegenstelling met een embedded processor echter nog altijd het voordeel single-chip te kunnen werken. Het blokschema van de MB91F361 controller van Fujitsu is opgenomen in figuur 2.8.4.1.
31
Figuur 2.8.4.1 Blokschema van de MB91F361 controller van Fujitsu
32
Hoofdstuk 3
3.1
8051 hardware
Inleiding
De eerste single chip CPU (de 4004 (MCS-4)) wordt door Intel in 1971 op de markt gebracht. Andere fabrikanten volgen. In 1974 brengt TI de eerste microcontroller op de markt (TMS1000). In1976 brengt Intel een single chip computer op de markt (de 8048 (MCS-48)) gevolgd in1980 door de microcontroller 8051 (MCS-51). De MCS-48 wordt nog steeds gebruikt voor het uitlezen van PC-klavieren. De MCS-51 (MicroComputerSystem 51) wordt als IP vrijgegeven aan andere fabrikanten die eigen derivaten op de markt brengen. Van de originele 8051 worden er in 1980 22.000.000 en in 1983 reeds 91.000.000 verkocht. Volgende websites geven gedetailleerde informatie aangaande de geschiedenis van CPU’s en microcontrollers: www.eee.bham.ac.uk/woolleysi/teaching/microhistory.htm www.computerhistory.org/semiconductor/timeline/1974-MCU.html
Men schat dat de helft van alle embedded systemen worden bestuurd door een controller uit de 8051 familie. De CPU (gekenmerkt door instructieset, interrupt structuur en memory map) van de 8051 microcontroller wordt een CORE genoemd. Fabrikanten die de 8051 core (kern) gebruiken voor hun eigen controllers (zoals PHILIPS, INFINEON, DALLAS, ATMEL, ANALOG DEVICES, SILICON LABORATORIES, enz.) voegen bijkomende I/O mogelijkheden toe. Het gebruik van een gemeenschappelijke core maakt dat dezelfde ontwikkel tools gebruikt kunnen worden. Bovendien laat dit ook toe reeds geschreven code te hergebruiken. Ook het feit dat de meeste ontwerpers "second-sources" willen voor hun componenten is een reden voor dit succes. Zo zijn er op het ogenblik honderden verschillende C’s die allemaal ‘8051 compatibel’ zijn, maar toch verschillen door hun specifieke extra’s zoals bv. bijkomende parallelle of seriële poorten, I2C Bus, AD/DA converters, hoeveelheid intern geheugen .... Ook het tegenovergestelde doet zich voor; voor minimale toepassingen bevat de oorspronkelijke 8051 nog te veel mogelijkheden. Door een aantal parallelle poorten weg te laten, krijgt men een C met minder aansluitpinnen en mogelijkheden en een compactere uitvoering, bv. de ATMEL 89C5115 in een 28 pin SO behuizing. De ATMEL 89C5115 C bestaat uit de 8051 CPU-CORE waarbij extra periferie werd toegevoegd en andere dan weer werd
33
weggelaten. De kleinste 8051 microcontroller (2010) is weergegeven in figuur 3.1.1 (pincount=10).
Figuur 3.1.1 Mini 8051 controller (C8051T606 van Silicon Labs)
3.2
De oorspronkelijke 8051 architectuur
De oorspronkelijke 8051C bestaat uit (figuur 3.2.1):
Een 8-bit CPU ( Central Processing Unit ) die er voor zorgt dat de instructies worden opgehaald uit het geheugen, worden gedecodeerd en uitgevoerd. Deze CPU is geoptimaliseerd voor controletoepassingen ( bit bewerkingen). Samen met de memory map en interrupt structuur vormt dit de CORE van de controller. 4K bytes ROM, voor programma opslag. 128 bytes interne RAM, voor het opslaan van variabelen, data, enz. De naam RAM is verwarrend. Eigenlijk zijn het registers (in een register kunnen bewerkingen uitgevoerd worden, in RAM niet). Twee 16 bit timer / counter circuits (TIMER0,TIMER1) die gebruikt kunnen worden om pulsen te tellen, of tijd af te passen. Een full duplex UART (Universele Asynchrone Receiver Transmitter) is een seriële poort voor communicatie toepassingen (COM poort). On chip clock oscillator. 4 acht bit parallelle poorten. (P0,P1,P2,P3) waarvan ook twee poorten gebruikt kunnen worden als ADRES en DATA BUS om extern geheugen aan te spreken. Dit heeft als nadeel dat deze poorten dan hun functie als I/O poort verliezen. De controller kan dus ook als CPU gebruikt worden in een computersysteem. Interrupt controller met twee interrupt niveaus, die 5 interrupt bronnen kunnen verwerken
34
Figuur 3.2.1 Basis 8051 architectuur
3.3
Memory map van de 8051 CPU
De memory map (kaart geheugenruimte) van een CPU is letterlijk een kaart die weergeeft hoe het geheugen van de CPU bereikbaar is, en wat de specifieke functie hiervan is. Bij de MCS-51 “ziet” de CPU extern en intern geheugen. Het externe geheugen wordt gebruikt voor het opslaan van uitvoerbare code (code of program memory) en variabelen (eXternal RAM). Alhoewel het de naam “extern” geheugen draagt, kan een deel geïntegreerd zijn in de chip. De instructies die je moet gebruiken om het extern geheugen uit te lezen blijven onveranderd, ongeacht de fysieke plaats van het geheugen. Het interne geheugen bestaat uit registers. Een register is een geheugenplaats die deel uitmaakt van de CPU. Daardoor kan de inhoud van registers door een berekening aangepast worden. Ook alle I/O gebeurt via registers. De CPU bevat twee groepen van instructies: MOVX en MOVC instructies die manipulatie van het extern geheugen toelaten Alle overige instructies die allemaal inwerken op registers
35
3.3.1
De inwendige geheugenruimte
Figuur 3.3.1.1 laat de inwendige geheugenruimte zien.
Figuur 3.3.1.1 Inwendige geheugenruimte 8051 De inwendige geheugenruimte bestaat uit 8 bit registers. Het belangrijkste verschil tussen een register en een RAM locatie is de mogelijkheid om data in registers te manipuleren via een berekening. De 8051 compatibele controllers hebben minstens 128 bytes algemeen bruikbare registers (General Purpose Registers of GPR’s) De meeste beschikken over 256 GPR’s. Daarnaast zijn er Special Function Registers (SFR’s) die de CPU toegang verlenen tot de I/O mogelijkheden van de controller. Omdat er veel registers zijn krijgen ze een 8 bit volgnummer (adres) om ze te identificeren. Dat adres moet in de instructie opgegeven worden, zodat de CPU weet welk register gemanipuleerd moet worden. Zoals figuur 3.3.1.1 laat zien is er een overlapping op de adressen 80h-ffh. Dit conflict wordt opgelost door de “adressering” die gebruikt wordt in de instructie. De adressering is de manier waarop de instructie het opgegeven adres aangeeft. In figuur 3.3.1.2 is de standaard 8051 SFR invulling weergegeven. Aan de linker kant staan de 8 bit adressen van de SFR’s. Aan de rechterkant staat de naam van het register, gegeven door de fabrikant, zodat de programmeur de functie van het register kan begrijpen. De figuur laat ook zien dat bepaalde registers opgesplitst worden in afzonderlijke bits die via hun bitadres door bepaalde instructies manipuleerbaar worden.
36
Figuur 3.3.1.2 Standaard 8051 SFR kaart De 8051 is een microcontroller die gebaseerd is op een accumulator architectuur. Dit wil zeggen dat alleen in de ACC (accumulator) berekeningen kunnen uitgevoerd worden. Dat vormt een “bottle neck” voor rekenkundige operaties. Daarom zijn er GPR’s registers met een speciaal statuut (R0-R7, beschikbaar in 4 banken zie figuur 3.3.1.3). Deze registers laten een kleiner aantal bewerkingen toe dan de ACC, maar meer dan op alle andere registers. Tenslotte zijn er de overige registers waarop minimale berekeningen mogelijk zijn. Hierdoor vormt de accumulatorstructuur nooit echt een probleem. De figuur 3.3.1.3 laat ook zien dat er in de GPR’s ook een aantal bytes zijn die over individueel adresseerbare bits beschikken (registers 20h-2fh). De controller kan in totaal 256 individuele bits manipuleren (GPR’s en SFR’s samen). 37
Figuur 3.3.1.3 Registerbanken in GPR’s De GPR’s met de adressen 80h-ffh hebben verder geen speciale mogelijkheden. In de originele 8051 en enkele kleinere derivaten zijn ze zelfs niet aanwezig.
38
3.3.2
De externe systeemarchitectuur
In sommige van de 8051 compatibele controllers is geen (of onvoldoende) intern programmageheugen geïntegreerd, dit is bv. zo bij de C517 van Infineon of de 8031 van Intel, die helemaal geen intern programmageheugen bezitten. In deze gevallen gebruiken de controllers uit deze reeks extern aangesloten geheugenchips op dezelfde manier als een microprocessor. Op deze manier kan men extern bijkomend programmageheugen, datageheugen of bijkomende periferiechips, zoals parallelleof seriële poorten, toevoegen aan een systeem door ze te verbinden met de bussen van de microcontroller. Niet alle derivaten van de 8051 beschikken nog over en externe bus. De XC888 heeft die mogelijkheden niet meer.
3.3.2.1
De externe geheugenruimte
De 8051 verschilt van een traditionele processor doordat hij een gescheiden geheugenruimte heeft voor data en programmacode. De maximale geheugenruimte voor programmageheugen (CODE MEMORY) is 64Kbytes en deze kan volledig extern, gedeeltelijk intern/extern (bv. 4Kbyte intern bij de 8051) of volledig on chip (intern) gesitueerd zijn. Het is mogelijk dat de volledige geheugenruimte niet opgevuld wordt. De fysische plaats van het geheugen doet geen afbreuk aan de uniforme manier waarop de CPU het geheugen aanspreekt. Speciaal is ook het ontbreken van een instructie om dit geheugen te schrijven. Het programma moet dus op voorhand in het programmageheugen geladen worden, en kan door de controller zelf niet gewijzigd worden (de XC888 is hierop een uitzondering en kan “in application programming” uitvoeren. Dit is niet zonder gevaar, en moet met de nodige omzichtigheid gebeuren. Zie hiervoor de UM.). Ook het externe datageheugen (EXTERNAL RAM) is maximaal 64Kbytes groot en kan deels op het IC vertoeven. In de meeste 8051 applicaties wordt dit externe geheugen niet gebruikt. Er zijn meestal immers voldoende GPR’s aanwezig die voor dataopslag gebruikt kunnen worden. De externe memory map van een 8051 controller wordt in figuur 3.3.2.1.1 weergegeven. De geheugenlocaties zijn 8 bit groot. Hun plaats in het geheugen wordt bepaald door een adres. Omdat er voor het aanspreken van beide types geheugen afzonderlijke instructies bestaan kunnen ze dezelfde adressen gebruiken. De externe adressen worden via een 16 bit adresbus doorgegeven. Dit laat 65536 unieke adressen toe (0000h-ffffh). Wanneer een deel van het geheugen “on chip” zit, en er worden adressen gebruikt die niet naar dat inwendig geheugen verwijzen, zal de CPU automatisch de bussen activeren en uitwendig de data lezen/schrijven (ook van toepassing op het programmageheugen).
39
Bij instruction fetches zal de controller instructies (code) steeds ophalen uit het code geheugen, dat zich intern of extern van de controller kan bevinden. Extern codegeheugen wordt aangesproken onder twee voorwaarden: wanneer de /EA (External Access) pin met een logisch laag niveau is verbonden of wanneer de program counter (PC) een adres aangeeft dat groter is dan de interne geheugenruimte. De bedoeling is dus dat bij ROM-less (zonder intern codegeheugen) controllers vb. C517 en 8031 de /EA-pin laag gemaakt wordt, om aan te geven dat instructies extern moeten opgehaald worden. Niet alleen voor instruction fetches kan het codegeheugen aangesproken worden, maar a.d.h.v. een MOVC A,@A+DPTR instructie kan ook data (bv. een tabel met constanten, tekstcodes…) die in dit codegeheugen staat, gelezen worden. Wanneer het externe codegeheugen gelezen wordt, zal de /PSEN (Program Store Enable) lijn door de controller geactiveerd worden als lees-controlesignaal naar het geheugen toe. Merk op dat er geen voorzieningen zijn getroffen om naar het codegeheugen te schrijven vanuit de controller, de code zit immers in ROM geheugen!
Codegeheugen
Datageheugen FFFFh
Externe code geheugenruimte
FFFFh
Externe data geheugenruimte
1000h Codegeheugen kan intern of extern zitten afhankelijk van de controller en de toestand van de EA-pin
0FFFh
0000h
0000h
Figuur 3.3.2.1.1 Externe memory map 8051 microcontroller
40
Het externe datageheugen kan wel worden gelezen en geschreven. Het moet immers geschikt zijn om variabelen te bevatten , het is dan ook uitgevoerd als RAM geheugen. Dit kan onder programmacontrole instructies:
gelezen of geschreven worden met volgende
MOVX A,@DPTR en MOVX A,@Ri om data te lezen uit het geheugen en MOVX @DPTR,A en MOVX @Ri,A om data te schrijven naar het geheugen.
Wanneer het externe datageheugen wordt gelezen of geschreven zal de controller respectievelijk de /RD of de /WR lijn activeren als controle signaal. Merk op dat de controller niet in staat is om uit het datageheugen instructies op te halen, daar hij hiervoor een ander lees-controlesignaal gebruikt. In sommige gevallen (bv. bij programma ontwikkeling) is het wenselijk dat een programma kan opgehaald (en uitgevoerd) worden uit het datageheugen, want daar kan men wel een programma in downloaden, het is immers schrijfbaar (RAM). Bij de 8051 reeks kan dit (het overlappen van data- en codegeheugenruimte) gedaan worden door de beide actief lage lees-controlesignalen (/RD en /PSEN) via een AND functie te combineren tot een nieuw actief laag leessignaal dat dan naar het fysische geheugen (RAM) met een bepaald geheugenbereik gaat. De externe verbindingen van een 8051 compatibele controller worden aangegeven in de figuur 3.3.2.1.2.
Figuur 3.3.2.1.2 Externe klemmen standaard 8051
41
De vier I/O poorten worden hier weergegeven samen met hun alternatieve functies tussen haakjes. In een basis 8051 systeem kunnen de vier poorten als 8-bit parallelle poorten gebruikt worden. Het is belangrijk te begrijpen dat wanneer een pin gebruikt wordt met zijn alternatieve functie deze pin niet meer beschikbaar is voor standaard I/O. Wanneer bv. externe periferiechips of geheugen nodig zijn, worden poort 0 en poort 2 gebruikt met hun alternatieve functie nl. als businterface. Poort 0 wordt dan de 8-bit gemultiplexte adres/data bus en poort 2 krijgt de functie toegewezen van hoogste byte van de adresbus. Enkele pinnen van poort 3 worden controlelijnen zoals /RD (read) en /WR (write). Deze poortpinnen zijn dan verloren als I/O-pinnen en er blijven in dit geval niet veel pinnen meer over die kunnen gebruikt worden als vrij programmeerbare I/O pin. . Het probleem wordt nog groter indien we ook enkele externe interrupts willen verwerken via de interrupt ingangen van poort 3. Ook de seriële poort en de timeringangen zitten als alternatieve functie op deze poort 3. Het besluit is dan ook dat we de 8051 controllers met vier poorten liefst single-chip gebruiken en niet met een externe businterface daar we immers op deze manier de voordelen (I/O) van de controller verliezen.
Figuur 3.3.2.1.3 C517 microcontroller met extern geheugen
42
Een 8051 compatibele controller met businterface gebruiken wordt pas interessant wanneer we kunnen beschikken over extra I/O poorten zoals bv. het geval is bij de C517 van Infineon, die beschikt over 8 poorten, zoals aangegeven in de figuur 3.3.2.1.3. De RST (reset) ingang moet bij power-up even hoog worden gehouden ( bij de C517 is dit het inverse signaal, dat dus laag moet gemaakt worden) om de controller zich te laten initialiseren en met de programma uitvoering te beginnen op locatie 0000h. De oscillator pinnen (XTAL1, XTAL2) worden gebruikt om een kwarts kristal aan te sluiten op de inwendige klokoscillator. Andere controlelijnen zijn: de adres latch enable (ALE) pin die gebruikt wordt om de adres/databus te demultiplexen, de program store enable (/PSEN) pin om het externe codegeheugen aan te spreken en de external access (/EA) pin die gebruikt wordt om de controller in het externe codegeheugen instructies te laten ophalen.
3.3.2.2
De bus architectuur
De figuur 3.3.2.2.1 geeft de busstructuur weer voor het aanspreken van externe geheugen- of periferie-elementen. We zien hier een 16 bit adresbus en een 8 bit databus, samen met vier controlesignalen. Om het aantal poortpinnen die gebruikt worden voor de adres- en databus te beperken worden de laagste byte van de adresbus en de data tijd-gemultiplexed op dezelfde bus. Het Address Latch Enable (ALE) signaal wordt gebruikt om de laagste byte van het adres te latchen en zo deze twee types informatie te demultiplexen. Op deze manier kan een stabiele 16 bit adresbus worden aangeboden aan het systeem, terwijl de adres/databus daarna wordt gebruikt voor een data transfer. De /RD en /WR lijnen controleren de datastroom van en naar de externe datageheugenruimte. De /PSEN lijn controleert de datastroom van de externe programmaruimte. In deze architectuur zijn er geen voorzieningen voor een gescheiden I/O ruimte (bv. voor andere periferiechips enz.) wat wil zeggen dat externe periferie in de geheugenruimte moet worden ondergebracht, we zeggen dat de periferie "memorymapped" is.
43
8051 compatibele controller P2
MSB adresbus (A8-A15)
ALE
P0
16-bit adresbus
Adres latch LSB adres/ databus AD0-AD7
LSB adresbus A0-A7
databus
P3
RD WR PSEN
Figuur 3.3.2.2.1 Busstructuur extern geheugen 3.3.2.3
De externe timing
De controller systeemklok bepaalt alle timing eigenschappen van het systeem. Ze wordt opgewekt door een interne oscillator in de controller. Het frequentiebepalend element van de systeemklok kan een inwendige RC-keten zijn of een extern kwartskristal of keramische resonator. De systeemklok kan ook door een uitwendig signaal opgewekt worden (XC888: intern RC circuit default, kan overschakelen naar extern signaal via programmatuur). De XTAL1 en XTAL2 pinnen zijn de ingang en de uitgang van een inverterende versterker die kan werken als een on-chip oscillator van het Pierce type. Hiertussen sluiten we extern een kwartskristal aan waarvan de parallelresonantiefrequentie gelijk is aan de gewenste klokfrequentie. Hoe hoger de klokfrequentie wordt gekozen, hoe sneller de controller instructies zal uitvoeren, maar hoe hoger het stroomverbruik en hoe groter de EMI. De minimum frequentie is afhankelijk van de functionaliteit van het systeem, m.a.w. de controller moet snel genoeg zijn om het systeem normaal te laten werken. De minimum frequentie is ook afhankelijk van de technologie waarmee de controller intern is opgebouwd (fully static?). Het bereik van de klokfrequentie wordt in de datasheet van de controller opgegeven en heeft meestal een maximum van enkele (tientallen) MHz. Men gebruikt in heel wat 8051 compatibele systemen een klok (en dus ook een kwartskristal) van 11.059
44
MHz. Men gebruikt deze frequentie omdat ze intern in de controller kan worden afgedeeld naar een standaard bitrate (communicatiesnelheid) voor de seriële poort. Naast dit kwartskristal worden er ook nog twee capaciteiten vanuit XTAL1 en XTAL2 verbonden naar de massa, zoals wordt aangegeven in de figuur 3.3.2.3.1. De datasheet van een parallel resonant kristal geeft de load capaciteit weer, wat de serieschakeling is van C1 en C2. De waarde van C1 en C2 ligt meestal tussen 20 en 40 pF. Het vergroten van de capaciteiten heeft als gevolg dat de start-up tijd van de oscillator stijgt tot het punt waar de oscillator niet meer start. De capaciteitswaarden verminderen heeft als gevolg dat de oscillator kan gaan oscilleren op een hogere harmonische (overtone) van de grondfrequentie. Daar dit een circuit is waar signalen worden gegenereerd van relatief hoge frequentie worden er ook enkele eisen gesteld aan de fysische layout van het circuit (EMI).
Figuur 3.3.2.3.1 Kristal oscillator
De signaalbanen op de PCB (Printed Cicuit Board) die het kristal, de capaciteiten en de controller oscillatorpinnen verbinden, moeten zo kort en zo breed mogelijk zijn om parasitaire inductantie en weerstand te beperken. Het kristal en de capaciteiten moeten dus zo kort mogelijk bij de oscillatorpinnen van de controller geplaatst
45
worden, terwijl de signaalbanen naar de oscillator moeten afgeschermd worden van andere signalen om overspraak te vermijden. Dit doet men meestal met een massavlak of met een massa ring rond de signaalbanen. De systeemklok bepaalt de interne klokfases, states en machinecycli m.a.w. de snelheid waarmee externe bustransacties en instructies worden uitgevoerd. Bij een klassieke 8051 bestaat een machine cyclus uit 6 toestanden (states) en is 12 oscillator periodes lang. Elke state is onderverdeeld in een phase1 (P1) en een phase2 (P2). Een machinecyclus bestaat dus uit 12 oscillatorperiodes die genummerd worden van S1P1 (State1, Phase1) tot S6P2. Rekenkundige en logische bewerkingen vinden typisch plaats gedurende phase1 en register naar register transfers gedurende phase2 (figuur 3.3.2.3.2).
Figuur 3.3.2.3.2 Relatie systeemklok en bustiming
46
De uitvoering van een één-cyclus instructie start op S1P2 (stijgende flank PSEN), wanneer de opcode wordt binnengelezen in het instructieregister. Als het een tweebyte instructie is, wordt de tweede byte gelezen tijdens S4 van dezelfde machinecyclus. Wanneer het over een één-byte instructie gaat, zal er ook tijdens S4 een byte gelezen worden (dit is de volgende opcode) maar deze wordt niet verder gebruikt. In elk geval is de uitvoering klaar op het einde van S6P2. De meeste 8051 instructies worden uitgevoerd in één machinecyclus, maar sommige doen er twee of vier cycli over.
Figuur 3.3.2.3.3 Timing lezen en schrijven 47
De timing en de logische gedachtegang van de controlesignalen is belangrijk om de buswerking van een 8051 controller te begrijpen. De timing diagramma’s voor het lezen van een byte uit het programmageheugen en het lezen en het schrijven van een byte uit het datageheugen worden hier in figuur 3.3.2.3.3 weergegeven en spreken voor zich. Alle buscycli starten met het aanbieden van de adresinfo op P0 en P2. Op de dalende flank van ALE wordt de info op P0 in de externe latch opgeslagen, het volledige 16 bit adres is nu stabiel tot het einde van de buscyclus. P0 kan nu veranderen van waarde en de data kan nu weergegeven of gelezen worden langs P0. Voor het ophalen van een instructiebyte uit het programmageheugen zal de stijgende flank van de /PSEN lijn de data (programmabyte) binnenlezen. In het geval het datageheugen wordt aangesproken zal de stijgende flank van het /RD signaal aangeven wanneer de data gelezen wordt, de data moet door het geheugen tegen deze tijd stabiel op de databus geplaatst zijn. Bij het schrijven naar het datageheugen zal de stijgende flank van /WR aangeven dat de data stabiel op de databus zit en dat de geheugencomponent deze mag latchen (lezen). Het ALE signaal wordt gebruikt om de adres/data info op P0 te demultiplexen, maar zelfs bij het uitvoeren van intern opgeslagen code zal dit signaal worden gegenereerd, het is dus een nuttig signaal voor debug doeleinden, dat aangeeft of de controller daadwerkelijk code uitvoert. In single-chip toepassingen kan het ALE signaal, afhankelijk van het type van controller, softwarematig worden uitgeschakeld wat nuttig is voor het beperken van EMI, ALE wordt immers anders geactiveerd met een constante frequentie van 1/6 van de oscillatorklok wat aanleiding kan geven tot elektromagnetische straling. Bij microcontrollers die niet over een externe bus beschikken is het ALE signaal niet aanwezig.
3.4
I/O systemen
De 8051 gebaseerde controllers hebben naast twee interne timers meestal minstens vier parallelle poorten en één seriële poort als I/O. Zoals reeds eerder werd aangehaald heeft bijna elke pin een alternatieve functie toegewezen gekregen. Het nadeel is dat wanneer deze pinnen met hun alternatieve functie worden gebruikt, ze natuurlijk niet tegelijk als gewone I/O pinnen beschikbaar zijn. Eigenlijk kan dit wel, maar dit kan vreemde gevolgen hebben. De gebruiker moet dan wel weten wat hij doet (vb DMX - of LIN break genereren).
48
Na een RESET zijn de vier poorten (I/O pinnen ) allemaal geconfigureerd als Input pinnen, en zijn de alternatieve functies uitgeschakeld. Omdat de werking van de I/O sterk afhankelijk is van de gebruikte component, wijden we verder in de cursus meer tijd aan de werking van de verschillende functies. De besturing van-, maar ook het uitwisselen van gegevens met de interne I/O componenten gebeurt via de SFR registers. In totaal zijn er 128 adressen voorzien om de SFR’s te bereiken. Omdat ze inwendig in de CPU zitten is het zelfs mogelijk bewerkingen uit te voeren op de registers.
3.4.1
Mapping/Paging van SFR’s (XC888)
Omdat de hoeveelheid I/O inwendig in de microcontroller steeds toeneemt volstaan 128 adressen soms niet meer (zie XC888 appendix C (356 SFR’s)). In dat geval wordt er “mapping” en of “paging” toegepast. Dit is een techniek waarbij er gekozen kan worden tussen meerdere sets van SFR’s. Dit systeem vraagt van de programmeur de nodige discipline om bij te houden wat de huidig geselecteerde pagina is. In de XC888 wordt het SYSCON0 register gebruikt om te kiezen tussen 2 sets (maps) van 128 registers (figuur 3.4.1.1).
Figuur 3.4.1.1 SYSCON0 register
49
Dit register is altijd bereikbaar en is dus in de twee sets SFR’s beschikbaar. In appendix C kan je zien dat er wel meer registers altijd bereikbaar zijn (grijze kleur). Binnen elke set van 128 SFR’s is er een bijkomende mogelijkheid om, per I/O module, opnieuw paging te gebruiken. De selectie gebeurt nu echter per groepje van registers die bij een I/O module horen. Elke I/O module heeft zijn eigen selectieregister om aan te geven welke functie een register krijgt (figuur 3.4.1.3). In figuur 3.4.1.2 staat een generieke weergave van het selectieregister dat er per I/O module aanwezig is. Bij standaard gebruik volstaat het schrijven naar de Page Bits om een selectie te maken (OP bits 00). Het systeem voorziet ook een mogelijkheid om bij het gebruik van interrupts de huidige waarde van het register te bewaren, en bij het verlaten van de interruptroutine te herstellen. Hiervoor moeten de OP en STNR bits aangepast worden (OP =10 of 11).
Figuur 3.4.1.2 Paging per I/O module XC888
50
Figuur 3.4.1.3 Paging van XC888 SFR’s 51
HOOFDSTUK 4 De 8051 instructieset
4.1
Inleiding
Na het inschakelen van de voedingsspanning van de microcontroller wordt automatisch een RESET signaal gegenereerd (PowerOnReset of POR). De gebruiker kan dit signaal soms ook manueel bedienen via een schakelaar. Het signaal activeert in de CPU een elektronische schakeling die alle I/O uitschakelt, en de adresteller (ProgramCounter of PC) op nul zet. Hierdoor wordt de controller in een ruststand geplaatst. Vanaf dit ogenblik start de CPU met het ophalen van de eerste programmainstructie uit het codegeheugen. Deze moet op het RESET ADDRESS 0000H staan (De reset vector is 0000h bij een 8051 compatibele controller). Aangezien op dit adres altijd ROM geheugen is geplaatst, moet het gebruikersprogramma hier reeds op voorhand worden gestockeerd. Na reset zal op deze manier het gebruikersprogramma starten. Als de CPU de eerste instructie heeft gelezen en gedecodeerd voert de CPU deze opdracht uit. Automatisch wordt de adresteller (program counter of PC) aangepast, zodat op de adresbus het adres van de volgende instructie geplaatst wordt. De CPU zal deze inlezen, met behulp van het PSEN signaal (read signaal voor programmageheugen), ze uitvoeren, en de volgende instructie ophalen, …. Dit gebeurt volledig automatisch. Als een instructie de CPU de opdracht geeft om een variabele uit het geheugen op te halen, dan worden de bussen eerst gebruikt voor het ophalen van de instructie uit het programmageheugen of “code memory”, waarna ze door de CPU gebruikt worden om de variabele te verplaatsen. Voor de meeste instructies wordt de uitvoeringstijd vooral bepaald door het aantal BUS CYCLES (tijd nodig voor het verplaatsen van 1 byte data over de bussen) nodig om de instructie te lezen en de eventuele data te verplaatsen. Hoe korter de instructie (aantal bytes zo klein mogelijk) hoe sneller de instructie uitgevoerd kan worden. Voor het gebruik van de CPU registers zijn geen extra buscycli nodig. Het programma zal sneller lopen als er registers gebruikt worden, i.p.v. extern geheugen. Voor de CPU bestaat een programma uit een reeks van 8 bit binaire getallen die geïnterpreteerd worden als commando’s en data en die aangeven hoe een taak moet worden uitgevoerd, dit noemt men “machinetaal”. Staat er op een geheugenlocatie geen getal voor een zinvolle instructie, dan zal de processor het getal toch als dusdanig verwerken, en onvoorspelbare acties uitvoeren. Een voorbeeld van een commando is de sequentie 02h,12h,34h in het programmageheugen. Deze getallen worden door de controller geïnterpreteerd als een sprong (02h, de operation code of opcode) naar een geheugenlocatie 1234h (de operand of data). De instructies voor een 8051 controller kunnen bestaan uit 1, 2 of 3 bytes, afhankelijk van de hoeveelheid data die nodig is in de instructie. Alle programma’s geschreven in verschillende programmeertalen, moeten eerst worden omgezet naar machinetaal vooraleer ze bruikbaar zijn voor de controller.
52
Elke instructie die kan uitgevoerd worden door de controller heeft een unieke opcode (eerste getal). Omdat 8 bit getallen (opcodes) voor ons niet erg leesbaar zijn, worden de instructies van de CPU met woorden en afkortingen voorgesteld. Deze MNEMONICS worden zo gekozen dat ze weerspiegelen wat het gevolg van de instructie is. Het zijn deze mnemonics die we op de PC gaan ingeven met behulp van een “editor”. We programmeren dan in assembler instructies. De instructie die hoort bij de vorige getalsequentie is LJMP 1234h. De assembler instructie is de vorm die bruikbaar is voor de programmeur, terwijl de machinetaal door de controller wordt gebruikt. Een assembler programma (vertaalprogramma) zal de mnemonics dan omzetten in opcodes en operands, die via het communicatiepakket in ons controllersysteem belanden (met behulp van het bootloader programma dat in de ROM van de microcontroller zit). De instructieset zijn alle mogelijke instructies (op het laagste niveau) die een controller kan uitvoeren en die dus ter beschikking staan van de programmeur om een programma te schrijven. De 8051 instructieset is geoptimaliseerd voor 8-bit en single bit controletoepassingen. Zo zijn er heel wat instructies die toelaten 1-bit variabelen (Booleaanse variabelen) te manipuleren. Deze bitvariabelen kunnen bits zijn van interne poorten of van verschillende special function registers of, bits uit interne bit-aanspreekbare RAM locaties voor het gebruik als flags. Dit is nuttig voor heel wat controle- en logische functies die booleaanse bewerkingen vereisen. Dit wordt duidelijk in de volgende delen van de tekst. Assembler programma's laten totale controle toe over uitvoeringssnelheid en geheugengebruik, dit betekent dat de programmacode compacter kan gemaakt worden dan bij een hogere programmeertaal en de uitvoeringssnelheid perfect kan gecontroleerd worden. Wanneer men over weinig geheugen beschikt en de timing kritisch is kan het aangeraden zijn om in assembler te programmeren. Het grote nadeel van assembler is dat de programmeur elk detail van het programma voor zijn rekening moet nemen, inclusief geheugen allocatie voor code en data, registergebruik, en de controle over de opslag van variabelen. Hogere programmeertalen verlossen de programmeur van de details die gepaard gaan met assemblerprogrammatie. Geheugen allocatie, beveiliging van variabelen, stackgebruik en vele andere details worden afgehandeld door de compiler. Het nadeel hiervan is echter dat de programmeur de controle verliest over deze details. Gewoonlijk zal de machinecode die resulteert van een programma, dat werd gemaakt in een hogere programmeertaal, beduidend groter zijn en trager werken dan de code uit een assembler programma dat dezelfde taak uitvoert. De keuze van de taal is afhankelijk van de aard van het project. Een efficiënt programma schrijven op een succesvolle manier, in om het even welke taal vereist echter een grondige kennis van de controller en zijn periferiebouwstenen en van het totale embedded systeem. Hierdoor is het schrijven van embedded software een uitdagender en dikwijls veel complexer geheel dan het schrijven van desktop software.
53
Voor de bespreking van de instructieset verwijzen we naar FTP site cd_microcontrollers\8051\8051 general\8051_programmers_guide.pdf. In appendix A en B is een lijst van de beschikbare instructies opgenomen. Volgende paragrafen geven enkele richtlijnen in verband met de aard van de instructies. Hiertoe worden ze in groepen ingedeeld. In de “programmers guide” worden de instructies in alfabetische orde afgewerkt.
4.2
De CPU en zijn basisregisters.
De CPU van de 8051 familie is een 8-bit processor die o.a. bestaat uit volgende delen: een instructiedecoder, een arithmatic section met hierin de ALU of Arithmatic Logic Unit (rekenkundige en logische eenheid), een program controller, een timingen controledeel en een array van registers (8-bit geheugenlocaties in de controller) die aanspreekbaar zijn door de programmeur. Elke programma-instructie wordt door de instructie decoder ontcijferd, en de juiste interne timing signalen worden door de timing/controle-eenheid gegenereerd voor het controleren van de interne werking van de verschillende delen van de CPU. De Timing/Controle eenheid zorgt voor de syncronisatie van de dataflow in en uit de CPU. Hij coördineert de verplaatsing van data op de interne en externe bussen van de microcontroller en hij genereert de /PSEN, /RD en /WR signalen waarvan eerder sprake. De arithmatic section zorgt ervoor dat de data op de juiste manier bewerkt wordt en bestaat uit de ALU ( Arithmatic & Logic Unit), ACCU of A werkregister, B hulpregister, en PSW (Processor Status Word). De ALU is verantwoordelijk voor het rekenkundig bewerken van bytes. Hij handelt de optelling, aftrekking, vermenigvuldiging, deling en de logische bewerkingen af zoals 'and'en en 'or'en van data. De ALU zit intern in de CPU en staat niet rechtstreeks onder controle van de programmeur. Het enige deel van de CPU waar de programmeur via programma-instructies directe controle over heeft zijn de registers, die gebruikt worden om data te stockeren en te manipuleren. De registers bij de 8051 familie bevatten enkele speciale werkregisters: een accumulatorregister (ACCU of gewoon A) dat ingangsdata bevat voor de rekenkundige bewerkingen die met de ALU worden uitgevoerd. De ACCU is ook het register dat het resultaat bevat na een rekenkundige bewerking met de ALU, bij sommige bewerkingen maakt de ALU ook gebruik van het B register (x en /), een 16 bit datapointer register (DPTR), wordt gebruikt voor het aanwijzen van data die in het externe geheugen zitten en die moeten worden gemanipuleerd, Het PSW is een register met een bijzondere functie. Het PSW (program status word) is een register dat enkel bit informatie bevat. Dit wil zeggen dat de waarde van de byte niet belangrijk is, wel de individuele toestand van de STATUS BITS (ook vlaggen of vlag bits genoemd) (paragraaf 4.2.1). een reeks general-purpose registers (registerbanken) voor opslag van data. 54
Een 16-bit Program Counter (PC) register bevat steeds het adres van de volgende uit te voeren instructie. Het PC register wordt beïnvloed door de spronginstructies. De program counter controleert dus de sequentie waarin de instructies worden uitgevoerd door de CPU. De PC wordt impliciet gebruikt, en is niet rechtstreeks toegankelijk.
4.2.1 De PSW
Figuur 4.2.1.1 Program Status Word
Het PSW register bevat een aantal vlaggen die aangepast worden door het resultaat van een uitgevoerde instructie (bv. een berekening). Bovendien worden enkele van de vlaggen als getalwaarde in een volgende berekening verder gebruikt. Er zijn ook speciale instructies die de vlaggen van de PSW gaan testen. In bovenstaande figuur worden de verschillende vlaggen aangegeven. Merk op dat de bits in de PSW een uniek bit-adres hebben. Aangezien de processor over bitinstructies beschikt, kan elke bit getest, veranderd of verplaatst worden. De PSW is een SFR. Ook andere SFR’s (die met I/O modules samenwerken) kunnen status bits of vlaggen bevatten, maar deze worden beïnvloed door hardware gebeurtenissen.
55
De betekenis van de vlaggen is als volgt: De CY bit (CarrY flag), is een bit die aangeeft of er een overflow of underflow conditie is opgetreden, tijdens een vorige bewerking. De carry-bit wordt ook gebruikt als accumulator bij Booleaanse (bit) bewerkingen. De RS0 en RS1 bits worden gebruikt om één van de vier registerbanken te selecteren zoals aangegeven in de tabel onderaan in de figuur 4.2.1.1. De AC (Auxiliary Carry) wordt gebruikt bij BCD bewerkingen en geeft een overflow aan van de LS nibble naar de MS nibble. De P bit geeft de pariteit aan van de accu. P=1 bij een oneven aantal 1's in de accu. De twee bits F0 en F1 zijn general purpose flagbits voor de gebruiker (vrij te gebruiken). De OV bit geeft aan dat er een overdracht is van bit6 naar bit7 in de ACCU, dit kan belangrijk zijn bij getallen waar de hoogste bit het teken voorstelt. Figuur 4.2.1.2 geeft weer welke instructies de vlaggen beïnvloeden. Om te weten welke waarde de vlaggen zullen hebben na de berekening, moet je weten wat de getallen zijn die deel uitmaken van de bewerking, maar ook hoe de vlaggen door de betreffende instructie aangepast worden (zie instructieset).
Figuur 4.2.1.2 Instructies die de vlaggen beïnvloeden
56
4.3 4.3.1
Adresseringsmethoden Algemeen
Om de instructieset van de controller te begrijpen, moet je weten hoe de CPU variabelen in het systeem kan adresseren. De algemene vorm van een instructie bestaat uit een mnemonic en een of twee operands (met tussen vierkante haakjes een parameter (operand) die niet steeds aanwezig is of [OPTIONEEL]): MNEMONIC [BESTEMMING],[BRON] MNEMONIC:
de omschrijving van wat de instructie doet. vb: MOV = verplaats ADD = tel op,....
BESTEMMING:
het register waarop de bewerking wordt uitgevoerd, of de bestemming van het te verplaatsen getal, soms optioneel. vb: 030H = adres register 30H A = de accumulator R0 = verkort adresseerbaar register, ....
BRON:
het register of het getal dat aangeeft van waar de tweede operand moet komen, of wat de tweede operand is. Deze parameter is niet steeds aanwezig (optioneel). Vb: #30h = getal 30h 030h = direct adres 30h R0 = verkort adresseerbaar register, …
Om de adressering toe te lichten gebruiken we de MOV of verplaats instructie. Die laat toe om gegevens te verplaatsen tussen de verschillende registers en geheugens in de controller. Andere instructies gebruiken een gelijkaardige adressering. De MOV instructie bestaat in 3 vormen: MOV voor het verplaatsen van getallen in de CPU (tussen registers) MOVC voor het lezen van getallen uit FLASH geheugen naar de accu (A) MOVX voor het lezen en schrijven van getallen tussen accu (A) en XRAM
57
Afhankelijk van de mnemonic (mov, movc, movx) zijn volgende operands mogelijk: Bestemming: A Rn Direct @Ri @DPTR DPTR C BIT
(alle MOV instructies) (enkel MOV) (enkel MOV) (enkel MOVX en MOV (MOVX onder voorwaarden!!)) (enkel MOVX (XC888 is hierop uitzondering, zie UM. (uitzonderlijk toepassen))) (enkel MOV) (enkel MOV) (enkel MOV)
Bron:
(alle MOV instructies) (enkel MOV) (enkel MOV) (enkel MOVX en MOV (MOVX onder voorwaarden!!)) (enkel MOVC en MOVX) (enkel MOV) (enkel MOVC) (enkel MOVC) (enkel MOV) (enkel MOV)
A Rn Direct @Ri @DPTR #getal (8 of 16 bit) @A+DPTR @A+PC C BIT
NIET ALLE COMBINATIES TUSSEN BESTEMMING EN BRON ZIJN MOGELIJK. OM TE WETEN WELKE MOGELIJKHEDEN TOEGELATEN ZIJN, MOET JE DE LIJST VAN DE INSTRUCTIESET RAADPLEGEN (zie appendix A&B) . Verklaring symbolen voor bronnen en bestemmingen:
A:
accumulator of accu (SFR reken register)
Rn:
werkregister huidig geselecteerde bank (n=0, 1,…,7)
direct:
een 8 bit hex getal dat het adres is van een direct adresseerbaar register vb: 15H (waarde tussen 00H en ffH)
@Ri:
i kan 0 of 1 zijn. Die twee registers kunnen gebruikt worden voor indirecte adressering. @ wil zeggen dat pointer adressering gebruikt wordt. Bij indirecte adressering bevat de pointer (R1 of R0) het adres van de te lezen/schrijven register.
DPTR:
de Data PionTeR is het enige register dat door de CPU als een 16 bit register wordt gebruikt. Het bestaat uit twee 8 bit registers, die afzonderlijk ook gebruikt kunnen worden (DPH en DPL). Door sommige instructies worden ze als een geheel gezien. De DPTR is het enige register dat met één instructie aangepast of geladen kan worden als 16 bit register.
58
@DPTR:
@A+DPTR: idem voorgaande, maar bij de waarde in de DPTR wordt, voor de duur van de instructie, de waarde van de accu opgeteld. De DPTR blijft na de instructie zijn originele waarde behouden (enkel voor code memory te gebruiken)
@A+PC:
PC staat voor Program Counter of programmateller. Dit 16 bit register wordt door de CPU gebruikt om bij te houden op welk adres de volgende opcode gelezen moet worden. Deze adressering werkt enkel met code memory.
#getal:
Wordt het # teken gebruikt, voor een getal, dan wordt het als een numerische waarde gezien, in het andere geval zal de assembler het getal als een adres zien (directe adressering). Het getal is normaal 8 bit, alleen voor het laden van de DPTR, wordt een 16 bit getal toegelaten.
C:
carry vlag in de PSW. De instructie zal slechts 1 bit manipuleren i.p.v een byte.
BIT:
elke bit die via bitadressering bereikbaar is.
4.3.2
de DPTR is het enige register dat gebruikt kan worden voor indirecte adressering met een 16 bit adres, wat ook de enige manier is om gegevens uit het external data- of code memory op te halen. Dit is een omslachtige werkwijze, aangezien eerst de datapointer geladen moet worden met het adres van een variabele, alvorens via de indirecte adressering de variabele gelezen of geschreven kan worden. Als bron (MOVX) of bestemming (MOVC en MOVX) in de CPU kan alleen de accu gebruikt worden.
Voorbeelden MOV A,#03FH
;plaats in de accu het getal 3FH
Het getal dat in de accu moet komen staat expliciet in de instructie. We spreken in dat geval van immediate adressering. Het kardinaal teken (#) geeft aan dat het getal dat er na komt als een getal (numerieke waarde) en niet als een adres moet gezien worden. Getallen mogen in verschillende notaties ingegeven worden: 030H het getal 30 HEXADECIMAAL (16 tallig talstelsel)
030
=030D het getal 30 in het decimale talstelsel. Het vertaalprogramma (ASSEMBLER) zal dit getal wel eerst omzetten naar hexadecimaal, alvorens het in de instructie te plaatsen. 030 of 030D wordt dan 01EH. De omrekening zie je enkel in de opcode, ze is niet merkbaar in de broncode (naam.ASM file), en ook niet in de listing (naam.LST).
59
000100001B = binair talstelsel. Dezelfde redenering is van toepassing zoals bij de decimale getallen. Het getal wordt 21H.
Sommige assemblers gebruiken de notatie 0x21 voor een hexadecimaal getal, 0b00010001 voor de binaire notatie. Je merkt op dat er voor elk getal een extra 0 wordt ingegeven. De 0 heeft geen numerische betekenis, en is enkel nodig om de assembler een foutloze omzetting te laten uitvoeren. De assembler herkent getallen alleen wanneer ze met een cijfer tussen 0-9 beginnen. Voor hexadecimale getallen die beginnen met de letters A-F is die extra 0 noodzakelijk. Uit gewoonte plaatsen we de 0 er overal bij. MOV A,03FH
;neemt een kopie van de inhoud van het ;register met adres 3FH naar de accumulator
Alle adressen tussen 00H en FFH zijn toegelaten. De registers bevinden zich in de controller. Voor de onderste 128 registers is er geen verwarring mogelijk omdat zij slechts één keer in de controller aanwezig zijn. Voor de adressen 80H-FFH zijn er per adres twee registers aanwezig (figuur 4.3.2.1). Omdat het adres van het bronregister expliciet in de instructie is opgenomen spreken we van directe adressering. Dit type instructie zal de SFR registers adresseren. Merk op dat bij de XC888 hetzelfde adres verschillende SFR’s kan betekenen, afhankelijk van de mapping en paging (zie paragraaf 3.4.1). Voor de mogelijke talstelsels die gebruikt mogen worden bij de ingave van het adres verwijzen we naar de immediate adressering.
Figuur 4.3.2.1 Adressering registers 8051 Omdat er veel SFR adressen zijn, en hun functie I/O georiënteerd is, is het gebruik van een naam die informatie geeft over de functie van het SFR handiger in gebruik. In de voorbeeld programma’s kan je zien hoe de numerische waarden door namen vervangen kunnen worden door het EQU directief te gebruiken, of door een bestand
60
op te nemen (include(…)) in jou programma waarin de namen gelijkgesteld worden aan de betreffende waarde (EQU, BIT, DATA directieven).
Bitadressen voor de registers 20h-2fh
Figuur 4.3.2.2 8051 registers tussen 00h en 7fh
MOV A,Rn
;zet in de accumulator de inhoud van ;een verkort adresseerbaar register. 61
In de figuur 4.3.2.2 kan je zien dat er 8 registers zijn waarop de verkorte adressering van toepassing is: R0 t.e.m. R7. Deze 8 registers horen bij elkaar in een registerbank. De verkorte adressering geeft kortere (en dus ook snellere) opcodes dan andere vormen van directe adressering. In de controller zijn er 4 registerbanken aanwezig. De verkorte adressering is slechts van toepassing op één bank. In de PSW zijn er twee bits die bepalen welke bank dat is. De PSW is opgenomen in figuur 4.3.2.3.
Figuur 4.3.2.3 8051 PSW
Bovendien zijn er instructies die alleen gebruikt kunnen worden met de verkort adresseerbare registers (R0-R7). Samen met de accumulator en het PSW register vormen deze registers de meest gebruikte registers in een programma. Merk op dat de registers zowel via hun 8 bit adres (4x8 registers altijd bereikbaar ongeacht de selectie via de adressen 00h-1fh) als de Rx naam (R0-R7 van de geselecteerde bank) bereikbaar zijn
MOV A,@Ri
;in Ri (met i=0 of 1), staat een adres dat verwijst ;naar het eigenlijke register dat we wensen te lezen.
Dit type van instructies wordt vooral gebruikt voor het adresseren van tabellen. Het wordt indirecte adressering genoemd omdat het adres van het te lezen/schrijven register niet expliciet in de instructie voorkomt. Wel wordt de plaats aangegeven waar 62
dat adres te vinden is. Er zijn zo maar twee mogelijke plaatsen: R1 of R0 van de huidig geselecteerde registerbank. De vier types van adressering (en combinaties hiervan) die werden besproken geven bijna alle mogelijke manieren om gegevens tussen registers te verplaatsen. Om dit te illustreren hebben we MOVE instructies gebruikt. Ook bij de andere instructies zijn dezelfde adresseringsmethoden mogelijk. Bij het maken van een programma kan je best met de instructielijst controleren of de instructie die je wenst te gebruiken wel bestaat. In de volgende drie voorbeelden geven we aan hoe het mogelijk is gegevens van en naar het externe geheugen te verplaatsen. Hier kan enkel indirecte adressering gebruikt worden. Omdat het adres nu 16 bit groot is, kan je R0 of R1 niet meer gebruiken. Die zijn immers maar 8 bit groot. Daarom zijn er in de controller 2 SFR’s beschikbaar (DPH en DPL) die samen een 16 bit register vormen (de DPTR). De DPTR bestaat uit de registers DPL en DPH. Ze zijn zowel afzonderlijk als twee 8 bit SFR registers bruikbaar of als een 16 bit SFR. Sommige instructies gebruiken enkel de 16 bit samenvoeging. Die kan je herkennen door dat in de mnemonic de afkorting DPTR gebruikt wordt. Om ze als 8 bit registers aan te spreken kan je al die instructies gebruiken die van toepassing zijn op SFR’s (directe adressering). MOVX
A,@DPTR
;lezen van het externe RAM geheugen.
MOVX
@DPTR,A
;schrijven naar het externe RAM geheugen.
In beide gevallen bevat de DPTR het adres van de externe RAM locatie die gelezen/geschreven wordt. Het lezen of schrijven naar externe RAM kan enkel via de accumulator gebeuren.
MOVC
A,@A+DPTR
;lezen van het code of programma ;geheugen.
Het externe programmageheugen kan enkel gelezen worden via de accumulator. Vermits het een ROM geheugen is, heeft het geen zin er naar te schrijven. Er zijn dan ook geen hardware mogelijkheden om dat te doen. De XC888 wijkt hier af van de standaard 8051 omdat hij een extra instructie heeft die schrijven naar het FLASH geheugen toelaat. Voor bijkomende uitleg verwijzen we naar de XC800_Arch_UM manual op de FTP site of www.infineon.com . LET OP: bij sommige controllers zijn er meerdere datapointers beschikbaar. Een controleregister laat toe om te bepalen hoe de pointers gebruikt kunnen worden. Bij de XC888 zijn er twee datapointers beschikbaar. Je kan via een SFR selecteren welke van de twee er gebruikt wordt (figuur 4.3.2.4). De twee DPTR’s gebruiken dezelfde geheugenlocaties, zodat je in de SFR lijst er maar 1 kan terugvinden (figuur 4.3.2.5). Het is dus aan de programmeur om bij te houden welke DPTR je voor wat gebruikt. In de meeste gevallen volstaat het gebruik
63
van 1 DPTR. In dat geval hoef je het extra controleregister (extended operation register of EO) niet te gebruiken.
Figuur 4.3.2.4 Selectie van de DPTR0 of DPTR1
Figuur 4.3.2.5 DPTR in de SFR’s
Als je aandachtig de instructieset doorneemt kom je twee instructies tegen die afwijken van bovenstaande uitleg: MOVX
@Ri,A
MOVX
A,@Ri
Deze instructies werden origineel bedoeld om te gebruiken met “off chip” XRAM geheugen. De 8051 controller laat dan toe om het P2 register te gebruiken als een page register. P2 kan bij extern geheugen niet meer gebruikt worden omdat de poortpinnen de hoogste 8 bit van de adresbus naar buiten brengen. Wanneer je echter de 2 bovenstaande instructies gebruikt wordt de inhoud van het P2 register op de adresbus geplaatst. Op die manier kan je het externe RAM geheugen verdelen in 256 pagina’s van elk 256 bytes. Vermits op de Ri registers meer bewerkingen 64
mogelijk zijn dan op de DPTR laat dit een flexibele adressering toe. Wanneer het XRAM geheugen “on chip” zit kan deze techniek niet gebruikt worden. In dat geval worden er immers geen externe bussen gebruikt en is P2 een vrij bruikbare I/O poort. De XC888 heeft daarom een SFR dat de paging functie overneemt van P2 (figuur 4.3.2.6).
Figuur 4.3.2.6 XC888 XADDRH SFR register
Voorbeeldprogramma’s algemene opmerkingen en eerste voorbeeld: Merk op dat we in de voorbeeld programma’s kleine letters gebruiken. De assembler is niet hoofdletter gevoelig! Een programma wordt ingegeven in een IDE (Integrated Design Environment) en als een naam.asm bestand opgeslagen. De IDE bevat een compiler (assembler) die de code omzet naar uitvoerbare opcodes voor de controller (een naam.hex bestand) en een bestand waarin de eventuele syntax fouten staan weergegeven (een naam.lst). Uiteindelijk wordt de code overgebracht naar de controller via een bootloader (naam.hex bestand wordt door een ROM programma in de controller ingelezen en in het FLASH geheugen geladen). Er bestaan verschillende freeware IDE’s voor 8051 compatibele microcontrollers. Het bootloader programma is controller specifiek, en kan je meestal downloaden op de site van de fabrikant. Voor de XC888 ( www.infineon.com ) is dat het programma “FLOAD” of “MEMTOOL”, voor andere 8051 compatibelen moet je naar de site van de betrokken fabrikant. De $ lijnen zijn IDE afhankelijk en komen niet noodzakelijk overeen met de gebruikte IDE in het laboratorium. Daar is het mogelijk dat, ter vervanging van ALLE $ (inclusief het END directief) lijnen uit de voorbeeldprogramma’s onderaan het programma volgende lijn opgenomen wordt: #include “c:\naam.inc” (naam is later te bepalen en afhankelijk van de werkomgeving) . In de les wordt hierover bijkomende uitleg gegeven (kan zijn dat ook “c” of een andere programmeertaal gebruikt wordt).
65
; Dit programma stuurt de waarde 05ah naar de LED’s op de XC888 kaart.
org
0000h
;start adres van het programma (zie uitleg labo)
; De poorten van de XC888 staan na reset als input geschakeld. Willen we de LED’s ; aansturen moeten de poorten als output geschakeld worden. Dat doe je door naar ; het register 0b1h (P3_DIR) het getal 0ffh te schrijven. Het register is na reset ; toegankelijk zonder mapping of paging te gebruiken. mov mov ljmp
p3_dir,#0ffh p3_data,#05ah 0000h
#include “c:\xcez1.inc”
;schakel poort 3 als output ;schrijf het getal 05ah naar de poort 3 (LED’s) ;sprong naar het startadres van het programma ;deze file zegt tegen de assembler welke SFR’s er ;in de XC888 controller aanwezig zijn, en ook ;welk adres die hebben. Hierdoor kunnen we de ;namen van de SFR’s gebruiken zoals die ;voorkomen in het databoek. Tussen de “ ” wordt de ;naam en het pad opgegeven van de include file. In ;dit geval staat het bestand in de root van de C ;drive op de PC.
De vetgedrukte delen in het programma zijn geen instructies maar directieven voor de assembler. Een directief is een aanwijzing die zegt hoe het vertalen moet gebeuren.
LET OP: Het END directief (laatste lijn in de xcez1.inc file, en hier dus niet te zien) zegt enkel tegen de assembler dat hij moet stoppen met vertalen. Omdat het geen instructie is, wordt het directief ook niet vertaald en via de opcodes doorgegeven naar de controller. Je zal op een andere manier de controller moeten zinvol bezighouden. Hij stopt immers nooit met het uitvoeren van instructies. In dit programma is daarom de: LJMP
16bit_address
instructie, of Long JuMP instructie gebruikt die de controller zegt dat hij naar het adres 0000h moet springen. In de voorbeeld programma’s die verder in de tekst zijn opgenomen laten we de directieven soms weg. Je moet ze wel altijd opnemen in je programma! Volgend directief gaan we ook veel gebruiken:
66
naam
EQU
getal
Het EQU directief laat toe om aan een naam een getal toe te kennen. tellerbyte
equ
032h
;tellerbyte=032h
Overal waar we in ons programma het getal 32h willen gebruiken kunnen we nu ook de naam “tellerbyte” plaatsen. Hierdoor is het mogelijk om een programma leesbaar te maken. De equ kan zowel voor 8 bit als voor 16 bit getallen gebruikt worden. LET OP!! Het EQU directief zorgt er voor dat de assembler in het programma de naam vervangt door het bijhorende getal. Het directief zegt niet wat de betekenis van het getal is. Het kan dus evengoed een adres voorstellen. De uiteindelijke betekenis wordt bepaald door de instructie die de naam gebruikt: mov mov
a,tellerbyte ;zet in de accumulator de inhoud van register 32h a,#tellerbyte ;zet in de accumulator het getal 32h
naam
DATA
getal
naam
BIT
getal
Data en BIT zijn directieven met een gelijkaardige werking als het EQU directief. Het enige verschil is dat de assembler zal nagaan of de naam enkel gebruikt wordt in een instructie waarbij de naam een register (data) is of een bitvariabele (bit). Bij het equ directief is er geen enkele controle. Voorbeeldprogramma EQU:
start
equ
0000h
output ledinfo
equ equ
0ffh 05ah
;de naam start wordt door de asm vervangen door ;het getal 0000h ;output=0ffh ;getal dat we naar de LED’s willen sturen
org
start
;start adres van het programma
; De poorten van de XC888 staan na reset als input geschakeld. Willen we de LED’s ; aansturen moeten de poorten als output geschakeld worden. Dat doe je door naar ; het register 0b1h (P3_DIR) het getal 0ffh te schrijven. Het register is na reset ; toegankelijk zonder mapping of paging te gebruiken. mov mov
p3_dir,#output p3_data,#ledinfo
ljmp
start
;schakel poort 3 als output ;schrijf het getal 05ah naar de poort 3 ;(LED’s) ;sprong naar het startadres van het
67
;programma #include
“c:\xcez1.inc”
Alle registers of bits die in het databoek een naam hebben, mogen met die naam in een programma gebruikt worden. Het is niet nodig (toegelaten) om ze met een equ directief te definiëren omdat in de include file reeds werd gedaan. Let op: sommige bits kunnen niet rechtstreeks geadresseerd worden, omdat de hardware in de controller dit niet toelaat. Je krijgt hiervoor geen foutmelding!!
4.4 4.4.1
Wiskundige instructies Inleiding
Deze instructies voeren een berekening uit op één of twee variabelen. Een variabele kan maar aangepast worden wanneer hij in een register staat. De tweede variabele kan de inhoud van een register zijn, maar ook een getal (constante) dat in de instructie wordt meegegeven. De 8051 CPU core heeft een ACCUMULATOR STRUCTUUR. De ACCU of A register heeft een speciale functie. Enkele belangrijke berekeningen kunnen enkel in de accumulator gebeuren (+, -, x, /, en sommige bitmanipulaties). Merk op dat de CPU slechts de vier hoofdbewerkingen kan uitvoeren, aangevuld met enkele logische bewerkingen (staan niet vermeld tussen de haakjes, omdat die op bijna alle registers uitgevoerd kunnen worden: AND, OR, XOR, INC, DEC,..). Het is niet onmogelijk om een sinus of een log te berekenen, je kan immers alle bewerkingen reduceren tot de vier hoofdbewerkingen. Alle berekeningen gebeuren op 8 bit getallen (vandaar dat de 8051 een 8 bit CPU is). De PSW is een belangrijk register bij het uitvoeren van de 4 hoofdbewerkingen. Het geeft informatie over de uitkomst van de berekening. Bij sommige bewerkingen (+ en -) kan de uitkomst 9 bit groot zijn. Die negende bit wordt in de PSW tijdelijk opgeslagen, zodat hij in kettingberekeningen verder verwerkt kan worden. Maar ook bij delen door 0 wordt in de PSW een indicatie gegeven (OV bit) De CPU kan enkel met gewone binaire getallen rekenen. Omwille van de compactere schrijfwijze noteren we deze getallen meestal in een hexadecimale schrijfwijze. Wensen we andere binaire voorstellingen van getallen te verwerken (BCD, two's complement, ...), dan zullen we ons programma zo moeten schrijven dat er eventuele correcties gebeuren op de uitkomst van de berekening. Zo is 00001001B (09H) +00000001B (01H) = 00001010B (0AH) voor de CPU. Wij verwachten in onze decimale wereld 10D als uitkomst, dit is 0001 0000 B als BCD voorstelling. Een correctie is dus noodzakelijk wanneer we decimale resultaten verwachten. Omdat die correcties niet steeds even eenvoudig zijn, zullen veel programmeurs er de voorkeur aan geven om alle getallen eerst naar een gewone binaire vorm om te
68
rekenen, ze inwendig te verrekenen, en alvorens ze als output naar buiten te sturen, opnieuw om te rekenen naar het gewenste talstelsel (hex of decimaal). Overzicht talstelsels: Een decimaal getal kan per karakter 10 toestanden weergeven: 0 1 2 3 4 5 6 7 8 9. Een getal bestaat uit meerdere van die karakters:
Hierbij is:
Een binair getal bestaat uit een aantal bits. Een getal dat bestaat uit één bit kan met één cijfer worden weergegeven en kan maar twee waarden aannemen: 0 of 1. Bestaat het getal uit meerdere cijfers, dan kunnen grotere getallen verkregen worden:
Meestal wordt het achtervoegsel B gebruikt. Het is belangrijk dat er duidelijke afspraken gemaakt worden over het talstelsel waar in gewerkt wordt. Inwendig in een computer bestaan er enkel binaire getallen. Ook al produceert een winkelkassa een bedrag in het decimaal talstelsel, inwendig wordt alles binair verwerkt. Vermits de controller enkel in binair kan rekenen zal het ingegeven getal eerst van decimaal naar binair omgezet moeten worden. Alle totalen worden berekend, waarna de uitkomst opnieuw naar een decimaal getal omgezet wordt. 18=10010B
Voor grotere getallen wordt het aantal eentjes en nullen aanzienlijk, waardoor het moeilijk wordt om verbaal getallen door te geven: 511= 111111111B 65535= 1111111111111111B Om dit probleem op te lossen worden getallen in groepjes van 4 bits weergegeven: 511= 0001 1111 1111B 65535= 1111 1111 1111 1111B Om minder karakters te moeten gebruiken, wordt het hexadecimale systeem gebruikt. Zo kan men per groep van 4 bits één hexadecimaal getal gebruiken. Een hexadecimaal getal kan per karakter 16 toestanden weergeven, de toestanden 0 tot en met 15 worden hier weergegeven door de karakters: 0123456789ABCDEF
69
Een getal bestaat uit meerdere van die karakters: 1B3H= 1*100H + b*10H + 3*1H Hierbij is 100H=10H*10H ; 10H=FH+1H De compacte schrijfwijze is dan ook de belangrijkste reden waarom het hexadecimale talstelsel veelvuldig gebruikt wordt. Het achtervoegsel H wordt gebruikt om aan te geven dat het over een hex getal gaat. Bij decimale getallen wordt geen achtervoegsel gebruikt. Omdat wij graag weten over hoeveel eenheden het werkelijk gaat, rekenen we het hex getal soms om naar een decimaal getal. Hierbij is:
Zo is: 511= 65535=
0001 1111 1111B = 1FFH 1111 1111 1111 1111B = FFFFH
Decimaal
Binair
Hexadecimaal
BCD
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
0000B 0001B 0010B 0011B 0100B 0101B 0110B 0111B 1000B 1001B 1010B 1011B 1100B 1101B 1110B 1111B
0H 1H 2H 3H 4H 5H 6H 7H 8H 9H AH BH CH DH EH FH
0000 0000B 0000 0001B 0000 0010B 0000 0011B 0000 0100B 0000 0101B 0000 0110B 0000 0111B 0000 1000B 0000 1001B 0001 0000B 0001 0001B 0001 0010B 0001 0011B 0001 0100B 0001 0101B
Een BCD (Binary Coded Decimal) getal is een binaire voorstelling van een decimaal getal waarbij elke digit van het decimaal getal voorgesteld wordt door zijn eigen 4bit binaire sequentie, de codes 1010B tot 1111B (hexadecimaal de letters A-F) worden hier dus niet gebruikt. Voor decimale getallen die bestaan uit één karakter (bij de cijfers 0-9) is de binaire voorstelling en BCD voorstelling gelijk. Zowel bij decimale als BCD getallen zijn de letters A-F uiteraard niet toegelaten.
70
In de vorige tabel zijn ook de BCD voorstellingen opgenomen. Merk op dat er voor elk decimale karakter 4 bits gebruikt worden, en dat de coderingen boven 1001 niet voorkomen. De BCD voorstelling wordt soms gebruikt om decimale getallen inwendig in de computer voor te stellen. Zo is: 511= 0101 0001 0001B (in BCD) 65535= 0110 0101 0101 0011 0101B (in BCD) Een BCD voorstelling van een getal ziet er hetzelfde uit als een gewoon binair getal, alleen stelt ze een totaal andere numerische waarde voor. Inwendig in de computer worden alle getallen binair voorgesteld. Hexadecimale en decimale voorstellingsvormen worden (door ons) uitwendig gebruikt. Wil de computer juiste berekeningen kunnen uitvoeren, is het belangrijk dat we bij het schrijven van het programma rekening houden met het gebruikte talstelsel. Indien niet uitdrukkelijk vermeld gebruiken we steeds HEX getallen.
4.4.2
ADD(C)
De ADD of tel op instructie laat toe om de som te maken tussen twee 8 bit getallen waarvan er minstens een in de accu moet staan. De uitkomst van de berekening komt steeds in de accu te staan. Omdat de som van twee 8 bit getallen een 9 bit uitkomst kan opleveren, wordt de carry gebruikt als negende bit. Er zijn vier mogelijke manieren om de instructie te gebruiken: ADD A,Rn
tweede operand is R0-R7 van de huidig geselecteerde registerbank
ADD A,direct
tweede operand is een direct adresseerbaar register met een adres 00H-FFH
ADD A,@Ri
het adres van het register dat de tweede operand bevat, staat in R0 of R1 van de huidig geselecteerde registerbank
ADD A,#getal
de tweede operand is een 8 bit getal dat in de instructie staat
De ADDC of ADD with Carry instructie is identiek aan de ADD instructie, behalve dat ook nog de waarde van de carry, zoals die was vlak voor de instructie, mee opgeteld wordt. Hierdoor is het mogelijk om getallen met elkaar op te tellen die groter zijn dan 8 bit. De Carry vlag is een bit in het PSW register die aangepast wordt na het uitvoeren van o.a. een add of addc instructie.
Voorbeeld van een 8 bit optelling
71
(dit voorbeeld is geschreven op de READS51 IDE) main reg1l reg1h reg2l reg2h
lus:
equ equ equ equ equ
00000h 020h 021h 022h 023h
;start adres programma ;cpu registers die we gaan ;gebruiken
org
main
;assembler zeggen waar programma ;start
mov mov add mov
reg1l,#020h a,#057h a,reg1l reg2l,a
;getal 20h in reg1l laden ;getal 57h in accu laden ;tel register op bij accu ;uitkomst naar reg2l
ljmp
lus
;blijf hier wachten (einde van programma)
#include “c:\xcez1.inc” Merk op dat als de som van de inhoud van de accu en reg1l groter wordt dan ffh, de carry (CY of c vlag) op 1 komt te staan. In het andere geval blijft (wordt) de carry 0. Dit programma is vrij zinloos omdat er geen output gegenereerd wordt. Voorbeeld van een 16 bit optelling
main reg1l reg1h reg2l reg2h uitl uith
equ equ equ equ equ equ equ
00000h 020h 021h 022h 023h 024h 025h
;start adres programma ;cpu registers die we gaan ;gebruiken
org
main
;assembler zeggen waar programma ;start
mov mov mov mov
reg1l,#0f0h reg1h,#035h reg2l,#060h reg2h,#015h
;getal f0h in reg1l laden ;getal 35h in reg1h laden ;getal 60h in reg2l laden ;getal 15h in reg2h laden
; we gaan de 16 bit optelling uitvoeren van 35f0h + 1560h mov add
a,reg2l a,reg1l
mov
uitl,a
;eerst som lage registers ;houden geen rekening met ;carry uit vorige berekeningen ;uitkomst bewaren
72
mov a,reg2h addc a,reg1h mov
uith,a
;hoge deel verwerken ;nu moet de carry uit de vorige ;berekening wel gebruikt worden ;uitkomst naar uith
lus:
ljmp
lus
;einde programma
#include
“c:\xcez1.inc”
In het programma worden alleen 8 bit getallen gebruikt, alhoewel het over een 16 bit berekening gaat. De programmeur moet weten hoe hij de deelgetallen moet bewerken om uiteindelijk tot een juiste uitkomst te komen. Die 16 bit uitkomst staat in 2 acht bit registers. Ook hier zal de programmeur bij de verdere verwerking rekening moeten houden met de waarde en de plaats van de deelgetallen.
4.4.3
SUBB
De SUBstract with Borrow instructie laat toe om het verschil te maken tussen het getal in de accumulator en een tweede operand. De adressering is identiek aan die van de ADD instructie. De carry heeft een andere betekenis dan bij de optelling, de carry krijgt de functie van “borrow”. De carry wordt nu op 1 gezet (na het uitvoeren van de subb instructie), als de inhoud van de accumulator kleiner was dan de tweede operand. Om de berekening te kunnen uitvoeren, moest de CPU gaan lenen. Dit wordt aangegeven door de carry op 1 te zetten. De instructie zal altijd de waarde van de carry, zoals die was vlak voor de uitvoering van de instructie, als extra aftrekken van het resultaat. Dit is enkel zinvol als we berekeningen van meer dan 8 bit uitvoeren. Het is daarom soms nodig om de carry op 0 te zetten alvorens een verschil te berekenen. Voorbeeld van een 8 bit verschil
main reg1l reg1h reg2l reg2h
equ equ equ equ equ
00000h 020h 021h 022h 023h
;start adres programma ;cpu registers die we gaan ;gebruiken
org
main
;assembler zeggen waar programma ;start
mov mov clr subb
reg1l,#020h a,#057h c a,reg1l
;getal 20h in reg1l laden ;getal 57h in accu laden ;carry op nul zetten ;verminder accu met inhoud reg1l
73
; omdat er niet geleend moet worden bij de berekening zal de ; carry nu op 0 staan mov
reg2l,a
;uitkomst naar reg2l (=37h)
ljmp
main
;blijf dit doen
De registers reg1h en reg2h worden in het programma niet gebruikt. Ze werden wel met het equ directief een waarde toegekend, wat eigenlijk overbodig is. Indien we 001h-001h zouden berekenen, dan is het resultaat afhankelijk van de Carry voor de aanvang van de berekening (de clr c instructie in programma zet c=0). Stel c=1 (we laten de clr c instructie weg): dan is de uitkomst: 001h-001h-c=0ffh en de Carry staat opnieuw op 1. De CPU voerde volgende berekening uit: 001h-001h-1 kan niet worden berekend omdat de uitkomst negatief is. Daarom gaat de CPU lenen ( geeft dit aan door c op 1 te zetten) en maakt volgende berekening: 100h+001h-001h-1=0ffh als uitkomst, en de Carry op 1 omdat er 100h geleend is. Stel c=0 (hier zijn we zeker van als we de clr c instructie toevoegen): Dan is de uitkomst 001h-001h-0=000h en de Carry staat nu op 0 (lenen was niet nodig)
Voorbeeld van een 16 bit verschil
main reg1l reg1h reg2l reg2h uitl uith
equ equ equ equ equ equ equ
00000h 020h 021h 022h 023h 024h 025h
;start adres programma ;cpu registers die we gaan ;gebruiken
org
main
;assembler zeggen waar programma ;start
mov mov mov mov
reg1l,#010h reg1h,#035h reg2l,#060h reg2h,#015h
;getal 10h in reg1l laden ;getal 35h in reg1h laden ;getal 60h in reg2l laden ;getal 15h in reg2h laden
; we gaan het 16 bit verschil berekenen van 3510h - 1560h mov
a,reg1l
;eerst verschil lage registers
74
clr
c
;wensen een vroegere carry niet ;te gebruiken ;bereken 010h-060h-0 (c=0) ;uitkomst bewaren
subb a,reg2l mov uitl,a
; omdat 010h – 060h een negatief getal zou geven zal de cpu gaan lenen waardoor ; het verschil van 110h-060h berekend wordt. Het gaan lenen wordt aangegeven ; door de carry te zetten. ; In een volgende berekening moet de carry verrekend worden. mov a,reg1h subb a,reg2h mov
;hoge deel verwerken ;nu moet de carry uit de vorige ;berekening wel gebruikt worden ;uitkomst naar uith
uith,a
; omdat de uitkomst van 3510h-1560h een positief getal is, zal de carry nu op 0 ; staan. Stel dat de uitkomst negatief zou zijn dan zal de carry op 1 staan. ; De uitkomst klopt dan slechts gedeeltelijk zoals volgend voorbeeld aangeeft: ; 1234h – 1235h = ffff en c=1 ; Eigenlijk is de uitkomst negatief. Microcontrollers kennen alleen positieve gehele ; getallen. Er zal dus een foutdetectie nodig zijn (tenzij je op voorhand de mogelijke ; resultaten beperkt). lus:
ljmp
lus
4.4.4
INC/DEC
;eindeloze lus (einde programma)
De INCrement of DECrement instructies verhogen, of verlagen de waarde in het betreffende register met 1. Er worden geen vlaggen aangepast. Wanneer een register de waarde 0ffh bevat en de inc instructie wordt uitgevoerd, komt de waarde op 000h, zonder dat de c vlag aangepast wordt. Wanneer de waarde in een register op 000h staat en de dec instructie wordt gebruikt, komt de waarde op 0ffh, zonder dat de c vlag aangepast wordt. Voorbeeld inc en dec instructies inc inc inc
R0 a 23H
;register R0 wordt met een verhoogd ;accumulator wordt met een verhoogd ;register 23H wordt met een verhoogd
mov inc
R0,#023h @R0
dec
R0
;het getal 23H wordt in R0 geladen ;de inhoud van het register, waarvan ;het adres in R0 staat wordt met een ;verhoogd (inhoud register 23h +1) ;register R0 wordt met een verminderd
75
4.4.5
dec dec
a 23H
;accumulator wordt met een verminderd ;register 23H wordt met een verminderd
mov dec
R0,#023h @R0
;het getal 23H wordt in R0 geladen ;de inhoud van het register, waarvan ;het adres in R0 staat wordt met een ;verminderd (inhoud register 23h-1)
INC DPTR
Deze instructie is de enige 16 bit bewerking die door de core uitgevoerd kan worden. Ze verhoogt de waarde van het 16 bit register DPTR (dat bestaat uit twee 8 bit registers), met 1. Ook hier worden er geen vlaggen aangepast. Deze instructie wordt vooral gebruikt als de DPTR als pointer gebruikt wordt. Wanneer de waarde in DPH en DPL 0ffh is, dan zal na de inc dptr instructie de waarde op 000h en 000h komen, zonder dat de c vlag aangepast wordt.
4.4.6
MUL AB
Met deze instructie wordt de inhoud van de accumulator vermenigvuldigd met de waarde van het B register. Het B register is een SFR die enkel bij de MUL en de DIV instructies een speciale functie heeft. Omdat de uitkomst groter is dan 8 bit (max 16 bit), worden A en B gebruikt voor het resultaat. Het B register bevat de 8 hoogste bits van het resultaat, de accumulator bevat de 8 laagste bits. Voorbeeld mul instructie mov mov mul
a,#023H b,#04aH ab
;getal 23H in de accumulator ;getal 4aH in het b register ;vermenigvuldig
na het programma staat er in de accumulator 1eh en het b register bevat 0ah, wat als uitkomst geeft 0a1eh. Indien a en b een maximale waarde van 0ffh zouden bevatten, is de uitkomst van de vermenigvuldiging fe01h, wat nog steeds een 16 bit getal is. Er kan dus nooit een overflow optreden. Is het de bedoeling om grotere getallen met elkaar te vermenigvuldigen, dan komt er wel wat rekenwerk aan te pas. In volgend voorbeeld stellen de letters een 8 bit register voor (vb x en y) de notatie xy is dan het product van de inhoud van de registers x en y, en dus een 16 bit getal (8bit vermenigvuldigen met 8 bit geeft een 16 bit uitkomst). De notatie x_y wil zeggen dat we de twee registers als één 16 bit getal
76
bekijken. In het voorbeeld vermenigvuldigen we twee 16 bit getallen met elkaar. De 0 in het voorbeeld is een byte met als inhoud 000h
Er zijn dus 4 acht bit registers nodig om de twee 16 bit getallen op te slaan die we met elkaar willen vermenigvuldigen. De vermenigvuldiging van de twee 16 bit getallen wordt vervangen door vier vermenigvuldigingen van 8 bit, met telkens een 16 bit uitkomst (vy, uy, vx en ux). Door de 16 bit tussenresultaten 0, 8 of 16 bit op te schuiven (doen we met de 0 byte toe te voegen) houden we rekening met de numerieke waarde van elk deelgetal. Die tussenresultaten worden bij elkaar opgeteld om uiteindelijk een 32 bit resultaat te geven. Onderstaand voorbeeld toont dit met 2 decimale getallen:
4.4.7
DIV AB
Bij de deling wordt de inhoud van de accu door de inhoud van het B register gedeeld. Bij een deling door 0 wordt de overflow flag geset. De uitkomst van de deling wordt in de accumulator bewaard, in het B register staat de rest van de deling. Voorbeeld: mov mov div
a,#023H b,#04aH ab
;getal 23H in de accumulator ;getal 4aH in het b register ;delen (a door b)
na het programma staat er in de accu 0H en het b register bevat 23H mov mov div
a,#04aH b,#023H ab
;getal 4aH in de accumulator ;getal 23H in het b register ;delen
na het programma staat er in de accu 2H en het b register bevat 4H
77
4.4.8
DA A
Zoals reeds vroeger werd aangehaald, kan de processor enkel rekenen met binaire getallen. In een vorige paragraaf hebben we gezien dat we gemakkelijkheids halve de hex_notatie gebruiken en dat dit slechts een andere vorm is om binaire getallen weer te geven. Soms is het wenselijk om met andere talstelsels te rekenen. De meest voorkomende zijn dan de two's complement en de BCD getallen. De CPU weet niet welk type van variabelen we gebruiken, hij gaat er van uit dat het steeds gewone binaire getallen zijn. Staan de getallen in een andere voorstelling (bv. BCD), dan worden foutieve uitkomsten bekomen. De processor zal voor binaire getallen in zowel de two’s complement, als voor de BCD voorstelling vlaggen aanpassen na een berekening (genereren), zodat aangepaste (zelf te schrijven) software de nodige correcties kan doorvoeren. Voor de BCD voorstelling is er zelfs een instructie voorzien, die de correctie doorvoert.
Let wel, de vlaggen en de correctie instructie werken slechts in welbepaalde gevallen. De instructie kan in geen enkel geval gebruikt worden om een omzetting tussen talstelsels door te voeren. Aangezien je heel uitzonderlijk two's complement getallen zal verwerken met deze core, gaan we daar niet verder op in. Voor de BCD voorstelling gebeurt dit wel meer. De correctie instructie is de Decimal Adjust Accumulator. Ze werkt enkel
vlak na de optelling van twee 8 bit getallen, die voor de berekening reeds in de BCD notatie stonden. Voorbeeld: mov a,#015H ;zet de BCD voorstelling van het getal 15 in de accu ;Je zou kunnen denken dat de instructie zonder de H gebruikt moet worden: ;mov a,#15 ;Stel dat we dit zouden doen, dan zal de assembler het getal eerst omrekenen naar ;het hex talstelsel, waardoor volgende instructie uitgevoerd wordt: ;mov a,#0fH ;wat natuurlijk niet het gewenste BCD getal 15 = 0001 0101 is. mov add
R0,#026H a,R0
;zet het getal 26H in R0 ;maak de optelling
;na de optelling staat er in de accumulator 15H + 26H = 3bH da
a
;voer de correctie uit
;nu staat er in de accumulator 41H wat wel de juiste BCD som is.
78
4.5
4.5.1
Logische instructies
Inleiding
Bij de wiskundige instructies wordt de inhoud van een register, of een operand als een 8 bit getal gezien. De bits binnen de byte horen bij elkaar, en vormen een geheel. Bij de logische operaties is dit niet het geval. Elke bit wordt als een afzonderlijke variabele gezien, en bij een logische bewerking tussen twee bytes worden de berekeningen uitgevoerd op de overeenkomstige bits binnen de bytes. Er zijn dus 8 resultaten na de bewerking. De accumulator heeft nog altijd een bevoorrechte rol, alhoewel ook andere registers gebruikt kunnen worden voor het uitvoeren van sommige instructies. 4.5.2
ANL/ORL/XRL
Met de ANd Logical instructie worden de overeenkomstige bits van de variabelen geand. OR Logical en eXclusive oR Logical werken op een vergelijkbare manier. Voorbeelden orl,anl,xrl mov anl
a,#10101010b a,#11110000b
;we laden een binair getal in a ;voeren de and bewerking ;uit met een constante
Na de bewerking zal in de accu 10100000b staan. De ANL instructie kan gebruikt worden om een deel van een register op nul te zetten, zonder dat de andere bits wijzigen (in het voorbeeld de laagste 4 bits). mov orl
a,#10101010b a,#11110000b
;we laden een binair getal in a ;voeren de or bewerking ;uit met een constante
Na de bewerking zal in de accu 11111010b staan. De ORL instructie wordt soms gebruikt om een deel van een register op 1 te zetten, zonder de andere bits aan te passen (in het voorbeeld de hoogste 4 bits). mov xrl
a,#10101010b a,#11110000b
;we laden een binair getal in a ;voeren de or bewerking ;uit met een constante
Na de bewerking zal in de accu 01011010b staan. De XRL instructie wordt soms gebruikt om bits te inverteren door een exclusive OR toe te passen met een constante waar in op de te inverteren bitposities een 1 staat. De bits die men niet wil
79
veranderen worden op 0 gezet. Ook voor het programmeren van een “compare” functie kan deze instructie samen met een JZ instructie als volgt worden gebruikt: xrl jz
4.5.3
a,#00000011b label
;we gaan de inhoud van a vergelijken met 3 ;spring naar label als gelijk aan 3
Specifieke accumulator instructies
CLR A
met deze instructie worden alle bits in de accu op 0 gezet
CPL A
ComPLement de accu maakt een ones complement van de waarde in de accu
Voorbeelden mov cpl
a,#11100000b a
;de accu bevat nu 00011111b, wat het complement is van 1110000b. clr
a
;de accu bevat nu 00000000b, ongeacht de vorige waarde.
Rotate instructies schuiven de bits in de accumulator. De tweede letter in de instructie geeft de richting aan waarin de bits opgeschoven worden: Left of Right. Het is ook mogelijk om de carry als negende bit mee door te schuiven. Er wordt dan aan de instructie een C toegevoegd. In het voorbeeld wordt grafisch weergegeven wat de instructie doet. Voorbeelden RL, RR, RLC en RRC mov clr RL
a,10101011b c A
;zet in de accu 10101011b ;zet de carry vlag op 0 ;A=01010111b CY=0
RR
A
;A=10101011b
CY=0
80
RR
SWAP A
A
;A=11010101b
CY=0
RLC A RLC A
;A=10101010b ;A=01010101b
CY=1 CY=1
RRC A
;A=10101010b
CY=1
Deze instructie verwisselt in de accu de hoge nibble en de lage nibble van plaats.
Voorbeeld SWAP A mov a,#11110000b swap a na de instructie staat er in de accumulator:
a=00001111b
81
4.6
Verplaats instructies
Deze instructies verplaatsen 8 bit getallen in het systeem. Hierbij komen alle registers en het external memory in aanmerking. Het external code memory kan echter niet geschreven worden. Hiertoe ontbreekt het aan instructies, en controle lijnen. De MOV instructies werden reeds als voorbeeld behandeld. We beperken ons hier tot de 3 buitenbeentjes: PUSH, POP, XCH.
4.6.1
De exchange instructie
De eXCHange instructie verwisseld de inhoud van de twee registers. De xchd werkt slechts op de laagste 4 bit in de betreffende registers (d staat voor digit en een digit is 4 bits groot (4bits=nibble)). Een voor beeld is terug te vinden in de uitgebreide instructieset.
4.6.2
De systeemstack, stackpointer en de PUSH / POP instructies.
4.6.2.1
De systeemstack en stackpointer (call instructies)
De stack ( ook wel stapelgeheugen) gebruikt een aantal GPR’s die elkaar in adres moeten opvolgen (GPR’s tussen 00h en ffh kunnen hiervoor gebruikt worden). De plaats en het aantal worden door de programmeur bepaald (gereserveerd door ze zelf nergens te gebruiken!). Reserveren we er te veel, dan blijven er een deel niet gebruikt. Reserveren we er te weinig, dan zal het systeem vastlopen omdat we variabelen die buiten de gereserveerde zone zijn opgeslagen overschrijven. Het benodigde aantal is afhankelijk van het programma dat we door de controller laten uitvoeren. Sommige controllers hebben een stack van 6 locaties diep, andere gebruiken een minimale stack van 128 bytes. Voor de XC888 volstaan meestal 20H bytes (blijkt uit ervaring, neem er 40h als je xcez routines gebruikt). Eens het programma klaar is moet men, in principe, nagaan of de stack voldoende opslagruimte biedt voor het uit te voeren programma. Data-elementen die in een bepaalde volgorde op stack worden geplaatst moeten er in de omgekeerde volgorde weer worden afgehaald. De stack heeft een LIFO werking (Last In First Out). De gegevens die door de CPU in de stack bewaard worden zijn de terugkeeradressen bij: een subroutine-oproep via een (L)CALL instructie een interrupt-routine oproep via interrupt verwerking. Daarnaast kan ook de programmeur beroep doen op de stack om tijdelijk gegevens op te slaan (zie PUSH en POP instructies in volgende paragraaf).
82
Bij de stack hoort een Stack Pointer (SP), wat een SFR is. Het SP register geeft de laatst gebruikte plaats aan in de stack. Het bevat m.a.w. een adres, vandaar de naam (stack)pointer. De pointer wijst naar de eerste vrije locatie waar de controller een terugkeeradres kan bewaren bij een CALL of INTERRUPT, of het programma gegevens kan opslaan met push en pop instructies. Na RESET staat de SP op 07h (default waarde). De SP wordt automatisch geïncrementeerd, voordat data wordt gestockeerd op de plaats waar de SP naar wijst ( pre-increment bij PUSH, CALL instructies en een interrupt), de SP wordt gedecrementeerd nadat de data werd gelezen ( post-decrement bij RET,RETI en POP instructies). Dit mechanisme verloopt trouwens volledig automatisch! Als programmeur moet u hiervoor niets ondernemen. De stack kan zich overal in GPR’s bevinden, en moet dus niet starten op adres 08H. De programmeur kan dit veranderen door de SP te initialiseren met een andere waarde a.d.h.v. een MOV SP,# xx instructie, waar xx staat voor het adres waar de stack moet starten (eerste gebruikte register heeft adres xx+1). Dit automatische mechanisme bij een subroutine-call wordt aangegeven in figuur 4.6.2.1.1. Voor bijkomende uitleg i.v.m. de (L)CALL instructie verwijzen we naar paragraaf 4.8.5 in dit hoofdstuk. Als vertreksituatie nemen we aan dat de SP nog op de resetwaarde 07h staat en dat de controller instructies uitvoert in een hoofdroutine en zo bij de 'CALL subroutine' instructie komt. De controller zal een routine op het adres 'subroutine' moeten gaan uitvoeren, maar eerst zal de SP met één worden verhoogd en zo wijzen (1) naar de volgende vrije locatie 08h op de stack. Dan zal de controller de low byte van het adres van de instructie na de “CALL subroutine” (YY) op stack plaatsen (2) . De volgende actie is de SP weer met één verhogen tot locatie 09h (3) en ook de high byte (XX) van het adres op stack plaatsen (4). De PC kan nu worden geladen met het adres 'subroutine' (5) zodat de controller op dit adres de volgende instructie gaat ophalen (sprong naar subroutine). Bij het beëindigen van de subroutine zal de RETurn instructie als gevolg hebben dat de high byte van de PC zal geladen worden met de inhoud van de geheugenlocatie waar de SP naar wijst (7) en dat de SP daarna met één wordt verminderd. Daarna wordt op het adres waar de SP naar wijst de byte opgehaald die in de low byte van de PC wordt geplaatst (8) waarna de SP weer met één wordt verminderd. De SP staat nu op de waarde die hij had voor het uitvoeren van “CALL subroutine”.
83
Doordat de PC nu is geladen met het terugkeeradres (XXYY) dat op stack werd geplaatst bij de CALL, zal de volgende instructie op dit adres worden opgehaald, de controller is m.a.w. terug naar de hoofdroutine gesprongen. Dit mechanisme verloopt volledig automatisch bij een CALL en RET instructie en de programmeur moet hiervoor geen acties ondernemen. Externe controllergeheugen met instructies
Adres van de instructies 16-bits
instructie n instructie n+1 instructie n+2 instructie n+3 call subroutine instructie n+5 instructie n+6
XXYY
9
Stack-pointer register (wijst naar intern geheugen in de controller)
Interne controllergeheugen
SP 8-bit SP na reset
1
SP+1
3
SP+1
2
opslaan LSB
4
opslaan MSB
YY XX
5
07h 08h 09h 0Ah 0Bh
PC = subroutine
Program counter 16-bit
Jump to XXYY
PChigh
PClow
subroutine
6
Jump to subroutine
7
PChigh =XX SP-1
8
PClow =YY SP-1
return
Figuur 4.6.2.1.1 Voorbeeld van stack-werking bij een (L)CALL en RETurn instructie
4.6.2.2
Push en POP instructies
84
De programmeur kan ook zelf data op de stack plaatsen, en data van stack halen, door gebruik te maken van resp. PUSH en POP instructies. Door deze instructies te gebruiken kunnen registers tijdelijk bewaard worden, zonder dat hiervoor (behoudens voor de stack) registers gereserveerd moeten worden.
PUSH register Deze instructie laat toe de inhoud van een direct adresseerbaar register op de stack te bewaren. Hierdoor kan het tijdelijk vrijgemaakt worden voor een berekening. Dit is vooral voor de A, B en PSW registers handig, maar ook voor andere systeemregisters (syscon0, DPh en DPL,…). De SP wordt impliciet gebruikt als adresregister voor indirecte adressering. Telkens de inhoud van een register naar de stack geschreven wordt (PUSH register), zal de SP eerst automatisch met 1 verhoogd worden, waardoor hij naar de volgende vrije locatie wijst. Dan wordt de waarde in het register in de stack zone geschreven. Het register kan nu voor iets anders gebruikt worden. POP register Bij het lezen van een byte uit de stack naar een register (POP register) gebeurt het omgekeerde (eerst lezen waarde van de stack naar het aangeduide register, dan wordt de SP-1). De stack werkt als een LIFO (Last In/ First Out) memory. Er wordt niet onthouden wat er waar zit, er is enkel de SP die aangeeft waar de laatste variabele op de stack opgeslagen is. De gebruiker moet zelf instaan voor het juiste gebruik van het LIFO geheugen. Figuur 4.6.2.2.1 geeft een grafische voorstelling van de stack ruimte. Na initialisatie “wijst” de SP naar de eerste locatie van de stack-1. In de figuur wordt er van uit gegaan dat de registers 86h t.e.m. 90h vrij zijn (door ze niet te gebruiken als opslagruimte voor variabelen in het programma). Merk op dat register 85h wel gebruikt kan worden. De SP wordt geladen met deze waarde, maar zal eerst verhoogd worden alvorens naar de stack te schrijven. De beschikbare stack ruimte wordt bepaald door het aantal opeenvolgende registers die we niet gebruiken in het programma. De werking voor PUSH en POP is dus analoog aan de werking bij interrupts en (L)CALL instructies. De PUSH en POP instructies gebruiken wel maar 1 byte stack ruimte, i.p.v. 2 voor de interrupt en (L)CALL. Vermits je in een interruptroutine of subroutine de PUSH en POP instructies kan gebruikt (soms moet gebruiken) zullen terugkeer adressen en opgeslagen data elkaar afwisselen. De programmeur is verantwoordelijk voor de correcte werking van dit systeem. Een aanzienlijk deel van de vastgelopen programma’s zondigen tegen het gebruik van de stack. Er zijn enkele vuistregels die een juiste werking garanderen: haal niets van de stack zonder het er eerst op te zetten zet niets op de stack zonder her erna weer af te halen
85
respecteer het LIFO systeem (volgorde PUSH en POP) gebruik altijd een RET instructie om een subroutine te verlaten gebruik nooit een sprong instructie om een subroutine op te starten een interruptroutine wordt altijd afgesloten met een RETI Indirect adresseerbare registers
Direct adresseerbare registers
GPR
SFR (I/O)
ffh
ffh
90h Stack ruimte 86h 85h
Eerste byte gebruikt als stack Wordt niet gebruikt als stack
85h 80h 7fh
81h=SP 80h
00h
Figuur 4.6.2.2.1 Grafische voorstelling stack ruimte. De stack kan enkel via indirecte adressering gebruikt worden (donker grijze registers).
Push en pop zijn pre_increment en post_decrement indirecte adressering instructies. Voorbeelden push en pop Stel: a=001h r0=002h
86
register 30h=003h register 31h=004h geselecteerde registerbank=0 (rs1=rs0=0 in de PSW) mov
sp,#020h
; de sp wordt met 20h geladen. De eerste locatie die gebruikt zal worden is 21h ; indien we de adressen 21h t.e.m. 24h nergens anders gebruiken, dan kan de stack ; 4 bytes groot worden. push
30h
; de sp wordt met 1 verhoogd (sp=sp+1) waardoor de inhoud 21h wordt ; de inhoud van register 30h (003h) wordt naar adres 21h weggeschreven push
31h
; de sp wordt met 1 verhoogd (sp=sp+1) waardoor de inhoud 22h wordt ; de inhoud van register 31h (004h) wordt naar adres 22h weggeschreven push
acc
; De push instructie heeft als operand het adres van het register. In de ; regxc888.inc en xcez0.inc files is hiervoor de naam acc gebruikt. De letter a ; wordt gebruikt in sommige mnemonics, en geeft aan dat er een speciale ; opcode is die op de accu inwerkt, zonder dat de vermelding van het adres ; nodig is. ; de sp wordt met 1 verhoogd (sp=sp+1) waardoor de inhoud 23h wordt ; de inhoud van de accu (001h) wordt naar adres 23h weggeschreven push
000h
; De push instructie heeft als operand het adres van het register. R0 is een ; een onderdeel van de mnemonic, en geeft aan dat er een speciale opcode is ; die op dat register inwerkt, zonder dat de vermelding van een 8 bit adres ; nodig is. Als registerbank 0 geselecteerd is kunnen we het register met ; de naam R0 bereiken via adres 000h. ; de sp wordt met 1 verhoogd (sp=sp+1) waardoor de inhoud 24h wordt ; de inhoud van de r0 (002h) wordt naar adres 24h weggeschreven In figuur 4.6.2.2.2 zie je de toestand van de stack en sp na het uitvoeren van het programma.
87
Indirect adresseerbare registers
Direct adresseerbare registers
GPR
SFR (I/O)
ffh
ffh
24h 80h 7fh
24h 23h 22h 21h 20h
81h=SP 80h
02h 01h 04h 03h
00h
Figuur 4.6.2.2.2 Stack en stackpointer na de 4 push instructies Als we nu de registers 30h, 31h, a en r0 gebruiken in het programma zal de originele inhoud overschreven worden. We kunnen ze terug laden met hun originele waarde door die van de stack te lezen. De stack werkt als LIFO (last in, first out). De eerste pop instructie zal de waarde uit register 24h lezen, waarna de sp met 1 verminderd wordt. De gelezen waarde komt in het register waarvan het adres in de pop instructie wordt opgegeven. pop
30h
zal de waarde 02h van de stack lezen en in register 30h opslaan. Willen we de registers met hun originele waarde herstellen, dan moet in de omgekeerde volgorde de stack leeg gemaakt worden: pop pop
000h acc 88
pop pop
31h 30h
De stack is een tijdelijk opslagmedium. Het is noodzakelijk dat de programmeur er voor zorgt dat stack steeds naar een “lege” toestand terugkeert. Je zou kunnen denken dat het herschrijven van de SP met zijn startwaarde gebruikt kan worden om de stack te “flush-en”. Niets is minder waar. De stack wordt immers ook door de CPU gebruikt bij het verwerken van interrupts en subroutines (zie vorige paragraaf). Indien enkel de PUSH en de POP instructies de stack zouden gebruiken, dan is het eenvoudig om de benodigde ruimte voor de stack te bepalen. Je hoeft maar het maximaal aantal PUSH-instructies te tellen, dat niet door een POP instructie wordt gevolgd. De CPU gebruikt echter ook de stack, zonder dat de gebruiker expliciet het bevel hiertoe geeft. De CPU zal de PC naar de stack schrijven telkens het programma op een plaats onderbroken wordt, waar later de draad weer opgenomen moet worden (dit is het geval bij de (L)CALL instructies en bij interrupt verwerking). Aangezien de PC 16 bit groot is, worden telkens twee bytes gebruikt. Gelukkig kan de programmeur weten wanneer de CPU de stack heeft gebruikt, zodat de eigen informatie niet verloren gaat. Omdat de stack niet meer is dan een gereserveerde geheugenruimte, met bijhorende adrespointer (SP), zijn er heel wat mogelijkheden om het systeem overhoop te halen:
de registers die als stack ruimte dienen, worden elders nog gebruikt er worden meer gegevens op de stack geplaatst dan er registers voorbehouden zijn (stack overflow) er worden meer gegevens van de stack gelezen dan er weggeschreven werden (stack underflow) de gegevens worden in een foutieve volgorde uit de stack gelezen er wordt informatie die door de CPU op de stack werd geschreven veranderd de stackpointer wordt veranderd tussen twee stack operaties
De stackpointer wordt door de CPU na RESET automatisch met de waarde 07H geladen. Omdat dit startadres van de stack in de tweede registerbank zit, en we de registerbanken in de meeste toepassingen gebruiken, wordt de SP meestal door een gebruikersprogramma met een andere waarde geladen. Voorbeeld gebruik stack mov sp,#080h push acc push b
;de stackpointer wordt met 80h geladen ;de inhoud van de accumulator wordt ;naar het adres 81h geschreven ;het b register wordt op de stack gezet op ;adres 82h 89
pop
b
pop
acc
;het b register wordt geladen met de ;informatie van adres 82h ;de sp staat na de instructie op 81h ;de accumulator wordt geladen met ;de informatie van adres 81h ;de sp staat na de instructie op ;80h
Merk op dat het adres 80h niet werd gebruikt. Ga na wat het volgende (deel)programma doet! mov
sp,#030h
push acc push b pop pop
4.7
4.7.1
acc b
Bit manipulaties
Inleiding
De 8051 (MCS51) controller is speciaal ontworpen voor embedded en industriële toepassingen. Dit type van applicaties gebruikt veel bit-variabelen. Pompen, ventielen, motoren, lampen,..., kunnen gezien worden als bit variabelen, omdat voor elke uitgang slechts 2 toestanden voorkomen: aan/uit. Einderit schakelaars, niveau detectie, bedieningsknoppen, ... zijn ook bit variabelen. Voor dit type in- en uitgangen, wensen we per variabele maar 1 aansluiting te gebruiken. Een poort bestaat uit 8 bit ( 8 aansluitingen) en wordt gebruikt om 8 variabelen in te lezen/aan te sturen. Het is dan heel handig, als je elke pin van een poort als individuele variabele kan aansturen/lezen/testen. De CPU core laat niet enkel toe dat de belangrijkste SFR's bitsgewijs aangesproken kunnen worden, maar een deel van de gewone registers ( registers 20h t.e.m. 2fh) kunnen op die manier gebruikt worden (figuur 4.7.1.1). De 8051 kan logische bewerkingen uitvoeren op bit-variabelen, ze verplaatsen en testen, zodat het programmaverloop afhankelijk kan zijn van de toestand van 1 bit. Voor de bit instructies werkt de carry als bit accumulator. De bits, behalve de carry in bepaalde instructies, worden aangesproken met een 8 bit adres. Er zijn dus 256 individueel adresseerbare bits in de CPU registers. 128 bits zijn terug te vinden in de GPR's (General Purpose Registers)(figuur 4.7.1.1), de overige 128 zijn
90
voorbehouden voor de SFR's, waarvan het adres eindigt op een 0 of een 8. Niet alle bit adressen in de SFR's zijn geïmplementeerd (aanwezig).
Figuur 4.7.1.1 Bitvariabelen in GPR’s
4.7.2
Berekeningen op bit variabelen
In de figuur 4.7.2.1 staan de instructies vermeld die bewerkingen uitvoeren op bit variabelen. De letter C in de mnemonic verwijst naar de carry vlag in de PSW. Die heeft voor bit variabelen dezelfde functie als de accu voor bytevariabelen. Het woordje “bit” in de mnemonic moet vervangen worden door het adres van de betrokken bit. De adressen van de bits in het GPR geheugen (figuur 4.2.1.1) zijn 000h t.e.m. 07fh. De bits in het SFR gebied hebben adressen van 080h t.e.m. 0ffh. Je kan ook hier met een EQU directief het adres vervangen door een naam. Gebruik je het BIT directief i.p.v. EQU, dan zal de assembler nagaan dat de naam enkel voorkomt in een bit instructie. Net zoals de accu impliciet gebruikt wordt als in de mnemonic de letter a staat, maar het effectieve adres gebruikt wordt als je de naam acc gebruikt, kan dat ook voor C en cy in het geval van de carry.
91
Figuur 4.7.2.1 Bewerkingen op bit variabelen Voorbeelden van bewerkingen op bitvariabelen clr clr
c cy
clr
psw.7
;zet de carry vlag op 0 ;zet de carry op 0 maar gebruikt andere ;opcode (cy=adres van carry bit) ;zet de carry op 0
; psw.7 is het adres van de zevende bit in het psw register. Deze notatie mag enkel ; gebruikt worden als het register bit adresseerbaar is!! setb setb
c p3_data.0
;zal de Carry op 1 zetten ;zal de nulde bit van p3_data op 1 zetten
; p3_dir kan op deze manier niet aangepast worden. Het adres van het SFR eindigt ; niet op 0 of 8 (adres p3_dir=0b1h) en is niet bit adresseerbaar. In figuur 4.7.2.2 is een logische schakeling afgebeeld. De uitgang is afhankelijk van de waarde van vier ingangen en de verbanden ertussen. P3_data.0 P3_data.1 t1 t2
t3
P4_data.0
P3_data.2 P3_data.3
Figuur 4.7.2.2 Vier inputs sturen 1 output
92
De figuur kan je omzetten in volgend programma (na initialisatie poorten): t1
equ
20h.0
;bit t1 is nulde bit adres 20h
mov anl mov mov anl orl cpl mov
c,p3_data.0 c,p3_data.1 t1,c c,p3_data.2 c,p3_data.3 c,t1 c p4_data.0,c
;c vlag is accumulator bij bit variabelen ;c vlag bevat nu waarde t1 ;tussenresultaat opslaan ;volgende inputs verwerken tot t2 ;carry bevat tussenresultaat t2 ;carry bevat nu tussenresultaat t3 ;waarde in carry inverteren ;waarde carry naar buiten sturen
De mogelijkheid om de bits te testen en hiermee het programmaverloop te wijzigen wordt in de volgende paragraaf besproken.
4.8 4.8.1
Programma- en machinecontrole Inleiding
Deze instructies laten toe om het programmaverloop te wijzigen, al of niet afhankelijk van de toestand van een (bit)variabele. Hierdoor is het niet alleen mogelijk om het programma een andere weg te laten volgen, afhankelijk van het resultaat van een berekening of variabele, maar ook om bepaalde delen programma vanuit verschillende plaatsen op te roepen. In eerste instantie moeten we duidelijk het verschil aantonen tussen instructies die het programmaverloop definitief wijzigen (jump instructies) en instructies die een procedure (subroutine of functie) oproepen (call instructies). De jump instructies voeren een, al dan niet voorwaardelijke, sprong uit naar het adres dat in de instructie is opgenomen. De programmeur wil niet terugkeren naar het adres dat door de sprong verlaten werd (figuur 4.8.1.1). Bij een voorwaardelijke sprong zullen we het programma verloop afhankelijk willen maken van de toestand van een variabele. Uit de flowcharts in figuur 4.8.1.1 kan je afleiden dat het gebruik van onvoorwaardelijke sprongen het programma wanordelijk en moeilijk leesbaar kan maken. Een goede programmeur zal trachten de “flow” van het programma zo lineair mogelijk te houden. Dit kan door het gebruik van voorwaardelijke sprongen of subroutines (figuur 4.8.1.2). Een subroutine is een apart stukje programma dat op een bepaald adres in het geheugen staat, en afgesloten wordt met een RET (return) instructie. Een subroutine kan van op verschillende plaatsen in het programma opgeroepen worden, en zal er voor zorgen dat er teruggekeerd wordt naar de juiste plaats (return address). 93
yes
Test
no
Conditioneel (na test)
onvoorwaardelijk (geen test)
Figuur 4.8.1.1 Uitvoering van een spronginstructie
LCALL
Return address 1
Subroutine
LCALL
RET
Return address 2
Figuur 4.8.1.2 Uitvoering van een call instructie 94
4.8.2
Jump instructies
De 8051 beschikt over 3 absolute jump instructies (1 werkt relatief, zie 4.8.3). Het enige verschil is het sprongbereik (het aantal geheugenplaatsen de instructie kan bereiken). LJMP of Long JuMP is een spronginstructie die 16 bit opcode gebruikt om het adres van de bestemming te bevatten. Daardoor is het sprongbereik 64K (volledige geheugen 8051). Gebruik van de instructie: ljmp
adres
Het adres mag vervangen worden door een label. Hierdoor moet de gebruiker niet het absolute adres kennen van de instructie waarnaar gesprongen wordt Voorbeeld van een ljmp lus:
ljmp
lus
;lus is het adres van de instructie na het label
Een label mag maar 1 keer opgenomen worden in het label-veld (vooraan de lijn). Het mag een willekeurig aantal keer gebruikt worden als bestemming. Het label heeft als numerische waarde het 16 bit adres van de instructie die er achter staat. Het label kan ook in berekeningen gebruikt worden als een constante met die waarde. AJMP is een instructie waarbij er slechts een 11 bit adres in de opcode wordt geplaatst. De hoogste 5 bit zullen dezelfde zijn als van het adres waar de sprong instructie staat. Door de beperking in het aantal adres bits, kan de sprong alleen uitgevoerd worden binnen eenzelfde blok van 2K.
4.8.3
Relatieve sprong instructies
De relatieve sprong plaatst in de opcode een 8 bit, two's complement offset, ten opzichte van de programcounter met het adres van de instructie die volgt op de relatieve sprong, en de plaats naar waar gesprongen moet worden. Dit heeft voor gevolg, dat er slechts maximaal 127 locaties voorwaarts, of 128 locaties terug gesprongen kan worden. Het sprongbereik is beperkt, maar door de instructies deskundig te kiezen geeft dit meestal geen probleem. De relatieve sprong instructies kunnen al of niet voorwaardelijk zijn. Bij de conditional jump (voorwaardelijke sprong), is het al of niet springen afhankelijk van de waarde van een (bit/byte) variabele. Bij de niet voorwaardelijke sprong is dit niet het geval, de sprong wordt steeds gemaakt. Als bij een voorwaardelijke sprong, de sprong niet gemaakt wordt, dan voert de CPU de instructie uit die vlak na de sprong instructie staat.
95
SJMP is een relatieve sprong instructie die onvoorwaardelijk wordt uitgevoerd. In de instructieset staat achter de mnemonic het woordje “rel”. Dat wordt vervangen door een label. De assembler berekend de two’s complement offset en vult die in in de opcode. Voorbeeld sjmp instructie lus:
sjmp
lus
4.8.4
Relatieve voorwaardelijke sprong instructies
Voorwaardelijke instructies voeren eerst een test of een bewerking uit. Het resultaat hiervan zal bepalen of de sprong al of niet wordt uitgevoerd. Wordt de sprong niet gemaakt, dan zal de instructie uitgevoerd worden die na de sprong instructie in het geheugen staat. Hier volgt een overzicht van deze instructies: JC JNC JB JNB JBC
plaats plaats address_bit, plaats address_bit, plaats address_bit,plaats
spring als de carry op 1 staat spring als de carry Niet op 1 staat spring als de bit op address_bit=1 spring als de bit op address_bit=0 spring als de bit op address_bit=1 en clear de bit
JZ JNZ
plaats plaats
spring als de inhoud van de accu=0 spring als de inhoud van de accu≠0
Er bestaat in de 8051 geen vlag die aangeeft of het resultaat van een berekening al of niet nul is. Bij andere controllers wordt hiervoor een zero vlag gebruikt (Z). Bij de 8051 test de instructie of de inhoud van de accu al of niet 0 is, en voert dan de sprong uit. CJNE is een instructie die toelaat om de accu of een Rn register te testen (compare), en afhankelijk van het resultaat een sprong uit te voeren (JNE=jump if not equal). Volgende combinaties zijn toegelaten: CJNE a,direct,plaats vergelijkt de inhoud van de accu met de inhoud van een direct adresseerbaar register. Indien ze niet gelijk zijn wordt de sprong gemaakt. CJNE a,#data,plaats vergelijkt de inhoud van de accu met een constant getal (#data). Indien ze niet gelijk zijn wordt de sprong gemaakt. CJNE Rn,#data,plaats vergelijkt de inhoud van Rn (n=0 t.e.m. 7) met een constant getal (#data). Indien ze niet gelijk zijn wordt de sprong gemaakt.
96
CJNE @Ri,#data,plaats vergelijkt de inhoud van het register waarvan het adres in Ri (i=0 of 1) staat met een constant getal (#data). Indien ze niet gelijk zijn wordt de sprong gemaakt. Extra mogelijkheid CNJE instructie De CJNE instructie zal de carry aanpassen. De waarde van de carry wordt bekomen door de compare uit te voeren als een verschilberekening. Stel: a=001h We voerende volgende instructie uit: cjne
a,#001h,lus
Dan zal de processor de compare uitvoeren als een verschilberekening en de carry een bijpassende waarde geven. In dit geval wordt dat a-001h=001h-001h=000h en c=0. Als de accu groter, of gelijk is aan de waarde waarmee we vergelijken, zal de carry=0 Stel: a=000h We voerende volgende instructie uit: cjne
a,#001h,lus
Dan zal de processor de compare uitvoeren als een verschilberekening en de carry een bijpassende waarde geven. In dit geval wordt dat a-001h=000h-001h=0ffh en c=1. Als de accu kleiner is dan de waarde waarmee we vergelijken, zal de carry=1 Volgend programma lijkt op het eerste zicht zinloos:
lus:
cjne ….
a,#getal,lus ….
;dit is de lijn die volgt op de cjne instructie
Door achter het label “lus” de carry vlag te testen, kan het programma testen of de waarde in de accu ≥ is dan getal, of < dan getal.
DJNZ is een instructie die eerst de variabele met een verminderd (Decrement), en indien het resultaat niet nul is springt naar een opgegeven bestemming (Jump if Not Zero). Volgende combinaties zijn toegelaten:
97
DJNZ Rn,plaats indien Rn ≠ 0 maak een sprong naar plaats
verminder Rn (n=0 t.e.m. 7) met 1, en
DJNZ direct,plaats verminder een direct adresseerbaar register met 1, en de inhoud ervan ≠ 0 maak een sprong naar plaats
Voorbeelden van spronginstructies
nog:
her: nog:
nog:
nog:
4.8.5
mov add ljmp
a,#0ffh a,#001h nog
;in accu ffh laden ;eentje bij optellen ;blijf dit steeds herhalen
mov add jnc ljmp
a,#000h a,#001h nog her
;in accu 00h laden ;eentje bij optellen ;het programma wordt nu 256 keer doorlopen ;na 256 optellingen wordt alles opnieuw opgestart
mov djnz
a,#001h a,nog
ljmp
start
;getal 01h in de accu zetten ;verminder de accu met 1, indien niet nul naar nog, ;anders volgende instructie ;terug naar label start
mov djnz
a,#000h a,nog
ljmp
start
mov cjne
a,#025h a,#025h,nog
;getal 0h in de accu zetten ;verminder de accu met 1, indien niet nul naar nog ;anders volgende instructie ;terug naar label start
;25h in de accumulator zetten ;als accu niet gelijk aan 25 naar nog, anders ;volgende instructie
De (L)CALL instructie
CALL instructies hebben tot doel om SUBROUTINES te kunnen oproepen. Een subroutine is een stuk programma dat we op verschillende plaatsen in het hoofdprogramma willen uitvoeren (figuur 4.8.4.1) (zonder de instructies telkens
98
opnieuw in te typen). Het is dus wel de bedoeling om terug te keren naar het adres van waar de subroutine opgeroepen werd. Het return adres wordt door de CPU op de stack geplaatst, en bij een RETURN (RET instructie) van de stack gehaald, en in de PC geplaatst.
LCALL
Return address 1
Subroutine
LCALL
RET
Return address 2
Figuur 4.8.4.1 Oproepen van subroutines Net als bij de JUMP kennen we de LONG en de ABSOLUTE CALL instructies. In de instructieset staan ze vermeld als de LCALL (Long CALL) met een bereik van 64kbyte, en ACALL (Absolute CALL) met een bereik van 2kbte. Meestal wordt altijd de LCALL instructie gebruikt. Relatieve CALL instructies kent de CPU niet. De CALL instructies zijn steeds onvoorwaardelijk. Er is een verschil tussen een HARDWARE CALL of SOFTWARE CALL (figuur 4.8.4.2).
De software CALL is een instructie die expliciet opgenomen wordt in het programma. Het aangeroepen programma is dan ook een subroutine.
99
Bij de hardware CALL wordt er geen instructie uitgevoerd, maar zal de CPU hardwarematig het programmaverloop aanpassen. Dit gebeurt enkel bij het opstarten van een interrupt routine. Je kan een interruptroutine zien als een subroutine die op een hardwarematige manier opgestart wordt. In elk geval, moet na het uitvoeren van de routine de controle terug overgedragen worden aan het hoofdprogramma (RETI instructie). Een hardware CALL noemen we verder in de tekst een INTERRUPT.
LCALL
HW 1
Return address 1
Interrupt routine
Subroutine
RETI
RET
Return address 2
HW
2
LCALL
Figuur 4.8.4.2 Software and hardware call
Bij alle CALL instructies wordt de stack gebruikt om het terugkeeradres te bewaren. De lengte van de stack wordt dus niet enkel bepaald door het aantal PUSH en POP instructies, maar ook door het aantal GENESTE CALLS EN HET AANTAL INTERRUPTS. Een subroutine of een interruptroutine moeten afgesloten worden met een instructie die het return adres van de stack haalt, en daar naar toe springt. Hiervoor worden de RETURN of RET instructies gebruikt. De RET instructie staat op het einde van
100
een gewone subroutine, de RETI (RETurn from Interrupt) instructie wordt gebruikt op het einde van een interruptroutine. De RETI instructie zal ingrijpen op de interrupt hardware van de CPU (zie 6.2). Interruptroutines worden door hardware opgestart, en het is dan ook onmogelijk om ten opzichte van het hoofdprogramma te weten wanneer ze uitgevoerd gaan worden. Je weet niet welke registers op dat ogenblik in gebruik zijn, en door de interruptroutine gebruikt mogen worden. Gelukkig beschikken we over de PUSH en POP instructies, om tijdelijk registers vrij te maken. Omdat de meeste bewerkingen op alle registers uitgevoerd kunnen worden, en omdat er een massa registers beschikbaar zijn, is het meestal slechts nodig om de accu, PSW en eventueel de DPTR op de stack te bewaren. Hierdoor kan het aantal bytes stack beperkt blijven. Dit is een van de grote verschillen tussen een gewone CPU en een controller core. Bij een gewone CPU zullen er minder registers zijn waardoor de stack heel groot moet kunnen worden (8086 = 64K), en vlot gebruikt kan worden. Helaas gebruiken hogere programmeertalen de stack om parameters door te geven tussen subroutine en hoofdprogramma. Dit is bij een controller uiterst moeilijk, omdat de stack een beperkte grootte heeft, en de access naar het extern geheugen moeilijk verloopt (stack is steeds inwendig in de controller).
4.8.6
De NOP instructie
De NOP instructie wordt door de CPU uit het geheugen gelezen, zodat ze een bepaalde tijd duurt. Bij de uitvoering van de instructie gebeurt er niets. De instructie lijkt dan ook overbodig. Er zijn echter bepaalde situaties, waar het handig is om over een instructie te beschikken die alleen CPU tijd in beslag neemt (opwekken van een korte puls, gebruik van de MDU, vertragingsroutines,...).
4.9
Uitvoeringstijd van een instructie
De uitvoeringstijd van een instructie is afhankelijk van volgende parameters: de snelheid van de systeemklok het aantal bytes waaruit de instructie bestaat de inwendige bouw van de CPU de snelheid waarmee het geheugen gelezen kan worden De eerste drie parameters maken dat er tabellen bestaan, die voor elke instructie vermelden hoeveel klokcycli ze in beslag nemen (appendix A, kolom cycles. Let op: 1 cycle is in dit geval 12 klokcycli (standaard 8051)).
101
Omdat processoren steeds sneller worden, en bepaalde types geheugen relatief trager zijn, worden verschillende systemen gebruikt om beide op elkaar af te stemmen. Meestal wordt de CPU kunstmatig trager gemaakt, door tijdens het lezen uit het geheugen klokpulsen te wachten (wait cycles). Dit is weinig efficiënt. Sommige fabrikanten gebruiken dan ook “accelerator” technieken. De meest gebruikte techniek is de bitbreedte van het geheugen verhogen (figuur 4.9.1). Op die manier worden er per leesbeurt meerdere bytes gelezen. Dit kan relatief traag gebeuren door hardware, die dan de gelezen info byte per byte snel doorgeeft aan de CPU. Hiervoor wordt een dubbele buffer gebruikt.
8 bit bus
8 bit CPU (8 bit databus) x ns/byte
Slow CPU each fetch for x ns/byte
8 bit memory 2x ns/byte
Memory accelerator
8 bit bus 16 bit 16 bit buffer buffer
16 bit memory 2x ns/16 bit
8 bit CPU (8 bit databus) x ns/byte
Read 2 bytes @ 2x ns/16 bits (one read from memory) Provide 2x 1byte @x ns/8bits (2 reads from CPU)
Figuur 4.9.1 Memory acceleration unit
102
Dit systeem werkt echter niet wanneer de benodigde info niet in de buffer zit. Dit is o.a. het geval bij spronginstructies, maar ook bij instructies waarvan de opcode niet in zijn geheel in de buffer beschikbaar is. Dit maakt dat de uitvoeringstijd van een programma niet meer deterministisch is, maar afhankelijk van de plaats in het geheugen (alineëring), het resultaat van tests bij spronginstructies enz. De XC888 gebruikt een accelerator voor zijn programmageheugen. In de ROM zitten twee subroutines die door de programmeur opgeroepen kunnen worden. Deze routines laten toe de accelerator in- (lcall dfffh) en uit- (lcall dffch) te schakelen. Bij het uitschakelen zal de uitvoeringstijd uit de kolom van 1 wait cycle van toepassing zijn (appendix B), in het andere geval de tijden uit de kolom par (parallel read, appendix B). Uitvoeringstijden zijn van belang bij tijdsvertragingen en interrupt routines. Interrupt routines moeten in de tijd beperkt blijven, en mogen soms niet langer duren dan een welbepaalde tijd. Gebruik in dat geval de “worst case” uitvoeringstijden uit de kolom par voor het berekenen van de uitvoeringstijd. Soms is er een “software delay” nodig om hardware de tijd te geven die nodig is om een event uit te voeren (een LED laten knipperen tegen enkele MHz is niet zinvol, door een tijdsvertraging wordt het knipperen langzamer en kan je het waarnemen). De wachttijden zijn meestal niet erg kritisch. Reken met de “best case” tijden om een idee te hebben van de tijdsvertraging (kan wel langer duren). Zijn exacte tijdsafpassingen nodig, dan zal je hardware timers moeten gebruiken.
103
Hoofdstuk 5
5.1
Toepassingen
Inleiding
Een microcontroller kan gebruikt worden voor tal van toepassingen. Helaas bestaat er maar weinig kant en klare software die gebruikt kan worden. Zowel voor het aansturen van de hardware, als voor berekeningen is de programmeur aangewezen op zichzelf. Je kan op het WWW voorbeeldprogramma’s vinden, maar hoe betrouwbaar deze software is, blijft de vraag. Dikwijls zijn application note’s van fabrikanten net niet die oplossing die gezocht wordt. In dit hoofdstuk wordt meer uitleg gegeven over een aantal manieren om courante problemen op te lossen.
5.2 5.2.1
I/O driver xcez1.inc Inleiding
Het XC888 bord dat gebruikt wordt in het laboratorium werd speciaal voor educatieve doeleinden ontworpen. Om de student in staat te stellen snel toepassingen te kunnen schrijven, is er een “driver” voorzien (xcez1.inc (dit was de beschikbare versie bij de eerste uitgave van de cursus. De algemene naam is xcezx.inc, waarbij de x het versie nummer is. Dit zal natuurlijk veranderen naarmate de driver aangepast wordt)).
Die maakt de software interfacing van de standaard I/O op het bord eenvoudig. De student kan met een minimale kennis van de (complexe) hardware snel tot resultaten komen. Een programma heeft maar zin als er gegevens met de omgeving uitgewisseld kunnen worden. In de labo_opstelling (XC888 SBC bord) zijn er volgende I/O mogelijkheden voorzien (LET OP! niet alle mogelijkheden zullen op alle kaarten zullen bestukt zijn. Raadpleeg uw docent):
8 schakelaars op poort 4 4 schakelaars op p2.0 – p2.3 (functietoetsen onder LCD scherm) 8 LED’s op poort 3 (uitschakelbaar via jumper 9) een potentiometer op p2.4 (via jumper 7) een temperatuursensor (LM335 (10mV/Kelvin)) op p2.5 (via jumper 8) een LCD scherm van 4 lijnen 20 karakters met achtergrond verlichting een uitgang voor een zoemer (buzzer) tal van communicatiemogelijkheden (IIC, LIN, CAN, RS232, RS485, USB) bijkomende I/O aansluitingen voor ADC, PWM en timers
De I/O kan niet zomaar gebruikt worden. Minimaal is er een initialisatie nodig. De initialisatie bestaat uit een aantal instructies die instellen hoe de I/O gebruikt gaat worden. Zo kan een poort als input, maar ook als output gebruikt worden. Zijn de 104
schakelaars aangesloten, dan zal de input mode geselecteerd worden. Bij de LED’s is dat de output mode. Voor sommige vormen van I/O (vb. LCD scherm) verloopt de aansturing vrij complex. Er is niet alleen de initialisatie van de I/O, maar de gegevens die het LCD scherm nodig heeft komen niet onmiddellijk overeen met een intuïtief gebruik. Bij het LCD scherm word bovendien een serieel naar parallel omvormer gebruikt om het aantal I/O pinnen die nodig zijn, voor de aansturing ervan, te beperken. Een driver is een verzameling van subroutines die door de gebruiker aangeroepen kunnen worden, en die de aansturing van de I/O vereenvoudigen. Voor het XC888 bord bestaat er een driver die de courante I/O kan aansturen (andere specifieke drivers voor bepaalde complexe functies zijn op de FTP-site beschikbaar). Een driver moet mee opgenomen worden in de source code van het programma. Dat doe je door de driver onderaan het programma mee te includen: $include(c:\xcez1.inc)
De *.inc bestanden zijn geschreven in assembler. Je kan ze openen met de IDE (maar ook elke andere editor/tekstverwerker (kladblok)). De commentaar in de code geeft toelichtingen hoe je de subroutines moet gebruiken. Bovenaan het bestand is een algemene beschrijving van de mogelijkheden opgenomen. Voor specifieke informatie moet je de header boven de effectieve subroutine lezen. Voor de I/O functies is er een init**** subroutine voorzien. Zo zal de “initlcd” subroutine de LCD klaar zetten voor gebruik. Dan pas kan je effectief informatie met de I/O module uitwisselen. Belangrijk is ook om na te gaan hoe parameters doorgegeven kunnen worden tussen de driverroutines en jou programma.
5.2.2
LED’s en dipswitch
De LED’s zijn aangesloten op poort 3. In de figuur 5.2.2.1 staan de registers afgebeeld die gebruikt worden voor het aansturen van de poorten. Per poort zijn er meerdere registers (tot 7). Nochtans zijn hiervoor per poort maar 2 adressen beschikbaar. De selectie van het te gebruiken register verloopt via het selecteren van een page (pagina). Dat gebeurt door een schrijfoperatie van het port_page register. Na reset staat dit op 000h, waardoor de registers van page 0 onmiddellijk beschikbaar zijn.
105
.
Port_page = SFR B2H
106
Figuur 5.2.2.1 SFR’s poorten Het Px_data register wordt gebruikt om de gegevens te lezen van de poortpinnen, als de poort als input is geïnitialiseerd (default). Wordt de poort als output gebruikt, dan wordt dit register geschreven om de uitgangen aan te sturen (figuur 5.2.2.2).
107
Figuur 5.2.2.2 Px_data register De poort als input of als output schakelen gebeurt via het Px_dir register (figuur 5.2.2.3).
Figuur 5.2.2.3 Px_dir register
108
Principieel zou volgende code moeten volstaan om de LED’s aan te sturen en de schakelaars in te lezen: Voorbeeld initialisatie LED’s en schakelaars (onvolledig) mov mov
p4_dir,#000h p3_dir,#0ffh
;poort 4 als input schakelen ;poort 3 als output schakelen
; als voorbeeld lezen we de schakelaars, en schrijven de data naar de LED’s mov
p3_data,p4_data
Dit programma vertoont een aantal onvolkomenheden: de initialisatie van poort 4 als input is overbodig. De controller schakelt na reset alle poorten als input poort 4 wordt met de massa verbonden als de schakelaars sluiten (figuur 5.2.2.4), anders is de poort zwevend (geen gekend niveau) port 3 stuurt de LED’s met een actief laag niveau (een 0 geeft licht, een 1 niet) (figuur 5.2.2.5) Willen we de schakelaars correct inlezen, moeten de pinnen van poort 4 op logisch niveau 1 komen wanneer de schakelaars niet gesloten zijn. Dit kan bekomen worden door de inwendige pull_up weerstanden in te schakelen. Dat kan via de registers Px_puden en Px_pudsel (figuur 5.2.2.6 en figuur 5.2.2.7)
Figuur 5.2.2.4 Schakelaars poort 4
109
Figuur 5.2.2.5 Poort 3 en de LED’s
Figuur 5.2.2.6 Px_puden
110
Figuur 5.2.2.7 Px_pudsel
Voorbeeld initialisatie LED’s en schakelaars (volledig) mov mov mov mov
port_page,#001h p4_pudsel,#0ffh p4_puden,#0ffh port_page,#000h
;selecteer poort page 1 ;selecteer pull_up device ;selectie inschakelen ;selectie poort pagina 0
mov mov
p4_dir,#000h p3_dir,#0ffh
;poort 4 als input schakelen ;poort 3 als output schakelen
; De initialisatie is klaar. We kunnen de poorten gebruiken zoals het hoort ; als voorbeeld lezen we de schakelaars, en schrijven de data naar de LED’s mov
p3_data,p4_data
Testprogramma kopieer schakelaars naar de LED’s
org
00000h
mov mov mov mov
port_page,#001h p4_pudsel,#0ffh p4_puden,#0ffh port_page,#000h
;selecteer poort page 1 ;selecteer pull_up device ;selectie inschakelen ;selectie poort pagina 0
mov mov
p4_dir,#000h p3_dir,#0ffh
;poort 4 als input schakelen ;poort 3 als output schakelen
; De initialisatie is klaar. We kunnen de poorten gebruiken zoals het hoort ; als voorbeeld lezen we de schakelaars, en schrijven de data naar de LED’s
111
lus:
mov ljmp
p3_data,p4_data lus
;blijf dit herhalen
Je kan ook gebruik maken van de bit-instructies om de schakelaars af te vragen. Zowel poort 4 (dip switch) als poort 2 (functie schakelaars) zijn bit adresseerbaar. Testprogramma met xcez1.inc driver
lus:
#include
org
00000h
;start adres programma
mov lcall lcall mov ljmp
sp,#07fh initdipswitch initleds p3_data,p4_data lus
;stack initialiseren ;zet poort 4 klaarvoor gebruik ;zet poort 3 klaar voor gebruik ;kopieer schakelaars nar LED’s ;blijf herhalen
“c:\xcez1.inc”
De voordelen van het gebruik van de driver is dat het programma compacter en leesbaarder wordt. De routines uit de driver beïnvloeden ook geen registers, en zorgen er voor dat de initialisatie perfect verloopt ongeacht de waarde in de port_page en syscon0 registers. Die worden na het uitvoeren van de routines opnieuw in hun originele waarde hersteld. Je kan de driver openen in de IDE en de code bekijken. Het is aangeraden een eigen programma zo veel mogelijk op te bouwen uit functies (subroutines). Hierdoor neemt de leesbaarheid toe, en kan je code ook in andere toepassingen hergebruiken. Hierbij is de documentatie van de routines uiterst belangrijk (zie xcez1.inc driver). De registers p3_data en p4_data zijn bit adresseerbaar. Je kan de schakelaars en de LED’s dan ook individueel opvragen en beïnvloeden.
5.2.3
Functie toetsen
De functietoetsen bevinden zich onder het LCD scherm (figuur 5.2.3.1). De elektrische aansluitingen zijn opgenomen in figuur 5.2.3.2.
112
Figuur 5.2.3.1 Functieschakelaars S2, S3, S4 en S5
Figuur 5.2.3.2 Schema functietoetsen Voor de initialisatie van deze 4 klemmen is er in de xcez1.inc driver de functie “initftoetsen”. Het registers p2_data is bit adresseerbaar. Je kan de schakelaars dan ook individueel opvragen (De 4 hoogste bits bevatten een onbekend resultaat!). De schakelaars staan onder het LCD scherm, zodat de onderste display lijn gebruikt kan worden om de betekenis van de schakelaars weer te geven.
113
5.2.3.1
Van schakelaar tot keyboard, algemeenheden…
Een groot aantal embedded producten (bijna allemaal) hebben een keyboard interface als input voor de gebruiker of moeten een aantal schakelaars lezen. Een keyboard kan zowel gebruikt worden voor het ingeven van numerische data als voor het selecteren van een werkingsmode van het toestel. Er bestaan voor het lezen van keyboards kant en klare oplossingen in chipvorm, maar een software aanpak voor keyboardscanning of het lezen van schakelaars heeft het voordeel dat de kostprijs van het product kleiner wordt en dit in ruil voor een kleine CPU overhead. De meeste keyboards bestaan uit een matrix van aansluitdraden waar op elk kruispunt een drukschakelaar is mee verbonden. De controller kan dan zeer eenvoudig bepalen welke schakelaar er is ingedrukt. 5.2.3.2
De keyboard hardware basics.
Bij een keyboard wordt meestal een drukschakelaar gebruikt, waarvan het sluiten eenvoudig kan worden gedetecteerd door volgend circuit. De pull-up weerstand zorgt ervoor dat er een logisch '1' signaal aan de uitgang staat wanneer de schakelaar open staat, en een logische '0' wanneer de schakelaar is gesloten. De pull-up weerstand zit bij de 8051 familie principieel reeds intern in de pindriver, maar meestal plaatst men extern toch nog een laagohmige pull-up weerstand (enkele KΩ) uit betrouwbaarheidsoverwegingen. +5V
Schakelaar open Naar de controlleringang
Contactdender
Schakelaar open
+5V
GND
Schakelaar dicht Schakelaar ingedrukt
t
Figuur 5.2.3.2.1 Schakelaar met bounce pull-up weerstand en contactdender
Helaas zijn schakelaars niet ideaal en genereren ze geen gedefinieerde '1' of '0' wanneer ze worden ingedrukt of losgelaten. Alhoewel een contact snel en stevig lijkt te sluiten, is dit voor de snelle controller een zeer trage gebeurtenis. Wanneer het contact wordt gesloten 'botst' het nog enkele keren als een bal. Dit botsen noemt men contactdender of bounce. De contactdender zorgt voor meerdere pulsen op de controller-ingang en deze dender duurt typisch tussen de 5 en 30ms (zie fig. 5.2.3.2.1) Wanneer meerdere schakelaars nodig zijn in een systeem kan men elke schakelaar op deze manier verbinden met een poortpin van de controller.
114
Voor een groot aantal schakelaars zijn er al snel niet genoeg poortpinnen beschikbaar. De meest efficiënte manier om schakelaars te verbinden met een controller is in de vorm van een tweedimensionale matrix zoals aangegeven in de figuur 5.2.3.2.2 . Dit levert winst op van poortpinnen vanaf 6 schakelaars. Scanlijnen (rijen)
Keyboard matrix
"0" 1 1 POORT X
1 1
AT89S8252 1 1 1
Leeslijnen (kolommen)
Figuur 5.2.3.2.2 Schakelaars verbonden in matrixvorm.
Het aantal schakelaars nodig in de matrix is natuurlijk afhankelijk van de toepassing. Elke rij wordt aangestuurd door een poortbit (output), terwijl elke kolom wordt omhoog getrokken door de (interne) pull-up van de poortpin (input), waarmee hij is verbonden. Uiteraard kan ook met elke kolom extern nog een extra pull-up weerstand worden verbonden. Keyboard scanning is het proces waarbij de controller op regelmatige tijdstippen naar de keyboard matrix kijkt om te zien of er een toets is gedrukt. Als er een toets werd ingedrukt is het aan de keyboard scanningsoftware om de dender weg te filteren en te bepalen welke toets er werd ingedrukt. Uiteraard hoeft het niet altijd te gaan om een keyboard dat op deze manier wordt verbonden, wanneer in een systeem een groot aantal contacten moet worden gelezen zoals bv. einderit schakelaars, contacten van relais, naderingsschakelaars enz. , kan dit ook op deze manier. 5.2.3.3
Een matrix (keyboard) scanning algoritme.
Een matrix kan op verschillende manieren worden gelezen, een eenvoudig algoritme wordt hier aangegeven.
115
In de vertrektoestand worden alle rijen (outputpinnen) op een logische '0' gezet. Wanneer er geen toets is gedrukt zullen alle kolommen (inputpinnen) als logisch '1' gelezen worden. Het indrukken van een toets (sluiten van een contact) heeft als gevolg dat één van de kolommen laag gemaakt wordt. Om te zien of een toets is ingedrukt moet de controller dus alleen kijken of er een inputpin (kolom) laag staat. Wanneer de controller gedetecteerd heeft dat er een toets is ingedrukt, moet hij nog bepalen welke toets het is. De controller stuurt een '0' op slechts één van de rijpinnen (outputpinnen). Als er een '0' wordt teruggevonden op de ingangspinnen (kolommen) dan weet de controller dat de ingedrukte toets zich bevindt op de met een '0' aangestuurde rij. Als alle ingangen hoog blijven bevindt de ingedrukte toets zich niet op de geselecteerde rij en de controller maakt dan de volgende rij logisch '0' en het proces herhaalt zich. Wanneer de rij is bepaald kan de controller nagaan welke van de kolommen (inputpinnen) er '0' is, dit geeft de kolom aan van de ingedrukte toets. De tijd die de controller nodig heeft om deze handelingen uit te voeren is zeer klein (enkele μs) vergeleken met de minimale tijd dat een schakelaar wordt ingedrukt (enkele honderden ms), er wordt dan ook verondersteld dat de schakelaar gesloten blijft tijdens dit proces. Om de contactdender weg te filteren zal de controller het keyboard scannen met een bepaald tijdsinterval, typisch tussen de 20 ms en de 100ms, wat de debounce periode wordt genoemd. Een toets is pas echt ingedrukt wanneer hij tweemaal na elkaar ingedrukt wordt terug gevonden, anders kan het gaan om een stoorsignaal of om de contactdender. Soms is het nodig te reageren op een toets die wordt ingedrukt of die wordt losgelaten, en niet op een ingedrukt gehouden schakelaar. Een dalende flank van een ingedrukte toets heeft men na opeenvolgend een niet ingedrukte en twee ingedrukte toestanden, en een stijgende flank van een losgelaten toets heeft men na opeenvolgend een ingedrukte en twee niet ingedrukte toestanden. De rij- en kolominformatie van de toets kan dan worden omgezet naar een scancode die dan in een buffer kan worden geplaatst. Buffering is handig omdat het voorkomt dat er toetsinfo verloren gaat wanneer de toepassing niet direct de toetsinfo kan verwerken. De buffergrootte is weer afhankelijk van de toepassing. Een 'keyflag' kan aangeven aan de toepassing dat er nog toetsinfo in de buffer aanwezig is. Het is ook mogelijk dat er geen “buffer” gebruikt wordt, en het scannen van het klavier gebeurt door een opgeroepen subroutine, die een code doorgeeft, in functie van het al of niet ingedrukt zijn van een toets (vb: ffh, niets ingedrukt en de codes 00h-18h voor een ingedrukte schakelaar).
116
Het scannen met een bepaald tijdsinterval kan worden opgestart door een periodische timerinterrupt die meestal in elk systeem wel aanwezig is als systeem'time-tick'. Het aansturen van een matrix van schakelaars kan een hardware probleem geven bij controllers die niet beschikken over een open-drain pindriver. Indien er immers twee toetsen worden ingedrukt op dezelfde kolom zal een rijpin die '0' is kunnen verbonden worden met een rijpin die op '1' gestuurd is. Dit geeft bij de 8051 familie geen probleem, maar bij een aantal controllers met een actieve (push-pull) uitgangstrap zal dit het defect van een pin veroorzaken. Dit probleem kan opgevangen worden door ofwel Schottkey diodes te gebruiken (laten alleen toe dat er stroom in de poort vloeit), of door de poort in de input mode te plaatsen voor een uitgang 1 (de pull-up weerstand zorgt voor het hoge signaal) en als output=0 voor het uitsturen van een “0”.
117
5.2.4
LCD scherm
Omdat die minimale (LED’s en schakelaars) I/O soms onvoldoende is, gebruiken we een LCD scherm voor bijkomende output. Het scherm bestaat uit 4 lijnen van elk 20 karakters (figuur 5.2.4.1).
Figuur 5.2.4.1 LCD scherm De figuur 5.2.4.2 laat zien dat elke display locatie een uniek adres heeft in het DDRAM geheugen van het display.
Figuur 5.2.4.2 DDRAM adressen van de scherm posities De gebruiker kan aan de hand van deze adressen bepalen waar hij op het scherm een boodschap wil laten zien. De boodschap zelf bestaat uit symbolen die aangeduid worden via een ASCII code. Die omvatten zowel symbolen als controle karakters (figuur 5.2.4.3)
118
Figuur 5.2.4.3 Standaard karakterset LCD display . De letter “A” wordt weergegeven door de code 0100 0001 ofwel 41h. De codes 0000xxxx kunnen gebruikt worden voor zelf aangemaakte karakters. De codes 0001xxxx zijn niet geïmplementeerd, en de codes >7fh worden door het beeldscherm als Japanse karakters weergegeven. Voor ons zijn die niet bruikbaar en beschouwen we als niet geïmplementeerd. De driver zal deze getalwaarden gebruiken om de cursor op een adres van het scherm te plaatsen.
119
Om het aantal aansluitklemmen van de controller, voor het aansturen van het scherm te beperken, wordt het display aangestuurd via een port expander op een IIC (I 2C) bus. De aansturing verloopt dan met slechts 2 klemmen van de controller (figuur 5.2.4.4), SDA en SCL.
Figuur 5.2.4.4 Schema aansturing LCD scherm Je kan in het schema ook zien dat de portexpander (PCF8574) op klem P6 de achtergrondverlichting van het display aanstuurt, en via klem P7 een extra I/O klem op JP6 (onderaan het XC888 SBC bord). De xcez1.inc driver laat toe om het LCD scherm te gebruiken zonder dat je op de hoogte moet zijn van de exacte werking ervan. Zelfs de manier waarop het aangesloten is op de controller is niet van belang. Voor je een driver mag gebruiken MOET je hem openen en lezen hoe je de subroutines mag/kan gebruiken.
120
Voorbeelden gebruik LCD display, backlight en buzzer: org
0000h
;start adres programma
mov lcall
sp,#07fh initlcd
;stackpointer klaar zetten ;zet I2C verbinding en zorgt ;voor initialisatie LCD scherm
Na de initialisatie zie je op het scherm een knipperende cursor. Wanneer je nu naar het scherm ASCII codes stuurt (1fh
a,#041h lcdoutchar
;zet in de accu de ASCII code van “A” ;drukt op het scherm het symbool “A”
Na het afdrukken van het symbool zal de cursor automatisch 1 positie naar rechts opschuiven. Je hoeft de ASCII codes niet uit het hoofd te kennen. De assembler aanvaardt volgende notatie: mov
a,#’A’
;zet in de accu de ASCII code van het ;symbool ‘A’
Let wel op: de accu is maar 8 bit groot. Je kan er slechts de ASCII code van 1 symbool in kwijt. Het woord “zoon” afdrukken moet in 4 keer gebeuren: mov lcall mov lcall lcall
a,#’z’ lcdoutchar a,#’o’ lcdoutchar lcdoutchar
mov lcall
a,#’n’ lcdoutchar
;zet in accu ASCII code “z” ;druk af ;zet in accu ASCII code “o” ;druk af ;druk af (lcdoutchar past de accu niet ;aan, je hoeft de “o” niet nog eens in ;de accu te plaatsen). ;zet in accu ASCII code “n” ;druk af
De routine lcdoutchar stuurt ook bevelen naar het LCD scherm: 00dh =cursor in de home positie 00ch =form feed =scherm leeg maken en cursor op locatie 000h 00ah =cursor op tweede lijn plaatsen 001h =cursor on blink 002h =cursor on, no blink 003h =cursor off 080h-0ffh =cursor plaatsen op adres (waarde in accu-80h) Andere codes zijn ongeldig! Stel dat je de cursor op de vierde plaats van de derde lijn wil positioneren, dan zoek je eerst het adres van die locatie in figuur 5.2.4.5
121
Figuur 5.2.4.5 Adressen posities op LCD scherm In dit geval is dat adres 17h. Volgens de driver moet je hierbij 80h optellen =97h (de 80h(msb=1) wordt er bij geteld om waarden te bekomen buiten de standaard ASCII codes). mov lcall
a,#097h lcdoutchar
;zet bewerkte adres in de accu ;zal de cursor naar locatie ;97h-80h=17h verplaatsen
Voor het afdrukken van een getal zijn er drie routines: lcdoutnib: drukt de 4 laagste bits van de accu af als een getal tussen 0 en F lcdoutbyte: drukt de 8 bits van de accu af als een getal tussen 00 en FF lcddispdptr: drukt de 16 bits in de DPTR af als een getal tussen 0000 en FFFF mov lcall lcall mov lcall
a,#01ch lcdoutnib lcdoutbyte dptr,#1234h lcddispdptr
;zet in de accu het getal 1ch ;drukt op het scherm C af ;drukt op het scherm 1C af ;drukt op het scherm 1234 af
Voor het afdrukken van grotere getallen kunnen combinaties van vorige routines gebruikt worden. Voor het afdrukken van een tekst string wordt de routine lcdoutmsga gebruikt. Deze routine krijgt als parameter het startadres van de string mee, via de dptr. We drukken nu opnieuw het woord zoon af: mov lcall
dptr,#boodschap lcdoutmsga
;laden in de dptr adres “boodschap” ;afdrukken maar
De boodschap zelf staat achteraan het programma. De controller kan het verschil niet zien tussen uitvoerbare code en constanten. We moeten er daarom voor zorgen dat de boodschap buiten het normale programmaverloop staat. De meest aangewezen plaats hiervoor is achter de subroutines. boodschap: db
“zoon”,000h
Dit is de syntax waarmee een string van constanten in het geheugen geplaatst wordt:
122
boodschap: het label vooraan de lijn, wordt door de assembler gelijk gesteld aan het adres waar het eerste karakter van de string in het geheugen staat (net zoals een label bij een subroutine). db directief dat tegen de assembler zegt dat al wat er volgt geen instructies zijn, maar constanten “zoon” alles wat tussen “ ” staat wordt vervangen door de bijhorende ASCII code. Per karakter wordt er 1 geheugenplaats gebruikt ,000h ;de constante 000h wordt in de laatste geheugenplaats opgeslagen. De driver weet dat deze “NULL” ASCII code het einde van de string betekend.
Omdat de string enkel uit ASCII codes bestaat, mogen ook de codes die bevelen vormen er tussen geplaatst worden. test:
db
003h,080h,”jantje”,00ah,”Piet”,094h,”Suzy”,000h
Bovenstaande string zal: 003h cursor uitschakelen 080h cursor eerste karakter eerste lijn plaatsen (80h-80h=00h) “jantje” jantje afdrukken 00ah cursor op eerste karakter van de tweede lijn plaatsen (0ah=cr) “Piet” Piet afdrukken 094h cursor op eerste karakter van derde lijn plaatsen (94h-80h=14h) “Suzy” Suzy afdrukken 000h einde van de string, stoppen met afdrukken en terugkeren naar aanroepende programma In/uit schakelen backlight/buzzer Het in- en uitschakelen van de achtergrondverlichting gebeurt met volgende subroutines: lcdlighton backlight aan lcdlightoff backlight uit lcdbuzon buzzer aan lcdbuzof buzzer uit Je kan deze routines maar oproepen nadat de initlcd routine is uitgevoerd! Alle bovenstaande subroutines zijn maar toegankelijk wanneer je onderaan het programma de driver mee opneemt: #include “c:\xcez1.inc”
;driver mee opnemen in programma
Wanneer het LCD scherm gebruikt wordt, is automatisch de I2C bus mee ingesteld.
123
5.2.5
I2C bus
Om de XC888 SBC zo veel mogelijk periferie te geven, en zo weinig mogelijk pinnen van de controller op te geven, werd geopteerd om 2 klemmen van de XC888 te gebruiken als een I2C (IIC) bus. P0.7 wordt gebruikt als SDA (serial data) en p0.3 als SCL (serial clock). De controller heeft geen hardware ondersteuning voor een IIC bus en het IIC protocol, zodat alle communicatie via software emulatie moet gebeuren. In figuur 5.2.5.1 Kan je zien hoe verschillende componenten op de bus worden aangesloten.
Figuur 5.2.5.1 IIC of I2C bus Figuur 5.2.5.2 laat de elektrische interface zien. De bus is een wired-or (ook wel open collector genoemd) systeem. Weerstanden zorgen voor het hoog niveau, de componenten maken de bus actief laag met een schakelelement.
Figuur 5.2.5.2 Elektrische aansluitingen IIC bus Het communicatie protocol gaat er van uit dat de bus in rust op 1 staat. Er wordt door de master een startconditie op de bus geplaatst bij de aanvang van een dataoverdracht.
124
Na de startconditie moet het 7 bit adres van de slave worden verstuurd, samen met een bit die aangeeft of er gelezen/geschreven wordt van/naar de slave. De slave zal antwoorden met een negende ACK bit als het adres ontvangen werd. Daarna is de communicatie afhankelijk van de R/W bit. In elk geval wordt er na elke byte dataoverdracht door de ontvangen een negende ACK of NACK bit verzonden. De klok wordt altijd door de master opgewekt. De communicatie wordt afgesloten met een stopconditie. Vooraleer je met een IC kan communiceren, moet je in de betrokken datasheet nagaan hoe het IC data verwacht, en/of doorstuurt. In figuur 5.2.5.3 Is dat weergegeven voor de LM75 (of DS75) temperatuursensor.
Figuur 5.2.5.3 IIC protocol LM75 De xcez0.inc driver bevat de elementaire routines voor het aansturen van de IIC bus: initiic klaar zetten iic interface iicstart genereren van een startconditie op iic poort iicstop genereren stop conditie op iic poort iicinbyteack lezen van 1 byte met verzenden ack (accu=output) iicinbytenack lezen van 1 byte met verzenden nack (accu=output) iicoutbyte schrijven van 1 byte (accu=input, c=output=waarde ack bit slave) Alle bovenstaande subroutines zijn maar toegankelijk wanneer je onderaan het programma de driver mee opneemt: #include
“c:\xcez1.inc”
;driver mee opnemen in programma
Voorbeeldprogramma 1 uitlezen LM75 ; Dit programma leest een LM75 uit over de IIC bus op het XC888 SBC. ; De 8 MSB bit resultaat worden weergegeven op de LED's ; De LM75 heeft volgend adres: 1001001x org
00000h
;start adres programma
mov lcall lcall
sp,#07fh initiic initleds
;klaar zetten van de stack ;iic interface klaar zetten ;LED’s klaar zetten voor gebruik
125
lus:
lcall mov lcall
iicstart a,#10010011b iicoutbyte
;start conditie op de iic bus ;adres in accu, 8ste bit geeft aan lezen ;wordt naar buiten gestuurd
; De routine geeft de ACK/NACK van de LM75 door in de carry. Als we dat willen ; kunnen we nagaan of de carry wel op 0 staat. Alleen dan is het IC aanwezig. jc
fout
lcall cpl
iicinbyteack a
mov lcall
p3_data,a iicinbytenack
lcall
iicstop
;lees een byte binnen en antwoord met ack ;de LED's geven licht bij een 0. Door te ;complementeren zien we licht voor 1 ;de gelezen waarde wordt op de LED'gezet ;volgende byte lezen en NACK sturen ;moet volgens het protocol van de LM75 ;gelezen data wordt niet verwerkt ;sluit communicatie af
; De sensor mag maar alle 0.1s uitgelezen worden. Daarom voeren we een wachtlus uit. ; Die is ook beschikbaar in de xcez driver
fout:
mov lcall ljmp
a,#10 delaya0k05s lus
;duurt accu*0,05s (nu 10*0,05s=0,5s) ;blijf herhalen
ljmp
fout
;sensor is er niet, dus geen verdere actie
#include
”c:\xcez1.inc”
Voorbeeldprogramma 2 uitlezen LM75 ; Dit programma leest een LM75 uit ove de IIC bus op het XC888 SBC. ; De 16 bit resultaat worden weergegeven op de LCD ; De LM75 heeft volgend adres: 1001001x
lus:
org
00000h
;start adres programma
mov lcall mov lcall lcall
sp,#07fh initlcd a,#003h lcdoutchar lcdlighton
;klaar zetten van de stack ;LCD klaar zetten voor gebruik (dus ook IIC interface) ;cursor uitschakelen (code 003h) ;naar LCD sturen ;achtergrond verlichting inschakelen
mov lcall lcall mov lcall
a,#080h lcdoutchar iicstart a,#10010011b iicoutbyte
;cursor op eerste plaats eerste lijn zetten ;naar scherm versturen ;start conditie op de iic bus ;adres in accu, 8ste bit geeft aan lezen ;wordt naar buiten gestuurd
; De routine geeft de ACK/NACK van de LM75 door in de carry. Als we dat willen ; kunnen we nagaan of de carry wel op 0 staat. Alleen dan is het IC aanwezig. jc
fout
126
lcall mov lcall push lcall
iicinbyteack b,a iicinbytenack acc iicstop
;lees een byte binnen en antwoord met ack ;even opslaan ;volgende byte lezen en NACK sturen ;even opslaan ;sluit communicatie af
; De beide uitgelezen waarden kunnen pas naar ht LCD scherm verzonden worden als de ; communicatie met de LM75 afgerond is. Het LCD scherm wordt immers ook via de IIC bus ; aangesproken, waardoor de communicatie met de LM75 onderbroken zou worden. ; De IIC bus is dus nu terug vrij, waardoor we de LCD kunnen aanspreken. mov lcall pop lcall
a,b lcdoutbyte acc lcdoutbyte
;eerste opgeslagen byte afdrukken ;weg er mee ;tweede opgeslagen byte afdrukken
; De sensor mag maar alle 0.1s uitgelezen worden. Daarom voeren we een wachtlus uit. ; Die is ook beschikbaar in de xcez driver
fout:
mov lcall ljmp
a,#10 delaya0k05s lus
mov lcall ljmp
dptr,#tekst lcdoutmsga lus
;blijf herhalen ;laten op LCD zien dat er iets mis is ;opnieuw proberen
tekst: db
080h,’er is iets mis met’,00ah,’ de lm75’,000h
#include
”c:\xcez1.inc”
5.2.6
ADC
De Analog to Digital Converter (ADC) is een onderdeel van de controller dat gebruikt wordt om analoge ingangssignalen te meten. De ADC heeft een resolutie van 10 bit (1024 schaaldelen). De meting gebeurt tussen 0V en een referentiespanning. Het XC888 SBC bord laat toe om die referentie te kiezen (figuur 5.2.6.1). De digitale waarde wordt bekomen door volgende formules toe te passen:
De maximale uitgangscode is 3ffh, en wordt bereikt bij een ingangsspanning van Vref-1LSB. 1LSB is de waarde waarmee de ingangsspanning moet wijzigen alvorens de digitale uitlezing met 1LSB zal veranderen.
127
Voor een Vref van 5v is 1LSB=0,0048828125V. Omdat de potentiometer (figuur 5.2.6.2) aangesloten is tussen 0 en 5V, gebruiken we de 5V als referentie (JP10 in positie 2-3).
Figuur 5.2.6.1 Instellen van de ADC referentie
Figuur 5.2.6.2 Potentiometer en temperatuursensor op XC888 SBC De XC888 ADC is vrij complex in gebruik. Wanneer je de driver gebruikt hoef je daar niets vanaf te weten. Volgende routines zijn beschikbaar: initadc klaar zetten ADC voor minimaal gebruik adclm335 uitlezen lm35 (a-b) bevat resultaat adcpotmeter uitlezen van de potmeter (a-b) bevat resultaat Initadc zet de ADC klaar voor gebruik in een vereenvoudigde mode, die enkel het bemonsteren van de pinnen p2.4, p2.5, p2.6 en p2.7 mogelijk maakt. De routines adclm335 en adcpotmeter meten de spanning die door beide elementen aan de controller worden aangelegd. In figuur 5.2.6.3 staat weergegeven welke informatie in accu en b register beschikbaar zijn. 128
De accu bevat de 8 hoogste bits van de omvorming. Het b register bevat volgende info:
Figuur 5.2.6.3 Inhoud accu en b register na xcez meetroutines
129
Voorbeeldprogramma inlezen potentiometer
lus:
org
00000h
;start adres programma
mov lcall mov lcall lcall lcall
sp,#07fh initlcd a,#003h lcdoutchar lcdlighton initadc
;klaar zetten van de stack ;LCD klaar zetten voor gebruik (ook IIC interface) ;cursor uitschakelen (code 003h) ;naar LCD sturen ;achtergrondverlichting aan ;adc klaar zetten voor gebruik
mov lcall lcall lcall mov lcall mov lcall lcall
a,#080h lcdoutchar adcpotmeter lcdoutbyte a,b lcdoutbyte a,#00ah lcdoutchar adcpotmeter
;cursor op eerste plaats eerste lijn zetten ;naar scherm versturen ;meet spanning potmeter ;druk 8 hoogste bits af ;laagste bits in accu ;afdrukken ;tweede lijn scherm nemen ;opnieuw meting uitvoeren
; we gaan de 10 bit uitkomst afzonderen en rechts afgelijnd afdrukken ; Ga op papier na wat hier allemaal gebeurt!!!! push anl rl rl lcall pop anl anl add rl rl lcall ljmp
acc a,#11000000b a a lcdoutbyte acc a,#00111111b b,#11000000b a,b a a lcdoutbyte lus
#include
“c:\xcez1.inc”
5.2.7
SIO
;straks nog nodig ;hoogste 2 bits bewaren ;hoogste bits worden laagste ;afdrukken ;accu herstellen ;laagste 6 bits bewaren ;hoogste 2 bits bewaren ;bij elkaar voegen ;alles op juiste plaats zetten
De XC888 beschikt over 2 seriële poorten van het type UART. Op een PC wordt dit type communicatiepoort een COM poort genoemd. Omdat de meeste PC’s niet meer zijn uitgerust met een COM poort, maar met USB porten, is het XC888 SBC bord uitgerust met een UART naar USB omvormer (figuur 5.2.7.1).
130
Figuur 5.2.7.1 UART naar USB omvormer op XC888 SBC Wanneer op de PC de juiste driver is geïnstalleerd, komt op de PC een COM poort beschikbaar wanneer het USB slot verbonden is met het XC888 SBC bord. Die COM (FT232RL chip) poort is verbonden met de UART van de XC888 (via de klemmen p1.1(RxD) en p1.0(TxD)). De SIO routines van de driver kunnen dan gebruikt worden om via de UART gegevens uit te wisselen met de PC. In onze voorbeelden gaan we er van uit dat een programma zoals “TERA TERM” beschikbaar is. Teraterm emuleert op de PC een terminal. Dat is een apparaat dat inkomende ASCII codes laat zien als symbolen op het scherm, en alle toetsaanslagen vertaalt naar ASCII codes en doorstuurt naar de COM poort (via USB naar de FT232RL chip, en zo naar de XC888 UART). Een ASCII code tabel kan je terugvinden in appendix E. Merk op dat de controlekarakters ( ASCII code<020h) afwijken van diegene die in de LCD driver gebruikt worden. Bovendien zal de terminal emulatiesoftware (vb. Tera term) ze niet allemaal verwerken. Volgende subroutines zijn beschikbaar in de driver: initsio klaar zetten seriële poort 9600 baud siooutchar afdrukken ascii code (accu=input) siooutbyte afdrukken getal in accu siooutnib afdrukken 4 laagste bits accu siooutmsga afdrukken ascii string @dptr tot 000h code sioinchar inlezen van 1 ascii code in de accu sioinbufa inlezen van ascii buffer vanaf adres strtbuf, max 20h karakters! sioinbyte inlezen van een byte zonder echo 131
Waarbij strtbuf de waarde 054h heeft. Deze waarde kan in de driver aangepast worden!! Merk op de “out” routines identieke werking hebben (op de cursorrouting na!!) in vergelijking met die van de LCD subroutines. LET OP!!! De XC888 zal na reset een inwendige RC oscillator gebruiken. Die is beperkt in nauwkeurigheid. Wanneer het systeem gebruikt wordt bij grote temperatuurschommelingen, of wanneer de component buiten tolerantie valt, is het mogelijk dat de baud rate onvoldoende nauwkeurig ingesteld wordt. Om dit te vermijden kan je overschakelen op de externe kristal oscillator. Hiervoor kan je de routine “XCsw2xtal” gebruiken. Voorbeeldprogramma SIO/UART
org mov lcall
00000h sp,#07fh initsio
;startadres programma ;stackpointer klaar zetten voor gebruik ;seriële poort klaar zetten.
;bij de initialisatie van de seriële poort worden volgende settings gebruikt: ;- 9600 baud (communicatiesnelheid van 9600 bit/seconde) ;- xonxoff flow control protocol ;- 1 start-, 8 data-, en 1 stop bit ;Volgende code zal de cursor vooraan de volgende lijn op het scherm plaatsen. mov lcall mov lcall
a,#00dh siooutchar a,#00ah siooutchar
;ascii code voor nieuwe lijn ;naar buiten sturen ;ascii code cursor vooraan de lijn ;naar buiten sturen
;Volgende code zal een toetsaanslag inlezen (blijft wachten op het indrukken van de ;toets op het klavier van de PC). Is de toets de letter “a”, dan gaan we verder in het ;programma, anders lezen we opnieuw een toets in lus:
lcall cjne
sioinchar a,#’a’,lus
;blijf wachten op indrukken toets ;indien niet “a” herhaal inlezen
;de ingedrukte toets was “a” dus gaan we verder in het programma ; De volgende routine leest een string ASCII codes binnen, en drukt ze af op het ; scherm van de PC. De ingave wordt afgesloten door op de enter toets te drukken. ; De ingelezen ASCII codes staan in de GPR’s vanaf het adres strtbuf (54h default). ; Je kan dat adres, en het aantal karakters dat maximaal ingelezen kan worden, ; aanpassen door in de driver de strtbuf en endbuf aan te passen.
132
lus2:
lcall mov mov cjne
sioinbufa r0,#strtbuf a,@r0 a,#’a’,lus2
;leest in en blijft wachten op “enter” ;start adres van de buffer laden ;lees eerste waarde uit buffer ;indien eerste karakter niet a, ;opnieuw inlezen
; indien het eerste karakter in de ingelezen buffer “a” is, gaat het programma verder. lus3:
ljmp
lus3
;afsluiten programma
LET OP!! De routine inbufa gebruikt dus GPR registers. Op dit ogenblik mag daar geen data in zitten die je nog nodig hebt! Die word immers overschreven en gaat verloren!
5.2.8
Wiskundige berekeningen
De XC888 is uitgerust met 2 hardware rekeneenheden: de CORDIC en de MDU. De subroutines in de driver “xcez” maken geen gebruik van de CORDIC of de MDU, zodat die ter beschikking blijven wanneer snelheid belangrijk is (interruptroutines). Voor de CORDIC is er op de FTP site een aparte driver beschikbaar. De MDU is zo eenvoudig in gebruik dat er geen driver nodig is. Afhankelijk van de grootte van de getallen waarop berekeningen uitgevoerd moeten worden, zullen er registers gebruikt worden om parameters door te geven. Voor 8 bit bewerkingen wordt de accu gebruikt als input en output. Waar er meerdere bytes nodig zijn zullen de Rn (n = 0-7). Voor elke subroutine staat er in de driver uitvoerig beschreven welke registers waarvoor gebruikt worden. LET OP!! De wiskundige routines gebruiken GPR’s om de berekeningen uit te voeren. Om te vermijden dat de gebruiker hiermee rekening moet houden, worden ze tijdelijk op de stack bewaard, en na de berekeningen hersteld. Dit kan enkel wanneer de stack dit deel van de GPR’s NIET gebruikt. Zodra de stack geheugenlocaties boven 7fh gebruikt is alles OK! Dit zijn de subroutines die in de xcez driver beschikbaar zijn: mul16 vermenigvuldigen 2 16 bit getallen mul32 vermenigvuldigen 2 32 bit getallen div16 delen 2 16 bit getallen div32 delen 2 32 bit getallen add16 optellen 2 16 bit getallen add32 optellen 2 32 bit getallen sub16 verschil 2 16 bit getallen sub32 verschil 2 32 bit getallen hexbcd8 omvormen 8 bit hex naar bcd 133
hexbcd16 bcdhex8 bcdhex16
omvormen 16 bit hex naar bcd omvormen 8 bit bcd naar hex omvormen 16 bit bcd naar hex
Voorbeeldprogramma wiskundige routines In het voorbeeld programma delen we het getal 9a8bh door 1f2eh. We gaan er van uit dat beide getallen in registers (20h-23h) zitten volgens figuur 5.2.8.1. De figuur laat ook zien in welke registers de getallen moeten staan zodat de DIV16 routine ze gebruikt. De figuur geeft ook weer waar de uitkomst terug te vinden is.
Input
Output Berekening
24h
R1
R0
R1
R0
9ah
8bh
rest
rest
R3
R2
quotient
quotient
23h
9ah
22h
8bh
R3
R2
21h
1fh
1fh
2eh
20h
2eh
DIV16
CY 1 indien /0
1fh
Figuur 5.2.8.1 Getallen voor en na de berekening De input en de output van de routine kan je terugvinden in de header boven de subroutine: ; ; div16 ; input: ; ; output: ; ; ;
is de routine die twee 16 bit getallen zal delen (deeltal/deler). r1,r0 = deeltal (r1=msb) r3,r2 = deler (r3=msb) r3,r2 = quotient (r3=msb) r1,r0 = rest (r1=msb) carry=1 bij deling door 0
Volgende code laadt de getallen in de juiste registers en roept de subroutine op: mov mov mov
r1,023h r0,022h r3,021h
;data in de inputregisters laden
134
mov lcall
r2,020h div16
;berekening oproepen
Nu zitten de getallen van de uitkomst in de R3, R2, R1, R0 registers zoals weergegeven in figuur 5.2.8.1. De carry zal op 1 staan als we gedeeld hebben door 0. LET OP!! De originele inhoud van de R-registers is overschreven. Je kan altijd één van de 4 registerbanken vrijhouden voor wiskundige bewerkingen. Vergeet dan niet om in de PSW de RS0 en RS1op de juiste waarde de brengen. 5.2.9
Diverse
Onder diverse vinden we volgende subroutines terug: delaya0k05s tijdsvertraging (waarde in accu)*0,05s delay1ms tijdsvertraging 1 milliseconde delay10us tijdsvertraging 10 microseconde XCsw2xtal overschakelen rc naar kristal (written by Pauwels D.) De delay1ms en delay10us hoef je enkel maar op te roepen om de beoogde vertraging te bekomen. De delaya0k05s routine gebruikt het getal in de accu om te bepalen hoe dikwijs 0k05s (0,05s) afgepast moeten worden. Volgende code zal 5 seconden duren: mov lcall
a,#100 delaya0k05s
;getal 10 in de accu ;duurt nu 100x0,05s=5s
LET OP!! De waarde van de accu kan nooit groter zijn dan 255. Dit geeft een maximale tijdsvertraging van 12,75 seconden. Wordt in de accu de waarde 000h geladen, dan komt dat overeen met 256x0,05s=12,8s. Belangrijk!! Software tijdsvertragingen zijn niet erg nauwkeurig. Dit heeft vooral te maken met het feit dat alle instructies uitvoeringstijd vragen. Je moet voor het bepalen van een delaytijd dus ook rekening houden met de uitvoeringstijd van de instructies die geen deel uitmaken van de delay routine. Bij de XC888 komt daar nog bovenop dat de uitvoeringssnelheid van de code afhankelijk is van hoe ze door de processor uit het geheugen wordt gehaald. Zowat alle recente CPU’s gebruiken één of meerdere acceleratiesystemen om hogere uitvoeringssnelheden te bekomen, met een niet deterministisch (op gebied van uitvoeringssnelheid) gedrag. Voor het afpassen van nauwkeurige tijden worden hardware timers gebruikt.
135
De routine “XCsw2xtal” laat toe om over te schakelen van de inwendige RC oscillator naar het uitwendige kristal. Hierdoor zal de frequentie van de systeemklok veel stabieler worden. Dit is vooral belangrijk bij nauwkeurige tijdsafpassingen of seriële communicatie.
5.3
5.3.1
Tips en trucs
Inleiding
In dit deel van de cursus willen we enkel tips en trucs aanbrengen. Het zijn veel gebruikte technieken voor standaard problemen. De oefeningen in het labo maken hier gebruik van.
5.3.2
Software tijdsvertraging
Een microcontroller voert relatief snel instructies uit. De XC888 controller uit het labo loopt op 24MHz. De CPU heeft 2 klokpulsen nodig om een elementaire operatie uit te voeren. Een elementaire operatie is het verplaatsen van 1 byte data tussen het geheugen en de CPU. De CPU voert de ene elementaire operatie na de andere uit (een elementaire operatie wordt een CYCLE of cyclus genoemd). Op die manier worden instructies gelezen en uitgevoerd. Sommige instructies kunnen in één cycle gelezen en uitgevoerd worden, andere in 2 of 4. De XC888 controller voegt voor het lezen van een byte uit het inwendige FLASH geheugen een wait cycle toe. Hierdoor is de uitvoeringstijd van een instructie afhankelijk van de fetch tijd en de uitvoeringstijd. Door het ingebouwde versnellingsmechanisme is de uitvoeringstijd ook nog afhankelijk van de locatie waar de instructie in het geheugen zit (zie appendix B voor de uitvoeringstijd van de instructies).
Voorbeeldprogramma looplicht met software tijdsvertraging Volgend programma laat een looplicht zien op de LED’s van poort 3.
lus:
org
00000h
;start adres programma
mov lcall lcall mov rl
sp,#07fh initleds initdipswitch a,#01111111b a
;klaar zetten van de stack ;poort 3 klaar zetten voor gebruik LED’s ;poort 4 klaar voor gebruik schakelaars ;getal dat naar P3 gaat (LED’s actief laag!) ;(4) we schuiven het getal op zodat een ;looplicht bekomen wordt 136
mov ljmp
#include
p3_data,a lus
;(6) laten zien ;(10) opnieuw schuiven, laten zien ;enz.
“c:\xcez1.inc”
;noodzakelijke driver inladen
Hoeveel tijd is er nodig om de lus één keer uit te voeren? In de instructie lijst staan het aantal klokpulsen waarin elke instructie uitgevoerd wordt. In bovenstaand programma zijn ze in vet opgenomen in de commentaar (we hebben de worst case tijd genomen uit appendix B, kolom 1ws). Voor de instructies in de lus zijn er 4+6+10=20 klokpulsen nodig (24MHz klokpulsen of 42ns per klokpuls) ofwel 0,833 µs. Het programma wordt ongeveer 1200000 per seconde uitgevoerd. Het lijkt alsof alle LED’s tegelijk licht geven. Om het programma te vertragen gebruiken we de djnz instructie. Hiermee kunnen we op een compacte en eenvoudige manier via een programmalus veel instructies uitvoeren.
Loop:
mov djnz
r0,#0ffh r0,loop
;(6) register laden met 255 ;(8) verminder r0 met 1 en indien ;niet nul spring naar loop anders ;verder gaan
De djnz instructie duurt 8*42ns=336ns en wordt 255 keer doorlopen zodat er 85µs nodig zijn. De mov instructie duur 6*42ns=252ns. De totale looptijd is dan 85,25µs. Na de uitvoering is het register r0=000h. Stel dat we volgend programma uitvoeren, hoe lang zal dit duren?
loop:
mov djnz
r0,#000h r0,loop
Als r0=000h dan is r0-1= 000h-1= 0ffh. De lus wordt dan ook 256 keer doorlopen. Wensen we de vertragingstijd verder op te voeren kunnen we meerdere lussen genest in elkaar gebruiken. Volgend voorbeeld geeft een vertraging van ongeveer 1 seconde:
loop:
mov mov djnz djnz
r1,#000h r0,#000h r0,loop r1,loop
;(6) aantal keer buitenste lus ;(6) aantal keer binnenste lus ;(8) binnenste lus ;(8) buitenste lus
De duurtijd van dit programma is: (
(
))
Merk op dat het niet zo eenvoudig is om er voor te zorgen dat een tijdsvertraging vb. exact 1000000µs (1s) tijdsvertraging geeft. We zullen software vertragingen dan ook maar gebruiken om “ongeveer” (10% juist) tijden af te passen.
137
Om de tijdsvertraging afhankelijk te maken van de schakelaars op het bord kunnen we bvb. het volgende programma gebruiken:
loop1: loop:
mov mov
r1,#000h r0,p4_data
djnz djnz
r0,loop r1,loop1
;aantal keer buitenste lus ;aantal keer binnenste lus ;p4=schakelaars ;binnenste lus ;buitenste lus (r0 moet herladen ;worden want is nu 000h)
De looptijd wordt gegeven door volgende formule: ( (
( (
)) ))
De programmeur kan een oneindig aantal variaties op bovenstaande programma’s bedenken. Je moet wel na(rekenen)meten wat de uiteindelijke vertraging zal zijn. In de xcez0.inc driver zit er een tijdsvertraging, waarvan de uitvoeringstijd afhankelijk is van de getalwaarde in de accu. De routine delaya0k05 duurt 0,05s als de accu=1, en 12,75s als de accu=255. Is de accu=000h dan zal de tijdsvertraging 12,8 seconden bedragen. LET OP!! Inwendig in de controller zijn er mechanismen voorzien die de uitvoering van code versnellen. Hierdoor klopt de berekende tijd niet steeds met de reële uitvoeringstijd. De controller heeft op tijdsgebied een niet deterministisch gedrag. Je zal een tijdsvertraging moeten nameten met de oscilloscoop!! Er bestaat ook de mogelijkheid om het versnellingsmechanisme uit te schakelen (lcall dffch) en opnieuw in te schakelen (lcall dfffh). De opgeroepen routines zitten in de boot ROM van de controller. Er wordt nergens informatie vrijgegeven over de gebruikte hardware om dit systeem te bedienen…
5.3.3
Aanmaken en uitlezen van tabellen
Er zijn veel toepassingen waar tabellen gebruikt worden. Volgend programma laat toe om een willekeurig looplicht te laten zien op de LED’s. Voorbeeldprogramma: looplicht met tabel (tellen aantal bytes in tabel)
;declaratie startadres start equ 00000h
;startadres programma 138
org
start
;start adres van het programma
mov lcall lcall mov mov clr movc mov mov lcall inc djnz ljmp
sp,#07fh initleds initdipswitch dptr,#tabel r7,#004h a a,@a+dptr p3_data,a a,p4_data delaya0k05s dptr r7,loop lus
;stackpointer laden ;poort 3 klaar zetten voor gebruik ;schakelaars klaar zetten voor gebruik ;startadres tabel in dptr ;aantal bytes in de tabel ;accu op 0 zetten ;waarde uit tabel halen ;laten zien ;schakelaars naar accu lezen ;tijdsvertraging oproepen ;adres tabel in dptr+1 ;herhaal tot r4=000h ;opnieuw opstarten
tabel:
db db db db
10000001b 01000010b 00100100b 00011000b
;eerste waarde in tabel ;tweede op een adres hoger ;nog een adres hoger ;vierde byte
#include
“c:\xcez1.inc”
lus: loop:
“tabel” is een label die als waarde het startadres krijgt van de tabel in het geheugen, dus de geheugenlocatie waar de byte 10000001b staat. Het toekennen van die adreswaarde gebeurt door de assembler die het programma omzet. Het “db” directief staat voor “DEFINE BYTE”. Hiermee wordt een constante mee opgenomen in het programma. De volgende uitvoering van het programma laat zien hoe we de assembler de lengte van de tabel kunnen laten berekenen.
;declaratie startadres start equ 00000h org
start
;startadres programma ;start adres van het programma
mov sp,#07fh ;stackpointer laden lcall initleds ;poort 3 klaar zetten voor gebruik lcall initdipswitch ;schakelaars klaar zetten voor gebruik lus: mov dptr,#tabel ;startadres tabel in dptr mov r7,#(tabel1-tabel) ;aantal bytes in de tabel ; Op het ogenblik dat de assembler het programma omzet zijn de adressen tabel en ; tabel1 gekend. Door het verschil van de adressen te berekenen, is het aantal bytes ; in de tabel gekend. loop: clr a ;accu op 0 zetten
139
movc mov mov lcall inc djnz ljmp
a,@a+dptr p3_data,a a,p4_data delaya0k05s dptr r7,loop lus
tabel:
db db db db
10000001b 01000010b 00100100b 00011000b
#include
“c:\xcez1.inc”
;waarde uit tabel halen ;laten zien ;schakelaars naar accu lezen ;tijdsvertraging oproepen ;adres tabel in dptr+1 ;herhaal tot r4=000h ;opnieuw opstarten
;eerste waarde in tabel ;tweede op een adres hoger ;nog een adres hoger ;vierde byte tabel1: ;dit is een “dummy” label die door de ;assembler de waarde krijgt van het eerste vrije adres na de tabel. Het verschil van ;beide getallen geeft de lengte van de tabel.
LET OP!! Niet alle assemblers laten dit toe. Het is best mogelijk dat deze oplossing niet toepasbaar is in het labo.
Voorbeeldprogramma: looplicht met tabel (verboden code) De volgende variant van het programma gebruikt geen teller om het einde van de tabel te detecteren, maar een verboden code. Dat is een constante die geen deel uitmaakt van de eigenlijke data in de tabel, maar aangeeft dat het de laatste entry van de tabel is.
;declaratie startadres start equ 00000h
lus: loop:
loop1:
;startadres programma
org
start
;start adres van het programma
mov lcall lcall mov mov clr movc cjne ljmp mov mov lcall inc ljmp
sp,#07fh initleds initdipswitch dptr,#tabel r7,#004h a a,@a+dptr a,#0ffh,loop1 lus p3_data,a a,p4_data delaya0k05s dptr loop
;stackpointer laden ;poort 3 klaar zetten voor gebruik ;schakelaars klaar zetten voor gebruik ;startadres tabel in dptr ;aantal bytes in de tabel ;accu op 0 zetten ;waarde uit tabel halen ;niet 0ffh in accu dan loop1 ;anders opnieuw starten ;laten zien ;schakelaars naar accu lezen ;tijdsvertraging oproepen ;adres tabel in dptr+1 ;opnieuw opstarten
140
tabel:
db db db db db
10000001b 01000010b 00100100b 00011000b 11111111b
#include
“c:\xcez1.inc”
;eerste waarde in tabel ;tweede op een adres hoger ;nog een adres hoger ;vierde byte ;verboden code (willen we niet laten zien)
In dit programma kunnen we de tabel verlengen zonder de code aan te passen. Let op!! De waarde 0ffh mag alleen op het einde van de tabel voorkomen. Voorbeeldprogramma: afdrukken tekst uit tabel (PC of LCD) Een tabel kan ook gebruikt worden om aan tekst boodschap in het geheugen op te slaan. Volgend programma geeft hier een voorbeeld van (op de PC loopt hyperterminal of tera term of een ander terminal emulatie programma):
start
equ
00000h
org
start
loop1:
mov lcall lcall mov lcall lcall mov lcall mov lcall lcall ljmp
sp,#07fh initsio initlcd dptr,#msg1 siooutmsga lcdoutmsga a,#100 delaya0k05s dptr,#msg2 siooutmsga lcdoutmsga loop1
msg1:
db
ff,’mij kan je bovenaan links lezen’,000h
msg2:
db
cr,lf,’ik sta 1 lijn lager (na 5s)’,000h
#include
“c:\xcez1.inc”
loop:
;startadres programma
;stack pointer klaar zetten voor gebruik ;seriële poort initialiseren ;LCD scherm klaar zetten ;adres eerste boodschap ;naar scherm PC ;en ook naar het LCD scherm ;5 seconden wachten ;100*0,05s delay ;adres tweede boodschap ;naar scherm PC ;en ook naar LCD ;stoppen controller
De ff in msg1 is een constante die in de driver de waarde 00ch krijgt (ASCII code voor form feed). “siooutmsga” en “lcdoutmsga” zijn subroutines uit de xcez driver die een ASCII string afdrukken op respectievelijk het scherm van de PC (sio) en het LCD scherm. Het startadres van de string moet in de dptr staan. Het laatste karakter van de string
141
moet 000h zijn. Wat tussen ‘ ’ staat wordt door de assembler omgezet in zijn ASCII codes. In figuur 5.3.3.1 kan je zien wat er op het scherm van de PC, en op het LCD verschijnt. LET OP!! Het LCD scherm en het scherm van de PC reageren anders op bepaalde ASCII codes. De PC volgt de codes uit appendix E, de LCD alleen diegene die beschreven staan in de header van de lcdoutchar routine in de xcez driver. LET OP!! De PC zal automatisch de volgende lijn nemen wanneer een lijn werd volgeschreven. Het LCD scherm doet dat niet. Het scherm vult zijn DDRAM en overschrijft die dan telkens opnieuw.
mij kan je bovenaan links lezen ik sta 1 lijn lager (na 5s)
Figuur 5.3.3.1 Data op scherm PC en LCD
Om variabelen af te drukken zijn er andere monitorroutines beschikbaar.
5.4
Een tabel gebruiken om te vertalen voor linearisering
In bepaalde gevallen is er geen, of een onduidelijk wiskundig verband tussen twee getallen. Het ene kan dus niet of zeer moeilijk via een berekening omgezet worden naar het andere. Een voorbeeld hiervan is het verband tussen de temperatuur en de spanning van een thermokoppel. De eenvoudigste manier om de omzetting te doen is via een tabel. In die tabel nemen we voor elke mogelijke spanning van het thermokoppel de overeenkomstige temperatuur op . Een praktisch voorbeeld is het omzetten van een brandstofniveau (h in cm) in een brandstof hoeveelheid (liter). In figuur 5.4.1 is een tekening opgenomen van een brandstoftank, met in bijhorende tabel de hoeveelheid brandstof in de tank, in functie van het gemeten brandstofniveau. Die gegevens kunnen opgenomen worden in een
142
tabel in het geheugen van de controller. Stel dat we de tabel opnemen in de adressen die vermeld staan in de eerste kolom van figuur 5.4.1. Volgende code zal dan de omzetting doen van de gemeten hoogte (cm) naar de beschikbare hoeveelheid: mov dptr,#1000h ;start adres tabel in de dptr mov a,[gemeten hoogte brandstof] movc a@a+dptr ;doe de vertaling Stel h=100cm, dan is [gemeten hoogte brandstof]=64h. a+dptr=64h+1000h OP het adres 1064h staat in het geheugen de hoeveelheid beschikbare brandstof. Die waarde wordt in de accu geladen. Adres in geheugen
niveau h
Liter in tank
1000h 1001h 1002h 1003h 1004h
0 1 2 3 4
0 1 3 5 6
1064h
100
87
10c8h
200
180
10ffh
255
215
Brandstoftank 255cm
h vloeistof 0cm
Figuur 5.4.1 Brandstoftank
Een ander voorbeeld is het omzetten van een binair getal naar een uitlezing op een 7-segment display. De hardware die we gebruiken is fictief. Poort 3 wordt gebruikt voor het aansturen van een 7-segment display (figuur 5.4.2). De laagste 4 schakelaars van poort 4 worden gebruikt om schakelaars in te lezen (p4.3=MSB en p4.0=LSB). De waarde van de schakelaars wordt omgezet naar een uitlezing op de 7-segment display.
143
loop:
org
00000h
;startadres programma
mov mov mov mov mov mov mov mov
sp,#07fh port_page,#001h p4_pudsel,#0ffh p4_puden,#0ffh port_page,#000h p4_dir,#000h p3_dir,#0ffh dptr,#tabel
;stackpointer laden ;selecteer poort page 1 ;selecteer pull_up device ;selectie inschakelen ;selectie poort pagina 0 ;poort 4 als input schakelen ;poort 3 als output schakelen ;startadres tabel laden
mov anl movc mov ljmp
a,p4_data a,#00fh a,@a+dptr p3_data,a loop
;lees de schakelaars ;hoogste 4 bits op 0 ;vertalen via tabel ;naar 7 segment uitlezing ;herhaal
;hier staan de binaire patronen die nodig zijn om de cijfers 0-9 en de letters a-f weer ;te geven op het 7-segment display. Let op!! Het display wordt actief laag ;aangestuurd: een 0 doet de LED licht geven, een 1 schakelt de LED uit. tabel:
db db db db db db db db db db db db db db db db
#include
“c:\xcez1.inc”
11000000b 11111001b 10100100b 10110000b 10011001b 10010010b 10000010b 11111000b 10000000b 10010000b 10001000b 10000011b 11000110b 10100001b 10000110b 10001110b
;binair patroon voor 0 ;1 ;2 ;3 ;4 ;5 ;6 ;7 ;8 ;9 ;A ;b ;C ;d ;E ;f
Merk op dat er geen wiskundig verband bestaat tussen de “display code” voor het 7segment display en het binaire getal dat via poort 4 ingelezen wordt. De dptr wijst steeds naar het adres van de eerste entry in de tabel. De waarde in de accu, afkomstig van p4, vormt een offsetwaarde van 000h tot 00fh, afhankelijk van de 4 schakelaars. De movc a,@a+dptr instructie leest naar de accu wat er op het adres staat gevormd door de som van dptr en a. Na de instructie zal de dptr niet van waarde veranderd zijn, de accu werd overschreven met het getal dat uit het geheugen gelezen is. Deze instructie (movc a,@a+dptr) werkt enkel via een tabel in het “code memory”.
144
A A F
B A=p3.0 B=p3.1 C=p3.2 D=p3.3 E=p3.4 F=p3.5 G=p3.6 DP=p3.7
B G
E
C D
C D
VDD
E F
DP
G
p3.0 p3.1 p3.2 p3.3 p3.4 p3.5 p3.6
DP
p3.7 7
6
5
4
3
2
1
0
1
1
1
1
1
0
0
1
7
6
5
4
3
2
1
0
1
0
1
0
0
1
0
0
7
6
5
4
3
2
1
0
1
0
1
1
0
0
0
0
Figuur 5.4.2
5.5
7 segment aansturing
HEX, BCD, BIN inlezen en uitschrijven
Alle communicatie tussen de PC en de controller gebeurt in ASCII code. Voor elk karakter dat op het scherm komt, of via het klavier verzonden wordt, wordt een 8 bit code gebruikt. Het symbool dat met die code overeenkomt kan je opzoeken in de ASCII code tabel in appendix E. Voor de verdere bespreking gebruiken we de driver routines “sioinchar” en “siooutchar”. Sioinchar wacht op een ASCII code. Komt die toe op de seriële poort, dan wordt ze in de accu geplaatst en wordt de routine afgesloten. Wordt het getal in binaire vorm ingegeven, dan wordt per bit één 8-bit ASCII code doorgegeven: 030h voor 0 en 031h voor 1. 145
Wordt het getal ingegeven in verkorte notatie, dan zijn de mogelijke ASCII codes 030h t.e.m. 039h voor de cijfers 0 t.e.m. 9 en 041h t.e.m. 046h voor de letters A t.e.m. F of 061h t.e.m. 066h voor de letters a t.e.m. f. Siooutchar zal voor dezelfde ASCII codes bijhorende symbolen op het scherm plaatsen. Moet een getal binair op het scherm afgedrukt worden, dan zullen we zoveel ASCII codes naar het scherm moeten sturen als er bits in het getal zitten. Bij het decimaal uitschrijven mogen we enkel de ASCII codes voor de cijfers gebruiken. Per symbool geven we eigenlijk 4 bits weer. Bij de weergave van hex getallen mogen ook de letters a-f gebruikt worden. Ook hier staat elk symbool voor 4 bits. Bij het weergeven van een getal zal het programma moeten nagaan wat de waarde van elke individuele bit is (of groepje van 4 bits) om de passende ASCII code te bepalen alvorens die naar het scherm te sturen. Bij het inlezen gebeurt het omgekeerde. Bij het binair inlezen krijgen we 1 ASCII code per bit. Vb:
Ingave via klavier: 10010011 geeft volgende reeks ASCII codes: 031h,030h,030h,031h,030h,030h,031h,031h. Per ASCII code kan maar 1 bit gebruikt worden bij de wedersamenstelling van het getal. Ingave via klavier: a7 geeft volgende twee ASCII codes:061h en 037h. Elke ASCII code is nu 4 bits waard.
Volgend programma drukt de inhoud van p4 binair en hex af op het scherm van de PC
org
00000h
;start adres programma
mov
sp,#07fh
;stackpointer in orde zetten
lcall lcall
initsio initdipswitch
;seriële poort klaar zetten voor gebruik ;schakelaars klaar zetten voor gebruik
mov a,#ff ;eerst scherm leeg maken ; de ff is een constante die in de driver de waarde 00ch krijgt (ASCII code voor form ; feed. lcall siooutchar ;naar scherm sturen loop: mov a,p4_data ;lees poort 4 lcall schrbin ;binair op scherm zetten mov a,#blank lcall siooutchar ;blank tussen de twee getallen mov a,p4_data ;poort 4 inlezen lcall schrhex ;hex uitschrijven mov a,#cr ;cursor vooraan lijn lcall siooutchar ;naar scherm schrijven ljmp loop ;lus sluiten
146
; schrbin is een subroutine die de inhoud van de accu op het ; scherm weergeeft als een binair getal. De routine gebruikt de ; accu, psw en r7. schrbin: schrbin1:
mov r7,#008h ;loopcounter (lusteller) rlc a ;bit naar carry schuiven push acc ;accu effe bewaren mov a,#030h ;ascii code 0 in accu addc a,#000h ;a=030h+000h+cy ; de accu zal 030h bevatten als carry=0 en 031h als carry=1 ; 030h is ASCII code van 0 en 031h is de ASCII code van 1 lcall siooutchar ;naar scherm PC verzenden pop acc ;accu herstellen djnz r7,schrbin1 ;herhaal tot einde 8 bits ret ; schrhex zal de inhoud van de accu in hexadecimale vorm op het ; scherm plaatsen. De routine gebruikt de accu en psw. schrhex:
schrhex1: schrhex2:
push anl swap cjne jc add add
acc a,#0f0h a a,#00ah,schrhex1 schrhex2 a,#007h a,#030h
;effe bewaren op de stack ;hoogste 4 bits afzonderen ;bits onderaan plaatsen ;acc<00ah? ;ja, dan springen ;neen, dan 007h bij optellen ;altijd 030h bij optellen
; wil je weten hoe dit werkt? Neem stukje papier en reken na ; voor alle mogelijke getallen in de accu.
schrhex1: schrhex2:
lcall
siooutchar
;naar het scherm sturen
pop anl cjne jc add add
acc a,#00fh a,#00ah,schrhex1 schrhex2 a,#007h a,#030h
;laagste 4 bits verwerken ;hoogste 4 bits afzonderen ;acc<00ah? ;ja, dan springen ;neen, dan 007h bij optellen ;altijd 030h bij optellen
; wil je weten hoe dit werkt? Neem stukje papier en reken na ; voor alle mogelijke getallen in de accu. lcall ret #include
siooutchar
;naar het scherm sturen
“c:\xcez1.inc”
147
Onderstaand programma leest een getal binair in via het klavier van de PC en laat het zien op poort 3. Bij de laatste ingave kan op de volgende lijn een nieuw getal ingegeven worden.
start
loop:
equ
00000h
;start adres programma
org
start
mov
sp,#07fh
;stackpointer laden
mov lcall
a,#ff siooutchar
;scherm leeg maken ;weg er mee
lcall mov mov lcall mov lcall ljmp
inbin p3_data,a a,#lf siooutchar a,#cr siooutchar loop
;binair inlezen byte ;naar LED’s sturen ;volgende lijn nemen op scherm ;cursor moet vooraan de lijn
; inbin leest een byte binnen als een reeks van 8 binaire ; getallen. De routine stuurt een echo naar het scherm. Het ; ingelezen getal wordt via de accu doorgegeven. De routine ; gebruikt geen registers. inbin:
push b ;wordt gebruikt als lusteller push psw ;de vlaggen gaan anders verloren mov b,#008h ;iets gaan we 8 keer doen inbin1: push acc ;gaan we gebruiken als werkreg. lcall sioinchar ;wachten op eerste ingave lcall siooutchar ;echo naar het scherm van de Pc rrc a ;laagste bit afzonderen ;030h is ASCII code van een 0 en 031h is ASCII code van 1 pop acc ;werk accu laden rlc a ;afgezonderde bit inschuiven djnz b,inbin1 ;herhaal 8 keer pop psw ;vlaggen herstellen pop b ;b herstellen ret #include
5.6
“c:\xcez1.inc”
Omrekening HEX naar BCD
Bij de omzetting van een hex getal naar een bcd getal gaan we kijken hoeveel keer 10D, 100D, 1000D, … in het getal gaan. Dit doen we door een deling uit te voeren.
148
Bij de eerste deling delen we het getal door 10D (00ah). Het quotiënt van die deling is het aantal keer dat 10D in het getal gaat. De rest is wat kleiner is dan 10D, dit zijn dus de eenheden. Het quotiënt kunnen we opnieuw delen door 10d (00ah). Het nieuwe quotiënt geeft aan hoeveel honderdtallen er in het getal zitten. De nieuwe rest geeft het aantal tientallen. We delen evenveel keer als er decimale karakters nodig zijn om het getal weer te geven. Stel het hex getal is 0fch. Een deling door 00ah geeft als quotiënt 019h. De rest is 002h. Er zitten 19h (25d) tientallen in het getal en 2 eenheden. De rest van de deling zit altijd tussen 000h en 009h. Het vorige quotiënt delen we opnieuw door 00ah. 019h/00ah geeft als nieuw quotiënt 002h en als rest 005h. Er zitten 2 honderdtallen in het getal en 5 tientallen. Bij de omzetting van een 8 bit hex getal kan het grootste decimale resultaat 255d zijn. In principe delen we drie keer. Eigenlijk is dat niet nodig omdat het quotiënt van de tweede deling nooit groter kan zijn dan 2, zodat verder delen niet zinvol is. Omdat we in een programma lussen zullen gebruiken voor de omzetting is het meestal eenvoudiger om nog eens te delen. 002h/00ah geeft als quotiënt 000h en als rest 002h. dit zijn dan de honderdtallen. In volgend programma voeren we de omzetting door voor een getal dat we inlezen van de schakelaars. Het decimale resultaat wordt naar het scherm van de PC gestuurd.
loop2:
org
00000h
mov lcall lcall mov lcall
sp,#07fh initsio initdipswitch a,#ff siooutchar
;stackpointer klaar zetten voor gebruik ;seriële poort initialiseren ;schakelaars klaar zetten ;scherm eenmalig leeg maken ;naar scherm sturen
mov
a,p4_data
;lees de schakelaars in accu
mov r0,#003h ; keer delen (getal bestaat uit drie karakters)
;max uitkomst is 255 dus 3
loop:
;we gaan delen door 10d ;a/b en a=quotient b=rest ;rest bewaren op de stack ;3 keer uitvoeren
mov div push djnz
b,#00ah ab b r0,loop
; op de stack zitten de drie resten. De stack werkt als een LIFO ; (last in first out). De eerste rest was de eenheden, de ; laatste de honderdtallen. Bij het poppen komen eerst de ; honderdtallen dan de tientallen, en als laatste de eenheden. ; Die waarden moeten nog omgezet worden naar ASCII codes. ; de ascii codes van 0-9 zijn 030h-039h.
149
loop1:
#include
mov pop add lcall djnz mov lcall ljmp
r0,#003h acc a,#030h siooutchar r0,loop1 a,#cr siooutchar loop2
;ook in lus zetten ;waarde van stack halen ;omzetting naar ASCII ;naar het scherm sturen ;er zitten drie waarden op de stack ;cursor vooraan de lijn plaatsen ;naar scherm ;volgende getal omzetten
“c:\xcez1.inc”
Voor de omzetting van grotere getallen (groter dan 8 bit) kunnen we de div instructie niet meer gebruiken. We zullen dan een deling moeten gebruiken die getallen van grotere omvang kan verwerken (zie uitleg vermenigvuldigen en delen van grote getallen in paragraaf 5.8). Onderstaand programma kan ook gebruikt worden maar duurt héééééél lang. Het staat hier enkel als voorbeeldprogramma. Het is geschreven voor een 16 bit omzetting (maximale waarde 65536 (5 decimale karakters) maar kan aangepast worden voor getallen van onbeperkte afmetingen). Het hex getal wordt ingelezen van het klavier van de PC, de uitkomst komt op het scherm van de PC. ; dit programma leest van de PC een 16 bit hex getal. Het ; wordt omgezet naar een 24 bit bcd getal. De gebruikte methode ; gaat van het hex getal 1 aftrekken tot 0 bereikt wordt. ; Gelijktijdig wordt bij het bcd getal 1 opgeteld. Deze methode ; trekt op niks en vraagt veel processor tijd. De oefening is ; enkel ter illustratie.
start
equ
00000h
;start adres programma
hexhoog hexlaag bcdhoog bcdmid bcdlaag
equ equ equ equ equ
020h 021h 022h 023h 024h
;hoogste 8 bits hex getal ;laagste bits hex getal ;hoogste bits bcd getal ;middelste bits bcd getal ;laagste bist bcd getal
org
start
mov lcall mov lcall mov lcall mov lcall
sp,#07fh initsio a,#ff siooutchar dptr,#msg1 sioinbufa r0,#startbuf ascii2
lus:
;stack pointer klaar zetten ;seriële poort klaar zetten ;scherm eenmalig leeg maken ;boodschappeke op het scherm ;lees in ;pointer naar start tabel (default =054h) ;2 eerste ASCII codes naar hex getal ;omzetten
150
jc mov mov lcall jc mov
start hexhoog,a r0,#(startbuf+2) ascii2 start hexlaag,a
;fout in omzetting ;opslaan ;volgende 2 ASCII codes ;omzetten ;fout in omzetting
; er is een geldig hex getal ingegeven. We gaan nu de omzetting ;starten mov mov mov
bcdhoog,#000h bcdmid,#000h bcdlaag,#000h
;initialisatie registers ;idem ;idem
; bij de omzetting gebruiken we geen lusteller maar gaan we ; omzetten tot het hex getal 0 is. Dit kan bij de ingave reeds ; het geval zijn! test:
mov orl jz
a,hexhoog a,hexlaag uit
;nagaan hexhoog en hexlaag=0 ;a=0 als hexhoog=hexlaag=0 ;dan uitkomst naar scherm
; we gaan het 16 bit hex getal met 1 verminderen mov clr subb mov mov subb mov
a,hexlaag c a,#001h hexlaag,a a,hexhoog a,#000h hexhoog,a
;gebruiken een 16 bit ;carry uit verleden weg ;-1 ;terug wegschrijven ;eventuele carry verrekenen ;terug wegschrijven
; nu het bcd getal met 1 verhogen. mov add da mov mov addc da mov mov addc da mov
a,bcdlaag a,#001h a bcdlaag,a a,bcdmid a,#000h a bcdmid,a a,bcdhoog a,#000h a bcdhoog,a
;met lage deel starten ;let op dit is een bcd + ;dus corrigeren ;terug wegschrijven ;eventuele carry doorgeven ;ook dit is bcd + ;hoogste deel
;terug wegschrijven
; nagaan of we mogen stoppen. Dit is het geval als het hex ; getal=0
151
ljmp
test
mov lcall mov lcall mov lcall mov lcall mov lcall ljmp
a,bcdhoog siooutbyte a,bcdmid siooutbyte a,bcdlaag siooutbyte a,#lf siooutchar a,#cr siooutchar lus
msg1:
db
’geef een 16 bit hex getal in:’,000h
#include
“c:\xcez1.inc”
uit:
5.7
;eerst hoge weg ;dan de middelste ;dan de laagste ;cursor lijn lager ;cursor vooraan lijn
Omrekening BCD naar HEX
Bij de omrekening van een bcd getal naar een hex getal is er een klein probleem. De controller kan op een bcd getal enkel de add of addc bewerking uitvoeren, om met behulp van de “da a” instructie, een correct resultaat te produceren. We kunnen wel berekeningen uitvoeren op de individuele nibbles van het getal. Die hebben immers altijd een correcte hex waarde (0bcd=0hex en 009bcd=009hex). Als we een bcd getal van rechts naar links bekijken, stellen we vast dat de waarde van de volgende nibble 00ah groter is (zie decimale getallen). Als we die berekening in een programma plaatsen en de getallen optellen bekomen we de hex waarde. Volgend programma voert dit uit voor een 2 karakter bcd getal. Voor grotere getallen zal je de routines voor het vermenigvuldigen van grote getallen moeten gebruiken. Andere mogelijkheden geven omslachtige programma’s. ; dit programma leest een 2 karakter bcd getal in via het ; klavier van de PC en zet het om naar een 8 bit hex getal. ; de ingave is heel summier. De echo komt pas na het ingeven ; van de 2 karakters van het getal. Na een spatie wordt de ; uitkomst afgedrukt.
start
equ
00000h
;start adres programma
uitkomst
equ
020h
;hier uitkomst in stoppen
org
start
mov
sp,#07fh
;stack pointer initialiseren
152
lcall mov lcall mov lcall lcall
lus:
initsio a,#ff siooutchar dptr,#msg1 siooutmsga sioinbyte
;seriële poort initialiseren ;scherm eenmalig leeg maken ;weg er mee ;boodschap naar het scherm ;inlezen van een 8 bit getal
; er is geen echo naar het scherm, we doen ook geen foutcontrole ; in de accu zit het bcd getal push lcall mov lcall pop push
acc siooutbyte a,#blank siooutchar acc acc
;effe copie naar stack ;byte naar scherm ;spatie naar het scherm
anl
a,#00fh
;laagste 4 bits afzonderen
;accu herstellen ;nieuwe copie op stack
; die laagste bits moeten niet omgerekend worden mov pop anl swap mov mul add
uitkomst,a acc a,#0f0h a b,#00ah ab a,uitkomst
lcall ljmp
siooutbyte lus
;bewaren ;opnieuw herstellen accu ;hoogste 4 bits afzonderen ;moeten in laagste 4 bits zitten ;deze nibble is 10 keer meer waard ;vermenigvuldigen ;vorige waarde bijtellen de maximale ;uitkomst kan 99d of 063h zijn zodat er maar ;1 byte nodig is om de uitkomst op te slaan. ;uitschrijven naar scherm
msg1: db
cr,lf,’geef getal in: (geen echo naar het scherm),000h
#include
“c:\xcez1.inc”
Hier laten we zien hoe een 4 karakter bcd getal omgezet wordt: bcd getal =1234h (0001 0010 0011 0100 b) omzetten naar nibbles: 01h 02h 03h 04h (elke nibble zit in een byte) elke nibble vermenigvuldigen met de passende decimale waarde: 01*1000d 02*100d
153
03*10d 04*1d omdat we niet met decimale getallen kunnen vermenigvuldigen wordt dat: 01h*03e8h=03e8h 02h*0064h=00c8h 03h*000ah=001eh 04h*0001h=0004h de som van de producten is 04d2h Merk op dat bij het schrijven van het programma het best een lus kan gebruikt worden. De lus wordt evenveel keer doorlopen als er bcd karakters zijn.
5.8
Vermenigvuldigen en delen van grote getallen
In dit deel van de cursus staan 2 voorbeeld programma’s die voor 32 bit getallen geschreven zijn. Het programma kan aangepast worden naar zo veel bits als nodig. De gebruikte rekentechnieken komen overeen met die voor decimale getallen, maar dan toegepast op binaire getallen. Voor de deling wordt dat de staartdeling en bij het vermenigvuldigen gebruiken we de sommatie van deelproducten. De techniek kan ook op andere niet 8051 controllers toegepast worden. ; dit programma zal twee getallen van 32 bit met elkaar ; vermenigvuldigen. We gaan er van uit dat ze reeds in de ; registers van de controller staan. De uitkomst wordt ook naar ; registers geschreven. We gebruiken registers in het indirect ; adresseerbare gebied. Omdat het een onderdeel is van een ; groter programma zijn de gebruikelijke org en # directieven ; weggelaten. ;LET OP!! deze adressen mogen niet overlappen met de stack ruimte. ;de traditionele “mov sp,#07fh” kan nu dus niet. ;de stack kan wel het geheugen gebruiken boven 093h. Mov sp,#093h ;kan dus wel (stack vertrekt vanaf basiswaarde +1, in dit geval 94h). get1hh get1h get1l get1ll
equ equ equ equ
080h 081h 082h 083h
;adres hoogste byte get1 ;lagere byte ;volgende lagere ;laagste
werkhh werkh werkl werkll
equ equ equ equ
084h 085h 086h 087h
;4 werkregisters
get2hh get2h
equ equ
088h 089h
;zie hierboven maar get2 ;idem
154
get2l get2ll
equ equ
08ah 08bh
uithhhh uithhh uithh uith uitl uitll uitlll uitllll
equ equ equ equ equ equ equ equ
08ch 08dh 08eh 08fh 090h 091h 092h 093h
;64 bit uitkomst
teller telbits
equ equ
020h 021h
;lusteller ;bitteller
; we gaan eerst de uitkomstregisters op 0 zetten
lus:
mov mov mov mov inc djnz
a,#000h teller,#008h r0,#uithhhh @r0,a r0 teller,lus
;gaan uitkomst op 0 zetten ;er zijn 8 registers ;r0 als pointer naar uithhhh ;naar geheugen schrijven ;pointer aanpassen ;herhaal 8 keer
; we gaan eerst de werkregisters op 0 zetten
lus1:
mov mov mov mov inc djnz
a,#000h teller,#004h r0,#werkhh @r0,a r0 teller,lus1
;gaan uitkomst op 0 zetten ;er zijn 8 registers ;r0 als pointer naar werkhh ;naar geheugen schrijven ;pointer aanpassen ;herhaal 4 keer
; we testen de bits van get1 van rechts naar links. Telkens een ; bit 1 is tellen we het 32 bit getal werkhh, werkh, werkl, ; werkll, get2hh, get2h, get2l, get2ll op bij het 32 bit getal ; uithhhh, uithhh, uithh, uith, uitl, uitll, uitlll, uitllll. ; Na elke bittest wordt het 32 bit getal werkhh, werkh, werkl, ; werkll, get2hh, get2h, get2l, get2ll met 2 vermenigvuldigd. ; omdat het over grote getallen gaat worden lussen gebruikt ; de operatie moet 32 keer uitgevoerd worden mov
telbit,#032d
;totale lusteller
; we schuiven getal1 1 plaats naar rechts op om die bit te ; testen lus6:
mov mov
teller,#004h r0,#get1hh
;gaat 0ver 4 bytes ;adres hoogste byte
155
lus2:
mov rrc mov inc djnz
a,@r0 a @r0,a r0 teller,lus2
;byte lezen ;rechts schuiven ;terug naar het geheugen ;pointer aanpassen ;herhaal voor 4 bytes
; in de carry zit de uiterst rechtse bit van get1. Die gaan we ; testen. Als die 1 is moeten we getal2 en de werkregs bij uit ; optellen. Anders niet jnc
lus4
;niet bijtellen dan lus4
; hier staat een 32+32 bit som.
lus3
mov mov mov clr mov addc mov dec dec djnz
teller,#008h r0,#get2ll r1,#uitllll c a,@r0 a,@r1 @r1,a r0 r1 teller,lus3
;gaat over 8 bytes ;adres laagste byte ;adres laagste byte ;carry moet op 0 staan ;som uitvoeren ;bijtellen ;naar uit schrijven ;pointer aanpassen ;pointer aanpassen ;herhaal voor 8 bytes
; De som is klaar. Nu moet get2 met twee vermenigvuldigd worden. ; Bij elke vermenigvuldiging wordt het getal 1 bit groter. ; Om dit op te vangen gebruiken we de 4 werk registers. Lus4:
lus5:
mov mov clr mov rlc mov dec djnz
teller,#008h r0,#get2ll c a,@r0 a @r0,a r0 teller,lus5
;lusteller ;adres laden laagste byte ;deze bit wordt ingeschoven ;getal lezen ;maal 2 ;terug schrijven ;pointer aanpassen ;klaar
djnz
telbit,lus6
;32 keer herhalen
; in de uit registers is het 64 bit resultaat beschikbaar.
Het volgende programma laat een deling van twee 32 bit getallen zien. Het is opnieuw een onderdeel van een groter programma. We gaan er van uit dat het te delen getal get2 is en de deler is get1. Na de bewerking staat de uitkomst in uit en de rest in de werk registers.
get1hh
equ
080h
;adres hoogste byte get1
156
get1h get1l get1ll
equ equ equ
081h 082h 083h
;lagere byte ;volgende lagere ;laagste
werkhh werkh werkl werkll
equ equ equ equ
084h 085h 086h 087h
;4 werkregisters ;bevatten rest na bewerking
get2hh get2h get2l get2ll
equ equ equ equ
088h 089h 08ah 08bh
;zie hierboven maar get2 ;idem
uithh uith uitl uitll
equ equ equ equ
08ch 08dh 08eh 08fh
;32 bit uitkomst
tussenhh tussenh tussenl tussenll
equ equ equ equ
090h 091h 092h 093h
;bewaren tussenresultaat
teller telbits
equ equ
020h 021h
;lusteller ;bitteller
; eerst gaan we de werk registers op 0 zetten. De uit registers ; moeten niet op 0 staan omdat ze gevuld worden tijdens de ; berekening.
lus:
mov mov mov mov inc djnz
teller,#004h r0,#werkhh a,#000h @r0,a r0 teller,lus
;het gaat over 4 bytes ;die vanaf dit adres staan ;ze moeten op 0 komen ;in geheugen stoppen ;volgende nemen ;4 keer uitvoeren
; de bitteller wordt gebruikt om te tellen hoe dikwijls het ; gehele programma doorlopen wordt. Dit is evenveel keer als er ; bits zitten in het te delen getal (nu 32) mov
telbits,#032d
;aantal keer lus doorlopen
; bij de deling gaan we het te delen getal in de werk registers ; shiften (1 bit per keer dat de lus doorlopen wordt). Dit ; nieuwe getal wordt vergeleken met de deler. Is de inhoud van ; de werk registers kleiner, dan wordt in de uit registers ; een 0 geshift. Is de inhoud van de werkregisters groter, dan ; wordt van werk de deler afgetrokken. In de uit registers wordt
157
; een 1 geshift. Op die manier voeren wij ook een staartdeling ; uit. ; het te delen getal wordt een bit opgeschoven naar links in de ; werk registers lus6: lus1:
mov mov mov rlc mov dec djnz
teller,#008h r0,#get2ll a,@r0 a @r0,a r0 teller,lus1
;het gaat over 8 bytes ;hiermee starten ;getal lezen ;shiften ;opnieuw opslaan ;naar volgende wijzen ;8 keer doen
; we gaan kijken of het getal in de werk registers groter is dan ; de deler. Is dat zo, dan komt het verschil in de werk ; registers te staan. Omdat we dat niet op voorhand weten, ; gebruiken we de tussen registers om het tussenresultaat op te ; slaan.
lus2:
mov mov mov mov clr mov subb
teller,#004h r0,#werkll r1,#get1ll b,#tussenll c a,@r0 a,@r1
;aantal bytes te bewerken ;adres brongetal ;adres deler ;adres tussen ;carry op 0 ;byte ophalen ;verschil berekenen
; we hebben maar twee pointers. Daarom effe de adressen ; van plaats veranderen. xch xch mov
a,b a,r0 @r0,b
;inhoud omruilen ;inhoud omruilen ;uitkomst wegschrijven
; adressen terug op hun plaats zetten xch xch
a,r0 a,b
; adressen aanpassen dec dec dec
r0 r1 b
; moeten we 4 keer doen djnz
teller,lus2
158
; als de carry op 1 staat was werk
lus4
;als carry 1 naar lus4
; we copieren de inhoud van de tussen registers naar de werk ; registers.
lus3:
mov mov mov mov mov dec dec djnz
teller,#004h r0,#werkll r1,#tussenll a,@r1 @r0,a r0 r1 teller,lus3
;gaat over 4 bytes ;adres bestemming ;adres bron ;ophalen tussen ;schrijven naar werk ;pointers aanpassen ;4 keer herhalen
; als de carry op 1 staat was werk
cpl c mov teller,#004h mov r0,#uitll lus5: mov a,@r0 rlc a mov @r0,a dec r0 djnz teller,lus5 ; dit moeten we 32 keer herhalen djnz
5.9
;moeten omgekeerde hebben ;moeten 4 bytes shiften ;adres bestemming ;byte lezen ;carry inshiften ;terug wegschrijven ;pointer aanpassen
telbits,lus6
Vierkantswortel berekenen
In sommige toepassingen kan het nodig zijn de vierkantswortel te berekenen. In onderstaand voorbeeld leggen we uit hoe dat voor een geheel decimaal getal kan uitgevoerd worden. We wensen de wortel te berekenen van 66564. We verdelen het getal in groepjes van twee digits. We gebruiken een “,” om de groepjes aan te geven. ____________ \/ 6,65,64 We zoeken van het eerste groepje het grootste getal waarvan het kwadraat kleiner of gelijk is aan dat groepje. In dit geval is dat 2 want 2*2=4 en 3*3 zou 9 geven, wat te groot is.
159
_2_________ \/ 6,65,64 -4 --2 We schrijven de twee (is onderdeel van de uitkomst) bovenaan, nemen er het kwadraat van en trekken het van het groepje af. We laten het volgende groepje zakken. Het dubbele resultaat (2*2=4) zetten we voor het nieuw gevormde getal. We zoeken een getal d, zodat d*4d kleiner is of gelijk aan 265. De juiste waarde lijkt hier 5, want 5*45=225 __2_5_______ \/ 6,65,64 -4 --45 2 65 -2 25 ------40 We herhalen de vorige redenering (het dubbel van 25 is 50 en d*50d ≤ 4064) __2__5__8___ 6,65,64 -4 --2 65 -2 25 -----508 40 64 -40 64 --------0 \/
De uitkomst kan je vinden op: http://www.homeschoolmath.net/teaching/square-root-algorithm.php http://www.ehow.com/how_5081134_calculate-square-root-hand.html http://www.itl.nist.gov/div897/sqg/dads/HTML/squareRoot.html Deze redenering gaan we toepassen op een binair getal. Het voorbeeldprogramma neemt de wortel van een 8 bit geheel getal. Om de berekening uit te voeren gebruiken we een aantal hulpregisters. Bron werk probeer uit tel
bevat het getal waar we de wortel willen van berekenen daarin shiften we het brongetal per 2 bits bevat de waarde van (uit *4 +1) zal de uitkomst bevatten lusteller=aantal bits/2
160
Het programma is geschreven als een onderdeel van een ander programma. We gaan er van uit dat in bron reeds het getal staat waar we de wortel willen van berekenen. mov mov mov
werk,#000h uit,#000h tel,#004h
;op 0 zetten ;idem ;bron is 8 bits groot
; eerst zetten we in werk twee bits van bron lus:
mov rlc mov mov rlc mov mov rlc mov mov rlc mov
a,bron a bron,a a,werk a werk,a a,bron a bron,a a,werk a werk,a
;in accu om te shiften ;eerste bit ;terug bewaren ;inschuiven
;in accu om te shiften ;tweede bit ;terug bewaren ;inschuiven
; nu in probeer (uit *4 +1) mov rl setb rlc mov
a,uit a c a probeer,a
;in accu klaar maken ;*2 ;carry inshiften=*2+1 ;klaar ;in register schrijven
; we gaan kijken of het in werk kan mov clr subb jc
a,werk c a,probeer lus1
;gaan verschil berekenen ;mag niet meespelen ;carry=0 ok ;carry 1 zie verder
; de carry is 0. Werk wordt vervangen door het berekende ; verschil. In uit wordt een 1 ingeschoven mov mov setb rlc mov ljmp
werk,a a,uit c a uit,a lus2
;verschil in werk ;moet 1 in komen ;*2+1 ;terug op zijn plaats
; de carry was 1, het verschil mag niet in werk. In uit moet een
161
; 0 ingeschoven worden lus1:
mov rl mov
a,uit a uit,a
;moet 0 ingeschoven worden ;*2 ;terug op zijn plaats
; er is een stap van de itteratie uitgevoerd. Dat moeten we ; herhalen tot tel=0 lus2:
djnz
tel,lus
;herhaal
; de berekening is klaar. In uit staat de wortel
5.11 Referenties naar de ftp site Op de FTP site van de hogeschool is een database van gegevens rond microcontrollers samengebracht. De informatie is in mappen ondergebracht per type controller. Algemene informatie aangaande 8051 controllers zit in de map 8051 general. Voor de XC888 controller is er een specifieke map met alle informatie en beschikbare software. Op het internet kan je tal van application notes vinden over binaire berekeningen. Deze application notes zijn niet steeds 100% foutloos. Neem ze niet klakkeloos over!!!
162
Hoofdstuk 6
6.1
XC888 hardware units
Inleiding
De vorige hoofdstukken hebben de controller behandeld vanuit een software standpunt. Om een controller te kunnen programmeren moet je een goed inzicht hebben van de geheugenstructuur en de beschikbare instructies. Om een minimale I/O toe te laten werd in het vorige hoofdstuk gebruik gemaakt van de parallelle poorten en van het LCD scherm en de seriële poort die via drivers aanspreekbaar waren. Een controller heeft echter heel wat bijkomende hardware units aan boord. In het blokschema in figuur 6.1.1 kan je de I/O units terugvinden.
Figuur 6.1.1 Hardware units van de XC888 microcontroller Figuur 6.1.2 geeft een opsomming van de hardware mogelijkheden van de controller. De hardware units kunnen de CPU ondersteunen bij specifieke opdrachten, die in software niet of nauwelijks uitvoerbaar zijn: gebeurtenissen tellen, tijd afpassen (nauwkeurig), spanning meten, signalen genereren (o.a. PWM), communicatie en complexe berekeningen.
163
Figuur 6.1.2 Hardware mogelijkheden controller Het is niet de bedoeling om de “XC888 User’s Manual” hier op te nemen. De volgende paragrafen geven een beperkte greep uit de hardware mogelijkheden. Voor meer info en een bespreking van alle mogelijkheden verwijzen we naar de XC888 User’s Manual, de XC888 datasheet en de XC800 Architecture and instruction set Manual. Deze documenten kan je terugvinden op de FTP site van de campus en de www.infineon.com site. Vele van deze units kunnen interrupts gebruiken. Een interrupt laat toe dat de hardware ingrijpt in het verloop van een programma. Op deze manier kan een hardware gebeurtenis een bijhorend (sub)programma opstarten. De reactietijd van de controller op een gebeurtenis of “event” wordt zo geminimaliseerd.
6.2 6.2.1
Interrupt Inleiding
Wanneer een processor een taak uitvoert, bestaat die meestal uit een cyclische opeenvolging van opdrachten. In figuur 6.2.1.1 wordt dat weergegeven door het programmaverloop uit te zetten in de tijd. Binnen een cyclus wordt er op een bepaald moment gekeken of er een hardware gebeurtenis zich voordoet (vb. indrukken schakelaar). Wanneer dat niet het geval is, gaat de processor verder met het normale programmaverloop, en is het wachten tot de volgende test, alvorens het indrukken van de schakelaar getest (gezien) wordt. In de tussentijd is het programma blind voor variaties in de hardware. De reactietijd is dus afhankelijk van de lengte van het programma. Je zou ook meerdere keren
164
kunnen testen op de hardware gebeurtenis, maar dan wordt de duurtijd van het programma nodeloos langer als er geen gebeurtenissen zijn.
1 cyclus programma
Tijd Test HW Geen test HW
Test HW Geen test HW
Test HW Geen test HW
Figuur 6.2.1.1 Programmaverloop in de tijd.
Door hardware opgestart Hoofd programma
Hoofd programma
Initialisaties (geen interrupts)
Initialisaties (ook interrupts)
Taak 1
Taak 1
Interrupt routine
Neen
Test HW Ja
Speciaal 1
Speciaal 1
Speciaal 2
Speciaal 2
RETI Taak 2
Taak 2
Taak 3
Taak 3
Zonder interrupt
Met interrupt
Figuur 6.2.1.2 Flowcharts programma zonder- en met interrupt
165
In figuur 6.2.1.2 zijn twee flowcharts opgenomen. De flowchart zonder interrupt komt overeen met de tijdslijn uit figuur 6.2.1.1. Wanneer we interrupt gebruiken, wordt de test HW (test op Hard Ware gebeurtenis) overgelaten aan een apart circuit in de microcontroller, het interrupt systeem of interrupt controller. Het gewone programma test de gebeurtenis niet meer, en voert cyclisch de normale taken uit (taak 1, taak 2 en taak 3). Wanneer de interrupt controller de hardware gebeurtenis detecteert (ongeacht waar de CPU in het hoofdprogramma zit), wordt het hoofdprogramma onderbroken, en zal de interruptroutine opgestart worden. Na de RETI instructie (RETurn from Interrupt), wordt het hoofdprogramma verder uitgevoerd (vanaf de plaats waar het onderbroken werd). Van zodra interrupt gebruikt wordt, bestaat de flowchart uit minimaal 2 delen (hoofdprogramma en minimaal 1 interruptroutine), die NIET met elkaar verbonden zijn. Er is immers geen verband tussen de uitvoering van het hoofdprogramma en het moment waarop de hardware gebeurtenis zich voordoet.
6.2.2
Interrupt bronnen
Interrupt bronnen zijn hardware units die een toestandsverandering kunnen detecteren, en op die manier een interrupt kunnen genereren. Zo zullen niet alle aansluitklemmen van de controller een verandering in toestand kunnen signaleren doormiddel van interrupt. Hier volgt een opsomming van de mogelijke interrupt bronnen:
Systeembronnen: Timers: Pinnen: Seriële communicatie: ADC CORDIC MDU
WDT, PLL, FLASH, Brown out T0, T1, T2, T21, CCU6 EXT0-EXT6, T2ex, T21ex UART, UART1, SSC, MULTICAN
Een bron kan maar een interrupt genereren wanneer het via het programma wordt toegestaan. Na reset zijn dus alle interrupt bronnen uitgeschakeld!! Het programma moet dus initialisatie software bevatten die zowel de hardware als de CPU klaar zet om interrupts te genereren en te verwerken.
6.2.3
Interrupt vectoren
In de figuur 6.2.1.2 kon je zien dat er een aparte (deel)flowchart is voor de interruptroutine. Je kan een interruptroutine het best vergelijken met een hardware 166
geactiveerde subroutine. Die wordt niet opgestart door het programma via een lcall instructie, maar door de hardware via een signaal naar de CPU. Voor elke interrupt bron is er een apart signaal naar de processor, die op deze manier weet, welke interrupt routine opgestart moet worden (figuur 6.2.3.1).
Figuur 6.2.3.1 Interrupt vectoren en interrupt bron
167
Er kunnen dus meerdere interruptroutines aanwezig zijn. De programmeur moet de door hem geschreven interruptroutines beschikbaar maken voor de CPU. Dat gebeurt door de routines op welbepaalde plaatsen in het geheugen op te slaan. M.a.w. de startadressen van de interruptroutines liggen vast, en zijn afhankelijk van de interrupt bron. Die adressen worden interrupt vectoren genoemd. In figuur 6.2.3.1 staan de interrupt vectoren met bijhorende interrupt bron afgebeeld In de figuur kan je ook vaststellen dat sommige vectoren door verschillende bronnen gebruikt worden (vb. 0073h). In dat geval zal bij de aanvang van de interruptroutine nagegaan moeten worden wie de interrupt effectief heeft gegenereerd (Indien je, bij voorbeeld, in de groep XINTR9 alleen external interrupt 3 hebt toegestaan, dan hoef je natuurlijk de andere (die niet werden ingeschakeld) niet te testen).
6.2.4
Interrupt prioriteit
Interrupts worden vooral gebruikt wanneer de CPU snel moet reageren op een hardware gebeurtenis. Zo zal er informatie verloren gaan, wanneer de CPU niet snel genoeg inkomende data op een seriële verbinding verwerkt. Is er maar 1 interrupt bron, dan stelt zich geen probleem. Het hoofdprogramma kan enkel door die ene interrupt routine onderbroken worden. Meestal worden meerdere Interrupts gebruikt. Ze kunnen dan ook gelijktijdig of kort na elkaar voorkomen. In dat geval kan het zijn dat er voorrang gegeven moet worden aan bepaalde interrupt routines. Nemen we een auto als voorbeeld. Wanneer een embedded systeem in een wagen een botsing detecteert, dan krijgt dit signaal best voorrang op het signaal van het gaspedaal (versnellen is immers niet erg zinvol, de airbag gebruiken wel). Worden geen prioriteiten gebruikt, dan zal de CPU het interrupt signaal verwerken dat eerst komt. Alle andere signalen moeten wachten tot de opgestarte interrupt routine is afgewerkt. Pas daarna wordt opnieuw een interrupt signaal verwerkt. Een interrupt kan dus pending (hangende of wachtend op verwerking) zijn. Om een inkomend interrupt signaal niet verloren te laten gaan, beschikt elke interrupt bron over een bit die als geheugen werkt voor de hardware gebeurtenis. In sommige gevallen zal de CPU deze pending bit zelf clearen na de uitvoering van de interrupt routine, in andere gevallen (wanneer er meerdere interrupt bronnen zijn per vector), zal dat via software moeten gebeuren voor het einde van de interrupt routine. Vermits er maar 1 pending bit is per bron, moet die terug op 0 staan voor de volgende gebeurtenis. Als dat niet zo is, gaan er interrupts verloren. Worden prioriteiten gebruikt, dan krijgen interrupt bronnen voorrang t.o.v. andere bronnen, en mogen ze zelfs de interrupt routines van andere bronnen onderbreken. De XC888 beschikt over 4 prioriteit niveaus (standaard bij 8051 is dat 2).
168
Figuur 6.2.4.1 laat zien wat er gebeurt voor 2 interrupt routines op een verschillend niveau van prioriteit.
Prioriteit Introut2
Introut1
bron2
bron1
bron1
bron2
Hoofdprogrmma
Tijd
bron1 pending
Figuur 6.2.4.1 Wisselwerking interruptroutines We doorlopen de figuur volgens de tijdsas (links naar rechts): 1. Het hoofdprogramma wordt onderbroken door een signaal van bron2. 2. Introut2 wordt opgestart, Het hoofdprogramma ligt stil. 3. Er komt een signaal van bron1. De bron heeft geen hogere prioriteit dan bron2, de interrupt moet wachten (bron1 pending). 4. Introut2 is afgewerkt. Het hoofdprogramma wordt opgestart voor 1 instructie, waarna het signaal van bron1 verwerkt wordt. 5. Na de uitvoering van de introut1 loopt het hoofdprogramma tot er opnieuw een signaal komt van bron1 en de bijhorende routine opgestart wordt. 6. Er komt een signaal van bron2. Dat heeft een hogere prioriteit dan bron1, waardoor de introut1 onderbroken wordt, en introut2 van start gaat 7. Introut2 is klaar, de CPU gaat verder met introut1 8. Introut1 is klaar waardoor het hoofdprogramma terug verder gezet wordt. Het al of niet toekennen van een hogere prioriteit aan een interruptroutine is afhankelijk van de lengte van de interruptroutine en het tijdsinterval tussen de interrupts. Figuur 6.2.4.2 laat zien wat er kan gebeuren indien een interruptroutine die relatief lang duurt een te hoge prioriteit krijgt.
169
Prioriteit Introut2
Introut1
bron1 pending
bron1
bron1
bron1
bron2
Hoofdprogrmma
Tijd
Nieuwe interrupt bron1 vorige gaat verloren
Figuur 6.2.4.2 Pending tijd te lang Binnen elk van de 4 interrupt prioriteiten is er een volgorde waarmee de CPU de interrupt bronnen afvraagt. Dat worden in de 8051 manuals ook prioriteiten genoemd. Gelet op het feit dat het hier slechts over een prioriteit gaat in de volgorde van scannen, en niet het onderbreken van elkaars routines, is dat geen echte prioriteit, en wordt dan verder ook niet besproken. 6.2.5
Interrupt initialisatie
In figuur 6.2.5.1 is voor een beperkt aantal interrupt bronnen het signaalpad weergegeven. Alle onderdelen aan de linker kant van de grijze lijn behoren tot de hardware van de unit die de interrupt kan genereren. Laten we als voorbeeld de UART nemen. De initialisatie en de werking van de UART is niet opgenomen in deze figuur. De twee signalen van de UART geven aan dat er een byte verzonden (TI) of ontvangen (RI) werd. Dat wordt opgeslagen in de RI en TI bits die de interrupt pending bits vormen. Wil het signaal tot bij de CPU komen (rechts van de grijze stippellijn), moeten de schakelaars ES en EA gesloten worden. De schakelaar EA is gemeenschappelijk voor alle interrupt bronnen en wordt de algemene interrupt enable schakelaar genoemd (Enable All). De ES schakelaar is er enkel voor de seriële poort. De andere bronnen hebben hun eigen individuele interrupt enable schakelaar. De IP schakelaars laten toe om de prioriteit in te stellen. De hardware russen beide grijze lijnen wordt de interrupt controller genoemd. 170
Figuur 6.2.5.1 Interrupt pad De schakelaars in de figuur worden aangestuurd door bitvariabelen in de SFR’s. De initialisatie bestaat uit volgende stappen: 1. Initialisatie van de betrokken hardware unit, zodat die doet wat de bedoeling is (vb. UART instellen voor juiste baud rate en frame format, pinnen activeren)
171
2. Initialisatie van de interrupt controller (sluiten van de individuele interrupt enable schakelaar, sluiten van de algemene interrupt enable schakelaar, en indien gewenst de juiste prioriteit instellen) 3. Interruptroutine moet op juiste adres in het geheugen staan 6.2.6
Vormvereisten interrupt routines
Een interrupt routine moet aan een aantal voorwaarden voldoen wil je voorkomen dat de toepassing vast loopt: 1. Interrupt routines moeten zo kort mogelijk zijn. 2. Interrupt routines mogen alleen relevante gegevens aanpassen. 3. Interrupt routines MOETEN afgesloten worden met de RETI instructie Verklaring: 1: Indien interrupt routines te lang duren kunnen ze de werking van andere interrupts hypothekeren (figuur 6.2.4.2), of ze kunnen het hoofdprogramma zo lang onderbreken dat dit schijnbaar te traag wordt. In interrupt routines worden dan ook: GEEN DELAY’s gebruikt geen lussen die de uitvoeringstijd lang maken (vb. oneindige loops) aansturen van trage I/O (vb. boodschappen naar LCD of seriële poort) vermeden wachten op externe gebeurtenissen (vb. indrukken schakelaars) vermeden 2: Zowat elk programma zal een variabele testen of wijzigen. Om dit te doen wordt meestal gebruik gemaakt van de accu en PSW. In het hoofdprogramma worden die ook gebruikt. De interrupt routine onderbreekt het hoofdprogramma, en moet er voor zorgen dat (door gebruik te maken van PUSH en POP instructies) de registers die in het hoofdprogramma in gebruik zijn hun originele waarde behouden (zie voorbeeldprogramma). LET OP!! Dit is ook van toepassing op de SFR’s. Doordat er verschillende pagina’s en mappen zijn, moet een interrupt routine die de I/O gebruikt, de originele selecties herstellen na gebruik. (zie voorbeeldprogramma) 3: Indien een interrupt routine niet afgesloten wordt met een RETI, maar met een gewone RET instructie, zal het interrupt systeem geen verdere interrupts verwerken. De RETI instructie grijpt namelijk in op de interrupt controller, en hersteld het scannen voor volgende interrupts. Voorbeeld van programma met een timer 0 interrupt Het programma gebruikt timer 0 om interrupts te genereren (voor de werking van de timer verwijzen we naar de volgende paragraaf). Het is de bedoeling om op interrupt 172
basis op de LED’s een looplicht te laten zien dat elke seconde 1 plaats opschuift. In het hoofdprogramma laten we een teller zien op het LCD scherm. loopl div1 div2 teller
main:
lus:
equ equ equ equ
020h 021h 022h 023h
;wordt gebruikt als geheugen looplicht ;hulpregisters introut ;idem ;register om teller op te slaan
org ljmp
00000h main
;start adres programma ;spring over de interrupt vector over
org ljmp
0000bh introutt0
;start adres interrupt routine timer 0 ;spring naar interrupt routine
mov lcall mov lcall lcall mov mov mov mov lcall lcall mov lcall mov lcall mov lcall inc ljmp
sp,#07fh initlcd a,#003h lcdoutchar initleds loopl,#01111111b div1,#250 div2,#192 teller,#000h initt0 initint a,#00dh lcdoutchar a,teller lcdoutbyte a,#020d delaya0k05s teller lus
;LCD scherm klaar voor gebruik ;code om cursor uit te schakelen ;afdrukken ;LED's klaar voor gebruik ;startwaarde looplicht ;software deler introutt0 ;idem ;startwaarde teller op 0 ;timer 0 klaar zetten voor gebruik ;interrupts toelaten ;cursor vooraan de lijn ;teller afdrukken ;wachten gedurende 1s ;20 keer 0,05s=1s ;teller +1 ;herhaal
; initto zet de timer 0 klaar voor gebruik. We gebruiken de timer in de 8 bit auto ; reload mode. De teller telt aan cyclustijd: 1/12000000 s. ; In de 8 bit auto reload mode delen we door 250. ; Er komen dus interrupts alle 1/48000 s ; we zullen verder in software moeten afdelen initt0:
mov mov mov setb ret
tl0,#6 th0,#6 tmod,#00000010b tr0
;teller telt omhoog en geeft overflow bij 256 ;reload waarde voor tl0 ;t0 in 8 bit auto reload, timer, no gate ;timer starten
; initint zet de interrupts klaar. initint:
setb setb ret
et0 ea
;enable timer 0 interrupts ;algemene schakelaar sluiten
; Dit is de interrupt routine. Ze wordt alle 12000000/250 s opgestart. ; Dat is 48000 per seconde. Door te delen door 250 en 192 komen we aan ; 1 interrupt per seconde introutt0:
djnz
div1,intend
;indien niet 0 einde introutt0
173
mov djnz mov
div1,#250 div2,intend div2,#192
;indien 0 herladen ;indien niet 0 einde introutt0 ;indien 0 herladen
; hier komen we na 48000 interrupts, dat wil zeggen na 1 seconde. Omdat we nu registers ; gaan gebruiken die in het hoofdprogramma ook gebruikt worden, moeten we pushen en poppen push push mov rl mov mov pop pop
acc psw a,loopl a loopl,a p3_data,a psw acc
;aanpassen looplicht ;door te shiften ;terug in geheugen plaatsen ;laten zien ;registers herstellen
; De psw werd in de interrupt routine niet aangepast, en hadden we dus niet moeten ; bewaren op de stack. intend:
reti
#include
“xcez1.inc”
Stel dat het hoofdprogramma de map en de pages zou gebruiken (omdat het dingen doet met poorten en vb. MDU) dan moeten de vetgedrukte lijnen aan de interrupt routine toegevoegd worden: ; Dit is de interrupt routine. Ze wordt alle 12000000/250 s opgestart. ; Dat is 48000 per seconde. Door te delen door 250 en 192 komen we aan ; 1 interrupt per seconde introutt0:
djnz mov djnz mov
div1,intend div1,#250 div2,intend div2,#192
;indien niet 0 einde introutt0 ;indien 0 herladen ;indien niet 0 einde introutt0 ;indien 0 herladen
; hier komen we na 48000 interrupts, dat wil zeggen na 1 seconde. Omdat we nu registers ; gaan gebruiken die in het hoofdprogramma ook gebruikt worden, moeten we pushen en poppen push push push mov push mov mov rl mov mov pop pop pop pop
acc psw syscon0 syscon0,#004h port_page port_page,#000h a,loopl a loopl,a p3_data,a port_page syscon0 psw acc
;map select ;basis map kiezen ;pagina bewaren ;nu is p3_data toegankelijk ;aanpassen looplicht ;door te shiften ;terug in geheugen plaatsen ;laten zien ;pagina herstellen ;map herstellen ;registers herstellen
; De psw werd in de interrupt routine niet aangepast, en hadden we dus niet moeten ; bewaren op de stack. intend:
reti
174
Syscon0 en port_page registers (zoals meerdere page registers in de XC888) hebben hun eigen hardware back-up registers. Hierdoor kan de push en mov instructie vervangen worden door 1 move instructie, waarbij automatisch de huidige waarde bewaard wordt in de back-up registers. Het herstellen gebeurt ook door een mov i.p.v. een pop. Dit systeem laat toe om 1 instructie te besparen per SFR in de interrupt routine. Het nadeel van het systeem is dat de programmeur goed moet bijhouden waar welk back-up register gebruikt wordt. Via de gewone stack kost het een extra instructie, maar dat systeem is makkelijker bij te houden. Voorbeeldprogramma uurwerk Dit programma drukt een uurwerk af op het LCD scherm. Omdat het afdrukken een trage gebeurtenis is, doen we dat vanuit het hoofdprogramma. De berekening van de klok zit in de interrupt routine. Door een bit te zetten in de interruptroutine geven we aan wanneer de tijd aangepast is. Op die manier wordt voorkomen dat het hoofdprogramma permanent data afdrukt die niet aangepast wordt. Merk op dat het programma het kristal als tijdsbasis gebruikt. De RC keten laat een nauwkeurigheid toe van 5s op 3 minuten (2,78%). Met het kristal wordt dat 3s/dag.
loopl div1 div2 teller uur min sec bitsec
main:
lus:
equ equ equ equ equ equ equ equ
020h 021h 022h 023h 024h 025h 026h 027h.0
;wordt gebruikt als geheugen looplicht ;hulpregisters introut ;idem ;register om teller op te slaan ;opslaan uren ;opslaan minuten ;opslaan seconden ;bit om aan te geven tijd aangepast
org ljmp
00000h main
;start adres programma ;spring over de interrupt vector over
org ljmp
0000bh introutt0
;start adres interrupt routine timer 0 ;spring naar interrupt routine
mov lcall lcall mov lcall lcall lcall mov mov mov mov mov mov clr lcall lcall mov lcall jnb mov
sp,#07fh xcsw2xtal initlcd a,#003h lcdoutchar initleds lcdlighton loopl,#01111111b div1,#250 div2,#192 uur,#020 min,#010 sec,#000 bitsec initt0 initint a,#00dh lcdoutchar bitsec,$ a,uur
;overschakelen van RC klok naar kristal ;LCD scherm klaar voor gebruik ;code om cursor uit te schakelen ;afdrukken ;LED's klaar voor gebruik ;achtergrond verlichting aan ;startwaarde looplicht ;software deler introutt0 ;idem ;startwaarde uurwerk
;timer 0 klaar zetten voor gebruik ;interrupts toelaten ;cursor vooraan de lijn ;afdrukken van uu:mm:ss ;wachten op verandering vanuit introutt0
175
lcall lcall mov lcall mov lcall lcall mov lcall mov lcall lcall ljmp
hexbcd8 lcdoutbyte a,#':' lcdoutchar a,min hexbcd8 lcdoutbyte a,#':' lcdoutchar a,sec hexbcd8 lcdoutbyte lus
;decimaal afdrukken
;herhaal
; initto zet de timer 0 klaar voor gebruik. We gebruiken de timer in de 8 bit auto ; reload mode. De teller tellen aan cyclustijd: 1/12000000 s. ; In de 8 bit auto reload mode delen we door 250. ; Er komen dus interrupts alle 1/48000 s ; we zullen verder in software moeten afdelen initt0:
mov mov mov setb ret
tl0,#6 th0,#6 tmod,#00000010b tr0
;teller telt omhoog en geeft overflow bij 256 ;reload waarde voor tl0 ;t0 in 8 bit auto reload, timer, no gate ;timer starten
; initint zet de interrupts klaar. initint:
setb setb ret
et0 ea
;enable timer 0 interrupts ;algemene schakelaar sluiten
; Dit is de interrupt routine. Ze wordt alle 12000000/250 s opgestart. ; Dat is 48000 per seconde. Door te delen door 250 en 192 komen we aan ; 1 interrupt per seconde introutt0:
djnz mov djnz mov
div1,intend div1,#250 div2,intend div2,#192
;indien niet 0 einde introutt0 ;indien 0 herladen ;indien niet 0 einde introutt0 ;indien 0 herladen
; hier komen we na 48000 interrupts, dat wil zeggen na 1 seconde. Omdat we nu registers ; gaan gebruiken die in het hoofdprogramma ook gebruikt worden, moeten we pushen en poppen setb push push mov add mov cjne mov mov add mov cjne mov mov
bitsec acc psw a,sec a,#001h sec,a a,#60,dag2 sec,#000h a,min a,#001h min,a a,#60,dag2 min,#000 a,uur
;uurwerk wordt aangepast
;berekening uurwerk in hex
176
intend:
add mov cjne mov mov mov rl mov pop pop reti
#include
“xcez1.inc”
dag2:
6.3 6.3.1
a,#001 uur,a a,#24,dag2 uur,#000h a,loopl p3_data,a a loopl,a psw acc
;terug in geheugen plaatsen ;laten zien (zit ook nog looplicht in)
;registers herstellen
Timer0 en timer1 Inleiding
De timers 0 en 1 zijn beschikbaar in zowat alle 8051 microcontrollers. Ze waren aanwezig in het originele ontwerp van Intel (1980) en zijn als minimum timers overgenomen in alle nieuwe ontwerpen. Beide timers zijn (op 1 uitzondering na) 100% compatibel. In onderstaande tekst wordt enkel timer 0 besproken. De naam “timer” laat vermoeden dat het gaat over hardware units om tijd af te passen. In realiteit is de kern van de unit een teller (counter) die “gebeurtenissen” kan tellen. Als die gebeurtenissen via een externe aansluiting worden aangelegd, wordt de benaming counter gebruikt. Worden inwendige systeemklok pulsen geteld, dan wordt de benaming timer gebruikt. Omdat het onderscheid tussen beide minimaal is, worden beide benamingen door elkaar gebruikt. Sommige fabrikanten spreken dan ook van counter/timer. In figuur 6.3.1.1 staan de 4 modes waarin de timers gebruikt kunnen worden. Mode 0 is een compatibiliteitsmode met de 8048 controllers. Deze mode wordt in 8051toepassingen niet gebruikt. Mode 3 wordt enkel gebruikt wanneer er te weinig timers/counters beschikbaar zijn. In deze mode wordt timer 0 ontbonden in 2 timers. Deze mode wordt uitzonderlijk toegepast in de kleinste 8051 derivaten die maar over 2 timers beschikken. De modes 1 (16 bit timer/counter) en 2 (8 bit auto reload mode) zijn de twee belangrijkste toepassingsvormen. Alleen deze twee worden verder besproken.
177
Figuur 6.3.1.1 T0 en T1 modes
6.3.2
16 bit timer/counter
In figuur 6.3.2.1 staat het blokdiagram afgebeeld van de timer0.
Figuur 6.3.2.1 Blokdiagram timer0 16 bit mode De registers TL0 en TH0 (beide 8 bit SFR’s) worden door de hardware als 1 geheel aanzien, en vormen een 16 bit counter. Wanneer de counter niet telt (TR0 bit=0)
178
kunnen beide registers geschreven worden met een willekeurige waarde. Ze kunnen altijd uitgelezen worden. LET OP!! wanneer de counter loopt is het mogelijk dat een foutieve waarde uitgelezen wordt. De counter moet immers per byte uitgelezen worden. Het is best mogelijk dat de 16 bit waarde tijdens de 2 leesbeurten aangepast wordt, waardoor de uitlezing foutief is (figuur 6.3.2.2).
TH0
TL0
00
fe r0
00
ff
mov r0,TL0
ff r1
01
00
01
01
01
02
mov r1,TH0
01
Figuur 6.3.2.2 Foutief uitlezen 16 bit timer/counter Aan de linker kant is de inhoud van de counter afgebeeld. We gaan er van uit dat de counter continu aangepast wordt t.g.v. een inkomend signaal. Aan de rechterkant staan de 2 mov instructies afgebeeld die de counter uitlezen. Uiterst rechts staan de getallen die uiteindelijk in de registers r0 en r1 terecht komen. Uitlezen doe je best wanneer de counter uitgeschakeld is. Hierdoor kunnen wel pulsen verloren gaan. Telt de teller relatief traag (t.o.v. uitvoeringstijd instructies), dan kan je overwegen om de counter meerdere keren na elkaar uit te lezen, tot twee opeenvolgende leesbeurten dezelfde waarde opleveren.
Figuur 6.3.2.3 Overflow bit 179
In figuur 6.3.2.3 zie je dat de 16 bit counter over een overflow bit beschikt. Deze wordt op 1 gezet als de counter waarde ffffh+1 wordt. De counter wordt op 0000h gezet en de TF0 vlag komt op 1. Zijn interrupts ingeschakeld, dan zal de CPU de overflow vlag automatisch clearen wanneer de interrupt routine opgestart wordt. Worden geen interrupts gebruikt, dan blijft de vlag op 1 staan, ook wanneer de counter opnieuw een overflow heeft. Je kan de vlag altijd software matig terug op 0 zetten. De TF0 vlag is ook de interrupt pending bit. Ze kan een overflow “onthouden”. De figuur 6.3.2.3 laat zien dat er twee mogelijke bronnen zijn voor de inkomende pulsen. Indien de externe klem T0 wordt gekozen, dan spreken we van counter werking (Fmax=500kHz bij een systeemklok van 24 Mhz). Wordt de systeemklok gekozen, dan wordt de unit een timer genoemd. In het laatste geval tellen we pulsen met een vaste tijdsbasis nl. één puls alle 83ns, of de dubbele periode van de systeemklok. De selectie tussen counter- en timermode gebeurt via de controlebit T0S.
Figuur 6.3.2.3 Counter/timer mode Interrupts kunnen gegenereerd worden na een bepaalde tijd, of na een bepaald aantal gebeurtenissen. Omdat de interrupt afkomstig is van een overflow (ffffh+1=0000h en een interrupt), kan het noodzakelijk zijn de counter te laden met een startwaarde. Wil je na 100 gebeurtenissen een interrupt, dan al de startwaarde ffffh+1-64h=ff9ch zijn. Na elke interrupt zal de programmatuur de counter moeten herladen (reloaden). Omdat hierbij de counter gestopt moet worden, kunnen inkomende pulsen verloren gaan. De klem T0 kan via het MODPISEL SFR toegekend worden aan 2 verschillende ingangspinnen (T0_0 en T0_1). De counter stoppen en starten gebeurt via de logica uit figuur 6.3.2.4.
180
Figuur 6.3.2.4 Aan/uit controle counter De TR0 bit is vergelijkbaar met een hoofdschakelaar. Die moet op 1 wil het controlesignaal actief worden. Door de GATE0 bit op 1 te zetten kan je het al of niet actief zijn van het controlesignaal laten afhangen van een extern signaal. Dit laat toe om de periode van een puls te meten. De puls wordt aangelegd aan de EXINT0 klem. De counter wordt in de timer mode gezet, en de GATE0 bit op 1. De counter telt nu alleen de systeemklok als de ingangsklem hoog is. In het andere geval wordt de counter gestopt. De EXTINT0 klem kan bovendien een interrupt genereren bij een verandering van signaal. Op die manier hoef je de pin niet te pollen om te weten wanneer de puls ten einde is (zie volgende paragraaf voor een gedetailleerde uitleg).
6.3.2.1
16 bit timers: periodemeting
Bij tijdsmetingen van events (tijdsinterval of periode) worden timers meestal 'gated' gebruikt. De signaalpuls waarvan we de tijdsduur willen kennen wordt m.a.w. gebruikt als schakelsignaal om een kloksignaal met gekende periode door te schakelen naar het tellerregister. Zolang de signaalpuls duurt zal de teller zal worden geïncrementeerd. Indien we het tellerregister voorafgaandelijk clearen, kan nadat de puls verstreken is de tellertoestand gelezen worden. De tellerwaarde is dan een maat voor de verstreken tijd uitgedrukt in klokperiodes. Voor een gekende klokperiode kan hieruit dan de verstreken tijd worden berekend (figuur 6.3.2.1.1). Als kloksignaal wordt meestal het interne oscillatorsignaal gedeeld door 2 gebruikt, wat er op neer komt dat de teller iedere machinecyclus geïncrementeerd wordt. Indien gewenst kan ook een extern kloksignaal op de T0 of T1 ingang als tellerklok gebruikt worden. Men moet het te meten pulssignaal aanleggen aan de INT0/P3.2 ingang, die naast de 'gate'-functie ook tegelijk als interruptingang kan aangeschakeld worden. Dit heeft het bijkomende voordeel dat bij het beëindigen van de puls (event) ook direct een interrupt kan worden gegenereerd, die de routine voor de tijdsmeting opstart.
181
OSC
Div 2
Set&Cleared door hardware C/T =0 TL0 (8 Bits)
T0 pin
5V Gnd
Interrupt TF0
C/T =1 ‘1’ Gate
Extern event
TH0 (8 Bits)
TR0
Control &
INT0 pin
>1
Resetten voor intervalmeting
t INT0 kan gelijktijdig gebruikt worden als een interruptingang en als gate.
Wat is de meetonzekerheid, nauwkeurigheid en het bereik van de timer? 166.7nSEC onzekerheid
166.7nSEC onzekerheid
t
Timerincrements elke 166.667nSEC.
Figuur 6.3.2.1.1 Meetonzekerheid bij tijdsmeting van het 'gating'signaal. Afhankelijk van de oscillatorfrequentie zal de teller met een bepaalde snelheid worden geïncrementeerd. Voor een klokfrequentie van 12 MHz is dit 166.7ns. Door het feit dat het 'event' (de te meten puls) asynchroon met het clocksignaal van de teller verloopt zal er een meetonzekerheid bestaan van maximum 166.7ns aan de beide flanken van de te meten puls. Dit resulteert in een onzekerheid van 333.4ns op de tijdsmeting van de puls. Dit is vooral nadelig bij een kleine pulsbreedte (procentuele fout groot). De nauwkeurigheid van de oscillatorfrequentie bepaald ook deze van de pulsbreedte, dit is meestal geen probleem daar de oscillatorfrequentie meestal wordt afgeleid van een quartskristal met een nauwkeurigheid van enkele ppm. Het bereik van de timer wordt bepaald door de mode waarin de timer wordt geschakeld, het grootste bereik (65536 counts of 10.923ms@12MHz) wordt bekomen als 16 bit teller in mode1. Softwarematig kan echter het bereik van de timer worden uitgebreid bij eenzelfde resolutie (clockfrequentie). Dit kan door de timeroverflows interrupts te laten genereren (IRQ Timer) die dan softwarematig worden geteld. Op deze manier kunnen we toch een timer implementeren met een praktisch onbeperkt bereik en een hoge resolutie (figuur 6.3.2.1.2).
182
Teller register 16 bit
10923µSEC @12MHz
10923µSEC @12MHz
10923µSEC @12MHz
10923µSEC @12MHz
4235µSEC @12MHz
FFFFh
Timer overflow
C/T=0 TR0=1 GATE=1 RESET TH0,TL0
108Bh
0000h
t IRQ Timer
IRQ Timer
IRQ Timer
IRQ Timer Stop: Gate dicht
Start meting Gatesignaal
t Gate tijd= 4x10923µSEC +4235 µSEC
IRQ gate
IRQ Timer : INC “COUNTER” IRQ gate : lees TH0, TL0 en “COUNTER” en bereken de tijd
Overflows tellen COUNTER
TH0
TL0
Figuur 6.3.2.1.2 Uitbreiding meetbereik timer
6.3.2.2
16 bit genereren van periodische timeroverflows of 'timeticks'
In de meeste systemen moeten er, naast een background taak(hoofdprogramma), op periodische basis een aantal andere taken uitgevoerd worden. De meest gebruikelijke manier om dit te implementeren is deze taken als interruptroutine uit te voeren die periodisch wordt opgestart aan de hand van een timer interrupt. Er zijn een aantal mogelijkheden om dit te verwezenlijken met een timer. 16 bit TIMER (mode1) met software reload. Wanneer we timer 0 of timer 1 in mode1 schakelen en starten, zal hij bij elke machinecyclus geïncrementeerd worden en na 65536 counts via een overflow weer vanaf 0000h verder tellen. Door interrupts te laten genereren bij timeroverflow kunnen we zo periodisch om de 10.923ms@12MHz een taak laten uitvoeren. Het
183
nadeel van deze methode is dat we een vaste periode verkrijgen als tijdsbasis (figuur 6.3.2.2.1). Timer overflow FFFFh
16 bit timer (mode1) zonder reload
0000h
t
10923µSEC @12MHz
TimerIRQ
TimerIRQ
TimerIRQ
Figuur 6.3.2.2.1 Time tick van 10.923 ms.
Een oplossing voor dit probleem is het preloaden van de tellerregisters (THx,TLx) met een bepaalde waarde (bv. XXYY) en dan pas de teller te starten. Op deze manier zal de teller veel vlugger zijn eindwaarde bereiken en een interrupt genereren bij het overlopen naar 0000h. Wanneer we nu in de interruptroutine, die als gevolg van de overflow opgestart wordt, de timer even stoppen om hem weer te herladen met de waarde XXYY en hem daarna weer starten, dan zal zich na de ingestelde periode weer een overflow voordoen. De periode wordt zo op een softwarematige manier aanpasbaar (figuur 6.3.2.2.2). De tijd tussen de overflows wordt gelijk aan Tp (10000h-xxyyh)*2/f, plus de tijd nodig om de timer softwarematig te herladen, plus de interruptlatency tijd van de microcontroller. In deze formule is xxyyh de reload waarde van de timer, en f de oscillatorfrequentie. De interruptlatency tijd is de tijd die de controller nodig heeft na het genereren van de interruptrequest (IRQ) tot het uitvoeren van de eerste instructie in de interruptroutine. Deze tijd is afhankelijk van het type instructie die de controller uitvoert bij het opwekken van de IRQ. Niet alle instructies duren immers even lang. Dit heeft timingjitter als gevolg tussen de opeenvolgende timeroverflows. Voor een aantal toepassingen heeft dit geen gevolgen, maar indien we deze 'timeticks' (periodische interrupts) gebruiken voor het genereren van timingsignalen op poortpinnen van de controller, of voor het updaten van een RTC dan zal dit ook hier aanleiding geven tot timingjitter, wat hier zeker niet toelaatbaar is. Zijn er nog andere interrupts mogelijk, dan wordt de tijdsafpassing helemaal onzeker.
184
Er moet dus een betere manier gevonden worden (zie reload modes T0 en T1, maar vooral de andere timers)! Timer overflows FFFFh
XXYY “preload”
Timing jitter! 0000h
t
(FFFF-XXYY) . 2/f
TimerIRQ
TimerIRQ
Interruptroutine bij timeroverflow: stop timer: TR0=0 reload timer: TH0=XX TL0=YY start timer: TR0=1 “toepassing” bv. Sturen van poortpinnen RETI
TimerIRQ
TimerIRQ
TimerIRQ
Interrupt latency time = constant !!! timingjitter = geschikt als “timetick”
Accumulatie van timingfouten!! Fig. 6.3.2.2.2 6.3.3
Timingjitter bij software reload van de timer
8 bit auto reload mode
In figuur 6.3.3.1 is het blokdiagram voor de 8 bit auto reload mode weergegeven. De werking van de counter/timer is bijna identiek aan de 16 bit mode. De enige afwijking zit in het counter deel van het systeem. De counter bestaat uit TL0 en vorm op die manier een 8 bit teller. Die heeft een maximale telwaarde van ffh. Wanneer ffh+1 bereikt wordt, wordt een overflow gegenereerd. De belangrijkste afwijking is het herladen (reloaden) van TL0 met de waarde uit TH0 wanneer er een overflow is. Dit herladen gebeurt in hardware, waardoor de counter niet gestopt moet worden, waardoor er geen inkomende pulsen verloren kunnen gaan. De programmeur moet op voorhand de waarde in de TH0 en TL0 registers schrijven.
185
Figuur 6.3.3.1 8 bit reload mode 6.3.3.1
8 bit TIMER met autoreload voor het genereren van timeticks
De onzekerheid bij het genereren van exacte tijdsintervallen (paragraaf 6.3.2.2) kan alleen worden opgelost indien de timer kan herladen worden zonder de softwarematige tussenkomst van de controller. De 8 bit auto reload mode biedt deze mogelijkheid. timeroverflows FFh
THx reload waarde
(100h-reload).2/f
t
00h TLx en THx laden
Start timer
timer IRQ
timer IRQ
timer IRQ
timer IRQ
timer IRQ
Fig. 6.3.3.1.1 Hardware reload van de timer
186
De timingjitter is hier verdwenen en de periode tussen de interrupt requests wordt enkel nog bepaald door de clockfrequentie en de reloadwaarde van de timer. Deze mode heeft het voordeel dat ze wel kan gebruikt worden voor bv. het incrementeren van een RTC. Zelfs indien de interruptverwerking even moet wachten, start de volgende tijdsperiode op het juiste moment (hardware reload). Het nadeel van deze timermode is dat er bij de standaard 8051 timers (TIMER0 &1) slechts met een 8-bit teller kan gewerkt worden. De maximale periode tussen de interrupts wordt daardoor beperkt tot 256 keer de clockperiode (42,7µs@12MHz) wat voor sommige systemen veel te klein is. Sommige 8051 varianten hebben een bijkomende 16-bit timer(s) (o.a. TIMER2) met autoreload mogelijkheden, wat het probleem oplost. Dit euvel kan echter bij de basistimers TIMER0&1 softwarematig worden weggewerkt door in de interruptroutine het aantal gegenereerde IRQ's te tellen, en pas na een bepaald aantal de toepassing uit te voeren (figuur 6.3.3.1.2). 'COUNTER' is de variabele die na elke IRQ wordt gedecrementeerd en die wanneer hij nul wordt softwarematig weer wordt herladen met de waarde 'SKIP_IRQ'. Ook bij de initialisatiefase van het systeem moet COUNTER met deze variabele worden geladen.
timerIRQ
Initialisatie: MOV COUNTER, #SKIP_IRQ
DJNZ COUNTER,BACK MOV COUNTER,#SKIP_IRQ
Toepassing
BACK: RETI
Figuur 6.3.3.1.2 Software verlengen van het tijdsinterval 6.3.4
Controleregisters
De controle en het aansturen van timer0 en timer1 gebeurt via de registers uit figuur 6.3.4.1. TL0, TH0, TL1 en TH1 zijn de registers die door de hardware gebruikt worden om te tellen. Via IEN0 kunnen interrupts ingeschakeld worden. TCON en TMOD worden in figuur 6.3.4.2 en 6.3.4.3 toegelicht. Merk op dat het TCON register 4 bits bevat die niets te maken hebben met de aansturing van T0 of T1. Het komt wel meer voor dat niet alle bits in een SFR dezelfde functie controleren. Om het register te laden kan je dus best bit-instructies gebruiken (indien bit adresseerbaar), of ORL
187
(bits op 1 zetten) of ANL (bits op 0 zetten) zodat enkel de specifieke bits van toestand veranderen.
Figuur 6.3.4.1 Controleregisters T0 en T1
Figuur 6.3.4.2 TCON register
188
Figuur 6.3.4.3 TMOD register
189
6.4
Timer2 en timer 21
6.4.1
Inleiding
De meeste 8051 derivaten bevatten ook een timer 2. In tegenstelling met T0 en T1 is de werking van T2 niet bij alle controllers dezelfde. Het is belangrijk de datasheet te raadplegen alvorens software te schrijven. De XC888 heeft T2 en T21. Dit zijn 2 quasi identieke timers. Voor de bespreking beperken we ons dan ook tot T2. LET OP!! De T2 registers zijn toegankelijk in RMAP=0 terwijl de T21 registers in RMAP=1 zitten. RMAP kan aangepast worden via SYSCON0 T2 en T21 zijn 16 bit timers met bijkomende mogelijkheden t.o.v. T0 en T1.
6.4.2
Basis counter T2
In figuur 6.4.2.1 is de basiscounter opgenomen van T2. Dit deel zal, ongeacht de mode waarin de timer gebruikt wordt, steeds terugkeren.
Figuur 6.4.2.1 Basis counter T2 (T21) De THL2 counter is een 16 bit teller die uit 2 8 bit SFR’s bestaat TH2 en TL2. Beide registers kunnen gelezen en geschreven worden (zie T0 en T1 voor beperkingen). Het inschakelen van de counter gebeurt via de controlebit TR2. Het ingangssignaal kan gekozen worden via de controlebit C/T2. Is de bit 1, dan wordt een extern signaal op klem T2 geteld (Fmax=500kHz indien systeemklok 24MHz). De T2 ingangsklem kan toegewezen worden aan T2_0 en T2_1.
190
Is de bit 0, dan wordt er geteld op basis van de systeemklok. Is de bit PREN=0, dan is het ingangssignaal systeemklok/24 = 12 cycles per puls (1µs/puls). Is PREN=1, dan wordt een prescaler (voordeler) gebruikt. Hierdoor kan de klok hardware gedeeld worden (figuur 6.4.3.2)
Figuur 6.4.3.2 Prescaler T2 Is fT2=fPCLK dan komt er een puls alle 83ns (12MHz) Is fT2=fPCLK/128 dan komt er een puls alle 10,67µs Afhankelijk van de andere settings kan de counter gebruikt worden in de volgende modes: auto reload zonder up/down counting auto reload met up/down counting capture mode Bij een overflow van de counter kan er een interrupt gegenereerd worden
6.4.3
Auto reload mode en toepassingen
De werking van de auto reload mode is afhankelijk van het al of niet selecteren van de Up/Down count mogelijkheid. Wanneer de U/D count niet geactiveerd is, is het blokdiagram van figuur 6.4.3.1 van toepassing. Wanneer de counter een overflow genereert, wordt de THL2 herladen met de inhoud van RC2. De mode is vergelijkbaar met de 8 bit auto reload mode van de T0/T1, alleen betreft het hier 16 bit waarden. Het is mogelijk T2 zo in te stellen dat een extern signaal een reload oplegt. Dit laat toe een time-out bewaking in hardware te implementeren. Indien pulsen die de counter moeten herladen te lang uit blijven, zal de timer een overflow genereren (missing of late pulse detection (figuur 6.4.3.2)). Het extern herladen van de counter kan een ook interrupt genereren. Omdat dezelfde vector gebruikt wordt als bij een overflow, kan het nodig zijn dat er in de interruptroutine de bron getest wordt. Omdat er meerdere interrupt bronnen zijn per vector, zal de gebruiker de pending bits zelf moeten clearen. 191
Figuur 6.4.3.1 T2 auto reload zonder U/D count
Timer overflow
FFFFh
TL2,TH2 laden
Reload XXYY
TR2= ‘on’ EXEN2= ‘on’
0000h
T2EX pin
ok
Geen overflow IRQ
Overflow IRQ
Niet ok
t
ok t
Vanaf hier detectie
Puls ontbreekt
Fig. 6.4.3.2 Missing pulse detectie
192
Missing pulse detectie kan o.a. gebruikt worden voor het bewaken van de netspanning. Indien we de pulstrein die de reload activeert, afleiden van een nuldoorgangsdetectie van de netspanning (de pulsen komen dan om de 10 ms) dan het wegvallen van de nuldoorgang een overflow veroorzaken. In de Up/Down counter mode wordt de werking weergegeven in figuur 6.4.3.2.
Figuur 6.4.3.3 T2 auto reload met U/D count
Timer2 kan bij de XC888 ook als up-down counter worden geschakeld, waarbij de 'direction' pin (T2EX) aangeeft in welke richting geteld wordt. In de count up mode wordt een overflow gegenereerd wanneer de waarde ffffh+1 wordt bereikt. De counter zal herladen met de waarde in RC2. In de count down mode wordt de counterwaarde vergeleken met de waarde in RC2. Wordt die bereikt, dan wordt een interrupt gegenereerd, en wordt de counter herladen met ffffh. Op de T2 pin kan in dit geval een extern signaal worden aangelegd waarvan we de pulsen kunnen tellen, terwijl T2EX de telrichting aangeeft . Op deze manier kunnen we een absolute positiemeting doen a.d.h.v. een incrementele lineaire- of hoekencoder. Deze encoders leveren twee 90º verschoven blokgolf-vormig signalen, waarbij elke alternantie overeenkomt met een bepaalde lineaire- of hoekverplaatsing (figuur 6.4.3.4). De fase van het 'B' signaal t.o.v. het 'A' signaal (voorijlend of naijlend) is afhankelijk van de bewegingsrichting van de encoder.
193
Zo zijn er bv. hoek-encoders op de markt met 100 tot enkele duizenden pulsen per omwenteling! Bij een lijnsensor (lineaire encoder) is een resolutie van 5 pulsen per mm verplaatsing (1 periode per 200 micrometer) courant, denk hierbij maar aan de lijnsensoren die worden gebruikt in de low cost inktjet printers.
B ccw
B cw
A
Figuur 6.4.3.4 Hoekencoder met een quadratuur gecodeerd uitgangssignaal
B
D
Q
Direction T2EX pin
Clk A
Count T2 pin
Figuur 6.4.3.5 Omzetting van quadratuursignalen in count en direction signalen
Door de signalen A en B aan te leggen aan een D-FF halen we uit deze signalen de 'direction' en 'count' informatie die we respectievelijk kunnen aanleggen aan de ingangen T2EX en T2 van timer2 (figuur 6.4.3.5). Op deze manier kunnen we de absolute positie bijhouden met de timer indien we deze preloaden met de offsetwaarde 7FFFh. Dit is onze 'home' positie (nulpositie), de positie waarvan we vertrekken met de meting. We kunnen nu evenveel pulsen (de helft van het 16-bit bereik of 32768 pulsen) naar boven als naar beneden tellen. Op deze manier kunnen we de absolute positie van onze encoder bijhouden zonder verdere tussenkomst van de CPU (figuur 6.4.3.6). Bij een lijnsensor met 5 pulsen per mm zorgt dit voor een bereik van 6,56 m in beide richtingen bij een resolutie van 200 μm!
194
Indien dit bereik moet worden uitgebreid, kan dit op eenvoudige manier door softwarematig a.d.h.v. de underflows en overflows een bijkomende variabele (die de MSB vormt van het uiteindelijke bereik) te decrementeren of te incrementeren .
FFFFh + TR2=’on’
‘0’ positie ‘home’
7FFFh
Preload
-
0000h
Direction
t
Up
Down
Up t
Count
t +
-
+
Start beweging
Figuur 6.4.3.6 Automatisch bijhouden absolute positie
6.4.4
Capture mode
Een 'capture' mode van een timer is een mode waarbij de tellerwaarde van een timer bij een bepaalde gebeurtenis (typisch een flank aan een controlepin van de timer) wordt gecopiëerd naar een capture-register. Dit capture-register kan dan door de CPU worden gelezen en bewaard in het geheugen. Bij een tweede 'capture' kan de CPU het capture-register opnieuw lezen en het verschil maken tussen de twee capture waarden. Het verschil tussen beide waarden is een maat voor het tijdsverschil (periode) tussen de captures (flanken op de controlepin). De CPU wordt op interruptbasis verwittigd van het capturemoment zodat deze op het gepaste moment het captureregister kan lezen ( figuur 6.4.4.1).
195
Figuur 6.4.4.1 Capture mode
De counter telt op het ritme van een vaste klok omhoog, van 0000h tot ffffh. Indien de waarde ffffh+1 bereikt wordt, zal een overflow opgewekt worden, en indien aangeschakeld ook een interrupt . Indien een stijgende- en/of dalende flank aangelegd wordt (instelbaar) aan klem T2EX, zal een copie genomen worden van de counterwaarde. Deze gecopieerde waarde kan uitgelezen worden uit RC2 (capture register). Dit systeem laat toe om heel nauwkeurig tijdsintervallen (periode) (clockresolutie!) tussen signalen meten (figuur 6.4.4.2). Capture2
FFFFh
E01fh overflow
Tellerwaarde Capture1 0000h
1713h
t Timeroverflow IRQ
T2EX t Capture IRQ
Capture IRQ
time= (capture2 - capture1)x 12/f
Figuur 6.4.4.2 Principe van periodemeting via de capture mode van een timer
196
6.4.5
Controleregisters
Voor de uitvoerige bespreking van de instellingen verwijzen we naar de XC888 user’s manual. Hier staan enkel de namen van de bijhorende SFR’s vermeld in figuur 6.4.5.1.
Figuur 6.4.5.1 T2/T21 SFR’s LET OP!! De SFR’s van T2 en T21 hebben dezelfde adressen maar zitten in een verschillende RMAP (SYSCON0). In de regxc888.inc file zijn de namen uit de figuur voorafgegaan door t2_ of t21_ (t2_t2l of t21_t2l).
6.5 6.5.1
Capture/Compare Unit 6 (CCU6) Inleiding
De CCU6module van de XC888 is een hardware unit die speciaal ontworpen is om complexe signalen te genereren. De tijdsbasis voor deze signalen is afkomstig van 2 timers, timer T12 en T13, waarbij T12 de hoofd timer is. T13 is vergelijkbaar met T2 maar kan enkel in de compare mode werken (indien een bepaalde waarde bereikt wordt, wordt er actie ondernomen). Beide timers zijn 16 bit groot. T12 stuurt 3 onafhankelijke kanalen aan, die elk in de capture of de compare mode gebruikt kunnen worden. Gelet op de hoge complexiteit en het scala aan mogelijkheden beperken we ons hier tot een principiële bespreking van de mogelijkheden. In figuur 6.5.1.1 is het blokdiagram van de CCU6 opgenomen. Uit de figuur kan je afleiden dat T13 een ondersteunende functie heeft. T12 beschikt over 3 kanalen, waarbij elk kanaal een aantal uitgangsklemmen kan aansturen. De voornaamste toepassingen van CCU6 zijn PWM sturingen (figuur 6.5.1.2).
197
Figuur 6.5.1.1 Blokdiagram CCU6
6.5.2
PWM sturing
Een Puls With Modulated signaal (figuur 6.5.2.1) wordt voornamelijk gebruikt om een aan/uit sturing te kunnen bekomen voor een belasting. Door het PWM signaal te gebruiken om de transistor of de H-brug aan te sturen, wordt de motor “verliesloos” in snelheid geregeld. De transistor(en) zal (zullen) ofwel sperren (geen stroom voeren, en dus ook geen vermogen dissiperen) ofwel in verzadiging zijn (kleine spanningsval, en dus weinig dissipatie), waarbij Ptransistor=Utransistor×Itransistor. Het PWM signaal wordt gekenmerkt door: resolutie frequentie De frequentie is het aantal PWM perioden per seconde. Is de frequentie te laag, dan zal het toerental (maar vooral het koppel) van de motor waarneembaar variëren.
198
De resolutie is een maat voor de minimale variatie in duty cycle van het PWM signaal. Omdat het signaal digitaal opgewekt wordt, wordt de resolutie uitgedrukt in een aantal bits. Is de resolutie 4 bit, dan kan de duty cycle met 1/16 van de PWM periode aangepast worden. In figuur 6.5.2.2 wordt weergegeven hoe het PWM signaal opgewekt wordt.
Amplitude / Gemiddelde waarde
tijd PWM periode
+ 24V
M
PWM
GND
Figuur 6.5.2.1 PWM motorsturing via een enkelvoudige transistor of H-brug.
199
klok 4 bit teller
PWM out 4 bit comparator
Compare waarde
4 bit teller compare waarde
E D C B A 9 8 7 6 5 4 3 2 1 0
tijd
PWM out
tijd
Figuur 6.5.2.2 PWM digitaal genereren In CCU6 wordt als PWM teller een 16 bit timer gebruikt. Hierdoor is de resolutie van de PWM 16 bit (1/65536). De CCU6 kan 4 (3 via T12 en 1 via T13) PWM signalen genereren in hardware. De CCU6 is vooral bedoeld om 3 fasige borstelloze DC motoren aan te sturen. Dit type motor kan best vergeleken worden met een 3 fasige AC motor met permanente magneet. Het toerental van de motor kan geregeld worden door een 3 fasig signaal met instelbare frequentie te maken. In dat geval is een schakeling nodig zoals in figuur 6.5.2.3. Hierbij is het vooral belangrijk op te merken dat er een dode tijd moet zijn in het omschakelen van de transistoren. Anders worden kortsluitingen gemaakt. De CCU6 kan dergelijke signalen perfect opwekken. Bovendien is er een voorziening om voor de aansturing gebruik te maken van een HALL sensor.
200
Figuur 6.5.2.3 Borstelloze DC motor De figuur laat ook zien dat het meten van de EMF signalen bijkomende info over de motor kan leveren. Deze metingen zijn maar zinvol wanneer de MOSFET’s niet worden aangestuurd. De ADC van de XC888 kan gesynchroniseerd worden met de andere I/O (vandaar de complexe structuur van de ADC).
6.6 6.6.1
UART en UART1 Inleiding
Een Universal Asynchronous Receiver and Transmitter Is een communicatiebouwsteen waarover zowat alle microcontrollers beschikken. Het systeem kan 1 byte verzenden of ontvangen volgens het frame format uit figuur 6.6.1.1.
Figuur 6.6.1.1 Byte format UART 8 en 9 bit mode
Voor de communicatie worden twee aansluitklemmen gebruikt: TxD voor de zender en RxD voor de ontvanger.
201
De communicatie verloopt asynchroon omdat er geen klok mee verzonden (ontvangen) wordt. De “bittijd” moet wel bij zender en ontvanger op dezelfde waarde ingesteld worden. Hiervoor wordt het begrip “baud rate” gehanteerd. De baud rate van een communicatie komt overeen met het aantal bit dat er per seconde verzonden (ontvangen) kan worden (inclusief start en stop bits). Het aantal databits is bij de 8051 compatibele microcontrollers steeds 8. De UART laat ook toe 9 bits te verzenden in de negen bit mode (NBM). De TB8 uit figuur 6.6.1.1 is de negende bit, en wordt gebruikt om de interrupt overhead op een asynchroon netwerk te reduceren. De XC888 UART’s laten ook toe dat de communicatie verloopt volgens het LIN protocol. Het byte format blijft hierbij hetzelfde, maar er wordt per boodschap een synchronisatie deel toegevoegd. Hierdoor kunnen zender en ontvanger hun baud rate beter op elkaar afstellen. Dit is vooral belangrijk wanneer er geen kristal als klok gebruikt wordt (vb. RC oscillator). De signalen die op de TxD en RxD pinnen verzonden/aangelegd worden zijn 0V en 5V. Omdat met deze spanningen geen grote afstanden kunnen overbrugd worden zijn “level shifters” noodzakelijk. In de figuren 6.6.1.2, 6.6.1.3, 6.6.1.4 en 6.6.1.5 zijn deeltjes van het XC888 SBC schema opgenomen met de beschikbare level shifters. De omvorming van UART naar USB is bovendien een protocol converter.
Figuur 6.6.1.2 UART RS232 level shifter
202
Figuur 6.6.1.3 UART LIN level shifter
Figuur 6.6.1.4 UART RS485 level shifter
203
Figuur 6.6.1.5 UART naar USB protocol omvorming De XC888 beschikt over 2 UART’s (UART en UART1). Omdat ze identiek zijn aan elkaar wordt enkel UART besproken. De XC888 UART is compatibel met de UART’s van alle andere 8051 controllers. Hij kan dan ook in 4 verschillende modes gebruikt worden (figuur 6.6.1.6). Voor de verdere bespreking beperken we ons tot de meest gebruikte “mode 1”, 8 bit UART met variabele baud rate. De 9 bit UART komt overeen met de negen bit mode.
Figuur 6.6.1.6 UART modes 6.6.2
Baud rate generator
De XC888 laat toe om de baud rate te genereren via een unieke baud rate generator per kanaal (UART en UART1), of via timer 1. Die laatste mogelijkheid werd behouden uit historisch perspectief, maar wordt in de XC voor dit doel niet gebruikt. In figuur 6.6.2.1 is het blokdiagram van de baud rate generator opgenomen.
204
Figuur 6.6.2.1 Dedicated baud rate generator UART
De baud rate wordt afgeleid van de systeemklok. Gelet op het gegeven dat de baud rate klok van zender en ontvanger best niet meer dan 2% afwijking mag hebben, kan de RC oscillator onvoldoende stabiel zijn (figuur 6.6.2.2).
Figuur 6.6.2.2 Toleranties inwendige RC oscillator
205
De XC888 kan in dat geval best gebruikt worden met een extern kristal (figuur 6.6.2.3). De XC888 SBC is uitgerust met een kristal van 12MHz.
Figuur 6.6.2.3 Extern kristal op XC888 SBC Voor de formules, en de registers die gebruikt worden bij het berekenen en instellen van de baud rate verwijzen we naar de XC888 User’s Manual.
6.6.3
SFR registers UART
De besturing van de UART gebeurt via 2 controleregisters SBUF (figuur 6.6.3.1) en SCON (figuur 6.6.3.3).
Figuur 6.6.3.1 UART data register Wanneer naar het SBUF register een byte geschreven wordt, start automatisch het verzenden van de byte (wanneer de UART geïnitialiseerd werd (zie
206
voorbeeldprogramma)). Wanneer de TI bit in SCON op 1 komt (aangestuurd door hardware) is de byte effectief verzonden. Er kan pas een nieuwe byte verzonden worden wanneer de TI bit door software op 0 werd gezet. Wanneer de RI bit in SCON op 1 komt (aangestuurd door hardware), dan werd een byte ontvangen, en kan in SBUF gelezen worden. RI moet ook door software op 0 gezet worden alvorens een nieuwe byte ontvangen kan worden. Het controleregister wordt gebruikt om de UART mode in te stellen via de SM0, SM1 en SM2 bits. Door de REN bit op 1 te zetten wordt de ontvanger ingeschakeld. Wordt de bit op 0 gezet, kunnen er geen bytes ontvangen worden. De zender is altijd actief. De bits TI en RI geven aan of een byte verzonden of ontvangen werd (worden in dat geval door hardware op 1 gezet). Deze bits moeten ALTIJD door software opnieuw laag gemaakt worden. Gebeurt dat niet, dan zal de UART niet langer in staat zijn bytes te verzenden of te ontvangen. De RI en TI bits zijn ook de interrupt pending bits (figuur 6.6.3.2).
Figuur 6.6.3.2 TI en RI interrupt pending bits
De TB8 en RB8 bits worden gebruikt om de negende bit die verzonden gaat worden, of die ontvangen werd op te slaan. Deze bits spelen alleen een rol in de negen bit mode van de UART. De UART kan ook gebruikt worden als een synchroon schuifregister. Deze mode laat toe om met standaard schuifregisters (74 reeks logica) poorten toe te voegen aan de controller. Omdat de SSC (zie paragraaf 6.7) en I 2C interfaces meer mogelijkheden bieden, wordt de synchrone mode van de UART zelden gebruikt.
207
Figuur 6.6.3.3 UART controle register
208
Voorbeeldprogramma initialisatie subroutine UART 9600 baud Het programma is een subroutine voor het instellen van de poortpinnen, zodat de UART functie naar buiten kan komen. Verder wordt de baud rate ingesteld op 9600 baud, en wordt UART mode 1 geselecteerd. De routine is zodanig geschreven dat het systeem niet verstoort wordt (syscon0, scu_page, en port_page registers worden hersteld in originele toestand) initsio:
push push mov push push mov
acc syscon0 syscon0,#004h port_page scu_page scu_page,#000h
;registers op de stack zetten ;map bewaren ;hier zit al wat we nodig hebben ;pagina op stack zetten ;pagina 0 system control regs
; eerst gaan we de pinnen van poort 1 juist zetten: ; -p1.0 als input en alt1 selectie ; -p1.1 als output en alt2 selectie mov anl orl anl orl mov anl orl
port_page,#002h p1_altsel0,#11111101b p1_altsel0,#00000001b p1_altsel1,#11111110b p1_altsel1,#00000010b port_page,#000h p1_dir,#11111110b p1_dir,#00000010b
;pagina 2 selecteren
;pagina 0 selecteren ;p1.0=input ;p1.1=output
; Nu moet de baud rate generator ingesteld worden ; We gaan er van uit dat de systeemklok 24MHz is mov
scon,#01010000b
;UART initialiseren
; LET OP!!!!!!!!!!!!!! eerst BG laden, dan bcon, anders wordt BG waarde niet ; gebruikt!! mov mov pop pop pop pop ret
bg,#155 bcon,#00010001b scu_page port_page syscon0 acc
;zie 12-13 in XC888 UM
;alles terug herstellen
209
Voorbeeldprogramma zenden 1 byte De routine moet geen rekening houden met mapping en paging omdat de registers steeds bereikbaar zijn! siooutchar: siooutchar1:
mov jnb clr ret
sbuf,a ti,siooutchar1 ti
;karakter verzenden ;wacht tot zender beschikbaar ;laag maken van de bit
Voorbeeldprogramma ontvangen 1 byte De routine moet geen rekening houden met mapping en paging omdat de registers steeds bereikbaar zijn! sioinchar:
6.6.4
jnb clr mov ret
ri,sioinchar ri a,sbuf
;blijf wachten op het karakter ;bit laag maken ;karakter in de accu
LIN interface
De UART modules kunnen gebruikt worden om het “Local Interconnect Network” of LIN protocol (zowel master als slave werking) te ondersteunen (UART volledig, UART1 mits bijkomende software). In figuur 6.6.4.1 is het LIN frame format opgenomen.
Figuur 6.6.4.1 LIN frame format
LIN is een communicatiesysteem dat vooral in automotive toepassingen gebruikt wordt. Figuur 6.6.4.2 geeft een overzicht van de andere automotive communicatiesystemen. Hieruit kan je afleiden dat de LIN bus voornamelijk een low cost/low speed communicatiesysteem is. 210
Figuur 6.6.4.2 Vergelijking automotive communicatiesystemen
De fysische interface (zie datasheet TJA1020) bestaat uit een 1 draad half duplex verbinding waarbij 0V een dominant - en 12V een recessief niveau is. De LIN bus gebruikt de traditionele asynchrone communicatie (zie werking UART). Het belangrijkste verschil is dat bij het verzenden van ELK frame de ontvanger automatisch de baud rate zal bepalen en aanpassen. Hierdoor vervalt de noodzaak om een kristal te gebruiken als klok voor de microcontroller (een kristal is mechanisch kwetsbaar en relatief duur). Door de data te verzenden in een pakket dat voorafgegaan wordt door een gestandaardiseerd break- en synchronisatie karakter, kan de ontvanger “meten” wat de exacte communicatiesnelheid is (de break duurt minimaal 13 bittijden en het synchronisatiekarakter is 55h). De controller biedt de mogelijkheid om T2 te gebruiken om te “meten” wat de inkomende baud rate is. De instellingen gebeuren via de controleregisters van de dedicated baud rate generator.
6.7 6.7.1
SSC Inleiding
De High Speed Synchronous Serial Interface (SSC) is een synchroon serieel communicatiesysteem dat bij andere fabrikanten omschreven wordt met de naam SPI (Serial Peripheral Interface). In figuur 6.7.1.1 zijn 3 devices op deze manier verbonden. In de figuur ontbreken de selectielijnen waarmee de master de slaves (devices 2 en 3) selecteert. Hiervoor worden gewone poortpinnen gebruikt die door software aangestuurd moeten worden. 211
Figuur 6.7.1.1 SPI verbinding tussen master en slaves
De communicatie gebeurt altijd per 8 bits. De master zal de 8 benodigde klokpulsen genereren die aangeven wanneer de 8 bits verzonden worden. Alleen de geselecteerde slave zal de data inlezen (op het ritme van de klok). Gelijktijdig zal (kan) het ontvangende device 8 bits terugsturen. De snelheid van de communicatie wordt ingesteld tussen de 183 baud en de 12 Mega baud. 6.7.2
Controleregisters
Voor de gebruikte controleregisters verwijzen we naar de XC888 User’s Manual.
212
6.8 6.8.1
CAN Inleiding
Een Controller Area Network (CAN) is een communicatiesysteem dat, over een busverbinding, tegen 1megabit/seconde data kan overdragen tussen microcontrollers. Het systeem wordt vooral gebruikt in automotive netwerken. Het belangrijkste voordeel van het CAN systeem is dat het belangrijkste deel van het communicatieprotocol in hardware afgehandeld wordt (foutdetectie, her-transmissie, synchronisatie,…). CAN gebruikt geen adressen, maar identifiers om een boodschap te routen. Een identifier bestaat uit een 11- of 29 bit getal dat de aard van de boodschap omschrijft. Alleen de nodes die een boodschap van deze aard willen bekijken (instelbaar) zullen de boodschap ontvangen. De hardware meldt een geldige ontvangst via een statusregister en/of interrupt. De boodschap zelf wordt een message object genoemd en kan maximaal uit 8 bytes bestaan. De XC888 beschikt over 2 onafhankelijke CAN modules zodat 2 onafhankelijke CAN netwerken kunnen gebruikt worden. In figuur 6.8.1.1 is een blokdiagram opgenomen van de CAN controller.
Figuur 6.8.1.1 Blokdiagram CAN controller XC888
213
6.8.2
CAN SFR’s
Omdat de CAN controller over veel SFR’s (tot 16000 bytes) moet kunnen beschikken is een speciaal systeem bedacht om met behulp van slechts 7 reële SFR’s (DATA03, ADH, ADL en ADCON) de CAN controller aan te spreken. De registers DATA0, DATA1, DATA2 en DATA3 vormen een 32 bit databus, die gebruikt wordt om de gegevens van en naar de controller te verzenden. ADH en ADL zijn de registers die de adresbus vormen. Het ADCON register wordt gebruikt om de aard van de transactie vast te leggen. Voor een gedetailleerde beschrijving van de CAN controller verwijzen we naar de XC888 User’s Manual. Uitgebreide informatie aangaande de CAN bus kan je terugvinden op volgende sites: http://www.semiconductors.bosch.de/en/20/can/index.asp http://www.can-cia.org/
6.9
6.9.1
Analoog naar digitaal omvormer (ADC)
Inleiding
De Analog to Digital Converter (ADC) is een unit die een analoge ingangsspanning kan omzetten naar een digitale getalwaarde. De analoge spanningen moeten aangelegd worden aan de klemmen van poort 2 (met de AGND als massapotentiaal). Het blokdiagram van de ADC is weergegeven in figuur 6.9.1.1.
Figuur 6.9.1.1 Blokdiagram van de XC888 ADC
Het analoge deel wordt met een externe referentiespanning verbonden (figuur 6.9.1.2 laat een deel van het schema van de XC888 SBC zien). Via een jumper op de SBC kan gekozen worden tussen 4.096V of 5V. De waarde van de 214
referentiespanning bepaalt de maximaal meetbare waarde en kan nooit hoger zijn dan 5V of lager dan 1V. Een ingangsspanning (op klemmen poort 2) lager dan 0V, of hoger dan de voedingsspanning, mag niet aangelegd worden! De nauwkeurigheid van de referentie is ook een maat voor de nauwkeurigheid van het resultaat.
Figuur 6.9.1.2 Referentiespanning ADC De XC888 ADC kan, in tegenstelling tot andere controllers, zonder tussenkomst van de CPU een aantal acties uitvoeren. Dit heeft wel een grotere complexiteit van de ADC voor gevolg. De features van de XC888 ADC zijn opgenomen in volgende aandachtspunten, telkens voorzien van bijkomende uitleg. • Successive approximation • Integrated sample and hold circuitry
De ADC werkt volgens het principe van opeenvolgende benaderingen (figuur 6.9.1.2).
215
Figuur 6.9.1.2 Successive approximation ADC Een Digitaal naar Analoog Convertor (DAC) wordt gebruikt om “te raden” wat de ingangsspanning is. Gedurende de omvorming moet het ingangssignaal constant gehouden worden. Dit wordt bekomen door de ADC te voorzien van een Sample & Hold (S&H) schakeling (figuur 6.9.1.3). Bij de XC888 is die opgenomen in de ADC module, en wordt die automatisch aangestuurd.
Figuur 6.9.1.3 S&H schakeling • 8-bit or 10-bit resolution De resolutie is de kleinste variatie in analoge ingangsspanning die door de ADC gemeten kan worden. Dit komt overeen met de spanningsvariatie die 216
nodig is om het digitale uitgangsresultaat met 1 te laten veranderen. Dit wordt berekend door het bereik van de ingangsspanning te delen door het aantal beschikbare schaaldelen:
Als de 5 volt referentie gekozen is, dan is het ingangsbereik 0-5V. De ADC kan maximaal 10 bits gebruiken om de uitgangswaarde weer te geven (210=1024 schaaldelen). Vullen we dat in de formule in, dan blijkt de kleinste meetbare spanningsverandering 0,004883V te zijn. Dit wordt 1 LSB genoemd.
Wordt de 4,096V als referentie gekozen komt 1LSB overeen met 0,004V. • Eight analog channels De 8 klemmen van poort 2 kunnen als ingang gebruikt worden. • Four independent result registers De ADC kan meerdere kanalen automatisch bemonsteren en de resultaten beschikbaar stellen in afzonderlijke registers. • Single conversion mode • Autoscan functionality De gebruiker kan kiezen of een meting slechts 1 maal uitgevoerd moet worden, of dat er automatisch metingen moeten plaatsgrijpen. • Limit checking for conversion results Het is mogelijk om automatisch te reageren wanneer een meting binnen bepaalde grenzen zit. Hierdoor hoeft de CPU niet permanent resultaten te toetsen. • Data reduction filter (accumulation of up to 2 conversion results) De ADC kan automatisch 2 metingen bij elkaar optellen. Dit laat toe om een gemiddelde meting te bekomen. • Flexible interrupt generation with configurable service nodes Er zijn meerdere mogelijkheden om vanuit de ADC een interrupt te genereren. Hierdoor wordt de CPU enkel onderbroken wanneer het absoluut nodig is.
217
• Programmable sample time • Programmable clock divider De ADC kloksystemen zijn instelbaar om een optimale afstemming te kunnen instellen tussen stroomverbruik, nauwkeurigheid en snelheid. De ADC unit heeft een maximale werkfrequentie van 10MHz! De ADC start op, na reset, in de laagste frequentie (24MHz/32=750kHz). • Two independent conversion request sources with programmable priority De ADC beschikt over 2 units die een conversie kunnen starten (figuur 6.9.1.4). De parallel request source en de sequential request source zijn twee arbitrage units die op een hardware manier bepalen welk event een ADC omvorming kan starten.
Figuur 6.9.1.4 ADC blokdiagram 6.9.2
ADC instellingen en gebruik
Voor een gedetailleerde beschrijving van alle mogelijkheden van de ADC wordt verwezen naar de XC888 User’s Manual. We beperken ons hier tot een summiere beschrijving van de werking en instellingen. De ADC is speciaal ontworpen een omvorming te starten op verzoek van een andere hardware unit. De arbiter zal selecteren welk vragende unit aan de beurt komt om
218
een ADC omvorming te starten. De vraag kan komen via de parallel- of de serial request source. Per ingangskanaal kan er een uitgangsregister gekozen worden om het resultaat in op te slaan (maximaal 4 16 bit uitgangsregisters). Sequential request source is een systeem dat aanvragen tot conversie opslaat in de volgorde waarin ze toekomen (in een buffer). In die volgorde worden de vragen aangeboden aan de arbiter. Parallel request source is een systeem waarbij de aanvragen afgehandeld worden in de volgorde van hun kanaalnummer (eerst hoogste nummer). In deze request source kan ook een autoscanning mode gebruikt worden (beperkt aantal kanalen). De gebruiker kan via de ASEN0 en ASEN1 bits in PRAR bepalen welke request source gebruikt mag worden. De ADC voorziet in een scala van interrupt mogelijkheden (optreden van een event, vullen van een ADC register, voldoen aan grenswaarden van het resultaat,…). Een gedetailleerde beschrijving van de ADC en zijn werking is terug te vinden in de XC888 User’s manual en AP0806310 van Infineon (Programming the Analog to Digital Converter on XC800 family of Microcontrollers). Voorbeeldprogramma ADC ; ; initadc is een subroutine die de adc klaar zet voor minimalistisch ; gebruik De routine zorgt er voor dat de potmeter en de lm335 als analoge inputs ; geschakeld worden. ; ; Gebruikt geen registers ; initadc:
push mov mov mov
syscon0 syscon0,#004h adc_page,#000h adc_globctr,#10110000b
mov
adc_inpcr0,#00ah
;map saven ;basis pagina selecteren ;pagina 0 selecteren ;adc inschakelen MUST!! ;klok delen door 32 (750kHz (max 10MHz) ;10 bit uitkomst ;sample time verlengen met x klokpulsen
; Dit is niet echt nodig, maar hiermee vangen we de hoge impedantie van de potmeter op mov
adc_prar,#080h
mov mov pop ret
adc_page,#006h adc_crmr1,#001h syscon0
;arbitration slot parallelle arbitratie activeren ;MUST!! ;adc page 6 selecteren MUST!! ;willen onmiddellijke actie MUST!! ;herstellen syscon
; ; adclm335 is een subroutine die de spanning meet die van de lm335 sensor ; komt. De waarde wordt doorgegeven in a en b registers (kanaal 5).
219
; ; Gebruikt: ; -accu als 8 msb meting ; -b als 2 lsb meting (links afgelijnd) (b bevat verder data van adc_resrxl) adclm335:
adclm3351:
push mov mov mov mov mov jb mov mov mov pop ret
syscon0 syscon0,#004h adc_page,#006h adc_crpr1,#00100000b adc_page,#000h a,adc_globstr acc.0,adclm3351 adc_page,#002h a,adc_resr0h b,adc_resr0l syscon0
;map kiezen en vorige bewaren ;adc pagina 6 kiezen ;kanaal 5 starten ;naar pagina 0 schakelen ;kijken omvorming klaar ;pagina 2 selecteren ;uitkomst in accu ;uitkomst in b
6.10 MDU 6.10.1
Inleiding
De XC888 is uitgerust met een Multiplication and Division Unit (MDU). Dit is een hardware unit die in staat is berekeningen uit te voeren op getallen tot 32 bit. In figuur 6.10.1.1 zijn de mogelijkheden van de MDU opgenomen.
Figuur 6.10.1.1 MDU mogelijkheden 6.10.2
Werking en gebruik van de MDU
De MDU bestaat voor de gebruiker uit een set van registers die geladen worden met de getallen waarop een berekening moet uitgevoerd worden. Via een controleregister
220
wordt aangegeven welke berekening men wil (MDUCON), en na een uitvoeringstijd zijn de resultaten beschikbaar in een set SFR’s (figuur 6.10.2.1).
Figuur 6.10.2.1 MDU timing In figuur 6.10.2.2 zijn de SFR’s afgebeeld die door de MDU gebruikt worden.
Figuur 6.10.2.2 MDU SFR’s LET OP! De registers van de MDU zitten in SFR map 1. Die moet geselecteerd worden via SYSCON0.
221
Het einde van de berekening kan op 3 manieren bepaald worden: delay routine (max 32 cycles of 2,67µs) pollen van een busy bit (BSY bit in MDUSTAT) een interrupt genereren De MDx registers en de MRx registers zitten fysisch op dezelfde adressen. Bij het schrijven worden de MDx registers geadresseerd, bij het lezen de MRx registers. M.a.w. de MDx worden als input naar de MDU gebruikt, de MRx bevatten de resultaten (figuur 6.10.2.3)
Figuur 6.10.2.3 MDU MDx en MRx registers
6.10.3
XCEZ1 driver en MDU
De XCEZ1.inc driver gebruikt de MDU NIET voor de wiskundige routines. Die zouden veel sneller uitgevoerd worden, mocht dat wel het geval zijn. Deze keuze werd opzettelijk gemaakt omdat de MDU niet onderbroken kan worden. Dit is belangrijk wanneer in het hoofdprogramma berekeningen gebeuren, terwijl een interruptroutine dit ook moet doen. De interruptroutine kan immers de MDU niet gebruiken zonder de resultaten van het hoofdprogramma verloren te laten gaan. 222
De beste optie is de XCEZ routines te gebruiken in het hoofdprogramma (tijd is hier niet belangrijk) en de MDU te gebruiken in interrupt routines.
6.11 CORDIC 6.11.1
Inleiding
Het CORDIC ( COordinate Rotation by DIgtal Computer) algorithme. CORDIC is een algoritme om langs het gebruik van elementaire basisbewerkingen, functies te bepalen zoals oa. SIN, COS, TAN, ATAN, … en dit op een iteratieve manier waarbij men bij elke iteratiestap 1 bit aan precisie wint. De elementaire bewerkingen zijn optel- en schuifbewerkingen, die op een eenvoudige processor of op digitale hardware kunnen worden uitgevoerd. CORDIC werkt door het coördinatenstelsel van een vector te roteren over een gevraagde hoek. Dit rotatieproces gebeurt iteratief met constante en vooraf bepaalde hoeken, en stopt wanneer het hoekverschil tussen de geroteerde en de ingestelde hoek klein genoeg is geworden. Dit hangt af van de benodigde nauwkeurigheid en dus het benodigde aantal bits van het resultaat. Deze vooraf bepaalde hoeken uit het iteratieproces worden in een kleine Look-UpTable (LUT) opgenomen in het systeem. Tijdens dit iteratieproces zullen ook de projecties van de vector op de X-as en de Yas stelselmatig gecumuleerd worden en zo een benadering van de SIN en de COS van de geroteerde hoek opleveren. Één en ander wordt verduidelijkt in de volgende paragrafen.
De voordelen van CORDIC zijn:
Er zijn géén Floating Point (FP) berekeningen en Taylor reeksontwikkelingen nodig om deze trigoniometrische (en andere) functies te berekenen. De combinatie van reeksontwikkelingen én FP bewerkingen, hebben het nadeel erg traag te verlopen op eenvoudige processoren. Er zijn géén grote LUT’s nodig, die wel nodig zijn in het geval de functiewaarden via een loop-up algorithme worden bepaald.
Voor de praktische implicaties verwijzen we naar de XC888 User’s manual en de application notes op de website van Infineon.
223
6.11.2
CORDIC werking in de “rotation mode”.
A) Vectorrotatie over een hoek . Y
y1
P1
x1, y1
P
y
x, y
0
x1
x
X
Figuur 6.11.2.1 vectorrotatie over een hoek Als een vector P( x, y) over een hoek wordt geroteerd in een X,Y assenstelsel dan kan de nieuwe geroteerde vector P1( x1, y1) als volgt worden berekend: x P cos en y P sin
x1 P cos( ) en y1 P sin( )
(1) (2)
Er kan bewezen worden dat: cos( ) cos cos sin sin sin( ) sin cos cos sin
(3) (4)
De vergelijkingen (2) worden dan: x1 P cos cos P sin sin y1 P sin cos P cos sin
(5)
Hierin kunnen we dan (1) substitueren, dit geeft: x1 x cos y sin y1 y cos x sin x sin y cos
(6)
Dit zijn de formules voor het bepalen van de coördinaten bij een eenvoudige vectorrotatie. Maar hoe kan een vectorrotatie ons nu helpen voor het bepalen van bv. SIN en COS functies? B) Rotatie van de eenheidsvector over een hoek . Laten we als startvector een eenheidsvector (1,0) nemen die samenvalt met de X-as.
224
Als we deze vector over een hoek roteren geeft dit volgende coördinaten in ons X,Y stelsel. x1 cos en y1 sin
(7)
Y
y1 sin
P
x1, y1
x1 cos
0
1,0 X
Figuur 6.11.2.2 Rotatie van een eenheidsvector over een hoek . C) Opdeling van de hoek in en aantal (getabuleerde) subhoeken. We kunnen de hoek iteratief proberen te bereiken door een soort van “successive approximation” toe te passen op de hoek , door er steeds kleinere subhoeken (uit de LUT) bij te tellen of af te trekken. Na elke rotatie over een subhoek kunnen we de nieuwe x,y coördinaten bepalen uitgaande van de vorige x,y coördinaten volgens de formules (6) van de vectorrotatie. xnieuw xvorig cos(subhoek ) yvorig sin( subhoek )
Y
y4 y2
ynieuw yvorig cos(subhoek ) xvorig sin( subhoek )
1
y1 y3
4
2
3 4 2
3 0
1
1,0
x1 x3 x4 x 2
X
Figuur 6.11.2.3 Iteratieve procedure voor het roteren van een eenheidsvector
D) De iteratie procedure. De iteratieve procedure voor het roteren van een eenheidsvector over een hoek ziet er dan als volgt uit: x(i 1) x(i ) cos((i ) ) y(i ) sin((i ) ) y(i 1) y(i ) cos((i ) ) x(i ) sin((i ) )
(8)
225
Om de vordering in het iteratieproces bij te houden introduceren we een nieuwe variabele z , die de totale gecumuleerde hoek voorstelt: z is m.a.w. de hoekaccumulator. (9) z(i 1) z(i ) (i ) Laten we dit iteratieproces illustreren a.d.h.v. een voorbeeld met 10 iteraties gebruik makend van volgende LUT. i (i ) in°
1 45
2 26,6
3 14,0
4 7,1
5 3,6
6 1,8
7 0,9
8 0,4
9 0,2
10 0,1
Tabel1:LUT met subhoeken voor het iteratieve rotatieproces. Om de eenheidsvector (1,0) over een hoek van bv. 30° te roteren via 10 iteraties moeten we volgend schema van hoekrotaties volgen voor een beste benadering: +45°-26,6°+14,0°-7,1°+3,6°+1,8°-0,9°+0,4°-0,2°+0,1°=30,1° ≈30° Uiteraard kunnen we na elke stap ook de x en de y waarden berekenen (8) om uiteindelijk te komen tot de benaderende SIN en COS (7) van de geroteerde hoek. Dit levert ons tot nu geen enkel voordeel op, immers om de benaderende SIN en COS van de totaal geroteerde hoek te berekenen hebben we na elke iteratie ook een SIN en COS functie nodig! Om dit probleem op te lossen voeren we een aantal vereenvoudigingen in. E) Vereenvoudigingen voor de hardware-implementatie. 1) Vereenvoudiging door afzonderen van de cos( (i ) ) factor. Om de uitvoering van het algorithme op een standaard processor te vereenvoudigen, gaan we de vergelijkingen (8) herschrijven en de cos( (i ) ) factor afzonderen. Dit geeft: x(i1) x(i ) cos((i ) ) y(i ) sin((i ) ) x(i1 ) cos((i ) ) ( x(i ) y(i ) tan((i ) ))
y(i 1) y(i ) cos( (i ) ) x(i ) sin( (i ) ) y(i 1) cos( (i ) ) ( y(i ) x(i ) tan( (i ) ))
(10)
Deze cos( (i ) ) factoren zijn constanten K (i ) voor de vaste subhoeken uit de LUT . Het is onbelangrijk of de subhoek in het iteratieproces nu moet worden opgeteld of afgetrokken van de gecumuleerde hoek z (i 1) , immers cos( (i ) ) = cos((i ) ) . Dit alles heeft als consequentie dat we het product van al deze constanten, nodig in het interactieproces, op voorhand kunnen berekenen.
226
K K (i )
(11)
n
We kunnen K dus berekenen voor het nodige aantal iteraties in onze toepassing, de precisie van dit product zal toenemen naarmate we meer iteratiestappen toepassen. We kunnen het aantal iteratiestappen beperken en dus ook dit product trunceren in functie van het aantal benodigde bits.
2)
Vereenvoudiging door de oordeelkundige keuze van de subhoeken (i ) .
Om de berekeningen nu mogelijk te maken op een standaard processor (of digitale hardware) gaat men de subhoeken kiezen zodat ze voldoen aan:
tan( (i ) ) d i
1 2i
met d i = -1 voor het optellen of +1 voor het aftrekken van de
subhoek. Dit geeft aan dat de subhoeken zodanig worden gekozen dat de TAN van de subhoek steeds een breuk is van een macht van 2. Immers,
1 1 1 1 1 1 1, , , , , , voor i 0,1,2,3,4, ,10 i 2 2 4 8 16 1024
Merk op dat deze delingen door machten van 2 heel eenvoudig kunnen worden uitgevoerd door ‘shift’ instructies op een processor. De hoeken (i ) die hiermee overeenkomen zijn dan gelijk aan:
1 2 opgeslagen.
(i ) arctan( i )
en deze kunnen vooraf worden berekend en in een LUT
We kunnen de vergelijkingen (10) dus in een andere en eenvoudiger vorm herschrijven:
1 ) 2i 1 y(i 1) K (i ) ( y(i ) di x(i ) i ) 2 z(i 1) z(i ) di (i ) x(i 1) K (i ) ( x(i ) di y(i )
met K (i ) = cos( (i ) )
(12)
met d i als rotatie richtingsvariabele
1 1 en x(i ) i op i 2 2 een standaard processor door ‘shift’ instructies kunnen worden bepaald, wat zeer snel kan gebeuren. Met deze iteratieve vergelijkingen (12) kunnen we een algoritme ontwikkelen dat de gegeven starthoek, die wordt opgegeven in de hoekaccumulator z, iteratief reduceert tot nul ( d i = -1 subhoek optellen of +1 subhoek aftrekken). Het grote voordeel van deze vergelijkingen is dat de termen y( i )
227
Bij elke iteratiestap wordt ook bij de x en de y coördinaten de benodigde waarde (de verschoven vorige x en y waarden) bijgeteld of afgetrokken zodat de nieuwe coördinaten worden bijgehouden doorheen de rotatie. Met de factoren K (i ) houden we tijdens het iteratieproces geen rekening, we brengen ze pas na het totale iteratieproces in rekening als factor K volgens (11). Na n iteraties zijn de x en y waarden respectievelijk benaderingen voor de cos en de sin met een gainfactor 1 (ten gevolge van het weglaten van de K (i ) factoren) die we dus nog moeten K compenseren door de resultaten in x en y nog te vermenigvuldigen met de K factor (11) die in tabel 2 wordt berekend.
i 0 1 2 3 4 5 6 7 8 9 10
1
2i 1 1/2 1/4 1/8 1/16 1/32 1/64 1/128 1/256 1/512 1/1024
(i ) in °
(i ) in rad
cos((i ) ) of K (i )
45,00000 26,56505 14,03624 7,12502 3,57633 1,78991 0,89517 0,44761 0,22381 0,11191 0,05595
0,78540 0,46365 0,24498 0,12435 0,06242 0,03124 0,01562 0,00781 0,00391 0,00195 0,00098
0,70711 0,89443 0,97014 0,99228 0,99805 0,99951 0,99988 0,99997 0,99998 0,99999 1,00000 K K (i ) n
K=0.60725 Tabel2: K K (i ) berekening n
Een programmaflow voor dit algorithme wordt in figuur 6.11.2.4 gegeven.
228
START
i0
in
Initialisatie: n, z , y 0, x K
ja
?
nee
END
dx x i 2 dy y i 2 dz (i )
ja
z0
nee
?
x x dy y y dx
x x dy y y dx
z z dz
z z dz
i i 1
Figuur 6.11.2.4 Programmaflow voor het iteratief rotatieproces Startwaarden van x, y en z voor het bepalen van x cos en y sin .
y1 sin
Y
x1 cos
0
(K,0)
X
Figuur 6.11.2.5 Rotation mode Hiervoor nemen we als startwaarden: z( 0) , x(0) K 1 en y( 0) 0 met K=0,60725. Er is convergentie als de starthoek z( 0) ligt tussen: A z( 0) A met A
rad (90°) 2
Waarbij A de som is van alle vooraf bepaalde en in de LUT opgeslagen subhoeken:
229
A arctan( n
1 ) of A ((i ) ) 2i n
Buiten dit bereik moet men de regels van de trigoniometrie toepassen, bv. cos( ) cos of bv. sin(
2
) sin .
Deze manier van werken waar de z( 0) iteratief naar 0 wordt gereduceerd noemt men de “rotation mode”van CORDIC. CORDIC kan ook op een andere manier gebruikt worden nl. de “vector mode”, deze iteratiemethode wordt besproken in het volgende deel.
6.11.3
CORDIC werking in “vector mode”
(Omzetting van carthesische coördinaten naar poolcoördinaten).
y r sin
Y
0
x r cos
X
Figuur 6.11.3.1 Vector mode
Bij deze manier van werken zal CORDIC de omzetting maken van een vector gegeven door zijn carthesische coördinaten ( x r cos en y r sin ) naar een weergave in poolcoördinaten (r en ). In deze mode zal de inputvector (x,y) geroteerd worden totdat hij samenvalt met de X-as. De resultaten van deze bewerking zijn dan de rotatiehoek , dit is de z component van het resultaat, en de (geschaalde) lengte r van de vector. Dit is de x component van het resultaat. De vector mode werkt door via iteratie de y( 0 ) component van de vector te reduceren naar nul door opeenvolgende rotaties over vaste subhoeken. Het teken van de y(i ) component wordt hierbij gebruikt om de richting van de volgende rotatie te bepalen.
230
Door in dit geval de hoekaccumulator met nul te initialiseren z( 0) 0 , zal z na het iteratieproces de benaderende waarde van de doorlopen hoek bevatten. De iteratie vergelijkingen zijn hier eveneens:
1 ) 2i 1 y(i 1) K (i ) ( y(i ) di x(i ) i ) 2 z(i 1) z(i ) di (i ) x(i 1) K (i ) ( x(i ) di y(i )
met K (i ) = cos( (i ) )
(12)
Hier zal nu de variabele d i =+1 zijn als y(i ) 0 en d i =-1 in het andere geval. Na n itteraties krijgen we dan:
x( n ) An
x
2 ( 0)
y(20)
en
y( n ) 0 (vector mode)
xn stelt dus de geschaalde lengte voor van de startvector (Pythagoras).
Waarbij
kan
bewezen
worden
dat
de
schaalfactor
An
gelijk
is
aan:
1 1 2i =1,64675… (zie tabel 3 ) 2 n n y y( 0) z( 0) arctan( ( 0) ) ) en meestal is z( 0) 0 . met arctan( x( 0) x( 0)
An A(i )
z( n ) i A(i )
0 1 2 3 4 5 6 7 8 1,41421 1,11803 1,03078 1,00778 1,00195 1,00049 1,00012 1,00003 1,00001 Tabel 3: De schaalfactoren in vector mode voor de verschillende itteratiestappen.
In vector mode kunnen we dus iteratief de arctan(
y( 0) x( 0)
) berekenen en tegelijk krijgen
we de absolute waarde van de inputvector.
231
A)
Bepaling van ARCSIN via CORDIC.
Y
1
4
Inputwaarde C
2
3 4 2
3
1
1,0
0
X
Figuur 6.11.3.2 Iteratie naar de y-inputwaarde De boogsinus (ARCSIN) kan worden berekend vertrekkend van de eenheidsvector op de positieve X-as. Door de vector zodanig te roteren dat zijn y-component gelijk wordt aan de inputwaarde vinden we de overeenkomende hoek . Deze hoek is de ASIN van het inputargument. De beslissingsvariabele d i is in dit geval het resultaat van de vergelijking tussen de inputwaarde en de y (i ) component van de geroteerde vector na elke iteratie. Ook hier gelden de iteratie vergelijkingen: 1 x(i 1) K (i ) ( x(i ) di y(i ) i ) met K (i ) = cos( (i ) ) 2 1 y(i 1) K (i ) ( y(i ) di x(i ) i ) 2 z(i 1) z(i ) di (i )
(12)
Hierbij is d i 1 wanneer y(i ) C inputwaarde en d i 1 in het andere geval. Na rotatie via n iteratiestappen krijgen we als resultaat:
xn
A x
2
n
( 0)
C2
met An A(i ) n
n
1 1 2i =1,64675… (zie tabel 3 ) 2
y n C inputwaarde C z n z ( 0) arcsin A x n ( 0)
Indien het iteratieproces start met arcsin C .
z( 0) 0 en An x( 0) 1 vinden we dus als z n de
232
CORDIC geeft een correcte waarde van voor inputwaarden van C die voldoen C 1 aan: 1 An x( 0) De nauwkeurigheid neemt echter wel af voor een hoek die dicht bij de Y-as ligt, dus voor inputs dicht bij 1 . De fout is significant voor inputwaarden groter dan ≈0,98. B)
Bepaling van ARCCOS via CORDIC.
De boogcosinus (ARCCOS) kan op gelijkaardige manier worden bepaald, alleen wordt nu het verschil tussen de x-component en de inputwaarde als beslissingsfunctie gebruikt. Het algoritme werkt echter alleen voor inputs kleiner dan 1 . An De ARCCOS kan dan ook beter worden berekend door gebruik te maken van de ARCSIN functie door
van het resultaat af te trekken, gevolgd door een 2
hoekreductie als het resultaat in het 4e kwadrant ligt.
6.11.4
Uitbreiding van CORDIC naar lineaire en hyperbolische functies.
Y
m 1
m0
m 1 x2 y2 1
x2 y2 1
y
x
1
X
Figuur 6.11.4.1 Verschillende modes van CORDIC
233
De CORDIC vergelijkingen die we tot nu gebruikten kunnen aangepast worden tot een meer algemene vorm. Dit door het invoeren van een bijkomende variabele m, de “mode” variabele. De factor m controleert de vectorrotatie en bepaalt ook de te gebruiken LUT met opgeslagen subhoeken voor circulaire, lineaire en hyperbolische functies. Deze mode variabele m kan de waarde +1 hebben, dit voor de gevallen in een circulair coördinatenstelsel die we tot nu toe illustreerden. De variabele m kan ook de waarde 0 hebben, wat toelaat om lineaire functies te berekenen. Tenslotte kan de variabele m ook de waarde -1 hebben, voor het bepalen van hyperbolische coördinatentransformaties. De algemene CORDIC vergelijkingen zien er, zonder rekening te houden met de constante factor K (i ) , als volgt uit:
x(i 1) x(i ) m d i y (i )
y (i 1) y (i ) d i x(i ) En
1 2i
1 2i
(13) met d i als rotatie richtingsvariabele
z (i 1) z (i ) d i arctan( z (i 1) z (i ) d i
1 ) 2i
voor m=1 (circulaire mode)
1 2i
z (i 1) z (i ) d i arctan h(
voor m=0 (lineaire mode)
1 ) 2i
voor m=-1 (hyperbolische mode)
Merk op dat er voor de hoekaccumulator z in de drie gevallen een andere functie staat, wat dus ook het gebruik van andere LUT’s veronderstelt. A) Berekening van lineaire functies met CORDIC (m=0). In dit geval herleiden de CORDIC iteratievergelijkingen zich tot: x(i 1) x(i )
(14)
y (i 1) y (i ) d i x(i ) z (i 1) z (i ) d i
1 2i
1 2i
met d i als rotatierichtingsvariabele voor m=0 (lineaire mode)
We kunnen met deze vergelijkingen werken in de “rotation mode” en dus de initiële waarde van de hoekaccumulator z naar nul reduceren via het reeds aangegeven iteratieproces.
234
Merk op dat de x waarde steeds gelijk blijft aan de initiële waarde x( 0) en dat we dus werken op een vertikale rechte (lineair) door het punt x( 0) . Hierbij is de beslissingsvariabele d i 1 als z (i ) 0 en d i 1 in het andere geval. De lineaire rotatie geeft dan volgende resultaten na n iteraties: x n x( 0 ) y n y ( 0 ) z ( 0 ) x( 0 )
zn 0 De werking is vergelijkbaar met de shift/add implementatie van een vermenigvuldiger en is dus niet zo’n optimale oplossing. Wanneer we werken in “vector mode” en dus de waarde van y naar nul reduceren in het iteratieproces kunnen we via deze methode verhoudingen (delingen) berekenen. Hierbij is de beslissingsvariabele d i 1 als y (i ) 0 en d i 1 in het andere geval. Het resultaat na n iteraties is: x n x( 0 )
yn 0
z n z (0)
y (0) x( 0 )
De rotaties in het lineaire coördinatenstelsel hebben een schaalfactor van 1, er is dus geen bijkomende correctiefactor meer nodig na het iteratieproces.
B) Berekening van hyperbolische functies met CORDIC (m=-1).
235
Y
x2 y 2 1
cosh 2 sinh 2 1
1
sinh
cosh
1
X
Figuur 6.11.4.2 Hyperbolische functies. De CORDIC vergelijkingen voor hyperbolische functies zien er als volgt uit:
1 2i 1 y ( i ) d i x( i ) i 2
x(i 1) x(i ) d i y (i )
(13)
y (i 1)
met d i als rotatierichtingsvariabele
z (i 1) z (i ) d i arctan h(
1 ) 2i
In de “rotation mode” is de beslissingsvariabele d i 1 als z (i ) 0 en d i 1 in het andere geval. Na een iteratieve reductie van de hoekaccumulator z tot nul geeft dit als resultaat: xn An x(0) cosh z (0) y( 0) sinh z ( 0) met An yn An y( 0) cosh z( 0) x(0) sinh z(0)
A
(i )
n
n
1 1 2i ≈0,8 2
zn 0 Voor een initiële waarde x( 0) 0 , y( 0) 1 en z( 0) geeft dit als resultaat:
xn An sinh
en
yn An cosh
236
Hieruit kan dan ook tanh θ =
sinh worden bepaald. cosh
In de “vector mode” waar de initiële y( 0 ) waarde naar nul wordt gereduceerd is de beslissingsvariabele d i 1 als y (i ) 0 en d i 1 in het andere geval. Dit geeft na iteratie:
xn An
x
2 ( 0)
y(20)
met An A(i ) n
n
1 1 2i ≈0,8 2
yn 0 y zn z( 0) arctan h ( 0) x (0) coördinatenstelsel.
y arctan h ( 0) x (0)
waarbij
in
het
hyperbolisch
De iteratieve rotaties in het hyperbolische coördinatensysteem convergeren echter niet. Het kan echter bewezen worden [1,Walther] dat convergentie kan bekomen worden als bepaalde iteraties ( i=4, 13, 40, … ,k , 3k+1) herhaald worden. De hyperbolische equivalenten van alle functies uit het circulaire coördinatenstelsel kunnen berekend worden. Bijkomend kan worden aangetoond [1, Walther] dat volgende functies kunnen worden afgeleid van de CORDIC functies. y ln 2 arctan h met x
= x2 y 2
met
x 1
x
1 4
en
y 1
en
y
1 4
Referenties. [1] J.S. Walther, A Unified Algorthm for elementary functions, Joint Computer Conference Proceedings v.38 Spring 1971, 379-385. [2] J. Volder, The CORDIC Computing Technique, IRE Trans. Computers, v.EC-8 (september 1959), 330-334. [3] Ray Anchaka, A survey of CORDIC algorithms for FPGA based computers. [4] Oliver Sinnen, Reconfigurable computing CORDIC algorithms Univ. of Auckland (presentation) [5] Sin Hitotumatu, Complex Arithmatic through CORDIC. Kodaï Math. Sem. Rep. 26 (1975) 175-186. [6] Daniel R. Llamocca-Obregón & Carla P. Agurto-Rios, A fixed point implementation of the expanded hyperbolic CORDIC algorithm. [7] Ken Turkowski, Fixed point trigonometry with CORDIC itterations, Apple Computer Jan. 17, 1990. [8] Neil Eklund, CORDIC: Elementary Function Computation using Recursive Sequences.
237
;************************************************************************************** ; Demoprogramma voor het gebruik van de CORDIC ; ; XC_sincos : Deze routine berekent de sinus en de cosinus van de in r1,r0 als ;integer opgegeven hoek. De hoek (in rad.) staat in 2compl. formaat en moet ;vermenigvuldigd worden met een schaalfactor 32768/pi. ; Het hoekbereik gaat van pi/2 tot -pi/2. ; Dwz. een hoek van pi/2 wordt gegeven als 16384 (4000h) en ; -pi/2 als -16384 (c000h) ; ; De return parameters zijn de sinus en cosinus van de opgegeven hoek die ook als ; geschaalde 2compl. getallen ter beschikking komen. ; Het bereik is hier: -1 (8000h) tot +1 (7fffh) dus 15 bit + teken (msb). ; INPUT= r1(msb),r0 => hoek (in rad) * 32768/pi ; OUTPUT SIN= r4,r5 (scaled) ; OUTPUT COS= r6,r7 (scaled) ; ;Deze routine gebruikt de r4,r5,r6,r7 registers ;************************************************************************************** XC_sincos: push acc push psw push syscon0 ;cordic instellen mov syscon0,#005h ;RMAP1 mov cd_statc,#000h mov cd_con,#00ah ;Circ. rotatie mode, MPS magnitude prescaler=1 mov cd_cordyl,#000h ;Y=0 mov cd_cordyh,#000h ;Y=0 mov cd_cordxh,#4dh ;X=32767/1.64676=19898 = 4dbah (K=1.64676) ;input hoek Z in scaled radians mov cd_cordzh,r1 ;bv. 90graden=16384 of 4000h mov cd_cordzl,r0 ;start Cordic mov cd_cordxl,#0bah ;X low byte schrijven start Cordic. jnb eoc,$ ;wachten tot cordic klaar is ;sinus laden mov r4,cd_cordyh mov r5,cd_cordyl ;cosinus laden mov r6,cd_cordxh mov r7,cd_cordxl pop syscon0 pop psw pop acc ret
238
6.12 WDT
6.12.1
Inleiding
Een WatchDog Timer (WDT) is een systeem dat het programmaverloop bewaakt (in mindere mate ook hardware). De WDT werkt als een keukenwekker. Die wordt ingesteld op een bepaalde tijd. Als de software de timer binnen dit tijdbestek niet reset, zal de timer aflopen en een hardware reset genereren. Een watchdog is geen garantie dat er altijd een herstart van een vastgelopen programma is. Wanneer het resetten van de watchdog vanuit een interrupt routine gebeurt is de kans groot dat een vastgelopen hoofdprogramma niet gedetecteerd wordt.
6.12.2
SFR’s en instellingen watchdog
In figuur 6.12.2.1 is het blokdiagram van de WDT opgenomen.
Figuur 6.12.2.1 Blokdiagram WDT De WDT werkt op een instelbare klok van f PCLK (standaard 24MHz) gedeeld door 2 of door 128. DE WDT zelf is een 16 bit timer, waarvan de 8 hoogste bits gebruikt kunnen worden om de time out in te stellen. De timer telt omhoog en zal bij een overflow het systeem resetten. Door vòòr de overflow een reload van de hoogste 8 bit af te dwingen (refresh van de WDT) wordt dit voorkomen. Er bestaat ook de mogelijkheid om een dode zone in te stellen waarbinnen een refresh onmogelijk is (figuur 6.12.2.2)
239
De periode waarbinnen de WDT gerefreshed moet worden kan berekend worden via onderstaande formule:
Figuur 6.12.2.2 WDT timing diagram Voor een gedetailleerde beschrijving van de SFR’s verwijzen we naar de XC888 User’s manual. LET OP!! Bepaalde bits in de SFR’s die de WDT besturen zijn beschermd! Je moet een speciale procedure volgen alvorens je de inhoud van deze bits/registers kan aanpassen.
240
6.13 OCDS 6.13.1
inleiding
De On Chip Debug Support (OCDS) is een combinatie van ROM software en bijkomende hardware. Het systeem laat toe om het debuggen van een programma te vereenvoudigen. Single stepping en breakpoints zijn de belangrijkste technieken. Single stepping laat toe dat het programma na elke instructie gestopt wordt, zodat nagegaan kan worden wat de inhoud van de CPU registers is. Bij een breekpunt wordt het programmaverloop gestopt wanneer een bepaalde locatie (adres) in het geheugen bereikt wordt. Helaas kunnen worden deze features minimaal gedocumenteerd, en zijn ze enkel bruikbaar via een JATG interface.
6.14 ROM floating point routines 6.14.1
Inleiding
De XC888 is uitgerust met een ROM geheugen dat door de fabrikant voorzien is van firmware. De ROM bevat routines voor het programmeren van de FLASH (bootloader en IAP routines), maar ook subroutines die gebruikt kunnen worden voor floating point berekeningen. De software werd geschreven door KEIL (ARM company). Informatie over de routines is dan ook enkel via hun site beschikbaar ( www.keil.com ). De werking van de routines wordt niet door Infineon (fabrikant van de XC888) gegarandeerd of gedocumenteerd! 6.14.2
Rom routines en hun gebruik
In de ROM van de XC888 zitten subroutines die toelaten om wiskundige berekeningen uit te voeren. Onderstaande tekst legt uit hoe de routines aangesproken worden.
241
;************************************************************************************** ; Gebruik van de in het ROM geheugen van de XC888 opgeslagen rekenkundige ; library routines van de firma Keil Elektronik GmbH. Deze mogen vrij gebruikt ; worden in eigen toepassingen. ; Deze routines zitten op een basisadres "baseadr" dat afhankelijk is van de versie ; van XC888 chip: XC888 step AA : baseadr=0e800h ; XC888 step AB : baseadr=0E734h ; Dit basisadres moet door de gebruiker zelf worden gecontroleerd. ; ; Keil ROM Library Routine Overview ; ================================= ; Integer operations. ; =================== ; XC_imul: int multiply 16int x 16int =16int prod ; XC_uidiv: unsigned int divide/modulo 16int / 16int =16int quot+16 rest ; XC_lmul: signed/unsigned long multiply 32int x 32int =32int prod ; XC_uldiv: unsigned long divide/modulo 32int / 32int =32int quot+32 rest ; XC_lneg: signed long negate 32int = -32int ; XC_ulshr: unsigned long shift right 32word >> invoeging 0 ; XC_slshr: signed long shift right 32long >>invoeging msb ; XC_lshl: signed/unsigned long shift left 32word/long <
242
;************************************************************************************** ; ; XC_imul: int multiply ; ======================= ; Input: R6|R7 = value1, R4|R5 = value2 (MSB|LSB) ; Return: R6|R7 = value1 * value2 ; Use Registers: R0 ; ;************************************************************************************** XC_imul: push acc push psw push b lcall Baseadr+015ch pop b pop psw pop acc ret
;?C?IMUL
CODE (BASEADR + 0x015C)
;int multiply
;************************************************************************************** ; ; XC_uidiv: unsigned int divide/modulo ; ===================================== ; Input: R6|R7 = value1, R4|R5 = value2 ; Return: R6|R7 = value1 / value2, R4|R5 = value1 % value2 ; Use Registers: R0 ; ;************************************************************************************** XC_uidiv: push acc push psw push b lcall Baseadr+016eh pop b pop psw pop acc ret
;?C?UIDIV
CODE (BASEADR + 0x016E)
; unsigned int divide/modulo
;************************************************************************************** ; ; XC_lmul: signed/unsigned long multiply ; ======================================== ; Input: R4|R5|R6|R7 = value1, R0|R1|R2|R3 = value2 ; Return: R4|R5|R6|R7 = value1 * value2 ; Use Registers: R0 ; ;************************************************************************************** XC_lmul: push acc push psw push b lcall Baseadr+01c3h pop b
;?C?LMUL
CODE (BASEADR + 0x01C3)
; signed/unsigned long multiply
243
pop psw pop acc ret ;************************************************************************************** ; ; XC_uldiv: unsigned long divide/modulo ; ====================================== ; Input: R4|R5|R6|R7 = value1, R0|R1|R2|R3 = value2 ; Return: R4|R5|R6|R7 = value1 / value2, R0|R1|R2|R3 = value1 % value2 ; Use Registers: none ; ;************************************************************************************** XC_uldiv: push acc push psw push b push dpl lcall Baseadr+024eh pop dpl pop b pop psw pop acc ret
;?C?ULDIV
CODE (BASEADR + 0x024E)
; unsigned long divide/modulo
;************************************************************************************** ; ; XC_lneg: signed long negate ; ============================= ; Input: R4|R5|R6|R7 = value1, ; Return: R4|R5|R6|R7 = -value1 ; Use Registers: none ; ;************************************************************************************** XC_lneg: push acc push psw lcall Baseadr+02e0h pop psw pop acc ret
;?C?LNEG
CODE (BASEADR + 0x02E0)
; signed long negate
;************************************************************************************** ; ; XC_ulshr: unsigned long shift right ; ==================================== ; Input: R4|R5|R6|R7 = value, R0 = shift_cnt ; Return: R4|R5|R6|R7 = value1 >> shift_cnt ; Use Registers: R0 ; ;************************************************************************************** XC_ulshr: push acc push psw lcall Baseadr+02eeh
;?C?ULSHR
CODE (BASEADR + 0x02EE)
;unsigned long shift right
244
pop psw pop acc ret ;************************************************************************************** ; ; XC_slshr: signed long shift right ; ================================== ; Input: R4|R5|R6|R7 = value, R0 = shift_cnt ; Return: R4|R5|R6|R7 = value1 >> shift_cnt ; Use Registers: R0 ; ;************************************************************************************** XC_slshr: push acc push psw lcall Baseadr+0301h pop psw pop acc ret
;?C?SLSHR
CODE (BASEADR + 0x0301)
;signed long shift right
;************************************************************************************** ; ; XC_lshl: signed/unsigned long shift left ; ========================================== ; Input: R4|R5|R6|R7 = value, R0 = shift_cnt ; Return: R4|R5|R6|R7 = value1 << shift_cnt ; Use Registers: R0 ; ;************************************************************************************** XC_lshl: push acc push psw lcall Baseadr+0315h pop psw pop acc ret
;?C?LSHL
CODE (BASEADR + 0x0315)
;signed/unsigned long shift left
;************************************************************************************** ; ; XC_fpadd: float addition ; ========================= ; Input: R4|R5|R6|R7 = value1, R0|R1|R2|R3 = value2 ; Return: R4|R5|R6|R7 = value1 + value2 ; Use Registers: R0, R1, R2, R3 ; ;************************************************************************************** XC_fpadd: push acc push psw push b lcall Baseadr+032fh pop b pop psw
;?C?FPADD
CODE (BASEADR + 0x032F)
; float addition
245
pop acc ret ;************************************************************************************** ; ; XC_fpsub: float subtract ; ========================= ; Input: R4|R5|R6|R7 = value1, R0|R1|R2|R3 = value2 ; Return: R4|R5|R6|R7 = value1 - value2 ; Use Registers: R0, R1, R2, R3 ; ;************************************************************************************** XC_fpsub: push acc push psw push b lcall Baseadr+032bh pop b pop psw pop acc ret
;?C?FPSUB
CODE (BASEADR + 0x032B)
; float subtract
;************************************************************************************** ; ; XC_fpmul: float multipy ; ======================== ; Input: R4|R5|R6|R7 = value1, R0|R1|R2|R3 = value2 ; Return: R4|R5|R6|R7 = value1 * value2 ; Use Registers: R0, R1, R2, R3 ; ;************************************************************************************** XC_fpmul: push acc push psw push b lcall Baseadr+0420h pop b pop psw pop acc ret
;?C?FPMUL
CODE (BASEADR + 0x0420)
; float multipy
;************************************************************************************** ; ; XC_fpdiv: float divide ; ======================= ; Input: R4|R5|R6|R7 = value1, R0|R1|R2|R3 = value2 ; Return: R4|R5|R6|R7 = value1 / value2 ; Use Registers: R0, R1, R2, R3 ; ;************************************************************************************** XC_fpdiv: push acc push psw push b lcall Baseadr+0529h
;?C?FPDIV
CODE (BASEADR + 0x0529)
; float divide
246
pop b pop psw pop acc ret
;************************************************************************************** ; ; XC_fpcmp: float compare with generic fuzzy setting ; XC_fpcmp3: float compare with 3-bit fuzzy setting ; ================================================= ; Input: R4|R5|R6|R7 = value1, R0|R1|R2|R3 = value2 ; Fuzzy Mask: ACC = float rounding mask before compare, examples: ; ACC = 0xFF (compare all bits) ; ACC = 0xF8 (ignore last 3-bits) (entry ?C?FPCMP3) ; ACC = 0x80 (ignore last 7-bits) ; Return: CY = 1 when (value1 > value 2), otherwise 0 ; ACC = 0 when (value1 == value2), otherwise != 0 ; Use Registers: R0, R1, R2, R3, R4, R5, R6, R7 ; ;************************************************************************************** XC_fpcmp: push acc push psw push b push dpl lcall Baseadr+05c8h pop dpl pop b pop psw pop acc ret XC_fpcmp3: push acc push psw push b push dpl lcall Baseadr+05c6h pop dpl pop b pop psw pop acc ret
;?C?FPCMP
CODE (BASEADR + 0x05C8)
; float compare with generic fuzzy setting
;?C?FPCMP3 CODE (BASEADR + 0x05C6)
; float compare with 3-bit fuzzy setting
;************************************************************************************** ; ; XC_suc2f: type cast: signed/unsigned char to float ; =================================================== ; Input: R4 = value, ACC.7 = MSB of value (for signed), ; ACC.7 = 0 (for unsigned) ; Return: R4|R5|R6|R7 = value in floating format ; Use Registers: none ; ;**************************************************************************************
247
XC_suc2f: push acc push psw push b lcall Baseadr+063dh pop b pop psw pop acc ret
;?C?FCASTC CODE (BASEADR + 0x063D)
; type cast: signed/unsigned char to float
;************************************************************************************** ; ; XC_sui2f: type cast: signed/unsigned int to float ; =================================================== ; Input: R4|R5 = value, ACC.7 = MSB of value (for signed), ; ACC.7 = 0 (for unsigned) ; Return: R4|R5|R6|R7 = value in floating format ; Use Registers: none ; ;************************************************************************************** XC_sui2f: push acc push psw push b lcall Baseadr+0638h pop b pop psw pop acc ret
;?C?FCASTI CODE (BASEADR + 0x0638)
; type cast: signed/unsigned int to float
;************************************************************************************** ; ; XC_sul2f: type cast: signed/unsigned long to float ; =================================================== ; Input: R4|R5|R6|R7 = value, ACC.7 = MSB of value (for signed), ; ACC.7 = 0 (for unsigned) ; Return: R4|R5|R6|R7 = value in floating format ; Use Registers: none ; ;************************************************************************************** XC_sul2f: push acc push psw push b lcall Baseadr+0633h pop b pop psw pop acc ret
;?C?FCASTL CODE (BASEADR + 0x0633)
; type cast: signed/unsigned long to float
;************************************************************************************** ; ; XC_f2sucil: type cast: float to signed/unsigned char/int/long ; ============================================================ ; Input: R4|R5|R6|R7 = value in float format
248
; Return: R4|R5|R6|R7 = value in integer format ; Use Registers: none ; ;************************************************************************************** XC_f2sucil: push acc push psw lcall Baseadr+0671h pop psw pop acc ret
;?C?CASTF
CODE (BASEADR + 0x0671)
; type cast:float to signed/unsigned char/int/long
;**************************************************************************************
6.14.3
FP-notatie
Scalars of type float are stored using four bytes (32-bits). The format used follows the IEEE-754 standard. A floating-point number is expressed as the product of two parts: the mantissa and a power of two. For example: ±mantissa × 2exponent The mantissa represents the actual binary digits of the floating-point number. The power of two is represented by the exponent. The stored form of the exponent is an 8-bit value from 0 to 255. The actual value of the exponent is calculated by subtracting 127 from the stored value (0 to 255) giving a range of –127 to +128. The mantissa is a 24-bit value (representing about seven decimal digits) whose most significant bit (MSB) is always 1 and is, therefore, not stored. There is also a sign bit that indicates whether the floating-point number is positive or negative. Floating-point numbers are stored on byte boundaries in the following format:
Contents
Address+0
Address+1
Address+2
Address+3
SEEE EEEE
EMMM MMMM
MMMM MMMM
MMMM MMMM
Where S represents the sign bit where 1 is negative and 0 is positive. E is the exponent with an offset of 127. M is the 24-bit mantissa (stored in 23 bits).
249
Zero is a special value denoted with an exponent field of 0 and a mantissa of 0. Using the above format, the floating-point number -12.5 is stored as a hexadecimal value of 0xC1480000. In memory, this value appears as follows: Address+0 Contents
Address+1
0xC1
Address+2
0x48
Address+3
0x00
0x00
It is fairly simple to convert floating-point numbers to and from their hexadecimal storage equivalents. The following example demonstrates how this is done for the value -12.5 shown above. The floating-point storage representation is not an intuitive format. To convert this to a floating-point number, the bits must be separated as specified in the floating-point number storage format table shown above. For example: Address+0
Address+1
Address+2
Address+3
Format
SEEEEEEE
EMMMMMMM
MMMMMMMM
MMMMMMMM
Binary
11000001
01001000
00000000
00000000
Hex
C1
48
00
00
From this illustration, you can determine the following:
The sign bit is 1, indicating a negative number. The exponent value is 10000010 binary or 130 decimal. Subtracting 127 from 130 leaves 3, which is the actual exponent. The mantissa appears as the following binary number: 10010000000000000000000
There is an understood binary point at the left of the mantissa that is always preceded by a 1. This digit is omitted from the stored form of the floating-point number. Adding 1 and the binary point to the beginning of the mantissa gives the following value: 1.10010000000000000000000 To adjust the mantissa for the exponent, move the decimal point to the left for negative exponent values or right for positive exponent values. Since the exponent is three, the mantissa is adjusted as follows: 1100.10000000000000000000 The result is a binary floating-point number. Binary digits to the left of the decimal point represent the power of two corresponding to their position. For example, 1100 represents (1 × 23) + (1 × 22) + (0 × 21) + (0 × 20), which is 12.
250
Binary digits to the right of the decimal point also represent the power of two corresponding to their position. However, the powers are negative. For example, .100... represents (1 × 2-1) + (0 × 2-2) + (0 × 2-3) + ... which equals .5. The sum of these values is 12.5. Because the sign bit was set, this number should be negative. So, the hexadecimal value 0xC1480000 is -12.5.
6.15 Low power modes 6.15.1
Inleiding
Het stroomverbruik van een microcontroller is afhankelijk van de kloksnelheid en het aantal onderdelen dat “onder spanning” staat. In realiteit wordt niet de spanning, maar de klok uitgeschakeld van het betrokken onderdeel. In bepaalde toepassingen (batterij gevoed) is het wenselijk het stroomverbruik laag te houden.
6.15.2
Uitschakelen onderdelen microcontroller
Na reset worden alle onderdelen voorzien van een kloksignaal, zodat ze na initialisatie gebruikt kunnen worden. Via de PMCON1 en PMCON2 SFR’s kan voor volgende I/O units de klok uitgeschakeld worden:
ADC SSC CCU6 T2 MDU CAN CORDIC T21 UART1
Bovendien kan het analoge deel van de controller (ADC) volledig uitgeschakeld worden, zodat het stroomverbruik verder daalt (via SFR ADC_GLOBCTR).
6.15.3
Algemene low power modes
De globale microcontroller (I/O en CPU) kan ook in stroombesparende modes geschakeld worden (figuur 6.15.3.1)
251
Figuur 6.15.3.1 Power saving modes microcontroller De figuur laat zien hoe tussen de verschillende stroombesparende modes overgeschakeld kan worden. De ACTIVE mode In de ACTIVE mode werkt de processor zonder stroombesparende maatregelen. De performantie is maximaal. Dit is de mode die na het resetten van de controller automatisch geselecteerd wordt. De IDLE mode In de IDLE mode wordt de klok van de core (CPU) uitgeschakeld. De I/O modules kunnen blijven werken, maar ook individueel uitgeschakeld worden. Omdat de CPU niet meer werkt, wordt er ook geen programma uitgevoerd. In het geval de watchdog opgestart werd, zal die na verloop van tijd de CPU resetten. De CPU kan door een reset signaal, of een geactiveerde interrupt in actieve mode gezet worden. Het inschakelen van de power-down modes verloopt via het “PROTECTION SCHEME”, zie XC888 User’s Manual). De POWER-DOWN mode
252
In de POWER-DOWN mode wordt de volledige klok gestopt. Omdat ook de I/O niet meer voorzien wordt van een kloksignaal kan de controller enkel in de actieve mode komen na een hardware reset, of een extern activeringssignaal (EXTINT0 of RxD). De SLOW-DOWN mode In de SLOW-DOWN mode wordt de systeemklok via een deler instelbaar tussen de 12MHz en de 62,5kHz. Hierdoor zal het stroomverbruik dalen in verhouding tot de klokfrequentie. LET OP!! Dit heeft invloed op ALLE onderdelen van de controller (o.a. rekensnelheid, resolutie timers, baud rate, MDU, CORDIC, ADC, …).
253
Stroomverbruik XC888 In figuur 16.15.3.2 is een tabel opgenomen die het stroomverbruik van de XC888 weergeeft in functie van de gekozen mode.
Figuur 16.15.3.2 Stroomverbruik XC888
254
De XC888 is niet bepaald een LOW-POWER microcontroller maar voor automotive en industriële applicaties is dat geen “must”. Hier is het belangrijker dat de controller “robuust” is.
6.16 Flash als data opslag 6.16.1
Inleiding
De meeste microcontrollers beschikken over een afzonderlijk EEPROM geheugen voor het opslaan van variabelen die niet verloren mogen gaan bij het uitschakelen van de voedingsspanning. In de XC888 werd geopteerd om hiervoor het FLASH geheugen te gebruiken. Bij een standaard 8051 is dat niet mogelijk omdat de FLASH programma geheugen is, dat niet geschreven kan worden. De XC888 CPU heeft een extra instructie die dit wel toelaat:
Hierdoor bestaat er ook de mogelijkheid dat een programma zichzelf kan overschrijven (zoals een PC een upgrade kan doen van zijn eigen BIOS). Dit wordt In Application Programming (IAP)genoemd. FLASH geheugen kan niet per byte , maar enkel per sector gewist worden. Daarom is het FLASH geheugen verdeeld in sectoren (figuur 6.16.1.1). Het FLASH geheugen bestaat uit banken van 4Kbytes. Er wordt een onderscheid gemaakt tussen P-banken en D-banken. Dit onderscheid heeft enkel te maken met hoe de bank inwendig verdeeld is in sectoren. Beide banken zijn enkel toegankelijk via het program memory van de controller. In figuur 6.16.1.2 is de verdeling van de sectoren in P- en D-banken weergegeven. Het wissen van het geheugen gebeurt per sector. De D-banken zijn dus beter geschikt voor het manipuleren van kleine hoeveelheden data. De FLASH kan niet onbeperkt gewist en geschreven worden. In figuur 6.16.1.3 zijn de endurance gegevens opgenomen. Je moet er bovendien rekening mee houden dat het wissen van het flashgeheugen tot 0,1s in beslag kan nemen. Het programmeren van gegevens duurt 2,6ms.
255
De werking van het IAP wordt in een volgende paragraaf besproken. Het gebruik van de bootloader en de bootloader mogelijkheden wordt volledig ondersteund door de XC888 ROM code en de door de fabrikant ter beschikking gestelde PC software.
Figuur 6.16.1.1 FLASH memory map .
256
Figuur 6.16.1.2 Sectoren in P en D-banken
Figuur 6.16.1.3 FLASH endurance gegevens 6.16.2
IAP (in application programming)
De XC888 bevat een aantal ROM routines die IAP van de FLASH ondersteunen. Voor deze subroutines en hun gebruik verwijzen we naar de XC888 User’s Manual.
257
LET OP!!
je kan enkel data schrijven naar een bank die tijdens de duur van de schrijfoperatie niet gelezen wordt. je moet zelf nagaan dat de schrijfoperatie correct werd uitgevoerd door een verify (terug lezen data en vergelijken met de geschreven waarden) Er wordt een NMI (moet je wel inschakelen, waardoor het geen echte NMI is) gegenereerd wanneer de FLASH actie voorbij is. Pas dan mag je een nieuwe starten. Je zal dus een NMI routine moeten voorzien! je kan ook via polling nagaan of een bak gelezen kan worden.
In figuur 6.16.2.1 is een flowchart weergegeven die de interactie tussen hoofdprogramma, NMI routine en ROM routines weergeeft.
Figuur 6.16.2.1 Flowchart FLASH programming
258
6.17 Systeem klok
6.17.1
Inleiding
De klok van een microcontroller is bepalend voor de uitvoeringssnelheid van instructies. Bovendien wordt ze gebruikt als tijdsbasis voor allerhande periferiebouwstenen. Zo is ze bepalend voor de communicatiesnelheid van CAN, SSC en UART. Alle timers gebruiken de klok als tijdsbasis voor het meten van tijdsintervallen, of voor het genereren van tijdsgerelateerde signalen (PWM). Omdat de periferiebouwstenen over prescalers beschikken (instelbare delers), is de eigenlijke frequentie van de klok niet zo belangrijk, zolang ze maar gekend is. De stabiliteit van de klok is veel belangrijker. Als de klokfrequentie varieert in functie van de tijd komt de communicatiesnelheid en nauwkeurigheid van tijdsmetingen in gedrang. Om een stabiele klok te waarborgen wordt meestal een kristal gebruikt (figuur 6.17.1.1)
Frequency Tolerance ± 30ppm(Standard) at 25 ± 3 ºC Stability vs. Temperature ± 30ppm(Standard) Aging ± 3ppm/year Figuur 6.17.1.1 Kristal
In de figuur zijn ook enkele tolerantiegegevens opgenomen, waarbij 30ppm overeenkomt met een afwijking van 0,003%. Omdat een kristal relatief kwetsbaar en duur is, zijn de meeste controllers voorzien van een inwendige RC keten die als klokgenerator gebruikt kan worden. In figuur 6.17.1.2 zijn de toleranties opgenomen van de inwendige klok van de XC888.
259
Figuur 6.17.1.2 Toleranties inwendige oscillator XC888
Het is duidelijk dat de foutmarge veel groter is. Voor niet tijdkritische toepassingen volstaat de inwendige oscillator (minder componenten, hoge betrouwbaarheid, eenvoudige PCB design).
6.17.2
Blokdiagram klokgenerator
In figuur 6.17.2.1 is het blokdiagram van de CGU (Clock Generator Unit) opgenomen. Als OSC (oscillator) kan de gebruiker kiezen uit een inwendige oscillator van 9,6MHz, of een uitwendig kristal tussen de 4 en de 12MHz. De prescaler (P), de delers (N en K) vormen samen met de PLL een schakeling die de inkomende OSC frequentie gebruikt om een stabiele, maar hogere, uitgangsfrequentie te genereren (frequentievermenigvuldiger). Hierdoor kan een kristal met een lage frequentie gebruikt worden zodat de signalen “off chip” minder storingen genereren. Sommige microcontrollers gebruiken een kristal van 32kHz om een uiteindelijke werkfrequentie te bekomen die veel hoger is (tientallen MHz). Fsys is de uiteindelijke systeemklok die door alle onderdelen van de controller gebruikt wordt als referentiesignaal. Fsys MOET altijd 96MHz zijn. De schakeling is ook voorzien van een klokbewaking (oscillator fail detect). Hierdoor kan de microcontroller detecteren of er nog een OSC signaal aanwezig is. 260
Figuur 6.17.2.1 CGU van de XC888
6.17.3
Klokgenerator na reset
Wanneer de XC888 uit reset komt zal de inwendige oscillator van 9.6MHz als default gekozen worden. De delers P, N en K krijgen een default waarde waardoor Fsys 96MHz zal zijn. In figuur 6.17.3.1 kan je zien dat Fsys verder gedeeld wordt, afhankelijk van de onderdelen die er gebruik van maken. Standaard wordt de klok voor de periferie en de CPU ingesteld op 24MHz (SD in PMCON0=0). Door de SD (Slow Down) bit op 1 te zetten kan doormiddel van een deler de systeemklok verder gedeeld worden, waardoor de 24MHz teruggebracht kan worden naar en frequentie tussen de 12MHz en de 62,5kHz. Hoe lager de systeemklok, hoe lager het stroomverbruik. Indien gewenst, kan de 96MHz klok (via een deler instelbaar tussen 48MHz en 2,4MHz) naar buiten gebracht worden op p0.0 of p0.7. Deze pinnen zijn beschikbaar op het XC888 SBC bord via een header, zodat het signaal voor andere toepassingen beschikbaar is.
261
Figuur 6.17.3.1 Distributie Fsys naar onderdelen XC888
6.17.4
Omschakelen tussen RC en kristal
Wanneer een hogere stabiliteit gewenst is, kan de gebruiker omschakelen naar een extern kristal. In de XC888 User’s Manual is een procedure opgenomen die strikt gevolgd moet worden.
Voorbeeldprogramma omschakelen RC naar kristal 12MHz ;************************************************************************************** ; XCsw2xtal - Deze routine zorgt voor de omschakeling van de on-chip oscillator met een ; frequentie van 9,6MHz (+5% ..-5%) naar de oscillator het die werkt met een extern ; kwarts kristal . De reden voor deze omschakeling is de veel hogere nauwkeurigheid van ; het externe kristal, wat nodig is voor bv. het nauwkeurig meten en het genereren van ; timingsignalen. Deze routine wordt meestal uitgevoerd na het opstarten van het ; systeem. Deze omschakelprocedure wordt beschreven in de XC888 Users Manual p7-13. ; ; Deze routine vernietigt geen registers en gebruikt geen geheugenlocaties. ; Geschreven door Pauwels D. 2010 ; Aangepast voor cursus door Roggemans M. ;**************************************************************************************
XCsw2xtal:
push
dph
262
push push push push
dpl psw syscon0 scu_page
mov mov
syscon0,#04h scu_page,#1
;selecteer RMAP0 ;page 1 van SCU selecteren
orl
pll_con,#00001000b
orl anl orl
pll_con,#00000100b osc_con,#11110111b osc_con,#00000100b
;VCOBYP=1 bypass de PLL ;freq= VCO-free running =5MHz...20MHz? ;OSCDISC=1 disconnect osc. en PLL ;XPD=0 externe osc. power-up ;OSCSS=1 externe oscillator selecteren
mov inc mov orl jnz
dptr,#0f000h dptr a,dpl a,dph XCsw2xtal0
;wacht tot externe osc stabiel is...
XCsw2xtal1:
orl
osc_con,#00000010b
XCsw2xtal2:
mov jb
a,osc_con acc.1,XCsw2xtal2
;ORDRES=1 osc run detectie starten ;wacht,...dit kan lang duren... tot >2048 VCO cycli... ;check OSCR en ORDRES bits (2 LSB's osc_con) ;Osc.RunDETect klaar? ='0'
XCsw2xtal0:
;Oscillator run detectie is klaar... jnb
acc.0,XCsw2xtal1
;OSCR=1? ja, dan oscillator running...
mov anl orl
a,pll_con a,#0fh a,#10100000b
;lees pll_con ;maskeer onderste bits ;stel NDIV in op 1010=waarde 24
;OPGELET: het password moet worden gegeven, dan kan er gedurende 32cclk cycli naar ;beveiligde locaties worden geschreven zoals NDIV en KDIV van de PLL, de watchdog ;enable bit WDTEN en de power-down en slow down enable bits PD en SD !!!!
XCsw2xtal3:
mov
passwd,#98h
mov anl orl
pll_con,a pll_con,#11111011b pll_con,#00000010b
;het is nodig het password in te stellen!!! ;bij aanpassing van de NDIV van de PLL ;N=24 is nodig bij een 8MHz xtal ;OSCDISC=0 connect osc en PLL ;RESLD=1 restart PLL lock detection
mov jb
a,pll_con acc.0,XCsw2xtal3
;check de LOCK bit (lsb van dit register) ;ga pas verder als PLL gelocked is op osc.
anl orl
pll_con,#11110111b osc_con,#00010000b
;VCOBYP=0 fsys is PLL gelockte frequentie. ;OSCPD=1 interne osc. power-down
pop pop pop pop pop ret
scu_page syscon0 psw dpl dph
;herstel controller status
263
Besluit & bedenkingen
In deze cursus wordt de XC888 microcontroller van Infineon besproken. Deze cursus moet aangevuld worden met de documenten die beschikbaar zijn op de webpagina van Infineon, om een volledig beeld van de controller te geven. Het volgen van de lessen, maar vooral het maken van oefeningen, en het veelvuldig (her)lezen van de datasheets en user’s manual zijn noodzakelijk om de technologie onder de knie te krijgen. De auteurs staan open voor elke vorm van constructieve kritiek die bijdragen tot het verbeteren van de kwaliteit van deze cursus. Toch willen we enkele veel voorkomende opmerking op voorhand weerleggen:
het gebruik van assembler als programmeertaal: Er werd opzettelijk gekozen voor assembler vanuit de pedagogische doelstellingen van deze cursus. Het is immers de bedoeling studenten een grondige basiskennis te verschaffen van de hardware van een computersysteem. Dit houdt in dat low level programming noodzakelijk is om o.a. device drivers te kunnen maken. Dit is bovendien een cursus waar geen voorkennis geëist kan worden, en de programmeertaal bovendien een goede basis biedt voor het beter begrijpen van de hogere programmeertalen die later in de opleiding aan bod komen ( vb. in “C” wordt opstartcode gebruikt die in assembler geschreven is ). De aandachtige lezer zal bovendien opmerken dat de programmertaal geen doel is, maar een middel om de controller beter te begrijpen.
de keuze van een 8 bit microcontroller: We merken dat alle controllers complexer worden. De 8 bit microcontrollers blijven (relatief) eenvoudig, alhoewel… Door voor een 8-bitter te kiezen lukken we er in om een totale benadering van de component te maken. Het gebruik van assembler is bovendien op grotere controllers niet wenselijk.
de keuze van een 8051 microcontroller: Nog altijd is de 8051 controller verantwoordelijk voor een aanzienlijk marktaandeel. Hij is uniek door zijn beschikbaarheid bij verschillende fabrikanten. Anderen blijven dikwijls beperkt tot 1 fabrikant (AVR, Microchip,…). De controllers van andere fabrikanten zijn niet “slechter”. Bovendien is de keuze ondergeschikt, gelet op de grote gelijkheid in werkingsprincipes en mogelijkheden.
264
Appendix A geheugen)
XC888
instructionset
(snel
265
266
267
268
Invloed van de instructies op de PSW vlaggen:
269
Appendix B
XC888 instructionset (reële core)
270
271
272
273
274
Appendix C SFR ADDR FF FE FD FC FB FA F9 F8 F7 F6 F5 F4 F3 F2 F1 F0 EF EE ED EC EB EA E9 E8 E7 E6 E5 E4 E3 E2 E1 E0 DF DE DD DC DB DA D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 CF CE CD CC CB CA C9 C8 C7 C6 C5 C4 C3 C2 C1 C0 BF BE BD BC BB BA B9 B8 B7 B6 B5 B4
NOT MAPPED (RMAP=0) PAGE 0 CCU6_CC62SRH CCU6_CC62SRL CCU6_CC61SRH CCU6_CC61SRL CCU6_CC60SRH CCU6_CC60SRL
SFR pages (XC888)
PAGE 1 CCU6_CC62RH CCU6_CC62RL CCU6_CC61RH CCU6_CC61RL CCU6_CC60RH CCU6_CC60RL
PAGE 2 CCU6_TRPCTRH CCU6_TRPCTRL CCU6_MODCTRH CCU6_MODCTRL CCU6_TCTR2H CCU6_TCTR2L
PAGE 3 CCU6_CMPSTATH CCU6_CMPSTATL CCU6_T13H CCU6_T13L CCU6_T12H CCU6_T12L IPH1 IP1
PAGE 4
PAGE 5
PAGE 6
RMAP=1 NO PAGE
HWBPDR HWBPSR MMDR MMICR MMBPCR MMSR MMCR2 B
FDRES FDSTEP FDCON
MMWR2 MMWR1 MISC_CON
MMCR2 IEN1
ACC ( A ONLY IF USED IN OPCODE ) DATA3 DATA2 DATA1 DATA0 ADH ADL ADCON
ADC_ETRCR ADC_INPCR0 ADC_LCBR ADC_PRAR ADC_GLOBSTR ADC_GLOBCTR P4_DIR P4_DATA
ADC_CHCTR7 ADC_CHCTR6
ADC_RESR3H ADC_RESR3L
ADC_CHCTR5 ADC_CHCTR4 ADC_CHCTR3 ADC_CHCTR2 ADC_CHCTR1 ADC_CHCTR0 P4_PUDEN P4_PUDSEL
ADC_RESR2H ADC_RESR2L ADC_RESR1H ADC_RESR1L ADC_RESR0H ADC_RESR0L P4_ALTSEL1 P4_ALTSEL0
ADC_RESRA3H ADC_RESRA3L ADC_PAGE PSW ADC_RESRA2H ADC_RESRA2L ADC_RESRA1H ADC_RESRA1L ADC_RESRA0H ADC_RESRA0L P4_OD
T2_T2H T2_T2L T2_RC2H T2_RC2L T2_T2MOD T2_T2CON SCU_PAGE BG BCON NMISR NMICON EXICON1
COCON FEAH FEAL PASSWD CMCON
ADC_VFCR ADC_RCR3 ADC_RCR2 ADC_RCR1 ADC_RCR0
ADC_EVINPR ADC_EVINSR
ADC_QINR0 ADC_QBUR0
ADC_EVINCR ADC_EVINFR ADC_CHINPR ADC_CHINSR ADC_CHINCR ADC_CHINFR
ADC_Q0R0 ADC_QSR0 ADC_QMR0 ADC_CRMR1 ADC_CRPR1 ADC_CRCR1
FDRES1 FDSTEP1 FDCON1 BG1 SBUF1 SCON1
T21_T2H T21_T2L T21_RC2H T21_RCL2 T21_T2MOD T21_T2CON WDTH WDTL WDTWINB WDTREL WDTCON
MODSUSP PMCON2 MODPISEL2 IPH IP
EXICON0 IRCON2 IRCON1 IRCON0
PLL_CON OSC_CON PMCON1 PMCON0
MODPISEL1 IRCON4 IRCON3
MD5 MD4 MD3 MD2
275
MR5 MR4 MR3 MR2
B3 B2 B1 B0 AF AE AD AC AB AA A9 A8 A7 A6 A5 A4 A3 A2 A1 A0 9F 9E 9D 9C 9B 9A 99 98 97 96 95 94 93 92 91 90 8F 8E 8D 8C 8B 8A 89 88 87 86 85 84 83 82 81 80
MODPISEL
ID
XADDRH PORT_PAGE
P3_DIR P3_DATA
P3_PUDEN P3_PUDSEL
P3_ALTSEL1 P3_ALTSEL0
CCU6_CMPMODIFH CCU6_CMPMODIFL CCU6_ISRH CCU6_ISRL
CCU6_TCTR0H CCU6_TCTR0L CCU6_T12DTCH CCU6_T12DTCL
CCU6_MCMCTR CCU6_PSLR CCU6_ISSH CCU6_ISSL
P2_DIR P2_DATA CCU6_MCMOUTSH CCU6_MCMOUTSL CCU6_TCTR4H CCU6_TCTR4L CCU6_CC63SRH CCU6_CC63SRL
P2_PUDEN P2_PUDSEL CCU6_T13PRH CCU6_T13PRL CCU6_T12PRH CCU6_T12PRL CCU6_CC63RH CCU6_CC63RL
CCU6_INPH CCU6_INPL CCU6_IENH CCU6_IENL CCU6_T12MSELH CCU6_T12MSELL
P5_DIR P5_DATA P1_DIR P1_DATA
P5_PUDEN P5_PUDSEL P1_PUDEN P1_PUDSEL
P5_ALTSEL1 P5_ALTSEL0 P1_ALTSEL1 P1_ALTSEL0
MD1 MR1 MD0 MR0 MDUCON MDUSTAT
P3_OD SSC_BRH SSC_BRL SSC_RBL SSC_TBL SSC_CONH SSC_CONL SSC_PISEL IEN0
CCU6_PISEL2 CCU6_PAGE EO
CCU6_PISEL0H CCU6_PISEL0L CCU6_ISH CCU6_ISL CCU6_MCMOUTH CCU6_MCMOUTL SBUF SCON
CD_CON CD_STATC CD_CORDZH CD_CORDZL CD_CORDYH CD_CORDYL CD_CORDXH CD_CORDXL
P5_OD P1_OD SYSCON0 TH1 TH0 TL1 TL0 TMOD TCON PCON
P0_DIR
P0_PUDEN
P0_ALTSEL1
DPH DPL SP P0_DATA
P0_PUDSEL
P0_ALTSEL0
P0_OD
276
Appendix D
xcez1.inc functies beknopt
; xcez is een board package driver voor het XC888 SBC bord van MGM/dp. ; ; De driver bevat subroutines die toelaten de hardware van het SBC bord op een ; eenvoudige manier te gebruiken: ; ; standaard I/O: ; initdipswitch klaar zetten poort 4 voor gebruik met de dipswitch ; initftoetsen klaar zetten 4 functieschakelaars onderaan scherm ; initleds klaar zetten LED's als outputs ; ; I2C interface (minimale interface op 100kbit/s) enkel master mode!! ; initiic klaar zetten iic interface ; iicstart genereren van een startconditie op iic poort ; iicstop genereren stop conditie op iic poort ; iicinbyteack lezen van 1 byte met ack (accu=output) ; iicinbytenack lezen van 1 byte met nack (accu=output) ; iicoutbyte schrijven van 1 byte (accu=input, c=waarde ack bit slave) ; ; LCD interace (kan maar gebruikt worden na lcall initiic!!!!!) ; initlcd klaar zetten LCD voor gebruik (incl i2c init) ; lcdoutchar schrijven van ascii code (accu=input) ; lcdoutbyte schrijven hex waarde accu naar LCD ; lcdoutnib afdrukken 4 laagste bits accu op LCD ; lcdoutmsga afdrukken ascii string @dptr, tot 000h code ; lcdlighton backlight aan ; lcdlightoff backlight uit ; lcdbuzon buzzer aan ; lcdbuzof buzzer uit ; ; Seriële interface (via USB stekker!!) ; initsio klaar zetten seriële poort 9600 baud ; siooutchar afdrukken ascii code (accu=input) ; siooutbyte afdrkken getal in accu ; siooutnib afdrukken 4 laagste bits accu ; siooutmsga afdrukken ascii string @dptr tot 000h code ; sioinchar inlezen van 1 ascii code in de accu ; sioinbufa inlezen van ascii buffer vanaf adres in r0, max 20h karakters! ; ; ADC ; initadc klaar zetten ADC voor minimaal gebruik ; adclm35 uitlezen lm35 (a-b) bevat resultaat ; adcpotmeter uitlezen van de potmeter (a-b) bevat resultaat ; ; Arithmetic: ; mul16 vermenigvuldigen 2 16 bit getallen ; mul32 vermenigvuldigen 2 32 bit getallen ; div16 delen 2 16 bit getallen 277
; div32 delen 2 32 bit getallen ; add16 optellen 2 16 bit getallen ; add32 optellen 2 32 bit getallen ; sub16 verschil 2 16 bit getallen ; sub32 verschil 2 32 bit getallen ; hexbcd8 omvormen 8 bit hex naar bcd ; hexbcd16 omvormen 16 bit hex naar bcd ; bcdhex8 omvormen 8 bit bcd naar hex ; bcdhex16 omvormen 16 bit bcd naar hex ; ; Diverse ; delaya0k05s tijdsvertraging (waarde in accu)*0,05s ; delay1ms tijdsvertraging 1 milliseconde ; delay10us tijdsvertraging 10 microseconde ; XCsw2xtal overschakelen rc naar kristal (Pauwels Danny) ; Declaratie van XC888 SFR’s en bitvariabelen
278
Appendix E
ASCII code tabel
279
Bronvermelding: Voor het samenstellen van deze cursus werd gebruik gemaakt van het WWW. De gebruikte afbeeldingen en gegevens komen uit datasheets van fabrikanten. Er werd vermoedelijk geen copyright geschonden. Verder werd gebruik gemaakt van datasheets van volgende fabrikanten: www.atmel.com www.infineon.com www.nxp.com www.ti.com www.national.com www.cinterion.com ftp://telescript.denayer.wenk.be www.maxim-ic.com www.digi.com www.beck-ipc.com
280