LOCODUINO
Parlons Arduino => Vos projets => Discussion démarrée par: 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 ?
-
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);
}
-
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 (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 (https://forum.locoduino.org/index.php?topic=1352.msg14748#msg14748)
On attend des résultats avec impatience, mais prennez votre temps pour bien tester. !
-
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
-
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é :
-
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 !
-
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)
-
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 :
-
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
-
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 :
-
Hello
Une suggestion de regarder les gestions avec les lib de Khoih...
https://github.com/khoih-prog?tab=repositories&q=timer&type=&language=&sort=
Je n ai pas regarde plus avant, si cela fait écho avec vos besoins précis, néanmoins, la gestion de timing précis via ISR est bien expliqué sur les lib de gestion des TimerIntrupt.
A voir donc...
Laurent