Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

В der erste Teil Ich habe versucht, Hobby-Elektronikingenieuren, die mit Arduino-Hosen aufgewachsen sind, zu erklären, wie und warum sie Datenblätter und andere Dokumentationen für Mikrocontroller lesen sollten. Da der Text umfangreich ausfiel, habe ich versprochen, in einem separaten Artikel praktische Beispiele zu zeigen. Nun, er nannte sich selbst einen Milchpilz ...

Heute zeige ich Ihnen, wie Sie mithilfe von Datenblättern ganz einfache, aber für viele Projekte notwendige Aufgaben auf STM32- (Blue Pill) und STM8-Controllern lösen können. Alle Demo-Projekte sind meinen Lieblings-LEDs gewidmet, wir werden sie in großen Mengen zum Leuchten bringen, wofür wir allerlei interessante Peripheriegeräte verwenden müssen.

Der Text erwies sich wiederum als umfangreich, daher erstelle ich der Einfachheit halber den Inhalt:

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

Haftungsausschluss: Ich bin kein Ingenieur und behaupte nicht, über umfassende Kenntnisse in der Elektronik zu verfügen. Der Artikel richtet sich an Amateure wie mich. Tatsächlich betrachtete ich mich selbst vor zwei Jahren als Zielgruppe. Wenn mir damals jemand gesagt hätte, dass das Lesen von Datenblättern auf einem unbekannten Chip nicht gruselig sei, hätte ich nicht viel Zeit damit verbracht, im Internet nach Codefragmenten zu suchen und mit Schere und Klebeband Krücken zu erfinden.

Der Schwerpunkt dieses Artikels liegt auf Datenblättern, nicht auf Projekten, daher ist der Code möglicherweise nicht sehr ordentlich und oft eng. Die Projekte selbst sind sehr einfach, eignen sich jedoch zum ersten Kennenlernen des neuen Chips.

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

STM32

16 LEDs mit DM634 und SPI

Ein kleines Projekt mit Blue Pill (STM32F103C8T6) und DM634-LED-Treiber. Anhand von Datenblättern ermitteln wir den Treiber, die STM-IO-Ports und konfigurieren SPI.

DM634

Taiwanesischer Chip mit 16 16-Bit-PWM-Ausgängen, kettenfähig. Das Low-End-12-Bit-Modell ist aus einem heimischen Projekt bekannt Lichtpaket. Als ich einmal zwischen dem DM63x und dem bekannten TLC5940 wählte, entschied ich mich aus mehreren Gründen für DM: 1) TLC auf Aliexpress ist definitiv eine Fälschung, aber dieses hier nicht; 2) DM verfügt über eine autonome PWM mit eigenem Frequenzgenerator; 3) Es könnte günstig in Moskau gekauft werden, anstatt auf ein Paket von Ali zu warten. Und natürlich war es interessant zu lernen, wie man den Chip selbst steuert, anstatt eine vorgefertigte Bibliothek zu verwenden. Chips werden heute hauptsächlich im SSOP24-Gehäuse präsentiert und lassen sich einfach an einen Adapter anlöten.

Da der Hersteller taiwanesisch ist, Datenblatt Der Chip ist in chinesischem Englisch geschrieben, was bedeutet, dass es Spaß machen wird. Zuerst schauen wir uns die Pinbelegung an (Pin-Verbindung), um zu verstehen, an welches Bein was angeschlossen werden soll, und eine Beschreibung der Stifte (Pin Beschreibung). 16 Stifte:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
DC-Senkenquellen (Open Drain)

Sinken / Open-Drain-Ausgang - Abfluss; Quelle des einströmenden Stroms; Der Ausgang ist im aktiven Zustand mit Masse verbunden – die LEDs sind über Kathoden mit dem Treiber verbunden. Elektrisch gesehen handelt es sich hierbei natürlich nicht um einen „offenen Abfluss“ (offener Abfluss), aber in Datenblättern findet man diese Bezeichnung für Pins im Drain-Modus häufig.

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Externe Widerstände zwischen REXT und GND zur Einstellung des Ausgangsstromwerts

Zwischen dem REXT-Pin und Masse ist ein Referenzwiderstand installiert, der den Innenwiderstand der Ausgänge steuert, siehe Grafik auf Seite 9 des Datenblatts. Beim DM634 lässt sich dieser Widerstand auch per Software steuern und so die Gesamthelligkeit einstellen (globale Helligkeit); Ich werde in diesem Artikel nicht auf Details eingehen, sondern hier nur einen 2.2-3 kOhm-Widerstand einbauen.

Um zu verstehen, wie der Chip gesteuert wird, schauen wir uns die Beschreibung der Geräteschnittstelle an:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Ja, hier ist es, chinesisches Englisch in seiner ganzen Pracht. Das zu übersetzen ist problematisch, Sie können es verstehen, wenn Sie möchten, aber es geht auch anders – schauen Sie sich an, wie die Verbindung zum funktional ähnlichen TLC5940 im Datenblatt beschrieben wird:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
... Für die Dateneingabe in das Gerät sind nur drei Pins erforderlich. Die steigende Flanke des SCLK-Signals verschiebt die Daten vom SIN-Pin in das interne Register. Nachdem alle Daten geladen wurden, speichert ein kurzes hohes XLAT-Signal die sequentiell übertragenen Daten in den internen Registern. Interne Register sind Gatter, die durch den XLAT-Signalpegel ausgelöst werden. Bei allen Daten wird das höchstwertige Bit zuerst übertragen.

Verriegeln – Riegel/Riegel/Schloss.
Steigende Flanke – Vorderkante des Pulses
MSB zuerst – höchstwertiges (ganz linkes) Bit vorwärts.
um Daten zu takten – Daten sequentiell (Bit für Bit) übertragen.

Слово verriegeln findet sich oft in der Dokumentation von Chips und wird auf verschiedene Arten übersetzt, daher erlaube ich es mir aus Gründen des Verständnisses

ein kleines BildungsprogrammDer LED-Treiber ist im Wesentlichen ein Schieberegister. "Schicht" (verschieben) im Namen – bitweise Bewegung von Daten innerhalb des Geräts: Jedes neue Bit, das hineingeschoben wird, schiebt die gesamte Kette vor sich nach vorne. Da niemand während der Verschiebung ein chaotisches Blinken der LEDs beobachten möchte, findet der Vorgang in Pufferregistern statt, die durch einen Dämpfer von den Arbeitsregistern getrennt sind (verriegeln) ist eine Art Wartezimmer, in dem die Bits in der gewünschten Reihenfolge angeordnet sind. Wenn alles fertig ist, öffnet sich der Verschluss und die Bits machen sich an die Arbeit und ersetzen die vorherige Charge. Wort verriegeln In der Dokumentation zu Mikroschaltungen ist fast immer ein solcher Dämpfer impliziert, egal in welchen Kombinationen er verwendet wird.

Die Datenübertragung zum DM634 erfolgt also wie folgt: Stellen Sie den DAI-Eingang auf den Wert des höchstwertigen Bits der entfernten LED ein, ziehen Sie DCK nach oben und unten; Setzen Sie den DAI-Eingang auf den Wert des nächsten Bits und ziehen Sie DCK. und so weiter, bis alle Bits übertragen wurden (getaktet), danach ziehen wir LAT. Dies kann manuell erfolgen (Bit-Bang), besser ist es jedoch, eine speziell dafür zugeschnittene SPI-Schnittstelle zu verwenden, da diese auf unserem STM32 in zwei Exemplaren vorliegt.

Blaue Pille STM32F103

Einleitung: STM32-Controller sind viel komplexer als Atmega328, als sie vielleicht beängstigend erscheinen. Zudem sind aus Energiespargründen fast alle Peripheriegeräte beim Start ausgeschaltet und die Taktfrequenz beträgt 8 MHz aus der internen Quelle. Glücklicherweise haben STM-Programmierer Code geschrieben, der den Chip auf die „berechneten“ 72 MHz bringt, und die Autoren aller mir bekannten IDEs haben ihn in die Initialisierungsprozedur einbezogen, sodass wir nicht takten müssen (aber Du kannst, wenn du wirklich willst). Sie müssen jedoch die Peripheriegeräte einschalten.

Dokumentation: Blue Pill ist mit dem beliebten STM32F103C8T6-Chip ausgestattet, es gibt zwei nützliche Dokumente dazu:

Im Datenblatt könnten wir an Folgendem interessiert sein:

  • Pinbelegung – Chip-Pinbelegung – für den Fall, dass wir uns entscheiden, die Platinen selbst herzustellen;
  • Speicherzuordnung – Speicherzuordnung für einen bestimmten Chip. Das Referenzhandbuch enthält eine Karte für die gesamte Linie und erwähnt Register, die bei uns nicht vorhanden sind.
  • Pin-Definitionstabelle – Auflistung der Haupt- und Alternativfunktionen von Pins; Für die „blaue Pille“ finden Sie im Internet weitere praktische Bilder mit einer Auflistung der Pins und ihrer Funktionen. Deshalb googeln wir sofort die Pinbelegung von Blue Pill und halten dieses Bild bereit:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
NB: Es gab einen Fehler im Bild aus dem Internet, der in den Kommentaren vermerkt wurde, vielen Dank dafür. Das Bild wurde ersetzt, aber das ist eine Lektion – es ist besser, Informationen zu überprüfen, nicht aus Datenblättern.

Wir entfernen das Datenblatt, öffnen das Referenzhandbuch und verwenden von nun an nur noch dieses.
Vorgehensweise: Wir beschäftigen uns mit der Standard-Ein-/Ausgabe, konfigurieren SPI und schalten die erforderlichen Peripheriegeräte ein.

Input-Output

Beim Atmega328 ist I/O äußerst einfach implementiert, weshalb die Fülle an STM32-Optionen verwirrend sein kann. Jetzt brauchen wir nur noch Schlussfolgerungen, aber auch diese haben vier Möglichkeiten:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
offener Abfluss, Push-Pull, alternativer Push-Pull, alternativer offener Abfluss

"Ziehen drücken" (drücken ziehen) ist der übliche Ausgang des Arduino, der Pin kann entweder den Wert HIGH oder LOW annehmen. Aber mit „Open Drain“ gibt es solche Komplexität, obwohl hier eigentlich alles einfach ist:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Ausgangskonfiguration / wenn der Port dem Ausgang zugewiesen ist: / Ausgangspuffer aktiviert: / – Open-Drain-Modus: „0“ im Ausgangsregister aktiviert N-MOS, „1“ im Ausgangsregister lässt den Port im Hi-Z-Modus ( P-MOS ist nicht aktiviert ) / – Push-Pull-Modus: „0“ im Ausgangsregister aktiviert N-MOS, „1“ im Ausgangsregister aktiviert P-MOS.

Der ganze Unterschied zwischen offenem Abfluss (offener Abfluss) von „Push-Pull“ (drücken ziehen) liegt darin, dass der erste Pin den HIGH-Zustand nicht annehmen kann: Wenn einer in das Ausgangsregister geschrieben wird, wechselt er in den Hochwiderstandsmodus (hochohmig, Hallo-Z). Beim Schreiben von Null verhält sich der Pin in beiden Modi gleich, sowohl logisch als auch elektrisch.

Im normalen Ausgabemodus sendet der Pin einfach den Inhalt des Ausgaberegisters. In der „Alternative“ erfolgt die Steuerung durch die entsprechende Peripherie (siehe 9.1.4):

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Wenn ein Portbit als alternativer Funktionspin konfiguriert ist, wird das Pin-Register deaktiviert und der Pin wird mit dem Peripherie-Pin verbunden.

Die alternative Funktionalität jedes Pins wird in beschrieben Pin Definitionen Das Datenblatt befindet sich auf dem heruntergeladenen Bild. Auf die Frage, was zu tun ist, wenn ein Pin mehrere alternative Funktionen hat, gibt eine Fußnote im Datenblatt die Antwort:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Wenn mehrere Peripheriegeräte denselben Pin verwenden, sollte zur Vermeidung von Konflikten zwischen alternativen Funktionen jeweils nur ein Peripheriegerät verwendet werden, das mit dem Peripherietakt-Aktivierungsbit (im entsprechenden RCC-Register) umgeschaltet wird.

Schließlich haben Pins im Ausgabemodus auch eine Taktrate. Dies ist eine weitere Energiesparfunktion; in unserem Fall stellen wir sie einfach auf Maximum und vergessen es.

Also: Wir verwenden SPI, was bedeutet, dass zwei Pins (mit Daten und mit einem Taktsignal) eine „alternative Push-Pull-Funktion“ haben sollten und ein weiterer (LAT) eine „normale Push-Pull“-Funktion haben sollte. Aber bevor wir sie zuweisen, beschäftigen wir uns mit SPI.

SPI

Ein weiteres kleines Bildungsprogramm

SPI oder Serial Peripheral Interface (serielle Peripherieschnittstelle) ist eine einfache und sehr effektive Schnittstelle zum Verbinden eines MK mit anderen MKs und der Außenwelt im Allgemeinen. Das Funktionsprinzip wurde bereits oben beschrieben, wo es um den chinesischen LED-Treiber ging (im Referenzhandbuch, siehe Abschnitt 25). SPI kann im Master- („Master“) und Slave- („Slave“)-Modus betrieben werden. SPI verfügt über vier grundlegende Kanäle, von denen möglicherweise nicht alle verwendet werden:

  • MOSI, Master-Ausgang/Slave-Eingang: Dieser Pin überträgt Daten im Master-Modus und empfängt Daten im Slave-Modus.
  • MISO, Master Input / Slave Output: im Gegenteil, es empfängt im Master und sendet im Slave;
  • SCK, Serial Clock: Legt die Frequenz der Datenübertragung im Master fest oder empfängt ein Taktsignal im Slave. Im Wesentlichen schlagende Beats;
  • SS, Slave Select: Mit Hilfe dieses Kanals weiß der Sklave, dass er etwas von ihm will. Auf STM32 heißt es NSS, wobei N = negativ, d. h. Der Controller wird zum Slave, wenn in diesem Kanal Masse vorhanden ist. Es lässt sich gut mit dem Open-Drain-Ausgabemodus kombinieren, aber das ist eine andere Geschichte.

Wie alles andere ist auch SPI auf STM32 reich an Funktionen, was das Verständnis etwas erschwert. Es kann beispielsweise nicht nur mit SPI, sondern auch mit einer I2S-Schnittstelle funktionieren, und in der Dokumentation sind ihre Beschreibungen gemischt, es ist notwendig, den Überschuss rechtzeitig abzuschneiden. Unsere Aufgabe ist äußerst einfach: Wir müssen nur Daten nur mit MOSI und SCK senden. Wir gehen zu Abschnitt 25.3.4 (Halbduplex-Kommunikation, Halbduplex-Kommunikation), wo wir finden 1 Uhr und 1 unidirektionale Datenleitung (1 Taktsignal und 1 unidirektionaler Datenstrom):

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
In diesem Modus verwendet die Anwendung SPI entweder im reinen Sende- oder im reinen Empfangsmodus. / Der Nur-Sende-Modus ähnelt dem Duplex-Modus: Daten werden auf dem Sende-Pin (MOSI im Master-Modus oder MISO im Slave-Modus) übertragen, und der Empfangs-Pin (MISO bzw. MOSI) kann als regulärer I/O-Pin verwendet werden . In diesem Fall muss die Anwendung lediglich den Rx-Puffer ignorieren (wenn er gelesen wird, werden dort keine Daten übertragen).

Großartig, der MISO-Pin ist frei, schließen wir das LAT-Signal daran an. Schauen wir uns Slave Select an, das auf dem STM32 programmgesteuert gesteuert werden kann, was äußerst praktisch ist. Wir lesen den gleichnamigen Absatz in Abschnitt 25.3.1 Allgemeine SPI-Beschreibung:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Software-Steuerung NSS (SSM = 1) / Slave-Auswahlinformationen sind im SSI-Bit des SPI_CR1-Registers enthalten. Der externe NSS-Pin bleibt für andere Anwendungsanforderungen frei.

Es ist Zeit, in die Register zu schreiben. Ich habe mich für die Verwendung von SPI2 entschieden und suche im Datenblatt nach seiner Basisadresse – in Abschnitt 3.3 Speicherzuordnung:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Nun, fangen wir an:

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

Öffnen Sie Abschnitt 25.3.3 mit dem selbsterklärenden Titel „Konfigurieren von SPI im Master-Modus“:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

1. Stellen Sie die serielle Taktfrequenz mit den Bits BR[2:0] im SPI_CR1-Register ein.

Die Register sind im gleichnamigen Referenzhandbuchteil zusammengefasst. Adressverschiebung (Adressoffset) für CR1 – 0x00, standardmäßig sind alle Bits gelöscht (Wert zurücksetzen 0x0000):

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Die BR-Bits legen den Controller-Taktteiler fest und bestimmen so die Frequenz, mit der der SPI arbeitet. Unsere STM32-Frequenz beträgt 72 MHz, der LED-Treiber arbeitet laut Datenblatt mit einer Frequenz von bis zu 25 MHz, wir müssen also durch vier teilen (BR[2:0] = 001).

#define _SPI_CR1 0x00

#define BR_0        0x0008
#define BR_1        0x0010
#define BR_2        0x0020

_SPI2_ (_SPI_CR1) |= BR_0;// pclk/4

2. Setzen Sie die CPOL- und CPHA-Bits, um die Beziehung zwischen Datenübertragung und seriellem Takt-Timing zu definieren (siehe Diagramm auf Seite 240).

Da wir hier ein Datenblatt lesen und keine Schaltpläne betrachten, werfen wir einen genaueren Blick auf die Textbeschreibung der CPOL- und CPHA-Bits auf Seite 704 (SPI General Description):

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Taktphase und Polarität
Mithilfe der CPOL- und CPHA-Bits des SPI_CR1-Registers können Sie programmgesteuert vier Zeitbeziehungen auswählen. Das CPOL-Bit (Taktpolarität) steuert den Zustand des Taktsignals, wenn keine Daten übertragen werden. Dieses Bit steuert den Master- und Slave-Modus. Wenn CPOL zurückgesetzt wird, ist der SCK-Pin im Ruhemodus niedrig. Wenn das CPOL-Bit gesetzt ist, ist der SCK-Pin im Ruhemodus hoch.
Wenn das CPHA-Bit (Taktphase) gesetzt ist, ist der High-Bit-Trap-Strobe die zweite Flanke des SCK-Signals (fallend, wenn CPOL frei ist, steigend, wenn CPOL gesetzt ist). Die Daten werden durch die zweite Änderung des Taktsignals erfasst. Wenn das CPHA-Bit gelöscht ist, ist der High-Bit-Trap-Strobe die steigende Flanke des SCK-Signals (fallende Flanke, wenn CPOL gesetzt ist, steigende Flanke, wenn CPOL gelöscht ist). Daten werden bei der ersten Änderung des Taktsignals erfasst.

Nachdem wir dieses Wissen aufgenommen haben, kommen wir zu dem Schluss, dass beide Bits Nullen bleiben müssen, weil Wir möchten, dass das SCK-Signal niedrig bleibt, wenn es nicht verwendet wird, und dass Daten mit der ansteigenden Flanke des Impulses übertragen werden (siehe Abb. Steigende Flanke im DM634-Datenblatt).

Übrigens sind wir hier zum ersten Mal auf ein Merkmal des Vokabulars in ST-Datenblättern gestoßen: Dort steht der Satz „Bit auf Null zurücksetzen“. etwas zurücksetzenUnd nicht um ein bisschen aufzuklären, wie zum Beispiel Atmega.

3. Setzen Sie das DFF-Bit, um zu bestimmen, ob der Datenblock ein 8-Bit- oder ein 16-Bit-Format hat

Ich habe speziell einen 16-Bit-DM634 genommen, um mich nicht wie der DM12 mit der Übertragung von 633-Bit-PWM-Daten herumschlagen zu müssen. Es ist sinnvoll, DFF auf eins zu setzen:

#define DFF         0x0800

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

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

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

5. Wenn im Hardware-Modus eine Eingabe vom NSS-Pin erforderlich ist, legen Sie während der gesamten Byte-Übertragungssequenz ein High-Signal an den NSS-Pin an. Legen Sie im NSS-Softwaremodus die SSM- und SSI-Bits im SPI_CR1-Register fest. Soll der NSS-Pin als Ausgang genutzt werden, muss lediglich das SSOE-Bit gesetzt werden.

Installieren Sie SSM und SSI, um den NSS-Hardwaremodus zu vergessen:

#define SSI         0x0100
#define SSM         0x0200

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

6. Die MSTR- und SPE-Bits müssen gesetzt sein (sie bleiben nur gesetzt, wenn das NSS-Signal hoch ist)

Tatsächlich bestimmen wir mit diesen Bits unseren SPI als Master und schalten ihn ein:

#define MSTR        0x0004
#define SPE         0x0040

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

SPI ist konfiguriert. Schreiben wir sofort Funktionen, die Bytes an den Treiber senden. Lesen Sie weiter unter 25.3.3 „SPI im Mastermodus konfigurieren“:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Auftrag zur Datenübertragung
Die Übertragung beginnt, wenn ein Byte in den Tx-Puffer geschrieben wird.
Das Datenbyte wird bei in das Schieberegister geladen parallel Modus (vom internen Bus) während der Übertragung des ersten Bits, danach wird es an übertragen sequentiell MOSI-Pin-Modus, erstes oder letztes Bit vorwärts, abhängig von der Einstellung des LSBFIRST-Bits im CPI_CR1-Register. Das TXE-Flag wird nach der Datenübertragung gesetzt vom Tx-Puffer zum Schieberegisterund generiert außerdem einen Interrupt, wenn das TXEIE-Bit im CPI_CR1-Register gesetzt ist.

Ich habe in der Übersetzung einige Wörter hervorgehoben, um auf ein Merkmal der SPI-Implementierung in STM-Controllern aufmerksam zu machen. Auf Atmega ist die TXE-Flagge (Tx leer, Tx ist leer und bereit zum Datenempfang) wird erst gesetzt, nachdem das gesamte Byte gesendet wurde raus. Und hier wird dieses Flag gesetzt, nachdem das Byte in das interne Schieberegister eingefügt wurde. Da es mit allen Bits gleichzeitig (parallel) dorthin geschoben wird und die Daten dann sequentiell übertragen werden, wird TXE gesetzt, bevor das Byte vollständig gesendet wird. Das ist wichtig, weil Im Fall unseres LED-Treibers müssen wir nach dem Senden den LAT-Pin ziehen alle Daten, d.h. Die TXE-Flagge allein wird uns nicht ausreichen.

Das bedeutet, dass wir eine weitere Flagge brauchen. Schauen wir uns 25.3.7 – „Status Flags“ an:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
<…>
Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
BUSY-Flagge
Das BSY-Flag wird von der Hardware gesetzt und gelöscht (das Schreiben darauf hat keine Auswirkung). Das BSY-Flag zeigt den Zustand der SPI-Kommunikationsschicht an.
Es wird zurückgesetzt:
wenn die Übertragung abgeschlossen ist (außer im Master-Modus, wenn die Übertragung kontinuierlich erfolgt)
wenn SPI deaktiviert ist
wenn ein Mastermode-Fehler auftritt (MODF=1)
Wenn die Übertragung nicht kontinuierlich erfolgt, wird das BSY-Flag zwischen jeder Datenübertragung gelöscht

Okay, das wird sich als nützlich erweisen. Lassen Sie uns herausfinden, wo sich der Tx-Puffer befindet. Lesen Sie dazu „SPI Data Register“:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Bits 15:0 DR[15:0] Datenregister
Empfangene oder zu übertragende Daten.
Das Datenregister ist in zwei Puffer unterteilt – einen zum Schreiben (Sendepuffer) und einen zum Lesen (Empfangspuffer). Beim Schreiben in das Datenregister wird in den Tx-Puffer geschrieben, beim Lesen aus dem Datenregister wird der im Rx-Puffer enthaltene Wert zurückgegeben.

Nun, und das Statusregister, in dem die TXE- und BSY-Flags zu finden sind:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Wir schreiben:

#define _SPI_DR  0x0C
#define _SPI_SR  0x08

#define BSY         0x0080
#define TXE         0x0002

void dm_shift16(uint16_t value)
{
    _SPI2_(_SPI_DR) = value; //send 2 bytes
    while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent
}

Nun, da wir je nach Anzahl der LED-Treiberausgänge 16 mal zwei Bytes übertragen müssen, sieht das etwa so aus:

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

    while (_SPI2_(_SPI_SR) & BSY); // finish transmission

    LAT_pulse();
}

Aber wir wissen noch nicht, wie wir den LAT-Pin ziehen sollen, also kehren wir zu I/O zurück.

Pins zuweisen

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

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Jeder der Allzweck-E/A-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-Set/Reset-Register (GPIOx_BSRR), ein 16-Bit-Reset-Register (GPIOx_BRR) und ein 32-Bit-Set/Reset-Register (GPIOx_BRR). Bitblockierungsregister (GPIOx_LCKR).

Die ersten beiden Register sind ungewöhnlich und auch ziemlich unpraktisch, da die 16 Port-Pins im Format „vier Bits pro Bruder“ über sie verteilt sind. Diese. Die Pins XNUMX bis XNUMX sind in CRL und der Rest in CRH. Gleichzeitig enthalten die verbleibenden Register erfolgreich die Bits aller Pins des Ports – oft bleibt die Hälfte „reserviert“.

Der Einfachheit halber beginnen wir am Ende der Liste.

Wir brauchen kein Sperrregister.

Die Set- und Reset-Register sind insofern ziemlich lustig, als sie sich teilweise gegenseitig duplizieren: Sie können alles nur in BSRR schreiben, wobei die höheren 16 Bits den Pin auf Null zurücksetzen und die niedrigeren auf 1 gesetzt werden, oder Sie können es auch Verwenden Sie BRR, dessen untere 16 Bits nur den Pin zurücksetzen. Die zweite Option gefällt mir. Diese Register sind wichtig, weil sie atomaren Zugriff auf Pins ermöglichen:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Atomares Setzen oder Zurücksetzen
Beim Programmieren von GPIOx_ODR auf Bitebene müssen Interrupts nicht deaktiviert werden: Ein oder mehrere Bits können mit einem einzigen atomaren Schreibvorgang APB2 geändert werden. Dies wird erreicht, indem eine „1“ in das Setz-/Reset-Register (GPIOx_BSRR oder, nur zum Zurücksetzen, GPIOx_BRR) des Bits geschrieben wird, das geändert werden muss. Andere Bits bleiben unverändert.

Die Datenregister haben recht selbsterklärende Namen – IDR = zufuhr Richtungsregister, Eingaberegister; ODR = Output Richtungsregister, Ausgaberegister. Wir werden sie im aktuellen Projekt nicht benötigen.

Und schließlich Kontrollregister. Da uns die zweiten SPI-Pins interessieren, nämlich PB13, PB14 und PB15, schauen wir uns gleich CRH an:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

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

Wir haben oben bereits herausgefunden, was wir von den Pins erwarten, deshalb verzichte ich hier auf einen Screenshot, ich sage nur, dass MODE die Richtung (Eingabe, wenn beide Bits auf 0 gesetzt sind) und die Pin-Geschwindigkeit (wir benötigen 50 MHz, d. h. beide Pins auf „1“) und CNF legt den Modus fest: normal „Push-Pull“ – 00, „Alternative“ – 10. Standardmäßig haben alle Pins, wie wir oben sehen, das dritte Bit von unten (CNF0), es versetzt sie in den Modus schwebender Eingang.

Da ich vorhabe, etwas anderes mit diesem Chip zu machen, habe ich der Einfachheit halber alle möglichen MODE- und CNF-Werte sowohl für das untere als auch für das obere Steuerregister definiert.

Gut so etwas

#define CNF0_0 0x00000004
#define CNF0_1 0x00000008
#define CNF1_0 0x00000040
#define CNF1_1 0x00000080
#define CNF2_0 0x00000400
#define CNF2_1 0x00000800
#define CNF3_0 0x00004000
#define CNF3_1 0x00008000
#define CNF4_0 0x00040000
#define CNF4_1 0x00080000
#define CNF5_0 0x00400000
#define CNF5_1 0x00800000
#define CNF6_0 0x04000000
#define CNF6_1 0x08000000
#define CNF7_0 0x40000000
#define CNF7_1 0x80000000
#define CNF8_0 0x00000004
#define CNF8_1 0x00000008
#define CNF9_0 0x00000040
#define CNF9_1 0x00000080
#define CNF10_0 0x00000400
#define CNF10_1 0x00000800
#define CNF11_0 0x00004000
#define CNF11_1 0x00008000
#define CNF12_0 0x00040000
#define CNF12_1 0x00080000
#define CNF13_0 0x00400000
#define CNF13_1 0x00800000
#define CNF14_0 0x04000000
#define CNF14_1 0x08000000
#define CNF15_0 0x40000000
#define CNF15_1 0x80000000

#define MODE0_0 0x00000001
#define MODE0_1 0x00000002
#define MODE1_0 0x00000010
#define MODE1_1 0x00000020
#define MODE2_0 0x00000100
#define MODE2_1 0x00000200
#define MODE3_0 0x00001000
#define MODE3_1 0x00002000
#define MODE4_0 0x00010000
#define MODE4_1 0x00020000
#define MODE5_0 0x00100000
#define MODE5_1 0x00200000
#define MODE6_0 0x01000000
#define MODE6_1 0x02000000
#define MODE7_0 0x10000000
#define MODE7_1 0x20000000
#define MODE8_0 0x00000001
#define MODE8_1 0x00000002
#define MODE9_0 0x00000010
#define MODE9_1 0x00000020
#define MODE10_0 0x00000100
#define MODE10_1 0x00000200
#define MODE11_0 0x00001000
#define MODE11_1 0x00002000
#define MODE12_0 0x00010000
#define MODE12_1 0x00020000
#define MODE13_0 0x00100000
#define MODE13_1 0x00200000
#define MODE14_0 0x01000000
#define MODE14_1 0x02000000
#define MODE15_0 0x10000000
#define MODE15_1 0x20000000

Unsere Pins befinden sich auf 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 dementsprechend können Sie Definitionen für LAT schreiben, die von den BRR- und BSRR-Registern gezuckt werden:

/*** 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 nur durch Trägheit, das war schon immer so, lass es bleiben)

Jetzt ist alles super, aber es funktioniert nicht. Da es sich um STM32 handelt, sparen sie Strom, was bedeutet, dass Sie die Taktung der erforderlichen Peripheriegeräte aktivieren müssen.

Schalten Sie die Uhrfunktion ein

Die Uhr, auch Uhr genannt, ist für die Taktung zuständig. Und schon fiel uns die Abkürzung RCC auf. Wir suchen in der Dokumentation danach: Das ist Reset and Clock Control.

Wie bereits oben erwähnt, wurde der schwierigste Teil des Zeiterfassungsthemas glücklicherweise von Leuten von STM für uns erledigt, wofür wir ihnen sehr danken (ich werde noch einmal einen Link dazu geben). Di Halts Website, um deutlich zu machen, wie verwirrend es ist). Wir benötigen lediglich Register, die für die Aktivierung der Peripherietaktung verantwortlich sind (Peripheral Clock Enable Registers). Suchen wir zunächst die Basisadresse des RCC, sie steht ganz am Anfang der „Memory Map“:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

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

Und dann klicken Sie entweder auf den Link, in dem Sie versuchen, etwas in der Platte zu finden, oder, noch besser, gehen Sie die Beschreibungen der Aktivierungsregister aus den Abschnitten darüber durch Register aktivieren. Wo wir RCC_APB1ENR und RCC_APB2ENR finden:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Und sie enthalten dementsprechend Bits, die die Taktung von SPI2, IOPB (I/O Port B) und alternativen Funktionen (AFIO) umfassen.

#define _APB2ENR 0x18
#define _APB1ENR 0x1C

#define IOPBEN 0x0008
#define SPI2EN 0x4000
#define AFIOEN 0x0001

//включаем тактирование порта B и альт. функций
_RCC_(_APB2ENR) |= IOPBEN | AFIOEN;

//включаем  тактирование SPI2
_RCC_(_APB1ENR) |= SPI2EN;

Der endgültige Code kann gefunden werden hier.

Wenn Sie die Möglichkeit und Lust zum Testen haben, dann schließen Sie den DM634 wie folgt an: DAI an PB15, DCK an PB13, LAT an PB14. Wir versorgen den Treiber mit 5 Volt, vergessen Sie nicht, die Masse anzuschließen.

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

STM8-PWM

PWM auf STM8

Als ich diesen Artikel gerade plante, beschloss ich, beispielsweise zu versuchen, einige Funktionen eines unbekannten Chips nur anhand eines Datenblatts zu beherrschen, damit ich nicht bei einem Schuhmacher ohne Stiefel lande. STM8 war ideal für diese Rolle: Erstens hatte ich ein paar chinesische Boards mit STM8S103, und zweitens ist es nicht sehr beliebt, und daher beruht die Versuchung, im Internet zu lesen und eine Lösung zu finden, auf dem Fehlen dieser Lösungen.

Der Chip hat auch Datenblatt и Referenzhandbuch RM0016, im ersten gibt es Pinbelegung und Registeradressen, im zweiten - alles andere. STM8 ist in C in einer schrecklichen IDE programmiert ST Visual Develop.

Taktung und I/O

Standardmäßig arbeitet STM8 mit einer Frequenz von 2 MHz, dies muss sofort korrigiert werden.

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
HSI-Uhr (High Speed ​​​​Intern).
Das HSI-Taktsignal wird von einem internen 16-MHz-RC-Oszillator mit einem programmierbaren Teiler (1 bis 8) abgeleitet. Es wird im Taktteilerregister (CLK_CKDIVR) eingestellt.
Hinweis: Zu Beginn wird ein HSI-RC-Oszillator mit einem Teiler von 8 als führende Quelle des Taktsignals ausgewählt.

Wir finden die Registeradresse im Datenblatt, die Beschreibung in refman und sehen, dass das Register gelöscht werden muss:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Da wir PWM betreiben und die LEDs anschließen werden, schauen wir uns die Pinbelegung an:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Der Chip ist klein, viele Funktionen hängen an denselben Pins. Was in eckigen Klammern steht, ist „alternative Funktionalität“, es wird durch „Optionsbytes“ vertauscht (Optionsbytes) – so etwas wie Atmega-Sicherungen. Sie können ihre Werte programmgesteuert ändern, dies ist jedoch nicht erforderlich, da Die neue Funktionalität wird erst nach einem Neustart aktiviert. Es ist einfacher, ST Visual Programmer (heruntergeladen mit Visual Develop) zu verwenden, der diese Bytes ändern kann. Die Pinbelegung zeigt, dass die Pins CH1 und CH2 des ersten Timers in eckigen Klammern versteckt sind; Es ist notwendig, die Bits AFR1 und AFR0 in STVP zu setzen, und der zweite überträgt auch den CH1-Ausgang des zweiten Timers von PD4 an PC5.

Somit steuern 6 Pins die LEDs: PC6, PC7 und PC3 für den ersten Timer, PC5, PD3 und PA3 für den zweiten.

Das Einrichten der I/O-Pins selbst ist auf STM8 einfacher und logischer als auf STM32:

  • Bekannt aus dem Atmega DDR-Datenrichtungsregister (Datenrichtungsregister): 1 = Ausgabe;
  • das erste Steuerregister CR1 stellt bei seiner Ausgabe den Push-Pull-Modus (1) oder Open Drain (0) ein; Da ich die LEDs über Kathoden mit dem Chip verbinde, lasse ich hier Nullen;
  • Das zweite Steuerregister CR2 stellt bei der Ausgabe die Taktfrequenz ein: 1 = 10 MHz

#define PA_DDR     *(volatile uint8_t *)0x005002
#define PA_CR2     *(volatile uint8_t *)0x005004
#define PD_DDR     *(volatile uint8_t *)0x005011
#define PD_CR2     *(volatile uint8_t *)0x005013
#define PC_DDR     *(volatile uint8_t *)0x00500C
#define PC_CR2     *(volatile uint8_t *)0x00500E

PA_DDR = (1<<3); //output
PA_CR2 |= (1<<3); //fast
PD_DDR = (1<<3); //output
PD_CR2 |= (1<<3); //fast
PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output
PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fast

PWM-Einstellung

Definieren wir zunächst die Begriffe:

  • PWM-Frequenz – Häufigkeit, mit der der Timer tickt;
  • Automatisches Nachladen, AR – automatisch ladbarer Wert, bis zu dem der Timer zählt (Impulsperiode);
  • Update-Event, UEV – ein Ereignis, das auftritt, wenn der Timer bis AR gezählt hat;
  • PWM-Arbeitszyklus – PWM-Tastverhältnis, oft auch „Tastverhältnis“ genannt;
  • Wert erfassen/vergleichen – Wert für Erfassung/Vergleich, bis zu dem der Timer gezählt hat werde etwas tun (im Fall von PWM wird das Ausgangssignal invertiert);
  • Vorspannungswert – Voreingestellter Wert. Wert vergleichen kann sich nicht ändern, während der Timer läuft, da sonst der PWM-Zyklus unterbrochen wird. Daher werden neu übertragene Werte in einem Puffer abgelegt und herausgezogen, wenn der Timer das Ende seines Countdowns erreicht und zurückgesetzt wird;
  • Kantenbündig и Mittig ausgerichtete Modi – Ausrichtung entlang der Grenze und in der Mitte, genau wie bei Atmel Schnelle PWM и Phasenrichtiges PWM.
  • OCiREF, Ausgangsvergleichsreferenzsignal – Referenzausgangssignal, also das, was im PWM-Modus am entsprechenden Pin erscheint.

Wie bereits aus der Pinbelegung hervorgeht, verfügen zwei Timer über PWM-Fähigkeiten – der erste und der zweite. Beide sind 16-Bit, der erste verfügt über viele zusätzliche Funktionen (insbesondere kann er sowohl aufwärts als auch abwärts zählen). Da beide gleichwertig funktionieren müssen, habe ich beschlossen, mit dem offensichtlich schlechteren zweiten zu beginnen, um nicht versehentlich etwas zu verwenden, das nicht vorhanden ist. Ein Problem besteht darin, dass sich die Beschreibung der PWM-Funktionalität aller Timer im Referenzhandbuch im Kapitel über den ersten Timer (17.5.7 PWM-Modus) befindet, sodass Sie im Dokument ständig hin und her springen müssen.

PWM auf STM8 hat einen wichtigen Vorteil gegenüber PWM auf Atmega:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Grenzausgerichtetes PWM
Kontokonfiguration von unten nach oben
Die Bottom-Up-Zählung ist aktiv, wenn das DIR-Bit im TIM_CR1-Register gelöscht ist
Beispiel
Das Beispiel verwendet den ersten PWM-Modus. Das PWM-Referenzsignal OCiREF wird hoch gehalten, solange TIM1_CNT < TIM1_CCRi ist. Ansonsten braucht es ein niedriges Niveau. Wenn der Vergleichswert im TIM1_CCRi-Register größer als der Autoload-Wert (TIM1_ARR-Register) ist, wird das OCiREF-Signal auf 1 gehalten. Wenn der Vergleichswert 0 ist, wird OCiREF auf Null gehalten....

STM8-Timer während Ereignis aktualisieren prüft zuerst Wert vergleichenund erzeugt erst dann ein Referenzsignal. Der Timer von Atmega vermasselt zuerst und vergleicht dann, was zu Folgendem führt: compare value == 0 Die Ausgabe ist eine Nadel, mit der irgendwie umgegangen werden muss (z. B. durch programmgesteuerte Umkehrung der Logik).

Was wir also machen wollen: 8-Bit-PWM (AR == 255), Zählung von unten nach oben, Ausrichtung entlang der Grenze. Da die Glühbirnen über Kathoden mit dem Chip verbunden sind, sollte die PWM bis dahin 0 (LED an) ausgeben Wert vergleichen und 1 danach.

Über einige haben wir bereits gelesen PWM-Modus, also finden wir das erforderliche Register des zweiten Timers, indem wir im Referenzhandbuch nach diesem Ausdruck suchen (18.6.8 - TIMx_CCMR1):

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
110: Erster PWM-Modus – beim Zählen von unten nach oben ist der erste Kanal aktiv, während TIMx_CNT < TIMx_CCR1. Andernfalls ist der erste Kanal inaktiv. [Weiter im Dokument gibt es ein fehlerhaftes Kopieren und Einfügen von Timer 1] 111: Zweiter PWM-Modus – beim Zählen von unten nach oben ist der erste Kanal inaktiv, während TIMx_CNT < TIMx_CCR1. Ansonsten ist der erste Kanal aktiv.

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

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Bit 3 OC1PE: Vorspannung von Pin 1 aktivieren
0: Vorladeregister auf TIMx_CCR1 ist deaktiviert. Sie können jederzeit auf TIMx_CCR1 schreiben. Der neue Wert funktioniert sofort.
1: Vorladeregister auf TIMx_CCR1 ist aktiviert. Lese-/Schreibvorgänge greifen auf das Preload-Register zu. Der vorab geladene Wert TIMx_CCR1 wird bei jedem Update-Ereignis in das Schattenregister geladen.
*Hinweis: Damit der PWM-Modus ordnungsgemäß funktioniert, müssen die Vorladeregister aktiviert sein. Dies ist im Einzelsignalmodus nicht erforderlich (das OPM-Bit ist im TIMx_CR1-Register gesetzt).

Okay, schalten wir alles ein, was wir für die drei Kanäle des zweiten Timers brauchen:

#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 Acht-Bit-Registern, alles ist 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, es muss nichts geändert werden. Stellen wir den Frequenzteiler beispielsweise auf 256 ein. Für den zweiten Timer wird der Teiler im TIM2_PSCR-Register eingestellt und ist eine Zweierpotenz:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Es bleibt nur noch, die Schlussfolgerungen und den zweiten Timer selbst einzuschalten. Das erste Problem wird durch Register gelöst Erfassen/Vergleichen Ermöglichen: Es gibt zwei, drei Kanäle, die asymmetrisch über sie verteilt sind. Hier erfahren wir auch, dass es möglich ist, die Polarität des Signals zu ändern, d.h. Grundsätzlich war es möglich, den PWM-Modus 1 zu verwenden. 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 TIMx_CR1-Register:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Schreiben wir ein einfaches Analogon von AnalogWrite(), das die tatsächlichen Werte zum Vergleich an den Timer überträgt. Die Register sind vorhersehbar benannt Register erfassen/vergleichen, es gibt zwei davon für jeden Kanal: die niederwertigen 8 Bits in TIM2_CCRxL und die höherwertigen in TIM2_CCRxH. Da wir ein 8-Bit-PWM erstellt haben, reicht es aus, nur die niedrigstwertigen 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;
}

Dem aufmerksamen Leser wird auffallen, dass es sich um eine leicht defekte PWM handelt, die nicht in der Lage ist, eine 100-prozentige Füllung zu erzeugen (bei einem Maximalwert von 255 wird das Signal für einen Timer-Zyklus invertiert). Bei LEDs spielt das keine Rolle, und der aufmerksame Leser kann bereits erahnen, wie man das Problem beheben kann.

PWM beim zweiten Timer funktioniert, fahren wir mit dem ersten fort.

Der erste Timer hat genau die gleichen Bits in den gleichen Registern (nur die Bits, die im zweiten Timer „reserviert“ blieben, werden im ersten aktiv für alle möglichen fortgeschrittenen Dinge verwendet). Daher reicht es aus, die Adressen derselben Register im Datenblatt zu finden und den Code zu kopieren. Nun, ändern Sie den Wert des Frequenzteilers, denn... Der erste Timer möchte keine Zweierpotenz, sondern einen exakten 16-Bit-Wert in zwei Registern erhalten Prescaler hoch и Sneaker. Wir machen alles und... der erste Timer funktioniert nicht. Was ist los?

Das Problem kann nur gelöst werden, indem wir den gesamten Abschnitt über die Steuerregister von Timer 1 durchsehen und nach dem suchen, das der zweite Timer nicht hat. Es wird sein 17.7.30 Breakregister (TIM1_BKR), wo es dieses Bit gibt:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Hauptausgabe aktivieren

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

Das ist jetzt alles sicher, der Code ibid.

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

STM8-Multiplex

Multiplexing auf STM8

Das dritte Miniprojekt besteht darin, acht RGB-LEDs im PWM-Modus an den zweiten Timer anzuschließen und sie in verschiedene Farben zu bringen. Es basiert auf dem Konzept des LED-Multiplexings, das heißt, wenn man LEDs sehr, sehr schnell ein- und ausschaltet, scheint es uns, als wären sie ständig an (Beharrlichkeit des Sehens, Trägheit der visuellen Wahrnehmung). Das habe ich einmal getan so etwas in der Art auf Arduino.

Der Arbeitsalgorithmus sieht folgendermaßen aus:

  • die Anode der ersten RGB-LED angeschlossen;
  • zündete es an und sendete die notwendigen Signale an die Kathoden;
  • wartete bis zum Ende des PWM-Zyklus;
  • die Anode der zweiten RGB-LED angeschlossen;
  • habe es angezündet...

Na ja, usw. Für einen schönen Betrieb ist es natürlich erforderlich, dass die Anode angeschlossen ist und gleichzeitig die LED „gezündet“ wird. Na ja, oder fast. In jedem Fall müssen wir einen Code schreiben, der Werte in drei Kanälen des zweiten Timers ausgibt, diese ändert, wenn UEV erreicht ist, und gleichzeitig die aktuell aktive RGB-LED ändert.

Da die LED-Umschaltung automatisch erfolgt, müssen wir einen „Videospeicher“ erstellen, von dem der Interrupt-Handler Daten empfängt. Dies ist ein einfaches Array:

uint8_t colors[8][3];

Um die Farbe einer bestimmten LED zu ändern, reicht es aus, die erforderlichen Werte in dieses Array zu schreiben. Und die Variable ist für die Nummer der aktiven LED verantwortlich

uint8_t cnt;

Demuxen

Für ordnungsgemäßes Multiplexen benötigen wir seltsamerweise einen CD74HC238-Demultiplexer. Demultiplexer – ein Chip, der den Operator in Hardware implementiert <<. Über drei Eingangspins (Bits 0, 1 und 2) speisen wir ihm eine Drei-Bit-Zahl X ein, und als Reaktion darauf aktiviert er die Ausgangszahl (1<<X). Die verbleibenden Eingaben des Chips werden zur Skalierung des gesamten Designs verwendet. Wir benötigen diesen Chip nicht nur, um die Anzahl der belegten Pins des Mikrocontrollers zu reduzieren, sondern auch aus Sicherheitsgründen – um nicht versehentlich mehr LEDs als möglich einzuschalten und den MK nicht durchzubrennen. Der Chip kostet einen Cent und sollte immer in der Hausapotheke aufbewahrt werden.

Unser CD74HC238 ist für die Spannungsversorgung der Anode der gewünschten LED verantwortlich. In einem vollwertigen Multiplex würde es die Säule über einen P-MOSFET mit Spannung versorgen, aber in dieser Demo ist dies direkt möglich, weil Es verbraucht laut Angaben 20 mA absolut beste Bewertungen im Datenblatt. Aus Datenblatt CD74HC238 Wir brauchen Pinbelegungen und diesen Spickzettel:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
H = Hochspannungsniveau, L = Niederspannungsniveau, X – egal

Wir verbinden E2 und E1 mit Masse, E3, A0, A1 und A3 mit den Pins PD5, PC3, PC4 und PC5 von STM8. Da die obige Tabelle sowohl niedrige als auch hohe Pegel enthält, konfigurieren wir diese Pins als Push-Pull-Pins.

PWM

Die PWM des zweiten Timers ist auf die gleiche Weise wie in der vorherigen Geschichte konfiguriert, mit zwei Unterschieden:

Zuerst müssen wir den Interrupt aktivieren Ereignis aktualisieren (UEV), das eine Funktion aufruft, die die aktive LED umschaltet. Dies geschieht durch einen Bitwechsel Aktualisierungsunterbrechung aktivieren in einem Register mit einem aussagekräftigen Namen

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
Interrupt-Freigaberegister

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

Der zweite Unterschied hängt mit dem Phänomen des Multiplexings zusammen, z Geisterbilder – parasitäres Leuchten von Dioden. In unserem Fall kann es daran liegen, dass der Timer, nachdem er einen Interrupt im UEV verursacht hat, weiter tickt und der Interrupt-Handler keine Zeit hat, die LED zu schalten, bevor der Timer beginnt, etwas auf die Pins zu schreiben. Um dem entgegenzuwirken, müssen Sie die Logik umkehren (0 = maximale Helligkeit, 255 = nichts leuchtet) und extreme Arbeitszykluswerte vermeiden. Diese. Stellen Sie sicher, dass nach UEV die LEDs für einen PWM-Zyklus vollständig erlöschen.

Polarität ändern:

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

Vermeiden Sie es, r, g und b auf 255 einzustellen, und denken Sie daran, sie bei der Verwendung umzukehren.

Unterbrechungen

Das Wesen eines Interrupts besteht darin, dass der Chip unter bestimmten Umständen die Ausführung des Hauptprogramms stoppt und eine externe Funktion aufruft. Unterbrechungen entstehen durch äußere oder innere Einflüsse, auch durch den Timer.

Als wir zum ersten Mal ein Projekt in ST Visual Develop erstellt haben, zusätzlich zu main.c Wir erhielten ein Fenster mit einer mysteriösen Datei stm8_interrupt_vector.c, automatisch in das Projekt eingebunden. In dieser Datei ist jedem Interrupt eine Funktion zugeordnet NonHandledInterrupt. Wir müssen unsere Funktion an den gewünschten Interrupt binden.

Das Datenblatt enthält eine Tabelle mit Interrupt-Vektoren, in der wir diejenigen finden, die wir benötigen:

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8
13 TIM2-Update/Überlauf
14 TIM2 erfassen/vergleichen

Wir müssen die LED am UEV austauschen, also brauchen wir Interrupt Nr. 13.

Dementsprechend erstens in der Akte stm8_interrupt_vector.c Ändern Sie den Standardnamen der für Interrupt Nr. 13 (IRQ13) verantwortlichen Funktion in Ihren eigenen:

{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);
#endif

Und schließlich schreiben Sie diese Funktion in Ihr main.c:

@far @interrupt void TIM2_Overflow (void)
{
    PD_ODR &= ~(1<<5); // вырубаем демультиплексор
    PC_ODR = (cnt<<3); // записываем в демультиплексор новое значение
    PD_ODR |= (1<<5); // включаем демультиплексор

    TIM2_SR1 = 0; // сбрасываем флаг Update Interrupt Pending

    cnt++; 
    cnt &= 7; // двигаем счетчик LED

    TIM2_CCR1L = ~colors[cnt][0]; // передаем в буфер инвертированные значения
    TIM2_CCR2L = ~colors[cnt][1]; // для следующего цикла ШИМ
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Jetzt müssen nur noch Interrupts aktiviert werden. Dies geschieht mit dem Assembler-Befehl rim - Sie müssen danach suchen Programmierhandbuch:

//enable interrupts
_asm("rim");

Ein weiterer Assembler-Befehl ist sim – schaltet Interrupts aus. Sie müssen ausgeschaltet sein, während neue Werte in den „Videospeicher“ geschrieben werden, damit ein im falschen Moment verursachter Interrupt das Array nicht beschädigt.

Alle Codes – auf GitHub.

Lesen Sie Datenblätter 2: SPI auf STM32; PWM, Timer und Interrupts auf STM8

Wenn zumindest jemand diesen Artikel nützlich findet, dann habe ich ihn nicht umsonst geschrieben. Ich freue mich über Kommentare und Anmerkungen, ich werde versuchen, alles zu beantworten.

Source: habr.com

Kommentar hinzufügen