Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

In parte uno Ho cercato di spiegare agli hobbisti di elettronica, che sono cresciuti con Arduino, come e perché dovrebbero leggere le schede tecniche e la documentazione correlata ai microcontrollori. Il testo è diventato lungo, quindi ho promesso di mostrare esempi pratici in un articolo separato. Bene, quindi mi sono presentato...

Oggi mostrerò come utilizzare le schede tecniche per risolvere compiti piuttosto semplici, ma necessari per molti progetti con i controllori STM32 (Blue Pill) e STM8. Tutti i progetti demo sono dedicati ai miei amati LED, e li accenderemo in grandi quantità, per farlo utilizzeremo una varietà di periferiche interessanti.

Il testo è di nuovo enorme, quindi per comodità faccio un sommario:

STM32 Blue Pill: 16 LED con driver DM634
STM8: Configurazione di sei uscite PWM
STM8: 8 LED RGB su tre pin, interruzioni

Disclaimer: non sono un ingegnere, non pretendo di avere profonde conoscenze in elettronica; l'articolo è destinato a chi, come me, è un appassionato. In effetti, ho considerato come pubblico target me stesso di due anni fa. Se qualcuno mi avesse detto allora che leggere i datasheet di un chip sconosciuto non è così spaventoso, non avrei perso un sacco di tempo a cercare pezzi di codice su Internet e a inventare soluzioni di fortuna con forbici e cerotti.

Il fulcro di questo articolo sono i datasheet, non i progetti, quindi il codice può non essere troppo pulito e spesso è di fortuna. I progetti stessi sono molto semplici, anche se adatti per un primo approccio a un nuovo chip.

Spero che il mio articolo possa aiutare qualcuno in una fase simile di immersione nell'hobby.

STM32

16 LED con DM634 e SPI

Un piccolo progetto utilizzando Blue Pill (STM32F103C8T6) e il driver LED DM634. Utilizzando i datasheet, esploreremo il driver, le porte IO del STM e configureremo SPI.

DM634

Chip taiwanese con 16 uscite PWM a 16 bit, possono essere collegati in serie. Il modello a 12 bit è noto per un progetto nazionale. Lightpack. Quando ho dovuto scegliere tra il DM63x e il ben noto TLC5940, alla fine ho optato per il DM per diverse ragioni: 1) il TLC su AliExpress è sicuramente contraffatto, mentre questo non lo è; 2) il DM ha un PWM autonomo con un proprio generatore di frequenza; 3) si poteva comprare a buon prezzo a Mosca, senza dover attendere un pacco da Ali. E naturalmente, ero curioso di imparare a gestire il chip da solo, invece di usare una libreria già pronta. Attualmente, i chip sono principalmente disponibili in un alloggiamento SSOP24, che è facile da saldare su un adattatore.

Poiché il produttore è di Taiwan, datasheet per il chip è scritto in un inglese cinese, il che significa che sarà divertente. Innanzitutto, diamo un'occhiata al pinout (Pin Connection), per capire a quale pin collegare cosa, e alla descrizione dei pin (Pin Description). 16 uscite:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Fonti di corrente continua in ingresso (open drain)

Sink / Open-drain output – drenaggio; fonte di corrente in ingresso; uscita, attivamente connessa a massa, – i LED sono collegati al driver con i catodi. Elettricamente, non si tratta certo di un «open drain» (open drain), ma nelle schede tecniche questa designazione per i pin in modalità drenaggio è piuttosto comune.

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Resistori esterni tra REXT e GND per impostare il valore della corrente di uscita

Tra il pin REXT e la terra si installa un resistore di riferimento che controlla la resistenza interna delle uscite; vedere il grafico a pagina 9 del datasheet. In DM634, questa resistenza può essere anche controllata tramite software, impostando la luminosità globale (luminosità globale); non entrerò nei dettagli in questo articolo, metterò semplicemente un resistore tra 2.2 e 3 kΩ.

Per capire come gestire il chip, diamo un'occhiata alla descrizione dell'interfaccia del dispositivo:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

Ecco, il cinese-inglese in tutto il suo splendore. Tradurre questo è complicato; è possibile comprenderlo se lo si desidera, ma c'è un altra via: guardare come viene descritto il collegamento nel datasheet del TLC5940, che è funzionalmente simile:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
… Per immettere dati nel dispositivo sono necessari solo tre pin. Il fronte del segnale SCLK sposta i dati dal pin SIN nel registro interno. Dopo che tutti i dati sono stati caricati, un breve segnale alto XLAT fissa i dati trasmessi sequenzialmente nei registri interni. I registri interni sono latch attivati dal livello del segnale XLAT. Tutti i dati vengono trasmessi dal bit più significativo al più basso.

Latch – serranda/catenaccio/blocco.
Fronte di salita – fronte anteriore dell'impulso
MSB prima – con il bit più significativo (il più a sinistra) in avanti.
per sincronizzare i dati – trasmettere i dati in sequenza (bit per bit).

Termine latch è spesso trovato nella documentazione dei chip e viene tradotto in vari modi, quindi per chiarire permettetemi

un piccolo chiarimentoIl LED driver è essenzialmente un registro a scorrimento. “Scorrimento” (shift) nel nome indica il movimento bit a bit dei dati all'interno del dispositivo: ogni nuovo bit inserito spinge la catena di bit esistente in avanti. Poiché durante lo scorrimento nessuno vuole osservare il lampeggiamento caotico dei LED, il processo avviene in registri buffer, separati dagli operativi da una serranda (latch) – è una sorta di anticamera, dove i bit si allineano nella sequenza corretta. Quando tutto è pronto, la serranda si apre e i bit vengono inviati a operare, sostituendo il lotto precedente. Il termine latch nella documentazione dei microchip implica quasi sempre tale serranda, in qualunque combinazione venga utilizzato.

Pertanto, il trasferimento dei dati nel DM634 avviene come segue: impostiamo l'ingresso DAI al valore del bit più significativo del LED remoto, azioniamo DCK su e giù; impostiamo l'ingresso DAI al valore del bit successivo, azioniamo DCK; e così via, fino a quando tutti i bit non sono stati trasferiti (ciclato dentro), dopo di che azioniamo LAT. Questo può essere fatto manualmente (bit-bang), ma è preferibile utilizzare un'interfaccia SPI appositamente progettata, e fortunatamente è presente sul nostro STM32 in due esemplari.

Pillola Blu STM32F103

Introduzione: i controller STM32 sono significativamente più complessi dell'Atmega328, più di quanto ci si possa aspettare. Inoltre, per motivi di risparmio energetico, all'avvio è quasi tutta la periferia disabilitata e la frequenza di clock è di 8 MHz da una sorgente interna. Fortunatamente, i programmatori STM hanno scritto un codice che porta il chip alla frequenza 'calcolata' di 72 MHz, e gli autori di tutti gli IDE a me noti l'hanno incluso nella procedura di inizializzazione, quindi non dobbiamo settare il clock (ma possiamo, se lo vogliamo davvero). Ma sarà necessario attivare la periferia.

Documentazione: sulla Blue Pill è installato un popolare chip STM32F103C8T6, per il quale ci sono due documenti utili:

Nel datasheet ci possono interessare:

  • Pinouts – pinout dei chip – nel caso decidessimo di produrre le schede noi stessi;
  • Memory Map – mappa della memoria per chip specifico. Nel Reference Manual è presente una mappa per l'intera gamma, nella quale sono menzionati registri che non sono presenti nel nostro.
  • Tabella delle definizioni dei pin – elenco delle principali e alternative funzioni dei pin; per la «blue pill» si possono trovare online immagini più utili con l'elenco dei pin e delle loro funzioni. Quindi, cerchiamo subito il pinout della Blue Pill e teniamo a portata di mano un'immagine simile:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
NB: nell'immagine trovata online c'era un errore, segnalato nei commenti, per cui grazie. L'immagine è stata sostituita, ma è una lezione: le informazioni non provenienti dai datasheet è meglio verificarle.

Eliminiamo il datasheet, apriamo il Reference Manual e da ora in poi usiamo solo quello.
Procedura: ci occupiamo dell'input/output standard, configuriamo l'SPI, attiviamo le periferiche necessarie.

Input/Output

Sull'Atmega328, l'input/output è realizzato in modo estremamente semplice, motivo per cui l'abbondanza di opzioni STM32 può confondere. Al momento ci servono solo le uscite, ma anche per queste ci sono quattro varianti:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
uscita a open collector, uscita "push-pull", uscita "push-pull" alternativa, open collector alternativa

"Push-pull" (push-pull) è l'uscita convenzionale di Arduino, il pin può assumere valore HIGH o LOW. Con "open collector", invece, ci sono difficoltà, anche se in realtà è tutto semplice:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Configurazione dell'uscita / quando il pin è impostato come uscita: / buffer di uscita attivato: / – modalità open collector: "0" nel registro di uscita attiva N-MOS, "1" nel registro di uscita mantiene il pin in modalità Hi-Z (P-MOS non è attivato) / – modalità "push-pull": "0" nel registro di uscita attiva N-MOS, "1" nel registro di uscita attiva P-MOS.

L'unica differenza tra open collector (open drain) e "push-pull" (push-pull) è che nel primo il pin non può assumere lo stato HIGH: scrivendo uno nel registro di uscita, passa in modalità alta impedenza (high impedance, Hi-Z). Scrivendo zero, il pin si comporta allo stesso modo in entrambe le modalità sia a livello logico che elettrico.

In modalità di uscita normale, il pin semplicemente trasmette il contenuto del registro di uscita. Nella modalità "alternativa", è controllato dalla relativa periferica (vedi 9.1.4):

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Se il bit della porta è configurato come uscita di una funzione alternativa, il registro di uscita si disabilita e il pin si collega al segnale di uscita della periferica.

La funzionalità alternativa di ogni pin è descritta nel Pin Definitions datasheet ed è visibile nell'immagine scaricata. In risposta alla domanda su cosa fare se un pin ha più funzioni alternative, la nota nel datasheet fornisce una risposta:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Se più blocchi periferici utilizzano lo stesso pin, per evitare conflitti tra le funzioni alternative si deve utilizzare contemporaneamente solo un blocco periferico, alternando tramite il bit di attivazione del clock della periferica (nel registro RCC corrispondente).

Infine, i pin in modalità uscita hanno anche una velocità di clock. Questa è un'altra caratteristica di risparmio energetico, nel nostro caso impostiamo semplicemente al massimo e dimentichiamo.

Quindi: stiamo usando SPI, quindi due pin (uno per i dati e uno per il segnale di clock) devono essere "funzione alternativa push-pull", mentre un altro (LAT) deve essere "normale push-pull". Ma prima di assegnarli, facciamo chiarezza su SPI.

SPI

E un'ulteriore lezione breve

SPI o Serial Peripheral Interface (interfaccia periferica seriale) è un'interfaccia semplice ed estremamente efficace per la comunicazione tra microcontrollori e il mondo esterno. Il principio di funzionamento è già stato descritto in precedenza, nella sezione riguardante il driver LED cinese (vedere il manuale di riferimento, sezione 25). SPI può operare in modalità master ("padroni") e slave ("schiavo"). SPI ha quattro canali fondamentali, di cui non tutti possono essere utilizzati:

  • MOSI, Master Output / Slave Input: questo pin, in modalità master, invia dati e, in modalità slave, riceve dati;
  • MISO, Master Input / Slave Output: il contrario, in modalità master riceve e in modalità slave invia;
  • SCK, Serial Clock: stabilisce la frequenza di trasmissione dei dati in modalità master o riceve il segnale di clock in modalità slave. Fondamentalmente, scandisce i bit;
  • SS, Slave Select: tramite questo canale, lo slave capisce che qualcuno desidera comunicare con esso. Su STM32 è chiamato NSS, dove N = negativo, ovvero il controller diventa slave se il segnale su questo canale è a terra. Si combina bene con la modalità Open Drain Output, ma questa è un'altra storia.

Come tutto il resto, SPI su STM32 è ricco di funzionalità, il che rende un po' più complesso il suo utilizzo. Ad esempio, può lavorare non solo con SPI, ma anche con l'interfaccia I2S, e nella documentazione le loro descrizioni sono mescolate, quindi è necessario fare attenzione a non includere informazioni superflue. La nostra esigenza è estremamente semplice: dobbiamo solo trasmettere dati utilizzando solo MOSI e SCK. Andiamo alla sezione 25.3.4 (comunicazione half-duplex, comunicazione a semiduplex), dove troviamo 1 clock e 1 cavo dati unidirezionale (1 segnale di clock e 1 flusso di dati unidirezionale):

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
In questa modalità, l'applicazione utilizza SPI o in modalità solo trasmissione o solo ricezione. / La modalità solo trasmissione è simile alla modalità duplex: i dati vengono trasmessi tramite il pin di uscita (MOSI in modalità master o MISO in modalità slave), mentre il pin di ricezione (MISO o MOSI di conseguenza) può essere utilizzato come un comune pin di ingresso/uscita. In questo caso, all'applicazione basta ignorare il buffer Rx (se lo si legge, non ci saranno dati trasmessi).

Ottimo, il pin MISO è ora libero, lo colleghiamo al segnale LAT. Ci occuperemo del Slave Select, che può essere gestito programmando su STM32, il che è estremamente utile. Consultiamo il paragrafo omonimo nella sezione 25.3.1 Descrizione Generale SPI:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Controllo programmato di NSS (SSM = 1) / Le informazioni sulla selezione dello slave sono contenute nel bit SSI del registro SPI_CR1. Il pin esterno NSS rimane libero per altre esigenze dell'applicazione.

È tempo di scrivere nei registri. Ho deciso di utilizzare SPI2, cerchiamo nel datasheet il suo indirizzo base – nella sezione 3.3 Mappa di Memoria:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

Iniziamo:

#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))

Apriamo la sezione 25.3.3 con l'esplicativo titolo «Configurazione SPI in modalità master»:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

1. Impostare la frequenza di clock dell'interfaccia seriale con i bit BR[2:0] nel registro SPI_CR1.

I registri sono raccolti nella sezione omonima del manuale di riferimento. L'offset dell'indirizzo (Address offset) in CR1 è 0x00, per impostazione predefinita tutti i bit sono resettati (Reset value 0x0000):

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

I bit BR impostano il divisore della frequenza di clock del controller, determinando così la frequenza operativa dello SPI. La frequenza STM32 sarà di 72 MHz, il driver LED, secondo il suo datasheet, opera con una frequenza fino a 25 MHz, quindi dobbiamo dividere per quattro (BR[2:0] = 001).

#define _SPI_CR1 0x00

#define BR_0        0x0008
#define BR_1        0x0010
#define BR_2        0x0020

_SPI2_ (_SPI_CR1) |= BR_0;// pclk/4

2. Imposta i bit CPOL e CPHA per definire le relazioni tra la trasmissione dati e il clock dell'interfaccia seriale (vedi schema a pagina 240)

Poiché stiamo leggendo il datasheet e non analizzando gli schemi, approfondiamo meglio la descrizione testuale dei bit CPOL e CPHA a pagina 704 (Descrizione Generale SPI):

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Fase e polarità del segnale di clock
Con i bit CPOL e CPHA del registro SPI_CR1 è possibile selezionare programmaticamente quattro opzioni di relazione temporale. Il bit CPOL (polarità del segnale di clock) controlla lo stato del segnale di clock quando i dati non vengono trasmessi. Questo bit gestisce le modalità master e slave. Se CPOL è azionato, il pin SCK è a livello basso in stato di riposo. Se il bit CPOL è impostato, il pin SCK è a livello alto in stato di riposo.
Se è impostato il bit CPHA (fase del segnale di clock), il secondo fronte del segnale SCK funge da stroboscopio per il bit più significativo (discendente se CPOL è azzerato, o ascendente se CPOL è impostato). I dati vengono registrati al secondo cambiamento del segnale di clock. Se il bit CPHA è azzerato, il primo fronte del segnale SCK funge da stroboscopio per il bit più significativo (discendente se CPOL è impostato, o ascendente se CPOL è azzerato). I dati vengono registrati al primo cambiamento del segnale di clock.

Acquisendo queste informazioni, giungiamo alla conclusione che entrambi i bit devono rimanere zero, poiché è necessario che il segnale SCK rimanga basso quando non viene utilizzato e che i dati vengano trasferiti al fronte ascendente dell'impulso (vedi Rising Edge nel datasheet DM634).

A proposito, qui ci imbattiamo per la prima volta in una peculiarità del lessico nei datasheet ST: in essi l'espressione ‘azzerare un bit’ viene scritta to reset a bit, e non to clear a bit, come ad esempio per l'Atmega.

3. Imposta il bit DFF per determinare il formato dei dati a 8 o 16 bit.

Ho scelto appositamente il DM634 a 16 bit per non dover affrontare la trasmissione dei dati PWM a 12 bit, come nel DM633. Ha senso impostare DFF a uno:

#define DFF         0x0800

_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode

4. Configurare il bit LSBFIRST nel registro SPI_CR1 per determinare il formato del blocco

LSBFIRST, come suggerisce il nome, imposta il trasferimento a partire dal bit meno significativo. Ma DM634 desidera ricevere i dati a partire dal bit più significativo. Pertanto, lo lasciamo resettato.

5. In modalità hardware, se è necessario l'ingresso dal pin NSS, fornire un segnale alto al pin NSS durante l'intera sequenza di trasmissione dei byte. In modalità programmatica, impostare i bit SSM e SSI nel registro SPI_CR1. Se il pin NSS deve funzionare come uscita, è necessario impostare solo il bit SSOE.

Impostiamo SSM e SSI per ignorare la modalità hardware di NSS:

#define SSI         0x0100
#define SSM         0x0200

_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high

6. Devono essere impostati i bit MSTR e SPE (rimarranno impostati solo se sul NSS viene fornito un segnale alto)

Con questi bit stiamo impostando il nostro SPI come master e lo attiviamo:

#define MSTR        0x0004
#define SPE         0x0040

_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;

SPI configurato, ora scriviamo immediatamente le funzioni per inviare byte al driver. Continuiamo a leggere il capitolo 25.3.3 «Configurazione di SPI in modalità master»:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Ordine di trasmissione dei dati
La trasmissione inizia quando un byte viene scritto nel buffer Tx.
Il byte di dati viene caricato nel registro di scorrimento in modalità parallela (dalla bus interna) durante la trasmissione del primo bit, dopo di che viene inviato a modalità (dalla bus interna) durante la trasmissione del primo bit, dopo di che viene trasmesso in sequenziale nella modalità MOSI, con il primo o l'ultimo bit in avanti a seconda dell'impostazione del bit LSBFIRST nel registro CPI_CR1. Il flag TXE viene impostato dopo la trasmissione dei dati dal buffer Tx al registro di scorrimento, e viene generata un'interruzione se il bit TXEIE è impostato nel registro CPI_CR1.

Ho evidenziato alcune parole nella traduzione per richiamare l'attenzione su una caratteristica dell'implementazione SPI nei controller STM. Sull'Atmega, il flag TXE (Tx Empty, Tx è vuoto e pronto a ricevere dati) viene impostato solo dopo che l'intero byte è stato inviato all'esterno. Qui invece, questo flag viene impostato dopo che il byte è stato inserito nel registro di scorrimento interno. Poiché viene inserito lì con tutti i bit contemporaneamente (in parallelo), i dati vengono quindi trasferiti in sequenza, e TXE viene impostato prima che il byte venga completamente inviato. Questo è importante, poiché nel nostro driver LED dobbiamo cambiare il pin LAT dopo aver inviato tutti i dati, quindi solo il flag TXE non sarà sufficiente.

Ciò significa che abbiamo bisogno di un altro flag. Diamo un'occhiata a 25.3.7 – "Flag di stato":

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
<…>
Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Flag BUSY
Il flag BSY viene impostato e resettato a livello hardware (la scrittura in esso non influisce su nulla). Il flag BSY indica lo stato dello strato di comunicazione SPI.
Viene resettato:
quando la trasmissione è completata (eccetto in modalità master, se la trasmissione è continua)
quando SPI è disabilitato
quando si verifica un errore nella modalità master (MODF=1)
Se la trasmissione non è continua, il flag BSY viene resettato tra ogni trasmissione di dati.

Ok, questo sarà utile. Scopriamo dove si trova il buffer Tx. Per fare ciò, leggiamo il 'Registro dati SPI':

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Bit 15:0 DR[15:0] Registro dati
Dati ricevuti o dati da trasmettere.
Il registro dati è diviso in due buffer: uno per la scrittura (buffer di trasmissione) e l'altro per la lettura (buffer di ricezione). Scrivere nel registro dati scrive nel buffer Tx, e leggere dal registro dati restituirà il valore contenuto nel buffer Rx.

Ecco il registro degli stati, dove troveremo i flag TXE e BSY:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

Scriviamo:

#define _SPI_DR  0x0C
#define _SPI_SR  0x08

#define BSY         0x0080
#define TXE         0x0002

void dm_shift16(uint16_t value)
{
    _SPI2_(_SPI_DR) = value; //send 2 bytes
    while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent
}

Poiché dobbiamo trasmettere 16 volte due byte, in base al numero di uscite del driver LED, sarà qualcosa del genere:

void sendLEDdata()
{
    LAT_low();
    uint8_t k = 16;
    do
    {   k--;
        dm_shift16(leds[k]);
    } while (k);

    while (_SPI2_(_SPI_SR) & BSY); // termina la trasmissione

    LAT_pulse();
}

Ma per ora non sappiamo come attivare il pin LAT, torniamo quindi a I/O.

Assegniamo i pin

Nei registri STM32F1 responsabili dello stato dei pin sono piuttosto particolari. È evidente che ce ne sono di più rispetto ad ATmega, ma sono anche diversi rispetto ad altri chip STM. Sezione 9.1 Descrizione generale del GPIO:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Ognuno dei porti di input/output generale (GPIO) ha due registri di configurazione a 32 bit (GPIOx_CRL e GPIOx_CRH), due registri di dati a 32 bit (GPIOx_IDR e GPIOx_ODR), un registro di impostazione/reset a 32 bit (GPIOx_BSRR), un registro di reset a 16 bit (GPIOx_BRR) e un registro bloccante a 32 bit (GPIOx_LCKR).

Sono insoliti e anche piuttosto scomodi, qui i primi due registri, poiché i 16 pin del porto sono distribuiti in essi in un formato "quattro bit per pin". Ovvero, i pin da zero a sette si trovano in CRL, mentre gli altri sono in CRH. Tuttavia, gli altri registri riescono a contenere i bit di tutti i pin del porto, spesso rimanendo a metà "riservati".

Per semplicità, iniziamo dalla fine dell'elenco.

Il registro bloccante non ci servirà.

I registri di impostazione e reset sono piuttosto divertenti poiché si duplicano parzialmente: si può scrivere tutto solo nel BSRR, dove i 16 bit superiori resetteranno il pin a zero e i bit inferiori lo imposteranno a 1, oppure si può usare anche BRR, i cui 16 bit inferiori resettano solo il pin. A me piace di più la seconda opzione. Questi registri sono importanti perché forniscono accesso atomico ai pin:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Impostazione o reset atomico
Non è necessario disabilitare le interruzioni durante la programmazione di GPIOx_ODR a livello di bit: è possibile modificare uno o più bit con un'operazione atomica di scrittura APB2. Questo si ottiene scrivendo "1" nel registro di impostazione/reset (GPIOx_BSRR o, solo per il reset, in GPIOx_BRR) del bit da modificare. Gli altri bit rimarranno invariati.

I registri dati hanno nomi abbastanza esplicativi – IDR = Ingresso Registro di Direzione, registro di ingresso; ODR = Uscita Registro di Direzione, registro di uscita. Nel progetto attuale non ci serviranno.

E, infine, i registri di controllo. Poiché ci interessano i pin del secondo SPI, ovvero PB13, PB14 e PB15, guardiamo subito a CRH:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

E vediamo che sarà necessario scrivere qualcosa nei bit dal ventesimo al trentunesimo.

Abbiamo già chiarito sopra cosa vogliamo dai pin, quindi non userò uno screenshot qui; dirò semplicemente che il MODE determina la direzione (input, se entrambi i bit sono a 0) e la velocità del pin (abbiamo bisogno di 50MHz, cioè entrambi i pin devono essere «1»), mentre il CNF determina la modalità: «pull-up/pull-down» normale – 00, «alternativa» – 10. Di default, come abbiamo visto sopra, tutti i pin hanno impostato il terzo bit dal basso (CNF0), che li configura nella modalità input flottante.

Poiché ho intenzione di fare qualcos'altro con questo chip, per semplificare ho definito tutti i possibili valori di MODE e CNF per i registri di controllo inferiori e superiori.

Ecco, più o meno è così

#define CNF0_0 0x00000004
#define CNF0_1 0x00000008
#define CNF1_0 0x00000040
#define CNF1_1 0x00000080
#define CNF2_0 0x00000400
#define CNF2_1 0x00000800
#define CNF3_0 0x00004000
#define CNF3_1 0x00008000
#define CNF4_0 0x00040000
#define CNF4_1 0x00080000
#define CNF5_0 0x00400000
#define CNF5_1 0x00800000
#define CNF6_0 0x04000000
#define CNF6_1 0x08000000
#define CNF7_0 0x40000000
#define CNF7_1 0x80000000
#define CNF8_0 0x00000004
#define CNF8_1 0x00000008
#define CNF9_0 0x00000040
#define CNF9_1 0x00000080
#define CNF10_0 0x00000400
#define CNF10_1 0x00000800
#define CNF11_0 0x00004000
#define CNF11_1 0x00008000
#define CNF12_0 0x00040000
#define CNF12_1 0x00080000
#define CNF13_0 0x00400000
#define CNF13_1 0x00800000
#define CNF14_0 0x04000000
#define CNF14_1 0x08000000
#define CNF15_0 0x40000000
#define CNF15_1 0x80000000

#define MODE0_0 0x00000001
#define MODE0_1 0x00000002
#define MODE1_0 0x00000010
#define MODE1_1 0x00000020
#define MODE2_0 0x00000100
#define MODE2_1 0x00000200
#define MODE3_0 0x00001000
#define MODE3_1 0x00002000
#define MODE4_0 0x00010000
#define MODE4_1 0x00020000
#define MODE5_0 0x00100000
#define MODE5_1 0x00200000
#define MODE6_0 0x01000000
#define MODE6_1 0x02000000
#define MODE7_0 0x10000000
#define MODE7_1 0x20000000
#define MODE8_0 0x00000001
#define MODE8_1 0x00000002
#define MODE9_0 0x00000010
#define MODE9_1 0x00000020
#define MODE10_0 0x00000100
#define MODE10_1 0x00000200
#define MODE11_0 0x00001000
#define MODE11_1 0x00002000
#define MODE12_0 0x00010000
#define MODE12_1 0x00020000
#define MODE13_0 0x00100000
#define MODE13_1 0x00200000
#define MODE14_0 0x01000000
#define MODE14_1 0x02000000
#define MODE15_0 0x10000000
#define MODE15_1 0x20000000

I nostri pin si trovano sulla porta B (indirizzo base – 0x40010C00), codice:

#define _PORTB_(mem_offset) (*(volatile uint32_t *)(0x40010C00 + (mem_offset)))

#define _BRR  0x14
#define _BSRR 0x10
#define _CRL  0x00
#define _CRH  0x04

//используем стандартный SPI2: MOSI на B15, CLK на B13
//LAT пусть будет на неиспользуемом MISO – B14

//очищаем дефолтный бит, он нам точно не нужен
_PORTB_ (_CRH) &= ~(CNF15_0 | CNF14_0 | CNF13_0 | CNF12_0);

//альтернативные функции для MOSI и SCK
_PORTB_ (_CRH) |= CNF15_1 | CNF13_1;

//50 МГц, MODE = 11
_PORTB_ (_CRH) |= MODE15_1 | MODE15_0 | MODE14_1 | MODE14_0 | MODE13_1 | MODE13_0;

E, di conseguenza, si possono scrivere le definizioni per LAT, che si attiverà con i registri BRR e BSRR:

/*** LAT pulse – high, then low */
#define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14)

#define LAT_low() _PORTB_(_BRR) = (1<<14)

(LAT_low è solo per abitudine, è sempre stato così, quindi lasciamolo stare)

Ora va tutto bene, solo che non funziona. Perché questo è un STM32, qui si risparmia energia, quindi bisogna attivare il clocking della periferica necessaria.

Attiviamo il clocking

La sincronizzazione è gestita dal clock, ovvero dai Clock. Abbiamo già notato l'abbreviazione RCC. Cerchiamola nella documentazione: si riferisce a Reset and Clock Control (Controllo del reset e della sincronizzazione).

Come detto sopra, fortunatamente, la parte più complessa del tema sulla sincronizzazione è stata gestita da STM, a cui va il nostro grande grazie (riporto nuovamente il link a sito di Di Halt, per far comprendere quanto sia complicato). Abbiamo bisogno solo dei registri che gestiscono l'attivazione della sincronizzazione delle periferiche (Peripheral Clock Enable Registers). Prima di tutto dobbiamo trovare l'indirizzo base RCC, che si trova all'inizio della "Mappa della memoria":

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))

Poi, possiamo cliccare sul link e cercare qualcosa nella tabella, oppure, molto meglio, scorrere le descrizioni dei registri di attivazione nelle sezioni relative a registri di attivazione. Dove troveremo RCC_APB1ENR e RCC_APB2ENR:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

E in essi, rispettivamente, i bit che attivano la sincronizzazione di SPI2, IOPB (I/O Port B) e delle funzioni alternative (AFIO).

#define _APB2ENR 0x18
#define _APB1ENR 0x1C

#define IOPBEN 0x0008
#define SPI2EN 0x4000
#define AFIOEN 0x0001

//включаем тактирование порта B и альт. функций
_RCC_(_APB2ENR) |= IOPBEN | AFIOEN;

//включаем  тактирование SPI2
_RCC_(_APB1ENR) |= SPI2EN;

Il codice finale può essere trovato qui.

Se c'è la possibilità e la voglia di testare, colleghiamo DM634 in questo modo: DAI a PB15, DCK a PB13, LAT a PB14. Alimentiamo il driver con 5 volt, senza dimenticare di unire le masse.

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

STM8 PWM

PWM su STM8

Quando ho iniziato a pianificare questo articolo, ho deciso di provare a comprendere qualche funzionalità di un chip a me sconosciuto utilizzando solo il datasheet, per non essere un calzolaio senza scarpe. Lo STM8 si adattava perfettamente a questo scopo: da un lato, avevo un paio di schede cinesi con STM8S103 e, dall'altro, non è molto popolare, quindi la tentazione di cercare soluzioni su Internet è limitata dalla mancanza di queste stesse soluzioni.

Il chip ha anche datasheet e il manuale di riferimento RM0016, il primo contiene la pinout e gli indirizzi dei registri, il secondo – tutto il resto. Lo STM8 è programmabile in C in un'IDE poco elegante ST Visual Develop.

Frequenza e I/O

Per impostazione predefinita, lo STM8 lavora a una frequenza di 2 MHz, e questo deve essere corretto subito.

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Il segnale di clock HSI (High-Speed Internal)
Il segnale di clock HSI proviene da un generatore RC interno da 16 MHz con divisore programmabile (da 1 a 8). È configurato nel registro del divisore di clock (CLK_CKDIVR).
Nota: all'avvio, HSI viene selezionato come principale fonte di clock con un divisore di 8.

Troviamo l'indirizzo del registro nel datasheet, la descrizione nel refman e vediamo che il registro deve essere azzerato:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Poiché stiamo per avviare il PWM e collegare i LED, controlliamo la mappatura:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

Il chip è piccolo, molte funzioni sono appese agli stessi pin. Ciò che è tra parentesi quadre è "funzionalità alternativa", che viene attivata dai "byte di opzione" (option bytes) – qualcosa di simile ai fuse dell'ATMega. È possibile modificarne i valori programmaticamente, ma non è necessario, poiché la nuova funzionalità si attiva solo dopo un riavvio. È più semplice utilizzare ST Visual Programmer (scaricabile insieme a Visual Develop), che può modificare questi byte. Nella mappatura si vede che le uscite CH1 e CH2 del primo timer sono nascoste tra parentesi quadre; bisogna impostare i bit AFR1 e AFR0 in STVP, e inoltre il secondo trasferirà anche l'uscita CH1 del secondo timer da PD4 a PC5.

Pertanto, il controllo dei LED avverrà tramite 6 pin: PC6, PC7 e PC3 per il primo timer, PC5, PD3 e PA3 per il secondo.

La configurazione dei pin di I/O sull'STM8 è più semplice e logica rispetto all'STM32:

  • il registro della direzione dei dati DDR,Data Direction Register: 1 = uscita;
  • il primo registro di controllo CR1 in uscita imposta il modo "push-pull" (1) o open-drain (0); poiché collego i LED al chip con i catodi, lascio qui degli zeri;
  • Il secondo registro di controllo CR2 determina la velocità di clock: 1 = 10 MHz

#define PA_DDR     *(volatile uint8_t *)0x005002
#define PA_CR2     *(volatile uint8_t *)0x005004
#define PD_DDR     *(volatile uint8_t *)0x005011
#define PD_CR2     *(volatile uint8_t *)0x005013
#define PC_DDR     *(volatile uint8_t *)0x00500C
#define PC_CR2     *(volatile uint8_t *)0x00500E

PA_DDR = (1<<3); //output
PA_CR2 |= (1<<3); //fast
PD_DDR = (1<<3); //output
PD_CR2 |= (1<<3); //fast
PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output
PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fast

Impostazione PWM

Iniziamo definendo i termini:

  • Frequenza PWM – la frequenza con cui il timer ticchetta;
  • Auto-reload, AR – valore autocaricato fino al quale il timer conta (periodo dell'impulso);
  • Update Event, UEV – evento che si verifica quando il timer raggiunge l'AR;
  • Ciclo di lavoro PWM – il fattore di riempimento PWM, spesso chiamato "duty cycle";
  • Valore di cattura/comparazione – valore per cattura/comparazione, al raggiungimento del quale il timer eseguirà un'azione (nel caso del PWM – inverte il segnale di uscita);
  • Valore di pre-carico – valore pre-caricato. Il valore di confronto non può essere modificato mentre il timer ticchetta, altrimenti il ciclo PWM si interromperà. Pertanto, i nuovi valori trasmessi vengono memorizzati in un buffer e prelevati da lì quando il timer raggiunge la fine del conteggio e si resetta;
  • Modalità allineate al bordo e Modalità allineate al centro – allineamento al bordo e al centro, come nelle modalità Atmel Fast PWM e Phase-correct PWM.
  • OCiREF, Segnale di riferimento per la comparazione di uscita – segnale di uscita di riferimento, in effetti, ciò che in modalità PWM si trova sul pin corrispondente.

Come già evidente dalla pinout, sono disponibili funzioni PWM su due timer: il primo e il secondo. Entrambi sono a 16 bit, il primo ha molte funzionalità aggiuntive (in particolare, può contare sia in su che in giù). Dobbiamo far funzionare entrambi allo stesso modo, quindi ho deciso di iniziare con il secondo, che è noto per essere più limitato, per non usare accidentalmente qualcosa che non ha. Un problema è che la descrizione delle funzionalità PWM di tutti i timer si trova nel manuale di riferimento nella sezione del primo timer (17.5.7 Modalità PWM), quindi devo continuamente saltare avanti e indietro nel documento.

Il PWM su STM8 ha un vantaggio importante rispetto al PWM di Atmega:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
PWM con allineamento al limite
Configurazione del conteggio dal basso verso l'alto
Il conteggio dal basso verso l'alto è attivo se il bit DIR nel registro TIM_CR1 è azzerato
Esempio
L'esempio utilizza la prima modalità PWM. Il segnale di riferimento PWM OCiREF rimane alto finché TIM1_CNT < TIM1_CCRi. Altrimenti assume un livello basso. Se il valore di confronto nel registro TIM1_CCRi è maggiore del valore di caricamento automatico (registro TIM1_ARR), il segnale OCiREF rimane a 1. Se il valore di confronto è 0, OCiREF rimane a zero.

Il timer STM8 durante l'evento di aggiornamento prima controlla valore di confronto, e solo poi fornisce un segnale di riferimento. Nell'Atmega il timer prima conta e poi confronta, il che porta a valore di confronto == 0 un'uscita a forma di ago, con cui bisogna in qualche modo combattere (ad esempio, invertendo programmaticamente la logica).

Quindi, cosa vogliamo fare: PWM a 8 bit (AR == 255), contando dal basso verso l'alto, allineamento al confine. Poiché le lampadine sono collegate ai chip tramite i catodi, il PWM deve generare 0 (LED acceso) fino a valore di confronto e 1 dopo.

Abbiamo già letto di alcuni modalità PWM, pertanto troviamo il registro necessario per il secondo timer cercando nell'manuale di riferimento questa frase (18.6.8 – TIMx_CCMR1):

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
110: Primo modo di funzionamento PWM – contando dal basso verso l'alto, il primo canale è attivo finché TIMx_CNT < TIMx_CCR1. Altrimenti, il primo canale non è attivo. [in seguito nel documento copia errata dal timer 1]
111: Secondo modo di funzionamento PWM – contando dal basso verso l'alto, il primo canale non è attivo finché TIMx_CNT < TIMx_CCR1. Altrimenti, il primo canale è attivo.

Poiché i LED sono collegati al microcontrollore tramite i catodi, ci adatta il secondo modo (anche il primo, ma per ora non lo sappiamo).

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Bit 3 OC1PE: Abilitare il caricamento preventivo dell'uscita 1
0: Il registro di pre-carico su TIMx_CCR1 è disabilitato. È possibile scrivere in TIMx_CCR1 in qualsiasi momento. Il nuovo valore entra in vigore immediatamente.
1: Il registro di pre-carico su TIMx_CCR1 è abilitato. Le operazioni di lettura/scrittura si riferiscono al registro di pre-carico. Il valore pre-caricato di TIMx_CCR1 viene caricato nel registro shadow ad ogni evento di aggiornamento.
*Nota: per il corretto funzionamento della modalità PWM, i registri di pre-carico devono essere abilitati. Questo non è necessario nella modalità single shot (nel registro TIMx_CR1 è impostato il bit OPM).

Ok, attiviamo tutto ciò che serve per i tre canali del secondo timer:

#define TIM2_CCMR1 *(volatile uint8_t *)0x005307
#define TIM2_CCMR2 *(volatile uint8_t *)0x005308
#define TIM2_CCMR3 *(volatile uint8_t *)0x005309

#define PWM_MODE2   0x70 //PWM mode 2, 0b01110000
#define OCxPE       0x08 //preload enable

TIM2_CCMR1 = (PWM_MODE2 | OCxPE);
TIM2_CCMR2 = (PWM_MODE2 | OCxPE);
TIM2_CCMR3 = (PWM_MODE2 | OCxPE);

AR è composto da due registri a otto bit, qui è tutto semplice:

#define TIM2_ARRH  *(volatile uint8_t *)0x00530F
#define TIM2_ARRL  *(volatile uint8_t *)0x005310

TIM2_ARRH = 0;
TIM2_ARRL = 255;

Il secondo timer può contare solo dal basso verso l'alto, allineamento per i bordi, non c'è bisogno di cambiare nulla. Imposteremo il divisore di frequenza, ad esempio, a 256. Per il secondo timer il divisore è impostato nel registro TIM2_PSCR ed è una potenza di due:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Resta da attivare le uscite e il secondo timer stesso. La prima operazione viene risolta tramite i registri Capture/Compare Enable: ci sono due o tre canali distribuiti in modo asimmetrico. Qui possiamo anche scoprire che è possibile cambiare la polarità del segnale, cioè in linea di principio si potrebbe usare anche la modalità PWM 1. Scriviamo:

#define TIM2_CCER1 *(volatile uint8_t *)0x00530A
#define TIM2_CCER2 *(volatile uint8_t *)0x00530B

#define CC1E  (1<<0) // CCER1
#define CC2E  (1<<4) // CCER1
#define CC3E  (1<<0) // CCER2

TIM2_CCER1 = (CC1E | CC2E);
TIM2_CCER2 = CC3E;

Infine, avviamo il timer nel registro TIMx_CR1:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Scriviamo una semplice analogia di AnalogWrite() che passerà al timer i valori da confrontare. I registri si chiamano prevedibilmente registri di cattura/confronto, ce ne sono due per ogni canale: gli 8 bit inferiori in TIM2_CCRxL e gli superiori in TIM2_CCRxH. Poiché abbiamo introdotto un PWM a 8 bit, è sufficiente scrivere solo i bit inferiori:

#define TIM2_CCR1L *(volatile uint8_t *)0x005312
#define TIM2_CCR2L *(volatile uint8_t *)0x005314
#define TIM2_CCR3L *(volatile uint8_t *)0x005316

void setRGBled(uint8_t r, uint8_t g, uint8_t b)
{
    TIM2_CCR1L = r;
    TIM2_CCR2L = g;
    TIM2_CCR3L = b;
}

Il lettore attento noterà che abbiamo ottenuto un PWM leggermente difettoso, incapace di fornire un riempimento del 100% (con un valore massimo di 255 il segnale si inverte per un ciclo del timer). Per i LED questo non ha importanza, e il lettore attento già intuisce come correggerlo.

Il PWM sul secondo timer funziona, passiamo al primo.

Il primo timer ha esattamente gli stessi bit negli stessi registri (i bit che erano stati "riservati" nel secondo timer sono utilizzati attivamente nel primo per varie funzioni avanzate). Pertanto, è sufficiente trovare gli indirizzi di questi stessi registri nel datasheet e copiare il codice. E cambiare il valore del divisore di frequenza, poiché il primo timer vuole ricevere non una potenza di due, ma un valore esatto a 16 bit in due registri. Prescaler High e Basso. Facciamo tutto e… il primo timer non funziona. Qual è il problema?

Il problema può essere risolto solo esaminando l'intero capitolo sui registri di controllo del timer 1, dove cerchiamo quello che non è presente nel secondo timer. Si troverà. 17.7.30 Break register (TIM1_BKR), dove c'è questo bit:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Attivare l'uscita principale

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Ora va tutto bene, codice lì stesso.

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

STM8 Multiplex

Multiplexing su STM8

Il terzo mini-progetto consiste nel collegare otto LED RGB al secondo timer in modalità PWM e farli mostrare colori diversi. Alla base c'è il concetto di multiplexing degli LED, secondo cui se accendiamo e spegniamo gli LED molto rapidamente, ci sembrerà che siano accesi in modo continuo (persistence of vision, inerzia della percezione visiva). Tempo fa ho fatto qualcosa del genere su Arduino qualcosa del genere su Arduino.

L'algoritmo funziona in questo modo:

  • abbiamo collegato l'anodo del primo LED RGB;
  • l'abbiamo acceso inviando i segnali necessari ai catodi;
  • abbiamo atteso la fine del ciclo PWM;
  • abbiamo collegato l'anodo del secondo LED RGB;
  • l'abbiamo acceso…

E così via. Ovviamente, per un funzionamento armonioso, è necessario che il collegamento dell'anodo e l'«accensione» del LED avvengano contemporaneamente. O quasi. In ogni caso, dobbiamo scrivere un codice che emetta i valori su tre canali del secondo timer, cambiandoli al raggiungimento dell'UEV e cambiando contemporaneamente il LED RGB attivo in quel momento.

Poiché lo switch degli LED avviene automaticamente, è necessario creare una «memoria video» dalla quale il gestore delle interruzioni riceverà i dati. Questo è un semplice array:

uint8_t colors[8][3];

Per cambiare il colore di un LED specifico, basta scrivere i valori desiderati in questo array. E il numero del LED attivo sarà gestito dalla variabile

uint8_t cnt;

Demux

Per il corretto multiplexing avremo bisogno, sorprendentemente, del demultiplexer CD74HC238. Il demultiplexer è un chip che realizza hardware il operatore <<. Attraverso tre pin di ingresso (bit 0, 1 e 2) forniamo un numero a tre bit X, e lui risponde attivando l'uscita numero (1<<X). Gli altri ingressi del chip sono utilizzati per scalare l'intera configurazione. Questo chip ci serve non solo per ridurre il numero di pin occupati dal microcontrollore, ma anche per la sicurezza: per evitare di accendere accidentalmente più LED di quanti se ne possano gestire e bruciare il microcontrollore. Il chip costa poco, ed è utile tenerlo sempre nel kit di pronto soccorso.

Il CD74HC238 si occuperà di fornire tensione all'anodo del LED corretto. In un multiplex completo fornirebbe tensione alla colonna tramite un P-MOSFET, ma in questa demo possiamo farlo direttamente, poiché consuma 20 mA, secondo valori massimi assoluti del datasheet. Dal datasheet CD74HC238 abbiamo bisogno della pinout e di questo promemoria:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
H = livello di tensione alto, L = livello di tensione basso, X – indifferentemente

Colleghiamo E2 e E1 a terra, E3, A0, A1 e A3 ai pin PD5, PC3, PC4 e PC5 STM8. Poiché la tabella sopra contiene sia livelli bassi che alti, impostiamo questi pin come uscite push-pull.

PWM

La PWM sul secondo timer viene impostata allo stesso modo della storia precedente, con due differenze:

In primo luogo, dobbiamo abilitare l'interruzione su Update Event (UEV), che chiamerà una funzione per accendere il LED attivo. Questo si ottiene modificando il bit Update Interrupt Enable nel registro con un nome esplicativo

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
Registro di abilitazione delle interruzioni

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

Una seconda differenza è legata a un fenomeno del multiplexing chiamato ghosting – un'illuminazione parassitaria dei diodi. Nel nostro caso, questo può avvenire perché il timer che ha generato l'interruzione su UEV continua a ticchettare, e il gestore dell'interruzione non è in grado di spegnere il LED prima che il timer inizi a scrivere qualcosa agli output. Per affrontare questo problema, dovremo invertire la logica (0 = luminosità massima, 255 = nessuna accensione) e evitare valori estremi del duty cycle. Ossia, dobbiamo fare in modo che dopo UEV i LED si spengano completamente per un ciclo PWM.

Invertiamo la polarità:

//set polarity 
    TIM2_CCER1 |= (CC1P | CC2P);
    TIM2_CCER2 |= CC3P;

Evitare di impostare r, g e b a 255 e ricordarsi di invertirli durante l'uso.

Interruzioni

La sostanza delle interruzioni è che, in determinate circostanze, il chip smette di eseguire il programma principale e chiama una funzione esterna. Le interruzioni si verificano a causa di fattori esterni o interni, incluso il timer.

Quando abbiamo creato il progetto in ST Visual Develop per la prima volta, oltre a main.c , abbiamo ottenuto una finestra con un file misterioso stm8_interrupt_vector.c, automaticamente incluso nel progetto. In questo file, a ogni interruzione è associata una funzione NonHandledInterrupt. Dobbiamo collegare la nostra funzione all'interruzione desiderata.

Nel datasheet c'è una tabella dei vettori di interruzione, dove troviamo quelli necessari:

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8
13 Aggiornamento/ripristino del TIM2
14 Cattura/confronto del TIM2

Dobbiamo cambiare il LED durante l'UEV, quindi abbiamo bisogno dell'interruzione n. 13.

Pertanto, per prima cosa, nel file stm8_interrupt_vector.c cambiamo il nome della funzione associata all'interruzione n. 13 (IRQ13) di default con il nostro:

{0x82, TIM2_Overflow}, /* irq13 */

In secondo luogo, dovremo creare un file main.h con il seguente contenuto:

#ifndef __MAIN_H
#define __MAIN_H

@far @interrupt void TIM2_Overflow (void);
#endif

Infine, dobbiamo registrare questa funzione nel nostro main.c:

@far @interrupt void TIM2_Overflow(void)
{
    PD_ODR &= ~(1<<5); // disattiviamo il demultiplexer
    PC_ODR = (cnt<<3); // registriamo un nuovo valore nel demultiplexer
    PD_ODR |= (1<<5); // attiviamo il demultiplexer

    TIM2_SR1 = 0; // reimpostiamo il flag dell'interruzione di aggiornamento pendente

    cnt++; 
    cnt &= 7; // spostiamo il contatore del LED

    TIM2_CCR1L = ~colors[cnt][0]; // inviamo al buffer i valori invertiti
    TIM2_CCR2L = ~colors[cnt][1]; // per il ciclo PWM successivo
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Rimane da attivare le interruzioni. Questo si fa con un comando assembler rim – dovrai cercarla in Manuale di programmazione:

//enable interrupts
_asm("rim");

Un altro comando assembler è sim – disabilita le interruzioni. Devono essere disattivate durante la scrittura di nuovi valori nella 'memoria video', per evitare che un'interruzione avvenuta in un momento inopportuno danneggi l'array.

Tutto il codice è su GitHub.

Leggendo le schede tecniche 2: SPI su STM32; PWM, timer e interruzioni su STM8

Se questa articolo sarà utile a qualcuno, significa che non l'ho scritto invano. Sarò felice di ricevere commenti e suggerimenti e cercherò di rispondere a tutti.

Fonte: habr.com

Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista un hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster