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
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;
}
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".
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;
}
}
}
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);
}
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;
}
}
}
-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;
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à, 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).