Hinweise für den Praktikumstermin 6:
- Es sollte immer auf eine Implementierung geachtet werden, die auf jeder Optimierungsstufe weiterhin ausführbar ist. Dabei ist auf die Verwendung von „volatil“ zu achten.
- Wenn es möglich ist, sollten bereits programmierte Aufgabenteile in die Gesamtlösung von Termin 6 integriert werden, aber nicht alles was im Voraus programmiert wurde, muss schlussendlich auch einen Platz in der Lösung von Termin 6 finden.
- „Je weniger Code, desto besser.“, d.h., dass eine Interrupt Service Routine oder das Beachten von Stromsparmodi für die Lösungen nicht zur notwendigen Implementierung gehören, sondern optional sind. Je mehr Code ein Programm besitzt, desto anfälliger für Fehler kann es werden.
Aufgabe 1
In eingebetteten Systemen und für Kartentreiber muss man oft auf Register zugreifen, die auf festen Adressen liegen. Wir werden uns zuerst mit den Registern der PIO des hier eingesetzten Mikrocontroller (AT91M63200) beschäftigen.
| Name | Adresse | Bedeutung |
| PIOB_PER | 0xFFFF0000 | PIOB Port Enable Register |
| PIOB_OER | 0xFFFF0010 | PIOB Output Enable Register |
| PIOB_SODR | 0xFFFF0030 | PIOB Set Output Data Register |
| PIOB_CODR | 0xFFFF0034 | PIOB Clear Output Data Register |
Legen Sie zunächst Zeigervariablen (PIOB_PER, PIOB_OER, ..) an und initialisieren Sie diese im Code auf die Adresse des Registers. Danach können Sie über diese Zeiger auf die Register zugreifen. Schreiben Sie zunächst den Wert 0x100 ins PIOB_PER und dann ins PIOB_OER. Danach schreiben Sie nacheinander den Wert 0x100 einige Male alternierend ins PIOB_SODR und PIOB_CODR. Dadurch sollte die LED DS1 auf dem Board AT91EB63 an und aus gehen. Testen Sie dieses mit Einzelschritten aus.
#define PIOB_PER ((volatile unsigned int*) 0xFFFF0000) // Port Enable Register
#define PIOB_OER ((volatile unsigned int*) 0xFFFF0010) // Output Enable Register
#define PIOB_SODR ((volatile unsigned int*) 0xFFFF0030) // Set Output Data Register (disable)
#define PIOB_CODR ((volatile unsigned int*) 0xFFFF0034) // Clear Output Data Register (enable)
int main(void)
{
*PIOB_PER = 0xE300; // LED's 1,2,6,7,8 an PIOB aktivieren
*PIOB_OER = 0xE300; // LED's 1, 2, 6, 7, 8 als Output definieren
*PIOB_SODR = 0xE300; // Set Output Data Register ausschalten
// Schleife zum Ein- und Ausschalten der LED 1
while(1)
{
*PIOB_SODR = 0x0100; // LED 1 ausschalten
*PIOB_CODR = 0x0100; // LED 1 einschalten
}
return 0;
}
Notizen: Während dem Start des Programmes haben die LEDs 2, 6, 7 und 8 gebrannt. Die Anpassungen zu Beginn des Codes dienen dazu, damit diese ausgeschaltet werden.
Aufgabe 2
Gut, wir können jetzt eine LED (DS1) kontrollieren. Schauen Sie sich die Dateien im Verzeichnis ~/mpsSS2012/h an. Sie können diese Header-Dateien in Ihren nächsten Programmen benutzen. Ändern und erweitern Sie Ihr Programm so, dass die LED DS1 durch drücken der Taste SW1 eingeschaltet und durch drücken der Taste SW2 ausgeschaltet wird. Sollte die Taste nicht funktionieren, so überprüfen Sie ob im Power Management Controller (PMC) der Clock für PIOB eingeschaltet ist. In welchem Register muss welches Bit gesetzt sein?
#include "../h/pmc.h"
#include "../h/pio.h"
int main(void)
{
StructPMC* pmcbase = PMC_BASE; // Basisadresse des PMC
StructPIO* piobaseB = PIOB_BASE; // Basisadresse PIO B
pmcbase->PMC_PCER = 0x4000; // Peripherial Clock einschalten für PIOB
piobaseB->PIO_PER = 0x0100; // PIO kontrolliert LED1 (0x100)
piobaseB->PIO_OER = 0x0100; // als Ausgang
piobaseB->PIO_PER = KEY1; // PIO kontrolliert SW1 bzw. Key 1
piobaseB->PIO_ODR = KEY1; // als Eingang ((!!!))
piobaseB->PIO_PER = KEY2; // PIO kontrolliert SW2 bzw. Key 2
piobaseB->PIO_ODR = KEY2; // als Eingang ((!!!))
// Schleife zum Ein- und Ausschalten der LED 1
while(1)
{
// Einschalten wenn Taste 1 gedrückt wird
if(!(piobaseB->PIO_PDSR & KEY1))
piobaseB->PIO_CODR = 0x0100;
// Ausschalten wenn Taste 2 gedrückt wird
if(!(piobaseB->PIO_PDSR & KEY2))
piobaseB->PIO_SODR = 0x0100;
}
return 0;
}
Notizen: Das Drücken der Taste SW1 erzeugt eine 0. Wenn die Taste SW1 nicht gedrückt wird, erzeugt sie als Signal eine 1. Deshalb müssen die IF-Abfragen innerhalb der Schleife mit negierten Bedinungen geprüft werden. Das & steht für die binäre Verknüpfung zwischen Status Register und Taste.
Aufgabe 3
Lassen Sie im nächsten Programm zusätzlich die LED DS2 mit ca. 0,5Hz (Zeitschleife programmieren) blinken. Wie reagieren Ihre Tastendrücke an SW1 und SW2?
#include "../h/pmc.h"
#include "../h/pio.h"
#define DELAY 500000 // Anzahl der Durchläufe für die Schleife
int main(void)
{
StructPMC* pmcbase = PMC_BASE; // Basisadresse des PMC
StructPIO* piobaseB = PIOB_BASE; // Basisadresse PIO B
pmcbase->PMC_PCER = 0x4000; // Peripherial Clock einschalten für PIOB (0x4200)
piobaseB->PIO_PER = 0x0100; // PIO kontrolliert LED1
piobaseB->PIO_OER = 0x0100; // als Ausgang
piobaseB->PIO_PER = 0x0200; // PIO kontrolliert LED2
piobaseB->PIO_OER = 0x0200; // als Ausgang
piobaseB->PIO_PER = 0x08; // PIO kontrolliert SW1 bzw. Key1
piobaseB->PIO_ODR = 0x08; // als Eingang
piobaseB->PIO_PER = 0x10; // PIO kontrolliert SW2 bzw. Key2
piobaseB->PIO_ODR = 0x10; // als Eingang
volatile int an = 1; // Variable zum Ein- und Ausschalten
// Schleife zum Ein- und Ausschalten der LED 1
while(1)
{
volatile int i=0; // Variable der Pausen-Schleife
// Einschalten
if(!(piobaseB->PIO_PDSR & 0x08))
piobaseB->PIO_CODR = 0x0100;
// Ausschalten
if(!(piobaseB->PIO_PDSR & 0x10))
piobaseB->PIO_SODR = 0x0100;
if(an==1){
piobaseB->PIO_CODR = 0x0200;
an=0;
}
else {
piobaseB->PIO_SODR = 0x0200;
an=1;
}
for(i = 0; i<DELAY;i++) {}
}
return 0;
}
Notizen: Um den Code zu optimieren, wurde anstelle zwei FOR-Schleifen einzusetzen, die Variante mit einer integer-Variablen „an“ gewählt, deren Wert bei jedem Durchlauf der WHILE-Schleife alterniert zwischen 1 (an=true) und 0 (an=false). Das Signal der Tasten wird während der Pausen-Schleife nicht angenommen, auch nachträglich wird das Signal nicht übertragen in die Register. Um die Taste zu setzen ist ein häufiges Drücken oder das permanente Drücken notwendig. Die Dauer der Schleife ist durch eine Programmierung in C stark von der Optimierungsstufe abhängig und sollte deshalb in Assembler geschrieben werden. Als weiteres Kriterium ist die Prozessor- und RAM-Taktrate und das Pipelining ausschlaggebend bei der Wahl der Durchläufe der Schleifen. Der Mikrocontroller im Labor arbeitet mit einer CPU-Taktrate von 25MHz, der interne RAM besitzt allerdings einen eigenen Takt, der noch durch Wait-States beeinflusst wird. Formel zur Berechnung der Durchläufe der Pausen-Schleife:
Prozessor-Geschwindigkeit = 25 MHz = 25.000.000 Hz Taktzeit = 1 / 25.000.000 Hz Takte pro Durchlauf = 4 Frequenz = 0,5 Hz = 2s Durchläufe = Frequenz / ( Taktzeit * Takte pro Durchlauf ) = 12500000 ?
Aufgabe 4
Schreiben Sie für die Tasten SW1 und SW2 eine passende Interruptserviceroutine. Erklären Sie die Wechsel der ARM-Betriebsmodi. Wie reagieren nun Ihre Tastendrücke an SW1 und SW2? Welchen Einfluss hat das Bedienen der Tasten auf die Blinkfrequenz von DS2?
#include "../h/pmc.h"
#include "../h/pio.h"
#include "../h/aic.h"
// Anzahl der Durchläufe für die Schleife
#define DELAY 500000
// Compiler mitteilen, dass es sich um eine Interrupt Service Routine handelt
void irq_handler(void) __attribute__ ((interrupt));
// Interrupt-Service-Routine
void irq_handler( void)
{
StructPIO* piobaseB= PIOB_BASE; // Basisadr. PIO B
StructAIC* aicbase= AIC_BASE; // Basisadresse AIC
// Einschalten
if(!(piobaseB->PIO_PDSR & 0x08))
piobaseB->PIO_CODR = 0x0100;
// Ausschalten
if(!(piobaseB->PIO_PDSR & 0x10))
piobaseB->PIO_SODR = 0x0100;
// Interrupt-Status-Register lesen und in das
// End of InterruptCommandRegister (EOICR) schreiben
aicbase->AIC_EOICR= piobaseB->PIO_ISR;
}
int main(void)
{
StructAIC* aicbase = AIC_BASE; // Basisadresse AIC
StructPIO* piobaseB= PIOB_BASE; // Basisadr. PIO B
aicbase->AIC_IDCR = 1 << 14; // InterruptPIOB ausschalten
aicbase->AIC_ICCR= 1 << 14; // InterruptPIOB löschen
aicbase->AIC_SVR[PIOB_ID] = (unsigned int)irq_handler;
aicbase->AIC_SMR[PIOB_ID] = 0x7; // Priorität definieren
aicbase->AIC_IECR= 1 << 14; // InterruptPIOB einschalten
piobaseB->PIO_IER= KEY1|KEY2; // Interrupt innerhalb PIOB erlauben
piobaseB->PIO_PER = 0x0100; // PIO kontrolliert LED1
piobaseB->PIO_OER = 0x0100; // als Ausgang
piobaseB->PIO_PER = 0x0200; // PIO kontrolliert LED2
piobaseB->PIO_OER = 0x0200; // als Ausgang
piobaseB->PIO_PER = 0x08; // PIO kontrolliert SW1 // Key1
piobaseB->PIO_ODR = 0x08; // als Eingang
piobaseB->PIO_PER = 0x10; // PIO kontrolliert SW2 // Key2
piobaseB->PIO_ODR = 0x10; // als Eingang
int an = 1; // Variable zum Ein- und Ausschalten
while(1)
{
volatile int i=0;
if(an==1){
piobaseB->PIO_CODR = 0x0200;
an=0;
}
else {
piobaseB->PIO_SODR = 0x0200;
an=1;
}
for(i = 0; i<DELAY;i++) {}
}
aicbase->AIC_IDCR= 1 << 14; // InterruptPIOB ausschalten
aicbase->AIC_ICCR= 1 << 14; // InterruptPIOB löschen
return(0);
}
Notizen: Im Gegensatz zu der Implementierung ohne ISR nehmen die Tasten nun jederzeit und ohne direkte Verzögerung die Tastendrücke an. Die Blinkfrequenz von LED2 wird durch das Drücken der Tasten nicht gestört. Für die menschliche Wahrnehmung sind Tastendrücke ab einer Reaktionszeit von weniger als 20ms in „Echtzeit“. Unterschiedliche Optimierungsstufen können unterschiedliche Blinkzeiten bei den LEDs erzeugen. Deswegen ist darauf zu achten, dass der Code so gut wie möglich auf alle Optimierungsstufen angepasst zu sein. Dafür soll künftig der Timer in ARM programmiert werden.