
In Ich habe versucht, Hobby-Elektronikern, die aus den AnfÀngen mit Arduino erwachsen sind, zu erklÀren, warum und wie sie DatenblÀtter und andere Dokumentationen zu Mikrocontrollern lesen sollten. Der Text ist sehr umfangreich geworden, daher habe ich versprochen, praktische Beispiele in einem separaten Artikel zu zeigen. Na dann, es ist gesagt...
Heute zeige ich, wie man mit Hilfe von DatenblĂ€ttern recht einfache, aber notwendige Aufgaben fĂŒr viele Projekte auf den Controllern STM32 (Blue Pill) und STM8 löst. Alle Demoprojekte sind meinen Lieblings-LEDs gewidmet, und wir werden sie in groĂen Mengen zum Leuchten bringen, wofĂŒr wir interessante PeripheriegerĂ€te einbeziehen mĂŒssen.
Der Text ist wieder riesig geworden, daher mache ich zur Ăbersicht ein Inhaltsverzeichnis:
Haftungsausschluss: Ich bin kein Ingenieur und beanspruche nicht, umfassende Kenntnisse in der Elektronik zu haben. Dieser Artikel richtet sich an Gleichgesinnte wie mich. TatsĂ€chlich habe ich als Zielgruppe mein vor zwei Jahren jĂŒngeres Ich betrachtet. HĂ€tte mir damals jemand gesagt, dass es nicht schwer ist, DatenblĂ€tter fĂŒr unbekannte Chips zu lesen, hĂ€tte ich nicht so viel Zeit mit der Suche nach Programmierfragmenten im Internet und dem Basteln mit Scheren und Pflastern vergeudet.
Im Mittelpunkt dieses Artikels stehen die DatenblĂ€tter und nicht die Projekte, daher könnte der Code nicht besonders sauber sein und oft improvisiert wirken. Die Projekte selbst sind sehr einfach, eignen sich jedoch gut fĂŒr das erste Kennenlernen eines neuen Chips.
Ich hoffe, dass mein Artikel jemandem in einer Àhnlichen Phase der Vertiefung in dieses Hobby helfen kann.
STM32
16 LEDs mit DM634 und SPI
Ein kleines Projekt mit dem Blue Pill (STM32F103C8T6) und dem LED-Treiber DM634. Wir werden den Treiber mithilfe der DatenblÀtter verstehen, die STM-I/O-Ports erkunden und SPI einrichten.
DM634
Ein taiwanesischer Chip mit 16 16-Bit PWM-AusgĂ€ngen, die kaskadierbar sind. Das kleinere 12-Bit-Modell ist aus einem heimischen Projekt bekannt. . Als ich mich zwischen dem DM63x und dem bekannten TLC5940 entscheiden musste, fiel meine Wahl aus mehreren GrĂŒnden auf den DM: 1) TLC auf AliExpress ist sicher gefĂ€lscht, dieser jedoch nicht; 2) der DM hat eine autonome PWM mit eigenem Frequenzgenerator; 3) ich konnte ihn gĂŒnstig in Moskau kaufen, anstatt auf ein Paket von Ali zu warten. NatĂŒrlich wollte ich auch selbst lernen, wie man den Chip steuert, anstatt eine fertige Bibliothek zu verwenden. Die Chips sind hauptsĂ€chlich im SSOP24-GehĂ€use erhĂ€ltlich, und man kann sie einfach auf einen Adapter löten.
Da der Hersteller taiwanesisch ist, ist die Dokumentation zum Chip auf Chinesisch-Englisch, was spannend sein wird. Zuerst sehen wir uns das Pinout an (Pin Connection), um zu verstehen, an welchen Pin was angeschlossen werden muss, sowie die Beschreibung der Pins (Pin-Beschreibung). 16 AusgÀnge:

Stromquellen fĂŒr den eintreffenden Gleichstrom (Open-Drain)
Sink / Open-Drain-Ausgang â Drain; Quelle des eindringenden Stroms; Ausgang, der im aktiven Zustand mit Masse verbunden ist â die LEDs werden mit ihren Kathoden an den Treiber angeschlossen. Elektrisch ist das natĂŒrlich kein âOpen-Drainâ (Open Drain), aber in den DatenblĂ€ttern wird diese Bezeichnung fĂŒr Pins im Drain-Modus hĂ€ufig verwendet.
![]()
Externe WiderstÀnde zwischen REXT und GND zur Festlegung des Ausgangsstroms
Zwischen dem Pin REXT und dem Erdungspunkt wird ein Referenzwiderstand installiert, der den Innenwiderstand der AusgĂ€nge steuert, siehe Diagramm auf Seite 9 des Datenblatts. Bei DM634 kann dieser Widerstand auch softwaregesteuert verwaltet werden, indem die globale Helligkeit eingestellt wird (globale Helligkeit); um in diesem Artikel ins Detail zu gehen, werde ich einfach einen Widerstand von 2,2 â 3 kΩ hier einfĂŒgen.
Um zu verstehen, wie man den Chip steuert, werfen wir einen Blick auf die Beschreibung der GerÀteschnittstelle:

Ah, hier ist er, chinesisches Englisch in seiner ganzen Pracht. Dies zu ĂŒbersetzen, ist problematisch, doch man kann es bei Wunsch verstehen, aber es gibt einen anderen Weg â sehen wir uns an, wie die Verbindung im Datenblatt des funktional Ă€hnlichen TLC5940 beschrieben wird:

⊠Zum Eingeben von Daten in das GerĂ€t sind nur drei Pins erforderlich. Der steigende Flankenimpuls des Signals SCLK verschiebt die Daten vom Pin SIN in das interne Register. Nachdem alle Daten geladen sind, fixiert ein kurzer hoher Signalimpuls XLAT die nacheinander ĂŒbertragenen Daten in den internen Registern. Die internen Register sind Level-triggered Schieberegister. Alle Daten werden mit dem höchstwertigen Bit ĂŒbertragen.
Latch â Schieberegister / Verriegelung.
Rising edge â steigende Flanke des Impulses
MSB first â mit dem höchsten (Ă€uĂersten linken) Bit nach vorne.
um Daten zu takten â Daten sequentiell (bitweise) zu ĂŒbertragen.
Das Wort Latch kommt hĂ€ufig in der Dokumentation von Chips vor und wird vielfĂ€ltig ĂŒbersetzt, daher erlaube ich mir fĂŒr das VerstĂ€ndnis
eine kleine EinfĂŒhrungEin LED-Treiber ist im Grunde ein Schieberegister. "Schieb" (Shift) im Namen bedeutet die bitweise Verschiebung von Daten innerhalb des GerĂ€ts: Jedes neue eingesteckte Bit schiebt die gesamte Kette vor sich her. Da wĂ€hrend der Verschiebung niemand wĂ€hrenddessen chaotisches Blinken von LEDs beobachten möchte, erfolgt der Prozess in Pufferregistern, die durch eine Klappe (Latch) vom Betriebsfluss getrennt sind â das ist sozusagen eine Vorhalle, in der die Bits in die gewĂŒnschte Reihenfolge gelangen. Wenn alles bereit ist, öffnet sich die Klappe, und die Bits gehen in Betrieb, ersetzen die vorherige Charge. Das Wort Latch bezieht sich in der Dokumentation von Mikrochips fast immer auf eine solche Klappe, unabhĂ€ngig davon, in welchen Kombinationen es verwendet wird.
Die DatenĂŒbertragung im DM634 erfolgt wie folgt: Setzen Sie den Eingang DAI auf den höchsten Bitwert der entfernten LED, ziehen Sie DCK hoch und runter; setzen Sie den Eingang DAI auf den nĂ€chsten Bitwert, ziehen Sie DCK; und so weiter, bis alle Bits ĂŒbertragen sind (einclockt), wonach LAT gezogen wird. Dies kann manuell erfolgen (Bit-Banging), es ist jedoch besser, eine speziell dafĂŒr ausgelegte SPI-Schnittstelle zu verwenden, die auf unserem STM32 in zweifacher AusfĂŒhrung vorhanden ist.
Die blaue Tablette STM32F103
EinfĂŒhrende Informationen: Die STM32-Controller sind deutlich komplexer als der Atmega328, als man zunĂ€chst vermuten könnte. Aus GrĂŒnden der Energieeinsparung ist zu Beginn fast alle Peripherie ausgeschaltet, und die Taktfrequenz betrĂ€gt 8 MHz aus einer internen Quelle. GlĂŒcklicherweise haben die STM-Programmierer Code geschrieben, der den Chip auf die âberechnetenâ 72 MHz bringt, und die Autoren aller mir bekannten IDE haben diesen in die Initialisierungsprozedur integriert, sodass wir ihn nicht anpassen mĂŒssen (aber ). Die Peripherie muss jedoch aktiviert werden.
Dokumentation: Der Blue Pill verwendet den beliebten Chip STM32F103C8T6, fĂŒr den es zwei nĂŒtzliche Dokumente gibt:
- fĂŒr die Mikrocontroller STM32F103x8 und STM32F103xB;
- fĂŒr die gesamte Reihe STM32F103 und mehr.
In den DatenblÀttern könnten uns Folgendes interessieren:
- Pinouts â die Zuordnungen der Pins â fĂŒr den Fall, dass wir selbst Platinen herstellen möchten;
- Speicherkarten â eine Ăbersicht ĂŒber den Speicher fĂŒr einen bestimmten Chip. Im Referenzhandbuch gibt es eine Ăbersicht fĂŒr die gesamte Produktreihe, in der die Register erwĂ€hnt werden, die in unserem nicht enthalten sind.
- Tabelle der Pin-Definitionen â eine Auflistung der Haupt- und alternativen Funktionen der Pins; fĂŒr die âblaue Pilleâ gibt es im Internet benutzerfreundlichere Bilder mit einer Liste der Pins und ihrer Funktionen. Daher sofort nach Blue Pill Pinout googeln und ein solches Bild bereithalten:

Hinweis: Das Bild aus dem Internet hatte einen Fehler, der in den Kommentaren bemerkt wurde. Danke dafĂŒr. Das Bild wurde ersetzt, aber dies ist eine Lektion â Informationen, die nicht aus den DatenblĂ€ttern stammen, sollten besser ĂŒberprĂŒft werden.
Wir legen das Datenblatt beiseite, öffnen das Referenzhandbuch und verwenden von nun an nur noch dieses.
Vorgehensweise: Wir klÀren den Standard-Ein-/Ausgang, konfigurieren SPI und aktivieren die benötigten PeripheriegerÀte.
Ein-/Ausgabe
Bei der Atmega328 ist die Ein-/Ausgabe Ă€uĂerst simpel umgesetzt, weshalb die Vielzahl an Optionen bei STM32 verwirrend sein kann. Derzeit benötigen wir nur die AusgĂ€nge, aber selbst dafĂŒr gibt es vier Varianten:

Ausgang mit offenem Kollektor, Push-Pull-Ausgang, alternativer Push-Pull-Ausgang, alternativer offener Kollektor
Push-Pull (Push-Pull) â der ĂŒbliche Ausgang von Arduino, der Pin kann entweder HIGH oder LOW annehmen. Bei "offenem Kollektor" gibt es jedoch , obwohl es hier eigentlich ganz einfach ist:


Konfiguration des Ausgangs / wenn der Port als Ausgang zugewiesen ist: / Ausgabepuffer ist 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 Pin im ersten Fall keinen HIGH-Zustand annehmen kann: Wenn eine Eins ins Ausgangsregister geschrieben wird, wechselt er in den Zustand hoher Impedanz (high impedance, Hi-Z). Wenn eine Null geschrieben wird, verhÀlt sich der Pin in beiden Modi sowohl logisch als auch elektrisch gleich.
Im normalen Ausgangsmodus ĂŒbertrĂ€gt der Pin einfach den Inhalt des Ausgangsregisters. Im "alternativen" Modus wird er von der entsprechenden Peripherie gesteuert (siehe 9.1.4):
![]()
Wenn der Port-Bit als alternativer Funktionsausgang konfiguriert ist, wird das Ausgangsregister deaktiviert, und der Pin wird mit dem Ausgangssignal der Peripherie verbunden.
Die alternativen Funktionen jedes Pins sind in der Pin Definitions Datenblatt beschrieben und auf dem heruntergeladenen Bild dargestellt. Auf die Frage, was zu tun ist, wenn ein Pin mehrere alternative Funktionen hat, gibt eine FuĂnote im Datenblatt Antwort:
![]()
Wenn mehrere Peripherieeinheiten denselben Pin verwenden, sollte zur Vermeidung von Konflikten zwischen den alternativen Funktionen gleichzeitig nur eine Peripherieeinheit verwendet werden, und zwar durch Umschalten mithilfe des Aktivierungsbits der Peripherietaktschaltung (im entsprechenden RCC-Register).
SchlieĂlich haben Pins im Ausgangsmodus auch eine Taktfrequenz. Dies ist ein weiteres Energiesparfeature; in unserem Fall stellen wir einfach auf Maximum und vergessen es.
Also: Wir verwenden SPI, was bedeutet, dass zwei Pins (mit Daten und Taktsignal) die "Pull-Pull-Alternative Funktion" haben sollten, und ein weiterer (LAT) â âgewöhnliche Pull-Pull-Funktionâ. Aber bevor wir sie zuweisen, klĂ€ren wir das SPI.
Der EHCI-Treiber wurde verbessert.
Noch eine kleine Schulung
SPI oder Serial Peripheral Interface (serieller Peripherie-Anschluss) ist eine einfache und Ă€uĂerst effektive Schnittstelle zur Kommunikation zwischen Mikrocontrollern und der AuĂenwelt. Das Funktionsprinzip wurde bereits oben beschrieben, dort, wo es um den chinesischen LED-Treiber geht (siehe Abschnitt 25 im Referenzhandbuch). SPI kann im Master- (âEigentĂŒmerâ) und Slave-Modus (âSklaveâ) betrieben werden. SPI verfĂŒgt ĂŒber vier grundlegende KanĂ€le, von denen eventuell nicht alle in Betrieb genommen werden.
- MOSI, Master Output / Slave Input: Dieser Pin gibt im Master-Modus Daten aus und empfÀngt sie im Slave-Modus.
- MISO, Master Input / Slave Output: Umgekehrt, im Master empfÀngt er, im Slave gibt er aus.
- SCK, Serial Clock: Er bestimmt die DatenĂŒbertragungsfrequenz im Master-Modus oder empfĂ€ngt das Taktsignal im Slave-Modus. Er schlĂ€gt essentially die Bits.
- SS, Slave Select: Mit diesem Kanal erfĂ€hrt der Slave, dass eine Anfrage an ihn gerichtet ist. Bei STM32 wird er als NSS bezeichnet, wobei N fĂŒr negative steht, d.h. der Controller wird zum Slave, wenn an diesem Kanal Masse anliegt. Kombiniert sich gut mit dem Open Drain Output-Modus, aber das ist eine andere Geschichte.
Wie alles andere auch, ist SPI auf dem STM32 mit Funktionen reich ausgestattet, was das VerstĂ€ndnis etwas komplizierter macht. Es kann nicht nur mit SPI, sondern auch mit dem I2S-Interface arbeiten, wobei die Beschreibungen in der Dokumentation vermischt sind, was es notwendig macht, Unnötiges rechtzeitig auszuschlieĂen. Unsere Aufgabe ist jedoch Ă€uĂerst einfach: Wir mĂŒssen lediglich Daten ĂŒber MOSI und SCK ĂŒbertragen. Wir gehen zu Abschnitt 25.3.4 (Half-Duplex-Kommunikation), wo wir finden. 1 Takt und 1 unidirektionale Datenleitung (1 clock signal and 1 unidirectional data stream):

In diesem Modus verwendet die Anwendung SPI entweder im Ăbertragungs- oder Empfangsmodus. Der Ăbertragungsmodus Ă€hnelt dem Duplexmodus: Daten werden ĂŒber den Ăbertragungspin (MOSI im Master-Modus oder MISO im Slave-Modus) ĂŒbertragen, wĂ€hrend der Empfangspin (MISO oder MOSI entsprechend) als normaler Ein-/Ausgangspin verwendet werden kann. In diesem Fall genĂŒgt es der Anwendung, den Rx-Puffer zu ignorieren (wenn er gelesen wird, sind keine ĂŒbertragenen Daten vorhanden).
Wunderbar, der MISO-Pin ist jetzt frei, wir verbinden das Signal LAT damit. Wir kĂŒmmern uns um den Slave Select, der auf dem STM32 programmatisch gesteuert werden kann, was Ă€uĂerst praktisch ist. Wir lesen den gleichnamigen Abschnitt aus dem Kapitel 25.3.1 SPI Allgemeine Beschreibung:

Programmierbare Steuerung des NSS (SSM = 1) / Informationen zur Auswahl des Slaves sind im SSI-Bit des SPI_CR1-Registers enthalten. Der externe NSS-Pin bleibt fĂŒr andere Zwecke der Anwendung frei.
Jetzt ist es Zeit, in die Register zu schreiben. Ich habe mich entschieden, SPI2 zu verwenden, und suche im Datenblatt nach seiner Basisadresse â im Abschnitt 3.3 Speicherkarte:
![]()
Nun legen wir los:
#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))Ăffnen Sie den Abschnitt 25.3.3 mit dem aussagekrĂ€ftigen Titel «Konfiguration des SPI im Master-Modus»:

1. Stellen Sie die Taktfrequenz des seriellen Interfaces mit den Bits BR[2:0] im Register SPI_CR1 ein.
Die Register sind im gleichnamigen Abschnitt des Referenzhandbuchs zusammengefasst. Der Adressversatz (Address offset) bei CR1 â 0x00, standardmĂ€Ăig sind alle Bits zurĂŒckgesetzt (Reset value 0x0000):

Die Bits BR setzen den Teilungsfaktor der Taktfrequenz des Controllers, wodurch die Frequenz bestimmt wird, mit der das SPI arbeiten wird. Die Frequenz des STM32 betrÀgt 72 MHz, der LED-Treiber arbeitet laut seines Datenblatts mit einer Frequenz von bis zu 25 MHz, daher 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 CPOL- und CPHA-Bits ein, um das VerhĂ€ltnis 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 CPOL- und CPHA-Bits auf Seite 704 (Allgemeine Beschreibung des SPI) untersuchen:

Phase und PolaritÀt des Taktsignals
Mit den CPOL- und CPHA-Bits im SPI_CR1-Register können vier Timing-Optionen programmatisch ausgewĂ€hlt werden. Das CPOL-Bit (PolaritĂ€t des Taktsignals) steuert den Zustand des Taktsignals, wenn keine Daten ĂŒbertragen werden. Dieses Bit regelt die Master- und Slave-Modi. Wenn CPOL zurĂŒckgesetzt ist, befindet sich der Pin SCK im Ruhezustand auf einem niedrigen Pegel. Wenn das CPOL-Bit gesetzt ist, befindet sich der Pin SCK im Ruhezustand auf einem hohen Pegel.
Wenn das Bit CPHA (Clock Phase) gesetzt ist, fungiert die zweite Flanke des SCK-Signals als Erfassungsstrobe fĂŒr das höherwertige Bit (fallend, wenn CPOL zurĂŒckgesetzt ist, oder steigend, wenn CPOL gesetzt ist). Die Daten werden beim zweiten Wechsel des Taktsignals erfasst. Wenn das Bit CPHA zurĂŒckgesetzt ist, fungiert die vordere Flanke des SCK-Signals als Erfassungsstrobe fĂŒr das höherwertige Bit (fallend, wenn CPOL gesetzt ist, oder steigend, wenn CPOL zurĂŒckgesetzt ist). Die Daten werden beim ersten Wechsel des Taktsignals erfasst.
Durch das VerstĂ€ndnis dieser Informationen kommen wir zu dem Schluss, dass beide Bits Null bleiben mĂŒssen, da das SCK-Signal niedrig bleiben muss, wenn es nicht verwendet wird, und die Daten beim Anstieg des Impulses ĂŒbertragen werden (siehe Rising Edge im Datenblatt DM634).
Ăbrigens sind wir hier zum ersten Mal auf eine Besonderheit der Terminologie in den ST-DatenblĂ€ttern gestoĂen: In ihnen wird der Ausdruck âeinen Bit auf null zurĂŒcksetzenâ geschrieben to reset a bit, und nicht to clear a bit, Ă€hnlich wie bei der Atmega.
3. Setzen Sie das DFF-Bit, um das 8-Bit- oder 16-Bit-Datenblockformat zu bestimmen.
Ich habe absichtlich den 16-Bit DM634 gewĂ€hlt, um mich nicht mit der Ăbertragung von 12-Bit PWM-Daten wie beim DM633 befassen zu mĂŒssen. Das DFF macht Sinn, auf Eins gesetzt zu werden:
#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, stellt die Ăbertragung so ein, dass das zuletzt ĂŒbertragene Bit zuerst kommt. Doch der DM634 möchte die Daten beginnend mit dem höchstwertigen Bit erhalten. Daher bleibt es deaktiviert.
5. Im Hardware-Modus, falls der PIN NSS Eingaben benötigt, legen Sie wĂ€hrend der gesamten Byte-Ăbertragungssequenz ein hohes Signal an den PIN NSS an. Im Software-Modus setzen Sie die Bits SSM und SSI im SPI_CR1-Register. Wenn der PIN NSS als Ausgang arbeiten soll, ist es erforderlich, nur das Bit SSOE zu setzen.
Setzen Sie SSM und SSI, um den Hardware-Modus von 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 werden (sie bleiben nur gesetzt, wenn ein hohes Signal an NSS angelegt wird).
Mit diesen Bits weisen wir unseren SPI als Master zu und aktivieren ihn:
#define MSTR 0x0004
#define SPE 0x0040
_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//ĐșĐŸĐłĐŽĐ° ĐČŃĐ” ĐłĐŸŃĐŸĐČĐŸ, ĐČĐșĐ»ŃŃĐ°Đ”ĐŒ SPI
_SPI2_ (_SPI_CR1) |= SPE;SPI ist konfiguriert, lassen Sie uns sofort Funktionen schreiben, die Bytes an den Treiber senden. Fahren Sie mit Abschnitt 25.3.3 âKonfiguration von SPI im Master-Modusâ fort:

DatenĂŒbertragungsreihenfolge
Die Ăbertragung beginnt, wenn ein Byte in den Tx-Puffer geschrieben wird.
Das Datenbyte wird in den Schieberegister geladen in parallelem Modus (von der internen Bus) beim Ăbertragen des ersten Bits, danach wird es ĂŒbertragen. sequentieller Im MOSI-Pin-Modus wird das erste oder letzte Bit je nach Einstellung des LSBFIRST-Bits im CPI_CR1-Register vorangestellt. Das TXE-Flag wird nach der DatenĂŒbertragung gesetzt. vom Tx-Puffer in das Schieberegister., und es wird 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 SPI-Implementierung in den STM-Controllern aufmerksam zu machen. Bei der Atmega wird das TXE-Flag (Tx Empty, Tx ist leer und bereit, Daten zu empfangen) nur gesetzt, nachdem das gesamte Byte gesendet wurde. nach auĂen.Hier wird dieses Flag jedoch gesetzt, nachdem das Byte in das interne Schieberegister gelangt ist. Da es dort mit allen Bits gleichzeitig (parallel) eingegeben wird und die Daten danach sequenziell ĂŒbertragen werden, wird TXE gesetzt, bevor das Byte vollstĂ€ndig gesendet wird. Das ist wichtig, denn bei unserem LED-Treiber mĂŒssen wir den LAT-Pin nach dem Senden alle der Daten ansteuern, d.h. das TXE-Flag allein reicht nicht aus.
Das bedeutet, dass wir noch ein weiteres Flag benötigen. Schauen wir in 25.3.7 â âStatusflagsâ:

<âŠ>

Das BUSY-Flag
Das BSY-Flag wird hardwareseitig gesetzt und zurĂŒckgesetzt (eine Speicherung hat keinen Einfluss darauf). 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 ist
wenn ein Fehler im Master-Modus auftritt (MODF=1)
Wenn die Ăbertragung nicht kontinuierlich ist, wird das BSY-Flag zwischen jeder DatenĂŒbertragung zurĂŒckgesetzt.
Okay, das wird nĂŒtzlich sein. Wir ermitteln, wo sich der Tx-Puffer befindet. Dazu lesen wir das 'SPI-Datenregister':

Bits 15:0 DR[15:0] Datenregister
Empfangene Daten oder Daten zur Ăbertragung.
Das Datenregister ist in zwei Puffer unterteilt â einen zum Schreiben (Ăbertragungspuffer) und einen zum Lesen (Empfangspuffer). Das Schreiben in das Datenregister schreibt in den Tx-Puffer, und das Lesen aus dem Datenregister gibt den Wert zurĂŒck, der im Rx-Puffer enthalten ist.
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 es so aus:
void sendLEDdata()
{
LAT_low();
uint8_t k = 16;
do
{ k--;
dm_shift16(leds[k]);
} while (k);
while (_SPI2_(_SPI_SR) & BSY); // Ăbertragung abschlieĂen
LAT_pulse();
}Aber wir können den LAT-Pin noch nicht ansteuern, also kehren wir zu I/O zurĂŒck.
Wir weisen Pins zu
Die Register der STM32F1, die den Zustand der Pins steuern, sind ziemlich ungewöhnlich. Es ist klar, dass es mehr gibt als bei der Atmega, aber sie unterscheiden sich auch von anderen STM-Chips. Abschnitt 9.1 Allgemeine Beschreibung von GPIO:

Jeder der allgemeinen Ein-/Ausgabe-Ports (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).
Es ist ungewöhnlich und auch ziemlich unpraktisch, da die ersten beiden Register die 16 Pins des Ports im Format âvier Bit pro Registerâ verteilt haben. Das heiĂt, die Pins von null bis sieben sind im CRL, wĂ€hrend die anderen im CRH sind. Dabei können die anderen Register die Bits aller Pins des Ports erfolgreich aufnehmen â bleiben oft halb âreserviertâ.
Zur Vereinfachung beginnen wir am Ende der Liste.
Das Sperrregister benötigen wir nicht.
Die Register fĂŒr Setzen und ZurĂŒcksetzen sind insofern interessant, als sie sich teilweise ĂŒberschneiden: Man kann alles nur in BSRR schreiben, wo die oberen 16 Bit den Pin auf Null zurĂŒcksetzen und die unteren ihn auf Eins setzen, oder auch BRR verwenden, dessen untere 16 Bit nur den Pin zurĂŒcksetzen. Mir gefĂ€llt die zweite Variante besser. Diese Register sind wichtig, da sie atomaren Zugriff auf die Pins gewĂ€hrleisten:

![]()
Atomar setzen oder zurĂŒcksetzen
Es ist nicht notwendig, Interrupts beim Programmieren von GPIOx_ODR auf Bit-Ebene auszuschalten: Man kann ein oder mehrere Bits mit einer atomaren Schreiboperation in APB2 Ă€ndern. Dies wird erreicht, indem eine "1" in das Setzen/ZurĂŒcksetzen-Register (GPIOx_BSRR oder, nur fĂŒrs ZurĂŒcksetzen, in GPIOx_BRR) des Bits geschrieben wird, das geĂ€ndert werden soll. Alle anderen Bits bleiben unverĂ€ndert.
Die Datenregister haben recht aussagekrĂ€ftige Namen â IDR = Input Richtungregister, Eingangsregister; ODR = Ausgabe Richtungregister, Ausgangsregister. In unserem aktuellen Projekt werden wir sie nicht benötigen.
Und schlieĂlich die Steuerregister. Da uns die Pins des zweiten SPI interessieren, also PB13, PB14 und PB15, schauen wir uns gleich CRH an:

Und sehen, dass wir etwas in die Bits 20 bis 31 schreiben mĂŒssen.
Wir haben bereits erklĂ€rt, was wir von den Pins erwarten, daher verzichte ich hier auf einen Screenshot und sage einfach, dass MODE die Richtung festlegt (Eingang, wenn beide Bits auf 0 gesetzt sind) und die Geschwindigkeit des Pins (wir benötigen 50 MHz, d.h. beide Pins auf "1"), wĂ€hrend CNF den Modus festlegt: normaler "Push-Pull" â 00, "alternativ" â 10. Wie oben gesehen, haben alle Pins standardmĂ€Ăig das dritte Bit von unten (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 die unteren als auch fĂŒr die oberen Steuerregister definiert.
So sieht das dann 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 kann man Defines fĂŒr LAT schreiben, der die Register BRR und BSRR steuert:
/*** 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, so war es schon immer, lass es so bleiben.)
Jetzt lÀuft alles gut, nur funktioniert es nicht. Weil es sich um STM32 handelt, wird hier Strom gespart, was bedeutet, dass die Taktung der benötigten Peripherie eingeschaltet werden muss.
Aktivieren Sie die Taktung.
Die Taktung wird von einem Timer, auch bekannt als Clock, gesteuert. Wir haben bereits das KĂŒrzel RCC bemerkt. Schauen wir in die Dokumentation: es steht fĂŒr Reset and Clock Control (ZurĂŒcksetzen und Taktsteuerung).
Wie oben erwĂ€hnt, haben zum GlĂŒck die Leute von STM die schwierigsten Aspekte der Taktung fĂŒr uns ĂŒbernommen, wofĂŒr wir ihnen sehr dankbar sind (hier ist noch einmal der Link zu , um zu zeigen, wie komplex das ist). Wir benötigen lediglich die Register, die fĂŒr die Aktivierung der Taktung der Peripherie verantwortlich sind (Peripheral Clock Enable Registers). Zuerst finden wir die Basisadresse von RCC, die sich am Anfang der âSpeicherkartenâ befindet:
![]()
#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))Dann können wir entweder auf den Link klicken, um in einer Tabelle etwas zu suchen, oder, was viel besser wĂ€re, die Beschreibungen der aktivierenden Register in den Abschnitten ĂŒber enable registersdurchgehen. Dort finden wir RCC_APB1ENR und RCC_APB2ENR:


In diesen befinden sich entsprechend die Bits, die die Taktung fĂŒr SPI2, IOPB (I/O-Port B) und alternative 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 finden Sie .
Wenn Sie die Möglichkeit und Lust haben, können Sie DM634 wie folgt anschlieĂen: DAI an PB15, DCK an PB13, LAT an PB14. Versorgen Sie den Treiber mit 5 Volt und vergessen Sie nicht, die Erdung zu verbinden.

STM8 PWM
PWM auf STM8
Als ich diesen Artikel plante, beschloss ich, zum Beispiel die Funktion eines mir unbekannten Chips nur mit Hilfe des Datenblatts zu erlernen, damit ich nicht wie ein Schuhmacher ohne Schuhe dastehe. Der STM8 war hierfĂŒr perfekt geeignet: Erstens hatte ich ein paar chinesische Platinen mit STM8S103, und zweitens ist er nicht besonders populĂ€r, sodass die Versuchung, die Lösung im Internet zu suchen, an dem Fehlen dieser Lösungen scheitert.
Der Chip hat auch und , im ersten Teil die Pinbelegung und die Adressen der Register, im zweiten â alles andere. Der STM8 wird in C in einer etwas furchtbaren IDE programmiert. .
Taktfrequenz und I/O
StandardmĂ€Ăig arbeitet der STM8 mit einer Frequenz von 2 MHz, das sollte sofort korrigiert werden.

Das Taktsignal HSI (High-Speed Internal)
Das HSI-Taktsignal stammt von einem internen 16-MHz-RC-Generator mit einem programmierbaren Teiler (von 1 bis 8). Es wird im Register fĂŒr den Taktsignalteiler (CLK_CKDIVR) festgelegt.
Hinweis: Zu Beginn wird der HSI-RC-Generator mit einem Teiler von 8 als Hauptquelle fĂŒr das Taktsignal ausgewĂ€hlt.
Wir finden die Adresse des Registers im Datenblatt, die Beschreibung im Refman und sehen, dass das Register zurĂŒckgesetzt werden muss:
#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6
CLK_CKDIVR &= ~(0x18);Da wir PWM ausfĂŒhren und LEDs anschlieĂen möchten, schauen wir uns die Pinbelegung an:

Der Chip ist klein, viele Funktionen sind an dieselben Pins gebunden. Das, was in eckigen Klammern steht â âalternative Funktionenâ, wird durch âOptionsbytesâ (Optionbytes) gewechselt â etwas Ă€hnliches wie die Fuses der Atmega. Ihre Werte können programmgesteuert geĂ€ndert werden, aber das ist nicht nötig, da die neue FunktionalitĂ€t erst nach einem Neustart aktiviert wird. Am einfachsten ist es, den ST Visual Programmer (heruntergeladen mit Visual Develop) zu verwenden, der in der Lage ist, diese Bytes zu Ă€ndern. In der Pinbelegung sieht man, dass die AusgĂ€nge CH1 und CH2 des ersten Timers in eckige Klammern versteckt sind; man muss in STVP die Bits AFR1 und AFR0 setzen, wobei das zweite den Ausgang CH1 des zweiten Timers von PD4 nach PC5 verschiebt.
Somit werden die LEDs von 6 Pins gesteuert: PC6, PC7 und PC3 fĂŒr den ersten Timer, PC5, PD3 und PA3 fĂŒr den zweiten.
Die Konfiguration der Ein- und Ausgabepins auf dem STM8 ist einfacher und logischer als auf dem STM32:
- bekannt aus dem Atmega-Datenrichtungsregister DDR (Data Direction Register): 1 = Ausgang;
- das erste Kontrollregister CR1 legt beim Ausgang den Modus âPush-Pullâ (1) oder offenen Kollektor (0) fest; da ich die LEDs mit Kathoden an den Chip anschlieĂe, lasse ich hier Nullen stehen;
- Der zweite Kontrollregister CR2 legt die Taktrate 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-Konfiguration
ZunÀchst lassen Sie uns die Begriffe festlegen:
- PWM-Frequenz â die Frequenz, mit der der Timer tickt;
- Auto-Reload, AR â der automatisch nachgeladenen Wert, bis zu dem der Timer zĂ€hlt (Impulsperiode);
- Update-Event, UEV â ein Ereignis, das eintritt, wenn der Timer bis zu AR gezĂ€hlt hat;
- PWM-TastverhĂ€ltnis â das VerhĂ€ltnis, oft als 'Duty Cycle' bezeichnet;
- Capture/Compare-Wert â der Wert fĂŒr das Erfassen/Vergleichen, bis zu dem der Timer etwas tun wird (im Fall von PWM â invertiert das Ausgangssignal);
- Vorabwert â der vorab geladene Wert. Der Vergleichswert darf sich nicht Ă€ndern, solange der Timer tickt, sonst bricht der PWM-Zyklus. Daher werden neue ĂŒbermittelte Werte in einen Puffer eingefĂŒgt und von dort abgerufen, wenn der Timer das Ende des Counts erreicht und zurĂŒckgesetzt wird;
- Kanten-aligned und Zentriert-aligned Modus â Ausrichtung nach der Kante und in der Mitte, Ă€hnlich den Atmellow-Standards Schnelles PWM und Phasenkorrektes PWM.
- OCiREF, Ausgangsvergleich Referenzsignal â der Referenzausgangssignal, tatsĂ€chlich das, was im PWM-Modus an dem entsprechenden Pin erscheint.
Wie bereits aus der Pinbelegung ersichtlich ist, verfĂŒgen zwei Timer ĂŒber PWM-Funktionen â der erste und der zweite. Beide sind 16-Bit, der erste bietet viele zusĂ€tzliche Funktionen (unter anderem kann er sowohl aufwĂ€rts als auch abwĂ€rts zĂ€hlen). Wir benötigen, dass beide identisch arbeiten, daher habe ich beschlossen, mit dem zweiteren zu beginnen, der bewusst weniger Funktionen hat, um nicht versehentlich etwas zu verwenden, was ihm fehlt. Ein gewisses Problem besteht darin, dass die Beschreibung der PWM-FunktionalitĂ€t aller Timer im Handbuch im Kapitel ĂŒber den ersten Timer zu finden ist (17.5.7 PWM-Modus), weshalb ich stĂ€ndig hin und her im Dokument springen muss.
PWM auf dem STM8 hat einen wichtigen Vorteil gegenĂŒber PWM der Atmega:

PWM mit Grenzausrichtung
ZĂ€hlkonfiguration von unten nach oben
Das ZĂ€hlen von unten nach oben 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 hoch, solange TIM1_CNT < TIM1_CCRi. Andernfalls nimmt es einen niedrigen Pegel an. Wenn der Vergleichswert im Register TIM1_CCRi gröĂer ist als der automatisch zu ladende Wert (Register TIM1_ARR), bleibt das Signal OCiREF auf 1. Wenn der Vergleichswert 0 ist, bleibt OCiREF auf null.âŠ
Timer STM8 wĂ€hrend Update-Ereignis ĂŒberprĂŒft zunĂ€chst Vergleichswert, und erst danach gibt er ein Referenzsignal aus. Bei der Atmega-Timer wird zunĂ€chst gearbeitet und dann verglichen, was dazu fĂŒhrt, dass bei Vergleichswert == 0 am Ausgang eine Nadel entsteht, mit der man irgendwie umgehen muss (zum Beispiel durch programmatisches Invertieren der Logik).
Also, was wollen wir tun: 8-Bit PWM (AR == 255), zÀhlen von unten nach oben, Ausrichtung an der Kante. Da die Lampen mit dem Chip an den Kathoden verbunden sind, muss die PWM 0 (LED leuchtet) vorgeben Vergleichswert und 1 danach.
Wir haben bereits ĂŒber einige PWM-Modus, deshalb suchen wir das benötigte Register des zweiten Timers, indem wir im Referenzhandbuch nach diesem Satz suchen (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 inaktiv. [spĂ€ter im Dokument fehlerhafter Copy-Paste vom Timer 1]
111: Zweiter PWM-Modus â beim ZĂ€hlen von unten nach oben ist der erste Kanal inaktiv, solange TIMx_CNT < TIMx_CCR1. Andernfalls ist der erste Kanal aktiv.
Da die LEDs mit dem Mikrocontroller an den Kathoden angeschlossen sind, passen wir in den zweiten Modus (der erste auch, aber das wissen wir noch nicht).

Bit 3 OC1PE: Ausgabe 1 Vorablage aktivieren
0: Der Preload-Register in TIMx_CCR1 ist deaktiviert. In TIMx_CCR1 kann jederzeit geschrieben werden. Der neue Wert tritt sofort in Kraft.
1: Der Preload-Register in TIMx_CCR1 ist aktiviert. Lese-/SchreibvorgÀnge beziehen sich auf das Preload-Register. Der vorbeladene Wert von TIMx_CCR1 wird bei jedem Update-Ereignis in das Schattenregister geladen.
*Hinweis: FĂŒr das ordnungsgemĂ€Ăe Funktionieren des PWM-Modus mĂŒssen die Preload-Register aktiviert sein. Dies ist im Einzelbenachrichtigungsmodus (wenn das OPM-Bit im TIMx_CR1 gesetzt ist) nicht erforderlich.
Okay, aktivieren wir alles, was fĂŒr die drei KanĂ€le des zweiten Timers benötigt wird:
#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 achtbitigen Registern, das ist ganz 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 aufwÀrts zÀhlen, mit Grenzausrichtung, da muss nichts geÀndert werden. Wir setzen den Frequenzteiler, beispielsweise auf 256. Der Frequenzteiler des zweiten Timers wird im Register TIM2_PSCR eingestellt und stellt eine Potenz von zwei dar:
#define TIM2_PSCR *(volatile uint8_t *)0x00530E
TIM2_PSCR = 8;Jetzt mĂŒssen wir nur noch die AusgĂ€nge und den zweiten Timer aktivieren. Die erste Aufgabe wird durch die Register gelöst: Capture/Compare Enable: Es gibt zwei oder drei KanĂ€le, die asymmetrisch verteilt sind. Hier können wir auch erfahren, dass die PolaritĂ€t des Signals geĂ€ndert werden kann, d.h. grundsĂ€tzlich könnte auch der PWM Modus 1 verwendet werden. 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 schreiben eine einfache Analogie zu AnalogWrite(), die dem Timer die Werte zum Vergleichen ĂŒbertrĂ€gt. Die Register werden erwartungsgemÀà genannt: Capture/Compare registers, es gibt zwei pro Kanal: die unteren 8 Bits in TIM2_CCRxL und die oberen in TIM2_CCRxH. Da wir einen 8-Bit-PWM verwenden, 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 defekte PWM erhalten haben, die nicht in der Lage ist, 100 % Duty Cycle auszugeben (bei einem Maximalwert von 255 wird das Signal in einem Zyklus des Timers invertiert). FĂŒr LED-Betrieb spielt das keine Rolle, und der aufmerksame Leser hat sicher schon Ideen, wie man das beheben kann.
Die PWM auf dem zweiten Timer funktioniert, wir wechseln zum ersten.
Der erste Timer verwendet genau dieselben Bits in denselben Registern (die Bits, die im zweiten Timer "reserviert" waren, werden im ersten aktiv fĂŒr verschiedene fortschrittliche Funktionen genutzt). Daher reicht es aus, die Adressen dieser Register im Datenblatt zu finden und den Code zu kopieren. AuĂerdem muss der Frequenzteiler geĂ€ndert werden, da der erste Timer nicht eine Potenz von zwei, sondern einen genauen 16-Bit-Wert in zwei Registern erwarten möchte. Prescaler High und Niedrig. Wir machen alles und⊠der erste Timer funktioniert nicht. Wo liegt das Problem?
Das Problem kann nur gelöst werden, indem man den gesamten Abschnitt ĂŒber die Steuerregister des Timers 1 durchgeht, wo wir nach dem suchen, das im zweiten Timer fehlt. Es wird gefunden. 17.7.30 Break Register (TIM1_BKR), in dem es ein solches Bit gibt:
![]()
Haupausgang aktivieren
#define TIM1_BKR *(volatile uint8_t *)0x00526D
TIM1_BKR = (1<<7);Jetzt ist alles in Ordnung, 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 sie verschiedene Farben anzeigen zu lassen. Der Grundgedanke basiert auf dem Konzept des LED-Multiplexings, bei dem es so aussieht, als wĂŒrden die LEDs stĂ€ndig brennen, wenn man sie sehr schnell ein- und ausschaltet (persistence of vision, die TrĂ€gheit der visuellen Wahrnehmung). FrĂŒher habe ich so etwas mit Arduino gemacht .
Der Algorithmus funktioniert wie folgt:
- wir haben die Anode der ersten RGB-LED angeschlossen;
- wir haben sie durch das Senden der erforderlichen Signale an die Kathoden eingeschaltet;
- wir haben auf das Ende des PWM-Zyklus gewartet;
- wir haben die Anode der zweiten RGB-LED angeschlossen;
- wir haben sie eingeschaltetâŠ
Und so weiter. NatĂŒrlich erfordert ein schönes Funktionieren, dass das AnschlieĂen der Anode und das 'Einschalten' der LED gleichzeitig erfolgen. Oder fast. In jedem Fall mĂŒssen wir den Code schreiben, der den drei KanĂ€len des zweiten Timers Werte ausgibt, diese Ă€ndert, wenn UEV erreicht wird, und gleichzeitig die aktuell aktive RGB-LED wechselt.
Da der LED-Wechsel automatisch erfolgt, mĂŒssen wir einen 'Speicher' erstellen, aus dem der Interrupt-Handler die Daten beziehen kann. Das ist ein einfaches 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, lautet
uint8_t cnt;Demux
FĂŒr das korrekte Multiplexing benötigen wir, seltsamerweise, den Demultiplexer CD74HC238. Der Demultiplexer ist ein Chip, der den Operator hardwareseitig implementiert <<. Ăber drei Eingangs-Pins (Bits 0, 1 und 2) speisen wir ihm eine dreibitige Zahl X zu, und er aktiviert als Antwort den Ausgang Nummer (1<<X). Die anderen EingĂ€nge des Chips werden zur Skalierung der gesamten Konstruktion verwendet. Dieser Chip ist nicht nur notwendig, um die Anzahl der belegten Pins des Mikrokontrollers zu reduzieren, sondern auch zur Sicherheit â um zu verhindern, dass versehentlich mehr LEDs eingeschaltet werden, als erlaubt, und den Mikrokontroller zu beschĂ€digen. Der Chip kostet kaum etwas, es lohnt sich, ihn immer in der Hausapotheke zu haben.
CD74HC238 wird verantwortlich dafĂŒr sein, Spannung zum Anoden des benötigten LEDs zu liefern. In einem vollstĂ€ndigen Multiplex wĂŒrde er Spannung ĂŒber einen P-MOSFET leisten, aber in diesem Demo kann man das auch direkt tun, da er laut absolute Maximalwerte Datenblatt 20 mA zieht. Datenblatt CD74HC238

benötigen wir die Pinbelegung und diese Merkhilfe:
H = hoher Spannungspegel, L = niedriger Spannungspegel, X â egal
Wir schlieĂen E2 und E1 an Ground, E3, A0, A1 und A3 an die Pins PD5, PC3, PC4 und PC5 des STM8 an. Da die obige Tabelle sowohl niedrige als auch hohe Pegel enthĂ€lt, konfigurieren wir diese Pins als Push-Pull-AusgĂ€nge.
PWM
PWM auf dem zweiten Timer wird genauso konfiguriert wie in der vorherigen Geschichte, mit zwei Unterschieden: Update-Ereignis (UEV) aktivieren, das eine Funktion aufruft, die die aktive LED umschaltet. Dies geschieht durch das Ăndern eines Bit Update Interrupt Enable im Register mit dem sprechenden Namen

Interrupt Enable Register
#define TIM2_IER *(volatile uint8_t *)0x005303
//enable interrupt
TIM2_IER = 1;Ein zweites Merkmal betrifft ein PhĂ€nomen des Multiplexings wie Ghosting â das unerwĂŒnschte Leuchten von Dioden. In unserem Fall kann es auftreten, weil der Timer, der das Interrupt fĂŒr UEV auslöst, weiterhin tickt, und der Interrupt-Handler nicht schnell genug die LED umschalten kann, bevor der Timer bereits beginnt, etwas auf die AusgĂ€nge zu schreiben. Um dem entgegenzuwirken, mĂŒssen wir die Logik umkehren (0 = maximale Helligkeit, 255 = nichts leuchtet) und extreme Werte der TastverhĂ€ltnisse vermeiden. Das heiĂt, wir mĂŒssen sicherstellen, dass nach dem UEV die LEDs fĂŒr einen PWM-Takt vollstĂ€ndig erlöschen.
Wir Àndern die PolaritÀt:
//set polarity
TIM2_CCER1 |= (CC1P | CC2P);
TIM2_CCER2 |= CC3P;Vermeiden Sie es, r, g und b auf 255 zu setzen und denken Sie daran, sie bei der Verwendung zu invertieren.
Interrupts
Das Wesen von Interrupts ist, dass der Chip unter bestimmten UmstĂ€nden die Hauptprogrammatik stoppt und eine externe Funktion aufruft. Interrupts treten durch Ă€uĂere oder innere EinflĂŒsse auf, einschlieĂlich von Timern.
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 eingebunden wurde. In dieser Datei ist fĂŒr jede Unterbrechung eine Funktion zugewiesen NonHandledInterrupt. Wir mĂŒssen unsere Funktion der gewĂŒnschten Unterbrechung zuweisen.
Im Datenblatt gibt es eine Tabelle der Interruptvektoren, in der wir das benötigte finden:

13 TIM2 Update/Ăberlauf
14 TIM2 Capture/Vergleich
Wir mĂŒssen die LED bei UEV wechseln, also ist Unterbrechung Nr. 13 erforderlich.
Dementsprechend Ă€ndern wir zuerst im stm8_interrupt_vector.c den Namen der Standardfunktion fĂŒr Unterbrechung Nr. 13 (IRQ13) in unseren:
{0x82, TIM2_Overflow}, /* irq13 */Zweitens mĂŒssen wir eine Datei erstellen main.h mit folgendem Inhalt:
#ifndef __MAIN_H
#define __MAIN_H
@far @interrupt void TIM2_Overflow (void);
#endifUnd schlieĂlich definieren wir diese Funktion in unserem main.c:
@far @interrupt void TIM2_Overflow (void)
{
PD_ODR &= ~(1<<5); // Demultiplexer ausschalten
PC_ODR = (cnt<<3); // Neue Werte in den Demultiplexer schreiben
PD_ODR |= (1<<5); // Demultiplexer aktivieren
TIM2_SR1 = 0; // Interrupt-Flag fĂŒr Update zurĂŒcksetzen
cnt++;
cnt &= 7; // LED-ZĂ€hler verschieben
TIM2_CCR1L = ~colors[cnt][0]; // Invertierte Werte in den Puffer schreiben
TIM2_CCR2L = ~colors[cnt][1]; // FĂŒr den nĂ€chsten PWM-Zyklus
TIM2_CCR3L = ~colors[cnt][2]; //
return;
}Unterbrechungen mĂŒssen jetzt aktiviert werden. Dies geschieht mit einem Assembler-Befehl. rim â sie muss in :
//enable interrupts
_asm("rim");Ein anderer Assembler-Befehl â sim â schaltet Unterbrechungen aus. Diese mĂŒssen wĂ€hrend der Aufnahme neuer Werte in den âVideospeicherâ deaktiviert werden, damit ein unterbrochener Moment nicht das Array beschĂ€digt.
Der gesamte Code ist auf .

Wenn dieser Artikel auch nur jemandem nĂŒtzlich ist, dann habe ich ihn nicht umsonst geschrieben. Ich freue mich ĂŒber Kommentare und Anmerkungen und werde versuchen, auf alle zu antworten.
Quelle: habr.com
