Een nieuwe AtMega32A.


TinyMega Home

ATMEGA32A


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.


Wat heeft u nodig?

Om een microprocessor te gebruiken heeft u een aantal gereedschappen nodig. We gaan er hier vanuit dat de volgende zaken beschikbaar zijn:


Wat is ISP

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.


Aansluiten op de ISP interface

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.


Device Manager

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".


Aanmelden van Programmer in Atmel Studio

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.


Fuses uitlezen in Atmel Studio

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.

Nu kunnen we dus de fuses aanpassen (JTAGEN is uit, EESAVE = aan) en vervolgens in onze chip schrijven door "Program".


Fuses uitlezen dmv STK500

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 -signature
Met 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 -readfuses
En 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:c
Mask 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-Erase
Dus 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.


Een knipperlichtje maken.

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.

Nieuw project aanmaken in Atmel Studio


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.eepHierin 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.elfDit is een bestand waar de compiler allerlei data opslaat waar andere tools dan uiteindelijk de andere files uit genereren.
ATMEGA32-Knipperlicht.hexDit 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.lssDit 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.mapHierin kunt u zien hoe het geheugen van de target processor door de compiler is ingedeeld.
ATMEGA32-Knipperlicht.srecDit 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.

De sourcecode voor het programma

/*
 * 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.


Aansluiten van een led

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.


De led aan en uitschakelen.

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.


Programma downloaden.

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.


Programma vertragen.

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.


Library funktie om te vertragen.

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.


StopLicht.

Stoplicht hardware

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.

Stoplicht software

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);
   }
}