ATtiny Software Tips
Deze pagina is een vervolg op mijn Starten met de
ATtiny2313 pagina... Het behandelt een aantal tips en trucks aan de
softwarekant voor mensen die met de 2313 willen werken en dient eigenlijk
voornamelijk als naslagwerk voor mezelf (voor hardware, zie ATtiny hardware tips).
Onderwerpen op deze pagina:
Geen garanties, maar hopelijk heeft naast mezelf nog iemand er iets aan.
English: MiniMon for ATtiny2313
I did not find any standard way to access variables and I/O locations in a
'life' system, i.e. for debugging and tuning a running system which would break
when single stepping or stopping, due to interaction with other parts/systems.
For this purpose, I made my MiniMon program.
It interacts with the running program via the serial port and a small
interrupt routine. It allows you to read and write SRAM and I/O locations,
without stopping the program (and taking at 4 MHz about 10 µs per
interaction).
Download: minimon_v0r2.zip (PC program for
interaction also included). Include in your program by including the
minimon.h file. Still very primitive, but working. More tips in
the Dutch main body of this page...
Een real-time monitor: MiniMon
Soms is het handig om in een draaiend ('life') programma real-time de
variabelen of geheugenlocaties te kunnen bekijken, of I/O poorten te lezen of
te schrijven (bijvoorbeeld omdat je de datasheet toch net wat anders had
geïnterpreteerd). Niet ieder programma kan zomaar gestopt worden, vooral bij
interactie met de omgeving of andere systemen. Daarvoor deze real-time monitor,
waarmee je over de seriële poort vanaf de PC in de SRAM/IO geheugenmap van de
ATtiny komt zonder het draaiende programma te stoppen of te beïnvloeden.
Het geheel bestaat uit drie delen:
- Het eigenlijke real-time programma op de ATtiny. Dit is een
interrupt-routine die de binnenkomende commando's op de seriële poort opvangt
en interpreteert, en de resultaten terugstuurt. Per commando neemt dit zo'n 10
µs; mijn programma's zijn niet zo tijd-kritisch dat ze dat niet
aankunnen.
- Het MiniMon programma op de PC, dat een wat gebruikersvriendelijke
interface laat zien, en waarmee je makkelijk stukken geheugen kunt lezen,
schrijven etc. Maar, je kan eventueel ook zelf vanuit een programma MiniMon
aansturen (heb zelf eerst getest in Windows hyperterm).
- Een stukje hardware, dat de seriële signalen van de ATtiny (3..5
Volt) omzet naar de RS232 seriële signalen die de PC verlangt(+/- 8 Volt), in
mijn geval gemaakt met een MAX232 level converter en wat losse onderdelen. Hier
ga ik hier verder niet op in, is vrij standaard.
Het eigenlijke programma op de ATtiny, de MiniMon, is compact (142
bytes programma = 7% van het 2K geheugen, 2 bytes SRAM+8 bytes stack), gebruikt
een minimaal aantal CPU cycles (bij 4 MHz ongeveer 10 µs per interactie) omdat
de communicatie erg kort gehouden is, en gebruikt de USART en twee pinnen van
de ATtiny2313: RxD(2) en TxD(3). Je moet de file MiniMon.h
invoegen in je main program file (#include "MiniMon.h"; ), dit
voegt de interrupt-routine toe aan je programma. Weet het, niet echt netjes
code in een .h file te stoppen, maar maakt het net wat makkelijker voor snelle
projectjes (niks mee te linken en zo). Wel moet je in main ook de
initialisatieroutine aanroepen met een geschikte baudratefactor, in mijn geval
(baudrate van 9600 bps bij een 4 MHz kristal):
#ifdef MINIMON
MiniMonInitUART(25);
#endif
Serieel via USB
Als je een Linux-computer met seriële poort hebt is gtkterm een
handig programma. Met een USB-to-serial adaptor met bijvoorbeeld een FTDI
FT232RQ chip is ook goed te werken, je port is dan bijvoorbeeld
/dev/ttyUSB0 . Voeg wel jezelf toe aan de groep
'dialout ' omdat je anders niet de rechten hebt om de seriële
porten te benaderen: sudo usermod -a -G dialout
<je_username> . Soms wordt de FTDI-chip al door andere
drivers geclaimd (lijkt bij 12.04 geen issue te zijn); zie dan WUP/modem,
ProdID, brltty.
Het geheel in versie 0.2 kan je downloaden (bevat MiniMon.h : de
eigenlijke ATtiny2313 code; MiniMon.c : een ATtiny
testprogramma'tje; MiniMon.exe : het PC programma voor poort COM1;
en MiniMon.mac , een voorbeeld van een nog te documenteren
macro-mogelijkheid voor MiniMon.exe). Download een eerste beta versie
(nog steeds erg primitief): minimon_v0r2.zip
Voorbeeld starten MiniMon.exe:
- Start default COM1, 9600,n,8,1 format: Minimon
- Start met manual com port settings: MiniMon COM2 19200,n,8,1
Voorbeeld gebruik MiniMon:
- Dump geheugen (0x80 bytes) vanaf locatie 0x60: D60 80
- Display SREG, X,Y en Z registers plus stack (pointer):
R
- Doe de LED op mijn bordje aan (hangt aan bit 2 in poort D, zet deze als
ouput) : S31 4
- Vraag de ingebouwde help op: H
RxD/TxD pinnen al gebruikt? Misschien dat ik nog eens een versie maak
gebaseerd op I²C, via de ISP header. Eerst mijn I2C master op orde
hebben...
C compiler tips voor optimale
code
Al met al heb ik tot nu toe alles in C gedaan. Wel is de ATtiny2313
natuurlijk nogal beperkt in geheugen; 2 Kbytes programmageheugen (flash) en 128
bytes datageheugen. Tot nu toe blijkt dit geen probleem te zijn, mijn
ondertussen al wat uitgebreidere RGB-LED programma gebruikt nu (inclusief
startup code en C runtime) ongeveer 1 Kbyte flash en 64 bytes datageheugen.
Daarnaast gebruikt C natuurlijk de stack; tot nu toe minder dan 32 bytes. Wel
een paar tips:
int main(void) __attribute__ ((noreturn)); // no context
save
int main(void)
{
....
En zo zijn er meer tips&tricks te bedenken.
Bit and byte manipulatie
Om bits in poorten efficient te toggelen is onderstaande
XORBIT niet altijd de meest efficiënte; kijk eens naar de
functionaliteit van de PINx poort en doe: SETBIT(PIND,
1<<PD3); Zelfde als XORBIT(PORTD, 1<<PD3) maar
kortere code...
Handig om een paar macro's te definiëren om bits en bytes te manipuleren op
een manier die efficiënte code oplevert... heb ze zelf in een headerfile bij
elkaar geveegd, maar hier even de belangrijkste, met alle haakjes op de juiste
plaats om verrassingen te voorkomen:
Voor bits in registers etc te manipuleren zijn onderstaande
definities handig; resulteren meestal in slechts één instructie (mits
losgelaten op registers die dit ondersteunen); te gebruiken als bijvoorbeeld
SETBIT(PORTD, 1<<PD3); en if(TSTBIT(GPIOR0, 1)) { ...
}
// some defines to handle I/O ports (and implicit bit flag
variables)
#define SETBIT(x,y) ((x) |= (uint8_t)(y));
#define CLRBIT(x,y) ((x) &= (uint8_t)(~(y)));
#define XORBIT(x,y) ((x) ^= (uint8_t)(y));
#define TSTBIT(x,y) ((x) & (uint8_t)(y))
Om efficiënt met de losse bytes van 16-bit pointers te werken de
volgende definities, waarbij de PTR-variant gebruikt kan worden om een
byte-wide pointer te krijgen uit een 16-bits pointer. Op deze manier hoef je
niet de byte-volgorde zelf te onthouden:
// Use LOW and HIGH for computations, LOWPTR and HIGHPTR for
16-bit mem locations
#define LOW(word) ((uint8_t)(((uint16_t)(word)) &
0xFF))
#define LOWPTR(ptr) ((uint8_t *)(ptr))
#define HIGH(word) ((uint8_t)(((uint16_t)(word)) >> 8))
#define HIGHPTR(ptr) ((uint8_t *)(ptr)+1)
Kan je ook voor variabelen gebruiken: zet low byte op nul met
*LOWPTR(&mijnvar) = 0; en haal low byte op met
LOW(mijnvar) (let op het gebruik van de '& ').
Soms wil je 2 bytes swappen, of twee bytes in de verkeerde volgorde laden in
een 16-bits integer. Concreet voorbeeld: de LM75 I2C temperatuursensor geeft
eerst in een buffer eerst het meest significante byte, en dan pas het minst
significante. Helaas, voor 16-bit integers vrewacht de ATtiny juist het
minst-significante byte op het laagste geheugenadres... Wil je dit echt
efficient doen, dan is er wat getruk nodig; in mijn geval heb ik twee
#define 's gemaakt die direct assembly-code genereren, een voor
ophalen uit geheugen in de verkeerde volgore (FETCHHIGHLOW ), en
een om bytes in een register te swappen (SWAPBYTES )... Meer info
in het avr
inline assembly cookbook. Bijvoorbeeld de code om twee waarden uit geheugen
te lezen:
#define FETCHHIGHLOW(result, highbyte, lowbyte) \
asm ( \
"lds %A0,%2" "\n\t" \
"lds %B0,%1" "\n\t" \
: "=r" (result) \
: "m" (highbyte), "m" (lowbyte)
\
);
Het gebruik hiervan in het geval van mijn I2C routine is als hieronder,
hierbij is i2c_messbuf een uint8_t array waarin data
van de i2c terecht komt, met in [1] het msb en in [2] het lsb byte. De code is
optimaal, namelijk alleen maar twee lds (load vanuit memory)
statements direct in de juiste registers die 'newest '
bevatten:
uint16_t newest;
FETCHHIGHLOW(newest, i2c_messbuf[1], i2c_messbuf[2]);
Bits in GPIO: efficient high level access
Voor efficiente bitmanipulatie-code, in bijvoorbeeld interrupt
service routines, is het handig een bit-addresseerbaar general purpose register
te gebruiken zoals GPIOR0 . Op deze manier kost testen en zetten
van bits slechts een instructie. Maar, hoe doe je dat netjes in C zonder
expliciet bitnummertjes uit te delen en te and'en/or'en? Bijvoorbeeld door een
struct er 'over heen te leggen':
typedef union runstruct // pack some bits indicating the
running state
{
unsigned char init; // for ease of
initialisation/erasure
struct
{
unsigned char halt : 1;
unsigned char fast : 1;
unsigned char down : 1;
unsigned char filler : 5; // some spare bits
};
} RunStruct;
// RunStruct run; // normal
(non-optimal) declaration
#define run (*((RunStruct *)&GPIOR0))
Op deze manier kunnen we GPIOR0 benaderen op een meer
symbolische manier onder de naam run ; bijvoorbeeld op 0
initialiseren met run.init=0; bits zetten met
run.down=1; en bits testen met if(run.down) { ... } .
Toch een stuk handiger (leesbaarder) dan GPIOR0 |= (1<<2);
en zo voort.
I²C master en slave interfaces
Nog enkele tips:
- Vergeet niet aan bijvoorbeeld de master kant pull-up weerstanden (4K7 naar
de +) aan te brengen; zonder deze werkt I2C niet...
- I2C deelt de pinnen met de ISP (in-system-programming) functionaliteit, dus
je programmer cable loskoppelen!
De ATtiny heeft ook hardware support voor I²C, maar deze is vrij
rudimentair: er moet nog vrij veel in software gebeuren. In de tiny zit de
USI (Universal Serial Interface), deze is zeker voor de master
functionaliteit veel simpeler dan de meer volledige TWI (Two-Wire
interface) in uitgebreidere AVR devices als veel ATmegas. In de Atmel
application notes staat er wel vrij veel over beschreven, zie hieronder.
Ik heb zelf zowel master als slave werkend gekregen: voor de slave zie ook
mijn 'Starten met de ATtiny2313 pagina', ben
uitgegaan van de beschreven I²C
eeprom simulatie met wat eigen aanpassingen; voor de master ben ik
uitgegaan van de Atmel application note AVR310, en heb de code omgezet naar
GCC. Ik zal mijn libraries mogelijk nog wel eens publiceren, maar hier al vast
enkele links:
Een andere keer meer...
|