Auteur Sujet: les centrales dcc à esp32 de trimarco232  (Lu 864 fois)

trimarco232

  • Full Member
  • ***
  • Messages: 171
    • Voir le profil
les centrales dcc à esp32 de trimarco232
« le: mai 20, 2022, 12:29:54 pm »
Bonjour,
ça fait un bail que j'ai ça sous mon coude gauche volumineux, c'est le moment d'en parler un peu
les choses à l'envers ? j'ai commencé par dessiner et réaliser le pcb, puis j'ai attaqué le soft de la génération et de l'envoi des bits dcc
actuellement j'en suis à l'étape suivante : le scheduler (ou planificateur de l'envoi des packets dcc), défi au niveau de l'analyse et de la programmation
ce que j'ai fait c'est la chose suivante, assez simple
les packets dcc créés sont classés prioritaires ou pas, selon leur nature ; par exemple les packets constituant un message de freinage, ou une commande d'accessoire dont le timing est critique, seront prioritaires, les autres non
les packets prioritaires sont placées dans des queues qui seront répétées, et exécutées avant la queue non prioritaire ; ça donne un truc de ce genre :
p3 /// queue fifo où sont mis, donc d'où sont envoyés, les nouveaux packets prioritaires
p2 /// queue fifo de la 1ère répétition des packets prioritaires
p1 /// queue fifo de la 2nde et dernière répétition des packets prioritaires, suite à quoi les packets ex prioritaires sont définitivement placée dans la queue p0
p0 /// queue qui envoie en boucle tous les packets existants, sans notion de priorité
la queue p0 ne fonctionne que si les 3 autres ont été vidées de leur packet(s)
un même packet ne peut être envoyé 2x directement à sa suite : par exemple un packet prioritaire x, qui passe de p3 à p2, attendra qu'un autre packet y soit envoyé, avant que ce packet x soit envoyé de p2 à son tour
ça, ça marche

reste quelques questions au niveau de l'analyse
- chaque packet de vitesse doit rester dans la boucle p0, dans laquelle ils sera (normalement) remplacé par un nouveau packet de vitesse qui a la même adresse
- les packets d'accessoires n'ont pas lieu de rester ni même de figurer dans la boucle p0 ?
- les packets de fonction doivent-ils rester dans la boucle p0 ? lesquels (je pense l'éclairage) ? pourquoi ?
« Modifié: juillet 12, 2022, 02:07:23 pm par trimarco232 »

ElGringo

  • Newbie
  • *
  • Messages: 21
    • Voir le profil
tentative de petite centrale DCC sur ESP32
« Réponse #1 le: mai 21, 2022, 08:57:45 am »
Bonjour, voilà grâce à des aides précieuses je me suis tenté une petite expérience pour créer une centrale DCC sur esp32.
Ma démarche est elle bonne? mon programme s'inspire des idées de Trimarco.
Si je suis sur le bon chemin est ce que je dois utiliser memcpy pour garder le bit paquet en mémoire ou bien est ce que je peux utiliser une structure pour l'envoyer comme message struct t_message?
en gros dans la fonction d'interruption, le signal pwm se répète tant que le bit ne change pas. Ainsi pour les 20 premiers bit 1, il n'y a pas d interruption. Dès que le bit start est atteint on envoie une interruption et ainsi de suite. ma synchro est elle bonne?

la fonction SpeedAndDir( 3, CRAN_1,128, 1);
permet dans le loop de sélectionner la loco 3, une vitesse sur 28 pas et une marche avant (j ai normé la vitesse a 128 pas discrets avec une interpolation linéaire).

Merci pour votre aide (et je ne suis qu'un humble débutant).


#include "driver/ledc.h"

uint64_t paquet;//=0b... 48 bits
uint64_t NewPacket;//0b.. 48bits;
uint64_t masque;
uint64_t dccBit;
bool alter =1;
bool previous=0;


#define CRAN_0 0
#define CRAN_1 1
#define CRAN_2 2
#define CRAN_IDLE 5

byte dccSpeed;
byte addr;
byte adresse;
byte vitesse;
byte cksum;
uint64_t paquet_start=0b111111111111111111110;  // 20 bits + bit start
byte startBit=0;
byte stopBit=1;

const int dcc_pin = 19;  // Sortie du signal DCC

// setting PWM properties
#define dcc_sig_1_freq 8621 //  = 115,99 microS
#define dcc_sig_0_freq 5000 // 200µs 5000 Hz donne 200 microS
const int dcc_pin_channel = 0; // on ouvre un canal qu on reliera à une sortie PWMN
const int dcc_sig_resolution = 1; // correspond à une résolution de 1 bit => 2^1bit =2 pas discrets0 et 100%.
const int dcc_sig_duty = 1; // correspond à une résolution de 1 bit => 2^1bit =2 pats discrets 0 et 100%.
uint32_t dcc_sig_freq = dcc_sig_1_freq; // on part de la fréquence du bit 1 toutes les trames commencent par une série de 20 bits a 1



void dcc_sig_isr() {

dccBit=!!(paquet&masque);  // les bits différents de 0 valent 1
masque>>=1; // le masque permet de parcourir les bits
if (dccBit==previous) {  // si le bit parcouru est == 0 alors il faut envoyer une interruption, alter qui valait 1 au début se transforme en 0
alter=!alter;
//Serial.print(alter);
alter?dcc_sig_freq =dcc_sig_1_freq:dcc_sig_freq =dcc_sig_0_freq;
}


ledc_set_freq(LEDC_HIGH_SPEED_MODE, LEDC_TIMER_0, dcc_sig_freq); // new -or not - period value
 //speed_mode: Select the LEDC channel group with specified speed mode. Note that not all targets support high speed mode.
//timer_num: LEDC timer index (0-3), select from ledc_timer_t
//freq_hz: Set the LEDC frequency
// est ce que je dois laisser cette ligne de code? quelle est son utilité ici? je comprends qu elle permet de selectionner le timer 0 mais pourquoi la mettre ici et pas ailleurs?
}

void setup() {
  Serial.begin(115200);
uint64_t paquet =0; // on initialise la trame
uint64_t NewPacket=0; // on utilise une trmne reformatée plus simple pour limiter les interruptions et aider l'esp32

 // configuration du canal pwm 0 avec une fréquence et la résolution
  ledcSetup(dcc_pin_channel, dcc_sig_freq, dcc_sig_resolution); // on set up le canal avec une résolution de 1 bit cad 50%
  ledcAttachPin(dcc_pin, dcc_pin_channel); /// et ensuite on l attache au PIN19
  ledcWrite(dcc_pin_channel, dcc_sig_duty);
  // programme l'interruption à la fin de la période
// je n'ai pas trouvé de manière élégante pour le faire avec le timer, alors je me suis rabattu pragmatiquement sur une interruption provoquée ... par le basculement de la pin
  attachInterrupt(dcc_pin, dcc_sig_isr, RISING); // l'interruption "dcc_sig_isr" provoquée à la fin de chaque période (voire au début de la suivante) =>moi non plus
 
}




/// Select Cran = 14,28 ou 128
/// Select Speed = 0......128
/// Select Direction 1 Avant, 2 Arriere, 0 Arret

struct __attribute__((packed)) t_message {
  byte adresse;
  byte vitesse;
  byte cksum;
}; t_message unMessage;




// fonction de calcul 3 octets
void SpeedAndDir( byte addr=0b11111111, byte SelectCran=0b00000000, byte SelectSpeed=0b00000000, bool SelectDirection=0) // vecteur de direction et de vitesse
{
  byte type;
  byte ext;
  byte data;
  byte cksum=0;
  uint64_t paquet=0;


      switch(SelectCran) {

          case 0:   ////DCC_PACKET_TYPE_STEP_14
              dccSpeed=map(SelectSpeed,0,128,0,14);
              if (dccSpeed) dccSpeed++; // pas de cran 1
              data=(SelectDirection?0x60:0x40)|dccSpeed;
              cksum=addr^data;
              break;

             
          case 1: ///DCC_PACKET_TYPE_STEP_28

              dccSpeed=map(SelectSpeed,0,128,0,28);
              if (dccSpeed) dccSpeed+=3; // pas de cran 1,2,3
              ext=(((dccSpeed&0x01)<<5)|dccSpeed)>>1;   ///
              data=(SelectDirection?0x60:0x40)|ext;
              cksum=addr^data;
              break;
             
          case 2:  //DCC_PACKET_TYPE_STEP_128;
               dccSpeed=map(SelectSpeed,0,128,0,126);
              if (dccSpeed) dccSpeed++; // pas de cran 1
              data=(SelectDirection?0x60:0x40)|dccSpeed;
              cksum=addr^data;
              break;

           case 5:  //IDLE;
               data=0b00000000;
              cksum=addr^data;
              break;             
      }

                       //unMessage.adresse=addr;
                      // unMessage.vitesse=data;
                      // unMessage.cksum = unMessage.adresse ^ unMessage.vitesse;

                            //memcpy(payload, &unMessage, sizeof unMessage);
                            //DccByteCount=sizeof unMessage;

paquet=(paquet_start << 27) |(addr<<19)|(startBit<<18)|(data<<10)|(startBit<<9)|(cksum<<1)|(stopBit<<0);


}





void loop() {
SpeedAndDir( 3, CRAN_1,128, 1);
delay(50000);
}



Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 2583
  • 100% Arduino et N
    • Voir le profil
Re : centrales dcc esp32 trimarco232
« Réponse #2 le: mai 21, 2022, 01:36:32 pm »
J’ai fusionné ces 2 sujets pour éviter le désordre dans le forum  :-[

Au passage vous pouvez utiliser le circuit imprimé et le schéma de LaBox pour ce projet :
https://forum.locoduino.org/index.php?topic=922.msg14938#msg14938

N’oubliez pas également le projet de BobyAndCo:
https://forum.locoduino.org/index.php?topic=1352.msg14748#msg14748

On attend des résultats avec impatience, mais prennez votre temps pour bien tester. !
« Modifié: mai 21, 2022, 01:44:21 pm par Dominique »
Cordialement,
Dominique

trimarco232

  • Full Member
  • ***
  • Messages: 171
    • Voir le profil
Re : centrales dcc esp32 trimarco232
« Réponse #3 le: mai 22, 2022, 12:04:13 am »
Bonjour,
(merci Dominique pour la fusion ! )
"Ainsi pour les 20 premiers bit 1, il n'y a pas d interruption"
c'était mon idée de départ ; mais pour l'instant, j'ai laissé l'interruption pour chaque bit du préamble, car il faudra bien un jour introduire le cutout railcom ...

tu es un peu + avancé que moi, pour l'instant je fais fonctionner le truc avec des packets uint64_t formés ... manuellement
le but, c'est de tester l'envoi correct des bits (à l'analyseur logique), ainsi que le fonctionnement du scheduler

la prochaine étape c'est de construire les packets à partir des adresses et des données

pour tester ton code, si tu n'as pas d'analyseur logique, utilises le SerialPrint : 1 ou 0 selon le bit envoyé, tu verras bien si tes packets partent correctement ; mais pour cela, multiplie la durée des périodes pwm par 10, (si non ça plante l'esp32 ...) faudra pas oublier de passer à la bonne vitesse pour un test réel

demain je mets mon code à l'état actuel, ça te donnera peut-être des idées

trimarco232

  • Full Member
  • ***
  • Messages: 171
    • Voir le profil
Re : centrales dcc esp32 trimarco232
« Réponse #4 le: mai 22, 2022, 11:47:47 am »
je te mets mon code actuel en l'état
il est fait pour mon pcb que j'ai depuis un certain temps, donc il y a du spi dont tu peux ne pas tenir compte
c'est le bazar dans l'organisation, car c'est en phase de recherches, mais ça fonctionne
je suis sous platformio, il faut peut-être adapter pour l'ide arduino
#include <Arduino.h>
// pour générer les bits dcc par pwm, // on utilise un module qui s'appelle ledc ...
// ... selon la tradition esp32 les modules pwm servent d'abord aux leds ...
#include "driver/ledc.h" // les fonctions spécifiques du périphérique ledc sont décrites ici
#include "driver/gpio.h" //
////#include "driver/spi_master.h" //
#include "SPI.h" //

  #define VSPI_MISO_S88N  13 // inS88n ; à redéfinir pour s88 : 35
  #define VSPI_MOSI   18
  #define VSPI_SCLK   16
   
  // le hc595 à bord
  #define HSPI_MOSI   26 // le hc595 à bord : DATAx
  #define HSPI_SCLK   14
  #define HSPI_SS     27 // pour le latch automatique

// 595 0     1      2     3      4       5      6   7      8 //
 #define L595 1
 #define  L5947 2
 #define  PS88 4
 #define  RST88 8
 #define  RST88N 16
 #define  PS88N 32
 #define  o6 64
 #define  L7912 128
 #define  SENSE_SEL 129 // pas de LATCHx

 volatile uint8_t latch_x_ram = 0 ; /// voir pour l'initialisation


SPIClass * vspi = NULL;
SPIClass * hspi = NULL;


 #define  DCC1 22
 #define  PWM1 25
 #define  DCC2 23
 #define  PWM2 19




volatile int32_t old_millis_595 = 0;


#define DCC_PIN 22  // choix de la broche dcc out : n'importe laquelle peut être utilisée /// en se servant de la matrice du gpio

// on définit les périodes pwm des bits dcc 1 et 0
const uint16_t  DCC_BIT_1_FREQ = 8621 ; // le sdk demande la fréquence : celle-ci correspond à ~116µs ... quelque chose arrondira à la valeur exacte
const uint16_t  DCC_BIT_0_FREQ = 5000 ; // 200µs (ici j'utilise des macros pour définir ces constantes)
const int dcc_pin_channel = 0; // il y a foison de canaux pwm, j'ai choisi le 1er
const int dcc_sig_resolution = 1; // nombre de bits définissant la résolution du pwm ; ici un seul bit (2 valeurs différentes) suffit
/// la résolution, c'est la granulométrie du rapport cyclique : la + grosse (ne) permet (que) de faire du 50%
const int dcc_sig_duty = 1; // pour le rapport cyclique de 1/2 il faut la valeur 1 (si non c'est 0 ... )
// initialisation de la variable au bit dcc 0 ; why not ; noter que la fonction utilisatrice veut le type uint32_t pour la fréquence
uint32_t dcc_sig_freq = DCC_BIT_1_FREQ;

/* les queues du scheduler */
/* cheduler */
volatile uint8_t schedule_p3[16], schedule_p3n = 0 ; // tableau queue, pointeur, nombre
volatile uint8_t schedule_p2[16], schedule_p2n= 0 ; // tous les pointeurs commencent à 1
volatile uint8_t schedule_p1[16], schedule_p1n = 0 ; // pointeur à 0 = pas de packet dans la queue
volatile uint8_t schedule_p0[128], schedule_p0p = 1 , schedule_p0n = 0  ; // 128 à dicuter


volatile uint64_t packet_bits[128] ; // à revoir la profondeur
volatile uint8_t packet_length[128] , packet_bit_pointer, packet_to_send = 0 ;
const uint8_t  PREAMBLE_LENGTH = 10, PACKET_2_LENGTH = 28 ;
const uint8_t  PACKET_3_LENGTH = 37, PACKET_4_LENGTH = 46 ;

void example_init(void)   { /* quelques packets manuels pour tester le générateur de bits dcc */
/// iddle          1111111111+11111111+00000000+EEEEEEEE1 // les + sont des 0
//                 +11111111+00000000+EEEEEEEE11111111111 // avec preamble à la fin
packet_bits[1] = (uint64_t)0b01111111100000000001111111111111111111; /// 10+9+9+9+1=38
packet_bits[2] = (uint64_t)0b01111111100000000001111111011111111111; /// e bidon ///
packet_length[1] = PACKET_2_LENGTH + PREAMBLE_LENGTH ;
schedule_p0n++ ; schedule_p0[schedule_p0n] = 1 ;  // met décodeur 0 dans la queue p0
packet_length[2] = PACKET_2_LENGTH + PREAMBLE_LENGTH ;
schedule_p0n++ ; schedule_p0[schedule_p0n] = 2 ;
/// v = 128        1111111111+0AAAAAAA+00111111+DSSSSSSS+EEEEEEEE1 /// 38+9=47
//  :              +0AAAAAAA+00111111+DSSSSSSS+EEEEEEEE11111111111 /// avec preamble à la fin
packet_bits[3] = 0b00000111100011111101000011101011011111111111111; /// a=15+ s=7
packet_length[3] = PACKET_3_LENGTH + PREAMBLE_LENGTH;
schedule_p3n++ ; schedule_p3[schedule_p3n] = 3 ; // met décodeur 2 dans la queue p3
/// V127 long:     1111111111+11AAAAAA+AAAAAAAA+00111111+DSSSSSSS+EEEEEEEE1 /// 47+9=56
//                 +11AAAAAA+AAAAAAAA+00111111+DSSSSSSS+EEEEEEEE11111111111 // avec preamble à la fin
packet_bits[4] = 0b01100000100000000100011111101000100000111011111111111111; /// a=16+ s=8
packet_length[4] = PACKET_4_LENGTH + PREAMBLE_LENGTH;
schedule_p0n++ ; schedule_p0[schedule_p0n] = 4 ;
/// /ACC1-2047:    1111111111+10AAAAAA+0AAA0AA1+000xxxxx+EEEEEEEE1 /// 47 // AAA = msb inversés ?
//                 +10AAAAAA+0AAA0AA1+000xxxxx+EEEEEEEE11111111111  // avec preamble à la fin
packet_bits[5] = 0b01000000000111001100001010101110011011111111111;
packet_length[5] = PACKET_3_LENGTH + PREAMBLE_LENGTH;
schedule_p0n++ ; schedule_p0[schedule_p0n] = 5 ;
//schedule_p3n++ ; schedule_p3[schedule_p3n] = 5 ;  // met décodeur 5 dans la queue p3
/// F20-F19-F18-F17-F16-F15-F14-F13 puis F28-F27-F26-F25-F24-F23-F22-F21
// FCT 13-20:      1111111111+0AAAAAAA+11011110+09876543+EEEEEEEE1 /// 47
//                 +0AAAAAAA+11011110+09876543+EEEEEEEE11111111111// avec preamble à la fin
packet_bits[6] = 0b00101010101101111000001000001001101111111111111; /// a=16+ s=8
packet_length[6] = PACKET_3_LENGTH + PREAMBLE_LENGTH;
/// schedule_p0n++ ; schedule_p0[schedule_p0n] = 6 ;
}







/* déclarations des voids */
void dcc_sig_isr() ; ///  il faut les déclarer
void setup_ledc(void) ;

void setup_spi(void) ;
void latch_x_low_high(uint8_t latche, uint8_t low_high) ;
void send_byte_595f(uint8_t byte_595) ;

void scheduler(void) ;


static uint8_t parti = 0; //// pour test entrée nouveau packet


void setup() {
Serial.begin(460800); // c'est la vitesse du téléversement

setup_ledc() ;
setup_spi() ;
example_init();

/// pour commencer
packet_to_send = 0 ;


Serial.print("pbp:") ; Serial.println(packet_bit_pointer);

pinMode(LED_BUILTIN, OUTPUT);
digitalWrite (LED_BUILTIN, HIGH); // allumé
}





void loop() {
  /* envoi des packets, appel scheduler */
  if (packet_bit_pointer > PACKET_4_LENGTH + PREAMBLE_LENGTH )   {  ////  grosse erreur pointeur
    packet_bit_pointer = 0;
    Serial.print("err_pbp > 5 : ");
    Serial.println(packet_bit_pointer);
  }
  /* very poor men's scheduler pour tester */
  else if (packet_bit_pointer == 0 )   {  // un packet est parti
  scheduler();
      packet_bit_pointer = packet_length[packet_to_send]  ;  // initialise le pointeur
      Serial.print(""); Serial.print(packet_to_send); Serial.println(" ");

  }

  /* timer millis pour test spi */
  int32_t millis_595 = millis();
  if ((millis_595 -  old_millis_595) > 10 )  {
old_millis_595 = millis_595 ;
//// Serial.println("o");
/*
   if (latch_x_ram & L5947)  {
    latch_x_low_high(L5947, LOW);      // test QA-QH
    latch_x_low_high(SENSE_SEL, LOW);  // test QH'
    digitalWrite (LED_BUILTIN, LOW); // pas allumé
   }
   else  {
    latch_x_low_high(L5947, HIGH);      // test QA-QH
    latch_x_low_high(SENSE_SEL, HIGH);  // test QH'
    digitalWrite (LED_BUILTIN, HIGH); // allumé
   }
   */
send_byte_595f(0);
 }


}


void dcc_sig_isr() {
  /* moteur pour la génération des bits dcc et l'égrainage des uint64_t */
  // dans l'isr, ne pas envoyer de serial pour déboger les bits, ça plante l'esp32 !
  static uint32_t old_dcc_sig_freq ;
 ///// Serial.println(packet_bit_pointer);
if (packet_bit_pointer == 0)   { // nouveau packet pas prêt, alors ...
 dcc_sig_freq = DCC_BIT_1_FREQ ; // ... on continue le preamble en attendant le packet
 ////Serial.print("x"); // signale "l'erreur" ///
    }
else {
 packet_bit_pointer-- ; // décrémente le pointeur de bit du packet
 uint64_t bit_pointer_mask = (uint64_t)1 << packet_bit_pointer ; // il faut caster le << sur 64 bits
 if ( bit_pointer_mask & packet_bits[packet_to_send])  { // si le bit pointé = 1 ...
    dcc_sig_freq = DCC_BIT_1_FREQ ; // ... envoie le bit dcc 1
 }
 else {
  dcc_sig_freq = DCC_BIT_0_FREQ ; // ... si non, envoie le bit dcc 0
 }
 }
 // la fonction qui permet d'écrire la nouvelle fréquence demande :
 // - le mode : ici high speed, (au pif) qui va bien
 // - le timer utilisé : j'ai supposé bêtement que le canal pwm 0 correspond au timer pwm 0 /// je crois qu'il y a 2 canaux par timer
 // - la fréquence, of course
 // ledc_set_freq(ledc_mode_tspeed_mode, ledc_timer_ttimer_num, uint32_t freq_hz)
  if (dcc_sig_freq != old_dcc_sig_freq) { // si la période pwm doit être changée
  ledc_set_freq(LEDC_HIGH_SPEED_MODE, LEDC_TIMER_0, dcc_sig_freq); // new period value
  dcc_sig_freq = old_dcc_sig_freq ; // actualisation du old
  /// Serial.println("c");
  }

}


void setup_ledc(void) {
/* configuration d'un timer pour l'envoi de bits dcc méthode pwm */
// configuration du canal pwm 0 avec une fréquence et la résolution
ledcSetup(dcc_pin_channel, dcc_sig_freq, dcc_sig_resolution); // ledc les faire ...
// relie le canal pwm à la pin choisie pour la sortie pwm /// ... et dcc par conséquent
ledcAttachPin(DCC_PIN, dcc_pin_channel); /// ! pour détacher (si besoin), il faut impérativement passer par la matrice gpio
// fixe le rapport cyclique du canal à 1/2
ledcWrite(dcc_pin_channel, dcc_sig_duty);
// programme l'interruption à la fin de la période
// je n'ai pas trouvé de manière élégante pour le faire avec le timer, alors je me suis rabattu pragmatiquement sur une interruption provoquée ... par le basculement de la pin
attachInterrupt(DCC_PIN, dcc_sig_isr, FALLING); // l'interruption "dcc_sig_isr" provoquée à la fin de chaque période (voire au début de la suivante)
// le signal DCC est inversé pour mes besoins ; si pas besoin, mettre RISING à la place de FALLING ci-avant et ...
// ... enlever la ligne qui suit :
gpio_matrix_out(DCC_PIN, LEDC_HS_SIG_OUT0_IDX, true, false); // inverse le signal LEDC 0

/// sortie PWM active pour débogage
pinMode(PWM1, OUTPUT); //HSPI SS
digitalWrite(PWM1, LOW); // HC04  : changer dznd la matrice pour le pwm

 }

void setup_spi(void) {
  /* initialisation du hspi pour le hc595 à bord, et du vspi pour les S88 et modules à décalage externes */
  hspi = new SPIClass(HSPI); // initialisation of onboard 74hct595
  hspi->begin(HSPI_SCLK, -1, HSPI_MOSI, -1);
  hspi->setHwCs(true); // end of /CS triggers automatically the latch of 74hct595
  pinMode(HSPI_SS, OUTPUT); //HSPI SS
  digitalWrite(HSPI_SS, HIGH); //pull ss high to signify end of data transfer
  vspi = new SPIClass(VSPI); // initialisation of outboard SPIs
  vspi->begin(VSPI_SCLK, VSPI_MISO_S88N, VSPI_MOSI, -1);
  // void gpio_matrix_out(uint32_t gpio, uint32_t signal_idx, bool out_inv, bool oen_inv)
  gpio_matrix_out(VSPI_SCLK, VSPICLK_OUT_IDX, true, false); // true = invert SCLK on VSPI
  gpio_matrix_out(VSPI_MOSI, VSPID_OUT_IDX, true, false); // true = invert MOSI on VSPI
 }

void latch_x_low_high(uint8_t latche, uint8_t low_high)  {
  /* positionne un des signaux du hc595x à board */
  uint8_t sense_sel ;
  ///// hspi->setHwCs(true); // mode Cs automatiquene peut pas être utilisé à cause du QH' (latches parasites aux changements de mode)
  if (latche == SENSE_SEL)  { // cas particulier : pas de changement en ram ni de latch
    sense_sel = 1 ; // flag pour pas de latch
     if (low_high == LOW) latche = latch_x_ram & ~128; // désactivation de QH'
     else   latche = latch_x_ram | 128; // activation de QH'
    }
  else   {  // cas courant : latch et changement en ram
    sense_sel = 0 ; // flag pour latch
     if (low_high == LOW) latch_x_ram &= ~latche; // désactivation en ram
     else  latch_x_ram |= latche; // activation en ram
    latche = latch_x_ram ;
  }
  ///Serial.print("n"); Serial.println(latche);
  hspi->beginTransaction(SPISettings {1000000, MSBFIRST, SPI_MODE0});  // à 1MHz ça fait 10us pour l'opération
  if (! sense_sel) digitalWrite(HSPI_SS, LOW); // prepare le latch
  hspi->transfer(latche);
  if (! sense_sel) digitalWrite(HSPI_SS, HIGH); // latch
  hspi->endTransaction();
 }

 void send_byte_595f(uint8_t byte_595) { /// pour commencer pour tester le vspi
  // envoie un octet en externe par le vspi
  ///Serial.print("595 "); Serial.println(byte_595);
  vspi->beginTransaction(SPISettings {10000, MSBFIRST, SPI_MODE0});  // à 10kHz ça fait 1ms pour un octet
  latch_x_low_high(L5947, LOW);      // prepare le latch
  vspi->transfer(0b01010101);
  latch_x_low_high(L5947, HIGH);     // latch
  vspi->endTransaction();
 }

  void scheduler(void) {
  /*  il y a 2 niveaux de priorité : 3 et 0
  les packets assez urgents arrivent en (priorité 3) p3, tous les autres en p0 (priorité 0)
  la p3, c'est par exemple les commandes de réduction de vitesse, ou des commandes de solénoîdes, dont le timing est critique
  tous le reste arriven en priorité p0
  les packets en p3 doivent être traités d'aabord, puis répétés (2x, à débattre) d'abord aussi
  ainsi un packet en p3 passe en p2 pui en p1 et finit en p0, après chacun de ses envois
  les packets en priorité p0 sont mis à la queue p0, ils y tournent en boucle
le scheduler va d'abord voir s'il y a des packets en p3, il les envoie, puis s'il ny en a plus, va voir en p2, etc.
à la fin de chaque envoi, on retourne voir ce qu'il y a en p1, etc
si un packet prioritaire pour être envoyé ... vient d'être envoyé, il passe son tour
 */
// va voir en p3 ; si le tableau est vide, schedule_p3n = 0 ;
if (schedule_p3n && (packet_to_send != schedule_p3[1]))  {  // priorité 3  // vérifie que le packet prioritaire ne vien pas d'être envoyé
  packet_to_send = schedule_p3[1];
  for (uint8_t i = 1; i < schedule_p3n; i++)   { // après envoi du 1er, décale tout à gauche, vois pas comment faire autrement ...
    schedule_p3[i] = schedule_p3[i+1] ;
  }
  schedule_p3n--; // décrémente le nombre de packets présents dans le p0
  Serial.print("p3 "); Serial.println(packet_to_send); /// debog
  schedule_p2n++; schedule_p2[schedule_p2n] = packet_to_send ;
}
else if (schedule_p2n && (packet_to_send != schedule_p2[1]))  {  // priorité 2
  packet_to_send = schedule_p2[1];
   for (uint8_t i = 1; i < schedule_p2n; i++)   {
    schedule_p2[i] = schedule_p2[i+1] ;
  }
  schedule_p2n--;
  Serial.print("p2 "); Serial.println(packet_to_send);
  schedule_p1n++; schedule_p1[schedule_p1n] = packet_to_send ;
}
else if (schedule_p1n && (packet_to_send != schedule_p1[1]))  {  // priorité 1
  packet_to_send = schedule_p1[1];
  for (uint8_t i = 1; i < schedule_p1n; i++)   {
    schedule_p1[i] = schedule_p1[i+1] ;
  }
  schedule_p1n--;
 Serial.print("p1 "); Serial.println(packet_to_send);
  schedule_p0n++; schedule_p0[schedule_p0n] = packet_to_send ;
}
else  {  // priorité 0 ; boucle de fond
  packet_to_send = schedule_p0[schedule_p0p]; // envoi du packet pointé
  Serial.print("p0 "); Serial.println(packet_to_send);
  schedule_p0p++; // pointeur va 1 rang à droite
  if (schedule_p0p > schedule_p0n)   {  // pointeur sorti de la queue
    schedule_p0p = 1; // les pointeurs commencent à 1
  }

  int read0 = digitalRead(0) ; /// pour tester la réception d'un nouveau packet par le scheduler
  if ( ! read0 && ! parti )   { // évite la récetion en boucle
    digitalWrite (LED_BUILTIN, LOW);
    parti = 1;
schedule_p3n++ ; schedule_p3[schedule_p3n] = 6 ;
  }
  else
     digitalWrite (LED_BUILTIN, HIGH); /// test button
 
  }

 }


et une image du hard visé :

ElGringo

  • Newbie
  • *
  • Messages: 21
    • Voir le profil
Re : centrales dcc esp32 trimarco232
« Réponse #5 le: juin 26, 2022, 09:16:34 am »
Bonjour à tous,

Je continue avec l'idée de Trimarco. J'ai repris qlq lignes. Je suis du coup sur paltformio. Tout compile correctement par contre dans l'interruption l'égrenage des bits ne semble pas fonctionner. Sur l'oscilloscope j'obtiens que le bit 0. J'ai remplacé le scheduler par une série de switch aussi pour simplifier et tester de manière simple.
J'ai peu être déraillé qlq part. Ci dessous mon code.

#include <Arduino.h>
#include "driver/ledc.h"

volatile uint8_t schedule_p3[16], schedule_p3n=0; // variable volatile stockée dans RAM
volatile uint8_t schedule_p2[16], schedule_p2n=0;
volatile uint8_t schedule_p1[16], schedule_p1n=0;
volatile uint8_t schedule_p0[128], schedule_p0p=1,schedule_p0n=0 ;
volatile uint8_t packet_bits[128];
volatile uint8_t packets_length[128], packet_bit_pointer, packet_to_send=0;

volatile int scheduler_case;
volatile int prio;


// DEFINITION DE LA SORTIE PWM
#define DCC_PIN 22

// ON DEFINIT LES PULSIONS PWM
const uint16_t DCC_BIT_1_FREQ = 8621; // 116 microsecondes
const uint16_t DCC_BIT_0_FREQ = 5000; // 200 microsecondes
const int dcc_pin_channel = 0; // CANAL PWM 0
const int dcc_sig_resolution =1; // on est sur 1 bit de resolution 2^1 2 valeurs possibles
const int dcc_sig_duty=1; // on est sur 1 bit de resolution 1/2^1 50%
//initialisation de la frequence
uint32_t dcc_sig_freq=DCC_BIT_1_FREQ;


void example(void) {

packet_bits[0]=(uint64_t)0b111111111011111111011111111011111111111111111111; // case 0
packet_bits[1]=(uint64_t)0b111001111011101111011110011011111111111111011111; // case 4
packet_bits[2]=(uint64_t)0b111001111011101111011110011011111111111101111111; // case 3 on le renvoie 3 fois de suite
packet_bits[3]=(uint64_t)0b111001111011101111011110011011111111110111111111; // case 3
packet_bits[4]=(uint64_t)0b111001111011101111011110011011111110111111111111; // case 6
packet_bits[5]=(uint64_t)0b111001111011101111011110011011111110111111111111; // case 6

packets_length[0]=48;
//schedule_p0n++;schedule_p0[schedule_p0n]=1;
packets_length[1]=48;
//schedule_p0n++;schedule_p0[schedule_p0n]=2;
packets_length[2]=48;
//schedule_p0n++;schedule_p3[schedule_p3n]=3;
packets_length[3]=48;
schedule_p3n++;schedule_p0[schedule_p0n]=4;
packets_length[4]=48;
//schedule_p0n++;schedule_p0[schedule_p0n]=5;
packets_length[5]=48;
//schedule_p0n++;schedule_p0[schedule_p0n]=5;


scheduler_case=(schedule_p3n)?3:0;


}

// declration des voids
void scheduler(void);
void dcc_sig_isr();
void setup_ledc(void);
//static uint8_t parti=0; // test packet parti

void setup() {
  // put your setup code here, to run once:
  Serial.begin(460800);
  setup_ledc();
  example();
 // pour commencer
 //packet_to_send=0;
 Serial.print("packet bit pointer:");Serial.println(packet_bit_pointer);

}

void loop() {
 // if (packet_bit_pointer>48){
   // packet_bit_pointer=0;
  //  Serial.print("err_pbp>5: ");Serial.print(packet_bit_pointer);
 // }else if (packet_bit_pointer != 0) {
  scheduler();
 packet_bit_pointer=packets_length[packet_to_send];
  //Serial.print("packet:   ");Serial.print(packet_to_send); Serial.print("    pbp   :"); Serial.println(packet_bit_pointer);
 // }

//Serial.print(packet_bit_pointer);
//delay(1000);
}

void dcc_sig_isr() {
  //if (dcc_sig_freq ==DCC_BIT_0_FREQ) dcc_sig_freq = DCC_BIT_1_FREQ ; // inverse la valeur du bit dcc précédent
 //else dcc_sig_freq = DCC_BIT_0_FREQ;
  static uint32_t old_dcc_sig_freq ; // fréquence précédente
 
  if (packet_bit_pointer==0) {
   dcc_sig_freq=DCC_BIT_1_FREQ; //on continue d envoyer des 1 tant qu on recoit rien
  }else{
 
      --packet_bit_pointer;
      Serial.println(packet_bit_pointer);
    uint64_t bit_pointer_mask=(uint64_t)1<<packet_bit_pointer;

            if (bit_pointer_mask&packet_bits[packet_to_send]) {

                    dcc_sig_freq=DCC_BIT_1_FREQ; // si tu pointes 1 envoie 1

           }else{

                   dcc_sig_freq=DCC_BIT_0_FREQ;  // si tu pointes 0 envoie 0
            }
  }
 
  if (dcc_sig_freq !=old_dcc_sig_freq) {
    ledc_set_freq(LEDC_HIGH_SPEED_MODE, LEDC_TIMER_0,dcc_sig_freq);
   dcc_sig_freq=old_dcc_sig_freq;
  }

}


void setup_ledc() {
  ledcSetup(dcc_pin_channel,dcc_sig_freq,dcc_sig_resolution);
  ledcAttachPin(DCC_PIN,dcc_pin_channel);
  ledcWrite(dcc_pin_channel,dcc_sig_duty);
  attachInterrupt(DCC_PIN,dcc_sig_isr,RISING);
 
}


void scheduler(void) {
   


switch (scheduler_case) {
 
  case 0:

            switch (prio) {
                case 0:
                //Serial.println("prio 0 send 1");
                packet_to_send=0;
                prio=4;
                break;
             
                case 4:
               // Serial.println("prio 0 send 1");
                packet_to_send=1;
                prio=5;
                break;


                case 5:
               // Serial.println("prio 0 send 2");
                packet_to_send=2;
                prio=6;
                break;

                case 6:
               // Serial.println("prio 0 send 2");
                packet_to_send=4;
                prio=7;
                break;

                case 7:
               // Serial.println("prio 0 send 3");
                packet_to_send=5;
                scheduler_case = 3;
                break;
            }
  //scheduler_case = 3;
  break;

  case 1:
 // Serial.println("prio 1 send 3");
  scheduler_case = 0;
  packet_to_send=3;
  prio=0;
  break;

  case 2:
 // Serial.println("prio 1 send 2");
  packet_to_send=3;
  scheduler_case = 1;
  break;

 case 3:
 // Serial.println("prio 1 send 1");
  packet_to_send=3;
  scheduler_case=2;
  break;

}

}

Ndlm : avec la balise code c'est plus joli !
« Modifié: juin 26, 2022, 09:27:18 am par Dominique »

trimarco232

  • Full Member
  • ***
  • Messages: 171
    • Voir le profil
Re : centrales dcc esp32 trimarco232
« Réponse #6 le: juin 26, 2022, 05:16:48 pm »
bonjour el gringo

je n'ai pas regardé ton code
je te mets la dernière version (toujours très incomplète) du programme
cela fonctionne bien à l'analyseur logique (clone salae + pulseview + plugin dcc)

trimarco232

  • Full Member
  • ***
  • Messages: 171
    • Voir le profil
Re : centrales dcc esp32 trimarco232
« Réponse #7 le: juillet 12, 2022, 12:55:31 pm »
bonjour à tous
parlons du cutout pour la lecture des signaux railcom
pour mémoire, "mes" signaux dcc sont générés par une méthode simple qui consiste à utiliser un signal pwm dont on change la période (la fréquence en fait, c'est pareil) selon le besoin
petite illustration :

trimarco232

  • Full Member
  • ***
  • Messages: 171
    • Voir le profil
Re : centrales dcc esp32 trimarco232
« Réponse #8 le: juillet 12, 2022, 01:13:56 pm »
le timing du cutout est, je trouve, pas très biendécrit dans la norme ; heureusement le site "open dcc" nous donne + d'explications ; pour l'instant j'ai adopté le timing avec les 4us de marge, préconisées par Wolfgang Kufer ; ça nous donne ça (voir la pj.)
le cutout intervient 26us après le début du 1er bit dcc du preamble, et se termine 104us après le début du 4ème bit dcc du preamble
« Modifié: juillet 12, 2022, 02:22:12 pm par trimarco232 »

trimarco232

  • Full Member
  • ***
  • Messages: 171
    • Voir le profil
Re : les centrales dccà esp32 de trimarco232
« Réponse #9 le: juillet 12, 2022, 02:06:39 pm »
je n'ai malheureusement pas pu (ou su ...) adapter la méthode du pwm à la génération du signal cutout ; il s'en suit une approximation d'environ 1us
on utilise un des 4 timers à disposition, pour générer une impulsion qui elle même générera une isr à sa fin
dans l'isr on inscrira le début du cutout, puis la fin
d'abord, dans les déclarations, on créé un pointeur du type hv_timer, qu'on nomme "cutout_timer" :
hw_timer_t *cutout_timer = NULL; // puis, dans le setup, on créé le timer et on lui colle une interruption
ici, le "0" c'est le n¨du timer (parmi 4) ; le "80", c'est le prescaler, qui va nous faire battre le timer à 1MHx (période = 1us) ; par "&on_cutout_timer" on donne le nom de l'isr qui se produit quand le timer a atteint sa consigne ; "true" veut dire que les opérations se font sur fronts montants
cutout_timer = timerBegin(0, 80, true); // config cutout timer - 1us -
timerAttachInterrupt(cutout_timer, &on_cutout_timer, true); // allow cutout timer interrupt
puis on initialise le timer, dans la l'isr qui se produit à chaque front montant du signal dcc pwm
la variable bits_sent compte les bits dcc 1 du preamble :
au 1er bit on initialise le timer avec les 26us, au 4ème on l'initialise avec 104us
void IRAM_ATTR dcc1sig_isr() {
  static uint8_t bits_sent ; // compte tous les bits du préamble
  bits_sent++ ;
if (bits_sent == 4)   { // tempo déclenchée au 3ème bit du preamble
   timerWrite(cutout_timer, 0) ; // clear cutout timer
   cutout_time_us = 58*2 -12 - 4 ; // -4 pour compenser le manque de réactivité
   timerAlarmWrite(cutout_timer, cutout_time_us, false); // configure timer 104us
   startstop_cutout = 2 ; // dit à l'isr ce qu'elle doit faire
   timerAlarmEnable(cutout_timer);  //enable interrupt
  }
else if (packet_bit_pointer == 1)  { // tempo avant cutout déclanchée au bit stop
   timerWrite(cutout_timer, 0) ; // clear cutout timer
   cutout_time_us = 58*2 + 26 - 4 ; // -4 pour compenser le manque de réactivité ...
   timerAlarmWrite(cutout_timer, cutout_time_us, false); // configure timer 26us
   startstop_cutout = 1 ; // dit à l'isr ce qu'elle doit faire
   timerAlarmEnable(cutout_timer);  //enable interrupt
   bits_sent = 0 ;
}
enfin l'isr, sans commentaire :
void IRAM_ATTR on_cutout_timer()  {
if ( startstop_cutout == 1 )  { // début du cutout
  digitalWrite(Pwm1piN, HIGH);
}
else if ( startstop_cutout == 2 )  {
  digitalWrite(Pwm1piN, LOW);
}
}
ceci fonctionne pour des ponts en H tels le lmd18200, qui ont une entrée dir (signal dcc) et un entrée pwm (signal booster/cutout)
pour les autres, il faut adapter
... 3 captures pulseview pour illustrer la chose :


« Modifié: juillet 12, 2022, 02:25:38 pm par trimarco232 »