
În Am încercat să explic pasionaților de electronică, care au crescut din pantalonașele Arduino, cum și de ce ar trebui să citească foile de date și alte documente despre microcontrolere. Textul a ieșit destul de lung, așa că am promis că voi arăta exemple practice într-un articol separat. Ei bine, mi-am asumat responsabilitatea...
Astăzi voi arăta cum, folosind foile de date, putem rezolva probleme destul de simple dar necesare pentru multe proiecte cu controlerele STM32 (Blue Pill) și STM8. Toate proiectele demo sunt dedicate LED-urilor mele preferate, pe care le vom aprinde în cantități mari, pentru care va trebui să folosim tot felul de periferice interesante.
Textul a ieșit iarăși uriaș, așa că, pentru comoditate, fac un cuprins:
Declinarea răspunderii: nu sunt inginer, nu pretind cunoștințe profunde în electronică, articolul este destinat celor ca mine, iubitori. De fapt, ca public țintă, m-am gândit la mine acum doi ani. Dacă cineva mi-ar fi spus atunci că nu este înfricoșător să citesc foile de date pentru un cip necunoscut, nu aș fi pierdut atât de mult timp căutând bucăți de cod pe internet și inventând soluții improvizate cu foarfece și bandă adezivă.
În centrul acestui articol se află foile de date și nu proiectele, așa că codul poate să nu fie perfect stilizat și adesea poate fi improvizat. Proiectele în sine sunt foarte simple, chiar dacă sunt potrivite pentru o primă întâlnire cu un cip nou.
Sper că articolul meu va ajuta pe cineva în asemenea etape de inițiere în hobby.
STM32
16 LED-uri cu DM634 și SPI
Un proiect mic folosind Blue Pill (STM32F103C8T6) și driverul de LED-uri DM634. Vom explora driverul, porțile IO STM și vom configura SPI folosind foile de date.
DM634
Cip taiwanez cu 16 ieșiri PWM de 16 biți, poate fi conectat în lanțuri. Modelul de bază de 12 biți este cunoscut dintr-un proiect autohton . La un moment dat, când am ales între DM63x și binecunoscutul TLC5940, m-am oprit asupra DM din mai multe motive: 1) TLC de pe Aliexpress este cu siguranță fals, iar acesta nu; 2) DM are un PWM autonom cu generator de frecvență propriu; 3) a fost disponibil la un preț accesibil în Moscova, fără a aștepta o livrare de pe Aliexpress. Și, desigur, mi-a plăcut să învăț singur cum să controlez cipul, în loc să folosesc o librărie gata făcută. Cipurile sunt acum în principal disponibile în carcasă SSOP24, nu sunt greu de sudat pe un adaptor.
Deoarece producătorul este din Taiwan, pentru cip este scrisă în engleză chineză, așa că va fi amuzant. Începem cu analiza pinilor (Conectarea pinilor), pentru a înțelege la ce pin se conectează fiecare, și descrierea pinilor (Descrierea pinilor). 16 ieșiri:

Surse de curent continuu (stoc deschis)
Sink / ieșire open-drain – stoc; sursa de curent care intră; ieșirea, activă atunci când este conectată la masă, se conectează la driverele LED prin catod. Din punct de vedere electric, aceasta nu este, bineînțeles, un 'stoc deschis' ( open drain), dar în fișele tehnice, această denumire pentru pinurile în mod stoc apare adesea.Rezistoare externe între REXT și GND pentru stabilirea valorii curentului de ieșire
![]()
Între pinul REXT și masă se instalează un rezistor de referință, care controlează rezistența internă a ieșirilor, vezi graficul de pe pagina 9 a fișei tehnice. În DM634, această rezistență poate fi, de asemenea, controlată software, stabilind luminozitatea globală (
luminozitate globală); în acest articol nu voi intra în detalii, doar voi menționa un rezistor de 2.2 – 3 kΩ.Pentru a înțelege cum să controlăm cipul, să ne uităm la descrierea interfeței dispozitivului:
Ah, iată-l, engleză chineză în toată splendoarea sa. Traducerea este problematică, dar poate fi înțeleasă, dacă vrei, dar există o altă cale – să vedem cum se descrie conexiunea în fișa tehnică pentru TLC5940, care funcționează similar:

… Pentru a introduce date în dispozitiv sunt necesari doar trei pini. Frontul pozitiv al semnalului SCLK deplasează datele de pe pinul SIN în registrul intern. După ce toate datele sunt încărcate, un scurt semnal de înaltă intensitate XLAT fixează datele transmise secvențial în registrele interne. Registrele interne sunt declanșate la nivelul semnalului XLAT al latch-urilor. Toate datele sunt transmise din cel mai semnificativ bit.

Latch
– latch/oglinză/fixator. Frontul crescător
– frontul impulsului MSB first
MSB prima – cu bitul cel mai semnificativ (cel din stânga) înainte.
pentru a sincroniza datele – a transmite datele secvențial (câte un bit).
Cuvântul zăvor se întâlnește adesea în documentația chipurilor și este tradus în diverse moduri, așa că pentru înțelegere îmi permit
o mică explicațieDriverul LED – de fapt, un registru de deplasare. „Deplasare” (shift) în nume se referă la mutarea bitilor în interiorul dispozitivului: fiecare nou bit introdus împinge întreaga serie înainte. Deoarece în timpul deplasării nimeni nu dorește să observe o luminație haotică a LED-urilor, procesul are loc în registrele tampon, separate de un zăvor (zăvor) – un fel de anticamera, unde bitii sunt aranjați în ordinea necesară. Când totul este gata, zăvorul se deschide și bitii sunt trimiși la lucru, înlocuind lotul anterior. Cuvântul zăvor în documentația chipurilor aproape întotdeauna implică un astfel de zăvor, indiferent de combinațiile în care este folosit.
Deci, transmiterea datelor în DM634 se realizează astfel: stabilim intrarea DAI la valoarea bitului semnificativ al LED-ului îndepărtat, ridicăm DCK sus-jos; stabilim intrarea DAI la valoarea următorului bit, ridicăm DCK; și așa mai departe, până când toți bitii sunt transmis (sincrozați), după care tragem LAT. Acest lucru se poate face manual (bit-bang), dar este mai bine să folosim o interfață special concepută pentru aceasta, dat fiind că este disponibilă pe STM32-ul nostru în două exemplare.
Tableta albastră STM32F103
Introducere: controalele STM32 sunt semnificativ mai complexe decât Atmega328, chiar dacă pot părea înfricoșătoare. Totuși, din motive de eficiență energetică, la început aproape toată periferia este dezactivată, iar frecvența de ceas este de 8 MHz din sursa internă. Din fericire, programatorii STM au scris un cod care duce chipul la frecvența „calculată” de 72 MHz, iar autorii tuturor IDE-urilor cunoscute de mine au inclus-o în procedura de inițializare, așa că nu trebuie să ne ocupăm cu tactarea (dar ). Dar va trebui să activăm periferia.
Documentație: pe Blue Pill este instalat chipul popular STM32F103C8T6, pentru care există două documente utile:
- pentru microcontrolerele STM32F103x8 și STM32F103xB;
- pentru întreaga gamă STM32F103 și nu numai.
În fișa de date, putem fi interesați de:
- Pinouts – schemele pinilor pentru chipuri – în cazul în care decidem să facem plăci singuri;
- Memory Map – harta de memorie pentru un anumit cip. În Manualul de Referință există o hartă pentru întreaga gamă, în care sunt menționate registrele care nu sunt prezente pe al nostru.
- Tabelul Definițiilor Pin-urilor – enumerarea funcțiilor principale și alternative ale pin-urilor; pentru "pastila albastră" se pot găsi pe internet imagini mai convenabile cu lista pin-urilor și funcțiile lor. Așa că haideți să căutăm imediat schemă de pin-uri pentru Blue Pill și să avem această imagine la îndemână:

NB: în imaginea de pe internet a fost o eroare, semnalată în comentarii, pentru care mulțumim. Imaginea a fost înlocuită, dar acesta este un lucru important — informațiile care nu provin din fișele tehnice ar trebui verificate.
Eliminăm fișa tehnică, deschidem Manualul de Referință, de acum înainte ne folosim doar de el.
Pașii de urmat: ne ocupăm de intrare/ieșire standard, configurăm SPI, activăm perifericele necesare.
Intrare-ieșire
Pe Atmega328, intrarea-ieșirea este realizată extrem de simplu, iar abundența de opțiuni STM32 poate confunda. Acum avem nevoie doar de ieșiri, dar chiar și acestea au patru variante:

ieșire cu colector deschis, ieșire „pull-push”, alternativă „pull-push”, alternativă colector deschis
„Pull-push” (push-pull) – ieșirea obișnuită de pe Arduino, pin-ul poate accepta fie valoarea HIGH, fie LOW. Dar cu „colector deschis” apar , deși, de fapt, este foarte simplu:


Configurarea ieșirii / când portul este desemnat ca ieșire: / activat buffer-ul de ieșire: / – modul colector 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 se activează) / – modul „pull-push”: „0” în registrul de ieșire activează N-MOS, „1” în registrul de ieșire activează P-MOS.
Singura diferență dintre colectorul deschis (), dar în fișele tehnice, această denumire pentru pinurile în mod stoc apare adesea.) și „pull-push” (push-pull) consta în faptul că la primul pin-ul nu poate primi starea HIGH: când se scrie o unitate în registrul de ieșire, acesta trece în modul de rezistență înaltă (high impedance, Hi-Z). Când se scrie un zero, pin-ul se comportă identic în ambele moduri, atât logic, cât și electric.
În modul obișnuit de ieșire, pin-ul pur și simplu transpune conținutul registrului de ieșire. În modul „alternativ”, acesta este controlat de perifericul corespunzător (vezi 9.1.4):
![]()
Dacă bitul portului este configurat ca ieșire cu funcție alternativă, registrul de ieșire este dezactivat, iar pin-ul este conectat la semnalul de ieșire al perifericului.
Funcționalitatea alternativă a fiecărui pin este descrisă în Pin Definitions Fișa tehnică este în imaginea descărcată. La întrebarea ce trebuie să facem dacă un pin are mai multe funcții alternative, răspunsul este dat de un apel în fișa tehnică:
![]()
Dacă mai multe blocuri periferice folosesc același pin, pentru a evita conflictele între funcțiile alternative, ar trebui utilizat simultan doar un singur bloc periferic, comutând cu ajutorul bitului de activare a temporizării periferice (în registrul corespunzător RCC).
În cele din urmă, pinii în modul de ieșire au și o viteză de temporizare. Aceasta este o altă caracteristică de economisire a energiei, în cazul nostru pur și simplu setăm la maxim și uităm de ea.
Deci: folosim SPI, ceea ce înseamnă că doi pini (cu date și cu semnal de ceas) trebuie să fie "funcție alternativă de tragere-împingere", iar unul (LAT) – "tragere-împingere" obișnuit. Dar înainte de a-i atribui, să ne ocupăm de SPI.
SPI
Încă un mic curs de introducere
SPI sau Serial Peripheral Interface (interfață periferică serială) – este o interfață simplă și foarte eficientă pentru comunicarea între microcontrolere (MK) și alte MK și, în general, cu lumea exterioară. Principiul de funcționare a fost deja descris mai sus, acolo unde s-a discutat despre driver-ul LED chinezesc (în manualul de referință la secțiunea 25). SPI poate funcționa în modul de master („stăpân”) și slave („sclav”). SPI are patru canale de bază, dintre care nu toate pot fi utilizate:
- MOSI, Master Output / Slave Input: acest pin, în modul de master, iese, iar în modul de slave, intră date;
- MISO, Master Input / Slave Output: din contră, în master intră, în slave iese;
- SCK, Serial Clock: stabilește frecvența de transmitere a datelor în modul de master sau primește semnalul de ceas în modul de slave. Practic, împarte biții;
- SS, Slave Select: prin intermediul acestui canal, slave-ul află că cineva dorește ceva de la el. Pe STM32 se numește NSS, unde N = negativ, adică controlerul devine slave dacă acest canal este la pământ. Se combină bine cu modul Open Drain Output, dar aceasta este o altă poveste.
Ca și tot restul, SPI pe STM32 este bogat în funcționalități, ceea ce complică puțin înțelegerea sa. De exemplu, poate funcționa nu doar ca SPI, ci și ca interfață I2S, iar în documentație descrierile lor sunt amestecate, trebuie să tăiem la timp ceea ce nu este necesar. Sarcina noastră este extrem de simplă: trebuie doar să transmitem date, utilizând doar MOSI și SCK. Ne îndreptăm către secțiunea 25.3.4 (comunicare half-duplex), unde găsim 1 semnal de ceas și 1 fir de date unidirecțional (1 semnal de ceas și 1 flux de date unidirecțional):

În acest mod, aplicația folosește SPI fie în modul de transmitere, fie în modul de recepție. / Modul de transmisie este similar cu modul duplex: datele sunt transmise prin pinul de transmisie (MOSI în modul master sau MISO în modul slave), iar pinul de recepție (MISO sau MOSI, respectiv) poate fi utilizat ca un pin de intrare-ieșire obișnuit. În acest caz, aplicația trebuie să ignore bufferul Rx (dacă este citit, nu vor fi date transmise acolo).
Excelent, pinul MISO este liber, vom conecta semnalul LAT la el. Să ne ocupăm de Slave Select, care pe STM32 poate fi gestionat programatic, ceea ce este extrem de convenabil. Citim paragraful omonim din secțiunea 25.3.1 Descriere generală SPI:

Control programatic al NSS (SSM = 1) / Informația referitoare la alegerea slave-ului este conținută în bitul SSI al registrului SPI_CR1. Pinul extern NSS rămâne liber pentru alte necesități ale aplicației.
E timpul să scriem în registre. Am decis să folosesc SPI2, căutăm în fișa tehnică adresa sa de bază - în secțiunea 3.3 Harta memoriei:
![]()
Așadar, să începem:
#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))Deschidem secțiunea 25.3.3 cu denumirea sugestivă „Configurarea SPI în modul master”:

1. Setează frecvența ceasului interfeței seriale cu bitii BR[2:0] în registrul SPI_CR1.
Registrele sunt concentrate în secțiunea omonimă a manualului de referință. Deplasarea adresei (Address offset) la CR1 - 0x00, în mod implicit, toți bitii sunt resetați (Reset value 0x0000):

Bitii BR stabilesc divizorul frecvenței ceasului controller-ului, determinând astfel frecvența la care va funcționa SPI. Frecvența STM32 va fi de 72 MHz, iar driver-ul LED, conform fișei sale tehnice, funcționează cu o frecvență de până la 25 MHz, astfel că trebuie să facem diviziune 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/42. Configurează bitii CPOL și CPHA pentru a determina relațiile dintre transmiterea datelor și sincronizarea interfeței seriale (vezi schema de pe pagina 240)
Deoarece citim fișa tehnică, și nu discutăm scheme, să studiem mai degrabă descrierea textuală a bitilor CPOL și CPHA de pe pagina 704 (Descriere generală SPI):

Faza și polaritatea semnalului de ceas
Prin intermediul bitilor CPOL și CPHA ai registrului SPI_CR1, putem selecta programatic patru variante de relații temporale. Bitul CPOL (polaritatea semnalului de ceas) controlează starea semnalului de ceas atunci când datele nu sunt transmise. Acest bit gestionează modurile master și slave. Dacă CPOL este resetat, pinul SCK în modul de repaus se află la nivel scăzut. Dacă bitul CPOL este setat, pinul SCK în modul de repaus se află la nivel înalt.
Dacă bitul CPHA (faza semnalului de ceas) este setat, stroboscopul pentru bitul cel mai semnificativ este al doilea front al semnalului SCK (cădere, dacă CPOL este resetat, sau creștere, dacă CPOL este setat). Datele sunt fixate la a doua schimbare a semnalului de ceas. Dacă bitul CPHA este resetat, stroboscopul pentru bitul cel mai semnificativ este frontul crescător al semnalului SCK (cădere, dacă CPOL este setat, sau creștere, dacă CPOL este resetat). Datele sunt fixate la prima schimbare a semnalului de ceas.
Având în vedere aceste cunoștințe, ajungem la concluzia că ambele biți trebuie să rămână zero, deoarece trebuie să păstrăm semnalul SCK scăzut atunci când nu este utilizat, iar datele trebuie transmise pe frontul crescător al impulsului (vezi Frontul Crescător în fișa tehnică DM634).
Aproape, aici ne-am confruntat pentru prima dată cu o particularitate a lexicului în fișele tehnice ST: în ele, fraza „resetează bitul în zero” este scrisă to reset a bit, nu to clear a bit, așa cum este, de exemplu, la Atmega.
3. Setează bitul DFF pentru a determina formatul blocului de date de 8 biți sau 16 biți.
Am ales special DM634 de 16 biți, pentru a nu avea probleme cu transmiterea datelor PWM de 12 biți, ca la DM633. Are sens să setăm DFF pe 1:
#define DFF 0x0800
_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode4. Configurează bitul LSBFIRST în registrul SPI_CR1 pentru a determina formatul blocului.
LSBFIRST, așa cum sugerează numele, configurează transmiterea cu bitul cel mai puțin semnificativ înainte. Dar DM634 vrea să primească datele, începând de la bitul cel mai semnificativ. Prin urmare, îl lăsăm resetat.
5. În modul hardware, dacă este necesară introducerea de pe pinul NSS, aplică un semnal înalt pe pinul NSS pe durata întregii secvențe de transmitere a octeților. În modul software, setează biții SSM și SSI în registrul SPI_CR1. Dacă pinul NSS trebuie să funcționeze ca ieșire, trebuie să setăm doar bitul SSOE.
Setăm 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 high6. Biții MSTR și SPE trebuie să fie setați (aceștia rămân setați doar dacă semnalul înalt este aplicat pe NSS).
De fapt, cu acești biți atribuim SPI-ului nostru rolul de master și îl activăm:
#define MSTR 0x0004
#define SPE 0x0040
_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;SPI configurat, să scriem imediat funcțiile care trimite octeți către driver. Continuăm să citim 25.3.3 „Configurarea SPI în modul master”:

Ordinea transmiterii datelor
Transmiterea începe când un octet este scris în bufferul Tx.
Octetul de date este încărcat în registrul de deplasare în modul paralel (din magistrala internă) în timpul transmiterii primului bit, după care este transmis în modului (din bus-ul intern) în timpul transferului primului bit, după care este transmis în secvențial în modul MOSI, cu bitul întâi sau ultimul, în funcție de setarea bitului LSBFIRST în registrul CPI_CR1. Flacul TXE este setat după transmiterea datelor din bufferul Tx în registrul de deplasare, iar o întrerupere este generată dacă bitul TXEIE este setat în registrul CPI_CR1.
Am evidențiat câteva cuvinte în traducere pentru a sublinia o particularitate a implementării SPI în controlerele STM. La Atmega, flacul TXE (Tx Empty, Tx este gol și gata să primească date) este setat doar după ce întreaga octet a fost trimisă în exterior. Aici, acest flag este setat după ce octetul a fost introdus în registrul intern de deplasare. Deoarece este introdus acolo cu toți bitii simultan (paralel), iar datele sunt transmise ulterior secvențial, TXE este setat înainte ca octetul să fie complet trimis. Acest lucru este important, deoarece în cazul driver-ului nostru de LED-uri trebuie să ridicăm pinul LAT după trimiterea tuturor datelor, adică doar cu flagul TXE nu ne va fi suficient.
Și asta înseamnă că avem nevoie de un alt flag. Să ne uităm în 25.3.7 – „Flaguri de stare”:

<…>

Flacul BUSY
Flacul BSY este setat și resetat hardware (scrierea în el nu afectează nimic). Flacul BSY arată starea stratului de comunicare SPI.
El este resetat:
când transmiterea este finalizată (cu excepția modului master, dacă transmiterea este continuă)
când SPI este oprit
când apare o eroare în modul master (MODF=1)
Dacă transmiterea nu este continuă, flacul BSY este resetat între fiecare transmitere de date
Ok, va fi util. Să vedem unde se află bufferul Tx. Pentru aceasta citim „Registrul de date SPI”:

Bitele 15:0 DR[15:0] Registrul de date
Datele primite sau datele de transmis.
Registrul de date este împărțit în două buffere – unul pentru scriere (bufferul de transmitere) și al doilea pentru citire (bufferul de recepție). Scrierea în registrul de date scrie în bufferul Tx, iar citirea din registrul de date va returna valoarea conținută în bufferul Rx.
Și registrul de stare, unde se află flagurile TXE și BSY:

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, deoarece trebuie să transmitem de 16 ori câte două octeți, conform numărului de ieșiri ale driver-ului de LED-uri, ar trebui să facem așa:
void sendLEDdata()
{
LAT_low();
uint8_t k = 16;
do
{ k--;
dm_shift16(leds[k]);
} while (k);
while (_SPI2_(_SPI_SR) & BSY); // finalizați transmisia
LAT_pulse();
}Dar momentan nu știm cum să ridicăm pinul LAT, așa că ne întoarcem la I/O.
Atribuim pinii
La STM32F1, registele care se ocupă de starea pinilor sunt destul de neobișnuite. Este clar că sunt mai multe decât la Atmega, dar și diferă de alte cipuri STM. Secțiunea 9.1 Descriere generală GPIO:

Fiecare dintre porturile de intrare/ieșire generale (GPIO) are două registre de configurare de 32 de biți (GPIOx_CRL și GPIOx_CRH), două registre de date de 32 de biți (GPIOx_IDR și GPIOx_ODR), un registru de setare/resetare de 32 de biți (GPIOx_BSRR), un registru de resetare de 16 biți (GPIOx_BRR) și un registru de blocare de 32 de biți (GPIOx_LCKR).
Sunt neobișnuite și destul de incomode, în special primele două registre, deoarece 16 pini ai portului sunt împrăștiați în ele în formatul „câte patru biți pe fiecare”. Adică, pinul zero până la șapte se află în CRL, iar restul – în CRH. Restul registrelor pot conține biții tuturor pinilor portului – adesea rămânând parțial „rezervați”.
Pentru simplificare, să începem de la sfârșitul listei.
Registrul de blocare nu ne va fi necesar.
Registrele de setare și resetare sunt destul de amuzante, deoarece se duplică parțial: putem scrie toate instrucțiunile doar în BSRR, unde cei 16 biți superiori vor reseta pinul la zero, iar cei inferiori – îl vor seta la 1, sau putem folosi și BRR, care conține doar biții inferiori de resetare a pinului. Prefer varianta a doua. Aceste registre sunt importante deoarece oferă acces atomic la pini:

![]()
Setare sau resetare atomică
Nu este necesar să dezactivăm întreruperile atunci când programăm GPIOx_ODR la nivel de bit: putem modifica unul sau mai mulți biți într-o singură operație atomică de scriere APB2. Acest lucru se realizează prin scrierea „1” în registrul de setare/resetare (GPIOx_BSRR sau, doar pentru resetare, în GPIOx_BRR) al bitului pe care dorim să-l modificăm. Alți biți vor rămâne neschimbați.
Registrele de date au denumiri destul de sugestive – IDR = Input Registrul direcției, registrul de intrare; ODR = Registrul direcției, registrul de ieșire. În acest proiect, nu ne vor fi necesare. Și, în final, registrele de control. Deoarece ne interesează pinurile celui de-al doilea SPI, adică PB13, PB14 și PB15, ne uităm imediat la CRH:
Și vedem că va trebui să scriem ceva în biții de la 20 la 31.

Și vedem că va trebui să scriem ceva în biții de la 20 la 31.
Am discutat deja mai sus despre ce dorim de la pini, așa că aici mă voi descurca fără screenshot, voi spune doar că MODE stabilește direcția (input, dacă ambele biți sunt setați la 0) și viteza pinului (avem nevoie de 50MHz, adică ambele pini la „1”), iar CNF stabilește modul: normal „trage-pușcă” – 00, „alternativ” – 10. După cum vedem mai sus, în mod implicit, toți pinii au setat al treilea bit de jos (CNF0), care îi fixează în modul input flotant.
Deoarece intenționez să fac ceva mai mult cu acest cip, am definit toate valorile posibile pentru MODE și CNF, atât pentru registrele de control inferioare, cât și pentru cele superioare.
Cam 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 0x20000000Pinii noștri se află pe portul B (adresa de bază – 0x40010C00), codul:
#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ță, putem scrie definiții pentru LAT, care va fi activat prin 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 pur și simplu din inerție, așa a fost întotdeauna, să rămână așa)
Acum totul arată bine, doar că nu funcționează. Pentru că este STM32, aici economisesc energie electrică, așa că trebuie să activăm sincronizarea perifericelor necesare.
Activăm sincronizarea
Sincronizarea este responsabilă de ceasuri, care sunt Clock. Și am putut observa deja abrevierea RCC. Căutăm în documentație: este Controlul Resetării și Sincronizării (Reset and Clock Control).
După cum am spus mai sus, din fericire, cea mai complicată parte a subiectului sincronizării a fost rezolvată de oamenii de la STM, pentru care le mulțumim foarte mult (încă o dată, voi da linkul la , pentru a fi clar cât de complicat este acest lucru). Avem nevoie doar de registrele care sunt responsabile pentru activarea sincronizării perifericelor (Peripheral Clock Enable Registers). La început, să găsim adresa de bază a RCC, aceasta este chiar la începutul „Hărților de memorie”:
![]()
#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))Apoi fie facem clic pe link, unde încercăm să găsim ceva în tabel, fie, mult mai bine, să alergăm prin descrierile registrelor de activare din secțiunile despre registrele de activare. Acolo vom găsi RCC_APB1ENR și RCC_APB2ENR:


Și în ele, respectiv, biții care activează sinusizarea SPI2, IOPB (I/O Port B) și funcțiile 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 .
Dacă aveți posibilitatea și dorința de a testa, conectați DM634 astfel: DAI la PB15, DCK la PB13, LAT la PB14. Alimentați driverul de la 5 volți, nu uitați să legați masele.

STM8 PWM
PWM pe STM8
Când am planificat acest articol, am decis să încerc să stăpânesc o funcționalitate a unui cip pe care nu-l cunoșteam, folosind doar fișa tehnică, astfel încât să nu fiu un cizmar fără cizme. STM8 a fost alegerea perfectă: pe de-o parte, aveam câteva plăci chinezești cu STM8S103, iar pe de altă parte, nu este foarte popular, așa că tentația de a căuta soluții pe internet este restricționată de absența acestor soluții.
Pentru cip există de asemenea și , în primul este pinout-ul și adresele registrelor, iar în al doilea – restul. STM8 este programat în C într-o IDE destul de rudimentară .
Tactarea și Input-Output
Implicit, STM8 funcționează la o frecvență de 2 MHz, ceea ce trebuie corectat imediat.

Semnalul de ceas HSI (intern rapid)
Semnalul de ceas HSI provine de la un generator RC intern de 16 MHz cu un divizor programabil (de la 1 la 8). Acesta este setat în registrul divizorului de ceas (CLK_CKDIVR).
Notă: La start, sursa principală de semnal de ceas este selectată generatorul RC HSI cu un divizor de 8.
Găsim adresa registrului în fișa tehnică, descrierea în refman și vedem că registrul trebuie resetat:
#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6
CLK_CKDIVR &= ~(0x18);Deoarece intenționăm să activăm PWM și să conectăm LED-uri, ne uităm la pinout:

Cipul este mic, multe funcții sunt legate de aceleași pini. Ceea ce este în paranteze pătrate – "funcționalitate alternativă", se comută prin "opțiuni de byte" (option bytes) – ceva de genul fusibilor pentru Atmega. Valorile acestora pot fi modificate din program, dar nu este necesar, deoarece noua funcționalitate este activată doar după repornire. Cel mai simplu este să folosești ST Visual Programmer (care se descarcă împreună cu Visual Develop), care poate schimba aceste byte-uri. În pinout se vede că ieșirile CH1 și CH2 ale primului temporizator sunt ascunse în paranteze pătrate; trebuie să setăm biții AFR1 și AFR0 în STVP, fiindcă al doilea va transfera de asemenea ieșirea CH1 a celui de-al doilea temporizator de pe PD4 pe PC5.
Astfel, LED-urile vor fi controlate de 6 pini: PC6, PC7 și PC3 pentru primul temporizator, PC5, PD3 și PA3 pentru al doilea.
Configurarea pinu-lui de intrare-ieșire pe STM8 este mai simplă și mai logică decât pe STM32:
- registrul de direcție de date familiar de la Atmega DDR (Data Direction Register): 1 = ieșire;
- primul registru de control CR1 la ieșire stabilește modul "pull-up/pull-down" (1) sau colector deschis (0); deoarece conectez LED-urile la cip cu catod, las aici zerouri;
- al doilea registru de control CR2 la ieșire stabilește viteza de tactare: 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)); //fastConfigurarea PWM
Pentru început, să ne stabilim terminologia:
- Frecvența PWM – frecvența cu care Tik-tak timerul;
- Auto-reload, AR – valoarea autoîncărcată, până la care va număra timerul (perioada impulsului);
- Update Event, UEV – evenimentul care apare când timerul a ajuns la AR;
- Ciclul de lucru PWM – coeficientul de umplere al PWM, adesea numit „duty cycle”;
- Valoarea Capture/Compare – valoarea pentru captură/comparație, până la care timerul va face ceva (în cazul PWM – va inversa semnalul de ieșire);
- Valoarea Preload – valoarea preîncărcată. Valoarea de comparație nu poate fi schimbată, cât timp timerul numără, altfel ciclul PWM se va strica. Prin urmare, noile valori transmise sunt plasate într-un buffer și extrase de acolo când timerul ajunge la sfârșitul numărătorii și se resetează;
- Moduri aliniate pe margine și Moduri aliniate pe centru – alinierea pe margine și pe centru, la fel ca modurile Atmelo; PWM Rapid și PWM Corectat în fază.
- OCiREF, Semnal de Referință Comparativ de Ieșire – semnalul de ieșire de referință, în esență, cel care în modul PWM este prezent pe pinul corespunzător.
După cum se vede din pinout, PWM este disponibil pe două temporizatoare – primul și al doilea. Ambele sunt de 16 biți, primul are o mulțime de funcții suplimentare (în special, poate număra și în sus și în jos). Trebuie să facem în așa fel încât ambele să funcționeze identic, așa că am decis să încep cu al doilea, care în mod evident este mai sărăcuț, pentru a nu folosi accidental ceva ce nu există în el. O problemă este că descrierea funcționalității PWM pentru toate temporizatoarele se află în manualul de referință în capitolul despre primul temporizator (17.5.7 Mod PWM), așa că trebuie să sar mereu acolo-încoace prin document.
PWM pe STM8 are un avantaj semnificativ față de PWM de pe Atmega:

PWM cu aliniere pe margine
Configurarea numărului în sus
Numărătoarea în sus este activă dacă bitul DIR din registrul TIM_CR1 este resetat
Exemplu
Exemplul folosește primul mod PWM. Semnalul de referință PWM OCiREF este menținut la nivel înalt, atâta timp cât TIM1_CNT < TIM1_CCRi. În caz contrar, el devine nivel jos. Dacă valoarea pentru comparație din registrul TIM1_CCRi este mai mare decât valoarea autoîncărcată (registrul TIM1_ARR), semnalul OCiREF este menținut pe 1. Dacă valoarea pentru comparație este 0, OCiREF este menținut pe zero.…
Timerul STM8 în timpul evenimentului de actualizare mai întâi verifică valoarea de comparație, și abia apoi emite semnalul de referință. La Atmega, timerul întâi își face treaba, și apoi compară, rezultând că la valoarea de comparație == 0 La ieșire obținem un ac cu care trebuie să ne confruntăm (de exemplu, invertind logic programatic).
Așadar, ce dorim să facem: PWM de 8 biți (AR == 255), numărând de jos în sus, alinierea pe margine. Deoarece becurile sunt conectate la cip prin catod, PWM-ul ar trebui să genereze 0 (LED-ul este aprins) până la valoarea de comparație și 1 după.
Am citit deja despre un anumit mod PWM, prin urmare, găsim registrul necesar al celui de-al doilea temporizator căutând în manualul de referință această frază (18.6.8 – TIMx_CCMR1):

110: Primul mod PWM – la numărarea de jos în sus, primul canal este activ cât timp TIMx_CNT < TIMx_CCR1. În caz contrar, primul canal nu este activ. [mai departe în document există o copiere greșită din temporizatorul 1]
111: Al doilea mod PWM – la numărarea de jos în sus, primul canal nu este activ cât timp TIMx_CNT < TIMx_CCR1. În caz contrar, primul canal este activ.
Deoarece LED-urile sunt conectate la MK prin catod, al doilea mod ne convine (primul este de asemenea, dar nu știm încă asta).

Bit 3 OC1PE: Activarea preîncărcării ieșirii 1
0: Registrul de preîncărcare la TIMx_CCR1 este dezactivat. Se poate scrie în TIMx_CCR1 în orice moment. Noua valoare este aplicată imediat.
1: Registrul de preîncărcare la TIMx_CCR1 este activat. Operațiile de citire/scriere se referă la registrul de preîncărcare. Valoarea preîncărcată TIMx_CCR1 este încărcată în registrul umbros în timpul fiecărui eveniment de actualizare.
*Notă: pentru funcționarea corectă a modului PWM, registreat de preîncărcare trebuie să fie activate. Acest lucru nu este necesar în modul semnal unic (în registrul TIMx_CR1 bitul OPM este setat).
Bine, activăm tot ce avem nevoie pentru cele trei canale ale celui de-al doilea temporizator:
#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 8 biți, aici 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 temporizator știe să numere doar de la jos în sus, alinierea pe margine, nu trebuie să schimbăm nimic. Să stabilim divizorul de frecvență, de exemplu, la 256. Divizorul pentru al doilea temporizator se stabilește în registrul TIM2_PSCR și reprezintă o putere a lui 2:
#define TIM2_PSCR *(volatile uint8_t *)0x00530E
TIM2_PSCR = 8;Rămâne să activăm ieșirile și pe cel de-al doilea temporizator. Prima sarcină se rezolvă prin registrele Capture/Compare Activare: sunt două, trei canale sunt dispersate simetric. Aici putem de asemenea să aflăm că putem schimba polaritatea semnalului, deci în principiu am fi putut folosi și PWM Mode 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 temporizatorul în registrul TIMx_CR1:

#define TIM2_CR1 *(volatile uint8_t *)0x005300
TIM2_CR1 |= 1;Vom scrie un analog simplu AnalogWrite(), care va transmite temporizatorului valorile proprii pentru comparație. Registrele sunt numite previzibil registri Capture/Compare, câte două pentru fiecare canal: cei 8 biți inferiori în TIM2_CCRxL și cei superiori în TIM2_CCRxH. Deoarece am configurat PWM pe 8 biți, este suficient să scriem doar biții 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;
}Cine citește cu atenție va observa că PWM-ul obținut este ușor defect, nefiind capabil să ofere 100% ciclu de duty (la valoarea maximă de 255, semnalul se inversează o dată pe ciclu de timer). Pentru LED-uri, aceasta nu contează, iar cititorul atent își dă seama deja singur cum să remedieze situația.
PWM-ul de pe al doilea timer funcționează, să trecem la primul.
Primul timer are exact aceleași biți în aceleași registre (doar că biții care au rămas „rezervați” în al doilea timer sunt folosiți activ în primul pentru diverse funcții avansate). Așa că este suficient să găsim adresele acestor registre în fișa tehnică și să copiem codul. Și să schimbăm valoarea divisorului de frecvență, deoarece primul timer vrea să primească nu o putere a lui 2, ci o valoare exactă pe 16 biți în două registre. Prescaler High și Low. Am făcut totul și… primul timer nu funcționează. Care este problema?
Problema poate fi rezolvată doar prin revizuirea întregii secțiuni despre registrele de control ale timerului 1, unde căutăm acel element care lipsește de la al doilea timer. Se va găsi 17.7.30 Break register (TIM1_BKR), unde există un astfel de bit:
![]()
Activează ieșirea principală
#define TIM1_BKR *(volatile uint8_t *)0x00526D
TIM1_BKR = (1<<7);Acum, cu siguranță, totul este corect, codul .

STM8 Multiplex
Multiplexarea pe STM8
Al treilea mini-proiect constă în conectarea a opt LED-uri RGB la al doilea timer în modul PWM și făcându-le să afișeze culori diferite. Conceptul de bază este multiplexarea LED-urilor, care constă în faptul că, dacă aprindem și stingem LED-urile foarte, foarte repede, ne va părea că ele ard constant (persistence of vision, inerția percepției vizuale). Cândva, am realizat .
Algoritmul de funcționare arată astfel:
- am conectat anodul primului LED RGB;
- l-am aprins, aplicând semnalele necesare pe catode;
- am așteptat sfârșitul ciclului PWM;
- am conectat anodul celui de-al doilea LED RGB;
- l-am aprins…
Și așa mai departe. Bineînțeles, pentru o funcționare frumoasă, este necesar ca conectarea anodului și „aprinderea” LED-ului să se întâmple simultan. Sau aproape. În orice caz, trebuie să scriem un cod care va furniza valori pe trei canale ale celui de-al doilea timer, schimbându-le la atingerea UEV și schimbând simultan LED-ul RGB activ în acel moment.
Întrucât comutarea LED-urilor se realizează automat, trebuie să creăm o «memorie video» din care handler-ul de întreruperi va obține datele. Este un simplu array:
uint8_t colors[8][3];Pentru a schimba culoarea unui LED specific, trebuie să scriem în acest array valorile dorite. Iar numărul LED-ului activ va fi gestionat de variabila
uint8_t cnt;Demultiplexare
Pentru un multiplexare corectă, avem nevoie, paradoxal, de demultiplexorul CD74HC238. Demultiplexorul este un chip care realizează hardware operatorul <<. Prin cele trei pinii de intrare (bitii 0, 1 și 2) îi furnizăm un număr pe 3 biți X, iar acesta, în schimb, activează ieșirea număr (1<<X). Celelalte intrări ale chip-ului sunt folosite pentru scalarea întregii construcții. Acest chip este necesar nu doar pentru a reduce numărul de pini ocupați ai microcontrolerului, ci și pentru siguranță – pentru a nu actifica accidental mai multe LED-uri decât este permis și a nu bricheta microcontrolerul. Chip-ul costă câțiva bani, merită să-l ai întotdeauna în trusa de acasă.
CD74HC238 va fi responsabil pentru a furniza tensiune către anodul LED-ului dorit. Într-un multiplex complet, ar furniza tensiune pe coloană prin P-MOSFET, dar în această demonstrație se poate face și direct, deoarece consumă 20 mA, conform specificațiilor maxime absolute din data sheet. Din ne va trebui pinout-ul și acest rezumat:

H = nivel înalt de tensiune, L = nivel scăzut de tensiune, X – nu contează
Conectăm E2 și E1 la masă, E3, A0, A1 și A3 la pinii PD5, PC3, PC4 și PC5 STM8. Deoarece tabela de mai sus conține atât niveluri ridicate, cât și scăzute, configurăm acești pini ca ieșiri push-pull.
PWM
PWM pe al doilea timer se configurează la fel ca în povestea anterioară, cu două excepții:
În primul rând, trebuie să activăm întreruperea pentru Update Event (UEV), care va apela funcția pentru a comuta LED-ul activ. Acest lucru se face prin modificarea bitului Update Interrupt Enable din registrul cu numele sugestiv

Registrul pentru activarea întreruperilor
#define TIM2_IER *(volatile uint8_t *)0x005303
//enable interrupt
TIM2_IER = 1;A doua excepție este legată de fenomenul multiplexării, numit ghosting – luminescență parazitară a diodelor. În cazul nostru, aceasta poate apărea din cauza faptului că temporizatorul, care a provocat o întrerupere la UEV, continuă să ticăie, iar handler-ul de întrerupere nu reușește să comute LED-ul înainte ca temporizatorul să înceapă deja să scrie ceva în ieșiri. Pentru a combate acest lucru, va trebui să inversăm logica (0 = luminozitate maximă, 255 = nimic nu este aprins) și să evităm valorile extreme ale ciclului de lucru. Asta înseamnă să ne asigurăm că, după UEV, diodele LED se sting complet pentru un ciclu PWM.
Schimbăm polaritatea:
//set polarity
TIM2_CCER1 |= (CC1P | CC2P);
TIM2_CCER2 |= CC3P;Evităm setarea r, g și b la 255 și nu uităm să le inversăm la utilizare.
Întreuperi
Esenta întreruperii este că, în anumite circumstanțe, cipul încetează să execute programul principal și apelează o funcție externă. Întreuperile apar din cauza unor influențe externe sau interne, inclusiv de la temporizator.
Când am creat prima dată proiectul în ST Visual Develop, am primit, pe lângă main.c , o fereastră cu un fișier misterios stm8_interrupt_vector.c, inclus în mod automat în proiect. În acest fișier, pentru fiecare întrerupere este legată o funcție NonHandledInterrupt. Trebuie să legăm propria noastră funcție la întreruperea dorită.
În documentația tehnică există un tabel cu vectorii de întreruperi, unde găsim cele necesare:

13 actualizare / depășire TIM2
14 captură / comparare TIM2
Trebuie să schimbăm LED-ul la UEV, așa că avem nevoie de întreruperea nr. 13.
Prin urmare, în primul rând, în fișierul stm8_interrupt_vector.c schimbăm numele funcției responsabile pentru întreruperea nr. 13 (IRQ13) pe cea aleasă de noi:
{0x82, TIM2_Overflow}, /* irq13 */În al doilea rând, va trebui să creăm un fișier main.h cu următorul conținut:
#ifndef __MAIN_H
#define __MAIN_H
@far @interrupt void TIM2_Overflow (void);
#endifȘi, în sfârșit, să definim această funcție în main.c:
@far @interrupt void TIM2_Overflow (void)
{
PD_ODR &= ~(1<<5); // dezactivăm demultiplexorul
PC_ODR = (cnt<<3); // înregistrăm noua valoare în demultiplexor
PD_ODR |= (1<<5); // activăm demultiplexorul
TIM2_SR1 = 0; // resetăm flag-ul de întrerupere Update Interrupt Pending
cnt++;
cnt &= 7; // înaintăm contorul LED
TIM2_CCR1L = ~colors[cnt][0]; // transmitem în buffer valorile inversate
TIM2_CCR2L = ~colors[cnt][1]; // pentru următorul ciclu PWM
TIM2_CCR3L = ~colors[cnt][2]; //
return;
}Rămâne să activăm întreruperile. Se face cu ajutorul comenzii în assembler rim – va trebui să o căutăm în :
//enable interrupts
_asm("rim");O altă comandă în assembler – sim – dezactivează întreruperile. Acestea trebuie dezactivate pe durata scrierii noilor valori în „memoria video”, pentru a evita ca o întrerupere nefericită să strice matricea.
Întreaga codare – .

Dacă acest articol este de folos cuiva, înseamnă că nu l-am scris degeaba. Aș fi bucuros să primesc comentarii și observații, voi încerca să răspund la toate.
Sursa: habr.com
