See site in english Voir le site en francais
Website skin:
home  download  forum  link  contact

Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

Author Topic: Le tableau de commande de Mars Bleu  (Read 41734 times)

0 Members and 1 Guest are viewing this topic.

Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
08 November 2016, 08:44:24

L’idée de construire un simucockpit pour mieux profiter d’Orbiter est présente dans ma tête depuis déjà un bon moment. J’avais les connaissances en électricité, en électronique, en dépannage, et en programmation pour commencer à m’aventurer dans ces terrains là. Il fallait seulement se lancer, ce que j’ai fini par faire depuis la fin de 2013.

LA GENÈSE:

J’ai commencé par rechercher plein de doc sur le Net, tant sur les simpits FSX que sur ceux dédiés à Orbiter. Sans idée préconçue, j’ai même envisagé l’utilisation de cartes SIOC dédiées à Flight Simulator.

J’ai suivi les travaux d’Antoo et de son frère; ils m’ont fait part de leur expérience qui a alimenté ma réflexion. Mais je voulais aller plus loin que les raccourcis clavier, et utiliser d’autres fonctions permettant d’afficher des valeurs telles que le cap, l’altitude, les fréquences radio, les jauges de carburant, etc… Le problème de la synchronisation des commutateurs du cockpit avec le scénario chargé au démarrage d’Orbiter était aussi à résoudre.

            La lecture des blogs consacrés au sujet des simpits montre que la concrétisation d’un tel projet est rare.

PRINCIPES GÉNÉRAUX:

Je pense que pour réussir, il faut poser des jalons :
                                                    -étudier la faisabilité
                                                    -faire quelques essais sur des fonctions principales
                                                    -passer à la construction proprement dite
                                                    -rester modulaire

Pour garder le fil de mes idées, j'utilise deux cahiers; l'un pour la partie programmation, et l'autre pour la construction des cartes électroniques associées à la carte Arduino.


Avant d’entamer un petit historique de mes travaux, je tiens à dire que les solutions que j'ai choisies sont possibles parmi d’autres. Ce ne sont que des indications qui pourront peut-être donner des idées à d’autres orbinautes. Aux yeux de codeurs avertis, j'aurai peut-être produit du vilain code (mais qui en principe doit fonctionner). C'est parfois intentionnel, car ça a été la seule façon que j'ai trouvée pour pouvoir m'en sortir.
La maître mot est compacité du code: il y peu de place dans nos microcontrôleurs préférés et les fréquences d'horloge ne sont pas très rapides.

 Mes premières lignes de code remontent il y a déjà bien longtemps, au temps du BASIC spaghetti, avec des numéros de ligne et des GOTO partout.

J'aurais bien aimé construire un vrai poste de pilotage déménageable, mais faute de place disponible, je pense m'orienter comme Hysot vers la réalisation d'un tableau de commande plus compact et plus léger.

LES PRINCIPAUX CHALLENGES:

Afin de parvenir à mes fins, plusieurs challenges se présentaient(liste non exhaustive):
                                 * Lire de manière automatique un fichier scn
                                 * Envoyer à Orbiter une commande simple avec la UNO
                                 * Multiplier les entrées/sorties sur la UNO
                                 * Commander des leds branchées sur la UNO au moyen d'un script Lua
                                 * Construction des modules Parallel Input Serial Output (PISO)
                                 * Construction des modules Serial Input Parallel Output (SIPO)
                                 * Construction des cartes de servitudes (Alims électriques, support de la UNO)
                                 * Gestion des leds associées aux auxiliaires APU
                                 * Gestion des signalisations liées à l'équilibre de l'engin (gimbal, gravityshift)

                  Il y aura d'autres challenges, mais ceux cités ci-dessus ont été déjà réussis.

                                                 A bientôt pour le premier challenge.



« Last Edit: 20 November 2016, 23:13:11 by Mars Bleu »

Offline nerofox

  • Sr. Member
  • ****
  • Posts: 366
  • Country: France fr
  • Karma: 23
Reply #1 - 08 November 2016, 13:19:25
Génial je ne serai pas seul à me lancer dans cette aventure  :beer:
J'ai hate de voir les premières photos ou éventuel croquis, schéma ou autre de ce que tu compte mettre en place  :badfinger:

J'ai plus qu'envisager d'utiliser lua pour la programmation j'en avais même parlé sur mon topic de tableau de bord, en revanche pour ma part le programme peut être assez conséquent et donc il serait préférable d'utiliser un langage plus modulable et qui s'organise de meilleur façon (le C++ notamment) c'est juste plus complexe a mettre en œuvre  :badsmile:

N'hésite pas a échanger sur la partie logiciel si tu a des questions ou autre  :wonder:

Good luck !


Offline antoo

  • Legend
  • ******
  • Posts: 3659
  • Country: France fr
  • Karma: 179
  • MSFS ❤️
Reply #2 - 08 November 2016, 15:12:51
Trop cool ! Bienvenue au club :beer: !

Hâte de voir l'avancement de ton projet.

A+

---------------------------------------------------------------------------------------------------
"ET C´EST PARTI!!" Youri Gagarine au lancement de vostok 1 le 12 avril 1961

Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #3 - 08 November 2016, 17:38:27
Merci, les gars. C'est en échangeant les idées qu'on avance le mieux.

Pour les photos et schémas, ne vous inquiétez pas, ça viendra. Faudra que je les uploade.


Offline nulentout

  • Legend
  • ******
  • Posts: 3356
  • Country: France fr
  • Karma: 242
Reply #4 - 10 November 2016, 11:46:22
Coucou les copains,
Si je devais me refaire un tableau de bord, je pense que j'opterais pour plusieurs cartes Arduino. Vu le coût de quelques Euros d'une carte Nano qui a autant, sinon plus de possibilités que la Uno, en introduire plusieurs augmente le nombre d'entrées/Sorties, d'entrées analogiques etc. De plus, le logiciel réparti en plusieurs modules sera plus facile à dominer. Il ne faut pas oublier que certaines commandes sont prioritaires et doivent être prises en compte immédiatement. Par exemple une carte s'occupe en priorité des commandes de pilotage. Avec six entrées analogiques on peut déjà gérer les poussées principales. Mais pour les RCS une deuxième sera certainement la bienvenue. Une troisième carte prendrait en charge les requêtes moins urgentes, par exemple les inverseurs, les boutons poussoir etc. En multiplexant on peut facilement gérer 8 x 8 boutons, ce qui ouvre pas mal de possibilités. Sans compter que les deux premières cartes qui assurent les commandes de vol peuvent aussi s'occuper des afficheurs, de diverses LEDs etc.
Bref, avant de foncer, il me semble salutaire de réfléchir assez loin à l'architecture électronique future, et ne pas oublier de diviser les difficultés pour vaincre !
Quoi qu'il en soit, cogitez, percez, soudez ... il en restera forcément ... un superbe vaisseau !

La sagesse est un trésor ... tellement bien caché.

Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #5 - 12 November 2016, 12:51:37
Tout à fait d'accord avec toi, ô Nulentout. Dans l'avancement de mon projet,
je tiens à rester modulaire.
Pour l'instant, mon sketch Arduino tient dans moins de 10 Ko, alors qu'il gère pour l'instant
20 switches ou BP, et 100 lumineux. Ce qui fait qu'une UNO ou Léonardo convient encore.  Je ne sais pas combien de temps quelle taille de simpit ça pourra gérer.
Mais la DUE me semble indiquée pour amplifier un projet déjà devenu imposant.

Tu soulèves un point intéressant en parlant de priorité de certaine commandes par rapport à d'autres.
Je me demande pour l'instant comment on peut programmer des priorités. Je sais que le soft du LEM
avait été basé sur cette gestion des priorités, ce qui leur avait permis de se poser en sécurité
malgré l'avalanche de "stack overflow".


Offline nulentout

  • Legend
  • ******
  • Posts: 3356
  • Country: France fr
  • Karma: 242
Reply #6 - 15 November 2016, 11:13:44
Coucou les copains,
Ben ... pour gérer des priorités deux approches sont envisageables :
Confier les plus hautes priorités à un microcontrôleur qui s’en occupe en temps réel et « ne fait que ça ».
Charger un processeur de tout, mais les actions prioritaires sont traitées par interruptions.
L’ATmega328 se prête bien aux interruptions, mais il faut te « cogner » la théorie pour pouvoir mettre en pratique.  :wall:
 

La sagesse est un trésor ... tellement bien caché.

Offline Bibi Uncle

  • Legend
  • ******
  • Posts: 2264
  • Country: Canada ca
  • Karma: 17
Reply #7 - 15 November 2016, 17:57:45
Pour une gestion des priorités efficaces, le mieux est de quitter le nid douillet de l'environnement Arduino, et de faire du "bare-metal" programming avec un RTOS. Ceci va te permettre d'avoir plusieurs tâches qui s'exécutent en même temps, d'avoir des tâches qui se réveillent par interruption et, si programmé correctement, d'avoir un environnement déterministe. C'est beaucoup plus complexe à programmer, mais ça peut être vraiment plus efficace à l'exécution.

Je te suggère de regarder FreeRTOS et TI-RTOS. Il y a aussi quelques projets de librairies pour simplifier la programmation bare-metal, notamment xpcc qui est une jolie librairie écrite en C++11.

Émile

Pluton, Saturne et Jupiter
Entendez-vous monter vers vous le chant de la Terre?

- Luc Plamondon

Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #8 - 15 November 2016, 18:31:07
Hou là!! Dans mon cours déjà lointain sur les microprocesseurs, ce que j'avais trouvé
le plus difficile était bien les interruptions.
Quant au "bare metal", alors là, je n'en suis pas là. Ça se programme en C++, ce truc là?

Disons que, pour l'instant, là où j'en suis, les réactions de mon système se font sans délai.
Mais je jetterai un œil là dessus, car c'était quelque chose dont j'ignorais l'existence...


Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #9 - 20 November 2016, 20:43:07
Allez, on attaque:
                                      PREMIER CHALLENGE: Lire un fichier SCN


Dans un premier temps, je voulais savoir s'il était possible pour moi de récupérer des données d'un fichier SCN, de façon à pouvoir les charger dans le simpit. Le moyen d'y arriver est bien entendu d'utiliser la console Lua d'Orbiter.

Elle s’obtient avec F4, puis bouton « Custom ». On sélectionne « Lua console window », et la console va s’ouvrir. A partir de là, on peut entrer des lignes de commande. Par exemple, pour l’altitude :
         
Code: [Select]
Term.out(oapi.get_altitude()) va donner l’altitude du vaisseau
Pour d’autres valeurs, il faut d’abord avoir le handle du vaisseau. Il faut faire en premier :
         
Code: [Select]
v=vessel.get_focusinterface()Puis, si on veut connaître par exemple l’état des RCS :
         
Code: [Select]
term.out(v :get_rcsmode()) va renvoyer 0, 1 ou 2 selon la position du commutateur RCS.
Dans ce domaine, j’ai bidouillé pour connaître la valeur des jauges de carburant (Main Fuel, RCS, SCRAM Fuel). En regardant la doc, j’ai pu trouver tout ce que je cherchais.

Après cette première prise de contact avec la console LUA, j’ai éprouvé le besoin de lancer un script Lua depuis un scénario. Comme je ne savais pas trop comment faire, j’ai regardé le scénario "Atlantis/launch".



Au début du scénario, on a:
Code: [Select]
BEGIN ENVIRONMENT
System Sol
Date MJD
Script Demos/Atmautopilot
END ENVIRONMENT

Donc, on peut appeler un script Lua, en l'occurrence "Atmautopilot" à exécuter au lancement du scénario. C'est parfait pour lire l'état  du vaisseau, à condition de pouvoir ouvrir et lire ce dont on a besoin dans le fichier SCN. Mais comment faire?

Dr Spock m'a donné un début de solution en écrivant un script Lua pour des HiScores dans le cadre de Orbiter Challenge.

Code: [Select]
Data.path='Script/Challenges/Challenge1.dat'
max_score=10

slist=hscore_read(data_path, max_score)

function hscore_read(file, n)
local slist={}         
local f=io.open(file, "r")
if f~=nil then
for i=1, n do
local t=f:read()
if t==nil then break end
local name, fuel
_,_,name, fuel=string.find(t, "(._):(.+)")
slist[i]={name, to number(fuel)}
end
f:close()
end
return slist
     end

En cherchant à afficher en surimpression à l'écran les valeurs liées à mon vaisseau, je finis par avoir:
Code: [Select]
-- ******************************************
-- ********read number lines from scn file***
-- ******************************************
function line_read (file)
    number_lines=0
    local g = io.open(file,"r")
    if g ~= nil then
        repeat
            t = g:read()
            number_lines=number_lines+1
        until t == nil
    g:close()
    end
end
-- *****************************************
-- *********end function line_read *********
-- *****************************************
Cette première fonction a pour but de connaître le nombre de lignes à lire.

Ensuite,
Code: [Select]
-- *******************************************
-- ********load lines from scn file***********
-- *******************************************
function load_scnfile (file,n)
    slist={}
    local f = io.open(file,"r")
    if f ~= nil then
        for i=1,n do
            t = f:read()
            if t == nil then break end
            slist[i]=t
        end     
        f:close()
    end
    return slist
end
-- ******************************************
-- *********end function load_scnfile *******
-- ******************************************
Celle-ci va remplir le tableau slist[] ligne par ligne avec le contenu du fichier SCN.

Une fois le tableau chargé, on veut connaître les valeurs concernant les auxiliaires du vaisseau. Chacun aura compris qu'on parle ici du Xr2 Ravenstar.
Code: [Select]
-- *******************************************
-- ***********load ship status****************
-- *******************************************
function load_xr2_status (pattern)
    for i=1,number_lines-1 do
       s1 = slist[i]
       _,_,name,classname = string.find (s1, "(.-):(.+)")
       if (classname=='XR2Ravenstar') and (ship=='XR2Ravenstar') then
           name_ship=name
           for j=i, number_lines-1 do
               if string.match (slist[j], 'NOSECONE')~=nil then
                   _,_,_,status,level = string.find(slist[j],"(.+) (.+) (.+)")
                   note:set_text('NOSECONE=>'..status..' '..level)
                   proc.wait_sysdt(0.41)
               elseif string.match (slist[j], 'GEAR')~=nil then
                   _,_,_,status,level = string.find(slist[j],"(.+) (.+) (.+)")
                    note:set_text('GEAR=>'..status..' '..level)
                   proc.wait_sysdt(0.41)
               elseif string.match (slist[j], 'RADIATOR')~=nil then
                   _,_,_,status,level = string.find(slist[j],"(.+) (.+) (.+)")
                   note:set_text('RADIATOR=>'..status..' '..level)
                   proc.wait_sysdt(0.41)
               elseif string.match (slist[j], 'HATCH')~=nil then
                   _,_,_,status,level = string.find(slist[j],"(.+) (.+) (.+)")
                   note:set_text('HATCH=>'..status..' '..level)
    elseif slist[j]=='END' then
                   flag_out=1
                   break
               end                     
           end
       

Cette partie de script lit les lignes du fichier SCN et extrait les valeurs associées au nosecone, gear, radiator, hatch. Status contient une valeur comprise entre 0 et 3 (0 pour replié; 1 pour déployé; 2 pour en repli; 3 pour en déploiement), tandis que level contient une valeur à 4 chiffres après la virgule comprise entre 0 et 1. Les valeurs extrêmes de level sont 0 pour replié, 1 pour déployé. Ce sont ces valeurs qui devront ensuite être transmises à la Uno Arduino afin de synchroniser le cockpit sur l'état du Xr2 dans le SCN. Mais nous n'y sommes pas encore. Pour l'instant, le script ne fait qu'afficher ces valeurs à l'écran, une fois le scénario lancé.

J'ai aussi vérifié qu'il était possible d'afficher à l'écran les valeurs de réservoir et celles concernant d'éventuels conteneurs de cargaison chargés dans la soute.
Il fallait aussi envisager le cas où on aurait deux Xr2 dans le scénario, et ne se charger que du vaisseau à bord duquel on se trouve.

Ces trois fonctions(compter le nombre de lignes du scn, charger ces lignes dans un tableau, lire les valeurs qu'on y trouve) sont appelées par ce qui suit:
Code: [Select]
--****************************************************************************
-- ********** begin program ************************* begin program *********
--*****************************************************************************
note = oapi.create_annotation()
note:set_pos (0.35,0.1,0.8,0.95)
note:set_colour ({r=0.9,g=0.5,b=0.2})

data_path = 'Scenarios/(Current state).scn'
intro = 'Load scn data'

note:set_text(intro)
proc.wait_sysdt(0.5)

note:set_colour ({r=0.9,g=0.0,b=0.0})

v=vessel.get_focusinterface()
ship=v:get_classname()
name_ship_focused=v:get_name()


-- ******************************************************
-- ***********begin call functions for scn file**********
-- ******************************************************

sfirst = line_read (data_path)

slist = load_file (data_path,number_lines)


if  ship=='XR2Ravenstar' then
    lstatus = load_xr2_status (ship)
elseif ship=='Xr5Vanguard' then
    note:set_text('Toward function loop load of XR5Vanguard')
    proc.wait_sysdt(2.01)
elseif ship=='ShuttleA' then
    note:set_text('Toward function loop load of ShuttleA')
    proc.wait_sysdt(2.01)
else
    note:set_text('vessel not implemented, simpit not activated')
    proc.wait_sysdt(2.01)
end
-- ******************************************************
-- ************ end call functions for scn file**********
-- ******************************************************
note:set_text('End of program')
proc.wait_sysdt(1)
note:set_text('  ')
proc.wait_sysdt(1)

J'ai arrangé quelque chose de façon à ce qu'on puisse avoir autant de classe de vaisseau qu'on veut. Ca dépendra ensuite du simpit qu'on a construit.

Ces quelques lignes de script Lua ont validé mon idée qu'il est possible d'aller chercher des données dans le fichier SCN, de façon à pouvoir les envoyer dans le simpit. Ce dernier pourra donc être synchronisé avec le scénario chargé au préalable.

Le prochain challenge consistera en l'envoi d'une commande simple par la UNO à Orbiter.

« Last Edit: 20 November 2016, 20:55:50 by Mars Bleu »

Offline nerofox

  • Sr. Member
  • ****
  • Posts: 366
  • Country: France fr
  • Karma: 23
Reply #10 - 21 November 2016, 18:30:41
excellente infos
lua est un langage de script avant tout il existe des librairies pour tout  :badfinger:
cherche du coté luars232 pour la communication série avec une Arduino

bon courage pour ta recherche :)


Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #11 - 23 November 2016, 18:06:21
Oui, Luars232 est bien utile. Il faut simplement mettre la dll au bon endroit.
J'avance bien dans mes travaux. Je sors d'une phase programmation et
débogage intensive pour attaquer une nouvelle phase de construction.


Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #12 - 05 December 2016, 18:22:51
DEUXIÈME CHALLENGE: faire envoyer à la Arduino UNO une commande simple à ORBITER



La deuxième chose à valider est l'utilisation d'une carte Arduino Uno configurée en clavier afin d'envoyer des caractères en fonction d'un switch manœuvré. (Par exemple "G" pour manœuvrer le train d'atterrissage.)
Les posts de Hysot ont été très, très utiles pour me guider.
Pour cela, voir à: http://orbiter.dansteph.com/forum/index.php?topic=12877.0


J'ai effectué ma première commande de matériel, comprenant bien sûr une UNO. J'avais commandé un kit de base (quelques résistances, condensateurs, leds, etc…)
L'idée est que la manœuvre du switch va ouvrir ou fermer un circuit électrique. Selon que le circuit est ouvert ou fermé, on applique ou non Vcc aux bornes de la résistance de 10KΩ. Il suffit de connecter la borne où Vcc est appliquée à un pin  configuré en entrée de la UNO.

On commence simple...



Dans la UNO, on charge un sketch qui enverra le caractère "g" à chaque changement d'état du switch:
Code: [Select]
#include <HIDKeyboard.h>                    // Inclut la librairie HIDKeyboard au programme

HIDKeyboard keyboard;                       // Initialise la librairie

int Buttonpin = 7;                          //Le bouton est branche sur la pin 7
int ButtonState = 0;                        //L'etat du bouton: ON (HIGH) ou OFF (HIGH). On lui donne pour le moment la valeur 0
int PreviousButtonState = 0;                //Le precedent etat du bouton

void setup()
{
  pinMode(Buttonpin, INPUT);                //On initialise la pin 7 comme une entree
 
  keyboard.begin();                         // Commence la communication
  delay(2000);                              // Attend que le peripherique soit reconnue comme un clavier
}

void loop()
{
  ButtonState = digitalRead(Buttonpin);     //On lit l'etat de la pin 7 et on le stocke dans la variable ButtonState
 
  if (ButtonState != PreviousButtonState)   //On compare les deux états. Si ils différents, la condition est validée et on écrit la lettre A
  {
    PreviousButtonState = ButtonState;      //On enregistre le nouvel état du bouton dans la variable PreviousButtonState pour
                                                             //la prochaine boucle.
    keyboard.pressKey('g');
    delay(100);
    keyboard.releaseKey();
    delay(50);
  }
}

Ensuite, il faut configurer la UNO en Keyboard selon la procédure indiquée par Hysot.

Il n'y a plus qu'à lancer Orbiter avec un DG en orbite, et manœuvrer le switch. Le train va suivre les mouvements du switch.

Ce petit montage a validé la possibilité de commander un auxiliaire d'un vaisseau d'Orbiter avec un raccourci clavier envoyé par une UNO. Ce n'était pas une première scientifique, mais j'étais bien content d'y être arrivé.


MAIS en avançant dans le projet, je me suis aperçu que la UNO une fois configurée en clavier ne peut pas
recevoir des données. Normal: un clavier n'est pas un périphérique qui reçoit des données, il en envoie.
Pour contourner la difficulté, il faut un Arduino capable d'être tant un périphérique capable de recevoir
des données, qu'un clavier (ou vu comme tel). c'est pour ça que je suis passé à la Leonardo. Du coup, plus
de librairie Keyboard à charger, ni de flashouillage de la UNO.

Le prochain challenge sera de multiplier les entrées/sorties connectées à la Arduino.



Offline nerofox

  • Sr. Member
  • ****
  • Posts: 366
  • Country: France fr
  • Karma: 23
Reply #13 - 05 December 2016, 19:24:32
Ta progression est intéressante et apporte de l'aide futur à d'autres personnes qui souhaiteraient se lancer dans l'aventure  :wor:

A noter que tu peux paramétrer ton switch non pas en mode INPUT mais en INPUT_PULLUP, cela évite d'ajouter une résistance au montage et donc simplifie l'électronique  :badfinger:

ce qui nous donne:
Code: [Select]
void setup()
{
  pinMode(Buttonpin, INPUT_PULLUP);                //On initialise la pin 7 comme une entree
 
  keyboard.begin();                         // Commence la communication
  delay(2000);                              // Attend que le peripherique soit reconnue comme un clavier
}


Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #14 - 05 December 2016, 19:47:11
 :friend: Merci, Nerofox :friend:

Je ne connaissais pas cette commande INPUT_PULLUP; ce qui fait que
j'ai assemblé mes cartes d'élaboration de signal en incluant à chaque canal
une résistance de 10kOhms. 144 entrées en tout! Comme ça fonctionne,
je ne change rien :badsmile:


Offline nulentout

  • Legend
  • ******
  • Posts: 3356
  • Country: France fr
  • Karma: 242
Reply #15 - 17 December 2016, 07:19:54
Coucou les copains,
Je ne suis pas certain d'avoir tout compris dans le détail, mais il me semble que tu comptes affecter une entrée binaire par inverseur. Si il s'agit de boutons poussoir, tu peux en placer 5 ou 6 par entrée analogique, mais je suppose que tu sais faire ...
Bonne continuation : Nulentout

La sagesse est un trésor ... tellement bien caché.

Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #16 - 18 December 2016, 10:11:33
Effectivement, je vais faire ça. Mais la voie analogique a été une impasse pour moi. Je suis passé
par la voie numérique, avec succès.
Je voulais poursuivre les descriptions de mes travaux, mais hostingpics a l'air surchargé, ce matin.
Pas moyen d'uploader les photos destinées à illustrer mon propos. A suivre...




Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #17 - 18 December 2016, 12:23:00
Eeeet voici le....
              TROISIÈME CHALLENGE: multiplier les entrées/sorties connectées à la UNO

En suivant le chemin ouvert par Hysot, j'ai cherché à connecter plusieurs switches sur une même entrée analogique de la UNO, en faisant varier la tension d'entrée par des ponts diviseurs. Avec deux ou trois switches et des résistances associées, j'arrivais à peu près à mes fins, mais avec une dizaine de switches, je ne m'en sortais plus. J'ai noirci des pages et des pages, fait des tas d'essais de sketches Arduino pour arriver à la conclusion que j'étais dans une impasse technique. Il fallait faire autrement.

En remettant le nez dans la doc du web, j'ai trouvé: il faut faire du numérique. Le composant qui va me permettre d'avancer est le 74HC165, shift register PISO (Parallel Input Serial Output). Ce registre est cascadable, on peut donc avoir une multitude de switches dont on peut observer l'état. Ca tombe bien, car les raccourcis claviers du Xr2 sont nombreux (une bonne centaine).
D'un autre côté, les lumineux dont je vais avoir besoin vont être nombreux. Il y a déjà 12 auxiliaires APU sans compter les jauges de carburant, les afficheurs 7 segments, et tout un tas de signalisations.

Le site Arduino parle bien des 74HC165, et aussi des 74HC595 (registres SIPO). Avec ce qu'on y trouve, on peut apprendre facilement à s'en servir.


Nota:
A contrario, je commence par les registres de sortie 74HC595 afin de pouvoir matérialiser ce qu'on détecte des registres d'entrée 74HC165.



Les registres SIPO (Serial Input Parallel Output, 74HC595) vont permettre de gérer de multiples sorties, parce que cascadables tout comme le 74HC165.
Afin de tester, j'ai fait un montage provisoire sur breadboard, afin de matérialiser l'état des registres par l'allumage de leds.



La photo ci-dessus montre le montage à deux 74HC595 chaînés. Dans un premier temps, j'avais utilisé seulement un seul registre SIPO et les leds rouges.

Pour commander l'allumage de 8 leds:

Code: [Select]
const int dataPin_out=2;   
const int latchPin_out=3;   
const int clockPin_out=4;
int byte1=0;
void setup()
{
pinMode(dataPin_out, OUTPUT);
pinMode(latchPin_out, OUTPUT);
pinMode(clockPin_out, OUTPUT);
Serial.begin(9600);
}

void loop()
{
byte1=byte1+1;
shiftOut(dataPin_out,clockPin_out,LSBFIRST,byte1);
delay(1000);
if byte1=255
{
Byte1=0;
}
}

Ca fait un compteur binaire dont l'état est matérialisé par l'allumage des leds.

Huit lumineux, c'est bien, mais il me fallait plus. L'étape suivante a été de chaîner un deuxième 74HC595, et d'y connecter huit leds supplémentaires, comme sur la photo.

Attention au bilan électrique du 74HC595: le circuit supporte une intensité totale de 70mA. Donc, pour au maximum 8 leds allumées, il faut au plus 8.75 mA par led. Pour cela, il faut une résistance d'atténuation de 300Ω pour chaque led. Dans la pratique, j'ai mis des résistances de 330 Ω, et je n'ai pas encore détruit de 74HC595.

Pour le remplissage des registres, j'ai voulu me passer de la commande shiftOut(), et utiliser des commandes plus élémentaires.

J'ai fini par arriver à cette fonction, où le tableau SIPOreg[ ] contient les valeurs commandant l'allumage ou l'extinction de leds. NumberSIPOreg est le nombre de registres à remplir divisé par deux. Cette division par deux est rendue nécessaire car j'utilise des mots de 16 bits (unsigned int) avec des registres de 8 bits.

Le principe est qu'on va pousser chaque bit dans la chaîne de registres, autant de fois qu'il le faut pour caler l'ensemble dedans. Si on a deux registres chaînés, on pousse 16 fois à chaque fois qu'on accède à cette fonction.

Code: [Select]
  // Fonction de remplissage des registres
  void updateRegister(unsigned int SIPOled[])
  {
      digitalWrite(latchPin_out, LOW); //déverrouillage des registres
      unsigned int value; //déclaration mémoire tampon pour scrutation SIPOreg[]
      for (byte Ii=0;Ii<NumberSIPOreg;Ii++)// traitement du nombre de registres divisé par 2:
      {                            // on marche en 16 bits sur des registres à 8 bits
        value=SIPOled[Ii];
        for (byte j=0;j<16;j++)          // scrutation des 16 bits de chaque élément de SIPOled[]
        {   
          digitalWrite(clockPin_out, HIGH); un coup d'horloge
          digitalWrite(dataPin_out, ((value&32768)==32768)); //détermination et écriture de DATA à 0 ou 1 sur pin 2 de la UNO
          digitalWrite(clockPin_out, LOW); fin du coup d'horloge
          value=value<<1;// décalage des bits
        }// fin de la boucle for j
      }//fin de la boucle for Ii
      digitalWrite(clockPin_out, HIGH); //ce coup d'horloge a été rajouté pour arriver à caler les bits
      digitalWrite(clockPin_out, LOW);// dans les registres. Je ne sais pas pourquoi c'est indispensable
                                      // mais ça marche comme ça
      digitalWrite(latchPin_out, HIGH);// verrouillage des registres
  }//fin remplissage des registres

Attention, ce code ne marche pas tout seul, il faut les déclarations, le setup, et le loop. C'est juste une fonction.


Si je rajoute un paquet de registres, il me suffit de changer la valeur de NumberSIPOreg dans la déclaration des variables, pour maintenir le calage.

Voilà, plus besoin de la fonction shiftOut(), et je peux multiplier les lumineux commandés par la UNO.

L'état des registres de sortie pouvant ainsi être très facilement matérialisé, on va pouvoir s'intéresser aux registres d'entrée en grand nombre


Offline Bibi Uncle

  • Legend
  • ******
  • Posts: 2264
  • Country: Canada ca
  • Karma: 17
Reply #18 - 20 December 2016, 05:57:03
Le coup d'horloge que tu ajoutes à la fin est dû au fait que SRCLK (ton clockPin_out) est activé sur un front montant (rising edge), ce qui décale tout ton encodage. Ainsi, ton premier bit est introduit dans le shift register lors du deuxième passage dans ta boucle, au front montant de SRCLK en prenant la valeur qui est toujours sur SER (ton dataPin_out).

J'ai refait un peu ton code:
Code: [Select]
// Fonction de remplissage des registres
void updateRegister(unsigned int SIPOled[], byte numSIPOreg)
{
    // Déverouillage des registres
    digitalWrite(latchPin_out, LOW);

    // On s'assure que l'horloge est dans un état connu
    digitalWrite(clockPin_out, LOW);

    // On parcourt chaque bloc de 16 bits
    for (byte i = 0; i < numSIPOreg; ++i)
    {
        unsigned int value = SIPOled[i];

        // On parcourt chaque bit
        for (byte j = 0; j < 16; ++j)
        {
            // Écriture de la valeur.
            unsigned int mask = 0x8000 >> j;
            digitalWrite(dataPin_out, value & mask)

            // Un coup d'horloge
            digitalWrite(clockPin_out, HIGH);
            digitalWrite(clockPin_out, LOW);
        }
    }

    // Verrouillage des registres
    digitalWrite(latchPin_out, HIGH);
}

Description des changements:

1. Ta variable NumberSIPOreg est maintenant passée en paramètre au lieu d'être une constante. Ça te permet de réutiliser ta fonction n'importe où.

2. Au début de la fonction, après avoir latché le shift register, c'est bien de réinitialiser l'horloge à LOW. Ainsi, si jamais ton code laisse par inadvertance l'horloge à HIGH, ta fonction est blindée.

3. La variable value est maintenant créée à l'intérieur de ta première boucle for. C'est une bonne pratique de limiter le scope de tes variables. Puisque celle-ci n'est utile qu'à l'intérieur de ta boucle, autant la déclarée à l'intérieur de celle-ci. Une nouvelle variable sera instanciée à chaque itération de ta boucle et elle ne sera jamais dans l'état non-initialisé (comme c'était le cas avant). N'est pas peur pour les performances, c'est une variable allouée sur la pile. Mon code est donc tout aussi performant que le tiens.

4. Au lieu de shifter la variable value, je préfère utiliser un masque. Comme ça, la variable value n'est jamais modifiée et représente toujours tes données. C'est seulement le masque (qui est en fait un 1 que je place à vis-à-vis le bit voulu, pour ensuite faire un AND avec la valeur pour l'extraire). Si tu n'es pas familier avec les masques, l'article Wikipédia en anglais donne de bonnes explications : https://en.wikipedia.org/wiki/Mask_(computing)

5. Mon masque utilise le 32768 que tu utilisais auparavant (pour mettre le 1 à la fin complétement). Quand tu as affaire à des valeurs comme celle-ci, je te suggère d'utiliser la notation hexadécimale, car c'est plus évident de retrouver l'utilité de ta valeur. 0x8000, pour quelqu'un avec un peu d'expérience est facilement décodable. Avec 32768, il faut sortir la calculatrice.... Aussi, à partir C++14, tu peux utiliser des binary literals comme 0b10000000 par exemple, qui est très facile à lire. Toutefois, je ne sais pas trop quel compilateur est utilisé sur la plateforme Arduino, donc je suis resté à du C++ classique.

6. Les coups d'horloge ont été déplacés comme je l'ai expliqué plus tôt. Ainsi, tu écris ta valeur sur ton SER, puis le front montant de l'horloge (créé quand tu la fais passer à HIGH) enregistre cette variable. Ensuite, on remet l'horloge à LOW pour se préparer à l'autre insertion.

7. J'ai aéré le code, ça fait du bien aux yeux. Particulièrement les paramètres des boucles qui sont gênants a lire lorsqu'ils sont tous collés. C'est toujours une question de préférence, mais certains standards rendent le code plus lisible.

Émile

Pluton, Saturne et Jupiter
Entendez-vous monter vers vous le chant de la Terre?

- Luc Plamondon

Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #19 - 20 December 2016, 14:20:30
Merci, Bibi Uncle de visiter mon humble cabane! :)

La clarté du code que tu as réaménagé est éclatante. Je suis bien content d'avoir le point de vue
de quelqu'un qui a été formé à coder. Pour ma part, j'ai appris à programmer sur le tas, et en
électronique, ma formation remonte aux années 80. :sage:
Au moins, je comprends maintenant le coup d'horloge supplémentaire qu'il fallait envoyer
pour avoir un bon calage de mes bits.

Tous tes points d'explications sont autant de nouveaux objectifs pour apprendre à coder plus
proprement. Afin de tenir compte de tes conseils, je vais avoir pas mal de réécriture à faire.

Dans mon code, je fais beaucoup de bitwise. J'utilise des masques assez souvent, donc, j'ai beaucoup
de valeurs à remettre en hexa ou en binary litteral. Le lien wiki est intéressant: en fait,
je faisais du common bitmask functions sans m'en rendre compte.

Je me demande si un code à base de bitwise est plus efficace en temps machine qu'un code
sans bitwise. Je serais enclin à penser que oui....


Offline Bibi Uncle

  • Legend
  • ******
  • Posts: 2264
  • Country: Canada ca
  • Karma: 17
Reply #20 - 20 December 2016, 19:48:21
Je me demande si un code à base de bitwise est plus efficace en temps machine qu'un code
sans bitwise. Je serais enclin à penser que oui....

Si tu écris du code qui sera exécuté sur une machine moderne, de manière générale, c'est mieux d'éviter les opérations bit à bit. En effet, les architectures modernes sont hautement optimisées pour faire des opérations sur leur "unité de base" (généralement des mots de 32 bits pour des machines modernes). Ainsi, il est souvent préférable d'utiliser un booléen enregistré dans un mot de 32 bits que de fusionner 32 booléens dans un seul mot. Il y a un gaspillage de mémoire, mais ça nécessite moins d'instructions.

Par contre, dans ton cas, tu programmes un système embarqué qui a peu de mémoire et un jeu d'instructions probablement réduit (je connaît pas beaucoup les microcontrôleurs d'Atmel, donc je suppose). Il est donc probablement préférable d'utiliser des opérations bit à bit dans ton cas.

Parfois on n'a pas le choix de les utiliser. Notamment, si tu implémentes un protocole de communication de couche un peu plus basse (couche transport et en dessous), tu n'auras pas le choix d'utiliser ce genre d'instructions pour extraire tes données des champs. Prends une paquet IP par exemple. Pour savoir la longueur de ton en-tête, il faut extraire les bits 4 à 7 à partir du premier mot que tu reçois. Dans ce cas, pas le choix de les extraire avec des opérations bit à bit.

Émile

Pluton, Saturne et Jupiter
Entendez-vous monter vers vous le chant de la Terre?

- Luc Plamondon

Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #21 - 04 January 2017, 17:11:28
J'ai un peu tardé, car pas trop le temps de me connecter pendant les fêtes.

Merci pour tes explications. Sur un Arduino, il vaut mieux donc être économe.


Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #22 - 04 January 2017, 17:47:43
                                              Registres d'entrée
Dans l'élaboration de mon tableau de commande, il faut aussi des switches et boutons poussoirs.

Avec 8 switches connectés, le sketch de base pour collecter l'état de switches et le transférer sur un registre d'entrée devient:
Code: [Select]
const int dataPin_out=2;
const byte latchPin_out=3;
const byte clockPin_out=4;
const byte dataPin_in=5;
const byte latchPin_in=6;
const byte clockPin_in=7;
byte value;

void setup()
{
pinMode(dataPin_out, OUTPUT);
pinMode(latchPin_out, OUTPUT);
pinMode(clockPin_out, OUTPUT);
pinMode(dataPin_in, INPUT);
pinMode(latchPin_in, OUTPUT);
pinMode(clockPin_in, OUTPUT);
Serial.begin(9600);
}

void loop()
{
digitalWrite(latchPin, HIGH);
delayMicrosecond(10);
digitalWrite(latchPin, LOW);
value=shiftIn(dataPin,clockPin);
shiftOut(dataPin_out,clockPin_out,LSBFIRST,value);
delay(10);
}

La valeur value va contenir la valeur binaire de la position 0 ou 1 des switches scrutés. La valeur variera de 0 à 255 (8 bits).

L'état de mon montage provisoire avec un 74HC165 et deux 74HC595, plus deux afficheurs 7 segments.
(Question fouillis, je m'arrêterai là pour passer à des montages plus rangés.)



En voulant chaîner un deuxième 74HC165, j'ai eu des problèmes de stabilité sur le MSB du deuxième registre.
J'ai à peu près tout essayé tant en soft que sur mon montage, rien n'y faisait.
J'ai alors choisi de me passer de la commande shiftIn(). Voici ce que j'ai codé.

@BibiUncle: j'ai soigné la présentation, mais n'hésites pas à me corriger.

Code: [Select]
   // Fonction de lecture des registres PISO (switches et boutons poussoir)

  void get_PISO_register()
  {
    //latch à LOW pour lire l'état des switches, et on attend 10 microsecondes
    digitalWrite(latchPin_in,LOW);
    delayMicroseconds(10);

    //latch à HIGH, verrouillage
    digitalWrite(latchPin_in,HIGH);

    for (unsigned int i=0;i<Data_width+1;++i){             //data_width est le nombre de bits à lire (multiple de 8)

//on "pousse" Data_width fois les valeurs lues dans la mémoire switchStatus; c'est le shiftIn
       unsigned int switchStatus=(switchStatus<<1)|(digitalRead(dataPin_in));

       //un coup d'horloge
       digitalWrite(clockPin_in,HIGH);
       delayMicroseconds(10);
       digitalWrite(clockPin_in,LOW);
       delayMicroseconds(10);

       //j est un modulo 16 de i, afin de cadencer le remplissage du tableau PISOreg[]
       byte j=i%16;
       if ((i>0)&&(j==15)){
        // envoi de la valeur dans le tableau PISOreg[]
        PISOreg[i/16]=switchStatus;
       }//Endif
      } //Next i
     
  }// fin lecture des registres PISO

Avec cette fonction, je peux scruter un grand nombre de switches ou boutons poussoirs et avoir une image de leur état dans le tableau PISOreg[ ].
Dans ma configuration actuelle (Janvier 2017), j'utilise 15 switches, 9 boutons-poussoir(s?), 4 joysticks à 2 ou
4 contacts, et le gros coup-de-poing du "undock". Mais j'ai de la réserve, ayant à ma disposition 144 entrées.

Mais avant d'aborder le montage de tout ça, il faut encore s'assurer de la possibilité de transmettre des
données, en vue de la synchronisation du tableau de commande avec le scénario chargé.


Offline Bibi Uncle

  • Legend
  • ******
  • Posts: 2264
  • Country: Canada ca
  • Karma: 17
Reply #23 - 04 January 2017, 19:18:49
Tes délais de 10 microsecondes sont-ils vraiment nécessaires ? L'Arduino a une horloge assez lente (j'ai trouvé 16 Mhz, mais ça reste à confirmer puisque Atmel spécifie une fréquence maximale de 20 Mhz pour le ATMega328p). Si on émet l'hypothèse très généreuse qu'un digitalWrite est atomique (un seul coup d'horloge du microcontrôleur pour l'exécuter), ce qui serait assez impressionnant, ton coup d'horloge sans délai sera à 16 Mhz. Selon la documentation de TI pour le SN74HC165, à température ambiante à 4.5V, la fréquence max est de 31 Mhz. Bref, je crois que le délai n'est pas nécessaire.

Sinon, j'ai regardé rapidement ton code et ça me semble très bien :top:

Émile

Pluton, Saturne et Jupiter
Entendez-vous monter vers vous le chant de la Terre?

- Luc Plamondon

Offline Mars Bleu

  • Hero Member
  • *****
  • Posts: 638
  • Karma: 33
Reply #24 - 04 January 2017, 20:47:47
 Effectivement, en y réfléchissant, ça n'est pas nécessaire, étant donné la fréquence
d'horloge qui est de 16Mhz.
Merci pour le :top:  :)