Seriál o ADC trochu rozvolníme příkladem o použití infračervených detektorů vzdálenosti, které jistě znáte z různých arduino robotků. V principu jde většinou o dvojici infra LED a infra fototranzistoru. Infra LED vyzařuje infračervené záření o vlnové délce typicky okolo 940nm, které se chová podobně jako běžné světlo. Po dopadu na předmět se rozptýlí a část světla se vydá zpět do směru odkud přišla. Stejně jako když v temné místnosti posvítíte na stěnu "baterkou". Na stěně vznikne světlá stopa. Světlo se na stěně rozptyluje, část ho putuje do prostoru kolem (díky tomu mohou světlou stopu vaší baterky vidět i ostatní) a část ho putuje zpět do vašich očí. A stejně tak i část infračerveného světla putuje zpět k čidlu a dopadá do infra fototranzistoru. Kolik ho tam dopadne záleží na mnoha faktorech.
Za dobrých vnějších podmínek - například v uzavřeném prostoru bez vlivu vnějšího světla, s neměnnou barvou povrchu atd., může být rozlišení a někdy i přesnost měření zlomky milimetru. V drsných podmínkách "arduino" robotka, který jednou stojí před zdí, jindy před kusem plechu, někdy na trávě a někdy na parketách, někdy mu svítí do čidel světlo z oblohy a někdy je překážka bílá kostka cukru a jindy černá bota, může být software rád když rozliší vzdálenost překážky na jednotky cm. I přes to všechno je tato metoda zajímavá. Trh je zasycen různými modulky "IR proximity" senzorů, které krom LED a fototranzistoru obsahují také komparátor, který má při určité přijaté intenzitě nastavit logický výstup a dát tak vědět, že detekoval překážku. Tento tutoriál je vlastně reakcí na to proč takovým způsobem při detekci překážky mikropočítačem nepostupovat. Zmíněná metoda s komparátorem trpí dvěma zásadními nedostatky. V prvé řadě se vzdala schopnosti odhadnout vzdálenost a dává jen informaci o přítomnosti a nepřítomnosti překážky a hlavně vzdala se schopnosti rozpoznat překážku na větší vzdálenost. Představte si modelovou situaci kdy do fototranzistoru dopadá pozadí o intenzitě dejme tomu "1" (třeba 1V). Před modul umístíme překážku do větší vzdálenosti (například 4cm) a signál vzroste na 1.2V. Položíme-li překážku do bezprostřední blízkosti (například 1cm) signál vzroste na 3.9V. Řeknu si že lepší vědět o překážce dřív než později, nastavím komparační hladinu na 1.2V a modul tak nastavím aby hlásil přítomnost překážky jakmile bude pod 4cm. Chvíli to funguje a najednou se robot otočí čidlem směrem k oknu odkud přichází malé množství IR záření zvenčí. Signál najednou vzroste tímto "pozadím" na 1.5V a robot vzdálené okno vyhodnotí jako překážku... Co teď s tím ? Buď snížíte detekční vzdálenost na třeba 2cm a budete doufat že to postačí a nebo na to půjdete úplně jinak.
Fototranzistor "mění" dopadající světlo na proud. Jinak řečeno otevírá se a proud který skrze něj protéká odpovídá intenzitě záření. V našich čidlech obsahuje fototranzistor "černé" sklíčko, které tlumí viditelné světlo a tím vzniká "infra fototranzistor". Proud fototranzistorem převedeme na napětí pomocí rezistoru - na schematu pomocí R10. Jak ale zvolit jeho hodnotu ? Je na to více metod a jednodušší z nich vypadá takto:
Druhým krokem je připravit LED tak aby šla rozsvěcet a vypínat. Zhasnutá LED nám totiž umožní měřit světelné pozadí a odečítat ho od "odraženého" signálu a značně si tím zvýšit citlivost a schopnost detekovat "slabé" (vzdálené nebo malé) překážky. Spínání LED má ještě další výhody. Víme že silnější osvětlení zvyšuje "dosah" detekce, je tedy žádoucí do LEDky pustit co nejvíc. Trvalý proud LEDkou je omezen v případě mého čidla (LTH-1550) na 60mA. A už jen fakt že 6 takových čidel někde na robotovi žere z baterky 0.36A vás donutí LEDky zhasínat. Jenže je tu ještě daleko zajímavější důvod proč LEDky spínat. Pulzně totiž snesou proudy klidně do 1A (na 10us). Takže pokud si váš program spolehlivě hlídá dobu sepnutí a nehrozí, že LED necháte omylem zapnutou, můžete toho využít k výraznému zvětšení dosahu. Vzhledem k tomu, že jde o "výukový" příklad tak si to nedovolím a rezistorem R11 omezím proud do LED na přibližně 52mA. Protože takový proud není možné spínat přímo pinem mikropočítače, posílil jsem jeho výstup tranzistorem Q1 (např.2N7000). Ten ovládám signálem IR z pinu PA4.
Celé ovládání naší sestavy bude vypadat tak, že nejprve změříme pozadí (se zhasnutou LED), pak LED rozsvítíme, počkáme až proud fototranzistorem "naběhne" (vzestupná hrana trvá desítky us) a pak změříme intenzitu dopadajícího světla. Od ní odečteme pozadí (a jak později uvidíte tak i přeslech) a dostaneme intenzitu "odraženého" světla. Díky ní v pohodě rozpoznáme lidskou ruku na vzdálenost 7cm (a kdo ví jak daleko bychom ji rozpoznali kdybychom pustili do LED pulzní proud například 250mA).
Dovolím si trochu netradičně probrat průběhy napětí za fototranzistorem ještě před zveřejněním zdrojového kódu. Podívejte se prosím na oscilogram. Na informativní modré stopě můžete vidět dobu sepnutí LED. Zelený průběh odpovídá situaci kdy je fototranzistor nasměrován do místnosti a není před ním žádná překážka. Můžete vidět, že před zapnutím LED skrze něj prochází jen malý proud (cca 15mV/1.2k ~= 12uA) odpovídající slabému pozadí. Po zapnutí LED proud tranzistorem roste a ustálí se na hodnotě přibližně 50mV (40uA). Odkud se tento nadbytečný signál bere ? Značná část ho pochází z povrchu stolu. Čidlo se "dívá" vodorovně s rovinou stolu (viz foto) ve výšce asi 1.5cm nad povrchem. Část světla se na povrchu stolu rozptýlí a promítne se jako "odražený" signál. I když ale čidlo natočíte "do stropu" (nebo do volného prostoru), stejně část signálu dostanete zpátky. Vzhledem k tomu, že se kolem něj nenachází nic od čeho by se mohlo světlo "odrážet", není mi úplně zřejmé odkud se tento signál bere. Mám hypotézu, že se jedná o "přeslech" kdy se část světla vyzařovaná z LEDky dozadu a do boku dostane do fototranzistoru. Tu si tedy v programu dovolím také odečítat, neboť nejde o užitečný signál. Teď se podívejte na červenou stopu. Je to stejná situace (nad stolem a bez překážky), ale čidlo ja natočené k oknu. Všimněte si že pozadí vzrostlo přibližně 3x. V obou případech je rozdíl mezi signály se zhasnutou a rozsvícenou LED stále stejný (cca 35mV - "přeslech"). Na žluté stopě můžete vidět signál když je před čidlem překážka (čidlo proti oknu, 5cm daleko červená krabička od xplained modulu). Všimněte si že "pozadí" kleslo, neboť je zorné pole čipu překryto krabičkou a z pozadí se do něj dostává méně světla. Po rozsvícení LED signál vzroste o poznání víc jak v předchozích případech. Rozdíl mezi signálem při zhasnuté a rozsvícené LED je velký a je z něj patrné, že se před čidlem nachází překážka.
No a teď vše napasujeme do tutoriálu o AD převodníku. Aplikaci necháme měřit "vzdálenost" výše zmíněným postupem a výsledky posílat 2x za sekundu UARTem do PC. Asi jedinou věcí, která stojí za diskuzi je volba reference. Jako kompromis jsem zvolil 1.5V. Pokud dám překážku velmi blízko k čidlu, vzroste napětí signálu nad 1.5V a já už nebudu schopen odhadovat vzdálenost. Tím se připravím o schopnost rozpoznávat vzdálenosti mezi přibližně 6-3mm. Na druhou stranu si touto volbou ponechám slušnou citlivost při slabých signálech (tedy menších nebo vzdálenějších překážkách). Kdybych chtěl aplikaci zdokonalit, měnil bych reference podle potřeby. Při slabém signálu bych přepnul na nižší a při silném signálu na vyšší. Silný signál ale o moc víc informace nedá. Jakmile se totiž přiblíží čidlo k překážce na vzdálenost menší jak nějaké 4mm, bude signál s dalším přibližováním proti našemu očekávání klesat. Tranzistor se totiž začne nacházet ve "stínu". Navíc hodně světla do něj musí přicházet pod úhlem a tam má nižší citlivost. Jestli si to chcete představit, přiložte si svítilnu k pravému spánku a namiřte ji před sebe. Zavřete pravé oko a dívejte se levým jen před sebe. Přibližujte si před oči papír nebo přibližujte celou hlavu proti stěně. Jakmile se přiblížíte těsně, přestanete levým okem vidět stopu svítilny. Stejně jako náš fototranzistor. Toto chování najdete i v datasheetu k čidlům. Samotné měření dělá program pomocí funkce get_reflection(). Není tam nic co bychom už neprobrali. Za komentář stojí jen zpracování dat. Nejprve si program ověří, že je signál s rozsvícenou LED (proměnná reflect) větší jak pozadí (bckg) a přeslech. Pokud ano, odečtou se obě zmíněné složky a funkce vrací hodnotu, která by měla odpovídat míře "odraženého" signálu. Jinak je výsledek měření nesmysl (náhodné šumy) a vrátí nulu. Po rozsvícení LED program čeká 80us aby měl jistotu že se signál ve fototranzistoru ustálí. Že je to potřeba jste mohli vidět na oscilogramech.
/* tutorial TinyAVR 1-Series * ADC2 - IR senzor "přiblížení" * PA4 spíná IR LED * PA3 (AIN3) snímá proud IR fototranzistorem * aplikace změří pozadí, pak aktivuje IR LED a změří "odražený" (rozptýlený) signál * odečte korekci na přeslech a pozadí. Vše s pevnou referencí 1.5V * lze zdokonalit automatickou volbou reference (slabší signály měřit s menší referencí) */ /* Ve fuses zvolen 20MHz oscilátor */ #define F_CPU 20000000UL #include <util/delay.h> #include <avr/io.h> #include <stdio.h> // kvůli Printf_P (formátovací řetězec je ve Flash) #include <avr/pgmspace.h> // kvůli makru PSTR (v printf_P) #define BAUDVAL 8334 // baudrate 9600 b/s #define IR_ON PORTA.OUTSET = PIN4_bm; // makra pro ovládání IR LED #define IR_OFF PORTA.OUTCLR = PIN4_bm; // přeslech - signál, který se z IR LED dostává do fototranzistoru bez okolních překážek #define PRESLECH 100 // empiricky určeno void clock_20MHz(void); uint16_t get_reflection(void); void init_adc(void); void usart_init(void); int usart_putchar(char var, FILE *stream); // kouzelná formule, pro přesměrování printf na UART static FILE mystdout = FDEV_SETUP_STREAM(usart_putchar, NULL, _FDEV_SETUP_WRITE); uint16_t tmp; int main(void){ clock_20MHz(); // taktujeme na plný výkon IR_OFF; // zhasneme IR LED PORTA.DIRSET = PIN4_bm; // PA4 ovládá IR LED (skrze tranzistor) // PA3 (AIN3) měří proud fototranzistorem (vypnout vstupní buffer) PORTA.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc; init_adc(); // konfigurovat ADC a referenci usart_init(); // konfigurace UARTu stdout = &mystdout; // přesměrujeme Printf na UART while (1){ tmp=get_reflection(); // změřit signál printf_P(PSTR("signal = %u\n\r"),tmp); // poslat ho do PC _delay_ms(500); // 2x za sekundu } } // vrátí intenzitu "odraženého" IR (v arbitrary jednotkách v rozsahu 0 až ~4092) uint16_t get_reflection(void){ uint16_t bckg,reflect; // spustíme převod pro měření pozadí ADC0.COMMAND = ADC_STCONV_bm; while(ADC0.COMMAND & ADC_STCONV_bm){} // počkat na dokončení převodu bckg = ADC0.RES; // intenzita pozadí IR_ON; // zapneme IR LED _delay_us(80); // čas na stabilizaci fototranzistoru ADC0.COMMAND = ADC_STCONV_bm; // zahájíme další převod while(ADC0.COMMAND & ADC_STCONV_bm){} // počkat na dokončení převodu reflect = ADC0.RES; // intenzita ("odraz" + přeslech + pozadí) IR_OFF; // vypnout IR LED // pokud je signál větší jak pozadí if(reflect < (bckg + PRESLECH)){reflect = 0;} // odečteme pozadí i přeslech a dostaneme intenzitu "odraženého" signálu else{reflect = reflect - bckg - PRESLECH;} return reflect; } void init_adc(void){ VREF.CTRLA = VREF_ADC0REFSEL_1V5_gc; // vybrat referenci pro ADC ADC0.CTRLA = ADC_RESSEL_10BIT_gc; // zvolit rozlišení 10bit ADC0.CTRLB = ADC_SAMPNUM_ACC4_gc; // akumulujeme výsledek 4 převodů // vzorkovací kondenzátor 5pF, prescaler 20M/16 = 1.25MHz, použít vnitřní referenci ADC0.CTRLC = ADC_SAMPCAP_bm | ADC_PRESC_DIV16_gc | ADC_REFSEL_INTREF_gc; ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // zvolit vstup pro ADC (PA3) ADC0.SAMPCTRL = 3; // 2+3 = 5 period (4us) vzorkovací čas ADC0.CTRLA |= ADC_ENABLE_bm; // spustit ADC _delay_us(25); // počkat na stabilizaci reference } // Nastaví clock 20MHz (z interního 20MHz bez děličky) void clock_20MHz(void){ // v případě potřeby zde dočasně vypněte přerušení CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLA = CLKCTRL_CLKSEL_OSC20M_gc; // vybírá 20MHz oscilátor CCP = CCP_IOREG_gc; // odemyká zápis do chráněného registru CLKCTRL.MCLKCTRLB = 0; // vypne prescaler (děličku) } // odešle jeden znak (formát pro "printf" implementaci) int usart_putchar(char var, FILE *stream){ while(!(USART0.STATUS & USART_DREIF_bm)){} // počkat než bude místo v odesílacím bufferu USART0.TXDATAL = var; // naložit data k odeslání return 0 ; } void usart_init(void){ // využijeme toho že po startu/restartu je UART nastaven ... // ... jako 8bit, 1stop bit, žádná parita, asynchronní režim // část konfigurace si tedy můžu odpustit PORTA.OUTSET = PIN1_bm; // log.1 PA1 (Tx) - neutrální hodnota na UARTu PORTA.DIRSET = PIN1_bm; // PA1 (Tx) - je výstup PORTMUX.CTRLB = PORTMUX_USART0_bm; // Remapujeme Tx na PA1 USART0.BAUD = BAUDVAL; // nastavit baudrate USART0.CTRLB |= USART_TXEN_bm; // povolit vysílač (od teď přebere UART kontrolu nad PA1) }
Závěrem bych si dovolil poznamenat, že určitě nejde o nejlepší metodu jak IR čidlo vzdálenosti využít. Tutoriál nepokrývá spoustu důležitých otázek. Například jestli jde rozpoznat rozměrné vzdálené překážky od malých blízkých. Jak velký vliv má výška čidla nad podložkou. Jak dobře rozptylují světlo různé povrchy atd. Tyto otázky by mohl vyřešit nějaký student průmyslovky nebo gymnázia například v rámci SOČ... Nechce se někomu ?
Home
| V1.00 10.2.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /