Jste zde

Von Neumannovský procesor na Hardwardské architektuře?

Odpověď ano na tuto zjevně nesmyslnou otázku může samozřejmě existovat pouze v rovině vhodně

vybrané metody konstrukce programu, a to s ohledem na způsob "obelhání" omezení daných architekturou

procesoru.

Architektura procesorů Hardwardského typu (s oddělením adresného prostoru kódu programu a adresného prostoru dat) má své nesporné přednosti v úspornosti výsledného kódu, ale má i své nevýhody - je obtížné v aplikaci vytvořit úsporný (z pohledu velikosti kódu a rychlosti vykonání) adaptibilní, neinterpretační program stavového automatu. Kde pod pojmem adaptibilní rozumíme: program přizpůsobitelný vstupním podmínkám. Pod pojmem neinterpretační rozumíme: program nevyžadující při každém spuštění nové - mnohdy zbytečně opakované - analýzy vstupních podmínek.

Shrnuto, koncepce procesoru s odděleným adresným prostorem pro program, je optimalizována (i povahou své instrukční sady, i faktem že program je v pevné paměti: ROM, flash, EPROM apod.) na interpretační konstrukci aplikačního SW, který je pak snadněji (a přehledněji) konstruován,co však je vykoupeno sníženou rychlostí SW řešení - obdobně jako použití interpretačního jazyka.

Von Neumannovský procesor nemá oddělený adresní prostor pro kód a data - jeho struktura je proto univerzálnější, ale nepoužívá se běžně pro jednočipové mikropočítače.

Ale buďme konkrétnější. Potřebujeme na klasickém klonu procesoru rodiny 80C51 (nevybaveném blokem přídavného časovače PCA) vytvořit podprogram pro generování signálu s nastavitelným výstupem typu PWM a nastavitelné hloubce PWM minimálně 6 bitů (64 úrovni) a maximálně 8-bitů (256 úrovni). Vybraný mikropočítač je průmyslovým, levným standardem a je typickým představitelem procesorů Hardwardského typu. Požadovaná činnost vyžaduje adaptaci obsluhy pouze při zadání nové vstupní podmínky a dále je pouze opakovaně prováděná.

Poznámka: popis realizace jednoho výstupu bude spíše jen ilustrativní, za účelem vysvětlení metody, poskytne nám však východisko pro realizaci programu pro dvojici nebo čtveřici souběžných výstupu PWM.

Zvolený mikropočítač je standardně vybaven přerušením od časovače, co s výhodou využijeme. Mějme však na paměti, že obsluha přerušení znamená spotřebu času procesoru navíc oproti vyžadované činnosti, a to na skok do rutiny obsluhy přerušení a návrat z ní zpět do hlavního cyklu. Aplikace musí být samozřejmě efektivní - je nezbytné aby vyžadovala obsluhu pouze tehdy, když je to nevyhnutné (když dochází ke změně na výstupu) a proto záměrně vynechejme z další diskuse banální řešení spočívající ve vygenerovaní kompletní tabulky výstupů portu pro všechny časové okamžiky periody. Důvody pro toto rozhodnutí jsou:

  1. velká a neefektivní zátěž procesoru, obsluha přerušení od čítače i tehdy když se nemění výstup (pro PWM o hloubce 6-bitů - 64 přerušení na periodu (pro 8-bitů - 256 přerušení na periodu) z čeho pouze 2 jsou doprovázené změnou na výstupu).
  2. velké nároky na obsazení paměti dat a její neefektivní použití.
  3. velká hodnota minimálního času mezi jednotlivými kroky tabulky (vnitřní časové rozlišení v rámci periody PWM). Kde vnitřním časovým rozlišením v rámci periody PWM myslíme takt PWM - nejmenší časový rozdíl pomezí sousedními hodnotami vstupního údaje.

Platí perioda_pwm = takt_pwm * 2exp(hloubka_pwm) čili pro:

takt_pwm = 1µs ; hloubka_pwm = 6; -> perioda_pwm = 64 µs (15,6 kHz)
resp. takt_pwm = 1µs ; hloubka_pwm = 8; -> perioda_pwm = 256 µs (3,9 kHz)

Výše uvedené hodnoty jsou srovnatelné s parametry hardwarově generovaného signálu PWM (viz. blok PCA) a budeme se snažit jim maximálně přiblížit nebo v nejlepším případě dodržet.

Při SW řešení zadané úlohy musíme si uvědomit, že takt_pwm je vlastně minimálním rozdílem cyklů procesoru mezi instrukcemi měnícími jeho výstupy (mov Px,#data; resp. setb(clr) Px.y apod.). Při hledání řešení musíme vycházet z nejhorší možnosti - nastavení pwm o hodnotě 1 (resp. max-1). Pro použitý krystal 24 MHz je délka cyklu procesoru 0,5 µs. Pak naše zadání splňuje pouze vykonání dvojice instrukcí setb Px.y a clr Px.y, resp. dvojice mov Px,#data1; mov Px,#data2 bezprostředně za sebou.

Procesor musí tedy mít možnost přímo, bez ztráty času na interpretaci zadání, vykonávat instrukce měnící výstup, co ale vzhledem na variabilitu vstupního nastavení je možné pouze tehdy, když si sám vygeneruje exekuční kód. A tady narazíme na bariéru uvedenou v úvodě - jak donutit procesor s Hardwardskou architekturou pracovat podobně jako von Neumannovský procesor, kde toto omezení neexistuje? Navrhované řešení je samozřejmě nepřímé, s oklikou přes zásobník, který má však všechny námi požadované vlastnosti ideálního prostředníka:

  • je umístěn ve vnitřní RAM paměti procesoru
  • může být plněn instrukcemi procesoru (jako daty)
  • jeho hlavní původní funkci je umožnit SW skoky
  • exekuční čas instrukcí se zásobníkem je krátký

Zbývající práci obstará nevelký počet optimalizovaných, krátkých rutin v paměti programu. V zásobníku tedy umístníme cílové adresy exekučních rutin a data.

Poznámka:

  • programové skoky jsou nahrazený exekucí instrukce - "ret" (cílová adresa je v zásobníku)
  • převzetí dat je pomocí instrukce - "pop #direct".

Popisovaná aplikace ( po svém vygenerování) je samostatným procesem, v úvodu požadovaným adaptibilním, sekvenčním automatem, který je oživován přerušením od čítače pouze v časových bodech, kdy má dojít ke změně na výstupu. Proto spotřeba strojového času procesoru je skutečně minimální.

Poznámka: samostatný proces vyžaduje:

  • SW prostředky pro synchronní přepnutí na nové vstupní podmínky
  • svůj exkluzivní krátký zásobník, ve kterém jsou adresy a data pro celou periodu procesu. Podmínku synchronního přepnutí řešíme zdvojením exkluzivního zásobníku.

Velmi důležitá je volba režimu přerušení čítače, která má přímou souvislost s přesností určení časových okamžiků změny na výstupu (volání procesu automatu). Zde musíme zvolit režim "10", tj. automatický "8bit-autoreload" předpřipravené nové hodnoty časového úseku (v THx) do TLx ihned po dosažení přetečení čítače.

Poznámka:

  • režim činnosti čítače "8bit-autoreload" omezuje praktickou rozlišitelnost PWM na max 8-bitů (256 úrovni)
  • hodnota času do THx musí být předpřipravená pro další přerušení
  • přerušení od pracovního čítače musí mít nejvyšší prioritu

Samostatný proces naší aplikace po ukončení své periody se znovu sám inicializuje - co znamená, že si obnoví adresu v zásobníku a nastaví počítadlo přerušení (obě tyto veličiny jsou mu předány daty v paměti).Touto cestou je zabezpečená také synchronizace - přepnutí na nové požadavky podle vstupních dat bez rušivých jevů na výstupu pwm. Viz výpis rutiny přerušení od čítače.

Začátek rutiny:

;*************************
cseg at 001BH              	;service of interupt CT1
xch a,stack_point
xch a,SP
ret				;jmp to service
Ukončení rutiny:
;*************************
resume_ct1int:
pop TH1                        	;new value for interval
xch a,SP		       	;pointer do SP
xch a,stack_point	       	;restore aktul stack pointer
djnz int_count,old_cycle
mov int_count,int_count_value
clr mutex                       ;sign for synchro
mov stack_point,stack_point_start
old_cycle:
reti
;*************************

Poznámka: nastavení příznaku "mutex" oznamuje převzetí nových dat procesem.

Nyní uvedeme kompletní výpis service_rutin pro ilustrační případ jednoho PWM výstupu (instrukce "pop Px" je nahrazená instrukcí "setb Px.y" resp. "clr Px.y" - proto aby jsme neblokovali celý 8bitový port procesoru. Dále z časového průběhu signálu PWM plyne, že signál pro hodnotu "max-X" vznikne invertováním signálu pro hodnotu "X" (kde max: je 256 pro hloubku 8 bitů, 128 - pro hloubku 7 bitů atd.).

Stačí nám proto pouze zvolit, v nulovém časovém bodě periody hodnotu 0 místo 1 a v časovém bodě "X" ji invertovat. Toto je vysvětlení proč jsou sady rutin vlastně dvě. Protože nastavení hodnoty v nulovém časovém bodě periody nastává vždy (s výjimkou vstupních hodnot "no-pwm" - hodnoty "0" a "max"), přerušení změny výstupu nastane pak vždy v první polovině periody.

Poznámka:časové zpoždění od okamžiku od přetečení čítače (vyvolání přerušení) do okamžiku vykonání první instrukce nastavení portu v rutine přerušení je stejné pro každé volání. Proto jeho vliv můžeme zanedbat při kalkulaci času, musíme s ním však počítat při posuzování konstrukce obsluhy (je uložená v nastavení konstanty : MAX_CONTIN1 set 12D).

Výpis rutin obsluhy pwm: /p>

; definition hw connecting of ports
bit_pwm                         bit P1.0
; rest of port_pwm is disposable as input or output
;
;*************************
; pwm activity
;*************************
speed0x2:
setb bit_pwm
speed0x1:
clr bit_pwm
ret
speed1x2:
clr bit_pwm
speed1x1:
setb bit_pwm
ret
speed0x3:
setb bit_pwm
nop
nop
clr bit_pwm
ret
speed1x3:
clr bit_pwm
nop
nop
setb bit_pwm
ret
speed0x4:
setb bit_pwm
pop loop_count
loop1:  djnz loop_count,loop1
clr bit_pwm
ret
speed1x4:
clr bit_pwm
pop loop_count
loop2:  djnz loop_count,loop2
setb bit_pwm
ret
;*************************

Zde je vidět,že případ velmi malých hodnot: 1,2 a skupina 3 až N je řešený speciálními procedurami (jde o hodnoty, které jsou menší jako čas, který uplyne od přetečení čítače (vyvolání přerušení) do okamžiku vykonání instrukce nastavení portu v rutine přerušení. Pro skupinu "3 až N" je použitá rutina "speed0x4" resp. "speed1x4" s čekací smyčkou dekremetující lokální proměnnou načtenou ze zásobníku. Tyto krátké procedury spolu s vhodným, předpřipraveným obsahem lokálního zásobníku zabezpečuji činnost procesu s minimálními požadavky na čas procesoru.

Obsah lokálního zásobníku je rekonstruován pouze jednou, při zadání nové vstupní podmínky (jedná se tedy o vygenerování kódu - dat pro zásobník - jednoprůchodově při kompilaci nové vstupní podmínky s přihlédnutím na časovou kauzalitu) Proces "žije" díky spolupráci SW s běžícím interním čítačem.

Obecně můžeme náš "kompilátor" rozdělit do tří části:

  1. precompiler+output_composer - úprava nové vstupní podmínky předané registry setup_pwm a val_pwm0 s ohledem na nastavenou hloubku PWM. Určení výstupu signálu PWM (pro vícekanálové PWM) - pro jeden kanál není třeba. Pro vstupní hodnoty "no-pwm" ( "0" a "max") ukončení kompilace a nastavení statické úrovně výstupu PWM.
  2. time_composer - určení časové posloupnosti kanálu PWM (pro vícekanálové PWM) - pro jeden kanál není třeba. Určení počtu přerušení na periódu. Nastavení lokálních příznaků (pro volbu rutiny pwm activity), kterou použije stack_constructor.
  3. stack_constructor - výpočet hodnot "THx" pro čítač.

Naplnění momentálně procesem nepoužívané poloviny lokálního zásobníku daty pro novou vstupní podmínku procesu. Po vygenerování obsahu nového zásobníku spuštění synchronního přepnutí - pomocí registrů int_count_value, stack_point_start a příznaku "mutex". Přepnutí na nové vstupní data si řídí proces sám synchronně vůči své dosavadní činnosti (na začátku periody PWM), avšak s pohledu algoritmu kompilace naprosto asynchronně.

Celý kompilační algoritmus je pojat jako volaný podprogram s předaním dat přes registry (direct address) v paměti RAM procesoru. Pro teď diskutovaný příklad jednoho PWM výstupu části jsou části 1. a 2. algoritmu značně zredukované. Viz výpis programu:

        extrn data(setup_pwm)   ;register for setup pwm
;      in setup_pwm in byte
;    b7      b6     x   x   b3   b2    b1    b0
;   --------------------------------------------
;  depth7  depth6   x   x   x     x    x     x
;1 true output pwm 8 bit depth (256 values) - depth7 is 0
;1 true output pwm 7 bit depth (128 values) - depth7 is 1
;1 true output pwm 6 bit depth (64 val)-depth7 and depth6 are 1
; for depth 6 must be set both  - depth7,depth6
extrn data(val_pwm0)     ;value of pwm reg 0
;   for depth 128 least significant bit (b0) of val_pwm0 is dummy
;   for depth  64 least significant bits (b1,b0) of val_pwm0 are dummy
;********************************
;       CONST   for STACKconstructor
;********************************
MAX_CONTIN1  set 12D    	;max value for uninterupted seq -1
;*************************
using 1
;*************************
gen_pwm:
push psw
mov psw,#08H            ;register bank 1
mov a,setup_pwm
anl a,#0C0H             ;mask on relevant bits
mov b,a                 ;store in b
mov a,val_pwm0
clr f0
jnb acc.7,not_compl_pwmx
cpl a
setb f0
not_compl_pwmx:
jnb b.6,depth7x1
clr C
rrc a
depth7x1:
jnb b.7,depth8x1
clr C
rrc a
depth8x1:
mov C,f0
jnz was_neg
clr TR1
clr mutex
mov bit_pwm,C       	;value on bit_pwm
pop psw             	;restore register bank
ret                 	;end of procedure
was_neg:
mov b.0,C
;time_composer
mov r2,#01H         		;max count of interupts
cjne a,#MAX_CONTIN1,fir1 	;value in acc < MAX_CONTIN1
fir1:
jnc stack_constuctor
setb b.1
mov r7,a                ;store value of delay
cjne a,#01,fir2
setb b.2
fir2:
dec r2
stack_constuctor:
;*************************

Nyní si vysvětlíme proces naplnění zásobníku v 3. části, to jest v "stack_constuctor"-u. Nejdříve jsou vypočítány "náplně THx" čítače.

Poznámka: pro hloubku 8 bitů je vzhledem k faktu, že námi zvolená minimální prodleva (je daná vykonáním dvojice instrukcí setb Px.y a clr Px.y,resp. dvojice mov Px,#data1; mov Px,#data2 bezprostředně za sebou ) vyžaduje 2 cykly procesoru a takt čítače je 1 cyklus, proto musí na jednu periodu PWM proběhnout dva plné cykly čítače. Proto v tomto případě musí proces generovat navíc jedno "dummy" přerušení bez změny na výstupu.

Pokračováni výpisu programu:

stack_constuctor:
;*************************
jb b.7,service_6or7bit
inc r2           	;necessary nested interupt for service 8bit
cjne r2,#2H,service_8bit_short1
add a,acc
mov r7,a
cpl a
inc a
setb C
rrc a
mov r6,a
mov r5,a
jmp stack_constuctor1
service_8bit_short1:
mov r5,#0H
mov r6,#0H
jmp stack_constuctor1
service_6or7bit:
cjne r2,#1H,service_7bit_short1
add a,acc
mov r6,a
cpl a
inc a
jnb b.6,stack_constuctor1x1
clr acc.7
stack_constuctor1x1:
mov r5,a
jmp stack_constuctor1
service_7bit_short1:
mov r5,#0H
jnb b.6,stack_constuctor1
mov r5,#80H
stack_constuctor1:             ;in r3 is 0
;*************************

Dále bude zjištěno, která půlka lokálního zásobníku procesu je volná a její začátek je umístněn do pointeru v registru "R1".

Na základě nastavení lokálních příznaků (reg. "R4"), které musí reprezentovat úplný (ortogonální) rozklad všech možností časové kauzality (následnosti), je proveden skok na tu část programu, která plní zásobník daty. Jako první je uložená adresa odpovídající rutiny ze souboru - "pwm activity". V případě, že je volána rutina "speed0x4" resp. "speed1x4" je také do zásobníku uložená náplň čekací smyčky. Na konci je do zásobníku uložená návratová adresa
na návěští "resume_ct1int:", následována hodnotou "THx".

V případě,že na jednu periodu procesu PWM je potřeba více přerušení, ukládání je opakováno znovu od uložení další adresy rutiny se souboru - "pwm activity".

Pokračováni výpisu programu:

;*************************
stack_constuctor1:
;*************************
inc r2          ;interupt for tpoint0
mov a,r2
mov r3,a        ;store count of interupts
mov r1,#(Zac_stack_pwm1+15D)
jbc tab_stack,toogle_stack
setb tab_stack          ;toogle pointer
mov r1,#(Zac_stack_pwm2+15D)
toogle_stack:                   ;normal start from tpoint0
jb mutex,toogle_stack   ;waiting for stack take over
mov a,r1        ;store begin of new stack
mov r4,a
mov a,b         ;flags appropriate tpoints
anl a,#07H      ;mask on alone interupts
add a,acc
mov dptr,#tab_count1
jmp @a+dptr
tab_count1:
ajmp serv10     ;service ii (standalone interupts)-f0 -0
ajmp serv11     ;service ii (standalone interupts)-f0 -1
ajmp serv12     ;service xi     -f0 -0
ajmp serv13     ;service xi     -f0 -1
ajmp serv14     ;service ix dummy
ajmp serv14     ;service ix dummy
ajmp serv16     ;service 11     -f0 -0
serv17:                 ;service 11 (first tpoint1 with tpoint0)
mov @r1,#HIGH(speed1x2)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed1x2)
jmp res_for_F1
serv16:
mov @r1,#HIGH(speed0x2)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed0x2)
jmp res_for_F0
serv14:
jmp serv14              ;error
serv12:       ;service xi(first interupt not alone) -f0 -0
mov a,r7
cjne a,#2,delay_more2
mov @r1,#HIGH(speed0x3)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed0x3)
jmp res_for_F0
delay_more2:
subb a,#2
mov @r1,#HIGH(speed0x4)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed0x4)
dec r1
mov @r1,a         ;value of wait loop
jmp res_for_F0
serv13:       ;service xi(first interupt not alone) -f0 -1
mov a,r7
cjne a,#2,delay_more2x1
mov @r1,#HIGH(speed1x3)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed1x3)
jmp res_for_F1
delay_more2x1:
subb a,#2
mov @r1,#HIGH(speed1x4)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed1x4)
dec r1
mov @r1,a         ;value of wait loop
jmp res_for_F1
serv10:            ;service ii (standalone interupts) f0 - 0
mov @r1,#HIGH(speed1x1)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed1x1)
call resum_interupt
dec r2
resum_serv10:
mov @r1,#HIGH(speed0x1)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed0x1)
res_for_F0:
call resum_interupt
djnz r2,resum_serv10
jmp switch_stack
serv11:            ;service ii (standalone interupts) f0 - 1
mov @r1,#HIGH(speed0x1)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed0x1)
call resum_interupt
dec r2
resum_serv11:
mov @r1,#HIGH(speed1x1)   ;PCh of address into stack
dec r1
mov @r1,#LOW(speed1x1)
res_for_F1:
call resum_interupt
djnz r2,resum_serv11
switch_stack:
;************************* to be continued...
;***************procedure for fill stack
;inputs r1 - pointer to stack
resum_interupt:
dec r1
mov @r1,#HIGH(resume_ct1int)   ;PCh of address into stack
dec r1
mov @r1,#LOW(resume_ct1int)     ;PCL
dec r1
mov a,r7        ;
xch a,r6        ; shift
xch a,r5        ; appropriate value TH1
cpl a
inc a           ;TH1 count up
mov @r1,a
dec r1
ret
;********************

Po vyčerpání zápisu dat pro všechna přerušení nastane odevzdání nové náplně zásobníku, včetně dat v registrech "int_count_value, stack_point_start" a vynulování příznaku "mutex".

Poznámka: Přepnutí je optimalizováno z pohledu rychlosti.

Pokračováni výpisu programu:

switch_stack:
;*************************
inc r1
xch a,r1           ;in acc last TH1 value
xch a,r0           ;store address to last TH1
xch a,r1
jb TR1,run_pwm
mov c,f0
mov bit_pwm,C
mov TL1,#80H
mov TH1,a          ;delay from first pwm tpoint0 to next
mov stack_point,r4
mov int_count,#01H
mov stack_point_start,r4
mov int_count_value,r3
setb TR1
pop psw            ;restore register bank
ret                ;end of procedure
run_pwm:
mov b,a
mov a,#01H
cjne a,int_count,enough_time1
mov a,#0F0H
cjne a,TL1,run_pwm2
run_pwm2:
jnc enough_time
setb mutex
run_pwm1:
jb mutex,run_pwm1
enough_time:
mov @r1,b           ;correct last TH1 for tpoint0
mov int_count_value,r3
mov stack_point_start,r4
setb mutex
pop psw             ;restore register bank
ret                 ;end of procedure
enough_time1:
clr EA
mov @r1,b           ;correct last TH1 for tpoint0
mov int_count_value,r3
mov stack_point_start,r4
setb mutex
setb EA
pop psw             ;restore register bank
ret                 ;end of procedure
end
;*************************

Funkční realizaci zadání jednoho výstupu pwm máme hotovou s následujícími vlastnostmi:

  1. volitelná hloubka pwm : 6, 7 a 8bitů
  2. časový krok pwm 2 cykly (1 us pro krystal 24 MHz)
  3. obsazený HW procesoru - 1 čítač - nejvyšší úroveň přerušení
  4. samostatný SW proces s odevzdáním nové vstupní podmínky voláním podprogramu.
  5. počet výstupu 1 I/O bit portu
  6. velkost programu 420 Byte

Ucelený zdrojového kódu výpisu programu pro obsluhu jednoho výstupu PWM je uvedený v příloze pod názvem "pwm1m.a51".

Zadejme si náročnější úlohu: dva výstupy PWM, každý křížově přepínatelný s nastavitelným statickým výstupem. Toto uspořádání je zvláště vhodné pro buzení 2ks krokových motorů v režimu mikrokroků. Požadujeme zachování prvních čtyř vlastností úlohy s jedním PWM.

;pwm clock is uP - CLKin/24
;2 true outputs pwm 8 bit depth (256 values) - depth7 is 0
;2 true outputs pwm 7 bit depth (128 values) - depth7 is 1
;2 true outputs pwm 6 bit depth (64 val)-depth7 and depth6 are 1
; for depth 6 must be set both  - depth7,depth6
; definition hw connecting of ports
port_pwm	equ P1
; pins port_pwm.4 and port_pwm.5    - output pwm0
; pins port_pwm.6 and port_pwm.7    - output pwm1
; rest of port_pwm is disposable as input
;
extrn data(setup_pwm)   ;register for setup pwm
; in pairs - one pin is pwm and the second is static
; toogled by bit sw                      in setup_pwm
; value of static toogled by bit val     in setup_pwm
;    b7      b6     x   x  b3       b2      b1      b0
;   ----------------------------------------------------
;  depth7  depth6   x   x  sw1     val1    sw0     val0
; for sw - 0:  p1.x - stat  p1.x+1 - pwm
; for sw - 1:  p1.x+1 - stat  p1.x - pwm
extrn data(val_pwm0)     ;value of pwm reg 0
extrn data(val_pwm1)     ;value of pwm reg 1
;   for depth 128 least significant bit (b0) is dummy
;   for depth  64 least significant bits (b1,b0) are dummy
;*************************

Na zbývající polovině bajtu nastavujeme "1", proto aby jsme ji mohli bez omezení využit jako 4bitový vstupní port procesoru. Rutina přerušení od čítače zůstává beze změny. Sada service_rutin pro případ dvou pwm výstupu není zdvojená (jedná instrukce "pop Px" - nahrazuje obě instrukce "setb Px.y" resp."clr Px.y" ). Má však víc položek pro realizaci časových následností.

;*************************
; pwm activity
;*************************
speed0x3:
pop port_pwm
speed0x2:
pop port_pwm
speed0x1:
pop port_pwm
ret
speed3x2:
pop port_pwm
nop
nop
pop port_pwm
ret
speed3x1:
nop
nop
pop port_pwm
ret
speed3x3:
pop port_pwm
nop
nop
ret
speed_Xx1:
pop port_pwm
speed_Xx2:
pop loop_count
loop1:  djnz loop_count,loop1
pop port_pwm
ret
speed_Xx0:
pop port_pwm
speed_Xx3:
pop loop_count
loop2:  djnz loop_count,loop2
ret
;*************************

Pro teď diskutovaný příklad dvou PWM výstupu jsou části 1. a 2. "kompilátoru vstupní podmínky" (připomeňme si: 1. precompiler+output_composer a 2. time_composer) značně rozšířené oproti realizaci s jedním výstupem. Zvláště je třeba zdůraznit seřazení zadaných hodnot "val_pwm0" a"val_pwm1" ve smyslu jejich časové následnosti. Poznámka:časové zpoždění od okamžiku přetečení čítače (vyvolání přerušení) do okamžiku vykonání instrukce nastavení portu,má nyní nastavené dvě hodnoty, ta větší přísluší časovému uzlu 0- startu periody PWM. Viz výpis programu:

;********************************
;       CONST   for STACKconstructor
;********************************
MAX_CONTIN1  set 12D    	;max value for uninterupted seq -1
MAX_CONTIN   set 9D   		 ;max value for uninterupted seq - not1
;*************************
using 1
;*************************
gen_pwm:
push psw
mov psw,#08H            	;register bank 1
mov b,setup_pwm
clr a
mov temp1,val_pwm1      	;copy of value pwm1
xch a,temp1
jb b.3,sw1_1           		;for bit sw1 - 1
mov count_tpoin,#80H    	;in  vect pwm1 (hi nibble)
call make_depth867
xch a,temp1
rlc a
mov C,b.2               	;value of static
rlc a
jmp chann0
sw1_1:
mov count_tpoin,#40H       	;in vect pwm1 (hi nibble)
jnb b.2,pwm1xis0           	;value of static
inc temp1
pwm1xis0:
call make_depth867
xch a,temp1
rlc a
chann0:
mov temp0,val_pwm0      	;copy of value pwm0
jb b.1,sw0_1           		;for bit sw1 - 1
orl count_tpoin,#02H    	; vect pwm0 (lo nibble)
xch a,temp0
call make_depth867
xch a,temp0
rlc a
mov C,b.0               	;value of static
rlc a
jmp tpoint0_ready
sw0_1:
orl count_tpoin,#01H      	;vect pwm0 (lo nibble)
mov C,b.0               	;value of static
rlc a
xch a,temp0
call make_depth867
xch a,temp0
rlc a
tpoint0_ready:
mov r7,a                	;store temp value vector tpoint0
mov a,temp1
xch a,temp0
mov r2,#2             		;maximal value of tpoints
cjne a,temp0,not_equal_tp   	;in acc pwm0 , in temp0 pwm1
dec r2
jnz no_stop_pwm
clr TR1
clr mutex
mov a,r7
swap a
orl a,#0FH
mov port_pwm,a      		;value on port_pwm
pop psw             		;restore register bank
ret                 			;end of procedure
no_stop_pwm:
mov r6,a              		;store reconditioned pwm0 =pwm1
mov a,count_tpoin
swap a
orl a,count_tpoin      		;join both vectors
anl a,#0FH
mov count_tpoin,a      		;store reconditioned vector
jmp lower_not0x1
not_equal_tp:
jc queue_ok            		;pwm0 < pwm1
xch a,count_tpoin       	;service for pwm0 > pwm1
swap a
xch a,count_tpoin
xch a,temp0            		;give in temp0 bigger
queue_ok:
jnz lower_not0
dec r2
mov a,count_tpoin
swap a
anl a,#0FH
mov count_tpoin,a
mov a,temp0           		;in temp0 is pwm not 0
lower_not0:
mov r6,a              		;store lower pwmx
lower_not0x1:
mov a,r7
xrl a,count_tpoin
anl a,#0FH
swap a
orl a,r7
mov r7,a
xrl a,count_tpoin
anl a,#0F0H
mov temp1,a
swap a
orl temp1,a
only_one_tpoint:
mov a,r6              		;restore lower pwmx
stayONfirst:
mov b,#0                	;clear register
mov r5,#0              		;clear register for second tpoint
mov r4,#0               	;clear register for accumulated value
cjne a,#MAX_CONTIN1,fir1 	;value in acc < MAX_CONTIN1
fir1:
jnc fir2
setb b.0
cjne a,#01,fir5
setb b.4
fir5:
dec r2
mov r4,a                	;in accum value
cjne r2,#01H,stack_constuctor
jmp second
fir2:
add a,acc
xch a,r6             		;store value of 1.difference in r6
cjne r2,#02H,stack_constuctor
second:
clr C
subb a,temp0
cpl a
inc a
cjne a,#MAX_CONTIN,sec1
sec1:
jnc sec2
setb b.1
cjne a,#01,sec5
setb b.5
sec5:
dec r2
swap a
orl a,r4
mov r4,a
jmp stack_constuctor
sec2:
cjne r4,#0H,sec3
add a,acc
mov r5,a               		;clear register for accumulated value
jmp stack_constuctor
sec3:
add a,r4
add a,acc
mov r6,a               		;store in first tpoint
stack_constuctor:
;*************************to be continued...
;***************procedure for shift vector - for depth
;in :byte acc flags in reg b
;out: byte acc and C flag
make_depth867:
clr f0
jnb acc.7,not_compl_pwmx
cpl a
setb f0
not_compl_pwmx:
jnb b.6,depth7x1
clr C
rrc a
depth7x1:
jnb b.7,depth8x1
clr C
rrc a
depth8x1:
jb f0,was_neg
jnz was_neg1
clr C
ret
was_neg1:
setb C
ret
was_neg:
jz was_neg1
clr C
ret
;*************************

Proces naplnění zásobníku v "stack_constuctor"-u je analogický jako u jednoho výstupu pwm. Nejdříve jsou vypočítány "náplně THx" čítače.

Poznámka:

  • úplný (ortogonální) rozklad všech možností podle časové kauzality (následnosti) je s ohledem na dvě nezávislé vstupní podmínky "val_pwm0" a "val_pwm1" samozřejmě složitější a má více stavů.
  • po uložení adresy odpovídající rutiny se souboru - "pwm activity", je do zásobníku také uložen bajt odpovídající novému stavu na výstupním portu ( realizováno pomocí vykonání instrukce "pop port_pwm").

V případě,že na jednu periodu procesu PWM je potřeba více přerušení, ukládání je opakováno znovu od uložení další adresy rutiny se souboru - "pwm activity" - obdobně jako u zápisu pro jeden výstup PWM. Po vyčerpání zápisu dat pro všechna přerušení nastane odevzdání nové náplně zásobníku, včetně dat v registrech "int_count_value, stack_point_start" a vynulování příznaku "mutex".

Kompilační podprogram odevzdá řízení a proces se sám autonomně novelizuje na nové vstupní podmínky při přechodu časovým uzlem 0. Vzhledem k podobnosti zdrojového kódu k již podrobně diskutovanému zadání s jedním PWM výstupem , neuvádím příklad výpisu programu. Funkční realizaci zadání dvou výstupů PWM s přepínáním pro mikrokrokové buzení 2 krokových motorů máme hotovou - s následujícími vlastnostmi:

  1. volitelná hloubka PWM : 6, 7 a 8bitů
  2. časový krok PWM 2 cykly (1 µs pro krystal 24 MHz)
  3. obsazený HW procesoru - 1 čítač - nejvyšší úroveň přerušení
  4. samostatný SW proces s odevzdáním nové vstupní podmínky voláním podprogramu.
  5. proces buzení výstupů zabírá i v nejhorším případě (6 bitová hloubka pwm + {hodnota pro 1. kanál PWM = [MAX_CONTIN1=12D] a hodnota pro 2. kanál PWM =[MAX_CONTIN1+ MAX_CONTIN= 12D+9D] }) < 36% strojového času procesoru. Pro 8 bitovou hloubku PWM je to typicky < 10%.
  6. počet výstupu 4 I/O bit portu ( zbývající 4 bity portu jsou použitelné jako univerzální vstupy).
  7. velkost programu 840 Byte

Ucelený výpis zdrojového kódu programu pro obsluhu dvou výstupů PWM je uvedený v příloze pod názvem "pwmm.a51".

Poznámka: V příloze je uvedený jednoduchý testovací a prezentační program pro odzkoušení obou zdrojových kódu: "pwm1m.a51" a "pwmm.a51". Musíme ho pouze "zlinkovat" s vybranou aplikaci.

Praktická aplikace

Životaschopnost výše popsaného modelu byla dále ověřená na úspěšné aplikaci umožňující buzení čtyř krokových motorů v režimu mikrokroků, která byla implementována na levném small-food procesoru AT89C2051 (cena okolo 1,1 $). Pro tuto aplikaci jsou v zásadě všechny programové moduly shodné s aplikaci pro 2 motory. Pouze značně rozšířená je část "time_composer" našeho kompilátoru, a to proto, že je třeba ošetřit všechny varianty nastavení jednotlivých kanálů PWM do jediné časové posloupnosti. Musíme určit konkrétní následnost kanálů, případnou shodu nastavení apod. , protože proces používá jediný čítač na svoji činnost.

Novým blokem pro tuto aplikaci je obsluha zadání nastavení a vstupních podmínek jednotlivých kanálů PWM přes volné I/O piny procesoru. Získáme tak za velmi nízkou cenu samostatný IO, který můžeme použit jako rozhraní pro řízení čtyř krokových motorů v režimu mikrokroků. Popis tohoto řešení je nad rámec tohoto článku, jenom uvedu, že celkový rozsah kódu je 1868 byte.

Závěr

Myšlenku realizace adaptibilního stavového automatu realizovaného na klasickém mikroprocesoru s Hadvardskou architekturou jsme úspěšně realizovali. Použití účelového pseudo-kódu spolu s nestandardním využitím stacku procesoru nám pomohlo ku získání parametrů aplikace srovnatelných s hardwarovým řešením pomocí bloku PCA. Naším účelovým pseudo-kódem jsou samozřejmě data uložené v stacku.

Popsaný model umožňuje "kompilovat" vstupní podmínku do optimálního ( s pohledu rychlosti) pseudo-kódu, který v součinnosti s relativně malým počtem už hotových podprogramů, nahrazuje vygenerování kódu programu sebou samým obdobně jako je to možné na procesorech s von Neumannovou architekturou. Když se Vám v paměti právě teď vybaví vzpomínka na Vaší oblíbenou stavebnici Lego, není to náhoda.

Uvedený princip samozřejmě není omezen pouze na aplikaci na klonech 8051, skoro všechny jednočipové procesory mají implementovaný stack a instrukce nad ním. Popis řešení byl vysvětlen na aplikaci simulující PWM.

Navrhnutá metoda má však použití i pro jiné aplikace, které mají charakter nastavitelného automatu. Vyplatí se totiž i za cenu složitějšího algoritmu přednastavení na novou vstupní podmínku, tak optimalizovat samotnou, mnohdy nesčetněkrát opakovanou, činnost automatu, aby jsme v maximální míře mohli využít jeho možnosti anebo jinak co nejpozději narazili na jeho omezení.

Platí totiž, že námahu (strojový čas) vložený do analýzy a optimalizace vkládáme pouze jednou při spuštění nebo při změně vstupní podmínky, kdežto zisk (úsporu strojového času) získáváme opakovaně během samotné požadované činnosti.

V případě vážného zájmu o variantu 4 kanálového ovládání motorů kontaktujte mne na e-adrese: brudny@atlas.cz.

DOWNLOAD & Odkazy

Hodnocení článku: