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ů:
- Vcc - napájení 9-16V
- GND - zem
- D1 - data 1
- D0 - data 0
- LED - na čelním panelu je dvojbarevná LED. Implicitně svítí červeně. Po připojení tohoto vodiče na GND se barva změní na zelenou.
- BEEP - připojením tohoto vodiče na GND začne pípák pískat. Mimo to čtečka sama pípne po přiložení karty. Zde jsem našel malou zradu: Pokud se čtečka nechá pískat trvale (BEEP na GND), tak neodesílá kód přijaté karty na D1 a D0. Je tedy nutné, pokud aplikace vyžaduje aktustický projev při snímání karty, nechat pískat čtečku přerušovaně, např. 200ms klid, 50ms píp. Pak snímá karty rychle a spolehlivě.
- Wiegand 26/34 - připojením tohoto vodiče na GND začne čtečka odesílat po přiložení karty 34 bitů dat místo výchozích 26. U mých klíčků je však v těchto dalších 8-mi bitech jen nula.
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í:
- rfidInit() - inicializace nastavení (GPIO, přerušení, pullupy apod.)
- rfidHeatBeat() - nutné spouštět každou sekundu časovačem, realizuje timeout čtení
- rfidCleanup() - zresetuje data o poslední přečtené kartě
- rfidAddCard(...) - přidá kartu do DB v EEPROM
- rfidDeleteCard(...) - smaže kartu z DB v EEPROM
- rfidFindCardInDB() - true/false, ověří, zdali je přijatá karta v DB v EEPROM
- rfidNewCardReceived - true/false příznak přijetí nové karty
Zobrazit/skrýt rfid.c
// -------------------------------------------------------------------------------------------------
// Modul: RFID
// Funkce: Obsluha ctecky RFID karet protokolem Wiegand 26 a sprava databaze v EEPROM
// Pozadavky: INT0, 2 GPIO pro signály D1 a D0 Wiegand 26, kdy jeden musi mit INT0
// Spec: D1 a D0 musi byt premosteno diodou, tak, aby preruseni INT0 vyvolal libovolny
// prichozi bit.
// -------------------------------------------------------------------------------------------------
// vstupy
#define RFID_D0_IN (PIND&(1<<PD2))
#define RFID_D1_IN (PIND&(1<<PD3))
// nastaveni IO pinu + pullup
#define RFID_IO_INPUT DDRD&=~((1<<PD2)|(1<<PD3));
#define RFID_IO_PULLUPS PORTD|=(1<<PD2)|(1<<PD3);
// timeout pro cteni kodu karty
#define RFID_RX_TIMEOUT 2
// max pocet karet v DB
#define RFID_DBCARDS 10
// indexy
#define TRUE 1
#define FALSE 0
// Promenne v EEPROM ------------------------------------------------------------------------------
#define EEPROM_SECTION __attribute__ ((section (".eeprom")))
typedef struct{
unsigned char data[4];
} RFIDCARDDB;
RFIDCARDDB ee_rfidCardDB[RFID_DBCARDS] EEPROM_SECTION;
// /Promenne v EEPROM -----------------------------------------------------------------------------
// globani promenne
volatile unsigned char rfidReceivedData[4];
volatile unsigned char rfidBitCount;
volatile unsigned char rfidTimeout;
volatile unsigned char rfidNewCardReceived;
// prototypy
void rfidInit(void);
void rfidCleanup(void);
void rfidHeartBeat(void);
void rfidDeleteCard(unsigned char index);
void rfidAddCard(unsigned char index);
unsigned char rfidFindCardInDB(void);
// -------------------------------------------------------------------------------------------------
// [INT] Obsluha preruseni na sestupnou hranu INT0
// Prijem bitu ze ctecky
// -------------------------------------------------------------------------------------------------
ISR (INT0_vect) {
unsigned char byte_index=rfidBitCount>>3;
unsigned char bit_index=rfidBitCount&0x07;
if( RFID_D1_IN ) {
rfidReceivedData[byte_index]|=(1<<bit_index);
} else {
rfidReceivedData[byte_index]&=~(1<<bit_index);
}
rfidBitCount++;
if( rfidBitCount==26 ) {
rfidReceivedData[3]&=0x03;
//debugSendString(PSTR("[RFID] Prijata nova karta\n"), FLASH);
rfidNewCardReceived=TRUE;
}
rfidTimeout=RFID_RX_TIMEOUT;
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Provede zakladni nastaveni RFID
// -------------------------------------------------------------------------------------------------
void rfidCleanup(void) {
rfidBitCount=0;
rfidNewCardReceived=FALSE;
rfidTimeout=0;
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Timeout pro cteni kodu karty - volat 1x za sekundu
// -------------------------------------------------------------------------------------------------
void rfidHeartBeat(void) {
if( rfidTimeout ) {
rfidTimeout--;
if( (rfidTimeout==0) && (rfidNewCardReceived==FALSE) ) {
rfidCleanup();
}
}
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Smaze kod karty z prislusne pozice z DB
// Vstup: index - pozice kodu karty v DB
// -------------------------------------------------------------------------------------------------
void rfidDeleteCard(unsigned char index) {
unsigned char i=4;
if(index<RFID_DBCARDS) {
while(i--){
eeprom_write_byte(&ee_rfidCardDB[index].data[i], 0xAA);
}
//debugSendString(PSTR("[RFID] Smazani karty "), FLASH);
//debugSendChar(index+'0');debugSendChar('\n');
}
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Vlozi kod karty z rfidReceivedData do DB na prislusnou pozici
// Vstup: index - pozice kodu karty v DB
// -------------------------------------------------------------------------------------------------
void rfidAddCard(unsigned char index) {
unsigned char i=4;
if(index<RFID_DBCARDS) {
while(i--){
eeprom_write_byte(&ee_rfidCardDB[index].data[i], rfidReceivedData[i]);
}
//debugSendString(PSTR("[RFID] Zapis karty "), FLASH);
//debugSendChar(index+'0');debugSendChar('\n');
}
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Vyhleda index v DB, kde se nachazi karta nactena v rfidReceivedData
// Vystup: TRUE/FALSE dle toho, zda-li byl kod karty v DB nalezen nebo ne
// -------------------------------------------------------------------------------------------------
unsigned char rfidFindCardInDB(void) {
unsigned char out, i, index=RFID_DBCARDS;
while(index--) {
i=4;
out=TRUE;
while(i--) {
if(eeprom_read_byte(&ee_rfidCardDB[index].data[i])!=rfidReceivedData[i]) {
out=FALSE;
break;
}
}
if(out==TRUE) {
//debugSendString(PSTR("[RFID] Karta v DB "), FLASH);
//debugSendChar(index+'0');debugSendChar('\n');
return(TRUE);
}
}
return(FALSE);
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Provede zakladni nastaveni RFID
// -------------------------------------------------------------------------------------------------
void rfidInit(void) {
// nastaveni GPIO jako vstup
RFID_IO_INPUT;
// nastaveni pullup na vstupni piny
RFID_IO_PULLUPS;
// preruseni na sestupnou hranu INT0
MCUCR |= (1 << ISC01);
// povoleni preruseni od INT0
GICR |= (1 << INT0);
// nastaveni vychoziho stavu
rfidCleanup();
}
// -------------------------------------------------------------------------------------------------
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í:
- gsmInit() - inicializace nastavení (UART, indikační LED, GPIO)
- gsmHeatBeat() - nutné spouštět každou sekundu časovačem, řídí starty a timeouty komunikací
- gsmSendSMS(...) - zařazení nové SMS do fronty odchozích SMS a spuštění odesílání
- gsmRing(...) - prozvonění
- gsmNewSMS - true/false příznak nově příchozí SMS zprávy
- PrijataSMS.tel - řetězec s telefonním číslem nově přijaté zprávy
- PrijataSMS.text - řetězec s textem nově přijaté zprávy
Zobrazit/skrýt gsm.c
// -------------------------------------------------------------------------------------------------
// Modul: GSM
// Funkce: Obsluha GSM modemu, prijem a cteni SMS, prozvaneni
// Pozadavky: HW UART a jeden GPIO pro indikacni LED
// -------------------------------------------------------------------------------------------------
// bitove flagy stavu USARTu
#define FRAMING_ERROR (1<<FE)
#define PARITY_ERROR (1<<PE)
#define DATA_OVERRUN (1<<DOR)
#define DATA_REGISTER_EMPTY (1<<UDRE)
#define RX_COMPLETE (1<<RXC)
// ruzne
#define FALSE 0
#define TRUE 1
// zdroj pro vypis retezcu
#define MEMORY 1
#define FLASH 0
// stavova LED pripojeni modemu
#define ONLINELED_OFF PORTD |= (1<<PD5);
#define ONLINELED_ON PORTD &= ~(1<<PD5);
#define ONLINELED_IOSET DDRD |= (1<<PD5);
// komunikacni buffer pro UART
#define RX_BUFFER_SIZE 40
volatile char unsigned rx_buffer_pos = 0;
volatile unsigned char rx_buffer[RX_BUFFER_SIZE];
// sktruktura pro ulozeni SMS zprav
#define TEL_SIZE 13
#define TEXT_SIZE 20
typedef struct {
unsigned char tel[TEL_SIZE+1];
unsigned char text[TEXT_SIZE+1];
} structMessage;
// fronta zprav
#define TXFRONTA_MAX_COUNT 5
volatile structMessage TXFronta[TXFRONTA_MAX_COUNT];
volatile unsigned char TXFrontaData = FALSE;
volatile unsigned char TXFrontaIndex = 0;
// promenna na ulozeni prijate zpravy a docasne id zpravy
volatile structMessage PrijataSMS;
volatile char gsmId[3] = {0,0,0};
// seznam stavu komunikace s modemem
#define NOT_COMM 0
#define MODEM_INIT 17
#define MODEM_INIT2 18
#define CHECK_PRESENT 20
#define SMS_REQUEST 21
#define SMS_READ 22
#define SMS_READ2 23
#define SMS_READ3 24
#define SMS_READ4 25
#define SMS_READ5 26
#define SMS_SEND 30
#define SMS_SEND1 31
#define SMS_SEND2 32
#define GSM_RINGRING 33
// retezec OK pro testy odpovedi od modemu
char GSM_AT_OK[] PROGMEM = "OK";
// makro pro vypnuti timeoutu komunikace
#define COMMTIMEOUT_OFF 0
// perioda spousteni komunikace v sec
#define COMM_PERIOD 10
// priznak, zda-li pripojeny GSM modem
volatile unsigned char gsmIsModemInitialized = FALSE;
// casovy limit pro komunikaci s modemem
volatile unsigned int gsmCommTimeout = 0;
// casovac pro periodicke spousteni komunikace
volatile unsigned int gsmCommTimer = COMM_PERIOD;
// stav komunikace se modemem
volatile unsigned int gsmState = NOT_COMM;
// priznak nove prichozi SMS
volatile unsigned int gsmNewSMS = FALSE;
// prototypy
static void gsmSendChar(char c);
static void gsmSendString(char *sss, unsigned char zdroj);
static void gsmStartComm();
static unsigned char gsmIsInBufferFromLeft(char *sss, char zdroj);
void gsmHeartBeat(void);
void gsmRing(char *tel, char zdrojtel);
unsigned char gsmSendSMS(char *tel, char zdrojtel, char *text, char zdrojtext);
void gsmInit(void);
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Funkce na vlozeni cisla a textu fronty SMS zprav k odeslani
// Vstupy *tel - ukazatel na string
// zdrojtel - 1=string v RAM, 0=string ve flash
// *text - ukazatel na string
// zdrojtext - 1=string v RAM, 0=string ve flash
// -------------------------------------------------------------------------------------------------
unsigned char gsmSendSMS(char *tel, char zdrojtel, char *text, char zdrojtext) {
char znak;
char i, index, found = FALSE;
// nalezeni volne pozice pro zapis nove SMS do fronty
for(index=0; index<TXFRONTA_MAX_COUNT; index++) {
if( (TXFronta[index].tel[0]==0) && (TXFronta[index].text[0]==0) ) {
found = TRUE;
break;
}
}
// pokud nebylo zadne volne misto nalezeno
if(!found) return(FALSE);
// vlozeni do fronty na volnou pozici
i=0;
while ( i<TEL_SIZE ) {
if (zdrojtel) znak=(*tel++); else znak=pgm_read_byte(tel++);
if (!znak) break;
TXFronta[index].tel[i++] = znak;
TXFronta[index].tel[i] = 0;
}
i=0;
while ( i<TEXT_SIZE ) {
if (zdrojtext) znak=(*text++); else znak=pgm_read_byte(text++);
if (!znak) break;
TXFronta[index].text[i++] = znak;
TXFronta[index].text[i] = 0;
}
TXFrontaData = TRUE;
if( gsmState==NOT_COMM ) gsmCommTimer=2;
return(TRUE);
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PRIVATE] Funkce na porovnani textu retezce v RAM s obsahem bufferu zleva
// Vstup: *sss - retezec v RAM
// Vystup: TRUE - retezec sss je obsazen v bufferu
// FALSE - retezec neni obsazen v bufferu
// -------------------------------------------------------------------------------------------------
static unsigned char gsmIsInBufferFromLeft(char *sss, char zdroj) {
char k,i=0;
if (rx_buffer[0]==0) return(FALSE);
if(zdroj) {
while ((k=(*sss++))>0) if ( rx_buffer[i++] != k ) return(FALSE);
} else {
while ((k=pgm_read_byte(sss++))>0) if ( rx_buffer[i++] != k ) return(FALSE);
}
return(TRUE);
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Funkce je volana casovacem (1s) a slouzi k realizaci timoutu pro komunikaci
// -------------------------------------------------------------------------------------------------
void gsmHeartBeat(void) {
// dekrementace casovace pro spusteni komunikace s modemem
// jde o to, aby se komunikace poustela periodicky po nekolika sekundach
// na nule se zastavi
if( gsmCommTimer==1 ) gsmStartComm();
if( gsmCommTimer ) gsmCommTimer--;
// casovac komunikace s modemem - realizuje timeout cteni
// na nule se vypne
// pruchodem 1 proveden akce
if( gsmCommTimeout == 1 ) {
//debugSendString(PSTR("\n[GSM] Modem not respond\n"), FLASH);
gsmIsModemInitialized = FALSE;
ONLINELED_OFF;
gsmState = NOT_COMM;
gsmCommTimer = COMM_PERIOD;
}
if( gsmCommTimeout ) gsmCommTimeout--;
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PRIVATE] Posle znak na UART
// Vstup: c - znak k odeslani na UART
// -------------------------------------------------------------------------------------------------
static void gsmSendChar(char c) {
while (!(UCSRA & DATA_REGISTER_EMPTY));
UDR=c;
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PRIVATE] Posle retezec na UART
// Vstup: *sss - retezec v RAM nebo FLASH
// -------------------------------------------------------------------------------------------------
static void gsmSendString(char *sss, unsigned char zdroj){
char k;
if(zdroj) {
while ((k=(*sss++))>0) gsmSendChar(k);
} else {
while ((k=pgm_read_byte(sss++))>0) gsmSendChar(k);
}
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [INT] Obsluha preruseni pri prichodu znaku na UART (od GSM modemu)
// -------------------------------------------------------------------------------------------------
ISR(USART_RXC_vect) {
unsigned char status = UCSRA, data = UDR, n, pu, k, mts;
// test korektnosti prijateho znaku
if ((status & (FRAMING_ERROR | PARITY_ERROR | DATA_OVERRUN))!=0) return;
// postupne naplnovani bufferu s ochranou proti overflow
if( ( data >= 32 ) && ( rx_buffer_pos < RX_BUFFER_SIZE ) ) {
rx_buffer[rx_buffer_pos++] = data;
}
// reakce na znak konce radku nebo zobacek pri odesilani SMS
if( (data=='\n') || ((data=='>') && (gsmState==SMS_SEND2)) ) {
// rozdeleni dle typu prave provadene komunikace
switch (gsmState){
// --- inicializace modemu ---
// reakce na odpoved zmeny na texmode, pozadavek na nastaveni pameti
case MODEM_INIT:
if( gsmIsInBufferFromLeft(GSM_AT_OK, FLASH) ) {
//debugSendString(PSTR("[GSM] Set textmode "), FLASH);
gsmSendString(PSTR("AT+CPMS=\"MT\",\"MT\",\"MT\"\r"), FLASH);
gsmState = MODEM_INIT2;
gsmCommTimeout = 5;
}
break;
// reakce na nastaveni pameti. Pokud OK, tak je modem zivy a korekne nastaven
case MODEM_INIT2:
if( gsmIsInBufferFromLeft(GSM_AT_OK, FLASH) ) {
//debugSendString(PSTR("& memory \n"), FLASH);
gsmCommTimeout = COMMTIMEOUT_OFF;
gsmState = NOT_COMM;
gsmIsModemInitialized = TRUE;
ONLINELED_ON;
gsmCommTimer = COMM_PERIOD;
}
break;
// --- cteni a smazani sms ---
// reakce na pozadavek cteni SMS, vyparsovani ID a ODESILATELE
case SMS_READ:
if( gsmIsInBufferFromLeft(PSTR("+CMGL:"), FLASH) ) {
// ulozeni ID zpravy
gsmId[0] = rx_buffer[7];
if( rx_buffer[8] != ',' ) gsmId[1] = rx_buffer[8]; else gsmId[1]=0;
// ulozeni telefonniho cisla odesilatele
n=0;
pu=0;
k=0;
while(n++<rx_buffer_pos) {
if ( rx_buffer[n]=='"' ) pu++;
if ( (pu==3) && (rx_buffer[n]>='0') && (rx_buffer[n]<='9') ) {
PrijataSMS.tel[k] = rx_buffer[n];
PrijataSMS.tel[++k]=0;
}
}
gsmState=SMS_READ2;
}
// zadna SMS
if( gsmIsInBufferFromLeft(GSM_AT_OK, FLASH) ) {
gsmCommTimeout = COMMTIMEOUT_OFF;
gsmState = NOT_COMM;
gsmCommTimer = COMM_PERIOD;
}
break;
// cteni textu prvni prijate zpravy
case SMS_READ2:
n=0;
while( n < rx_buffer_pos ) {
PrijataSMS.text[n++] = rx_buffer[n]; // prepis zpravy do bufferu
PrijataSMS.text[n] = 0; // doplneni znaku konce retezce
if (n == TEXT_SIZE) break; // orez, pokud je zprava delsi jak buffer zpravy
}
gsmState=SMS_READ3;
break;
// cekani na OK po seznamu zprav a nasledne smazani nactene zpravy
case SMS_READ3:
if( gsmIsInBufferFromLeft(GSM_AT_OK, FLASH) ) {
gsmSendString(PSTR("AT+CMGD="), FLASH);
gsmSendString(gsmId, MEMORY);
gsmSendChar('\r');
gsmState = SMS_READ4;
gsmCommTimeout = 5;
}
break;
// potvrzeni o smazani zpravy
case SMS_READ4:
if( gsmIsInBufferFromLeft(GSM_AT_OK, FLASH) ) {
//debugSendString(PSTR("[GSM] <- "), FLASH);
//debugSendString(gsmId, MEMORY);
//debugSendChar(':');
//debugSendString(PrijataSMS.tel, MEMORY);
//debugSendChar(':');
//debugSendString(PrijataSMS.text, MEMORY);
//debugSendChar('\n');
gsmCommTimeout = COMMTIMEOUT_OFF;
gsmState = NOT_COMM;
gsmNewSMS = TRUE;
gsmCommTimer = COMM_PERIOD;
}
break;
// --- odeslani fronty SMS ---
// zahajeni odesilani SMS zpravy
case SMS_SEND:
if( gsmIsInBufferFromLeft(GSM_AT_OK, FLASH) ) {
TXFrontaIndex=0;
mts=FALSE;
while (TXFrontaIndex < TXFRONTA_MAX_COUNT) {
if( TXFronta[TXFrontaIndex].tel[0] && TXFronta[TXFrontaIndex].text[0] ) {
mts=TRUE;
break;
} else {
TXFrontaIndex++;
}
}
if(mts) {
//debugSendString(PSTR("[GSM] -> "), FLASH);
//debugSendString(TXFronta[TXFrontaIndex].tel, MEMORY);
//debugSendChar(':');
gsmSendString(PSTR("AT+CMGS=\"+"), FLASH);
gsmSendString(TXFronta[TXFrontaIndex].tel, MEMORY);
gsmSendString(PSTR("\"\r"), FLASH);
gsmState = SMS_SEND2;
gsmCommTimeout = 8;
} else {
gsmCommTimeout = COMMTIMEOUT_OFF;
gsmState = NOT_COMM;
TXFrontaData = FALSE;
gsmCommTimer = COMM_PERIOD;
}
}
break;
// odesilani SMS zprav, text
case SMS_SEND2:
if( gsmIsInBufferFromLeft(PSTR(">"), FLASH) ) {
gsmSendString(TXFronta[TXFrontaIndex].text, MEMORY);
//debugSendString(TXFronta[TXFrontaIndex].text, MEMORY);
//debugSendChar('\n');
gsmSendChar(0x1a);
// smazani zaznamu ve fronte zprav
TXFronta[TXFrontaIndex].tel[0] = 0;
TXFronta[TXFrontaIndex].text[0] = 0;
gsmState = SMS_SEND;
gsmCommTimeout = 8;
}
break;
}
// reset vstupniho bufferu
rx_buffer_pos=0;
rx_buffer[0]=0;
}
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PRIVATE] Vstupni bod do stavoveho prostoru komunikace s GSM modemem
// -------------------------------------------------------------------------------------------------
static void gsmStartComm() {
// dle pripravenosti modemu
switch (gsmIsModemInitialized) {
// modem neni pripraven
case FALSE:
// pokusime se ho detekovat a nastavit
if( gsmState==NOT_COMM ) {
//debugSendString(PSTR("[GSM] Modem init\n"), FLASH);
gsmSendString(PSTR("AT+CMGF=1\r"), FLASH);
gsmState = MODEM_INIT;
gsmCommTimeout = 5; // cas 5 sekund na odpoved
}
break;
// modem je pripraven
case TRUE:
// odesleme frontu SMS zprav
if( ( gsmState==NOT_COMM ) && ( TXFrontaData ) ) {
//debugSendString(PSTR("[GSM] Sending queue of SMS\n"), FLASH);
gsmSendString(PSTR("AT\r"), FLASH);
gsmState = SMS_SEND;
gsmCommTimeout = 5; // cas 5 sekund na odpoved
}
// precteme prichozi SMS zpravy, pokud se nema nic odeslat a neni prijata nezpracovana zprava
if( ( gsmState==NOT_COMM ) && ( !TXFrontaData ) && ( !gsmNewSMS ) ) {
//debugSendString(PSTR("[GSM] Checking new SMS\n"), FLASH);
gsmSendString(PSTR("AT+CMGL=\"ALL\"\r"), FLASH);
gsmState = SMS_READ;
gsmCommTimeout = 15; // cas 8 sekund na odpoved
}
break;
}
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Prozvoneni
// -------------------------------------------------------------------------------------------------
void gsmRing(char *tel, char zdrojtel) {
unsigned long x = 0x0FFFFFF;
// modem musi byt nastaveny a bez komunikace
if(!gsmIsModemInitialized) return;
while(gsmState != NOT_COMM);
// sestaveni hovoru
//debugSendString(PSTR("[GSM] Ringing to "), FLASH);
//debugSendString(tel, zdrojtel);
//debugSendString(PSTR("\r"), FLASH);
gsmState = GSM_RINGRING;
gsmSendString(PSTR("ATD+"), FLASH);
gsmSendString(tel, zdrojtel);
gsmSendString(PSTR(";\r"), FLASH);
// casova prodleva
while(x--) asm("nop");
// polozeni hovoru
gsmSendString(PSTR("ATH\r"), FLASH);
//debugSendString(PSTR("[GSM] Ringing terminated\r"), FLASH);
gsmState = NOT_COMM;
gsmCommTimer = COMM_PERIOD;
}
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// [PUBLIC] Inicializace
// -------------------------------------------------------------------------------------------------
void gsmInit(void) {
// nastaveni indikacni LED
ONLINELED_IOSET;
ONLINELED_OFF;
// UART RX: on, TX: on, 9600 baud, 8bit, 1 stop bit, no parity
UCSRA=0x00;
UCSRB=0x98;
UCSRC=0x86;
UBRRL=51;
UBRRH=0x00;
// povoleni preruseni
sei();
}
// -------------------------------------------------------------------------------------------------
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.
Zobrazit/skrýt debug.c
// -------------------------------------------------------------------------------------------------
// Modul: DEBUG
// Funkce: Odeslani retezce formou UART ramce na GPIO
// Pozadavky: 1 GPIO
// -------------------------------------------------------------------------------------------------
// pin pro generovani vystupniho signalu (musi byt nastaven jako vystupni)
#define DEBUGLINE_UP PORTC |= (1<<PC3)
#define DEBUGLINE_DOWN PORTC &= ~(1<<PC3)
#define DEBUGLINE_IOSET DDRC |= (1<<PC3)
// casova smycka (8MHz CPU, UART speed 38400bps@8MHz)
#define DEBUGLINE_COUNTER 23
#define FLASH 1
#define MEMORY 0
// prototypy funkci
void debugSendChar(unsigned char arg);
void debugSendString(char *sss, unsigned char zdroj);
void debugInit(void);
// ----------------------------
// Posle znak na ladici pin
// ----------------------------
void debugSendChar(unsigned char arg){
unsigned int counter, x;
unsigned char n = 11;
x = 0x7E00 | arg<<1;
while(n--) {
if ((x & 1)) DEBUGLINE_UP; else DEBUGLINE_DOWN;
counter = DEBUGLINE_COUNTER;
while(counter--) asm("nop");
x = x >> 1;
}
}
// ----------------------------
// Posle retezec na debug UART
// zdroj - FLASH nebo MEMORY
// ----------------------------
void debugSendString(char *sss, unsigned char zdroj){
char k;
if(zdroj) {
while ((k=(*sss++))>0) debugSendChar(k);
} else {
while ((k=pgm_read_byte(sss++))>0) debugSendChar(k);
}
}
// ----------------------------
// Nastaveni debugline
// ----------------------------
void debugInit(void) {
// Nastaveni portu na debugline
DEBUGLINE_IOSET;
}
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;
}