I. Apie sensorių
Mėgėjiškų ultragarso HC-SR04 sensorių galima rasti ebay.com ir pan. Tai
vienas iš populiariausių žaisliukų, galintis pasitarnauti atstumų matavimui, robotikoje
ir pan.
Pagal skelbiamus duomenis, sensorius pasižymi dideliu tikslumu, net iki +/-3 mm, bet realybėje nėra
skirtas preciziniams matavimams, nes matavimai gana smarkiai svyruoja. Matuoja
atstumus iki nuo 2cm iki 5m, yra lengvai valdomas (kaip pamatysime vėliau). Veikimo
kampas į šoną – koncentruotas 15 laipsnių priešais modulį. Tik keturi išvadai, du iš
jų VCC ir GND, kiti du – Trigger matavimo pradžios impulsui generuoti bei Echo
signalo priėmimui. Matavimo funkcijas atlieka nugarinėje modulio dalyje
esančios mikroschemos.
Belieka tik pasižiūrėti, kaip komunikuoti su tokiu moduliu per du duomenų
išvadus.
II. Komunikavimas
Padavus maitinimą moduliui - niekas nevyksta, todėl visada reikia pirmiausia
inicijuoti matavimus. Šiam veiksmui atlikti reikia paduoti ne trumpesnį nei 10us
aukšto lygio signalą (5V) į Trigger įšvadą. Žinoma, prieš pradedant Trigger
reikšmė turi būti 0V. Po impulso padavimo, šiame išvade įtampos lygį galima vėl
pakeisti į 0V. Realizuoti visą šį veiksmą nesudėtinga pasinaudojus _delay_us(10) standartine funkcija. Toliau
modulis automatiškai siunčia 8 garso (40kHz) impulsus ir juos, atsispindėjusius nuo kliūties, vėl priima. Tuo
metu jau reikia būti pasiruošus nuskaityti įtampos reikšmę iš Echo išvado. Kuomet
modulis baigia impulsų priėmimą, Echo išvade įtampa pasikeičia į aukštą lygį. Toliau
šis signalas išlaikomas tam tikrą laiko tarpą ir vėl pakeičiamas į žemą (žiūr.
paveikslą). Priklausomai nuo atstumo iki kliūties signalo trukmė kinta. Kuo
toliau bus kliūtis, tuo ilgiau šis signalas bus išlaikomas aukštame lygyje.
Artimoms kliūtims signalo trukmė gali būti mikrosekundžių eilės, didesnėms –
milisekundžių ir pan.
Man betestuojant būdavo ir taip, jog signalas visada išlieka aukštame
lygyje – kliūtis labai tolima arba dėl triukšmų ultragarsiniai impulsai
nebegrįžta arba grįžta pavėlavę ir pan. Todėl reikia visada įvertinti, kokį max laiką skirti matavimui, nuo to priklausys ir matuojamo atstumo ilgis. Šiam
atvejui reikia pasiruošti, ir jei signalo lygis išvade nenukrenta iki žemo per
tam tikrą max laiką (200ms, 1s ar pan.)
– imtis atitinkamų veiksmų (fiksuoti, kad kliūčių nėra, jos labai tolimos ar
grąžinti max atstumą) ir baigti matavimą.
Kitas esminis dalykas – tarp matavimų daryti pauzę (~50ms ar pan.). Tai reikalinga tam, kad grįžtantys
pavėlavę ultragarso impulsai nesutrukdytų naujam matavimui ar neiškreiptų naujo
matavimo duomenų. Taigi, reikia prisiminti – po įvykdyto matavimo iškart kito
geriau nedaryti, o palaukti.
Taigi uždavinys manau aiškus – reikia surasti impulso trukmę, o iš jos po
to bus galima paskaičiuoti ir atstumą iki objekto. Šiam reikalui gerai
pasitarnauja Atmega esantys išorinių pertraukimų įvestys – INT0, INT1. Jomis
pasinaudojus automatiškai galima nustatyti, kada įtampa Atmega išvade
pasikeičia (iš žemo į aukštą, iš aukšto į žemą arba fiksuoti abu įvykius). Pasikeitus
įtampai INTx išvade gaunamas INTx_vect pertraukimas
programoje ir galima atlikti reikiamus veiksmus.
Taigi, viską apibendrinus, po matavimo paleidimo, reikės laukti įtampos
pasikeitimo į aukštą lygį, tuomet įjungti Atmegoje esantį TIMER laikmatį ir laukti įtampos pasikeitimo į žemą.
Jei signalas Echo išvade visgi pasikeis į 0V, tada galėsime bevargo
apskaičiuoti atstumą ir matavimas bus baigtas. Kitu atveju – jei matavimui skirtas laikas bus viršytas, t.y. timeout (nesumaišyti su overflow), tai matavimą teks baigti ir sakyti, kad
kliūtis labai tolima ir nepatenka į matavimo diapazoną ar pan. Na ir po kiekvieno užbaigto matavimo palauksime tam tikrą laiko tarpą, kad aplinka išsivalytų nuo pašalinių
ultragarsinių impulsų pėdsakų.
III. Realizacija
Be abejo, viską, apie ką kalbėjau, prieš tai reikia sukonfigūruoti ir įjungti
mikrokontroleryje. Tai daroma redaguojant registrų reikšmes. Savo bandymuose
modulį prijungiau taip: PB0 – Trigger, INT1 – Echo, kaip pavaizduota paveiksle. Pradžioje nustatome žemą 0V signalą PB0 išvestyje (matavimas nevyksta).
Toliau reikia nustatyti pertraukimų režimą priklausomai nuo įtampos
pasikeitimų INT išvade. Šiam projektui būtų galima naudoti abu INT0 ir INT1 išvadus. Vienam nustatyti pertraukimus, kai išvade
pasiekiamas aukštas lygis, kitam, kai įtampa nukrenta į žemą lygį 0V. Siekiant
sutaupyti, pasinaudojau tik vienu – INT1, o kitą palikau
laisvą(bus galima prijungti dar vieną modulį). Taigi INT1 nustačiau, jog pertraukimai būtų iškviečiami ant bet kokio įtampos
lygio pasikeitimo, o koks lygis yra esamu momentu, bus sekama pačioje
programoje vienu kintamuoju.
GICR registre nustatome, kokius naudosime INTx išvadus. Mano atveju INT1 pasirinktas. MCUCR register nustatome,
kokiam pasikeitimui esant yra iškviečiamas pertraukimas.
Toliau reikia sukonfigūruoti laikmatį, kuris padės garantuoti, jog
matavimas neužsitęs be galo ilgai ir skaičiuos impulso laiką. Pasirinkau 8 bitų laikmatį, tačiau galima naudoti ir 16 bitų. Skirtumas būtų tik toks, jog su didesnės rezoliucijos persipildymų skaičius būtų
mažesnis. Laikmačio daliklis nustatomas į 1 pakeičiant TCCR0 registrą, t.y. laikmačio dažnis
nebus dalinamas ir sutaps su mikrokontrolerio veikimo dažniu. Taip pat laikmačio reikšmė TCNT0 išvaloma į 0 ir
įjungiamas laikmačio persipildymo pertraukimas nustatant TIMSK pertraukimų bitą.
Prie visų nustatymų nereikia pamiršti įjungti pertraukimų vykdymą komanda sei().
Na štai, manau užteks šių įžanginių nustatymų. Toliau pasižiūrėsime, kaip
vykdyti sensoriaus paleidimą bei matavimus pertraukimų kode. Kaip ir minėjau
anksčiau, sensoriaus paleidimas yra gana paprastas – 10us impulsas į Trigger išvadą. Nepamirštant vėl nustatyti
išvado lygio į žemą 0V. Papildomai galime sekti, jog matavimas paleistas nustatant
kintamąjį.
Dabar peržiūrėsime pertraukimų kodą. INT1 pertraukime pirmiausia registruojame aukštą Echo išvado lygį. Išvalome laikmatį ir
nustatome, jog pradedame Echo impulso laiko matavimą. Sekantį kartą, kai šis
petraukimas yra iškviečiamas, sustabdome matavimus, apskaičiuojame atstumą iki
kliūties pagal laikmatyje sukauptą laiką, atstumą iki kliūties išsaugome "result" kintamajame.
Kitas pertraukimas yra vykdomas, kai laikmatis perpildomas, t.y. kai TCNT0 baito reikšmė iš 255 pereidama į 256 tampa
0. Šiame pertraukimo kode skaičiuojame persipildymų skaičių, kai ultragarso
sonaras vykdo matavimus. Skaičiuoti
persipildymų skaičių reikia tam, kad paskui galėtume tiksliai atstatyti, kiek
laiko praėjo, nes laikmačio reikšmė po persipildymo tampa 0. Taip pat čia paskaičiuojame
tiksliai, kiek laiko praėjo ir tikriname, ar matavimas neužtruko pernelyg ilgai
(timeout), nes vis dėlto nesiruošiame
laukti amžinybę rezultato nuo labai tolimų kliūčių. Šiuo atveju matavimo atstumas
nustatomas į -1, tai
nusako, jog kliūčių nėra.
Na ir galiausiai pakalbėsime apie matavimų paleidimą bei laukimą po
kiekvieno matavimo. Taigi visa tai realizuoti galima pagrindinėje programos
dalyje. Nutariau tai padaryti su delay funkcija, kol praeina
reikiamas 50 ms laiko tarpas. Žinoma, galima realizuoti ir kitaip - panaudojant tą
patį laikmatį arba tą patį uždelsimą vykdyti po 1ms per 50 iteracijų - taip nebūtų ilgam laikui blokuojamas kitas kodo vykdymas cikle. Kam gi be reikalo švaistyti mikroprocesoriaus ciklus nenaudingam miegojimui? Paprastumo dėlei pavyzdyje žemiau tiesiog panaudojama uždelsimo funkcija.
Visą projekto kodą rasite straipsnio pabaigoje.
IV. Atstumo apskaičiavimas ir rezultatai
Žinodami Echo impulso trukmę galime apskaičiuoti atstumą iki kliūties. Tai
daroma pagal žemiau pateiktą formulę:
Laikas t formulėje bus mikrosekundėmis, garso greitis v ore ~340m/s. Kadangi matavimo vienetai nesutampa, tai tenka
atlikti porą
pakeitimų, po kurių bendrą formulę gauname tokią (atstumas iki kliūties
milimetrais):
Rezultatas kode saugomas skaitine reikšme kintamajame "result". Kaip naudosite ar išvesite šitą informaciją - LCD ekrane, siųsite į kompiuterį ar pan. palieku spręsti pačiam skaitytojui. Aš savo testų informaciją persiunčiau į kompiuterį per USB, kad lengviau būtų analizuoti duomenis, tačiau sensoriaus veikimui tai visiškai nėra būtina. Projekto kode ši dalis praleista ir paliekama realizuoti savarankiškai pagal poreikius.
V. Veikimas
Na ir realizuoto projekto demonstraciją galite pamatyti žemiau video. Su šiuo
sensoriumi taip pat galima ir vykdyti atvirkščius matavimus – nustatinėti garso
greitį, kai atstumas tarp modulio ir kliūties yra tiksliai žinomas ir pan. O
jeigu primontuojame jį prie servo motoro, sukti 360 laipsnių kampu ir padaryti 5 m spindulio radarą
ir pan. Na fantazijai ribų nėra, taigi jeigu dabar išmokote naudotis šiuo
moduliu ir gaminate kažką įdomaus – pasidalinkite savo atradimais ir su manimi.
Sėkmės.
Sveiki,
AtsakytiPanaikintiišsibandžiau viskas veikia. Iškilo klausimas, kodėl negalima naudoti "delay" pagrindinėje programos dalyje? Jutiklis nustoja matuoti. Gal yra galimybė apeiti šią problemą? Jei taip, kokius pakeitimus atlikti programoje?
Pvz.:
for(;;){
_delay_ms(200);
if(running ==0){ ......
Dėkoju už atsakymą.
Viską galima naudoti. Pagal dabartinį kodą įdėjus toje vietoje 200ms delay vienas matavimas bus atliekamas tik po (200 x N + N) ms, t.y. po ~10sek., čia N pagal mano pavyzdį - 50. Taip padaryta tam, jog kaskart 50ms neužblokuoti ciklo vykdymo, o tai daryti palaipsniui po 1ms per 50 ciklo iteracijų (50 x 1ms = 50ms).
PanaikintiGeras klausimas būtų, kam to reikia? (pvz.: jeigu toliau ciklo viduje yra vykdomas kitas kodas, kuris negali toleruoti didesnio nei >5ms uždelsimo vienu metu).
Tokiu atveju manau tiesiog galima supaprastinti, kad būtų aiškiau:
for (;;) {
_delay_ms(200);
// kiti veiksmai ...
if (running == 0) {
_delay_ms(50); // būtina užtikrinti tarp matavimų bent >=50ms pauzę,
// jei pauzė atliekama prieš tai, galima ir pašalinti
sonar(); // paleidimas
}
}
Dėkui už atsakymą. To reikia tam, kad galėčiau tuo pačiu metu valdyti variklį ir jutiklį (lygiagrečiai). Variklio valdymo funkcijoje yra keletas delay su 0,5s uždelsimu. Rezultatas toks: kol varikliai sukasi, tol jutiklis nematuoja. Galbūt išeitų ta pačią sonar() funkciją perkelti į pertrauktį (tokiu atveju pagrindinėje programos dalyje būtų galima naudoti uždelsimą?
AtsakytiPanaikintiPrincipas toks:
for(;;){
variklis_i_kaire();
_delay_ms(500);
variklis_i_desine();
_delay_ms(500);
if(running == 0){
_delay_ms(50);
sonar();
}
}
Jeigu reikia lygiagrečiai, tai siūlau pasinaudoti dar vienu taimeriu TCNT2 (atmega8), kuris tarkim persipildytų kas 50 ms ir tikrintų, ar galima paleisti matavimą, t.y. sonar() metodą.
PanaikintiVėlgi reiktų aprašyti analogišką TIMER2_OVF_vect petraukimą ir ten perkelti sonaro paleidimą. Taip pat svarbu ilgai neužtrukti pačių pertraukimų vykdyme, nes tuo metu kiti ateinantys pertraukimai nėra apdorojami.
Kažkas panašaus čia: http://bsiswoyo.lecture.ub.ac.id/2012/10/timer-2-interrupt-event-timer2_ovf_vect/
swx, as darau su atmega328 gal nujauciate kokius nustatymus reiketu atlikti, nes sie GICR |= (1 << INT1); kaip ir netinka ir meta klaida...
AtsakytiPanaikintiNa i6kilus tokiems klausimams reiktų geriausia pasikonsultuoti su oficialiu Atmega328 datasheet aprašymu, va: http://www.atmel.com/Images/doc8161.pdf
PanaikintiTaip, kagandi Atmegos modelis kitas, tai nevisada yra viskas suderinama. Kaip dokumente 12.2 skyriuje parašyta, pertraukimas INT1 tiek krentančiam/kylančiam įtampos lygiui nustatomas pakeičiant registrą EICRA taip:
EICRA = (0<<ISC11)|(1<<ISC10);
Tada INT1 pertraukimas įjungiamas pakeičiant kaukės bitą:
EIMSK = (1<<INT1);
Tada belieka įjungti petraukimus nustatant SREG registro I bitą su komanda: sei().
Is the Circuit same for Atmega32 ??
AtsakytiPanaikintiNo, as atmega8 has 28 pins, while atmega32 - 40 pins, though it is possible to convert it to work with atmega32 - I suggest checking both datasheets for differences.
PanaikintiHi,
AtsakytiPanaikintiI wanted to modify your code so that it would display "result" in my PC (with one sec period) over serial connection (using USB<->RS232 TTL converter). Here is what I did:
after "// other code here..." I put:
const char* p;
sprintf(p, "%lu\r\n", result); //convert long "result" variable into string
for ( ; *p; ++p )
{
while(!(UCSRA & 1<
UBRRH = UBRRH_VALUE;
UBRRL = UBRRL_VALUE;
#if USE_2X
UCSRA |= (1<<U2X);
#else
UCSRA &= ~(1<<U2X);
#endif
UCSRB = (1<<RXEN)|(1<<TXEN);
// 8 bit data; 1 stop
UCSRC = (1<<URSEL)|(3<<UCSZ0);
}
but what I get on terminal is "0" (repeated in new line after each second).
What I'm doing wrong???
Could you please send me your code that displays "result" in terminal?
Hi,
Panaikintifirst of all you should check that your variable "p" is only a pointer, not a char buffer - it has no length, so using sprintf will not work & I suspect that's why you get "0" everytime. Change "p" to at least: "char p[256];". Then try again.
You can find more info about using the terminal here: http://www.wzona.info/2012/10/usb-duomenu-perdavimas-atmega.html
Tinklaraščio administratorius pašalino šį komentarą.
PanaikintiPlease, use pastebin.com or similar services for pasting code - just a link would be fine. Will remove the message as there's no formatting and it's hard to analyze it.
PanaikintiHi,
PanaikintiHere is the code: http://pastebin.com/ZVTVEVEb
Could you please tell me why I get always "0" in my PC ?
On the other hand, can you share with us version of your code that displays result in PC over serial connection?
Thanks,
Maciej
There's a bunch of mistakes in your code:
Panaikinti1) First of all it's obvious that TIMER0 has been messed up and the TIMER1_OVF_vect interrupt is never even called as TIMER1 (and its interrupt) is not even enabled! This explains why you always get 0 for "result".
More details: In the main function TIMER0 is first configured for HC-SR04 - this is correct, then you change TIMER0 (which again - is used by HC-SR04) prescaler value to 1024 & use TIMER0 for your own needs - this is not good (by default - no prescaler should be used & you can't use TIMER0). Even if it "somehow" worked, the the time measurement would be incorrect and the "result" value would not contain the true distance value - just junk.
So, leave TIMER0 for ultrasound sensor & use TIMER1 for your own needs, don't use TIMER0 for both.
2) Secondly, it's a bad design to use delay functions inside an interrupt vector, like: _delay_ms(50). Never do this! If you wan't to create a delay - do it in the main loop using extra logic. Interrupts should contain only fast non-blocking code & never freeze the whole AVR mcu. Also use SIGNAL instead of ISR, to eliminate cli/sei in interrupt code.
For future: analyze the given code & try to understand what resources(timers/interrupts) are used before using them for your own debugging or etc. needs. If something does not work - check your code again. The current HC-SR04 interfacing implementation is working and was tested a lot of times in other projects.
Your mentioned code uses V-USB library & works over USB, not over USART. See the previously mentioned article(with code) to understand the technique. The decision of how to implement & display the result (as mentioned in this article) is left as an exercise for the reader.
Hi, my friend Do you have de library for atmega32?
AtsakytiPanaikintio How can I use this one in atmega32?
thanks
=)
Hello, Thank you so much, I'm from Colombia, your code was a great base to the project I'm developing. I just want to tell you something about it, I had to modify the code, because I thought there was a mistake: If you have an external clock of 12MHz I reckon you have to divide your equation of "result" by 12, in my case, I had to divide by 16 because I've used a 16MHz oscillator, afterwards it worked perfectly.
AtsakytiPanaikintiThanks!
Thank you for spotting the issue - corrected.
PanaikintiHey man, thanks for the code and the whole post, this helped me so much.
AtsakytiPanaikintiI just don't understand one thing, can you explain me the use of wdt.h library?
Thanks!
Not required, you can safely remove.
PanaikintiHi, I used this tutorial logic and applied it to my microcontroller and it worked, however I wanted to change it to the interrupt INT2 and it is not working. I just changed the following:
AtsakytiPanaikintiEIMSK |= (1 << INT1); to EIMSK |= (1 << INT2);
and
SIGNAL(INT1_vect) to SIGNAL(INT2_vect)
It should be the only thing that I need to adjust right?
What microcontroller are you using? You also need to connnect to Echo to INT2 pin.
PanaikintiBtw, Atmega8 does not have INT2.