ATTiny10 — blikáme LEDkou

Nedávno jsem si koupil dva kusy mikrokontroléru ATTiny10, jednoho z nejmenších mikrokontrolérů, na které jsem zatím narazil. Za cenu menší jak 1 € za kus obsahuje 4 vstupně/výstupní piny, AD převodník, komparátor, časovač, PWM a vnitřní 8MHz oscilátor.

Sice mu chybí EEPROM a má jenom 1024kB flash paměti, ale pokud použijete jazyk symbolických instrukcí, je jí více než dost. Je zde však jeden zádrhel, který může méně zkušeným znepříjemnit začátky — obvod se nedá programovat skrze klasické STK500 a ISP, ale využívá tzv. TPI protokol. V tomto článku bych chtěl ukázat, jak s tímto prckem začít. Pojďme si zablikat LEDkou…

ATTiny10 patří do skupiny nejmenších mikrokontrolérů, společně s ATTiny4, 5 a 9. Všechny čtyři čipy se liší jen v drobnostech a ceně. V následující tabulce je stručný přehled rozdílů:

Typ Paměť AD převodník
ATTiny4 512 B Ne
ATTiny5 512 B Ano
ATTiny9 1024 B Ne
ATTiny10 1024 B Ano

Všechny jsou v miniaturním pouzdru SOT6 nebo UDFN  a jinak jsou zcela identické. Vše, co bude v následujícím textu řečeno, platí pro všechny typy.

Redukce SOT6 na DIP6

Redukce z SOT6 na DIP6.
Redukce z SOT6 na DIP6.

Než se budeme moci pustit do experimentování, musíme vyřešit první problém. Tyto mikrokontroléry se prodávají pouze v pouzdru SOT6 o rozměru asi 3×1,5 mm. Abychom jej mohli použít v nepájivém poli, budeme muset vytvořit redukci.

Já jsem si navrhl velmi jednoduchou redukci o celkových rozměrech 20×11 mm. Přesně sedí do nepájivého pole, jenom je trochu náročnější na ni čip zapájet. Při výrobě doporučuji vytvořit desku s více kusy. Lépe se s tím při vrtání manipuluje.

Redukce pro ATTiny10 s již osazeným čipem a piny na obou stranách (ta nečistota byla na objektivu fotoaparátu :)).
Redukce pro ATTiny10 s již osazeným čipem a piny na obou stranách (ta nečistota byla na objektivu fotoaparátu :)).

Programování

Blokové schéma programovacího rozhraní TPI. (Zdroj: datasheet.)
Blokové schéma programovacího rozhraní TPI. (Zdroj: datasheet.)

ATTiny10 se programuje pomocí protokolu TPI (Tiny Programming Interface), který vyžaduje tři vodiče — hodiny, data a reset. Hodinový signál generuje programátor a data jsou obousměrná.

S resetem je to maličko složitější, protože pin číslo 6 (PB3), na který je reset vyveden se dá pomocí fuses bitů (pro vypnutí resetu je to 0xFE) přepnout na vstupně/výstupní port. V takovém případě je nutné před zahájením programování na tento pin přivést kladné napětí 12V. Pokud má reset svou původní funkci (fuses jsou 0xFF), stačí reset pin spojit se zemí.

Celý programovací protokol je podrobně popsán v datasheetu a je velmi jednoduchý, takže není problém si napsat vlastní programátor. Já začal TPI implementovat pro svůj USB-UART převodník (takže by to byl USB-TPI převodník), ale pak jsem narazil na projekt, který implementuje TPI pro Arduino. Časem bych chtěl svoji verzi dopsat, ale zatím budu používat toto.

Pravděpodobně existují i komerční programátory (nejspíš přímo od Atmelu) kompatibilní přímo s AVR Studiem. Nevím, nehledal jsem.

Zapojení

Piny TPIDATA, TPICLK a VCC připojíme do Arduina k digitálním pinům 3, 4 a 2. V originálním kódu se reset připojuje přes tranzistor (báze na pinu 5) k napětí 12V. Moje upravená verze počítá s resetem v nule a tak je možné spojit pin číslo 5 přímo k resetu ATTiny10.

Na konci tohoto článku je odkaz na ZIP archiv se všemi potřebnými soubory. Nachází se tam i kód pro Arduino. Ten, který má v názvu HV je původní verze a vyžaduje množství dalších součástek pro měnič napětí 5V na 12V (v případě, že reset používáte jako I/O pin). Verze, která má v názvu LV je mnou upravená, která počítá s tím, že pro reset jej stačí spojit se zemí. Stačí si vybrat podle potřeby.

Na pin 4 (PB2) připojíme LED sériově s rezistorem k napájení. Teď již máme vše připraveno, můžeme začít programovat.

Zdrojový kód

Pro ATTiny10 budeme psát rovnou v jazyku symbolických instrukcí. Těch je 54 a většina z nich se vykoná během jediného hodinového cyklu (výjimkou jsou skoky a větvení). Podrobně jsou popsány v datasheetu na straně 152.

Zde ještě poznámku: pokud budete kód překládat pomocí GNU assembleru, pak je nutné všechny IO registry obalit do makra _SFR_IO_ADDR(JMENO_REGISTRU). Pokud budete překládat jiným asemblerem (např. v AVR studiu), pak si toto makro z kódu vymažte.

#include <avr/io.h>

.section .text
.global main
.org 0x00
main:
    ; Pin PB2 na portu PORTB je výstupní.
    ldi     R16, 0b0100
    out     _SFR_IO_ADDR(DDRB), R16

    ; Nastaví CCP registr, aby jsme mohli měnit
    ; chráněné registry. Viz datasheet str. 12
    ldi     R16, 0xD8
    out     _SFR_IO_ADDR(CCP), R16

    ; Zapne 8MHz interní oscilátor a děličku na 16.
    ; výsledná frekvence je tedy 500 kHz
    ldi     R16, 0x04                   ; 0.5MHz
    out     _SFR_IO_ADDR(CLKPSR), R16

    ; Vypne všechna přerušení
    cli

; Hlavní smyčka programu
loop:
    ldi     R16, 0x0F
    out     _SFR_IO_ADDR(PORTB), R16
    rcall   delay
    ldi     R16, 0x00
    out     _SFR_IO_ADDR(PORTB), R16
    rcall   delay
    rjmp    loop

; Jednoduchá zpožďovací smyčka
delay:
    ldi     R17, 0xFF
dl1:
    ldi     R18, 0xFF
dl2:
    dec     R18
    brne    dl2
    dec     R17
    brne    dl1
    ret
.end

A to je vše. Jednoduché a přímočaré. Teď již stačí program přeložit:

$ avr-gcc -mmcu=attiny10 -o blink.elf blink.S -nostdlib
$ avr-objcopy -j .text -j .data -O ihex blink.elf blink.hex     

Přepínač -nostdlib je důležitý. Pokud se nepoužije, překladač se snaží udělat množství práce za nás a nastavuje vektory přerušení, ošetřuje hlavní smyčku, nastavuje vrchol zásobníku a mnoho dalších, pro nás zbytečných věcí.

Před programováním se ještě můžeme podívat na výsledný kód:

$ avr-objdump -h -S blink.elf | less

Toto je velmi užitečná věc, pokud se dostanete do problémů a nedaří se vám je vyřešit. Výstup z objdump je vpodstatě překladačem okomentovaný strojový kód.

A teď už jenom stačí naprogramovat ATTiny10. Jelikož používáme jako programátor Arduino, které se hlásí jako sériová linka, můžeme soubor .hex jednoduše odeslat. Například pomocí Pythonu:

f = open('blink.hex')
s = serial.Serial('/dev/tty,usbmodem411', 57600)

prog = f.read()
for ch in prog: 
    s.write(ch)
    time.sleep(0.001)

Kód jsem hodně zkrátil, ukázkový skript najdete v ZIP archivu (na konci tohoto textu).

Samotné programování je dost krkolomné, občas se nepovede, občas se zapíše jenom část programu. Na této části stále pracuji. Chtěl bych časem Python kód upravit tak, by po sobě zkontroloval zapsaná data a ošetřil všechny možné stavy. Pokud je mezi čtenáři nějaký zkušený programátor, který má více času jak já, zde se může realizovat. Budu více než rád :)

Závěr

ZIP archiv se zdrojovými kódy a eagle soubory. Vše potřebné najdete tam. Pokud budete mít nápady na vylepšení nebo přípomínky, můj mail znáte.