Wir lesen Datenblätter 2: SPI auf STM32; PWM-Timer und Interrupts auf dem STM8


Im ersten Teil habe ich versucht, elektronischen Hobbyisten, die aus Arduino-Hosen hervorgegangen sind, zu sagen, wie und warum sie Datenblätter und andere Dokumentationen für Mikrocontroller lesen sollten. Der Text erwies sich als groß, daher versprach ich, praktische Beispiele in einem separaten Artikel zu zeigen. Nun, er nannte sich eine Fracht ...


Heute werde ich zeigen, wie man Datenblätter verwendet, um ziemlich einfache Aufgaben zu lösen, die für viele Projekte auf den Controllern STM32 (Blue Pill) und STM8 erforderlich sind. Alle Demo-Projekte sind meinen Lieblings-LEDs gewidmet, wir werden sie in großen Mengen beleuchten, für die wir alle Arten von interessanten Peripheriegeräten verwenden müssen.


Der Text stellte sich wieder als riesig heraus. Der Einfachheit halber mache ich den Inhalt:


STM32 Blue Pill: 16 LEDs mit DM634-Treiber
STM8: Konfigurieren von sechs PWM-Pins
STM8: 8 RGB-LEDs an drei Pins, Interrupts


Haftungsausschluss: Ich bin kein Ingenieur, ich gebe nicht vor, ein tiefes Wissen in Elektronik zu haben, der Artikel ist für Liebhaber wie mich gedacht. Tatsächlich habe ich mich vor zwei Jahren als Zielgruppe betrachtet. Wenn mir damals jemand gesagt hätte, dass es nicht beängstigend ist, Datenblätter auf einem unbekannten Chip zu lesen, hätte ich nicht viel Zeit damit verbracht, im Internet nach Code zu suchen und Krücken mit einer Schere und einem Pflaster zu erfinden.


Im Zentrum dieses Artikels stehen Datenblätter, keine Projekte, sodass der Code möglicherweise nicht zu stark gekämmt und häufig überfüllt ist. Die Projekte selbst sind sehr einfach, obwohl sie für eine erste Bekanntschaft mit dem neuen Chip geeignet sind.


Ich hoffe, dass mein Artikel jemandem in einem ähnlichen Stadium eines Hobby-Tauchgangs hilft.


STM32


16 LEDs mit DM634 und SPI


Ein kleines Projekt mit der Blue Pill (STM32F103C8T6) und dem DM634 LED-Treiber. Mit Hilfe von Datenblättern werden wir uns mit dem Treiber, den IO-Ports STM und der Konfiguration von SPI befassen.


DM634


Der taiwanesische Chip mit 16 16-Bit-PWM-Ausgängen kann in Ketten geschaltet werden. Das jüngste 12-Bit-Modell ist für das Inlandsprojekt Lightpack bekannt . Als ich einmal zwischen dem DM63x und dem bekannten TLC5940 gewählt habe, habe ich aus mehreren Gründen beim DM angehalten: 1) TLC auf Aliexpress ist definitiv eine Fälschung, aber diese ist es nicht; 2) der DM hat eine autonome PWM mit einem eigenen Frequenzgenerator; 3) es könnte billig in Moskau gekauft werden und nicht auf ein Paket mit Ali warten. Natürlich war es interessant zu lernen, wie man den Chip selbst verwaltet und keine vorgefertigte Bibliothek verwendet. Die Chips sind jetzt hauptsächlich im SSOP24-Paket enthalten und lassen sich leicht an den Adapter anlöten.


Da der Hersteller Taiwaner ist, ist das Datenblatt für den Chip in chinesischem Englisch verfasst, was bedeutet, dass es Spaß machen wird. Schauen Sie sich zunächst die Pin-Verbindung ( Pin-Verbindung ) an, um zu verstehen, mit welchem ​​Bein verbunden werden soll, und die Beschreibung der Pins ( Pin-Beschreibung ). 16 Schlussfolgerungen:



Zuflussquellen von Gleichstrom (offener Drain)


Sink / Open-Drain-Ausgang - Drain; Quelle des eingehenden Stroms; aktiver Ausgang mit Masse verbunden - LEDs sind über Kathoden mit dem Treiber verbunden. Elektrisch ist dies natürlich kein „offener Drain“, aber in Datenblättern ist diese Bezeichnung für Ausgänge im Drain-Modus üblich.



Externe Widerstände zwischen REXT und GND zum Einstellen des Ausgangsstromwerts


Zwischen dem REXT-Pin und der Masse ist ein Referenzwiderstand installiert, der den Innenwiderstand der Ausgänge steuert (siehe Grafik auf Seite 9 des Datenblattes). In DM634 kann dieser Widerstand auch programmgesteuert durch Einstellen der globalen Helligkeit gesteuert werden. Ich werde in diesem Artikel nicht auf Details eingehen, ich habe hier nur einen 2,2 - 3 kOhm Widerstand eingefügt.


Informationen zur Steuerung des Chips finden Sie in der Beschreibung der Geräteschnittstelle:



Ja, hier ist es, chinesisches Englisch in seiner ganzen Pracht. Es ist problematisch zu übersetzen, Sie können es verstehen, wenn Sie es wünschen, aber es gibt noch einen anderen Weg - um zu sehen, wie die Verbindung im Datenblatt zum funktional geschlossenen TLC5940 beschrieben wird:



... Es sind nur drei Pins erforderlich, um Daten in das Gerät einzugeben. Die Vorderflanke des SCLK-Signals verschiebt Daten vom SIN-Pin zum internen Register. Nachdem alle Daten heruntergeladen wurden, erfasst ein kurzes hohes XLAT-Signal die seriellen Daten in internen Registern. Interne Register - XLAT-ausgelöste Absperrschieber. Alle Daten werden im höchstwertigen Bit vorwärts übertragen.


Riegel - ein Riegel / Riegel / Klemme.
Steigende Flanke - Vorderkante des Impulses
MSB first - das höchstwertige Bit ganz links.
Daten takten - Daten sequentiell (bitweise) übertragen.


Das Wort " Latch" wird häufig in der Dokumentation für Chips gefunden und auf verschiedene Arten übersetzt, sodass ich es mir erlauben werde, es zu verstehen

kleines Bildungsprogramm
Der LED-Treiber ist im Wesentlichen ein Schieberegister. "Shift" im Namen ist eine bitweise Bewegung von Daten innerhalb des Geräts: Jedes neue Bit, das nach innen verschoben wird, schiebt die gesamte Kette vor sich hin. Da niemand das chaotische Blinken von LEDs während der Verschiebung beobachten möchte, findet der Prozess in Pufferregistern statt, die durch einen Latch von den Arbeitsregistern getrennt sind - dies 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 werden an die Arbeit gesendet, wobei der vorherige Stapel ersetzt wird. Das Wort Latch in der Dokumentation für Mikroschaltungen impliziert fast immer einen solchen Dämpfer, unabhängig davon, in welcher Kombination er verwendet wird.

Die Datenübertragung zum DM634 ist also wie folgt: Setzen Sie den DAI-Eingang auf das höherwertige Bit der Fern-LED, ziehen Sie das DCK nach oben und unten; Setzen Sie den DAI-Eingang auf das nächste Bit und ziehen Sie DCK. und so weiter, bis alle Bits übertragen ( getaktet ) sind, wonach wir die LAT ziehen. Dies kann manuell erfolgen ( Bit-Bang ), es ist jedoch besser, die geschärfte SPI-Schnittstelle speziell dafür zu verwenden, da sie auf unserem STM32 in zwei Kopien dargestellt wird.


Blaue Tablette STM32F103


Einführung: STM32-Controller sind viel komplizierter als Atmega328, als sie erschrecken können. Gleichzeitig sind aus Gründen der Energieeinsparung fast alle Peripheriegeräte zu Beginn deaktiviert, und die Taktfrequenz von einer internen Quelle beträgt 8 MHz. Glücklicherweise haben die STM-Programmierer Code geschrieben, der den Chip auf die „berechneten“ 72 MHz brachte, und die Autoren aller mir bekannten IDEs haben ihn in den Initialisierungsvorgang einbezogen, sodass wir nicht takten müssen (aber Sie können, wenn Sie wirklich wollen ). Aber Sie müssen die Peripheriegeräte einschalten.


Dokumentation: Der beliebte STM32F103C8T6-Chip ist auf der Blue Pill installiert. Es gibt zwei nützliche Dokumente dafür:


  • Datenblatt für Mikrocontroller STM32F103x8 und STM32F103xB;
  • Referenzhandbuch für die gesamte STM32F103-Linie und darüber hinaus.

In einem Datenblatt könnten wir interessiert sein an:


  • Pinbelegung - Pinbelegung von Chips - für den Fall, dass wir uns entscheiden, selbst Bretter herzustellen;
  • Memory Map - Speicherkarte für einen bestimmten Chip. Im Referenzhandbuch gibt es eine Karte für die gesamte Zeile, in der die Register aufgelistet sind, die sich nicht in unserer befinden.
  • Tabellen-Pin-Definitionen - Auflistung der Haupt- und Alternativfunktionen von Pins; Für die „blaue Pille“ im Internet finden Sie bequemere Bilder mit einer Liste der Stifte und ihrer Funktionen. Google daher sofort die Pinbelegung der Blauen Pille und halte dieses Bild zur Hand:


NB: Auf dem Bild aus dem Internet wurde in den Kommentaren ein Fehler festgestellt, wofür ich mich bedanke. Das Bild wurde ersetzt, aber dies ist eine Lektion - es ist besser, Informationen aus Nicht-Datenblättern zu überprüfen.


Wir entfernen das Datenblatt, öffnen das Referenzhandbuch, jetzt verwenden wir es nur noch.
Vorgehensweise: Wir beschäftigen uns mit Standardeingabe / -ausgabe, konfigurieren SPI und schalten die gewünschten Peripheriegeräte ein.


Eingabe-Ausgabe


Atmega328 I / O ist extrem einfach, weshalb die Fülle an STM32-Optionen verwirrend sein kann. Jetzt brauchen wir nur noch Schlussfolgerungen, aber es gibt sogar vier Möglichkeiten:



Open-Drain-Ausgang, Push-Pull-Ausgang, alternativer Push-Pull-Ausgang, alternativer offener Drain


" Push-Pull " ( Push-Pull ) - die übliche Schlussfolgerung von Arduina, der Pin kann entweder HIGH oder LOW sein. Aber mit dem "offenen Abfluss" gibt es Schwierigkeiten , obwohl hier tatsächlich alles einfach ist:




Ausgangskonfiguration / Wenn der Port dem Ausgang zugewiesen ist: / Der Ausgangspuffer ist eingeschaltet: / - Open Drain-Modus: „0“ aktiviert N-MOS im Ausgangsregister, „1“ belässt den Port im Ausgangsregister im Hi-Z-Modus (P-MOS ist nicht aktiviert ) / - "Pull-Push" -Modus: "0" im Ausgangsregister aktiviert N-MOS, "1" im Ausgangsregister aktiviert P-MOS.


Der ganze Unterschied zwischen Open Drain und Push-Pull besteht darin, dass der erste Pin den HIGH-Zustand nicht akzeptieren kann: Wenn er eine Einheit in das Ausgangsregister schreibt, schaltet er auf hochohmig , Hi -Z ). Bei der Aufnahme von Null verhält sich der Pin in beiden Modi logisch und elektrisch gleich.


Im normalen Ausgangsmodus übersetzt der Pin einfach den Inhalt des Ausgangsregisters. In der "Alternative" wird es von der entsprechenden Peripherie gesteuert (siehe 9.1.4):



Wenn das Portbit als alternativer Funktionsausgang konfiguriert ist, wird das Ausgangsregister deaktiviert und der Pin mit dem peripheren Ausgangssignal verbunden


Eine alternative Funktionalität für jeden Pin ist im Datenblatt Pin-Definitionen beschrieben und befindet sich auf dem heruntergeladenen Bild. Auf die Frage, was zu tun ist, wenn der Pin mehrere alternative Funktionen hat, gibt die Antwort eine Fußnote im Datenblatt:



Wenn mehrere Peripherieblöcke denselben Pin verwenden, sollte zur Vermeidung eines Konflikts zwischen alternativen Funktionen gleichzeitig nur ein Peripherieblock verwendet werden, der mit dem Aktivierungsbit für den Peripherietakt (im entsprechenden RCC-Register) umschaltet.


Schließlich haben Pins im Ausgangsmodus auch eine Taktrate. Dies ist eine weitere Energiesparfunktion. In unserem Fall setzen wir sie einfach auf das Maximum und vergessen sie.


Also: Wir verwenden SPI, daher sollten zwei Pins (mit Daten und einem Taktsignal) eine „alternative Push-Push-Funktion“ und eine andere (LAT) eine „normale Pull-Push-Funktion“ sein. Aber bevor wir sie zuweisen, werden wir uns mit SPI befassen.


SPI


Ein weiteres kleines Bildungsprogramm

SPI oder Serial Peripherial Interface (serielle Peripherieschnittstelle) - eine einfache und sehr effektive Schnittstelle für die Kommunikation von MK mit anderen MK und der Außenwelt im Allgemeinen. Das Funktionsprinzip wurde bereits oben beschrieben, wo es um den chinesischen LED-Treiber geht (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 beteiligt sind:


  • MOSI, Master-Ausgang / Slave-Eingang: Dieser Pin sendet im Master-Modus, empfängt aber im Slave-Modus Daten.
  • MISO, Master Input / Slave Output: im Gegenteil, im Master akzeptiert, im Slave - gibt;
  • SCK, Serial Clock: Legt die Frequenz der Datenübertragung im Master fest oder empfängt ein Taktsignal im Slave. Im Wesentlichen schlägt Bits;
  • SS, Slave Select: Über diesen Kanal erfährt der Slave, dass er etwas davon möchte. Auf STM32 heißt es NSS, wobei N = negativ ist, d.h. Der Controller wird zum Slave, wenn in diesem Kanal Masse vorhanden ist. Kann gut mit dem Open Drain-Ausgabemodus kombiniert werden, aber das ist eine andere Geschichte.

Wie alles andere ist auch das SPI des STM32 reich an Funktionen, was das Verständnis etwas erschwert. Zum Beispiel kann es nicht nur mit dem SPI, sondern auch mit der I2S-Schnittstelle funktionieren, und in der Dokumentation, deren Beschreibungen verwechselt sind, müssen Sie den Überschuss rechtzeitig abschneiden. Unsere Aufgabe ist sehr einfach: Sie müssen nur Daten mit MOSI und SCK senden. Wir gehen zu Abschnitt 25.3.4 (Halbduplex-Kommunikation), wo wir 1 Takt und 1 unidirektionalen Datenkabel (1 Taktsignal und 1 unidirektionaler Datenstrom) finden:



In diesem Modus verwendet die Anwendung SPI entweder nur im Sende- oder im Empfangsmodus. / Der Nur-Senden-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 Eingangs- / Ausgangs-Pin verwendet werden. In diesem Fall reicht es aus, wenn die Anwendung den Empfangspuffer ignoriert (wenn Sie ihn lesen, werden keine Daten übertragen).


Nun, der MISO-Pin ist frei von uns. Verbinden wir das LAT-Signal damit. Wir werden uns mit Slave Select befassen, das auf dem STM32 programmgesteuert gesteuert werden kann, was äußerst praktisch ist. Wir lesen den gleichnamigen Absatz in Abschnitt 25.3.1 der Allgemeinen Beschreibung des SPI:



NSS-Programmsteuerung (SSM = 1) / Informationen zur Auswahl des Slaves sind im SSI-Bit des Registers SPI_CR1 enthalten. Der externe NSS-Pin bleibt für andere Anwendungsanforderungen frei.


Es ist Zeit, in Register zu schreiben. Ich habe mich für SPI2 entschieden. Wir suchen nach der Basisadresse im Datenblatt - in Abschnitt 3.3 Speicherzuordnung:



Nun, wir fangen an:


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

Wir öffnen Abschnitt 25.3.3 mit dem Spruch „Konfigurieren von SPI im Master-Modus“:



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


Die Register werden im gleichnamigen Referenzhandbuch gesammelt. CR1 hat einen Adressoffset ( Adressoffset ) von 0x00, standardmäßig werden alle Bits zurückgesetzt ( Reset-Wert 0x0000):



Die BR-Bits setzen den Regler-Taktfrequenzteiler und bestimmen so die Frequenz, bei der der SPI arbeiten wird. Wir werden eine STM32-Frequenz von 72 MHz haben, der LED-Treiber arbeitet laut Datenblatt mit einer Frequenz von bis zu 25 MHz, daher ist es notwendig, durch vier zu 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 Taktung der seriellen Schnittstelle zu bestimmen (siehe Abbildung auf Seite 249).


Da wir hier Datenblätter lesen und keine Schaltkreise berücksichtigen, sollten wir die Textbeschreibung der CPOL- und CPHA-Bits auf Seite 704 (SPI General Description) besser studieren:



Taktphase und Polarität
Unter Verwendung der CPOL- und CPHA-Bits des SPI_CR1-Registers können vier Optionen für Zeitsteuerungsbeziehungen programmgesteuert ausgewählt werden. Das CPOL-Bit (Clock Polarity) steuert den Status des Takts, wenn keine Daten übertragen werden. Dieses Bit steuert den Master- und Slave-Modus. Wenn der CPOL zurückgesetzt wird, ist der SCK-Pin im Leerlaufmodus niedrig. Wenn das CPOL-Bit gesetzt ist, befindet sich der SCK-Pin im Leerlaufmodus auf einem hohen Pegel.
Wenn das CPHA-Bit (Taktphase) gesetzt ist, fungiert die zweite Flanke des SCK-Signals als Trap-Gate des High-Bits (absteigend, wenn CPOL gelöscht ist, oder aufsteigend, wenn CPOL gesetzt ist). Daten werden durch die zweite Änderung des Taktsignals erfasst. Wenn das CPHA-Bit gelöscht ist, fungiert die Vorderflanke des SCK-Signals als Trap-Gate des High-Bits (nach unten, wenn CPOL gesetzt ist, oder nach oben, wenn CPOL gelöscht ist). Daten werden durch die erste Änderung des Taktsignals erfasst.


Nachdem wir dieses Wissen geraucht haben, schließen wir, dass beide Bits Nullen bleiben müssen, weil Das SCK-Signal muss niedrig bleiben, wenn es nicht verwendet wird, und die Daten müssen entlang der Vorderflanke des Impulses übertragen werden (siehe Steigende Flanke im DM634-Datenblatt).


Übrigens haben wir hier zum ersten Mal eine Funktion des Wortschatzes in ST-Datenblättern gefunden: In diesen wird der Ausdruck „Bit auf Null zurücksetzen“ geschrieben , um ein Bit zurückzusetzen und nicht ein bisschen zu löschen , wie zum Beispiel in Atmega.


3. Setzen Sie das DFF-Bit, um ein 8-Bit- oder 16-Bit-Datenblockformat zu definieren.


Ich habe speziell das 16-Bit-DM634 genommen, um mich nicht mit der Übertragung von 12-Bit-PWM-Daten wie dem DM633 zu beschäftigen. DFF macht Sinn, eine Einheit einzutragen:


 #define DFF 0x0800 _SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode 

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


LSBFIRST setzt, wie der Name schon sagt, das niederwertige Bit vorwärts. Der DM634 möchte jedoch Daten empfangen, die mit dem High-Bit beginnen. Deshalb lassen wir es weggeworfen.


5. Wenn im Hardwaremodus eine Eingabe vom NSS-Pin erforderlich ist, senden Sie während der gesamten Byte-Übertragungssequenz ein hohes Signal an den NSS-Pin. Setzen Sie im NSS-Programmiermodus die SSM- und SSI-Bits im Register SPI_CR1. Wenn der NSS-Pin am Ausgang arbeiten soll, muss nur 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 ein hohes Signal an das NSS angelegt wird).


Mit diesen Bits weisen wir unseren SPI als Master zu und schalten ihn ein:


 #define MSTR 0x0004 #define SPE 0x0040 _SPI2_ (_SPI_CR1) |= MSTR; //SPI master //  ,  SPI _SPI2_ (_SPI_CR1) |= SPE; 

Wenn SPI konfiguriert ist, schreiben wir Funktionen, die sofort Bytes an den Treiber senden. Wir lesen weiterhin 25.3.3 "Konfigurieren von SPI im Master-Modus":



Datenübertragungsverfahren
Die Übertragung beginnt, wenn ein Byte in den Sendepuffer geschrieben wird.
Das Datenbyte wird während der Übertragung des ersten Bits im Parallelmodus (vom internen Bus) in das Schieberegister geladen, wonach es im seriellen Modus an den MOSI-Pin übertragen wird, wobei das erste oder letzte Bit in Abhängigkeit von der Einstellung des LSBFIRST-Bits im Register CPI_CR1 weitergeleitet wird. Das TXEIE-Flag wird gesetzt, nachdem Daten vom Tx-Puffer in das Schieberegister übertragen wurden , und ein Interrupt wird auch erzeugt, wenn das TXEIE-Bit im CPI_CR1-Register gesetzt ist.


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


Und das bedeutet, dass wir eine andere Flagge brauchen. Mal sehen in 25.3.7 - "Statusflags":



<...>

BESETZTE Flagge
Das BSY-Flag wird von der Hardware gesetzt und zurückgesetzt (das Schreiben darauf hat keine Auswirkungen). 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 Assistentenmodusfehler auftritt (MODF = 1)
Wenn die Übertragung nicht kontinuierlich ist, wird das BSY-Flag zwischen jeder Datenübertragung gelöscht.


Okay, sei praktisch. Wir finden heraus, wo sich der Tx-Puffer befindet. Lesen Sie dazu 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 (Sendepuffer) und einen zum Lesen (Empfangspuffer). Das Schreiben in das Datenregister schreibt in den Tx-Puffer, und das Lesen aus dem Datenregister gibt den im Rx-Puffer enthaltenen Wert zurück.


Nun, das Statusregister, in dem es TXE- und BSY-Flags gibt:



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 Ausgänge des LED-Treibers zweimal 16 Bytes senden müssen, ungefähr so:


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

Wir wissen jedoch immer noch nicht, wie wir den Stift LAT ziehen sollen, und kehren daher zu I / O zurück.


Stifte zuweisen


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



Jeder der Allzweck-Eingangs- / Ausgangsports (GPIO) verfügt über zwei 32-Bit-Konfigurationsregister (GPIOx_CRL und GPIOx_CRH), zwei 32-Bit-Datenregister (GPIOx_IDR und GPIOx_ODR), ein 32-Bit-Set / Reset-Register (GPIOx_BSRR) und 16 Bit ein Rücksetzregister (GPIOx_BRR) und ein 32-Bit-Blockregister (GPIOx_LCKR).


Die ersten beiden Register sind ungewöhnlich und eher unpraktisch, da 16 Port-Pins im Format „vier Bits pro Bruder“ über sie verteilt sind. Das heißt, Die Pins 0 bis 7 befinden sich in der CRL und der Rest in der CRH. Gleichzeitig passen die restlichen Register erfolgreich in die Bits aller Pins des Ports - oft bleibt die Hälfte „reserviert“.


Beginnen Sie der Einfachheit halber am Ende der Liste.


Wir brauchen kein Blockregister.


Die Setup- und Reset-Register sind insofern sehr lustig, als sie sich teilweise duplizieren: Sie können alles nur in BSRR schreiben, wobei die höchsten 16 Bits den Pin auf Null zurücksetzen und die unteren auf 1 setzen oder auch BRR verwenden, dessen untere 16 Bits nur den Pin zurücksetzen . Ich mag die zweite Option. Diese Register sind insofern wichtig, als sie einen atomaren Zugang zu den Pins ermöglichen:




Atomic Installation oder Reset
Sie müssen Interrupts nicht deaktivieren, wenn Sie GPIOx_ODR auf Bitebene programmieren: Sie können ein oder mehrere Bits mit einer atomaren Schreiboperation APB2 ändern. Dies wird erreicht, indem „1“ in das Set / Reset-Register (GPIOx_BSRR oder nur zum Zurücksetzen in GPIOx_BRR) das Bit geschrieben wird, das Sie ändern möchten. Andere Bits bleiben unverändert.


Datenregister haben ganz sprechende Namen - IDR = Eingangsrichtungsregister , Eingangsregister; ODR = Ausgangsrichtungsregister , Ausgangsregister. Im aktuellen Projekt werden wir sie nicht brauchen.


Und schließlich Kontrollregister. Da wir an den Pins des zweiten SPI interessiert sind, nämlich PB13, PB14 und PB15, schauen wir uns sofort CRH an:



Und wir sehen, dass es notwendig sein wird, vom 20. bis zum 31. etwas in Bits zu schreiben.


Wir haben bereits herausgefunden, was wir von den Pins wollen, also kann ich hier auf einen Screenshot verzichten. Sagen wir einfach, dass MODE die Richtung (Eingabe, wenn beide Bits auf 0 gesetzt sind) und die Pin-Geschwindigkeit (wir brauchen 50 MHz, d. H. Beide) einstellt Pin in „1“) und CNF stellt den Modus ein: normales „Push-Push“ - 00, „Alternative“ - 10. Wie oben gezeigt, haben alle Pins standardmäßig das dritte Bit von unten (CNF0), auf das sie gesetzt werden schwebender Eingabemodus .


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


So etwas in der Art
 #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 an Port B (die Basisadresse ist 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; 

Dementsprechend können Sie Definitionen für die LAT schreiben, die die Register BRR und BSRR zucken:


 /*** 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, irgendwie war es immer, lass dich bleiben)


Jetzt ist schon alles super, funktioniert einfach nicht. Da es sich um STM32 handelt, wird Strom gespart, sodass Sie die Uhr für die erforderlichen Peripheriegeräte aktivieren müssen.


Timing einschalten


Die Uhr ist für die Uhr verantwortlich, sie sind auch Uhr. Und wir konnten bereits die Abkürzung RCC sehen. Wir suchen es in der Dokumentation: Dies ist Reset und Clock Control.


Wie oben erwähnt, haben die Mitarbeiter von STM zum Glück den schwierigsten Teil des Timing-Themas für uns erledigt. Vielen Dank für sie (ich werde Ihnen einen Link zurück zur Website von Di Halt geben , um zu verdeutlichen, wie verwirrt dies ist). Wir brauchen nur die Register, die für die Aktivierung von Peripherietakten verantwortlich sind (Peripheral Clock Enable Registers). Suchen Sie zuerst die Basisadresse von RCC, sie befindet sich ganz am Anfang der "Speicherkarte":



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

Klicken Sie dann entweder auf den Link, über den Sie versuchen, etwas in der Platte zu finden, oder lesen Sie die Beschreibungen der Einschlussregister in den Abschnitten zu den Aktivierungsregistern . Wo wir RCC_APB1ENR und RCC_APB2ENR finden:




Und in ihnen jeweils Bits, einschließlich Taktung von SPI2, IOPB (E / A-Port B) und alternativen Funktionen (AFIO).


 #define _APB2ENR 0x18 #define _APB1ENR 0x1C #define IOPBEN 0x0008 #define SPI2EN 0x4000 #define AFIOEN 0x0001 //   B  .  _RCC_(_APB2ENR) |= IOPBEN | AFIOEN; //  SPI2 _RCC_(_APB1ENR) |= SPI2EN; 

Den endgültigen Code finden Sie hier .


Wenn es eine Gelegenheit und einen Wunsch zum Testen gibt, verbinden wir DM634 wie folgt: DAI mit PB15, DCK mit PB13, LAT mit PB14. Wir speisen den Fahrer ab 5 Volt, vergessen Sie nicht, die Erde zu kombinieren.



STM8 PWM


PWM auf STM8


Als ich diesen Artikel zum Beispiel plante, habe ich mich entschlossen, einige Funktionen eines unbekannten Chips mit nur einem Datenblatt zu erlernen, damit ich keinen Schuhmacher ohne Stiefel bekomme. STM8 war ideal für diese Rolle geeignet: Erstens hatte ich ein paar chinesische Motherboards mit STM8S103, und zweitens ist es nicht sehr beliebt, und daher besteht die Versuchung, im Internet eine Lösung zu lesen und zu finden, darin, dass genau diese Lösungen fehlen.


Der Chip hat auch ein Datenblatt und ein Referenzhandbuch RM0016 , in der ersten Pinbelegung und Registeradressen, in der zweiten - alles andere. STM8 ist in C in der hässlichen IDE ST Visual Develop programmiert.


Uhr und E / A.


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



HSI-Uhr (interne Geschwindigkeit)
Der HSI-Takt wird von einem internen 16-MHz-RC-Oszillator mit einem programmierbaren Teiler (1 bis 8) erhalten. Sie wird im Register des Taktteilers (CLK_CKDIVR) eingestellt.
Hinweis: Zu Beginn wird der HSI-RC-Oszillator mit Teiler 8 als führende Taktquelle 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 starten und die LEDs anschließen, sehen wir uns die Pinbelegung an:



Der Chip ist klein, viele Funktionen sind an denselben Pins aufgehängt. Die Tatsache, dass in eckigen Klammern „alternative Funktionalität“ steht, wird durch „ Optionsbytes “ umgeschaltet - so etwas wie Atmegas Sicherungen. Sie können ihre Werte programmgesteuert ändern, sind aber nicht erforderlich, da Neue Funktionen werden erst nach einem Neustart aktiviert. Es ist einfacher, ST Visual Programmer (Downloads zusammen mit Visual Develop) zu verwenden, mit dem diese Bytes geändert werden können. Die Pinbelegung zeigt, dass die Schlussfolgerungen von 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 Ausgang von CH1 des zweiten Timers von PD4 auf 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 Konfigurieren der E / A-Pins selbst auf dem STM8 ist einfacher und logischer als auf dem STM32:


  • vertraut mit Atmega Data Direction Register : 1 = Ausgabe;
  • das erste Steuerregister CR1 am Ausgang stellt den Gegentaktmodus (1) oder den offenen Drain (0) ein; Da ich die LEDs mit Kathoden an den Chip anschließe, lasse ich hier Nullen.
  • Das zweite Steuerregister CR2 am Ausgang stellt die Taktrate 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-Setup


Definieren wir zunächst die Begriffe:


  • PWM-Frequenz - Frequenz, mit der der Timer tickt;
  • Auto-Reload, AR - Autoload-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-Arbeitszyklus, häufig als "Arbeitszyklus" bezeichnet;
  • Capture / Compare Value - Wert für Capture / Compare , der gezählt hat, bis zu dem der Timer etwas unternimmt (im Fall von PWM invertiert er das Ausgangssignal);
  • Preload Value - vorinstallierter Wert. Der Vergleichswert kann sich nicht ändern, während der Timer tickt, da sonst der PWM-Zyklus unterbrochen wird. Daher werden die neu übertragenen Werte in den Puffer gelegt und von dort herausgezogen, wenn der Timer das Ende der Zählung erreicht und zurückgesetzt wird.
  • Modi für Kantenausrichtung und Mittelausrichtung - Ausrichtung am Rand und in der Mitte, genau wie bei Atmelovskie Fast PWM und Phasenkorrektem PWM .
  • OCiREF, Output Compare Reference Signal - das Referenzausgangssignal, das sich im PWM-Modus tatsächlich am entsprechenden Pin befindet.

Wie bereits aus der Pinbelegung hervorgeht, verfügen zwei Timer über die PWM-Funktionen - der erste und der zweite. Beide sind 16-Bit, das erste hat viele zusätzliche Funktionen (insbesondere kann es sowohl nach oben als auch nach unten zählen). Wir müssen beide auf die gleiche Weise arbeiten, also habe ich beschlossen, mit der offensichtlich ärmeren Sekunde zu beginnen, um nicht versehentlich etwas zu verwenden, das nicht darin enthalten ist. Ein Problem besteht darin, dass die Beschreibung der PWM-Funktionalität aller Timer im Referenzhandbuch im Kapitel über den ersten Timer (17.5.7 PWM-Modus) enthalten ist, sodass Sie im gesamten Dokument hin und her springen müssen.


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



PWM mit Kantenausrichtung
Kontokonfiguration von unten nach oben
Die Bottom-Up-Zählung ist aktiv, wenn das DIR-Bit im Register TIM_CR1 gelöscht wird
Beispiel
Das Beispiel verwendet den ersten PWM-Modus. Das OCiREF PWM-Referenzsignal wird hoch gehalten, während TIM1_CNT <TIM1_CCRi. Ansonsten nimmt es ein niedriges Niveau. Wenn der Vergleichswert im Register TIM1_CCRi größer als der Startwert (Register TIM1_ARR) ist, wird das OCiREF-Signal auf 1 gehalten. Wenn der Vergleichswert 0 ist, wird OCiREF auf Null gehalten. ...


Der STM8-Timer überprüft während des Aktualisierungsereignisses zuerst den Vergleichswert und gibt erst dann ein Referenzsignal aus. Der Timer von Atmega mischt zuerst und vergleicht dann, wodurch die Ausgabe beim compare value == 0 zu einer Nadel führt, die irgendwie bekämpft werden muss (z. B. durch programmgesteuertes Invertieren der Logik).


Also, was wir tun wollen: 8-Bit-PWM ( AR == 255 ), betrachten wir von unten nach oben, Ausrichtung entlang der Grenze. Da die Glühbirnen über Kathoden mit dem Chip verbunden sind, muss die PWM vor dem Vergleich des Werts 0 (LED leuchtet) und danach 1 ausgeben.


Wir haben bereits über einige PWM-Modi gelesen und finden das gewünschte Register des zweiten Timers, indem wir im Referenzhandbuch nach diesem Satz suchen (18.6.8 - TIMx_CCMR1):



110: Erster PWM-Modus - Wenn von unten nach oben gezählt wird, ist der erste Kanal aktiv, während TIMx_CNT <TIMx_CCR1. Andernfalls ist der erste Kanal inaktiv. [weiter im Dokument fehlerhaftes Kopieren und Einfügen von Timer 1]
111: Zweiter PWM-Modus - Wenn von unten nach oben gezählt wird, ist der erste Kanal inaktiv, während TIMx_CNT <TIMx_CCR1. Ansonsten ist der erste Kanal aktiv.


Da die LEDs an die MK-Kathoden angeschlossen sind, ist der zweite Modus für uns geeignet (der erste auch, aber wir wissen es noch nicht).



Bit 3 OC1PE: Aktivieren Sie das Vorladen von Ausgang 1
0: Register vorladen bei TIMx_CCR1 aus. Sie können jederzeit in TIMx_CCR1 schreiben. Der neue Wert funktioniert sofort.
1: Das Preload-Register bei TIMx_CCR1 ist aktiviert. Lese- / Schreibvorgänge greifen auf das Preload-Register zu. Der vorinstallierte Wert TIMx_CCR1 wird bei jedem Aktualisierungsereignis in das Schattenregister geladen.
* Hinweis: Damit der PWM-Modus ordnungsgemäß funktioniert, müssen die Vorspannungsregister aktiviert sein. ( TIMx_CR1 OPM).

, , , :


 #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 , :


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

-, , . , , 256. TIM2_PSCR :


 #define TIM2_PSCR *(volatile uint8_t *)0x00530E TIM2_PSCR = 8; 

. Capture/Compare Enable : , . , , .. PWM Mode 1. :


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

, , TIMx_CR1:



 #define TIM2_CR1 *(volatile uint8_t *)0x005300 TIM2_CR1 |= 1; 

AnalogWrite(), . Capture/Compare registers , : 8 TIM2_CCRxL TIM2_CCRxH. 8- , :


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

, , 100% ( 255 ). , , .


, .


( , «» , ). . , .. , 16- Prescaler High Low . … . Was ist los?


1, , . 17.7.30 Break register (TIM1_BKR) , :




 #define TIM1_BKR *(volatile uint8_t *)0x00526D TIM1_BKR = (1<<7); 

, .



STM8 Multiplex


STM8


- , RGB- . – LED-, , - , , ( persistence of vision , ). - - .


:


  • RGB LED;
  • , ;
  • ;
  • RGB LED;
  • ...

.. , , «» . . , , , UEV RGB-.


LED , «», . :


 uint8_t colors[8][3]; 

, , .


 uint8_t cnt; 


, , CD74HC238. – , << . ( 0, 1 2) X, ( 1<<X ). . , – , , . , .


CD74HC238 , . P-MOSFET, , .. 20 , absolute maximum ratings . CD74HC238 :



H = , L = , X –


E2 E1 , E3, A0, A1 A3 PD5, PC3, PC4 PC5 STM8. , , push-pull .



, , :


-, Update Event (UEV), , LED. Update Interrupt Enable




 #define TIM2_IER *(volatile uint8_t *)0x005303 //enable interrupt TIM2_IER = 1; 

, ghosting – . - , , UEV, , LED - . (0 = , 255 = ) . Das heißt, , UEV .


:


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

r, g b 255 .



, - . - , .


ST Visual Develop, main.c stm8_interrupt_vector.c , . NonHandledInterrupt . .


, :



13 TIM2 /
14 TIM2 /


LED UEV, №13.


, -, stm8_interrupt_vector.c , №13 (IRQ13) :


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

-, main.h :


 #ifndef __MAIN_H #define __MAIN_H @far @interrupt void TIM2_Overflow (void); #endif 

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

. rimProgramming Manual :


 //enable interrupts _asm("rim"); 

sim – . «», .


.



- , , . , .

Source: https://habr.com/ru/post/de456094/


All Articles