Il jeta un coup d'œil vers mbed. À première vue, cela semblait très intéressant - un cadre indépendant de fer, en C ++, avec un support pour un tas de microcontrôleurs et de cartes de démonstration, un compilateur en ligne avec intégration dans le système de contrôle de version. Un tas d'exemples qui convainquent davantage l'élégance du cadre. Presque toutes les interfaces du microcontrôleur sont accessibles directement hors de la boîte en utilisant les classes correspondantes déjà implémentées. Prenez-le directement de la boîte et programmez en C ++ sans regarder la fiche technique du microcontrôleur - n'est-ce pas un rêve?La plate-forme de test est le STM Nucleo F030 de longue date, pris en charge par cette plate-forme. Il y a beaucoup de bons tutoriels sur la façon de s'inscrire et de démarrer le premier projet, nous n'en parlerons pas. Allons directement à l'intéressant.Cette carte ne contient pas trop de périphériques "embarqués". LED et bouton - c'est toute la richesse. Eh bien, le premier projet - le "Hello world" classique du monde des microcontrôleurs - clignote avec une LED. Voici le code:#include "mbed.h"
DigitalOut myled(LED1);
int main() {
while(1)
{
wait_ms(500);
myled = myled ^ 1;
}
}
Sympa après tout, non? Vous n'avez vraiment même pas besoin de consulter la fiche technique du contrôleur!Soit dit en passant, le classique «Hello world» est également disponible dès la sortie de la boîte:#include "mbed.h"
Serial pc(USBTX, USBRX);
int main() {
pc.printf("Hello world\n\r");
while(1)
{
}
}
N'est-ce pas vraiment génial? «Out of the box» avons-nous une console dans laquelle une impression système standard fonctionne? Soit dit en passant, le port apparaît également immédiatement lorsque la carte est connectée à l'ordinateur, avec un disque virtuel, sur lequel il vous suffit de copier le binaire assemblé - et la carte se redémarrera.Mais revenons à la LED clignotante. Compilation, téléchargement sur la carte - clignotant! Mais en quelque sorte trop vite, évidemment plus d'une fois par seconde, mais au moins 5 fois ... Oups ...Je suis un peu distrait par les "abstractions trouées" mentionnées dans le titre. J'ai lu ce terme avec Joel Spolsky. Voici sa citation: "Si j'enseigne aux programmeurs C ++, ce serait bien si je n'avais pas besoin de leur parler de l'arithmétique char * et pointeur, mais je pourrais aller directement aux lignes de la bibliothèque de modèles standard. Mais un jour ils écriront "foo" + "bar" et d'étranges problèmes surgiront, mais je dois encore leur expliquer ce qu'est char *. Ou ils essaieront d'appeler une fonction Windows avec un paramètre de type LPTSTR et échoueront jusqu'à ce qu'ils apprennent char * et les pointeurs et les fichiers d'en-tête Unicode et wchar_t et TCHAR - tout ce qui brille à travers les trous dans les abstractions. "Aussi paradoxal que cela puisse être, la loi des abstractions trouées ne permet pas de prendre et de commencer à programmer un microcontrôleur comme celui-ci (même s'il s'agit du «bonjour» le plus simple de trois lignes), sans regarder sa fiche technique et sans avoir une idée que le microcontrôleur a le même intérieur, ainsi que ce qui se cache derrière toutes ces classes en abstraction. Contrairement aux promesses des marketeurs ...Alors, après avoir fortement soupiré, nous commençons à chercher. Si sous mes yeux ce n'était pas une abstraction, mais un environnement de développement complet pour le contrôleur, il serait clair où creuser. Mais où creuser en cas de code ci-dessus? Si même le contenu du fichier mbed.h ne permet pas de voir l'abstraction?Heureusement, la bibliothèque mbed elle-même est open source, et vous pouvez la télécharger et voir ce qu'elle contient. Il s'est avéré, encore une fois, heureusement, que grâce à l'abstraction pour le programmeur, tous les registres du microcontrôleur sont assez accessibles, bien qu'ils ne soient pas explicitement déclarés. Déjà mieux. Par exemple, vous pouvez vérifier que nous avons une vitesse d'horloge en exécutant ceci (pour lequel j'ai dû regarder les fiches techniques et creuser assez bien mbed): RCC_OscInitTypeDef str;
RCC_ClkInitTypeDef clk;
pc.printf("SystemCoreClock = %d Hz\n\r", HAL_RCC_GetSysClockFreq());
HAL_RCC_GetOscConfig(&str);
pc.printf("->HSIState %d\n\r", str.HSIState);
pc.printf("->PLL.PLLState %d\n\r", str.PLL.PLLState);
pc.printf("->PLL.PLLSource %d\n\r", str.PLL.PLLSource);
pc.printf("->PLL.PLLMUL %d\n\r", str.PLL.PLLMUL);
pc.printf("->PLL.PREDIV %d\n\r", str.PLL.PREDIV);
pc.printf("\n\r");
HAL_RCC_GetClockConfig(&clk, &flat);
pc.printf("ClockType %d\n\r", clk.ClockType);
pc.printf("SYSCLKSource %d\n\r", clk.SYSCLKSource );
pc.printf("AHBCLKDivider %d\n\r", clk.AHBCLKDivider );
pc.printf("APB1CLKDivider %d\n\r", clk.APB1CLKDivider );
Tout s'est bien passé avec la fréquence, elle était de 48 MHz, comme prévu.Eh bien, creusez plus loin. Nous essayons de connecter un timer au lieu de wait_ms (): Timer timer;
timer.start();
while(1) {
myled ^= 1;
t1 = timer.read_ms();
t2 = timer.read_ms();
while (t2 - t1 < 500)
{
t2 = timer.read_ms();
}
}
C'est beau, non? Mais la LED clignote toujours 5 fois plus vite que souhaité ...D'accord, nous creusons encore plus profondément et voyons ce que nous avons derrière le minuteur magique, qui, comme la documentation le promet, peut être créé en toute quantité, quel que soit le nombre de minuteurs matériels. microcontrôleur. Cool, ouais ...Et dans le cas du STM F030, la minuterie matérielle du contrôleur TIM1 est cachée derrière, programmée pour compter les ticks en microsecondes. Et sur cette base, tout le reste a déjà été construit.Donc, il fait déjà chaud. Rappelez-vous, j'ai dit que tous les registres sont disponibles? Nous examinons les éléments suivants:pc.printf("PSC: %d\n\r", TIM1->PSC);
Voila! Cela met fin à l'enquête sur qui est à blâmer: le numéro 7 est envoyé à la console. Dans le registre PSC (scribe? Est-ce vraiment juste une coïncidence?) Il y a une valeur, lorsque le minuteur va générer une interruption et recommencer à compter. Et sur cette interruption et le compteur de microsecondes est raccroché. Et pour qu’à une fréquence de 48 MHz, l’interruption se produise une fois par microseconde, il ne devrait pas y en avoir du tout 7, mais 47. Et 7 sont arrivés, très probablement, au tout premier stade du chargement du microcontrôleur, car il commence à 8 MHz, puis réaccorde la PLL pour que la fréquence soit multipliée par 6, donnant les 48 MHz souhaités. Et il semble que la minuterie s'initialise trop tôt ...Qui est à blâmer, bien sûr. Mais que faire? Les fouilles du cadre ont conduit aux chaînes d'appels suivantes:Premièrement: SetSysClock_PLL_HSI () -> HAL_RCC_OscConfig (), HAL_RCC_ClockConfig () -> HAL_InitTick () - lors du changement de fréquence, appelez la fonction qui définit le tick de microseconde.Deuxièmement: HAL_Init () -> HAL_InitTick () -> HAL_TIM_Base_Init () -> TIM_Base_SetConfig () -> TIMx-> PSC - appelé depuis l'une des fonctions les plus globales, HAL_InitTick écrit la valeur requise dans le registre PSC, en fonction de l'horloge actuelle fréquences ...Ce qui reste un mystère pour moi, c'est que c'est pour la famille STM F0 que la deuxième chaîne n'est pas appelée. Je n'ai trouvé aucun appel à la fonction HAL_Init ()!De plus, si vous regardez l'implémentation de HAL_InitTick (), alors au tout début il y aura les lignes suivantes: static uint32_t ticker_inited=0;
if(ticker_inited)return HAL_OK;
ticker_inited=1;
Appelez cette fonction une seule fois. Autrement dit, vous pouvez l'appeler autant de fois que vous le souhaitez, mais elle ne fera rien pour tous les appels ultérieurs. Et le fait que le framework l'appelle au cas où les changements de fréquence d'horloge sont inutiles ...J'étais trop paresseux pour reconstruire la bibliothèque, donc la correction a pris cette forme: la première ligne de la fonction principale était la suivante:TIM1->PSC = (SystemCoreClock / 1000000) - 1;
Après cela, la LED a finalement commencé à clignoter avec la fréquence correcte de 1 Hz ...Je me demande à quelle étape une personne hypothétique arrêterait que les spécialistes du marketing mbed ont convaincu d'essayer de rendre la programmation des microcontrôleurs si facile à partir de zéro? S'il n'a jamais rencontré de microcontrôleurs auparavant?D'accord, si je ne suis pas très fatigué, nous allons de l'avant. Faire clignoter une LED est bon, mais pas intéressant. Je veux quelque chose de plus. Le capteur de température DS1820 a été trouvé à partir d'un plus grand, et la bibliothèque terminée pour lui a été googlé. Facile à utiliser, cachant la complexité de travailler avec ce capteur à l'intérieur. Il suffit d'écrire quelque chose comme#include "DS1820.h"
DS1820 probe[1] = {D4};
probe[0].convert_temperature(DS1820::all_devices);
float temperature = probe[0].temperature('c');
Que pensez-vous qu'il s'est passé après la compilation et le lancement? Correctement. Ça n'a pas marché :) Desabstractions qui fuient, oui. Eh bien, une autre étude passionnante des internes mbed nous attend, semble-t-il. Et les détails du capteur lui-même.Le capteur est très intéressant. Avec son interface numérique. En une ligne, c'est-à-dire fonctionne en mode semi-duplex. Le contrôleur initie l'échange de données, le capteur envoie une séquence de bits en réponse. Surtout pour de tels cas, le framework mbed a la classe DigitalInOut, avec laquelle vous pouvez changer la direction de son travail sur la broche GPIO à la volée. Quelque chose comme ça:bool DS1820::onewire_bit_in(DigitalInOut *pin) {
bool answer;
pin->output();
pin->write(0);
wait_us(3);
pin->input();
wait_us(10);
answer = pin->read();
wait_us(45);
return answer;
}
Le contrôleur envoie une impulsion «1 -> 0 -> 1» au capteur, qui est un signal pour qu'il envoie un bit en réponse. Qu'est-ce qui pourrait ne pas fonctionner ici? Voici une partie de la fiche technique du capteur:
Comme vous pouvez le voir, après avoir envoyé une impulsion au capteur, afin de lire le bit, nous avons jusqu'à 15 microsecondes.Nous connectons l'oscilloscope et voyons que le capteur s'envoie des bits, comme indiqué dans la fiche technique. Mais ici, le contrôleur lit les ordures. Quelle pourrait être la raison?Je n’écrirai pas grand-chose sur la façon dont j’en suis venu à exécuter ce code: pin->output();
t1 = timer.read_us();
pin->input();
t4 = timer.read_us();
pc.printf("Time: %d us\n\r", t4-t1);
Eh bien, qui devine, sans autre lecture, combien de temps sur un microcontrôleur avec une fréquence d'horloge de 48 MHz il faut pour changer la direction d'une broche GPIO (en fait, c'est UNE écriture dans le registre, si cela), si cela se fait en utilisant le cadre mbed écrit en C ++ en utilisant une couche indépendante du fer?13 microsecondes.Et pour lire la réponse du capteur, nous en avons jusqu'à 15. Et même si nous supprimons complètement wait_us (10) du code ci-dessus, la commande answer = pin-> read (); prend également un certain temps plus de 2 microsecondes. De quoi ne pas avoir le temps de lire la réponse.Heureusement, il a été possible d'envoyer une impulsion à la STM sans changer la direction du GPIO. En mode d'entrée, lorsque vous connectez la résistance PullDown intégrée, l'effet est le même. Et encore une fois, heureusement, appeler pin-> mode (PullUp) au lieu de pin-> input () ne nécessite que 6 microsecondes.De quoi avoir le temps de lire la réponse.Et enfin, un autre trou dans l'abstraction. Après que le DS1820 a fonctionné, le capteur suivant qui est venu à la main était le DHT21, un capteur qui mesure à la fois la température et l'humidité. Également avec une interface à 1 fil, mais cette fois une réponse de codage avec une durée d'impulsion. Il semblerait que la solution évidente serait de suspendre cette ligne à l'interruption d'entrée GPIO, ce qui facilite la mesure de la durée d'impulsion. Et il y a même une classe en mbed pour ça. Et cela fonctionne même comme documenté.Mais le problème est que le capteur fonctionne également en semi-duplex. Et pour qu'il commence à transmettre des données, il doit envoyer une impulsion "1 -> 0 -> 1" comme dans l'exemple ci-dessus.Et ici, mbed ne le permet pas. Ou vous déclarez le port comme DigitalInOut, mais vous ne pouvez alors pas utiliser d'interruptions. Ou vous déclarez le port comme InterruptIn, mais vous ne pouvez rien lui envoyer. Malheureusement, l'astuce PullDown n'a pas fonctionné ici. le capteur a un PullUp intégré, et le microcontrôleur PullDown intégré n'est pas suffisant pour tirer une broche à 0.J'ai donc dû faire un cycle d'interrogation de ligne beaucoup moins beau. Bien sûr, tout a fonctionné comme ça, mais cela n'a pas fonctionné à merveille. Ce qui ne serait pas un problème d'accès direct aux registres ...C'est ici. Une tentative de faire une abstraction pour que n'importe qui puisse immédiatement commencer à programmer des microcontrôleurs dignes. Beaucoup de choses sont vraiment très simplifiées et fonctionnent même. Mais, comme toute abstraction de haut niveau, cette abstraction est également pleine de trous. Et en cas de collision avec l'un des nombreux trous, il faut descendre du ciel à la terre.