In voorbereidingATtiny 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.

EnglishEnglish: 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

C compiler? GCC, zie mijn AVR overzichtpagina voor info/links...

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:

  • Lees het document 'AVR035: Efficient C Coding for AVR'; hier staan nuttige tips in, bijvoorbeeld over de meest efficiënte vorm van data structuren.
  • Houd je datastructuren compact; beperk je tot 8 bits indien mogelijk, en pack meerdere bits in bytes: bits kan de AVR toch wel efficient er weer uitkrijgen (mits goed geschreven).
  • Kijk zo nu en dan eens naar de gegenereerde code (als je werkt met AVR Studio: kijk naar de gegenereerde .lss file in de default folder, waar ook de .hex file staat). Soms genereert de compiler 'ongewild' (maar wel conform de C standaard) 16-bit code, terwijl 8-bit code voldoende is, zie bijvoorbeeld de uitleg op de AVR libc pagina's. Soms moet je dit echt forceren, zie bijvoorbeeld:

    // if(var8bit & (uint8_t)0x07) // this generates 16-bit code...
       uint8_t temp;               // trick to force 8-bit arithmetic
       temp = var8bit & 0x07;      // now only 3 instructions
       if(temp)                    // (ld/and/breq) instead of 6!

  • Standaard worden kleine functies ge-'inline'd; oftewel op de plek van gebruik uitgeschreven. Efficiënt in tijd, maar niet altijd in grootte als ze op meerdere plekken gebruikt worden. Speel met compiler-parameters als -finline-limit=3. En zie ook Optimisations of AVR programs using avr-gcc.
  • Het hoofdprogramma eindigt normaal niet, maar loopt eindeloos door. Dan hoeft er ook geen 'context save' aan het begin plaats te vinden (registers bewaren), want er is toch geen einde waar ze weer hersteld moeten worden. Dat kan door de compiler dit te hinten (voeg wel -ffreestanding toe aan de compiler opties om de waarschuwing over de return value te onderdrukken):

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...