Manchmal hilft der Code eines anderen wirklich beim Anschließen des peripheren Eisens an den Mikrocontroller. Leider kann es schwieriger sein, den Code eines anderen an Ihr Projekt anzupassen, als ihn selbst neu zu schreiben, insbesondere wenn es um Mega-Frameworks wie Arduino oder Mbed geht. Um ein chinesisches LCD auf ILI9341-Basis mit der STM32L476G DISCOVERY-Karte zu verbinden, wollte der Autor den für mbed im Demo-Projekt von ST geschriebenen Treiber verwenden, ohne eine einzelne Zeile in seinem Code zu ändern. Infolgedessen war es gleichzeitig möglich, den Bildschirm auf beispiellose Aktualisierungsgeschwindigkeiten von 27 fps zu übertakten.
Einführung in das Problem
ST Microelectronics produziert Mikrocontroller, die sowohl hinsichtlich der Funktionen als auch des Preises sehr interessant sind, sowie Release-Boards für eine schnelle Entwicklung. Eine davon wird besprochen - STM32L476G DISCOVERY . Die Rechenfähigkeiten dieser Karte sind sehr ermutigend - ein 32-Bit-ARM mit einer maximalen Taktfrequenz von 80 MHz kann Gleitkommaoperationen ausführen. Gleichzeitig ist es möglich, den Stromverbrauch auf ein Minimum zu reduzieren und an der Batterie zu arbeiten, wobei auf die Gelegenheit gewartet wird, etwas Nützliches zu tun. Ich entschied mich, ein billiges chinesisches Farb-LCD mit einer Auflösung von 320 x 240 an der SPI-Schnittstelle an dieses Gerät anzuschließen. Wie man es mit mbed benutzt, wird hier ausführlich beschrieben . Mbed- Dies ist eine Online-Programmierumgebung, in der Sie Firmware für sich selbst kompilieren können, ohne einen Compiler auf Ihrem Computer zu haben. Laden Sie sie dann herunter und flashen Sie sie, indem Sie sie einfach auf Ihre mbed-kompatible Karte kopieren, die bei Anschluss an USB wie eine Wechseldiskette aussieht. Das alles ist großartig, aber es gibt ein paar Probleme. Erstens sind nicht alle Boards mbed-kompatibel. Zweitens gibt es viele bestehende Projekte, die in keiner Weise mit mbed kompatibel sind, einschließlich der von ST bereitgestellten Software. Und schließlich sind nicht alle Entwickler mit mbed kompatibel. Einige (zum Beispiel der Autor dieser Zeilen) finden in diesem wunderbaren Tool mehr Nachteile als Vorteile. Was sind diese Nachteile, die wir weiter unten diskutieren werden. Im Moment reicht es aus, dies nach dem Anschließen des Bildschirmtreibers zu erwähnen Nach dem Demo-Projekt von ST und einigen einfachen Optimierungen begann es etwa zehnmal schneller zu arbeiten.Den Treibercode lernen
Es ist Zeit, den Quellcode des Bildschirmtreibers herunterzuladen und zu studieren. Die Arbeit mit Ports in mbed wird durch Aufrufe von Klassenmethoden organisiert, die E / A-Ports darstellen. Beispielsweise implementiert die DigitalOut-Klasse den Zugriff auf den Ausgabeport. Das Zuweisen einer Instanz dieses Objekts Null oder Eins initiiert das Schreiben des entsprechenden Bits in den Ausgangsport. Die DigitalOut-Klasse wird mit dem aufgezählten PinName-Typ initialisiert, dessen einziger Zweck darin besteht, den Prozessorabschnitt zu identifizieren. Einer der Hauptnachteile bei der Implementierung von DigitalOut und anderen Klassen, die E / A implementieren, besteht darin, dass der Port im Konstruktor der Klasseninstanz initialisiert wird. Dies ist ideal zum Blinken einer LED, wenn eine Instanz der DigitalOut-Klasse auf dem Stapel in der Hauptfunktion erstellt wird. Stellen Sie sich jedoch vor, wir haben viele verschiedene Arten von Eisen, deren Initialisierung auf mehrere Module verteilt ist.Wenn wir Instanzen unserer E / A-Klassen statisch machen, verlieren wir die Kontrolle über die Initialisierung, da diese vor der Hauptfunktion und in beliebiger Reihenfolge erfolgt. ST-Bibliotheken (HAL - Hardware Abstraction Level) verwenden ein anderes, flexibleres Paradigma. Jeder Eingabe- / Ausgabeport hat seinen eigenen Kontext und eine Reihe von Funktionen, die damit arbeiten. Sie können jedoch bei Bedarf genau aufgerufen werden. Portkontexte werden normalerweise als statische Variablen erstellt, es findet jedoch keine automatische unkontrollierte Initialisierung statt (ST-Bibliotheken werden in C geschrieben). Erwähnenswert ist auch ein äußerst praktisches Dienstprogramm.ST-Bibliotheken (HAL - Hardware Abstraction Level) verwenden ein anderes, flexibleres Paradigma. Jeder Eingabe- / Ausgabeport hat seinen eigenen Kontext und eine Reihe von Funktionen, die damit arbeiten. Sie können jedoch bei Bedarf genau aufgerufen werden. Portkontexte werden normalerweise als statische Variablen erstellt, es findet jedoch keine automatische unkontrollierte Initialisierung statt (die ST-Bibliotheken sind in C geschrieben). Erwähnenswert ist auch ein äußerst praktisches Dienstprogramm.ST-Bibliotheken (HAL - Hardware Abstraction Level) verwenden ein anderes, flexibleres Paradigma. Jeder Eingabe- / Ausgabeport hat seinen eigenen Kontext und eine Reihe von Funktionen, die damit arbeiten. Sie können jedoch bei Bedarf genau aufgerufen werden. Portkontexte werden normalerweise als statische Variablen erstellt, es findet jedoch keine automatische unkontrollierte Initialisierung statt (die ST-Bibliotheken sind in C geschrieben). Erwähnenswert ist auch ein äußerst praktisches Dienstprogramm.In diesem Fall findet jedoch keine automatische unkontrollierte Initialisierung statt (ST-Bibliotheken sind in C geschrieben). Erwähnenswert ist auch ein äußerst praktisches Dienstprogramm.In diesem Fall findet jedoch keine automatische unkontrollierte Initialisierung statt (ST-Bibliotheken sind in C geschrieben). Erwähnenswert ist auch ein äußerst praktisches Dienstprogramm.CubeMX , das den gesamten erforderlichen Initialisierungscode für die von Ihnen benötigten Ports generieren kann und es Ihnen sogar ermöglicht, anschließend Änderungen an diesen Ports vorzunehmen, ohne Ihren eigenen Code zu beeinflussen. Der einzige Nachteil ist die Unfähigkeit, mit vorhandenen Projekten zu verwenden. Sie müssen das Projekt mit diesem Dienstprogramm starten.Die mbed-Bibliothek verwendet dieselben HAL-Funktionen wie die ST-Bibliothek, um die Ressourcen des Mikrocontrollers zu initialisieren. Dies macht sie jedoch stellenweise auffallend gedankenlos. Um dies sicherzustellen, sehen Sie sich einfach den Initialisierungscode des SPI-Ports (den wir für die Arbeit mit der Anzeige benötigen) in der Datei spi_api.c an. Die Funktion spi_init sucht zunächst an den Beinen, die sie verwenden wird, nach einem geeigneten SPI-Port und ruft dann die Funktion init_spi auf, die den Port tatsächlich initialisiert. Gleichzeitig verwenden alle drei möglichen SPI-Ports eine statische Kontextstrukturstatic SPI_HandleTypeDef SpiHandle;
Dies ist im Wesentlichen ein klassisches Beispiel für die Verwendung globaler Variablen anstelle lokaler Variablen. Selbst unter Berücksichtigung der Tatsache, dass wir einen Rechenkern haben, ist der globale Kontext nicht vor der gleichzeitigen Verwendung an verschiedenen Stellen des Codes geschützt, es gibt immer noch Unterbrechungen sowie das Verdrängen von Multitasking.Verbinden Sie die Bibliothek mit Ihrem Projekt
Ich möchte also nicht den gesamten Code auf mbed schreiben. Ich mag die ST-Beispiele, die mit CubeMX geliefert werden, viel mehr . Ich habe den fertigen Treiber für mein LCD für ST-Bibliotheken nicht gefunden, ich wollte ihn nicht selbst schreiben. Es blieb eine alternative Möglichkeit, irgendwie Spaß zu haben - den für mbed geschriebenen Treiber anzuschließen, damit nichts geändert werden muss. Sie müssen lediglich die mbed-Bibliotheken auf alternative Weise implementieren. Tatsächlich ist die Aufgabe einfacher als es scheint, da der LCD-Treiber aufgrund aller mbed-Bibliotheken nur den Ausgangsport und SPI verwendet. Darüber hinaus benötigt er Verzögerungsgenerierungsfunktionen sowie Datei- und Stream-Klassen. Bei letzteren ist alles einfach - wir brauchen sie nicht und werden durch Stecker ersetzt, die nichts bewirken. Verzögerungsgenerierungsfunktionen sind einfach zu schreiben - sie befinden sich in der Dateiwait_api.h . Das Implementieren von E / A-Klassen erfordert einen etwas kreativeren Ansatz. Wir werden das Fehlen von mbed-Bibliotheken beheben und die Hardware-Initialisierung nicht im Konstruktor durchführen. Der Konstruktor erhält einen Link zum Portkontext an einer anderen Stelle. Der Initialisierungscode ist völlig unabhängig von unseren Schnittstellenklassen. Es gibt die einzige Möglichkeit, diese Informationen an den Konstruktor zu übergeben, ohne den Treibercode zu ändern - über PinName, der anstelle der einfachen Auflistung der Legs jetzt den Zeiger auf den Port, die Nummer des Legs und optional den Zeiger auf die Ressource (wie SPI) speichert, mit der dieser Leg verbunden ist.class PinName {
public:
PinName() : m_port(0), m_pin(0), m_obj(0) {}
PinName(GPIO_TypeDef* port, unsigned pin, void* obj = 0)
: m_port(port), m_pin(pin), m_obj(obj)
{
assert_param(m_port != 0);
}
GPIO_TypeDef* m_port;
unsigned m_pin;
void* m_obj;
static PinName not_connected;
};
Die Implementierung des Ausgabeports ist recht trivial. Um die Leistung zu verbessern, werden wir versuchen, weniger HAL-Funktionen zu verwenden und wenn möglich direkt mit Portregistern zu arbeiten. Außerdem werden wir Inline-Code schreiben, damit der Compiler Funktionsaufrufe vermeiden kann.class DigitalOut {
public:
DigitalOut(GPIO_TypeDef* port, unsigned pin)
: m_port(port), m_pin(pin)
{
assert_param(m_port != 0);
}
DigitalOut(PinName const& N)
: m_port(N.m_port), m_pin(N.m_pin)
{
assert_param(m_port != 0);
}
void operator =(int bit) {
if (bit) m_port->BSRR = m_pin;
else m_port->BRR = m_pin;
}
private:
GPIO_TypeDef* m_port;
unsigned m_pin;
};
Der SPI-Port-Implementierungscode ist nicht viel komplizierter. Sie können ihn hier sehen . Da wir die Initialisierung des Ports vom Schnittstellencode getrennt haben, ignorieren wir Anforderungen für Konfigurationsänderungen. Die Bittiefe des Wortes wird einfach gespeichert. Wenn der Benutzer ein 16-Bit-Wort übertragen möchte und der Port als 8-Bit konfiguriert ist, müssen wir nur die Bytes neu anordnen und einzeln übertragen - bis zu 4 Bytes werden noch im Portpuffer abgelegt. Alle zum Kompilieren des Treibers erforderlichen Dateien werden im kompatiblen Verzeichnis gesammelt . Jetzt können Sie die ursprünglichen Treiberdateien mit dem Projekt verbinden und kompilieren. Wir benötigen außerdem einen Code , der die Ports initialisiert, eine Instanz des Treibers erstellt und etwas Sinnvolles auf den Bildschirm zeichnet.Übertakten
Wenn das LCD zur Ausgabe von etwas Dynamischem verwendet wird, besteht ein natürlicher Wunsch, die Kommunikation mit ihm zu beschleunigen. Das erste, was mir in den Sinn kommt, ist die Erhöhung der SPI-Taktfrequenz, die der Treiber auf 10 MHz einstellt. Wir ignorieren jedoch seine Wünsche und können alles einstellen. Es stellte sich heraus, dass der Bildschirm einwandfrei funktioniert und eine Frequenz von 40 MHz aufweist - dies ist die maximale Frequenz, die unser Prozessor mit einer Taktfrequenz von 80 MHz erreichen kann. Um die Leistung zu bewerten, wurde ein einfacher Code geschrieben, der eine Bitmap von 100 x 100 Pixel in einer Schleife anzeigt. Das Ergebnis wurde dann auf den gesamten Bildschirm extrapoliert (eine Bitmap, die den gesamten Bildschirm belegt, passt einfach nicht in den Speicher). Das Ergebnis - 11 fps ist sehr weit von der theoretischen Grenze von 32 fps entfernt, die erhalten wird, wenn Sie 16 Bit auf jedes Pixel übertragen, ohne anzuhalten. Der Grund wird klar, wenn Sie sich ansehenTreiber-Quellcode . Wenn er eine Folge von Bytes übertragen muss, überträgt er sie einfach einzeln, bestenfalls in 16-Bit-Wörtern. Der Grund für dieses ineffiziente Design liegt in der mbed-API. Die SPI-Klasse verfügt über eine Methode zum Übertragen eines Datenarrays. Sie kann jedoch nur asynchron verwendet werden, indem die Benachrichtigungsfunktion nach Abschluss und im Kontext eines Interrupt-Handlers aufgerufen wird. Es ist nicht überraschend, dass nur wenige Menschen diese Methode anwenden. Ich habe meine Implementierung der SPI-Klasse durch eine Funktion ergänzt, die einen Puffer sendet und auf das Ende der Übertragung wartet. Nachdem ich dem Bitmap-Übertragungscode einen Aufruf dieser Funktion hinzugefügt hatte, stieg die Leistung auf 27 fps, was bereits sehr nahe an der theoretischen Grenze liegt.Quellcode
Liegt hier . Für die Kompilierung wurde IAR Embedded Workbench für ARM 7.50.2 verwendet. Basierend auf der Code-Demo-Firmware von ST. Beschreibung des Stiftes, der mit dem LCD verbunden ist , kann in der Datei gefunden werden lcd.h .