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é :