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

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 starters-pagina 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 '&').

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

Top of page Opmerkingen, commentaar, feedback: mail naar .
Nuttige site? Antwoorden gevonden? Zet dan een link naar mijn site op je eigen site, hebben anderen er misschien ook wat aan!