
Im 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:
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. . 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, 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:

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.
![]()
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:

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:

⊠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 ). 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:
- fĂŒr die Mikrokontroller STM32F103x8 und STM32F103xB;
- 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:

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:

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 , obwohl es in Wirklichkeit ganz einfach ist:


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):
![]()
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:
![]()
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):

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:

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:
![]()
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â:

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):

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/42. 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:

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 mode4. 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 high6. 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:

Ă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â:

<âŠ>

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â:

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:

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:

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:

![]()
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:

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 0x20000000Unsere 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 , 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:
![]()
#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:


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 .
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.

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 und , 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 .
programmiert.
StandardmĂ€Ăig arbeitet der STM8 mit einer Frequenz von 2 MHz, das sollte sofort geĂ€ndert werden.

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:

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)); //fastPWM-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:

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):

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).

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:

#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:
![]()
Den Hauptausgang aktivieren
#define TIM1_BKR *(volatile uint8_t *)0x00526D
TIM1_BKR = (1<<7);Jetzt stimmt alles, der Code .

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 .
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. dem Datenblatt des CD74HC238

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

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:

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);
#endifUnd 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 :
//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 â .

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
