LOCODUINO
Parlons Arduino => Vos projets => Discussion démarrée par: Loco28 le décembre 07, 2017, 12:05:56 am
-
Bonsoir,
J'ai commencé depuis quelques temps la réalisation de décodeurs d'accessoires sur base ARDUINO.
J'utilise la bibliothèque DCC_Decoder. Le système marche très bien pour les aiguillages mais je me suis aperçu récemment que du côté des signaux il y avait des ratés.
Après analyse, tests et interrogations sur les forums, je suis arrivé à la conclusion que le traitement des données, que je faisais à la suite du void BasicAccDecoderPacket_Handler, me faisait louper des données.
j'ai donc inclus une autre bibliothèque, CircularBuffer, pour enregistrer les données immédiatement après le "Handler" puis les récupérer et les traiter, cette fois-ci dans la "loop".
Et là, impeccable, je ne loupais plus de données, au vu du Serial.print.
J'ai donc continué le traitement des données pour activer les sorties: tout est OK jusqu'au calcul de la valeur à envoyer (ici CDE4) mais rien ne se passe avec le ShiftOut (j'utilise des registres à décalage).
J'en viens à me demander si l'instruction ShiftOut ne se percute pas avec la fonction buffer.shift() de CircularBuffer!
Je précise que je travaille avec CDM-Rail et que je ne suis pas un grand spécialiste de la programmation (cela ressemble sûrement à "la programmation pour les nuls")
Ci-dessous une copie de mon code que j'ai allégé pour aller au plus simple, mais avec lequel j'ai bien un problème au moment de l'envoi du ShiftOut.
------------------------------------------------------------------------------------------------------------------------------------
#include <DCC_Decoder.h>
#define kDCC_INTERRUPT 0 // pin 2 for UNO
#include <CircularBuffer.h>
CircularBuffer <int, 100> storadresse;
CircularBuffer <byte, 100> storaction;
int add = 0;
int act = 0;
int old_add = 0;
int old_act = 0;
int action = 0;
const int CLOCK2 = 8;
const int LATCH2 = 9;
const int DATA2 = 10;
int OUT4[8];
int CDE4 = 0;
//Handler DCC
void BasicAccDecoderPacket_Handler(int address, boolean activate, byte data){
//Serial.print(address);
//Serial.print(" ");
//Serial.println(data);
storadresse.push (address);
storaction.push (data);
}
void setup() {
DCC.SetBasicAccessoryDecoderPacketHandler(BasicAccDecoderPacket_Handler, true);
DCC.SetupDecoder( 0x00, 0x00, kDCC_INTERRUPT );
Serial.begin (115200);
Serial.println("Debut test");
}
void loop() {
DCC.loop();
while(!storadresse.isEmpty()) {
add = storadresse.shift();
act = storaction.shift();
if ((add != old_add) || (act != old_act)){
old_add = add;
old_act = act;
// Conversion de l'adresse NMRA en adresse decodeur d'accessoire
add -= 1;
add *= 4;
add += 1;
add += (act & 0x06) >> 1;
action =(act & 0x01);
Serial.print(add);
Serial.print(" ");
Serial.println(action);
//Gestion des signaux
//Registre à décalage OUT4
if((add >=127) && (add <= 134)){
if(action = 1){
OUT4[add - 127] = bit(add - 127);
}
else{
OUT4[add - 127] = 0;
}
}//fin boucle OUT4
//Envoi OUTT4
CDE4 = OUT4[0]+OUT4[1]+OUT4[2]+OUT4[3]+OUT4[4]+OUT4[5]+OUT4[6]+OUT4[7];
Serial.println(CDE4);
digitalWrite(LATCH2, LOW);
shiftOut(DATA2, CLOCK2, LSBFIRST, CDE4);
digitalWrite(LATCH2, HIGH);
} //fin nouvelle donnée
} //fin while
} //fin loop
--------------------------------------------------------------------------------------------------------------------
Merci d'avance à ceux qui pourront m'aider
-
Désolé, je clos le post.
le problème est dans le SETUP: il manque la définition en OUTPUT pour CLOCK, LATCH et DATA
-
Souvent il suffit d'exposer son problème pour le voir sous un autre jour et détecter l'erreur :)
-
Bonjour
Aurais-tu STP l'amabilité de nous faire partager ton fichier ".ino" revisé
et le montage qui va avec.
Cordialement
Marcel
-
Effectivement il y avait une grossière erreur (suite à un effacement malheureux ou un copier/coller raté)) que je ne voyais pas, d'où l'intérêt de faire appel à d'autres paires d'yeux.
Tout est rentré dans l'ordre.
J'ai encore du boulot pour finaliser mon projet mais j'ai commencé à écrire un document pour rassembler tout ça (schémas, programme, photos de cartes, ...).
En attendant, ci-dessous le programme, sûrement perfectible, qui me permet de piloter 12 signaux, au travers de 3 registres à décalage
-------------------------------------------------------------------------------------------------------------
#include <DCC_Decoder.h>
#define kDCC_INTERRUPT 0 // pin 2 for UNO
#include <CircularBuffer.h>
CircularBuffer <int, 100> storadresse;
CircularBuffer <byte, 100> storaction;
int add = 0;
int act = 0;
int old_add = 0;
int old_act = 0;
int action = 0;
//pour les 595
const int CLOCK1 = 4;
const int LATCH1 = 5;
const int DATA1 = 6;
int OUT5[8];
int OUT6[8];
int OUT7[8];
int CDE5 = 0;
int CDE6 = 0;
int CDE7 = 0;
//Handler DCC
void BasicAccDecoderPacket_Handler(int address, boolean activate, byte data){
storadresse.push (address);
storaction.push (data);
}
void setup() {
DCC.SetBasicAccessoryDecoderPacketHandler(BasicAccDecoderPacket_Handler, true);
DCC.SetupDecoder( 0x00, 0x00, kDCC_INTERRUPT );
Serial.begin(115200);
Serial.println("ADRESSE ACTION");
pinMode(LATCH1, OUTPUT);
pinMode(CLOCK1, OUTPUT);
pinMode(DATA1, OUTPUT);
//mise à zéro OUT5, OUT6, et OUT7
digitalWrite(LATCH1, LOW);
shiftOut(DATA1, CLOCK1, MSBFIRST, 0);
shiftOut(DATA1, CLOCK1, MSBFIRST, 0);
shiftOut(DATA1, CLOCK1, MSBFIRST, 0);
digitalWrite(LATCH1, HIGH);
}
void loop() {
DCC.loop();
while(!storadresse.isEmpty()) {
add = storadresse.shift();
act = storaction.shift();
if ((add != old_add) || (act != old_act)){
old_add = add;
old_act = act;
// Conversion de l'adresse NMRA en adresse decodeur d'accessoire
add -= 1;
add *= 4;
add += 1;
add += (act & 0x06) >> 1;
act =(act & 0x01);
Serial.print(add);
Serial.print(" ");
Serial.println(act);
//Gestion des signaux
if((add >=151) && (add <= 174)) { //boucle adresse feux
//Registre à décalage OUT5
if((add >=151) && (add <= 158)) {
if(act == 1){
OUT5[add - 151] = bit(add - 151);
}
else{
OUT5[add - 151] = 0;
}
}//fin boucle OUT5
//Registre à décalage OUT6
if((add >=159) && (add <= 166)) {
if(act == 1){
OUT5[add - 159] = bit(add - 159);
}
else{
OUT5[add - 159] = 0;
}
}//fin boucle OUT6
//Registre à décalage OUT7
if((add >=167) && (add <= 174)) {
if(act == 1){
OUT5[add - 167] = bit(add - 167);
}
else{
OUT5[add - 167] = 0;
}
}//fin boucle OUT7
//envoi data vers OUT5, OUT6 et OUT7
CDE5 = OUT5[0]+OUT5[1]+OUT5[2]+OUT5[3]+OUT5[4]+OUT5[5]+OUT5[6]+OUT5[7];
CDE6 = OUT6[0]+OUT6[1]+OUT6[2]+OUT6[3]+OUT6[4]+OUT6[5]+OUT6[6]+OUT6[7];
CDE7 = OUT7[0]+OUT7[1]+OUT7[2]+OUT7[3]+OUT7[4]+OUT7[5]+OUT7[6]+OUT7[7];
digitalWrite(LATCH1, LOW);
shiftOut(DATA1, CLOCK1, LSBFIRST, CDE7);
shiftOut(DATA1, CLOCK1, LSBFIRST, CDE6);
shiftOut(DATA1, CLOCK1, LSBFIRST, CDE5);
digitalWrite(LATCH1, HIGH);
}//fin boucle adresse feux
}//fin boucle si nouveaux data
}//fin while
}//fin loop
----------------------------------------------------------------------------------------------------------------------
-
Bonjour
Merci pour ce partage :)
On attends la suite......
Cordialement
Marcel
-
J'aurais besoin que quelques spécialistes (plus que moi) regardent mon code.
Ce code fonctionne bien dans 95% des cas (sous CDM-Rail). par contre, dans le cas de commande d'itinéraires, quand, par exemple, 3 aiguillages doivent être commandés en même temps, et malgré le fait que CDM temporise les envois (0,5s), je loupe des commandes.
Il me semblait, qu'à partir du moment où je remplis un fichier FIFO dans le cadre de la routine d'interruption et que je le traite en dehors de cette routine (dans la LOOP) je ne pouvais rien rater.
Est-ce que j'ai bien compris? et est-ce que mon code est correct?
Une petite indication, en m'aidant des Serial.print (qui ne sont là que pour la mise au point), j'ai constaté qu'en supprimant les SHIFOUT je ne loupais plus de commandes DCC.
Ci-dessous mon code.
//Programme de gestion des aiguillages de la zone Bréauté
#include <DCC_Decoder.h>
#define kDCC_INTERRUPT 0 // pin 2 for UNO
#include <CircularBuffer.h>
CircularBuffer <int, 100> storadresse;
CircularBuffer <byte, 100> storaction;
int add = 0;
int act = 0;
int old_add = 0;
int old_act = 0;
int action = 0;
const int LATCH1 = 6;
const int CLOCK1 = 7;
const int DATA1 = 5;
int OUT1[8];
int OUT2[8];
int OUT3[8];
int CDE1 = 0;
int CDE2 = 0;
int CDE3 = 0;
//Handler DCC
void BasicAccDecoderPacket_Handler(int address, boolean activate, byte data){
storadresse.push (address);
storaction.push (data);
Serial.print(address);
Serial.print(" ");
Serial.println(data);
}
void setup() {
DCC.SetBasicAccessoryDecoderPacketHandler(BasicAccDecoderPacket_Handler, true);
DCC.SetupDecoder( 0x00, 0x00, kDCC_INTERRUPT );
Serial.begin (115200);
Serial.println(" DCC brut ADRESSE ACTION");
pinMode(LATCH1, OUTPUT);
pinMode(CLOCK1, OUTPUT);
pinMode(DATA1, OUTPUT);
}
void loop() {
DCC.loop();
while(!storadresse.isEmpty()) {
add = storadresse.shift();
act = storaction.shift();
if ((add != old_add) || (act != old_act)){
old_add = add;
old_act = act;
// Conversion de l'adresse NMRA en adresse decodeur d'accessoire
add -= 1;
add *= 4;
add += 1;
add += (act & 0x06) >> 1;
action = (act & 0x01);
Serial.print(" ");
Serial.print(add);
Serial.print(" ");
Serial.println(action);
//Gestion aiguillages
//registre à décalage OUT1
if((add >= 1) && (add <= 8)) {
if (action == 1) {
OUT1[add-1] = bit(add - 1);
}
else {
OUT1[add-1] = 0;
}
} //fin OUT1
//registre à décalage OUT2
if((add >= 9) && (add <= 16)) {
if (action == 1) {
OUT2[add-9] = bit(add - 9);
}
else {
OUT2[add-9] = 0;
}
} //fin OUT2
//registre à décalage OUT3
if((add >= 17) && (add <= 24)) {
if (action == 1) {
OUT3[add-17] = bit(add - 17);
}
else {
OUT3[add-17] = 0;
}
} //fin OUT3
//envoi des commandes
CDE1 = OUT1[0]+OUT1[1]+OUT1[2]+OUT1[3]+OUT1[4]+OUT1[5]+OUT1[6]+OUT1[7];
CDE2 = OUT2[0]+OUT2[1]+OUT2[2]+OUT2[3]+OUT2[4]+OUT2[5]+OUT2[6]+OUT2[7];
CDE3 = OUT3[0]+OUT3[1]+OUT3[2]+OUT3[3]+OUT3[4]+OUT3[5]+OUT3[6]+OUT3[7];
digitalWrite(LATCH1, LOW);
shiftOut(DATA1, CLOCK1, LSBFIRST, CDE3);
shiftOut(DATA1, CLOCK1, LSBFIRST, CDE2);
shiftOut(DATA1, CLOCK1, LSBFIRST, CDE1);
digitalWrite(LATCH1, HIGH);
}//fin boucle si nouveaux data
}//fin while
}//fin programme principal
-
Commence par retirer les
Serial.print(address);
Serial.print(" ");
Serial.println(data);
De debug dans ton handler, ça prend des plombes à s'executer. Ça peut aussi bloquer ton application car ça ne doit pas être utilisé dans un handler d'interruption.
Ensuite tu as un problème de concurrence sur tes buffers entre le programme principal et ton handler d'IT.
-
OK pour enlever les "serial.print". C'était mon intention mais j'avais le problème sans ça.
Par contre, tu peux m'expliquer la "concurrence" entre les buffers?
-
Peut-être faut-il arrêter les interruptions avant
add = storadresse.shift();
act = storaction.shift();
et les rétablir après ?
Cette discussion est intéressante : je ne connaissais pas la biblio CircularBufffer !
-
Oui,
Si une interruption survient alors que l'un des shift est en cours d'exécution, l'un des push va modifier un tampon circulaire qui est au milieu de son shift avec pour conséquence une altération des données ou des index de lecture et d'écriture. Il faut effectivement bloquer les IT pour les deux shift.
-
D'accord mais qu'est ce qui se passe si des données DCC arrivent pendant cet instant de blocage?
Et, question d'un néophyte dans le domaine des IT: comment bloque-t-on une IT et comment on la réactive?
-
Si les IT sont bloquées, l’IT la plus prioritaire sera prise en compte quand les IT seront débloquées. Donc à moins de bloquer pendant une durée supérieure au temps dl’arrivée de deux IT consécutives de même type, rien ne sera perdu. Mais, dans le cas du DCC, le temps est important et il ne faut pas que le temps de blocage conduise à mal interpréter les bits d’une trame DCC.
Pour bloquer les IT, il faut appeler la fonction noInterrupts(). Pour les débloquer, interrupts().
https://www.arduino.cc/reference/en/language/functions/interrupts/nointerrupts/
https://www.arduino.cc/reference/en/language/functions/interrupts/interrupts/
-
Donc, si j'ai bien compris, j'ajoute une ligne "nointerrupts() avant mes 2 lignes ".shift" et j'ajoute une ligne "interrupts()" juste après?
-
Exactement
-
Bon, c'est fait. j'ai ajouté un "noInterrupts()" juste avant le dépilage des fichiers (.shift) et j'ai mis un "interrupts()" après.
Malheureusement cela n'a rien changé.
J'ai évidemment supprimé le "Serial.print" dans le Handler DCC.
J'ai eu l'idée de supprimer les 3 lignes "shiftout" et là, cela marche, du moins en affichage car du coup je n'ai plus la commande de mes aiguilles.
Après j'ai remis 1 ligne "shiftout", OK puis 2, OK et à 3 NOK.
J'ai aussi essayé en mettant "noInterrupts()" et "interrupts()" de chaque côté des lignes "shiftout" mais sans succès.
Là, j'avoue que je suis un peu "sec" sur la suite.
-
Tu pourrais mettre ton sketch en fichier attaché ?
-
Ci-joint mon sketch
-
As tu essayé de mesurer le temps pris par les 3 shiftOut ? et par la totalité de ton programme principal ?
Ajoute avant le premier shiftOut
unsigned long date = micros();
Puis après les shiftOut
Serial.print("Duree = ");
Serial.println(micro() - date);
-
Le programme principal (LOOP) s'exécute en 500 microsecondes et les 3 SHIFTOUT en 420/424 microsecondes.
D'un autre côté, la commande des aiguillages est censée se faire avec 0,5s entre chaque soit 500 000 microsecondes. Il ne devrait donc pas y avoir de problème.
J'ai déjà signalé à JPP38 sur le forum CDM-rail ce problème et il m'assure que même lors des commandes d'itinéraires les commandes sont cadencées.
Ce qui est bizarre, c'est qu'à l'initialisation du RUN, tous les aiguillages sont mis en place et là, je constate bien ce cadencement à 0,5s et il n'y a aucun ratés.
-
Quel registre à décalage utilises-tu ?
-
C'est des 74HC595
-
Ok,
Je pense que déjà tu peux améliorer les choses en utilisant le SPI pour envoyer tes données. Regarde la dernière section de http://www.instructables.com/id/74HC595-Shift-Register-With-Arduino-Uno/
Tu passera de 420µs à, à vue de nez, 4µs
Il y a aussi du temps à gagner dans la manière dont tu assembles les bits que tu envoies.
-
Effectivement cela me semble plus qu'intéressant et relativement simple. Il faut effectivement que je gagne du temps sur l'envoi vers les registres. j'ai refait des essais cet am en enlevant 1 registre et cela marchait presque à tous les coups.
Je sens que je vais ressortir ma platine d'essai.
Par contre, ce que je ne vois pas, c'est comment on fait si on veut envoyer des valeurs vers 2 ou 3 HC595.
J'ai essayé de m'inscrire sur ce site pour poser une question mais je n'y arrive pas ("date de naissance erronée" ??)
-
Par contre, ce que je ne vois pas, c'est comment on fait si on veut envoyer des valeurs vers 2 ou 3 HC595.
Il faut les chainer, la sortie série du premier sur l'entrée série du second, etc
J'ai essayé de m'inscrire sur ce site pour poser une question mais je n'y arrive pas ("date de naissance erronée" ??)
Je n'y arrive pas non plus.
-
Je sais les chainer sur l'aspect hardware mais je ne vois pas, dans son programme avec le SPI, comment on envoie plusieurs valeurs à la suite surtout qu'il utilise un sous-programme qui ne prends qu'un seul argument. A moins qu'il suffise de passer plusieurs arguments comme value1, value2, value3 et de les envoyer avant la ligne de "fin SPI"?
-
SPI.transfer envoie un octet comme shiftOut. Pour envoyer plusieurs octets il faut faire plusieurs SPI.transfer
-
Donc ca donnerait quelque chose comme:
void shiftByte( CDE1, CDE2, CDE3){
PORTC &= B11111101;//Data clock low
SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0));
SPI.transfer(CDE3);
SPI.transfer(CDE2);
SPI.transfer(CDE1);
SPI.endTransaction();
PORTC |= B00000010;//Data clock high, latch data
}
-
Plus simplement, remplace
digitalWrite(LATCH1, LOW);
shiftOut(DATA1, CLOCK1, LSBFIRST, CDE3);
shiftOut(DATA1, CLOCK1, LSBFIRST, CDE2);
shiftOut(DATA1, CLOCK1, LSBFIRST, CDE1);
digitalWrite(LATCH1, HIGH);
Par
digitalWrite(LATCH1, LOW);
SPI.beginTransaction(SPISettings(16000000, LSBFIRST, SPI_MODE0));
SPI.tranfer(CDE3);
SPI.tranfer(CDE2);
SPI.tranfer(CDE1);
SPI.endTransaction();
digitalWrite(LATCH1, HIGH);
-
J'ai effectué une modification "hard" (changement des pins CLOCK, LATCH, DATA) pour pouvoir tester avec SPI.
Pour le LATCH, j'ai pris la pin 12 (à priori on n'est pas obligé de prendre A1). Comme ça, mes 3 pins sont groupées (11, 12 et 13).
Je me suis assuré que la carte fonctionne toujours en "SHIFTOUT", puis j'ai modifié le programme pour utiliser la fonction SPI.
Problème: je reste "coincé" au premier passage: j'envoi une commande, le la vois bien avec le Serial.print, puis plus rien.
Je ne peux envoyer aucune autre commande.
Je mets mon sketch en pièce jointe.
-
Il manque le
SPI.begin();
dans setup
Et tu devrais commencer par tester que tu envoies bien en SPI aux 595 dans un sketch séparé sans DCC. Test unitaire !
-
Un truc qui serait bien : indenter correctement le code. À chaque fois que je regarde un programme il faut que je le ré-indente, c'est pas cool
-
OK, je vais tester en rajoutant le SPI.begin()
Excuse ma question, mais "re-indente" cela veut dire quoi?
-
https://fr.wikipedia.org/wiki/Style_d%27indentation
Je préfère le K&R ou le Allmann.
-
OK, je vais essayer de faire au mieux.
Je sais que mon câblage est bon car la carte a fonctionné avec les "SHIFTOUT".
Je vais faire un skectch pour tester un registre en allumant puis éteignant chaque sortie.
-
c'est bon ça fonctionne mais il a fallut que je mette le LATCH sur la pin A1 (au lieu de 12), alors que sur le site il est dit"generally this one can be any pin".
Maintenant il me faut tout remettre sous le train, recâbler les relais de commande des aiguillages et repasser à une phase de test sous CDM pour voir si mon problème de "ratés" est résolu.
-
C’est pas tout à fait anormal. La pin 12 c’est MISO. Ton chip select entrait en conflit avec la réception des données sur le SPI (qui ne sert certes à rien ici mais qui ne peut pas être utilisé comme chip select quoiqu’il en soit)
Sur le site, la pin 12 est celle du 595, pas celle de l’Arduino.
-
Tout fonctionne parfaitement. Plus de ratés.
Merci pour ton aide.....et ta patience.
Je mets en pièce jointe mon sketch (je l'ai "arrangé" au mieux).
-
Bonjour,
J'ai regardé l'implémentation de CircularBuffer et je ne suis pas très fan de ce qui y est fait, j'ai notamment tiqué sur :
template<typename T, __CB_ST__ S>
T CircularBuffer<T,S>::pop() {
void(* crash) (void) = 0;
if (count <= 0) crash();
T result = *tail--;
if (tail < buffer) {
tail = buffer + S - 1;
}
count--;
return result;
}
Traduction :
- Déclaration d'un pointeur de fonction appelé crash qui pointe sur l'adresse 0 de la mémoire
- Si le buffer est vide, appel de crash
Donc exécuté sur une machine de bureau, ce genre de code va planter l'application, ce qui est le but recherché, soit.
Exécuté sur un Arduino/AVR, on exécute le code situé à l'adresse 0. Qu'a-t-on à l'adresse 0 ? le vecteur reset. Sur un Arduino/ARM on a la valeur du pointeur de pile à l'adresse 0 :o
BREF ÇA FAIT N'IMPORTE QUOI. Comment peux-t-on écrire des trucs pareils ::)
J'ai donc fait mon tampon circulaire, qui en plus occupe moins de mémoire, et qui propose aussi des fonctions de lecture et d'écriture qui sont blindées contre les interruptions.
C'est pour l'instant ici : https://github.com/Locoduino/RingBuffer/releases
et d'ici quelques jours dans le gestionnaire de bibliothèques de l'IDE.
-
Ça c’est du beau code !
Comme j’ai écrit mon propre tampon circulaire dans les réceptions de messages Can, je vais intégrer cette bibliothèque à la place.
Merci Jean-Luc ;D
-
Coucou,
super échange entre Jean-Luc et Dominique sur les tampons circulaires.
Comme toutes mes cartes communiquent par CAN avec ton ancien tampon (à Dominique), pourrais-tu me faire parvenir un exemple avec le nouveau tampon, j'ai beau essayer de comprendre, mon cerveau ne suit plus.
Bon WE
Antoine
-
Bonsoir Tony
Il y a des exemples dans la bibliothèque qui est disponible via le gestionnaire de l'IDE. Il y a également quelques bouts de code à la fin de la doc en bas de page : https://github.com/Locoduino/RingBuffer
-
Salut Loco28,
As-tu changé CircularBuffer pour la solution RingBuffer?
Si oui, peux-tu nous donner ton dernier sketch (je n'ai pas réussi à trouver circularbuffer et vu les remarques de Jean-Luc je ne le ferai pas....)?
Merci d'avance!
A+
Yves