AVR-GCC střípky

Už je to tak 2 roky, kdy jsem si řekl, že s assemblerem končím :-) Fakt, je to sice úžasný způsob programování jednočipů, ale jelikož bych nerad datlováním delších programů strávil půlku života, tak když jsem se rozhodoval co dál, Céčko byla jasná volba :-)))

Po prvotním zděšení z avr-gcc a jeho MakeFile jsem na chvíli zakotvil u komerčního CodeVison, které bylo na první klik funkční a předvařené kontrukce a konfigurace v začítcích opravdu dost pomohly. Poté jsem se ale vrátil k avr-gcc a to hlavně z toho důvodu, že je free a bez nějakých zbytečných omezení.

Na této stránce bych chtěl postupně umísťovat různé postřehy a drobnosti, které mě potkají. Není mým cílem zde uvádět nějaké advanced záležitosti, ale opravdu jednoduché věci, které mohou začátečníkům pomoci se v C pro AVR zorientovat.

Všechno je odladěno a odzkoušeno na WinAVR (GCC + AVRLibc). Na webu AVRLibc je taky dosti dobrá dokumentace, především co se týče eeprom, přerušení a všeho možného u AVR architektury a C.


Jednoduché čtení teploty z čidla DS1820.

Tento kousek kódu je schopen komunikovat s čidlem DS1820 na one-wire sběrnici a přečíst z něj teplotu. Omezení je, vzhledem k jednoduchosti, ve schopnosti komunikovat pouze s jedním zařízením na sběrnici. Ve většině případů to stačí a implementace plné podpory one-wire je zbytečná.

#include <avr/io.h>
#define F_CPU 4000000UL  // 4 MHz - takt MCU
#include <util/delay.h>
#include "myds.h"

#define PD6         0x40            // na PD6 je připojeno čidlo
#define TX          DDRD |= PD6;
#define RX          DDRD &= ~PD6;
#define RXPIN       PIND&PD6


// provede reset a test prezence ds-18b20 na sbernici 
unsigned char ow_detect_presence(void) {
  unsigned char out=1;              // vychozi navratova hodnota
  RX;                               // vychozi stav sbernice
  _delay_us(1000);                  // pro ustaleni
  TX;                               // bus low
  _delay_us(480);                   // cas pro prikaz reset
  RX;                               // uvolneni sbernice
  _delay_us(70);                    // cekani na potvrzeni teplomerem
  if(RXPIN) out=0;                  // pokud detekovana log.1, tak teplomer na sbernici neni
  _delay_us(410);                   // pauza pred dalsi komunikaci
  return out;                       // vrati stav 1=teplomer nalezen,  0=teplomer nenalezen
}

// posle na sbernici log.1
void ow_write_one(void) {
  TX;                               // bus low
  _delay_us(6);                     // pauza definujici log.1
  RX;                               // uvolneni sbernice
  _delay_us(64);                    // pauza pred dalsi komunikaci 
}

// posle na sbernici log.0
void ow_write_zero(void) {
  TX;                               // bus low
  _delay_us(60);                    // pauza definujici log.0
  RX;                               // uvolneni sbernice
  _delay_us(10);                    // pauza pred dalsi komunikaci 
}

// precte jeden bit ze sbernice
unsigned char ow_read_bit(void) {
  unsigned char out=0;              // vychozi navratova hodnota bitu
  TX;                               // bus low
  _delay_us(6);                     // pauza pro stav cteni
  RX;                               // uvolneni sbernice
  _delay_us(9);                     // pauza pro reakci teplomeru
  if(RXPIN) out=1;                  // test stavu sbernice, vlastni cteni
  _delay_us(55);                    // pauza pred dalsi komunikaci  
  return out;                       // prectena hodnota, 1 nebo 0
}

// odeslne na sbernici jeden byte. Odesila se prvni LSB
void ow_write_byte(unsigned char tosend) {
  int n=8;
  while(n--) {
    if(tosend&1) ow_write_one(); else ow_write_zero();
    tosend >>= 1;
  }
}

// prijde ze sbernice jeden byte. Prijima jako prvni LSB.
unsigned char ow_read_byte(void) {
  int n=8, out=0;
  while(n--) {
    out >>= 1;                       // bitovy posuv doprava
    if(ow_read_bit()) out |= 0x80;   // nastaveni nejvyssiho bitu na 1
  }
  return out;
}

// nacte teplotu z teplomeru a vrati ji ve formatu 1000+t*10
// priklad: 23.5°C = 1235,  -10.5°C = 895
// tento format lze snadneji zpracovavat nez nejake floaty (zerou moc pameti)
unsigned int zmer(void) {
  unsigned char data_lo, data_hi, desetiny;
  signed char teplota;
  unsigned int tmp = 0;
  cli();
  ow_detect_presence();
  ow_write_byte(0x0CC);
  ow_write_byte(0x44);
  _delay_ms(300);
  ow_detect_presence();
  ow_write_byte(0x0CC);
  ow_write_byte(0x0BE);
  data_lo=ow_read_byte();     // 1. byte scratchpadu teplomeru = spodni byte teploty
  data_hi=ow_read_byte();     // 2. byte scratchpadu teplomeru = horni byte teploty
  teplota = (data_lo & 0x0F0) >> 4 | (data_hi & 0x0F) << 4 ;   // signed teplota
  tmp = 10 * teplota + 1000;
  desetiny = (data_lo & 0x0F) * 0.625;
  if(tmp<1000) tmp -= desetiny; else tmp += desetiny;
  if((tmp<700)||(tmp>2200)) tmp=0;
  sei();
  return tmp; 
}


int main(void) {
 while(1) {
   funkce_co_nekam_posle_vysledek( zmer() );
 }
}


Vlastní zpožďovadlo s využitím timeru a přerušení

#include <avr/io.h>
#include <avr/interrupt.h>

// povoleni a zakazani preruseni pro generovani casove prodlevy
#define TIMER1_OVF_ENABLE TIMSK|=1<<TOIE1
#define TIMER1_OVF_DISABLE TIMSK&=~(1<<TOIE1)

// globalni promenna pouzita v preruseni
volatile unsigned int counter = 0;

// preruseni kazdou 1ms na generovani zpozdeni
// TCNT1=65535-(F_CPU/1000)
// Pro 4MHz: 65535-(4000000/1000)=65535-4000=61535=F05F
ISR(TIMER1_OVF_vect) {
 TCNT1H=0x0F0;
 TCNT1L=0x5F;
 if (counter) {
  counter--;
  asm("wdr");
 }
}

// cekaci smycka po kroku 1ms
void delay(unsigned int cas) {
 counter=cas;
 TIMER1_OVF_ENABLE;
 while(counter) asm("nop");
 TIMER1_OVF_DISABLE;
}

int main {
 TCCR1A=0x00;
 TCCR1B=0x01; // timer1 bez preddelicky, vstup systemovy takt
 sei(); // povoleni preruseni
 while(1) {
  delay(1000);
 }
}

EEPROM aneb ukládáme na 100 let

Pracovat z EEPROM nelze tak jednoduše, jako tomu bylo například v CodeVision, ale musí se využívat funkce k tomu určené.

#include <avr/io.h>
#include <avr/eeprom.h>

#define EEPROM_SECTION __attribute__ ((section (".eeprom")))

// globalni promenne v EEPROM, například tyto
uint8_t ee_pole[3] EEPROM_SECTION = {'1','2','3'}; // unsigned char pole
uint16_t ee_bigpole[200] EEPROM_SECTION = {0x6581,0x3283}; // unsigned int pole
uint16_t ee_cislo EEPROM_SECTION = 15; // unsigned int
uint8_t ee_kolo EEPROM_SECTION; // unsigned char

// globalni promenne v RAM na ukazku prace s EEPROM
unsigned int a;
unsigned char x,y;

int main(void) {
 // "běžná" práce s EEPROM je pak například tato
 a=eeprom_read_word(&ee_bigpole[1]);
 x=2;
 eeprom_write_byte(&ee_pole[x],0x2F);
 y=x+eeprom_read_byte(&ee_kolo);
 y=200;
 while(y--){
  eeprom_write_word(&ee_bigpole[y],eeprom_read_word(&ee_cislo););
 }
}

Konstanty ve FLASHce

K čemu je to dobré? V případě, že máte velké množství konstant, které ve vašem programu potřebujete, tak je celkem nesmyslné je cpát do RAM, které je vždycky málo. Daleko vhodnější je nechat tyto konstatnty ve flash paměti a v případě potřeby si pro ně jednoduše sáhnout. Příkladem můžou být texty a hlášky, které jednočip vypisuje na displej a nebo posílá přes RS232 do světa. Opět dokumentace. Taková jednoduchá ukázka je zde:

#include <avr/io.h>
#include <avr/pgmspace.h>

char HELLO[] PROGMEM = "Ahoj svete, ja jsem konstanta";
char CHYBA[] PROGMEM = "Chyba, uzivatel je nepritomen";
char ZIVOT[] PROGMEM = "Je pondeli rano a to nemam rad :-)";

// vypsani znakoveho retezce na terminal z flash pameti
void write(char *sss){
 char k;
 while ((k=pgm_read_byte(sss++))>0) {
  funkce_co_posle_znak_nekam(k);
 }
 lfcr();
}

int main(void) {
 write(HELLO);
 write(CHYBA);
 write(ZIVOT);
}

Na počátku byla LED a jednočip

Blikání LED je sice doopravdy dost velká blbost, ale někdy se hodí, aby obsluha věděla, že procesor žije. A to nejlépe tak, aby blikání nezatěžovalo a nekomplikovalo vlastní program. Pokud je k dispozici nějaký časovač, je blikání snadné

#include <avr/io.h>
#include <avr/interrupt.h>

// stavova ledka
#define LED 0x04 // PD2
#define LED_SLOW TCCR0=0x05 // nastaveni TCCR0 na delicku 1024 pro pomale blikani led
#define LED_FAST TCCR0=0x04 // nastaveni TCCR0 na delicku 256 pro rychle blikani led
#define LED_SUPERFAST TCCR0=0x02 // nastaveni TCCR0 na delicku 8 pro super rychle blikani led

// bitova delicka
volatile unsigned char ledscaler = 0x08;

// preruseni kde se prevraci stav vystupu s LEDkou
ISR(TIMER0_OVF_vect) {
 if(!(ledscaler>>=1)) {
  if(!(PORTD&LED)) PORTD|=LED; else PORTD&=~LED;
   ledscaler=0x10;
 }
}

int main(void) {
 PORTD=0x0;
 DDRD=0x0FF; // PortD jako vystup
 TIMSK|=1<<TOIE0; // povoleni preruseni timeru0 na preteceni
 sei(); // globani povoleni preruseni
 LED_SLOW;
 //LED_FAST;
 //LED_SUPERFAST;
}


Nahoru

Spáchal Walda 2008 - Datum: