Auf dieser Seite gibt es einige Hinweise und Tipps für die Programmierung von AVR-Microcontrollern.
I/O-Pins
Zur Ansteuerung jedes I/O-Ports gibt es drei Register:
PORTx:
Zustand des Pins, low (0) oder high (1) bzw. ohne (0) oder mit (1) Pull-Up-WiderstandDDRx
(Data Direction Register): Legt fest, ob der Pin hochohmig (0) ist, so dass man eine anliegende Spannung messen kann oder niederohmig (1), so dass man Strom "sourcen" oder "sinken" kann.PINx
: Gibt den tatsächlichen Zustand (high oder low), also die am Pin anliegende Spannung zurück.
Damit kann jeder I/O-Pin vier Zustände (Kombination von (PORTx, DDRx)) annehmen:
- (
PORTx=1
,DDRx=1
): High: Der MC versucht, den Pin auf High zu ziehen, also VCC auszugeben - (
PORTx=0
,DDRx=1
): Low: Der MC zieht den Pin auf Masse - (
PORTx=0
,DDRx=0
): Hochohmig (High Impedance, High-Z): Der MC macht gar nichts mit dem Pin, man kann in PINx das anliegende Level (high / low) ablesen - (
PORTx=1
,DDRx=0
): Pull-up: Der MC verbindet den Pin über einen Widerstand (ca. 60kOhm) mit VCC. Der Pin lässt sich aber noch extern, zum Beispiel durch einen Taster oder Schalter auf Masse ziehen.
Moduliertes Signal ausgeben (z.B. RC5)
Um ein moduliertes Rechteck-Signal auszugeben, etwa für eine, kann man gut den in vielen AVRs verfügbaren Waveform Generator Mode (WGM), einen Bestandteil der Timer/Counter ausnutzen. Damit benötigt die Erzeugung des Signals fast keine Rechenleistung. Vorgehensweise:
- Timer einstellen, so dass er mit einer konstanten Frequenz einen Output Compare Match erreicht
- Den Output Compare Mode so einstellen, dass ein Output Compare Pin (OCx) bei jedem Compare Match toggelt.
- Nun nur noch den WGM zu geeigneten Zeitpunkten an- oder ausschalten, so dass das korrekte Signal am OCx-Pin anliegt.
Beispiel für ATTiny2313:
// setup waveform generation to 36kHz on timer 0
TCCR0A |= _BV(WGM01);
// clear timer on compare match
TCCR0B |= _BV(CS00);
// timer source is system clock
OCR0A = 107;
// output compare value for 36kHz @ 8 MHz
// output compare pin to output mode, low level
DDRB |= _BV(PB2);
// port PB2 as output
PORTB &= ~_BV(PB2);
// PB2 is low
// setup half-bit clock (555Hz) on timer 1
TCCR1B = _BV(WGM12);
// Clear timer on compare match
OCR1A = 7049;
// ~ 889us @ 8MHz
TIMSK |= _BV(OCIE1A);
// output compare interrupt enable for timer 1a
ISR(TIMER1_COMPA_vect)
{
if(...)
{
// enable modulated signal
TCCR0A |= _BV(COM0A0);
}
else
{
// disable modulated signal
TCCR0A &= ~_BV(COM0A0);
}
}
Datenstrukturen im Flash-ROM
Der GNU C Compiler für AVR unterstützt die Ablage von Daten im Flash-Speicher. Dazu wird bei der Definition einer Variablen das KeywordPROGMEMverwendet. Leider kann der Compiler auf diese Variablen danach aber nicht mehr wie auf Daten im RAM zugreifen. Dies hängt damit zusammen, dass die AVR-Architektur getrennte Datenbusse für den Zugriff auf den Flashspeicher und den RAM vorsieht. Eine Übergabe einer solchen Variable etwa als Funktionsparameter funktioniert schlicht nicht und führt teilweise zu recht seltsamem Verhaltes des Codes.
Um dennoch an die im Flash-Speicher abgelegten Daten zu gelangen, gibt es in der avrlibc entsprechende Funktionen, um einzelne Bytes aus dem Flash-Speicher zu lesen. Um mit den im Flash abgelegten Daten zu arbeiten, ist man also immer gezwungen, diese zunächst in den RAM zu kopieren. Dazu ist zum Beispiel die folgende Funktion sehr nützlich, da man damit einfach auch größere Strukturen aus dem Flash laden kann:
uint8_t memdrv_read(uint8_t *buffer, memaddr_t address, uint8_t numBytes)
{
uint8_t i;
if((address + numBytes) >= FLASH_SIZE)
{
return MEM_ERROR;
}
for(i = 0; i < numBytes; ++i)
{
buffer[i] = pgm_read_byte_near(address + i);
}
return MEM_SUCCESS;
}
Damit kann man dann eine im Flash ablegte Struktur
struct mystruct\_t flash\_var PROGMEM = {7, 0x13, 0xffff};
mit dem Aufruf
struct mystruct_t rambuf_var;
memdrv_read(&rambuf_var, (memaddr_t)(&flash_var), sizeof(flash_var));
wieder in den RAM lesen, ohne sich um das exakte Layout der einzelnen Bytes im Flash oder im RAM kümmern zu müssen. Dieses Konzept lässt sich auch ganz gut für andere Speicher, wie etwa ein externes EEPROM oder den RAM einer RTC (dann naürlich nicht nur zum Lesen, sondern auch zum Schreiben) einsetzen. Es genügt, wenn der Speicher eine Funktion zum Lesen (oder Schreiben) einzelner Bytes unterstützt.