Utilisation du code mbed dans votre propre projet STM32 - expérience de l'overclocking de l'écran LCD chinois

Parfois, le code de quelqu'un d'autre aide vraiment à connecter le fer périphérique au microcontrôleur. Malheureusement, l'adaptation du code de quelqu'un d'autre à votre projet peut être plus difficile que de le réécrire vous-même, surtout quand il s'agit de méga frameworks comme Arduino ou mbed. Voulant connecter un écran LCD chinois basé sur ILI9341 à la carte STM32L476G DISCOVERY, l'auteur a décidé d'utiliser le pilote écrit pour mbed dans le projet de démonstration de ST sans changer une seule ligne dans son code. En conséquence, il était possible en même temps d'overclocker l'écran à des vitesses de mise à jour sans précédent de 27 ips.





Introduction au problème


ST Microelectronics produit des microcontrôleurs très intéressants, à la fois en termes de capacités et de prix, ainsi que des cartes de sortie pour un développement rapide. L'un d'eux sera discuté - STM32L476G DISCOVERY . Les capacités de calcul de cette carte sont très encourageantes - un ARM 32 bits avec une fréquence d'horloge maximale de 80 MHz peut effectuer des opérations en virgule flottante. Dans le même temps, il est capable de réduire la consommation d'énergie au minimum et de travailler sur batterie, en attendant la possibilité de faire quelque chose d'utile. J'ai décidé de connecter un écran LCD couleur chinois bon marché avec une résolution de 320 par 240 en travaillant sur l'interface SPI à cet appareil. Comment l'utiliser avec mbed est décrit en détail ici . Mbed- Il s'agit d'un environnement de programmation en ligne où vous pouvez compiler votre firmware sans avoir de compilateur sur votre ordinateur, après quoi vous pouvez le télécharger et le flasher en le copiant simplement sur votre carte compatible mbed, qui ressemble à un disque amovible lorsqu'il est connecté à USB. Tout cela est super, mais il y a quelques problèmes. Premièrement, toutes les cartes ne sont pas compatibles avec mbed. Deuxièmement, il existe de nombreux projets existants qui ne sont en aucun cas compatibles avec mbed, y compris les logiciels fournis par ST. Et enfin, tous les développeurs ne sont pas compatibles avec mbed, certains (par exemple, l'auteur de ces lignes) trouvent plus d'inconvénients que d'avantages dans ce merveilleux outil. Quels sont ces inconvénients, nous en discuterons ci-dessous, pour l'instant il suffit de mentionner qu'après avoir connecté le pilote d'affichage au projet de démonstration de ST et à quelques optimisations simples, il a commencé à fonctionner environ 10 fois plus rapidement.

Apprendre le code du conducteur


Il est temps de télécharger et d'étudier le code source du pilote d'affichage. L'utilisation des ports dans mbed est organisée via des appels aux méthodes de classe qui représentent les ports d'E / S. Par exemple, la classe DigitalOut implémente l'accès au port de sortie. L'affectation d'une instance de cet objet à zéro ou à un lance l'initialisation du bit correspondant sur le port de sortie. La classe DigitalOut est initialisée par le type énuméré PinName, dont le seul but est d'identifier la jambe du processeur. L'un des principaux inconvénients de l'implémentation de DigitalOut et d'autres classes qui implémentent les E / S est que le port est initialisé dans le constructeur de l'instance de classe. C'est idéal pour faire clignoter une LED si une instance de la classe DigitalOut est créée sur la pile dans la fonction principale. Mais imaginez que nous avons beaucoup de fer différent, dont l'initialisation est dispersée sur plusieurs modules.Si nous rendons les instances de nos classes d'E / S statiques, nous perdons tout contrôle sur l'initialisation, car elle se produira avant la fonction principale et dans un ordre arbitraire. Les bibliothèques ST (appelées HAL - niveau d'abstraction matérielle) utilisent un paradigme différent et plus flexible. Chaque port d'entrée / sortie a son propre contexte et un ensemble de fonctions qui fonctionnent avec lui, mais ils peuvent être appelés exactement lorsque cela est nécessaire. Les contextes de port sont généralement créés sous forme de variables statiques, mais aucune initialisation automatique non contrôlée ne se produit (les bibliothèques ST sont écrites en C). Un utilitaire extrêmement pratique mérite également d'être mentionné.Les bibliothèques ST (appelées HAL - niveau d'abstraction matérielle) utilisent un paradigme différent et plus flexible. Chaque port d'entrée / sortie a son propre contexte et un ensemble de fonctions qui fonctionnent avec lui, mais ils peuvent être appelés exactement lorsque cela est nécessaire. Les contextes de port sont généralement créés sous forme de variables statiques, mais aucune initialisation automatique non contrôlée ne se produit (les bibliothèques ST sont écrites en C). Un utilitaire extrêmement pratique mérite également d'être mentionné.Les bibliothèques ST (appelées HAL - niveau d'abstraction matérielle) utilisent un paradigme différent et plus flexible. Chaque port d'entrée / sortie a son propre contexte et un ensemble de fonctions qui fonctionnent avec lui, mais ils peuvent être appelés exactement lorsque cela est nécessaire. Les contextes de port sont généralement créés sous forme de variables statiques, mais aucune initialisation automatique non contrôlée ne se produit (les bibliothèques ST sont écrites en C). Un utilitaire extrêmement pratique mérite également d'être mentionné.mais aucune initialisation automatique non contrôlée ne se produit dans ce cas (les bibliothèques ST sont écrites en C). Un utilitaire extrêmement pratique mérite également d'être mentionné.mais aucune initialisation automatique non contrôlée ne se produit dans ce cas (les bibliothèques ST sont écrites en C). Un utilitaire extrêmement pratique mérite également d'être mentionné.CubeMX , qui peut générer tout le code d'initialisation nécessaire pour l'ensemble de ports dont vous avez besoin et vous permet même de modifier ultérieurement cet ensemble de ports sans affecter votre propre code. Son seul inconvénient est l'impossibilité de l'utiliser avec des projets existants, vous devez démarrer le projet avec l'utilisation de cet utilitaire.

La bibliothèque mbed utilise les mêmes fonctions HAL de la bibliothèque ST pour initialiser les ressources du microcontrôleur, mais cela le rend étonnamment irréfléchi par endroits. Pour s'en assurer, il suffit de regarder le code d'initialisation du port SPI (dont nous avons besoin pour travailler avec l'affichage) dans le fichier spi_api.c. La fonction spi_init recherche d'abord un port SPI approprié par les segments qu'elle utilisera, puis appelle la fonction init_spi, qui initialise en fait le port. Dans ce cas, les 3 ports SPI possibles utilisent une seule structure de contexte statique
static SPI_HandleTypeDef SpiHandle;

Il s'agit essentiellement d'un exemple classique d'utilisation de variables globales au lieu de variables locales. Même en tenant compte du fait que nous avons un cœur de calcul, le contexte global n'est pas protégé de l'utilisation simultanée à différents endroits du code, il y a encore des interruptions, ainsi que l'éviction du multitâche.

Connectez la bibliothèque à votre projet


Je ne veux donc pas écrire tout le code sur mbed. J'aime beaucoup plus les exemples ST fournis avec CubeMX . Je n'ai pas trouvé le pilote fini pour mon LCD pour les bibliothèques ST, je ne voulais pas l'écrire moi-même. Il restait un moyen alternatif de s'amuser en quelque sorte: connecter le pilote écrit pour mbed, et pour qu'il ne nécessite rien de changer. Pour ce faire, il vous suffit d'implémenter les bibliothèques mbed d'une manière alternative. En fait, la tâche est plus simple qu'il n'y paraît, en raison de toutes les bibliothèques mbed, le pilote LCD utilise uniquement le port de sortie et SPI. De plus, il a besoin de fonctions de génération de retard et de classes de fichiers et de flux. Avec ce dernier, tout est simple - nous n'en avons pas besoin et sommes remplacés par des prises qui ne font rien. Les fonctions de génération de retard sont faciles à écrire - elles sont dans le fichierwait_api.h . L'implémentation de classes d'E / S nécessite une approche légèrement plus créative. Nous allons corriger le manque de bibliothèques mbed et ne pas faire l'initialisation matérielle dans le constructeur. Le constructeur recevra un lien vers le contexte du port situé ailleurs, son code d'initialisation sera complètement indépendant de nos classes d'interface. Il existe le seul moyen de transmettre ces informations au constructeur sans modifier le code du pilote - via PinName, qui au lieu de simplement répertorier les jambes stockera désormais le pointeur sur le port, le numéro de la jambe et éventuellement le pointeur sur la ressource (comme SPI) à laquelle cette jambe est connectée.
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;
};


L'implémentation du port de sortie est assez banale. Pour améliorer les performances, nous essaierons d'utiliser moins les fonctions HAL, et travaillerons directement avec les registres de ports, si possible, ainsi que d'écrire du code en ligne, ce qui permettra au compilateur d'éviter les appels de fonction.
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;
};


Le code d'implémentation du port SPI n'est pas beaucoup plus compliqué, vous pouvez le voir ici . Puisque nous avons séparé l'initialisation du port du code d'interface, nous ignorons les demandes de modifications de configuration. La profondeur de bits du mot est simplement mémorisée. Si l'utilisateur souhaite transmettre un mot de 16 bits et que le port est configuré sur 8 bits, il nous suffit de réorganiser les octets et de les transférer un par un - jusqu'à 4 octets sont toujours placés dans la mémoire tampon du port. Tous les fichiers nécessaires à la compilation du pilote sont collectés dans le répertoire compat . Vous pouvez maintenant connecter les fichiers du pilote d' origine au projet et les compiler. Nous aurons également besoin d'un code qui initialise les ports, crée une instance du pilote et dessine quelque chose de significatif à l'écran.

Overclocking


Si l'écran LCD est utilisé pour produire quelque chose de dynamique, il y a un désir naturel de rendre la communication avec lui plus rapide. La première chose qui me vient à l'esprit est d'augmenter la fréquence d'horloge SPI, que le pilote définit à 10 MHz, mais nous ignorons ses souhaits et pouvons tout régler. Il s'est avéré que l'écran fonctionne correctement et à une fréquence de 40 MHz - c'est la fréquence maximale dont notre processeur avec une fréquence d'horloge de 80 MHz est capable. Pour évaluer les performances, un code simple a été écrit qui affiche une image bitmap de 100x100 pixels dans une boucle. Le résultat a ensuite été extrapolé à l'ensemble de l'écran (un bitmap occupant tout l'écran ne tient tout simplement pas en mémoire). Le résultat - 11fps est très loin de la limite théorique de 32fps, qui est obtenue si vous transférez 16 bits à chaque pixel sans s'arrêter. La raison devient claire si vous regardezcode source du pilote . S'il a besoin de transmettre une séquence d'octets, il les transfère simplement un par un, dans le meilleur des cas, en mots de 16 bits. La raison de cette conception inefficace réside dans l'API mbed. La classe SPI a une méthode pour transmettre un tableau de données, mais elle ne peut être utilisée que de manière asynchrone, en appelant la fonction de notification à la fin et dans le contexte d'un gestionnaire d'interruption. Il n'est pas surprenant que peu de gens utilisent cette méthode. J'ai complété mon implémentation de la classe SPI avec une fonction qui envoie un tampon et attend la fin du transfert. Après avoir ajouté un appel à cette fonction au code de transfert bitmap, les performances ont augmenté à 27 ips, ce qui est déjà très proche de la limite théorique.

Code source


Se trouve ici . Pour la compilation, IAR Embedded Workbench pour ARM 7.50.2 a été utilisé. Basé sur le firmware de démonstration de code de ST. Description de la broche, qui est connecté à l'écran se trouve dans le fichier lcd.h .

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


All Articles