
Në Provova të shpjegoj elektronikëve hobi, të rritur nga Arduino, se si dhe pse duhet të lexojnë datashit dhe dokumentacionin tjetër për mikrocontrolerët. Teksti doli i gjatë, prandaj premtova të paraqes shembuj praktikë në një artikull të veçantë. Mirë, atëherë quhem kërpudha...
Sot do të tregoj se si, me ndihmën e datashit, të zgjidhim disa detyra të thjeshta, por të nevojshme për shumë projekte mbi kontrolerët STM32 (Blue Pill) dhe STM8. Të gjitha projektet demo i kushtohen dritave të mia të preferuara LED, do t'i ndezim ato në numër të madh, për të cilin do të angazhojmë periferi interesante.
Teksti përsëri doli jashtëzakonisht i gjatë, prandaj për lehtësi po bëj përmbajtje:
Diskleimer: Unë nuk jam inxhinier, nuk pretendoj për njohuri të thella në elektronikë, ky artikull është për të njëjtit si unë pasionantë. Në të vërtetë, për publikun target e konsideroja veten time dy vjet më parë. Po të më kishte thënë dikush atëherë se leximin e datashit për një çip të panjohur nuk është e frikshme, nuk do të kisha humbur shumë kohë në kërkimin e disa copave kodi në internet dhe shpikjen e ndihmave me gishta dhe ndihma adhezuese.
Qendra e këtij artikulli janë datashitët, jo projektet, prandaj kodi mund të mos jetë shumë i pastër dhe shpesh i përcaktuar. Vetë projektet janë shumë të thjeshta, megjithatë të përshtatshme për një njohje të parë me një çip të ri.
Shpresoj që artikulli im të ndihmojë dikë në një fazë të ngjashme të futjes në hobin.
STM32
16 drita LED me DM634 dhe SPI
Një projekt i vogël duke përdorur Blue Pill (STM32F103C8T6) dhe driverin e dritës LED DM634. Me ndihmën e datashit, do të kuptojmë driverin, portet IO të STM dhe do të konfigurojmë SPI.
DM634
Çipi tajvanez me 16 dalje PWM 16-bitëshe, mund të lidhen në zinxhir. Modeli i vogël 12-bit është i njohur për projektin e brendshëm . Në kohën e saj, duke zgjedhur midis DM63x dhe TLC5940 të njohur, vendosa për DM për disa arsye: 1) TLC në Aliexpress është me siguri i falsifikuar, ndërsa ky – jo; 2) DM ka PWM autonom me gjeneratorin e tij të frekuencës; 3) mund ta blija me çmim të arsyeshëm në Moskë, e jo të prisja një dërgesë nga Ali. Po ashtu, ishte interesante të mësoja si ta menaxhoja çipin vetë, e jo të përdorja një bibliotekë gati. Çipat tani kryesisht janë të disponueshëm në formën SSOP24, dhe nuk është e vështirë t’i bashkëngjitim në një adaptues.
Duke qenë se prodhuesi është tajvanez, për çipin është shkruar në anglisht kinez, kështu që do të jetë argëtues. Fillimisht shikojmë lidhjet (Pin Connection), në mënyrë që të kuptojmë se cila këmbë lidhet me çfarë dhe përshkrimi i pinëve (Pin Description). 16 daljet:

Burimet e rrymës continue (stoku i hapur)
Sink / Open-drain output – stoku; burim i rrymës së ardhshme; dalje, kur është aktive, është e lidhur me tokën, – LED-të lidhen me drejtuesin nga katodet. Në mënyrë elektrike, sigurisht që nuk është ndonjë «stok i hapur» (open drain), por në datasheet-et kjo shënjë përdoret shpesh për pinët në mënyrën e stokut.
![]()
Rezistorët e jashtëm midis REXT dhe GND për vendosjen e vlerës së rrymës së daljes
Midis pin-it REXT dhe tokës vendoset një rezistor reference, që kontrollon rezistencën e brendshme të daljeve, shih grafikun në fq. 9 të datasheet-it. Në DM634, kjo rezistencë gjithashtu mund të menaxhohet në mënyrë programore, duke vendosur ndriçimin e përgjithshëm (global brightness); nuk do të hyj në detaje në këtë artikull, thjesht do të vendos një rezistor prej 2.2 – 3 kΩ këtu.
Për të kuptuar se si të menaxhojmë çipin, shikojmë përshkrimin e ndërfaqes së pajisjes:

Ah, ja ai, anglishtja kineze në gjithë shkëlqimin e saj. Të përkthesh këtë është problematike, mund ta kuptosh nëse dëshiron, por ka një rrugë tjetër – të shohim si përshkruhet lidhja në datasheet për TLC5940, që është funksionalisht e ngjashme:

… Për të futur të dhënat në pajisje ne duam vetëm tre pinë. Ura e përparme e sinjalit SCLK zhvendos të dhënat nga pini SIN në regjistrin e brendshëm. Pas ngarkimit të të gjitha të dhënave, një sinjal i shkurtër i lartë XLAT fikson të dhënat e dërguara radhazi në regjistrat e brendshëm. Regjistrat e brendshëm – peshore që aktivizohen nga niveli i sinjalit XLAT. Të gjitha të dhënat dërgohen me bitin më të lartë përpara.
Latch – peshore/fiksues.
Rising edge – ura e përparme e impulsit
MSB first – bitin më të lartë (bitin ekstrim të majtë) përpara.
për të clock datat – për të transmetuar të dhënat në mënyrë sekondare (bit për bit).
Fjala latch shpesh paraqitet në dokumentacionin e çipeve dhe përkthehet në mënyra të ndryshme, prandaj për të kuptuar lejoj veten një
përmbledhje të vogëlLED-drajveri – në thelb një regjistër lëvizës. "Lëvizje" (shift) në emër do të thotë lëvizje bitësh brenda pajisjes: çdo bit i ri që shtohet brenda shtyn të gjithë zinxhirin përpara. Duke qenë se gjatë lëvizjes askush nuk dëshiron të shohë një ndricim kaotik të LED-ëve, procesi ndodh në regjistra buffer, të ndarë nga një bllokim (latch) – kjo është një lloj pritjeje, ku bitët rreshtohen në rendin e nevojshëm. Kur gjithçka është gati, bllokimi hapet dhe bitët dërgohen të punojnë, duke zëvendësuar grupin e kaluar. Fjala latch në dokumentacionin e mikroçipave shpesh nënkupton një bllokim të tillë, pa marrë parasysh kombinimet në të cilat përdoret.
Pra, transmetimi i të dhënave në DM634 bëhet kështu: vendosim hyrjen DAI në vlerën e bitit më të lartë të LED-it të largët, tërheqim DCK lart-poshtë; vendosim hyrjen DAI në vlerën e bitit tjetër, tërheqim DCK; dhe kështu me radhë, derisa të gjithë bitët të jenë transmetuar (clocked in), pas së cilës tërheqim LAT. Këtë mund ta bëjmë manualisht (bit-bang), por është më mirë të përdorim një ndërfaqe të veçantë për këtë, dhe fatmirësisht ajo është e pranishme në STM32 tonë në dy kopje.
Pilula Blu STM32F103
Hyrjet: kontrollorët STM32 – janë ndjeshëm më të komplikuar se Atmega328, për sa i përket asaj që mund të trembin. Në të njëjtën kohë, për arsye energjetike, në fillim pothuajse e gjithë periferia është e ndaluar, dhe frekuenca e orës është 8 MHz nga burimi i brendshëm. Fatmirësisht, programuesit e STM shkruan kodin që e çon çipin në "frekuencat" 72 MHz, dhe autorët e të gjitha IDE-ve të njohura që di unë e përfshinë atë në procedurën e inicimit, kështu që nuk na duhet ta orientojmë (por ). Megjithatë, do të duhet të aktivizojmë periferi.
Dokumentacioni: në Blue Pill është instaluar çipi popullor STM32F103C8T6, për të cilin ka dy dokumente të dobishme:
- për mikroçipet STM32F103x8 dhe STM32F103xB;
- për të gjithë linjën STM32F103 dhe jo vetëm.
Në fletën e dhënash na interesojnë:
- Pinouts – definicionet e çipave – për rasti kur vendosim të bëjmë vetë pllakata;
- Hartë Memorje – harta e memories për një çip specifik. Në Manualin Reference ka një hartë për të gjithë linjën, ku përmenden regjistrat që nuk gjenden te ne.
- Tabela e Definimin e Pin-ëve – një listë me funksionet kryesore dhe alternative të pin-ëve; për "tabletën blu", në internet mund të gjeni figura më të përshtatshme me listën e pin-ëve dhe funksioneve të tyre. Pra, menjëherë gjejmë pinout për Blue Pill dhe mbajmë këtë figurë në dorë:

NB: në figurën nga interneti kishte një gabim, i cili u vërejt në komentet, për të cilin falënderojmë. Figura është zëvendësuar, por kjo është një mësim — informacioni që nuk vjen nga datasheet-et është më mirë të verifikohet.
Hiqim datasheet-in, hapim Manualin Reference, tani do të përdorim vetëm atë.
Renditja e veprimeve: mësojmë mbi hyrjen/daljen standarde, konfiguroni SPI, aktivizoni periferi të nevojshme.
Hyrje-dalje
Në Atmega328, hyrja-dalja është realizuar në mënyrë të thjeshtë, për shkak të së cilës shumica e opsioneve të STM32 mund të krijojnë konfuzion. Tani na duhen vetëm daljet, por madje edhe ato kanë katër variante:

dalje me stok të hapur, dalje "tërheqëse-dhënëse", alternative "tërheqëse-dhënëse", stok i hapur alternativ
"Tërheqëse-dhënëse" (push-pull) – një dalje e njohur nga Arduino, pin-i mund të ketë ose vlerën HIGH ose LOW. Ndërsa me "stok të hapur" dalin , megjithatë në të vërtetë, gjithçka është e thjeshtë:


Konfigurimi i daljes / kur porta është caktuar për dalje: / aktivizohet buferi i daljes: / – mod i stokut të hapur: "0" në regjistrin e daljes aktivizon N-MOS, "1" në regjistrin e daljes e lë portën në modin Hi-Z (P-MOS nuk aktivizohet) / – mod "tërheqëse-dhënëse": "0" në regjistrin e daljes aktivizon N-MOS, "1" në regjistrin e daljes aktivizon P-MOS.
E gjithë dallimi i stokut të hapur (open drain) nga "tërheqëse-dhënëse" (push-pull) qëndron në faktin se në të parin, pin-i nuk mund të marrë gjendjen HIGH: kur shkruhet një njësi në regjistrin e daljes, ai kalon në modin e rezistencës së lartë (high impedance, Hi-Z). Kur shkruhet zero, pin-i në të dyja modalitetet vepron në të njëjtën mënyrë, si logjikisht ashtu edhe elektrikisht.
Në modin normal të daljes, pin-i thjesht transmeton përmbajtjen e regjistrit të daljes. Në "modalitetin alternativ", ai menaxhohet nga periferia përkatëse (shih. 9.1.4):
![]()
Nëse bita i portës është i konfiguruar si një dalje funksion i alternativ, regjistri i daljes shkëputet, dhe pin-i lidhet me sinjalin e daljes nga periferi.
Funksionaliteti alternativ i çdo pin-i përshkruhet në Definimet e Pin-ëve flet, dhe ajo është në imazhin e shkarkuar. Për pyetjen se çfarë të bëni nëse pini ka disa funksione alternative, përgjigjen e jep nota në flet.
![]()
Nëse disa blloqe periferike përdorin të njëjtin pin, për të shmangur konfliktin midis funksioneve alternative, duhet të përdoret një bllok periferik në të njëjtën kohë, duke kaluar me bitin e aktivizimit të taktimeve të periferisë (në regjistrin përkatës RCC).
Së fundmi, pikat në modalitetin e daljes kanë gjithashtu shpejtësinë e taktimeve. Kjo është një tjetër tipare kursimi energjie, në rastin tonë thjesht vendosim në maksimum dhe harrojmë.
Pra: ne përdorim SPI, kjo do të thotë që dy pinit (me të dhëna dhe me sinjal taktim) duhet të jenë "funksion alternativ tërheqësi-dhënës", dhe një tjetër (LAT) – "tërheqësi i zakonshëm-dhënës". Por para se t'i caktojmë, le të sqarojmë SPI-në.
SPI
Një shpjegim i vogël
SPI ose Serial Peripheral Interface (ndërfaqja periferike seriale) – një ndërfaqe e thjeshtë dhe tepër efektive për komunikimin e MK me MK të tjera dhe në përgjithësi me botën e jashtme. Prinsipi i saj i punës është përshkruar tashmë më sipër, atje ku flitet për drejtorin LED kinez (në manualin e referencës, shiko seksionin 25). SPI mund të funksionojë në modalitetin e masterit („pronarit”) dhe të skllavit („skllavit”). SPI ka katër kanale bazë, nga të cilat mund të aktivizohen jo të gjithë:
- MOSI, Master Output / Slave Input: ky pin në modalitetin e masterit jep të dhëna, dhe në modalitetin e skllavit merr të dhëna;
- MISO, Master Input / Slave Output: përkundrazi, në master merr, në skllav jep;
- SCK, Serial Clock: përcakton frekuencën e transmetimit të të dhënave në master ose merr sinjal taktim në skllav. Në thelb, ai rrah bitet;
- SS, Slave Select: me këtë kanal skllavi merr vesh se dikush e dëshiron atë. Në STM32 quhet NSS, ku N = negative, dmth kontrollori bëhet skllav nëse në këtë kanal është toka. Kombinohet mirë me modalitetin Open Drain Output, por kjo është një histori tjetër.
Siç është çdo gjë tjetër, SPI në STM32 është i pasur me funksionalitete, të cilat paksa e komplikojnë kuptimin e tij. Për shembull, ai di të punojë jo vetëm si SPI, por edhe si një ndërfaqe I2S, dhe për më tepër, në dokumentacionin e tyre përshkrimi është i ngatërruar, duhet të ndajmë të tepërtin në kohë. Misioni ynë është shumë i thjeshtë: duhet thjesht të dërgojmë të dhëna, duke aktivizuar vetëm MOSI dhe SCK. Shkoni në seksionin 25.3.4 (komunikimi me gjysmë-dupleks, komunikimi gjysmë-dhënës), ku gjejmë 1 clock and 1 unidirectional data wire (1 sinjal taktim dhe 1 rrjedhë të dhënash njëanësore):

Në këtë modalitet, aplikacioni përdor SPI ose në modalitetin e transmetimit të vetëm, ose të pranimit të vetëm. / Modaliteti i transmetimit të vetëm është i ngjashëm me modalitetin dupleks: të dhënat transmetohen përmes pin-it transmetues (MOSI në modalitetin e mjeshtrit ose MISO në modalitetin e skllavit), dhe pini i pranuar (MISO ose MOSI përkatësisht) mund të përdoret si një pin i zakonshëm hyrës-dalës. Në këtë rast, aplikacioni thjesht duhet të injorojë tamponin Rx (nëse e lexoni, aty nuk do të ketë të dhëna të transmetuara).
Shkëlqyeshëm, pini MISO tani është i lirë, do ta lidhim me sinjalin LAT. Do të merremi me Slave Select, i cili në STM32 mund të kontrollohet programatikisht, që është jashtëzakonisht e përshtatshme. Lexojmë paragrafi me të njëjtin emër të seksionit 25.3.1 Përshkrimi i Përgjithshëm të SPI:

Kontrolli programatik i NSS (SSM = 1) / Informacioni mbi zgjedhjen e skllavit përmbanet në bitin SSI të regjistrit SPI_CR1. Pini i jashtëm NSS mbetet i lirë për nevoja të tjera të aplikacionit.
Është koha të shkruajmë në regjistra. Kam vendosur të përdor SPI2, gjejmë adresën e tij bazë në dokumentin e të dhënave – në seksionin 3.3 Hartë e Memorisë:
![]()
Pra, le të fillojmë:
#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))Hapim seksionin 25.3.3 me emrin që flet ‘Konfigurimi i SPI në modalitetin mjeshtër’:

1. Cilësoni frekuencën e taktimeve të interfeces sekuencial me bits BR[2:0] në regjistrin SPI_CR1.
Regjistrat janë të mbledhur në seksionin e njëjtë të manualit referues. Shkalla e adresës (Address offset) te CR1 – 0x00, si rregull të gjithë bitët janë resetuar (Reset value 0x0000):

Bitët BR vendosin ndarësin e frekuencës së kontrollorit, duke përcaktuar kështu frekuencën me të cilën do të punojë SPI. Frekuenca e STM32 do të jetë 72 MHz, dhe LED-drivieri, sipas dokumentit të tij të të dhënave, punon me një frekuencë deri në 25 MHz, kështu që duhet të ndajmë me katër (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. Vendosni bitët CPOL dhe CPHA për të përcaktuar marrëdhëniet ndërmjet transmetimit të të dhënave dhe taktimeve të interfeces sekuencial (shih skemën në fq. 240)
Duke qenë se ne po lexojmë dokumentin e të dhënave dhe jo duke shqyrtuar skemat, le të studiojmë përshkrimin tekstual të bitëve CPOL dhe CPHA në fq. 704 (Përshkrimi i Përgjithshëm të SPI):

Faza dhe polariteti i sinjalit të taktimeve
Me ndihmën e bitëve CPOL dhe CPHA të regjistrit SPI_CR1, mund të zgjidhni programatikisht katër variante marrëdhëniesh të kohëve. Biti CPOL (polariteti i sinjalit të taktimeve) kontrollon gjendjen e sinjalit të taktimeve kur të dhënat nuk transmetohen. Ky bit kontrollon modalitetet mjeshtër dhe skllav. Nëse CPOL është resetuar, pini SCK në modalitetin e qetësisë është në nivelin e ulët. Nëse biti CPOL është vendosur, pini SCK në modalitetin e qetësisë është në nivelin e lartë.
Nëse është vendosur bita CPHA (fazën e ciklit të sinjalit), brenda sinjalit SCK, fronti i dytë i sinjalit shërben si strobe për bitin më të lartë (rdhjesa, nëse CPOL është zero, ose rritja, nëse CPOL është e vendosur). Të dhënat regjistrohen me ndryshimin e dytë të ciklit të sinjalit. Nëse bita CPHA është zero, fronti i parë i sinjalit SCK shërben si strobe për bitin më të lartë (rdhjesa, nëse CPOL është i vendosur, ose rritja, nëse CPOL është zero). Të dhënat regjistrohen me ndryshimin e parë të ciklit të sinjalit.
Duke u thelluar në këto njohuri, arrijmë në përfundimin se të dy bita duhet të mbeten zero, sepse duam që sinjali SCK të mbetet i ulur kur nuk përdoret, dhe të dhënat të transmetohen me frontin e rritjes së impulsit (shih Rising Edge në fletën e të dhënave DM634).
Sigurisht, këtu për herë të parë u përballë me një veçori të leksikut në fletat e të dhënave ST: në to fraza ‘të rivendosësh bitin në zero’ shkruhet to reset a bit, e jo to clear a bit, si për shembull në Atmega.
3. Vendosni bitin DFF për të përcaktuar formatin e blokut të të dhënave 8-bitë ose 16-bitë
E mora veçanërisht DM634 16-bitë për të mos u shqetësuar me transmetimin e të dhënave 12-bitë PWM, siç është DM633. DFF ka kuptim të vendoset në një:
#define DFF 0x0800
_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode4. Konfiguroni bitin LSBFIRST në regjistrin SPI_CR1 për të përcaktuar formatin e blokut
LSBFIRST, siç tregon emri i tij, konfiguroni transmetimin duke filluar nga bita më i ulët. Por DM634 dëshiron të marrë të dhëna duke filluar nga bita më e lartë. Prandaj, e lëmë të paprekur.
5. Në modalitetin harduerik, nëse kërkohet hyrja nga pina NSS, jepni një sinjal të lartë në pinin NSS gjatë tërë sekuencës së transmetimit të byte-ëve. Në modalitetin programues NSS vendosni bita SSM dhe SSI në regjistrin SPI_CR1. Nëse pini NSS duhet të punojë si dalëse, duhet të vendosni vetëm bitin SSOE.
Vendosim SSM dhe SSI, për të harruar modalitetin harduerik NSS:
#define SSI 0x0100
#define SSM 0x0200
_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high6. Duhet të jenë vendosur bita MSTR dhe SPE (këta mbeten të vendosur vetëm nëse në NSS jepet një sinjal i lartë)
Në fakt, me këta bita ne emërojmë SPI-në tonë si master dhe e aktivizojmë atë:
#define MSTR 0x0004
#define SPE 0x0040
_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;SPI është konfiguruar, le të shkruajmë menjëherë funksionet që dërgojnë byte-ët tek drejtuesi. Vazhdoni të lexoni 25.3.3 'Konfigurimi i SPI në modalitetin master':

Rendi i transmetimit të të dhënave
Transmetimi fillon kur një byte shkruhet në tamponin Tx.
Byte i të dhënave ngarkohet në regjistrin e lëvizjes në modalitetin paralel (nga autobusi i brendshëm) gjatë transmetimit të bitit të parë, pas së cilës transmetohet në modalitetin e në modalitetin e pin MOSI, me bitin e parë ose të fundit përpara në varësi të vendosjes së bitit LSBFIRST në regjistrin CPI_CR1. Flagi TXE vendoset pas dërgesës së të dhënave nga buferri Tx në regjistrin e zhvendosjes, dhe gjithashtu krijohet një ndërprerje nëse blloku TXEIE është vendosur në regjistrin CPI_CR1.
Kam nxjerrë disa fjalë në përkthim për të theksuar një veçori të realizimit të SPI në kontrolerët STM. Në Atmega, flagi TXE (Tx Empty, Tx është bosh dhe gati për të pranuar të dhëna) vendoset vetëm pas dërgimit të gjithë bajtëve jashtë. Ndërsa këtu, ky flag vendoset pasi bajti është futur në regjistrin e brendshëm të zhvendosjes. Pavarësisht se ai futet me të gjithë bitët njëherësh (paralelisht), të dhënat dërgohen më pas në mënyrë të njëpasnjëshme, TXE vendoset para se bajti të dërgohet plotësisht. Kjo është e rëndësishme, sepse në rastin e drejtuesit tonë të LED-ve, na duhet të lëvizim pinin LAT pas dërgimit të gjitha të dhënave, dmth, vetëm flagu TXE nuk do të jetë i mjaftueshëm.
Kjo do të thotë se na nevojitet edhe një flag tjetër. Le të shohim në 25.3.7 – "Flakët e Statusit":

<…>

Flagi BUSY
Flagi BSY vendoset dhe resetohet në mënyrë harduerike (shkrimi në të nuk ndikon në asgjë). Flagi BSY tregon gjendjen e shtresës komunikative SPI.
Ai resetohet:
kur dërgimi përfundon (përveç modalitetit master, nëse dërgimi është i vazhdueshëm)
kur SPI është i çaktivizuar
kur ndodh një gabim në modalitetin master (MODF=1)
Nëse dërgimi nuk është i vazhdueshëm, flagi BSY resetohet midis çdo dërgimi të dhënash
Mirë, do të na duhen. Po përcaktojmë se ku ndodhet buferri Tx. Për këtë, le të lexojmë "Regjistrin e të Dhënave SPI":

Bitët 15:0 DR[15:0] Regjistri i të Dhënave
Të dhënat e marra ose të dhënat për dërgim.
Regjistri i të dhënave ndahen në dy bufra – një për shkrim (bufri i dërgimit) dhe një tjetër për lexim (bufri i pranimit). Shkrimi në regjistrin e të dhënave shkruan në bufri Tx, ndërsa leximi nga regjistri i të dhënave do të kthejë vlerën që gjendet në bufri Rx.
Po ashtu, regjistri i statusit, ku do të gjejmë flakët TXE dhe BSY:

Shkruajmë:
#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
}Dhe pasi na duhen të dërgojmë 16 herë nga dy bajta, për numrin e daljeve të drejtuesit të LED-ve, atëherë ndoshta kështu:
void sendLEDdata()
{
LAT_low();
uint8_t k = 16;
do
{ k--;
dm_shift16(leds[k]);
} while (k);
while (_SPI2_(_SPI_SR) & BSY); // përfundo dërgimin
LAT_pulse();
}Por për momentin nuk dimë si të lëvizim pinin LAT, prandaj le të kthehemi në I/O.
Dërgojmë pinët
Në STM32F1, regjistrat që lidhen me gjendjen e pinëve janë mjaft të pazakontë. E qartë është se ka më shumë prej tyre se sa në Atmega, por gjithashtu dallohet nga çipat e tjerë STM. Seksioni 9.1 Përshkrimi i përgjithshëm i GPIO:

Çdo port i hyrje/ daljeve të përgjithshme (GPIO) ka dy regjistra konfigurimi 32-bitë (GPIOx_CRL dhe GPIOx_CRH), dy registre të dhënash 32-bitë (GPIOx_IDR dhe GPIOx_ODR), një regjir 32-bitë të vendosjes/zhbllokimit (GPIOx_BSRR), një regjir 16-bitë të zhbllokimit (GPIOx_BRR) dhe një regjir 32-bitë bllokues (GPIOx_LCKR).
Sa të pazakontë, gjithashtu mjaft të pakëndshëm, këtu janë dy regjistra të parë, sepse 16 pinë të portit janë shpërndarë në to në formatin "nga katër bits për vëlla". Domethënë, pinët nga zero deri në shtatë janë në CRL, ndërsa të tjerët janë në CRH. Ndërkohë, regjistrat e tjerë njësi të gjithë bitët e pinëve të portit – shpesh duke mbetur gjysmë "të rezervuar".
Për lehtësinë, do të fillojmë nga fundi i listës.
Ne nuk do ta kemi nevojën për regjistrin bllokues.
Regjistrat e vendosjes dhe zhbllokimit janë mjaft argëtues sepse pjesërisht dublojnë njëri-tjetrin: mund të shkruani gjithçka vetëm në BSRR, ku 16 bitët më të lartë do ta zbresin pinin në zero, ndërsa të voglat – do ta vendosin në 1, ose gjithashtu mund të përdorni BRR, 16 bitët e tij më të vegjël që vetëm e zbresin pinin. Më pëlqen shumë varianti i dytë. Këta regjistra janë të rëndësishëm sepse sigurojnë akses atomik në pinë:

![]()
Vendosje ose zhbllokim atomik
Nuk është nevoja të çaktivizoni ndërprerjet kur programoni GPIOx_ODR në nivelin e bitëve: mund të ndryshoni një ose më shumë bita me një operacion të vetëm atomik shkrimi APB2. Kjo arrihet duke shkruar "1" në regjistrin e vendosjes/zhbllokimit (GPIOx_BSRR ose, vetëm për zhbllokim, në GPIOx_BRR) të bitit që duhet të ndryshohet. Bitët e tjerë do të qëndrojnë të pandryshuar.
Regjistrat e dhënash kanë emra mjaft të folur – IDR = Input Direction Register, regjistri i hyrjes; ODR = Output Direction Register, regjistri i daljes. Në projektin aktual, ata nuk do na nevojiten.
Për ta përfunduar, kemi regjistrat kontrollues. Duke pasur parasysh se na interesojnë pinët e dytë SPI, konkretisht PB13, PB14 dhe PB15, menjëherë shikojmë në CRH:

Dhe shohim se do të duhet të shkruajmë diçka në bitët nga 20 në 31.
Ne kemi diskutuar më parë për atë që duam nga pins, prandaj këtu do ta kaloj pa screenshot, thjesht do të them se MODE përcakton drejtimin (input, nëse të dy bitët janë të vendosur në 0) dhe shpejtësinë e pin-it (na nevojitet 50MHz, dmth. të dy pinët në "1"), ndërsa CNF përcakton modalitetin: normal "tërheqje-dhënies" – 00, "alternativ" – 10. Sipas parashikimit, siç e shohim më sipër, të gjithë pinët kanë bitin e tretë nga poshtë (CNF0), ai i vendos ata në modalitet floating input.
Pasi planifikoj të bëj diçka tjetër me këtë çip, për thjeshtësi kam definuar të gjitha vlerat e mundshme të MODE dhe CNF si për regjistrat e kontrollit të poshtëm, ashtu edhe për ata të sipërm.
Kështu po, ndoshta kështu
#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 0x20000000Pins tanë ndodhen në portin B (adresa bazë – 0x40010C00), kodi:
#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;Dhe, për rrjedhojë, mund të shkruajmë definicionet për LAT, që do të aktivizojë regjistrat BRR dhe 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 thjesht nga zakoni, ka qenë gjithmonë kështu, le të mbetet kështu)
Tani gjithçka është fantastike, por nuk funksionon. Sepse është STM32, këtu kursen energji elektrike, dhe domethënë, duhet të aktivizojmë orarin e periferi tuaj.
Aktivizojmë orarin
Orari menaxhohet nga orë, ato janë Clock. Dhe ne tashmë mund të kemi vënë re shkurtesën RCC. Ta kërkojmë në dokumentacion: kjo është Kontrolli i Rindizjes dhe Orarit (Reset and Clock Control).
Siç thuhet më sipër, fatmirësisht, më e komplikuara nga tema e orarizimit është bërë për ne nga njerëzit e STM, për të cilët faleminderit shumë (do të jap përsëri linkun në , në mënyrë që të kuptohet sa e komplikuar është kjo). Na duhen vetëm regjistrat që menaxhojnë aktivizimin e orarizimit të periferi (Peripheral Clock Enable Registers). Për fillim, le të gjejmë adresën bazë të RCC, ajo është në fillim të "Hartës së Memories":
![]()
#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))Dhe pastaj ose të klikoni në lidhjen ku të provoni të gjeni diçka në tabelë, ose, shumë më mirë, të kaloni përmes përshkrimeve të regjistrave aktivizues nga seksionet për enable registers. Ku do të gjejmë RCC_APB1ENR dhe RCC_APB2ENR:


Dhe në to, përkatësisht, bitët që aktivizojnë orarizimin SPI2, IOPB (I/O Port B) dhe funksionet 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;Kodi përfundimtar mund të gjendet .
Nëse ka mundësi dhe dëshirë për të provuar, atëherë lidhim DM634 kështu: DAI në PB15, DCK në PB13, LAT në PB14. Furnizojmë driverin nga 5 volt, mos harrojmë të bashkojmë tokat.

STM8 PWM
PWM në STM8
Kur kur planifikuar këtë artikull, vendosa të provoj të zotëroj ndonjë funksionalitet të një çipi të panjohur vetëm me fletushkën teknike, për të mos përfunduar si një shampanjë pa shampanjë. STM8 ishte i përshtatshëm për këtë rol: së pari, kisha disa pllaka kineze me STM8S103, dhe së dyti, ai nuk është shumë i njohur, kështu që tundimi për të kërkuar një zgjidhje në internet përballet me mungesën e këtyre zgjidhjeve.
Për çipin ka gjithashtu dhe , në të parin pinout dhe adresat e regjistrave, në të dytin – gjithçka tjetër. STM8 programohet në C në një IDE jo shumë tërheqëse .
Taktime dhe hyrje-dalja
Për default, STM8 punon në një frekuencë prej 2 MHz, kjo duhet të korrigohet menjëherë.

Sinjali takt HSI (sinjali i brendshëm i shpejtë)
Sinjali takt HSI merret nga një gjenerator RC të brendshëm 16 MHz me një ndarës të programueshëm (nga 1 deri në 8). Ai përcaktohet në regjistrin e ndarësit të sinjalit takt (CLK_CKDIVR).
Shënim: në fillim, burimi kryesor i sinjalit takt zgjidhet gjeneratori HSI RC me ndarës 8.
Gjejmë adresën e regjistrit në fletushkë, përshkrimi në refman dhe shohim se regjistri duhet të pastrohet:
#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6
CLK_CKDIVR &= ~(0x18);Duke qenë se po planifikojmë të aktivizojmë PWM dhe të lidhem me LED, shikojmë pinout-in:

Çipi është i vogël, shumë funksione janë të lidhura me pin të njëjtë. Ajo që është në kllapa katrore është "funksionalitet alternativ", ai kalon me "bytet e mundësive" (option bytes) – diçka si fuzat e Atmegës. Mund t'i ndryshoni vlerat programatikisht, por nuk është e nevojshme, pasi funksionaliteti i ri aktivizohet vetëm pas ri-ngritjes. Më e lehtë është të përdorni ST Visual Programmer (shkarkohet së bashku me Visual Develop), i cili di të ndryshojë këto byte. Në pinout duket se daljet CH1 dhe CH2 të timerit të parë janë të fshehura në kllapa; duhet të vendosim bitet AFR1 dhe AFR0 në STVP, dhe bita e dytë gjithashtu do ta çojë daljen CH1 të timerit të dytë nga PD4 në PC5.
Kështu, ndriçimi i LED do të menaxhohet nga 6 pin: PC6, PC7 dhe PC3 për timerin e parë, PC5, PD3 dhe PA3 për të dytin.
Konfigurimi i vetë pini të hyrje-daljes në STM8 është më i thjeshtë dhe më logjik se sa në STM32:
- regjistri i drejtimeve të të dhënave DDR (Data Direction Register): 1 = dalje;
- regjistri i kontrollit CR1 për dalje përcakton modalitetin "tërheq dhe shty" (1) ose stok të hapur (0); pasi unë po lidhem me LED me katodet, lë këtu zerot;
- regjistri i dytë të kontrollit CR2 për dalje përcakton shpejtësinë e taktimit: 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)); //fastKonfigurimi PWM
Në fillim, le të sqarojmë disa termine:
- Frekuenca PWM – frekuenca me të cilën tikuar timeri;
- Auto-reload, AR – vlera që ngarkohet automatikisht deri në të cilën do të llogarisë timeri (periudha e impulsit);
- Ngjarja e përditësimit, UEV – ngjarja që ndodh kur timeri arriti në AR;
- Cikli i Përmbushjes PWM – koeficienti i mbushjes PWM, shpesh quhet "shkalla e mbushjes";
- Vlera e Kapjes/Shtesës – vlera për kapjen/shtesën, deri në të cilën timeri do të bëjë diçka (në rastin e PWM – inverton sinjalin dalës);
- Vlera e Parakohshme – vlerë e ngarkuar paraprakisht. Vlera e krahasimit nuk mund të ndryshohet ndërsa timeri tikun, në të kundërt ndodh kollapsi i ciklit të PWM. Prandaj, vlerat e reja të dërguara vendosen në një buffer dhe nxirren prej andej kur timeri arrin fundin e numërimit dhe reset-het;
- Modet e Palosjes me Rrymë të Drejtë dhe Modet e Palosjes Qendrore – palosja në skaj dhe në qendër, ato janë të njëjta si ato të ATMEL. PWM e Shpejtë dhe PWM e Korrigjuar në Fazë.
- OCiREF, Sinjali Referues i Krahasimit të Daljes – sinjali referues dalës, ndoshta, ajo që në modin PWM gjendet në pinsin përkatës.
Siç është e qartë nga pinout-i, PWM është i disponueshëm nga dy timerat – i pari dhe i dyti. Të dy janë 16-bit, i pari disponon shumë karakteristika shtesë (për shembull, mund të llogarisë edhe në mënyrë rritëse dhe zvogëlues). Na nevojitet që të dy të funksionojnë njësoj, prandaj vendosa të filloj me të dytin që është më i kufizuar, për të mos rënë rastësisht në përdorim të ndonjë funksioni që nuk e ka. Një problem i caktuar është se përshkrimi i funksionalitetit të PWM për të gjitha timerat është në manualin referues në kapitullin për timerin e parë (17.5.7 Moda PWM), prandaj ndonjëherë duhet të kalojmë nga njëra në tjetrën në dokument.
PWM në STM8 ka një avantazh të rëndësishëm mbi PWM të Atmega:

PWM me Palosje në Skaj
Konfigurimi i numërimit nga poshtë lart
Numërimi nga poshtë lart është aktiv, nëse b it DIR në regjistrin TIM_CR1 është resetuar.
Shembulli
Shembulli përdor modin e parë të PWM. Sinjali referues PWM OCiREF mbahet në nivel të lartë, për sa kohë TIM1_CNT < TIM1_CCRi. Në të kundërt, ai merr nivel të ulët. Nëse vlera për krahasim në regjistrin TIM1_CCRi është më e madhe se vlera e auto-ngarkuar (regjistri TIM1_ARR), sinjali OCiREF mbahet në 1. Nëse vlera për krahasim është 0, OCiREF mbahet në zero.…
Timeri STM8 gjatë ngjarjes së përditësimit për herë të parë kontrollon vlerën e krahasimit, dhe pastaj jep sinjalin referues. Në Atmega, timeri së pari punon dhe pastaj krahasohet, si rezultat, kur vlera e krahasimit == 0 Në daljen ka një gjilpërë me të cilën duhet të luftojmë ndonjë mënyrë (p.sh. duke invertuar logjikën programore).
Pra, çfarë duam të bëjmë: 8-bit PWM (AR == 255), llogaritur nga poshtë lart, me rregullim në kufi. Duke qenë se llambat janë lidhur me çipin me katode, PWM duhet të japë 0 (LED ndezur) deri vlerën e krahasimit dhe 1 pas.
Kemi lexuar tashmë për disa modalitetet PWM, prandaj gjejmë regjistrin e dytë të timerit duke kërkuar në manualin referues me këtë frazë (18.6.8 – TIMx_CCMR1):

110: Modaliteti i parë PWM – në llogaritjen nga poshtë lart, kanali i parë është aktiv derisa TIMx_CNT < TIMx_CCR1. Në të kundërt, kanali i parë nuk është aktiv. [më pas në dokument ka kopjime të gabuara nga timeri 1]
111: Modaliteti i dytë PWM – në llogaritjen nga poshtë lart, kanali i parë nuk është aktiv derisa TIMx_CNT < TIMx_CCR1. Në të kundërt, kanali i parë është aktiv.
Duke qenë se LED-et janë lidhur me MK me katode, na përshtatet modaliteti i dytë (edhe i pari, por ende nuk e dimë këtë).

Bit 3 OC1PE: Aktivizo ngarkimin paraprak të daljes 1
0: Regjistri i ngarkimit paraprak në TIMx_CCR1 është i fikur. Mund të shkruhet në TIMx_CCR1 në çdo kohë. Vlera e re funksionon menjëherë.
1: Regjistri i ngarkimit paraprak në TIMx_CCR1 është i aktivizuar. Operacionet e leximit/shkrimit adresojnë regjistrin e ngarkimit paraprak. Vlera e ngarkuar paraprakisht TIMx_CCR1 ngarkohet në regjistrin e hijeve gjatë secilës ngjarje përditësimi.
*Shënim: për funksionimin e saktë të modalitetit PWM, regjistrat e ngarkimit paraprak duhet të aktivizohen. Kjo nuk është e nevojshme në modalitetin e sinjalit të vetëm (kur bita OPM në regjistrin TIMx_CR1 është e vendosur).
Ok, aktivizojmë gjithçka që na nevojitet për tre kanale të timerit të dytë:
#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 përbëhet nga dy regjistra tetëbitësh, këtu është shumë e thjeshtë:
#define TIM2_ARRH *(volatile uint8_t *)0x00530F
#define TIM2_ARRL *(volatile uint8_t *)0x005310
TIM2_ARRH = 0;
TIM2_ARRL = 255;Timeri i dytë mund të numërojë vetëm nga poshtë lart, me rregullim në kufi, nuk ka nevojë të ndryshohet asgjë. Do të vendosim pjesëtimin e frekuencës, p.sh. në 256. Te timeri i dytë, pjesëtimi vendoset në regjistrin TIM2_PSCR dhe përfaqëson një fuqi të dyshes:
#define TIM2_PSCR *(volatile uint8_t *)0x00530E
TIM2_PSCR = 8;E mbetur është të aktivizojmë daljet dhe vetë timerin e dytë. Problemi i parë zgjidhet me regjistrat Capture/Compare Aktivizo: ka dy nga ata, tre kanale të shpërndara në mënyrë asimetrike. Këtu gjithashtu mund të mësojmë se mund të ndryshohet polariteti i sinjalit, pra në parim mund të ishte përdorur edhe PWM Mode 1. Shkruajmë:
#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;Dhe përfundimisht, nisëm timerin në regjistrin TIMx_CR1:

#define TIM2_CR1 *(volatile uint8_t *)0x005300
TIM2_CR1 |= 1;Do të shkruajmë një analog më të thjeshtë të AnalogWrite(), e cila do t'i dërgojë timerit vlerat për krahasim. Regjistrat me siguri quhen Capture/Compare registers, dy dy dy kanale: 8 bitët e para në TIM2_CCRxL dhe 8 bitët e lartë në TIM2_CCRxH. Duke qenë se kemi vendosur PWM me 8 bitë, mjafton të shkruajmë vetëm bitët e poshtëm:
#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;
}Lexuesi i kujdesshëm do të vërejë se kemi një PWM pak defekt, i paaftë për të dhënë 100% mbushje (në vlerën maksimale 255, si si është e invertohet për një cikël timer). Për LED-të, kjo nuk ka rëndësi, dhe lexuesi i kujdesshëm tashmë e kupton se si ta rregullojë këtë.
PWM në timerin e dytë funksionon, kalojmë te i pari.
Timeri i parë ka të njëjtat bite në të njëjtat regjistra (thjesht ato bite që mbetën "të rezervuar" në timerin e dytë, në të parin përdoren aktivisht për gjëra të avancuara). Prandaj, mjafton të gjeni adresat e këtyre regjistrave në fletëkujtim dhe të kopjoni kodin. Po ashtu, duhen ndryshuar vlerat e ndarësit të frekuencës, sepse timeri i parë do të marrë një vlerë precize 16-bit në dy regjistra, jo një fuqi e dyshes. Prescaler High dhe I Ulët. E gjithë kjo bëhet dhe… timeri i parë nuk funksionon. Çfarë ndodhi?
Problemi mund të zgjidhet vetëm duke shikuar tërë seksionin mbi regjistrat e kontrollit të timerit 1, ku kërkojmë atë që nuk e ka timeri i dytë. Do të gjendet 17.7.30 Regjistri i Prishjes (TIM1_BKR), ku ka një bit të tillë:
![]()
Aktivizo daljen kryesore
#define TIM1_BKR *(volatile uint8_t *)0x00526D
TIM1_BKR = (1<<7);Tani gjithçka është e saktë, kodi .

STM8 Multiplex
Multipleximi në STM8
Projekti i tretë përbëhet nga lidhja e tetë RGB LED-ësh në timerin e dytë në mënyrë PWM dhe të bëjë që ata të shfaqin ngjyrat e ndryshme. Në thelb - koncepti i multipleximit të LED-ve, që e bën deri në atë pikë që nëse ndizni dhe fikni LED-të shumë-shumë shpejt, do të duket se ata janë të ndezur vazhdimisht (përshtypja e qëndrueshmërisë, inercia e perceptimit vizual). Njëherë e kam bërë .
Algoritmi i punës duket kështu:
- lidhi anodin e LED-it të parë RGB;
- e ndizni atë, duke dërguar sinjale të nevojshme për katodet;
- pritni fundin e ciklit PWM;
- lidhi anodin e LED-it të dytë RGB;
- e ndizni atë…
Dhe kështu me radhë. Sigurisht, për të funksionuar bukur, është e nevojshme që lidhja e anodit dhe "ndizja" e LED-it të ndodhin njëkohësisht. Ose pothuajse. Në çdo rast, duhet të shkruajmë kodin që do të japë vlera në tri kanale të timerit të dytë, dhe kur të arrihet UEV, t'i ndryshojë ato dhe njëkohësisht të ndryshojë RGB-LED-in aktiv në atë moment.
Duke switch LED bëhet automatikisht, duhet të krijohet një "memorie video", nga e cila manipuluesi i ndërprerjeve do të marrë të dhënat. Kjo është një masiv i thjeshtë:
uint8_t colors[8][3];Për të ndryshuar ngjyrën e një LED të veçantë, mjafton të shkruani vlerat e nevojshme në këtë masiv. Ndërsa për numrin e LED aktiv do të kujdeset variabla
uint8_t cnt;Demuks
Për multipleximin e duhur, na nevojitet, siç është e pritur, demultiplexori CD74HC238. Demultiplexori është një çip, i cila implementon operatorin <<. Duke përdorur tre pinë hyrës (bitët 0, 1 dhe 2) i ofrojmë një numër tre-bitësh X, dhe ai në përgjigje aktivizon daljen numër (1<<X). Hyrjet e tjera të çipit përdoren për të shkallëzuar të gjithë konstrukcionin. Ky çip na nevojitet jo vetëm për të reduktuar numrin e pinëve të zënë të mikro-kontrollorit, por edhe për siguri – që aksidentalisht të mos ndizni më shumë LED se sa është e mundur dhe të mos digjni MK. Çipi është shumë i lirë, ia vlen të mbahet gjithmonë në kitin tuaj të shtëpisë.
CD74HC238 do të përgjigjet që të furnizojë tensionin në ankorën e LED të duhur. Në një multiplex të plotë, do të dërgonte tensionin në kolonë përmes P-MOSFET, por në këtë demo mund të bëhet direkt, pasi ai tërheq 20 mA, sipas vlerave maksimale absolute në fletën e të dhënave. Nga na nevojitet pinout dhe ky shënim:

H = nivel i lartë tensioni, L = nivel i ulët tensioni, X – çdo gjë është në rregull
Lidhni E2 dhe E1 me tokë, E3, A0, A1 dhe A3 me pinët PD5, PC3, PC4 dhe PC5 STM8. Duke qenë se tabela më sipër përmban dhe nivele të ulëta dhe të larta, konfiguroni këta pinë si dalje push-pull.
PWM
PWM në timerin e dytë konfigurohet ashtu si në historinë e mëparshme, me dy përjashtime:
Së pari, na duhet të aktivizojmë ndërprerjen për Ngjarje e Azhurnimit (UEV), e cila do të thërrasë funksionin që kalon LED aktiv. Kjo bëhet duke ndryshuar bitin Aktivizimi i Ndërprerjes në regjistrin me emrin e dukshëm

Regjistri i Aktivizimit të Ndërprerjeve
#define TIM2_IER *(volatile uint8_t *)0x005303
//enable interrupt
TIM2_IER = 1;Përjashtimi i dytë lidhet me një fenomen të multipleximit, siç është ghosting – ndriçimi parazitar i diodave. Në rastin tonë, ky ndriçim mund të shfaqet për shkak se timer-i, që shkakton ndërprerjen në UEV, vazhdon të ikë, dhe manipuluesi i ndërprerjes nuk arrin ta ndërron LED përpara se timer-i të fillojë të shkruajë diçka në daljet. Për të luftuar këtë, do të duhet të invertim logjikën (0 = ndriçim maksimal, 255 = asgjë nuk ndriçon) dhe të mos lejojmë vlerat ekstreme të ngjeshjes. Pra, duhet të arrijmë që pas UEV LED-at të fikën plotësisht për një cikël PWM.
Ndryshojmë polaritetin:
//set polarity
TIM2_CCER1 |= (CC1P | CC2P);
TIM2_CCER2 |= CC3P;Evitojmë vendosjen e r, g dhe b në 255 dhe mos harrojmë t'i invertim kur i përdorim.
Ndërprerjet
Qëllimi i ndërprerjes është që në rrethana të caktuara, çipi ndalon ekzekutimin e programit kryesor dhe thërret një funksion të jashtëm. Ndërprerjet ndodhin për shkak të efekteve të jashtme ose të brendshme, përfshirë ato nga timer-i.
Kur krijuam për herë të parë projektin në ST Visual Develop, morëm përveç main.c një dritare me një skedar misterios stm8_interrupt_vector.c, që u përfshi automatikisht në projekt. Në këtë skedar, për çdo ndërprerje është lidhur një funksion NonHandledInterrupt. Ne duhet të lidhim funksionin tonë me ndërprerjen e duhur.
Në datasheet ekziston një tabelë e vektorëve të ndërprerjeve, ku gjejmë të nevojshmet:

13 Riatdhesim/mbushje TIM2
14 Kapje/krahasim TIM2
Na duhet të ndryshojmë LED-in për UEV, kështu që na nevojitet ndërprerja nr. 13.
Përkatësisht, së pari, në skedarin stm8_interrupt_vector.c ndryshojmë emrin e funksionit që i përgjigjet ndërprerjes nr. 13 (IRQ13) në atë që kemi përzgjedhur:
{0x82, TIM2_Overflow}, /* irq13 */Së dyti, do të duhet të krijojmë një skedar main.h me përmbajtjen e tillë:
#ifndef __MAIN_H
#define __MAIN_H
@far @interrupt void TIM2_Overflow (void);
#endifDhe, në fund, të shkruajmë këtë funksion në main.c:
@far @interrupt void TIM2_Overflow (void)
{
PD_ODR &= ~(1<<5); // fikim demultiplexorin
PC_ODR = (cnt<<3); // shkruajmë në demultiplexorin vlerën e re
PD_ODR |= (1<<5); // ndizim demultiplexorin
TIM2_SR1 = 0; // rivendosim flamurin e Ndërprerjes së Pritur të Azhurnimit
cnt++;
cnt &= 7; // lëvizim numëruesin e LED-it
TIM2_CCR1L = ~colors[cnt][0]; // dërgojmë vlerat e invertuara në buffer
TIM2_CCR2L = ~colors[cnt][1]; // për ciklin e ardhshëm të PWM
TIM2_CCR3L = ~colors[cnt][2]; //
return;
}Kemi mbetur vetëm të aktivizojmë ndërprerjet. Kjo bëhet me komandën assemblore rim – do ta kërkojmë në :
//enable interrupts
_asm("rim");Një komandë tjetër assemblore – sim – çon në fikjen e ndërprerjeve. Ato duhet të çohen përkohësisht gjatë shkrimit të vlerave të reja në 'memorinë vizuale', në mënyrë që një ndërprerje e papërshtatshme të mos prishë masën.
I gjithë kodi është .

Nëse kjo artikull është e dobishme për dikë, atëherë nuk e kam shkruar kot. Do të jem i lumtur për komentet dhe sugjerimet, do të përpiqem të përgjigjem për të gjitha.
Burimi: habr.com
