|
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
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
'&').
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.
Fuse programming vanuit je C source, of met
AVRDUDE
Naast het programmageheugen heeft de tiny2313 nog meer programmeerbare
delen; met name de fuses voor het instellen van de verschillende modes (zoals
welke klok te gebruiken). Die fuses zijn vaak wat raadselachtig (zo is
'programmed' een 0, en 'cleared' een 1), maar gelukkig is er op het net wel wat
uitleg verkrijgbaar; zo is er bijvoorbeeld de fuse calculator, welke de fuse waardes
netjes uitrekent aan de hand van je keuzes. Als je de waardes berekend hebt kan
je met avrdude deze programmeren. Pas op; een verkeerde waarde kan je
chip onbruikbaar maken (zeker als je alleen een seriële programmer
hebt)! Meer info ook op de AVR
Fuses HOWTO Guide (en natuurlijk in de datasheet).
Het is nuttig eerst eens de huidige waardes uit te lezen, met het
commando:
avrdude -p t2313 -c siprog -P com1 -U lfuse:r:-:h -U
hfuse:r:-:h -U efuse:r:-:h
Dit levert bij mij de waarden op (even zoeken in de output): low fuse
0x64, high fuse 0xdf, extended fuse
0xff. Dit zijn de standaard fabriekswaarden (interne RC
oscillator, etc). Ik wil de processor op het kristal laten lopen (extern
kristal, 4 MHz, geen deling door 8); dit levert een low fuse waarde op
van 0xdf. De high fuse en extended fuse kunnen ongewijzigd
blijven! Commando:
avrdude -p t2313 -c siprog -P com1 -U lfuse:w:0xfd:m
En ja, programmer uitgebreid met een kristal en 2 22 pF condensatoren, en
mijn ledje knippert nu 4x sneller (in plaats van de 1 MHz interne RC-klok, de 4
MHz kristalwaarde). Zoals gezegd: een verkeerde fuse waarde kan je chip
onbruikbaar maken; altijd dubbel-checken!!!
Fuses in je C programma
Je kunt ook de waarde van fuses in je programma (en daarmee direct in je
binary files) opnemen, zodat deze informatie in de bijhorende source en
.elf file staat en niet verloren gaat als je je kladblaadje per
ongeluk weg gooit, en ze bovendien automatisch op de goede manier
meegeprogrammeerd worden. Dit gaat (op een wat primitieve manier, de fuse calculator blijft nodig) met
behulp van de avr/fuses.h include file en een door jezelf in je C
file op te geven configuratie, bijvoorbeeld:
#include <avr/io.h>
#include <avr/fuse.h>
FUSES = // specified bits will be zeroed...
{ // select 128 kHz internal
oscilator with slow start
.low = (FUSE_CKSEL0 & FUSE_CKSEL3 &
FUSE_SUT0),
.high = HFUSE_DEFAULT,
.extended = EFUSE_DEFAULT,
};
Je kunt de waarde van de fuses uitlezen uit de .elf file met
behulp van het commando avr-objdump -s -j .fuse
<filenaam>.elf. Helaas, de waardes worden nog niet
automatisch door AVRDUDE geprogrammeerd (staan niet in de
.hex file), dit moet je nog steeds 'handmatig' doen (zie
hierboven)...
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...
|