Page 1 sur 1

[Arduino] 4 La rencontre Arduino - memoryshared

Publié : dim. août 09, 2015 9:51 am
par Topper Harley.
Bonjour.

Précédemment, d'un côté, nous avons appris à extraire des données de BMS pendant un vol, et d'un autre côté, nous avons découvert Arduino qui va faire interface entre le PC et des commandes électroniques ou électriques.
A propos d'arduino, dans notre exemple précédent nous avons exécuté un programme qui était trans-versé dans son microcontrôleur, puis qui s'est exécuté de manière autonome.

Pour ce que nous voulons faire aujourd'hui, les données doivent être traitées en temps réel par arduino : au moment où le voyant s'allume ou s'éteint dans le cockpit, il faut qu'au même moment arduino reçoive et traite cette information.

Pour cela, nous pouvons utiliser le port série qu'utilise la carte arduino uno.

Il est donc nécessaire d'une part d'avoir un programme qui, du PC va envoyer des informations sur le port série. Et d'autre part, écrire pour l'arduino un code qui va guetter si des données sont accessible via le port série, et dans l'affirmative va les récupérer et les traiter.

1.Pour le code côté PC, on va réutiliser le programme que nous avions fait précédemment : Win32_con_BMSsharedmemory.cpp. Dans l'article où j'en parlais, nous l'avions modifié pour qu'à la fin il affiche l'état du voyant CONFIG:
1 lorsque le voyant est allumé.
0 lorsque le voyant est éteint.

La première étape consiste à ajouter la classe que va permettre d'envoyer des données sur le port série.
Là, je n'ai pas grand chose à expliquer, il va falloir singer "bêtement" ce que je détaille ci après, quitte à approfondir plus tard si vous y tenez.

Par l'explorateur de solution (à gauche), créez dans les fichiers d’entête un nouvel élément que vous nommez SerialClass.h et copiez y le code suivant :

Code : Tout sélectionner

#ifndef SERIALCLASS_H_INCLUDED #define SERIALCLASS_H_INCLUDED #define ARDUINO_WAIT_TIME 50 #include <windows.h> #include <stdio.h> #include <stdlib.h> class Serial { private: //Serial comm handler HANDLE hSerial; //Connection status bool connected; //Get various information about the connection COMSTAT status; //Keep track of last error DWORD errors; public: //Initialize Serial communication with the given COM port Serial(char *portName); //Close the connection //NOTA: for some reason you can't connect again before exiting //the program and running it again ~Serial(); //Writes data from a buffer through the Serial connection //return true on success. bool WriteData(char *buffer, unsigned int nbChar); //Check if we are actually connected bool IsConnected(); }; #endif // SERIALCLASS_H_INCLUDED
A présent, dans les fichiers source, créez un fichier nommé serial.cpp et copiez-y le code suivant :


Code : Tout sélectionner

#include "stdafx.h" #include "SerialClass.h" Serial::Serial(char *portName) { //We're not yet connected this->connected = false; //Try to connect to the given port throuh CreateFile this->hSerial = CreateFileA(portName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); //Check if the connection was successfull if(this->hSerial==INVALID_HANDLE_VALUE) { //If not success full display an Error if(GetLastError()==ERROR_FILE_NOT_FOUND){ //Print Error if neccessary printf("ERROR: Handle was not attached. Reason: %s not available.\n", portName); } else { printf("ERROR!!!"); } } else { //If connected we try to set the comm parameters DCB dcbSerialParams = {0}; //Try to get the current if (!GetCommState(this->hSerial, &dcbSerialParams)) { //If impossible, show an error printf("failed to get current serial parameters!"); } else { //Define serial connection parameters for the arduino board dcbSerialParams.BaudRate=CBR_9600; dcbSerialParams.ByteSize=8; dcbSerialParams.StopBits=ONESTOPBIT; dcbSerialParams.Parity=NOPARITY; //Set the parameters and check for their proper application if(!SetCommState(hSerial, &dcbSerialParams)) { printf("ALERT: Could not set Serial Port parameters"); } else { //If everything went fine we're connected this->connected = true; //We wait 2s as the arduino board will be reseting Sleep(ARDUINO_WAIT_TIME); } } } } Serial::~Serial() { //Check if we are connected before trying to disconnect if(this->connected) { //We're no longer connected this->connected = false; //Close the serial handler CloseHandle(this->hSerial); } } bool Serial::WriteData(char *buffer, unsigned int nbChar) { DWORD bytesSend; //Try to write the buffer on the Serial port if(!WriteFile(this->hSerial, (void *)buffer, nbChar, &bytesSend, 0)) { //In case it don't work get comm error and return false ClearCommError(this->hSerial, &this->errors, &this->status); return false; } else return true; } bool Serial::IsConnected() { //Simply return the connection status return this->connected; }

3.Ouvrez le fichier stdafx.h et ajoutez en fin de fichier la ligne :
#include "SerialClass.h"


4.Enfin, ouvrez votre fichier Win32_con_BMSsharedmemory.cpp et modifiez le de la façon suivante :

Code : Tout sélectionner

#include "stdafx.h" #include <windows.h> #include "FlightData.h" #include <iostream> using namespace std; HANDLE gSharedMemHandle = NULL; void* gSharedMemPtr = NULL; char *a; int main (void){ FlightData* flightData; do{ gSharedMemHandle = OpenFileMapping(FILE_MAP_READ, TRUE, TEXT("FalconSharedMemoryArea")); system("CLS"); printf ("Unable to open file. Is Falcon Running?\n"); }while (!gSharedMemHandle); gSharedMemPtr = MapViewOfFile(gSharedMemHandle, FILE_MAP_READ, 0, 0, 0); flightData = (FlightData*)gSharedMemPtr; while (TRUE){ system("CLS"); a = (flightData->lightBits & flightData->CONFIG)?"1":"0"; cout << "Config : " << a << " "; Serial Serial("\\\\.\\COM14"); // Serial Serial("COM2"); Serial.WriteData(a,1); } // Close shared memory area if (gSharedMemPtr){ UnmapViewOfFile(gSharedMemPtr); gSharedMemPtr = NULL; } CloseHandle (gSharedMemHandle); return EXIT_SUCCESS; }
Voilà, le programme C++ est prêt.
Comme lors du précédent article, il va ouvrir la mémoire partagée, récupérer l'info voulue (ici, la valeur binaire flightData->CONFIG), (puis, petit changement, il va mettre cette valeur dans une variable de type pointeur char*), et ensuite, il va envoyer cette valeur via le port série qu'il vous faudra désigner. Notez que jusqu'à 9, vous écrirez le port sous la forme "COMx", ensuite, il faut écrire sous la forme "\\\\.\\COMx".

Image
Ci-dessus : découverte du port utilisé par arduino.

Reste ensuite le programme à envoyer sur l'arduino. Là ce sera beaucoup plus simple :

Code : Tout sélectionner

const int ledPin = 13; // pin the LED is connected to void setup(){ Serial.begin(9600); // Initialize le port série while (!Serial) ; //leonardo pinMode(ledPin, OUTPUT); // règle la broche 13 en sortie } void loop(){ if (Serial.available() > 0) { char ch = Serial.read(); switch(ch){ case '0': digitalWrite(ledPin,LOW); //Si cette valeur lue =0, éteint la broche 13 break; case '1': digitalWrite(ledPin,HIGH);//Si cette valeur lue =1, alimente la broche 13 break; } } }
Le résultat :
La micro diode rouge de la broche 13 s'allume en fonction de l'état du voyant config.

Bon, jusque là ça va, mais c'est pas mal de trucs pour un maigre résultat. Comment faire pour gérer plusieurs voyants ?

Une solution serait d'envoyer des données différentes selon l'état de chaque voyant désiré. On pourrait tout envoyer à arduino qui traiterait lui même les infos, mais je préfère effectuer un maximum de calcul sur le PC et ensuite l'envoyer à arduino car su ce dernier, la mémoire est bien plus limitée.

Donc, pour gérer plusieurs voyants, modifions légèrement le programme C++, dans la boucle while(TRUE) :

Code : Tout sélectionner

while (TRUE){ Serial Serial("\\\\.\\COM14"); // Serial Serial("COM2"); a = (flightData->lightBits & flightData->CONFIG)?"1":"0"; Serial.WriteData(a,1); a = (flightData->lightBits & flightData->MasterCaution)?"3":"2"; Serial.WriteData(a,1); a = (flightData->speedBrake)?"5":"4"; Serial.WriteData(a,1); }
Dans arduino :

Code : Tout sélectionner

const int led_Config = 12; // const int led_MasterCaution = 13; // const int led_SpeedBrake = 11; // void setup(){ Serial.begin(9600); // Initialize le port série while (!Serial) ; //leonardo pinMode(led_Config, OUTPUT); // règle la broche 13 en sortie pinMode(led_MasterCaution, OUTPUT); // règle la broche 13 en sortie pinMode(led_SpeedBrake, OUTPUT); // règle la broche 13 en sortie } void loop(){ if (Serial.available() > 0) { char ch = Serial.read(); switch(ch){ case '0': digitalWrite(led_Config,LOW); // break; case '1': digitalWrite(led_Config,HIGH);// break; case '2': digitalWrite(led_MasterCaution,LOW);// break; case '3': digitalWrite(led_MasterCaution,HIGH);// break; case '4': digitalWrite(led_SpeedBrake,LOW);// break; case '5': digitalWrite(led_SpeedBrake,HIGH);// break; } } }
Voilà le résultat :

-Le voyant de droite est le voyant Config
-Le voyant du milieu est le voyant Master Caution
-Le voyant de gauche est particulier car il n'existe pas dans BMS : il s'allume lorsque la valeur du speedbrake n'est pas égale à 0 (donc lorsque les aérofreins sont déployés, quelle que soit leur position).

Pour "découvrir" arduino, j'avais acheté aussi un petit module d'affichage LCD. La gestion de son affichage, (si toutefois il existe une librairie correspondante) est aussi extrèmement simple, et en quelques modifs on peut afficher des valeurs variables sur son écran.
P.exemple, pour afficher le fuel flow en direct sur l'écran LCD, modifiez le prog c++ de cette façon :
remplacez

Code : Tout sélectionner

char *a;
par

Code : Tout sélectionner

char str[10];

Copiez dans la boucle While (true){

Code : Tout sélectionner

while (TRUE){ int a = flightData->fuelFlow; sprintf (str, "%u", a); Serial Serial("\\\\.\\COM14"); //Serial Serial("COM2"); Serial.WriteData(str,sizeof(&str)+1); system("cls"); cout << "Fuel Flow : " << a << "\n"; }

A présent, dans arduino :

Code : Tout sélectionner

#include <LiquidCrystal.h> LiquidCrystal lcd(8, 9, 4, 5, 6, 7); void setup() { Serial.begin(9600); while (!Serial) ; //leonardo lcd.begin(16,2); lcd.print("Fuel FLow:"); lcd.setCursor(10, 0); } void loop(){ if (Serial.available()>0) { char recieved = Serial.read(); if (recieved==0){ lcd.print(" "); lcd.setCursor(10, 0); }else{ lcd.print(recieved); } }else{ lcd.setCursor(10, 0); lcd.print(" "); } }
Voilà le résultat (désolé pour la qualité) :


Voilà, nous sommes arrivés à notre but : afficher en temps réel des infos tirées du simulateur. Cela a peut être été parfois un peu difficile, mais dites vous que le plus dur est fait : accéder et extraire les données désirées de Falcon BMS, envoyer des données via le port série et les utiliser avec arduino.

Pour autant, d'autres questions restent en suspens :

-Une carte arduino peut elle piloter tous les boutons et affichages du cockpit ?
Peut être... (je ne sais pas trop) mais sûrement pas en utilsant uniquement les broches comme nous venons de le faire. Je pense que la solution est d'utiliser un bus série présent sur arduino (je pense au bus I2C qui est sur les broches analogiques A4 et A5). Le bus série permet en effet de relier plusieurs "périphériques" ensemble. Il sera donc possible d'ajouter des CI spécifiques pour afficher des infos sur des écrans LCD, connaitre l'état d'interrupteurs, afficher plusieurs voyants, envoyer des données analogiques pour commander des cadrans, commander des relais, etc... (et connecter plusieurs cartes ensembles).

EDIT : Oui et non : Avec une seule carte on pourrait gérer tous les boutons, afficher tous les voyants et cadrans du cockpit, mais au niveau des axes, chaque port USB n'en reconnait que 8 au maximum. Il faudra donc deux cartes si vous voulez gérer tous les potentiomètres du cockpit (volume, brillance HUD, etc..).

-Comment envoyer des infos de l'arduino vers BMS ?
Je pense qu'il y a plusieurs solutions : Peut etre écrire un pilote virtuel dans le PC par lequel on va exploiter les données venant d'arduino via le port série. Ou bien en C++ simuler seulement des appuis clavier. Encore une autre piste serait d'utiliser les cartes qui gèrent nativement le port USB, ou, flasher sa carte arduino afin qu'elle puisse le faire. Perso, je vais explorer la piste de la carte Arduino qui gère nativement l'USB. Il y a aussi d'autres cartes, pas seulement arduino. TigerMan m'a parlé de Pockey's, carte apparemment utilisée par plusieurs pit builders et qui a un soft bien abouti pour relier le cockpit virtuel au réel.

EDIT : Effectivement plusieurs solutions, la plus facile est sans conteste d'utiliser la carte Arduino Leonardo qui nativement va envoyer des séquences de touches à BMS. Ensuite, une petite manip (décrite dans un autre article) fait reconnaitre la carte Uno ou Leonardo comme joystick, et permet alors d'utiliser des potentiomètres qui seront reconnus dans BMS.

Je ferai probablement d'autres articles, au grès de ce que je jugerai utile pour la communauté. Si vous avez des questions, n'hésitez pas (mais pas sûr que je puisse répondre à tout).

Re: [Arduino] 4 La rencontre Arduino - memoryshared

Publié : mer. déc. 21, 2016 8:54 am
par lgv_simeur
Je ne te remercirai jamais assez.
Rémy.

Re: [Arduino] 4 La rencontre Arduino - memoryshared

Publié : jeu. déc. 22, 2016 4:52 pm
par Paul-Henri
Hello,

Merci pour les informations :) A noter qu'une librairie existe pour faire reconnaître l'Arduino comme une manette de jeu et bien plus.
Un exemple ici :

http://www.instructables.com/id/Arduino ... rJoystick/

La librairie est intéressante également pour se confectionner des panneaux personnalisés, pour ceux qui voudraient plus de boutons hors clavier et qui seraient bricoleurs. Avec un simple arduino on peut gérer déjà pas mal de boutons.

Paul-H