Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

В første del Jeg prøvde å fortelle hobbyelektronikkingeniører som vokste opp fra Arduino-bukser hvordan og hvorfor de skulle lese datablad og annen dokumentasjon for mikrokontrollere. Teksten viste seg å være stor, så jeg lovet å vise praktiske eksempler i en egen artikkel. Vel, han kalte seg en melkesopp...

I dag vil jeg vise deg hvordan du bruker dataark for å løse ganske enkle, men nødvendige for mange prosjekter, oppgaver på STM32 (Blue Pill) og STM8-kontrollere. Alle demoprosjekter er dedikert til favoritt-LED-ene mine, vi vil tenne dem i store mengder, som vi må bruke alle slags interessante periferiutstyr for.

Teksten viste seg igjen å være enorm, så for enkelhets skyld lager jeg innholdet:

STM32 Blue Pill: 16 LED-er med DM634-driver
STM8: Sette opp seks PWM-pinner
STM8: 8 RGB LED-er på tre pinner, avbryter

Ansvarsfraskrivelse: Jeg er ikke en ingeniør, jeg later ikke til å ha dyp kunnskap innen elektronikk, artikkelen er ment for amatører som meg. Faktisk så jeg meg selv for to år siden som målgruppen. Hvis noen da hadde fortalt meg at dataark for en ukjent brikke ikke var skummelt å lese, ville jeg ikke ha brukt mye tid på å lete etter noen kodebiter på Internett og finne opp krykker med saks og teip.

Fokuset i denne artikkelen er på dataark, ikke prosjekter, så koden er kanskje ikke veldig ryddig og ofte trang. Prosjektene i seg selv er veldig enkle, selv om de passer for et første bekjentskap med den nye brikken.

Jeg håper at artikkelen min vil hjelpe noen på et lignende stadium av fordypning i hobbyen.

STM32

16 lysdioder med DM634 og SPI

Et lite prosjekt som bruker Blue Pill (STM32F103C8T6) og DM634 LED-driver. Ved å bruke dataark vil vi finne ut driveren, STM IO-porter og konfigurere SPI.

DM634

Taiwanesisk brikke med 16 16-bits PWM-utganger, kan kobles i kjeder. Low-end 12-bits modellen er kjent fra et innenlandsk prosjekt Lightpack. På en gang, ved å velge mellom DM63x og den velkjente TLC5940, valgte jeg DM av flere grunner: 1) TLC på Aliexpress er definitivt falsk, men denne er ikke det; 2) DM har en autonom PWM med egen frekvensgenerator; 3) det kunne kjøpes billig i Moskva, i stedet for å vente på en pakke fra Ali. Og det var selvfølgelig interessant å lære å kontrollere brikken selv, i stedet for å bruke et ferdig bibliotek. Brikker presenteres nå hovedsakelig i SSOP24-pakken; de er enkle å lodde til en adapter.

Siden produsenten er taiwansk, datablad brikken er skrevet på kinesisk engelsk, noe som betyr at det blir moro. Først ser vi på pinouten (Pin tilkobling) for å forstå hvilket ben som skal kobles til, og en beskrivelse av pinnene (Pin Beskrivelse). 16 pinner:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
DC synkekilder (åpent avløp)

Synke / Utgang for åpent avløp - avløp; kilde til innstrømmende strøm; utgangen er koblet til jord i aktiv tilstand - LED-ene er koblet til driveren med katoder. Elektrisk er dette selvfølgelig ikke et "åpent avløp" (åpent sluk), men i dataark finnes ofte denne betegnelsen for pinner i dreneringsmodus.

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Eksterne motstander mellom REXT og GND for å angi utgangsstrømverdien

En referansemotstand er installert mellom REXT-pinnen og jord, som kontrollerer den interne motstanden til utgangene, se grafen på side 9 i dataarket. I DM634 kan denne motstanden også kontrolleres av programvare, og stille inn den generelle lysstyrken (global lysstyrke); Jeg vil ikke gå inn på detaljer i denne artikkelen, jeg legger bare en 2.2 - 3 kOhm motstand her.

For å forstå hvordan du kontrollerer brikken, la oss se på beskrivelsen av enhetsgrensesnittet:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

Ja, her er det, kinesisk engelsk i all sin prakt. Å oversette dette er problematisk, du kan forstå det hvis du ønsker det, men det er en annen måte - se på hvordan tilkoblingen til den funksjonelt lignende TLC5940 er beskrevet i dataarket:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
... Bare tre pinner kreves for å legge inn data i enheten. Den stigende flanken til SCLK-signalet forskyver dataene fra SIN-pinnen til det interne registeret. Etter at alle data er lastet inn, låser et kort høyt XLAT-signal de sekvensielt overførte dataene inn i de interne registrene. Interne registre er porter som utløses av XLAT-signalnivået. Alle data overføres den viktigste biten først.

Klinke – lås/lås/lås.
Stigende kant – forkant av pulsen
MSB først – mest betydningsfulle (lengst til venstre) bit fremover.
å klokke data – overføre data sekvensielt (bit for bit).

Word klinke finnes ofte i dokumentasjonen for sjetonger og er oversatt på ulike måter, så for forståelsens skyld vil jeg tillate meg selv

et lite pedagogisk programLED-driveren er i hovedsak et skiftregister. "Skift" (skift) i navnet - bitvis bevegelse av data inne i enheten: hver ny bit som dyttes inn, skyver hele kjeden foran seg. Siden ingen ønsker å observere kaotisk blinking av lysdiodene under skiftet, foregår prosessen i bufferregistre atskilt fra arbeidsregistrene med en spjeld (klinke) er et slags venterom hvor bitene er ordnet i ønsket rekkefølge. Når alt er klart, åpnes lukkeren og bitene går på jobb, og erstatter forrige batch. Ord klinke i dokumentasjonen for mikrokretser innebærer nesten alltid en slik demper, uansett i hvilke kombinasjoner den brukes.

Så, dataoverføring til DM634 utføres slik: sett DAI-inngangen til verdien av den mest signifikante biten av den fjerne LED-en, trekk DCK opp og ned; sett DAI-inngangen til verdien av neste bit, trekk DCK; og så videre til alle biter er overført (klokket inn), hvoretter vi trekker LAT. Dette kan gjøres manuelt (bit-smell), men det er bedre å bruke et SPI-grensesnitt spesielt skreddersydd for dette, siden det presenteres på vår STM32 i to eksemplarer.

Blå pille STM32F103

Innledning: STM32-kontrollere er mye mer komplekse enn Atmega328 enn de kan virke skumle. Av hensyn til energisparing er dessuten nesten alle eksterne enheter slått av ved starten, og klokkefrekvensen er 8 MHz fra den interne kilden. Heldigvis skrev STM-programmerere kode som bringer brikken opp til de "beregnede" 72 MHz, og forfatterne av alle IDE-ene jeg kjenner inkluderte den i initialiseringsprosedyren, så vi trenger ikke å klokke (men du kan hvis du virkelig vil). Men du må slå på periferiutstyret.

Dokumentasjon: Blue Pill er utstyrt med den populære STM32F103C8T6-brikken, det er to nyttige dokumenter for den:

I dataarket kan vi være interessert i:

  • Pinouts – chip pinouts – i tilfelle vi bestemmer oss for å lage brettene selv;
  • Minnekart – minnekart for en spesifikk brikke. Referansemanualen har et kart for hele linjen, og den nevner registre som vår ikke har.
  • Tabell med definisjoner av pinner – viser hovedfunksjonene og alternative funksjoner til pinner; for den "blå pillen" kan du finne mer praktiske bilder på Internett med en liste over pinner og deres funksjoner. Derfor googler vi umiddelbart Blue Pill pinout og holder dette bildet for hånden:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
NB: det var en feil på bildet fra Internett, som ble notert i kommentarfeltet, takk for det. Bildet er erstattet, men dette er en leksjon - det er bedre å sjekke informasjon ikke fra datablad.

Vi fjerner dataarket, åpner referansehåndboken, og fra nå av bruker vi bare det.
Prosedyre: vi håndterer standard input/output, konfigurerer SPI, slår på nødvendig periferiutstyr.

Inngang Utgang

På Atmega328 er I/O implementert ekstremt enkelt, og derfor kan overfloden av STM32-alternativer være forvirrende. Nå trenger vi bare konklusjoner, men selv disse har fire alternativer:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
åpent avløp, push-pull, alternativ push-pull, alternativ åpent avløp

"Pull-push" (Push-pull) er den vanlige utgangen fra Arduino, pinnen kan ta verdien enten HØY eller LAV. Men med "åpent avløp" er det vanskeligheter, selv om alt er enkelt her:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Utgangskonfigurasjon / når porten er tilordnet utgang: / utgangsbuffer aktivert: / – åpen drain-modus: "0" i utgangsregisteret aktiverer N-MOS, "1" i utgangsregisteret forlater porten i Hi-Z-modus ( P-MOS er ikke aktivert ) / – push-pull-modus: "0" i utgangsregisteret aktiverer N-MOS, "1" i utgangsregisteret aktiverer P-MOS.

Hele forskjellen mellom åpent avløp (åpent sluk) fra "push-pull" (Push-pull) er at i den første pinnen ikke kan akseptere HØY-tilstanden: når du skriver en til utgangsregisteret, går den inn i høymotstandsmodus (høy impedans, Hei-Z). Når du skriver null, oppfører pinnen seg likt i begge modusene, både logisk og elektrisk.

I normal utgangsmodus sender pinnen ganske enkelt innholdet i utgangsregisteret. I "alternativet" styres den av tilsvarende periferiutstyr (se 9.1.4):

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Hvis en portbit er konfigurert som en alternativ funksjonspinne, deaktiveres pinneregisteret og pinnen kobles til den perifere pinnen.

Alternativ funksjonalitet for hver pinne er beskrevet i Pin Definisjoner Dataarket er på det nedlastede bildet. På spørsmålet om hva du skal gjøre hvis en pinne har flere alternative funksjoner, er svaret gitt av en fotnote i dataarket:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Hvis flere perifere enheter bruker samme pinne, for å unngå konflikt mellom alternative funksjoner, bør kun én perifer enhet brukes om gangen, vekslet ved å bruke den perifere klokkeaktiveringsbiten (i det aktuelle RCC-registeret).

Til slutt har pinner i utgangsmodus også en klokkehastighet. Dette er en annen energisparende funksjon; i vårt tilfelle setter vi den bare til maksimum og glemmer den.

Så: vi bruker SPI, som betyr at to pinner (med data og med et klokkesignal) skal være "alternativ push-pull-funksjon", og en annen (LAT) skal være "vanlig push-pull". Men før vi tildeler dem, la oss ta oss av SPI.

SPI

Nok et lite pedagogisk program

SPI eller Serial Peripheral Interface (serielt perifert grensesnitt) er et enkelt og veldig effektivt grensesnitt for å koble en MK med andre MKer og omverdenen generelt. Prinsippet for driften er allerede beskrevet ovenfor, hvor om den kinesiske LED-driveren (i referansehåndboken, se avsnitt 25). SPI kan operere i master (“master”) og slave (“slave”) modus. SPI har fire grunnleggende kanaler, hvorav ikke alle kan brukes:

  • MOSI, Master Output / Slave Input: denne pinnen overfører data i mastermodus, og mottar data i slavemodus;
  • MISO, Master Input / Slave Output: tvert imot, den mottar i masteren, og sender i slaven;
  • SCK, Serial Clock: stiller inn frekvensen for dataoverføring i masteren eller mottar et klokkesignal i slaven. I hovedsak treffer beats;
  • SS, Slave Select: ved hjelp av denne kanalen vet slaven at noe er ønsket fra ham. På STM32 heter det NSS, hvor N = negativ, dvs. kontrolleren blir en slave hvis det er jord i denne kanalen. Den kombinerer godt med Open Drain Output-modus, men det er en annen historie.

Som alt annet er SPI på STM32 rik på funksjonalitet, noe som gjør det noe vanskelig å forstå. For eksempel kan det fungere ikke bare med SPI, men også med et I2S-grensesnitt, og i dokumentasjonen er beskrivelsene deres blandet, det er nødvendig å kutte av overskuddet i tide. Vår oppgave er ekstremt enkel: vi trenger bare å sende data ved å bruke bare MOSI og SCK. Vi går til avsnitt 25.3.4 (halvduplekskommunikasjon, halvduplekskommunikasjon), hvor vi finner 1 klokke og 1 ensrettet dataledning (1 klokkesignal og 1 ensrettet datastrøm):

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
I denne modusen bruker applikasjonen SPI i enten kun overføring eller kun mottak. / Kun overføringsmodus ligner på dupleksmodus: data overføres på overføringspinnen (MOSI i mastermodus eller MISO i slavemodus), og mottakspinnen (henholdsvis MISO eller MOSI) kan brukes som en vanlig I/O-pinne . I dette tilfellet trenger applikasjonen bare å ignorere Rx-bufferen (hvis den leses, vil det ikke være overførte data der).

Flott, MISO-pinnen er ledig, la oss koble LAT-signalet til den. La oss se på Slave Select, som på STM32 kan styres programmatisk, noe som er ekstremt praktisk. Vi leser avsnittet med samme navn i avsnitt 25.3.1 SPI Generell beskrivelse:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Programvarekontroll NSS (SSM = 1) / Slavevalginformasjon er inneholdt i SSI-biten til SPI_CR1-registeret. Den eksterne NSS-pinnen forblir ledig for andre applikasjonsbehov.

Det er på tide å skrive til registrene. Jeg bestemte meg for å bruke SPI2, se etter basisadressen i dataarket - i avsnitt 3.3 Minnekart:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

Vel, la oss begynne:

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

Åpne seksjon 25.3.3 med den selvforklarende tittelen "Konfigurere SPI i mastermodus":

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

1. Still inn den serielle klokkefrekvensen med bits BR[2:0] i SPI_CR1-registeret.

Registrene er samlet i referansehåndbokdelen med samme navn. adresseskift (Adresseforskyvning) for CR1 – 0x00, som standard slettes alle biter (Tilbakestill verdi 0x0000):

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

BR-bitene setter kontrollerens klokkedeler, og bestemmer dermed frekvensen som SPI-en vil operere med. Vår STM32-frekvens vil være 72 MHz, LED-driveren, ifølge databladet, opererer med en frekvens på opptil 25 MHz, så vi må dele på fire (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. Still inn CPOL- og CPHA-bitene for å definere forholdet mellom dataoverføring og seriell klokketiming (se diagram på side 240)

Siden vi leser et dataark her og ikke ser på skjemaer, la oss se nærmere på tekstbeskrivelsen av CPOL- og CPHA-bitene på side 704 (SPI Generell beskrivelse):

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Klokkefase og polaritet
Ved å bruke CPOL- og CPHA-bitene til SPI_CR1-registeret kan du programmere velge fire tidsforhold. CPOL-biten (klokkepolaritet) kontrollerer tilstanden til klokkesignalet når ingen data blir overført. Denne biten styrer master- og slavemodusene. Hvis CPOL er tilbakestilt, er SCK-pinnen lav i hvilemodus. Hvis CPOL-biten er satt, er SCK-pinnen høy under hvilemodus.
Når CPHA (klokkefase)-biten er satt, er høybit-felle-stroben den andre kanten av SCK-signalet (faller hvis CPOL er klar, stiger hvis CPOL er satt). Dataene fanges opp av den andre endringen i klokkesignalet. Hvis CPHA-biten er klar, er høybit-felle-stroben den stigende flanken til SCK-signalet (fallende flanke hvis CPOL er satt, stigende flanke hvis CPOL er slettet). Data fanges opp ved første endring i klokkesignalet.

Etter å ha absorbert denne kunnskapen, kommer vi til den konklusjon at begge bitene må forbli null, fordi Vi vil at SCK-signalet skal forbli lavt når det ikke er i bruk, og at data skal overføres på den stigende kanten av pulsen (se fig. Rising Edge i DM634-dataarket).

Forresten, her møtte vi først et trekk ved ordforrådet i ST-dataark: i dem er uttrykket "tilbakestill biten til null" skrevet å tilbakestille littOg ikke å rydde litt, som for eksempel Atmega.

3. Still inn DFF-biten for å bestemme om datablokken er 8-biters eller 16-biters format

Jeg tok spesifikt en 16-bit DM634 for ikke å bry meg med å overføre 12-bit PWM-data, som DM633. Det er fornuftig å sette DFF til én:

#define DFF         0x0800

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

4. Konfigurer LSBFIRST-biten i SPI_CR1-registeret for å bestemme blokkformatet

LSBFIRST, som navnet antyder, konfigurerer overføring med den minst signifikante biten først. Men DM634 ønsker å motta data fra den mest betydningsfulle biten. Derfor lar vi den tilbakestilles.

5. I maskinvaremodus, hvis inndata fra NSS-pinnen er nødvendig, påfør et høyt signal til NSS-pinnen under hele byteoverføringssekvensen. I NSS-programvaremodus setter du SSM- og SSI-bitene i SPI_CR1-registeret. Hvis NSS-pinnen skal brukes som en utgang, er det kun SSOE-biten som må settes.

Installer SSM og SSI for å glemme NSS-maskinvaremodusen:

#define SSI         0x0100
#define SSM         0x0200

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

6. MSTR- og SPE-bitene må stilles inn (de forblir satt bare hvis NSS-signalet er høyt)

Faktisk, med disse bitene utpeker vi vår SPI som en master og slår den på:

#define MSTR        0x0004
#define SPE         0x0040

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

SPI er konfigurert, la oss umiddelbart skrive funksjoner som sender byte til driveren. Fortsett å lese 25.3.3 "Konfigurere SPI i mastermodus":

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Dataoverføringsordre
Overføringen begynner når en byte skrives til Tx-bufferen.
Databyten lastes inn i skiftregisteret kl parallell modus (fra den interne bussen) under overføringen av den første biten, hvoretter den overføres til sekvensiell MOSI pin-modus, første eller siste bit fremover avhengig av innstillingen til LSBFIRST-biten i CPI_CR1-registeret. TXE-flagget settes etter dataoverføring fra Tx buffer til skiftregister, og genererer også et avbrudd hvis TXEIE-biten i CPI_CR1-registeret er satt.

Jeg fremhevet noen få ord i oversettelsen for å trekke oppmerksomhet til en funksjon ved SPI-implementeringen i STM-kontrollere. På Atmega TXE-flagget (Tx tom, Tx er tom og klar til å motta data) settes først etter at hele byten er sendt ytre. Og her settes dette flagget etter at byten er satt inn i det interne skiftregisteret. Siden det skyves dit med alle bitene samtidig (parallelt), og deretter dataene overføres sekvensielt, settes TXE før byten er fullstendig sendt. Dette er viktig fordi når det gjelder LED-driveren vår, må vi trekke LAT-pinnen etter sending Alle data, dvs. TXE-flagget alene vil ikke være nok for oss.

Det betyr at vi trenger et flagg til. La oss se på 25.3.7 - "Statusflagg":

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
<…>
Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
OPPTATT flagg
BSY-flagget settes og slettes av maskinvare (skriving til det har ingen effekt). BSY-flagget indikerer tilstanden til SPI-kommunikasjonslaget.
Den tilbakestiller:
når overføringen er fullført (unntatt i mastermodus hvis overføringen er kontinuerlig)
når SPI er deaktivert
når en mastermodusfeil oppstår (MODF=1)
Hvis overføringen ikke er kontinuerlig, slettes BSY-flagget mellom hver dataoverføring

Ok, dette kommer godt med. La oss finne ut hvor Tx-bufferen er plassert. For å gjøre dette, les "SPI Data Register":

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Bits 15:0 DR[15:0] Dataregister
Data mottatt eller data som skal overføres.
Dataregisteret er delt inn i to buffere - en for skriving (sendebuffer) og en for lesing (mottaksbuffer). Å skrive til dataregisteret skriver til Tx-bufferen, og lesing fra dataregisteret vil returnere verdien i Rx-bufferen.

Vel, og statusregisteret, der TXE- og BSY-flaggene finnes:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

Vi skriver:

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

Vel, siden vi trenger å overføre 16 ganger to byte, i henhold til antall LED-driverutganger, noe sånt som dette:

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

Men vi vet ikke hvordan vi skal trekke LAT-pinnen ennå, så vi går tilbake til I/O.

Tilordning av pinner

I STM32F1 er registrene som er ansvarlige for tilstanden til pinnene ganske uvanlige. Det er tydelig at det er flere av dem enn Atmega, men de er også forskjellige fra andre STM-brikker. Seksjon 9.1 Generell beskrivelse av GPIO:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Hver av de generelle I/O-portene (GPIO) har to 32-bits konfigurasjonsregistre (GPIOx_CRL og GPIOx_CRH), to 32-biters dataregistre (GPIOx_IDR og GPIOx_ODR), et 32-bits set/reset-register (GPIOx_BSRR), et 16-bits tilbakestillingsregister (GPIOx_BRR) og et 32- bitblokkeringsregister (GPIOx_LCKR).

De to første registrene er uvanlige, og også ganske upraktiske, fordi de 16 portpinnene er spredt over dem i et "fire bits per bror"-format. De. pinnene null til syv er i CRL, og resten er i CRH. Samtidig inneholder de gjenværende registrene bitene til alle pinnene i porten - ofte forblir halvparten "reservert".

For enkelhets skyld, la oss starte fra slutten av listen.

Vi trenger ikke et blokkeringsregister.

Sett- og tilbakestillingsregistrene er ganske morsomme ved at de delvis dupliserer hverandre: du kan bare skrive alt i BSRR, der de høyere 16 bitene vil tilbakestille pinnen til null, og de lavere vil bli satt til 1, eller du kan også bruk BRR, de nederste 16 bitene tilbakestiller bare pinnen . Jeg liker det andre alternativet. Disse registrene er viktige fordi de gir atomtilgang til pinner:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Atomsett eller tilbakestilling
Det er ikke nødvendig å deaktivere avbrudd når du programmerer GPIOx_ODR på bitnivå: en eller flere biter kan endres med en enkelt atomskriveoperasjon APB2. Dette oppnås ved å skrive en "1" til set/reset-registeret (GPIOx_BSRR eller, kun for tilbakestilling, GPIOx_BRR) til biten som må endres. Andre biter vil forbli uendret.

Dataregistrene har ganske selvforklarende navn - IDR = Input Retning Register, inngangsregister; ODR = Produksjon Retningsregister, utgangsregister. Vi trenger dem ikke i det nåværende prosjektet.

Og til slutt, kontrollregistre. Siden vi er interessert i de andre SPI-pinnene, nemlig PB13, PB14 og PB15, ser vi umiddelbart på CRH:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

Og vi ser at vi må skrive noe i biter fra 20 til 31.

Vi har allerede funnet ut ovenfor hva vi vil ha fra pinner, så her vil jeg klare meg uten et skjermbilde, jeg vil bare si at MODE spesifiserer retningen (inngang hvis begge bitene er satt til 0) og pinnehastigheten (vi trenger 50MHz, dvs. både pin til "1"), og CNF setter modusen: vanlig "push-pull" - 00, "alternativ" - 10. Som standard, som vi ser ovenfor, har alle pinner den tredje biten fra bunnen (CNF0), den setter dem i modus flytende inngang.

Siden jeg planlegger å gjøre noe annet med denne brikken, har jeg for enkelhets skyld definert alle mulige MODE- og CNF-verdier for både nedre og øvre kontrollregister.

På en eller annen måte sånn

#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

Pinnene våre er plassert på port B (baseadresse – 0x40010C00), kode:

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

Og følgelig kan du skrive definisjoner for LAT, som vil bli rykket av BRR- og BSRR-registrene:

/*** 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 bare ved treghet, det har alltid vært sånn, la det bli)

Nå er alt flott, men det fungerer ikke. Fordi dette er STM32, sparer de elektrisitet, noe som betyr at du må aktivere klokkefunksjonen av nødvendig periferiutstyr.

Slå på klokkefunksjonen

Klokken, også kjent som Clock, er ansvarlig for klokke. Og vi kunne allerede legge merke til forkortelsen RCC. Vi ser etter det i dokumentasjonen: dette er Tilbakestilling og klokkekontroll.

Som det ble sagt ovenfor, ble heldigvis den vanskeligste delen av klokkeemnet gjort for oss av folk fra STM, noe vi takker dem veldig for (noen en gang vil jeg gi en lenke til Di Halts hjemmeside, for å gjøre det klart hvor forvirrende det er). Vi trenger bare registre som er ansvarlige for å aktivere perifer klokke (Peripheral Clock Enable Registers). Først, la oss finne baseadressen til RCC, den er helt i begynnelsen av "Minnekartet":

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

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

Og klikk deretter enten på lenken der du prøver å finne noe i platen, eller, mye bedre, gå gjennom beskrivelsene av aktiveringsregistrene fra seksjonene om aktivere registre. Hvor finner vi RCC_APB1ENR og RCC_APB2ENR:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

Og de inneholder følgelig biter som inkluderer klokking av SPI2, IOPB (I/O Port B) og alternative funksjoner (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;

Den endelige koden kan bli funnet her.

Hvis du har mulighet og lyst til å teste, så koble til DM634 slik: DAI til PB15, DCK til PB13, LAT til PB14. Vi driver driveren fra 5 volt, ikke glem å koble til begrunnelsen.

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

STM8 PWM

PWM på STM8

Da jeg bare planla denne artikkelen, bestemte jeg meg for, for eksempel, å prøve å mestre funksjonaliteten til en ukjent brikke med kun et dataark, slik at jeg ikke ender opp med en skomaker uten støvler. STM8 var ideell for denne rollen: For det første hadde jeg et par kinesiske brett med STM8S103, og for det andre er det ikke veldig populært, og derfor hviler fristelsen til å lese og finne en løsning på Internett på mangelen på nettopp disse løsningene.

Brikken har også datablad и referansehåndbok RM0016, i den første er det pinout og registeradresser, i den andre - alt annet. STM8 er programmert i C i en forferdelig IDE ST visuell utvikling.

Klokking og I/O

Som standard opererer STM8 med en frekvens på 2 MHz, dette må korrigeres umiddelbart.

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
HSI (High Speed ​​​​Intern) klokke
HSI-klokkesignalet er utledet fra en intern 16 MHz RC-oscillator med en programmerbar deler (1 til 8). Den settes i klokkedelerregisteret (CLK_CKDIVR).
Merk: ved starten velges en HSI RC-oscillator med en deler på 8 som ledende kilde for klokkesignalet.

Vi finner registeradressen i dataarket, beskrivelsen i refman og ser at registeret må ryddes:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Siden vi skal kjøre PWM og koble til LED-ene, la oss se på pinouten:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

Brikken er liten, mange funksjoner er suspendert på de samme pinnene. Det som står i hakeparenteser er "alternativ funksjonalitet", det byttes av "opsjonsbytes" (alternativbytes) – noe sånt som Atmega-sikringer. Du kan endre verdiene deres programmatisk, men det er ikke nødvendig, fordi Den nye funksjonaliteten aktiveres først etter en omstart. Det er lettere å bruke ST Visual Programmer (lastet ned med Visual Develop), som kan endre disse bytene. Pinouten viser at CH1- og CH2-pinnene til den første timeren er skjult i hakeparenteser; det er nødvendig å sette AFR1- og AFR0-bitene i STVP, og den andre vil også overføre CH1-utgangen til den andre timeren fra PD4 til PC5.

Dermed vil 6 pinner styre LED-ene: PC6, PC7 og PC3 for den første timeren, PC5, PD3 og PA3 for den andre.

Å sette opp selve I/O-pinnene på STM8 er enklere og mer logisk enn på STM32:

  • kjent fra Atmega DDR dataretningsregister (Dataretningsregister): 1 = utgang;
  • det første kontrollregisteret CR1, når det sendes ut, setter push-pull-modus (1) eller åpent avløp (0); siden jeg kobler LED-ene til brikken med katoder, lar jeg nuller her;
  • det andre kontrollregisteret CR2, når det sendes ut, setter klokkehastigheten: 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-innstilling

Først, la oss definere begrepene:

  • PWM-frekvens – hvor ofte tidtakeren tikker;
  • Last inn automatisk, AR – automatisk lastbar verdi som timeren vil telle til (pulsperiode);
  • Oppdater hendelse, UEV – en hendelse som oppstår når tidtakeren har telt til AR;
  • PWM Duty Cycle – PWM arbeidssyklus, ofte kalt "duty factor";
  • Ta opp/sammenlign verdi – verdi for fangst/sammenligning, som tidtakeren har telt til vil gjøre noe (i tilfelle av PWM, inverterer den utgangssignalet);
  • Forhåndsinnlastingsverdi – forhåndsinnlastet verdi. Sammenlign verdi kan ikke endres mens tidtakeren tikker, ellers vil PWM-syklusen bryte. Derfor blir nye overførte verdier plassert i en buffer og trukket ut når tidtakeren når slutten av nedtellingen og tilbakestilles;
  • Kantjustert и Senterjusterte moduser – justering langs grensen og i midten, det samme som Atmels Rask PWM и Fasekorrekt PWM.
  • OCiREF, utgangssammenligningsreferansesignal – referanseutgangssignal, faktisk, det som vises på den tilsvarende pinnen i PWM-modus.

Som det allerede er klart fra pinouten, har to timere PWM-funksjoner - den første og den andre. Begge er 16-bit, den første har mange tilleggsfunksjoner (spesielt kan den telle både opp og ned). Vi trenger begge å jobbe likt, så jeg bestemte meg for å begynne med den åpenbart dårligere andre, for ikke å bruke noe som ikke er der ved et uhell. Et problem er at beskrivelsen av PWM-funksjonaliteten til alle timere i referansehåndboken er i kapittelet om den første timeren (17.5.7 PWM-modus), så du må hoppe frem og tilbake gjennom dokumentet hele tiden.

PWM på STM8 har en viktig fordel fremfor PWM på Atmega:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Grensejustert PWM
Kontokonfigurasjon fra bunn til topp
Nedenfra og opp-telling er aktiv hvis DIR-biten i TIM_CR1-registeret slettes
Eksempel
Eksemplet bruker den første PWM-modusen. PWM-referansesignalet OCiREF holdes høyt så lenge TIM1_CNT < TIM1_CCRi. Ellers krever det et lavt nivå. Hvis sammenligningsverdien i TIM1_CCRi-registeret er større enn autoload-verdien (TIM1_ARR-register), holdes OCiREF-signalet på 1. Hvis sammenligningsverdien er 0, holdes OCiREF på null....

STM8-timer under oppdater hendelsen sjekker først sammenligne verdi, og produserer først da et referansesignal. Atmegas tidtaker skrus først opp og sammenligner deretter, noe som resulterer i compare value == 0 utgangen er en nål, som må håndteres på en eller annen måte (for eksempel ved å programmere invertering av logikken).

Så hva vi vil gjøre: 8-bit PWM (AR == 255), teller fra bunn til topp, justering langs kanten. Siden lyspærene er koblet til brikken med katoder, skal PWM gi ut 0 (LED på) til sammenligne verdi og 1 etter.

Vi har allerede lest om noen PWM-modus, så vi finner det nødvendige registeret til den andre timeren ved å søke i referansehåndboken etter denne frasen (18.6.8 - TIMx_CCMR1):

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
110: Første PWM-modus – når man teller fra bunn til topp, er den første kanalen aktiv mens TIMx_CNT < TIMx_CCR1. Ellers er den første kanalen inaktiv. [lenger i dokumentet er det en feilaktig copy-paste fra timer 1] 111: Andre PWM-modus – når man teller fra bunn til topp, er den første kanalen inaktiv mens TIMx_CNT < TIMx_CCR1. Ellers er den første kanalen aktiv.

Siden lysdiodene er koblet til MK med katoder, passer den andre modusen oss (den første også, men det vet vi ikke ennå).

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Bit 3 OC1PE: Aktiver pin 1 forhåndsbelastning
0: Forhåndsinnlastingsregister på TIMx_CCR1 er deaktivert. Du kan skrive til TIMx_CCR1 når som helst. Den nye verdien virker umiddelbart.
1: Forhåndslastregister på TIMx_CCR1 er aktivert. Lese-/skriveoperasjoner får tilgang til forhåndsinnlastingsregisteret. Den forhåndsinnlastede verdien TIMx_CCR1 lastes inn i skyggeregisteret under hver oppdateringshendelse.
*Merk: For at PWM-modus skal fungere skikkelig, må forhåndsbelastningsregistrene være aktivert. Dette er ikke nødvendig i enkeltsignalmodus (OPM-biten er satt i TIMx_CR1-registeret).

Ok, la oss slå på alt vi trenger for de tre kanalene til den andre timeren:

#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 består av to åtte-bits registre, alt er enkelt:

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

TIM2_ARRH = 0;
TIM2_ARRL = 255;

Den andre timeren kan bare telle fra bunn til topp, justering langs grensen, ingenting må endres. La oss sette frekvensdeleren, for eksempel, til 256. For den andre timeren er deleren satt i TIM2_PSCR-registeret og er en potens på to:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Alt som gjenstår er å slå på konklusjonene og selve den andre timeren. Det første problemet løses av registre Ta opp/sammenlign aktiver: det er to, tre kanaler spredt over dem asymmetrisk. Her kan vi også lære at det er mulig å endre polariteten til signalet, d.v.s. i prinsippet var det mulig å bruke PWM Mode 1. Vi skriver:

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

Og til slutt starter vi timeren i TIMx_CR1-registeret:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

La oss skrive en enkel analog av AnalogWrite(), som vil overføre de faktiske verdiene til tidtakeren for sammenligning. Registrene er navngitt forutsigbart Ta opp/sammenlign registre, er det to av dem for hver kanal: lavordens 8 bits i TIM2_CCRxL og høyordens i TIM2_CCRxH. Siden vi har laget en 8-bits PWM, er det nok å skrive bare de minst signifikante bitene:

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

Den oppmerksomme leseren vil legge merke til at vi har en litt defekt PWM, som ikke er i stand til å produsere 100% fylling (ved en maksimal verdi på 255, blir signalet invertert for en tidtakersyklus). For lysdioder spiller dette ingen rolle, og den oppmerksomme leseren kan allerede gjette hvordan man fikser det.

PWM på den andre timeren fungerer, la oss gå videre til den første.

Den første timeren har nøyaktig de samme bitene i de samme registrene (det er bare at de bitene som forble "reservert" i den andre timeren, brukes aktivt i den første til alle slags avanserte ting). Derfor er det nok å finne adressene til de samme registrene i dataarket og kopiere koden. Vel, endre verdien på frekvensdeleren, fordi... den første tidtakeren ønsker ikke å motta en potens på to, men en nøyaktig 16-bits verdi i to registre Prescaler High и Lav. Vi gjør alt og... den første timeren fungerer ikke. Hva er i veien?

Problemet kan kun løses ved å se gjennom hele avsnittet om kontrollregistrene til timer 1, hvor vi ser etter den andre timeren ikke har. Det vil være 17.7.30 Pauseregister (TIM1_BKR), hvor det er denne biten:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Aktiver hovedutgang

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Det er helt sikkert nå, koden der.

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

STM8 multipleks

Multipleksing på STM8

Det tredje miniprosjektet er å koble åtte RGB LED-er til den andre timeren i PWM-modus og få dem til å vise forskjellige farger. Den er basert på konseptet med LED-multipleksing, som er at hvis du slår av og på lysdioder veldig, veldig raskt, vil det se ut for oss som om de hele tiden er på (vedvarende syn, treghet i visuell persepsjon). Det gjorde jeg en gang noe slikt på Arduino.

Arbeidsalgoritmen ser slik ut:

  • koblet til anoden til den første RGB LED;
  • tente den og sendte de nødvendige signalene til katodene;
  • ventet til slutten av PWM-syklusen;
  • koblet til anoden til den andre RGB LED;
  • tente den...

Vel, osv. For vakker drift kreves det selvfølgelig at anoden er tilkoblet og LED-en "tennes" samtidig. Vel, eller nesten. I alle fall må vi skrive en kode som vil sende ut verdier i tre kanaler av den andre timeren, endre dem når UEV er nådd, og samtidig endre den aktive RGB-LED-en.

Siden LED-bytte er automatisk, må vi lage et "videominne" som avbruddsbehandleren vil motta data fra. Dette er en enkel matrise:

uint8_t colors[8][3];

For å endre fargen på en bestemt LED, vil det være nok å skrive de nødvendige verdiene inn i denne matrisen. Og variabelen vil være ansvarlig for nummeret på den aktive LED-en

uint8_t cnt;

Demux

For riktig multipleksing trenger vi merkelig nok en CD74HC238 demultiplekser. Demultiplexer - en brikke som implementerer operatøren i maskinvare <<. Gjennom tre inngangspinner (bit 0, 1 og 2) mater vi den med et tre-bits nummer X, og som svar aktiverer den utgangsnummer (1<<X). De resterende inngangene til brikken brukes til å skalere hele designet. Vi trenger denne brikken ikke bare for å redusere antall okkuperte pinner til mikrokontrolleren, men også for sikkerhet - for ikke å ved et uhell slå på flere lysdioder enn mulig og ikke brenne MK. Brikken koster en krone og bør alltid oppbevares i ditt hjemmemedisinskap.

Vår CD74HC238 vil være ansvarlig for å levere spenning til anoden til ønsket LED. I en fullverdig multipleks vil den levere spenning til kolonnen gjennom en P-MOSFET, men i denne demoen er det mulig direkte, fordi den trekker 20 mA, iht absolutte maksimale karakterer i dataarket. Fra datablad CD74HC238 vi trenger pinouts og dette juksearket:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
H = høyt spenningsnivå, L = lavt spenningsnivå, X – ikke bryr seg

Vi kobler E2 og E1 til jord, E3, A0, A1 og A3 til pinnene PD5, PC3, PC4 og PC5 på STM8. Siden tabellen ovenfor inneholder både lave og høye nivåer, konfigurerer vi disse pinnene som push-pull pinner.

PWM

PWM på den andre timeren er konfigurert på samme måte som i forrige historie, med to forskjeller:

Først må vi aktivere avbruddet Oppdater hendelse (UEV) som vil kalle en funksjon som slår av den aktive LED-en. Dette gjøres ved å bytte bit Aktiver oppdateringsavbrudd i et register med et sigende navn

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
Avbryt aktiver register

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

Den andre forskjellen er knyttet til fenomenet multipleksing, som f.eks ghosting – parasittisk glød av dioder. I vårt tilfelle kan det vises på grunn av det faktum at timeren, etter å ha forårsaket et avbrudd på UEV, fortsetter å tikke, og avbruddsbehandleren har ikke tid til å bytte lysdioden før timeren begynner å skrive noe til pinnene. For å bekjempe dette, må du invertere logikken (0 = maksimal lysstyrke, 255 = ingenting lyser) og unngå ekstreme driftssyklusverdier. De. sørg for at LED-ene slukker helt etter UEV i én PWM-syklus.

Endring av polaritet:

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

Unngå å sette r, g og b til 255 og husk å snu dem når du bruker dem.

Avbryter

Essensen av et avbrudd er at under visse omstendigheter slutter brikken å kjøre hovedprogrammet og kaller en ekstern funksjon. Avbrudd oppstår på grunn av ytre eller indre påvirkninger, inkludert timeren.

Da vi først laget et prosjekt i ST Visual Develop, i tillegg til main.c vi mottok et vindu med en mystisk fil stm8_interrupt_vector.c, automatisk inkludert i prosjektet. I denne filen er en funksjon tildelt hvert avbrudd NonHandledInterrupt. Vi må binde funksjonen vår til ønsket avbrudd.

Dataarket har en tabell med avbruddsvektorer, der vi finner de vi trenger:

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8
13 TIM2 oppdatering/overløp
14 TIM2 fangst/sammenlign

Vi må bytte LED ved UEV, så vi trenger avbrudd #13.

Følgelig, for det første i filen stm8_interrupt_vector.c endre standardnavnet på funksjonen som er ansvarlig for avbrudd nr. 13 (IRQ13) til ditt eget:

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

For det andre må vi lage en fil main.h med følgende innhold:

#ifndef __MAIN_H
#define __MAIN_H

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

Og til slutt, skriv denne funksjonen i din 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;
}

Alt som gjenstår er å aktivere avbrudd. Dette gjøres ved å bruke assembler-kommandoen rim - du må lete etter det Programmeringshåndbok:

//enable interrupts
_asm("rim");

En annen assembler-kommando er sim – slår av avbrudd. De må slås av mens nye verdier skrives til "videominnet", slik at et avbrudd forårsaket i feil øyeblikk ikke ødelegger matrisen.

All kode - på GitHub.

Leser datablad 2: SPI på STM32; PWM, tidtakere og avbrudd på STM8

Hvis i det minste noen finner denne artikkelen nyttig, så skrev jeg den ikke forgjeves. Jeg vil gjerne motta kommentarer og kommentarer, jeg vil prøve å svare på alt.

Kilde: www.habr.com

Legg til en kommentar