Tellement inutile qu’il en est indispensable, un ordinateur de bord a l’échelle 1/16, au plus proche de l’original sans que cela coûte un bras. On en trouve sur certains modèles onéreux, pour indiquer la pression d’huile ou la tension de la batterie. Ici, pas d’huile, mais plutôt l’accès au sBus, donc toutes les voies, et peut-être la télémétrie, via le protocole Smartport. Cela se fera avec un Arduino et un petit écran.
Arduino Nano
Il existe plusieurs Arduino. Ils fonctionnent tous plus ou moins de la même façon, embarquent des capteurs plus ou moins nombreux, idem pour les ports d’entrée/sortie, … Leur format peut varier aussi, ainsi que leur puissance. Il existe un Arduino mini, qui semble adapte aux projets de modélisme quand la place est un problème, mais il est regardant à l’alimentation qu on lui fourni, max 9V, ce qui est gênant quand on travaille sur tout le modèle en 3S (11.1v). Il existe un modèle un peu plus grand, mais plus tolérant sur l’alimentation, avec tous les ports nécessaire, le Nano. Le Nano est plus grand que le mini, allez comprendre.
Il existe plusieurs modèles du Nano. l’original, de chez Arduino, des copies chinoises pas chères, et des versions plus récentes de chez Arduino. Chacun fait bien comme il veut, mais pour ma part, je bosse avec les cartes originales de chez Arduino. J’avais commencé ce projet avec un Arduino Nano v3, de chez Arduino:
Mais depuis le début du projet, la gamme Nano a été renouvelée, et étoffée, avec bien des modèles intéressants, comme la version qui supporte des capteurs de températures, une centrale inertielle, un micro, du BLE, … sur une taille identique. Mais on a pas besoin de tout cela, on a juste besoin de … la même chose. Il faut donc se porter sur le Nano every.
Même taille, même emplacements pour les connecteurs, c’est ce qu’il faut. Outre son prix inférieur, son processeur est un poil plus puissant, et il a plus de mémoire, ce qui sera déterminant pour la suite. Par contre, le processeur différent rend certaines librairies non compatibles, il faut en utiliser d’autres. On ne peut donc pas interchanger impunément ces 2 cartes. Et l’une de ces librairies ne fonctionne pas, celle qui lit la télémétrie.
Donc on ne sait pas utiliser la carte plus moderne, et on ne trouve plus que des clones chinois de la nano v3… Soit. Essayons le clone chinois. On repassera sur une originale quand on en aura l’occasion.
Lire le sBus
Le sBus est un protocole identique a ce que l’on trouve sur Futaba. Même quand le récepteur ne possède qu’un nombre limité de voies/pins pour y connecter des servos, il reçoit toutes les voies que la radio lui envoie, jusqu’à 24 en Access, et les transmet sur un port sBus. Être capable de lire ce port donne accès à toutes les voies. Pour ce projet, cela signifie être capable d’afficher la vitesse, la position des relevages, …
De plus, si on peut décoder le sBus avec un Arduino, on peut aisément transmettre la valeur a un servo. Donc en plus d’afficher des informations sur un petit écran, on peut jouer le rôle de décodeur sBus, gagnant ainsi de la place.
Et finalement, on peut aussi imaginer ajouter encore une autre fonction. J’utilisais jusqu’à présent des modules CTI pour gérer les phares. Si je peux lire les voies, je peux piloter des phares. La limite de l’Arduino est qu’il ne peut pas piloter beaucoup de puissance. Les modules CTI ont une commande par le neutre. Donc la led pilotée est connectée au module et au + de la batterie. Le module ULN2003a est justement un circuit permettant d’être piloté par un Arduino et de commander des leds par le neutre.
Le protocole sBus est un protocole série inversé. Un 1 est un 0, et vis versa. Il faut donc inverser le signal. avec un transistor et 2 résistances, ça va le faire.
On a donc le contour du projet, y’a plus qu’a…
Design du circuit
Tout faire rentrer sur un petit pcb, avec des lignes 12V pour les phares ou les moteurs annexes, des lignes 5/6v pour les servos et un neutre, le tout autour d’un Arduino Nano de l’ULN2003a et du transistor du sBus, le tour sera joué.
Ainsi schématisé, on a la possibilité de piloter 7 canaux de leds, 11 canaux de servos, lire le sBus inversé et connecter l’écran alimenté en 3.3v par l’Arduino lui-même. le 5v ne peut pas provenir de l’Arduino. Même si celui-ci sort du 5v, la puissance délivrée est bien trop faible que pour se permettre d’alimenter les servos avec. Si on souhaite lire la télémétrie, il suffit de connecter le Smartport sur la broche 12 de l’Arduino, réduisant alors à 10 le nombre de servos connectable au sBus.
On pourra souder le tout sur une carte de prototypage, ou demander l’impression d’un pcb si ce se révèle utile.
Lire le sBus depuis l’Arduino
Ce qui semblait la tâche la plus ardue du projet s’est révélée être la plus simple. Le protocole étant similaire à celui de Futaba, je ne suis pas le premier a vouloir le lire depuis un Arduino. Il existe pléthores de modules qui font cela très bien sur Github:
- https://github.com/fdivitto/sbus/tree/master
- https://github.com/mikeshub/FUTABA_SBUS/blob/master/FUTABA_SBUS/FUTABA_SBUS.cpp
- https://github.com/zendes/SBUS/tree/master
- https://github.com/george-hawkins/arduino-sbus
- …
Inutile de réinventer la roue. j’ai choisi une des possibilités et intégré à la liste de mes librairies.
Lire la télémétrie
Ça devait être la seconde tâche la plus ardue, et a été résolue de la même manière que la tâche précédente: https://www.rcgroups.com/forums/showthread.php?2245978-FrSky-S-Port-telemetry-library-easy-to-use-and-configurable
Cependant, sur un Arduino Nano Every, il aurait fallu utiliser un des pins entre D3 ou D7 pour le port série. Ports utilise par les phares dans le design du circuit. Pas bien grave, ce sera changé dans une nouvelle version du dispositif, pas de télémétrie dans cette version.
Connecter un écran
Ça devrait être relativement simple, tant les exemples sont légions. Mais le diable se cache dans les détails. Toutes ces librairies fonctionnent à merveille, tant qu’elles sont seules… Une fois à plusieurs, la mémoire disponible sur l’Arduino Nano est rapidement saturée, voire même dépassée. Il faudra donc optimiser et procéder à des choix.
Il faut une console pour placer l’écran. Étant programmeur, j’utilise un logiciel de dessin utilisant une syntaxe proche d’un programme informatique pour dessiner. Donc ici, je dessine un support écran proche d’un terminal gps du groupe Agco.
difference() {
union() {
//main frame
translate([0,0,-1])cube([18, 19, 4] ,center=true);
//handle
translate([12, 2,0.25]) cube([1.5, 12.5, 1.5] ,center=true);
translate([12, 7.5,0.25]) cube([7, 4, 1.5] ,center=true);
translate([10, -3.5,0.25]) cube([3, 1.5, 1.5] ,center=true);
//fix
translate([0, -11.8, -1.5]) rotate(20, [1,0,0]) cube([6, 6, 3] ,center=true);
};
//screen
translate([0,0.5,0.5]) cube([15, 9, 1] ,center=true);
//electronic card
translate([0,0,-1.5])cube([15, 16, 3] ,center=true);
//fix
translate([0, -11.8, -1.2]) rotate(20, [1,0,0]) cylinder(r=1.5, h=20, center=true);
translate([0, -11.8, -1.2]) rotate(20, [1,0,0]) translate([0, 0, -3.5]) cylinder(r=2.3, h=4, center=true);
};
Il reste à l’imprimer en 3D.
Câblage
Premiers câblages, et premiers soucis à corriger. Un seul connecteur par phare, ce n’est pas suffisant. Entre les câbles du modèle lui-même et le connecteur pour les feux de la remorque, il faut au minimum 2 connecteurs par feu. De plus, le connecteur pour le SmartPort requiert une résistance de 4.7k ohms, et c’est dommage de ne pas l’avoir intégrée. Et enfin, l’ alimentation de l’Arduino qui requiert un câble, c’ est pas terrible. Il y aura une version 2 du circuit.
Du code
/***********************************************
* FrSky Sbus decoder, light management *
* and screen display *
* *
* Read channels values on SBus port (rx port) *
* Manage ligths based on 1 channel *
* Patterns are 1,2 or 3 times up or down *
* Display them on a 1inch display *
***********************************************/
#include <Modelisme.h>
#include <Servo.h>
/* Display */
#include "U8glib.h"
U8GLIB_SSD1306_64X48 u8g(U8G_I2C_OPT_DEV_0|U8G_I2C_OPT_NO_ACK|U8G_I2C_OPT_FAST);
uint32_t currentTime, displayTime;
int nbrdisp;
int angle=45;
int endpointx;
int endpointy;
Receiver rec;
LightManager lm;
Servo myservo[10];
#define Logo_challenger_small_width 63
#define Logo_challenger_small_height 16
static unsigned char Logo_challenger_small_bits[] U8G_PROGMEM = {
0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x09, 0xc0, 0x06, 0x00, 0x00, 0x00,
0x00, 0xc3, 0x0c, 0x40, 0x01, 0x00, 0x00, 0x00, 0xc0, 0x61, 0x06, 0xa0,
0x02, 0x00, 0x00, 0x00, 0x60, 0x00, 0x03, 0x58, 0x01, 0x00, 0x00, 0x00,
0x18, 0x80, 0xc5, 0xed, 0x02, 0x00, 0x00, 0x00, 0x0c, 0xc0, 0xa7, 0xb6,
0xea, 0x0c, 0x3b, 0x00, 0x06, 0xc0, 0x57, 0xd2, 0xfe, 0x96, 0x3a, 0x00,
0x07, 0x68, 0xfa, 0xcf, 0x7e, 0xd9, 0x1d, 0x00, 0xff, 0x27, 0x7f, 0xfb,
0xef, 0xfb, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
void setup(void) {
//Serial.begin(115200);
displayTime = 0;
nbrdisp = 0;
rec.setup();
lm.setup(2, 3, 4, 5, 6, 7, 8);
//Sbus Decoder
myservo[0].attach(9);
myservo[1].attach(10);
myservo[2].attach(11);
myservo[3].attach(12);
myservo[4].attach(13);
myservo[5].attach(A0);
myservo[6].attach(A1);
myservo[7].attach(A2);
myservo[8].attach(A3);
//Optional sound
//pinMode(1, OUTPUT);
//myservo[9].attach(1);
}
void drawScreen1() {
//Just a logo :-)
String tmp;
u8g.drawXBMP( 2, 19, Logo_challenger_small_width, Logo_challenger_small_height, Logo_challenger_small_bits);
u8g.setFont(u8g_font_4x6);
tmp = "MT 1050";
u8g.drawStr( 18, 42, tmp.c_str());
};
void drawScreen2() {
//Servo outputs 1->8
String tmp;
int i;
for (i=0;i<8;i++) {
u8g.drawLine(2+i*8, 2, 6+i*8, 2);
u8g.drawLine(2+i*8, 38, 2+i*8, 2);
u8g.drawLine(3+i*8, 38, 3+i*8, 38-(rec.channels[i].angle/5));
u8g.drawLine(4+i*8, 38, 4+i*8, 38-(rec.channels[i].angle/5));
u8g.drawLine(5+i*8, 38, 5+i*8, 38-(rec.channels[i].angle/5));
u8g.drawLine(6+i*8, 38, 6+i*8, 2);
};
u8g.setFont(u8g_font_4x6);
tmp = " 1 2 3 4 5 6 7 8";
u8g.drawStr( 0, 47, tmp.c_str());
};
void drawScreen3() {
//Servo outputs 9->16
String tmp;
int i;
for (i=8;i<16;i++) {
u8g.drawLine(2+(i-8)*8, 2, 6+(i-8)*8, 2);
u8g.drawLine(2+(i-8)*8, 38, 2+(i-8)*8, 2);
u8g.drawLine(3+(i-8)*8, 38, 3+(i-8)*8, 38-(rec.channels[i].angle/5));
u8g.drawLine(4+(i-8)*8, 38, 4+(i-8)*8, 38-(rec.channels[i].angle/5));
u8g.drawLine(5+(i-8)*8, 38, 5+(i-8)*8, 38-(rec.channels[i].angle/5));
u8g.drawLine(6+(i-8)*8, 38, 6+(i-8)*8, 2);
};
u8g.setFont(u8g_font_4x6);
tmp = "8 910111213141516";
u8g.drawStr( 0, 47, tmp.c_str());
};
void drawScreen4() {
//Power gauge
u8g.drawCircle(32, 47, 30, U8G_DRAW_UPPER_RIGHT|U8G_DRAW_UPPER_LEFT);
u8g.drawCircle(32, 47, 29, U8G_DRAW_UPPER_RIGHT|U8G_DRAW_UPPER_LEFT);
u8g.drawCircle(32, 47, 28, U8G_DRAW_UPPER_RIGHT|U8G_DRAW_UPPER_LEFT);
angle = abs(rec.channels[1].angle-90)*2;
if (angle>175) {angle=175;};
if (angle<30) {angle=30;};
endpointx = 32 - (int) (28 * cos((angle * 71) / 4068.0));
endpointy = 47 - (int) (28 * sin((angle * 71) / 4068.0));
u8g.drawLine(31, 47, endpointx, endpointy);
u8g.drawLine(32, 47, endpointx, endpointy);
u8g.drawLine(33, 47, endpointx, endpointy);
// Lights
u8g.setFont(u8g_font_cursor);
if ((lm.lWarn || lm.warnings) && lm.blinkstate) {
u8g.drawStr( 1, 20, "\x8f");
};
if ((lm.rWarn || lm.warnings) && lm.blinkstate) {
u8g.drawStr(63, 20, "\x91");
};
if (rec.channels[1].angle>85){
u8g.drawStr(27, 23, "\x93");
};
if (rec.channels[1].angle<95){
u8g.drawStr(37, 38, "\x8b");
};
if (lm.turningWarn) {
u8g.drawStr(32, 38, "\xaf");
};
if (lm.lights || lm.lightWarn) {
u8g.drawStr(14, 40, "\x40");
};
if (lm.highlights || lm.lightWarn) {
u8g.drawStr(50, 40, "\x41");
};
};
void loop()
{
delay(50);
if (rec.read()==1){
myservo[0].write(rec.channels[6].angle);
myservo[1].write(rec.channels[7].angle);
myservo[2].write(rec.channels[8].angle);
myservo[3].write(rec.channels[9].angle);
myservo[4].write(rec.channels[10].angle);
myservo[5].write(rec.channels[11].angle);
myservo[6].write(rec.channels[13].angle);
myservo[7].write(rec.channels[14].angle);
myservo[8].write(rec.channels[15].angle);
};
//Manage ligths
lm.checkLights(rec.channels[11].angle, rec.channels[2].angle, rec.channels[4].angle, rec.channels[1].angle);
currentTime = millis();
if(currentTime > displayTime)
{
displayTime = currentTime + 5000;
nbrdisp = nbrdisp + 1;
if (nbrdisp>6) {
nbrdisp=0;
}
u8g.firstPage();
do {
if (nbrdisp==0) {
drawScreen1();
} else if (nbrdisp==1){
drawScreen2();
} else if (nbrdisp==2){
drawScreen3();
} else {
drawScreen4();
};
} while( u8g.nextPage() );
};
}