Démonstrateur du bus CAN
IMPORTANT : Ce tutoriel soit être effectué après celui-ci : Mise en place d'un bus CAN. Cet exemple est testé sur des cartes NUCLEO-L432KC
Sommaire
Présentation et objectifs
Ce tutoriel explique pas à pas la mise en œuvre d'un démonstrateur CAN simple à utiliser. Le démonstrateur a pour but de communiquer avec d'autres démonstrateurs identiques via une liaison CAN.
L'utilisateur va pouvoir, pour chaque démonstrateur déterminer à l'aide du commutateur :
- L'ID du démonstrateur
- L'ID du démonstrateur destinataire
- La donnée à envoyer
Il faut appuyer sur le bouton de validation à chaque étape d'insertion des informations. Pendant cette période, le démonstrateur ne lit pas les informations sur le bus CAN et ne reçois donc pas les données qu'une autre pourrais lui envoyer à ce moment là.
Une fois les information entrées, il se met en mode écoute et affiche les éventuelles données qu'il reçois. Pour sortir de ce mode et renvoyer une donnée, il suffit de ré-appuyer sur le bouton de validation.
Pré-requis
Voter environnement de travail doit être configuré comme dans le tutoriel précédent.
Matériel
En plus du matériel déjà présent, il faut :
- un bouton poussoir
- un commutateur BCD à 5 broches (Sur AliExpress chercher "digital decimal BCD code")
- une LED RGB
- 3 résistances entre 100 et 200 Ohms
Logiciels
Toujours les mêmes logiciels, c'est à dire :
- STM32CubeMX
- AtollicTRUESTUDIO
Démonstrateur CAN
Pour mettre en place ce démonstrateur, nous allons reprendre le projet STM32CubeMX du tutoriel précédent et ajouter les nouvelles liaisons nécessaires à l'ajout des composants du démonstrateur. Il faudra ensuite effectuer le branchement pour finir sur le code.
Configurer l'environnement de travail :
Cette étape sera rapide : il suffit d'ajouter 4 entrées pour le commutateur, 1 entrée pour le bouton poussoir, et 3 sorties pour la LED RGB.
Pour ceci, cliquez directement sur des broches libres ( grises ) autour de la puce au centre de l'écran. Définissez en 5 en tant que GPIO_Input (entrée) et 3 en tant que GPIO_Output (sortie).
Ensuite, allez dans l'onglet Configuration, puis au milieu de l'écran dans la partie System, cliquez sur GPIO. Dans cette fenêtre vous pouvez voir toutes les broches que vous venez d'activer. Cliquez sur chacune des broche en "Input mode", et passez les en "Pull-down".
Pour chacune des entrées ou sorties, je vous conseille fortement de les renommez dans la partie "User Label" avec un nom qui est significatif de sa fonction. De cette manière, le schéma dans l'onglet Pinout deviendra plus parlant, mais cela aura surtout un impact sur la lisibilité du code, où ces noms pourront être utilisés. Pour la partie code du tutoriel, j'utiliserai les nom suivants :
- Pour le commutateur : COMM1, COMM2, COMM4 et COMM8
- Pour le bouton : VALIDATION
- Pour la LED RGB : RED, GREEN, BLUE
Si vous ne choisissez pas les même labels, faites attention à prendre en compte cette différence sur les parties de code plus bas.
Attention toutefois, certaines broches sont reliées à d'autres fonctions sur la carte, ce qui peux fausser les données reçues. Vérifiez que les broches que vous choisissez n'aient pas de pont vers d'autres broches de la carte. Par exemple, la PA5 et PA6 sur la L432KC sont reliées aux broches PB6 et PB7 qui gèrent l'I2C, les informations reçues sur les broches PA5 et PA6 ne sont donc pas fiables.
Une fois ceci fait, vous pouvez générer le code dans Project -> Generate Code. Attention, cette manipulation effacera tout le code que vous n'avez pas placé entre les commentaires "USER CODE BEGIN" et "USER CODE END". Faites attention à bien replacer votre code si il ne se trouve pas au bon endroit.
Effectuer le branchement :
Voici une image d'un exemple de câblage, attention le modèle de la carte et l'emplacement des pins sera sûrement différent selon votre configuration CubeMX :
Nous allons prendre chaque nouveau composant un à un ( hormis le CAN et l'écran décris dans le tutoriel précédent ) :
- Le bouton poussoir
- Ce branchement est le plus simple, il faut relier le 3,3V d'un coté, et la broche qui correspond à l'entrée sur la carte de l'autre coté. Il n'y a pas besoin de résistance supplémentaire grâce à l'utilisation du Pull-down.
- Le commutateur
- Ce composant possède 5 broches. La broche "Commun" ( qui devrait être notifiée d'un C ) doit être relié au 3,3V. Les autres broches doivent chacune être reliée à une entrée différente de la carte.
- La LED RGB
- La LED RGB possède 4 broches, la plus longue doit être reliée au GND, puis, par ordre de longueur décroissant, c'est la couleur verte, rouge, puis bleue. L'ordre devrait être le suivant : ROUGE, GND, VERT, BLEU ( la broche GND est toujours la plus longue ). Chaque couleur doit être reliée à une sortie différente de la carte, en passant par une résistance entre 100 et 200 Ohms pour ne pas griller la LED.
Écrire le programme :
Assurez vous d'avoir encore toutes les parties de code du tutoriel précédent, au cas où la génération du code aurait effacé quelques parties.
Pour commencer, nous allons légèrement modifier le code précédent. A la suite des variables déclarées au début de programme, déclarez les variables suivantes :
uint8_t Id = 0; uint8_t Dest = 0; uint8_t comVal = 0; uint8_t lastDataReceived = 0; uint8_t lastIdReceived = 0;
Id représente l'identifiant du démonstrateur, Dest l'identifiant du démonstrateur destinataire, comVal la valeur du commutateur, lastDataReceived la dernière donnée reçue sur le bus CAN, et lastIdReceived l'identifiant de la source qui a envoyé la dernière donnée.
Le démonstrateur utilisera le mode Interruption, nous devons donc modifier la fonction de réception que vous devez déjà avoir : tout d'abord affecter lastDataReceived et lastIdReceived avec les données reçues, puis les afficher sur la 2ème et 3ème ligne de l'écran.
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) { HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData; if ((RxHeader.IDE == CAN_ID_STD) && (RxHeader.DLC == 2)) { lastIdReceived = RxData[0]; lastDataReceived = RxData[1]; ssd1306_SetCursor(0, 20); sprintf(buffer, "Source : %d ", lastIdReceived); ssd1306_WriteString(buffer, Font_11x18, White); ssd1306_SetCursor(0, 40); sprintf(buffer, "Donnee : %d ", lastDataReceived); ssd1306_WriteString(buffer, Font_11x18, White); ssd1306_UpdateScreen(); } }
Voici à quoi la fonction pourrait ressembler après modification.
Dans la fonction CAN_Config, il faut modifier une des valeur du filtre qui ne prendra plus un identifiant fixe, mais le contenu de la variable Id. Il faut également changer la structure de la transmission à envoyer, qui enverra deux données (L'ID de la carte et le chiffre sélectionné). Cherchez donc les lignes en question et modifiez les :
... sFilterConfig.FilterIdHigh = Id << 5; ... TxHeader.StdId = Dest; TxHeader.ExtId = 0x01; TxHeader.RTR = CAN_RTR_DATA; TxHeader.IDE = CAN_ID_STD; TxHeader.DLC = 2; TxHeader.TransmitGlobalTime = ENABLE;
Il faut également enlever l'appel à la fonction CAN_Config() au début de la fonction main, car à cet endroit, la variable Id ne sera pas encore défini. Supprimez donc la ligne qui correspond à l'appel de cette fonction, nous verrons plus loin à quelle moment la remettre.
Une dernière petite modification est la fonction CAN_Transmit, qui doit, en plus de la data en paramètre, également envoyer son identifiant. Je l'ai légèrement modifiée pour la rendre plus simples d'utilisation que le tutoriel précédent, mais aussi moins flexibles, étant donné que le contexte du démonstrateur est clairement défini et que le nombre de données à envoyer ne change pas.
void CAN_Transmit(uint8_t data) { TxData[0] = Id; TxData[1] = data; HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox); }
Les modifications du code précédent est faite, passons aux nouveautés.
Pour plus de lisibilité du code, nous allons créer deux fonctions qui nous serviront tout au long du programme, setRGBLed et waitforValidation. La première décide de la couleur de la LED RGB, la seconde est une boucle d'attente de validation, durant la quelle l'utilisateur va manipuler le commutateur pour choisir un chiffre.
Pour setRGBLed, la fonction a besoin de trois paramètres : les valeur de ROUGE, VERT, et BLEU. Nous ne sommes pas en analogique ou nous pourrions choisir l'intensité de chaque couleur et donc toute une gamme de couleur, ici chaque couleur sera soit complètement éteinte, soit allumée au maximum, ce qui laisse une combinaison de 7 couleurs différentes qui seront largement suffisantes pour l'utilisation du démonstrateur.
void setRGBLed(int RED, int GREEN, int BLUE){ if(RED != 0 && RED != 1 || GREEN != 0 && GREEN != 1 || BLUE != 0 && BLUE != 1 ) return; HAL_GPIO_WritePin(RED_GPIO_Port, RED_Pin, RED); HAL_GPIO_WritePin(BLUE_GPIO_Port, BLUE_Pin, BLUE); HAL_GPIO_WritePin(GREEN_GPIO_Port, GREEN_Pin, GREEN); }
Dans cette fonction, on s'assure en premier lieu que les valeurs en paramètre sont correct, c'est à dire 0 ou 1. Ensuite on envois sur la sortie correspondante les valeurs soumises. Nous utiliserons cette LED pour notifier que nous changeons d'étape lors de l'utilisation du démonstrateur.
Pour waitforValidation, la fonction n'a pas besoin de paramètre car elle sera simplement une représentation d'une boucle while qui serait redondante dans le code principal.
void waitforValidation(){ while (HAL_GPIO_ReadPin(VALIDATION_GPIO_Port, VALIDATION_Pin) == 0) { comVal = HAL_GPIO_ReadPin(COMM1_GPIO_Port, COMM1_Pin) + HAL_GPIO_ReadPin(COMM2_GPIO_Port, COMM2_Pin) * 2 + HAL_GPIO_ReadPin(COMM4_GPIO_Port, COMM4_Pin) * 4 + HAL_GPIO_ReadPin(COMM8_GPIO_Port, COMM8_Pin) * 8; ssd1306_SetCursor(0, 40); sprintf(buffer, " %d ", comVal); ssd1306_WriteString(buffer, Font_11x18, White); // White est la couleur d'écriture, interchangeable avec Black ssd1306_UpdateScreen(); } HAL_Delay(500); ssd1306_Fill(Black); // Black est la couleur de fond, interchangeable avec White }
Cette fonction a le comportement suivant : elle entre directement dans une boucle et n'en sortira que si le bouton VALIDATION est pressé. Dans cette boucle, une variable est affectée à la valeur renvoyée par le commutateur.
Pour la petite explication, le commutateur envoie simplement la valeur indiqué sur son cadran sous forme de code binaire. Il suffit d'ajouter chaque sortie en les multipliant par leur code binaire, et le chiffre est décodé. Par exemple, pour le chiffre 5, le commutateur renverra 0101. Le calcul sera donc : 0*8 + 1*4 + 0*2 + 1*1 = 5.
Après avoir décodé le chiffre envoyé par le commutateur et l'avoir placé dans une variable, on l'écris sur l'écran OLED pour montrer que la manipulation du commutateur est bien prise en compte. J'ai choisi de l'écrire sur la 3ème ligne car les 2 premières serviront à indiquer à l'utilisateur quelle est la signification de la valeur qu'il doit entrer. Pour rappel, en Font_11x18, la première ligne est accessible via ssd1306_SetCursor(0, 0), la deuxième via ssd1306_SetCursor(0, 20) et la troisième via ssd1306_SetCursor(0, 40).
Une fois que l'utilisateur a choisi sa valeur et a appuyé sur la bouton VALIDATION, un délai est nécessaire pour laisser le temps au bouton de revenir à l'état neutre, sinon la pression du bouton pourrait valider accidentellement une future boucle de ce type. Il est également important d'effacer entièrement l'écran avant la prochaine écriture pour éviter de voir apparaitre des caractères résiduels des précédents affichages.
Nous pouvons maintenant passer à l'écriture du programme principal. Après les initialisations et avant la boucle principale, dans le bloc de code "USER CODE 2", placez vous juste après l'initialisation de l'écran OLED, qui devrait ressembler à ça
HAL_Delay(2000); ssd1306_Init(); ssd1306_Fill(Black); // White pour mettre un fond blanc ssd1306_UpdateScreen();
La première chose à faire, au démarrage de la carte, est de demander à l'utilisateur de définir l'identifiant CAN du démonstrateur actuel. Pour cela on émet une couleur sur la LED RGB ( ici rouge ), on inscris sur les deux premières lignes de l'écran OLED que l'on attend l'Id de la carte, puis on entre dans la boucle d'attente de validation. Ensuite, on retiens l'identifiant, puis on appelle la fonction CAN_Config() que l'on avait préalablement enlevé du début du programme. Maintenant que la variable Id est correctement affectée, le filtre peut être configuré correctement.
setRGBLed(1,0,0); ssd1306_SetCursor(0, 0); ssd1306_WriteString("Id de la", Font_11x18, White); ssd1306_SetCursor(0, 20); ssd1306_WriteString("carte : ", Font_11x18, White); ssd1306_UpdateScreen(); waitforValidation(); Id = comVal; CAN_Config();
Nous entrons ensuite dans la boucle principale du programme while(1). Dès l'entrée dans cette boucle, nous allons désactiver les interruptions pour empêcher l'affichage d'une donnée reçue sur l'écran lorsque l'utilisateur est en train de manipuler le démonstrateur.
HAL_CAN_DeactivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
Nous allons ensuite demander à l'utilisateur de rentrer l'identifiant du destinataire. Pour cela, il suffit de reprendre le même code que la demande de la carte, à quelques détails près. On peux déjà changer la couleur de la LED pour notifier un changement d'étape (ici Bleu), écrire ce qu'il faut sur l'écran pour que l'utilisateur comprenne qu'il doit entrer le numéro de destinataire, puis attendre la validation. Ensuite, on retiens le numéro de destinataire et on le place dans la configuration de la prochaine trame CAN à envoyer.
setRGBLed(0,0,1); ssd1306_SetCursor(0, 0); ssd1306_WriteString("Id de la", Font_11x18, White); ssd1306_SetCursor(0, 20); ssd1306_WriteString("destination : ", Font_11x18, White); ssd1306_UpdateScreen(); waitforValidation(); Dest = comVal; TxHeader.StdId = Dest;
La prochaine étape est l'invitation à entrer le chiffre à envoyer à un autre démonstrateur. Encore une fois le même code, en changeant la couleur de la LED (ici Jaune), le message affiché, et à la fin envoyer la donnée en CAN.
setRGBLed(1,1,0); ssd1306_SetCursor(0, 0); ssd1306_WriteString("Chiffre a", Font_11x18, White); ssd1306_SetCursor(0, 20); ssd1306_WriteString("envoyer : ", Font_11x18, White); ssd1306_UpdateScreen(); waitforValidation(); CAN_Transmit(comVal);
Pour la dernière partie du code, il faut se mettre en mode standby et attendre les données des autres cartes. Il faut donc commencer par réactiver les interruptions. Ensuite on peux encore changer la couleur de la LED (ici Vert), puis afficher dans l'ordre : l'identifiant de la carte, l'identifiant de la source du dernier message reçu, et enfin le chiffre contenu dans le dernier message reçu.
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING); setRGBLed(0,1,0); ssd1306_SetCursor(0, 0); sprintf(buffer, "Id CAN : %d", Id); ssd1306_WriteString(buffer, Font_11x18, White); ssd1306_SetCursor(0, 20); sprintf(buffer, "Source : %d ", lastIdReceived); ssd1306_WriteString(buffer, Font_11x18, White); ssd1306_SetCursor(0, 40); sprintf(buffer, "Donnee : %d ", lastDataReceived); ssd1306_WriteString(buffer, Font_11x18, White); ssd1306_UpdateScreen(); while(HAL_GPIO_ReadPin(VALIDATION_GPIO_Port, VALIDATION_Pin) == 0); HAL_Delay(500);
Pour rester en standby entre deux moments où l'utilisateur veux envoyer un nouveau message, un crée une boucle vide et on en sort que si le bouton validation est appuyé. Vous pouvez mettre du contenu dans cette boucle si vous le souhaitez, mais attention aux HAL_Delay() trop longs, qui empêchent de sortir de la boucle si le bouton a été appuyé durant le temps du délai. On termine d'ailleurs par un délai pour éviter que la pression du bouton ne perturbe le début de la prochaine itération de la boucle.