[PIC programování] – 10. díl
Vítám vás u dalšího dílu ze série PIC programování.
Dnes si ukážeme, jak s několika piny procesoru ovládat více 7-segmentových displejů najednou pomocí tzv. multiplexu.
Nejprve schéma:
Pozn.: V zapojení používám dva PNP tranzistory s označením BC327.
Představme si nyní klasické zapojení displeje. Ke každému segmentu potřebujeme jeden pin na MCU (tedy celkem 8 pinů). Pro každý další displej bychom potřebovali dalších 8 pinů. Pro n displejů bychom tedy potřebovali 8*n pinů.
Při multiplexu chvíli svítí jeden displej, chvíli druhý displej. Tedy nejprve máme displeje zhasnuté (ovládáme PNP tranzistory), poté nastavíme segmenty, rozsvítíme první displej, chvíli čekáme, zhasneme displej, nastavíme segmenty pro druhý displej a rozsvítíme jej. Tento proces se opakuje stále dokola. Takto nám postačí 8+n pinů na MCU pro n displejů.
Příklad.:
Pokud máme 4 displeje, potřebujeme 8*4 tj. 32 pinů. Při multiplexu nám postačí pouze 8+4 tj. 12 pinů. Pořádný rozdíl, že?
Proč to tak funguje?
Pokud displeje střídáme dostatečně rychle, našemu mozku se díky persistenci vidění bude zdát, že oba displeje svítí současně. Je to tedy čistě biologická záležitost. Pokud by náš mozek byl dostatečně rychlý, viděli bychom displeje střídavě blikat. V našem příkladě budeme displeje střídat s frekvencí 100 Hz. Pokud se podíváte pozorně, budete moci blikání matně vnímat. Pro odstranění tohoto nežádoucího efektu si pak sami můžete vyzkoušet upravit program na frekvenci 1 kHz.
V minulém díle jsme zmínili (a jednu použili) instrukce lsrf a lslf pro manipulaci s bity. Dnes k nim přidáme jednu šikovnou instrukci pro prohození nibblů.
Počkat, co je nibble?
Tak jako máme byte, což je 8 bitů (tedy ve většině případů, v obecném pojetí by bylo asi lepší říkat oktet), můžeme definovat nibble, to jsou 4 bity. Každý registr v našem MCU má tedy celkem dva nibble (horní a dolní – higher and lower). Shodou okolností (tedy shodou okolností ne, ale je to tak) naše 8-bitové číslo v registru může být reprezentováno dvouciferným číslem v šestnáctkové soustavě. Tedy na jednu číslici v šestnáctkové soustavě vychází přesně jeden nibble. Budeme tedy potřebovat nějak vypreparovat horní nibble z našeho 8-bitového čísla. Než abychom použili čtyřikrát za sebou instrukci lsrf, můžeme použít instrukci swapf. Instrukce swapf slouží k prohození nibblů v registru.
Příklad.:
WREG = b’10010011′
provedu: swapf WREG
WREG = b’00111001′
Dále budeme muset upravit náš podprogram, který nastavuje registry LATx (rozsvěcí segmenty), aby si pamatoval nastavení pinů LATA<4> a LATA<5> (kterými ovládáme jednotlivé displeje). K tomu budeme používat novou instrukci iorlw, který funguje jako andlw, akorát dělá inklusivní logický součet (inclusive OR – IOR).
Pro jistotu uvedu pravdivostní tabulku:
A | B | Výsledek |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
Velmi stručně: Pokud je alespoň jeden ze vstupů 1, pak výstup je 1, jinak 0.
Instrukce iorlw provede logický součet registru W a konstanty. Dále pak ještě existuje iorwf, což je obdoba, která provede logický součet registru W a registru f (operand instrukce).
Pro budoucí účely ještě uvedu instrukce xorlw a xorwf. Tyto instrukce reprezentují operaci exklusivní logický součet (exclusive OR – XOR).
Zde je opět pravdivostní tabulka:
A | B | Výsledek |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Tuto operaci můžeme jednoduše popsat aritmetickou operací: (A + B) % 2, tedy zbytek po dělení dvěma součtu A + B.
Takto bude vypadat náš upravený podprogram:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
;načte segmenty ve formátu <C4:C3:C2:C1:C0:A2:A1:A0> ;z registru W a přiřadí je do registrů LATA a LATC disp_zobraz: banksel R_POM movwf R_POM ;uložím si nastavení LATx movlw b'111000' banksel LATA andwf LATA ;vynuluji bity segmentů banksel R_POM movfw R_POM andlw b'111' banksel LATA iorwf LATA ;nastavím pouze bity segmentů banksel R_POM movfw R_POM lsrf WREG lsrf WREG lsrf WREG banksel LATC movwf LATC return |
My v našem podprogramu potřebujeme nastavit pouze dolní 3 bity v registru LATA. Bity LATA<4:5> musí být zachovány, protože se jedná o spínání displejů. Proto nejprve vynulujeme spodní tři bity operací AND a poté operací IOR přiřadíme bitům LATA<0:2> nové nastavení pro segmenty.
Nyní již je celý algoritmus popsán a všechny potřebné informace s ním, zde je rovnou celý hotový program.
Pozn.: PNP tranzistor spínáme log. nulou.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
#include "p16f1705.inc" __CONFIG _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF __CONFIG _CONFIG2, _WRT_OFF & _PPS1WAY_OFF & _ZCDDIS_ON & _PLLEN_OFF & _STVREN_ON & _BORV_LO & _LPBOR_OFF & _LVP_OFF #define CITAC .100 #define TECKA b'11011111' R_CITAC equ 0x20 ;dekrementující čítač R_POM equ 0x21 ;univerzální pomocná proměnná R_DISPCITAC equ 0x22 ;hodnota zobrazovaná na displeji R_MPXCITAC equ 0x23 ;čítač, který rozhoduje o aktivním ; displeji org 0 goto START ;====== PODPROGRAMY ============================================================ ;vezme cislo 0 - 15 z registru W a vrátí ;odpovídající segmenty na displeji ;ve formátu: <C4:C3:C2:C1:C0:A2:A1:A0> disp_hex_cislo: andlw 0x0F ;ochrana proti nechtěným hodnotám brw ;(vyšším než 15) retlw b'00100001' ; 0 retlw b'11100111' ; 1 retlw b'00110010' ; 2 retlw b'10100010' ; 3 retlw b'11100100' ; 4 retlw b'10101000' ; 5 retlw b'00101000' ; 6 retlw b'11100011' ; 7 retlw b'00100000' ; 8 retlw b'10100000' ; 9 retlw b'01100000' ; A retlw b'00101100' ; B retlw b'00111001' ; C retlw b'00100110' ; D retlw b'00111000' ; E retlw b'01111000' ; F ;------------------------------------------------------------------------------- ;načte segmenty ve formátu <C4:C3:C2:C1:C0:A2:A1:A0> ;z registru W a přiřadí je do registrů LATA a LATC disp_zobraz: banksel R_POM movwf R_POM ;uložím si nastavení LATx movlw b'111000' banksel LATA andwf LATA ;vynuluji bity segmentů banksel R_POM movfw R_POM andlw b'111' banksel LATA iorwf LATA ;nastavím pouze bity segmentů banksel R_POM movfw R_POM lsrf WREG lsrf WREG lsrf WREG banksel LATC movwf LATC return ;====== INICIALIZACE PROGRAMU ================================================== START: ;nastavení vnitřního oscilátoru na frekvenci 4MHz movlw b'01101010' banksel OSCCON ;je třeba vybrat správnou banku movwf OSCCON ;nastavení dekrementujícího čítače movlw CITAC ;čítač začíná na hodnotě .100 banksel R_CITAC movwf R_CITAC ;nastavení timeru 4 banksel T4CON movlw b'1001101' ;1:10 postscaler, tmr on, 1:4 prescaler movwf T4CON movlw .250 movwf PR4 ;nastavení IO pro displej banksel TRISA ;odpovídající segmenty na displeji a movlw b'001000' ;PNP tranz. jsou výstupy, zbytek vstupy movwf TRISA movlw b'100000' movwf TRISC banksel ANSELA ;všechny piny jsou digitální clrf ANSELA clrf ANSELC banksel LATA movlw b'110111' ;displeje jsou zhasnuté movwf LATA movlw b'11111' movwf LATC banksel R_DISPCITAC clrf R_DISPCITAC clrf R_MPXCITAC ;====== HLAVNÍ PROGRAM ========================================================= HLAVNI_SMYCKA: banksel PIR2 ;testujeme timer4 btfss PIR2,TMR4IF goto HLAVNI_SMYCKA bcf PIR2,TMR4IF ; --- zde provedeme multiplexovou rutinu ------------------------------------- banksel R_MPXCITAC incf R_MPXCITAC btfss R_MPXCITAC,0 ;Je mpx čítač lichý? goto __mpx_d1 ;Každou sudou periodu rozsvítíme disp 1 goto __mpx_d2 ;Každou lichou periodu rozsvítíme disp 2 __mpx_d1: movfw R_DISPCITAC swapf WREG call disp_hex_cislo banksel LATA bsf LATA,4 ;zhasneme disp 2 call disp_zobraz banksel LATA bcf LATA,5 ;rozsvítíme disp 1 goto __mpx_konec ;přeskočíme nežádoucí kód __mpx_d2: movfw R_DISPCITAC call disp_hex_cislo banksel LATA bsf LATA,5 ;zhasneme disp 1 call disp_zobraz banksel LATA bcf LATA,4 ;rozsvítíme disp 2 __mpx_konec: ; ---------------------------------------------------------------------------- banksel R_CITAC decfsz R_CITAC ;je čítač na nule? goto HLAVNI_SMYCKA ;není, vracíme se zpět na začátek movlw CITAC ;je movwf R_CITAC ;čítač nastavíme na výchozí hodnotu banksel R_DISPCITAC incf R_DISPCITAC goto HLAVNI_SMYCKA end |
Princip multiplexu lze aplikovat i na jiné potřeby, např. čtení velkého množství digitálních vstupu (případ klávesnice u PC). K tomuto příkladu se možná v této sérii dostaneme.
Pro tento díl to bude vše a v příštím díle se podíváme na čtení analogových vstupů.
Ahoj Same!
Ty ses zase krásně rozjel! Ale mám i připomínky.
1.
Není už nejvyšší čas naučit budoucí programátory používání konstant? Víš přece dobře, že konstanty rozeseté různě po kódu jako holá nic neříkající čísla jsou známý programátorský nešvar. Sám ses chytil, když v komentáři Ti zůstalo 10 a v kódu máš 255. Nebo dovedeš si představit, že by si tabulku disp_hex_cislo měli sami generovat? Přitom se právě na této tabulce dá použití konstant krásně demonstrovat…
2.
Program by byl robustnější, kdybys opravdu testoval, zda hodnota R_DISPCITAC je větší nebo rovna než horní mez, namísto jen testu na shodu. Takto, když přiletí splašený neutron a změní hodnotu registru na .11, provede se ještě 245 cyklů s hodnotou mimo toleranci. A to máš štěstí, že máš jen osmibitový procesor. Vždyť jsi to porovnávání tak krásně vysvětlil.
3.
Používáš implicitní cílové registry instrukcí. MPASM na to tuším vydává hlášení. Vede to k chybám, lépe je cílový registr vždy specifikovat.
4.
Měl bys stanovit nějaká pravidla pro názvy konstant, proměnných a podprogramů a návěští a pak je dodržovat, třeba:
TOTO_JE_KONSTANTA
nějakáProměnná
NávěštíNeboPodprogram
5.
Příliš mnoho bankselů znepřehledňuje kód. To je nemoc architektury PIC, ale trochu se to dá omezit umístěním pracovních registrů do stejné banky, ve které jsou periferie, se kterou pracují. Riskuje se pak, že někde bude nějaký ten banksel chybět, a vím, že je to nejčastější chyba programů psaných v assembleru PIC. Ale gpasm (z gputils) a s použitím šikovných maker i MPASM už dokáží většinu takových chyb odhalit, takže to přestává být takový problém. Někdy o tom také napíšu.
Univerzální pomocné proměnné pak bývají horcí kandidáti na umístění do common RAM. disp_zobraz by pak mohla vypadat třeba takhle:
DispZobraz:
banksel LATA
movwf common1 ;uložím si nastavení LATx
xorwf LATA,W
andlw b’111′ ;ponechám změněné bity segmentů
xorwf LATA,F
lsrf common1,W
lsrf WREG,W
lsrf WREG,W
movwf LATC
return
Algoritmus není z mé hlavy, obdobný už byl použit v ROM ZX Spectra.
Prostě jednou jsi v roli pedagoga, a tak by ses měl snažit, aby si studenti osvojovali hned od začátku dobré programátorské návyky.
Srdečně Tě zdravím a také jsem zvědavý na další díl.
Ahoj Petře,
díky za postřehy, pokusím se reagovat na všechny body komentáře.
1) Nevím přesně, na jaké díly série míříš, ale v posledních dílech jsem nenápadně všechny konstantní hodnoty přesunul nahoru do programu a definoval je pomocí direktivy #define. Pravda je, že spoustu kódu kopíruji, takže mi sem tam nějaký ten starý komentář k aktualizovanému kódu zůstane. Myslím ale, že není na škodu, když nějaká taková chyba donutí čtenáře se nad tím kódem trochu zamyslet.
2) K tomuto bodu bych jen dodal, že kvůli samým detailům unikne čtenáři podstata věci. Snažím se kód udržovat co nejjednodušší, aby ho i začátečník snadno pochopil. Musím si do tebe trochu rýpnout, protože při luštění tvých kódů jsem se párkrát zapotil :). Ale souhlasím, že do reálného provozu patří robustnější řešení.
3) Zde máš pravdu, pokud jsem to dobře pochopil, narážíš na druhý operand u instrukcí jako iorwf, andwf atd? Měl bych ale správně specifikovat, kam se výsledek uloží, takže od dalšího dílu to začnu dělat.
4) Zde odpověď trochu souvisí s bodem 1). Pro konstanty jsem nenápadně zavedl definici pomocí direktivy #define. Adresy registrů pak definuji pomocí equ, navíc s prefixem R_. Pro podprogramy používám malá písmena a slova oddělená podtržítkem, pro logické části kódu používám velká písmena a slova oddělená podtržítkem, pro podčásti kódu pak používám malá písmena, slova oddělená podtržítkem a prefix dvou podtržítek.
5) Ten postřeh s Common RAM je naprosto geniální a začnu ho používat, díky za tip 😉 Nicméně s bankami jsem snad ještě nikdy nenarazil, protože už od prvních zkušeností s MPASM si na ně dávám dobrý pozor. A navíc kontroluji výstup kompilátoru, který mě na to případně upozorní.
Ahoj Same,
půjdu rovnou k věci:
1) Definuješ CITAC, ale už ne DISP_CITAC.
S tou tabulkou jsem to myslel třeba takto:
#define SG_A 4
…
#define MSK_A (1<<SG_A)
…
#define SPACE 0xFF
#define DISP_8 (~SPACE^MSK_DP)
…
retlw DISP_8^MSK_G ;0
retlw SPACE^MSK_B^MSK_C ;1
…
Můžeš si vybrat, zda raději definuješ znak podle toho, co svítí, nebo podle toho, co je zhasnuté. Stejně už učíš lsrf, tak snad by čtenáři pochopili i shifty ve výrazech.
2) Stydím se a červenám… Chtěl jsem jen říci, že v tom testu je lépe skákat podle Carry než podle Zero.
3) Přesně to jsem myslel.
4) No, je to úplně naruby proti tomu, na co je člověk zvyklý třeba z Javy, ale proti gustu žádný dišputát.
5) To Ti gratuluji, já se vždycky někde chytnu. A horor to začíná být, když portuji na procesor, který má jinak uspořádány SFR v bankách..
Jaký výstup kompilátoru kontroluješ? Já vím jenom o neslavně známém message 302, který je vygenerován před přístupem do kteréhokoliv registru, který není v bance 0, a proto ho má většina lidí vypnutý. Používáš něco jiného?
Ahoj,
Tak s tou konstantou… to byl kód, co jsem smáznul, ale asi jsem neuložil změny, tak jsem to už opravil, chování je úplně stejně s ním i bez něj. Nicméně stojím si za tím, že v případě, který si zmínil, je použití konstanty minimálně zbytečné.
Jinak použití tabulky, kterou uvádíš, je podle mého názoru pro čtenáře začátečníka naprosto matoucí (už jen proto, že začnu používat matematické operace preprocesoru, ne MCU) a sám bych je zrovna pro tabulku segmentů nepoužil, protože není o nic složitější upravit tu jednu jedinou konstantu, kterou potřebuji upravit, přímo v podprogramu. Nicméně, každý má jiný styl programování. Pokud si to bude chtít čtenář udělat po svém, nikdo mu v tom nebrání. K hlubšímu popisu preprocesoru se ale možná časem v sérii dopracuji.