Hello World - med ATmega

Tillbaka till startsidan

Seriekommunikation

ATmega processorn har en inbyggd enhet för seriekommunikation, en USART (Universal Synchronous Asynchronous Receiver Transmitter).
I Arduinomiljön är denna ansluten till en USB-to-serial converter (typ FTDI TTL-232R-5V-USB eller motsvarande) och används för nedladdning av koden till processorn, men också för seriekommunikation mellan processorn och ett terminalprogram. Arduino IDE har ett inbyggt terminalfönster för sådan seriekommunikation.

  Eftersom seriekommunikationen med terminalfönstret är ett mycket användbart felsökningshjälpmedel, så det är viktigt att utveckla kod för seriekommunikation med processorn.

init_uart()   getchar_uart()   putchar_uart()

Figuren visar ett förenklat blockschema över ATmega processorns USART. Den verkliga kretsen är ändå mer komplex och den beskrivs detaljerat i processorns datablad.
Vi begränsar oss här till att initiera och använda USART för enkel kommunikation med Arduinos terminalfönster.

Blockschemats USART har tre kontrollregister UCSR0A, UCSR0B, UCSR0C, där man gör de inställningar som styr enheten. Registrens bitar har egna namn, och vi kommer att behöva använda bitarna med beteckningarna  RXEN0 TXEN0 (i UCSRB), RXC0 (i UCSRA), UDRE (i UCSRA), UCSZ01 UCSZ00 (i UCSRC).

init_uart()   UBRR

Inställningen av dataöverföringshastigheten, Baude rate, görs med 16-bitars registret UBRR. En vanlig överföringshastighet är 9600.
Databladet anger en formel för att räkna ut vilket tal som ska läggas i UBRR registret (UBRR består av två 8 bitsregister men registerparet kan laddas direkt som ett 16 bitarstal).

Med bitarna RXEN0 och TXEN0 slår man på mottagardelen och sändardelen, och med bitparet UCSZ01 UCSZ00 ställer man in arbetssättet - vårt fall asynkront 8 bitar.

void init_uart( void )
{
   UCSR0B |= (1 << ( RXEN0 )) | (1 << ( TXEN0 ));   // Transmitter on, Reciever on
   UCSR0C |= (1 << ( UCSZ01 )) | (1 << ( UCSZ00 )); // Mode: 8 bit asynk
   UBRR0   = 103;  // Baud prescaler 103 -> 9600 Baud
}

getchar_uart()   putchar_uart()

Man hämtar mottagna tecken i UDR(Reciever) registret. Detta är "dubbelbuffrat" så upp till två tecken kan finnas att läsa. Biten RXC0 (i UCSR0A) är 1 när minst ett tecken finns tillgängligt. Som funktionen getchar_uart() nedan är skriven så är den blockerande - så länge inget tecken inkommer så väntar vi här.

unsigned char getchar_uart( void )
{
    unsigned char ch;
    // wait until a byte is ready to read
    while( ( UCSR0A & (1 << ( RXC0 )) ) == 0 ){}
 
    // grab the byte from the serial port
    ch = UDR0;
    return ch;
}

Man lämnar tecken för sändning till UDR(Transmitter) registret. Sändarenheten är redo för detta när biten UDRE0 (i UCSR0A) är 1.

void putchar_uart( unsigned char ch )
{
  // wait until the port is ready to be written to
  while( ( UCSR0A & ( (1 << ( UDRE0 )) ) == 0 ){}
 
  // write the byte to the serial port
  UDR0 = ch;
}

echo()

Man kan pröva att getchar_uart() och putchar_uart() fungerar genom att köra ett eko-program. Det man skriver i terminalfönstret ska då ekas tillbaka från Arduinokortet!



Strängar och strängkonstanter

ATmega processorn har en Harvard arkitektur. RAM minnet består av ett litet antal General Purpose Register (GPU), och programminnet består av ett separat större flash minne. Programspråket C är egentligen framtaget för datorer med von Newman arkitektur som karaktäriseras av att allt minne är gemensamt.

unsigned char buffer[20];
const unsigned char hello[] = "hello world";

Den första deklarationen innebär att buffer blir en array med 20 st GPU register.
Registren kan indexeras (tex, 5:e elementet som buffer[4] ) eller nås med pekare ( tex, 5:e elementet som *(buffer+4) ).

Den andra deklarationen är en strängkonstant. Det är vanligt att man behöver lagra olika sådana förutbestämda (= konstanta) meddelanden som ska skrivas ut vid olika tillfällen i programmet.
Strängkonstanter lagras initialt i programminnet, men för att kunna sedan indexeras eller nås med pekare så kommer de att kopieras över till GPU register vid programmets start.
Problemet med detta är att GPU registren ofta är så få att de förmodligen behövs bättre till annat!

För att inte slösa med RAM så behöver man kunna lagra konstanter direkt i programminnet, och man behöver kunna nå dem där utan att de först kopieras över till RAM.
Man har löst detta problem genom att utöka AVR kompilatorn med ett antal makron och funktioner för detta. De finns med i includefilen pgmspace.h.

#include <avr/pgmspace.h>
const unsigned char FlashString[] PROGMEM = "This is a string held completely in flash memory.";

Vill man skriva ut en strängkonstant, utan att den först kopieras till RAM, måste man använda makron för "pekare" till programminnet. Funktionen string_out_uart_P() tar en sådan "pekare" till programminnet som argument. Den har fått ett funktionsnamn som slutar på _P för att ange detta. Många av C:s strängfunktioner för AVR finns att tillgå i två varianter, en för pekare till RAM och en för "pekare" till programminne (_P).

string_out_uart_P( FlashString );

void string_out_uart_P( const unsigned char * string )  // use for strings in program memory
{
  while ( pgm_read_byte( string ) != 0x00 )
    putchar_uart( pgm_read_byte( string ++) );
}

Så länge man inte har problem med bristande RAM minne kan man använda de normala C-funktionerna!

 string_out_uart("Hello World\r\n");    // Warning! this string will be copied to RAM registers at program start

 void string_out_uart( unsigned char * string ) // use for strings in RAM
{
   while (* string != '\0')
     putchar_uart(*string ++);
} 

Hello World!

Programmet helloworld visar hur man kan använda olika lagringssätt och olika utskriftsfunktioner för strängar i RAM och i programminne.

Detta programexempel använder "Windows style" för ny rad, både return och newline tecken.


Tillbaka till startsidan

 


William Sandqvist    william@kth.se