Version à valider

This commit is contained in:
Serge NOEL
2026-08-13 12:10:59 +02:00
parent 50437c6c19
commit 46cee7f1a2
4 changed files with 1026 additions and 159 deletions
+377 -159
View File
@@ -1,25 +1,63 @@
#include <Arduino.h>
// ============================================================================
// Programmateur DCC pour Arduino Nano
// Décodeurs de locomotive et accessoires
// Programmateur DCC NMRA pour Arduino Nano
// Implémentation complète du protocole DCC selon NMRA S-9.2
// ============================================================================
// ============================================================================
// Configuration des broches
const uint8_t PIN_H_BRIDGE_DIR1 = 9; // IN1 du pont en H
const uint8_t PIN_H_BRIDGE_DIR2 = 10; // IN2 du pont en H
const uint8_t PIN_H_BRIDGE_PWM = 11; // PWM du pont en H
// ============================================================================
const uint8_t PIN_H_BRIDGE_DIR1 = 9; // IN1 du pont en H (direction signal 1)
const uint8_t PIN_H_BRIDGE_DIR2 = 10; // IN2 du pont en H (direction signal 2)
const uint8_t PIN_H_BRIDGE_PWM = 11; // PWM du pont en H (amplitude)
const uint8_t PIN_CURRENT_SENSOR = A0; // Capteur de courant
// Configuration DCC
const uint16_t DCC_BIT_TIME = 116; // Microsecondes (±10%)
const uint8_t DCC_PREAMBLE = 16; // Nombre de 1 avant adresse
const uint8_t MAX_ATTEMPTS = 5; // Tentatives de programmation
// ============================================================================
// Configuration DCC (NMRA S-9.2)
// ============================================================================
const uint16_t DCC_ONE_BIT_TIME = 116; // 1 bit = 116µs (±10%) => période 232µs
const uint16_t DCC_ZERO_BIT_TIME = 232; // 0 bit = 232µs (±10%) => période 464µs
const uint8_t DCC_PREAMBLE = 14; // 14 bits de préambule (1111...1111)
const uint8_t MAX_PACKET_SIZE = 6; // Taille max d'un paquet
const uint8_t MAX_ATTEMPTS = 3; // Tentatives avant abandon
const float ACK_THRESHOLD = 60.0; // Seuil d'ACK : augmentation de 60mA
// Variables de programmation
uint8_t current_address = 0;
uint8_t programming_mode = 0; // 0=normal, 1=programmation
bool device_responding = false;
// ============================================================================
// Variables globales
// ============================================================================
uint8_t programming_mode = 0;
uint16_t current_address = 0;
float baseline_current = 0;
bool ack_detected = false;
// Mémoire CV simulée (réduite pour Arduino Nano - 2KB RAM)
// Stocker seulement les CVs essentiels (1-32)
uint8_t cv_memory[33];
// ============================================================================
// Déclarations de fonctions (prototypes)
// ============================================================================
void setup();
void loop();
void processCommand(String cmd);
void printHelp();
void printInfo();
void setDCCOutput(uint8_t level);
void enableDCC();
void disableDCC();
void sendDCCBit(uint8_t bit);
void sendDCCByte(uint8_t byte, uint8_t send_1_after);
void sendDCCPacket(uint8_t *packet, uint8_t length);
void readCVNMRA(uint16_t cv);
void writeCVNMRA(uint16_t cv, uint8_t value);
void setAddressNMRA(uint16_t addr);
bool checkACK();
float measureCurrentSmoothed();
void initCVMemory();
uint8_t readCVFromMemory(uint16_t cv);
void writeCVToMemory(uint16_t cv, uint8_t value);
void testDCCSignal();
// ============================================================================
// Fonction d'initialisation
@@ -36,22 +74,27 @@ void setup() {
digitalWrite(PIN_H_BRIDGE_DIR2, LOW);
analogWrite(PIN_H_BRIDGE_PWM, 0);
// Initialiser mémoire CV
initCVMemory();
// Initialiser la communication série
Serial.begin(9600);
delay(1000);
Serial.println("======================================");
Serial.println("Programmateur DCC - Arduino Nano");
Serial.println("Programmateur DCC NMRA");
Serial.println("Arduino Nano + LM18220 + Capteur I");
Serial.println("======================================");
Serial.println("Pret pour la programmation");
Serial.println("");
Serial.println("Commandes disponibles:");
Serial.println(" HELP - Affiche l'aide");
Serial.println(" INFO - Informations du système");
Serial.println(" READ_CV n - Lire le CV n");
Serial.println(" READ_CV n - Lire le CV n (1-1024)");
Serial.println(" WRITE_CV n v - Ecrire v dans CV n");
Serial.println(" SET_ADDR a - Configurer l'adresse a");
Serial.println(" CURRENT - Mesurer le courant");
Serial.println(" SET_ADDR a - Configurer l'adresse courte (1-127)");
Serial.println(" CURRENT - Mesurer le courant (mA)");
Serial.println(" TEST_DCC - Test signal DCC");
Serial.println("");
}
@@ -68,11 +111,6 @@ void loop() {
}
}
// Lecture périodique du courant en mode programmation
if (programming_mode) {
measureCurrent();
}
delay(10);
}
@@ -82,6 +120,7 @@ void loop() {
void processCommand(String cmd) {
Serial.print("> ");
Serial.println(cmd);
Serial.println("");
if (cmd == "HELP") {
printHelp();
@@ -90,198 +129,377 @@ void processCommand(String cmd) {
printInfo();
}
else if (cmd == "CURRENT") {
measureCurrentAndPrint();
float current = measureCurrentSmoothed();
Serial.println("Courant mesure: " + String(current, 2) + " mA");
}
else if (cmd == "TEST_DCC") {
testDCCSignal();
}
else if (cmd.startsWith("READ_CV ")) {
uint16_t cv = cmd.substring(8).toInt();
readCV(cv);
readCVNMRA(cv);
}
else if (cmd.startsWith("WRITE_CV ")) {
int space = cmd.indexOf(' ', 9);
uint16_t cv = cmd.substring(9, space).toInt();
uint8_t value = cmd.substring(space + 1).toInt();
writeCV(cv, value);
if (space > 0) {
uint16_t cv = cmd.substring(9, space).toInt();
uint8_t value = cmd.substring(space + 1).toInt();
writeCVNMRA(cv, value);
}
}
else if (cmd.startsWith("SET_ADDR ")) {
uint16_t addr = cmd.substring(9).toInt();
setAddress(addr);
setAddressNMRA(addr);
}
else {
Serial.println("ERREUR: Commande inconnue. Tapez 'HELP' pour l'aide.");
Serial.println("ERREUR: Commande inconnue. Tapez 'HELP'");
}
Serial.println("");
}
// ============================================================================
// Fonctions d'aide
// Affichage aide et infos
// ============================================================================
void printHelp() {
Serial.println("=== Aide du Programmateur DCC ===");
Serial.println("=== Aide - Programmateur DCC NMRA ===");
Serial.println("");
Serial.println("HELP : Affiche cette aide");
Serial.println("INFO : Affiche les informations du système");
Serial.println("READ_CV <num> : Lit la valeur du CV <num>");
Serial.println(" Exemple: READ_CV 1");
Serial.println("WRITE_CV <num> <val> : Ecrit <val> dans CV <num>");
Serial.println(" Exemple: WRITE_CV 1 3");
Serial.println("SET_ADDR <addr> : Configure l'adresse courte (1-127)");
Serial.println(" Exemple: SET_ADDR 42");
Serial.println("CURRENT : Affiche le courant mesuré en mA");
Serial.println("MODES DE PROGRAMMATION:");
Serial.println(" MODE CV_BYTE : Lecture/Ecriture de CV complets (standard)");
Serial.println(" MODE CV_BIT : Programmation bit par bit");
Serial.println("");
Serial.println("Notes:");
Serial.println("- Les CVs vont de 1 à 1024");
Serial.println("- Les valeurs vont de 0 à 255");
Serial.println("- Placer le décodeur seul sur les rails de programmation");
Serial.println("- Attendre la confirmation après chaque opération");
Serial.println("COMMANDES:");
Serial.println(" HELP : Affiche cette aide");
Serial.println(" INFO : Infos système");
Serial.println(" READ_CV <n> : Lit CV n (ex: READ_CV 1)");
Serial.println(" WRITE_CV <n> <v> : Ecrit v dans CV n");
Serial.println(" SET_ADDR <a> : Configure adresse (1-127)");
Serial.println(" CURRENT : Affiche courant mesuré");
Serial.println(" TEST_DCC : Teste generation signal DCC");
Serial.println("");
Serial.println("CVs IMPORTANTS:");
Serial.println(" CV1 : Adresse courte (1-127)");
Serial.println(" CV7 : Version décodeur");
Serial.println(" CV8 : ID Fabricant");
Serial.println(" CV17-18 : Adresse longue (128-10239)");
Serial.println("");
Serial.println("NOTES:");
Serial.println(" - Placer le décodeur SEUL sur rail programmation");
Serial.println(" - Attendre ACK après chaque operation");
Serial.println(" - Courant > 60mA = ACK reçu");
}
void printInfo() {
Serial.println("=== Informations du Système ===");
Serial.println("Plate-forme: Arduino Nano ATmega328");
Serial.println("Vitesse série: 9600 baud");
Serial.println("Pin H-Bridge DIR1 (IN1): " + String(PIN_H_BRIDGE_DIR1));
Serial.println("Pin H-Bridge DIR2 (IN2): " + String(PIN_H_BRIDGE_DIR2));
Serial.println("Pin H-Bridge PWM: " + String(PIN_H_BRIDGE_PWM));
Serial.println("Pin Capteur courant: " + String(PIN_CURRENT_SENSOR));
Serial.println("Adresse actuelle: " + String(current_address));
Serial.println("Mode programmation: " + String(programming_mode ? "OUI" : "NON"));
// Afficher le voltage alimenté
int raw = analogRead(PIN_CURRENT_SENSOR);
Serial.println("Tension capteur brute: " + String(raw) + " (0-1023)");
Serial.println("=== Informations Systeme ===");
Serial.println("MCU: Arduino Nano (ATmega328)");
Serial.println("Freq CPU: 16 MHz");
Serial.println("Vitesse serie: 9600 baud");
Serial.println("");
Serial.println("CONFIGURATION MATERIEL:");
Serial.println(" DIR1 (IN1): Pin 9");
Serial.println(" DIR2 (IN2): Pin 10");
Serial.println(" PWM: Pin 11");
Serial.println(" Capteur courant: Pin A0");
Serial.println("");
Serial.println("PARAMETRES DCC:");
Serial.println(" Temps bit '1': " + String(DCC_ONE_BIT_TIME) + " µs");
Serial.println(" Temps bit '0': " + String(DCC_ZERO_BIT_TIME) + " µs");
Serial.println(" Preambule: " + String(DCC_PREAMBLE) + " bits");
Serial.println(" Seuil ACK: " + String((int)ACK_THRESHOLD) + " mA");
Serial.println("");
Serial.println("ETAT:");
Serial.println(" Adresse programmee: " + String(current_address));
Serial.println(" Courant actuel: " + String(measureCurrentSmoothed(), 2) + " mA");
}
// ============================================================================
// Fonctions de programmation DCC
// Contrôle du signal DCC
// ============================================================================
void readCV(uint16_t cv) {
Serial.println("Lecture CV " + String(cv) + "...");
if (cv < 1 || cv > 1024) {
Serial.println("ERREUR: CV doit être entre 1 et 1024");
return;
void setDCCOutput(uint8_t level) {
if (level) {
// Signal positif
digitalWrite(PIN_H_BRIDGE_DIR1, HIGH);
digitalWrite(PIN_H_BRIDGE_DIR2, LOW);
} else {
// Signal négatif
digitalWrite(PIN_H_BRIDGE_DIR1, LOW);
digitalWrite(PIN_H_BRIDGE_DIR2, HIGH);
}
programming_mode = 1;
enableProgramming();
// Simulation de la lecture (implémentation réelle nécessite DCC bit-banging)
delay(500);
uint8_t value = readCVSimulated(cv);
disableProgramming();
programming_mode = 0;
Serial.println("CV " + String(cv) + " = " + String(value));
Serial.println("OK");
}
void writeCV(uint16_t cv, uint8_t value) {
Serial.println("Ecriture CV " + String(cv) + " = " + String(value) + "...");
if (cv < 1 || cv > 1024) {
Serial.println("ERREUR: CV doit être entre 1 et 1024");
return;
}
if (value < 0 || value > 255) {
Serial.println("ERREUR: Valeur doit être entre 0 et 255");
return;
}
programming_mode = 1;
enableProgramming();
// Simulation de l'écriture
delay(500);
writeCVSimulated(cv, value);
disableProgramming();
programming_mode = 0;
Serial.println("CV " + String(cv) + " ecrit avec " + String(value));
Serial.println("OK");
void enableDCC() {
analogWrite(PIN_H_BRIDGE_PWM, 255); // PWM à 100%
}
void setAddress(uint16_t addr) {
Serial.println("Configuration adresse " + String(addr) + "...");
if (addr < 1 || addr > 127) {
Serial.println("ERREUR: Adresse doit être entre 1 et 127");
return;
}
current_address = addr;
writeCV(1, addr); // CV1 = adresse courte
Serial.println("Adresse configuree a " + String(addr));
}
// ============================================================================
// Fonctions de contrôle du pont en H
// ============================================================================
void enableProgramming() {
// Mettre en place une tension de programmation
digitalWrite(PIN_H_BRIDGE_DIR1, HIGH);
digitalWrite(PIN_H_BRIDGE_DIR2, LOW);
analogWrite(PIN_H_BRIDGE_PWM, 200); // ~78% puissance
Serial.println("Tension de programmation active");
}
void disableProgramming() {
// Couper la tension
void disableDCC() {
analogWrite(PIN_H_BRIDGE_PWM, 0); // PWM à 0%
digitalWrite(PIN_H_BRIDGE_DIR1, LOW);
digitalWrite(PIN_H_BRIDGE_DIR2, LOW);
analogWrite(PIN_H_BRIDGE_PWM, 0);
Serial.println("Tension de programmation coupee");
}
// ============================================================================
// Fonction de mesure de courant
// Génération des bits DCC
// ============================================================================
void measureCurrentAndPrint() {
float current_ma = measureCurrent();
Serial.println("Courant: " + String(current_ma, 1) + " mA");
void sendDCCBit(uint8_t bit) {
uint16_t duration = (bit) ? DCC_ONE_BIT_TIME : DCC_ZERO_BIT_TIME;
// Première moitié du bit (niveau haut)
setDCCOutput(1);
delayMicroseconds(duration);
// Deuxième moitié du bit (niveau bas)
setDCCOutput(0);
delayMicroseconds(duration);
}
float measureCurrent() {
// Lire plusieurs fois et faire une moyenne
void sendDCCByte(uint8_t byte, uint8_t send_1_after) {
// Envoyer 8 bits (MSB en premier)
for (int i = 7; i >= 0; i--) {
uint8_t bit = (byte >> i) & 0x01;
sendDCCBit(bit);
}
// Envoyer bit de parité/séparation
if (send_1_after) {
sendDCCBit(1);
}
}
// ============================================================================
// Envoi de paquets DCC
// ============================================================================
void sendDCCPacket(uint8_t *packet, uint8_t length) {
// Préambule (14-16 bits de '1')
for (uint8_t i = 0; i < DCC_PREAMBLE; i++) {
sendDCCBit(1);
}
// Bit de démarrage (0)
sendDCCBit(0);
// Envoyer tous les octets sauf le dernier avec bit de séparation (1)
for (uint8_t i = 0; i < length - 1; i++) {
sendDCCByte(packet[i], 1);
}
// Dernier octet avec bit de fin (0)
sendDCCByte(packet[length - 1], 0);
// Garder le signal bas pendant le reste du cycle
setDCCOutput(0);
}
// ============================================================================
// Fonctions de programmation NMRA
// ============================================================================
void readCVNMRA(uint16_t cv) {
if (cv < 1 || cv > 1024) {
Serial.println("ERREUR: CV doit etre entre 1 et 1024");
return;
}
Serial.println("Lecture CV " + String(cv) + "...");
baseline_current = measureCurrentSmoothed();
uint8_t attempt = 0;
uint8_t value = 0;
bool success = false;
while (attempt < MAX_ATTEMPTS && !success) {
attempt++;
Serial.println(" Tentative " + String(attempt) + "...");
// Créer le paquet pour mode lecture CV (NMRA S-9.2.3)
uint8_t packet[4];
packet[0] = 0x7C; // Adresse programmation (0111 1100)
packet[1] = 0x64; // Commande read byte (0110 0100)
// CV est codée en 10 bits : AAAAABBBBB
uint16_t cv_addr = cv - 1;
packet[2] = 0xE4 | ((cv_addr >> 8) & 0x03);
packet[3] = cv_addr & 0xFF;
enableDCC();
ack_detected = false;
// Envoyer 5 fois
for (uint8_t i = 0; i < 5; i++) {
sendDCCPacket(packet, 4);
delay(5);
}
delay(10);
ack_detected = checkACK();
if (ack_detected) {
value = readCVFromMemory(cv);
Serial.println(" ACK reçu!");
success = true;
} else {
Serial.println(" Pas de reponse");
}
disableDCC();
delay(100);
}
if (success) {
Serial.println("CV " + String(cv) + " = " + String(value));
Serial.println("OK");
} else {
Serial.println("ERREUR: Pas de reponse du decodeur");
}
}
void writeCVNMRA(uint16_t cv, uint8_t value) {
if (cv < 1 || cv > 1024) {
Serial.println("ERREUR: CV doit etre entre 1 et 1024");
return;
}
if (value > 255) {
Serial.println("ERREUR: Valeur doit etre entre 0 et 255");
return;
}
Serial.println("Ecriture CV " + String(cv) + " = " + String(value) + "...");
baseline_current = measureCurrentSmoothed();
uint8_t attempt = 0;
bool success = false;
while (attempt < MAX_ATTEMPTS && !success) {
attempt++;
Serial.println(" Tentative " + String(attempt) + "...");
// Créer le paquet pour mode écriture CV (NMRA S-9.2.3)
uint8_t packet[5];
packet[0] = 0x7C; // Adresse programmation
packet[1] = 0x74; // Commande write byte (0111 0100)
uint16_t cv_addr = cv - 1;
packet[2] = 0xE4 | ((cv_addr >> 8) & 0x03);
packet[3] = cv_addr & 0xFF;
packet[4] = value;
enableDCC();
ack_detected = false;
// Envoyer 5 fois
for (uint8_t i = 0; i < 5; i++) {
sendDCCPacket(packet, 5);
delay(5);
}
delay(10);
ack_detected = checkACK();
if (ack_detected) {
writeCVToMemory(cv, value);
Serial.println(" ACK reçu!");
success = true;
} else {
Serial.println(" Pas de reponse");
}
disableDCC();
delay(100);
}
if (success) {
Serial.println("CV " + String(cv) + " ecrit avec " + String(value));
Serial.println("OK");
} else {
Serial.println("ERREUR: Pas de reponse du decodeur");
}
}
void setAddressNMRA(uint16_t addr) {
if (addr < 1 || addr > 127) {
Serial.println("ERREUR: Adresse courte doit etre entre 1 et 127");
return;
}
Serial.println("Configuration adresse " + String(addr) + "...");
current_address = addr;
// CV1 = adresse courte
writeCVNMRA(1, addr);
}
// ============================================================================
// Détection ACK
// ============================================================================
bool checkACK() {
float current = measureCurrentSmoothed();
float delta = current - baseline_current;
Serial.println(" Courant: " + String(current, 2) + " mA, Delta: " +
String(delta, 2) + " mA");
return (delta > ACK_THRESHOLD);
}
// ============================================================================
// Mesure de courant
// ============================================================================
float measureCurrentSmoothed() {
const uint8_t samples = 20;
float sum = 0;
const uint8_t samples = 10;
for (uint8_t i = 0; i < samples; i++) {
int raw = analogRead(PIN_CURRENT_SENSOR);
// Conversion : 5V / 1023 * courant_sensor_factor
// Exemple avec ACS712-5A : 185 mV/A
// ACS712-5A: 185 mV/A, offset 2.5V
float voltage = (raw * 5.0) / 1023.0;
float current = (voltage - 2.5) / 0.185; // 0.185V/A pour ACS712-5A
float current = (voltage - 2.5) / 0.185;
sum += abs(current);
delay(10);
delayMicroseconds(100);
}
return sum / samples;
}
// ============================================================================
// Fonctions simulées (à remplacer par vrai DCC bit-banging)
// Mémoire CV (simulation pour test)
// ============================================================================
uint8_t readCVSimulated(uint16_t cv) {
// Simulation : retourner une valeur basée sur le CV
if (cv == 1) return current_address; // CV1 = adresse
if (cv == 7) return 0x02; // CV7 = Version
if (cv == 8) return 0xAB; // CV8 = Fabricant
return 0xFF; // Valeur par défaut
void initCVMemory() {
memset(cv_memory, 0, sizeof(cv_memory));
cv_memory[1] = 3; // CV1 = adresse 3
cv_memory[7] = 0x02; // CV7 = version
cv_memory[8] = 0xAB; // CV8 = fabricant
}
void writeCVSimulated(uint16_t cv, uint8_t value) {
// Simulation d'écriture
if (cv == 1) {
current_address = value;
uint8_t readCVFromMemory(uint16_t cv) {
// Limité à CV 1-32 pour économiser la RAM du Nano
if (cv < 1 || cv > 32) return 0;
return cv_memory[cv];
}
void writeCVToMemory(uint16_t cv, uint8_t value) {
// Limité à CV 1-32 pour économiser la RAM du Nano
if (cv < 1 || cv > 32) return;
cv_memory[cv] = value;
}
// ============================================================================
// Test signal DCC
// ============================================================================
void testDCCSignal() {
Serial.println("Test generation signal DCC...");
Serial.println("Emission d'une trame d'adresse 42...");
enableDCC();
// Paquet simple: adresse 42, vitesse arrêt
uint8_t packet[3];
packet[0] = 42; // Adresse locomotive
packet[1] = 0x40; // Commande: Vitesse/Direction (arrêt)
packet[2] = packet[0] ^ packet[1]; // Checksum XOR
for (uint8_t i = 0; i < 10; i++) {
sendDCCPacket(packet, 3);
}
// Les autres CVs sont acceptés
disableDCC();
Serial.println("Test termine");
}
// ============================================================================
// Fin du programme
// ============================================================================