Een AtMega32A is een AVR microcontroller van Atmel die met zijn 40 pinnen heel geschikt is voor veel projecten. De chip is verkrijgbaar in een Dual-in-line uitvoering en is daarmee ook geschikt om op een breadboard proefproject te gebruiken. Deze chip komt vanaf fabriek niet met ingebouwde bootloader, dus moet de chip eerst worden ingesteld. Hier laten we u stap-voor-stap zien wat er zoal bij komt kijken om de chip aan het werk te zetten.
Om een microprocessor te gebruiken heeft u een aantal gereedschappen nodig. We gaan er hier vanuit dat de volgende zaken beschikbaar zijn:
ISP staat voor "In-System-Programming". Het is een interface specifikatie die door de firma Atmel in de meeste AVR processors wordt ingebouwd. Door middel van ISP kunt u de processor instellen. Daarbij kunt u de fuses, de locks en het programma laden. ISP is een seriële interface die bestaat uit 3 draden.
Naast bovenstaande pinnen zijn er nog aan paar support pinnen aanwezig op een ISP interface connector. Die heeft u strikt genomen niet nodig, maar maakt het programmeren wel veel gemakkelijker.
Om de processor te programmeren moet u die dus op een ISP programmer aansluiten, zoals hiernaast in het schema is aangegeven.
De programmer wordt op zijn beurt met de PC gekoppeld, meestal op een USB poort.
De XTAL aansluiting is optioneel. Niet alle ISP programmers hebben zo een aansluiting, en op de ISP standaard connector komt die aansluiting dan ook niet voor. U kunt die aansuiting weglaten zolang de processor op zijn interne oscillator loopt. En anders moet je een Kristal aansluiten, of een andere klokfrequestie aanbieden. De frequentie is niet zo belangrijk. Ergens tussen 1 MHz en 16 MHz werkt meestal wel goed. Een lagere frequentie kan ook maar dan moet wel de programmer op een lagere ISP frequentie worden ingesteld.
Let ook op de nummering van de pinnen. De nummering op een DIL processor loopt van boven naar beneden, en dan aan de andere kant verder van beneden naar boven. Op de ISP connector daarentegen gaat de nummering steeds van rechts naar links. Dus dan liggen de aders van een eventuele bandkabel op volgorde.
Als u de programmer de eerste keer aan de PC koppelt dan moeten er eerste device drivers worden geïnstalleerd. De TinyMega-500 gebruikt de standaard "SerialPort over USB" device driver die in windows aanwezig is. Een klein tekst bestand "TinyMega-500.inf" is dan voldoende om de device driver te installeren. Er zijn allerlei andere ISP programmers in de handel, en daarbij is er inmiddels ook een groot aantal verschillende protocollen waar je uit kunt kiezen.
Eenmaal geïnstalleerd dan kunt u de windows device manager opstarten om te zien welk poortnummer gebruikt wordt.
Dit poortnummer heeft u nodig om de programmer aan te sturen. De device manager kunt u op verschillende manieren opstarten. Bijvoorbeeld
via "Programma uitvoeren => devmgmt.msi", of via het windows control panel => "Device manager", of "Apparaat beheer".
Hier kunt u zien dat de programmer bereikbaar is als "COM9".
Voordat u de programmer kunt gebruiken in Atmel studio moet die eerst worden aangemeld. Atmel Studio kan sommige programmers automatisch detecteren, maar de STK500 moet eerst worden aangemeld. Dat gaat als volgt: Start Atmel Studio. Open "View" => "Available Atmel Tools". Vervolgens rechts klikken en "Add Target" kiezen. Dan kunt u een target type kiezen en de toegewezen compoort vastleggen.
Dit hoeft alleen de eerste keer te worden ingesteld. Atmel Studio onthoud de instelling voor de volgende keer.
Nadat de programmer is aangemeld kunnen we contact maken met de processor en de fuses uitlezen. Daarvoor kiest u in Atmel studio "Tools" => "Device Programming" en vervolgens kiest u "STK500" als programmer, "ATMEGA32A" als processor en "ISP" als interface. Vervolgens klikt u op "Apply", dan "Read", en dan krijgt u de devic signature. Hier "0x1E9502". Dat is de vaste signature voor een ATMEGA32A, dus daaraan kunt u zien dat die inderdaad aanwezig is.
Dan kunt u de fuses uitlezen en dan krijgt u de instellingen van de processor. De fuses staan af fabriek op "0x99", "0xE1", en dat is in het bovenstaande fuse window uitgedecodeerd.
INTRCOSC_1MHZ INTRCOSC_2MHZ INTRCOSC_4MHZ INTRCOSC_8MHZ | Staat voor "Interne-RC-Oscillator 1MHz". Die interne oscillators gebruiken geen externe componenten, maar zijn ook niet erg stabiel. De uiteindelijke kloksnelheid kan tot 10 % afwijken. |
EXTRCOSC.. | Staat voor "Externe-RC-Oscillator". Dan moet u een externe weerstand en condensator aan XTAL1 aansluiten om de oscillator te laten lopen. Dat kan interessant zijn als u een RC-oscillator wilt met een andere frequentie. |
EXTHIFXTAL | Staat voor "Extern High-Frequency-Xtal". Dus dan moet u een kristal aansluiten ergens tussen 8 MHZ en 16 MHZ. Dat kost een paar centen maar dan krijgt u ook een zeer stabiele frequentie. |
Nu kunnen we dus de fuses aanpassen (JTAGEN is uit, EESAVE = aan) en vervolgens in onze chip schrijven door "Program".
STK500.exe is een command-line utility van TinyMega.nl waarmee u de programmer kunt aansturen zonder Atmel Studio op te starten. Om dat te gebruiken moet je dan wel de nodige parameters meegeven, maar als u die eenmaal vastlegt in een script file dan kunt u die telkens opnieuw gebruiken.
STK500 -port:COM9 -cpu:ATMEGA32 -signatureMet bovenstaande commando kunt u het signatue van de processor uitlezen. Als het goed gaat geeft de programmer het volgende resultaat:
Verify signature Signature : 1E 95 02 ... Verify signature : Ok
Fuses uitlezen gaat als volgt:
STK500 -port:COM9 -cpu:ATMEGA32 -readfusesEn dan krijgt u dit resultaat:
Read Fuses Reading Extended-Fuse not supported by this target-device. hFuse = 0x99 lFuse = 0xE1 -- --------,1-------,-------- 'On-Chip Debugging' disabled -- --------,-0------,-------- 'Jtag' enabled -- --------,--0-----,-------- ISP programming enabled -- --------,---1----,-------- CKOPT disabled -- --------,----1---,-------- Eeprom erased during Chip-Erase -- --------,-----00-,-------- BootSector size : 2048 Words -- --------,-------1,-------- Vectors in App code -- --------,--------,1------- Brownout level 2.7 Volt -- --------,--------,-1------ Brownout disabled -- --------,--------,--10---1 StartUp delay : 16K CK, 4.1 ms -- --------,--------,----0001 Internal RC, 1 MHz
Dus ook hier zien we dat de fuses staan op "0x99", "0xE1", en vervolgens worden die bytes gedecodeerd. De betekenis van de bits hangt af van het type processor, en bovenstaande decodering is dan ook vantevoren vastgelegd in een configuratie bestand dat met STK500 wordt meegeleverd. Als u wilt kunt u voorzien in een nederlandse vertaling door dat bestand aan te passen.
We zien hier dat de codes "0x99" en "0xE1" worden omgezet naar het bijbehorende bitpatroon ("10011001-11100001") en vervolgens zien we wat die bits individueel betekenen.
Om de fuses te wijzigen moet u in de tabel kijken welke bits moeten worden aangepast, en vervolgens het resultaat omrekenen naar de corresponderende hex waarden. Het eerste byte wordt dan "0xD1" als we JTAGEN afzetten en EESAV aanzetten.
Om het tweede byte goed te zetten kunt u de datasheet van de processor raadplegen voor alle details. Ook kunt u de configuratie-file raadplegen door het volgende commando:
STK500 -cpu:ATMEGA32 -d:cMask geeft aan welke bits effect hebben op een bepaalde instelling, en Value geeft dan aan of die bits aan of uit moeten staan. Bijvoorbeeld:
-- Bitmap : Mask=0x000800, Value=0x000800 : Eeprom erased during Chip-Erase -- Bitmap : Mask=0x000800, Value=0x000000 : Eeprom preserved during Chip-EraseDus om de EEPROM te wissen moet bit-waarde 0x0800 aanstaan, en om de EEPROM te behouden moet bit 0x0800 uitstaan.
Of u kunt de volgende -veel gebruikte- waardes gebruiken:
Default fuses voor Interne oscillator 1MHZ: 0xD1E1
STK500 -port:COM9 -cpu:ATMEGA32 -writefuses:0xD1E1
Default fuses voor Interne oscillator 8MHZ: 0xD1E4
STK500 -port:COM9 -cpu:ATMEGA32 -writefuses:0xD1E4
Default fuses voor Extern kristal (1 - 16 MHZ): 0xC1EF
maar dan moet u wel een kristal aansluiten.
STK500 -port:COM9 -cpu:ATMEGA32 -writefuses:0xC1EF
Als het contact met de processor verloren gaat dan kunt u op een lagere ISP snelheid nog eens proberen door als argument -s:10000 toe te voegen aan het commando.
Iedereen start bij een nieuwe processor met het maken van een knipperlichtje. Niet omdat dat zo een gewild apparaat is, maar wel omdat het een heel zichtbaar resultaat geeft. Het project is zo simpel dat er weinig mis kan gaan, maar er komt nogal wat kijken om de volledige toolset op de goede manier in te richten. Hier zult je zien welke stappen daarvoor nodig zijn.
Open Atmel Studio. Het opstarten gaat nogal traag, dus u moet even geduld hebben. Dan kiest u "File"=>"New Project" en dan krijgt u het bovenstaande scherm. Dan kiest u het template voor een "GCC executable C project" en onderin voert u een naam voor het nieuwe project (hier "ATMEGA32 Knipperlicht"), en u geeft aan waar u het project wilt opslaan. De optie "Create new directory for solution" kunt u uit laten staan. En vervolgens klikt u op "OK". Dan krijgt u een scherm waarin u de processor kunt kiezen. In dit geval dus ATMEGA32A. Daarna wordt een nieuw project aangemaakt.
Hier kunt u zien dat het project is opgeslagen in een nieuwe directory, met de naam die u aan het project heeft gegeven. Binnen die directory staan de project bestanden. Een "Solution" bestand, een "Project" bestand, een bestand met user instellingen, en een sourcefile. Die laatste heeft als extensie ".c" en daarin schrijft u uw programma. Een eerste begin is al aangemaakt door de Atmel studio, maar daar gebeurt nog niet veel.
Ook is er een "Debug" directory aangemaakt waar de aangemaakte bestanden worden opgeslagen. Atmel Studio kan 2 versies genereren, een "Release" en een "Debug", en heeft dan ook beide directories om de output te bewaren. De Debug versie is bedoeld om gebruikt te worden in een debug sessie. Daar zit naast de code voor de processor allerlei aanvullende debug informatie in die door een debug tool gebruikt kan worden voor single-step, funkties, traces enz. Maar zolang je geen debugger beschikbaar hebt kun je daar niet veel mee. In het project scherm kun je kiezen of je een Debug versie of een Release versie wilt bouwen.
Binnen het project open ik altijd de solution explorer (via "View" => "Solution Explorer") want daarmee krijg je een overzicht van de bestanden in dit project.
Door te klikken op "Build"=>"Build solution" worden de output files gegenereerd. Zoals u kunt zien in de Solution explorer onder "output files". Die bestanden staan in een subdirectory "Debug" of "Release" afhankelijk van het project-type is nu aktief is. U ziet daar de volgende bestanden:
ATMEGA32-Knipperlicht.eep | Hierin staat de data die in de EEprom van de processor moet worden geschreven. Maar die is meestal leeg, tenzij u in de sourcefile data-tabellen voor de EEprom genereert. Data is in "Intel-Hex" format. |
ATMEGA32-Knipperlicht.elf | Dit is een bestand waar de compiler allerlei data opslaat waar andere tools dan uiteindelijk de andere files uit genereren. |
ATMEGA32-Knipperlicht.hex | Dit is het Hex-Bestand dat door de programmer wordt gelezen en uiteindelijk in het flash geheugen van de target processor wordt geladen. Data format is "Intel-Hex". |
ATMEGA32-Knipperlicht.lss | Dit is een tekstbestand met daarin de assembler instrukties die door de compiler zijn gegenereerd. Die kunt u bestuderen om te zien of u tevreden bent met de gegenereerde code. |
ATMEGA32-Knipperlicht.map | Hierin kunt u zien hoe het geheugen van de target processor door de compiler is ingedeeld. |
ATMEGA32-Knipperlicht.srec | Dit bestand bevat dezelfde data als het .hex bestand, maar in een ander format ("Motorola S-Record"). U heeft maar een van de twee nodig om de firmware te laden. De meeste programmers kunnen beide formats lezen. Maar Intel-Hex wordt het meeste gebruikt tegenwoordig. |
/* * ATMEGA32_Knipperlicht.c * * Created: 30-4-2016 21:08:08 * Author: Kees */ #include <avr/io.h> int main(void) { while(1) { //TODO:: Please write your application code } }Hierboven zie je de sourcecode die bij elk nieuw project wordt aangeleverd. Al het groen is commentaar, tekst die wel in de source code staat maar die geen effect heeft op het uiteindelijke resultaat. Die dient vooral als geheugensteun zodat u wat context kunt vastleggen zoals wat de bedoeling is van de statements en waarom voor een bepaalde aanpak is gekozen.
De lijn met #include geeft u toegang tot de volledige set definities van de I/O registers van de ATMega32 in dit geval. Atmel studio houdt rekening met welke processor voor het project is gekozen en kiest via io.h een set processor-specifieke header files. Welke dat zijn kun je zien in de solution explorer onder "dependencies".
Verder bevat het programma een main funktie. De standaard funktie die in elk C project wordt opgestart. En binnen die funktie zit dan een while() loop die aktief blijft zolang 1 ongelijk is aan nul. Die stopt dus nooit. Wat een goed idee is voor een programma in een embedded project. Want als het programma klaar is gebeurt er niets meer en dat wil je niet.
Binnen de while loop gebeurt helemaal niets. Je kunt dit programma wel in de processor laden, maar dat heeft niet veel nut omdat je dan niet kunt zien of het loopt of niet.
Om een knipperlicht te maken moet er een lampje aan de processor worden aangesloten. Dat doen we tegenwoordig met een LED.
Deze processor heeft 32 pinnen om uit te kiezen, Laten we gaan voor PORTC, pin PC0.
Een weerstand loopt vanaf 5-Volt via de LED naar PC0. Dus er loopt stroom, en de led licht op, als PC0 laag is. En de led
gaat weer uit als PC0 hoog wordt. De weerstand mag een waarde hebben van ergens tussen 220 Ohm en 10K. Maar hogere weerstand
resulteert natuurlijk wel in minder licht.
Zoals u ziet hebben de pinnen van de processor nu andere namen. Hier staan nu de pinnen zoals ze vanuit de firmware kunnen worden aangesproken. Pinnen kunnen voor verschillende doelen gebruikt worden. De ISP pinnen (MOSI, MISO etc) zijn alleen aktief tijdens programmeren (dus als RESET laag is). Bij normaal gebruik kunnen diezelfde pinnen als I/O gebruikt worden.
Nu gaan we aan programma aanmaken om de led aan en uit te schakelen. Daarvoor moeten we bovenstaande sourcefile aanpassen en in de target processor downloaden. De processor voert dan de instrukties in het programma uit en de LED gaat knipperen.
De controller heeft een aantal registers om de I/O pinnen te besturen. Die registers zijn speciale geheugen lokaties met vooraf gedefiniëerde namen. De datasheet van de AtMega32 geeft een uitgebreide beschrijving van alle registers. En ook binnen Atmel studio zijn die namen beschikbaar.
Dus we voegen een paar regels toe om alle pinnen van PortC te aktiveren en vervolgens PC0 aan en uit te zetten:
/* * ATMEGA32_Knipperlicht.c * * Created: 30-4-2016 21:08:08 * Author: Kees */ #include <avr/io.h> int main(void) { DDRC = 0xFF; //< Activeer de pin drivers van alle pinnen op PORTC while(1) { PORTC=0x00; //< Alle Pinnen van PortC laag. LED gaat aan. PORTC=0xFF; //< Alle Pinnen van PortC hoog. LED gaat uit. } }
Dus de pin drivers worden aktief door de waarde 0xFF in register DDRC te schrijven. 0xFF is een Hexadecimale waarde (aangegeven door 0x) zodat die gemakkelijk omgerekend kan worden naar een binaire waarde. Als je wilt kun je de waarde ook binair vastleggen (0b11111111) of decimaal (255). Elke bit bestuurt een pin driver, en met deze waarde worden alle 8 pin drivers van PORT C aktief.
De compiler wordt nu gestart dmv "BUILD" => "Build Solution" met het volgende resultaat:
In het "output" window rechts kunt u zien dat het programma is aangemaakt zonder fouten. Het uiteindelijke programma voor de target heeft een omvang van 122 bytes.
Dus nu kunnen we het programma in de processor laden. Dat gaat via "Tools"=>"Device Programming". U kiest weer de programmer, klikt op "Apply" om de programmer aktief te maken, en vervolgens op "Memories".
En daar ziet u dat Atmel Studio alles al heeft ingevuld. Onder "Flash" staat de naam van het bestand dat zojuist is aangemaakt. De opties voor "Erase device" en "Verify Flash" zijn ook al aangevinkt, dus nu kunnen we de firmware downloaden door te klikken op "Program" in de flash box. Het programma wordt geladen, en de controller begint direkt met uitvoeren. De LED gaat aan.
Maar geen spoor van knipperen. Of eigenlijk ook wel, maar dat gaat zo snel dat je dat niet ziet. Dus om dat te zien moeten we de zaak flink vertragen. En dat doen we door de processor te laten tellen tussen het aan- en uit-zetten.
/* * ATMEGA32_Knipperlicht.c * * Created: 30-4-2016 21:08:08 * Author: Kees */ #include <avr/io.h> int main(void) { DDRC = 0xFF; //< Activeer de pin drivers van alle pinnen op PORTC while(1) { long i; for(i=0; i < 100000; i++) {} PORTC=0x00; //< Alle Pinnen van PortC laag. LED gaat aan. for(i=0; i < 100000; i++) {} PORTC=0xFF; //< Alle Pinnen van PortC hoog. LED gaat uit. } }
Nu is het programma aangepast met de nodige vertraging. We gebruiken een variable "i" van type 'long', en een 'for loop' die telt van 0 tot 100000. De variable moet type 'long' zijn want een 'int' is te klein. Een int is 16-bit signed, en gaat dus niet verder dan 32767. Een long is 32 bit en gaat tot ruim 2000000000.
Dus programma weer compileren, dan firmware downloaden, en dan knippert de led. Bij mij telkens met ongeveer 1 seconde aan en 1 seconde uit, terwijl de fuses staan ingesteld op "Interne RC oscillator 1MHz".
Dus bij een trage instelling van de klok moet de processor tot 100000 tellen om een 1-seconde vertraging te krijgen. De max snelheid van deze processor is 16 MHz.
Het is niet zo gemakkelijk om te achterhalen hoelang een teller erover doet om te tellen. Dat is afhankelijk van de code die door de compiler wordt aangemaakt, en ook van de kloksnelheid van de processor. De gemakkelijkse methode is gewoon een getal te kiezen, en dan te kijken of het resultaat goed genoeg is.
Maar je kunt ook een library funktie gebruiken. Die funkties staan beschreven in de documentatie van de compiler. Die wordt samen met de avr-gcc compiler meegeleverd met AtmelStudio, maar kan in Atmel studio niet zo gemakkelijk tevoorschijn worden gehaald. Maar de documentatie is wel te vinden in de install directory van Atmel Studio. Bij mij onder "C:\Program Files (x86)\Atmel\Atmel Toolchain\AVR8 GCC\Native\3.4.1056\avr8-gnu-toolchain\doc\avr-libc"
#include <avr/io.h> #define F_CPU 1000000 //< Set Klok-Frequentie zodat de compiler de juiste delay kan berekenen. #include <util/delay.h> int main(void) { DDRC=0b11111111; //< Alle pin drivers van PORTC aktief while(1) { _delay_ms(2000); PORTC=0b00000000; //< Alle pinnen laag : LED aan _delay_ms( 300); PORTC=0b11111111; //< Alle Pinnen hoog : LED Uit _delay_ms( 300); PORTC=0b00000000; //< Alle pinnen laag : LED aan _delay_ms( 300); PORTC=0b11111111; //< Alle Pinnen hoog : LED Uit } }
Nu gebruikt het programma een _delay_ms() funktie uit de library om de juiste tijd af te tellen. Die funktie is gedefinieerd in "util/delay.h" dus die moet je #includen om de funktie te gebruiken. En je moet ook de klokfrequentie vastleggen in een "#define F_CPU". Ook is het programma iets interessanter gemaakt met een langere pause en vervolgens 2 snellere lichtpulsen.
Nu we toch bezig zijn kunnen we ook het voorbeeld uitbreiden naar een stoplicht besturing. Dan hebben we wel meer leds nodig.
Als we elke LED een eigen pin geven dan kunnen we de LED's elk los van de anderen aansturen. Zoals je hiernaast kunt zien hebben we de LED's deze keer omgedraaid,
nu met de Anode aan de pin, en de kathode via een weerstand naar GND. Dat werkt net zo goed, en de software wordt iets beter leesbaar omdat de led nu aangaat
als de bijbehorende pin hoog is.
Het bijbehorende programma kan dan bijvoorbeeld zo worden geschreven:
#include <avr/io.h> #define F_CPU 1000000 //< Set Klok-Frequentie zodat de compiler de juiste delay kan berekenen. #include <util/delay.h> int main(void) { DDRC=0b11111111; //< Alle pin drivers van PORTC aktief while(1) { PORTC = 0b00001100; //< Groen- Rood _delay_ms(5000); PORTC = 0b00010100; //< Geel - Rood _delay_ms(1000); PORTC = 0b00100100; //< Rood - Rood _delay_ms(1000); PORTC = 0b00100001; //< Rood - Groen _delay_ms(5000); PORTC = 0b00100010; //< Rood - Geel _delay_ms(1000); PORTC = 0b00100100; //< Rood - Rood _delay_ms(1000); } }