Programmera Arduino med C

Tillbaka till startsidan

Arduino UNO3 är en open-source elektronikplattform baserad på lättanvänd hårdvara och mjukvara. Man "berättar" för Arduinon vad den ska göra genom att skicka en uppsättning instruktioner till processorn på kretskortet.

Programmeringen av Arduino sker med en flexibel mjukvara, och ett enkelt språk, "Processing", som från början togs fram för att lära ut programmering inom ramen för olika konstproject ("visual arts").

Arduino IDE

Till Arduinokortet finns en gratis IDE (Integrated Development Environment), för operativsystemen Windows MAC och Linux.

Så här enkelt gör man ett Arduino-program som blinkar med den lysdiod som finns inbyggd på kretskortet. (Bara 5 enkla kodrader!)

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

Arduinomiljön är mycket användbar när man snabbt vill prova olika ideér för att visa andra hur man tänkt, och när kraven på en ekonomisk lösning inte är så höga.

Arduinos enkelhet kommer emellertid med en kostnad. Den genererade koden kan bli onödigt stor och ineffektiv. Den enkla Arduino koden döljer vad som egentligen utförs och detta gör det svårt att hitta fel i programmen. När det hela inte fungerar som tänkt är det detaljkunskaper om processorn och dess programmering som behövs.
Den som går på en programmeringsutbildning behöver därför lära sig att programmera processorn direkt - exempelvis med programspråket C. Detta krävs för att man ska kunna hitta svårfunna programmeringsfel, och för att kunna skapa en effektiv programkod.



Atmel processorn ATmega328/P och AVR toolchain

  

ATmega328/P på Arduino UNO 3 kortet

Arduino UNO 3 är byggd kring microcontrollern ATmega328/P från Atmel. Den beskrivs detaljerat i kretsens datablad ATmega328P.zip.

 

De flesta av ATmega chippets pinnar är direkt anslutna till Arduinokortets kontakter. Det går därför enkelt att koppla in yttre komponenter till uno3-kortet. Det finns också mängder av färdiga kretskort med extra utrustning att ansluta direkt till uno3-kortets kontakter (så kallade Shields).

ATmega328/P på kopplingsdäck

Den som vill kan i stället för ett färdigt Arduinokort placera processorn ATmega328P direkt på ett kopplingsdäck. Det finns i många beskrivningar på webben om hur man gör detta och vilka övriga komponenter som då behövs.

En Arduinokopia på kopplingsdäck, dokumenterat med Fritzing: Arduino UNO 3.

Atmel AVR Toolchain

  

För att programmera AVR processorer effektivt med programspråket C kan man använda en komplett miljö med en GCC kompilator

Men det finns ett enklare sätt ...

Arduino IDE är i själva verket byggd kring exakt samma C-miljö som den man laddar ned som "Atmel AVR Toolchain". Har man en Arduino kan man helt enkelt skriva ren C-kod direkt i Arduino texteditorn, genom att undvika alla Arduino specifika kommandon ( använd inga "Processing-funktioner" ).

(En skillnad mot en ren C miljö är att källkodsfilerna i en Arduino miljö får filnamnstillägget *.ino och att dom hamnar inuti mappar med samma namn som filnamnet.)

TIPS!  Arduino texteditor visar radnumret i nedre vänstra hörnet för den rad markören råkar stå på. Detta behöver man känna till när kompilatorn rapporterar fel på någon specifik rad!

Det är på detta sättet vi kommer att presentera de följande kodexemplen!


Blinka med lysdioden - nu C program

Man börjar med att placera kretsdokumentationen direkt i källkodsfilen! Då blir det lätt att hitta rätt bland pinnarna.
Kopiera ASCII-grafiken som visar ATmega processorns pinnplacering och anslutningarna till Arduino kontakterna (stackable header).
Uppdatera sedan grafiken varje gång Du ändrar - kopplar in något!


/* *********************************** */
/*            HARDWARE                 */
/* *********************************** */

/*                   Chip ATMega328                          Arduino Uno R3 stackable header
                                                                            _______
                                                            Digital:  _____/       \__  Analog:
             ______________  ______________                         -|D00 >RXD      A5|-
            |              \/              |                        -|D01 <TXD      A4|-
      Res---|01 PC6/RES' ATM328  SCL/PC5 28|-(A5)-                  -|D02           A3|-
     -(D00)-|02 PD0/RXD          SDA/PC4 27|-(A4)-                  -|D03~          A2|-
     -(D01)-|03 PD1/TXD              PC3 26|-(A3)-                  -|D04           A1|-
     -(D02)-|04 PD2/INT0             PC2 25|-(A2)-                  -|D05~          A0|-
     -(D03)-|05 PD3/INT1/PWM         PC1 24|-(A1)-                  -|D06~            | Power:      
     -(D04)-|06 PD4                  PC0 23|-(A0)-                  -|D07          Vin|-
     +5V ---|07 VCC                  GND 22|--- Gnd                  |             GND|-
     Gnd ---|08 GND                 AREF 21|--- Vin                 -|D08          GND|- 
  Xtal |X|--|09 PB6/OSC1            AVCC 20|--- +5V                 -|D09~         +5V|-
 16MHz |X|--|10 PB7/OSC2         SCK/PB5 19|-(D13)-                 -|D10~       +3.3V|-
     -(D05)-|11 PD5/PWM         MISO/PB4 18|-(D12)-                 -|D11~         Res|-
     -(D06)-|12 PD6/PWM     PWM/MOSI/PB3 17|-(D11)-                 -|D12        IOREF|-
     -(D07)-|13 PD7          PWM/SS'/PB2 16|-(D10)-                 -|D13 LED     --- |
     -(D08)-|14 PB0              PWM/PB1 15|-(D09)-                 -|GND             | 
            |______________________________|                        -|AREF            |
                                                                    -|SCL             |
                                                                    -|SDA             |
                                                                     |________________|
*/

Initiering av portpinnar

ATmega328 har 28 pinnar. De är fördelade på tre portar med register PORTB och PORTC och PORTD. Till varje register finns också ett riktningsregister, Data Direction Register DDRB DDRC DDRD. De bitpositioner i riktningsregistren som är 1 gör motsvarande pinne till en utgång, annars förblir den en ingång.

För att kunna använda de namn på bitar och register som förekommer i databladet behöver man ta med includefilen:

#include <avr/io.h>

ATmega328 processorns portar B C D.

On och Off

För att blinka med Arduino kortets inbyggda lysdiod, som är kopplad till pinne 19 (PB5, PORTB bit 5), måste den pinnen ställas in som utgång. DDRB bit 5 ska vara 1.
Man kan skriva DDRB = 0b01000000; men då påverkar man samtidigt riktningen för alla andra pinnar på porten.
Bättre är att skriva   DDRB |= ( 1 << 5 );  som innebär att man maskar (or) med en bitmask med bit 5 ettställd. Övriga bitar påverkas inte av detta.
Ändå bättre är att skriva   DDRB |= ( 1 << PB5 );  här utnyttjar vi att includefilen innehåller information om vilka nummer bitarnas namn motsvarar!

För att "tända" lysdioden måste portpinnen PORTB bit 5 bli 1.   PORTB |= ( 1 << PB5 );.

För att sedan "släcka" lysdioden maskar (and) man med en bitmask med alla bitar ettställda utom bit 5 som är nollställd. PORTB &= ~( 1 << PB5 );.

Man kan också "toggla" (varannan gång 1 och varannan gång 0) en portpinne genom att maska (xor).   PORTB ^= ( 1 << PB5 );.

Tidsfördröjning

För att vänta kan man låta processorn utföra en lång meningslös beräkning.

void wait(void)
{
   long unsigned int local_delay; 
   for (local_delay = 0; local_delay < 400000; local_delay++ ) /* empty loop */ ;
}

ATmega är en 8-bitars processor.

En for-loop med en long som räknevariabel räcker till ett mycket stort tidsintervall. Med ett startvärde 40000 blir tiden av storleksordningen sekund ( vid vår klockfrekvens 16 MHz ).

MEN TYVÄRR! det visar sig att denna räkne-loop inte fungerar!?

Problemet är att kompilatorn upptäcker att loopen inte gör något, att den är meningslös, så den optimerar helt enkelt bort koden! Så hur gör man för att tala om för kompilatorn att man menar allvar och vill ha kvar koden?

volatile long unsigned int local_delay;

Nyckelordet volatile innebär att variabeln kan komma att ändras utanför kompilatorns kontroll och att den därför inte får optimeras bort. Nu fungerar koden!


Fördröjningsfunktioner att inkludera

Bekväma fördröjningsloopar. Parametern är antalet ms eller us angivet som en flyttalskonstant. Antalet varv som behövs i loopen räknar kompilatorn ut med ett makro, och använder sedan den grundläggande delay loopen till fördröjningen.
(Viktigt att parametern är en konstant och inte en variabel. Är parametern en variabel kan antalet varv i fördröjningsloopen inte räknas ut i förväg under kompileringen, utan måste beräknas av processorn - som då måste inkludera alla flyttals bibliotek bara för detta!)

För att kunna beräkna korrekta tider måste kompilatorn veta vilken klockfrekvens processorn arbetar med. I Arduinomiljön avgörs det av vilken typ av Arduino man valt, men informationen kan också skrivas i koden med  #define F_CPU 16000000UL.

#define F_CPU 16000000UL
#include <util/delay.h>
void _delay_ms (double);
void _delay_us (double);


Tillbaka till startsidan

 


William Sandqvist    willsandqvist@gmail.com