Portál AbcLinuxu, 25. dubna 2024 17:08

Jednočipy pod Linuxem - II

23. 12. 2003 | Jan Martínek
Články - Jednočipy pod Linuxem - II  

Jestliže se vám pomocí návodu v předchozím dílu podařilo smazat čip a následně z něj přečíst samé jedničky, je na čase pokusit se vecpat do něj něco aspoň trochu smysluplného.

Programování je obvykle prosyceno zapisováním nějakých záhadných hodnot do nějakých záhadných registrů, které pak způsobují záhadné chování. Těch registrů je několik desítek, v každém registru je spousta bitů, co něco dělají. Je to jediný způsob, jak ovládat periférie čipu (časovače, čítače, řadič přerušení, komparátor, watchdog, sériový port, sleep módy a mnoho dalších). Každý registr se nějak jmenuje a dokonce i každý důležitý bit v něm se nějak jmenuje. Bity se nastavují na jedničky pomocí funkce sbi() - set bit a nulují pomocí funkce cbi() - clear bit. Takže například

sbi(FOO, BAR);

nastaví bit BAR v registru FOO. V dalším textu se zmíním pouze o několika základních, díky kterým lze používat sériový port (UART), nastavovat napětí na nějakém pinu (díky tomu lze rozsvítit připojenou LEDku), číst napěťovou úroveň na nějakém pinu (demonstrační příklad hlídá hladinu vody) a nakonfigurovat časovač s děličem frekvence a povolit přerušení (to se hodí při programování hodin reálného času).

Těch několik málo zde popsaných registrů a bitů v nich vám rozhodně nemůže k další práci stačit, takže vás odkazuji na dokumentaci k čipu, která je zde: DOC0839.PDF

Vězte, že bez této dokumentace není možné udělat téměř nic pořádně, takže si ji vytiskněte a najděte alespoň tři chyby. Těch 92 stran technického popisu dává tušit, že za svých 70 Kč jste si koupili zajímavý kus křemíku.

Programovat jednočipy je samozřejmě nejlepší ve strojovém kódu, assembleru, ale to nechme pro ostřílené elektroniky, kteří jsou nuceni nebo chtějí vymáčknout z dané technologie maximum. Psaní zdrojáku v jazyce C vám, myslím, přinese větší radost, i když je pak kód pomalejší a hlavně rozežranější.

Přejděme rovnou ke konkrétnímu příkladu. Typická ukázka toho, že čip žije, je blikání LEDky, takže zkuste připojit diodku mezi GND a pin č.8 (PD4). Do série s diodou zapojte odpor cca 220 Ohmů. Schéma tedy bude vypadat takto:

Zdroják, který by měl rozblikat LEDku, je zde:

#include <avr/io.h>

void delay(unsigned int ms) {
  unsigned int i,j;
  for ( j=0; j<1000; j++)
    for ( i=0; i<ms; i++);
}

int main (void){
  
  sbi(DDRD,PD4); /*enable port 5 for output*/

  while (1) {
	cbi (PORTD, PD4);
	delay (500);
	sbi (PORTD, PD4);
	delay (500);
  }
  
  return 0;
}

Mezi každým rozsvícením a zhasnutím LEDky se provádí půl miliónu prázdných cyklů, jinak by dioda blikala pekelně rychle. Za zmínku stojí příkaz

sbi(DDRD,PD4);

který nastaví pin PD4 jako výstupní. Bližší informace viz dokumentace strana 56.

Pro kompilaci céčkovského zdrojáku a následné přeprogramování čipu se vám může hodit následující Makefile. Makefile zpracovává céčkovský zdroják s názvem helloworld.c, tak si to kdyžtak změňte.

MCU=at90s2313
CC=avr-gcc
OBJCOPY=avr-objcopy
PROJECT=helloworld
# optimize for size:
CFLAGS=-g -mmcu=$(MCU) -Wall -Wstrict-prototypes 
       -Os -mcall-prologues
#-------------------
all: $(PROJECT).hex
#-------------------
$(PROJECT).hex : $(PROJECT).out 
  $(OBJCOPY) -R .eeprom -O ihex $(PROJECT).out 
  $(PROJECT).hex 
$(PROJECT).out : $(PROJECT).o 
  $(CC) $(CFLAGS) -o $(PROJECT).out -Wl,-Map,
  $(PROJECT).map $(PROJECT).o 
$(PROJECT).o : $(PROJECT).c 
  $(CC) $(CFLAGS) -Os -c $(PROJECT).c
asm : $(PROJECT).c 
  $(CC) $(CFLAGS) -O -S $(PROJECT).c
# you need to erase first before loading the program.
# load (program) the software into the eeprom:
load: $(PROJECT).hex
  uisp -dlpt=/dev/parport0 --erase --upload 
  --verify if=$(PROJECT).hex -dprog=dapa  -v=3 --hash=12
#-------------------
clean:
        rm -f *.o *.map *.out *.hex
#-------------------

Spustíte li make load, zdrojový kód se zkompiluje, binárka se rovnou naprogramuje do jednočipu a tam se ihned spustí.

Blikání diodky je sice hezké na koukání, ale moc informací to nenese. Daleko lepší je komunikace po sériovém portu. Obvod AT90S2313 obsahuje pro tyto účely rozhraní RS232 neboli UART. To sestává ze tří vývodů:

Nejjednodušší způsob vzájemného propojení dvou sériových portů je přivedení vysílacího drátu jednoho zařízení na přijímací pin druhého zařízení a naopak. Jde o zapojení křížem, takže RxD povede do TxD a naopak TxD povede do RxD. Zem zůstane zemí.

Bohužel to u jednočipů není tak jednoduché, protože jejich napěťové úrovně jsou jiné než u klasického sériového portu, který máte v počítači. Jednočipy pracují s úrovněmi 0V a cca 5V (podle napájecího napětí), zatímco standardní sériový port používá úrovně -12V a +12V.

Východiskem z této zdánlivě beznadějné situace je použití obvodu, který se postará o vzájemný převod napěťových úrovní. Pro tento účel lze použít např. notoricky známý MAX 232 CPE, kterých máte jistě doma po kotníky. Zmíněný obvod dokáže nejen napětí zmenšovat na požadovanou úroveň, ale i zvyšovat. Jestliže neznáte princip nábojové pumpy, považujte to klidně za perpetuum mobile v ceně třiceti korun.

A jestliže princip nábojové pumpy znáte, tak vás jistě nepřekvapí chomáč pěti elektrolytických kondenzátorů o kapacitě 1μF, kterým je nutno onen zázračný obvod obalit. Pokuste se tedy vše zapojit podle následujícího schématu:

Nyní je konstelace nakloněna tomu, aby bylo možné sestavit fenomenální projekt "Hello world.", který by měl na obrazovce vykouzlit jakýsi nápis. Jen si teď nemůžu vzpomenout, jaký nápis to má být. Zdrojový kód vypadá následovně:

#include <avr/io.h>

void print (char *string){
  while (*string) {
  loop_until_bit_is_set(USR, UDRE);
  UDR = *string;
  string++;
  }
  return;
}

int main(void) {
  /* UART init */
  sbi(UCR, TXEN);
  sbi(UCR, RXEN);
  UBRR = 59; 
  
  while (1) {
    print ("Hello world.\n"); 
  }
  return 0;
}

Je načase pohlédnout do očí kruté pravdě - při programování jednočipů v Céčku nemáte k dispozici skoro nic z klasické knihovny libc. Sice existuje knihovna avr-libc, ale pokud můžete, vyhýbejte se tomu, protože paměti v čipu je zoufale málo. Takže i v mém příkladě jsem napsal funkci print(), aby byla malá, jednoduchá, škaredá a hloupá. Ve zmíněné funkci je řádek

loop_until_bit_is_set(USR, UDRE);

který zařídí čekání na vyprázdnění vysílacího bufferu, aby bylo možné poslat další byte. Vyprázdnění bufferu se pozná podle bitu UDRE (UART Data Register Empty) v registru USR (UART status register). Datový byte pro vysílání se pak zapíše do registru UDR (UART Data Register).

Jistě jste si všimli, že piny pro sériový port (RxD a TxD) jsou totéž co IO porty PD0 a PD1. Aby jednočip věděl, jakým způsobem má tyto vývody chápat, musí se mu to říct. Po zapnutí čipu jsou piny nakonfigurovány jako IO porty, jenže my chceme místo toho sériové rozhraní. Přepnutí se musí zařídít nastavením bitů TXEN (Transmit Enable) a RXEN (Receive Enable). Oba bity jsou v registru UCR (UART Control Register). Tím jsou vysvětleny ony dva tajemné řádky:

sbi(UCR, TXEN);
sbi(UCR, RXEN);

Zbývá ještě popsat nastavení přenosové rychlosti, baudu, která je v tomto případě definována zapsáním čísla 59 do registru UBRR (UART Baud Rate Register). Proč právě čísla 59? Baud rate se spočítá podle vztahu f/(16*(UBRR+1)), kde f je frekvence krystalu. Doufám, že jste si posledně vzali mou radu k srdci a opravdu koupili krystal o frekvenci f=9.216 MHz. V takovém případě pro vás bude platit následující tabulka:

Baud Rate UBRR
2400239
4800119
960059
1440039
1920029
2880019
3840014
576009
768007
1152004

Tabulka platí pro frekvenci krystalu f=9.216 MHz.

Více informací o sériovém portu je v dokumentaci počínaje stranou 45.

Jak je patrné z tabulky, číslo 59 odpovídá přenosové rychlosti 9600 baudů. Tato přenosová rychlost byla zvolena proto, že je to defaultní nastavení sériového portu, takže na straně Linuxu nemusíte vůbec nic dělat, stačí rovnou číst.

Zkuste tedy program zkompilovat a nahrát do čipu pomocí

make load

A ze sériového portu by nyní měl vyhřeznout nekonečný proud nápisů Hello world. Ze sériového portu můžete číst příkazem

cat /dev/ttyS0

případně

cat /dev/ttyS1

podle toho, na který port je čip připojený. Vřele doporučuji udělat si (jako root) symbolický link na to správné zařízení např. pomocí

ln -s /dev/ttyS0 /dev/jednocip

Dále se již na ten správný sériový port budu odkazovat jako na /dev/jednocip.

Dalším příkladem nechť je ukázka, jak se zachází s piny coby vstupy. Předpokládejme, že chceme sestavit kontrolní systém, který upozorní na to, že se někde vylila voda, přišla povodeň, praskla hadice od pračky nebo něco podobného. Voda sice moc dobrý vodič není (ve srovnání s drátem), ale bude to stačit na to, aby se změnila napěťová úroveň na nějakém pinu.

Zapojení nechme stejné jako u "Hello world", přičemž zdrojový kód by mohl vypadat např. takto:

#include <avr/io.h>

void print (char *string){
  while (*string) {
  loop_until_bit_is_set(USR, UDRE);
  UDR = *string;
  string++;
  }
  return;
}

int main(void) {
  /* UART init */
  sbi(UCR, TXEN);
  sbi(UCR, RXEN);
  UBRR = 59; 
  
  cbi (DDRD, PD5); /*direction - DDR*/
  sbi (PORTD, PD5); /* VALUE */
  
  while (1) {
    loop_until_bit_is_clear(PIND, PD5);    
    print ("Po nas at prijde potopa\n"); 
  }
  return 0;
}

Použijte obvyklé make load, v terminálu pustťe cat /dev/jednocip a zkuste nasliněnými prsty šáhnout na pin č.9 (PD5) a zároveň na zem (mám na mysli GND, nikoli podlahu). Mělo by to vyhodit varování o stoupající hladině. Aby bylo dílo dokonáno, lze využít krásné vlastnosti *nixových systémů a nechat si poslat třeba mail poté, co přijde padesát varování:

head -n 50 /dev/jednocip > /dev/null && echo "Potopa" | mail $USER

Nemohu se nezmínit alespoň stručně o obsluze přerušení a jako příklad nechť je typická aplikace - měření času. Zde je kód na ukázku:

#include <avr/io.h>
#include <avr/sfr_defs.h>
#include <avr/interrupt.h>
#include <avr/signal.h>

void uart_putchar(char c) {
  loop_until_bit_is_set (USR, UDRE);
  UDR = c;
  return;
}

SIGNAL (SIG_OVERFLOW0) {
  static unsigned char count = 1;

  outp (6, TCNT0); /* 9.216e6 / 1024 / 250 = 36, 
            takze dostaneme 36 preruseni
            za vterinu. 256-250=6 */
  count--;

  if (count) return;

  count = 36;

  uart_putchar ('.');
  uart_putchar ('\n');
}

int main(void) {

  /* UART init */
  sbi(UCR, TXEN);
  sbi(UCR, RXEN);
  UBRR = 59; 

  outp (5,TCCR0); /* Clock/1024 */
  sbi (TIMSK, TOIE0); /*povol preruseni od casovace 0*/
  sei();              /*povol preruseni globalne*/

  while (1) {}
  return 0;
}

Tiky krystalu jsou v čipu přivedeny do děliče frekvence (tzv. prescaler), který je pomocí instrukce

outp (5,TCCR0);

nastaven pro dělení 1024. Dělící konstanta pro časovač 0 se nastavuje pomocí registru TCCR0, viz dokumentace na straně 27. Takže na výstupu děliče je nyní frekvence 9.216 MHz / 1024 = 9000 Hz. Tato frekvence je přivedena do osmibitového časovače 0 a ten je nastaven tak, aby při každém přetečení generoval přerušení. To zajistí bit TOIE0 v registru TIMSK. Přerušení se navíc musí povolit globálně funkcí sei(). Čítač čítá vzestupně, takže chceme-li generovat přerušení každých 250 cyklů, je potřeba jej při každém přerušení nastavit na hodnotu 256-250=6 (slovy šest). Těmito magickými čísly, která jsem dlouho vypiplával, se docílí toho, že se přerušení vyvolá 36 krát za sekundu. V obslužné rutině je tudíž potřeba čítat do šestatřiceti, a pak tedy víme, že uplynula sekunda.

Každou sekundu jednočip pošle na sériový port tečku "." a odentruje. Přesnost hodin můžete snadno ověřit pomocí

$ head -n 2 /dev/jednocip > /dev/null && time \
  cat /dev/jednocip | head -n 59 > /dev/null 

real    1m0.000s
user    0m0.000s
sys     0m0.000s

Je to možná trochu krkolomné, ale funguje to. To není podvod - příkaz time opravdu naměřil přesně jednu minutu nachlup, sám se tomu divím.

Na závěr přikládám několik fotografií, jejichž cílem je doložit, že je celkem normální stav, když dílo vypadá jako nepřehledný chumáč drátů. Kliknutím se fotky zvětší na obří velikost.


Obr. 2272x1704 a 1,2 MB

Připravte se na to, že programování v součinnosti s elektronikou s sebou přináší daleko více chyb, než jste dosud poznali.

Přeji mnoho euforických zážitků.

Související články

Jednočipy pod Linuxem - I
cfIDE: Compact Flash přes IDE

Odkazy a zdroje

AVR-Tutorial - Das UART
AVR 8-Bit RISC - Datasheets
+5V-Powered, Multichannel RS-232 Drivers/Receivers
Porty procesorů AVR
AVR Cross Development Kit

Další články z této rubriky

HW novinky: podzimní přehled #2
HW novinky: podzimní přehled #1
HW novinky: návrat skleněných ploten v HDD
HW novinky: PCI Express 4.0 prý ještě letos
HW novinky: i Skylake-X s 12 jádry používá levnou teplovodivou pastu

ISSN 1214-1267, (c) 1999-2007 Stickfish s.r.o.