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:
- 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).
- velké nároky na obsazení paměti dat a její neefektivní použití.
- 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:
- 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.
- 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.
- 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:
- volitelná hloubka pwm : 6, 7 a 8bitů
- časový krok pwm 2 cykly (1 us pro krystal 24 MHz)
- obsazený HW procesoru - 1 čítač - nejvyšší úroveň přerušení
- samostatný SW proces s odevzdáním nové vstupní podmínky voláním podprogramu.
- počet výstupu 1 I/O bit portu
- 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:
- volitelná hloubka PWM : 6, 7 a 8bitů
- časový krok PWM 2 cykly (1 µs pro krystal 24 MHz)
- obsazený HW procesoru - 1 čítač - nejvyšší úroveň přerušení
- samostatný SW proces s odevzdáním nové vstupní podmínky voláním podprogramu.
- 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%.
- počet výstupu 4 I/O bit portu ( zbývající 4 bity portu jsou použitelné jako univerzální vstupy).
- 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
- Stažení rutin - prgm.zip