diff --git a/doc/d18.cpp b/doc/d18.cpp new file mode 100644 index 0000000..3a892b6 --- /dev/null +++ b/doc/d18.cpp @@ -0,0 +1,2226 @@ +/* D18 ACC DECODER + +Introduction: + + FRANCAIS: + + Sans extension, ce decodeur permet de contrôler 16 sorties directes (ou 8 paires de sorties). + Cela est le double d'un decodeur d'accessoires traditionnel (4 paires de sorties). + Les sorties peuvent etre fixes ou generer des impulsions. + Vous pouvez ajouter des modules MAX7219/21 a la place de 3 sorties directes. Chaque module supporte 64 LEDs. + Le programme actuel supporte jusqu'a 4 modules, soit 256 LEDs. + Vous pouvez aussi contrôler le clignotement des LEDs ainsi que la phase. + Vous pouvez ajouter des modules PCA9685 a la place de 2 sorties directes. Chaque module ajoute 16 sorties. + Chacune de ces sorties peut fonctionner en mode PWM (0-100%), cela permet de faire varier l'eclairage ou la vitesse d'un moteur. + Pour une sortie traditionnelle, utilisez 0% ou 100% + Chacune de ces sorties peut fonctioner en mode SERVO (500ms->2500ms 50Hz), pour contrôler un servo-moteur. + Le programme actuel supporte jusqu'a 6 modules, soit 96 nouvelles sorties. + Vous pouvez ajouter des modules TLC5947 a la place de 3 sorties directes. Chaque module supporte 24 LEDs reglables en luminosite. + Le programme actuel supporte jusqu'a 8 modules, soit 192 LEDs. + Vous pouvez aussi contrôler des Neopixels (LEDs chainables multicolores) a la place d'une sortie directe. + Pour etre compatible avec toutes les centrales (et surtout la vôtre) et etre passe partout. + L’utilisateur peut utiliser n'importe quel ordre DCC ("Accessory" ou "Output") pour contrôler n'importe quelle sortie. + La configuration se fait dans ce code (dans la partie USER) au lieu d'utiliser les CV. + Par defaut le programme est configure pour contrôler 8 paires d sorties. + + ENGLISH: + + Without extension, this decoder allows to control 16 direct outputs (or 8 output pairs). + This is the double of traditional DCC accessories decoders having 4 output pairs. + Output can be fix or generate pulses + Configuration is done in this code (in USER part) instead of using CVs. + You can add MAX7219/21 modules in place of 3 direct outputs. Each module can drive 64 LEDs. + Actual SW supports up to 4 modules so 256 leds. + You can also control LEDs blinking and blinking phase. + You can add PCA9685 modules in place of 2 direct outputs. Each module adds 16 outputs. + Each of these outputs can work in PWM mode (0-100%), this allow dimming to control for instance lights intensity or motor speed. + To have the behavior of a traditional output, you can une 0% and 100%. + Each of these outputs can also work in SERVO mode to control a servo-motor. + Actual SW supports up to 6 modules so 96 new outputs. + You can add TLC5947 modules in place of 3 direct outputs. Each module support 24 LEDs with dimming + Actual SW supports up to 8 modules so 192 pwm leds. + You can also drive Neopixels (chained RGB leds) in place of 1 direct out. + To be compatible with any centrals (and mainly your central) and be versatile, + User can map any order ("Accessory" or "Output") from DCC signal to any output. + By default SW is delivered to manage 8 pairs on address 10 & 11 + +History: + 2020-04-08: Ulysse Delmas-Begue: ajout des aiguillages multiplexes + 2020-04-06: Ulysse Delmas-Begue: correction d'un bug sur user_out + 2020-03-05: Ulysse Delmas-Begue: ajout du support des TLC5947 (24 sorties pwm pour des leds et par TLC5947 8max=192leds) + possibilite de changer les pattes de connexion des modules + remplacement des constantes d'activation des modules par le nombre de modules + 2020-03-03: Ulysse Delmas-Begue: clean acvant release + 2020-03-02: Ulysse Delmas-Begue: ajout de la detection des paquets dupliques (pour eventuellement les rejetter) + ajout de l'eeprom dans le moniteur + ajout du deplacement lent pour onboard_servo (test ok) + 2020-02-29: Ulysse Delmas-Begue: ajout de onboard_servo (12 max, sans mouvement lent pour l'instant) (test ok) --> mais vibrations au debut des tests + ajout de onboard_pwm (6max sur UNO, 13max sur MEGA, sans fading pour l'instant) (test ok) + 2020-02-29: Ulysse Delmas-Begue: ajout du fading sur ls sorties pwm (test ok) --> mais clignotement lors du passage de 50% a 100% avec T=10000ms + 2020-02-28: Ulysse Delmas-Begue: commande des servos sans glitchs (test ok) + 2020-02-27: Ulysse Delmas-Begue: ajout du moniteur serie pour mettre au point/debuguer plus facilement (test ok) + 2020-02-26: Ulysse Delmas-Begue: commande des pca9685 dans fastloop() pour eviter les sacades des servos (test ok) + 2017-02-08: Ulysse Delmas-Begue: ajout de la rotation lente des servos + changement de la resolution des tempos de 20ms a 125ms + 2017-01-12: Ulysse Delmas-Begue: support Arduino Mega board to add 50 direct outputs in addition to Arduino UNO and Nano + AT Mega is enbaled when user select Arduino Mega board in Arduino IDE + serigraphied number is now used for direct outputs instead of out 0-15 + output number is checked in user_out function + 2017-01-12: Ulysse Delmas-Begue: support 256LEDs (via 4 MAX7219/21) and 96PWM/SERCO (via 6 PCA9685) + 2017-01-11: Ulysse Delmas-Begue: add 128leds (via MAX7219/21) OK, 48pwm/servos (via PCA9685 I2C), 60neopixels (test MAX, PCA(PWM&SRV) et pulses ok, neo not stable board finish to freeze) + 2017-01-10: Ulysse Delmas-Begue: creation (et test out ok avec D17) + +To be done: + + - le fading ne fonctionne pas correctement + - reduire a 20 ou 50ms la mise a jour des mouvements lents et fading. + - reduire l'utilisation memoire pour les mouvements lents + - ajout du decodage des decodeurs des loc ? + - rajouter fading sur onboard pwm ? + - rajouter des aides pour le multiplexage des aiguillages ? + +License: GPL v2 + +Notes: + - I used foloowing code as startup point: + Simple DCC Led Accessory Decoder Luca Dentella, 25.11.2017 + http://www.lucadentella.it/en/2017/11/25/dcc-decoder-accessori-per-led/ + + Normally decoder address and pulses are set by following CVs but here they are hardcoded + - CV513 ADR LSB 6bits + - CV515 pair 0 pulse + - CV516 pair 1 pulse + - CV517 pair 2 pulse + - CV518 pair 3 pulse duration + - CV521 ADR MSB 3bits + +Pinout on an Arduino UNO + + L5947 + CLI +-+ +~+ +~+ +~+ +++ +++ +~+ +~+ +++ +~+ DCC TX RX + D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 +++ = out + ########################################################### +~+ = out with pwm + #USB###########################################TOP#view#### +-+ = out or used by MAX,PCA,NEO,TLC5947 (it is possible to change pin) + ########################################################### use following numbers in SW: 3-12 & A0-A5 + ########################################################### max out = 16 + x x x x x x x x A0 A1 A2 A3 A4 A5 + +-+ +-+ +-+ +-+ +-+ +-+ + CLK DAT LLED NEO SDA SCK + +Pinout on an Arduino Nano + + L5947 + +++ +~+ +~+ +~+ +++ +++ +~+ +~+ +++ +~+ DCC TX RX +++ = out + D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 GND RST D1 D0 +~+ = out with pwm + ############################################################## +-+ = out or used by MAX,PCA,NEO,TLC5947 (it is possible to change pin) + USB ##############################################TOP#view#### use following numbers in SW: 3-12 & A0-A5 + ############################################################## max out = 16 + D13 3.3 X A0 A1 A2 A3 A4 A5 A6 A7 5V RST GND VIN + CLI +-+ +-+ +-+ +-+ +-+ +-+ + CLK DAT LLED NEO SDA SCK + +Pinout on Arduino Mega + + horizontal connectors + L5947 + CLI +~+ +~+ +~+ +~+ +~+ +~+ +~+ +~+ +~+ +~+ DCC TX RX +++ +++ +++ +++ +++ +++ +++ +++ + D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0 D14 D15 D16 D17 D18 D19 D20 D21 + ####################################################################################################### + USB############################################TOP#view################################################ + ####################################################################################################### + ####################################################################################################### + x x x x x x x x A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15 + +-+ +-+ +-+ +-+ +-+ +-+ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ + CLK DAT LLED NEO SDA SCK + + vertical connector: + + GND ##### GND + +++ D22 ##### D23 +++ +++ = out + +++ D24 ##### D25 +++ +~+ = out with pwm + +++ D26 ##### D27 +++ +-+ = out or used by MAX,PCA,NEO,TLC5947 (it is possible to change pin) + +++ D28 ##### D29 +++ use following numbers in SW: 3-12,14-53 & A0-A15 + +++ D30 ##### D31 +++ max out = 66 + +++ D32 ##### D33 +++ + +++ D34 ##### D35 +++ + +++ D36 ##### D37 +++ + +++ D38 ##### D39 +++ + +++ D40 ##### D41 +++ + +++ D42 ##### D43 +++ + +~+ D44 ##### D45 +~+ + +~+ D46 ##### D47 +++ + +++ D48 ##### D49 +++ + +++ D50 ##### D51 +++ + +++ D52 ##### D53 +++ + GND ##### GND + + +note: onboard pwm + Uno, Nano, Mini: 3, 5, 6, 9, 10, 11 + timer 0 (pins 5, 6) 980Hz + timer 1 (pins 9, 10) 490Hz 9 et 10 non dispo si arduino servos + timer 2 (pins 3, 11) 490Hz 3 et 11 non dispo si utilisation de tone + Mega 2 - 13, 44 - 46 + timer 0 (pins 4, 13 ) 980Hz 13 non dispo car LED-CLI + timer 1 (pins 11, 12 ) 490Hz + timer 2 (pins 9, 10 ) 490Hz 9 et 10 non dispo si arduino servos + timer 3 (pins 2, 3, 5) 490Hz 2 non dispo car DCC-IN, 2,3,5 non dispo si utilisation de tone + timer 4 (pins 6, 7, 8) 490Hz + +*/ +#include + + +//============================================================================================== +// USER PART (Partie utilisateur, vous pouvez faire des modifications) +//============================================================================================== + +// nombre d'elements supportes (verifier qu'il reste suffisament de memoire sur UNO/nano) + +#define MAX7219or21_NB 0 // nombre de MAX7219/21 supportes (out A0, A1 & A2 ne sont alors plus disponibles) + // 4max (4*64=256 leds) + +#define PCA9685_NB 0 // nombre de PCA9685 supportes (si different de 0: out A3 & A4 ne sont alors plus disponibles) + // 6max (6*16=96 sorties) mais verifier qu'il reste suffisament de memoire sur UNO/nano + +#define TLC5947_NB 0 // nombre de TLC5947 supportes(si different de 0: out A0, A1, 12 ne sont alors plus disponibles) + // 8max (8*24=192 leds pwm) + +#define NEO_NB 0 // nombre de neopixels supportes (out A5 ne sont alors plus disponibles) + // 60max + +#define ARDUINO_SRV_NB 0 // nombre de servos directement connectes sur l'Arduino (pas sur un PCA9685) + // 12max +byte arduino_srv_pins[] = {}; // liste des pattes de l'arduino sur lesquelles sont connectes les servos + // ex: { 4, 5}; leur nombre doit etre egal a ARDUINO_SRV_NB + +#define TEMPO_NB 80 // nombre de tempo (mettre un multiple de 8) + +#define MUX_AIG_NB 0 // nombre d'aiguillages multiplexes + +// functions disponibles pour l'utilisateur +void user_out(byte num, byte val); // num = numero de la sortie 3-12,A0-A6(UNO/nano) +14-53,A7-A15(MEGA), val 0=off-1=on +void user_neo(byte num, byte r, byte v, byte b); // num = numero du neopixel 0-59, r=rouge=0-255, v=vert=0-255, b=bleu=0-255 +void user_blink(void); // fait cligoter la led de la carte a 4Hz pendant 2s + +// sur MAX7219/21 +void user_max7219_led(byte num, byte val); // num = numero de la led 0-255, val 0=off-1=on +void user_max7219_led_cli(byte num, byte val); // val 0=pas de cli, 1 = cli +void user_max7219_led_pha(byte num, byte val); // val 0=phase 1=phase inverse + +// sur PCA9685 (num = numero de la sortie 0-95 (pca0:0-15, pca1:16-31 ...) +void user_pca9685_pwm_0_100(byte num, byte val); // pwm 0-100% +void user_pca9685_servo_500_2500(byte num, unsigned int val); // 500us-2500us (souvent sur 9G: 500=-90° 1000=-45° 1500=0°=neutre 2000=+45° 2500=+90°) +void user_pca9685_servo_speed(byte num, unsigned int speed); // 0-10000ms (temps pour faire varier l'impulsion de 1ms = 90° sur servo 9G 10000=10s/90°) +byte user_is_pca9685_servo_in_position(byte num); // indique si le servo a termine son deplacement lent 0=non 1=oui + +// sur TLC5947 +void user_tlc5947_pwmled_0_100(byte num, byte val); // 0-100% + +// sur la carte Arduino (num = numero de la sortie) +void user_arduino_pwm_0_100(byte num, byte val); // val=0-100% seulement dispo sur certaines pins, pas de fading +void user_arduino_servo_500_2500(byte num, unsigned int val); // val=500-2500us +void user_arduino_servo_speed(byte num, unsigned int speed); // vitesse=0-10000ms (temps en ms pour faire varier l'impulsion de 1ms, soit 90° sur servo 9G) +byte user_is_arduino_servo_in_position(byte num); // indique si le servo a termine son deplacement lent 0=non 1=oui + +// aiguillages multiplexes +void user_cmd_mux_aig(byte num, byte pos); // demande de mettre l'aiguillage num en position pos (0=direct, 1=devie) +void user_define_mux_aig_pulse_ms(byte num, unsigned int pulse); // regle la duree de l'impulsion de commande en ms 125-60000ms (l'impulsion sera un multiple de 125ms) + +// autres +void user_tempo_start(byte num_tempo, unsigned int duration_ms); // num_tempo= +//EEPROM.write(unsigned long addr_0_1024, byte val_0_255); //commentee car definit dans une bibliotheque +//EEPROM.read(unsigned long addr_0_1024); // return: 0_255 + +// Ce code est fourni avec un exemple permettant de se comporter comme 2 decodeurs de 4 paires chacun +// (2 sorties par paire. Les adresse 1 a 510 des 2 decodeurs basiques sont les constantes DECODER_ADR_1 & 2 ) +// - mettre a 1 (5V) une sortie de la paire, met automatiquement a 0 (0V=masse) l'autre sortie de la paire +// - les sorties ne sont pas impulsionnelles mais fixes +// - la led de la carte Arduino cligote a 4Hz pendant 2s lors de la reception d'un paquet pour le decodeur +// Bien entendu, ce n'est qu'un exemple et vous devraez surement le modifier pour l'adapter a vos besoins. + +// Constantes utilisateur +#define DECODER_ADR_1 10 // adresse du premier decodeur de 4 paires +#define DECODER_ADR_2 11 // adresse du second decodeur de 4 paires + +// appelee au demarrage +void user_init(void) +{ + +} + +// appelee lorsque le decodeur recoit un ordre pour un decodeur basique +void user_notify_bas_acc_dec(unsigned int adr_1_510, byte out_0_7, byte val_0_1, byte duplicate_0_1) +{ + if(duplicate_0_1) return; + + if(adr_1_510 == DECODER_ADR_1) + { + user_blink(); + if(out_0_7 == 0) { user_out(12, val_0_1); user_out(11, 0); } + if(out_0_7 == 1) { user_out(11, val_0_1); user_out(12, 0); } + if(out_0_7 == 2) { user_out(10, val_0_1); user_out( 9, 0); } + if(out_0_7 == 3) { user_out( 9, val_0_1); user_out(10, 0); } + if(out_0_7 == 4) { user_out( 8, val_0_1); user_out( 7, 0); } + if(out_0_7 == 5) { user_out( 7, val_0_1); user_out( 8, 0); } + if(out_0_7 == 6) { user_out( 6, val_0_1); user_out( 5, 0); } + if(out_0_7 == 7) { user_out( 5, val_0_1); user_out( 6, 0); } + } + if(adr_1_510 == DECODER_ADR_2) + { + user_blink(); + if(out_0_7 == 0) { user_out( 4, val_0_1); user_out( 3, 0); } + if(out_0_7 == 1) { user_out( 3, val_0_1); user_out( 4, 0); } + if(out_0_7 == 2) { user_out(A0, val_0_1); user_out(A1, 0); } + if(out_0_7 == 3) { user_out(A1, val_0_1); user_out(A0, 0); } + if(out_0_7 == 4) { user_out(A2, val_0_1); user_out(A3, 0); } + if(out_0_7 == 5) { user_out(A3, val_0_1); user_out(A2, 0); } + if(out_0_7 == 6) { user_out(A4, val_0_1); user_out(A5, 0); } + if(out_0_7 == 7) { user_out(A5, val_0_1); user_out(A4, 0); } + } +} + +// appelee lorsque le decodeur recoit un ordre pour un decodeur etendu +void user_notify_ext_acc_dec(unsigned int adr_1_2044, byte val_0_31, byte duplicate_0_1) +{ +} + +// appelee lorsque la tempo num_tempo expire +void user_notify_tempo_end(byte num_tempo) +{ +} + +// appelee toute les 125ms (pour faire des taches recurentes) +void user_125ms() +{ +} + +// demande a l'utilisateur de commencer la commande de l'aiguillage num en position pos +void user_notify_start_mux_aig(byte num, byte pos) +{ +} + +// demande a l'utilisateur d'arreter de commander l'aiguillage num +void user_notify_stop_mux_aig(byte num, byte pos) +{ +} + + + +//============================================================================================== +// END OF USER PART (Fin de la partie utilisateur) +// +// (Ne pas faire de modifications apres de point (a moins de savoir ce que vous faite) +//============================================================================================== + + + +//============================================================================================== +// IO (if needed, you can redefine these pins) +//============================================================================================== + +#define PIN_LED 13 +#define PIN_DCC 2 + +// Les pattes suivantes sont utilisees en fonction des modules actives +#define CLK_PIN A0 +#define DAT_PIN A1 +#define LLED_PIN A2 +#define NEO_PIN A3 +#define SDA_PIN A4 +#define SCK_PIN A5 +#define L5947_PIN 12 + + + +//============================================================================================== +// Leds (256) +//============================================================================================== + +#define IO_LED_NB 256 // pour l'instant si on active, on supporte 4 modules soit 256 leds + +#if MAX7219or21_NB >= 1 + +byte io_led[IO_LED_NB / 8] = { 0, 0, 0, 0 ,0, 0, 0, 0, 0, 0, 0, 0 ,0, 0, 0, 0 }; // possibilite d'initialiser les valeurs +byte io_cli[IO_LED_NB / 8] = { 0, 0, 0, 0 ,0, 0, 0, 0, 0, 0, 0, 0 ,0, 0, 0, 0 }; // ex led clignotante +byte io_pha[IO_LED_NB / 8] = { 0, 0, 0, 0 ,0, 0, 0, 0, 0, 0, 0, 0 ,0, 0, 0, 0 }; +byte led_cpt = 0; + +void led_shift8(unsigned char dat) +{ + unsigned char i; + + pinMode(DAT_PIN, OUTPUT); + + for(i = 0; i < 8; i++) + { + if(dat & 0x80) digitalWrite(DAT_PIN, HIGH); + else digitalWrite(DAT_PIN, LOW); + + delayMicroseconds(4); + digitalWrite(CLK_PIN, HIGH); + delayMicroseconds(4); //periode=8us = 125KHz (ok sur longue distances) + digitalWrite(CLK_PIN, LOW); + + dat = dat<<1; + } +} + +void led_reg(unsigned char reg0, unsigned char dat0, unsigned char reg1, unsigned char dat1, unsigned char reg2, unsigned char dat2, unsigned char reg3, unsigned char dat3) +{ + digitalWrite(CLK_PIN, LOW); + digitalWrite(LLED_PIN, HIGH); delayMicroseconds(10); digitalWrite(LLED_PIN, LOW); delayMicroseconds(10); + led_shift8(reg3); led_shift8(dat3); + led_shift8(reg2); led_shift8(dat2); + led_shift8(reg1); led_shift8(dat1); + led_shift8(reg0); led_shift8(dat0); + digitalWrite(LLED_PIN, HIGH); delayMicroseconds(10); +} + +byte compute_led_on_cli_phase(byte on, byte cli, byte phase) +{ + byte led; + + if(led_cpt >= 4) led = 0xff; else led = 0; //clignotement 1Hz + led ^= phase; //changement de phase + led |= ~cli; //allumer si pas clignotant + led &= on; //eteindre si pas on + + return led; +} + +// needs about 2x 2.5ms to update all LEDs +void led_maj(byte index) +{ + byte i, j, dat0, dat1, dat2, dat3; + + if(index == 0) for(i = 0; i < 4; i++) + { + dat0 = compute_led_on_cli_phase(io_led[ i], io_cli[ i], io_pha[ i]); + dat1 = compute_led_on_cli_phase(io_led[ 8 + i], io_cli[ 8 + i], io_pha[ 8 + i]); + dat2 = compute_led_on_cli_phase(io_led[16 + i], io_cli[16 + i], io_pha[16 + i]); + dat3 = compute_led_on_cli_phase(io_led[24 + i], io_cli[24 + i], io_pha[24 + i]); + led_reg(1 + i, dat0, 1 + i, dat1, 1 + i, dat2, 1 + i, dat3); + } + + if(index == 1) for(i = 4; i < 8; i++) + { + dat0 = compute_led_on_cli_phase(io_led[ i], io_cli[ i], io_pha[ i]); + dat1 = compute_led_on_cli_phase(io_led[ 8 + i], io_cli[ 8 + i], io_pha[ 8 + i]); + dat2 = compute_led_on_cli_phase(io_led[16 + i], io_cli[16 + i], io_pha[16 + i]); + dat3 = compute_led_on_cli_phase(io_led[24 + i], io_cli[24 + i], io_pha[24 + i]); + led_reg(1 + i, dat0, 1 + i, dat1, 1 + i, dat2, 1 + i, dat3); + } + +} + +void led_init(void) +{ + // R0 : bypass + // R1-8 : data + led_reg(9 , 0, 9, 0, 9, 0, 9, 0); // R9 : decode mode off (1ere fois pas sure que ca marche) + led_reg(9 , 0, 9, 0, 9, 0, 9, 0); // R9 : decode mode off + led_reg(0xa,0x4,0xa,0x4,0xa,0x4,0xa,0x4); // R10 : intensite 4/15 (0xf=max) + led_reg(0xb, 7,0xb, 7,0xb, 7,0xb, 7); // R11 : 8 digit + led_reg(0xc, 1,0xc, 1,0xc, 1,0xc, 1); // R12 :shutdown mode off + // R13-14: NA + led_reg(0xf, 0,0xf, 0,0xf, 0,0xf, 0); // R15 :test mode off (1=on) + + led_maj(0); + led_maj(1); +} +#endif + +void user_max7219_led(byte num, byte val) //on +{ +#if MAX7219or21_NB >= 1 + byte msk; + + if(num > IO_LED_NB) return; + msk = 1 << (num & 7); + if(val) io_led[num / 8] |= msk; + else io_led[num / 8] &= (0xff ^ msk); +#endif +} + +void user_max7219_led_cli(byte num, byte val) +{ +#if MAX7219or21_NB >= 1 + byte msk; + + if(num > IO_LED_NB) return; + msk = 1 << (num & 7); + if(val) io_cli[num / 8] |= msk; + else io_cli[num / 8] &= (0xff ^ msk); +#endif +} + +void user_max7219_led_pha(byte num, byte val) +{ +#if MAX7219or21_NB >= 1 + byte msk; + + if(num > IO_LED_NB) return; + msk = 1 << (num & 7); + if(val) io_pha[num / 8] |= msk; + else io_pha[num / 8] &= (0xff ^ msk); +#endif +} + + +//============================================================================================== +// Leds PWM par TLC5947(24 par TLC5947) +//============================================================================================== + +void fast_loop(void); + +// user 0-100% --> storage 0-255 --> TLC5947 0-4095 +// 4096 steps precision reduced to 256 to save space + +#if TLC5947_NB >= 1 +byte tlc5947_to_update = 0; +byte tlc5947_val0_255[24 * TLC5947_NB]; + +void update_tlc5947(void) +{ + digitalWrite(CLK_PIN, LOW); + digitalWrite(L5947_PIN, LOW); + delayMicroseconds(10); + + for(byte k = 0; k < TLC5947_NB; k++) + { + for(byte j = 0; j < 24; j++) + { + byte data = tlc5947_val0_255[24 * k + j]; // 8MSB + for(byte i = 0; i < 8; i++) + { + if(data & 0x80) digitalWrite(DAT_PIN, HIGH); + else digitalWrite(CLK_PIN, LOW); + + delayMicroseconds(4); + digitalWrite(CLK_PIN, HIGH); + data = data << 1; + delayMicroseconds(4); + digitalWrite(CLK_PIN, LOW); + } + + digitalWrite(DAT_PIN, LOW); // 4LSB to 0 + for(byte i = 0; i < 4; i++) + { + delayMicroseconds(4); + digitalWrite(CLK_PIN, HIGH); + delayMicroseconds(4); + digitalWrite(CLK_PIN, LOW); + } + } + fast_loop(); + } + + digitalWrite(L5947_PIN, HIGH); + delayMicroseconds(10); + digitalWrite(L5947_PIN, LOW); + delayMicroseconds(10); + + tlc5947_to_update = 1; +} + +void init_tlc5947(void) +{ + for(byte i = 0; i < (24 * TLC5947_NB); i++) tlc5947_val0_255[i] = 0; + tlc5947_to_update = 1; + update_tlc5947(); +} +#endif + +void user_tlc5947_pwmled_0_100(byte num, byte val) // 0-100% +{ +#if TLC5947_NB >= 1 + if(num > (24 * TLC5947_NB)) return; + + tlc5947_val0_255[num] = map(val, 0, 100, 0, 255); + tlc5947_to_update = 1; +#endif +} + + + +//============================================================================================== +// NEOPIXELS (60) +//============================================================================================== + +/* +neopixel +official ns +H0 200-500 L0 650-950 +H1 550-850 L1 450-750 +RESET 50000 min +test +H0 200-500 +H1 550-oo L 450-5000 +RESET 6000 min +G(8bits7-->0)R(8bits)B(8bits) +*/ + +#if NEO_NB >= 1 + +#define NEO_R 0 +#define NEO_V 1 +#define NEO_B 2 +volatile unsigned char neo_rvb[NEO_NB][3]; +volatile byte neo_to_be_updated = 0; + +#define NOP __asm__ __volatile__ ("nop\n\t") //62.5ns + +/* + Addr Name Bit7 Bit6 Bit5 Bit4 Bit3 Bit2 Bit1 Bit0 + ... + 0x03 PINB PINB7 PINB6 PINB5 PINB4 PINB3 PINB2 PINB1 PINB0 + 0x04 DDRB DDB7 DDB6 DDB5 DDB4 DDB3 DDB2 DDB1 DDB0 + 0x05 PORTB PORTB7 PORTB6 PORTB5 PORTB4 PORTB3 PORTB2 PORTB1 PORTB0 + 0x06 PINC x PINC6 PINC5 PINC4 PINC3 PINC2 PINC1 PINC0 + 0x07 DDRC x DDC6 DDC5 DDC4 DDC3 DDC2 DDC1 DDC0 + 0x08 PORTC x PORTC6 PORTC5 PORTC4 PORTC3 PORTC2 PORTC1 PORTC0 + 0x09 PIND PIND7 PIND6 PIND5 PIND4 PIND3 PIND2 PIND1 PIND0 + 0x0A DDRD DDD7 DDD6 DDD5 DDD4 DDD3 DDD2 DDD1 DDD0 + 0x0B PORTD PORTD7 PORTD6 PORTD5 PORTD4 PORTD3 PORTD2 PORTD1 PORTD0 +*/ + +void neo_reset(void) //6 us mini -> 10us +{ + unsigned char i; + + __asm__ __volatile__ ("cbi 8, 3\n\t"); // 8,3 = PORTC bit3 = AN3 ou 5,5=D13 + for(i = 0; i < 100; i++) // par bcl: 3cycle+nb nop 1nop -> 4*62.5ns = 250ns 01us=40bcl + { + //__asm__ __volatile__("nop\n\t"); //62.5ns + NOP; + } +} + +// mesures +// 1court: 0.375us +// 1long : 0.750us +// bas : 0.750-937us 1625us entre 2 bytes 3125us entre 2 24bits +void neo_tx_byte(unsigned char val) //UU: check the code of this function in assembly (mainly after opimization !!!!) +{ + unsigned char i; + + for(i = 0; i < 8; i++) + { + if(val & 0x80) // TX_H_1 550-oo ns --> 600ns 10nop+1port=10*62.5=625ns //uu:try to reduce + { + asm volatile( + "sbi 8, 3\n\t" //mise a 1 AN3 PC3 + "nop\n\t" //9x NOP + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t" + "cbi 8, 3\n\t"); //mise a 0 AN3 PC3 + } + else // TX_H_0 200-500ns --> 400ns 4nop+port=5*62.5=310ns + { + asm volatile( + "sbi 8, 3\n\t" //mise a 1 digital 13 PB5 + "nop\n\t" //4x NOP + "nop\n\t" + "nop\n\t" + "nop\n\t" + "cbi 8, 3\n\t"); //mise a 0 digital 13 PB5 + } + // TX_L 450->5000ns --> 600ns sans les nop:500ns, 4nop: +248ns -> 1us //uu: try tu reduce + asm volatile( + "nop\n\t" + "nop\n\t" + "nop\n\t" + "nop\n\t"); + val = val << 1; + } +} + +// dure 60 * 24* 1.5us = 2.1ms max pour 60 neopixels +// desactive les ITs +void neo_display(void) +{ + unsigned char i; + + //wait an end of packet + //loco_pkt = 0; + //while(loco_pkt == 0) { process_packet(); } + noInterrupts(); + + neo_reset(); + + for(i = 0; i < NEO_NB; i++) + { + neo_tx_byte(neo_rvb[i][NEO_V]); + neo_tx_byte(neo_rvb[i][NEO_R]); + neo_tx_byte(neo_rvb[i][NEO_B]); + } + + neo_reset(); + + interrupts(); + Serial.print("$"); +} + +void neo_set_rvb_pixel(unsigned char pixel, unsigned char r, unsigned char v, unsigned char b) +{ + neo_rvb[pixel][NEO_R] = r; + neo_rvb[pixel][NEO_V] = v; + neo_rvb[pixel][NEO_B] = b; +} + +void neo_init(void) +{ + byte i; + + for(i = 0; i < NEO_NB; i++) + { + neo_set_rvb_pixel(i, 0, 0, 0); //on demarre a 0 pour ne pas trop charger une faible alimentation + } + neo_display(); + neo_display(); +} + +void neo_maj(void) +{ + neo_to_be_updated = 1; + //neo_display(); +} + +#endif // NEO_NB >= 1 + +void user_neo(byte num, byte r, byte v, byte b) +{ +#if NEO_NB >= 1 + if(num <= NEO_NB) neo_set_rvb_pixel(num, r, v, b); +#endif +} + + +//============================================================================================== +// I2C (I2C fait a la main pour pouvoir utiliser une basse frequence et n'importe quelle patte) +//============================================================================================== + +//on emule un collecteur ouvert en mettant les sorties a 0 et en jouant sur la direction. +#define SDA_1 pinMode(SDA_PIN, INPUT) +#define SDA_0 pinMode(SDA_PIN, OUTPUT) +#define SCK_1 pinMode(SCK_PIN, INPUT) +#define SCK_0 pinMode(SCK_PIN, OUTPUT) + +void i2c_init(void) +{ + pinMode(SCK_PIN, INPUT); digitalWrite(SCK_PIN, LOW); + pinMode(SDA_PIN, INPUT); digitalWrite(SDA_PIN, LOW); +} + +void i2c_start(void) +{ + SDA_1; SCK_1; delayMicroseconds(5); + SDA_0; delayMicroseconds(5); + SCK_0; delayMicroseconds(5); +} + +void i2c_stop(void) +{ + SDA_0; SCK_0; delayMicroseconds(5); + SCK_1; delayMicroseconds(5); + SDA_1; delayMicroseconds(5); +} + +void i2c_wr_8(byte data) +{ + byte i; + + for(i = 0; i < 8; i++) + { + if(data & 0x80) { SDA_1; } else { SDA_0; } delayMicroseconds(4); + SCK_1; delayMicroseconds(4); + SCK_0; delayMicroseconds(1); + data = data << 1; + } + SDA_1; delayMicroseconds(1); +} + +byte i2c_rd_ack(void) +{ + byte ack; + + delayMicroseconds(4); //before 0 + SCK_1; delayMicroseconds(4); + if(digitalRead(SDA_PIN) == HIGH) ack = 0; /*nack*/ else ack=1; /*ack*/ + SCK_0; delayMicroseconds(1); + return ack; +} + +byte i2c_rd_8(void) +{ + byte i, data; + + data=0; + for(i=0;i<8;i++) + { + delayMicroseconds(4); //before 0 + SCK_1; delayMicroseconds(4); + if(digitalRead(SDA_PIN) == HIGH) data |= 1; + SCK_0; delayMicroseconds(1); + data = data << 1; + } + return data; +} + +byte i2c_wr_ack(byte ack) //1=ack, 0=nack +{ + if(ack) { SDA_0; } else { SDA_1; } delayMicroseconds(4); + SCK_1; delayMicroseconds(4); + SCK_0; delayMicroseconds(1); +} + + + +//============================================================================================== +// PCA9685 (6x16=96) +//============================================================================================== + +//#define PCA9685_NB est maintenant definit dans la partie utilisateur +#define IO_PCA9685_OUT_NB (16 * PCA9685_NB) + +#if PCA9685_NB >= 1 + +#define PCA9685_MODE1_REG 0x00 +#define PCA9685_MODE2_REG 0x01 +#define PCA9685_LED0_REG 0x06 +#define PCA9685_PRESCALE_REG 0xfe + +unsigned int pca9685_on[ IO_PCA9685_OUT_NB]; +unsigned int pca9685_off[IO_PCA9685_OUT_NB]; + +byte srv_speed[IO_PCA9685_OUT_NB]; +unsigned int srv_millis_init[IO_PCA9685_OUT_NB]; +byte srv_start[IO_PCA9685_OUT_NB]; +byte srv_target[IO_PCA9685_OUT_NB]; +byte srv_pos[IO_PCA9685_OUT_NB]; //UU: verifier si on peut l'enlever +byte srv_status[IO_PCA9685_OUT_NB]; // bit7=first bit2=srv_init bit1=srv bit0=update + + +byte set_pca9685_reg(byte i2c_adr, byte adr, byte dat) +{ + i2c_start(); + i2c_wr_8(0x80 + 2 * i2c_adr); //A6=1,A5-A0=pin,R/W# + if(i2c_rd_ack() == 0) + { + i2c_stop(); + return 0; + } + i2c_wr_8(adr); + if(i2c_rd_ack() == 0) + { + i2c_stop(); + return 0; + } + i2c_wr_8(dat); + if(i2c_rd_ack() == 0) + { + i2c_stop(); + return 0; + } + i2c_stop(); + return 1; +} + + +byte set_pca9685_on_off(byte i2c_adr, byte out, unsigned int on, unsigned int off) +{ + byte on_h, on_l, off_h, off_l; + + on_h = byte(on >> 8); on_l = byte(on & 255); //existe aussi lowByte highByte + off_h = byte(off >> 8); off_l = byte(off & 255); + + i2c_start(); // A6=1,A5-A0=pin,R/W# + i2c_wr_8(0x80+2*i2c_adr); i2c_rd_ack(); + i2c_wr_8(PCA9685_LED0_REG+4*out); i2c_rd_ack(); + i2c_wr_8(on_l); i2c_rd_ack(); + i2c_wr_8(on_h); i2c_rd_ack(); + i2c_wr_8(off_l); i2c_rd_ack(); + i2c_wr_8(off_h); i2c_rd_ack(); + i2c_stop(); +} + + +byte set_pca9685_only_off_out(byte i2c_adr, byte out, unsigned int off) +{ + byte on_h, on_l, off_h, off_l; + + off_h = byte(off >> 8); off_l = byte(off & 255); + + i2c_start(); // A6=1,A5-A0=pin,R/W# + i2c_wr_8(0x80 + 2 * i2c_adr); i2c_rd_ack(); + i2c_wr_8(PCA9685_LED0_REG + 4 * out + 2); i2c_rd_ack(); + i2c_wr_8(off_l); i2c_rd_ack(); + i2c_wr_8(off_h); i2c_rd_ack(); + i2c_stop(); +} + + +// to set directly pwm (just used at init) +void set_pca9685_pwm0_100(byte num, byte pwm) +{ + byte i2c_adr; + byte out; + unsigned int off; + + i2c_adr = num / 16; + out = num & 15; + off = map(pwm, 0, 100, 0, 4096); + + if(off == 0) set_pca9685_on_off(i2c_adr, out, 0, 4096); + else if(off == 4096) set_pca9685_on_off(i2c_adr, out, 4096, 0); + else set_pca9685_on_off(i2c_adr, out, 0, off ); +} + + +// to set directly srv (no more used) +void set_paca9685_srv50_250(byte num, byte pulse) //50 -> 150 -> 250 +{ + byte i2c_adr; + byte out; + unsigned int off; + + i2c_adr = num / 16; + out = num & 15; + off = map(pulse, 50, 250, 102, 512); + + set_pca9685_on_off(i2c_adr, out, 0, off); +} + + +byte i2c_pca9685_online[PCA9685_NB]; + +void pca9685_init(void) +{ + byte i; + byte i2c_adr; + + for(i2c_adr = 0; i2c_adr < PCA9685_NB; i2c_adr++) + { + // reset et config MODE1 + i2c_pca9685_online[i2c_adr] = set_pca9685_reg(i2c_adr, PCA9685_MODE1_REG, 0xB0); //reset=1, extclk=0, autoincrement=1, sleep=1 (all leds are off after reset) + Serial.print(F("PCA")); Serial.print(i2c_adr); if(i2c_pca9685_online[i2c_adr]) { Serial.println(F("=ONLINE")); } else Serial.println(F("=OFFLINE")); + if(i2c_pca9685_online[i2c_adr] == 0) continue; + delay(1); + + // Frequence de decoupage + // 50Hz pour les servos: 25.000.000/(4096*50Hz) - 1 = 122-1 = 121. + // 60Hz pour les servos: 25.000.000/(4096*60Hz) - 1 = 102-1 = 101. + // mesure: + // 121 -> 18.7ms -1.3ms err -6.5% + // 130 -> 19.92ms -0.08ms err 0.4% + // 131 -> 20.07ms +0.07ms err 0.4% + set_pca9685_reg(i2c_adr, PCA9685_PRESCALE_REG, 130); + + // exit from sleep + set_pca9685_reg(i2c_adr, PCA9685_MODE1_REG, 0x20); //reset=0, extclk=0, autoincrement=1, sleep=0 + delay(1); //wait exit from low power mode + + // config MODE2 + set_pca9685_reg(i2c_adr, PCA9685_MODE2_REG, 0x04); // invert=0 change=onSTOP out=totem off=led_0 UU: voir si pa smieux de clr sorties avant + + // mise des sorties a 0 + for(i = 0; i < 16; i++) + { + byte n = 16 * i2c_adr + i; + set_pca9685_pwm0_100(n, 0); + pca9685_on[n] = 0; + pca9685_off[n] = 4096; + srv_speed[n] = 0; + srv_status[n] = 0x80; + } + } +} + + +void update_pwm(byte num) +{ + byte i2c_adr = num / 16; + byte out = num & 15; + + if(srv_status[num] & 4) // init + { + unsigned int pulse = map(srv_target[num], 0, 100, 0, 4096); + + if(pulse == 0) { pca9685_on[num] = 0; pca9685_off[num] = 4096; } + else if(pulse == 4096) { pca9685_on[num] = 4096; pca9685_off[num] = 0; } + else { pca9685_on[num] = 0; pca9685_off[num] = pulse; } + + set_pca9685_on_off(i2c_adr, out, pca9685_on[num], pca9685_off[num]); + + srv_status[num] = 0; + + return; + } + + unsigned int pwm; + unsigned int pulse; + if(srv_speed[num] == 0) // slow deactivated + { + pwm = srv_target[num]; + pulse = map(pwm, 0, 100, 0, 4096); + } + else // slow + { + unsigned int duration = (unsigned int) millis() - srv_millis_init[num]; + //if(num==3) { Serial.print("duration="); Serial.print(duration); } + + unsigned int max_duration; + if(srv_target[num] >= srv_start[num]) max_duration = srv_speed[num] * (srv_target[num] - srv_start[num]); //0-100*0-100=0-10s + else max_duration = srv_speed[num] * (srv_start[num] - srv_target[num]); + if(duration > max_duration) duration = max_duration; + //if(num==3) { Serial.print("/"); Serial.print(max_duration); } + + unsigned int pwm100 = map(duration, 0, max_duration, 100*srv_start[num], 100*srv_target[num]); //*100 astuce pour avoir plus de pas + pwm = pwm100 / 100; + pulse = map(pwm100, 0, 10000, 0, 4096); + //if(num==3) { Serial.print("-->pulse="); Serial.println(pulse); } + } + + if(pwm == 0 || pwm == 100 || pca9685_on[num] == 4096 || pca9685_off[num] == 4096) + { + if(pulse == 0) { pca9685_on[num] = 0; pca9685_off[num] = 4096; } + else if(pulse == 4096) { pca9685_on[num] = 4096; pca9685_off[num] = 0; } + else { pca9685_on[num] = 0; pca9685_off[num] = pulse;} + + set_pca9685_on_off(i2c_adr, out, pca9685_on[num], pca9685_off[num]); + } + else + { + unsigned int current_pulse = (pca9685_off[num] - pca9685_on[num]) & 4095; + if(current_pulse != pulse) + { + // ce dispositif permet d'annuller les glitches + // si on veut une plus grande pulse on augmente OFF + // si on veut une plus petite pulse on augmente ON + if(pulse > current_pulse) pca9685_off[num] = (pca9685_on[num] + pulse) & 4095; + else pca9685_on[num] = (pca9685_off[num] - pulse) & 4095; + + set_pca9685_on_off(i2c_adr, out, pca9685_on[num], pca9685_off[num]); + } + } + if(pwm == srv_target[num]) srv_status[num] = 0; +} + + +void update_servo(byte num) +{ + byte i2c_adr = num / 16; + byte out = num & 15; + + if(srv_status[num] & 4) // force (so no slow) + { + pca9685_on[num] = 0; + pca9685_off[num] = map(srv_target[num], 50, 250, 102, 512); //4096=20ms > 102=0.5ms 512=2.5ms + + set_pca9685_on_off(i2c_adr, out, pca9685_on[num], pca9685_off[num]); + + srv_status[num] = 2; // init=0, servo=1, update=0 + + return; + } + + byte pulse_50_250; + if(srv_speed[num] == 0) // slow deactivated + { + pulse_50_250 = srv_target[num]; + } + else // slow + { + unsigned int duration = (unsigned int) millis() - srv_millis_init[num]; + //if(num==3) { Serial.print("duration="); Serial.print(duration); } + + unsigned int max_duration; // ex: speed=100 (10s) 0->90=1ms=100 --> 10s + if(srv_target[num] >= srv_start[num]) max_duration = srv_speed[num] * (srv_target[num] - srv_start[num]); + else max_duration = srv_speed[num] * (srv_start[num] - srv_target[num]); + //if(num==3) { Serial.print("/"); Serial.print(max_duration); } + + if(duration > max_duration) duration = max_duration; + pulse_50_250 = map(duration, 0, max_duration, srv_start[num], srv_target[num]); + //if(num==3) { Serial.print("-->pulse="); Serial.println(pulse_50_250); } + } + + //unsigned int current_pulse = (pca9685_off[num] - pca9685_on[num]) & 4095; + if(pulse_50_250 == srv_pos[num]) return; //deja dans cette position + srv_pos[num] = pulse_50_250; + + // ce dispositif permet d'annuller les glitches + // si on veut une plus grande pulse on augmente OFF + // si on veut une plus petite pulse on augmente ON + unsigned int pulse_9685 = map(pulse_50_250, 50, 250, 102, 512); + if(pulse_50_250 >= srv_start[num]) pca9685_off[num] = (pca9685_on[num] + pulse_9685) & 4095; + else pca9685_on[num] = (pca9685_off[num] - pulse_9685) & 4095; + + set_pca9685_on_off(i2c_adr, out, pca9685_on[num], pca9685_off[num]); + + if(pulse_50_250 == srv_target[num]) srv_status[num] = 2; // init=0, servo=1, update=0 +} + + +// mise a jour d'une sortie pca9685 (avec variation lente des servos) +void pca9685_update_one(void) +{ + static byte num = IO_PCA9685_OUT_NB; + + num ++; + if(num >= IO_PCA9685_OUT_NB) num = 0; + + if(i2c_pca9685_online[num / 16] == 0) // PCA9685 module is not present + { + num += 16; + return; + } + + if((srv_status[num] & 1) == 0) return; // update only + + if((srv_status[num] & 2) == 0) update_pwm(num); else update_servo(num); +} +#endif // PCA9685_NB + + +// speed = time (in ms) to make the pulse changing by 1ms (90°) +// 0 = deactivated (max) +// 100 = 100ms, saved 1 +// 1000 = 1s, saved 10 +// 10000 = 10s, saved 100 +void user_pca9685_servo_speed(byte num, unsigned int speed) +{ +#if PCA9685_NB >= 1 + if(num >= IO_PCA9685_OUT_NB) return; + + if(speed > 10000) speed = 10000; + + srv_speed[num] = speed / 100; //1=100ms +#endif +} + + +void user_pca9685_pwm_0_100(byte num, byte pwm) +{ +#if PCA9685_NB >= 1 + unsigned int off; + + if(num >= IO_PCA9685_OUT_NB) return; + + if(srv_status[num] & 0x82) // power up or was servo + { + srv_start[num] = pwm; + srv_target[num] = pwm; + srv_status[num] = 4 + 1; // init + update + return; + } + + srv_start[num] = srv_target[num]; // on laisse, mais on pourrait prendre le on/off + srv_target[num] = pwm; + srv_millis_init[num] = millis(); + + srv_status[num] = 1; // update +#endif +} + + +void user_pca9685_servo_500_2500(byte num, unsigned int val) +{ +#if PCA9685_NB >= 1 + if(num >= IO_PCA9685_OUT_NB) return; + + if(val < 500 || val > 2500) return; + byte pulse_50_250 = val / 10; + + if((srv_status[num] & 2) == 0) // etat en pwm; passe en servo + { + srv_target[num] = pulse_50_250; + srv_pos[num] = pulse_50_250; + srv_status[num] = 4 + 2 + 1; // force + servo + update + return; + } + + if(pulse_50_250 == srv_pos[num]) return; + + if(srv_speed[num] == 0) + { + srv_target[num] = pulse_50_250; + srv_start[num] = srv_pos[num]; + srv_status[num] = 2 + 1; // servo + update (pas de force pour ne pas faire de glitch) + return; + } + + srv_millis_init[num] = millis(); + srv_start[num] = srv_pos[num]; + srv_target[num] = pulse_50_250; + srv_status[num] = 2 + 1; // servo + update +#endif +} + + +byte user_is_servo_in_position(byte num) +{ +#if PCA9685_NB >= 1 + if(num >= IO_PCA9685_OUT_NB) return 0; + return srv_status[num] & 1; +#else + return 0; +#endif +} + + + +//============================================================================================== +// onboard servos & PWM +//============================================================================================== + +#define ONBOARD_SRV_NB ARDUINO_SRV_NB + +#if ONBOARD_SRV_NB >= 1 + +#include + +Servo servos[ONBOARD_SRV_NB]; + +byte onsrv_start[ONBOARD_SRV_NB]; +byte onsrv_target[ONBOARD_SRV_NB]; +byte onsrv_pos[ONBOARD_SRV_NB]; +unsigned int onsrv_millis_init[ONBOARD_SRV_NB]; +byte onsrv_status[ONBOARD_SRV_NB]; // bit2=first bit1=force bit0=update +byte onsrv_speed[ONBOARD_SRV_NB]; + +void onboard_servo_init(void) +{ + for(int i = 0; i < ONBOARD_SRV_NB; i++) + { + servos[i].attach(arduino_srv_pins[i]); + onsrv_status[i] = 4; // first + } +} + + +void onboard_servo_update(byte ind) +{ + //byte num = arduino_srv_pins[ind]; + + if(onsrv_status[ind] & 2) // force + { + byte val = onsrv_pos[ind]; + byte pulse_180 = map(val, 50, 250, 0, 180); + servos[ind].write(pulse_180); + //if(num==4) { Serial.print("P180="); Serial.print(pulse_180); } + onsrv_status[ind] = 0; + return; + } + + if((onsrv_status[ind] & 1) == 0) return; + + unsigned int duration = (unsigned int) millis() - onsrv_millis_init[ind]; + //if(num==4) { Serial.print("duration="); Serial.print(duration); } + + unsigned int max_duration; // ex: speed=100 (10s) 0->+180=1ms=100 --> 10s + if(onsrv_target[ind] >= onsrv_start[ind]) max_duration = onsrv_speed[ind] * (onsrv_target[ind] - onsrv_start[ind]); + else max_duration = onsrv_speed[ind] * (onsrv_start[ind] - onsrv_target[ind]); + //if(num==4) { Serial.print("/"); Serial.print(max_duration); } + + if(duration > max_duration) duration = max_duration; + byte pulse_50_250 = map(duration, 0, max_duration, onsrv_start[ind], onsrv_target[ind]); + //if(num==4) { Serial.print("-->pulse="); Serial.println(pulse_50_250); } + + if(pulse_50_250 == onsrv_pos[ind]) return; //bonne position + + byte pulse_180 = map(pulse_50_250, 50, 250, 0, 180); + servos[ind].write(pulse_180); + //if(num==4) { Serial.print("p180="); Serial.print(pulse_180); Serial.print("/"); Serial.print(onsrv_target[ind]); } + + onsrv_pos[ind] = pulse_50_250; + + if(pulse_50_250 == onsrv_target[ind]) onsrv_status[ind] = 0; +} + + +void onboard_servo_update_one(void) +{ + static byte cpt = 96; + + cpt++; + if(cpt > 96) cpt = 0; // pour faire comme pca9685 + if(cpt < ONBOARD_SRV_NB) onboard_servo_update(cpt); +} +#endif + + +void user_arduino_pwm_0_100(byte num, byte val) //UU: mettre la liste et les timers +{ + analogWrite(num, map(val, 0, 100, 0, 255)); +} + + +// no fading on onboard PWM because else need to create variables for each pin +// speed = time (in ms) to make the pulse changing by 1ms (90°) +// 0 = deactivated (max) +// 100 = 100ms, saved 1 +// 1000 = 1s, saved 10 +// 10000 = 10s, saved 100 +void user_arduino_servo_speed(byte num, unsigned int speed) +{ +#if ONBOARD_SRV_NB >= 1 + int ind = -1; + for(int i = 0; i < ONBOARD_SRV_NB; i++) if(arduino_srv_pins[i] == num) ind = i; + if(ind == -1) return; + + if(speed > 10000) speed = 10000; + + onsrv_speed[ind] = speed / 100; //1=100ms +#endif +} + + +void user_arduino_servo_500_2500(byte num, unsigned int val) +{ +#if ONBOARD_SRV_NB >= 1 + int ind = -1; + for(int i = 0; i < ONBOARD_SRV_NB; i++) if(arduino_srv_pins[i] == num) ind = i; + if(ind == -1) return; + + if(val<500 || val>2500) return; + + val = val / 10; + + if((onsrv_status[ind] & 4) || onsrv_speed[ind]==0) + { + onsrv_pos[ind] = val; + onsrv_status[ind] = 2 + 1; // force update + return; + } + + if(val == onsrv_pos[ind]) return; + + onsrv_millis_init[ind] = millis(); + onsrv_start[ind] = onsrv_pos[ind]; + onsrv_target[ind] = val; + onsrv_status[ind] = 1; // update +#endif +} + + +byte user_is_arduino_servo_in_position(byte num) +{ +#if ONBOARD_SRV_NB >= 1 + int ind = -1; + for(int i = 0; i < ONBOARD_SRV_NB; i++) if(arduino_srv_pins[i] == num) ind = i; + if(ind == -1) return 0; + + return onsrv_status[ind] & 1; +#endif +#if ONBOARD_SRV_NB == 0 + return 0; +#endif +} + + + +//============================================================================================== +// Aiguillages multiplexes +//============================================================================================== + +byte mux_aig_cmd[MUX_AIG_NB]; //0=direct, 1=devie +byte mux_aig_pos[MUX_AIG_NB]; //0=direct, 1=devie, 2=non initialise +byte mux_aig_pulse[MUX_AIG_NB]; //duree de la commande (1=125ms ...) +byte mux_aig_num = 255; // aiguillage courant +byte mux_aig_next_pos = 0; //position a atteindre +byte mux_aig_dcpt = 0; // decompteur +byte mux_aig_init_dcpt = 16; // attente 2s au demarrage avant de commander les aiguillages + + +// demande de mettre l'aiguillage num en position pos (0=direct, 1=devie) +void user_cmd_mux_aig(byte num, byte pos) +{ + if(num >= MUX_AIG_NB) return; + + mux_aig_cmd[num] = pos; +} + +// regle la duree de l'impulsion de commande en ms 125-60000ms (l'impulsion sera un multiple de 125ms) +void user_define_mux_aig_pulse_ms(byte num, unsigned int pulse) +{ + byte p = pulse / 125; + if(p == 0) p = 1; + + if(num < MUX_AIG_NB) mux_aig_pulse[num] = p; + + if(num == 255) for(byte i = 0; i < MUX_AIG_NB; i++) mux_aig_pulse[i] = p; + + return; +} + +void mux_aig_maj(void) +{ + if(mux_aig_init_dcpt) + { + mux_aig_init_dcpt--; + return; + } + + if(mux_aig_dcpt) + { + mux_aig_dcpt--; + if(mux_aig_dcpt) return; + user_notify_stop_mux_aig(mux_aig_num, mux_aig_next_pos); + mux_aig_pos[mux_aig_num] = mux_aig_next_pos; + } + + for(byte i=0; i < MUX_AIG_NB; i++) + { + mux_aig_num++; if(mux_aig_num >= MUX_AIG_NB) mux_aig_num = 0; + + if(mux_aig_cmd[mux_aig_num] != mux_aig_pos[mux_aig_num]) + { + mux_aig_next_pos = mux_aig_cmd[mux_aig_num]; + user_notify_start_mux_aig(mux_aig_num, mux_aig_next_pos); + mux_aig_dcpt = mux_aig_pulse[mux_aig_num]; + break; + } + } +} + +void mux_aig_init(void) +{ + for(byte i = 0; i < MUX_AIG_NB; i++) + { + mux_aig_cmd[i] = 0; // direct + mux_aig_pos[i] = 2; // non initialise + mux_aig_pulse[i] = 16; // 125 ms + } +} + + + +//============================================================================================== +// DCC decoder engine part +//============================================================================================== + +// remenber to use volatile when variable is shared between main and isr +volatile unsigned int actMicros, lastMicros, bitMicros; +volatile byte in_ext_irq = 0; +volatile byte last_bit_val = 0; +volatile byte preambule_state = 1; +volatile byte preambule_len = 0; +volatile byte bit_phase = 0; +volatile byte bit_nb = 0; +volatile byte byte_val = 0; +volatile byte dat[4]; //including xor +volatile byte dat_len = 0; +volatile byte skip_sep = 0; + +volatile byte msg[4]; +volatile byte msg_len = 0; +volatile byte msg_ok; + +volatile byte err_preambule = 0; +volatile byte err_same = 0; +volatile byte err_nested_it = 0; +volatile byte err_short = 0; +volatile byte err_pkt_long = 0; +volatile byte err_pkt_short = 0; +byte err_checksum = 0; +byte dbg_pkt3 = 0; +byte dbg_pkt4 = 0; +byte dbg_pkt_idle = 0; + +inline void process_bitval(byte bit_val) +{ + if(preambule_state) + { + if(bit_val) { preambule_len++; return; } + + if(preambule_len < 18) { preambule_len = 0; err_preambule++; return; } //waiting at last 9 x "1" symbol + + preambule_len = 0; + preambule_state = 0; + bit_phase = 0; + bit_nb = 0; + dat_len = 0; + skip_sep = 1; + return; + } + + bit_phase ^= 1; + if((bit_phase & 1)==0) return; + if(bit_val != last_bit_val) { preambule_state = 1; err_same++; return; } + + if(skip_sep) { skip_sep=0; return; } //skip the first 0 separator beetween preambule and first byte + +#if NEO_NB >= 1 + // To update, neopixels, we need to stop interrupts for sometime (ie:2.1ms for 60 neopixels) + // During this time, decoder is not able to decode the DCC signal + // Since this accessory decoder do not take care about loco packets, as soon as we detect the begining of a loco packet + // we call neo_display() if an update was required by neo_to_be_updated. + // loco packet will last about 4ms, so we will be ready to sync on the following preambule + // Note this ughly method causes some errors in statistics + if(neo_to_be_updated) if(dat_len == 0) if(bit_nb == 0) if(bit_val == 0) + { + neo_display(); + //Serial.print("£"); + neo_to_be_updated = 0; + preambule_state = 1; + return; + } +#endif + + byte_val = (byte_val << 1) | bit_val; + bit_nb++; + + if(bit_nb < 8) return; + + if(bit_nb == 8) { dat[dat_len] = byte_val; dat_len++; return; } + + // bit == 9 + if(bit_val == 0) + { + bit_nb = 0; + if(dat_len >= 4) { err_pkt_long++; preambule_state = 1; } + return; + } + + msg_ok = 1; + msg_len = dat_len; + msg[0] = dat[0]; + msg[1] = dat[1]; + msg[2] = dat[2]; + msg[3] = dat[3]; + preambule_state = 1; + return; +} + +void ExternalInterruptHandler(void) +{ + byte bit_val = 0; + + if(in_ext_irq) { err_nested_it++; return; } //nested IT + actMicros = micros(); + bitMicros = actMicros - lastMicros; + if (bitMicros < 35) { err_short++; return; } //glitch + lastMicros = actMicros; + in_ext_irq = 1; + interrupts(); //to not block micros + if(bitMicros < 82) bit_val = 1; else bit_val=0; + process_bitval(bit_val); + last_bit_val = bit_val; + in_ext_irq = 0; + return; +} + +void print_err(void); + +// IDLE : 1111111111 0 11111111 0 00000000 0 EEEEEEEE 1 +// +// VIT 28 : 1111111111 0 0AAAAAAA 0 01DFSSSS 0 EEEEEEEE 1 +// VIT 127 : 1111111111 0 0AAAAAAA 0 00111111 0 DSSSSSSS 0 EEEEEEEE 1 +// +// FCT 0-4 : 1111111111 0 0AAAAAAA 0 100-FL-F4-F3-F2-F1 0 EEEEEEEE 1 +// FCT 5-8 : 1111111111 0 0AAAAAAA 0 1011-F8-F7-F6-F5 0 EEEEEEEE 1 +// FCT 9-12 : 1111111111 0 0AAAAAAA 0 1010-F12-F11-F10-F9 0 EEEEEEEE 1 +// FCT 13-20: 1111111111 0 0AAAAAAA 0 11011110 0 F20-F19-F18-F17-F16-F15-F14-F13 0 EEEEEEEE 1 +// FCT 11-28: 1111111111 0 0AAAAAAA 0 11011111 0 F28-F27-F26-F25-F24-F23-F22-F21 0 EEEEEEEE 1 +// +// ACC 1-511: 1111111111 0 10AAAAAA 0 1AAA1DDD 0 EEEEEEEE 1 // decodeur d'accessoire attention, les 3xAAA sont inverses, ils forment aussi le MSB adr0 est reserve le 1 entre AetD peut etre mis a 0 pour mettre la sortie a 0, mais normalement allumer la 0 eteint la 1 et inversement. ou alors remise a 0 automatique apres une pulse +// ACC1-2044: 1111111111 0 10AAAAAA 0 0AAA0AA1 0 000DDDDD 0 EEEEEEEE 1 // decodeur de sortie 32 valeurs pour chaque sorties (ex: signal multi aspects, servos, pwm ...) +// une explication accessible sur le DCC est dispo sur: http://trainminiature.discutforum.com/t12784-dcc-comment-ca-marche-place-a-la-technique + +unsigned int last_adr_1_511 = 0; +byte last_out_0_7; +byte last_state_0_1; + +void notify_dcc_acc(unsigned int adr_1_511, byte out_0_7, byte state_0_1) +{ + Serial.print(F(" BAS ACC pkt adr(1-511)=")); Serial.print(adr_1_511); + Serial.print(F(" out(0-7)=")); Serial.print(out_0_7); + Serial.print(F(" val(0-1)=")); Serial.print(state_0_1); + + byte duplicate = 0; + if((adr_1_511 == last_adr_1_511) && (out_0_7 == last_out_0_7) && (state_0_1 == last_state_0_1)) duplicate = 1; + last_adr_1_511 = adr_1_511; + last_out_0_7 = out_0_7; + last_state_0_1 = state_0_1; + Serial.print(F(" dup(0-1)=")); Serial.println(duplicate); + + user_notify_bas_acc_dec(adr_1_511, out_0_7, state_0_1, duplicate); +} + +unsigned int last_out_1_2044 = 0; +byte last_val_0_31; + +void notify_dcc_ext(unsigned int out_1_2044, byte val_0_31) +{ + Serial.print(F(" EXT ACC pkt out(1-2044)=")); Serial.print(out_1_2044); + Serial.print(F(" val(0-31)=")); Serial.print(val_0_31); + + byte duplicate = 0; + if((out_1_2044 == last_out_1_2044) && (val_0_31 == last_val_0_31)) duplicate = 1; + last_out_1_2044 = out_1_2044; + last_val_0_31 = val_0_31; + Serial.print(F(" dup(0-1)=")); Serial.println(duplicate); + + user_notify_ext_acc_dec(out_1_2044, val_0_31, duplicate); +} + +void process_packet(void) //to call at least each 5ms +{ + unsigned int adr_1_511; + byte out_0_7; + byte state_0_1; + unsigned int out_1_2044; + byte val_0_31; + + if(msg_ok == 0) return; + msg_ok = 0; + + Serial.print("#"); Serial.print(msg_len); + //print_err(); + + if(msg_len < 3) { err_pkt_short++; return; } + + Serial.print(" "); Serial.print(msg[0]); + Serial.print(" "); Serial.print(msg[1]); + Serial.print(" "); Serial.print(msg[2]); + + if(msg_len == 3) + { + if(msg[0] ^ msg[1] ^ msg[2]) { err_checksum++; return; } + dbg_pkt3++; + + // IDLE: 1111111111 0 11111111 0 00000000 0 EEEEEEEE 1 + if(msg[0] == 0xff && msg[1] == 0x00) + { + dbg_pkt_idle++; + } + + // ACC 1-511: 1111111111 0 10AAAAAA 0 1AAA1DDD 0 EEEEEEEE 1 + else if((msg[0] & 0xC0) == 0x80) + { + adr_1_511 = (((unsigned int)((msg[1] & 0x70) ^ 0x70)) << 2) | msg[0] & 0x3f; + out_0_7 = msg[1] & 7; + state_0_1 = (msg[1] >> 3) & 1; + + if(adr_1_511) notify_dcc_acc(adr_1_511, out_0_7, state_0_1); + } + + Serial.println(""); + return; + } + + Serial.print(" "); Serial.println(msg[3]); + + if(msg_len == 4) + { + // msg_len == 4 + if(msg[0] ^ msg[1] ^ msg[2] ^ msg[3]) { err_checksum++; return; } + dbg_pkt4++; + + // ACC1-2044: 1111111111 0 10AAAAAA 0 0AAA0AA1 0 000DDDDD 0 EEEEEEEE 1 // decodeur de sortie 32 valeurs pour chaque sorties (ex: signal multi aspects, servos, pwm ...) + if((msg[0] & 0xC0) == 0x80 && (msg[1] & 0b10001001) == 1 && (msg[2] & 0xE0) == 0) + { + adr_1_511 = (((unsigned int)((msg[1] & 0x70) ^ 0x70)) << 2) | msg[0] & 0x3f; + // OutputAddress = (((BoardAddress - 1) << 2 ) | TurnoutPairIndex) + 1 ; + // 1-511 + // 0-2040 +0..3 + 1 1-2044 + out_1_2044 = (((adr_1_511 - 1) << 2) | ((msg[1] >> 1) & 3)) + 1 ; + val_0_31 = msg[2] & 0x1F; + notify_dcc_ext(out_1_2044, val_0_31); + } + + Serial.println(""); + return; + } +} + +void print_err(void) +{ + Serial.print(F("PMerr_nested_it = ")); Serial.println(err_nested_it); + Serial.print(F("PMerr_short = ")); Serial.println(err_short); + Serial.print(F("PMerr_preambule = ")); Serial.println(err_preambule); + Serial.print(F("PMerr_same = ")); Serial.println(err_same); + Serial.print(F("PMerr_pkt_long = ")); Serial.println(err_pkt_long); + Serial.print(F("PMerr_pkt_short = ")); Serial.println(err_pkt_short); + Serial.print(F("PMerr_checksum = ")); Serial.println(err_checksum); + Serial.print(F("PMdbg_pkt3 = ")); Serial.println(dbg_pkt3); + Serial.print(F("PMdbg_pkt4 = ")); Serial.println(dbg_pkt4); + Serial.print(F("PMdbg_pkt_idle = ")); Serial.println(dbg_pkt_idle); +} + + + + +//============================================================================================== +// Serial commands +//============================================================================================== + +/* +Commandes: +- A taper dans le moniteur serie de l'environement Arduino +- Appuyer sur le buton send ou la touche entree pour envoyer la commande +- les commandes peuvent etre en minuscule ou majuscule +- OK est retourne si la commande est executee, ERR-cmd ou ERR-ard dans le cas contraire + +Les indications UNO valent aussi pour Mini et Nano + +H : pour l'aide + +B,, : simule la reception d'une commande pour un decodeur d'accessoire basique d'adresse adr_0_511 pour mettre la sortie 0_7 a la valeur 0_1 +ex: B10,3,1 + +E, : simule la reception d'une commande pour un decodeur d'accessoire etendu de sortie out_1_2044 a mettrec dans l'etat val_0_31 +ex: E20,16 + +R: affiche un rapport de statistiques du decodeur DCC (nombre de commandes, erreurs ...) + +U: fait clignoter la LED de la carte Arduino a 4Hz pendant 2s + +L, : met la LED led_0_255 des MAX7219/21 a la valeur val_0_7 +val_0_7 = 1 (si on) + 2 (si cli) + 4 (si inversion de phase) + +O,<0,1> : met la sortie out_3_19 de l'arduino UNO a la valeur val_0_1 (0=0V, 1=5V) +O,<0,1> : met la sortie out_3_69 de l'arduino MEGA a la valeur val_0_1 +sur UNO: A0-A5=14-19 +sur MEGA:A0-A15=54-69 + +S, : commande le servo servo_0_95 des PCA9685 avec des impulsions de 500_2500us +S, : commande le servo connecte sur la patte servo_103_169-100 de l'arduino +ex: S0,1500 servo sur le PCA9685 d'adresse 0 et sortie 0, impulsion de 1500us=1.5ms=neutre=0° +ex: S10,2500 servo sur le PCA9685 d'adresse 0 et sortie 10, impulsion de 2500us=2.5ms=neutre=+90° +ex: S95,500 servo sur le PCA9685 d'adresse 5 et sortie 15, impulsion de 500us=0.5ms=neutre=-90° +ex: S103,1500 servo sur la patte 3 de l'Arduino +ex: S114,1500 servo sur la patte A0 de l'Arduino UNO ou 14 de l'Arduino MEGA +ex: S169,1500 servo sur la patte A15 de l'Arduino MEGA + +T, : duree de deplacement en ms pour tourner de 90° pour le servo servo_0_95 des PCA9685 +T, : duree de deplacement en ms pour tourner de 90° pour le servo connecte sur la patte servo_103_169-100 de l'arduino +0 desactive le mouvement lent +ex: T0,0 desactivation de mouvement lent pour le servo sur le PCA9685 d'adresse 0 et sortie 0 +ex: T95,1000 regalge du mouvement lent a 1 sec / 90° pour le servo sur le PCA9685 d'adresse 5 et sortie 15 +ex: T105,10000 regalge du mouvement lent a 10 sec / 90° pour le servo sur la patte 5 de l'Arduino + +P, : commande la sortie pca9685_pwm_0_95 des PCA9685 avec une pwm a pwm_0_100% +P, : commande la sortie arduino_pwm_103_169-100 de l'Arduino avec une pwm a pwm_0_100% +P, : commande la sortie tlc5947_pwm_200_295-200 des TLC5947 avec une pwm a pwm_0_100% +ex: P0,25 sortie 0 du PCA9685 d'adresse 0 en mode PWM a 25% +ex: P17,50 sortie 1 du PCA9685 d'adresse 1 en mode PWM a 50% +ex: P103,60 sortie 3 de l'Arduino en mode PWM a 60% +ex: P200,70 sortie 0 du TLC5947 numero 0 a 70% +ex: P223,71 sortie 23 du TLC5947 numero 0 a 71% +ex: P224,70 sortie 0 du TLC5947 numero 1 a 72% +sur UNO seules les sorties suivantes fonctionnent en pwm: 3, 5, 6, 9, 10, 11 +sur MEGA seules les sorties suivantes fonctionnent en pwm: 3 - 12, 44 - 46 +si les servos sont utilises sur Arduino 9 et 10 ne fonctionnent pas en pwm + +T, : duree de variation en ms de la sortie pwm_0_95 des PCA9685 pour passer de 0 a 100% +0 desactive le fading (la variation lente) +Cette commande est la meme que les servos + +N,,, : met la couleur du neopixel neo_0_59 a rouge=r_0_255=0-255 vert=v_0_255=0-255 bleu=b_0_255=0-255 +ex: N0,255,0,0 rouge au max pour le neopixel 0 +ex: N1,0,128,0 vert a 50% pour le neopixel 1 +ex: N59,0,0,64 bleu a 25% pour le neopixel 59 +ex: N2,32,32,0 jaune (rouge+vert) a 12% (32/255) pour le neopixel 2 +ex: N3,255,255,255 blanc (rouge+vert+bleu) a 100% pour le neopixel 3 + +W, : ecrit la valeur val_0_255 dans l'EEPROM a l'adresse adr_0_1023 +G : lit et affiche la valeur qui se trouve dans l'EEPROM a l'adresse adr_0_1023 +ex: W12,100 +ex: G28 +sur MEGA l'adresse est de 0 a 4095 + +M,[,pulse] commande l'aiguillage multiplexe en position pos [change si besoin la duree de l'impulsion en ms] + +*/ + +void maj_debug_serial(void) +{ + static byte serial_order = ' '; + static byte serial_step = 0; + static unsigned int val; + static unsigned int cmd1, cmd2, cmd3, cmd4; + + while(Serial.available()) + { + int rcmd = Serial.read(); + if(rcmd < 0) return; + unsigned char cmd = (unsigned char)rcmd; + + if(cmd >= 'a' && cmd <='z') cmd += 'A' - 'a'; + if(cmd >= 'A' && cmd <='Z') { serial_order = cmd; serial_step = 0; val = 0; continue;} + + if(serial_order == ' ') break; + + if(cmd >='0' && cmd <='9') val = 10 * val + (cmd - '0'); + + if(cmd == ',' || cmd == '\n' || cmd == ';') + { + if(serial_step == 0) cmd1 = val; + if(serial_step == 1) cmd2 = val; + if(serial_step == 2) cmd3 = val; + if(serial_step == 3) cmd4 = val; + serial_step++; + val = 0; + } + + if(cmd == '\n' || cmd == ';') + { + char s[2]; s[0]=serial_order; s[1]='\0'; + Serial.print(s); + if(serial_order == 'H' || serial_order == 'U' || serial_order == 'R') serial_step = 0; + if(serial_step >= 1) { Serial.print(cmd1); } + if(serial_step >= 2) { Serial.print(","); Serial.print(cmd2); } + if(serial_step >= 3) { Serial.print(","); Serial.print(cmd3); } + if(serial_step >= 4) { Serial.print(","); Serial.print(cmd4); } + Serial.println(""); + + byte ok=2; + + if(serial_order == 'H') + { + Serial.println(F("H B511,7,1 E2044,31 Sn,500-2500 Tn,10000 ")); + Serial.println(F("Pn,100 Ln,7 On,1 Nn,255,255,255 U R")); + Serial.println(F("W1023,255 G1023 Mn,1[,ms]")); + ok = 1; + } + + else if(serial_order == 'B' && serial_step == 3) + { + if(cmd1 >=1 && cmd1 <=511 && cmd2 <=7 && cmd3 <=1) + { + notify_dcc_acc(cmd1, cmd2, cmd3); + ok = 1; + } + } + + else if(serial_order == 'E' && serial_step == 2) + { + if(cmd1 >=1 && cmd1 <=2044 && cmd2 <=31) + { + notify_dcc_ext(cmd1, cmd2); + ok = 1; + } + } + + else if(serial_order == 'L' && serial_step == 2) + { + if(cmd1 <= 255 && cmd2 <= 7) + { + user_max7219_led(cmd1, cmd2&1); + user_max7219_led_cli(cmd1, (cmd2>>1)&1); + user_max7219_led_pha(cmd1, (cmd2>>2)&1); + ok = 1; + } + } + + else if(serial_order == 'O' && serial_step == 2) + { + if(cmd2 <=1) //UNO:A0-A5=14-19 MEGA:A0-A15=54-69 + { + user_out(byte(cmd1), byte(cmd2)); + ok = 1; + } + } + + else if(serial_order == 'S' && serial_step == 2) + { + if(cmd1 < IO_PCA9685_OUT_NB && cmd2 >=500 && cmd2 <=2500) + { + user_pca9685_servo_500_2500(cmd1, cmd2); + ok = 1; + } + + if(cmd1 >= 100 && cmd2 >=500 && cmd2 <=2500) + { + user_arduino_servo_500_2500(cmd1-100, cmd2); + ok = 1; + } + } + + else if(serial_order == 'T' && serial_step == 2) + { + if(cmd1 < IO_PCA9685_OUT_NB && cmd2 <=10000) + { + user_pca9685_servo_speed(cmd1, cmd2); + ok = 1; + } + + if(cmd1 >= 100 && cmd2 <=10000) + { + user_arduino_servo_speed(cmd1-100, cmd2); + ok = 1; + } + } + + else if(serial_order == 'P' && serial_step == 2) + { + if(cmd1 < IO_PCA9685_OUT_NB && cmd2 <=100) + { + user_pca9685_pwm_0_100(cmd1, cmd2); + ok = 1; + } + + if(cmd1 >= 100 && cmd1 < 200 && cmd2 <=100) + { + user_arduino_pwm_0_100(cmd1-100, cmd2); + ok = 1; + } + + if(cmd1 >= 200 && cmd1 < 400 && cmd2 <=100) + { + user_tlc5947_pwmled_0_100(cmd1-200, cmd2); + ok = 1; + } + } + + else if(serial_order == 'N' && serial_step == 4) + { + if(cmd1 < NEO_NB && cmd2 <= 255 && cmd3 <= 255 && cmd4 <= 255) + { + user_neo(cmd1, cmd2, cmd3, cmd4); + ok = 1; + } + } + + else if(serial_order == 'U') + { + user_blink(); ok = 1; + } + + else if(serial_order == 'R') + { + print_err(); ok = 1; + } + + else if(serial_order == 'W' && serial_step == 2) + { + if(cmd1 < EEPROM.length() && cmd2 <= 255) + { + EEPROM.write(cmd1, cmd2); + ok = 1; + } + } + + else if(serial_order == 'G' && serial_step == 1) + { + if(cmd1 < EEPROM.length()) + { + Serial.print(F("dat=")); Serial.println(EEPROM.read(cmd1)); + ok = 1; + } + } + + else if(serial_order == 'M' && serial_step <= 3) + { + if(cmd1 < MUX_AIG_NB && cmd2 <= 1) + { + if(serial_step == 3) user_define_mux_aig_pulse_ms(cmd1, cmd3); + user_cmd_mux_aig(cmd1, cmd2); + ok = 1; + } + } + + else ok = 0; + + if(ok==0) Serial.println(F("ERR-cmd")); + else if(ok == 2) Serial.println(F("ERR-arg")); + else Serial.println("OK"); + + serial_order = ' '; + serial_step = 0; + } + } +} + + +//============================================================================================== +// MAIN part +//============================================================================================== + +byte wait_125ms = 0; // 125ms flag to update MAX & tempos +byte rx = 0; // rx indicates packet matching + +void user_blink(void) +{ + rx = 16; // * 125ms = 2sec +} + +//#define TEMPO_NB 80 //mettre un multiple de 8 // definit dans la partie utilisateur maintenant +unsigned int tempo_dcpt[TEMPO_NB]; +byte tempo_flag[TEMPO_NB / 8]; + + +void user_tempo_start(byte num_tempo, unsigned int duration_ms) +{ + if(num_tempo >= TEMPO_NB) return; + + tempo_dcpt[num_tempo] = duration_ms / 125; // 125ms increment + + if(duration_ms) tempo_flag[num_tempo / 8] |= (1 << (num_tempo & 7)); + else tempo_flag[num_tempo / 8] &= (0xff ^ (1 << (num_tempo & 7))); +} + + +void tempo_maj(void) +{ + byte i, j, num; + + for(j = 0; j < (TEMPO_NB / 8); j++) if(tempo_flag[j]) + for(i = 0; i < 8; i++) if(tempo_dcpt[i]) + { + num = 8*j + i; + tempo_dcpt[num]--; + if(tempo_dcpt[num] == 0) + { + tempo_flag[num / 8] &= (0xff ^ (1 << (num & 7))); + user_notify_tempo_end(num); + } + } + if(rx) rx--; +} + + +void user_out(byte num, byte val) +{ +#if defined(__AVR_ATmega2560__) //in arduino lib Dmax is before A0, so there is no output after Amax + if(num > A15) return; +#else + if(num > A5) return; +#endif + + if(num <= 1) return; // Tx & Rx + if(num == PIN_DCC) return; + if(num == PIN_LED) return; + +#if MAX7219or21_NB >= 1 + if(num == CLK_PIN || num == DAT_PIN || num == LLED_PIN) return; +#endif + +#if TLC5947_NB >= 1 + if(num == CLK_PIN || num == DAT_PIN || num == L5947_PIN) return; +#endif + +#if PCA9685_NB >= 1 + if(num == SDA_PIN || num == SCK_PIN) return; +#endif + +#if NEO_NB >= 1 + if(num == NEO_PIN) return; +#endif + + if(val) digitalWrite(num, HIGH); + else digitalWrite(num, LOW); +} + + +unsigned int time0; //normaly unsigned long, but unsigned int is suffiscient +//byte t0_delay; +//byte flag_delay = 0; + +void setup(void) +{ + // Serial output for debugging + Serial.begin(115200); + Serial.println(F("D18 DCC Accessory Decoder v20200306a")); + + // configure pins + // D13-2-1-0 not in the list because D13 is led, D2 DCC in, D1 TX, D0 RX + // les sorties sont mises a 0 a l'initialisation, pour changer cela et les mettre a 1, rajouter digitalWrite(numero de la pin, HIGH); + pinMode(12, OUTPUT); + pinMode(11, OUTPUT); + pinMode(10, OUTPUT); + pinMode(9, OUTPUT); + pinMode(8, OUTPUT); + + pinMode(7, OUTPUT); + pinMode(6, OUTPUT); + pinMode(5, OUTPUT); + pinMode(4, OUTPUT); + pinMode(3, OUTPUT); + + //on Nano there is also A6 & A7 but they cannot be used as output + pinMode(A0, OUTPUT); + pinMode(A1, OUTPUT); + pinMode(A2, OUTPUT); + pinMode(A3, OUTPUT); + pinMode(A4, OUTPUT); + pinMode(A5, OUTPUT); + + #if defined(__AVR_ATmega2560__) + Serial.println("AT_MEGA detected"); + pinMode(14, OUTPUT); + pinMode(15, OUTPUT); + pinMode(16, OUTPUT); + pinMode(17, OUTPUT); + pinMode(18, OUTPUT); + pinMode(19, OUTPUT); + pinMode(20, OUTPUT); + pinMode(21, OUTPUT); + + pinMode(22, OUTPUT); + pinMode(23, OUTPUT); + pinMode(24, OUTPUT); + pinMode(25, OUTPUT); + pinMode(26, OUTPUT); + pinMode(27, OUTPUT); + pinMode(28, OUTPUT); + pinMode(29, OUTPUT); + + pinMode(30, OUTPUT); + pinMode(31, OUTPUT); + pinMode(32, OUTPUT); + pinMode(33, OUTPUT); + pinMode(34, OUTPUT); + pinMode(35, OUTPUT); + pinMode(36, OUTPUT); + pinMode(37, OUTPUT); + + pinMode(38, OUTPUT); + pinMode(39, OUTPUT); + pinMode(40, OUTPUT); + pinMode(41, OUTPUT); + pinMode(42, OUTPUT); + pinMode(43, OUTPUT); + pinMode(44, OUTPUT); + pinMode(45, OUTPUT); + + pinMode(46, OUTPUT); + pinMode(47, OUTPUT); + pinMode(48, OUTPUT); + pinMode(49, OUTPUT); + pinMode(50, OUTPUT); + pinMode(51, OUTPUT); + pinMode(52, OUTPUT); + pinMode(53, OUTPUT); + + pinMode(A6, OUTPUT); + pinMode(A7, OUTPUT); + + pinMode(A8, OUTPUT); + pinMode(A9, OUTPUT); + pinMode(A10, OUTPUT); + pinMode(A11, OUTPUT); + pinMode(A12, OUTPUT); + pinMode(A13, OUTPUT); + pinMode(A14, OUTPUT); + pinMode(A15, OUTPUT); + #endif + + pinMode(PIN_LED, OUTPUT); + + #if MAX7219or21_NB >= 1 + led_init(); + #endif + #if PCA9685_NB >= 1 + i2c_init(); + pca9685_init(); + #endif + #if NEO_NB >= 1 + neo_init(); + #endif + #if ONBOARD_SRV_NB >= 1 + onboard_servo_init(); + #endif + #if TLC5947_NB >= 1 + void init_tlc5947(); + #endif + + #if MUX_AIG_NB >= 1 + mux_aig_init(); + #endif + + user_init(); + + #if NEO_NB >= 1 + neo_display(); + #endif + + pinMode(PIN_DCC, INPUT); + digitalWrite(PIN_DCC, HIGH); //enable pull-up //or: pinMode(PIN_DCC, INPUT_PULLUP); + attachInterrupt( digitalPinToInterrupt(PIN_DCC), ExternalInterruptHandler, CHANGE); //on UNO: IT 0 = ExtInt Pin 2 or use digitalPinToInterrupt(2) + + time0 = millis(); +} + + +void fast_loop(void) +{ + // gestion du nouveau paquet + process_packet(); //A appeller au moins toutes les 5ms pour ne pas manquer de packets + + // mise a jour des sorties PWM et servos + // dans fast_loop pour eviter les sacades sur les servos et en fading pwm + #if PCA9685_NB >= 1 + pca9685_update_one(); + #endif + + #if ONBOARD_SRV_NB >= 1 + onboard_servo_update_one(); + #endif +} + + +void loop(void) +{ + static byte cpt_125ms = 0; + + unsigned int time1 = millis(); //unsigned int is suffiscient + + fast_loop(); + + //if(((byte)time1 - t0_delay) > 25) // set flag each 25ms + //{ + // flag_delay = 1; + // t0_delay = time1; + //} + + if((time1 - time0) < 125) return; + // on arrive ici toute les 125ms + //Serial.println(time1 - time0); + time0 = time1; + cpt_125ms++; + + // gestion des tempos + tempo_maj(); + + // tache de fond de l'utilisateur + user_125ms(); + + // gestion des aiguillages multiplexes + #if MUX_AIG_NB >= 1 + mux_aig_maj(); + #endif + + // clignotement de la LED a 1Hz (ou 4Hz pendant 2s lorsque l'on recoit un packet) + if(rx == 0) { if(cpt_125ms & 4) digitalWrite(PIN_LED, HIGH); else digitalWrite(PIN_LED, LOW); } + else { if(cpt_125ms & 1) digitalWrite(PIN_LED, HIGH); else digitalWrite(PIN_LED, LOW); } + + // mise a jour des LEDs + #if MAX7219or21_NB >= 1 + led_cpt++; if(led_cpt == 8) led_cpt=0; // 0 - 7 used to blink leds (off 0-3, on 4-7) + led_maj(0); fast_loop(); // total led_maj lasts 5ms, so we cut it to call process_packet() between; + led_maj(1); fast_loop(); + #endif + + // mise a jour + #if TLC5947_NB >= 1 + update_tlc5947(); + #endif + + // demande de mise a jour des neopixels + #if NEO_NB >= 1 + neo_maj(); + #endif + + // debug + maj_debug_serial(); + //Serial.print(pca9685_on[3]);Serial.print("-");Serial.println(pca9685_off[3]); +}