AVR Chronograph från koncept till PCB (6 / 13 steg)
Steg 6: Koden
Jag ska klistra in instruktionerna här ett avsnitt i taget och kortfattat förklara vad de gör. Kom ihåg "C programmering för Microcontrollers" för en mer djupgående tutorial förklarar användningen av operatörer och sådant. ATMega328P databladet är också en bra referens för att lära sig syftet med ett register eller en bit. Jag ska försöka hålla denna del kort, men informativ. Lärande C är ett projekt av sig själv, så bli inte avskräckt om du få helt förlorad först! Jag vet att jag gjorde.
Den faktiska koden på denna sida kommer att vara i fetstil att göra det lättare att urskilja från text... Kommentarer kommer kontrollelementet.
/*
* Speed_measurement.c
*
* Skapat: 9/15/2012 8:50:23 PM
* Författare: Thomas L
*/
#define F_CPU 14.7456E6 #include < avr/io.h >
#include < util/delay.h >
#include < avr/interrupt.h >
Dessa första raderna ge bara kompilatorn lite information om vilka filer det behov och den CPU klockfrekvensen. Vi kommer att behöva använda vissa förseningar senare göra AVR bara vänta, så vi måste omfatta filen util/delay.h. Filen innehåller de faktiska försening funktioner som vi kallar i huvudprogrammet. Dröjsmål funktionerna leverera en tidsinställd försening, så kräver de oss att ge CPU klockfrekvens. #define F_CPU berättar kompilatorn hastigheten på kristalloscillator och detta nummer ges sedan för alla funktioner som behöver använda den.
/************************************************************************/
/ * förklara globala variabler * /
/************************************************************************/
unsigned int resultatet = 0;
int interruptcount = 0;
långa int tid = 0;
långa int resetcount = 0;
Detta är globala variabeldeklarationer. Om vi vill tilldela ett värde till en variabel, måste vi tilldela variabeln en först. Till exempel 'int' för heltal och är ett helt tal representeras av 16 bitar (15 antal bitar och lite tecken.) 'Unsigned int' är också ett heltal, men med ingen teckenbiten. Detta innebär att det kan bara vara positivt, men det kan vara dubbelt så stor på grund av den extra nummer lite. Dessa variabler behövs i funktionen Main () samt avbrott rutiner, så jag förklarade dem här innan funktionen Main () börjar. Detta gör dessa variabler globala, vilket innebär att de kan öppnas och ändras av någon del av programmet.
Även är på denna punkt det viktigt att observera att varje uttryck måste avsluta med ett semikolon!!! Jag glömmer dessa hela tiden, och kompilatorn ger mig fel... hela tiden.
int main(void) DDRD = 0X00;
{
DDRC = 0xDF; //portc utgång för 7 segment multiplexing och 1 ingång för avstånd display
DDRB = 0xFF; //portb utdata fot 7 segment i bcd
PORTD | = 0xFF; //enable portd dra upp motstånd
//(int0 and int1 require external pull up resistors or 1 interrupt will be triggered at reset)
Nu har vi funktionen Main (). Detta är början av själva programmet, och det börjar genom att konfigurera några saker. De DDRn rapporterna ovan tilldela värden till Data riktning registrerar för 3 portar. Dessa 8-bitars register konfigurera I/O pins som antingen ingångar och utgångar. En 1 i en bit ståndpunkt kommer att göra motsvarande stift en utgång och en 0 blir det en ingång. De värden som kan vara i decimaltal, binär eller hexadecimalt. Decimaltal skrivs normalt som 255. Binär och hex har ett prefix till tala kompilatorn vilken typ av nummer det är. Binär skulle vara 0b11111111. Hex vore 0xFF. Alla tre av dessa är lika med 255 och kompilatorn vård inte som du använder. Hex är normalt antal system val eftersom det är lättare att läsa än binärt, och lättare att relatera till binär än decimal... Binära motsvarande av de nummer som tilldelas varje DDR är 8 bitar som representerar data riktning 8 stift i hamnen. PORTD | = FF; tilldelning kan pull-up motstånd på de ingående stift. Om PORTD var konfigurerad som en utgång, skulle då uppdraget ändra utdata påstår av de stift
Lär dig din HEX till binära omvandlingar och engagera dem i minnet! Det är praktiskt...
En HEX Självstudiekurs: http://www.microbuilder.eu/Tutorials/Fundamentals/Hexadecimal.aspx ***
EICRA | = (1 << ISC11) | (1 << ISC01) ; //configure externa avbryter
EIMSK | = (1 << INT1) | (1 << INT0);
TCCR1B | = (1 << CS12); //set prescaling för timer1 256
Denna den delen där jag brukar ha att dra ut i databladet för AVR och göra lite grävande. EICRA är den externa avbryta kontroll registrera A. Detta register har 4 bitar vi kan ange för att konfigurera hur avbrott utlöses av yttre mellanjobb stiften INT0 och INT1. 4 bitar har ett unikt namn som identifierar det när ställa in eller rensa bitar. ISC00 och ISC01 är avbrott känsla kontroll för INT0. Anger vilken typ av ändring krävs på INT0 stiftet att utlösa avbrottet. ISC10 och ISC11 är samma kontroll för INT1 stift. Kontrollera i databladet visar att genom att sätta ISC01 och ISC11, vi konfigurerat båda avbrott stift att utlösa i utkanten negativa kommer bara. Uttrycket (1 << ISC11) är lite SKIFT operatören att ställa in den ISC11 biten. Se "C programmering för Microcontrollers" att lära sig allt om aktörer och lite manipulation; Det är lite för mycket att gå in på här.
EIMSK (extern avbryta MaSK) registret har två bitar för att aktivera eller inaktivera externa avbrotten. Båda av dessa bitar måste anges eller avbryta rutiner kommer aldrig köra.
TCCR1B (Timer/Counter kontroll registrera 1 B) har flera bitar som kan konfigurera hur timern ska fungera. Allt vi behöver tänka på här är det prescaling värdet. Detta betyder bara, hur många CPU klocka pulser räknas innan timern ökas med 1. I det här fallet valde jag ett prescale värde av 256, företrädd av att CS12 biten (kolla databladet.) Detta delar CPU klockan med 256 innan det för timer/counter hjälper till att förhindra counter svämmar över i den tidsram som vi försöker mäta.
/************************************************************************/ SEI();
/ * deklarera variabler för beräkning och * /
/************************************************************************/
unsigned int de = 0;
unsigned int tior = 0;
unsigned int hundratals = 0;
unsigned int x = 0;
Double ticsfoot = 0;
dubbel fps = 0;
dubbel fph = 0;
dubbel mph = 0;
dubbel km/h = 0;
Dubbelrum m/s = 0;
int avstånd = 0;
OK, en lätt avsnitt! Detta är bara några mer variabeldeklarationer och att sei() sak. "sei()" är ett kommando som känt att kompilatorn att betyda "Aktivera global avbrott." Inget avbrott kommer att utlösa om denna bit inte har angetts. "cli()" rensar lite inaktivera globala avbrott. Vi kommer att använda som man senare för att förebygga problem.
While(1)
{
/************************************************************************/
/ * få sensorn avståndet i fötter från pind 0,1,4,5 * /
/************************************************************************/
int distanceinput = (~ PIND & 0x33);
int hibits = (distanceinput >> 2); //getting pind bitar 0,1 och 4,5 tillsammans för att
int lobits = (distanceinput & 0x03); //distance värde i BCD. bitar 2,3 är den
avstånd = (hibits + lobits); //ext avbrott stift redan.
om (avstånd == 0) avstånd = 16.
Detta lilla avsnitt läser status för 4 dip-switchar som möjligt för användaren att välja avståndet mellan sensorerna. Det finns vissa bitar manipulering pågår här eftersom de 4 ingångarna inte i följd. De variabler 'hibits' och 'lobits' är bara tillfällig lagring platser att sortera ut bitarna. Den första raden isolerar de bitarna som vi vill med den & operatör. Den andra raden skiftar bitarna över 2 ställen få den Hej bitar i rätt position. Tredje raden isolerar endast den lo bitar. Sedan i den fjärde raden i lo och Hej bitar läggs till göra dem ett nummer. Uttrycket "if" det hindrar i slutet användaren från att välja '0' och orsakar en "dela med noll" tillstånd senare.
/************************************************************************/
/ * "redo" indikator LED * /
/************************************************************************/
om (interruptcount == 0)
{
PORTC | = (1 << 3);
}
annat
{
PORTC & = (0 << 3);
}
Det här avsnittet får reda på om det finns en mätning i framsteg, och lyser klar ledde om inte. Variabeln interruptcount ökas i de avbryta rutinerna att hjälpa programmet hålla reda på vilken del av mätningen pågår. Vi får se de avbryta rutinerna i slutet av koden.
/************************************************************************/
/ * beräkningar för att hitta hastigheten i 4 enheter * /
/************************************************************************/
om (interruptcount == 2) //only beräkna när båda avbrott har inträffat EIMSK | = (1 << INT1) | (1 << INT0);
{
CLI(); //disable global avbryter
ticsfoot = (tid / avstånd); //distance är avståndet mellan sensorer i fot - ticsfoot är counter tics eller foten
fps = (57600 / ticsfoot), //57600 är counter tics/SEK (cpu clk/prescaler)
fph = (fps * 60 * 60);
mph = (fph / 5280);
km/h = (km/h * 1.609344);
m/s = (fps * 0.3048);
SEI();
}
Om variabeln interruptcount spätt två gånger (detta sker i rutinerna som avbrott senare,) det betyder som båda avbrott har inträffat och det uppmätta värdet är redo att användas i beräkningarna. Här ser vi cli() instruktionen som inaktiverar avbrotten. Det är god praxis att göra detta när man läser en variabel som ett avbrott har förmågan att ändra. Varför? Normala 'int' variabler, till exempel är 16 bitar långa eller två byte. AVR är en 8-bitars system, så det tar två instruktioner att läsa 2 byte av ett enda värde. Det är möjligt, då för ett avbrott uppstå efter första byte läses, men innan andra. Om du försöker läsa en variabel men avbrottet inträffar när du har läst den första byten, kan rutinen avbrott ändra värdet på variabeln innan det är färdigt att läsa! Programmet kommer att återupptas där den slutade och läsa byten av variabeln med data som inte matchar den första byten! Då är det värde som du har laddat inte vad förväntas och kan orsaka problem.
Matten här är inte alltför komplicerat. Kommentarer bör vara tillräckligt för att räkna ut vad som händer. Det är där vi använder variabeln avstånd från dip-switchar. Variabeln tid kommer från rutinerna som avbrott senare.
På första avbryta inaktiveras själv i sin rutin så att den kan endast köras en gång innan mätningen är klar. Det är därför EIMSK instruktionen är det. Det aktiverar båda yttre avbrott så att oavsett vilket var första och fick funktionshindrade ska aktiveras igen.
/************************************************************************/
/ * välja utmatningsalternativ * /
/************************************************************************/
if (!( PIND & (1 << PIND6)) & & (PIND & (1 << PIND7))) //choose fot per sekund om (resultat > = 999) resultatet = 999;
{
Round(fps);
resultat = fps;
}
annars om (PIND & (1 << PIND6) & &! () PIND & (1 << PIND7))) //choose meter/sekund
{
Round(MPS);
resultat = mps;
}
annars om (PIND & (1 << PIND6) & & (PIND & (1 << PIND7))) //choose kilometer i timmen
{
Round(KPH);
resultat = km/h;
}
annat //default miles/tim
{
Round(mph);
resultat = mph;
}
Här hela avsnittet läser två sista dip-switchar för att bestämma önskad effekt enheter. Sedan lagras beroende på vilket värde behövs i variabeln resultatet.
Round () funktionen är ett enkelt sätt att få heltalsvärden som vår produktion utan att för mycket fel. Denna funktion ingår i math.h-filen som ingår i programmet av filen delay.h som vi ingår manuellt. Om du tittar till lösning explorer fönster på höger sida i ATMEL Studio, ser du projektet namn följt av "Beroenden" (märkt i bilden.) När du bygger lösningen för första gången (Klicka på bygga >> Build Solution) alla filer som ingår i programmet kommer att listas här. Du kan sedan öppna filen delay.h som vi ingår i början av koden och ta en titt på den. Om du bläddrar igenom det, bör du hitta uttrycket #include < math.h > nära toppen. Längre ner hittar du funktionen _delay_ms() som vi kallar för att skapa 1ms fördröja senare.
Den "om" i slutet förhindrar försöker visa ett tal som inte får plats på våra 3 skärmar.
/************************************************************************/ om ((resetcount > = 0x00FF) & & (interruptcount > = 2))
/ * dröjsmål att stoppa flera "2nd avbrott" utlöser * /
/ * utan att försena viktigaste kod * /
/************************************************************************/
ResetCount ++;
{ //before reset. 0x00FF ca 3 SEK
interruptcount = 0;
ResetCount = 0;
}
Denna del är (typ av) en timer gjord av den huvudsakliga while(1) loopen. Varje gång programmet loopar bortanför den här punkten, ökas resetcount. Beräkningarna sker bara om interruptcount är exakt 2. så, om det andra avbrottet inträffar igen brukar det röra till matten. Om interruptcount är 2 eller mer, kan inget annat mätas tills resetcount steg upp till 0x00FF ger ca 3 sekunder fördröjning för objektet att lämna sensorn området. Vi kan inte använda en normal fördröjning här eftersom vi behöver visar att visa oss det uppmätta värdet. När interruptcount återställs till 0, ready-lampan är tänd och nästa mätning kan göras.
/********************************************************************************/
/ * Visa int resultatet på 3 siffrig sju segment display * /
/ * försening ger sju segment dekoder tid att avkoda och Visa siffror * /
/********************************************************************************/
if (!( PINC & (1 << PINC5))) //to Visa avstånd inställningen på displayen
{ //only när knappen är nedtryckt
resultat = avstånd;
}
annat
Detta "om" klockor bara på knappen som är en begäran att få avståndet börvärdet visas på displayerna. Senaste fart beräkningen lagras in i resultatet varje gång avsnittet utdata enheter (ovan) körs i slingan, så resultatet endast är lika avstånd medan knappen trycks.
hundratals = (resultatet / 100), //Get 100-tals placera siffran TENS = (x / 10); de = x;
x = (resultatet % 100).
PORTB = (0x00|hundreds);
PORTC | = (1 << 2); / / Skriv siffran
_delay_ms(1);
PORTC & = (0 << 2);
x = x % 10.
PORTB = (0x00|tens);
PORTC | = (1 << 1); / / Skriv siffran
_delay_ms(1);
PORTC & = (0 << 1);
PORTB = (0x00|ones);
PORTC | = (1 << 0); / / Skriv siffran
_delay_ms(1);
PORTC & = (0 << 0);
}
}
Äntligen! i visningen! Nu när "resultat" är lika med ett giltigt 3 siffrigt decimal nummer, måste vi dela upp den i enstaka siffror och skicka dem till visar i sin tur. Vi får varje siffra och förvara den i en variabel kallas, tiotals eller hundratals. Hundratals placera siffran är lätt; bara dela av 100. Variabeln är en "int" typ, så ingenting efter decimalkommat lagras. "x" är en tillfällig variabel att lagra nästa behövs värde. Operatorn '%' inte uppdelningen, men Returnerar resten till "x". Detta ger oss den transeuropeiska nät och som siffran att hantera. Dividera det med 10 och upprepa för de. Det är ett enkelt sätt att separera alla dina siffror att gå till displayerna och mycket enkelt kan byggas ut för att arbeta med större siffror.
PORTB instruktionerna skriva siffrorna till produktionen stift kommer att 7-segment dekodern. Nästan samtidigt aktivera PORTC instruktionerna utdata till motsvarande bildskärmens marken transistor att lysa upp siffran. Sedan håller förseningen på displayen för 1ms innan du inaktiverar transistorn tillbaka och gå vidare till nästa siffra. Ganska coolt.
De avslutande klammerparenteserna avsluta While(1) loop kodblocket och avsluta kodblocket main () funktion.
/************************************************************************/ }
/ * sensor 1 avbrott * /
/************************************************************************/
ISR(INT0_vect)
{
om (interruptcount == 0)
{
TCNT1 = 0X0000; //reset counter 0
interruptcount ++; //Increment avbrott räknas
EIMSK & = (1 << INT1) | (0 << INT0); //disable INT0
}
annat if (interruptcount == 1)
{
tid = TCNT1; //capture räknarvärdet
interruptcount ++; //Increment avbrott räknas
}
annat resetcount = 0;
/************************************************************************/
/ * sensor 2 avbrott * /
/************************************************************************/
ISR(INT1_vect)
{
om (interruptcount == 0)
{
TCNT1 = 0X0000; //reset counter 0
interruptcount ++; //Increment avbrott räknas
EIMSK & = (0 << INT1) | (1 << INT0); //disable INT1
}
annat if (interruptcount == 1)
{
tid = TCNT1; //capture räknarvärdet
interruptcount ++; //Increment avbrott räknas
}
annat resetcount = 0;
}
Nu, de två avbryta rutiner... De är exakt samma med undantag för en instruktion, så jag ska bara tala om en.
Ser att avbryta rutinerna är utanför main () funktionen. Programmet helt slutar vad dess gör lämnar funktionen Main () och utför rutinen avbrott. Efter avbrottet returneras rutin är komplett, till Main ()-funktionen där den slutade.
Den första raden, ISR(INT0_vect), är namnet på avbrottet från databladet. Om avbrottet är aktiverat i registret över EIMSK och globala avbrott är aktiverade, programmet kommer att hoppa till den här raden när avbrottet utlöses.
Ser du att rutinerna som avbrott i princip består av en förening "om... ANNARS om "uttalande som bara gör några saker. Vanligtvis är det bäst att hålla avbrott rutiner mycket kort, ändra bara vad måste ändras och snabbt komma tillbaka till Main ()-funktionen. Använda variabeln interruptcount för att spåra antalet avbrott som har inträffat tillåter oss att göra rutinerna identiska, och kan även sensorer för att "se" target-objekt kommer från båda håll.
Oavsett vilken avbrott kör först återställer TCNT1 timern (den som vi satt prescaler för i början) sedan ökar variabeln interruptcount och sedan inaktiveras sig så att det inte kan vara gav extra gånger av en oregelbundet formad målobjekt.
Eftersom först avbryta ökat interruptcount, körs den andra en nästa par rader. Denna gång vi fånga värdet i timern TCNT1 och lagrar det i variabeln "tid" att användas i beräkningarna. Sedan ökas interruptcount igen.
Med interruptcount nu lika med 2, beräkningarna utförs och resetcount räknar upp till 0x00FF innan återställs allt för nästa mätning. Om det andra avbrottet utlöses igen av någon anledning, resetcount är satt till 0 och väntan börjar igen. Detta är hur jag förebygga flera utlösare av 2: a avbrottet för så länge som behövs för objektet vara ur vägen.
Usch, hoppas jag att åtminstone en liten känsla... I nästa steg kommer vi ladda programmet i våra chip och testa breadboarded kretsen!