DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Im ersten Teils Ich habe versucht, Hobby-Elektronikern, die aus den Shorts von Arduino herausgewachsen sind, zu erklÀren, wie und warum sie DatenblÀtter und andere Dokumentationen zu Mikrocontrollern lesen sollten. Der Text ist ziemlich lang geworden, daher habe ich versprochen, praktische Beispiele in einem separaten Artikel zu zeigen. Nun, was soll's, ich habe mich als Pilz bezeichnet...

Heute zeige ich, wie man mit Hilfe von DatenblĂ€ttern ziemlich einfache, aber notwendige Aufgaben fĂŒr viele Projekte auf den STM32 (Blue Pill) und STM8 Controllern löst. Alle Demo-Projekte sind meinen Lieblings-LEDs gewidmet, die wir in großen Mengen aktivieren werden, wozu wir verschiedene interessante PeripheriegerĂ€te einbeziehen mĂŒssen.

Der Text ist wieder sehr umfangreich geworden, daher mache ich zur besseren Übersicht ein Inhaltsverzeichnis:

STM32 Blue Pill: 16 LEDs mit DM634 Treiber
STM8: Sechs PWM-Pins einstellen
STM8: 8 RGB-LEDs an drei Pins, Interrupts

Haftungsausschluss: Ich bin kein Ingenieur und beanspruche kein tiefes Wissen in Elektronik, der Artikel richtet sich an Gleichgesinnte wie mich. TatsĂ€chlich habe ich als Zielgruppe mich selbst vor zwei Jahren betrachtet. Wenn mir damals jemand gesagt hĂ€tte, dass es nicht schlimm ist, die DatenblĂ€tter eines unbekannten Chips zu lesen, hĂ€tte ich nicht viel Zeit mit der Suche nach Codefragmente im Internet und der Erfindung von Provisorien mit Scheren und Pflastern verschwenden mĂŒssen.

Im Mittelpunkt dieses Artikels stehen die DatenblĂ€tter und nicht die Projekte, daher könnte der Code nicht besonders sauber sein und oft provisorisch wirken. Die Projekte selbst sind sehr einfach, auch wenn sie sich gut fĂŒr die erste Bekanntschaft mit einem neuen Chip eignen.

Ich hoffe, dass mein Artikel jemandem in einer Àhnlichen Phase des Einstiegs in das Hobby helfen wird.

STM32

16 LEDs mit DM634 und SPI

Ein kleines Projekt unter Verwendung des Blue Pill (STM32F103C8T6) und des LED-Treibers DM634. Mithilfe von DatenblÀttern werden wir den Treiber, die IO-Ports von STM verstehen und SPI einrichten.

DM634

Taiwanesischer Chip mit 16 16-Bit PWM-AusgĂ€ngen, kann in Ketten verbunden werden. Das kleinere 12-Bit Modell ist aus einem einheimischen Projekt bekannt. Lightpack. Zu seiner Zeit, als ich zwischen DM63x und dem bekannten TLC5940 wĂ€hlte, entschied ich mich aus mehreren GrĂŒnden fĂŒr DM: 1) Der TLC auf AliExpress ist sicher eine FĂ€lschung, dieser hier nicht; 2) Der DM hat eine eigenstĂ€ndige PWM mit eigenem Frequenzgenerator; 3) Ich konnte ihn gĂŒnstig in Moskau kaufen, anstatt auf ein Paket von Ali zu warten. Und natĂŒrlich war es interessant, selbst zu lernen, wie man den Chip steuert, anstatt eine fertige Bibliothek zu nutzen. Die Chips sind jetzt hauptsĂ€chlich im SSOP24-GehĂ€use verfĂŒgbar, was es einfach macht, sie auf einen Adapter zu löten.

Da der Hersteller taiwanesisch ist, Datenblatt ist die Beschreibung des Chips in einem Chinesisch-Englisch verfasst, deshalb wird es lustig. Zuerst schauen wir uns das Pinout an (Pin Connection), um zu verstehen, an welches Bein was angeschlossen wird, und die Beschreibung der Pins (Pin-Beschreibung). 16 Pins:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Stromquelle fĂŒr Stromfluss (Open Drain)

Sink / Open-drain output – Drain; Stromquelle fĂŒr den Stromfluss; AusgĂ€nge, die im aktiven Zustand mit der Erde verbunden sind – die LEDs werden an den Treiber mit den Kathoden angeschlossen. Elektrisch gesehen ist dies natĂŒrlich kein „Open Drain“ (open drain), aber in den DatenblĂ€ttern wird diese Bezeichnung fĂŒr Pins im Drain-Modus oft verwendet.

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Externe WiderstÀnde zwischen REXT und GND zur Einstellung des Ausgangsstroms

Zwischen dem Pin REXT und der Erde wird ein Referenzwiderstand installiert, der den inneren Widerstand der AusgĂ€nge steuert, siehe Diagramm auf Seite 9 des Datenblatts. Bei DM634 kann dieser Widerstand auch softwaregesteuert eingestellt werden, um die Gesamthelligkeit (global brightness); ich werde hier nicht ins Detail gehen, sondern einfach einen Widerstand von 2.2 – 3 kOhm einfĂŒgen.

Um zu verstehen, wie man den Chip steuert, schauen wir uns die Beschreibung der GerÀteschnittstelle an:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Aha, da ist er, das Chinesisch-Englisch in seiner vollen Pracht. Das zu ĂŒbersetzen ist problematisch; man kann es jedoch verstehen, wenn man möchte. Aber es gibt einen anderen Weg – wir schauen uns an, wie die Verbindung im Datenblatt zum funktional Ă€hnlichen TLC5940 beschrieben wird:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

 Zur Eingabe von Daten in das GerĂ€t sind nur drei Pins erforderlich. Die steigende Flanke des SCLK-Signals verschiebt die Daten vom Pin SIN in den internen Register. Nachdem alle Daten geladen sind, fixiert ein kurzer hoher Signalimpuls XLAT die nacheinander ĂŒbertragenen Daten in den internen Registern. Die internen Register sind Flanken-triggerte Register, die auf das Signal XLAT reagieren. Alle Daten werden mit dem höchstwertigen Bit zuerst ĂŒbertragen.

Latch – Schieber/Latch/Fixator.
Steigende Flanke – die steigende Flanke des Impulses
MSB zuerst – den höchsten (Ă€ußeren linken) Bit nach vorne.
Daten takten – Daten sequenziell (bitweise) ĂŒbertragen.

Das Wort latch kommt hĂ€ufig in der Dokumentation zu Chips vor und wird unterschiedlich ĂŒbersetzt, daher erlaube ich mir zur VerstĂ€ndigung

eine kleine EinfĂŒhrungDer LED-Treiber ist im Grunde ein Schieberegister. "Verschiebung" (Shift) im Namen bedeutet die bitweise Verschiebung von Daten innerhalb des GerĂ€ts: jedes neu eingefĂŒgte Bit schiebt die gesamte Reihe vor sich her. Da wĂ€hrend der Verschiebung niemand ein chaotisches Blitzen der LEDs beobachten möchte, geschieht der Prozess in Pufferregistern, die von einem Arbeitsverschluss (latch) getrennt sind – das ist sozusagen ein Vorzimmer, in dem die Bits in die richtige Reihenfolge gebracht werden. Wenn alles bereit ist, öffnet sich der Verschluss, und die Bits werden zur Arbeit geschickt, wobei sie die vorherige Charge ersetzen. Das Wort latch impliziert in der Dokumentation zu Mikrochips fast immer solch einen Verschluss, unabhĂ€ngig von den Kombinationen, in denen es verwendet wird.

So erfolgt die DatenĂŒbertragung im DM634: Wir setzen den Eingang DAI auf das höchste Bit der weitesten LED, ziehen DCK hoch und runter; setzen den Eingang DAI auf das nĂ€chste Bit, ziehen DCK; und so weiter, bis alle Bits ĂŒbertragen sind (eingetaktet), danach ziehen wir LAT. Das kann man manuell machen (bit-bang), aber es ist besser, ein speziell dafĂŒr optimiertes SPI-Interface zu verwenden, das glĂŒcklicherweise auf unserem STM32 in zwei Exemplaren verfĂŒgbar ist.

Die blaue Tablet STM32F103

EinfĂŒhrung: Die STM32-Controller sind deutlich komplizierter als Atmega328, als man vielleicht denken könnte. Dabei ist aus EnergiespargrĂŒnden zu Beginn fast alle Peripherie deaktiviert, und die Taktfrequenz betrĂ€gt 8 MHz von der internen Quelle. GlĂŒcklicherweise haben die Programmierer von STM einen Code geschrieben, der den Chip auf die "berechneten" 72 MHz bringt, und die Autoren aller mir bekannten IDEs haben ihn in das Initialisierungsverfahren integriert, sodass wir nichts takten mĂŒssen (aber es wĂ€re möglich, wenn man es wirklich möchte). Die Peripherie mĂŒssen wir jedoch aktivieren.

Dokumentation: Auf dem Blue Pill ist der beliebte Chip STM32F103C8T6 installiert, und es gibt zwei nĂŒtzliche Dokumente dazu:

  • Datenblatt fĂŒr die Mikrokontroller STM32F103x8 und STM32F103xB;
  • Handbuch fĂŒr die gesamte Reihe STM32F103 und darĂŒber hinaus.

Im Datenblatt sind fĂŒr uns folgende Punkte interessant:

  • Pinouts – Pinbelegungen der Chips – fĂŒr den Fall, dass wir Platinen selbst herstellen möchten.
  • Memory Map – eine Speicherkartierung fĂŒr einen bestimmten Chip. Im Referenzhandbuch gibt es eine Karte fĂŒr die gesamte Produktreihe, in der Register erwĂ€hnt werden, die in unserem nicht vorhanden sind.
  • Tabelle der Pin-Deklarationen – eine AufzĂ€hlung der Haupt- und Alternativfunktionen der Pins; fĂŒr die "blaue Pille" kann man im Internet praktischere Bilder mit einer Liste der Pins und ihrer Funktionen finden. Daher sofort nach dem Blue Pill Pinout googeln und solch ein Bild bereithalten:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
NB: Auf dem Bild aus dem Internet gab es einen Fehler, der in den Kommentaren angemerkt wurde, dafĂŒr danke. Das Bild wurde ersetzt, aber das ist eine Lektion – Informationen, die nicht aus DatenblĂ€ttern stammen, sollten besser ĂŒberprĂŒft werden.

Das Datenblatt entfernen, das Referenzhandbuch öffnen und ab jetzt nur noch damit arbeiten.
Handlungsablauf: Wir kĂŒmmern uns um den Standard-Ein- und Ausgang, konfigurieren SPI und aktivieren die benötigten PeripheriegerĂ€te.

Ein- und Ausgang

Bei der Atmega328 ist der Ein- und Ausgang Ă€ußerst einfach umgesetzt, weshalb die FĂŒlle an Optionen bei STM32 verwirren kann. Derzeit benötigen wir nur die AusgĂ€nge, aber selbst davon gibt es vier Varianten:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Offener Kollektor, Push-Pull, alternativer Push-Pull, alternativer offener Kollektor

"Push-Pull" (push-pull) – der vertraute Ausgang von Arduino, der Pin kann entweder den Wert HIGH oder LOW annehmen. Bei "offenem Kollektor" entstehen jedoch bei der Bereitstellung von Paketen in Distributionen schafft. Insbesondere verbieten die Nutzungsbedingungen der Marken von Mozilla die Beibehaltung des Projektnamens im Falle von Änderungen oder der Anwendung von Patches. Distributionen können das Paket nur unter den Namen Rust und Cargo verbreiten, wenn es aus den Original-Quelltexten erstellt wurde, andernfalls ist eine vorherige schriftliche Genehmigung des Rust Core Team erforderlich oder der Name muss geĂ€ndert werden. Diese Besonderheit erschwert die schnelle und eigenverantwortliche Behebung von Fehlern und Schwachstellen in Paketen mit Rust und Cargo ohne Abstimmung von Änderungen mit dem Upstream., obwohl es in Wirklichkeit ganz einfach ist:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Konfiguration des Ausgangs / wenn der Port als Ausgang eingestellt ist: / Ausgangspuffer aktiviert: / – offener Kollektor-Modus: "0" im Ausgangsregister aktiviert N-MOS, "1" im Ausgangsregister lĂ€sst den Port im Hi-Z-Modus (P-MOS wird nicht aktiviert) / – Push-Pull-Modus: "0" im Ausgangsregister aktiviert N-MOS, "1" im Ausgangsregister aktiviert P-MOS.

Der gesamte Unterschied zwischen offenem Kollektor (open drain) und Push-Pull (push-pull) besteht darin, dass der erste Pin keinen Zustand HIGH annehmen kann: Wenn man eine Eins in das Ausgangsregister schreibt, wechselt er in den Hochimpedanzmodus (high impedance, floating). Bei der Einspeisung von Null verhÀlt sich der Pin in beiden Modi sowohl logisch als auch elektrisch gleich.

Im normalen Ausgabemodus gibt der Pin einfach den Inhalt des Ausgangsregisters wieder. Im "alternativen" Modus wird er von der entsprechenden Peripherie gesteuert (siehe 9.1.4):

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Wenn das Bit des Ports als Ausgang der alternativen Funktion konfiguriert ist, wird das Ausgangsregister deaktiviert und der Pin wird mit dem Ausgangssignal der Peripherie verbunden.

Die alternativen Funktionen jedes Pins sind beschrieben in Pin Definitions Das Datenblatt ist auf dem heruntergeladenen Bild zu finden. Auf die Frage, was zu tun ist, wenn ein Pin mehrere alternative Funktionen hat, beantwortet eine Fußnote im Datenblatt:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Wenn mehrere Peripherieblöcke denselben Pin verwenden, sollte zur Vermeidung von Konflikten zwischen den alternativen Funktionen immer nur ein Peripherieblock gleichzeitig verwendet werden, indem man durch das Aktivierungsbit der Peripherietaktung (im entsprechenden RCC-Register) umschaltet.

ZusÀtzlich haben die Pins im Ausgangsmodus eine weitere Taktgeschwindigkeit. Dies ist ein weiteres Energiesparmerkmal, in unserem Fall stellen wir einfach auf Maximum und vergessen es.

Also: Wir verwenden SPI, das bedeutet, zwei Pins (fĂŒr Daten und Takt) sollten die 'alternative Funktion Ziehen-Schieben' haben, und ein weiterer (LAT) – 'gewöhnliches Ziehen-Schieben'. Aber bevor wir sie zuweisen, klĂ€ren wir das SPI.

SPI

Noch eine kleine Bildungseinheit

SPI oder Serial Peripheral Interface (serielle Peripherieschnittstelle) ist eine einfache und sehr effektive Schnittstelle zur Kommunikation zwischen Mikrocontrollern und der Außenwelt. Das Prinzip seiner Funktionsweise wurde bereits oben beschrieben, dort, wo es um den chinesischen LED-Treiber geht (im Referenzhandbuch siehe Abschnitt 25). SPI kann im Master- ('Besitzer') und Slave-('Sklave') Modus betrieben werden. SPI bietet vier grundlegende KanĂ€le, von denen nicht alle genutzt werden können:

  • MOSI, Master Output / Slave Input: Dieser Pin gibt im Master-Modus aus und empfĂ€ngt im Slave-Modus Daten;
  • MISO, Master Input / Slave Output: Im Master-Modus empfĂ€ngt er, im Slave-Modus gibt er aus;
  • SCK, Serial Clock: Dieser legt die DatenĂŒbertragungsrate im Master fest oder empfĂ€ngt das Taktsignal im Slave. Er bestimmt im Grunde die Bits;
  • SS, Slave Select: Über diesen Kanal erfĂ€hrt der Slave, dass er angesprochen wird. Bei STM32 wird dies als NSS bezeichnet, wobei N = negativ, d.h. der Controller wird zum Slave, wenn in diesem Kanal Masse liegt. Funktioniert gut im Modus Open Drain Output, aber das ist eine andere Geschichte.

Wie alles andere ist auch SPI auf STM32 funktional reich und macht es etwas kompliziert, es zu verstehen. Zum Beispiel kann es nicht nur im SPI-Modus, sondern auch im I2S-Modus arbeiten, wobei die Beschreibungen in der Dokumentation durcheinander gehen, das ÜberflĂŒssige muss rechtzeitig herausgefiltert werden. Unsere Aufgabe ist jedoch extrem einfach: Wir mĂŒssen nur Daten senden, indem wir nur MOSI und SCK verwenden. Gehe zu Abschnitt 25.3.4 (Half-Duplex-Kommunikation), wo wir finden 1 Takt und 1 unidirektionales Datenkabel (1 Takt und 1 unidirektionaler Datenstrom):

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
In diesem Modus verwendet die Anwendung SPI entweder im Modus nur Senden oder nur Empfangen. Der Modus nur Senden Ă€hnelt dem Duplexmodus: Daten werden ĂŒber den Sendepin (MOSI im Master-Modus oder MISO im Slave-Modus) ĂŒbertragen, wĂ€hrend der EmpfĂ€ngerpins (MISO oder MOSI entsprechend) als normale I/O-Pins verwendet werden können. In diesem Fall muss die Anwendung den Rx-Puffer ignorieren (wenn er gelesen wird, sind keine ĂŒbertragenen Daten vorhanden).

Toll, der Pin MISO ist jetzt frei, wir schließen das Signal LAT an. Lassen Sie uns mit dem Slave Select befassen, der auf STM32 programmatisch gesteuert werden kann, was Ă€ußerst praktisch ist. Lesen wir den gleichnamigen Absatz im Abschnitt 25.3.1 SPI Allgemeine Beschreibung:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Programmgesteuerte Steuerung von NSS (SSM = 1) / Informationen zur Slave-Auswahl befinden sich im Bit SSI des Registers SPI_CR1. Der externe Pin NSS bleibt fĂŒr andere Anforderungen der Anwendung frei.

Es ist an der Zeit, in die Register zu schreiben. Ich habe mich entschieden, SPI2 zu verwenden, wir suchen in den technischen Unterlagen nach seiner Basisadresse – im Abschnitt 3.3 Adresskarte:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Nun, dann fangen wir an:

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

Öffnen Sie Abschnitt 25.3.3 mit dem vielversprechenden Titel „Einrichtung von SPI im Master-Modus“:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

1. Stellen Sie die Taktrate des seriellen Interfaces ĂŒber die Bits BR[2:0] im Register SPI_CR1 ein.

Die Register sind im gleichnamigen Abschnitt des Referenzhandbuchs zusammengefasst. Der Adressversatz (Adressverschiebung) im CR1 betrĂ€gt 0x00. StandardmĂ€ĂŸig sind alle Bits zurĂŒckgesetzt (RĂŒcksetzwert 0x0000):

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Die Bits BR legen den Teiler der Taktrate des Controllers fest und bestimmen so die Frequenz, mit der SPI arbeitet. Die Frequenz des STM32 betrĂ€gt 72 MHz, der LED-Treiber arbeitet gemĂ€ĂŸ seinem Datenblatt mit einer Frequenz von bis zu 25 MHz, also muss auf vier geteilt werden (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. Stellen Sie die Bits CPOL und CPHA ein, um die Beziehungen zwischen DatenĂŒbertragung und Taktung des seriellen Interfaces zu bestimmen (siehe Diagramm auf Seite 240).

Da wir hier das Datenblatt lesen und keine SchaltplÀne betrachten, lassen Sie uns besser die textuelle Beschreibung der Bits CPOL und CPHA auf Seite 704 (SPI Allgemeine Beschreibung) studieren:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Phase und PolaritÀt des Taktsignals
Mit den Bits CPOL und CPHA des Registers SPI_CR1 können vier Varianten von Timing-Beziehungen programmiert werden. Das Bit CPOL (PolaritĂ€t des Taktsignals) steuert den Zustand des Taktsignals, wenn keine Daten ĂŒbertragen werden. Dieses Bit steuert die Master- und Slave-Modi. Wenn CPOL zurĂŒckgesetzt ist, befindet sich der Pin SCK im Ruhezustand auf einem niedrigen Pegel. Wenn das Bit CPOL gesetzt ist, befindet sich der Pin SCK im Ruhezustand auf einem hohen Pegel.
Wenn das CPHA-Bit (Taktsignalphase) gesetzt ist, fungiert die zweite Flanke des SCK-Signals (fallend, wenn CPOL zurĂŒckgesetzt ist, oder steigend, wenn CPOL gesetzt ist) als Strobe fĂŒr das höchstwertige Bit. Die Daten werden bei der zweiten Änderung des Taktsignals erfasst. Wenn das CPHA-Bit zurĂŒckgesetzt ist, wird die vordere Flanke des SCK-Signals (fallend, wenn CPOL gesetzt ist, oder steigend, wenn CPOL zurĂŒckgesetzt ist) als Strobe fĂŒr das höchstwertige Bit verwendet. Die Daten werden bei der ersten Änderung des Taktsignals erfasst.

Mit diesen Erkenntnissen kommen wir zu dem Schluss, dass beide Bits Null bleiben mĂŒssen, da das SCK-Signal niedrig bleiben soll, wenn es nicht verwendet wird, wĂ€hrend die Daten bei der vorderen Flanke des Impulses ĂŒbertragen werden (siehe Rising Edge im DM634-Datenblatt).

Übrigens sind wir hier zum ersten Mal auf eine Lexikbesonderheit in den ST-DatenblĂ€ttern gestoßen: dort wird der Ausdruck „bit auf null zurĂŒcksetzen“ geschrieben to reset a bit, nicht to clear a bit, wie beispielsweise bei der Atmega.

3. Setzen Sie das DFF-Bit zur Festlegung des 8-Bit- oder 16-Bit-Datenblockformats.

Ich habe absichtlich den 16-Bit-DM634 gewĂ€hlt, um mir die Übertragung von 12-Bit-PWM-Daten, wie beim DM633, zu ersparen. Es macht Sinn, das DFF auf Eins zu setzen:

#define DFF         0x0800

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

4. Konfigurieren Sie das LSBFIRST-Bit im SPI_CR1-Register, um das Blockformat festzulegen.

LSBFIRST, wie der Name schon sagt, konfiguriert die Übertragung mit dem niederwertigsten Bit zuerst. Aber der DM634 möchte die Daten beginnend mit dem höchstwertigen Bit empfangen. Daher lassen wir es zurĂŒckgesetzt.

5. Im Hardwaremodus, wenn ein Eingangs-Pin NSS erforderlich ist, legen Sie wĂ€hrend der gesamten Byte-Übertragungssequenz ein hohes Signal an den NSS-Pin an. Im Softwaremodus setzen Sie die Bits SSM und SSI im SPI_CR1-Register. Soll der NSS-Pin als Ausgang arbeiten, muss nur das Bit SSOE gesetzt werden.

Setzen Sie SSM und SSI, um den Hardwaremodus des NSS zu ignorieren:

#define SSI         0x0100
#define SSM         0x0200

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

6. Die Bits MSTR und SPE mĂŒssen gesetzt sein (sie bleiben nur gesetzt, wenn am NSS ein hohes Signal angelegt wird).

Mit diesen Bits ernennen wir unseren SPI zum Master und aktivieren ihn:

#define MSTR        0x0004
#define SPE         0x0040

_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//ĐșĐŸĐłĐŽĐ° ĐČсД ĐłĐŸŃ‚ĐŸĐČĐŸ, ĐČĐșĐ»ŃŽŃ‡Đ°Đ”ĐŒ SPI
_SPI2_ (_SPI_CR1) |= SPE;

SPI konfiguriert, lassen Sie uns sofort Funktionen schreiben, die Bytes an den Treiber senden. Lassen Sie uns mit 25.3.3 "Konfiguration von SPI im Mastermodus" fortfahren:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Übertragungsreihenfolge der Daten
Die Übertragung beginnt, wenn ein Byte in den Tx-Puffer geschrieben wird.
Das Datenbyte wird in das Schieberegister geladen in parallelem Modus (aus dem internen Bus) wĂ€hrend der Übertragung des ersten Bits, wonach es ĂŒbertragen wird. sequentieller im MOSI-Pin-Modus, entweder mit dem ersten oder letzten Bit zuerst, abhĂ€ngig von der Einstellung des LSBFIRST-Bits im CPI_CR1-Register. Das TXE-Flag wird nach der DatenĂŒbertragung gesetzt. vom Tx-Puffer in das Shift-Register, und es wird auch eine Unterbrechung erzeugt, wenn das TXEIE-Bit im CPI_CR1-Register gesetzt ist.

Ich habe einige Wörter in der Übersetzung hervorgehoben, um auf eine Besonderheit der Implementierung von SPI in STM-Controllern hinzuweisen. Bei der Atmega wird das TXE-Flag (Tx leer, Tx ist leer und bereit, Daten zu empfangen) erst gesetzt, nachdem das gesamte Byte gesendet wurde nach außen.Hier hingegen wird dieses Flag gesetzt, nachdem das Byte in das interne Shift-Register geschoben wurde. Da es dort alle Bits gleichzeitig (parallel) hinein geschoben wird und die Daten dann sequentiell ĂŒbertragen werden, wird TXE gesetzt, bevor das Byte vollstĂ€ndig gesendet wurde. Das ist wichtig, denn in unserem LED-Treiber mĂŒssen wir den LAT-Pin nach der Übertragung Umgebungen und Clustern des Projekts verwendet wird. Dieses Prinzip bildet die Grundlage fĂŒr ein gutes der Daten ansteuern, d.h. nur mit dem TXE-Flag reicht es uns nicht aus.

Das bedeutet, dass wir ein weiteres Flag benötigen. Schauen wir in 25.3.7 – „Statusflags“:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
<
>
DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Das BUSY-Flag
Das BSY-Flag wird hardwareseitig gesetzt und zurĂŒckgesetzt (ein Schreiben hat keine Auswirkung). Das BSY-Flag zeigt den Status der SPI-Kommunikationsschicht an.
Es wird zurĂŒckgesetzt,
wenn die Übertragung abgeschlossen ist (außer im Master-Modus, wenn die Übertragung kontinuierlich ist)
wenn SPI deaktiviert wird
wenn ein Fehler im Master-Modus auftritt (MODF=1)
Ist die Übertragung nicht kontinuierlich, wird das BSY-Flag zwischen jeder DatenĂŒbertragung zurĂŒckgesetzt.

Okay, das ist nĂŒtzlich. Lassen Sie uns herausfinden, wo sich der Tx-Puffer befindet. Dazu lesen wir das „SPI-Datenregister“:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Bits 15:0 DR[15:0] Datenregister
Die empfangenen Daten oder die zu ĂŒbertragenden Daten.
Das Datenregister ist in zwei Puffer unterteilt – einen zum Schreiben (Übertragungspuffer) und einen zum Lesen (Empfangspuffer). Ein Schreiben ins Datenregister schreibt in den Tx-Puffer, und ein Lesen aus dem Datenregister gibt den Inhalt des Rx-Puffers zurĂŒck.

Und das Statusregister, wo die Flags TXE und BSY zu finden sind:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Wir schreiben:

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

Da wir 16 Mal zwei Bytes ĂŒbertragen mĂŒssen, entsprechend der Anzahl der AusgĂ€nge des LED-Treibers, sieht das ungefĂ€hr so aus:

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

    while (_SPI2_(_SPI_SR) & BSY); // Übertragung beenden

    LAT_pulse();
}

Aber da wir den LAT-Pin noch nicht ansteuern können, kehren wir zu I/O zurĂŒck.

Wir ordnen die Pins zu.

Bei STM32F1 sind die Register, die den Status der Pins steuern, recht ungewöhnlich. Es ist klar, dass es mehr davon gibt als bei der Atmega, aber sie unterscheiden sich auch von anderen STM-Chips. Abschnitt 9.1 Allgemeine Beschreibung von GPIO:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Jeder der allgemeinen Eingangs-/Ausgangsports (GPIO) verfĂŒgt ĂŒber zwei 32-Bit-Konfigurationsregister (GPIOx_CRL und GPIOx_CRH), zwei 32-Bit-Datenregister (GPIOx_IDR und GPIOx_ODR), ein 32-Bit-Setzen/RĂŒcksetzen-Register (GPIOx_BSRR), ein 16-Bit-Reset-Register (GPIOx_BRR) und ein 32-Bit-Sperrregister (GPIOx_LCKR).

Ungewöhnlich und auch ziemlich unpraktisch sind hier die ersten beiden Register, weil 16 Pins des Ports ĂŒber sie verteilt sind im Format „vier Bits pro Pin“. D.h. die Pins von null bis sieben sind im CRL, wĂ€hrend die anderen im CRH sind. Die ĂŒbrigen Register fassen die Bits aller Pins des Ports erfolgreich zusammen – oft bleiben sie halb „reserviert“.

Zur Vereinfachung beginnen wir am Ende der Liste.

Das Sperrregister benötigen wir nicht.

Die Setzen- und RĂŒcksetzen-Register sind recht amĂŒsant, da sie sich teilweise duplizieren: Man kann alles nur im BSRR schreiben, wo die oberen 16 Bits den Pin auf Null setzen, wĂ€hrend die unteren ihn auf 1 setzen, oder man verwendet auch BRR, dessen unteren 16 Bits nur den Pin zurĂŒcksetzen. Mir gefĂ€llt die zweite Variante besser. Diese Register sind wichtig, da sie atomaren Zugriff auf die Pins ermöglichen:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Atomare Setzen oder RĂŒcksetzen
Es ist nicht notwendig, Interrupts beim Programmieren von GPIOx_ODR auf Bit-Ebene zu deaktivieren: Man kann einen oder mehrere Bits mit einer atomaren Schreiboperation auf APB2 Ă€ndern. Dies geschieht, indem man eine „1“ in das Setzen/RĂŒcksetzen-Register (GPIOx_BSRR oder, nur zum ZurĂŒcksetzen, in GPIOx_BRR) des Bits schreibt, das man Ă€ndern möchte. Die ĂŒbrigen Bits bleiben unverĂ€ndert.

Die Datenregister haben recht sprechende Namen – IDR = Input Richtungsregister, Eingangsregister; ODR = Ausgabe Richtungsregister, Ausgangsregister. In dem aktuellen Projekt benötigen wir sie nicht.

Und schließlich die Steuerregister. Da uns die Pins des zweiten SPI interessieren, insbesondere PB13, PB14 und PB15, schauen wir uns sofort das CRH an:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Und sehen, dass wir etwas in die Bits von 20 bis 31 schreiben mĂŒssen.

Wir haben bereits oben geklĂ€rt, was wir von den Pins erwarten, daher kann ich hier auf einen Screenshot verzichten und einfach sagen, dass MODE die Richtung festlegt (Eingang, wenn beide Bits auf 0 gesetzt sind) und die Geschwindigkeit des Pins (wir benötigen 50MHz, d.h. beide Pins auf „1“), wĂ€hrend CNF den Modus festlegt: normaler „Ziehen und Schieben“ – 00, „alternativ“ – 10. StandardmĂ€ĂŸig, wie oben zu sehen, ist bei allen Pins das drittniedrigste Bit (CNF0) gesetzt, das sie in den Modus versetzt schwebender Eingang.

Da ich plane, noch etwas mit diesem Chip zu machen, habe ich zur Vereinfachung alle möglichen Werte fĂŒr MODE und CNF sowohl fĂŒr das untere als auch das obere Steuerregister definiert.

So sieht das aus

#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

Unsere Pins befinden sich am Port B (Basisadresse – 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;

Und entsprechend können Definitionen fĂŒr LAT geschrieben werden, die die Register BRR und BSRR steuern:

/*** 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 einfach aus Gewohnheit, das war schon immer so, soll so bleiben)

Jetzt ist alles gut, funktioniert aber nicht. Weil dies ein STM32 ist, der Strom spart, mĂŒssen wir die Taktung der erforderlichen Peripherie aktivieren.

Aktivierung der Taktung

FĂŒr die Taktung sind die Uhren zustĂ€ndig, ebenso wie der Clock. Und wir haben bereits die AbkĂŒrzung RCC bemerkt. Suchen wir sie in der Dokumentation: Das steht fĂŒr Reset and Clock Control (Reset- und Taktsteuerung).

Wie oben erwĂ€hnt, haben die Leute von STM uns glĂŒcklicherweise die komplexesten Teile des Taktthemas abgenommen, dafĂŒr ein großes Dankeschön (ich gebe erneut den Link zu Di Halt’s Website, um zu verdeutlichen, wie kompliziert das ist). Wir benötigen lediglich die Register, die fĂŒr die Aktivierung der Taktung der Peripherie (Peripheral Clock Enable Registers) verantwortlich sind. Zuerst finden wir die Basisadresse RCC, die ganz oben in der „Speicherkarte“ zu finden ist:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

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

Dann entweder den Link anklicken und in der Tabelle nach etwas suchen, oder viel besser, die Beschreibungen der aktivierenden Register in den Abschnitten ĂŒber AktivierungsregisterDurchsuchen. Dort finden wir RCC_APB1ENR und RCC_APB2ENR:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Und in diesen befinden sich entsprechend die Bits, die die Taktung von SPI2, IOPB (I/O Port B) und alternativen Funktionen (AFIO) aktivieren.

#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 finalen Code findet man hier.

Wenn es die Möglichkeit und den Wunsch gibt zu testen, dann verbinden wir DM634 so: DAI zu PB15, DCK zu PB13, LAT zu PB14. Wir speisen den Treiber mit 5 Volt, vergessen nicht, die Masse zu verbinden.

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

STM8 PWM

PWM auf STM8

Als ich diesen Artikel plante, beschloss ich, zum Beispiel die Funktionen eines mir unbekannten Chips nur anhand des Datenblatts zu erkunden, damit ich nicht wie ein Schuhmacher ohne Schuhe dastehe. Der STM8 eignete sich perfekt fĂŒr diese Rolle: Erstens hatte ich ein paar chinesische Boards mit STM8S103, und zweitens ist er nicht allzu populĂ€r, sodass die Versuchung, im Internet nach einer Lösung zu suchen, an dem Fehlen der Lösungen scheitert.

Der Chip hat auch ein Datenblatt und Referenzhandbuch RM0016, das im ersten Fall die Pinbelegung und die Adressen der Register enthÀlt, im zweiten alles andere. Der STM8 wird in einer etwas eingeschrÀnkten IDE ST Visual Develop.

programmiert.

StandardmĂ€ĂŸig arbeitet der STM8 mit einer Frequenz von 2 MHz, das sollte sofort geĂ€ndert werden.

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Der Taktsignal HSI (High-Speed Internal)
stammt von einem internen 16-MHz-RC-Oszillator mit einem programmierbaren Teiler (von 1 bis 8). Er wird im Register fĂŒr den Taktsignalteiler (CLK_CKDIVR) eingestellt.
Hinweis: Beim Start wird der HSI-RC-Oszillator mit einem Teiler von 8 als Haupttaktquelle gewÀhlt.

Wir suchen die Adresse des Registers im Datenblatt, die Beschreibung im Refman und sehen, dass das Register gelöscht werden muss:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Da wir PWM starten und LEDs anschließen wollen, schauen wir uns die Pinbelegung an:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Der Chip ist klein, viele Funktionen sind an die gleichen Pins gebunden. Das, was in quadratischen Klammern steht, ist "alternativer Funktionsumfang", der durch "Optionsbytes" umgeschaltet wird (Optionsbytes) – etwas wie die Fuses der Atmega. Man kann ihre Werte programmatisch Ă€ndern, aber das ist nicht nötig, da die neuen Funktionen nur nach einem Neustart aktiviert werden. Es ist einfacher, den ST Visual Programmer zu verwenden (er wird zusammen mit Visual Develop heruntergeladen), der diese Bytes Ă€ndern kann. In der Pinbelegung sieht man, dass die AusgĂ€nge CH1 und CH2 des ersten Timers in quadratischen Klammern versteckt sind; man muss im STVP die Bits AFR1 und AFR0 setzen, wobei das zweite auch den Ausgang CH1 des zweiten Timers von PD4 auf PC5 verschiebt.

So werden insgesamt 6 Pins die LEDs steuern: PC6, PC7 und PC3 fĂŒr den ersten Timer, PC5, PD3 und PA3 fĂŒr den zweiten.

Die Konfiguration der I/O-Pins des STM8 ist einfacher und logischer als beim STM32:

  • Register fĂŒr die Datenrichtung DDR (Data Direction Register): 1 = Ausgang;
  • Der erste Kontrollregister CR1 legt im Ausgangsbetrieb den Modus „Push-Pull“ (1) oder offenen Kollektor (0) fest; da ich die LEDs mit Kathoden an den Chip anschließe, lasse ich hier 0.
  • Der zweite Kontrollregister CR2 legt im Ausgangsbetrieb die Taktschaltungsgeschwindigkeit fest: 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-Einstellung

ZunÀchst definieren wir die Begriffe:

  • PWM-Frequenz – die Frequenz, mit der der Timer tickt;
  • Auto-Reload, AR – der automatisch ladende Wert, bis zu dem der Timer zĂ€hlt (Impulsdauer);
  • Update-Ereignis, UEV – ein Ereignis, das auftritt, wenn der Timer bis AR gezĂ€hlt hat;
  • PWM-Duty-Cycle – der TastverhĂ€ltnis im PWM, oft als "TastverhĂ€ltnis" bezeichnet;
  • Capture/Compare-Wert – der Wert fĂŒr das Erfassen/Vergleichen, bis zu dem der Timer eine Aktion ausfĂŒhrt (im Falle von PWM – invertiert das Ausgangssignal);
  • Preload-Wert – vorab geladener Wert. Der Vergleichswert darf sich nicht Ă€ndern, solange der Timer tickt, andernfalls wĂŒrde der PWM-Zyklus fehlerhaft sein. Neue ĂŒbergebene Werte werden daher in einen Puffer gelegt und von dort abgerufen, wenn der Timer das Ende seiner ZĂ€hlung erreicht und zurĂŒckgesetzt wird;
  • Edge-aligned und Center-aligned-Modi – Ausrichtung am Rand und in der Mitte, das entspricht den atmel'schen Fast PWM und Phasen-korrekt PWM.
  • OCiREF, Ausgangsvergleichsreferenzsignal – das Referenz-Ausgangssignal, das im PWM-Modus tatsĂ€chlich am entsprechenden Pin anliegt.

Wie bereits aus der Pinbelegung ersichtlich, haben zwei Timer – der erste und der zweite – PWM-FunktionalitĂ€t. Beide sind 16-Bit, der erste hat viele zusĂ€tzliche Funktionen (unter anderem kann er sowohl aufwĂ€rts als auch abwĂ€rts zĂ€hlen). Wir möchten, dass beide gleich arbeiten, deshalb habe ich beschlossen, mit dem bewusst Ă€rmeren zweiten zu beginnen, um nicht versehentlich etwas zu verwenden, das ihm fehlt. Ein kleines Problem besteht darin, dass die Beschreibung der PWM-FunktionalitĂ€t aller Timer im Referenzhandbuch im Kapitel ĂŒber den ersten Timer (17.5.7 PWM-Modus) zu finden ist, sodass ich stĂ€ndig hin und her springen muss.

PWM auf STM8 hat einen wichtigen Vorteil gegenĂŒber PWM der Atmega:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
PWM mit Randausrichtung
ZÀhlkonfiguration aufwÀrts
Der ZĂ€hlbetrieb aufwĂ€rts ist aktiv, wenn das Bit DIR im Register TIM_CR1 zurĂŒckgesetzt ist.
Beispiel
Das Beispiel verwendet den ersten PWM-Modus. Das Referenzsignal PWM OCiREF bleibt auf hohem Niveau, solange TIM1_CNT < TIM1_CCRi. Andernfalls nimmt es ein niedriges Niveau an. Wenn der Vergleichswert im Register TIM1_CCRi grĂ¶ĂŸer ist als der Auto-Reload-Wert (Register TIM1_ARR), bleibt das Signal OCiREF auf 1. Wenn der Vergleichswert 0 betrĂ€gt, bleibt OCiREF auf null.


Der Timer STM8 prĂŒft wĂ€hrend des Update-Ereignisses zuerst den Vergleichswert, und erst dann gibt er das Referenzsignal aus. Bei Atmega arbeitet der Timer zuerst und vergleicht dann, was dazu fĂŒhrt, dass bei Vergleichswert == 0 Am Ende ergibt sich eine Nadel, mit der man irgendwie umgehen muss (zum Beispiel durch programmatische Inversion der Logik).

Also, was wollen wir tun: 8-Bit PWM (AR == 255), zĂ€hlen von unten nach oben, Ausrichtung am Rand. Da die Lampen ĂŒber Kathoden mit dem Chip verbunden sind, sollte PWM 0 (LED leuchtet) bis Vergleichswert und 1 danach ausgeben.

Wir haben bereits ĂŒber einige PWM-Modigelesen, also suchen wir den entsprechenden Register des zweiten Timers im Referenzhandbuch anhand dieser Phrase (18.6.8 – TIMx_CCMR1):

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
110: Erster PWM-Modus – beim ZĂ€hlen von unten nach oben ist der erste Kanal aktiv, solange TIMx_CNT < TIMx_CCR1. Andernfalls ist der erste Kanal nicht aktiv. [weiter im Dokument ein fehlerhafter Copy-Paste aus Timer 1]
111: Zweiter PWM-Modus – beim ZĂ€hlen von unten nach oben ist der erste Kanal nicht aktiv, solange TIMx_CNT < TIMx_CCR1. Andernfalls ist der erste Kanal aktiv.

Da die LEDs ĂŒber Kathoden mit dem MK verbunden sind, passt uns der zweite Modus (der erste auch, aber das wissen wir momentan noch nicht).

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Bit 3 OC1PE: Auslösen der Vorladung von Ausgang 1
0: Der Vorlade-Register auf TIMx_CCR1 ist ausgeschaltet. Schreiben in TIMx_CCR1 ist jederzeit möglich. Der neue Wert wird sofort wirksam.
1: Der Vorlade-Register auf TIMx_CCR1 ist eingeschaltet. Lese-/Schreiboperationen betreffen das Vorlade-Register. Der vorab geladene Wert von TIMx_CCR1 wird bei jedem Aktualisierungsereignis in das Schattenregister geladen.
*Hinweis: Damit der PWM-Modus korrekt funktioniert, mĂŒssen die Vorlade-Register aktiviert sein. Dies ist im Einzelimpulsmodus nicht erforderlich (im Register TIMx_CR1 ist das Bit OPM gesetzt).

Okay, wir aktivieren alles, was fĂŒr die drei KanĂ€le des zweiten Timers erforderlich ist:

#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 besteht aus zwei 8-Bit-Registern, hier ist alles einfach:

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

TIM2_ARRH = 0;
TIM2_ARRL = 255;

Der zweite Timer kann nur von unten nach oben zĂ€hlen, Ausrichtung am Rand, da muss nichts geĂ€ndert werden. Wir setzen den Frequenzteiler zum Beispiel auf 256. Der Teiler fĂŒr den zweiten Timer wird im Register TIM2_PSCR eingestellt und ist eine Potenz von zwei:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Nun mĂŒssen wir die AusgĂ€nge und den zweiten Timer selbst aktivieren. Die erste Aufgabe wird mit den Registern Capture/Compare Aktivieren: es gibt zwei, drei KanĂ€le sind asymmetrisch verteilt. Hier erfahren wir auch, dass wir die PolaritĂ€t des Signals Ă€ndern können, d.h. grundsĂ€tzlich hĂ€tte man auch PWM Mode 1 verwenden können. Wir schreiben:

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

Und schließlich starten wir den Timer im Register TIMx_CR1:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Wir werden eine einfache Analogie zu AnalogWrite() schreiben, die dem Timer die Werte zum Vergleichen ĂŒbergibt. Die Register heißen vorhersehbar Capture/Compare-Register, zwei pro Kanal: die unteren 8 Bit in TIM2_CCRxL und die oberen in TIM2_CCRxH. Da wir einen 8-Bit-PWM-Timer implementiert haben, reicht es aus, nur die unteren Bits zu schreiben:

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

Ein aufmerksamer Leser wird bemerken, dass wir eine leicht fehlerhafte PWM erhalten haben, die nicht in der Lage ist, 100% Duty Cycle abzugeben (bei einem maximalen Wert von 255 wird das Signal fĂŒr einen Timerzyklus umgekehrt). FĂŒr LEDs spielt das keine Rolle, und der aufmerksame Leser kann sich bereits denken, wie man das behebt.

Die PWM des zweiten Timers funktioniert, jetzt wenden wir uns dem ersten zu.

Der erste Timer hat genau die gleichen Bits in den gleichen Registern (nur die Bits, die im zweiten Timer „reserviert“ waren, werden im ersten aktiv fĂŒr verschiedene fortgeschrittene Funktionen verwendet). Daher reicht es aus, die Adressen dieser Register im Datenblatt zu finden und den Code zu kopieren. Außerdem muss der Wert des Frequenzteilers geĂ€ndert werden, da der erste Timer keinen Potenz von zwei, sondern einen genauen 16-Bit-Wert in zwei Registern benötigt. Prescaler High und Niedrig. Wir machen alles und
 der erste Timer funktioniert nicht. Was ist das Problem?

Das Problem kann nur gelöst werden, indem wir den gesamten Abschnitt ĂŒber die Steuerregister des Timers 1 durchsehen, wo wir nach dem suchen, das es im zweiten Timer nicht gibt. Es wird gefunden: 17.7.30 Break register (TIM1_BKR), wo es ein solches Bit gibt:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Den Hauptausgang aktivieren

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Jetzt stimmt alles, der Code dort.

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

STM8 Multiplex

Multiplexing auf STM8

Das dritte Mini-Projekt besteht darin, acht RGB-LEDs im PWM-Modus an den zweiten Timer anzuschließen und verschiedene Farben anzuzeigen. Basierend auf der Konzept des LED-Multiplexings, bei dem es darum geht, dass, wenn wir die LEDs sehr, sehr schnell ein- und ausschalten, es uns so erscheint, als ob sie stĂ€ndig leuchten (persistence of vision, die TrĂ€gheit der visuellen Wahrnehmung). FrĂŒher habe ich so etwas auf Arduino gemacht..

Der Arbeitsablauf sieht folgendermaßen aus:

  • Wir verbinden die Anode der ersten RGB-LED;
  • wir schalten sie ein, indem wir die erforderlichen Signale an die Kathoden senden;
  • wir warten auf das Ende des PWM-Zyklus;
  • wir verbinden die Anode der zweiten RGB-LED;
  • wir schalten sie ein


Und so weiter. NatĂŒrlich ist es fĂŒr ein schönes Funktionieren erforderlich, dass die Verbindung der Anode und das "ZĂŒnden" der LED gleichzeitig stattfinden. Oder fast gleichzeitig. In jedem Fall mĂŒssen wir Code schreiben, der in drei KanĂ€len des zweiten Timers Werte ausgibt, die beim Erreichen von UEV geĂ€ndert werden und gleichzeitig die aktuell aktive RGB-LED wechseln.

Da das Umschalten der LEDs automatisch erfolgt, mĂŒssen wir einen "Videospeicher" erstellen, aus dem der Interrupt-Handler die Daten beziehen kann. Dies ist ein einfacher Array:

uint8_t colors[8][3];

Um die Farbe einer bestimmten LED zu Ă€ndern, reicht es aus, die entsprechenden Werte in dieses Array zu schreiben. Die Variable, die fĂŒr die Nummer der aktiven LED verantwortlich ist, heißt

uint8_t cnt;

Demux

FĂŒr das korrekte Multiplexing benötigen wir, erstaunlicherweise, den Demultiplexer CD74HC238. Der Demultiplexer ist ein Chip, der den Operator <<hardwaremĂ€ĂŸig implementiert. Über drei Eingangspins (Bits 0, 1 und 2) geben wir ihm eine dreibitige Zahl X, und er aktiviert als Antwort den Ausgang Nummer (1<<X). Die anderen EingĂ€nge des Chips werden zur Skalierung des gesamten Aufbaus verwendet. Wir brauchen diesen Chip nicht nur, um die Anzahl der belegten Pins des Mikrocontrollers zu reduzieren, sondern auch zur Sicherheit – um versehentlich nicht mehr LEDs zu aktivieren, als zulĂ€ssig ist, und um den mikrokontroller nicht zu zerstören. Der Chip kostet kaum etwas und sollte immer in der Hausapotheke erhĂ€ltlich sein.

Der CD74HC238 wird dafĂŒr zustĂ€ndig sein, die Spannung zum Anoden der erforderlichen LED zu liefern. Im vollstĂ€ndigen Multiplexbetrieb wĂŒrde er die Spannung ĂŒber einen P-MOSFET an eine Spalte liefern, aber in dieser Demo kann es auch direkt erfolgen, da er laut absoluten Höchstwerte Datenblatt 20 mA zieht. Aus dem Datenblatt des CD74HC238

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
benötigen wir die Pinbelegung und diese Anleitung hier:

H = hoher Spannungspegel, L = niedriger Spannungspegel, X – egal

Wir schließen E2 und E1 an die Erde an, E3, A0, A1 und A3 an die Pins PD5, PC3, PC4 und PC5 des STM8. Da die obere Tabelle sowohl niedrige als auch hohe Pegel enthĂ€lt, konfigurieren wir diese Pins als Push-Pull-AusgĂ€nge.

PWM

Die PWM am zweiten Timer wird genauso konfiguriert wie in der vorherigen Geschichte, mit zwei Unterschieden: Erstens mĂŒssen wir das Interrupt fĂŒr Update Event (UEV) aktivieren, welches die Funktion aufruft, die die aktive LED umschaltet. Dies geschieht durch Anpassen des Bits Update Interrupt Enable

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
im Register mit dem sprechenden Namen

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

Interrupt Enable Register Der zweite Unterschied betrifft das PhĂ€nomen des Multiplexings, das als – parasitĂ€res Leuchten von Dioden. In unserem Fall kann es auftreten, weil der Timer, der einen Interrupt bei UEV auslöst, weiter tickt und der Interrupt-Handler es nicht rechtzeitig schafft, die LED umzuschalten, bevor der Timer bereits mit dem Schreiben in die AusgĂ€nge beginnt. Um dem entgegenzuwirken, mĂŒssen wir die Logik umkehren (0 = maximale Helligkeit, 255 = nichts leuchtet) und extreme Werte der TastverhĂ€ltnisse vermeiden. D.h., wir mĂŒssen sicherstellen, dass nach UEV die Dioden fĂŒr einen PWM-Takt vollstĂ€ndig erlöschen.

Polarisierung Àndern:

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

Wir vermeiden es, r, g und b auf 255 zu setzen und denken daran, sie bei der Verwendung umzukehren.

Interrupts

Die Idee eines Interrupts besteht darin, dass der Chip unter bestimmten UmstĂ€nden die Hauptprogrammverarbeitung stoppt und eine externe Funktion aufruft. Interrupts entstehen durch externe oder interne EinflĂŒsse, einschließlich des Timers.

Als wir zum ersten Mal ein Projekt in ST Visual Develop erstellt haben, erhielten wir neben main.c ein Fenster mit einer mysteriösen Datei stm8_interrupt_vector.c, die automatisch in das Projekt einbezogen wurde. In dieser Datei ist einer Funktion fĂŒr jeden Interrupt zugewiesen NonHandledInterrupt. Wir mĂŒssen unsere Funktion dem entsprechenden Interrupt zuordnen.

Im Datenblatt gibt es eine Tabelle der Interruptvektoren, wo wir die benötigten finden:

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
13 TIM2 Update/Überlauf
14 TIM2 Capture/Vergleich

Wir mĂŒssen die LED bei UEV Ă€ndern, also benötigen wir Interrupt Nr. 13.

Daher mĂŒssen wir zunĂ€chst in der Datei stm8_interrupt_vector.c den Funktionsnamen, der fĂŒr Interrupt Nr. 13 (IRQ13) standardmĂ€ĂŸig zustĂ€ndig ist, in unseren umbenennen:

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

Zweitens mĂŒssen wir eine Datei main.h mit folgendem Inhalt erstellen:

#ifndef __MAIN_H
#define __MAIN_H

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

Und schließlich diese Funktion in unserem main.c:

@far @interrupt void TIM2_Overflow (void)
{
    PD_ODR &= ~(1<<5); // Demultiplexer ausschalten
    PC_ODR = (cnt<<3); // Neues Wert im Demultiplexer speichern
    PD_ODR |= (1<<5); // Demultiplexer einschalten

    TIM2_SR1 = 0; // Update Interrupt Pending Flag zurĂŒcksetzen

    cnt++; 
    cnt &= 7; // LED-ZĂ€hler bewegen

    TIM2_CCR1L = ~colors[cnt][0]; // Invertierte Werte in den Puffer ĂŒbergeben
    TIM2_CCR2L = ~colors[cnt][1]; // FĂŒr den nĂ€chsten PWM-Zyklus
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Es bleibt, die Interrupts zu aktivieren. Das geschieht mit dem Assemblerbefehl rim – ihn mĂŒssen wir in Programmiertutorial:

//enable interrupts
_asm("rim");

Ein anderer Assemblerbefehl – sim – schaltet die Interrupts aus. Sie mĂŒssen wĂ€hrend der Speicherung neuer Werte im „Videospatial“ deaktiviert werden, damit ein unglĂŒcklicher Moment des Interrupts das Array nicht beschĂ€digt.

Der gesamte Code – auf GitHub.

DatenblÀtter lesen 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Wenn dieser Artikel auch nur einer Person nĂŒtzlich ist, dann habe ich ihn nicht umsonst geschrieben. Ich freue mich auf Kommentare und Anmerkungen und werde versuchen, auf alles zu antworten.

Quelle: habr.com

60GB SSD 8Gb DDR4