Šestá část pokračování stránek o základech programování. Další část Vás seznámí s komunikací čipu s okolím pomocí sériové linky.

     Sériová linka - jinak UART nebo USART (to nejsou sprostá slova, ale označení Univerzal Asynchronous Receiver Transmitter) slouží pro označení periferie jednočipového mikroprocesoru, pomocí které je možno komunikovat s jiným procesorem, a nebo po převodu napěťových úrovní s počítačem PC.
Nevím proč, ale tato oblast pro mnoho začátečníků působí jako něco nadpřirozeného a velmi složitého (soudím podle mnoha dotazů na toto téma) - ač je práce s touto periferií velmi jednoduchá.

     Tuto stránku bych spíše pojal jako několik příkladů, než vysvětlování co, proč a jak funguje. Takový popis naleznete na dvou jiných stránkách tohoto webu. (Sériový kanál a Sériový kanál-programy). K tomu, abychom mohli komunikovat s PC (což pro začátek doporučuji, abyste videli hned výsledky svého snažení) je třeba vlastnit převodník úrovní. Klasický sériový port má totiž definované napěťové úrovně pro log. 1 zápornou úrovní (-5 až -15V), zatímco logická 0 je přenášena kladnou úrovní (+5 až 15V) výstupních vodičů. Tyto úrovně je třeba pro zpracování jednočipovým mikroprocesorem převést na TTL úrovně (0 až 5V). Jak si takový převodník zkonstruovat se dočtete na jiné stránce tohoto webu. Rozhodně ale takové zařízení doporučuji, je to základ pro práci se seriovou linkou.

Nastavení Terminalu pro naše příklady       Dál budeme potřebovat nějaký vhodný software do PC pro příjem a vysílání znaků. Osobně používám nejraději stařičký TELIX, ale je možné použít například Hyperterminál přímo ve Windows - není to nic světoborného, ale je to použitelné. Mimo jiné uvádím na konci stránky další program pro komunikaci pomocí sériové linky ke stažení. Nastavení programu pro zde uváděné příklady je vidět na obrázku vlevo. Je třeba nastavit použitou Baudovou rychlost (Baud - zkratka Bd je počet bitů za sekundu - zde 19200), nastavit použitý port (já zde mám COM4), no a parametry přenosu - tedy 8 datových bitů, bez parity a jeden STOP bit.
     Pak ještě musíme nastavit "handshaking" na NONE (přesný překlad je "potřásání rukou" - jedná se o druh komunikace, kdy nadřízený systém vyšle dotaz a čeká na odpověď - u jednoduché komunikace to nepoužíváme. Jde o pojem řízení toku, což je možno najít i ve správci zařízení ve Windows - toto řízení toku je třeba vypnout - tedy zde to vypneme v nabídce a program to zabezpečí za nás, pokud pouzžijeme jiný software je třeba na to pamatovat. Pokud totiž necháme povolenou volbu např. řízení toku hardwarem, může dojít k tomu, že PC čeká na nahození linky DTR, RTS - tyto linky se používají právě k řízení "plnokrevné" komunikace na sériovém portu - je to použito například u modemu. No a pokud toto zůstane nastaveno, a my to nepoužijeme, nemusí nám komunikace fungovat vůbec, prostě proto, že PC čeká na změnu na těchto signálech, které ale my nevyužíváme. Jako poslední věc je třeba ručně zapnout právě signály DTR a RTS trvale - jsou totiž použity pro napájení kompaktního převodníku -je napájen díky nízké spotřebě z těchto signálů a pokud zůstanou vypnuté, nebude převodník napájen.

      Nyní se již můžeme "po hlavě" vrhnout do prvního pokusu. Nejprve je vhodné si např. na nepájivé pole zapojit klasické zapojení procesoru (krystal - POZOR ZMĚNA 11,059200 MHz, reset - kondenzátor, napájení atd.) například 89C2051 (když jsme již začli s těmi ATMELy). Dál propojíme přijímací a vysílací linky. Je vhodné si nejprve vyzkoušet komunikaci směrem od / do PC. To se nejlépe vyzkouší tak, že spustíme terminál v PC, a na straně TTL (kterou posléze připojíme do mikroprocesoru) spojíme přijímací a vysílací vodič. Po stisku klávesy (resp. odeslání znaku) se musí ten samý znak objevit v přijímacím okně terminálu. Pokud tomu tak není, je třeba zkontrolovat převodník, popřípadě je možné opět zkusit propojení TxD (vysílací - transmitt) a RxD (přijímaci - receive) linky přímo v PC.
     Jestliže je vše v pořádku, připojíme si linky k procesoru. Nezapomeňte, že se vždy připojuje vysílací linka na přijímací a obráceně přijímací na vysílací. Leckomu to přijde logické , nespojovat proti sobě dvě vysílací linky, ale již jsem "opravoval" na dálku několik zaručeně zkontrolovaných konstrukcí, které měli jen prohozené vysílací a přijímací linky. Tak....hotovo ? Budeme pokračovat trochu obráceně. Nejprve si program vyzkoušíme a pak si k tomu něco řekneme.
$MOD51
  ORG        0000H
  JMP        Init
  ORG        0030H 
;============================================================
; hlavní program
;============================================================
Init:
  	  mov sp,#35H
          mov scon,#01010000B   ;nastavení ser.linky+časovače1
          mov tmod,#00100000B   ;19200 bps,8 databit,1 stopbit,bez parity
          mov 87H,#10000000B    ;zdvojnásobení rychlosti (nastaví bit SMOD)
          mov th1,#253          ;časovač mód 2,ser.linka mód 1
	  setb TR1


	mov	R0,#'T'
	call	vysilani
	mov	R0,#'e'
	call	vysilani
	mov	R0,#'s'
	call	vysilani
	mov	R0,#'t'
	call	vysilani
	mov	R0,#' '
	call	vysilani
	mov	R0,#'R'
	call	vysilani
	mov	R0,#'S'
	call	vysilani
	mov	R0,#'2'
	call	vysilani
	mov	R0,#'3'
	call	vysilani
	mov	R0,#'2'
	call	vysilani

	jmp	$

vysilani:  
          clr ti                  ;nulování příznaku dokončeného odvysílání
          mov sbuf,r0             ;vysílání obsahu R0
          jnb ti,$                ;čekání na odvysílání
          clr ti                  ;nulování příznaku dokončeného odvysílání
          ret
  END
       Po naprogramování čipu zkontrolujte zapojení a propojte sériovou linku (stačí RxD převodníku resp. PC a TxD pin procesoru). V PC spusťte terminál a nastavte parametry programu - komunikační rychlost 19200 bitů za sekundu (19200 kBd), 8 datových bitů, bez parity, 1 stop bit. Nyní připojte napájení procesoru. Pokud máte vše správně zapojeno, musíte na obrazovce PC číst text: Test RS232. Pro opakované vypsání je třeba resetovat procesor. Nyní se vrátím k popisu programu.
     Vlastní vysílání je velmi jednoduché. Nejprve se nastaví parametry linky - je třeba nastavit mód linky - zde mód 1 (více o módech na jiné stránce tohoto webu) - v registru SCON. Pro časování sériové linky je použit TIMER1. Dále povolíme příjem (nastavení bitu REN v SCON) - zde není příjem sice použit, ale to nám nevadí.
Následně nastavíme časovač 1. V byte TMOD je nakonfigurován jako 8 bitový čítač s automatickou předvolbou - MÓD2 (více o této volbě se dočtete na jiné stránce tohoto webu). To znamená, že časovač běží, maximalní dosažitelná hodnota je 8-mi bitová (256) - pro čítání je použit dolní byte timeru - TL1. Po přetečení a vyvolání přerušení je v tomto módu automaticky do časovače uložena předvolba z vyššího byte časovače - TH1. Byte TH1 je tedy využit jen a pouze pro uchovávání předvolby časovače (pevně doufám, že víte co to znamená, pokud se řekne předvolba časovače - ne ? ajajaj, pak si to tedy nastudujte třeba zde). Ještě jedna věc ohledně časovače - při tomto použití časovače se nepovoluje generování přerušení od tohoto časovače ! Časování UARTu je interně ovládáno. Vždy se tedy jen nastaví parametry časovače, a časovač se spustí.
     Jako předposlední věc nastavíme bit SMOD v registru PCON (na adrese 87H) pro zdvojnásobení komunikační rychlosti. Jako poslední věc nastavíme předvolbu časovače 1 na 253 decimálně (0FDH) - proč zrovna tuto hodnotu se dozvíte na stránce popisu UARTu. No a spustíme časovač nastavením bitu TR1. To je vše, a více se o časovač starat nebudeme.
     Dále si napíšeme rutinu odeslání jednoho byte přes seriovou linku. Pojmenujeme si ji jako vysílání. Před napsáním rutinky si osvěžíme, co víme o registru SBUF a příznaku TI. Zápisem hodnoty do SBUF započne odesílání znaku sériovou linkou. Pokud není nastaven příznak TI v registru SCON, není vysílání ukončeno.
Takže ve vlastní rutině nejprve před vlastním odesláním pro jistotu vynulujeme příznk TI. Následně přesuneme hodnotu z R0 do vysílacího registru SBUF (hodnotu do R0 jsme si uložili v hlavní smyčce programu). Tím započne vysílání. Dále testujeme příznak odeslání znaku TI. Dokud není nastaven, čekáme (skok jmp $ znamená skočit sám na sebe, řádek se tedy opakuje). Jakmile je příznak nastaven, pokračujeme dál - víme, že znak byl odeslán a výstupní buffer procesoru je vyprázdněn. Příznak TI vynulujeme a následuje návrat z rutiny instrukcí RET. A to je vše.

A jak to tedy vlastně pracuje ?
     Instrukcí MOV R0,#'T' načteme hodnotu do registru R0. Jakou ? Teda co to je za zmatek to #'T' ? Znak # již známe, znamená to, že budeme zapisovat číselnou hodnotu. A protože programátoři, pokud jde o psaní na klávesnici si to zjednoduší, kde jen mohou, je použita konstrukce 'T', která během překladu udělá to, že za sebe dosadí ASCII hodnotu znaku T (velké T). Je to tedy úspora v tom, že nemusíme hledat ASCII tabulku, a zadávat hodnotu 54H, což je totéž. Překladač prostě "ví", že pokud narazí na hodnotu, číslo, znak uzavřené v apostrofech, má za něj dosadit jeho ASCII hodnotu. Na to pozor, pokud chcete odeslat například číslo 1 (prostě pro začátek tak, aby Vám terminál v PC vypsal jedničku), pak musíte zaslat ne 1, ale její ASCII reprezentaci - tedy podle ASCII tabulky hodnotu 31. No a to buď uděláme (složitěji) tak, že si hodnotu v tabulce pracně najdeme, a nebo jednodušeji tak, že zapíšeme '1'. Pokud se podíváte do listingu překladu (*.lst) na levou stranu, kde jsou vlastní již přeložené instrukce uvidíte, že je ASCII reprezentace znaku opravdu součástí instrukce:
0041 7854  28    mov     R0,#'T' 
0046 7865  30    mov     R0,#'e'
T má ASCII reprezetaci 54 - což je v prvním řádku za kódem instrukce 78, no a znak malé e má ASCII reprezentaci 65, což opět vidíme v druhé řádce zase za kódem instrukce.

      Základní věcí pro komunikaci je to, že si musíme předem naplánovat, jak budeme komunikovat - jakou komunikační rychlostí, s paritou nebo bez, kolik datových bitů atd. Nebudu to zde vše rozebírat, neboť to zatím není potřeba a většinou si myslím, že pro první "nakopnutí" stačí funkční příklad. Pokud budete později potřebovat nějaké parametry komunikace měnit, neměl by to již být nějaký velký problém.

     Tak, a abych se vrátil k programu, vždy tedy načteme znak do registru R0, a zavoláme rutiny vysilani. V té přesuneme znak z R0 do vysílacího bufferu (odešleme), počkáme na nastavení příznaku, že byl znak odeslán a vrátíme se zpět. Načteme další znak a pokračujeme opět jeho odesláním. Po odeslání všech znaků zůstane hlavní program stát na instrukci jmp $ (instrukce skáče sama na sebe). Pro příklad vysílání je to tedy celé. Samozřejmě pokud chcete jinou komunikační rychlost, popřípadě parametry přenosu, je třeba patřičně změnit parametry časovače atd. V této rutině není zatím využito přerušení. Tím bychom měli část o vysílání řekl bych vysvětlenou.
Příjem znaků
     Nyní si popíšeme, jak je to s příjmem znaků. Jsou dvě možnosti, s přerušením nebo bez. Rutinu bez přerušení můžeme použit tehdy, pokud například odešleme nějaký dotaz a čekáme na něj odpověď. Prostě když předem víme, že má "něco" přijít. Pokud má program normálně běžet, a zpracovávat znaky, přicházející sériovou linkou průběžně a v podstatě kdykoliv, je nutno zpracovávat obsluhu v přerušení - to znamená přijde znak linkou, je vyvoláno přerušení a my si přijatý znak zpracujeme. Nejprve si popíšeme jednodušší příjem, a tím je příjem bez využití přerušení.

$MOD51
  ORG        0000H
  JMP        Init
  ORG        0030H 
;============================================================
; hlavní program
;============================================================
Init:
  	  mov sp,#35H
          mov scon,#01010000B   ;nastavení ser.linky+časovače1
          mov tmod,#00100000B   ;19200 bps,8 databit,1 stopbit,bez parity
          mov 87H,#10000000B    ;zdvojnásobení rychlosti (nastaví bit SMOD)
          mov th1,#253          ;časovač mód 2,ser.linka mód 1
	  setb TR1



main:
	jnb 	ri,$		;cekej na znak z linky	
	mov	a,sbuf		;nacti hodnotu z UARTU do akumulatoru
	clr	ri		;nuluj flag
;-----------------------------------------------------------
	mov	R0,#'('
	call	vysilani
	mov	R0,ACC		;prijatou hodnotu uloz do R0
	call	vysilani	;a odesli
	mov	R0,#')'
	call	vysilani
	jmp	main	

vysilani:  
          clr ti                  ;nulování příznaku dokončeného odvysílání
          mov sbuf,r0             ;vysílání obsahu R0
          jnb ti,$                ;čekání na odvysílání
          clr ti                  ;nulování příznaku dokončeného odvysílání
          ret
  END
       Takže nastavení je totožné jako z minulého příkladu. Ohledně hardware musíme nyní již propojit obě komunikační linky (RxD čipu TxD PC a TxD čipu na RxD PC - samozřejmě přes převodník úrovni).
Na začátku se neustále testuje příznak RI, který svým nastavení signalizuje dokončení příjmu znaku přes sériovou linku. Po nastavení RI je možno přečíst znak z bufferu SBUF. Takže jakmile je příznak nastaven, víme, že přišel znak. Znak z bufferu SBUF přesuneme do akumulátoru a následně vynulujeme příznak RI (příznaky RI a TI se musí nulovat softwarově na rozdíl od ostatních příznaků). Následně vyšleme zpět do terminálu v PC závorku, dále odešleme přijatý znak, který máme v akumulátoru a nakonec odešleme uzavírací závorku. Po jejím odeslání se vrátíme zpět na začátek (jmp main) a čekáme na další příchozí znak. Výsledkem tohoto krátkého prográmku je to, že vrátí do terminálu v PC znak, který jsme odeslali uzavřený v závorkách např. odešleme písmeno A, vrátí se (A). Na tomto asi není dále co vysvětlovat, zkuste si prográmkem pohrát, zkusit například odeslat přijatý znak mezi hvězdičkami a podobně. Případně si můžete zkusit napsat rutinu tak, aby přijímala dva znaky a po příjmu druhého odeslal zpět oba znaky do PC.

Příjem znaku v přerušení
$MOD51
  ORG        0000H
  JMP        Init
;--------------------------------------------------
	ORG 23H
	jmp	prerus_uart
;--------------------------------------------------

;--------------------------------------------------
LED	equ	P1.2		;vystup LED
;--------------------------------------------------

  ORG        0030H 
;============================================================
; hlavní program
;============================================================
Init:
  	  mov sp,#35H
          mov scon,#01010000B   ;nastavení ser.linky+časovače1
          mov tmod,#00100000B   ;19200 bps,8 databit,1 stopbit,bez parity
          mov 87H,#10000000B    ;zdvojnásobení rychlosti (nastaví bit SMOD)
          mov th1,#253          ;časovač mód 2,ser.linka mód 1
	  setb TR1
	  setb	ES		;povol int od uartu ale ne globalne
	  setb	EA		;povol preruseni globalne	
;----------------------------------------------------------------

;=============================Hlavni smycka=================================
main:	setb	LED

zacatek:cpl	LED
	mov R3,#167
cas_1: 	mov R2,#171
cas_2:	mov R1,#16
cas_3: 	djnz R1,cas_3
   	djnz R2,cas_2
   	djnz R3,cas_1
   	sjmp	zacatek
;============================================================================
vysilani:  
          clr ti                  ;nulování příznaku dokončeného odvysílání
          mov sbuf,r0             ;vysílání obsahu R0
          jnb ti,$                ;čekání na odvysílání
          clr ti                  ;nulování příznaku dokončeného odvysílání
          ret
;------------------------------------------------------------ 
;*************************Obsluha preruseni*************************************
prerus_uart:
	push	ACC		;uloz hodnotu v akumulatoru 
	push	PSW		;uloz stavove slovo
	jnb 	ri,navrat	;preruseni od prijmu ? (muze byt vyvolano i vysilanim)	
	mov	a,sbuf		;nacti hodnotu z UARTU do akumulatoru
	clr	ri		;nuluj flag
;------------------------------------------------------------
	mov	R0,#'('
	call	vysilani
	mov	R0,ACC		;prijatou hodnotu uloz do R0
	call	vysilani	;a odesli
	mov	R0,#')'
	call	vysilani
navrat:
	pop	PSW
	pop	ACC
	reti
;------------------------------------------------------------

 END
        Příjem znaku v přerušení je poněkud složitější, ale v podstatě použijeme znalosti z minulého příkladu. Příklad bude opět vracet znaky uzavřené v závorce. Navíc, aby bylo dostatečně ilustrované to, že procesor stále "dělá" něco jiného v hlavní smyčce, a při příjmu znaku "si odskočí" udělat obsluhu přerušení je v hlavní smyčce rutinka blikání LED diodou na portu P1.2 (vývod 14 procesoru).
Funkce programu je následující. Na začátku je povoleno přerušení od sériové linky a globální přerušení (tím se povolují/zakazují všechna přerušení). V hlavní smyčce programu je tedy rutinka blikání LED diodou pomocí časové smyčky. Nyní, jestliže přijde po sériové lince znak (data) je nastaven příznak RI a vyvoláno přerušení. Program odskočí z vykonávání blikání na adresu 23H. To je tzv. vektor přerušení sériové linky. Na tuto adresu program vždy skočí, pokud je vyvoláno přerušení od sériové linky. Tato adresa je pevně definovaná výrobcem (více na jiné stránce tohoto webu), tak jako adresy (vektory) od jiných zdrojů přerušení (časovačů, externích vstupů přerušení atd.). Protože vektory jsou relativně blízko u sebe (mám na mysli adresy) vzhledem k programu se na tyto adresy umisťují skoky na obslužné rutiny toho kterého přerušení. My si tedy na adresu vektoru od sériové linky umístíme instrukci skoku na rutinu prerus_uart. Program po příjmu znaku tedy skočí na adresu 23H, následuje skok na rutinu prerus_uart.
     Na počátku této rutiny se nejprve uschová obsah akumulátoru a stavové slovo (tohle již známe z kapitoly o přerušení). Dále je nutno zjistit, odkud přerušení vzešlo. Jde o to, že přerušení od sériové linky může být vyvoláno jak odesláním, tak příjmem znaku a tyto dvě věci si musí programátor ošetřit sám. Takže otestujeme příznak příjmu RI. Pokud není nastaven, přišlo přerušení od jiné vnitřní periferie, a následuje návrat z obsluhy přerušení bez činnosti.
Jestliže je příznak RI nastaven, signalizuje nám to příjem znaku - resp. to že přijatý znak je v přijímacím bufferu. My nejprve přesuneme hodnotu z registru SBUF do akumulátoru a následně smažeme programově příznak RI (clr RI). Tento příznak jako jediný (kromě TI) se musí nulovat programově (nenuluje jej hardware procesoru při obsluze přerušení tak při obsluze ostatních periferií jádra).

     Pokračujeme tím, že odešleme znak závorky, tak, jak již to známe, pak odešleme přijatý znak a dál uzavírací závorku. Následuje obnovení stavového slova a obsahu akumulátoru hodnotami, které v nich byli před vstupem do přerušení. A instrukcí reti se vrátíme z obsluhy přerušení do vykonávání hlavní smyčky programu do místa, ze kterého bylo vyvoláno přerušení (skok do přerušení se vykonává vždy až po dokončení rozpracované instrukce). Pro zjednodušení, program tedy stále vykonává hlavní smyčku, při příjmu znaku vykoná rutinu prerus_uart v rámci které vykoná rutinu vysilani.

     Tímto okamžikem by měla být problematika komunikace jasná. Na závěr přidám ještě jednu úpravu poslední rutiny příjmu v přerušení. Jde o ovládání blikání - resp. průběhu časování u blikání LED diody. Jestliže odešleme do procesoru znak v (malé písmeno v) dojde k zastavení blikání - tedy něco jako funkce HOLD - pokud LED zrovna svítí, zůstane trvale svítit, pokud je zhasnutá, zůstane zhasnutá. A to vše až do doby, než odešleme do procesoru znak z (malé z). Jak je to udělané ? V rychlosti si řekneme pouze změny oproti rutině příjmu znaku v přerušení.

;--------------------------------------------------
LED	equ	P1.2		;vystup LED
zap	BIT	0
;--------------------------------------------------


zacatek:jnb	zap,zacatek
	cpl	LED
	mov R3,#167
cas_1: 	mov R2,#171
cas_2:	mov R1,#16
cas_3: 	djnz R1,cas_3
   	djnz R2,cas_2
   	djnz R3,cas_1
   	sjmp	zacatek
;--------------------------------------------------
	
;--v preruseni-----
	mov	R0,#'('
	call	vysilani
	mov	R0,ACC		;prijatou hodnotu uloz do R0
	call	vysilani	;a odesli
	mov	R0,#')'
	call	vysilani
	cjne	A,#'z',druhy_test
	setb	zap
	jmp	navrat

druhy_test:
	cjne	A,#'v',navrat
	clr	zap

       V hlavičce si nadefinujeme příznak - jeden bit v bitově adresovatelné části paměti RAM. Tento bit můžeme nahodit do H, shodit do L, popřípadě jeho stav otestovat.
V hlavní smyčce programu tento příznak testujeme a pokud není nastaven, blikání se nekoná (jnb zap,zacatek = jump not bit = pokud není nastaven bit zap, skoc na návěští zacatek) -prostě pokud je bit vynulován, smyčka stojí na jednom místě. Jestliže v přerušení bit nastavíme do log. H, pak po návratu smyčka normálně pokračuje dál.
     Nastavování a nulování příznaku je v rutině přerušení provedeno po odeslání odpovědi v závorkách do PC. Přijatý znak máme uložený v akumulátoru, provedeme tedy test CJNE - nejprve pokud se přijatý znak nerovná z skoč na návěští druhy_test. Pokud se znak rovná z, instrukcí setb zap se příznak nastaví a následuje návrat z přerušení. Jestliže přijatý znak nebyl z, pak se testem za návěštím druhy_test otestuje, zda je přijatý znak v. Pokud ano, pak se instrukcí clr zap příznak vynuluje, a následuje návrat z přerušení. Pokud znak nebyl ani z, ani v, je proveden návrat z přerušení beze změny příznaku. Kompletní kód rutiny naleznete v archivu se zdrojovými kódy na konci stránky ke stažení.

Ke stažení jsou tyto soubory:


Velikost 30 kByte  Příklady - ASM i HEX soubor a schémata
Velikost 243 kByte  Terminal - program pro komunikaci přes sériovou linku


Dnešní kapitola byla poslední. Nyní umíte oživit procesor na bázi 8051, a měli byste ovládat alespoň úplné základy. Pokud ano, pak se splnilo to, co si tento miniseriál kladl za cíl.


Vytisknout stránku

Zpátky Zpátky
© DH servis 2002 -