Zápisník experimentátora
Hierarchy: Časovač (timer)
Arduino Uno má 6 PWM kanálov na pinoch 3, 5, 6, 9, 10 a 11. V tomto článku si ukážeme, že to nemusí byť konečný počet. Hardverové PWM ale musíme nahradiť vlastným kódom. V tomto príklade využijeme timer, pomocou ktorého si budeme generovať prerušenie na dostatočne vysokej frekvencii, aby sme boli schopní simulovať PWM na ôsmich kanáloch naraz.
Na vytvorenie animácie môžeme použiť:
Všetko je zapojené podľa nasledovného obrázka. LED diódy sú pripojené na piny RX (0), TX (1), 2-7 a na obrázku je ešte vidno pripojenie GND na mojej LED doske pomocou dvoch drôtených prepojok k Arduinu. Arduino Pro Mini má vyvedené GND aj na druhej strane, takže som mohol použiť aj jedinú prepojku, ale potom by sa to zle fotografovalo.
RX a TX sú navzájom prehodené, pretože sa bude zobrazovať bitová podoba 8bitového čísla pomocou priameho zápisu do registra PORTD a potrebujeme mať piny zoradené rovnako, ako sú bity v registri.
Aby sme mohli vytvoriť efekt rôzneho jasu všetkých diód, musíme vhodne zapínať a vypínať tieto LED diódy a musíme to robiť tak rýchlo, aby ľudské oko nevnímalo blikanie, ale rôznu intenzitu svetla. Na minimálny plynulý obraz potrebujeme frekvenciu minimálne 25 Hz, ale na plynulý dojem je lepšie 50-75 Hz. A pretože potrebujeme v každom jednom okienku našej animácie zobraziť 256 odtieňov, potrebujeme výslednú frekvenciu 75x256=19200 Hz. Zaokrúhlime to na 20 kHz.
Na tejto frekvencii potrebujeme vyvolávať prerušenie, ktoré na všetkých diódach nastaví jas tak, že danú diódu zapne alebo vypne. Čím bude dióda počas celej doby viac zapnutá, tým bude jasnejšia. Zjednodušená schéma s tromi políčkami po 10 úrovní jasu nám ukazuje, ako svietia rôzny čas štyri diódy. Presne rovnako to funguje aj pri našej frekvencii 20 kHz.
123456789012345678901234567890 x x x xx xx xx xxxxx xxxxx xxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
To je celá podstata efektu. A teraz poďme na zdrojový kód.
Na začiatku je možné zadefinovať alebo vypoznámkovať dve makrá. Tie nemajú vplyv na výslednú animáciu. Ovplyvňujú iba to, či budeme na port zapisovať rýchlo alebo funkciou digitalWrite. A ovplyvňujú, či nahradíme Arduino funkciu main za našu, aby sme docielili stav, že nám jadro Arduina neponastavuje registre podľa svojho implicitného nastavenia. Obe voľby v podstate len skrátia výsledný kód na minimum. A prípadnému odporcovi Arduina ukážu, že priamo na port sa dá pristupovať aj v Arduine a že chápeme ako vlastne funguje linker.
#define DIRECT_WRITE #define FAKE_MAIN
Premenná counter
slúži na sledovanie pozície v rámci jasu v jednom okienku. Môže obsahovať hodnoty 0-255. Podľa hodnoty sa rozhodujem, či bude dióda svietiť alebo nie. A hodnoty jasu pre každú diódu mám v poli light
. Každá hodnota je dvojnásobkom predchádzajúcej, aby som zohľadnil nelineárne správanie ľudského oka.
uint8_t counter = 0;
uint8_t light[] = {1, 2, 4, 8, 16, 32, 64, 128};
Tento kód som si kompletne nechal vygenerovať v mojom kalkulátore na CTC timer. Nastavená frekvencia je 20 kHz a kalkulátor sa postaral o vhodné nastavenie registrov. Používam timer1.
void setupTimer() {
noInterrupts();
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 99; // 20000 Hz
TCCR1A |= 0;
TCCR1B |= (1 << WGM12);
TCCR1B |= (0 << CS12) | (1 << CS11) | (0 << CS10);
TIMSK1 |= (1 << OCIE1A);
interrupts();
}
Vo funkcii setup
iba nastavujem, ktoré piny majú byť výstupné. Vďaka tomu, že sú všetky piny na porte D, môžeme využiť aj priame nastavenie registra.
void setup() {
#ifdef DIRECT_WRITE
DDRD = 0b11111111;
#else
for (uint8_t i = 0; i < 8; i++)
pinMode(i, OUTPUT);
#endif
setupTimer();
}
Vo funkcii loop nerobíme nič. Všetku prácu vykoná obsluha prerušenia. Ak použijeme priamy zápis do registra, musíme si pripraviť všetkých 8 bitov v pomocnej premennej tmp
. Ak je jas pre konkrétnu LED vyšší ako hodnota v premennej counter
, bude dióda svietiť. V opačnom prípade bude zhasnutá. Z toho môžeme odvodiť, že čím väčšie je číslo v light[i]
, tým bude dióda svietiť jasnejšie.
void loop() {
}
ISR(TIMER1_COMPA_vect) {
#ifdef DIRECT_WRITE
uint8_t tmp = 0;
for (uint8_t i = 0; i < 8; i++)
if(light[i] > counter)
bitSet(tmp,i);
PORTD = tmp;
#else
for (uint8_t i = 0; i < 8; i++)
digitalWrite(i, light[i] > counter ? true : false);
#endif
counter++;
counter = counter % 255;
}
Vlastná funkcia main. Toto môžeme urobiť preto, lebo linker postupne hľadá skompilované kúsky kódu a ak sú dva s identickým názvom funkcie, uprednostní ten prvý a druhý bude ignorovať. V tomto prípade je tým prvým main
vždy tá funkcia, ktorú umiestnime do kódu nášho hlavného programu.
V programe si všimnite ešte jednu zaujímavosť. Ňou je nastavenie registra UCSR0B
. Musíme to urobiť, pretože Arduino bootloader upraví správanie pinov RX a TX a na týchto dvoch pinoch by sme nevideli naše PWM. Preto oba piny odpojíme od USART a dosiahneme želané správanie.
#ifdef FAKE_MAIN
int main(void) {
UCSR0B = 0; // disable bootloader USART
setup();
while(1)
loop();
return 0;
}
#endif
Zdrojový kód sa nachádza na GitHub.
Video je na YouTube.
03.02.2017