Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

В prima parte Am încercat să spun inginerilor electronici amatori care au crescut din pantaloni Arduino cum și de ce ar trebui să citească fișele de date și alte documentații pentru microcontrolere. Textul s-a dovedit a fi mare, așa că am promis să arăt exemple practice într-un articol separat. Ei bine, el s-a numit ciupercă de lapte...

Astăzi vă voi arăta cum să utilizați foile de date pentru a rezolva destul de simple, dar necesare pentru multe proiecte, sarcini pe controlerele STM32 (Blue Pill) și STM8. Toate proiectele demo sunt dedicate LED-urilor mele preferate, le vom aprinde în cantități mari, pentru care va trebui să folosim tot felul de periferice interesante.

Textul s-a dovedit din nou a fi imens, așa că, pentru comoditate, fac conținutul:

STM32 Blue Pill: 16 LED-uri cu driver DM634
STM8: Configurarea a șase pini PWM
STM8: 8 LED-uri RGB pe trei pini, întreruperi

Disclaimer: Nu sunt inginer, nu pretind că am cunoștințe profunde în electronică, articolul este destinat amatorilor ca mine. De fapt, acum doi ani m-am considerat publicul țintă. Dacă cineva mi-ar fi spus atunci că foile de date pe un cip necunoscut nu sunt înfricoșătoare de citit, nu aș fi petrecut mult timp căutând niște bucăți de cod pe Internet și inventând cârje cu foarfece și bandă adezivă.

Acest articol se concentrează pe fișele de date, nu pe proiecte, așa că este posibil ca codul să nu fie foarte îngrijit și adesea înghesuit. Proiectele în sine sunt foarte simple, deși potrivite pentru o primă cunoaștere cu noul cip.

Sper că articolul meu va ajuta pe cineva într-un stadiu similar de imersiune în hobby.

STM32

16 LED-uri cu DM634 și SPI

Un proiect mic care folosește Blue Pill (STM32F103C8T6) și driver LED DM634. Folosind foile de date, vom descoperi driverul, porturile STM IO și vom configura SPI.

DM634

Cip taiwanez cu 16 ieșiri PWM pe 16 biți, poate fi conectat în lanțuri. Modelul low-end pe 12 biți este cunoscut dintr-un proiect intern Lightpack. La un moment dat, alegând între DM63x și binecunoscutul TLC5940, am ales DM din mai multe motive: 1) TLC pe Aliexpress este cu siguranță fals, dar acesta nu este; 2) DM are un PWM autonom cu generator de frecvență propriu; 3) ar putea fi cumpărat ieftin de la Moscova, mai degrabă decât să aștepte un colet de la Ali. Și, desigur, a fost interesant să înveți cum să controlezi singur cipul, mai degrabă decât să folosești o bibliotecă gata făcută. Cipurile sunt acum prezentate în principal în pachetul SSOP24; ele sunt ușor de lipit la un adaptor.

Deoarece producătorul este taiwanez, fișa cu date cipul este scris în chineză engleză, ceea ce înseamnă că va fi distractiv. Mai întâi ne uităm la pinout (Conexiune pin) pentru a înțelege ce picior să conectați la ce și o descriere a pinii (Descrierea pinului). 16 pini:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Surse de chiuvetă DC (scurgere deschisă)

Chiuvetă / Ieșire cu scurgere deschisă – scurgere; sursa curentului de intrare; ieșirea este conectată la masă în starea activă - LED-urile sunt conectate la driver prin catozi. Din punct de vedere electric, aceasta nu este, desigur, o „scurgere deschisă” (canal deschis), dar în fișele de date se găsește adesea această denumire pentru pini în modul de scurgere.

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Rezistoare externe între REXT și GND pentru a seta valoarea curentului de ieșire

Între pinul REXT și masă este instalat un rezistor de referință, care controlează rezistența internă a ieșirilor, vezi graficul de la pagina 9 a fișei de date. În DM634, această rezistență poate fi controlată și prin software, setând luminozitatea generală (luminozitatea globală); Nu voi intra în detalii în acest articol, voi pune doar o rezistență de 2.2 - 3 kOhm aici.

Pentru a înțelege cum să controlați cipul, să ne uităm la descrierea interfeței dispozitivului:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

Da, aici este, engleza chineză în toată gloria ei. Traducerea acestui lucru este problematică, o puteți înțelege dacă doriți, dar există o altă modalitate - uitați-vă la modul în care este descrisă conexiunea la TLC5940 similar funcțional în fișa de date:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
... Sunt necesari doar trei pini pentru a introduce date în dispozitiv. Marginea ascendentă a semnalului SCLK mută datele de la pinul SIN la registrul intern. După ce toate datele au fost încărcate, un semnal scurt XLAT înaltă blochează datele transferate secvenţial în registrele interne. Registrele interne sunt porți declanșate de nivelul semnalului XLAT. Toate datele sunt transmise mai întâi pe bitul cel mai semnificativ.

Zăvor – zăvor/ zăvor/ zăvor.
Front crescator – marginea anterioară a pulsului
MSB mai întâi – cel mai semnificativ (cel mai din stânga) bit înainte.
pentru a sincroniza datele – transmiterea datelor secvenţial (bit cu bit).

Word zăvor se găsește adesea în documentația pentru cipuri și este tradus în diverse moduri, așa că, de dragul înțelegerii, îmi voi permite

program educațional micDriverul LED este în esență un registru de deplasare. "Schimb" (schimbare) în nume - mișcarea pe biți a datelor în interiorul dispozitivului: fiecare bit nou introdus în interior împinge întregul lanț înainte în fața lui. Deoarece nimeni nu vrea să observe clipirea haotică a LED-urilor în timpul schimbului, procesul are loc în registre tampon separate de registrele de lucru printr-un amortizor (zăvor) este un fel de sală de așteptare în care biții sunt aranjați în succesiunea dorită. Când totul este gata, obturatorul se deschide și biții trec la lucru, înlocuind lotul anterior. Cuvânt zăvor în documentația pentru microcircuite implică aproape întotdeauna un astfel de amortizor, indiferent în ce combinații este folosit.

Deci, transferul de date către DM634 se realizează astfel: setați intrarea DAI la valoarea celui mai semnificativ bit al LED-ului îndepărtat, trageți DCK în sus și în jos; setați intrarea DAI la valoarea următorului bit, trageți DCK; și așa mai departe până când toți biții au fost transmisi (înăuntru), după care tragem LAT. Acest lucru se poate face manual (bit-bang), dar este mai bine să folosiți o interfață SPI special adaptată pentru aceasta, deoarece este prezentată pe STM32-ul nostru în două exemplare.

Pastila albastră STM32F103

Introducere: Controlerele STM32 sunt mult mai complexe decât Atmega328 decât ar putea părea înfricoșătoare. Mai mult, din motive de economisire a energiei, aproape toate perifericele sunt oprite la pornire, iar frecvența ceasului este de 8 MHz de la sursa internă. Din fericire, programatorii STM au scris cod care aduce cipul la 72 MHz „calculat”, iar autorii tuturor IDE-urilor pe care le cunosc l-au inclus în procedura de inițializare, așa că nu trebuie să facem ceas (dar poti daca vrei cu adevarat). Dar va trebui să porniți perifericele.

Documentație: Blue Pill este echipat cu popularul cip STM32F103C8T6, există două documente utile pentru acesta:

În fișa tehnică ne-ar putea interesa:

  • Pinouts – chip pinouts – în cazul în care decidem să facem singuri plăcile;
  • Harta memoriei – harta memoriei pentru un anumit cip. Manualul de referință are o hartă pentru întreaga linie și menționează registre pe care al nostru nu le are.
  • Tabelul de definiții de pin – listând funcțiile principale și alternative ale pinurilor; pentru „pilula albastră” puteți găsi imagini mai convenabile pe Internet cu o listă de pini și funcțiile acestora. Prin urmare, cautăm imediat pe Google Blue Pill pinout și păstrăm această imagine la îndemână:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
NB: a apărut o eroare în poza de pe Internet, care a fost notă în comentarii, mulțumesc pentru asta. Imaginea a fost înlocuită, dar aceasta este o lecție - este mai bine să verificați informațiile nu din fișele de date.

Îndepărtăm fișa de date, deschidem Manualul de referință și de acum înainte o folosim doar pe ea.
Procedură: ne ocupăm de intrare/ieșire standard, configuram SPI, pornim perifericele necesare.

Intrare ieșire

Pe Atmega328, I/O este implementat extrem de simplu, motiv pentru care abundența de opțiuni STM32 poate fi confuză. Acum avem nevoie doar de concluzii, dar chiar și acestea au patru opțiuni:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
scurgere deschisă, împingere-tragere, împingere-tragere alternativă, scurgere deschisă alternativă

"Trage împinge" (împingere-tragere) este ieșirea obișnuită de la Arduino, pinul poate lua valoarea fie HIGH, fie LOW. Dar cu „scurgere deschisă” există dificultăți, deși de fapt totul este simplu aici:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Configurare ieșire / când portul este alocat la ieșire: / tampon de ieșire activat: / – modul de scurgere deschis: „0” în registrul de ieșire activează N-MOS, „1” în registrul de ieșire lasă portul în modul Hi-Z ( P-MOS nu este activat ) / – modul push-pull: „0” în registrul de ieșire activează N-MOS, „1” în registrul de ieșire activează P-MOS.

Toată diferența dintre scurgerea deschisă (canal deschis) din „push-pull” (împingere-tragere) este că în primul pin nu poate accepta starea HIGH: când scrieți unul în registrul de ieșire, acesta intră în modul de rezistență ridicată (de înaltă impedanță, Bună ziua). Când scrieți zero, pinul se comportă la fel în ambele moduri, atât logic, cât și electric.

În modul normal de ieșire, pinul pur și simplu transmite conținutul registrului de ieșire. În „alternativă” este controlat de perifericele corespunzătoare (vezi 9.1.4):

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Dacă un bit de port este configurat ca pin de funcție alternativă, registrul de pin este dezactivat și pinul este conectat la pinul periferic.

Funcționalitatea alternativă a fiecărui pin este descrisă în Definiții Pin Fișa de date este pe imaginea descărcată. La întrebarea ce trebuie făcut dacă un pin are mai multe funcții alternative, răspunsul este dat de o notă de subsol din foaia de date:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Dacă mai multe periferice folosesc același pin, pentru a evita conflictul între funcțiile alternative, ar trebui utilizat doar un periferic la un moment dat, comutat folosind bitul de activare a ceasului periferic (în registrul RCC corespunzător).

În cele din urmă, pinii din modul de ieșire au și o viteză de ceas. Aceasta este o altă caracteristică de economisire a energiei; în cazul nostru, doar o setăm la maxim și o uităm.

Deci: folosim SPI, ceea ce înseamnă că doi pini (cu date și cu semnal de ceas) ar trebui să fie „funcție alternativă push-pull”, iar un altul (LAT) ar trebui să fie „push-pull obișnuit”. Dar înainte de a le atribui, să ne ocupăm de SPI.

SPI

Un alt mic program educațional

SPI sau Serial Peripheral Interface (interfață serial periferică) este o interfață simplă și foarte eficientă pentru conectarea unui MK cu alte MK-uri și cu lumea exterioară în general. Principiul funcționării sale a fost deja descris mai sus, unde este vorba despre driverul LED chinezesc (în manualul de referință, vezi secțiunea 25). SPI poate funcționa în modul master („master”) și slave („slave”). SPI are patru canale de bază, dintre care nu toate pot fi utilizate:

  • MOSI, Master Output / Slave Input: acest pin transmite date în modul master și primește date în modul slave;
  • MISO, Master Input / Slave Output: dimpotrivă, primește în master și transmite în slave;
  • SCK, Serial Clock: setează frecvența de transmisie a datelor în master sau primește un semnal de ceas în slave. În esență, lovind bătăi;
  • SS, Slave Select: cu ajutorul acestui canal, sclavul știe că se dorește ceva de la el. Pe STM32 se numește NSS, unde N = negativ, adică. controlerul devine slave dacă există masă în acest canal. Se combină bine cu modul Open Drain Output, dar asta este o altă poveste.

Ca orice altceva, SPI pe STM32 este bogat în funcționalități, ceea ce îl face oarecum dificil de înțeles. De exemplu, poate funcționa nu numai cu SPI, ci și cu o interfață I2S, iar în documentație descrierile lor sunt amestecate, este necesar să tăiați excesul în timp util. Sarcina noastră este extrem de simplă: trebuie doar să trimitem date folosind doar MOSI și SCK. Trecem la secțiunea 25.3.4 (comunicare semi-duplex, comunicare semi-duplex), unde găsim 1 ceas și 1 fir de date unidirecțional (1 semnal de ceas și 1 flux de date unidirecțional):

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
În acest mod, aplicația folosește SPI fie în modul numai de transmisie, fie în modul numai de recepție. / Modul numai de transmisie este similar cu modul duplex: datele sunt transmise pe pinul de transmisie (MOSI în modul master sau MISO în modul slave), iar pinul de recepție (MISO sau respectiv MOSI) poate fi folosit ca pin I/O obișnuit . În acest caz, aplicația trebuie doar să ignore bufferul Rx (dacă este citit, nu vor fi date transferate acolo).

Grozav, pinul MISO este liber, haideți să conectăm semnalul LAT la el. Să ne uităm la Slave Select, care pe STM32 poate fi controlat programatic, ceea ce este extrem de convenabil. Citim paragraful cu același nume în secțiunea 25.3.1 Descriere generală SPI:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Controlul software NSS (SSM = 1) / Informațiile de selecție a slave sunt conținute în bitul SSI al registrului SPI_CR1. Pinul extern NSS rămâne liber pentru alte nevoi ale aplicației.

E timpul să scriem în registre. Am decis să folosesc SPI2, căutați adresa de bază în foaia de date - în secțiunea 3.3 Harta memoriei:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

Ei bine, să începem:

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

Deschideți secțiunea 25.3.3 cu titlul auto-explicativ „Configurarea SPI în modul Master”:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

1. Setați frecvența ceasului serial cu biții BR[2:0] în registrul SPI_CR1.

Registrele sunt colectate în secțiunea manuală de referință cu același nume. Schimbarea adresei (Compensarea adresei) pentru CR1 – 0x00, în mod implicit toți biții sunt șterși (Resetează valoarea 0x0000):

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

Biții BR setează divizorul de ceas al controlerului, determinând astfel frecvența la care va funcționa SPI. Frecvența noastră STM32 va fi de 72 MHz, driverul LED, conform fișei sale, funcționează cu o frecvență de până la 25 MHz, așa că trebuie să împărțim la patru (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. Setați biții CPOL și CPHA pentru a defini relația dintre transferul de date și sincronizarea ceasului serial (vezi diagrama de la pagina 240)

Deoarece citim o fișă de date aici și nu ne uităm la scheme, să aruncăm o privire mai atentă la descrierea textului biților CPOL și CPHA de la pagina 704 (Descriere generală SPI):

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Faza ceasului și polaritatea
Folosind biții CPOL și CPHA ai registrului SPI_CR1, puteți selecta în mod programatic patru relații de sincronizare. Bitul CPOL (polaritatea ceasului) controlează starea semnalului de ceas atunci când nu sunt transmise date. Acest bit controlează modurile master și slave. Dacă CPOL este resetat, pinul SCK este scăzut în modul de repaus. Dacă bitul CPOL este setat, pinul SCK este ridicat în timpul modului de repaus.
Când bitul CPHA (fază de ceas) este setat, stroboscopul de captare a bitului înalt este a doua margine a semnalului SCK (scădere dacă CPOL este clar, crescând dacă CPOL este setat). Datele sunt captate de a doua modificare a semnalului de ceas. Dacă bitul CPHA este clar, stroboscopul de captare a bitului înalt este marginea ascendentă a semnalului SCK (marginea descendentă dacă este setat CPOL, marginea ascendentă dacă CPOL este șters). Datele sunt captate la prima modificare a semnalului de ceas.

După ce am absorbit aceste cunoștințe, ajungem la concluzia că ambii biți trebuie să rămână zero, deoarece Dorim ca semnalul SCK să rămână scăzut atunci când nu este utilizat, iar datele să fie transmise pe marginea ascendentă a pulsului (vezi Fig. Front crescator în fișa DM634).

Apropo, aici am întâlnit prima dată o caracteristică a vocabularului din foile de date ST: în ele este scrisă expresia „resetează bitul la zero” pentru a reseta un picȘi nu sa limpezeasca putin, cum ar fi, de exemplu, Atmega.

3. Setați bitul DFF pentru a determina dacă blocul de date este format pe 8 biți sau pe 16 biți

Am luat în mod special un DM16 pe 634 biți pentru a nu mă deranja să transmit date PWM pe 12 biți, cum ar fi DM633. Este logic să setați DFF la unul:

#define DFF         0x0800

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

4. Configurați bitul LSBFIRST în registrul SPI_CR1 pentru a determina formatul blocului

LSBFIRST, după cum sugerează și numele, configurează transmisia mai întâi cu bitul cel mai puțin semnificativ. Dar DM634 vrea să primească date începând de la bitul cel mai semnificativ. Prin urmare, îl lăsăm resetat.

5. În modul hardware, dacă este necesară intrarea de la pinul NSS, aplicați un semnal ridicat pinului NSS pe toată durata secvenței de transfer de octeți. În modul software NSS, setați biții SSM și SSI în registrul SPI_CR1. Dacă pinul NSS urmează să fie folosit ca ieșire, trebuie setat doar bitul SSOE.

Instalați SSM și SSI pentru a uita de modul hardware NSS:

#define SSI         0x0100
#define SSM         0x0200

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

6. Biții MSTR și SPE trebuie setați (ea rămân setați doar dacă semnalul NSS este ridicat)

De fapt, cu acești biți desemnăm SPI-ul nostru drept master și îl activăm:

#define MSTR        0x0004
#define SPE         0x0040

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

SPI este configurat, să scriem imediat funcții care trimit octeți către driver. Continuați să citiți 25.3.3 „Configurarea SPI în modul master”:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Ordin de transfer de date
Transmisia începe când un octet este scris în memoria tampon Tx.
Octetul de date este încărcat în registrul de deplasare la paralel mod (din magistrala internă) în timpul transmiterii primului bit, după care este transmis către secvenţial Modul PIN MOSI, primul sau ultimul bit înainte în funcție de setarea bitului LSBFIRST din registrul CPI_CR1. Steagul TXE este setat după transmiterea datelor de la tamponul Tx la registrul de deplasare, și generează, de asemenea, o întrerupere dacă bitul TXEIE din registrul CPI_CR1 este setat.

Am evidențiat câteva cuvinte în traducere pentru a atrage atenția asupra unei caracteristici a implementării SPI în controlerele STM. Pe Atmega, steagul TXE (Tx gol, Tx este gol și gata să primească date) este setat numai după ce a fost trimis întregul octet exterior. Și aici acest flag este setat după ce octetul a fost introdus în registrul de deplasare intern. Deoarece este împins acolo cu toți biții în același timp (în paralel), iar apoi datele sunt transmise secvențial, TXE este setat înainte ca octetul să fie complet trimis. Acest lucru este important pentru că în cazul driverului nostru LED, trebuie să tragem pinul LAT după trimitere toate date, adică Doar steagul TXE nu va fi suficient pentru noi.

Asta înseamnă că avem nevoie de un alt steag. Să ne uităm la 25.3.7 - „Semnale de stare”:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
<...>
Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Steagul OCUPAT
Indicatorul BSY este setat și șters de hardware (scrierea pe acesta nu are efect). Indicatorul BSY indică starea stratului de comunicare SPI.
Se resetează:
când transferul este finalizat (cu excepția modului master dacă transferul este continuu)
când SPI este dezactivat
când apare o eroare în modul master (MODF=1)
Dacă transferul nu este continuu, indicatorul BSY este șters între fiecare transfer de date

Bine, asta va fi util. Să aflăm unde se află tamponul Tx. Pentru a face acest lucru, citiți „Registrul de date SPI”:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Biți 15:0 DR[15:0] Registrul de date
Date primite sau date de transmis.
Registrul de date este împărțit în două buffere - unul pentru scriere (buffer de transmisie) și unul pentru citire (buffer de recepție). Scrierea în registrul de date scrie în tamponul Tx, iar citirea din registrul de date va returna valoarea conținută în tamponul Rx.

Ei bine, și registrul de stare, unde se găsesc steagurile TXE și BSY:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

Noi scriem:

#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
}

Ei bine, din moment ce trebuie să transmitem de 16 ori doi octeți, în funcție de numărul de ieșiri ale driverului LED, ceva de genul acesta:

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

    while (_SPI2_(_SPI_SR) & BSY); // finish transmission

    LAT_pulse();
}

Dar nu știm încă cum să tragem pinul LAT, așa că ne vom întoarce la I/O.

Atribuirea de pini

În STM32F1, registrele responsabile pentru starea pinilor sunt destul de neobișnuite. Este clar că sunt mai multe decât Atmega, dar sunt și diferite de alte cipuri STM. Secțiunea 9.1 Descrierea generală a GPIO:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Fiecare dintre porturile de I/O de uz general (GPIO) are două registre de configurare pe 32 de biți (GPIOx_CRL și GPIOx_CRH), două registre de date pe 32 de biți (GPIOx_IDR și GPIOx_ODR), un registru de setare/resetare pe 32 de biți (GPIOx_BSRR), un registru de resetare pe 16 biți (GPIOx_BRR) și un registru de resetare de 32 de biți registru de blocare a biților (GPIOx_LCKR).

Primele două registre sunt neobișnuite și, de asemenea, destul de incomode, deoarece cei 16 pini de porturi sunt împrăștiați peste ele într-un format „patru biți per frate”. Acestea. pinii de la zero la șapte sunt în CRL, iar restul sunt în CRH. În același timp, registrele rămase conțin cu succes biții tuturor pinii portului - rămânând adesea jumătate „rezervate”.

Pentru simplitate, să începem de la sfârșitul listei.

Nu avem nevoie de un registru de blocare.

Registrele de setare și de resetare sunt destul de amuzante prin faptul că se dublează parțial unul pe celălalt: puteți scrie totul numai în BSRR, unde cei 16 biți mai mari vor reseta pinul la zero, iar cei inferioare vor fi setați la 1, sau puteți, de asemenea utilizați BRR, cei 16 biți inferiori din care doar resetați pinul. Imi place a doua varianta. Aceste registre sunt importante deoarece oferă acces atomic la pini:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Setare atomică sau Resetare
Nu este nevoie să dezactivați întreruperile la programarea GPIOx_ODR la nivel de biți: unul sau mai mulți biți pot fi modificați cu o singură operație de scriere atomică APB2. Acest lucru se realizează prin scrierea unui „1” în registrul de setare/resetare (GPIOx_BSRR sau, numai pentru resetare, GPIOx_BRR) al bitului care trebuie schimbat. Alți biți vor rămâne neschimbați.

Registrele de date au nume destul de explicite - IDR = Intrare Registrul de direcție, registrul de intrare; ODR = producție Registrul de direcție, registrul de ieșire. Nu vom avea nevoie de ele în proiectul actual.

Și în sfârșit, registrele de control. Deoarece suntem interesați de al doilea pini SPI, și anume PB13, PB14 și PB15, ne uităm imediat la CRH:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

Și vedem că va trebui să scriem ceva în biți de la 20 la 31.

Ne-am dat deja seama mai sus ce vrem de la pini, așa că aici mă voi descurca fără o captură de ecran, voi spune doar că MODE specifică direcția (intrarea dacă ambii biți sunt setați la 0) și viteza pinii (avem nevoie de 50MHz, adică. ambele pin la „1”), iar CNF setează modul: „push-pull” obișnuit – 00, „alternativ” – 10. În mod implicit, așa cum vedem mai sus, toți pinii au al treilea bit de jos (CNF0), le pune pe modul intrare flotantă.

Deoarece intenționez să fac altceva cu acest cip, pentru simplitate am definit toate valorile posibile MODE și CNF atât pentru registrele de control inferioare, cât și pentru cele superioare.

Cumva așa

#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

Pinii noștri sunt localizați pe portul B (adresa de bază – 0x40010C00), cod:

#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;

Și, în consecință, puteți scrie definiții pentru LAT, care vor fi zvâcnite de registrele BRR și 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 doar prin inerție, așa a fost mereu, lasă-l să rămână)

Acum totul este grozav, dar nu funcționează. Deoarece acesta este STM32, economisesc energie electrică, ceea ce înseamnă că trebuie să activați sincronizarea perifericelor necesare.

Activați ceasul

Ceasul, cunoscut și sub numele de Ceas, este responsabil de ceas. Și am putut observa deja abrevierea RCC. Îl căutăm în documentație: acesta este Reset și Clock Control.

După cum s-a spus mai sus, din fericire, cea mai dificilă parte a subiectului de cronometrare a fost făcută pentru noi de către oameni de la STM, pentru care le mulțumim foarte mult (încă o dată voi da un link către Site-ul lui Di Halt, ca să fie clar cât de confuz este). Avem nevoie doar de registre responsabile pentru activarea tacării periferice (Peripheral Clock Enable Registers). Mai întâi, să găsim adresa de bază a RCC, este chiar la începutul „Harții memoriei”:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

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

Și apoi fie dați clic pe linkul unde încercați să găsiți ceva în placă, fie, mult mai bine, parcurgeți descrierile registrelor de activare din secțiunile despre activați registrele. Unde vom găsi RCC_APB1ENR și RCC_APB2ENR:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

Și, în consecință, conțin biți care includ tactarea SPI2, IOPB (I/O Port B) și funcții 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;

Codul final poate fi găsit aici.

Dacă aveți ocazia și doriți să testați, atunci conectați DM634 astfel: DAI la PB15, DCK la PB13, LAT la PB14. Alimentam driverul de la 5 volți, nu uitați să conectați împământarea.

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

STM8 PWM

PWM pe STM8

Când tocmai plănuiam acest articol, am decis, ca exemplu, să încerc să stăpânesc unele funcționalități ale unui cip necunoscut folosind doar o foaie de date, astfel încât să nu ajung cu un cizmar fără cizme. STM8 a fost ideal pentru acest rol: în primul rând, am avut câteva plăci chinezești cu STM8S103 și, în al doilea rând, nu este foarte popular și, prin urmare, tentația de a citi și de a găsi o soluție pe Internet se bazează pe lipsa acestor soluții.

Cipul are si fișa cu date и manual de referință RM0016, în primul există adrese de pinout și înregistrare, în al doilea - orice altceva. STM8 este programat în C într-un IDE teribil ST Visual Develop.

Clock și I/O

Implicit, STM8 funcționează la o frecvență de 2 MHz, acest lucru trebuie corectat imediat.

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Ceas HSI (Intern de mare viteză).
Semnalul de ceas HSI este derivat dintr-un oscilator RC intern de 16 MHz cu un divizor programabil (1 la 8). Este setat în registrul divizor al ceasului (CLK_CKDIVR).
Notă: la început, un oscilator HSI RC cu un divizor de 8 este selectat ca sursă principală a semnalului de ceas.

Găsim adresa registrului în fișa de date, descrierea în refman și vedem că registrul trebuie șters:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Deoarece vom rula PWM și vom conecta LED-urile, să ne uităm la pinout:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

Cipul este mic, multe funcții sunt suspendate pe aceiași pini. Ceea ce este între paranteze drepte este „funcționalitate alternativă”, este comutat prin „octeți de opțiune” (octeți de opțiune) – ceva de genul siguranțelor Atmega. Puteți modifica valorile lor în mod programatic, dar nu este necesar, deoarece Noua funcționalitate este activată numai după o repornire. Este mai ușor să utilizați ST Visual Programmer (descărcat cu Visual Develop), care poate schimba acești octeți. Pinout-ul arată că pinii CH1 și CH2 ai primului temporizator sunt ascunși între paranteze pătrate; este necesar să setați biții AFR1 și AFR0 în STVP, iar al doilea va transfera și ieșirea CH1 a celui de-al doilea temporizator de la PD4 la PC5.

Astfel, 6 pini vor controla LED-urile: PC6, PC7 și PC3 pentru primul timer, PC5, PD3 și PA3 pentru al doilea.

Configurarea pinilor I/O pe STM8 este mai simplă și mai logică decât pe STM32:

  • familiar din registrul de direcție a datelor Atmega DDR (Registrul de direcție a datelor): 1 = ieșire;
  • primul registru de control CR1, la ieșire, setează modul push-pull (1) sau scurgere deschisă (0); din moment ce conectez LED-urile la cip cu catozi, las zerouri aici;
  • al doilea registru de control CR2, la ieșire, setează viteza de ceas: 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

Setare PWM

Mai întâi, să definim termenii:

  • Frecvența PWM – frecvența cu care bifează cronometrul;
  • Reîncărcare automată, AR – valoare autoîncărcabilă până la care va conta cronometrul (perioada pulsului);
  • Eveniment de actualizare, UEV – un eveniment care are loc atunci când cronometrul a numărat până la AR;
  • Ciclu de lucru PWM – ciclu de lucru PWM, numit adesea „factor de sarcină”;
  • Capturați/Comparați valoarea – valoarea pentru captură/comparație, la care a numărat temporizatorul va face ceva (în cazul PWM, inversează semnalul de ieșire);
  • Valoarea de preîncărcare – valoare preîncărcată. Comparați valoarea nu se poate schimba în timp ce temporizatorul bifează, altfel ciclul PWM se va întrerupe. Prin urmare, noile valori transmise sunt plasate într-un buffer și scoase atunci când cronometrul ajunge la sfârșitul numărătoarei inverse și este resetat;
  • Aliniat la margine и Moduri aliniate la centru – aliniere de-a lungul graniței și în centru, la fel ca și a lui Atmel PWM rapid и PWM corect de fază.
  • OCiREF, semnal de referință de comparare de ieșire – semnal de ieșire de referință, de fapt, ceea ce apare pe pinul corespunzător în modul PWM.

După cum este deja clar din pinout, două temporizatoare au capacități PWM - primul și al doilea. Ambele sunt pe 16 biți, primul are o mulțime de caracteristici suplimentare (în special, poate număra atât în ​​sus, cât și în jos). Avem nevoie ca amândoi să lucreze în mod egal, așa că am decis să încep cu cel de-al doilea evident mai sărac, pentru a nu folosi accidental ceva care nu există. O problemă este că descrierea funcționalității PWM a tuturor temporizatoarelor din manualul de referință este în capitolul despre primul temporizator (17.5.7 PWM Mode), așa că trebuie să săriți înainte și înapoi prin document tot timpul.

PWM pe STM8 are un avantaj important față de PWM pe Atmega:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
PWM aliniat la limită
Configurarea contului de jos în sus
Numărarea de jos în sus este activă dacă bitul DIR din registrul TIM_CR1 este șters
Exemplu
Exemplul folosește primul mod PWM. Semnalul de referință PWM OCiREF este menținut ridicat atâta timp cât TIM1_CNT < TIM1_CCRi. Altfel este nevoie de un nivel scăzut. Dacă valoarea de comparație din registrul TIM1_CCRi este mai mare decât valoarea de încărcare automată (registrul TIM1_ARR), semnalul OCiREF este menținut la 1. Dacă valoarea de comparație este 0, OCiREF este menținut la zero....

Cronometrul STM8 în timpul eveniment de actualizare verificări mai întâi comparați valoarea, și abia apoi produce un semnal de referință. Cronometrul Atmega se dărâmă mai întâi și apoi compară, rezultând compare value == 0 ieșirea este un ac, care trebuie tratat cumva (de exemplu, prin inversarea programatică a logicii).

Deci, ce vrem să facem: PWM pe 8 biți (AR == 255), numărând de jos în sus, alinierea de-a lungul graniței. Deoarece becurile sunt conectate la cip prin catozi, PWM ar trebui să iasă 0 (LED aprins) până când comparați valoarea si 1 dupa.

Am citit deja despre unele Modul PWM, deci găsim registrul necesar al celui de-al doilea cronometru căutând în manualul de referință această frază (18.6.8 - TIMx_CCMR1):

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
110: Primul mod PWM – când se numără de jos în sus, primul canal este activ în timp ce TIMx_CNT < TIMx_CCR1. În caz contrar, primul canal este inactiv. [mai departe în document există un copy-paste eronat de la timer 1] 111: Al doilea mod PWM – când se numără de jos în sus, primul canal este inactiv în timp ce TIMx_CNT < TIMx_CCR1. În caz contrar, primul canal este activ.

Deoarece LED-urile sunt conectate la MK prin catozi, al doilea mod ni se potrivește (și primul, dar nu știm asta încă).

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Bit 3 OC1PE: Activați preîncărcarea pinului 1
0: Registrul de preîncărcare pe TIMx_CCR1 este dezactivat. Puteți scrie oricând la TIMx_CCR1. Noua valoare funcționează imediat.
1: Registrul de preîncărcare pe TIMx_CCR1 este activat. Operațiile de citire/scriere accesează registrul de preîncărcare. Valoarea preîncărcată TIMx_CCR1 este încărcată în registrul umbră în timpul fiecărui eveniment de actualizare.
*Notă: Pentru ca modul PWM să funcționeze corect, registrele de preîncărcare trebuie să fie activate. Acest lucru nu este necesar în modul semnal unic (bitul OPM este setat în registrul TIMx_CR1).

Bine, să pornim tot ce avem nevoie pentru cele trei canale ale celui de-al doilea cronometru:

#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 constă din două registre de opt biți, totul este simplu:

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

TIM2_ARRH = 0;
TIM2_ARRL = 255;

Al doilea cronometru poate conta doar de jos în sus, alinierea de-a lungul graniței, nimic nu trebuie schimbat. Să setăm divizorul de frecvență, de exemplu, la 256. Pentru al doilea temporizator, divizorul este setat în registrul TIM2_PSCR și este o putere de doi:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Tot ce rămâne este să pornești concluziile și al doilea cronometru în sine. Prima problemă este rezolvată prin registre Captură/Compara Permite: există două, trei canale împrăștiate peste ele asimetric. Aici putem afla, de asemenea, că este posibil să se schimbe polaritatea semnalului, adică în principiu, a fost posibil să se utilizeze Modul PWM 1. Scriem:

#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;

Și, în sfârșit, pornim cronometrul în registrul TIMx_CR1:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Să scriem un analog simplu al AnalogWrite(), care va transfera valorile reale la cronometru pentru comparație. Registrele sunt denumite previzibil Captură/Compara registre, există doi dintre ei pentru fiecare canal: cei 8 biți de ordin inferior în TIM2_CCRxL și cei de ordin înalt în TIM2_CCRxH. Deoarece am creat un PWM de 8 biți, este suficient să scrieți doar biții mai puțin semnificativi:

#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;
}

Cititorul atent va observa că avem un PWM ușor defect, incapabil să producă umplere 100% (la o valoare maximă de 255, semnalul este inversat pentru un ciclu de cronometru). Pentru LED-uri, acest lucru nu contează, iar cititorul atent poate deja ghici cum să-l repare.

PWM pe al doilea timer funcționează, să trecem la primul.

Primul timer are exact aceiași biți în aceleași registre (doar că acei biți care au rămas „rezervați” în al doilea timer sunt folosiți activ în primul pentru tot felul de lucruri avansate). Prin urmare, este suficient să găsiți adresele acelorași registre în fișa de date și să copiați codul. Ei bine, schimbați valoarea divizorului de frecvență, pentru că... primul timer vrea să primească nu o putere de doi, ci o valoare exactă de 16 biți în două registre Prescaler High и Jos. Facem totul și... primul timer nu funcționează. Ce s-a întâmplat?

Problema poate fi rezolvată doar căutând întreaga secțiune despre registrele de control ale cronometrului 1, unde îl căutăm pe cel pe care al doilea cronometru nu îl are. Vor exista 17.7.30 Registrul de rupere (TIM1_BKR), unde este acest bit:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Activați ieșirea principală

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Asta e totul sigur acum, codul Acolo.

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

STM8 Multiplex

Multiplexare pe STM8

Al treilea mini-proiect este de a conecta opt LED-uri RGB la al doilea timer în modul PWM și de a le face să arate culori diferite. Se bazează pe conceptul de multiplexare LED, și anume că dacă porniți și opriți LED-urile foarte, foarte repede, ni se va părea că sunt aprinse în permanență (persistența vederii, inerția percepției vizuale). Am făcut o dată ceva de genul acesta pe Arduino.

Algoritmul de lucru arată astfel:

  • conectat anodul primului LED RGB;
  • aprins-o, trimițând semnalele necesare către catozi;
  • așteptat până la sfârșitul ciclului PWM;
  • conectat anodul celui de-al doilea LED RGB;
  • a aprins-o...

Ei bine, etc. Desigur, pentru o funcționare frumoasă este necesar ca anodul să fie conectat și LED-ul să fie „aprins” în același timp. Ei bine, sau aproape. În orice caz, trebuie să scriem un cod care va scoate valori în trei canale ale celui de-al doilea temporizator, să le schimbăm când se atinge UEV și, în același timp, să schimbăm LED-ul RGB activ în prezent.

Deoarece comutarea LED-urilor este automată, trebuie să creăm o „memorie video” din care gestionarea întreruperii va primi date. Acesta este un tablou simplu:

uint8_t colors[8][3];

Pentru a schimba culoarea unui anumit LED, va fi suficient să scrieți valorile necesare în această matrice. Și variabila va fi responsabilă pentru numărul LED-ului activ

uint8_t cnt;

Demux

Pentru o multiplexare corectă, avem nevoie, destul de ciudat, de un demultiplexor CD74HC238. Demultiplexer - un cip care implementează operatorul în hardware <<. Prin trei pini de intrare (biții 0, 1 și 2) îi dăm un număr X pe trei biți, iar ca răspuns activează numărul de ieșire (1<<X). Intrările rămase ale cipului sunt folosite pentru a scala întregul design. Avem nevoie de acest cip nu numai pentru a reduce numărul de pini ocupați ai microcontrolerului, ci și pentru siguranță - pentru a nu aprinde accidental mai multe LED-uri decât este posibil și a nu arde MK-ul. Cipul costă un ban și trebuie păstrat întotdeauna în dulapul cu medicamente de acasă.

CD74HC238 va fi responsabil pentru alimentarea cu tensiune la anodul LED-ului dorit. Într-un multiplex cu drepturi depline, ar furniza tensiune coloanei printr-un P-MOSFET, dar în această demonstrație este posibil direct, deoarece trage 20 mA, conform evaluări maxime absolute în fișa de date. Din Foaie de date CD74HC238 avem nevoie de pinouts și această foaie de cheat:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
H = nivel de tensiune înaltă, L = nivel de tensiune scăzută, X – nu-ți pasă

Conectam E2 și E1 la masă, E3, A0, A1 și A3 la pinii PD5, PC3, PC4 și PC5 ai STM8. Deoarece tabelul de mai sus conține atât niveluri scăzute, cât și niveluri înalte, configurăm acești pini ca pini push-pull.

PWM

PWM pe al doilea timer este configurat în același mod ca în povestea anterioară, cu două diferențe:

În primul rând, trebuie să activăm întreruperea Actualizare eveniment (UEV) care va apela o funcție care comută LED-ul activ. Acest lucru se face prin schimbarea bitului Actualizare Activare întrerupere într-un registru cu un nume grăitor

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
Întreruperea registrului de activare

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

A doua diferență este legată de fenomenul de multiplexare, cum ar fi reflexii – strălucire parazită a diodelor. În cazul nostru, poate apărea din cauza faptului că temporizatorul, după ce a provocat o întrerupere pe UEV, continuă să bifeze, iar operatorul de întrerupere nu are timp să comute LED-ul înainte ca temporizatorul să înceapă să scrie ceva pe pini. Pentru a combate acest lucru, va trebui să inversați logica (0 = luminozitate maximă, 255 = nimic nu este aprins) și să evitați valorile extreme ale ciclului de lucru. Acestea. asigurați-vă că după UEV LED-urile se sting complet pentru un ciclu PWM.

Schimbarea polarității:

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

Evitați să setați r, g și b la 255 și nu uitați să le inversați atunci când le utilizați.

întreruperi

Esența unei întreruperi este că, în anumite circumstanțe, cipul încetează să execute programul principal și apelează o funcție externă. Întreruperile apar din cauza influențelor externe sau interne, inclusiv a temporizatorului.

Când am creat prima dată un proiect în ST Visual Develop, pe lângă main.c am primit o fereastră cu un dosar misterios stm8_interrupt_vector.c, incluse automat în proiect. În acest fișier, fiecărei întreruperi este atribuită o funcție NonHandledInterrupt. Trebuie să ne legăm funcția la întreruperea dorită.

Fișa de date are un tabel de vectori de întrerupere, unde îi găsim pe cei de care avem nevoie:

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8
13 Actualizare/depășire TIM2
14 Captură/comparare TIM2

Trebuie să schimbăm LED-ul la UEV, așa că avem nevoie de întrerupere #13.

În consecință, în primul rând, în dosar stm8_interrupt_vector.c schimbați numele implicit al funcției responsabile pentru întreruperea nr. 13 (IRQ13) cu propriul dvs.:

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

În al doilea rând, va trebui să creăm un fișier main.h cu urmatorul continut:

#ifndef __MAIN_H
#define __MAIN_H

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

Și, în sfârșit, scrieți această funcție în dvs main.c:

@far @interrupt void TIM2_Overflow (void)
{
    PD_ODR &= ~(1<<5); // вырубаем демультиплексор
    PC_ODR = (cnt<<3); // записываем в демультиплексор новое значение
    PD_ODR |= (1<<5); // включаем демультиплексор

    TIM2_SR1 = 0; // сбрасываем флаг Update Interrupt Pending

    cnt++; 
    cnt &= 7; // двигаем счетчик LED

    TIM2_CCR1L = ~colors[cnt][0]; // передаем в буфер инвертированные значения
    TIM2_CCR2L = ~colors[cnt][1]; // для следующего цикла ШИМ
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Tot ce rămâne este să activați întreruperile. Acest lucru se face folosind comanda assembler rim - va trebui să-l cauți înăuntru Manual de programare:

//enable interrupts
_asm("rim");

O altă comandă a asamblatorului este sim – dezactivează întreruperile. Ele trebuie dezactivate în timp ce valori noi sunt scrise în „memoria video”, astfel încât o întrerupere cauzată la momentul nepotrivit să nu strice matricea.

Tot codul - pe GitHub.

Citirea fiselor tehnice 2: SPI pe STM32; PWM, temporizatoare și întreruperi pe STM8

Dacă măcar cineva consideră acest articol util, atunci nu l-am scris degeaba. Voi fi bucuros să primesc comentarii și observații, voi încerca să răspund la toate.

Sursa: www.habr.com

Adauga un comentariu