Voir les contributions

Cette section vous permet de consulter les contributions (messages, sujets et fichiers joints) d'un utilisateur. Vous ne pourrez voir que les contributions des zones auxquelles vous avez accès.


Messages - Dominique

Pages: 1 ... 89 90 [91] 92 93 ... 106
1351
Le logiciel DCC++ / Re : DCC++ BaseStation
« le: août 29, 2016, 08:46:22 pm »
Ah oui, la réponse est dans le fil d'à coté :

http://forum.locoduino.org/index.php?topic=46.msg1862#msg1862

Il y a le code de la version DCC++ que j'ai modifiée pour ajouter la fonction discover() qui fait une lecture de CV (le N°1 pour l'adresse DCC) sur la voie principale au moment du Setup().

Bonne "découverte"

1352
Le logiciel DCC++ / Re : DCC++ BaseStation
« le: août 29, 2016, 07:14:23 pm »
Je viens de tester à l'instant la lecture du CV 1 (adresse DCC) et c'est bon, ma loco a bien l'adresse 18.

DCCpp-VV V0.10 du 9/08/2016

<iDCC++ BASE STATION FOR ARDUINO NANO / ARDUINO MOTOR SHIELD: V-1.2.1+ / Aug 15 2016 19:04:05>
<N0: SERIAL><p1><rm123|123|1 18><p0>
adresse DCC : 18

D'ailleurs j'ai ajouté cette lecture de l'adresse DCC dans le Setup : c'est donc automatique au démarrage

Mon matériel : Nano + LMD18200 + Max471


1353
Vos projets / Re : Mesure de vitesse, échelles N ou HO
« le: août 28, 2016, 04:06:20 pm »
Bonjour,

Selon les conseils d'Hubert, j'utilise des capteurs Hall TLE4905L .

Pour les aimants, les plus puissants possibles à base de Neodyme N54 (le plus fort) comme on en trouve sans difficulté suite à une recherche telle que : "disc neodymium magnets n54"

La note d'application donne les dimensions des aimants.

Je place le capteur Hall entre 2 traverses (en N) et l'aimant sous un wagon et je fais des essais de passage pour déterminer la vitesse maximum. La distance doit être la plus petite possible (quelques millimètres)

Sur le site de RocRail, il y a une page dédiée à ce type de capteurs : http://wiki.rocrail.net/doku.php?id=gca173-fr

1354
Vos projets / Re : Rétro-signalisation sur réseau Marklin
« le: août 28, 2016, 09:54:33 am »
Merci Goupil,

Maintenant j'ai compris. Le détecteur n'est pas en série pour une détection de courant mais en paralelle pour une détection de tension.

Dans ce cas le pont de diodes est bien câblé en redresseur double alternance classique et le schéma est bon.

Amicalement
Dominique

1355
Vos projets / Re : Rétro-signalisation sur réseau Marklin
« le: août 27, 2016, 11:35:08 pm »
Peux-tu me donner un schéma complet avec la centrale et les rails, car là je n'y comprend plus rien  :-[ :-[ :-[

1356
Présentez vous ! / Re : Présentation
« le: août 27, 2016, 11:30:00 pm »
Si si, tout est possible si on sait ce qu'on veut (c'est ça la vrai question en fait)

1357
Vos projets / Re : Rétro-signalisation sur réseau Marklin
« le: août 27, 2016, 08:32:07 am »
A mon avis la diode D4 est à l'envers ...


Le circuit doit se connecter EN SÉRIE entre l'une des sorties de la centrale et l'un des rails. Donc le courant doit pouvoir passer dans les 2 sens. Ce que l'opto détecte c'est une ddp de 1,2 v aux bornes des diodes quand le courant passe.

1358
Vos projets / Re : Mesure de vitesse, échelles N ou HO
« le: août 26, 2016, 06:42:08 pm »
Bonjour,

Pour les ILS, je ne sais pas, je n'ai pas d'experience.
Pour les capteurs à effet Hall, étant donné qu'ils sont utilisés dans tous les moteurs à combustion pour déclencher l'allumage et que ça tourne à des milliers de tours/mn, ils sont certainement assez rapide pour détecter un train roulant à 500 km/h réel.
En théorie. J'ai fait quelques essais qui semblent concluants.

Je pense qu'il faut chercher du côté de la distance entre aimant et capteur, l'orientation des pôles, etc..

Peut-être quelqu'autre viendra compléter ma réponse.

Bon courage.

1359
Je suis un peu comme toi Denis, certain qu'il existe des outils pour convertir le résultat d'une IHM graphique en programme, soit en passant par un traducteur de tableau (ça peut s'appeler un compilateur), soit en générant du code à partir de Macros paramètrées à partir du tableau ou de l'IHM directement. Il doit bien y avoir d'autres méthodes encore.

C'est ce que font JMRI, RocRail et TrainControler.

Les 2 premiers sont en open source donc on peut regarder dans leur code comment il font (tâche certainement très fastidieuse) !

Attention, en réalisant l'IHM interactive que tu veux faire, il est probable qu'il faille avoir une idée précise de la conversion en code qui doit suivre.

Mais nos experts sauront mieux que moi t'expliquer ...
Sujet très intressant  ::)

1360
Le logiciel DCC++ / Re : DCC++ BaseStation
« le: août 23, 2016, 09:00:51 pm »
En fait j'avais envie de faire un article sur CmdrArduino mais l'inspiration tardait à venir. Et puis DCC++ est arrivé et tout est devenu évident parce que ce logiciel est bien foutu  8).

Christophe va bientôt nous éclairer sur Ethernet et ça vaudra le coup de faire un article complet dans la zone éditoriale à partir de toutes ces contributions.

Amicalement
dominique

1361
Le logiciel DCC++ / Re : DCC++ BaseStation
« le: août 23, 2016, 07:15:54 pm »
Merci pour la détection d'erreur que j'ai quand même corrigée   :-[

J'étais en train de terminer quand toute la famille est rentrée et j'ai du être distrait !

1362
Le logiciel DCC++ / Re : DCC++ BaseStation
« le: août 23, 2016, 05:30:57 pm »
La détection de courant de DCC++

Un des gros avantages de DCC++ par rapport aux autres logiciels connus qui génèrent du DCC est sa mesure de courant qui fonctionne bien sur divers matériels (shield Arduino Motor, ou Polulu ou LMD18200).

La mesure de courant se fait par la lecture d'un port analogique à chaque tour de loop() et l'intérêt de cette mesure est qu'elle intègre une moyenne glissante ou "lissage exponentiel" avec les dernières mesures, c'est une sorte de filtre passe-bas : on n'est pas tous capable d'inventer cela, donc profitons en  ;)

La formule utilisée est c_t = coeff x apin + c_t-1 x (1-coeff) où
- c_t est la valeur de courant calculée au tour t
- c_t-1 est la valeur de courant calculée au tour t-1 (précédent)
- apin est la valeur analogique lue sur la pin analogique utilisée pour la mesure
- coeff est le coefficient de pondération qui est ici de 1%

La valeur lue sur le port apin est donc réduite au 1/100 ème de sa valeur et ajoutée à 99/100 du résultat précédent (initialisé à 0 au départ). La valeur calculée converge donc progressivement vers une valeur moyenne du courant, avec un grande stabilité car les mesures affectent peu le résultat.

On trouve le code suivant dans l'onglet CurrentMonitor.cpp

void CurrentMonitor::check(){
#define  CURRENT_SAMPLE_SMOOTHING   0.01

  current=analogRead(pin)*CURRENT_SAMPLE_SMOOTHING+current*(1.0-CURRENT_SAMPLE_SMOOTHING);        // compute new exponentially-smoothed current
  if(current>CURRENT_SAMPLE_MAX && digitalRead(SIGNAL_ENABLE_PIN_PROG)==HIGH){                    // current overload and Prog Signal is on (or could have checked Main Signal, since both are always on or off together)
    digitalWrite(SIGNAL_ENABLE_PIN_PROG,LOW);                                                     // disable both Motor Shield Channels
    digitalWrite(SIGNAL_ENABLE_PIN_MAIN,LOW);                                                     // regardless of which caused current overload
    INTERFACE.print(msg);                                                                         // print corresponding error message
  }   
} // CurrentMonitor::check 

Ce code, appelé dans la loop permet de couper l'alimentation en cas de dépassement de seuil (ici 300), c'est à dire en cas de court-circuit.

La mesure de courant pour les commandes de programmation

C'est l'application la plus interessante de cette mesure de courant lissée.

Nous avons tous remarqué que lors de la programmation du décodeur d'une loco (on commence toujours par son adresse DCC), celle-ci se met à bouger  :-[
Cela vient du fait que le décodeur va provoquer des impulsions de consommation de courant sur les rails en activant le moteur pendant une fraction de seconde. Il répète plusieurs cycles de "consommation" pour transmettre une information qui est un bit 0 ou un bit 1.

Pour récupérer cette information, notre logiciel BaseStation va d'abord mesurer une valeur de repos du courant "base" c'est à dire quand la loco est au repos : ce courant n'est pas nul car le décodeur en consomme toujours un peu.
Puis quand la loco va transmettre ses "bits" le logiciel va mesurer la consommation en faisant une moyenne glissante pendant la durée du bit, puis comparer le résultat avec la valeur de repos. Si le résultat dépasse la valeur de repos d'un seuil ACK_SAMPLE_THRESHOLD alors le bit est un "1", sinon c'est un "0"

Ce mécanisme se répète 8 fois pour récupérer les 8 bits de l'octet de réponse de la loco.

Les constantes utilisées dans cette mesure se trouvent dans l'onglet PacketRegister.h :

// Define constants used for reading CVs from the Programming Track

#define  ACK_BASE_COUNT            100      // number of analogRead samples to take before each CV verify to establish a baseline current
#define  ACK_SAMPLE_COUNT          500      // number of analogRead samples to take when monitoring current after a CV verify (bit or byte) has been sent
#define  ACK_SAMPLE_SMOOTHING      0.2      // exponential smoothing to use in processing the analogRead samples after a CV verify (bit or byte) has been sent
#define  ACK_SAMPLE_THRESHOLD       30      // the threshold that the exponentially-smoothed analogRead samples (after subtracting the baseline current) must cross to establish ACKNOWLEDGEMENT

On constate que les paramètres de lissage ne sont pas tout à fait les même que pour la mesure dans la loop. Cela vient du fait que les mesures dans la loop se font en fonction du temps d’exécution de la loop toute entière, alors que dans la réponse lors d'une programmation, ce sont seulement quelques instructions qui s’exécutent.

Et le code de récupération de chaque bit se trouve dans l'onglet PacketRegister.cpp, à l'intérieur de chaque fonction de programmation avec réponse, comme readCV, writeCVByte, writeCVBit sur la voie de programmation :

    c=0;
    d=0;
    base=0;

    for(int j=0;j<ACK_BASE_COUNT;j++)
      base+=analogRead(CURRENT_MONITOR_PIN_PROG);
    base/=ACK_BASE_COUNT;

    bRead[2]=0xE8+i; 

    loadPacket(0,resetPacket,2,3);          // NMRA recommends starting with 3 reset packets
    loadPacket(0,bRead,3,5);                // NMRA recommends 5 verfy packets
    loadPacket(0,resetPacket,2,1);          // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)

    for(int j=0;j<ACK_SAMPLE_COUNT;j++){
      c=(analogRead(CURRENT_MONITOR_PIN_PROG)-base)*ACK_SAMPLE_SMOOTHING+c*(1.0-ACK_SAMPLE_SMOOTHING);
      if(c>ACK_SAMPLE_THRESHOLD)
        d=1;
    }


Il va de soi que ce code ne doit pas être modifié car il fonctionne bien : nous avons testé ce code sur plusieurs plateformes à base d'Uno, Nano et probablement Mega.

BaseStation confirme donc son universalité en permettant de commander la voie principale, et aussi la voie de programmation avec un compatibilité NMRA bien étudiée.

L'interface "textuelle" par le port série/usb ou par ethernet va permettre de développer diverses interfaces, en concurrence avec celle de Gregg, mais en s'appuyant sur la qualité de BaseStation. J'en suis convaincu.

J'espère que la présentation de cette partie du logiciel BaseStation ne vous a pas semblé trop ardue  ::)


1363
Le logiciel DCC++ / Re : DCC++ BaseStation
« le: août 21, 2016, 10:37:08 pm »
L'interface avec le Contrôleur par liaison série ou ethernet

Nous avons vu dans l'épisode précédent comment la BaseStation fabrique ses paquets DCC à partir de commandes sous forme textuelle qui sont passées à des fonctions de l'objet RegisterList (qui n'est pas une classe mais une structure, mais bon..).

Les onglets SerialCommand.h et SerialCommand.cpp vont se charger d'assurer l'interface entre le canal de communication (série ou ethernet) et les fonctions de RegisterList.

SerialCommand.h déclare une structure SerialCommand :

struct SerialCommand{
  static char commandString[MAX_COMMAND_LENGTH+1];
  static volatile RegisterList *mRegs, *pRegs;
  static CurrentMonitor *mMonitor;
  static void init(volatile RegisterList *, volatile RegisterList *, CurrentMonitor *);
  static void parse(char *);
  static void process();
}; // SerialCommand

On y trouve pas mal de choses importantes :

  • un tableau de caractères commandString: c'est la commande
  • deux RegisterList, l'une mRegs pour la voie principale et l'autre pRegs pour la voie de programmation. C'est ce qui permet d'appeler simplement les fonctions de RegisterList vues précédemment
  • une structure CurrentMonitor qui permet d'avoir une mesure de courant
  • une fonction parse qui se chargera du décodage des commandString et qui va donc appeler les fonctions de RegisterList
  • une fonction process qui va lire l'interface série ou ethernet et va passer la commande reçue à la fonction parse

Le code d'exécution se trouve donc dans l'onglet SerialCommand.cpp :

La fonction process ne fait pas grand chose d'autre que d'enregistrer les caractères compris entre "<" et ">" et de passer le résultat à parse.

La fonction parse analyse le 1er caractère de la commande pour envoyer le reste des caractères (sans cette 1ère lettre) vers la bonne fonction de RegisterList

"t" pour setThrottle (commande DCC)
"f" pour setFunction (commande DCC)
"a" pour setAccessory (commande DCC)
"T" pour une commande d'aiguille directe (commande non DCC)
"Z" pour une commande d'une pin de sortie de l'Arduino  (commande non DCC)
"S" pour lire l'état d'un capteur relié à une pin d'entrée de l'Arduino  (commande non DCC)
"Q" pour lire l'état de tous les capteurs à la fois  (commande non DCC)
"w" pour programmer un CV sur la voie principale en écriture seule sans vérification (commande DCC)
"b" pour programmer un bit particulier d'un CV sur la voie principale en écriture seule sans vérification (commande DCC)
"W" pour programmer un CV sur la voie de programmation avec vérification donc réponse du décodeur (commande DCC)
"B" pour programmer un bit particulier d'un CV sur la voie de programmation avec vérification donc réponse du décodeur (commande DCC)
"R" pour lire un CV sur la voie de programmation avec réponse du décodeur évidemment (commande DCC)
"1" pour appliquer le signal DCC sur les rails (power ON)
"0" pour couper le signal DCC sur les rails (power OFF)
"c" pour lire la valeur du courant sur la voie principale  (commande non DCC)
"s" pour envoyer sur la liaison un état du système  (commande non DCC)
"E" pour sauvegarder les valeurs des aiguilles et capteurs en EEPROM  (commande non DCC)
"e" pour effacer les valeurs enregistrées dans l'EEPROM  (commande non DCC)

A cela s'ajoute quelques utilitaires comme :
"M" pour envoyer un paquet DCC de 2 à 5 octets sur la voie principale (commande DCC)
"P" pour envoyer un paquet DCC de 2 à 5 octets sur la voie de programmation (commande DCC)

Si on s'arrête un instant pour regarder la loop() dans DCCp_Uno, on y lit :

///////////////////////////////////////////////////////////////////////////////
// MAIN ARDUINO LOOP
///////////////////////////////////////////////////////////////////////////////

void loop(){
 
  SerialCommand::process();              // check for, and process, and new serial commands
 
  if(CurrentMonitor::checkTime()){      // if sufficient time has elapsed since last update, check current draw on Main and Program Tracks
    mainMonitor.check();
    progMonitor.check();
  }

  Sensor::check();    // check sensors for activate/de-activate
 
} // loop

Ce qui veut dire que le programme BaseStation ne fait rien d'autre que de traiter les commandes ci-dessus venues de la liaison série ou ethernet, ainsi que de surveiller le courant en cas de court-circuit et de tester les capteurs, ce qui se traduit par l'envoi de messages vers la liaison série ou ethernet.

Il ne fait donc rien de lui-même, mais cela nous ouvre la voie vers des réalisations personnelles intéressantes et sophistiquées, avec une telle belle boite à outils.

Mais revenons à la fonction parse :

On constate qu'il y a deux familles de fonctions :
  • Celles qui sont conformes à la norme DCC NMRA : t, f, a, w, b, W, B, R, M et P
  • Celles qui sont spécifiques à BaseStation et ne concernent pas la norme DCC : T, Z, S, Q, 1, 0, c, s, E, e

Les fonctions conformes à la norme DCC sont réalisées par RegisterList comme on l'a vu précédemment. Mais RegisterList ne s'occupe que des fonctions propres au DCC puisqu'il se charge de la gestion des paquets DCC envoyés aux rails.

Alors, pour les autres fonctions, il existe des onglets spécifiques qui traitent des fonctions non DCC :

Les onglets Accessories.h et Accessories.cpp s'occupent des aiguilles en commande directe par l'Arduino (et non en DCC via un décodeur d'accessoire, ce que BaseStation supporte aussi via la fonction setAccessory). Elles gèrent les structures :

struct TurnoutData {
  byte tStatus;
  byte subAddress;
  int id;
  int address; 
};

struct Turnout{
  static Turnout *firstTurnout;
  int num;
  struct TurnoutData data;
  Turnout *nextTurnout;
  void activate(int s);
  static void parse(char *c);
  static Turnout* get(int);
  static void remove(int);
  static void load();
  static void store();
  static Turnout *create(int, int, int, int=0);
  static void show(int=0);
}; // Turnout

On y retrouve la fonction parse qui fait le lien avec la fonction parse de SerialCommand, pour permettre au contrôleur de commander les aiguilles de cette façon.

Les onglets Outputs.h et Outputs.cpp s'occupent des pins disponibles de l'Arduino qui peuvent être programmées en sortie. Elles gère les structures :

struct OutputData {
  byte oStatus;
  int id;
  byte pin;
  byte iFlag;
};

struct Output{
  static Output *firstOutput;
  int num;
  struct OutputData data;
  Output *nextOutput;
  void activate(int s);
  static void parse(char *c);
  static Output* get(int);
  static void remove(int);
  static void load();
  static void store();
  static Output *create(int, int, int, int=0);
  static void show(int=0);
}; // Output

On y retrouve aussi la fonction parse qui fait le lien avec la fonction parse de SerialCommand, pour permettre au contrôleur de commander les pins de sortie de l'Arduino.

Les onglets Sensor.h et Sensor.cpp s'occupent des pins disponibles de l'Arduino qui peuvent être programmées en entrée pour lire des états de capteurs. Elles gèrent les structures :

struct SensorData {
  int snum;
  byte pin;
  byte pullUp;
};

struct Sensor{
  static Sensor *firstSensor;
  SensorData data;
  boolean active;
  float signal;
  Sensor *nextSensor;
  static void load();
  static void store();
  static Sensor *create(int, int, int, int=0);
  static Sensor* get(int); 
  static void remove(int); 
  static void show();
  static void status();
  static void parse(char *c);
  static void check();   
}; // Sensor

On y retrouve encore la fonction parse qui fait le lien avec la fonction parse de SerialCommand, pour permettre au contrôleur de lire les états des capteurs de rétrosignalisation.

De plus, la fonction check est scrutée par la loop de façon à informer le contrôleur au plus vite des changements d'état des capteurs.

Bien entendu je ne vais pas rentrer dans l'analyse des fonctions de ces 3 dernières familles, mais une série de conclusions d'imposent :

1) BaseStation est un logiciel très complet rédigé de façon propre et claire donc fiable (bien que je n'ai pas tout testé, loin s'en faut, mais cela fleure bon).

2) Sa modularité en fait un logiciel adaptable : on peut enlever et ajouter des fonctions, on peut réaliser certaines fonctions différemment, mais il convient de respecter le noyau principal qui est le moteur DCC.

3) BaseStation est principalement destiné à être piloté par un logiciel de contrôle sur PC. Gregg a écrit Controleur en Processing (version récente) : on peut l'apprécier ou pas. D'autres ont réalisé les adaptations dans JMRI (dernières versions seulement).
N'ayant pas pu installer de version récente dans mon MacBook, j'ai réalisé une petite manette perso que l'on peut voir en document joint.

4) Mais rien n'empêche d'ajouter d'autres fonctions, notamment un automate de gestion de circulation, une gestion de Bal, une gestion de signaux, etc..
Les modèles de programmation des onglets peuvent être une bonne base pour ces extensions. Un bel automate peut aussi trouver sa place dans la loop ou appelé depuis la loop.

Je vous invite maintenant à partager vos impressions et expériences sur DCC++.




1364
Présentez vous ! / Re : Présentation
« le: août 21, 2016, 06:47:43 pm »
Bonjour et bienvenue drahcoc,

Avec ton matériel et ton expérience il doit être possible de faire beaucoup de choses  :D

Nous espérons que tu trouveras la substantifique moelle dans Locoduino, sinon, il suffit de nous demander et nous tâcherons de faire ce qui est possible.

Bien amicalement

1365
Le logiciel DCC++ / Re : DCC++ BaseStation
« le: août 21, 2016, 06:43:16 pm »
La génération du signal DCC

C'est là où réside l'intérêt de ce logiciel :

Comme je l'ai indiqué auparavant, DCC++ utilise des tableaux d'octets dits "Packet" organisés en "Register" qui sont gérés dans une liste "RegisterList". Ceci est décrit dans l'onglet PacketRegister.h. Les définitions de base sont :

struct Packet{
  byte buf[10];
  byte nBits;
}; // Packet

struct Register{
  Packet packet[2];
  Packet *activePacket;
  Packet *updatePacket;
  void initPackets();
}; // Register

struct RegisterList{ 
  int maxNumRegs;
  Register *reg;
  Register **regMap;
  Register *currentReg;
  Register *maxLoadedReg;
  Register *nextReg;
  Packet  *tempPacket;
  byte currentBit;
  byte nRepeat;
  int *speedTable;
  int addressDccToDiscover;      // ajout Dominique
  static byte idlePacket[];
  static byte resetPacket[];
  static byte bitMask[];
  RegisterList(int);
  void loadPacket(int, byte *, int, int, int=0) volatile;
  void setThrottle(char *) volatile;
  void setFunction(char *) volatile; 
  void setAccessory(char *) volatile;
  void writeTextPacket(char *) volatile;
  void readCV(char *) volatile;
  void readCV_Main(char *s) volatile;      // ajout Dominique
  void writeCVByte(char *) volatile;
  void writeCVBit(char *) volatile;
  void writeCVByteMain(char *) volatile;
  void writeCVBitMain(char *s) volatile; 
  void printPacket(int, byte *, int, int) volatile;
};

Au passage, on voit dans RegisterList les méthodes disponibles dans CPP++, ainsi que celle que j'ai osé ajouter. Mais nous y reviendrons plus loin.

Il peut y avoir 12 registres dans un Uno et probablement jusqu'à 50 dans un Mega. En tout cas c'est réglable dans la configuration.
Le registre N°0 est réservé pour les commandes à envoyer une seule fois (paquets Idle et Reset, commandes de programmation, commandes de fonctions, commandes d'accessoires). Les registres N° 1 au maximum sont réservés chacun à une machine sur le réseau. Il ne doit pas y avoir plusieurs registres affectés à une même machine, on verra pourquoi bientôt.

Il y a un jeu de registres pour la voie principale et un autre, plus petit pour la voie de programmation car elles peuvent être commandées simultanément.

L'ensemble de ces Registres constitue donc un grand tableau de bits 0 et de bits 1 où chaque ligne est une trame DCC conforme à la norme NMRA et où chaque bit se traduit par une alternance de 200 microsecondes pour un bit ZERO ou 116 microsecondes pour un bit UN.

On se reportera à l'article "L’Arduino et le système de commande numérique DCC" http://www.locoduino.org/spip.php?article14 pour se remémorer le principe de fonctionnement du bus DCC.

Pour produire le signal DCC automatiquement il faut une routine sous interruption pilotée par les 2 compteurs OCR1A (pour les 2 alternances du bit) et OCR1B (pour chaque alternance, donc la moitié de la durée du bit) de l'ATMega.  Il est évident que cette mécanique dépend du processeur utilisé et DCC++ ne s'applique dans son état actuel qu'au UNO et au MEGA. On peut le modifier légèrement pour qu'il fonctionne sur NANO et MINI car ils utilisent le même processeur que le UNO, un ATMega328.

Les 2 routines d'interruption, l'une pour la voie principale et l'autre pour la voie de programmation sont déclarées dans le programme principal DCCpp_Uno

ISR(TIMER1_COMPB_vect){              // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track
  DCC_SIGNAL(mainRegs,1)
}

#ifdef ARDUINO_AVR_UNO      // Configuration for UNO

ISR(TIMER0_COMPB_vect){              // set interrupt service for OCR1B of TIMER-0 which flips direction bit of Motor Shield Channel B controlling Prog Track
  DCC_SIGNAL(progRegs,0)
}

#else      // Configuration for MEGA

ISR(TIMER3_COMPB_vect){              // set interrupt service for OCR3B of TIMER-3 which flips direction bit of Motor Shield Channel B controlling Prog Track
  DCC_SIGNAL(progRegs,3)
}

#endif

Cette écriture simplifiée vient du fait que Gregg utilise une "Macro" qui évite d'écrite 3 fois à peu prês la même chose. J'avoue que je me suis gratté la tête en tombant là dessus, mais Thierry et Jean-Luc m'ont éclairé à temps.

Dans mon adaptation à un prochain va et vient, j'ai limité le fonctionnement à la seule voie de programmation et à un ATMega328 (Uno ou Nano ou Mini)

Dans le code qui suit, j'ai "dé-macro-isé" la routine pour la voie principale :


ISR(TIMER1_COMPB_vect){              // set interrupt service for OCR1B of TIMER-1 which flips direction bit of Motor Shield Channel A controlling Main Track
  //DCC_SIGNAL(mainRegs,1)
  if(mainRegs.currentBit==mainRegs.currentReg->activePacket->nBits){  /* IF no more bits in this DCC Packet */
    mainRegs.currentBit=0;                                            /*   reset current bit pointer and determine which Register and Packet to process next--- */   
    if(mainRegs.nRepeat>0 && mainRegs.currentReg==mainRegs.reg){      /*   IF current Register is first Register AND should be repeated */
      mainRegs.nRepeat--;                                             /*     decrement repeat count; result is this same Packet will be repeated */
    } else if(mainRegs.nextReg!=NULL){                                /*   ELSE IF another Register has been updated */
      mainRegs.currentReg=mainRegs.nextReg;                           /*     update currentReg to nextReg */
      mainRegs.nextReg=NULL;                                          /*     reset nextReg to NULL */
      mainRegs.tempPacket=mainRegs.currentReg->activePacket;          /*     flip active and update Packets */         
      mainRegs.currentReg->activePacket=mainRegs.currentReg->updatePacket;
      mainRegs.currentReg->updatePacket=mainRegs.tempPacket;         
    } else{                                                           /*   ELSE simply move to next Register */
      if(mainRegs.currentReg==mainRegs.maxLoadedReg)                  /*     BUT IF this is last Register loaded */
        mainRegs.currentReg=mainRegs.reg;                             /*       first reset currentReg to base Register, THEN */
      mainRegs.currentReg++;                                          /*     increment current Register (note this logic causes Register[0] to be skipped when simply cycling through all Registers) */
    }                                                                 /*   END-ELSE */
  }                                                                   /* END-IF: currentReg, activePacket, and currentBit should now be properly set to point to next DCC bit */
                                                                     
  if(mainRegs.currentReg->activePacket->buf[mainRegs.currentBit/8] & mainRegs.bitMask[mainRegs.currentBit%8]){     /* IF bit is a ONE */
    OCR1A=DCC_ONE_BIT_TOTAL_DURATION_TIMER1;                          /*   set OCRA for timer N to full cycle duration of DCC ONE bit */
    OCR1B=DCC_ONE_BIT_PULSE_DURATION_TIMER1;                          /*   set OCRB for timer N to half cycle duration of DCC ONE but */
  } else{                                                             /* ELSE it is a ZERO */
    OCR1A=DCC_ZERO_BIT_TOTAL_DURATION_TIMER1;                         /*   set OCRA for timer N to full cycle duration of DCC ZERO bit */
    OCR1B=DCC_ZERO_BIT_PULSE_DURATION_TIMER1;                         /*   set OCRB for timer N to half cycle duration of DCC ZERO bit */
  }                                                                   /* END-ELSE */ 
                                                                       
  mainRegs.currentBit++;                                              /* point to next bit in current Packet */ 
}

Rien qu'avec cette routine d'interruption, et après l'initialisation des variables qui doit être faite dans le Setup(), notre BaseStation est capable d'emblée de produire le signal d'alimentation DCC des voies.

Il reste à comprendre maintenant comment les Registres sont programmés (octets et bits), c'est à dire comment BaseStation peut envoyer des commandes DCC sur les rails.

Pour ce faire, il y a une méthode de RegisterList qui est très importante et qu'il ne faut absolument pas modifier :

// LOAD DCC PACKET INTO TEMPORARY REGISTER 0, OR PERMANENT REGISTERS 1 THROUGH DCC_PACKET_QUEUE_MAX (INCLUSIVE)
// CONVERTS 2, 3, 4, OR 5 BYTES INTO A DCC BIT STREAM WITH PREAMBLE, CHECKSUM, AND PROPER BYTE SEPARATORS
// BITSTREAM IS STORED IN UP TO A 10-BYTE ARRAY (USING AT MOST 76 OF 80 BITS)

void RegisterList::loadPacket(int nReg, byte *b, int nBytes, int nRepeat, int printFlag) volatile {
 
  nReg=nReg%((maxNumRegs+1));         // force nReg to be between 0 and maxNumRegs, inclusive

  while(nextReg!=NULL);               // pause while there is a Register already waiting to be updated -- nextReg will be reset to NULL by interrupt when prior Register updated fully processed
 
  if(regMap[nReg]==NULL)              // first time this Register Number has been called
   regMap[nReg]=maxLoadedReg+1;       // set Register Pointer for this Register Number to next available Register
 
  Register *r=regMap[nReg];           // set Register to be updated
  Packet *p=r->updatePacket;          // set Packet in the Register to be updated
  byte *buf=p->buf;                   // set byte buffer in the Packet to be updated
         
  b[nBytes]=b[0];                     // copy first byte into what will become the checksum byte 
  for(int i=1;i<nBytes;i++)           // XOR remaining bytes into checksum byte
    b[nBytes]^=b[i];
  nBytes++;                           // increment number of bytes in packet to include checksum byte
     
  buf[0]=0xFF;                        // first 8 bytes of 22-byte preamble
  buf[1]=0xFF;                        // second 8 bytes of 22-byte preamble
  buf[2]=0xFC + bitRead(b[0],7);      // last 6 bytes of 22-byte preamble + data start bit + b[0], bit 7
  buf[3]=b[0]<<1;                     // b[0], bits 6-0 + data start bit
  buf[4]=b[1];                        // b[1], all bits
  buf[5]=b[2]>>1;                     // b[2], bits 7-1
  buf[6]=b[2]<<7;                     // b[2], bit 0
 
  if(nBytes==3){
    p->nBits=49;
  } else{
    buf[6]+=b[3]>>2;                  // b[3], bits 7-2
    buf[7]=b[3]<<6;                   // b[3], bit 1-0
    if(nBytes==4){
      p->nBits=58;
    } else{
      buf[7]+=b[4]>>3;                // b[4], bits 7-3
      buf[8]=b[4]<<5;                 // b[4], bits 2-0
      if(nBytes==5){
        p->nBits=67;
      } else{
        buf[8]+=b[5]>>4;              // b[5], bits 7-4
        buf[9]=b[5]<<4;               // b[5], bits 3-0
        p->nBits=76;
      } // >5 bytes
    } // >4 bytes
  } // >3 bytes
 
  nextReg=r;
  this->nRepeat=nRepeat;
  maxLoadedReg=max(maxLoadedReg,nextReg);
 
  if(printFlag && SHOW_PACKETS)       // for debugging purposes
    printPacket(nReg,b,nBytes,nRepeat); 

} // RegisterList::loadPacket

C'est loadPacket qui va positionner les octets et les bits de chaque commande DCC dans un des registres.

LoadPacket contient aussi un mécanisme très important : une sorte de synchronisation avec la routine d'interruption ISR (pendant laquelle le programme principal est mis en attente) qui empêche la modification d'un registre tant que la fois précédente d'appel de loadPacket n'a pas complètement terminé sont travail et que la routine ISR n'a pas envoyé le paquet DCC sur les rails.

C'est la ligne suivant qui fait cela :

while(nextReg!=NULL);               // pause while there is a Register already waiting to be updated -- nextReg will be reset to NULL by interrupt when prior Register updated fully processed
Cette astuce permet de simplifier considérablement l'écriture du programme dans les niveaux plus hauts en faisant abstraction des contraintes précitées.

Par exemple, dans une autre fonction de RegisterList, on trouve :

loadPacket(0,resetPacket,2,3);          // NMRA recommends starting with 3 reset packets
loadPacket(0,bRead,3,5);                // NMRA recommends 5 verify packets
loadPacket(0,resetPacket,2,1);          // forces code to wait until all repeats of bRead are completed (and decoder begins to respond)
On est certain, grâce à ce mécanisme, que les paquets seront bien transmis intégralement sur les rails dans l'ordre indiqué. C'est génial, moi j'aime !

Avant de conclure sur cette partie, voici les autres fonctions présentes dans RegisterList. Leurs arguments sont contenus dans un tableau de caractères :

  void setThrottle(char *); // commande de vitesse (0..126) et directions (1=avant, 0=arriere)
  void setFunction(char *); // commande de fonction loco F0..F28
  void setAccessory(char *); // commande d'accessoire (0..2048)
  void writeTextPacket(char *); //envoi d'un paquet libre
  void readCV(char *); // lecture d'un CV sur voie de programation
  void readCV_Main(char *s); // lecture d'un CV sur voie principale (ajout perso de Dominique)
  void writeCVByte(char *); // écriture d'un octet entier de CV sur voie de programation
  void writeCVBit(char *); // écriture d'un bit de CV sur voie de programation
  void writeCVByteMain(char *); // écriture d'un octet entier de CV sur voie principale (sans réponse du décodeur)
  void writeCVBitMain(char *s);  // écriture d'un bit de CV sur voie principale (sans réponse du décodeur)

Si vous comparez avec le source original de BaseStation, vous trouverez que la fonction readCV_Main(char *s) n'existe pas. C'est un exemple de modification qui est facile à faire. Dans cet exemple, j'ai simplement adressé la commande aux registres de la voie principale et j'ai pris les mesures de courant sur la voie principale (en lieu et place de la voie de programmation). Il va de soi que cette fonction n'a d'intérêt que si une seule loco est présente sur la voie principale.

Dans la suite de cette série, nous regarderons la couche supérieure, c'est à dire l'interface entre RegisterList et la communication série ou ethernet.
Dans la série suivante nous regarderons les autres fonctions accessibles à la communication, qui sont les commandes des ports d'entrée et de sortie restant de l'Arduino, ce qui permet de commander des aiguilles directement sans passer par les commandes DCC d'accessoires et un peu de rétrosignalisation (détections de zones principalement).

A suivre …

Pages: 1 ... 89 90 [91] 92 93 ... 106