ATMega16 Laborationer av Kjell 2 Rev:5 Datum: 29.09.2010 Page 1 of 7 Laboration 4: Knappstuds Drivrutiner för att eliminera störningar. Inledning: Laborationskortet EasyAVR6 har bland annat tryckknappar och LEDn kopplade till alla pinnar på alla portar. I denna laboration ska vi undersöka hur enkla tryckknappar beter sig och vilka problem som uppstår då man vill avläsa knappnedtryckningar. Vi ska också se hur man med programkod enkelt kommer tillrätta med dessa problem. Tryckknapparna är kopplade så att de ger en logisk etta (+5V) då de trycks ner. I uppsläppt läge finns en logisk nolla på ingångarna, det finns möjlighet att svänga knapparna så att de bryter spänningen till pinnarna om man vill använda sig av det. Förberedelser: Läs manualen sid. 68-70 om External interrupt. Alla förberedelseuppgifter ska vara gjorda vid laborationens början. Uppgift 1. För att få en fördröjning kan man skriva en enkel for-loop eller en while-sats: char i; for (i=5;i>0;i--); eller char i=5; while (i>0) i--; Men I AVR LibC finns fördefinerade funktioner för pauser i program. Läs om delay.h i libc manualen http://www.nongnu.org/avr-libc/usermanual/index.html För att delay funktionerna skall fungera krävs att F_CPU defineras, vad används F_CPU till och hur skall F_CPU anges?
ATMega16 Laborationer av Kjell 2 Rev:5 Datum: 29.09.2010 Page 2 of 7 Uppgift 2. Om man ska övervaka insignaler som är relaterade till mänsklig avtivitet (som t.ex. nedtryckning av knappar) är det onödigt att avläsa signalerna varje mikrosekund. Det är också uppenbart att det inte räcker att göra en avläsning var tionde sekund. Vad tror du kan vara lämpliga intervall? 1 sek 0,1 sek 10 ms 1 ms 100 µs 10 µs Uppgift 3. Antag att vi deklarerar tre variable enligt följande unsigned char nya_knappar, knappar, gamla_knappar; Variabeln nya_knappar uppdateras enligt nedan: nya_knappar = (knappar & ~gamla_knappar); Vad blir det nya värdet i variabeln nya_knappar om variablerna innehåller följande bitmönster: nya_knappar 0b00000010 knappar 0b00000001 gamla_knappar 0b00000000 Nytt värde i nya_knappar: Motivera!
ATMega16 Laborationer av Kjell 2 Rev:5 Datum: 29.09.2010 Page 3 of 7 Laborationsuppgifter: Uppgift 1. Enkel avläsning av knappnedtryckning. En enkel programkonstruktion som avläser en knappnedtryckning kan se ut såhär: #include <avr/io.h> unsigned char gamla_knappar = 0; // Är =0 om ingen knapp var nere förra gången int main(void) { DDRC = 0x00; //Port C ingångar DDRB = 0xFF; //Port B utgångar PORTB = 0; while(1){ // Om någon knapp är nere OCH ingen var nere förra gången: öka PORTB if ( PINC & 0x03 && (gamla_knappar == 0)){ PORTB++; // Uppdatera variabeln " gamla_knappar " gamla_knappar = PORTC & 0x03; return 0; Prova detta program. Logiskt sett så ser programmet bra ut: PORTB räknas upp om en knapp är nedtryckt och variabeln gamla_knappar har värdet noll. Vi ser ändå att programmet fungerar riktigt dåligt. Ofta detekteras flera studsar både vid nedtryckning och uppsläpp. Ibland missar programmet en nedtryckning. Hur kan det komma sig? Hur ska vi göra för att programmet absolut inte ska kunna missa en nedtryckning? Inför en ny 8-bitarsvariabel och förändra ditt program så att ingen nedtryckning missas. Du förstår säkert att de falska uppräkningarna kommer av att alla små studsar räknas som särskilda knappnedtryckningar. Signalerna från knapparna ser ju ut ungefär som på bilden här:
ATMega16 Laborationer av Kjell 2 Rev:5 Datum: 29.09.2010 Page 4 of 7 Tydligen läser vi av knapparna lite för ofta. Ungefär hur ofta? Hur ofta kan det vara lagom att göra en avläsning? Lägg in en delay i loopen: _delay_ms(??); eller _delay_us(??); Programraderna utgör en fördröjningsloop i loopen så att programmet kommer att pausa mellan varje knappavläsning.
ATMega16 Laborationer av Kjell 2 Rev:5 Datum: 29.09.2010 Page 5 of 7 Uppgift 2. Avbrottsstyrd drivrutin för knappavläsning. En stor förbättring får vi genom att ersätta fördröjningsloopen med ett periodiskt avbrott. Programmet binds nu inte upp i loopar. Avbrottsrutinen som kommer in med jämna mellanrum kommer att fungera som en fristående enhet som lämnar ifrån sig uppsnyggade och lattanvända värden. O.B.S: Variabler som används både i interrupten och övriga programmet måste deklareras volatile Ex: volatile unsigned char count_ctc; Interruptrutinen skall ligga under hela huvudprogrammet och det finns olika interruptvektorer. Nedan finns ett exempel på hur Timer 1 outputcompare A skall skrivas. (Övriga vektorer, se sid 45-46 i manualen) Ex: ISR(TIMER1_COMPA_vect) { //Er kod Steg 1: Inför en avbrottsrutin: ISR(Interrupt_vect){ Vi använder en inbyggd avbrottsmekanism som kallas ISR. Den skrivs som en funktion på ovanstående sätt. Det som skiljer från en vanlig function är att man kan ha samma funktions namn på olika ISR funktioner så länge Interrupt vektorn skiljer. Detta behövs för att mekanismen som styr programexekveringen till avbrottsfunktionen ska veta var i minnet den ligger. Interrupt vektorer för AVR mcpun finns på adressen http://www.nongnu.org/avr-libc/usermanual/group avr interrupts.html OBS! ATMega16 har 3 stycken externa interrupter, vi kommer att använda oss av INT0 som ligger på port D pin 2 Steg 2: Flytta de tre programraderna i main som ligger efter fördröjningsloopen in till avbrottsfunktionen och ta bort fördröjningsloopen: ISR(INT0_vect){ knappar = PIND & (1 << PD2); if ( knappar && (gamla_knappar == 0)){ PORTB++; gamla_knappar = knappar;
ATMega16 Laborationer av Kjell 2 Rev:5 Datum: 29.09.2010 Page 6 of 7 I funktionen main finns nu endast. DDRD = 0x00; // Inport DDRB = 0xff; // Utport while(1); Steg 3: Starta avbrottet. Kontrollera i manualen på sid 68-69 hur du skall ställa registret MCUCR för att reagera på stigande flank på INT0 och hur du skall ställa registeret GICR för att aktivera INT0. MCUCR =?? // INT0 stigande flank GICR =?? //INT0 Aktiverad sei(); // Starta hela avbrottssystemet Gör om programmet med avbrott. Prova så att det fungerar lika bra som innan.
ATMega16 Laborationer av Kjell 2 Rev:5 Datum: 29.09.2010 Page 7 of 7 3. Den slutliga drivrutinen. Det finns en stor nackdel med de knappavläsningsrutiner vi skrivit hittills: Man kan inte lätt ändra uppgift för vad som ska utföras vid resp. knappnedtryckning. Vi ska nu ändra lite i avbrottsrutinen: 1) if ( knappar && (gamla_knappar == 0)) PORTB++; ersätts med: nya_knappar = (knappar & ~gamla_knappar); 2) Vi tar bort PORTB++ så vi lättare kan ändra på vad som ska hända när en knapp trycks ner. 3) Vi skapar ytterligare en variabel, nya_knappar. Ettställda bitar i den variabeln betyder att någon tryckt på en knapp. Den programdel som använder sig av den informationen kvitterar motsvarande bit genom att nollställa den. På så vis får man ett mycket behändig kommunikation mellan avbrottsfunktionen och de övriga delarna. Huvudprogrammet behöver inte längre veta om hur knapparna är inkopplade och hur dåliga dom är. Vi har skaffat oss ett par idealiserade knappar (två bitar i variabeln nya_kappar). Ersätt nu den tomma while loopen med detta: while(1){ if (nya_knappar & (1 << PD1)){ // Har knappen tryckts igen? PORTB++; // Ja, räkna upp nya_knappar &= ~(1 << PD1); // och kvittera. Komplettera programmet så att man kan räkna ner värdet med knappen ansluten till INT1.