Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

В het eerste deel Ik probeerde hobby-elektronica-ingenieurs die zijn opgegroeid met Arduino-broeken te vertellen hoe en waarom ze datasheets en andere documentatie voor microcontrollers zouden moeten lezen. De tekst bleek groot, dus beloofde ik praktische voorbeelden in een apart artikel te laten zien. Nou, hij noemde zichzelf een melkpaddestoel...

Vandaag zal ik je laten zien hoe je datasheets kunt gebruiken om vrij eenvoudige, maar noodzakelijke voor veel projecten, taken op STM32 (Blue Pill) en STM8-controllers op te lossen. Alle demoprojecten zijn gewijd aan mijn favoriete LED's, we zullen ze in grote hoeveelheden aansteken, waarvoor we allerlei interessante randapparatuur zullen moeten gebruiken.

De tekst bleek wederom enorm groot, dus voor het gemak maak ik de inhoud:

STM32 Blue Pill: 16 LED's met DM634-driver
STM8: zes PWM-pinnen instellen
STM8: 8 RGB-LED's op drie pinnen, interrupts

Disclaimer: ik ben geen ingenieur, ik pretendeer niet een diepgaande kennis van elektronica te hebben, het artikel is bedoeld voor amateurs zoals ik. Sterker nog, ik beschouwde mezelf twee jaar geleden als de doelgroep. Als iemand mij toen had verteld dat datasheets op een onbekende chip niet eng waren om te lezen, zou ik niet veel tijd hebben besteed aan het zoeken naar stukjes code op internet en het uitvinden van krukken met een schaar en plakband.

De focus van dit artikel ligt op datasheets, niet op projecten, dus de code is misschien niet erg netjes en vaak krap. De projecten zelf zijn heel eenvoudig, maar geschikt voor een eerste kennismaking met de nieuwe chip.

Ik hoop dat mijn artikel iemand zal helpen die zich in een vergelijkbaar stadium van onderdompeling in de hobby bevindt.

STM32

16 LED's met DM634 en SPI

Een klein project met Blue Pill (STM32F103C8T6) en DM634 LED-driver. Met behulp van datasheets zullen we de driver, STM IO-poorten achterhalen en SPI configureren.

DM634

Taiwanese chip met 16 16-bit PWM-uitgangen, kan in ketens worden aangesloten. Het low-end 12-bits model is bekend van een binnenlands project Lichtpakket. Toen ik ooit moest kiezen tussen de DM63x en de bekende TLC5940, koos ik om verschillende redenen voor DM: 1) TLC op AliExpress is absoluut nep, maar deze niet; 2) DM heeft een autonome PWM met een eigen frequentiegenerator; 3) het zou goedkoop in Moskou kunnen worden gekocht, in plaats van te wachten op een pakketje van Ali. En het was natuurlijk interessant om zelf te leren hoe je de chip kunt besturen, in plaats van een kant-en-klare bibliotheek te gebruiken. Chips worden nu vooral in de SSOP24-verpakking gepresenteerd; ze zijn eenvoudig aan een adapter te solderen.

Aangezien de fabrikant Taiwanees is, data papier de chip is geschreven in Chinees-Engels, wat betekent dat het leuk zal zijn. Eerst kijken we naar de pinout (Pin-verbinding) om te begrijpen op welke poot u wat moet aansluiten, en een beschrijving van de pinnen (Pin Beschrijving). 16 pinnen:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
DC-gootsteenbronnen (open afvoer)

Wastafel / Open afvoer uitgang - droogleggen; bron van inkomende stroom; de uitgang is in actieve toestand met aarde verbonden - de LED's zijn via kathodes met de driver verbonden. Elektrisch gezien is dit uiteraard geen “open drain” (open afvoer), maar in datasheets wordt deze aanduiding voor pinnen in drain-modus vaak aangetroffen.

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Externe weerstanden tussen REXT en GND om de uitgangsstroomwaarde in te stellen

Tussen de REXT-pin en aarde is een referentieweerstand geïnstalleerd, die de interne weerstand van de uitgangen regelt, zie de grafiek op pagina 9 van het gegevensblad. Bij de DM634 kan deze weerstand ook softwarematig worden geregeld, waarbij de algehele helderheid wordt ingesteld (mondiale helderheid); Ik zal in dit artikel niet in details treden, ik plaats hier gewoon een weerstand van 2.2 - 3 kOhm.

Laten we, om te begrijpen hoe we de chip kunnen besturen, eens kijken naar de beschrijving van de apparaatinterface:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

Ja, hier is het, Chinees-Engels in al zijn glorie. Het vertalen hiervan is problematisch, je kunt het begrijpen als je wilt, maar er is een andere manier - kijk hoe de verbinding met de functioneel vergelijkbare TLC5940 wordt beschreven in het gegevensblad:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
... Er zijn slechts drie pinnen nodig om gegevens in het apparaat in te voeren. De stijgende flank van het SCLK-signaal verschuift de gegevens van de SIN-pin naar het interne register. Nadat alle gegevens zijn geladen, vergrendelt een kort hoog XLAT-signaal de sequentieel overgedragen gegevens naar de interne registers. Interne registers zijn poorten die worden geactiveerd door het XLAT-signaalniveau. Alle gegevens worden eerst met het meest significante bit verzonden.

LATCH – grendel/grendel/slot.
Stijgende flank – voorflank van de puls
MSB eerst – meest significante (meest linkse) bit naar voren.
om gegevens te klokken – gegevens opeenvolgend verzenden (bit voor bit).

Woord klink is vaak te vinden in de documentatie voor chips en is op verschillende manieren vertaald, dus ter wille van het begrip zal ik mezelf toestaan

klein educatief programmaDe LED-driver is in wezen een schuifregister. "Verschuiving" (verschuiving) in de naam - bitsgewijze verplaatsing van gegevens binnen het apparaat: elk nieuw bit dat naar binnen wordt geschoven, duwt de hele keten naar voren. Omdat niemand het chaotische knipperen van de LED's tijdens de dienst wil waarnemen, vindt het proces plaats in bufferregisters die door een demper van de werkregisters zijn gescheiden (klink) is een soort wachtkamer waar de bits in de gewenste volgorde worden gerangschikt. Als alles klaar is, gaat de sluiter open en gaan de bits aan het werk, ter vervanging van de vorige batch. Woord klink in de documentatie voor microschakelingen impliceert bijna altijd een dergelijke demper, ongeacht in welke combinaties deze wordt gebruikt.

De gegevensoverdracht naar de DM634 wordt dus als volgt uitgevoerd: stel de DAI-ingang in op de waarde van het meest significante bit van de verre LED, trek DCK op en neer; stel de DAI-ingang in op de waarde van het volgende bit, trek DCK; enzovoort totdat alle bits zijn verzonden (ingeklokt), waarna we LAT trekken. Dit kan handmatig (bit-bang), maar het is beter om hiervoor een speciaal op maat gemaakte SPI-interface te gebruiken, aangezien deze in twee exemplaren op onze STM32 wordt gepresenteerd.

Blauwe pil STM32F103

Inleidend: STM32-controllers zijn veel complexer dan Atmega328 dan ze eng lijken. Bovendien zijn om redenen van energiebesparing in het begin bijna alle randapparatuur uitgeschakeld en is de klokfrequentie 8 MHz vanaf de interne bron. Gelukkig hebben STM-programmeurs code geschreven die de chip op de ‘berekende’ 72 MHz brengt, en de auteurs van alle IDE’s die ik ken hebben dit opgenomen in de initialisatieprocedure, dus we hoeven niet te klokken (maar je kunt het als je het echt wilt). Maar u zult de randapparatuur moeten inschakelen.

Documentatie: Blue Pill is uitgerust met de populaire STM32F103C8T6-chip, er zijn twee handige documenten voor:

  • Data Sheet voor microcontrollers STM32F103x8 en STM32F103xB;
  • Naslaggids voor de gehele STM32F103-lijn en meer.

In de datasheet zijn we mogelijk geïnteresseerd in:

  • Pinouts – chip pinouts – voor het geval we besluiten de borden zelf te maken;
  • Geheugenkaart – geheugenkaart voor een specifieke chip. De Naslaggids bevat een kaart voor de hele lijn en vermeldt registers die de onze niet hebben.
  • Tabel Pindefinities – met een overzicht van de belangrijkste en alternatieve functies van pinnen; voor de “blauwe pil” kun je op internet handigere afbeeldingen vinden met een lijst met pinnen en hun functies. Daarom googlen we meteen de pinout van Blue Pill en houden deze foto bij de hand:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
NB: er zat een fout in de afbeelding van internet, die werd opgemerkt in de reacties, bedankt daarvoor. De afbeelding is vervangen, maar dit is een les: het is beter om informatie te controleren, niet uit datasheets.

We verwijderen het gegevensblad, openen de Naslaggids en vanaf nu gebruiken we deze alleen.
Procedure: we behandelen standaard input/output, configureren SPI, zetten de benodigde randapparatuur aan.

Invoer uitvoer

Op de Atmega328 is I/O uiterst eenvoudig geïmplementeerd, waardoor de overvloed aan STM32-opties verwarrend kan zijn. Nu hebben we alleen nog maar conclusies nodig, maar zelfs deze hebben vier opties:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
open afvoer, push-pull, alternatieve push-pull, alternatieve open afvoer

"Trekken duwen" (duwen trekken) is de gebruikelijke uitvoer van de Arduino, de pin kan de waarde HOOG of LAAG aannemen. Maar met “open drain” zijn er wel moeilijkheden, hoewel alles hier eigenlijk eenvoudig is:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Uitgangsconfiguratie / wanneer de poort is toegewezen aan uitgang: / uitgangsbuffer ingeschakeld: / – open drain-modus: “0” in het uitgangsregister schakelt N-MOS in, “1” in het uitgangsregister laat de poort in Hi-Z-modus ( P-MOS is niet geactiveerd ) / – push-pull-modus: “0” in het uitgangsregister activeert N-MOS, “1” in het uitgangsregister activeert P-MOS.

Het hele verschil tussen open afvoer (open afvoer) van “push-pull” (duwen trekken) is dat de eerste pin de HIGH-status niet kan accepteren: wanneer er een naar het uitvoerregister wordt geschreven, gaat deze naar de hoge weerstandsmodus (hoge impedantie, Hallo-Z). Bij het schrijven van nul gedraagt ​​de pin zich in beide modi hetzelfde, zowel logisch als elektrisch.

In de normale uitvoermodus zendt de pin eenvoudigweg de inhoud van het uitvoerregister uit. In het "alternatief" wordt het bestuurd door de overeenkomstige randapparatuur (zie 9.1.4):

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Als een poortbit is geconfigureerd als een alternatieve functiepin, wordt het pinregister uitgeschakeld en wordt de pin verbonden met de perifere pin.

Alternatieve functionaliteit van elke pin wordt beschreven in Pin-definities Het gegevensblad staat op de gedownloade afbeelding. Op de vraag wat te doen als een pin meerdere alternatieve functies heeft, wordt het antwoord gegeven in een voetnoot in de datasheet:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Als meerdere randapparaten dezelfde pin gebruiken, mag er, om conflicten tussen alternatieve functies te voorkomen, slechts één randapparaat tegelijk worden gebruikt, geschakeld met behulp van de perifere klokinschakelbit (in het juiste RCC-register).

Ten slotte hebben pinnen in de uitvoermodus ook een kloksnelheid. Dit is nog een energiebesparende functie; in ons geval zetten we hem gewoon op het maximum en vergeten we het.

Dus: we gebruiken SPI, wat betekent dat twee pinnen (met data en met een kloksignaal) een “alternatieve push-pull-functie” moeten zijn, en een andere (LAT) moet een “normale push-pull” zijn. Maar voordat we ze toewijzen, laten we eerst SPI behandelen.

SPI

Nog een klein educatief programma

SPI of Serial Peripheral Interface (seriële perifere interface) is een eenvoudige en zeer effectieve interface voor het verbinden van een MK met andere MK's en de buitenwereld in het algemeen. Het principe van de werking ervan is hierboven al beschreven, waar het gaat om de Chinese LED-driver (in de naslaghandleiding, zie sectie 25). SPI kan werken in master (“master”) en slave (“slave”) modus. SPI heeft vier basiskanalen, waarvan niet alle gebruikt mogen worden:

  • MOSI, Master Output / Slave Input: deze pin verzendt gegevens in de mastermodus en ontvangt gegevens in de slavemodus;
  • MISO, Master Input / Slave Output: integendeel, het ontvangt in de master en verzendt in de slave;
  • SCK, Serial Clock: stelt de frequentie van de datatransmissie in de master in of ontvangt een kloksignaal in de slave. In wezen beats slaan;
  • SS, Slave Select: met behulp van dit kanaal weet de slaaf dat er iets van hem verlangd wordt. Op STM32 wordt dit NSS genoemd, waarbij N = negatief, d.w.z. de controller wordt een slave als er aarde in dit kanaal zit. Het combineert goed met de Open Drain Output-modus, maar dat is een ander verhaal.

Net als al het andere is SPI op STM32 rijk aan functionaliteit, wat het enigszins moeilijk te begrijpen maakt. Het kan bijvoorbeeld niet alleen met SPI werken, maar ook met een I2S-interface, en in de documentatie zijn hun beschrijvingen gemengd, het is noodzakelijk om het teveel tijdig af te sluiten. Onze taak is uiterst eenvoudig: we hoeven alleen maar gegevens te verzenden met alleen MOSI en SCK. We gaan naar sectie 25.3.4 (half-duplexcommunicatie, half-duplexcommunicatie), waar we vinden 1 klok en 1 unidirectionele datadraad (1 kloksignaal en 1 unidirectionele datastroom):

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
In deze modus gebruikt de applicatie SPI in de modus voor alleen verzenden of alleen ontvangen. / Alleen verzenden-modus is vergelijkbaar met duplexmodus: gegevens worden verzonden op de verzendpin (MOSI in mastermodus of MISO in slave-modus), en de ontvangstpin (respectievelijk MISO of MOSI) kan worden gebruikt als een gewone I/O-pin . In dit geval hoeft de applicatie alleen de Rx-buffer te negeren (als deze wordt gelezen, worden daar geen gegevens overgedragen).

Geweldig, de MISO-pin is vrij, laten we het LAT-signaal erop aansluiten. Laten we eens kijken naar Slave Select, dat op de STM32 programmatisch kan worden bestuurd, wat buitengewoon handig is. We lezen de gelijknamige paragraaf in paragraaf 25.3.1 Algemene beschrijving van SPI:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Softwarebesturing NSS (SSM = 1) / Slave-selectie-informatie bevindt zich in de SSI-bit van het SPI_CR1-register. De externe NSS-pin blijft vrij voor andere toepassingsbehoeften.

Het is tijd om naar de registers te schrijven. Ik besloot SPI2 te gebruiken, zoek het basisadres in de datasheet - in sectie 3.3 Geheugenkaart:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

Nou, laten we beginnen:

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

Open sectie 25.3.3 met de voor zichzelf sprekende titel “SPI configureren in Master Mode”:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

1. Stel de seriële klokfrequentie in met bits BR[2:0] in het SPI_CR1-register.

De registers zijn verzameld in het gelijknamige gedeelte van de naslaghandleiding. Adresverschuiving (Adresverschuiving) voor CR1 – 0x00 worden standaard alle bits gewist (Reset waarde 0x0000):

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

De BR-bits stellen de klokdeler van de controller in en bepalen zo de frequentie waarop de SPI zal werken. Onze STM32-frequentie zal 72 MHz zijn, de LED-driver werkt volgens de datasheet met een frequentie tot 25 MHz, dus we moeten delen door vier (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. Stel de CPOL- en CPHA-bits in om de relatie tussen gegevensoverdracht en seriële kloktiming te definiëren (zie diagram op pagina 240)

Omdat we hier een datasheet lezen en niet naar schema's kijken, gaan we de tekstbeschrijving van de CPOL- en CPHA-bits op pagina 704 (algemene beschrijving van SPI) eens nader bekijken:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Klokfase en polariteit
Met behulp van de CPOL- en CPHA-bits van het SPI_CR1-register kunt u programmatisch vier timingrelaties selecteren. Het CPOL-bit (klokpolariteit) regelt de status van het kloksignaal wanneer er geen gegevens worden verzonden. Deze bit bestuurt de master- en slave-modi. Als CPOL wordt gereset, is de SCK-pin laag in de rustmodus. Als de CPOL-bit is ingesteld, is de SCK-pin hoog tijdens de rustmodus.
Wanneer het CPHA-bit (klokfase) is ingesteld, is de trap-stroboscoop met hoge bits de tweede flank van het SCK-signaal (dalend als CPOL vrij is, stijgend als CPOL is ingesteld). De gegevens worden vastgelegd door de tweede verandering in het kloksignaal. Als de CPHA-bit vrij is, is de hoge bit-trap-stroboscoop de stijgende flank van het SCK-signaal (dalende flank als CPOL is ingesteld, stijgende flank als CPOL is gewist). Gegevens worden vastgelegd bij de eerste verandering in het kloksignaal.

Nadat we deze kennis hebben geabsorbeerd, komen we tot de conclusie dat beide bits nullen moeten blijven, omdat We willen dat het SCK-signaal laag blijft wanneer het niet in gebruik is, en dat gegevens worden verzonden op de stijgende flank van de puls (zie Fig. Stijgende flank in het DM634-gegevensblad).

Trouwens, hier kwamen we voor het eerst een kenmerk van de woordenschat in ST-datasheets tegen: daarin staat de zinsnede "reset de bit naar nul" een beetje te resettenEn niet een beetje op te helderen, zoals bijvoorbeeld Atmega.

3. Stel de DFF-bit in om te bepalen of het datablok een 8-bits of 16-bits formaat heeft

Ik heb specifiek een 16-bit DM634 genomen om geen last te hebben van het verzenden van 12-bit PWM-gegevens, zoals de DM633. Het is zinvol om DFF in te stellen op één:

#define DFF         0x0800

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

4. Configureer de LSBFIRST-bit in het SPI_CR1-register om het blokformaat te bepalen

LSBFIRST configureert, zoals de naam al doet vermoeden, de transmissie met het minst significante bit eerst. Maar DM634 wil gegevens ontvangen vanaf het meest significante bit. Daarom laten we het resetten.

5. Als in de hardwaremodus invoer van de NSS-pin vereist is, past u een hoog signaal toe op de NSS-pin gedurende de gehele byteoverdrachtsreeks. Stel in de NSS-softwaremodus de SSM- en SSI-bits in het SPI_CR1-register in. Als de NSS-pin als uitvoer moet worden gebruikt, hoeft alleen de SSOE-bit te worden ingesteld.

Installeer SSM en SSI om de NSS-hardwaremodus te vergeten:

#define SSI         0x0100
#define SSM         0x0200

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

6. De MSTR- en SPE-bits moeten worden ingesteld (ze blijven alleen ingesteld als het NSS-signaal hoog is)

Eigenlijk wijzen we met deze bits onze SPI aan als master en zetten we hem aan:

#define MSTR        0x0004
#define SPE         0x0040

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

SPI is geconfigureerd, laten we onmiddellijk functies schrijven die bytes naar de driver sturen. Lees verder 25.3.3 “SPI configureren in mastermodus”:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Bestelling voor gegevensoverdracht
De verzending begint wanneer een byte naar de Tx-buffer wordt geschreven.
De databyte wordt in het schuifregister geladen op parallel modus (vanaf de interne bus) tijdens de verzending van het eerste bit, waarna het wordt verzonden opeenvolgend MOSI-pinmodus, eerste of laatste bit vooruit, afhankelijk van de instelling van de LSBFIRST-bit in het CPI_CR1-register. De TXE-vlag wordt ingesteld na de gegevensoverdracht van Tx-buffer naar schuifregister, en genereert ook een interrupt als de TXEIE-bit in het CPI_CR1-register is ingesteld.

Ik heb een paar woorden in de vertaling gemarkeerd om de aandacht te vestigen op een kenmerk van de SPI-implementatie in STM-controllers. Op Atmega de TXE-vlag (Tx leeg, Tx is leeg en klaar om gegevens te ontvangen) wordt pas ingesteld nadat de volledige byte is verzonden naar buiten. En hier wordt deze vlag ingesteld nadat de byte in het interne schuifregister is ingevoegd. Omdat het daar met alle bits tegelijkertijd (parallel) wordt gepusht en vervolgens de gegevens opeenvolgend worden overgedragen, wordt TXE ingesteld voordat de byte volledig is verzonden. Dit is belangrijk omdat in het geval van onze LED-driver moeten we na het verzenden de LAT-pin eruit trekken Alle gegevens, d.w.z. De TXE-vlag alleen zal voor ons niet genoeg zijn.

Dit betekent dat we een andere vlag nodig hebben. Laten we eens kijken naar 25.3.7 - “Statusvlaggen”:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
<…>
Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
BEZIG vlag
De BSY-vlag wordt ingesteld en gewist door hardware (schrijven ernaar heeft geen effect). De BSY-vlag geeft de status van de SPI-communicatielaag aan.
Het reset:
wanneer de overdracht is voltooid (behalve in de mastermodus als de overdracht continu is)
wanneer SPI is uitgeschakeld
wanneer er een mastermodusfout optreedt (MODF=1)
Als de overdracht niet continu is, wordt de BSY-vlag tussen elke gegevensoverdracht gewist

Oké, dit zal van pas komen. Laten we eens kijken waar de Tx-buffer zich bevindt. Lees hiervoor “SPI Data Register”:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Bits 15:0 DR[15:0] Gegevensregister
Ontvangen gegevens of gegevens die moeten worden verzonden.
Het dataregister is verdeeld in twee buffers: één voor schrijven (zendbuffer) en één voor lezen (ontvangstbuffer). Schrijven naar het dataregister schrijft naar de Tx-buffer, en lezen uit het dataregister retourneert de waarde die zich in de Rx-buffer bevindt.

Nou, en het statusregister, waar de TXE- en BSY-vlaggen worden gevonden:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

We schrijven:

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

Omdat we 16 keer twee bytes moeten verzenden, afhankelijk van het aantal LED-driveruitgangen, zoiets als dit:

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();
}

Maar we weten nog niet hoe we de LAT-pin eruit moeten trekken, dus gaan we terug naar I/O.

Pinnen toewijzen

In de STM32F1 zijn de registers die verantwoordelijk zijn voor de staat van de pinnen vrij ongebruikelijk. Het is duidelijk dat er meer zijn dan Atmega, maar ze zijn ook anders dan andere STM-chips. Paragraaf 9.1 Algemene beschrijving van GPIO:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Elk van de I/O-poorten voor algemene doeleinden (GPIO) heeft twee 32-bit configuratieregisters (GPIOx_CRL en GPIOx_CRH), twee 32-bit dataregisters (GPIOx_IDR en GPIOx_ODR), een 32-bit set/reset-register (GPIOx_BSRR), een 16-bit reset-register (GPIOx_BRR) en een 32-bit bitblokkeringsregister (GPIOx_LCKR).

De eerste twee registers zijn ongebruikelijk en ook behoorlijk lastig, omdat de 16 poortpinnen erover verspreid zijn in een "vier bits per broer" -formaat. Die. pinnen nul tot zeven zijn in CRL en de rest is in CRH. Tegelijkertijd bevatten de resterende registers met succes de bits van alle pinnen van de poort - vaak blijven ze half "gereserveerd".

Laten we voor de eenvoud beginnen aan het einde van de lijst.

We hebben geen blokkeerregister nodig.

De set- en reset-registers zijn behoorlijk grappig omdat ze elkaar gedeeltelijk dupliceren: je kunt alles alleen in BSRR schrijven, waarbij de hogere 16 bits de pin op nul zetten, en de lagere op 1, of je kunt ook gebruik BRR, waarvan de onderste 16 bits alleen de pin resetten. Ik hou van de tweede optie. Deze registers zijn belangrijk omdat ze atomaire toegang tot pinnen bieden:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Atomaire set of reset
Het is niet nodig om interrupts uit te schakelen bij het programmeren van GPIOx_ODR op bitniveau: een of meer bits kunnen worden gewijzigd met een enkele atomaire schrijfbewerking APB2. Dit wordt bereikt door een "1" te schrijven naar het set/reset-register (GPIOx_BSRR of, alleen voor reset, GPIOx_BRR) van het bit dat moet worden gewijzigd. Andere bits blijven ongewijzigd.

De dataregisters hebben voor zichzelf sprekende namen: IDR = Invoer Richtingsregister, invoerregister; ODR = uitgang Richtingsregister, uitgangsregister. We hebben ze niet nodig in het huidige project.

En tot slot de controleregisters. Omdat we geïnteresseerd zijn in de tweede SPI-pinnen, namelijk PB13, PB14 en PB15, kijken we meteen naar CRH:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

En we zien dat we iets in bits van 20 tot 31 moeten schrijven.

We hebben hierboven al uitgezocht wat we van pinnen willen, dus hier zal ik het doen zonder een screenshot, ik zal alleen zeggen dat MODE de richting specificeert (invoer als beide bits op 0 zijn ingesteld) en pinsnelheid (we hebben 50 MHz nodig, d.w.z. beide pin op “1”), en CNF stelt de modus in: normaal “push-pull” – 00, “alternatief” – 10. Standaard hebben, zoals we hierboven zien, alle pinnen het derde bit van onderen (CNF0), het zet ze in de modus zwevende ingang.

Omdat ik van plan ben iets anders met deze chip te doen, heb ik voor de eenvoud alle mogelijke MODE- en CNF-waarden gedefinieerd voor zowel het onderste als het bovenste besturingsregister.

Op de een of andere manier zoals dit

#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

Onze pinnen bevinden zich op poort B (basisadres – 0x40010C00), code:

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

En dienovereenkomstig kunt u definities voor LAT schrijven, die worden getriggerd door de BRR- en BSRR-registers:

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

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

(LAT_laag alleen door traagheid, het is altijd zo geweest, laat het zo blijven)

Nu is alles geweldig, maar het werkt niet. Omdat dit STM32 is, besparen ze elektriciteit, wat betekent dat u het klokken van de benodigde randapparatuur moet inschakelen.

Schakel klokken in

Het horloge, ook wel Clock genoemd, is verantwoordelijk voor de klokken. En we konden de afkorting RCC al opmerken. We zoeken het in de documentatie: dit is Reset en Clock Control.

Zoals hierboven al werd gezegd, werd het moeilijkste deel van het klokonderwerp gelukkig voor ons gedaan door mensen van STM, waarvoor we hen hartelijk bedanken (ik zal nogmaals een link geven naar website van Di Halt, om duidelijk te maken hoe verwarrend het is). We hebben alleen registers nodig die verantwoordelijk zijn voor het mogelijk maken van perifere klokken (Peripheral Clock Enable Registers). Laten we eerst het basisadres van de RCC vinden, dit staat helemaal aan het begin van de "Memory Map":

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

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

En klik vervolgens op de link waar u iets in de plaat probeert te vinden, of, veel beter, doorloop de beschrijvingen van de activeringsregisters uit de secties over registers inschakelen. Waar we RCC_APB1ENR en RCC_APB2ENR zullen vinden:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

En ze bevatten dienovereenkomstig bits die de klokken van SPI2, IOPB (I/O-poort B) en alternatieve functies (AFIO) omvatten.

#define _APB2ENR 0x18
#define _APB1ENR 0x1C

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

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

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

De definitieve code is te vinden hier.

Als u de mogelijkheid en wens heeft om te testen, sluit de DM634 dan als volgt aan: DAI op PB15, DCK op PB13, LAT op PB14. Wij voeden de driver vanaf 5 volt, vergeet niet de massa aan te sluiten.

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

STM8 PWM

PWM op STM8

Toen ik dit artikel net aan het plannen was, besloot ik bijvoorbeeld een aantal functionaliteiten van een onbekende chip onder de knie te krijgen met alleen een datasheet, zodat ik niet zou eindigen met een schoenmaker zonder laarzen. STM8 was ideaal voor deze rol: ten eerste had ik een paar Chinese borden met STM8S103, en ten tweede is het niet erg populair, en daarom berust de verleiding om op internet te lezen en een oplossing te vinden op het ontbreken van deze oplossingen.

De chip heeft dat ook data papier и referentiehandleiding RM0016, in de eerste zijn er pinout- en registeradressen, in de tweede - al het andere. STM8 is geprogrammeerd in C in een vreselijke IDE ST Visuele ontwikkeling.

Klokken en I/O

Standaard werkt STM8 op een frequentie van 2 MHz, dit moet direct gecorrigeerd worden.

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
HSI (High Speed ​​Interne) Klok
Het HSI-kloksignaal wordt afgeleid van een interne 16 MHz RC-oscillator met een programmeerbare deler (1 tot 8). Het wordt ingesteld in het klokdelerregister (CLK_CKDIVR).
Let op: bij de start wordt een HSI RC-oscillator met een deler van 8 geselecteerd als de leidende bron van het kloksignaal.

We vinden het registeradres in de datasheet, de beschrijving in refman en zien dat het register moet worden gewist:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Omdat we PWM gaan gebruiken en de LED's gaan aansluiten, laten we eens kijken naar de pin-out:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

De chip is klein, veel functies zijn op dezelfde pinnen opgehangen. Wat tussen vierkante haken staat is “alternatieve functionaliteit”, deze wordt geschakeld door “optiebytes” (optiebytes) – zoiets als Atmega-zekeringen. Je kunt hun waarden programmatisch wijzigen, maar dat is niet nodig, omdat De nieuwe functionaliteit wordt pas geactiveerd na een herstart. Het is gemakkelijker om ST Visual Programmer te gebruiken (gedownload met Visual Develop), waarmee deze bytes kunnen worden gewijzigd. De pin-out laat zien dat de CH1- en CH2-pinnen van de eerste timer verborgen zijn tussen vierkante haakjes; het is noodzakelijk om de AFR1- en AFR0-bits in STVP in te stellen, en de tweede zal ook de CH1-uitvoer van de tweede timer overbrengen van PD4 naar PC5.

Dus 6 pinnen zullen de LED's besturen: PC6, PC7 en PC3 voor de eerste timer, PC5, PD3 en PA3 voor de tweede.

Het zelf instellen van de I/O-pinnen op STM8 is eenvoudiger en logischer dan op STM32:

  • bekend van Atmega DDR data direction register (Gegevensrichting registreren): 1 = uitgang;
  • het eerste besturingsregister CR1 stelt bij uitvoer de push-pull-modus (1) of open drain (0) in; aangezien ik de LED's met kathodes op de chip verbind, laat ik hier nullen achter;
  • het tweede stuurregister CR2 stelt bij uitvoer de kloksnelheid in: 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

PWM-instelling

Laten we eerst de termen definiëren:

  • PWM-frequentie – frequentie waarmee de timer tikt;
  • Automatisch herladen, AR – automatisch laadbare waarde tot waar de timer zal tellen (pulsperiode);
  • Update-evenement, UEV – een gebeurtenis die plaatsvindt wanneer de timer tot AR heeft geteld;
  • PWM-werkcyclus – PWM-dutycycle, vaak “duty-factor” genoemd;
  • Waarde vastleggen/vergelijken – waarde voor vastleggen/vergelijking, waarnaar de timer heeft geteld zal iets doen (in het geval van PWM keert het het uitgangssignaal om);
  • Waarde vooraf laden – voorgeladen waarde. Vergelijk waarde kan niet veranderen terwijl de timer loopt, anders wordt de PWM-cyclus verbroken. Daarom worden nieuwe verzonden waarden in een buffer geplaatst en eruit getrokken wanneer de timer het einde van het aftellen bereikt en wordt gereset;
  • Rand-uitgelijnd и Gecentreerde modi – uitlijning langs de grens en in het midden, hetzelfde als die van Atmel Snelle PWM и Fase-correcte PWM.
  • OCIREF, uitgangsvergelijkingsreferentiesignaal – referentie-uitgangssignaal, in feite wat op de overeenkomstige pin in PWM-modus verschijnt.

Zoals al duidelijk blijkt uit de pin-out, hebben twee timers PWM-mogelijkheden: de eerste en de tweede. Beide zijn 16-bits, de eerste heeft veel extra functies (hij kan met name zowel op- als aftellen). We hebben beide nodig om even goed te werken, dus besloot ik te beginnen met de duidelijk armere tweede, om niet per ongeluk iets te gebruiken dat er niet is. Een probleem is dat de beschrijving van de PWM-functionaliteit van alle timers in de naslaggids in het hoofdstuk over de eerste timer staat (17.5.7 PWM-modus), dus je moet de hele tijd heen en weer door het document springen.

PWM op STM8 heeft een belangrijk voordeel ten opzichte van PWM op Atmega:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Grens uitgelijnd PWM
Accountconfiguratie van onder naar boven
Bottom-up tellen is actief als de DIR-bit in het TIM_CR1-register wordt gewist
Voorbeeld
In het voorbeeld wordt de eerste PWM-modus gebruikt. Het PWM-referentiesignaal OCiREF wordt hoog gehouden zolang TIM1_CNT < TIM1_CCRi. Anders is er een laag niveau nodig. Als de vergelijkingswaarde in het TIM1_CCRi-register groter is dan de autoload-waarde (TIM1_ARR-register), wordt het OCiREF-signaal op 1 gehouden. Als de vergelijkingswaarde 0 is, wordt OCiREF op nul gehouden....

STM8-timer tijdens gebeurtenis bijwerken controleert eerst waarde vergelijken, en produceert dan pas een referentiesignaal. De timer van Atmega verprutst eerst en vergelijkt vervolgens, wat resulteert in compare value == 0 de uitvoer is een naald, waarmee op de een of andere manier moet worden omgegaan (bijvoorbeeld door de logica programmatisch om te keren).

Dus wat we willen doen: 8-bit PWM (AR == 255), geteld van onder naar boven, uitlijning langs de rand. Omdat de lampen via kathodes met de chip zijn verbonden, moet de PWM 0 (LED aan) uitvoeren tot waarde vergelijken en 1 erna.

Over een aantal hebben we al gelezen PWM-modus, dus we vinden het vereiste register van de tweede timer door in de referentiehandleiding naar deze zin te zoeken (18.6.8 - TIMx_CCMR1):

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
110: Eerste PWM-modus – bij het tellen van onder naar boven is het eerste kanaal actief terwijl TIMx_CNT < TIMx_CCR1. Anders is het eerste kanaal inactief. [verderop in het document staat een foutieve copy-paste van timer 1] 111: Tweede PWM-modus – bij het tellen van onder naar boven is het eerste kanaal inactief terwijl TIMx_CNT < TIMx_CCR1. Anders is het eerste kanaal actief.

Omdat de LED’s via kathodes met de MK verbonden zijn, komt de tweede modus ons goed uit (de eerste ook, maar dat weten we nog niet).

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Bit 3 OC1PE: Voorbelasting van pin 1 inschakelen
0: Register vooraf laden op TIMx_CCR1 is uitgeschakeld. U kunt op elk gewenst moment naar TIMx_CCR1 schrijven. De nieuwe waarde werkt onmiddellijk.
1: Register vooraf laden op TIMx_CCR1 is ingeschakeld. Lees-/schrijfbewerkingen hebben toegang tot het preloadregister. De vooraf geladen waarde TIMx_CCR1 wordt tijdens elke updategebeurtenis in het schaduwregister geladen.
*Opmerking: Om de PWM-modus correct te laten werken, moeten de voorgeladen registers zijn ingeschakeld. Dit is niet nodig in de enkelvoudige signaalmodus (de OPM-bit wordt ingesteld in het TIMx_CR1-register).

Oké, laten we alles inschakelen wat we nodig hebben voor de drie kanalen van de tweede timer:

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

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

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

AR bestaat uit twee acht-bits registers, alles is eenvoudig:

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

TIM2_ARRH = 0;
TIM2_ARRL = 255;

De tweede timer kan alleen van onder naar boven tellen, uitlijning langs de rand, er hoeft niets te worden veranderd. Laten we de frequentiedeler bijvoorbeeld instellen op 256. Voor de tweede timer wordt de deler ingesteld in het TIM2_PSCR-register en is deze een macht van twee:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Het enige dat overblijft is het inschakelen van de conclusies en de tweede timer zelf. Het eerste probleem wordt opgelost door registers Vastleggen/Vergelijken Enable : er zijn twee, drie kanalen asymmetrisch verspreid. Hier kunnen we ook leren dat het mogelijk is om de polariteit van het signaal te veranderen, d.w.z. in principe was het mogelijk om PWM-modus 1 te gebruiken. We schrijven:

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

En ten slotte starten we de timer in het TIMx_CR1-register:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Laten we een eenvoudige analoog van AnalogWrite() schrijven, die de werkelijke waarden ter vergelijking naar de timer zal overbrengen. De registers zijn voorspelbaar benoemd Registers vastleggen/vergelijken, er zijn er twee voor elk kanaal: de 8 bits van lage orde in TIM2_CCRxL en de 2 bits van hoge orde in TIM8_CCRxH. Omdat we een XNUMX-bit PWM hebben gemaakt, volstaat het om alleen de minst significante bits te schrijven:

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

De oplettende lezer zal merken dat we een enigszins defecte PWM hebben, die niet in staat is om 100% vulling te produceren (bij een maximale waarde van 255 wordt het signaal gedurende één timercyclus omgekeerd). Voor LED's maakt dit niet uit, en de oplettende lezer kan al raden hoe dit te repareren.

PWM op de tweede timer werkt, laten we verder gaan met de eerste.

De eerste timer heeft precies dezelfde bits in dezelfde registers (alleen de bits die in de tweede timer “gereserveerd” bleven, worden in de eerste actief gebruikt voor allerlei geavanceerde dingen). Daarom volstaat het om de adressen van dezelfde registers in het gegevensblad te vinden en de code te kopiëren. Verander de waarde van de frequentiedeler, want... de eerste timer wil geen macht van twee ontvangen, maar een exacte 16-bits waarde in twee registers Prescaler hoog и Laag. We doen alles en... de eerste timer werkt niet. Wat is er aan de hand?

Het probleem kan alleen worden opgelost door het hele gedeelte over de controleregisters van timer 1 door te nemen, waar we zoeken naar het register dat de tweede timer niet heeft. Er zal zijn 17.7.30 Pauzeregister (TIM1_BKR), waar dit stukje staat:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Schakel hoofduitgang in

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Dat is nu allemaal zeker, de code daar.

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

STM8-multiplex

Multiplexen op STM8

Het derde miniproject is om acht RGB-LED's in PWM-modus op de tweede timer aan te sluiten en deze verschillende kleuren te laten weergeven. Het is gebaseerd op het concept van LED-multiplexing, wat inhoudt dat als je LED's heel, heel snel aan en uit zet, het voor ons lijkt alsof ze constant aan zijn (doorzettingsvermogen van visie, traagheid van visuele perceptie). Dat heb ik ooit gedaan zoiets als dit op Arduino.

Het werkalgoritme ziet er als volgt uit:

  • verbond de anode van de eerste RGB-LED;
  • stak het aan en stuurde de nodige signalen naar de kathodes;
  • wachtte tot het einde van de PWM-cyclus;
  • verbond de anode van de tweede RGB-LED;
  • steek het aan...

Nou, enz. Voor een mooie werking is het uiteraard wel nodig dat de anode aangesloten is en tegelijkertijd de LED “ontstoken” wordt. Nou ja, of bijna. In ieder geval moeten we een code schrijven die waarden in drie kanalen van de tweede timer uitvoert, deze wijzigt wanneer UEV wordt bereikt en tegelijkertijd de momenteel actieve RGB-LED verandert.

Omdat LED-schakeling automatisch gebeurt, moeten we een "videogeheugen" creëren waarvan de interrupt-handler gegevens zal ontvangen. Dit is een eenvoudige array:

uint8_t colors[8][3];

Om de kleur van een specifieke LED te veranderen, volstaat het om de vereiste waarden in deze array te schrijven. En de variabele is verantwoordelijk voor het nummer van de actieve LED

uint8_t cnt;

Demux

Voor een goede multiplexing hebben we, vreemd genoeg, een CD74HC238-demultiplexer nodig. Demultiplexer - een chip die de operator in hardware implementeert <<. Via drie invoerpinnen (bits 0, 1 en 2) voeren we het een drie-bits nummer X in, en als reactie hierop activeert het uitvoernummer (1<<X). De overige ingangen van de chip worden gebruikt om het hele ontwerp te schalen. We hebben deze chip niet alleen nodig om het aantal bezette pinnen van de microcontroller te verminderen, maar ook voor de veiligheid - om niet per ongeluk meer LED's in te schakelen dan mogelijk is en de MK niet te verbranden. De chip kost een cent en moet altijd in uw medicijnkastje thuis worden bewaard.

Onze CD74HC238 is verantwoordelijk voor het leveren van spanning aan de anode van de gewenste LED. In een volwaardige multiplex zou het via een P-MOSFET spanning aan de kolom leveren, maar in deze demo is het direct mogelijk, omdat het trekt 20 mA, volgens absolute maximale beoordelingen in het gegevensblad. Van gegevensblad CD74HC238 we hebben pinouts en dit spiekbriefje nodig:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
H = hoogspanningsniveau, L = laagspanningsniveau, X – maakt niet uit

We verbinden E2 en E1 met aarde, E3, A0, A1 en A3 met pinnen PD5, PC3, PC4 en PC5 van STM8. Omdat de bovenstaande tabel zowel lage als hoge niveaus bevat, configureren we deze pinnen als push-pull-pinnen.

PWM

PWM op de tweede timer is op dezelfde manier geconfigureerd als in het vorige verhaal, met twee verschillen:

Eerst moeten we de interrupt inschakelen Evenement bijwerken (UEV) die een functie zal aanroepen die de actieve LED schakelt. Dit gebeurt door het bit te wisselen Update onderbreken inschakelen in een register met een veelzeggende naam

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
Onderbreek het inschakelen van het register

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

Het tweede verschil houdt verband met het fenomeen multiplexing, zoals ghosting – parasitaire gloed van diodes. In ons geval kan dit lijken te wijten aan het feit dat de timer, die een interrupt op de UEV heeft veroorzaakt, blijft tikken en dat de interrupthandler geen tijd heeft om de LED te schakelen voordat de timer iets naar de pinnen begint te schrijven. Om dit tegen te gaan, moet je de logica omkeren (0 = maximale helderheid, 255 = er brandt niets) en extreme duty-cycle-waarden vermijden. Die. zorg ervoor dat na UEV de LED's gedurende één PWM-cyclus volledig uitgaan.

Veranderende polariteit:

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

Vermijd het instellen van r, g en b op 255 en vergeet niet om ze om te keren wanneer u ze gebruikt.

Onderbreekt

De essentie van een interrupt is dat de chip onder bepaalde omstandigheden stopt met het uitvoeren van het hoofdprogramma en een externe functie aanroept. Onderbrekingen ontstaan ​​door externe of interne invloeden, waaronder de timer.

Toen we voor het eerst een project in ST Visual Develop maakten, naast main.c we ontvingen een venster met een mysterieus bestand stm8_interrupt_vector.c, automatisch opgenomen in het project. In dit bestand wordt aan elke interrupt een functie toegewezen NonHandledInterrupt. We moeten onze functie aan de gewenste interrupt binden.

De datasheet bevat een tabel met interruptvectoren, waarin we de vectoren vinden die we nodig hebben:

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8
13 TIM2-update/overloop
14 TIM2 vastleggen/vergelijken

We moeten de LED bij UEV veranderen, dus we hebben interrupt #13 nodig.

Dienovereenkomstig, in de eerste plaats in het dossier stm8_interrupt_vector.c verander de standaardnaam van de functie die verantwoordelijk is voor interrupt nr. 13 (IRQ13) in uw eigen naam:

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

Ten tweede zullen we een bestand moeten maken main.h met de volgende inhoud:

#ifndef __MAIN_H
#define __MAIN_H

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

En tenslotte schrijf je deze functie in je 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;
}

Het enige dat overblijft is het inschakelen van interrupts. Dit gebeurt met behulp van het assembler-commando rim - je zult het moeten zoeken in Programmeerhandleiding:

//enable interrupts
_asm("rim");

Een ander assembler-commando is sim – schakelt interrupts uit. Ze moeten worden uitgeschakeld terwijl nieuwe waarden naar het “videogeheugen” worden geschreven, zodat een interrupt die op het verkeerde moment wordt veroorzaakt de array niet bederft.

Alle code - op GitHub.

Datasheets 2 lezen: SPI op STM32; PWM, timers en interrupts op STM8

Als tenminste iemand dit artikel nuttig vindt, dan heb ik het niet voor niets geschreven. Ik ontvang graag opmerkingen en opmerkingen, ik zal proberen alles te beantwoorden.

Bron: www.habr.com

Voeg een reactie