PIC na wodę - fotomontaż

wiedźmy czary-mary z mikrokontrolerami PIC



nieco zmodyfikowane preludium


Nie będę tu pisała o swoich problemach z pisaniem programów w assembler dla PIC ponieważ póki co jedyny problem w tym wszystkim to ja sama i jak się okazało bagaż przyzwyczajeń i nawyków związanych z innymi rodzajami mikrokontrolerów i składnią assemblera czy dziwacznymi mnemonikami, ale wszystko idzie zwalczyć. Testowy układzik na PIC16F876 ruszył przed noworoczną północą, albo to znak od duchów albo za dużo grzańca, nie wiem i nieważne. Istotne, że udało mi się w miarę gładko pokonać jak to się mawia - próg wejścia, "coś tam" sklecić i to coś - zadziałało, a fakt ten uskrzydla i motywuje do dalszych prac. Nie chcę tu pisać jakichś sążnistych tutoriali i tłumaczyć firmowej (btw. w moim przekonaniu naprawdę dobrej dokumentacji). Chce podzielić się drobnymi prawie projektami, które niejako z marszu można sobie wczytać do swojego MPLABX i skleciwszy układzik na płytce stykowej - mieć punkt startu do własnych eksperymentów i przeróbek. Ten i kolejne posty to czytanie na przemian pdf do kostek 16F oraz googlowanie za detalami w sieci, to jakoś wystarczyło. To lecimy...

pickitowa piaskownica - migaj sobie ledzie


PICKIT3 z Chinowa oczywiście i cokolwiek by nie mówić o ichniejszych "realizacjach" - programator ruszył bez problemu, skomunikował się z kostką, finalnie - zaprogramował ją tym prościtkim wsadem. Układ na płytce stykowej, garstka drucików oraz kwarc póki co 8MHz, robiłam z tego co poniewierało się po biurku. Ledy vintage, próbnik logiczny też, a garstka zdjęć w załączniku. Aha, no i oprogramowanie - najnowszy dostępny MPLAB-X (dograne XC8 oraz XC16, wersje free, to na potem) oraz konfigurator MCC, fajna sprawa, też na potem. Oczywiście Linux Mint Tina na średnich lotów maszynie. W rolach głównych wspomniany w tagu PIC16F876, jak samplusie od Microchipów nadjadą - będą i inne okazy.

Sztandarowy przykład migadełka na dwóch diodach LED - elementy na wyjściach kontrolera RB.0 oraz RB.1 przez rezystory 200R do masy, aktywują się stanem logicznym H. Programator PICKIT3 zapięty klasycznie, zasilanie całości z Meratronik P303 @ 5V/50mA (klauzula minimalnego zaufania)





W projekcie cudów nie ma - pętla główna zmieniająca stan diodek:
;asm-simple-led-1.X/main.asm
main:
      ; init rupieci
      ; STATUS jest mapowany na wszystkie banki!!

      bsf STATUS, RP0 ; bank 1, bo tam konfig portu B TRISB   

      bcf TRISB, LED_1 ; port leda 1 na wyjscie   
      bcf TRISB, LED_2 ; port leda 2

      bcf STATUS, RP0 ; bank 0
      ; stan poczatkowy led
      bcf PORTB, LED_1 ; led off
      bcf PORTB, LED_2 ; led off      
mainLoop:   
      bsf PORTB, LED_1    ; swieci, nie swieci
      bcf PORTB, LED_2

      call delay       ; daj popatrzec
      
      bcf PORTB, LED_1    ; nie swieci, swieci
      bsf PORTB, LED_2

      call delay       ; daj popatrzec
      
      goto mainLoop

Oraz zrobiona na kolanie procedurka opóźniająca, eksploatuje dwa programowe licznik wrzucone gdzieś na początek banku 0.
;asm-simple-led-1.X/main.asm
      ; blok danych w bank 0, wolne mamy od 0x20
      cblock 0x020
delCntr1 : 1   ; programowy licznik   
delCntr2 : 1   ; i kolejny
      endc
      ;
delay:
      movlw 0xFF
      movwf delCntr1
delay_1:   ; petelka zewnetrzna (dekrementuje cntr1)   
      movlw 0xFF      
      movwf delCntr2
delay_2:   ; petelka wewnetrzna (dec na cntr2)
      nop
      decfsz delCntr2,f   ; delCntr2--
      goto delay_2      ; while (delCntr2 != 0)
      decfsz delCntr1,f   ; delCntr1--
      goto delay_1      ; while (delCntr1 != 0)
        return 

Cały projekcik jak ktoś ma ochotę się pobawić jest tu: https://github.com/bienata/picnawode/tree/master/asm-simple-led-1.X
No i oczywiście tradycyjne filmiki - migające lampki oraz sesja z MPLAB-X na żywo, włącznie z zamulaniem IDE, ono bazuje na NetBeans, a te mają swoje problemy...





Aha, ten projekcik da się odpalić pod symulatorem, ładnie widać zmieniające się bity portu B podczas pracy krokowej.

UART, komunikacja szeregowa

Kolejny odcinek grafomanii będzie traktował o komunikacji szeregowej z wykorzystaniem pokładowego UART-a. Napisano o tym w sieci wiele, ja akurat zerkałam na te stronki:

http://www.oz1bxm.dk/PIC/628uart.htm
https://www.teachmemicro.com/serial-usart-pic16f877a
https://www.exploreembedded.com/wiki/Serial_Communication_with_PIC16F877A
serial communication with uart
http://ww1.microchip.com/downloads/en/devicedoc/31029a.pdf

Wprawdzie korzystam z cudzego, no ale niech będzie, że świadomie. Aby nie robić wioski dołączam schemat ideowy piaskownicy jak i fotki. Ledy tym razem są trzy, ponieważ celem ćwiczenia jest spreparowanie programiku, który oprócz realizacji prostego echa (znaczkowej papugi) będzie identyfikował czy ma do czynienia z cyfrą czy literą (zarówno dużą jak i małą) i pokaże to na żółtej lub zielonej lampce. W przypadku odebrania znaczka innego niż wymienione - pozostaje mu lampka czerwona.



Oczywiście nie wszystko na raz, najpierw powstał prosty progamik realizujący znakowe echo:

asm-echo-rxtx-simple-1.X/main.asm  
   processor 16f876
   include 
   ; https://www.tme.eu/Document/c3972e2483251b2e1f702409912d1888/pic16f87x.pdf
   ; ustawienia procesora   
   __config _FOSC_HS & _WDT_OFF & _PWRTE_OFF & _CP_OFF & _BOREN_OFF & _LVP_OFF & _CPD_OFF & _WRT_OFF
   radix dec   ; (!)
   
XTAL_REQ    equ       8000000
BAUD_9600   equ       XTAL_REQ/(16*9600)-1       ; 51      
   
   ; blok danych w bank 0, wolne mamy od 0x20
   cblock 0x020
tempByte : 1   ; znaczek tymczasowy   
delCntr1 : 1   ; programowy licznik   
delCntr2 : 1   ; i kolejny      
   endc      
   
reset_vector:   code 0x0000
   goto main
   ;-------------------------------------------         
application:   code   
main:
   call initUart
mainLoop:
   call getChar   
;   movwf tempByte
;   xorlw 0x0D       ; czy == CR?
;   btfsc  STATUS, Z
;   call sendExtraLF    ; wtedy dodaj LF-a
;   movfw tempByte
   call putChar
   goto mainLoop   
   ;-------------------------------------------
   ; dosłanie LF-a na terminal
sendExtraLF:
   movlw 0x0A
   call putChar
   return
   ;-------------------------------------------            
   ; blokujące wysłanie znaczka z W
putChar:
   bcf STATUS,RP0   ; bank 0   
   movwf TXREG   
   bsf STATUS,RP0   ; bank 1      
putChar_wait:
   btfss TXSTA, TRMT ; while ( TXSTA.TRMT != 1 )
   goto putChar_wait
   bcf STATUS,RP0   ; bank 0      
   return
   ;-------------------------------------------               
   ; blokujące odbieranie znaczka do W
getChar:
   bcf STATUS,RP0   ;   bank 0         
getChar_wait:   
   btfss PIR1,RCIF         ; while ( PIR1.RCIF != 1)
        goto getChar_wait
        movf RCREG,W            ; save received data in W
        return   
   ;-------------------------------------------               
   ; setup portu szeregowego
initUart:   
   bsf STATUS, RP0       ; bank 1

   movlw BAUD_9600       ; szybkość transmisji
   movwf SPBRG
   
   ; włącz nadajnik generator, na highspeed, reszta bitów 0 co daje
   ; transmisje 8bit, asynchroniczną
   movlw (1 << TXEN)|(1 << BRGH)      
   movwf TXSTA

   bcf STATUS, RP0       ; bank 0

   movlw (1 << SPEN)|(1 << CREN); włącz UART
   movwf RCSTA
       
   ; to do zastanowienia czy z sensem
   call delay   
   ; ślepe odczyty zaległości z fifo
   movf RCREG,W   
   movf RCREG,W
   movf RCREG,W   
   return   
   ;-------------------------------------------                  
delay:
   movlw 0xFF
   movwf delCntr1
delay_1:; petelka zewnetrzna (dekrementuje cntr1)   
   movlw 0xFF      
   movwf delCntr2
delay_2:; petelka wewnetrzna (dec na cntr2)
   nop
   decfsz delCntr2,f   ; delCntr2--
   goto delay_2      ; while (delCntr2 != 0)
   decfsz delCntr1,f   ; delCntr1--
   goto delay_1      ; while (delCntr1 != 0)
        return    
   
   end

Powyższe jest w pewnym sensie średnią z detali dostępnych na wskazanych stronkach, pogrupowałam całość w trzy główne procedurki: initUart,getChar,putChar, delay już znamy. No i tu mpasm pokazał pazury, a w pewnym momencie cztery litery. Wyliczanie stałej dla szybkości transmisji to był zonk. Proszę, niby proste (pomijając literówkę - miało być FREQ, oczywiście, sorki):
XTAL_REQ    equ       8000000
BAUD_9600   equ       XTAL_REQ/(16*9600)-1  
Sztuczka polega na tym, że gdy zerkniemy na ustawienia IDE: Project Properties -> Conf -> mpasm to znajdziemy tam:
Default radix : HEX, co oznacza, że wszelkie stałe numeryczne pozbawione prefiksu (czy 0x, D', B') są traktowane jako szesnastkowe. Łatwo zgadnąć, że w takim wypadku powyższe wyliczenia dadzą wartości z koziej trąbki, co nie zmieni faktu, że program się skompiluje, załaduje, tylko nie zadziała. Oj nagoniłam się chwilę zanim oko me nie zawisło na dyrektywie assemblera radix, która mówi jak narzędzie ma domyślnie potraktować liczby bez żadnych ozdobników.
Następna rzecz, której nie jestem do końca jeszcze pewna to końcówka inicjalizacji uarta, a mianowicie delay i potem kilka pustych odczytów, aby wysycić fifo, PIC-owy Uart posiada FIFO (dwa znaczki) więc jakiś sens to ma, ale pewnie zalety objawią się, gdy urządzenie nadawcze będzie już coś wysyłało, podczas gdy systemik z PIC-em dopiero się ogarnia.
No i z ewidentnie słabych stron, to zabrakło mi oprogramowania błędów odbierania, overrun i framig error, no tak. Programik testowałam w warunkach cieplarnianych, strona nadawcza (mój PC) raczej-*** nie wycinała numerów. Filmik z działania tej wersji mamy poniżej.




I tu taka ciekawostka - GTK Term na skutek ENTER wysyła tylko znaczek CR. No i linijki się nie łamały w okienku. Rozwiązanie proste - dorobić wysyłanie ekstra znaczka LF gdy przyjdzie CR, wtedy jest dobrze. Wiem, że jest w opcjach `auto cr-lf`, ale wtedy nie było by tej odrobiny prostej algorytmiki. Ten fragment kodu mamy w pętli głównej chwilowo objęty komentarzem, na filmie są oba.

Z całego tego programiku najciekawsze jest ciągłe żaglowanie bankami, normalnie masakra aby się nie pomylić, ale autentycznie zaczyna się pamiętać, w którym co siedzi, choć dlaczego akurat tak rozsypali SFR-y po przestrzeni adresowej - to jest ciągle dla mnie zagadka.

Cały projekt dla MPLAB-X: https://github.com/bienata/picnawode/tree/master/asm-echo-rxtx-simple-1.X

*** kluczowe jest słowo - raczej
No więc właśnie, po wczorajszych śwatełkowych zbytkach, prosto z trasy i mocno po północy - myślę sobie: a coś jeszcze podłubię, nie szło klecenie więc w bety. Od rana do komputera i brzdęk! Coś co wcześniej - dam sobie głowę uciąć - działało, rano szaleje. UART zachowywał się idiotycznie, w sensie nie reagował w ogólne na nadchodzące znaki lub po odebraniu jednego - przestawał reagować i tylko jego zwracał, masakra z elementami paniki. Faktem jest, że płytka stykowa została pod prądem (i z podłączonymi próbnikami logicznymi), nie porozłączałam USB od PICKIT-a, a system zahibernowałam zamiast zamknąć, możliwe że to wprowadziło całość w jakiś dziwny stan i nawet clean+build i przeprogramowanie kostki nie dawało rezultatu. I tu powstało zagadnienie - nie mam pewnego, mega-prostego kawałka softu, którym mogę niezależnie zweryfikować czy cześć sprzętowa jest ok. No i łatwo zgadnąć - tu przydało się C, na kolanie sklecony ratunkowy program wygląda tak:
c-echo-rxtx-simple-1.X/main.c
#define _XTAL_FREQ 8000000
#include 
#include 

#pragma config FOSC = HS       
#pragma config WDTE = OFF       
#pragma config PWRTE = OFF     
#pragma config BOREN = ON       
#pragma config LVP = OFF       
#pragma config CPD = OFF       
#pragma config WRT = OFF       
#pragma config CP = OFF         

void putch( char data ){         
    TXREG = data;
    while( TRMT == 0 );
}

unsigned char getch( void ){                         
    while( RCIF == 0 );
    return RCREG;
}
 
void main( void ) {
    unsigned char c;
    TRISB = 0;  // port out
    PORTB = 0;  // ledy off
    SPBRG = 51; //9600 @ 8MHz
    TXEN=1; // nadajnik on
    BRGH=1; // baudy na high
    SPEN=1; // serial on
    CREN=1; // cont.rec. on
    while( 1 ) {
        c = getch();
        PORTB = c & 0x07; // pokaż, że żyjesz
        putch ( c );
   }
}
Program realizuje echo i mruga lampkami, to taki test czy chiński PICKIT3 oraz biedny mały PIC w ogóle jeszcze żyją, filmik pamiątkowy:



I kolejny projekt dla MPLAB-X https://github.com/bienata/picnawode/tree/master/c-echo-rxtx-simple-1.X

Totalny restart całego mojego majdanu na szczęście pomógł, dalsza zabawa to zgodnie ze wstępem - rozróżniaczka do cyferek i literek. Zaczęłam coś tam szkicować w assemblerze i powstało zagadnienie porównywania wartości numerycznych, a do obsłużenia są trzy zakresy wartości kodów ASCII - "0"..."9", "a"..."z" oraz "A"..."Z" no i to co pomiędzy nimi. I tak goglując wpadłam na bardzo ciekawe opisy różnych implementacyjnych przydasiów, proszę: http://picprojects.org.uk/projects/pictips.htm
A to spodobało mi się najbardziej: mpasm posiada garść pseudo instrukcji (12-Bit/14-Bit Instruction Width Pseudo-Instructions) ponieważ to mega ułatwienie moim zdaniem. http://picprojects.org.uk/projects/pseudoins.htm

BTW, chwila poszukiwań wykazała, że takie instrukcje są dostępne w burżuazyjnych kostkach 18F, małe bidy mają to udawane na poziomie translatora. Finalnie wyszedł mi niezbyt skomplikowany programik, ale pokręceń nieco tam jest, pętla główna:
asm-echo-rxtx-sel-led-1.X/main.asm
mainLoop:
   call getChar   
   movwf tempByte       ; na bok
   
   ; zgas poprzednie stany ledów
   movlw 0x00
   movwf PORTB
   
   ; badamy kolejno - czy cyfry
   movlw "0"
   movwf asciiMin
   movlw "9"
   movwf asciiMax
   call  isCharInRange ; sprawdzenie 
   bz indicateDigit
   ; jak nie, to czy dolne literki
   movlw "a"
   movwf asciiMin
   movlw "z"
   movwf asciiMax
   call isCharInRange ; sprawdzenie 
   bz indicateAlpha
   ; a duże litery?
   movlw "A"
   movwf asciiMin
   movlw "Z"
   movwf asciiMax
   call isCharInRange ; sprawdzenie 
   bz indicateAlpha
   ; no to nie wiem, czerwony OTHER led
   bsf PORTB, OTHER_LED
main_sendBack:
   ; odeslij znaczek
   movfw tempByte
   call putChar   
   goto mainLoop   ; while ( 1 )
   ; a tu zaczyna się spagetti   
indicateDigit:   
   bsf PORTB, DIGIT_LED   
   goto main_sendBack
indicateAlpha:   
   bsf PORTB, ALPHA_LED
   goto main_sendBack
No i procedurka rozpoznająca znaki i jej zmienne robocze:
asm-echo-rxtx-sel-led-1.X/main.asm
       cblock 0x020
    tempByte : 1   ; znaczek tymczasowy      
    asciiMin : 1   ; zmienne procedurek badających zakres
    asciiMax : 1    ; wejściowego kodu ASCII
       endc      

asm-echo-rxtx-sel-led-1.X/main.asm
    isCharInRange:
       movfw  asciiMin
            subwf  tempByte,W     
       bc isCharInRange_gteq_min
       ; skasuj Z, znaczek poniżej zakresu
       clrz
       return
    isCharInRange_gteq_min:
           ; to teraz czy < max+1
           movfw  asciiMax
       addlw  1   ; +1  aby domknąć przedział
            subwf  tempByte,W     
       bnc isCharInRange_lt_max
       ; skasuj Z, znaczek powyżej zakresu max
       clrz
       return
    isCharInRange_lt_max:
       setz      ; znaczek w domknietym przedziale
       return          ; wychodzimy z Z := 1
Procedurki putChar, getChar i reszta jak w poprzednim programie, a tak wygląda całość na żywo:



W podsumowaniu odcinka - na chwilę obecną moje zdanie jest takie, że mpasm jest całkiem sprytnym narzędziem, a mnemoniki typu btfss można nawet polubić. W końcu `ipmdbk butelka,2` rozwinięte do `idź po mleko do Biedry, kup 2 butelki` nie budzi wątpliwości...

Do trzech razy sztuka, ostatni dziś zatem projekt dla MPLAB-X https://github.com/bienata/picnawode/tree/master/asm-echo-rxtx-sel-led-1.X a kolejna opowiastka będzie o ...

SPI z PIC16F876 vs układ DAC MCP4802

No właśnie, czyli podobnie jak w przypadku niegdysiejszej historii o Księżniczce Motoroli - szukam po pudełkach, co by tu do małego PIC-a fajnego podłączyć, oprogramować i pokazać jak działa. Z okazji segregowania próbek (serio, bez kitu, czasem coś układam) wpadła w ręce listewka z kostkami MCP4802 - to bardzo przyjemne w wykorzystaniu przetworniki cyfrowo-analogowe, sympatyczne o tyle, że mają wbudowane "okrągłe" źródło napięcia odniesienia 2.048V i nie wymagają symetrycznego zasilania. A to, że rozmawiają po SPI daje mi teraz możliwość przetestowania z czym się zajada ten interfejs w plebejskich PIC-ach 16F. Dokumentacja przetwornika tutaj: http://ww1.microchip.com/downloads/en/devicedoc/20002249b.pdf a schemat instalacji po koniecznych modyfikacjach poniżej.



Na potrzeby interfejsu SPI oddelegowano w PIC-u końcówki RC3 jako SCK, RC5 - SDO oraz (tu nieużywane) RC4 dla SDI. U mnie PIC pracuje jako master, więc w ruchu są SCK i SDO, do wybierania układu podrzędnego służy mi sterowany niezależnie bit RB3. Z programowego punktu widzenia wiele przy SPI do roboty nie ma - po ogarnięciu kierunku portu C (TRISC), należy skonfigurować rejestry SSPSTAT (krytyczna jest wartość CKE) oraz SSPCON (tu tylko włączyć całość bitem SSPEN). Jeżeli nasze urządzenie peryferyjne da czasowo radę - możemy wykorzystać domyślne ustawienia SSPM3:SSPM0 dające maksymalna szybkość zegara SPI - Fosc/4, przykładowy DAC-ek 4802 śmiga cudnie. Więcej teorii o SPI w dokumencie, zapraszam do poczytania:
http://ww1.microchip.com/downloads/en/devicedoc/spi.pdf

No i oczywiście strzępki kodu - inicjalizacja i wysłanie na DAC wartości dla kanałów A i B:
asm-mcp4802-dac-1.X/main.asm
   cblock 0x020
valDacA : 1   ; DAC A
valDacB : 1   ; DAC B   
   endc
   ....
   banksel TRISB   
   clrf TRISB       ; port B na out
   ; piny SPI, czytaj 9.3.3 ENABLING SPI I/O
   bcf TRISC, RC5       ; RC.5 (SPI SDO)
   bsf TRISC, RC4       ; RC.4 (SPI SDI), nie korzystam, ale...
   bcf TRISC, RC3       ; RC.3 (SPI SCK)      
   ; SPI
   movlw (1 << SMP)|(1 << CKE)
   movwf SSPSTAT   
   banksel SSPCON   
   movlw (1 << SSPEN)
   movwf SSPCON       ; domyślne   
   ; /CS DAC-ka na H
   bsf PORTB, MCP4802_CS

   movlw 0xAB
   movwf valDacA
   movlw 0xCD
   movwf valDacB
   call updateDAC

Procedurka updateDAC wysyłająca dane do DAC-a:
asm-mcp4802-dac-1.X/main.asm
   ; ustawia kanały A,B ze zmiennych valDacA, valDacA
updateDAC:
   ; kanal A
   ; transakcja  /CS := L
   bcf PORTB, MCP4802_CS
   ; przekładka bitów
   swapf valDacA, W    ; b74<->b30
   movwf valTemp       ; przełożone na bok, przyda się
   andlw 0x0F       ; tylko najmłodsze
   iorlw DAC_CH_A|DAC_SHDN ; polecenie DAC /SHDN := 1 dla A
   call writeSPI       ; MSB (komenda i kawałek danej)
   movfw valTemp       ; daj po przekładce
   andlw 0xF0       ; zostaw górny nib, dolne zera
   call writeSPI       ; LSB (dolny kawałek danej)
   ; domknij transakcje SPI /CS := H
   bsf PORTB, MCP4802_CS   
   ; kanal B
   ; transakcja  /CS := L
   bcf PORTB, MCP4802_CS
   ; przekładka bitów
   swapf valDacB, W    ; b74<->b30
   movwf valTemp       ; przełożone na bok, przyda się
   andlw 0x0F       ; tylko najmłodsze
   iorlw DAC_CH_B|DAC_SHDN ; polecenie DAC /SHDN := 1 dla B
   call writeSPI       ; MSB (komenda i kawałek danej)
   movfw valTemp       ; daj po przekładce
   andlw 0xF0       ; zostaw górny nib, dolne zera
   call writeSPI       ; LSB (dolny kawałek danej)
   ; domknij transakcje SPI /CS := H
   bsf PORTB, MCP4802_CS   
   return

Tu dwa słowa - jak zerkniemy w dokumentację do przetworników MCP48xx (strona 22) to zauważymy w jak dziwny (ale i spójny) sposób pozycjonowane są dane dla DAC w ramce SPI - a mianowicie po odbębnieniu czterech bitów sterujących (wybór kanału, wzmocnienie, itp ) zaczyna się sekcja danych, od MSB począwszy. Dla wypasionego 12-bitowego DAC-ka nie ma tu nic szczególnego, po prostu cztery zupełnie najstarsze bity są młodszym nibblem pierwszego bajtu (MSB), osiem niższych bitów DAC to kolejny bajt danych, ten LSB.
W przypadku wersji 8-bitowej (jak tutaj maltretowana) jest śmiesznie, bo bajt wejściowy dla DAC trzeba rozsmarować po 16-bitowej ramce SPI tak, aby jego starsze cztery bity stały się młodszymi pierwszego (MSB) bajta ramki, młodszy nibble DAC trzeba utknąć jako starszy w bajcie LSB. Kołomyja normalnie, ale z pomocą przychodzi nam instrukcja swapf robiąca bitowego przekładańca, no super. Oczywiście sterowanie /CS-em przetwornika na piechotę, przy okazji ten sygnał można wykorzystać do pomierzenia ile trwa tu cała transakcja SPI (dla najszybszego zegara ~20us, nieźle). No i oczywiście - surowy zapis bajta do SPI z oczekiwaniem, na koniec wysyłki z bufora szeregowego.
asm-mcp4802-dac-1.X/main.asm
   ; nadaje bajta z W i czeka aż wyśle
writeSPI:
   movwf SSPBUF   
   bsf STATUS, RP0       ; bank 1         
writeSPI_wait:
   btfss SSPSTAT, BF    ; skonczyleś transmisje?
   goto writeSPI_wait  ; neee
   bcf STATUS, RP0       ; bank 0            
   return
Wszystko to poskładane w całość daje programik w miarę sensownie sterujący układem MCP4802, zdjęcie z pierwszych eksperymentów gdzie dla wartości 0xFF przetwornik zwraca 4.096V, co w sumie cieszy:



Skoro DAC jest skłonny współpracować, dalsze eksperymenty to oczywiście generowanie przebiegów okresowych na podstawie definicji w postaci serii stałych z ROM. No, teraz rozumiem skąd te wszystkie narzekania na stronicowanie pamięci w PIC (głównie małych). Pobranie zawartości komórki wskazanej innym rejestrem dla takiego na przykład Z80 jest banalne i raczej intuicyjne:
ld HL, tabela
   ld A,(HL)
tabela:
   db 0xAA, 0xBB, 0xCC
W PIC16 niestety trzeba się deko pomęczyć. Zagadnienie przechowywania stałych w ROM jest dość popularne, googlując za np. "assembly PIC16F lookup table" dostaniemy sporo do poczytania, ja osobiście polecam dokument prosto od źródła wszelkiego zła, czyli notę aplikacyjną http://ww1.microchip.com/downloads/en/appnotes/00556e.pdf oraz garść ciekawych wskazówek implementacyjnych:
https://phanderson.com/PIC/16C84/mult_string.html a także http://www.piclist.com/techref/microchip/tables.htm

No to robimy tego sinusa. Wartości próbek przebiegu wygenerowałam sobie na poczekaniu stronką (Sine Lookup Table Generator Calculator), w kodzie opakowane są dyrektywą DT assemblera. Funkcyjka zwracająca przy każdym wywołaniu kolejną próbkę wygląda u mnie następująco:
asm-mcp4802-dac-1.X/main.asm pisze:
getSineSample:
   movf sineHiPtr, W         
    movwf PCLATH       ; górny adres
    movf sineLoPtr, W   ; dolny
    incf sineLoPtr, f   ; ++
    skpnz          ;
    incf sineHiPtr, f   ; hi++ tylko gdy zawinęła się strona (lo)   
    movwf PCL           ; skok na bungee
SINE_DATA:   
   dt 128,134,140,146,152,158,165,170
   dt 176,182,188,193,198,203,208,213
   dt 218,222,226,230,234,237,240,243
   dt 245,248,250,251,253,254,254,255
   dt 255,255,254,254,253,251,250,248
   dt 245,243,240,237,234,230,226,222
   dt 218,213,208,203,198,193,188,182
   dt 176,170,165,158,152,146,140,134
   dt 128,121,115,109,103,97,90,85
   dt 79,73,67,62,57,52,47,42
   dt 37,33,29,25,21,18,15,12
   dt 10,7,5,4,2,1,1,0
   dt 0,0,1,1,2,4,5,7
   dt 10,12,15,18,21,25,29,33
   dt 37,42,47,52,57,62,67,73
   dt 79,85,90,97,103,109,115,121
Do kompletu jest blok zmiennych roboczych oraz procedura je inicjująca:
asm-mcp4802-dac-1.X/main.asm
   cblock 0x020
sineHiPtr : 1   
sineLoPtr : 1      
sineSampleCntr : 1
   endc
   ...
initSinePointers:
   clrf sineSampleCntr
   movlw high SINE_DATA       
    movwf sineHiPtr           
   movlw low SINE_DATA       
    movwf sineLoPtr              
   return
I teraz tak: zmienne sineHiPtr/sineLoPtr jako para stanowią w istocie szesnastobitowy licznik wskazujący aktualną próbkę do zwrócenia. Sztuka polega na tym, aby znieczulić procedurę zwracającą dane na fakt, że idąc po kolejnych próbkach właśnie minęliśmy podział stron (cześć LO wykręciła się z FF nazat na 00). Zauważmy, że inkrementacja zmiennej sineLoPtr w przypadku przekręcenia się na 0 będzie skutkowała inkrementacja sineHiPtr. Wprawdzie w tym, n-tym obiegu wiele to nie wniesie, bo mamy już zapisany adres w PCLATH (program counter latch high), ale następna iteracja dostanie już adres wskazujący kolejna stronę, całość będzie szła liniowo do końca tabeli. Oczywiście ilość iteracji kontrolujemy na zewnątrz - u mnie to zmienna sineSampleCntr, bo nie mogłam wykorzystać `magicznej wartości` np. 0x00 jak dla napisów tekstowych. Samo zwracanie danych do rejestru W też jest przedziwne, w dokumentacji mpasm http://ww1.microchip.com/downloads/en/devicedoc/33014j.pdf rozwija się do rozkazu RETLW ze argumentem w postaci wymaganej akurat stałej. Jeden taki elementarny `return NN` per bajt danych. No a jak tam skoczyć? Ano wpisując na żywioł do PCL dolny adres docelowy rozkazem `movwf PCL`. Oczywiście gdy starsze bity adresu będą chybione - program pójdzie w buraki, bo powyższe to nie jest tylko odczyt danych, to jest przekazanie sterowania skutkujące zwróceniem wskazanej wartości.

Mając funkcyjkę do próbek sinusa i obsługę przetwornika C/A można sobie tego sinusa wyświetlić na oscyloskopie, taką na przykład pętelką:
asm-mcp4802-dac-1.X/main.asm
   ; reset wskaźników
   call initSinePointers
mainLoop:
   call getSineSample  ; probka do W
   movwf valDacA       ; oba kanały
   movwf valDacB       ; to samo
   call updateDAC
   incf sineSampleCntr   ; cntr++
   movlw 128
   subwf sineSampleCntr, W
   btfsc STATUS, Z      ; gdy != 128
   call initSinePointers   ; pomin reset wskaźników
   goto mainLoop   
Inicjujemy wskaźniki na start, w pętli wybieramy kolejne próbki i podajemy na DAC, póki co na oba kanały naraz.
W każdym obiegu sprawdzamy, czy może doszliśmy do ostatniej próbki, gdy tak - resetujemy wskaźniki i kręcimy się dalej. Finalnie daje to ciągły, niezakłócony przebieg wyjściowy, jak na zdjęciach poniżej.



Na pamiątkę mamy też zdjęcie z trudnych początków (uzyskałam prawie jak sinus) oraz pokazanie, że przebiegi z kanałów A oraz B przetwornika w tej implementacji są odrobinę przesunięte w fazie. To przesunięcie wynika z sekwencyjnego odświeżenia wartości w kanałach, dzieje się to megaszybko ale drobne zmulenie jest. Gdy takie zjawisko jednak nam przeszkadza to należy wykorzystać sygnał /LDAC przetwornika MCP4802 i nim kontrolować symultaniczny wpis obu wartości, ot drobiazg taki. Kompletny projekt testowy dla MPLAB tu: https://github.com/bienata/picnawode/tree/master/asm-mcp4802-dac-1.X

Taka mnie myśl za rękaw capła, że programowanie PIC-ów w asseblerze wyzwaniem pewnym jest, nie przeczę. I oprzeć się nie mogę pokusie porównania tego całego szaleństwa do pole-dance. Co się poobijam, siniaków i odcisków narobię czy zakwasów dostanę to moje własne i nikt mi juz tego nie zabierze. A można przecież na poślad nalepić sobie plasterek slim-patch z napisem "C-lang", leżeć i pachnieć i będzie to samo. Z dokładnością do satysfakcji z każdego, choćby najdrobniejszego progresu. Ot, refleksja taka...

Tematem kolejnej dobranocki jest:

okiełznanie modułu wyświetlacza matrycowego 8x8 z układem MAX7219

Zacznę typowo, czyli od wskazania dokumentacji: https://datasheets.maximintegrated.com/en/ds/MAX7219-MAX7221.pdf i tam tak naprawdę jest wszystko co potrzeba do oswojenia tej kostki.
Z moich spostrzeżeń to takie, że na ogólnie pojętym rynku mamy dwa konkurencyjne wydania modułków matrycowego LED i tegoż układu sterującego (przykłady z lokalnych źródeł, na Aliexpress tego od cholery mają):
a) https://www.gotronik.pl/modul-wyswietlacza-matrycy-led-matrix-8x8-max7219-p-4874.html
b) https://www.gotronik.pl/modul-wyswietlacza-matrix-8x8-sterowanego-ukladem-max7219-p-2353.html

W pogadance występuje wersja A, z kostką w DIP na wierzchu, ponieważ właśnie kilka takich modułów sprawiłam na zajęcia z Arduino mojej zbójeckiej szkolnej gromadce i ostał mi się jeden, ostatni. Na nich można było wyświetlać emotikony, tajne znaczki i inne bezeceństwa, modułki rozlazły się więc, że tak ujmę błyskiem, ale na zdrowie. Konstrukcja mechaniczna jest wygodna do tymczasowych podłączeń, bo wszystko ładnie opisane i wyprowadzone, do edukacji jak znalazł.
Wersja B jest praktyczniejsza jeżeli chcemy budować większą matrycę, ponieważ mamy dużą swobodę w orientowaniu modułu - jest na obrysie kwadratu i można go obracać o 90'. To polecam to poważniejszych zastosowań.

Do komunikacji z układem wykorzystamy sprzętowe SPI, znane juz z DAC-ka MCP4802 i w sumie na tym czary się kończą. Pozostaje inicjalizacja układu MAX7219 zgodnie z wytycznymi przedstawionymi w dokumentacji, ja do tego celu spreparowałam sobie makro-przydasia setMAX7219reg
asm-max7219-led-1.X/main.asm
setMAX7219reg  macro   register, value
       bcf PORTB, MAX7219_CS      
       movlw register
       call writeSPI      
       movlw value
       call writeSPI      
       bsf PORTB, MAX7219_CS      
       endm
oraz przepisaną z dokumentu garstkę stałych:
asm-max7219-led-1.X/main.asm
MAX7219_DecodeMode  equ       0x09 
MAX7219_Intensity   equ       0x0A
MAX7219_ScanLimit   equ       0x0B
MAX7219_Shutdown    equ       0x0C 
MAX7219_DisplayTest equ       0x0F 
przez co zainicjowanie układu jest dość klarowne, równie czytelne jest wpisanie wartości do rejstrów danych:
asm-max7219-led-1.X/main.asm
   ; normalna praca (0) a nie test lampek (1)
   setMAX7219reg MAX7219_DisplayTest, 0
   ; bez dekodowania, mapowanie 1:1
   setMAX7219reg MAX7219_DecodeMode, 0   
   ; jasność niewielka (0..7)
   setMAX7219reg MAX7219_Intensity, 1
   ; obsługuj wszystkie linie (0..7)
   setMAX7219reg MAX7219_ScanLimit, 7
   ; włącz sterownik (0/1)
   setMAX7219reg MAX7219_Shutdown, 1
    . . .
   setMAX7219reg 1, b'11111111'
   setMAX7219reg 2, b'10000001'   
   setMAX7219reg 3, b'10000001'
   setMAX7219reg 4, b'10000001'   
   setMAX7219reg 5, b'10000001'
   setMAX7219reg 6, b'10000001'   
   setMAX7219reg 7, b'10000001'
   setMAX7219reg 8, b'11111111'
Oczywiście początki były z sękami, ale w końcu udało się skomunikować z układem, wersja fotograficzna poniżej:



Skoro można wyświetlić jedną `bitmapkę` to można i kilka, ich sekwencja da nam prymitywną, ale w sumie sympatyczną animację, tak wygląda na żywo:



a kompletny projekt dla MPLAB-X znajdziemy tu: https://github.com/bienata/picnawode/tree/master/asm-max7219-led-1.X

Następny krok to już deko bardziej skomplikowana zabawka - wyświetlanie ruchomych napisów. No cóż, mam jeden wyświetlacz 8x8 więc za bardzo nie poszalejemy, ale od czegoś zacząć trzeba. Chyba od rozpoznania, jaka jest orientacja bitowo-geometryczna naszego modułku, bo to będzie rzutowało na dalszą implementację przesuwającego się jak w autobusie napisu. Wszystko mówi zdjęcie:



Prawa dolna kropka to bit zerowy zerowego rowka danych, będzie to odpowiadało zerowemu elementowi ośmiobajtowej zmiennej screenBuff zadeklarowanej w programie. No i właśnie - program. Sztuczka polega na tym, aby mieć w pamięci bufor odzwierciedlający zawartość wyświetlacza (screenBuff) oraz funkcję updateDisplay, która jego zawartość przepisze na fizyczną matrycę, niech wygląda to następująco:
asm-max7219-led-2.X/main.asm
   cblock 0x020
   ; bufor ekranowy
   screenCntr : 1
   screenBuff : 8   
   endc   
   ; . . .
   ; przepisuje obszar screenBuff na matrycę
updateDisplay:
   movlw screenBuff
   movwf FSR       ; wskaźnik na ekran
   movlw 1
   movwf screenCntr       ; licznik pasków := 1
updateDisplay_continue:   
   bcf PORTB, MAX7219_CS      
   movfw screenCntr
   call writeSPI          ; adres paska
   movfw INDF          ; screenBuffer [ screenCntr ]
   call writeSPI          ; zawartośc paska
        bsf PORTB, MAX7219_CS      
   incf screenCntr          ; adres dla MAX ++
   incf FSR          ; cntr++
   movlw screenBuff+8       ; czy koniec bufora?
   subwf FSR, W
   bnz updateDisplay_continue
   return
Powyższy okruch kodu to przykład wykorzystania adresowania pośredniego w PIC16F, mega fajowy wynalazek, który ułatwia iterowanie po dostępnej wewnątrz PIC-a pamięci RAM. A mianowicie mamy dwa magiczne rejestry - FSR, do którego wpisujemy (jak do każdego innego rejestru) wartość liczbową, ona będzie adresem komórki pamięci gdy odwołamy się do niej odczytując/zapisując/przeliczając rejestr INDF. INDF to takie jakby *FSR - chyba na miejscu jest porównanie do C-owego operatora wyłuskania. To umożliwia nam wyliczenie lokalizacji na której chcemy wykonać żądaną operację, tu jest to tylko odczyt - iterujemy po elementach buforka matrycy celem wysłania ich via SPI do układu MAX.

Aby zrealizować płynący napis, potrzeba nam jeszcze procedurki, która jakby przewinie bufor wyświetlacza, zastępując N+1 element N-tym, a finalnie robiąc miejsce na nową wartość (w miejscu zerowego elementu), która pojawi się z prawej, skrajnej strony matrycy. No, przyznaję z tej procedury dumna nie jestem, jest ciosana siekierką normalnie, ale byłam tak omamiona wizją działającej aplikacji, że odstawiłam przyznaję bez bicia - wioskę. Ale jakby nie patrzeć - działającą.
asm-max7219-led-2.X/main.asm
; przepisuje paski N na N+1
scrollBuffer:
      movfw screenBuff+6
   movwf screenBuff+7
       movfw screenBuff+5
       movwf screenBuff+6
       movfw screenBuff+4
   movwf screenBuff+5
       movfw screenBuff+3
       movwf screenBuff+4
   movfw screenBuff+2
   movwf screenBuff+3
       movfw screenBuff+1
       movwf screenBuff+2
   movfw screenBuff+0
   movwf screenBuff+1
   return
Sama pętla główna nie jest specjalnie skomplikowana, ot pozyskanie kolejnego bajta napisu (bannera) do wstawienia do bufora, przewinięcie bufora, odświeżenie matrycy i kontrola czy poszły na wyświetlacz wszystkie dane. Jak tak - reset wskaźników i zabawa od nowa.
asm-max7219-led-2.X/main.asm
call initBannerPointers
loop:   
   call scrollBuffer   ; item + 1 := item, miejsce na 1 wpis   
   call getBannerItem
   movwf screenBuff+0   ; zapisz 1-szy element
   call updateDisplay   ; wyslij na MAX-a
   call delay
   incf bannerItemCntr   ; cntr++
   movlw BANNER_DATA_END-BANNER_DATA
   subwf bannerItemCntr, W
   bnz loop      ; gdy jeszcze nie koniec
   call initBannerPointers   ; reset wskaźników   
   goto loop
Pokazywałam na zdjęciu, jak wygląda bitowa organizacja wyświetlacza i szybko okazało się, że definiowanie napisów z wykorzystaniem na większą skalę dyrektywy DT i notacji binarnej doprowadzi mnie do spazmów. Panaceum jest zdefiniowanie sobie zestawu makr odpowiadających kolejnym kształtom znaków czy emotków, dopiero ich wywołania definiują nam zawartość napisu:
asm-max7219-led-2.X/main.asm
FONT_M   macro
   dt b'01111111'      
   dt b'00100000'      
   dt b'00010000'   
   dt b'00100000'      
   dt b'01111111'   
   dt b'00000000'      
   endm
   ...
   FONT_M
   FONT_I
   FONT_C
   FONT_R
   FONT_O
   FONT_G
   FONT_E
   FONT_E
   FONT_K
   FONT_SPACE
   ...
Oczywiście naturalną konsekwencją w/w podejścia jest powstanie procedurki tłumaczącej ciąg znaków ASCII na sekwencję adresów ze wzorcami bitowymi kolejnych znaków, ale to może później. Za to testowy programik wyświetlający pozdrowienia mamy na filmiku:



A drugi komplet źródełek dla MPLAB znajduje się tu: https://github.com/bienata/picnawode/tree/master/asm-max7219-led-2.X

Temat napisu reklamowego wydaje się mieć spory potencjał, tym bardziej, że z klasowej wystawki wypożyczono mi (sic!) na kilka dni dwa moje własne modulki ledowo-matrycowe, mam zatem szansę zmierzyć się z oprogramowaniem tego o mało co bannera z LED i z paroma ciekawostkami translatora mpasm, niejako przy okazji. Zdjęcie banerka poskładanego z trzech modułów mamy poniżej, załączam też focię jednego ze zdjętą matrycą diodową, chcę pokazać jak opisane są konektory modułu.



Kaskadę (czy jak kto woli łańcuch) robimy tak, że sygnały SCK, /CS są wspólne, a DOUT n-tego modułu łączymy z DIN modułku n+1. Oczywiście dane wprowadzamy na DIN pierwszego dostępnego. Programowych podejść do kaskady jest kilka, jedni wykorzystują technikę "ślepych zapisów" wartości no-op (0x00) i w ten sposób trafiają ramką danych w wybrany moduł. Inne podejście to potraktowanie kaskady jako 16*N bitowego rejestru przesuwnego i ja takie właśnie zastosowałam.
Ułatwia to w/g mnie bardzo konfigurowanie kaskady, po prostu wpisujemy N razy to samo do wszystkich ciurkiem. Natomiast upierdliwe nieco staje się ładowanie danych, bo musimy oprogramować mapowanie pomiędzy obrazem danych w RAM (bufor ekranowy) a zawartością rejestrów w kaskadzie. Ale to jest wykonalne, zaraz zobaczymy.

makra i modernizacje

Zacznę od najprostszej sprawy - wyrzucenia definicji znaczków do osobnego pliku i dołączania go do głównego programu dyrektywą include i to nawet ładnie działa, a w głównym pliku jest jakby mniejszy śmietnik. Kolejna rzecz to podglądnięty gdzieś na PIC-owym forum patent, aby makra nazywać w specyficzny sposób np. prefiksujac jakimś wybranym (ale dopuszczonym przez mpasm) znaczkiem. U mnie padło na małpkę (@) i w sumie to sama już nie wiem, czy to nie jest udziwnienie. W kodzie niby widać od razu, że symbol jest czymś nietypowym - tu: makrem i nie wykonam pod to skoku goto ani nie użyje jako argumentu do call. Inna sprawa, że to od razu wyrzuci błędy translacji więc poprawienie też nieskomplikowane. Tak więc @ w makrach zostaje ale tymczasowo. Definicje znaczków jak łatwo zgadnąć wyglądają tak:
asm-max7219-led-banner-1.X/chargen.inc
@CHAR_9   macro
   dt b'00110010'   
   dt b'01001001'         
   dt b'01001001'         
   dt b'01001001'      
   dt b'00111110'   
   dt b'00000000'   
   endm
@CHAR_2   macro
   dt b'00100111'   
   dt b'01001001'      
   dt b'01001001'   
   dt b'00110001'
   dt b'00000000'   
   endm
   . . .
a znaczki same w sobie choć szpetne, to mają specyficzny urok...takiej prowizorki. Wykorzystanie definicji makr znakowych (z małpką) takowe:
asm-max7219-led-banner-1.X/main.asm
BANNER_DATA:   
   @CHAR_H
   @CHAR_E
   @CHAR_L
   @CHAR_L
   @CHAR_O
      @CHAR_SPACE
BANNER_DATA_END:
Grubsza modyfikacja spotkała makro do inicjowania układu MAX7219, a to z racji tego, że mamy kaskadę całą, a nie jedną sztukę, małpka w nazwie to przy tym pikuś. Popatrzmy na fragment kodu:
asm-max7219-led-banner-1.X/main.asm
       ; ustawia rejestr kostki dla zadanej ilości w łańcuchu
@setMAX7219reg  macro   register, value, how_many_modules
       variable n
n = 0
       bcf PORTB, MAX7219_CS            
       while n < how_many_modules
      movlw register
      call writeSPI      
      movlw value
      call writeSPI             
n = n + 1   ; kolejny modułek
       endw
       bsf PORTB, MAX7219_CS            
       endm
O, i to jest megafajny wynalazek - możliwość wykonywania obliczeń i aktywnego oddziaływania ich wynikami na przebieg translacji. Powyższe makro ma za zadanie wygenerowanie tylu par zapisów SPI (bajt rejestru, bajt danych) ile jest modułków w łańcuchu, pisałam wyżej - traktuje całość jako dłuuuuugi rejestr. Deklarujemy zatem lokalną zmienną, inkrementujemy ją w kontrolowanej porównaniem pętelce (na poziomie translacji), w efekcie makro rozwinie się do tylu par `movlw + call writeSPI` ile nakażmy parametrem `how_many_modules`, przykładowo tak:
asm-max7219-led-banner-1.X/main.asm
         MODULES_NO       equ       3
         . . .
         ; normalna praca (0) a nie test lampek (1)
         @setMAX7219reg MAX7219_DisplayTest, 0, MODULES_NO
Oto fragment wynikowego listingu:
asm-max7219-led-banner-1.X/main.lst
                      00076         ; normalna praca (0) a nie test lampek (1)
                      00077         @setMAX7219reg MAX7219_DisplayTest, 0, MODULES_NO
  0000                    M             variable n
  00000000                M n = 0
000D   1186               M             bcf PORTB, MAX7219_CS                           
                          M             while n < MODULES_NO
000E   300F               M                 movlw MAX7219_DisplayTest
000F   2???               M                 call writeSPI       
0010   3000               M                 movlw 0
0011   2???               M                 call writeSPI               
  00000001                M n = n + 1       ; kolejny modułek
0012   300F               M                 movlw MAX7219_DisplayTest
0013   2???               M                 call writeSPI       
0014   3000               M                 movlw 0
0015   2???               M                 call writeSPI               
  00000002                M n = n + 1       ; kolejny modułek
0016   300F               M                 movlw MAX7219_DisplayTest
0017   2???               M                 call writeSPI       
0018   3000               M                 movlw 0
0019   2???               M                 call writeSPI               
  00000003                M n = n + 1       ; kolejny modułek
                          M             endw
001A   1586               M             bsf PORTB, MAX7219_CS
Z rzeczy upierdliwych co mi wyszło przy okazji, to konieczność zaczynania operacji na zmiennych makra od pierwszej kolumny, tam gdzie zwyczajowo jest miejsce na etykietę. Albo ja coś nie doczytałam, albo gdzieś jakiś prztyczek jest nieustawiony, to mam zaparkowane do wyjaśnienia, bo kod źródłowy makra wygląda brzydko i mnie to wkurza. Tak czy inaczej kaskadę MAX-ów udało się zainicjować, czas na ładowanie danych.

W programie ekranik rozwinął się do 24-bajtowej tabelki w pamięci RAM, oczywiście tam gdzie się dało - kod jest sparametryzowany. I teraz pierwsza zabawka - przeładowanie tych 24 bajtów do kaskady wyświetlaczy, znana z ostatniego postu procedura updateDisplay. Skoro traktuję je jako jeden mega-rejestr to znaczy, że wpisując jako adres rejestru w MAX to samo, ale z trzeba różnymi wartościami danych - ustalam wygląd pierwszych, drugich...ósmych pasków na trzech modułach jakby jednocześnie. Nie ma konieczności omijania żadnych rejestrów w MAX tym całym no-op, programowa realizacja jest dajmy na to taka:
asm-max7219-led-banner-1.X/main.asm
   SCREEN_SIZE       equ       MODULES_NO*8
   . . .
   cblock 0x020
   screenBuff : SCREEN_SIZE    ; moduły*8
   endc
   . . .
   ; przepisuje obszar screenBuff na matrycę
updateDisplay:
   clrf screenCurrentLine   ; od zerowej
updateDisplay_nextLine:       ; iteracja po liniach modułu 0-1   
   call updateSelectedLine    ; odświeża wybraną linijke w modłach, ile by ich nie było
   incf screenCurrentLine   ; screenCurrentLine++
   movlw 8
   subwf screenCurrentLine,W   ; screenCurrentLine == 8
   bnz updateDisplay_nextLine  ; kolejna
   return
   
   ; w screenCurrentLine bieżąca linijka
   ; trzeba ją wysłać tyle razy ile modułów
updateSelectedLine:   
   clrf moduleCntr       ; licznik modulów na 0
   movlw screenBuff   
   movwf FSR   ; wskaźnik na początek ekranu
   movfw screenCurrentLine
   addwf FSR   ; nalóż offset (linijkę)
   ; rozpocznij transakcje SPI
   bcf PORTB, MAX7219_CS                
updateSelectedLine_nextModule:
   ; dla każdego wskazanego modulu wyslij:
   ; a) numer linii (powiększony o 1
   movfw screenCurrentLine
   incf screenCurrentLine,W    ; W := linijka + 1
   call writeSPI
   ; b) daną spod adresu FSR
   movfw INDF
   call writeSPI
   ; przelicz FSR-a czyli (baza + linijka) + modul * 8
   movlw 8
   addwf FSR   ; FSR := FSR + 8   
   incf moduleCntr
   movlw MODULES_NO       ; <--- liczba modulów
   subwf moduleCntr,W   ; czy istatni?
   bnz updateSelectedLine_nextModule ; jak nie - kontynuuj
   ; zakończ transakcje SPI
   bsf PORTB, MAX7219_CS                
   return
 
Tu naprawdę przydaje się adresowanie pośrednie via FSR/INDF, bez tego to byłaby masakra normalnie. Przyznaje, to co powyżej to któreś tam z rzędu podejście do tematu, pętla zewnętrzna w updateDiplay iteruje po pionowych paskach, w procedurze updateSelectedLine jest wyliczanie skąd (z jakiego miejsca bufora ekranu) pobierać dane i w pętli zależnej od ilości modułów - wysyłanie do kaskady. Podrasowaniu uległa procedurka do przewijania ekranu scrollBuffer, no i cóż rzec...wersja, która pracowała podczas uruchamiania updateDisplay (bo to mi dało do wiwatu) bazuje na prostackim, siłowym, podejściu, ale żeby nieco wybielić - z wykorzystaniem makr, a zatem:
asm-max7219-led-banner-1.X/main.asm
   ; przepisuje paski N na N+1   
   ; wersja z cyklu #JPDL
@moveScreenbyte   macro src
               movfw screenBuff+src
            movwf screenBuff+src+1
            endm   
scrollBuffer_OLD:
   @moveScreenbyte 22
   @moveScreenbyte 21
   . . .
   @moveScreenbyte 1
   @moveScreenbyte 0
   return
W uzasadnieniu - musiałam mieć kawałek kodu, którego działania byłam już na 100% pewna, gdy wykluła się updateDisplay nabazgrałam mniej obciachową wersję scrollBuffer, o taką:
asm-max7219-led-banner-1.X/main.asm
   ; przepisuje paski N na N+1      
   ; wersja dla ludzi
scrollBuffer:
   movlw SCREEN_SIZE-1   ;
   movwf itemCntr ; bo przekładamy size-1 elementów
   movlw screenBuff+SCREEN_SIZE-2   ; zacznij od przed,przed ostatniego elementu
   movwf FSR   ; wskaźnik prawie na koniec ekranu (-2)
scrollBuffer_moveNext:
   ; przesun elementy N na N+1
   movfw INDF   ; W := *ptr
   incf FSR   ; ptr++
   movwf INDF   ; *ptr := W
   movlw 2
   subwf FSR   ; ustaw ptr na kolejny
   decf itemCntr   
   bnz scrollBuffer_moveNext   ; while ( itemCntr != 0 )
   return
I ponownie podziękowania za FSR/INDF, szkoda wielka, że to nie działa po pamięci ROM, no ale trudno. Pętla główna aplikacji praktycznie bez zmian, ale tego można się było spodziewać - w niej było wybieranie kolejnego bajtu dla pierwszego elementu bufora ekranowego i wołanie scroll-a, tu na szczęście nie grzebałam. I filmiki - pierwsze podejście do kaskady i efekty specjalne, ponieważ pomyliłam kolejność modułów.



Wyświetlanie napisu z ROM na większej matrycy, w sumie sympatycznie wygląda:



Kompletny projekt MPLAB-X tu: https://github.com/bienata/picnawode/tree/master/asm-max7219-led-banner-1.X

zabawa z generatorem DDS AD9833

Temat wieczorynki napatoczył się nieco przypadkowo, niemniej jednak wdzięczy się okazał i trafił na warsztat. Jest nim moduł generatorka DDS na bazie układu AD9833 od Analog Devices, dyskusja o nim toczy się, że tak ujmę w niedalekim sąsiedztwie. Rzeczony modułek dostałam na zatracenie od koleżanki z fabrycznej zagrody, pozostał się w formie `odpadu` po przygotowaniach do dyplomu, a teraz (modułek) ma szanse pobrylować w mediach, przynajmniej do chwili, kiedy nie zrobię mu krzywdy. Przy okazji poruszę pewną kwestię związaną z tym, jak ma się symulacja komputerowa układu elektronicznego do rzeczywistości i gdzie są zasadzki. Oprogramowanie w C, ponieważ bardziej zależało mi na zabawie generatorkiem niż na masochistycznych kombinacjach z rejestrem W i przełączaniem banków.

AD9833

Modułek tytułowego generatora jest całkiem sympatyczną konstrukcją i tak po prawdzie nie nastręcza kłopotów przy podłączaniu do mikrokontrolera, sygnały opisane, reszta detali jest w dokumentacji do wykorzystanych w nim układów. Oczywiście byłoby fajnie posiadać schemat tego cudaka, tak więc inżynieria odwrotna w moim wykonaniu sprowadziła się do kilku minut z Googlem i proszę: https://pmdway.com/products/ad9833-dds-programmable-frequency-function-generator
Podłączenie do PIC16F876 typowe dla poprzednio męczonych układów z SPI więc schemat sobie daruję. Dla formalności tylko dwa drobiazgi: sygnał /FSYNC kostki AD9833 idzie do wyprowadzenia RB3 kontrolera, sygnał /CS potencjometru MCP41010 do RB4. Testowa instalacja wygląda aktualnie tak:



Program sterujący powstał w C, wielką pomocą były strzępki kodu znalezione w sieci, a przygotowane dla AVR, linki do materiałów w kodzie źródłowym. Z sympatycznych wynalazków - powstało drobne zagadnienie: jak sterować sygnałami /CS i /FSYNC aby mieć w miarę spójne API realizujące zapis 16-bitowej wartości przez SPI, ale do wielu układów. No i wydumałam sobie tak, że funkcja spiWrite16() oprócz danej do wysłania dostanie też ... wskaźnik na funkcję odpowiedzialną za odpowiednie ustawienie sygnałów wybierających docelową kostkę. Funkcyjek tych mam tyle co układów, czyli całe dwie, niemniej jednak patent jest generyczny i raczej skuteczny, a kod (w mojej opinii przynajmniej) w miarę czytelny, o, proszę:
typedef void (*TChipSelectFoo)(bool state);

// wybieranie AD9833, FSYNC -->  RB3
void selectAD9833 (bool state) {
    RB3 = state;   
}

// wybieranie MCP41010, CS -->  RB4
void selectMCP41010 (bool state) {
    RB4 = state;   
}

// zapis słowa via SPI, machanie CS-em oddelegowane na zewnątrz
void spiWrite16 ( unsigned short aWord , TChipSelectFoo chipSelector ) {
    unsigned char msb = (( aWord >> 8 ) & 0x00FF );
   unsigned char lsb = ( aWord & 0x00FF );         
    chipSelector( false );     
   SSPBUF = msb;   
   while( BF == 1 );
   SSPBUF = lsb;   
    while( BF == 1 );
    chipSelector( true ); 
}
I zastosowanie:
. . .
spiWrite16 ( FREQ_0_LO ( freq ), &selectAD9833 );
. . .
spiWrite16 ( POT_0_COMMAND | (unsigned char)potValue , &selectMCP41010 );
. . .

Cały projekt z pierwszego podejścia do oswojenia generatorka znajdziemy tu: https://github.com/bienata/picnawode/tree/master/AD9833-gen-1.X

Kolejny krok to oprogramowanie generatora, którym można by sterować przez port szeregowy, taki jakby model. Polecenia z konsoli to 4/6 do zmiany częstotliwości, 8/2 lub +- do zmiany amplitudy (to jak łatwo zauważyć klawiatura numeryczna), literki s-t-p przełączają odpowiednio sinus, trójkąt i prostokąt. Oczywiście na ekranie oscyloskopu przebieg ma czasem ADHD, to jest jednak sztuka napisać program tak, aby zmiana parametrów sygnału była płynna i bez wierzgnięć. Garść zdjęć:



Oraz filmiki - test sterowania amplitudą:



Sterowanie częstotliwością, amplitudą i przełączanie generowanych kształtów:



Przy okazji ładnie widać, jakie cuda dzieją się na oscyloskopie podczas programowania PIC-a, normalnie kolejne wcielenie `Matki` z Nostromo. Komplet źródełek do tej wersji programu: https://github.com/bienata/picnawode/tree/master/AD9833-gen-fun-1.X

symulacja

Wspomniałam na początku o dyskusji o AD9833 w pobliżu, szczególnie zaintrygowała mnie sprawa programowalnego wzmacniacza ochrzczonego PGA (programmable gain amplifier) i wątpliwości odnośnie jego działania na podstawie wyników symulacji. No więc sprawa jest w sumie prosta - wzmacniacz wespół z cyfrowym potencjometrem stanowią technicznie poprawna konstrukcję, ale pod mocnym warunkiem, że wykorzystamy WO, który jest dedykowany do zasilania unipolarnego i który może pracować z sygnałami bliskimi potencjałowi dolnej szyny zasilania, więcej poczytajki tu: https://www.analog.com/en/analog-dialogue/raqs/raq-issue-38.html Kostka AD8051 doskonale się tu sprawdza, pierwsze testy po uruchomieniu sinusa to własnie było sprawdzenie, jak działa to całe PGA:



Zrobiłam symulację po swojemu, ja lubię dłubać w plikach więc oto pisany wsad dla symulatora ngspice:

* https://www.egr.msu.edu/classes/ece445/mason/Files/445lab8_pre_subcrct.txt
.include ua741.txt
* https://www.analog.com/en/design-center/simulation-models/spice-models.html
.include op183g.cir

*  I+, I-, VCC, VEE, Out
X1 2 1 3 0 5 UA741
R1 5 1 1k
R2 1 0 1k
* sinus, 1V, 1kHz
Vin1 2 0 sin (1v 1v 1k 0 0)
VCC1 3 0 dc 15V


X20 20 10 30 0 50 OP183G
R10 50 10 1k
R20 10 0 1k
Vin10 20 0 sin (1v 1v 1k 0 0)
VCC10 30 0 dc 15V

.tran 0.02ms 2ms
.control
run
plot v(2) v(5)
plot v(20) v(50)

.endc

Drugi model - OP183G wyszukałam specjalnie na stronie Anaglog Devices, filtrując po `single supply amplifier`

Bipolarny klasyk 741 przyciął dolną połówkę sygnału i trudno się dziwić, preferuje zasilanie symetryczne. Koleżka OP183 doskonale poradził sobie z sygnałem sterującym w okolicy masy, czyli de facto - dolnego potencjału zasilania układu. Pliki do symulatora dla ciekawskich: 99_sim-ngspice.zip


#slowanawiatr, styczeń 2020

 tasza  2004-2021 | archiwum kabema 2021