[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 lsrflslf 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:

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.

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ů.

4 Replies to “[PIC programování] – 10. díl”

  1. 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.

    1. 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í.

      1. 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?

        1. 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.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna.