AVR drobnosti

RFID čtečka a ATmega8

V říjnu 2014 jsem bastlil domácí alarm a jelikož jsem ke vstupním dveřím nechtěl dávat klávesnici, rozhodl jsem se pro RFID. Tak jsem si od jednoho "soudruha" z Číny objednal níže uvedenou čtečku + 50 přívěsků na klíče. To vše za 400,-Kč, takže OK :-)

Čtečka má výstup protokolem Wiegand, jehož pěkný popis je např. zde http://www.dhservis.cz/dalsi_1/wiegand.htm

Popis výstupů:

Příjem jednotlivých bitů je realizován reakcí na sestupnou hranu signálu D0 - fous D0 je připojen na pin PD2(INT0). V programu je pak nastaveno, aby bylo na sestupnou hranu INT0 vyvoláno přerušení - tedy informace o tom, že čtečka poslala "nulu". Obdobným způsobem lze vyřešit i D1 - připojit ho na PD3(INT1) a reagovat na sestupnou hranu D1. Jenže problém ATMega8 je, že INT0 a INT1 jsou jen dva a pokud oba dva využijí na čtečku, tak už nezbyde pro nic jiného. Novější malé AVRka mají PCINTx, tedy možnost vyvolat přerušení na libovoleném pinu - mám však doma hromádku ATMega8... Čili vyřešeno je to následovně: D0 je připojen PD2(INT0), D1 pak na libovolný jiný GPIO. Mezi D1 a D0 je vložena dioda (anodou na D0 a katodou na D1). To způsobí, že pokud přijde od čtečky "jednička", tak se přes diodu stáhne k zemi i D0 a vyvolá se přerušení. Pokud přijde čtečky "nula", stáhne se k zemi jen D0 a D1 zůstane v klidovém stavu. Přerušení INT0 je tak vyvoláno libovolným bitem od čtečky a rozhodnutí, zda-li je příchozím bitem "nula" nebo "jedna" je test stavu pinu s připojenou D1.

Funkce a proměnné jsou následující:

Použití v souboru main.c


#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#define F_CPU 8000000UL  
#include <util/delay.h>
#include "rfid.c"
//#include "debug.c"

#define FALSE 0
#define TRUE  1

// Sekundove tiky od casovace Timer 1
ISR(TIMER1_COMPA_vect) {
  // takt pro provoz rfid
  rfidHeartBeat();
}

int main(void) {
  
  // nastaveni gsm
  rfidInit();   
  
  // nastaveni debugline
  //debugInit();
  
  // Nastaveni casovace Timer 1 na sekundove pulsy jako preruseni
  TCCR1B = (1<<CS12) | (1<<CS10) | (1<<WGM12);   // timer1 1024 prescaler, CTC mode
  OCR1AH = 0x1E; 
  OCR1AL = 0x84;
  TIMSK |= (1<<OCIE1A);

  // Povoleni preruseni
  sei();
  
  // hlavni smycka
  while(1) {

    // zpracovani nove prijate RFID karty           
    if( rfidNewCardReceived ) {

      // nyni je prijata nova karta, kod je v poli rfidReceivedData
      // test, zda-li v DB v EEPROM

      if(rfidFindCardInDB()) {

        // přijatou kartu zname, je v DB

      }

      // ukončeni zpracovani prijate karty
      rfidCleanup();      
    } 
   
   
    // zapis prijate karty do DB na pozici 5
    rfidAddCard(5);    

    // smazani karty z DB na pozici 0
    rfidDeleteCard(0);    

  }
  
}

Komunikace s GSM modemem (Siemens TC35i, aj.)

Umožňuje tři základní funkce: přijmout SMS, odeslat SMS a prozvonit. A to tak, že vše je řízeno časovačem posunováním se v malém stavovém prostoru. Program tedy může dělat cokoliv jiného, než jen čekat na odpovědi z modemu. Funkce a proměnné jsou následující:

Použití v souboru main.c


#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#define F_CPU 8000000UL  
#include <util/delay.h>
#include "gsm.c"
//#include "debug.c"

#define FALSE 0
#define TRUE  1

// Sekundove tiky od casovace Timer 1
ISR(TIMER1_COMPA_vect) {
  // takt pro provoz GSM modemu
  gsmHeartBeat();
}

int main(void) {
  
  // nastaveni gsm
  gsmInit();   
  
  // nastaveni debugline
  //debugInit();
  
  // Nastaveni casovace Timer 1 na sekundove pulsy jako preruseni
  TCCR1B = (1<<CS12) | (1<<CS10) | (1<<WGM12);   // timer1 1024 prescaler, CTC mode
  OCR1AH = 0x1E; 
  OCR1AL = 0x84;
  TIMSK |= (1<<OCIE1A);

  // Povoleni preruseni
  sei();
  
  // hlavni smycka
  while(1) {

    // Test priznaku prijate zpravy
    if( gsmNewSMS ) {
      
      // PrijataSMS.tel   - zde je cislo odesilatale SMS
      // PrijataSMS.text  - zde je text zpravy

      // odeslani odpovedi - papousek - posle to co dostal
      gsmSendSMS(PrijataSMS.tel, MEMORY, PrijataSMS.text, MEMORY);
      
      // Nastaveni priznaku, ze SMS byla zpracovava
      gsmNewSMS = FALSE;
    }
    
    // odeslani SMS
    gsmSendSMS(PSTR("420608123456"), FLASH, PSTR("Ahoj svete"), FLASH);
    
    // zpozdeni smycky - aby se v tomto pripade poslala SMS jednou za 5 sekund
    _delay_ms(5000);

  }

}

UART debug

Jedná se o dvě funkce, které umějí poslat na jeden GPIO pin UART rámec se znakem nebo textem. Nepotřebují k tomu žádné HW zdroje, kromě zmíněného jednoho pinu. Ideální pro debug malých AVRek.

Použití v souboru main.c

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#define F_CPU 8000000UL  
#include <util/delay.h>
#include "debug.c"

#define FLASH   1
#define MEMORY  0

// Retezec v FLASH
char RETEZEC[] PROGMEM  = "Ahoj svete z FLASH";

// Retezec v RAM
char mystring[31]= {"Ahoj svete z RAM"};

int main(void) {

  // nastaveni debugline
  debugInit();

  // hlavni smycka
  while(1) {

    // vypis retezce z RAM
    debugSendString(mystring, MEMORY);

    // vypis retezce z FLASH
    debugSendString(RETEZEC, FLASH);

    // vypis jednorazoveho retezce z FLASH
    debugSendString(PSTR("Ahoj"), FLASH);

    // vypis jednoho znaku
    debugSendChar('X');

    // krokovaci zpozdeni
    _delay_ms(1000);
  }

}

29.08.2007

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