ARM mit Cortex Mx-Kern (am Beispiel STM32F10x)

Der Mikrocontroller ARM Cortex M3 STM32F103c8t6 wird häufig als 32-Bit-Mikrocontroller für Amateurprojekte verwendet. Wie für fast jeden Mikrocontroller gibt es ein SDK, einschließlich C ++ - Header-Dateien zur Bestimmung der Peripherie des Controllers.
Dort wird beispielsweise die serielle Schnittstelle als Datenstruktur definiert, und eine Instanz dieser Struktur befindet sich in dem für Register reservierten Adressbereich, und wir haben über einen Zeiger auf eine bestimmte Adresse Zugriff auf diesen Bereich.
Für diejenigen, die dies noch nicht erlebt haben, werde ich ein wenig beschreiben, wie es definiert ist. Dieselben Leser, die damit vertraut sind, können diese Beschreibung überspringen.
Diese Struktur und ihre Instanz werden wie folgt beschrieben:
typedef struct { __IO uint32_t CR1; . . . __IO uint32_t ISR; } USART_TypeDef;
Weitere Details finden Sie hier
stm32f103xb.h ≈ 800 kBWenn Sie nur die Definitionen in dieser Datei verwenden, müssen Sie wie folgt schreiben (Beispiel für die Verwendung des Statusregisters für die serielle Schnittstelle):
Und Sie müssen es verwenden, weil die vorhandenen proprietären Lösungen CMSIS und HAL zu komplex sind, um in Amateurprojekten verwendet zu werden.
Wenn Sie jedoch in C ++ schreiben, können Sie folgendermaßen schreiben:
Eine veränderbare Referenz wird mit einem Zeiger initialisiert. Das ist eine kleine Erleichterung, aber angenehm. Besser noch, natürlich eine kleine Wrapper-Klasse darüber zu schreiben, während diese Technik immer noch nützlich ist.
Natürlich möchte ich diese Wrapper-Klasse sofort über eine serielle Schnittstelle (EUSART - erweiterter universeller serieller asinhronischer Reseiver-Sender) schreiben, die so attraktiv ist, über erweiterte Funktionen verfügt, einen seriellen asynchronen Transceiver und die Möglichkeit bietet, unseren kleinen Mikrocontroller mit einem Desktop-System oder Laptop, aber mit Mikrocontrollern zu verbinden Cortex zeichnet sich durch ein entwickeltes Taktsystem aus, und Sie müssen davon ausgehen und dann die entsprechenden E / A-Pins für die Arbeit mit Peripheriegeräten konfigurieren, da in der STM32F1xx-Serie wie in legged andere Mikrocontrollern ARM Cortex können den Port-Pins zu Ein- oder Ausgang und arbeitet in der gleichen Zeit mit der Peripherie nicht nur konfigurieren.
Beginnen wir mit dem Einschalten des Timings. Das Taktsystem wird als RCC-Register zur Taktsteuerung bezeichnet und stellt auch eine Datenstruktur dar, deren deklariertem Zeiger ein bestimmter Adresswert zugewiesen ist.
typedef struct { . . . } RCC_TypeDef;
Felder dieser Struktur werden wie folgt deklariert, wobei __IO flüchtig definiert:
__IO uint32_t CR;
entsprechen den Registern von RCC, und die einzelnen Bits dieser Register sind eingeschaltet oder die Taktfunktionen der Peripherie des Mikrocontrollers. All dies ist in der
Dokumentation (pdf) gut beschrieben.
Ein Zeiger auf eine Struktur ist definiert als
#define RCC ((RCC_TypeDef *)RCC_BASE)
Das Arbeiten mit Registerbits ohne Verwendung des SDK sieht normalerweise folgendermaßen aus:
Hier ist die Aufnahme von Port A.
Sie können zwei oder mehr Bits gleichzeitig aktivieren
Es sieht für C ++ etwas ungewöhnlich aus oder etwas Ungewöhnliches. Es wäre besser, anders zu schreiben, beispielsweise mit OOP.
Es sieht besser aus, aber im 21. Jahrhundert werden wir etwas weiter gehen, C ++ 17 verwenden und mit Vorlagen mit einer variablen Anzahl von Parametern schreiben, die noch schöner sind:
Wo Rcc wie folgt definiert ist:
Daraus werden wir beginnen, einen Wrapper über die Taktregister zu bauen. Zuerst definieren wir eine Klasse und einen Zeiger (Link) darauf.
Zuerst wollte ich in den C ++ 11/14-Standard schreiben, indem ich die Parameter einer Funktionsvorlage rekursiv entpackte. Ein guter Artikel dazu finden Sie am Ende des Artikels im Link-Bereich.
Betrachten Sie den Aufruf, um die Port-Taktung zu aktivieren:
Rcc.PortOn<GPort::A>();
GCC wird es in einer Reihe von Befehlen bereitstellen:
ldr r3, [pc, #376] ; (0x8000608 <main()+392>) ldr r0, [r3, #24] orr.w r0, r0, #4 str r0, [r3, #24]
Hat es geklappt? Überprüfen Sie als nächstes
Rcc.PortOn<GPort::A, GPort::B, GPort::C>();
Leider hat der nicht so naive GCC den nachfolgenden Rekursionsaufruf separat bereitgestellt:
ldr r3, [pc, #380] ; (0x8000614 <main()+404>) ldr r0, [r3, #24] orr.w r0, r0, #4 ; APB2ENR |= GPort::A str r0, [r3, #24] ldr r0, [r3, #24] orr.w r0, r0, #28 ; APB2ENR |= Gport::B | GPort::C str r0, [r3, #24] #24]
Zur Verteidigung von GCC muss ich sagen, dass dies nicht immer der Fall ist, sondern nur in komplexeren Fällen, die bei der Implementierung der E / A-Portklasse auftreten werden. Nun, C ++ 17 hat es eilig zu helfen. Schreiben Sie die TRCC-Klasse mithilfe der integrierten Bildlauffunktionen neu.
Nun stellte sich heraus:
ldr r2, [pc, #372] ; (0x800060c <main()+396>) ldr r0, [r2, #24] orr.w r0, r0, #28 ; APB2ENR |= Gport::A | Gport::B | GPort::C str r0, [r3, #24]
Und der Klassencode ist einfacher geworden.
Fazit: Mit C ++ 17 können wir die Vorlagen mit einer variablen Anzahl von Parametern verwenden, um den gleichen Mindestbefehlssatz (auch wenn die Optimierung deaktiviert ist) zu erhalten, der bei Verwendung der klassischen Arbeit mit dem Mikrocontroller durch Registerdefinitionen erhalten wird. Gleichzeitig erhalten wir jedoch alle Vorteile einer starken C ++ - Typisierung und -Überprüfung während der Kompilierung durch die Struktur der Basisklassen des Codes wiederverwendet und so weiter.
Hier ist so etwas in C ++ geschrieben
Rcc.PortOn<Port::A, Port::B, Port::C>();
Und der klassische Text auf Registern:
RCC->APB2 |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN;
entfalten Sie in einem optimalen Satz von Anweisungen. Hier ist der von GCC generierte Code (Optimierung aus -Og):
ldr r2, [pc, #372] ; (0x800060c <main()+396>) [ RCC] ldr r0, [r2, #0] ; r0 = RCC->APB2 // [ APB2] orr.w r0, r0, #160 ; r0 |= 0x10100000 str r0, [r2, #0] ; RCC->APB2 = r0
Jetzt sollten Sie weiterarbeiten und die Klasse des Eingabe-Ausgabe-Ports schreiben. Das Arbeiten mit E / A-Portbits wird durch die Tatsache erschwert, dass vier Bits für die Konfiguration eines Portabschnitts zugewiesen werden und daher 64 Bit Konfiguration für einen 16-Bit-Port erforderlich sind, die in zwei 32-Bit-CRL- und CRH-Register unterteilt sind. Außerdem wird die Breite der Bitmaske größer als 1. Das Scrollen durch C ++ 17 zeigt hier jedoch seine Funktionen.

Als nächstes werden die TGPIO-Klasse sowie Klassen für die Arbeit mit anderen Peripheriegeräten, eine serielle Schnittstelle, I2C, SPI, DAP, Timer und vieles mehr geschrieben, die normalerweise in ARM Cortex-Mikrocontrollern vorhanden sind, und dann können sie mit solchen LEDs blinken.
Aber mehr dazu in der nächsten Notiz.
Quellen des Projekts auf Github .
Internetartikel zum Schreiben von Notizen
Vorlagen mit einer variablen Anzahl von Argumenten in C ++ 11 .
Innovationen in den Vorlagen .
C ++ Sprachinnovation 17. Teil 1. Faltung und Ableitung .
Liste der Links zur Dokumentation für STM-Mikrocontroller .
Makros für variable ParameterArtikel über Khabr, die mich dazu veranlassten, diese Notiz zu schreiben
Ampel auf Attiny13 .
Julian Assange von der britischen Polizei festgenommenRaum als vage ErinnerungGeschrieben am 04/12/2019 - Happy Cosmonautics Day!
PS
Bild STM32F103c8t6 von CubeMX.
Als Ausgangspunkt wird der von der Eclips-Erweiterung für die Arbeit mit den
GNU MCU-Eclipse ARM Embedded- und
STM CubeMX-Mikrocontrollern erstellte Text verwendet , d. H. Es gibt Dateien mit Standardfunktionen C ++, _start () und _init (), Definitionen von Interruptvektoren stammen aus Eclipse MCU ARM Embedded und das Cortex M3-Kernregister sowie die Arbeitsdateien stammen aus einem Projekt von CubeMX.
PPSBeim KDPV-Debugging mit dem STM32F103c8t6-Controller ist dargestellt. Nicht jeder hat ein solches Board, aber es ist nicht schwierig, es zu kaufen. Dies würde jedoch den Rahmen dieses Artikels sprengen.