Sveicināti!
Vēlējos iemēģināt prasmes rakstīšanā un nolēmu izveidot šo rakstiņu. Pastāstīšu nedaudz par iespējām, kuras var noderēt, un, kuras var izmantot, rakstot "gaismas efektu" projektus iesācējiem. Rakstīsim C valodā. Es konkrēti izmantoju AVRStudio vidi.

Kas būs nepieciešams:
- ATMEGA8/8A mikrokontrolieris (ar nelielām programmas izmaiņām Atmega48/88/168/328)
- 8x gaismas diodes
- 8x rezistori (gaismas diožu strāvas ierobežošanai)
- kondensātors 100n jebkāds
- barošanas bloks vai baterijas, vai cits barošanas avots 3-5V
- spiedpoga ("tactswitch")
- maketplate (nelodējamā, bet protams, var arī uztaisīt iespiedplati)
- savienotājvadiņi
- AVR programmātors (piem. AVRISP vai cits, vai paštaisīts)
- nedaudz brīva laika ;)
| Slēguma shēma: (klikšķiniet, lai palielinātu) |
Maketplate: |
 |
 |
Pirms turpināt pārliecinieties, ka jūs protat programmu nokompilēt un rezultātu ielādēt mikrokontrolierī!
Tad nu ķersimies pie lietas!
Pirmais piemērs:
Šis veids, kā attēlot informāciju uz gaismas diodēm ir vienkāršākais un visvairāk flash un ram atmiņas aizņemošais. Piemērā redzam, ka tiek daudzkārt atkārtoti ievietota viena un tā pati funkcija ('_delay_ms()'), tas aizņem vietu 'flash' atmiņā. Arī izvadāmo datu kopa (pattern) nav īpaši ērti pārskatāma.
// Piemers 1
#include <avr/io.h> // ieejas-izejas biblioteka
#include <util/delay.h> // aiztures biblioteka
void ioinit(){
DDRB = 0b11111111; // PORTB visas izejas (prieks diodem)
}
void showSequence1(){ // paradam pirmo sablonu un atgriezamies atpakal
PORTB = 0b00000001;
_delay_ms(300);
PORTB = 0b00000011;
_delay_ms(300);
PORTB = 0b00000111;
_delay_ms(300);
PORTB = 0b00001111;
_delay_ms(300);
PORTB = 0b00011111;
_delay_ms(300);
PORTB = 0b00111111;
_delay_ms(300);
PORTB = 0b01111111;
_delay_ms(300);
PORTB = 0b11111111;
_delay_ms(300);
}
int main(void){
ioinit(); // sagatavojam mikroprocesoru
while(1){ // izpildit vienmer (bezgaliga cilpa)
showSequence1();
}
return 0;
}
Otrs piemērs:
Otrajā piemērā redzam, ka attēlojamie dati ir sakārtoti "sequence2data[8]" kopā. Tas piešķir datiem pārskatāmību un ir ērti tos modificēt vai labot. Pati datu izvades funkcija "showSequence2()" ir izveidota kā 'for()' cikls, kas ļauj atbrīvoties no daudzkārtējajiem vienādajiem funkciju izsaukumiem. Rakstot garas gaismas efektu programmas ļauj ekonomēt ievērojamu daudzumu 'flash' atmiņas, bet joprojām aizņemam daudz 'RAM' atmiņas. Definējot mainīgo, tiek aizņemta vieta 'flash' atmiņā, un, ieslēdzot mikroprocesoru, mainīgais tiek ielādēts 'RAM' atmiņā! Piemēram – ja gribēsiet izveidot programmu ar 500 stāvokļiem (vai 10 programmas pa 50) priekš 8 gaismas diodēm (8 kanāliem) tiks aizņemti 500 baiti 'RAM'. Atmega8 satur tikai 1024 baitus RAM. Tātad šādā veidā arī neko īpaši garu nevar uzrakstīt.
// Piemers 2
#include <avr/io.h> // ieejas-izejas biblioteka
#include <util/delay.h> // aiztures biblioteka
uint8_t sequence2data[8] = {
0b10000000, //otra sablona dati
0b01000000,
0b00100000,
0b00010000,
0b00001000,
0b00000100,
0b00000010,
0b00000001 };
void ioinit(){
DDRB = 0b11111111; // PORTB visas izejas (prieks diodem)
}
void showSequence1(){ // paradam pirmo sablonu un atgriezamies atpakal
PORTB = 0b00000001; // izvadam sablona datus uz diodem
_delay_ms(300); // pauze 300ms (neko nedaram / daram neko)
PORTB = 0b00000011; // utt.
_delay_ms(300);
PORTB = 0b00000111;
_delay_ms(300);
PORTB = 0b00001111;
_delay_ms(300);
PORTB = 0b00011111;
_delay_ms(300);
PORTB = 0b00111111;
_delay_ms(300);
PORTB = 0b01111111;
_delay_ms(300);
PORTB = 0b11111111;
_delay_ms(300);
}
void showSequence2(){ // paradam otro sablonu un atgriezamies atpakal
for(uint8_t i=0; i<8; i++){ // atkartojam ciklus, lidz i < 8, pec katra cikla beigam 'i' vertiba palielinas par 1
PORTB = sequence2data[i]; // izvadam datus uz diodem, dati ar adresi 'i' no kopas squenceData2[]
_delay_ms(300); // pauze 300ms (neko nedaram / daram neko)
}
}
int main(void){
ioinit(); // sagatavojam mikroprocesoru
while(1){ // izpildit vienmer (bezgaliga cilpa)
showSequence1();
showSequence2();
}
return 0;
}
Trešais piemērs:
Trešajā piemērā parādīts "pgmspace" bibliotēkas pielietojums, veidojot "mirgojošās" programmas. Mainīgo kopa "sequence3data[]" tiek novietota PROGMEM tas ir 'flash' atmiņā un netiek iekopēta RAM., līdz ar to ieekonomējam daudz RAM atmiņas. BET, lai nolasītu datus, kas atrodas šajā kopā, ir jāizmanto speciāla funkcija "pgm_read_byte()" (no pgmspace bibliotēkas). Šī funkcija nolasa 1 baitu sākot ar norādīto adresi (mūsu gadījumā no 'sequence3data', kas atrodas PROGMEM jeb 'flash' atmiņā).
// Piemers 3
#include <avr/io.h> // ieejas-izejas biblioteka
#include <util/delay.h> // aiztures biblioteka
#include <avr/pgmspace.h> // biblioteka tiesai datu piekluve "program memory"
uint8_t sequence2data[8] = {
0b10000000, //otra sablona datu kopa RAM atmina
0b01000000,
0b00100000,
0b00010000,
0b00001000,
0b00000100,
0b00000010,
0b00000001 };
uint8_t sequence3data[8] PROGMEM = {
0b10000001, //tresa sablona datu kopa PROGMEM atmina
0b11000011,
0b11100111,
0b11111111,
0b01111110,
0b00111100,
0b00011000,
0b00000000 };
void ioinit(){
DDRB = 0b11111111; // PORTB visas izejas (prieks diodem)
}
void showSequence1(){ // paradam pirmo sablonu un atgriezamies atpakal
PORTB = 0b00000001; // izvadam sablona datus uz diodem
_delay_ms(300); // pauze 300ms (neko nedaram / daram neko)
PORTB = 0b00000011; // utt.
_delay_ms(300);
PORTB = 0b00000111;
_delay_ms(300);
PORTB = 0b00001111;
_delay_ms(300);
PORTB = 0b00011111;
_delay_ms(300);
PORTB = 0b00111111;
_delay_ms(300);
PORTB = 0b01111111;
_delay_ms(300);
PORTB = 0b11111111;
_delay_ms(300);
}
void showSequence2(){ // paradam otro sablonu un atgriezamies atpakal
for(uint8_t i=0; i<8; i++){ // atkartojam ciklus, lidz i < 8, pec katra cikla beigam 'i' vertiba palielinas par 1
PORTB = sequence2data[i]; // izvadam datus uz diodem, dati ar adresi 'i' no kopas squenceData2[]
_delay_ms(300); // pauze 300ms (neko nedaram / daram neko)
}
}
void showSequence3(){ // radam 3 sablonu
for(uint8_t i=0; i<8; i++){
PORTB = pgm_read_byte(sequence3data + i); // nolsam datus, kura adrese ir datuKopa + ierakstaNumurs
_delay_ms(500);
}
}
int main(void){
ioinit(); // sagatavojam mikroprocesoru
while(1){ // izpildit vienmer (bezgaliga cilpa)
showSequence1();
showSequence2();
showSequence3();
}
return 0;
}
Tagad esam sarakstījuši vairākus efektus (piemērā 3 tie ir 3 gabali). Piemērā 3 efekti tiek rādīti pēc kārtas un katrs vienu reizi. Bet kā lai pārslēdzas starp tiem, piemēram ar pogas palīdzību? Ja mēs pievienotu funkciju galvenajā cilpā, kas pārbauda pogas stāvokli, pogas spiediens tiktu ignorēts efekta attēlošanas laikā, kas, protams, neder. Šeit mums var palīdzēt pārtraukumi.
Ceturtais piemērs:
Ceturtajā piemērā tiek demonstrēta mikroprocesora spēja 'pārtraukties', izmainoties loģiskajam līmenim uz kājiņas – mūsu gadījumā uz PORTD PD2 kājiņas. Konkrēti šim pārtraukumam ir piešķirts nosaukums "INT0".
Pārtraukums ir mikroprocesora spēja noreaģet uz kādu notikumu (piem. līmeņa izmaiņu uz kājiņas, iekšējā skaitītāja stāvokli, pabeigtu analogi-digitālo pārveidojumu utt.) kura rezultātā tiek pārtraukta programmas izpilde un tiek izpildīta pārtraukuma funkcija, un pēc tās izpildes, mikroprocesors atgriežas pie pārtrauktās programmas izpildes.
Mūsu gadījumā – INT0 pārtraukums, šī pārtraukuma funkcijas nosaukums ir "INT0_vect".
Piemērā 4 demonstrēju šī pārtraukuma darbību. Pēc notikuša pārtraukuma tiek izslēgti pārtraukumi ("cli()", lai nekas šī pārtraukuma funkcijas laikā mūs nepārtrauktu), pēc tam gaidām līdz poga tiek atlaista, izmainām mainīgā "flag" vērtību, slēdzam atpakaļ pārtraukumus ('sei()') un atgriežamies kur pārtraucāmies.
Tajā pašā laikā galvenajā cilpā ('while(1)') visu laiku tiek pārbaudīta "flag" mainīgā vērtība, un balstoties uz to, tiek vai nu ieslēgtas visas gaismas diodes, vai nu izslēgtas.
// Piemers 4
#include <avr/io.h> // ieejas-izejas biblioteka
#include <avr/interrupt.h> // partraukumu biblioteka
#include <util/delay.h> // aiztures biblioteka
uint8_t flag = 0;
ISR(INT0_vect) { //leksim seit, ja konstatets partraukums uz PD2 (INT0)
cli(); // izsledzam partraukumus, lai seit mus neviens nepartrauktu
while(!(PIND & _BV(PD2))){ // gaidam, kamer poga tiks atlaista
_delay_ms(30); // 'debounce' pauzite
}
flag++; //palielinam mainigo par 1
if(flag > 1){
flag = 0; // ja mainigais lielaks par 1 pieskiram tam veribu 0
} // mainiga veriba pec partraukumiem 1-0-1-0-utt.
sei(); // iesledam partraukumus atpakal
} // pec izpildisanas lecam atpakal, kur partraucamies
void ioinit(){
DDRB = 0b11111111; // PORTB visas izejas (prieks diodem)
DDRD = 0b11111011; // PD2 ieeja (pogai)
PORTD |= _BV(PD2); // iesledzam "pullup" PD2 ieejai
// Ja lietojat Atmega8, tad INT0 partraukumu konfigurejam sadi
MCUCR = 0; // partraukums uz INT0 un INT1 kajam pie nulles limena, un mes negulam
GICR |= _BV(INT0); // aktivizejam INT0 partraukumu
// Ja lietojat Atmega48/88/168/328, tad INT0 partraukumu konfigurejam sadi
// EICRA = 0; // partraukums uz INT0 un INT1 kajam pie nulles limena
// EIMSK |= _BV(INT0); // aktivizejam INT0 partraukumu
sei(); // iesledzam partraukumus
}
int main(void){
ioinit(); // sagatavojam mikroprocesoru
while(1){ // izpildit vienmer (bezgaliga cilpa)
if(flag == 1){
PORTB = 0b11111111; // ja mainigais ir '1' iesledam visas diodes
}else{
PORTB - 0b00000000; // ja mainigais ir '0' izsledam visas diodes
}
}
return 0;
}
Tad kā mēs varētu izmantot šo "pārtraukšanos", lai pārslēgtos starp efektiem? Līdzīgi kā Piemērā 4 – pārtraukuma rezultātā mainīt kādu mainīgā (piem 'flag') vērtību, vēl piešķirt citam mainīgajam (piem 'buttonInterruptFlag') vertibu, kuru galvenajā cilpā to nemitīgi pārbaudīt, un balstoties uz mainiga ('flag') vertību – pārslēgt efektu.
Piektais piemērs:
Piektajā piemērā tas viss ir izdarīts =] .
Nozpiežot pogu notiek pārtraukums, kura laikā izslēdzam visas diodes (lai nepaliktu iepriekšējie dati), mainīgajam 'buttonInterruptFlag' piešķiram '1' un pārslēdazm par 1 mainīgā 'flag' vērtību (0-1-2-3-0-1-…).
Galvenajā cilpā izmantoju "switch()" funkciju, kas atkarībā no mainīgā 'flag', izpilda savu apakšfunkciju. Savu kārt apakšfunkcijas showSquence1(); (2,3 un4 arī) ir izveidotas 'while()' ciklā. Tas nepieciešams, lai varētu pārbaudīt, vai nav noticis pārtraukums ( vai 'buttonInterruptFlag' nav palicis par '1'). Ja tas ir noticis, cikls tiek pārtraukts, un mēs atgriežamies galvenajā cilpā – pie 'switch()' funkcijas, un balstoties uz jau izmainijušos 'flag' vērtību, ejam uz citu apakšfunkciju.
Vēl viena lieta – ja mēs izsaucam aiztures funkciju piem: '_delay_ms(1000)', veselu sekundi mums būs jāgaida, līdz varēsim 'izlekt' ārā no apakšfunkcijas 'showSequence1()' 'while()' cikla. Tādēļ izveidoju speciālu aiztures funkciju 'special_delay()', kura var tikt pārtraukta reizi milisekundē.
// Piemers 5
#include <avr/io.h> // ieejas-izejas biblioteka
#include <avr/interrupt.h> // partraukumu biblioteka
#include <util/delay.h> // aiztures biblioteka
#include <avr/pgmspace.h> // biblioteka tiesai datu piekluve "program memory"
volatile uint8_t flag = 0;
volatile uint8_t buttonInterruptFlag = 0;
volatile uint8_t _index = 0;
uint8_t sequence1data[8] PROGMEM = {
0b10000000, //pirma sablona datu kopa PROGMEM atmina
0b01000000,
0b00100000,
0b00010000,
0b00001000,
0b00000100,
0b00000010,
0b00000001 };
uint8_t sequence2data[2] PROGMEM = {
0b11110000, //otra sablona datu kopa PROGMEM atmina
0b00001111 };
uint8_t sequence3data[16] PROGMEM = {
0b00000001, //tresa sablona datu kopa PROGMEM atmina
0b00000011,
0b00000111,
0b00001111,
0b00011111,
0b00111111,
0b01111111,
0b11111111,
0b11111110,
0b11111100,
0b11111000,
0b11110000,
0b11100000,
0b11000000,
0b10000000,
0b00000000 };
uint8_t sequence4data[8] PROGMEM = {
0b00011000, //ceturta sablona datu kopa PROGMEM atmina
0b00111100,
0b01111110,
0b11111111,
0b11100111,
0b11000011,
0b10000001,
0b00000000 };
ISR(INT0_vect) { //leksim seit, ja konstatets partraukums uz PD2 (INT0)
cli(); // izsledzam partraukumus, lai seit mus neviens nepartrauktu
PORTB = 0b00000000; // nodzesam visas diodes, pogas spiediena laika
while(!(PIND & _BV(PD2))){ // gaidam, kamer poga tiks atlaista
_delay_ms(30); // 'debounce' pauzite
}
buttonInterruptFlag = 1; // atzimejam, ka ir noticis partraukums, un ir japarsledz rezims
flag++; //palielinam mainigo par 1
if(flag > 3){
flag = 0; // ja mainigais lielaks par 3 pieskiram tam veribu 0
} // mainiga veriba pec partraukumiem 1-0-1-0-utt.
sei(); // iesledam partraukumus atpakal
} // pec izpildisanas lecam atpakal, kur partraucamies
void special_delay(uint16_t time_ms){// speciala pauze, kura tiek partraukta, ja noticis partraukums
while((time_ms > 0) && (buttonInterruptFlag == 0)){ // kamer nav noticis partraukums un nav veikts 'time_ms'
_delay_ms(1); // rezes partraukums, turpinam 'cilpot' !
time_ms--; // pec katras cilpas samazinam cilpu skaititaju par 1
}
}
void showSequence1(){
_index = 8; // cik efektam ir ierakstu
while((_index > 0) && (buttonInterruptFlag == 0)){ // ja nav paradits viss efekts un nav partraukums, turpinam cilpot
PORTB = pgm_read_byte(sequence1data + (8-_index)); // nolsam datus, kura adrese ir datuKopa + ierakstaNumurs
special_delay(200); // speciala pauze, kura tiek partraukta, ja mainiga noticis partraukums
_index--;
}
}
void showSequence2(){
_index = 2; // cik efektam ir ierakstu
while((_index > 0) && (buttonInterruptFlag == 0)){ // ja nav paradits viss efekts un nav partraukums, turpinam cilpot
PORTB = pgm_read_byte(sequence2data + (2-_index)); // nolsam datus, kura adrese ir datuKopa + ierakstaNumurs
special_delay(700); // speciala pauze, kura tiek partraukta, ja mainiga noticis partraukums
_index--;
}
}
void showSequence3(){
_index = 16; // cik efektam ir ierakstu
while((_index > 0) && (buttonInterruptFlag == 0)){ // ja nav paradits viss efekts un nav partraukums, turpinam cilpot
PORTB = pgm_read_byte(sequence3data + (16-_index)); // nolsam datus, kura adrese ir datuKopa + ierakstaNumurs
special_delay(100); // speciala pauze, kura tiek partraukta, ja mainiga noticis partraukums
_index--;
}
}
void showSequence4(){
_index = 8; // cik efektam ir ierakstu
while((_index > 0) && (buttonInterruptFlag == 0)){ // ja nav paradits viss efekts un nav partraukums, turpinam cilpot
PORTB = pgm_read_byte(sequence4data + (8-_index)); // nolsam datus, kura adrese ir datuKopa + ierakstaNumurs
special_delay(500); // speciala pauze, kura tiek partraukta, ja mainiga noticis partraukums
_index--;
}
}
void ioinit(){
DDRB = 0b11111111; // PORTB visas izejas (prieks diodem)
DDRD = 0b11111011; // PD2 ieeja (pogai)
PORTD |= _BV(PD2); // iesledzam "pullup" PD2 ieejai
// Ja lietojat Atmega8, tad INT0 partraukumu konfigurejam sadi
MCUCR = 0; // partraukums uz INT0 un INT1 kajam pie nulles limena, un mes negulam
GICR |= _BV(INT0); // aktivizejam INT0 partraukumu
// Ja lietojat Atmega48/88/168/328, tad INT0 partraukumu konfigurejam sadi
// EICRA = 0; // partraukums uz INT0 un INT1 kajam pie nulles limena
// EIMSK |= _BV(INT0); // aktivizejam INT0 partraukumu
sei(); // iesledzam partraukumus
}
int main(void){
ioinit(); // sagatavojam mikroprocesoru
while(1){ // izpildit vienmer (bezgaliga cilpa)
buttonInterruptFlag = 0; // atzimejam, ka esam jau 'izlekusi' ara no efekta, lai tagad tur paliktu
switch(flag){
case 0: showSequence1(); break;
case 1: showSequence2(); break;
case 2: showSequence3(); break;
case 3: showSequence4(); break;
default: break;
}
}
return 0;
}
LEJUPIELĀDE:
Atmega-Blink-Int.zip – arhīvs ar visiem piemēriem (sākumkodu un '.hex' datnēm)
ATMEGA8 Datasheet – šeit var pasmelties, ko tad īsti mikrokontrolieris prot
Atmega168 Datasheet – un šeit
P.S.
Ja rodas jautājumi vai ierosinājumi – laipni lūgti! Tikai nesodiet bargi – pirmais raksts ;)
Aplūkot oriģinālo rakstu