Poängen med Direct Port Manipulation

Sedan programmering blev en möjlighet för gemene man, finns risk för att enkel men användbar kod förbises eller helt glöms bort – exempelvis direct port manipulation (DPM). Vissa programmerare menar att sådana kommandon kan vara otydliga och orsaka förvirring hos nybörjare och därför inte bör kombineras med mer välbekanta strukturer. Men varför inte använda alla tillgängliga verktyg för att göra programkoden konsekvent och effektiv?

Till exempel behövs det – för varje port i en 8-bitars microcontroller – tre stycken 8-bitars register för att kunna styra portaktiviteten. Registren är: DDRx för indata-/utdatadriktning, PORTx för stiftlogikstyrning samt PINx för att lagra portens aktuella stiftstatus. Vi utvecklar det hela genom att titta närmare på port C. I setup-skedet kanske programmeraren vill definiera C-portens stift som utgångar med en inledande LÅG-status. Ofta används en prydlig ”for-loop” med 3 till 4 upprepade kodrader för att stega igenom stiften 0 till 7. Detta ger totalt 32 kodrader att köra. Genom att använda biblioteksfunktioner i loopen, exempelvis pinMode() och digitalWrite() kan man få mycket mer att hända ”under ytan” – biblioteksfunktionerna gör att mer kod körs.

De åtta registerbitarna för riktning hos port C (DDRC) sätts till HÖG och de åtta registerbitarna för logiknivå hos port C (PORTC) sätts till LÅG. Exakt samma åtgärd kan utföras genom att sätta bitarna till HÖG eller LÅG med hjälp av följande direkta portregisterkommandon:

DDRC = 0xFF; //Sätt port C-stift till utgångar (binärt DDRD = 0b11111111;)

PORTC = 0x00; //Sätt port C-stift till LÅG (binärt PORTC = 0b00000000;)

Och om vi vill sätta stiften i port A till ingångar?

DDRA = 0x00; //Sätt port A-stift till ingångar (binärt DDRA = 0b00000000;)

Slutligen kanske vi vill ha både ingångar och utgångar i port D:

DDRD = 0x0F; //Sätt port D:s övre fyra bitar som ingångar och de undre som utgångar (binärt DDRD = 0b00001111);

Med ett hexadecimalt värde som 0xFF, ökar förståelsen av bitvärdetilldelningen i registret. ”0x” är hexadecimal-indikatorn, det första F:et representerar de övre fyra bitarna i ett 8-bitarsregister och det andra F:et representerar de undre fyra bitarna.

Alla möjliga kombinationer av bitar ställda till HÖG eller LÅG kan representeras av ett hexadecimalt värde eller binärt värde. Binära värden är tydligare eftersom alla åtta bitarna är synliga när koden skrivs. Båda varianterna kan användas om kompilatorn stöder dem men hexadecimala värden är kortare och coolare.

Obs: Användning av binära värden är inte universell standard i C/C++.

Vi vidareutvecklar

Vi konfigurerade port A och port C i setup-delen. Om vi använder DPM krävs det nu endast ytterligare några kodrader i huvudloopen för att avläsa digitala enheter och driva andra digitala enheter. För att programmera två stegmotorer med gränslägesbrytare som styrs av en digital joystick krävs det i praktiken endast ett fåtal registerkommandon och en uppslagstabell. Maskinvarukonfigurationen för det beskrivna scenariot visas i figur 1.

Figur 1: Maskinvarukonfiguration för stegmotor-/joysticksystem med microcontroller-portarna A och C. (Bildkälla: DigiKey)

Maskinvara:

En digital joystick med fyra normalt öppna (NO) kontakter (NO) anslutna till VCC är också anslutna till stiften 0 till 3 i port A. Utgångarna sätts till LÅG vid microcontrollern. När kontakterna sluts, sätts portstiften till HÖG. Kontakterna representerar UPP, NED, VÄNSTER och HÖGER och är konfigurerade så att två intilliggande kontakter kan aktiveras samtidigt, vilket ger åtta möjliga utkombinationer. En nionde utgång representerar STOPPA HELT, vilket inträffar när joysticken är centrerad och alla kontakter är öppna.

Fyra gränslägesbrytare med NO-kontakter kopplade till jord är också anslutna till de återstående stiften i port A, vilka motsvarar joystickkonfigurationen UPP, NED, VÄNSTER och HÖGER. Utgångarna sätts till HÖG vid microcontrollern. När kontakterna sluts, sätts portstiften till LÅG.

Stegmotorns drivkort skapar mekanisk rörelse genom att sätta tre styrstift till omväxlande hög och låg, för att åstadkomma steg-, riktnings- och hållfunktionerna. I det här exemplet är alla åtta bitarna för port C dedikerade för två drivkort, trots att två av stiften inte används.

Programmering:

En uppslagstabell används för att översätta de fyra joystickbitarna till åtta drivbitar. En kodrad i huvudloopen anropar funktionen get_output() och skickar PINA-registrets innehåll till funktionen. Det värde som funktionen returnerar skrivs direkt till PORTC-registret:

PORTC = get_output(PINA);

Inom funktionen används uppslagstabellen lookup_output[ ] och ett indexerat värde returneras. Men i den här funktionen pågår också något mer, som har att göra med gränslägesbrytarna. Uppslagstabellens indexvärde, inom hakparenteserna i lookup_output[ ], representeras av ett bitväxlings- och maskeringsuttryck. Den resulterande indexvariabeln är ett bitvis AND på de övre 4 bitarna i indataregistret (gränslägesbrytarens värden) och de undre fyra bitarna (joystickvärdena). Om någon av gränslägesbrytarens kontakter stängs och det finns ett nollvärde i någon av de fyra övre bitarna, rensas den motsvarande undre biten.

Obs: Fyra bitar kan representera 16 olika bitkombinationer. De sju tabellindexpositioner som inte används översätts därför till 0x00, för att förhindra fel.

Copyuint8_t get_output(uint8_t porta_val) { return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)]; } 

Exempel:

Joysticken i positionen UPP och HÖGER med alla gränslägesbrytare öppna, resulterar i det binära PINA-registervärdet 0b11111001 (eller 0xF9 hex) som skickas till funktionen. Funktionen ”nollar bort” de övre fyra bitarna med hjälp av ett bitvis AND på 0b00001111 (0x0F), vilket resulterar i 0b00001001 (0x09) som indexvärde i uppslagstabellen. Använder man ett annat bitvis AND för att jämföra detta resultat med det skiftade gränslägesbrytarvärdet 0b00001111 (0x0F), är det föregående värdet oförändrat, vilket returnerar 0b00001001 (0x09) som slutligt indexvärde, pekandes till 0b00100001 (0x21) i uppslagstabellen. Det motsvarar översättningen UPP och HÖGER för stegmotordrivaren. Se figur 2.

Figur 2: Microcontrollerns tolkning av port A- och port C-indata när joysticken är i positionen UPP och HÖGER. (Bildkälla: DigiKey)

Om UPP-gränslägesbrytaren hade nåtts, så att en nollbit genererats, skulle det skiftade värdet bli 0b00000111 (0x07) i stället för 0b00001111 (0x0F), vilket hade negerat det motsvarande UPP-joystickvärdet, så att det slutliga indexvärdet blev 0b00000001 (0x01) i stället för 0b00001001 (0x09). Det översatta värdet för 0b00000001 (0x01) är 0x26 i uppslagstabellen. Det motsvarar översättningen för endast HÖGER för stegmotordrivaren. Se figur 3.

Figur 3: Microcontrollerns tolkning av port A- och port C-indata när joysticken är i positionen UPP och HÖGER då UPP-gränslägesbrytaren löser ut. (Bildkälla: DigiKey)

Slutsatser

Det är alltid bra att minimera mängden kod, och för det ändamålet kan programmeraren använda DPM, som också är ett effektivt verktyg för att konfigurera, läsa eller skriva portdata. För att utföra samma operationer med vanliga biblioteksfunktioner krävs det mycket mer kod och (oftast) ganska mycket mer programminne. Koden nedan använder mindre än 1 % av minnet hos den testade microcontrollern ATMEGA328P. Att infoga lämpliga kommentarer i koden är avgörande för att förstå hur DPM-funktionerna används och gör det lättare att utföra eventuell felsökning.

Digi-Keys maskinvaruexempel:

Stegmotorer – https://www.digikey.com/short/pdnfp4

Styrenheter för stegmotorer – https://www.digikey.com/short/pdnf4r

Joystick – https://www.digikey.com/short/pdnf57

Gränslägesbrytare – https://www.digikey.com/short/pdnfwm

Exempelkod:

Copyconst uint8_t lookup_output[16] = { 0x09, //Index 0 helt stopp. Använd hållström0x26, // Index 1 höger 0x34, // Index 2 vänster 0x00, // Index 3 oanvänd 0x36, // Index 4 ned 0x0C, // Index 5 ned/höger 0x31, // Index 6 ned/vänster 0x00, // Index 7 oanvänd 0x24, // Index 8 upp 0x21, // Index 9 upp/höger 0x0E, // Index 10 upp/vänster 0x00, // Index 11 oanvänd 0x00, // Index 12 oanvänd 0x00, // Index 13 oanvänd 0x00, // Index 14 oanvänd 0x00 // Index 15 oanvänd }; void setup() { // Set all bits in port A direction register as INPUTs; // Limits (up, down, left, right) Joystick (up, down, left, right) DDRA = 0x00; // Set all bits of port C direction register as OUTPUTs; // Motorstyrning (oanvänd, Mot_1, Dir_1, En_1, oanvänd, Mot_2, Dir_2, En_2 DDRC = 0xFF; } void loop() { //skicka port A-värden till funktionen. Skriv returvärdet till port C. PORTC = get_output(PINA); } /***** Översättningsfunktion för invärde *******/ uint8_t get_output(uint8_t porta_val) { // Jämför gränslägesbrytar- och joystickvärden. Hämta och returnera det översatta värdet.
    return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)]; } 

Om skribenten

Image of Don Johanneck

Don Johanneck är teknisk innehållsutvecklare på DigiKey och har arbetat med oss sedan 2014. Han är ganska ny på sin tjänst som ansvarig för upprättande av videobeskrivningar och produktinnehåll. Don har nyligen tagit sin universitetsexamen i tillämpad vetenskap inom elektronikteknik och automatiserade system på Northland Community & Technical College med ett DigiKey-stipendium. På fritiden sätter han gärna av lite tid för radiostyrda modeller, renovering av gamla maskiner och mekande i allmänhet.

More posts by Don Johanneck
 TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum