Stap 6: De Code
Ik plak de instructies hier een sectie op een moment en kort uitleggen wat ze doen. Vergeet niet 'C programmeren voor Microcontrollers' voor een meer diepgaande tutorial uitleg van het gebruik van operatoren en dergelijke. Het gegevensblad van de ATMega328P is ook een goede referentie voor het leren van het doel van een register of bit. Ik zal proberen te houden van dit deel korte, maar informatief. Leren C is een project alle door haarzelf, dus laat je niet ontmoedigd als je de weg volledig kwijt op het eerste! Ik weet dat ik deed.
De eigenlijke code op deze pagina zal worden in vet gemakkelijker te onderscheiden van tekst... Opmerkingen zullen worden cursief weergegeven.
/*
* Speed_measurement.c
*
* Gemaakt: 9/15/2012 8:50:23 PM
* Auteur: Thomas L
*/
#define F_CPU 14.7456E6 #include < avr/io.h >
#include < util/delay.h >
#include < avr/interrupt.h >
Deze eerste paar regels enkel de compiler wat informatie geven over welke het behoeften en de CPU-kloksnelheid bestanden. Zullen we enige vertraging om later te gebruiken om de AVR gewoon wachten, dus we hebben het util/delay.h bestand wilt meesturen. Dit bestand bevat de werkelijke vertraging functies die we in het hoofdprogramma noemen. De vertraging functies leveren een getimede vertraging, dus ze ons verplichten om de CPU-kloksnelheid. De #define F_CPU vertelt de compiler de snelheid van de kristal oscillator en dit nummer wordt dan voorzien van alle functies die nodig zijn om het te gebruiken.
/************************************************************************/
/ * verklaar globale variabelen * /
/************************************************************************/
unsigned int resultaat = 0;
int interruptcount = 0;
lang int tijd = 0;
lange int resetcount = 0;
Dit zijn de globale variabelen worden gedeclareerd. Als we een waarde aan een variabele toewijzen willen, hebben we de variabele toewijzen een type eerst. Bijvoorbeeld 'int' is voor geheel getal en is een geheel getal vertegenwoordigd door 16-bits (15 aantal bits en een tekenbit.) 'Unsigned int' is ook een geheel getal, maar met geen teken-bit. Dit betekent dat het kan alleen worden positief, maar het kan tweemaal zo groot vanwege het extra aantal bits. Deze variabelen zijn nodig in de main() functie, evenals de interrupt routines, dus ik heb ze hier verklaard voordat de main() functie begint. Dit maakt deze variabelen global, wat betekent dat ze kunnen worden geopend en gewijzigd door enig deel van het programma.
Ook is op dit punt het belangrijk op te merken, elke expressie moet eind met een puntkomma! Vergeet ik deze hele tijd, en de compiler geeft me fouten... altijd.
int main(void) DDRD = 0X00;
{
DDRC = 0xDF; //portc uitgang voor 7 segment multiplexing en 1 ingang voor afstand weergeven
DDRB = 0xFF; //portb fot 7 segment uitvoergegevens in bcd
PORTD | = 0xFF; //Enable portd optrekken van weerstanden
//(int0 and int1 require external pull up resistors or 1 interrupt will be triggered at reset)
Nu hebben we de main() functie. Dit is het begin van de eigenlijke programma, en het begint met het configureren van een paar dingen. De bovenstaande verklaringen van de DDRn toewijzen waarden aan de Data richting Registers voor de 3 poorten. Deze 8-bit registers configureren de I/O pinnen als ingangen of uitgangen. Een 1 op de positie van een bit zal de corresponderende pin een uitgang zou, en een 0 het een ingang. De toegewezen waarden kunnen in decimaal, binair of hexadecimaal. Decimale getallen worden normaal geschreven als 255. Binaire en hexadecimale hebben een voorvoegsel dat je wil vertellen de compiler wat voor soort nummer het is. Binaire zou 0b11111111. Hex zou 0xFF. Alle drie van deze zijn gelijk aan 255 is, en de compiler die u gebruikt niet de zorg. Hex is normaal gesproken het talstelsel van keuze, omdat het is makkelijker te lezen dan binaire, en makkelijker te relateren aan binaire dan decimale... De binaire equivalent van het nummer toegewezen aan elke DDR is 8 bits die de richting van de gegevens van de 8 pinnen van de haven vertegenwoordigt. Het PORTD | = FF; toewijzing maakt de pull-up weerstanden op deze invoer pennen. Als PORTD waren geconfigureerd als uitgang, dan verandert de toewijzing de Staten van uitvoer van die pinnen
Leer uw HEX naar binaire omzettingen en hen ertoe van geheugen! Het komt in handig...
Voor een zelfstudie HEX: http://www.microbuilder.eu/Tutorials/Fundamentals/Hexadecimal.aspx ***
EICRA | = (1 << ISC11) | (1 << ISC01) ; //configure externe interrupts
EIMSK | = (1 << INT1) | (1 << INT0);
TCCR1B | = (1 << CS12); //set prescaling voor timer1 256
Dit het deel waar ik meestal te trekken uit het gegevensblad voor de AVR en doen een beetje graven hebben. EICRA is de externe Interrupt besturingselement registreren A. Dit register heeft 4 bits die we instellen kunnen om te configureren hoe interrupts worden getriggerd door de externe interrupt-pinnen INT0 en INT1. Elk van de 4 bits heeft een unieke naam te identificeren wanneer instellen of wissen van bits. ISC00 en ISC01 zijn Interrupt gevoel controle voor INT0. Deze bepalen het type wijziging op de pin van de INT0 moet leiden tot de interrupt. ISC10 en ISC11 zijn hetzelfde besturingselement voor de INT1 pin. Controle van het gegevensblad zal laten zien dat we door het instellen van ISC01 en ISC11, beide interrupt-pinnen te activeren op de rand van negatieve gaan alleen geconfigureerd. De expressie (1 << ISC11) is een beetje shift operator die de ISC11-bit ingesteld. Zie "C programmeren voor Microcontrollers" om te leren alles over exploitanten en beetje manipulatie; het is een beetje te veel te gaan hier.
Het register van de EIMSK (externe Interrupt masker) heeft twee bits voor in- of uitschakelen van de externe interrupts. Beide van deze bits moeten worden ingesteld of de interrupt routines zal nooit opraken.
TCCR1B (Timer/teller besturingselement registreren 1 B) heeft verschillende stukjes die kunnen configureren hoe de timer zich gedraagt. Alles wat wij nood voor kopzorg zowat hier is de prescaling waarde. Alleen daardoor, hoeveel CPU kloksnelheid pulsen voordat de timer met 1 verhoogd zal worden geteld. In dit geval, ik koos voor een prescale waarde van 256, vertegenwoordigd door de CS12 bit (Controleer het gegevensblad.) Dit verdeelt de CPU-kloksnelheid door 256 voordat u het toepast op de timer/teller helpen om te voorkomen dat de teller overflows in het tijdsbestek dat wij proberen te meten.
/************************************************************************/ Sei();
/ * verklaar variabelen voor berekening en weergave * /
/************************************************************************/
unsigned int degenen = 0;
unsigned int tientallen = 0;
unsigned int honderden = 0;
unsigned int x = 0;
dubbele ticsfoot = 0;
dubbele fps = 0;
dubbele fph = 0;
dubbele mph = 0;
dubbele km/uur = 0;
dubbele mps = 0;
int afstand = 0;
OK, een gemakkelijk sectie! Dit is gewoon wat meer variabele declaraties en dat sei() ding. "sei()" is een commando bekend aan de compiler in de betekenis 'Activeer global interrupts.' Geen interrupts zal teweegbrengen als deze bit wordt niet ingesteld. "cli()" worden de bits globale interrupts uitschakelen gewist. We zullen dat men later gebruiken om problemen te voorkomen.
while(1)
{
/************************************************************************/
/ * krijg sensor afstand in de voeten van pind 0,1,4,5 * /
/************************************************************************/
int. distanceinput = (~ PIND & 0x33);
int. hibits = (distanceinput >> 2); //getting pind bits 0,1 en 4,5 samen te zijn
int lobits = (distanceinput & 0x03); //distance waarde in BCD. bits 2,3 zijn de
afstand = (hibits + lobits); //ext interrupt-pinnen al in gebruik.
als (afstand == 0) afstand = 16;
Deze kleine sectie leest de status van de 4 dip-schakelaars, waarmee de gebruiker om te kiezen de afstand tussen de sensoren. Er is sommige bits manipulatie hier aan de hand omdat de 4 ingangen niet opeenvolgende zijn. De variabelen 'hibits' en 'lobits' zijn slechts tijdelijke opslagplaatsen uitzoeken van de bits. De eerste regel isoleert de bits die we met willen de & exploitant. De tweede regel verschuift de bits over 2 plaatsen krijgen de Hallo bits in de juiste positie. De derde regel alleen isoleert de lo bits. Klik vervolgens in de vierde regel de lo en Hallo bits zijn toegevoegd om te maken hen één nummer. De instructie 'als' er voorkomt dat gebruikers kiezen '0' en het veroorzaken van een 'kloof-door-zero' voorwaarde later aan het einde. /************************************************************************/
/* 'ready' indicator LED */
/************************************************************************/
Als (interruptcount == 0)
{
PORTC | = (1 << 3);
}
anders
{
PORTC & = (0 << 3);
}
In deze sectie vindt uit als er een meting in de vooruitgang, en lichten de klaar geleid als niet. De variabele interruptcount wordt verhoogd in de interrupt routines om te helpen het programma bijhouden van welk deel van de meting is momenteel plaats. We zullen het zien van de interrupt routines in de buurt van het einde van de code.
/************************************************************************/
/ * berekeningen aan snelheid in 4 eenheden zoeken * /
/************************************************************************/
als (interruptcount == 2) //only berekenen wanneer beide interrupts hebben plaatsgevonden
{
cli(); //disable global interrupts
ticsfoot = (tijd / afstand); //distance is afstand tussen sensoren in voeten - ticsfoot teller tics/voet
FOD = (57600 / ticsfoot); //57600 is teller tics/sec (clk/prescaler cpu)
fph = (fps * 60 * 60);
mph = (fph / 5280);
km/uur = (mph * 1.609344);
MPS = (fps * 0.3048);
EIMSK | = (1 << INT1) | (1 << INT0);
Sei(); //Re-Enable externe interrupts en globale interrupts
}
Als de variabele interruptcount tweemaal heeft verhoogd (dit gebeurt in de interrupt routines later,) dat betekent dat beide interrupts hebben voorgedaan en de gemeten waarde is klaar om te worden gebruikt in de berekeningen. Hier zien we de cli()-instructie die de interrupts schakelt. Het is een goede gewoonte om dit te doen bij het lezen van een variabele die een interrupt heeft het vermogen om te veranderen. Waarom? De normale 'int' variabelen, bijvoorbeeld, zijn 16 bits lang, of twee bytes. De AVR is een 8-bit systeem, dus het duurt twee instructies om de 2 bytes van een enkele waarde te lezen. Het is vervolgens mogelijk, voor een interrupt optreden nadat de eerste byte wordt gelezen, maar vóór het tweede. Als u probeert te lezen van een variabele, maar de interrupt treedt op nadat u de eerste byte leest, kan de interrupt routine de waarde van de variabele veranderen voordat het klaar is wordt gelezen! Het programma zal worden hervat waar het weg wegging en lezen van de tweede byte van de variabele met gegevens die niet overeenkomt met de eerste byte! Dan is de waarde die u hebt geladen niet wat wordt verwacht en kan problemen veroorzaken.
De wiskunde hier niet te ingewikkeld. De opmerkingen moet genoeg om erachter te komen wat gaande is er. Dit is waar we gebruik maken van de variabele van de afstand van de dip-schakelaars. De variabele tijd komt later uit de interrupt routines.
De eerste interrupt schakelt zelf in haar routine, zodat het kan slechts eenmaal uitgevoerd voordat de meting voltooid is. Dat is waarom de EIMSK-instructie is er. Het wordt opnieuw ingeschakeld zowel externe interrupts zodat welke eerste en kreeg was gehandicapten zal opnieuw worden ingeschakeld.
/************************************************************************/
/* choose output options */
/************************************************************************/
if (!( PIND & (1 << PIND6)) & & (PIND & (1 << PIND7))) //choose voeten/sec Als (resultaat > = 999) resultaat = 999;
{
Round(fps);
resultaat = fps;
}
anders als (PIND & (1 << PIND6) & &! () PIND & (1 << PIND7))) //choose meter per seconde
{
Round(MPS);
resultaat = mps;
}
anders als (PIND & (1 << PIND6) & & (PIND & (1 << PIND7))) //choose km/hr
{
Round(kph);
resultaat = km/uur;
}
else //default miles/hr
{
Round(mph);
resultaat = mph;
}
Dit hele gedeelte is het lezen van de laatste twee dip-schakelaars om te bepalen van de gewenste output-eenheden. Vervolgens is welke waarde is nodig opgeslagen in de variabele resultaat.
De round() functie is een gemakkelijke manier om andere integerwaarden als onze productie zonder teveel fout. Deze functie is opgenomen in het math.h bestand dat al is opgenomen in het programma door het delay.h bestand dat we handmatig opgenomen. Als je naar het venster van de Verkenner oplossing aan de rechterkant in ATMEL Studio kijkt, ziet u het project naam gevolgd door "Dependencies" (met het label in de pic.) Nadat u de oplossing voor de eerste keer bouwen (Klik op bouwen >> bouwen oplossing,) alle bestanden opgenomen in het programma vindt u hier. U kunt vervolgens openen het delay.h bestand dat we aan het begin van de code opgenomen en neem een kijkje op het. Als u doorloopt, moet u de instructie #include < math.h > in de buurt van de top. Verder naar beneden vindt u de functie van de _delay_ms() die wij noemen maken de 1ms vertraging later.
De 'als' eind voorkomt probeert weer te geven van een getal dat niet past op onze 3 beeldschermen.
/************************************************************************/
/ * vertraging om te stoppen met meerdere "2de interrupt" triggers * /
/ * zonder te vertragen belangrijkste codeuitvoering * /
/************************************************************************/
resetcount ++;
Als ((resetcount > = 0x00FF) & & (interruptcount > = 2)) //resetcount bovengrens duurt, is afhankelijk
{ //before reset. 0x00FF approx. 3 sec
interruptcount = 0;
resetcount = 0;
}
Dit deel is (soort van) een timer gemaakt uit de lus van de belangrijkste while(1). Elke keer dat het programma lus voorbij dit punt, wordt de resetcount verhoogd. De berekeningen alleen optreden als interruptcount precies 2 is; Dus, als de tweede interrupt optreedt opnieuw het niet zal verknoeien de wiskunde. Als de interruptcount 2 of meer is, kan niets anders gemeten worden totdat de resetcount stappen tot een maximum 0x00FF geven ongeveer 3 seconden vertraging voor het target-object om de sensor gebied te verlaten. We kunnen een normale vertraging hier niet gebruiken, want we de schermen moeten te tonen ons de gemeten waarde. Wanneer interruptcount wordt teruggesteld aan 0, de klaar LED brandt en de volgende meting kan worden gemaakt.
/********************************************************************************/
/ * int resultaat weergeven op 3 cijfers 7 segment display * /
/ * vertraging geeft zeven Segmenttijd decoder te decoderen en weergeven van cijfers * /
/********************************************************************************/
if (!( PINC & (1 << PINC5))) //to afstand scherminstelling tentoongesteld
{ //only while button is pressed
resultaat = afstand;
}
anders
Deze 'als' horloges alleen de drukknop thats een verzoek om de afstand instelpunt weergegeven op de schermen. De laatste herberekening van de snelheid wordt opgeslagen in resultaat, telkens wanneer de sectie uitvoer eenheden (zie hierboven) wordt uitgevoerd in de lus, zodat het resultaat alleen gelijk aan afstand, terwijl de knop is ingedrukt.
honderden = (resultaat / 100); //Get 100 's plaats cijfers tientallen = (x / 10); degenen = x;
x = (resultaat % 100);
PORTB = (0x00|hundreds);
PORTC | = (1 << 2); / / cijfers schrijven
_delay_ms(1);
PORTC & = (0 << 2);
x = x % 10;
PORTB = (0x00|tens);
PORTC | = (1 << 1); / / cijfers schrijven
_delay_ms(1);
PORTC & = (0 << 1);
PORTB = (0x00|ones);
PORTC | = (1 << 0); / / cijfers schrijven
_delay_ms(1);
PORTC & = (0 << 0);
}
}
Tot slot! naar de display! Nu 'resultaat' is gelijk aan een geldig 3 decimale cijfer, moeten we het breken in enkele cijfers en stuur ze naar de displays op zijn beurt. We krijgen elk cijfer en opslaan in een variabele met de naam enen, tientallen of honderden. De honderden plaats cijfer is eenvoudig; gewoon delen door 100. De variabele is een 'int'-type, zodat niets na het decimaalteken is opgeslagen. 'x' is een tijdelijke variabele de volgende benodigde waarde op te slaan. De operator '%' doet de divisie, maar de rest terug naar 'x'. Dit geeft ons het cijfer van het trans-Europese netwerken en degenen te behandelen. Dat delen door 10 en herhaal voor degenen. Dat is een gemakkelijke manier om te scheiden van alle uw cijfers naar de displays en heel eenvoudig kan worden uitgebreid om te werken met grotere getallen.
De PORTB-instructies schrijven de cijfers op de pinnen van de uitvoer naar de decoder 7-segment. Tegelijkertijd bijna inschakelen de PORTC-instructies de uitgang van het bijbehorende beeldscherm grond transistor aan het licht van het cijfer. Dan geldt de vertraging het display op voor 1ms voordat de transistor terug uitschakelen en gaan over naar het volgende cijfer. Vrij koel.
De accolades sluiten einde van het blok van de code van de lus van While(1) en eindigen van de main() functie codeblok.
/************************************************************************/ }
/* sensor 1 interrupt */
/************************************************************************/
ISR(INT0_vect)
{
Als (interruptcount == 0)
{
TCNT1 = 0X0000; //reset teller op 0
interruptcount ++; //increment interrupt graaf
EIMSK & = (1 << INT1) | (0 << INT0); //disable INT0
}
else if (interruptcount == 1)
{
tijd = TCNT1; waarde van de teller van de //Capture
interruptcount ++; //increment interrupt graaf
}
anders resetcount = 0;
/************************************************************************/
/* sensor 2 interrupt */
/************************************************************************/
ISR(INT1_vect)
{
Als (interruptcount == 0)
{
TCNT1 = 0X0000; //reset teller op 0
interruptcount ++; //increment interrupt graaf
EIMSK & = (0 << INT1) | (1 << INT0); //disable INT1
}
else if (interruptcount == 1)
{
tijd = TCNT1; waarde van de teller van de //capture
interruptcount ++; //increment interrupt graaf
}
anders resetcount = 0;
}
Nu, de twee interrupt routines... Ze zijn precies hetzelfde, behalve voor één instructie, dus ik enkel over een spreken zal.
Ten eerste opmerken dat de interrupt routines buiten de main() functie. Het programma volledig stopt wat haar doet, laat de main() functie, en voert de interrupt routine. Na de interrupt routine is voltooid, wordt teruggekeerd naar de main() functie waar deze was gebleven.
De eerste lijn, ISR(INT0_vect), is de naam van de interrupt van het gegevensblad. Als de interrupt is ingeschakeld in het register van de EIMSK en globale interrupts zijn ingeschakeld, wordt het programma zal gaan naar deze regel wanneer de interrupt wordt geactiveerd.
U zult zien dat de interrupt routines in feite zijn opgebouwd uit een samengestelde ' als... ELSE IF'-instructie die slechts een paar dingen doet. Meestal is het best om te houden van de interrupt routines zeer kort, veranderen alleen wat moet worden gewijzigd en krijgt snel terug naar de main() functie. Met behulp van de interruptcount-variabele voor het bijhouden van het aantal interrupts die zich hebben voorgedaan laat ons toe om de routines identiek te maken, en kunt ook de sensoren te 'zien' doelobjecten vanuit beide richtingen.
Welke interrupt voert eerst Hiermee stelt u de timer van de TCNT1 (degene die wij de prescaler voor in het begin stellen) verhoogt de variabele interruptcount, vervolgens schakelt zelf zodat het niet kan worden extra keer geactiveerd door een onregelmatig gevormde doelobject.
Aangezien de eerste onderbreken opgehoogde interruptcount, de tweede uitgevoerd de volgende paar regels. Deze keer we vangen de waarde in de timer TCNT1 en opslaan in de variabele 'tijd' om te worden gebruikt in de berekeningen. Vervolgens wordt de interruptcount opnieuw verhoogd.
Met interruptcount nu gelijk is aan 2, worden de berekeningen uitgevoerd en de resetcount is tot 0x00FF tellen voordat het alles voor de volgende meting zal gereset. Als de tweede interrupt wordt weer geactiveerd om wat voor reden, resetcount is ingesteld op 0 en het wachten begint opnieuw. Dit is hoe ik voorkomen veelvoudige trekkers van de 2de interrupt voor zo lang als nodig voor het doelobject als uit de weg.
Oef, hoop ik dat ten minste een beetje logisch... In de volgende stap, we laadt het programma in onze chip en testen van het breadboarded circuit!