Távolságmérés AVR-rel (Timer1 Normál működési mód)
Nincs mese, ahhoz hogy megértsük, hogy hogyan működik az AVR-GCC-ben írt ultrahangos távolságmérő kód, az AVR timer-einek működését kell alap szinten megismerni.
Röviden, a Timer egy olyan regiszter, ami egy megadott időnként növeli az értékét anélkül hogy a mikrovezérlőnek foglalkozni kellene vele. A mai modern mikrovezérlők timerei sokféle működési módra állíthatóak be, amik különféle célokra használhatóak. Korábbi cikkjeimben már ezek közül röviden tárgyaltam néhányat (amelyekre most nem térek ki részletesen):
- Normál működési mód: megadott időközönként előállított megszakítások létrehozására alkalmas (a Timer0-t a soros LCD panelt vezérlő a szoftveres UART rutin pontos időzítésére használtam - Link)
- Output Compare mód: különböző frekvenciájú és szimmetriáju jelek előállítására alkalmas (a Timer1-et a robotjaimat meghajtó áttételes DC motorok sebességének PWM jellel történő szabályozására használtam - Link)
Az AVR-es példákban az Echo lábon lévő jel hosszát kétféle módszerrel fogom megmérni. Először pollingolással a Timer Normál módjának használatával, másodszor pedig megszakításokkal az Input Capture mód használatával.
ATMega8 Timerek
A példámban egy ATMega8-ast használok, aminek két 8 bites és egy 16 bites Timere van. A 16 bites Timer sokkal több funkcióval rendelkezik mint a 8 bites, és a 16 bites felbontásnak köszönhetően sokkal pontosabb jelgenerálásra és időzítésre használható (amíg a 8 bites timerek számlálója max. 255-ig tud számolni, addig a 16 bites Timer számlálóregisztere maximálisan 65535-ig). Az ATMega8-as esetén a Timer1 a 16 bites, aminek igen sokféle működési módja van (a részletekért lásd az ATMega8 adatlapját). Rendelekezik két 16 bites output compare regiszterrel (OCR1A és OCR1B), amivel pl PWM jel állítható elő. Továbbá rendelkezik egy 16 bites Input Capture regiszterrel is (ICR1). A Timer1 beállításait és a működési módjait a hozzá tartozó TCCR1A és TCCR1B kontrol regiszterrel vezérelhetjük. (A Timerekhez tartozó regiszterek neve Timer-enként és AVR típusonként eltérő lehet, ezért másmilyen típusú AVR használata esetén mindenki nézze meg az adott AVR adatlapjában az eltéréseket).
A TCCR1A regiszter az Output Compare működési módok beállítására szolgál, amivel PWM jelet állíthatunk elő a Timerhez tartozó megfelelő kimeneti lábakon. Később a példákban a Timer1-et normál módban és input capture módban fogom használni, ezért a TCCR1A regiszter bitjeit 0-nak állítom be.
A TCCR1B regiszter a Timer-t meghajtó órajelimpulzusok és az Input Capture működési mód beállítására szolgál.
AVR példa 1
Először pollingolással a Timer Normál módjának használatával fogom megmérni az Echo lábon lévő jel hosszát. A soros LCD modulról szóló cikkemben a Timer0 normál módjának a használatával már részletesen foglalkoztam, ami nagyon hasonló a Timer1-hez, ezért erre most nem térek ki.
Az AVR-es példákban az SRF04 szenzor Trigger lábát az ATMega8 PD7-es lábára, az Echo lábat pedig a PB0 lábára kötöttem.
/*
Szoftveres idomeres + polling
SRF04 + AVR + soros LCD
Hardware: ATmega8 @ 7.372800MHz
PC Software:Hyper terminal @ 9600 baud, Nincs Paritas Bit, 1 Stop Bit, Flow Control = NONE
*/
#define F_CPU 7372800UL // rendszer orajel: 7.3728 MHz
#define USART_BAUDRATE 9600 // soros kommunikacio sebessege: 9600 bps
#define UBRR_ERTEK ((F_CPU / (USART_BAUDRATE * 16UL)) - 1) // UBRR
#include <avr/io.h>
#include <inttypes.h>
#include <util/delay.h>
#define Trigger_PORT PORTD
#define Trigger_PIN PIND
#define Trigger_DDR DDRD
#define Trigger_Lab PD7 //SRF04 Trigger laba PORTD7-re kotve
#define Echo_PORT PORTB
#define Echo_PIN PINB
#define Echo_DDR DDRB
#define Echo_Lab PB0 //SRF04 Echo laba PORTB0-re kotve
void KonfigUART() // UART beallitasa
{
// 9600 bps soros kommunikacio sebesseg beallitasa
UBRRL = UBRR_ERTEK; // UBRR_ERTEK also 8 bitjenek betoltese az UBRRL regiszterbe
UBRRH = (UBRR_ERTEK>>8); // UBRR_ERTEK felso 8 bitjenek betoltese az UBRRH regiszterbe
// Aszinkron mod, 8 Adat Bit, Nincs Paritas Bit, 1 Stop Bit
UCSRC |= (1 << URSEL) | (1 << UCSZ0) | (1 << UCSZ1);
//Ado es Vevo aramkorok bekapcsolasa
UCSRB |= (1 << RXEN) | (1 << TXEN); //
}
char UARTAdatFogad() // Ez a fuggveny a beerkezo adatokat kiolvassa az UDR regiszter bejovo pufferebol
{
while(!(UCSRA & (1<<RXC))) // Varakozas amig nincs uj bejovo adat
{
//Varakozas
}
//Most mar van beerkezett adat, amit kiolvasunk a pufferbol
return UDR;
}
void UARTAdatKuld(char data) // Ez a fuggveny a kuldendo adatot beirja az UDR regiszter kimeno pufferjebe
{
while(!(UCSRA & (1<<UDRE))) // Varakozas amig az Ado kesz nem lesz az adatkuldesre
{
//Varakozas
}
// Az Ado mar kesz az adatkuldesre, a kuldendo adatot a kimeno pufferjebe irjuk
UDR=data;
}
void UARTSzovegKuld( char *p)
{
while(*p)
{
UARTAdatKuld( *p++);
}
}
uint16_t JelSzelesseg()
{
uint16_t result;
//az SRF04 Trigger labara 15us-os, magas allapotu impulzus adasa
Trigger_PORT |= (1<<Trigger_Lab); //magas allapot
_delay_us(15); // varakozas 15 mikroszekundumig
Trigger_PORT &= (~(1<<Trigger_Lab)); //alacsony allapot
// _delay_us(20);
//varakozas az Echo labon megjeleno felfuto elre (polling)
while(!(Echo_PIN & (1<<Echo_Lab)));
//Timer1 beallitasa es nullazasa
TCCR1A=0X00;
TCCR1B=(1<<CS11); //eloosztas = Fcpu/8
TCNT1=0x00; //Timer szamlalo nullazasa
//varakozas az Echo labon megjeleno lefuto elre (polling)
while(Echo_PIN & (1<<Echo_Lab));
result=TCNT1;
//Timer leallitasa
TCCR1B=0x00;
if(result > 60000) //ha az impulzus hosszabb mint 60ms,nincs celtargy a szenzor elott
return 0x0000; //0 ertek visszaadasa eredmenyul
else
return (result);
}
int main(void) // Foprogram
{
uint16_t Time = 0;
KonfigUART(); // UART Konfiguralasa
//Trigger pin kimenet
Trigger_DDR |= (1<<Trigger_Lab);
//Echo pin bemenet
Echo_DDR &= (~(1<<Echo_Lab));
while(1)
{
//az Echo labon levo impulzus szelessegenek merese
Time = JelSzelesseg();
UARTSzovegKuld("?f"); // ?f soros LCD parancs: LCD torlese
UARTAdatKuld('0'+(Time/10000) % 10); // Tizezresek kiirasa
UARTAdatKuld('0'+(Time/1000) % 10); // Ezresek ertekenek kiirasa
UARTAdatKuld('0'+(Time/100) % 10); // Szazasok ertekenek kiirasa
UARTAdatKuld('0'+(Time/10) % 10); // Tizesek ertekenek kiirasa
UARTAdatKuld('0'+Time % 10); // Egyesek ertekenek kiirasa
UARTSzovegKuld(" us"); //
UARTSzovegKuld("?m"); // ?m soros LCD parancs: ugras a sor elejere
_delay_ms(50); // várakozás
}
}
A program elején a Trigger lábat kimenetnek állítom be, az Echo lábat pedig bemenetnek. A Trigger lábat alacsony állapotra kapcsolom (ez lesz az alapértelmezett állapot)
Létrehozok egy JelSzelesseg() függvényt, ami az Echo lábon lévő impulzus hosszát méri meg. A függvény a távolságmérés minden lépését végrehajtja: előállítja a Trigger jelet, elindítja a Timert és megméri a visszhang jel hosszát:
- A Trigger lábat magas állapotra kapcsolja
- Vár 15us-ig
- A Trigger lábat alacsony állapotra kapcsolja
- Vár az Echo láb magas állapotára
- Amikor az Echo láb magas állapotra kapcsol, nullázza és elindítja a Timert normál módban.
- Vár amíg az Echo láb alacsony állapotra nem kapcsol, ekkor a Timer értékét kiolvassa egy változóba és kikapcsolja a Timert.
A fenti függvénynél a Trigger láb és az Echo láb szabadon választható, ezért a szenzor az AVR bármelyik szabad I/O lábára ráköthető. Az Arduino-s példához hasonlóan, a fenti függvény is teljesen lefoglalja a mikrovezérlőt, amíg a távolságmérés be nem fejeződik. A mikrovezérlő folyamatosan figyeli az Echo lábat. A függvény maximum 36ms-ig vár a céltárgyról visszaérkező visszhangra, ha eddig nem érkezik visszhang (nincs a szenzor előtt semmi, vagy a tárgy túl messze van), akkor a függvény 0 értéket ad vissza. Fontos, hogy legalább 10ms legyen a mérések között, hogy az előző mérés alatt generált ultrahang impulzusok elhaljanak és az új mérésbe ne zavarjanak be.
A példában az AVR rendszerórajele 7.3728 MHz, ezért 8-as leosztást használva egy 1.085 mikroszekundumonként számláló Timert tudunk csak létrehozni. Ez az időalap különbözik az Arduino-nál használt 1 us-os időalaptól, ezért a céltárgy távolságát megadó képlet is változni fog. De először egy mérőszalag segítségével felvettem a szenzor impulzushossz - céltárgytávolság függvényét.
Az 1.085 mikroszekundum alatt az ultrahang impulzus 0.37 mm-nyi utat tesz meg. Ez 0.185mm-es távolságnak felel meg, ami jó közelítéssel 10/54-el egyenlő. A 7.3728 MHz-es AVR órajel esetén a JelSzelesseg() függvénnyel a céltárgy távolsága az alábbi módosított képlettel számolható:
Távolság (mm-ben) = JelSzelesség()*10/54
A fenti példában láthattuk, hogy a Timer1 normál módjának felhasználásával csak pollingolva mérhető meg az Echo lábon lévő jel hossza. Ez a mikrovezérlőt a mérés idejére teljesen lefoglalja. Ezzel a módszerrel ugyanazt az eredményt értük el mint az Arduino-val, de az AVR-GCC kód bonyolultabb és lényegesebben nagyobb méretű.
Most nézzük meg hogyan lehet a jel hosszának mérését elegánsan, pollingolás nélkül, megszakítások használatával, a Timer1 Input Capture működési módjának a segítségével megoldani.
A cikk még nem ért véget, lapozz!
Értékeléshez bejelentkezés szükséges!