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

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

In het eerste deel probeerde ik hobby-elektronici, die zijn opgegroeid uit de Arduino-broekjes, te vertellen hoe en waarom ze datasheets en andere documentatie van microcontrollers moeten lezen. De tekst is vrij uitgebreid, daarom beloofde ik praktische voorbeelden in een apart artikel te laten zien. Nou, ik heb me geuit als een champignon...

Vandaag zal ik laten zien hoe we met datasheets relatief eenvoudige, maar noodzakelijke taken voor veel projecten kunnen oplossen op de STM32 (Blue Pill) en STM8 controllers. Alle demo-projecten zijn gewijd aan mijn favoriete LEDs, die we in grote hoeveelheden zullen aansteken, waarvoor we interessante randapparatuur zullen moeten gebruiken.

De tekst is weer enorm geworden, daarom maak ik voor de duidelijkheid een inhoudsopgave:

STM32 Blue Pill: 16 LEDs met DM634-driver
STM8: Zes PWM-pinnen instellen
STM8: 8 RGB-LEDs op drie pinnen, interrupts

Disclaimer: ik ben geen ingenieur, en ik pretendeer niet diepgaande kennis van elektronica te hebben; dit artikel is bedoeld voor andere amateurs zoals ik. Eigenlijk beschouwde ik mezelf als de doelgroep van twee jaar geleden. Als iemand me toen had verteld dat het niet eng is om datasheets van een onbekende chip te lezen, had ik veel tijd bespaard met het zoeken van stukjes code op internet en het uitvinden van oplossingen met scharen en pleisters.

In het midden van dit artikel staan datasheets, en niet projecten, dus de code kan niet te netjes zijn en vaak een tijdelijke oplossing bevatten. De projecten zelf zijn heel eenvoudig, hoewel ze nuttig zijn voor een eerste kennismaking met een nieuwe chip.

Ik hoop dat mijn artikel iemand helpt in een vergelijkbare fase van het hobby-onderdompelen.

STM32

16 LEDs met DM634 en SPI

Een klein project met de Blue Pill (STM32F103C8T6) en de LED-driver DM634. We gebruiken datasheets om de driver, de IO-poorten van de STM en de SPI in te stellen.

DM634

Taiwanesische chip met 16 16-bits PWM-uitgangen, kan in ketens worden verbonden. Het lagere 12-bits model is bekend uit een lokaal project Lightpack. Tijdens mijn keuze tussen de DM63x en de goed bekende TLC5940, heb ik om enkele redenen voor de DM gekozen: 1) de TLC op AliExpress is zeker nep, maar deze niet; 2) de DM heeft een autonome PWM met een eigen frequentiegenerator; 3) ik kon het goedkoop kopen in Moskou, in plaats van te wachten op een pakketje van Ali. En natuurlijk was het interessant om zelf te leren hoe je met de chip omgaat en niet alleen een kant-en-klare bibliotheek te gebruiken. Chips zijn tegenwoordig voornamelijk beschikbaar in SSOP24 behuizing, die zijn niet moeilijk te solderen op een adapter.

Aangezien de fabrikant Taiwanees is, datasheet is in Chinees-Engels geschreven, dus dat zal wel leuk worden. Laten we eerst naar de pinout kijken (Pinverbinding), om te begrijpen welke pin wat verbindt, en de beschrijving van de pinnen (Pinbeschrijving). 16 pinnen:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Voedingsbronnen van constante stroom (open drain)

Sink / Open-drain output – drain; stroombron; een output die in actieve toestand op aarde is aangesloten – de LED's worden met de kathodes op de driver aangesloten. Elektrisch is dit natuurlijk geen 'open drain' (open drain), maar in datasheets komt deze aanduiding voor pinnen in drain-modus vaak voor.

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Externe weerstanden tussen REXT en GND voor het instellen van de uitgangenstroom

Tussen pin REXT en aarde wordt een referentieweerstand geplaatst, die de interne weerstand van de uitgangen controleert, zie de grafiek op pagina 9 van de datasheet. In de DM634 kan deze weerstand ook programmatisch worden beheerd, door de globale helderheid in te stellen (globale helderheid); in dit artikel zal ik niet in detail treden, ik zal hier gewoon een weerstand van 2,2 - 3 kOhm plaatsen.

Om te begrijpen hoe je de chip aanstuurt, laten we kijken naar de beschrijving van de interface van het apparaat:

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

Ah, daar hebben we het, het Chinese Engels in al zijn glorie. Dit vertalen is problematisch, je kunt het begrijpen als je wilt, maar er is een andere manier – kijken hoe de aansluiting in de datasheet van de functioneel vergelijkbare TLC5940 wordt beschreven:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
… Voor het invoeren van gegevens in het apparaat zijn slechts drie pinnen nodig. De stijgende rand van het SCLK-signaal schuift de gegevens van de SIN-pin naar het interne register. Nadat alle gegevens zijn geladen, fixeert een korte hoge XLAT-signaal de sequentieel verzonden gegevens in de interne registers. De interne registers zijn kleppen die triggeren op de niveau van het XLAT-signaal. Alle gegevens worden met de meest significante bit eerst verzonden.

Latch – klep / vergrendeling / fixator.
Stijgende rand – stijgende rand van de puls
MSB eerst – met de meest significante (linker) bit naar voren.
om de klokgegevens – gegevens bit voor bit over te dragen.

Het woord sluiting komt vaak voor in documentatie over chips en wordt op verschillende manieren vertaald; daarom laat ik me toe om

een korte uitleg te geveneen LED-driver is in wezen een verschuivregister. ‘Verschuiven’ (verschuiven) in de naam houdt in dat gegevens bit voor bit binnen het apparaat worden verplaatst: elk nieuwe bit die erin wordt gestopt, duwt de hele keten vooruit. Omdat niemand tijdens het verschuiven een chaotisch knipperen van LED's wil zien, proces vindt plaats in bufferregisters, gescheiden van actieve door een klep (sluiting) – dit is een soort wachtruimte waar bits in de juiste volgorde worden uitgelijnd. Wanneer alles klaar is, gaat de klep open en worden de bits in werking gesteld, ter vervanging van de vorige groep. Het woord sluiting in de documentatie voor microchips impliceert bijna altijd zo'n klep, ongeacht in welke combinaties het wordt gebruikt.

Dus, de gegevensoverdracht in de DM634 gebeurt als volgt: stel de ingang DAI in op de waarde van het meest significante bit van de verre LED, trek DCK omhoog en omlaag; stel de ingang DAI in op de waarde van het volgende bit, trek DCK; en zo verder, totdat alle bits zijn overgedragen (verwerkt), waarna je LAT trekt. Dit kan handmatig worden gedaan (bit-bang), maar het is beter om gebruik te maken van een speciaal voor dit doel ontworpen SPI-interface, gelukkig is hij in twee exemplaren beschikbaar op onze STM32.

De blauwe tablet STM32F103

Inleiding: STM32-controllers zijn aanzienlijk complexer dan Atmega328, wat ontmoedigend kan lijken. Tegelijkertijd is bijna alle perifere hardware tijdens de opstart uitgeschakeld ter energiezuinigheid en de kloksnelheid bedraagt 8 MHz uit een interne bron. Gelukkig hebben de STM-programmeurs code geschreven die de chip naar 'calculerende' 72 MHz brengt, en de makers van alle bekende IDE's hebben dit opgenomen in de initialisatieprocedure, dus hoeven we geen klokfrequentie in te stellen (maar het kan, als je dat echt wilt). Maar je moet de perifere hardware wel inschakelen.

Documentatie: op de Blue Pill is een populaire chip STM32F103C8T6 geïnstalleerd, en er zijn twee nuttige documenten voor beschikbaar:

In het datasheet kunnen we mogelijk geïnteresseerd zijn in:

  • Pinouts – pinindelingen van de chips – voor het geval we besluiten om zelf platen te maken;
  • Geheugenkaart – een geheugenkaart voor een specifieke chip. In de Referentiegids is er een kaart voor de hele serie, waarop registers staan die niet in de onze zijn vermeld.
  • Tabel Pin Definitions – een opsomming van de belangrijkste en alternatieve functies van de pinnen; online vind je handigere afbeeldingen met een lijst van pinnen en hun functies voor de "blauwe pil". Dus laten we onmiddellijk "Blue Pill pinout" googelen en zo'n afbeelding bij de hand houden:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
NB: de afbeelding van het internet had een fout, opgemerkt in de reacties, waarvoor dank. De afbeelding is vervangen, maar dit is een les — informatie niet uit datasheets beter te controleren.

We verwijderen de datasheet, openen de Referentiegids, en gebruiken vanaf nu alleen deze.
Stappenplan: we behandelen standaard invoer/uitvoer, configureren SPI, en schakelen de vereiste periferie in.

Invoer-uitvoer

Op de Atmega328 is invoer-uitvoer heel eenvoudig gerealiseerd, waardoor de overvloed aan opties bij STM32 verwarrend kan zijn. Voor nu hebben we alleen de uitvoer nodig, maar zelfs daarvan zijn er vier varianten:

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

Push-pull (push-pull) – de gebruikelijke uitvoer van Arduino, de pin kan ofwel een waarde HIGH of LOW aannemen. Maar bij "open collector" ontstaan er problemen, hoewel het eigenlijk heel eenvoudig is:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
De configuratie van de uitvoer / wanneer de poort toegewezen is aan uitvoer: / output buffer is ingeschakeld: / – open collector modus: "0" in het uitvoerschrijfreferent regelt N-MOS, "1" in het uitvoerschrijfreferent laat de poort in Hi-Z modus (P-MOS wordt niet ingeschakeld) / – push-pull modus: "0" in het uitvoerschrijfreferent activeert N-MOS, "1" in het uitvoerschrijfreferent activeert P-MOS.

Het enige verschil tussen open collector (open drain) en push-pull (push-pull) is dat in de eerste de pin geen HIGH-toestand kan aannemen: bij het schrijven van een een in het uitvoerschrijfreferent gaat hij naar de hoge impedantie modus (high impedance, Hi-Z). Bij het schrijven van een nul gedraagt de pin zich in beide modi hetzelfde, zowel logische als elektrische.

In de normale uitvoermodus vertaalt de pin eenvoudig de inhoud van het uitvoerschrijfreferent. In de "alternatieve" modus wordt deze aangestuurd door de bijbehorende periferie (zie 9.1.4):

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Als de poortbit is geconfigureerd als uitvoer van een alternatieve functie, wordt het uitvoerschrijfreferent uitgeschakeld en wordt de pin aangesloten op het uitvoersignaal van de periferie.

De alternatieve functionaliteit van elke pin is beschreven in Pin Definitions De datasheet is zichtbaar op de gedownloade afbeelding. Op de vraag wat te doen als een pin meerdere alternatieve functies heeft, geeft een voetnoot in de datasheet antwoord:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Als meerdere perifere blokken dezelfde pin gebruiken, moet om conflicten tussen alternatieve functies te vermijden, tegelijkertijd slechts één perifere blok gebruikt worden, door over te schakelen met de activatiebit voor de perifere klok (in het betreffende RCC-register).

Ten slotte hebben pinen in outputmodus ook nog een klokfrequentie. Dit is weer een energiebesparingsfunctie, in ons geval zetten we hem gewoon op maximaal en vergeten we het.

Dus: we gebruiken SPI, wat betekent dat twee pinen (voor gegevens en de klok) de 'alternatieve functie pull-push' moeten zijn en nog een (LAT) – 'gewone pull-push'. Maar voordat we ze toewijzen, laten we ons eerst met SPI bezig houden.

SPI

Nog een kleine les

SPI of Serial Peripheral Interface (serieel perifeer interfase) – een eenvoudige en zeer effectieve interfase voor de communicatie tussen microcontrollers en de externe wereld. Het principe van de werking is hierboven al beschreven, waar het gaat over de Chinese LED-driver (zie sectie 25 in de referentiehandleiding). SPI kan werken in master ('baas') en slave ('dienaar') modus. SPI heeft vier basiskanalen, waarvan er niet allemaal gebruikt hoeven te worden:

  • MOSI, Master Output / Slave Input: deze pin geeft in de mastermodus uit, en ontvangt gegevens in de slavemodus;
  • MISO, Master Input / Slave Output: omgekeerd, ontvangt in de mastermodus, geeft uit in de slavemodus;
  • SCK, Serial Clock: bepaalt de gegevensoverdrachtsfrequentie in de master of ontvangt de kloksignaal in de slave. In wezen timet het de bits;
  • SS, Slave Select: met behulp van deze kanaal weet de slave dat er iets van hem verwacht wordt. Op de STM32 wordt dit NSS genoemd, waarbij N = negatief, d.w.z. de controller wordt een slave als deze op dit kanaal is aangesloten op de aarde. Goed te combineren met de Open Drain Output-modus, maar dat is een ander verhaal.

Net als alles wat we hebben besproken, is SPI op de STM32 rijk aan functionaliteit, wat het begrip ervan enigszins bemoeilijkt. Bijvoorbeeld, het kan niet alleen als SPI werken, maar ook als I2S-interface, en in de documentatie staan deze beschrijvingen door elkaar, wat tijdig moet worden gescheiden. Onze taak is echter uiterst eenvoudig: we hoeven alleen gegevens te verzenden, met alleen MOSI en SCK in gebruik. We gaan naar sectie 25.3.4 (half-duplex communicatie), waar we vinden 1 klok en 1 unidirectionele gegevenslijn (1 kloksignaal en 1 eenrichtingsgegevensstroom):

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
In deze modus gebruikt de applicatie SPI, hetzij in een verzend- of ontvangmodus. / De verzendmodus lijkt op de duplexmodus: gegevens worden verzonden via de zendpin (MOSI in de mastermodus of MISO in de slavemodus), terwijl de ontvangpin (MISO of MOSI respectievelijk) kan worden gebruikt als een gewone in-uit pin. In dit geval hoeft de applicatie de Rx-buffer alleen te negeren (als je deze leest, zijn er geen verzonden gegevens).

Geweldig, de MISO-pin is nu vrij, we zullen het signaal LAT daarop aansluiten. Laten we de Slave Select bekijken, die op de STM32 programmatisch kan worden bestuurd, wat ongelooflijk handig is. Laten we de bijbehorende alinea in sectie 25.3.1 SPI Algemene Beschrijving lezen:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Programmatische controle van NSS (SSM = 1) / Informatie over het selecteren van de slaaf bevindt zich in de SSI-bit van het SPI_CR1-register. De externe NSS-pin blijft vrij voor andere behoeften van de applicatie.

Het is tijd om naar de registers te schrijven. Ik heb besloten SPI2 te gebruiken, laten we in de datasheet naar het basisadres zoeken – in sectie 3.3 Geheugenkaart:

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

Dus laten we beginnen:

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

We openen sectie 25.3.3 met de veelzeggende titel 'Instelling van SPI in mastermodus':

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

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

De registers zijn verzameld in de overeenkomstige sectie van de reference manual. Het adresoffset (Adresoffset) bij CR1 – 0x00, standaard zijn alle bits gereset (Resetwaarde 0x0000):

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

De bits BR stellen de deler van de klokfrequentie van de controller in, waardoor de frequentie wordt bepaald waarop SPI zal werken. De frequentie van de STM32 is 72 MHz, de LED-driver, volgens zijn datasheet, werkt met een frequentie tot 25 MHz, dus we moeten door vier delen (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 bits CPOL en CPHA in om de verhoudingen tussen gegevensoverdracht en kloksignalen van de seriële interface te bepalen (zie het schema op pagina 240)

Aangezien we hier de datasheet lezen en geen schema's bekijken, laten we het tekstuele beschrijving van de bits CPOL en CPHA op pagina 704 (SPI Algemene Beschrijving) bestuderen:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Fase en polariteit van het kloksignaal
Met de bits CPOL en CPHA van het SPI_CR1-register kan programmatisch één van de vier timingverhoudingen worden gekozen. De bit CPOL (polariteit van het kloksignaal) beheert de status van het kloksignaal wanneer gegevens niet worden verzonden. Dit bit bestuurt de master- en slavemodi. Als CPOL is gereset, bevindt de pin SCK zich in rustniveau op een laag niveau. Als de bit CPOL is ingesteld, bevindt de pin SCK zich in rustniveau op een hoog niveau.
Als de bit CPHA (clock phase) is ingesteld, fungeert de tweede flank van het SCK-signaal als het latch-signaal voor de meest significante bit (aflopend, als CPOL is gereset, of oplopend, als CPOL is ingesteld). Gegevens worden vastgelegd bij de tweede verandering van het kloksignaal. Als de bit CPHA is gereset, fungeert de voorste flank van het SCK-signaal als het latch-signaal voor de meest significante bit (aflopend, als CPOL is ingesteld, of oplopend, als CPOL is gereset). Gegevens worden vastgelegd bij de eerste verandering van het kloksignaal.

Door ons in deze kennis te verdiepen, komen we tot de conclusie dat beide bits op nul moeten blijven, omdat we willen dat het SCK-signaal laag blijft wanneer het niet wordt gebruikt, en de gegevens moeten worden verzonden bij de voorste flank van de puls (zie Rising Edge in de datasheet DM634).

Overigens komen we hier voor het eerst een lexicale eigenaardigheid tegen in ST-datasheets: in deze wordt de zin "reset een bit naar nul" geschreven to reset a bit, en niet to clear a bit, zoals bijvoorbeeld bij de Atmega.

3. Stel de DFF-bit in voor de bepaling van het 8-bits of 16-bits dataformaat.

Ik heb speciaal gekozen voor de 16-bits DM634, zodat ik me geen zorgen hoef te maken over het verzenden van 12-bits PWM-gegevens, zoals bij de DM633. De DFF moet ingesteld worden op één:

#define DFF         0x0800

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

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

LSBFIRST, zoals de naam al aangeeft, configureert de overdracht van de minst significante bit eerst. Maar de DM634 wil gegevens ontvangen, beginnend met de meest significante bit. Daarom laten we het gereset.

5. In de hardwaremodus, als invoer vanaf de NSS-pin vereist is, dien je tijdens de gehele byteoverdrachtsreeks een hoog signaal op de NSS-pin aan te leveren. In de programmamodus stel je de bits SSM en SSI in het SPI_CR1-register in. Als de NSS-pin als uitgang moet functioneren, moet je alleen de SSOE-bit instellen.

Stel SSM en SSI in om de hardwaremodus voor NSS te vergeten:

#define SSI         0x0100
#define SSM         0x0200

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

6. De bits MSTR en SPE moeten worden ingesteld (ze blijven ingesteld zolang er een hoog signaal op NSS wordt aangelegd).

Met deze bits wijzen we onze SPI als master aan en schakelen we deze in:

#define MSTR        0x0004
#define SPE         0x0040

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

SPI is ingesteld, laten we meteen functies schrijven die bytes naar de driver sturen. Laten we verder lezen in 25.3.3 "SPI configureren in de mastermodus":

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Gegevensoverdrachtsorder
De overdracht begint wanneer een byte in de Tx-bufferschrijf.
De gegevensbyte wordt geladen in het verschuifregister in parallele modus (van de interne bus) tijdens de overdracht van de eerste bit, waarna deze wordt verzonden in seriële in MOSI-modus, met het eerste of laatste bit vooruit, afhankelijk van de instelling van de LSBFIRST-bit in het register CPI_CR1. De TXE-vlag wordt ingesteld na het verzenden van gegevens van de Tx-buffer naar het shifregister, en er wordt ook een onderbreking gecreëerd als de TXEIE-bit is ingesteld in het register CPI_CR1.

Ik heb enkele woorden in de vertaling gemarkeerd om een specifiek kenmerk van de SPI-implementatie in STM-controllers te benadrukken. Op de Atmega wordt de TXE-vlag (Tx Empty, Tx is leeg en klaar om gegevens te ontvangen) pas ingesteld nadat het hele byte is verzonden naar buiten. Hier wordt deze vlag ingesteld nadat het byte in het interne shifregister is geplaatst. Aangezien het daar met alle bits tegelijk (parallel) wordt geplaatst en de gegevens vervolgens sequentieel worden verzonden, wordt TXE ingesteld voordat het byte volledig is verzonden. Dit is belangrijk, omdat we in het geval van onze LED-driver de LAT-pin moeten aansteken na het verzenden van alle gegevens, dwz alleen de TXE-vlag is niet voldoende.

En dat betekent dat we nog een vlag nodig hebben. Laten we kijken in 25.3.7 - "Statusvlaggen":

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
<…>
Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
De BUSY-vlag
De BSY-vlag wordt hardwarematig ingesteld en gereset (schrijven naar deze vlag heeft geen effect). De BSY-vlag toont de status van de SPI-communicatielaag.
Hij wordt gereset:
wanneer de transmissie is voltooid (behalve in mastermodus, als de transmissie continu is)
wanneer SPI is uitgeschakeld
wanneer er een fout optreedt in de mastermodus (MODF=1)
Als de transmissie niet continu is, wordt de BSY-vlag gereset tussen elke gegevensoverdracht

Oké, dat is handig. We ontdekken waar de Tx-buffer zich bevindt. Hiervoor lezen we "SPI Gegevensregister":

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Bits 15:0 DR[15:0] Gegevensregister
Ontvangen gegevens of gegevens voor verzending.
Het gegevensregister is verdeeld in twee buffers - één voor schrijven (verzendbuffer) en één voor lezen (ontvangbuffer). Schrijven naar het gegevensregister schrijft naar de Tx-buffer, terwijl lezen uit het gegevensregister de waarde retourneert die in de Rx-buffer is opgeslagen.

En dan is er het statusregister, waar de TXE en BSY-vlaggen te vinden zijn:

Datasheets lezen 2: 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
}

Aangezien we 16 keer twee bytes moeten verzenden, rekening houdend met het aantal uitgangen van de LED-driver, ziet het er zo uit:

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

    while (_SPI2_(_SPI_SR) & BSY); // voltooi transmissie

    LAT_pulse();
}

Maar we weten nog niet hoe we de LAT-pin moeten aansteken, dus laten we teruggaan naar I/O.

We wijzen de pinnen toe

De registers van de STM32F1 die verantwoordelijk zijn voor de status van de pinnen zijn nogal ongebruikelijk. Het is duidelijk dat er meer zijn dan bij de ATmega, maar ze verschillen ook van andere STM-chips. Sectie 9.1 Algemene beschrijving van GPIO:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Elk van de algemene in-/uitvoerpoorten (GPIO) heeft twee 32-bits configuratieregisters (GPIOx_CRL en GPIOx_CRH), twee 32-bits dataregisters (GPIOx_IDR en GPIOx_ODR), een 32-bits set/reset register (GPIOx_BSRR), een 16-bits resetregister (GPIOx_BRR) en een 32-bits vergrendelingsregister (GPIOx_LCKR).

Ze zijn ongebruikelijk en ook vrij onhandig, vooral de eerste twee registers, omdat de 16 pinnen van de poort verspreid zijn in een formaat 'vier bits per stuk'. Dat wil zeggen, de pinnen van nul tot zeven bevinden zich in CRL, terwijl de overige pinnen in CRH zitten. Tegelijkertijd kunnen de andere registers de bits van alle pinnen van de poort succesfully bevatten, waarbij ze vaak half 'gereserveerd' blijven.

Voor de eenvoud beginnen we aan de onderkant van de lijst.

Het vergrendelingsregister hebben we niet nodig.

De set- en resetregisters zijn gedeeltelijk grappig omdat ze elkaar gedeeltelijk dupliceren: je kunt alles alleen in BSRR schrijven, waar de bovenste 16 bits de pin naar nul resetten, terwijl de onderste deze op 1 zetten, of ook BRR gebruiken, waarvan de onderste 16 bits alleen de pin resetten. Ik geef de voorkeur aan de tweede optie. Deze registers zijn belangrijk omdat ze atomische toegang tot de pinnen bieden:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Atomische set of reset
Je hoeft geen interrupties uit te schakelen bij het programmeren van GPIOx_ODR op bitniveau: je kunt een of meerdere bits in één atomische schrijfbewerking op APB2 wijzigen. Dit wordt bereikt door '1' in het set/reset register (GPIOx_BSRR of, alleen voor reset, in GPIOx_BRR) te schrijven voor de bit die je wilt wijzigen. De andere bits blijven onveranderd.

De dataregisters hebben vrij duidelijke namen – IDR = Invoer Richtingregister, invoerregister; ODR = Uitvoer Richtingregister, uitvoerregister. In dit project hebben we ze niet nodig.

En tot slot, de controle registers. Aangezien we geïnteresseerd zijn in de pinnen van de tweede SPI, namelijk PB13, PB14 en PB15, kijken we meteen naar CRH:

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

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

We have already discussed what we want from the pins, so I will skip the screenshot here and just say that MODE sets the direction (input if both bits are set to 0) and the speed of the pin (we need 50MHz, i.e., both pins to '1'), while CNF defines the mode: standard 'push-pull' – 00, 'alternating' – 10. By default, as we see above, every pin has the third bit from the bottom (CNF0) set, which places them in mode floating input.

Since I plan to do something else with this chip, I have defined all possible values for MODE and CNF for both the lower and upper control registers for simplicity.

So that’s how it is

#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

Our pins are located on port B (base address – 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;

And accordingly, we can write defines for LAT, which will toggle the BRR and 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_low just by inertia, it has always been like that, let it stay)

Now everything is great, but it doesn't work. Because this is STM32, power-saving is a priority, so we need to enable the relevant peripheral clock.

Enabling the clock

The clock is responsible for timing, known as the Clock. And we might have already noticed the abbreviation RCC. Let's look it up in the documentation: it stands for Reset and Clock Control.

As mentioned above, fortunately, the most complex aspects of clocking have been handled by the people at STM, for which we thank them (I’ll provide the link to Di Halt's site, to understand how complicated this is). We only need the registers that enable the peripheral clock (Peripheral Clock Enable Registers). First, we need to find the base address of RCC, which is at the very beginning of the 'Memory Map':

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

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

Then either click the link to try to find something in the table, or, much better, run through the descriptions of the enabling registers from the sections about enable registers. Where we will find RCC_APB1ENR and RCC_APB2ENR:

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

And in them, accordingly, the bits enabling the clock for SPI2, IOPB (I/O Port B), and alternate functions (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;

The final code can be found here.

If there’s an opportunity and a desire to test, then connect the DM634 like this: DAI to PB15, DCK to PB13, LAT to PB14. Power the driver with 5 volts, and don't forget to connect the grounds.

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

STM8 PWM

PWM on STM8

Toen ik deze artikel aan het plannen was, besloot ik een onbekende chip te verkennen aan de hand van alleen de datasheet, zodat ik niet als een schoenmaker zonder schoenen eindigde. De STM8 was hiervoor perfect: ten eerste had ik een paar Chinese borden met STM8S103, en ten tweede is hij niet zo populair, waardoor de verleiding om te zoeken en een oplossing op internet te vinden op zijn gebrek aan oplossingen stuitte.

Bij de chip is er ook een datasheet en referentiedocument RM0016, het eerste document bevat de pinout en registeradressen, het tweede – al het andere. De STM8 wordt geprogrammeerd in C in een eenvoudige IDE ST Visual Develop.

Klokken en invoer/uitvoer

Standaard werkt de STM8 op een frequentie van 2 MHz, dit moet meteen worden aangepast.

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
De HSI klok (high-speed internal)
De HSI klok komt van de interne 16 MHz RC-generator met een programmeerbare divider (van 1 tot 8). Deze wordt ingesteld in het klokdeelregister (CLK_CKDIVR).
Opmerking: bij de opstart wordt de HSI RC-generator met een divider van 8 als hoofdklokbron gekozen.

We vinden het adres van het register in de datasheet, de beschrijving in het referentiedocument en zien dat het register gewist moet worden:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Aangezien we PWM gaan starten en LED's aansluiten, bekijken we de pinout:

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

De chip is klein, veel functies zijn verbonden aan dezelfde pinnen. Wat tussen vierkante haken staat, is 'alternatieve functie', deze worden geschakeld met 'optiebits' (option bytes) – iets soortgelijks als de fuzes van de Atmega. Hun waarden kunnen programmatisch worden gewijzigd, maar dat is niet nodig, omdat de nieuwe functionaliteit alleen na een herstart wordt geactiveerd. Het is makkelijker om de ST Visual Programmer te gebruiken (die wordt gedownload met Visual Develop), deze kan deze bytes veranderen. In de pinout is te zien dat de uitgangen CH1 en CH2 van de eerste timer tussen vierkante haken verborgen zijn; we moeten in STVP de bits AFR1 en AFR0 instellen, waarbij de tweede ook de uitgang CH1 van de tweede timer van PD4 naar PC5 verplaatst.

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

Het instellen van de input/output pinnen op de STM8 is eenvoudiger en logischer dan op de STM32:

  • de bekende datarichtingregister DDR (Data Direction Register): 1 = uitgang;
  • de eerste controle register CR1 bij de uitgang stelt de modus 'push-pull' in (1) of open collector (0); omdat ik de LED's met de kathodes op de chip aansluit, laat ik hier nullen staan;
  • de tweede controle register CR2 bij de uitgang stelt de klokfrequentie 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 beginnen met het definiëren van enkele termen:

  • PWM-frequentie is de frequentie waarmee de timer tikt;
  • Automatisch herladen, AR is de automatisch te laden waarde tot waar de timer zal tellen (pulsperiode);
  • Update-evenement, UEV is een gebeurtenis die zich voordoet wanneer de timer tot AR heeft geteld;
  • PWM-duty cycle is de verhouding van de tijd dat het signaal hoog is, vaak aangeduid als ‘duty ratio’;
  • Vang-/vergelijkingswaarde is de waarde voor het vangen/vergelijkingen, waarvoor de timer is geteld zal iets uitvoeren (in het geval van PWM – zal het uitgangssignaal inverteren);
  • Voorgeladen waarde is de voorgeladen waarde. Vergelijkingswaarde mag niet veranderen terwijl de timer tikt, anders zal de PWM-cyclus breken. Daarom worden nieuwe waarden in een buffer geplaatst en daaruit gehaald wanneer de timer het einde van de telling bereikt en reset;
  • Randuitgelijnde en Centrisch uitgelijnde modi is de uitlijning aan de rand en in het midden, wat dezelfde is als de Atmega-modi. Snelle PWM en Fase-correcte PWM.
  • OCiREF, Output Compare Reference-signal is het referentiesignaal dat wordt uitgegeven en dat in de PWM-modus op de bijbehorende pin verschijnt.

Zoals al duidelijk is uit de pinouts, hebben twee timers PWM-capaciteiten – de eerste en tweede. Beide zijn 16-bits, de eerste heeft veel extra functies (onder andere kan hij zowel oplopend als aflopend tellen). We willen dat beide gelijk functioneren, dus besloot ik te beginnen met de opzettelijk eenvoudigere tweede, om niet per ongeluk iets te gebruiken dat daar niet in zit. Een probleem is dat de beschrijving van de PWM-functionaliteit van alle timers in de handleiding te vinden is in het hoofdstuk over de eerste timer (17.5.7 PWM-modus), wat betekent dat ik steeds heen en weer moet springen in het document.

PWM op de STM8 heeft een belangrijk voordeel boven de Atmega PWM:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
PWM met randuitlijning
Configuratie van onder naar boven tellen
De onder naar boven tellen is actief als de bit DIR in het register TIM_CR1 is gereset.
Voorbeeld
Het voorbeeld gebruikt de eerste PWM-modus. Het referentiesignaal PWM OCiREF blijft hoog totdat TIM1_CNT < TIM1_CCRi. Anders wordt het laag. Als de vergelijkingswaarde in het register TIM1_CCRi hoger is dan de automatisch te laden waarde (register TIM1_ARR), blijft het signaal OCiREF op 1. Als de vergelijkingswaarde gelijk is aan 0, blijft OCiREF op nul.…

De STM8-timer controleert tijdens het update-evenement eerst de vergelijkingswaarde, en geeft dan het referentiesignaal. Bij de Atmega-timer wordt er eerst geteld en dan vergeleken, wat ertoe leidt dat bij vergelijkingswaarde == 0 Aan de uitgang komt een naald uit, waarmee we op een of andere manier moeten omgaan (bijvoorbeeld door de logica programmatisch om te keren).

Dus, wat willen we doen: 8-bits PWM (AR == 255), tellen van onder naar boven, uitlijning op de grens. Aangezien de lampjes aan de chip zijn verbonden met kathodes, moet de PWM 0 (LED brandt) tot vergelijkingswaarde en 1 daarna geven.

We hebben al gelezen over bepaalde PWM-modus, dus we zoeken het juiste register van de tweede timer door in de referentiegids naar deze zin te zoeken (18.6.8 - TIMx_CCMR1):

Datasheets lezen 2: 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 zolang TIMx_CNT < TIMx_CCR1. Anders is het eerste kanaal niet actief. [verderop in het document is er een foutieve kopieer-/plakactie uit timer 1]
111: Tweede PWM-modus - bij het tellen van onder naar boven is het eerste kanaal niet actief zolang TIMx_CNT < TIMx_CCR1. Anders is het eerste kanaal actief.

Aangezien de LED's aan de MK zijn verbonden met kathodes, is de tweede modus geschikt voor ons (de eerste ook, maar dat weten we nog niet).

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
Bit 3 OC1PE: Schakel de voorlading van uitvoer 1 in.
0: Voorverwerkingsregister op TIMx_CCR1 is uitgeschakeld. Schrijven naar TIMx_CCR1 kan op elk moment. De nieuwe waarde werkt onmiddellijk.
1: Voorverwerkingsregister op TIMx_CCR1 is ingeschakeld. Lezen/schrijfbewerkingen zijn gericht op het voorverwerkingsregister. De voorverwerkte waarde TIMx_CCR1 wordt tijdens elk update-evenement in het schaduwregister geladen.
*Opmerking: voor de juiste werking van de PWM-modus moeten de voorverwerkingsregisters zijn ingeschakeld. Dit is niet vereist in de enkele signaalmodus (de bit OPM is ingesteld in het register TIMx_CR1).

Oké, we schakelen alles in wat nodig is voor 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 achtbitregistres, hier is het vrij 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 op de grens, verder hoeft er niets veranderd te worden. We stellen de frequentiedeler in, bijvoorbeeld op 256. De deler van de tweede timer wordt ingesteld in het register TIM2_PSCR en is een macht van twee:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

We moeten de uitgangen en de tweede timer zelf inschakelen. De eerste taak wordt opgelost door de registers Capture/Compare Inschakelen: er zijn er twee, drie kanalen zijn ongelijkmatig over hen verdeeld. Hier kunnen we ook ontdekken dat we de polariteit van het signaal kunnen wijzigen, dus in principe hadden we ook PWM-modus 1 kunnen 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 tot slot starten we de timer in het register TIMx_CR1:

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

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

We schrijven een eenvoudige analoog van AnalogWrite(), die de timer de eigenlijke waarden voor vergelijking zal doorgeven. De registers worden voorspelbaar genoemd Capture/Compare registers, ze zijn er twee voor elk kanaal: de lagere 8 bits in TIM2_CCRxL en de hogere in TIM2_CCRxH. Aangezien we 8-bits PWM hebben ingesteld, is het voldoende om alleen de lagere 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 licht defecte PWM hebben gekregen, die niet in staat is om 100% duty cycle te genereren (bij de maximale waarde van 255 wordt het signaal gedurende één timercyclus omgekeerd). Voor LED's maakt dit niet uit, en de oplettende lezer raadt zelf al hoe dit op te lossen.

PWM op de tweede timer werkt, laten we nu naar de eerste gaan.

De eerste timer heeft precies dezelfde bits in dezelfde registers (de bits die 'gereserveerd' waren in de tweede timer, worden in de eerste actief gebruikt voor allerlei geavanceerde dingen). Daarom is het voldoende om de adressen van dezelfde registers in de datasheet te vinden en de code te kopiëren. En natuurlijk de waarde van de frequentiedeler veranderen, aangezien de eerste timer geen machten van twee wil, maar een exact 16-bits waarde in twee registers. Prescaler Hoog en 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 controle registers van timer 1 door te nemen, waar we zoeken naar degene die niet aanwezig is bij de tweede timer. We vinden 17.7.30 Break register (TIM1_BKR), waar er zo'n bit is:

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

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Nu is het echt alles, de code daarzelfde.

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

STM8 Multiplex

Multiplexing op STM8

Het derde mini-project is om acht RGB-LED's aan de tweede timer aan te sluiten in PWM-modus en ze verschillende kleuren te laten weergeven. De basis is het concept van LED-multiplexing, wat inhoudt dat als we de LED's heel snel aan en uit schakelen, het ons lijkt alsof ze constant branden (persistentie van het zicht, traagheid van de visuele waarneming). Eertijds deed ik iets dergelijks op Arduino.

Het werkingsalgoritme ziet er als volgt uit:

  • aangekoppeld de anode van de eerste RGB LED;
  • het aangestoken, door de juiste signalen naar de kathodes te sturen;
  • wacht tot het einde van de PWM-cyclus;
  • aangekoppeld de anode van de tweede RGB LED;
  • het aangestoken…

Enzovoort. Uiteraard, voor een goede werking is het noodzakelijk dat de aansluiting van de anode en het 'afsteken' van de LED's gelijktijdig plaatsvinden. Of bijna. Hoe dan ook, we moeten code schrijven die op drie kanalen van de tweede timer waarden afgeeft, deze verandert bij het bereiken van UEV en tegelijkertijd de actieve RGB-LED verandert.

Aangezien de LED-schakeling automatisch plaatsvindt, moet er 'videogeheugen' worden gecreëerd vanwaar de interrupt-handler gegevens zal ophalen. Dit is een eenvoudige array:

uint8_t colors[8][3];

Om de kleur van een specifieke LED te veranderen, hoeft u alleen maar de juiste waarden in deze array te schrijven. De actieve LED wordt beheerd door de variabele

uint8_t cnt;

Demux

Voor de juiste multiplexing hebben we, vreemd genoeg, een demultiplexor CD74HC238 nodig. Een demultiplexor is een chip die de operator <<. Via drie ingangs-pinnen (bits 0, 1 en 2) voeren we er een driebits getal X in, en hij activeert de uitgang nummer (1<<X). De overige ingangen van de chip worden gebruikt voor het schalen van de hele constructie. Deze chip is niet alleen nodig om het aantal gebruikte pinnen van de microcontroller te verminderen, maar ook voor de veiligheid – om per ongeluk niet meer LED's in te schakelen dan toegestaan en de microcontroller niet te verbrandens. De chip kost slechts een paar cent, het is handig om altijd in uw huisapotheek te hebben.

De CD74HC238 is verantwoordelijk voor het aanleveren van spanning naar de anode van de juiste LED. In een volledige multiplex zou hij spanning aan de kolom geven via een P-MOSFET, maar in deze demo kan dat ook direct, aangezien hij 20 mA trekt, volgens absolute maximum ratings in de datasheet. Uit de datasheet van de CD74HC238 hebben we de pinout en dit geheugensteuntje nodig:

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

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

PWM

PWM op de tweede timer wordt ingesteld zoals in het vorige verhaal, met twee uitzonderingen:

Ten eerste moeten we de interrupt voor Update Event (UEV) inschakelen, die de functie aanroept die de actieve LED schakelt. Dit gebeurt door de bit Update Interrupt Enable in het register met de veelzeggende naam

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

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

De tweede uitzondering heeft te maken met een fenomeen van multiplexing, genaamd ghosting – parasitaire verlichting van diodes. In ons geval kan dit optreden omdat de timer, die de onderbreking bij UEV oproept, verder tikt en de interrupt-handler niet snel genoeg de LED kan schakelen voordat de timer al iets begint te schrijven naar de uitgangen. Om dit te bestrijden, moeten we de logica omkeren (0 = maximale helderheid, 255 = niets brandt) en extreme waarden van de duty cycle vermijden. Dat wil zeggen, ervoor zorgen dat de LED's volledig doofd zijn voor één PWM-cyclus na UEV.

We veranderen de polariteit:

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

Vermijd het instellen van r, g en b op 255 en vergeet niet ze om te keren tijdens het gebruik.

Onderbrekingen

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

Toen we voor het eerst een project in ST Visual Develop maakten, kregen we naast main.c een venster met het mysterieuze bestand stm8_interrupt_vector.c, automatisch opgenomen in het project. In dit bestand is aan elke onderbreking een functie gekoppeld: NonHandledInterrupt. We moeten onze functie aan de juiste onderbreking koppelen.

In de datasheet is er een tabel met onderbrekingsvectoren, waarin we de juiste vinden:

Datasheets lezen 2: SPI op STM32; PWM, timers en interrupts op STM8
13 TIM2 update/overflow
14 TIM2 capture/compare

We moeten de LED veranderen bij UEV, dus we hebben onderbreking nummer 13 nodig.

Dus, allereerst moeten we in het bestand stm8_interrupt_vector.c de naam van de functie die standaard verantwoordelijk is voor onderbreking nummer 13 (IRQ13) in de juiste naam veranderen:

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

Ten tweede, we moeten een bestand aanmaken, main.h met de volgende inhoud:

#ifndef __MAIN_H
#define __MAIN_H

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

En tot slot moeten we deze functie in main.c:

@far @interrupt void TIM2_Overflow(void)
{
    PD_ODR &= ~(1<<5); // demultiplexer uitschakelen
    PC_ODR = (cnt<<3); // nieuwe waarde in demultiplexer schrijven
    PD_ODR |= (1<<5); // demultiplexer inschakelen

    TIM2_SR1 = 0; // vlag Update Interrupt Pending resetten

    cnt++; 
    cnt &= 7; // LED teller verplaatsen

    TIM2_CCR1L = ~colors[cnt][0]; // omgekeerde waarden doorgeven naar buffer
    TIM2_CCR2L = ~colors[cnt][1]; // voor de volgende PWM-cyclus
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Nu moeten we onderbrekingen inschakelen. Dit wordt gedaan met de assembleropdracht rim – we moeten deze vinden in Programming Manual:

//enable interrupts
_asm("rim");

Een andere assembleropdracht – sim – schakelt onderbrekingen uit. Deze moeten worden uitgeschakeld tijdens het schrijven van nieuwe waarden naar het "videogeheugen", zodat een onverwachte onderbreking geen array beschadigt.

De gehele code is op GitHub..

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

Als deze artikel iemand van nut is, dan was het niet voor niets dat ik het schreef. Ik kijk uit naar opmerkingen en feedback, en zal proberen op alles te antwoorden.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers 🔥 Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster