From 6ac2326bcfd0103a23a82205fe448c75b729b032 Mon Sep 17 00:00:00 2001 From: Bruno Rocci Date: Sat, 15 Aug 2026 16:05:49 +0200 Subject: [PATCH] Add configurable transmit delay via set_transmit_delay() --- examples/init_handler/init_handler.ino | 39 +++++---- .../init_handler_with_delay.ino | 79 +++++++++++++++++++ src/CMRI.cpp | 9 ++- src/CMRI.h | 9 +++ test/test_cmri/test_main.cpp | 14 ++++ 5 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 examples/init_handler_with_delay/init_handler_with_delay.ino diff --git a/examples/init_handler/init_handler.ino b/examples/init_handler/init_handler.ino index e135649..07167ab 100644 --- a/examples/init_handler/init_handler.ino +++ b/examples/init_handler/init_handler.ino @@ -18,11 +18,17 @@ * 3: Open Tools > Tables > Lights and add a light at address 1 * 4: Open the C/MRI Monitor to watch the INIT message * Raw format: [41 49 43 00 0A ...] = UA 'A', cmd 'I', NDP='C', DL=10 + * + * Wiring: + * Serial (pins 0/1) -> RS-485 transceiver -> host (JMRI) + * SoftwareSerial (pins 10/11) -> Serial Monitor for debug output */ #include +#include -CMRI cmri; // defaults to a SMINI with address 0 +SoftwareSerial console(10, 11); // RX, TX for debug output +CMRI cmri; // defaults to a SMINI with address 0, using Serial // --------------------------------------------------------------------------- // INIT handler callback @@ -32,44 +38,45 @@ CMRI cmri; // defaults to a SMINI with address 0 // --------------------------------------------------------------------------- void on_init(const uint8_t *data, int len) { - Serial.print(F("INIT received (")); + console.print(F("INIT received (")); if (len >= 1) { - Serial.print(F("NDP=")); - Serial.write(data[0]); - Serial.print(F(" ")); + console.print(F("NDP=")); + console.write(data[0]); + console.print(F(" ")); } if (len >= 3) { int delay_us = (data[1] * 256 + data[2]) * 10; - Serial.print(F("DL=")); - Serial.print(delay_us); - Serial.print(F("us ")); + console.print(F("DL=")); + console.print(delay_us); + console.print(F("us ")); } if (len > 3) { - Serial.print(F("options=")); + console.print(F("options=")); for (int i = 3; i < len; i++) { if (i > 3) - Serial.print(F(" ")); + console.print(F(" ")); if (data[i] < 16) - Serial.print(F("0")); - Serial.print(data[i], HEX); + console.print(F("0")); + console.print(data[i], HEX); } - Serial.print(F(" ")); + console.print(F(" ")); } - Serial.print(len); - Serial.println(F(" bytes")); + console.print(len); + console.println(F(" bytes")); } void setup() { - Serial.begin(9600, SERIAL_8N2); + Serial.begin(9600, SERIAL_8N2); // CMRI bus + console.begin(9600); // debug output cmri.set_init_handler(on_init); pinMode(13, OUTPUT); } diff --git a/examples/init_handler_with_delay/init_handler_with_delay.ino b/examples/init_handler_with_delay/init_handler_with_delay.ino new file mode 100644 index 0000000..afe2286 --- /dev/null +++ b/examples/init_handler_with_delay/init_handler_with_delay.ino @@ -0,0 +1,79 @@ +/** + * C/MRI INIT handler with transmit delay example + * ================================================ + * Demonstrates how to register an INIT callback that parses the + * transmit delay (dH/dL) from the INIT payload and applies it + * using set_transmit_delay(). + * + * The INIT message body follows NMRA LCS-9.10.1: + * byte 0: NDP (Node Definition Parameter) + * bytes 1-2: dH/dL (transmit delay, high & low bytes) + * bytes 3+: node-type-specific options + * + * The transmit delay is computed as (dH * 256 + dL) * 10 microseconds. + * Modern hosts set dH/dL to zero; non-zero values are for legacy + * compatibility. This delay is applied before each GET (R) reply to + * allow RS-485 transceiver turnaround. + * + * To set up in JMRI: + * 1: Create a new C/MRI connection (Serial, 9600 baud) + * 2: Configure a node with address 0 — JMRI sends an INIT at startup + * 3: Open Tools > Tables > Lights and add a light at address 1 + * 4: Open the C/MRI Monitor to watch the INIT message + * + * Wiring: + * Serial (pins 0/1) -> RS-485 transceiver -> host (JMRI) + * SoftwareSerial (pins 10/11) -> Serial Monitor for debug output + */ + +#include +#include + +SoftwareSerial console(10, 11); // RX, TX for debug output +CMRI cmri; // defaults to a SMINI with address 0, using Serial + +// --------------------------------------------------------------------------- +// INIT handler callback +// Parses dH/dL and applies the transmit delay via set_transmit_delay(). +// --------------------------------------------------------------------------- +void on_init(const uint8_t *data, int len) +{ + console.print(F("INIT received (")); + + if (len >= 1) + { + console.print(F("NDP=")); + console.write(data[0]); + console.print(F(" ")); + } + + if (len >= 3) + { + unsigned int dH = data[1]; + unsigned int dL = data[2]; + unsigned int delay_us = (dH * 256 + dL) * 10; + + cmri.set_transmit_delay(delay_us); + + console.print(F("DL=")); + console.print(delay_us); + console.print(F("us applied ")); + } + + console.print(len); + console.println(F(" bytes")); +} + +void setup() +{ + Serial.begin(9600, SERIAL_8N2); // CMRI bus + console.begin(9600); // debug output + cmri.set_init_handler(on_init); + pinMode(13, OUTPUT); +} + +void loop() +{ + cmri.process(); + digitalWrite(13, cmri.get_bit(0)); +} diff --git a/src/CMRI.cpp b/src/CMRI.cpp index e359b1e..af3a35c 100644 --- a/src/CMRI.cpp +++ b/src/CMRI.cpp @@ -39,7 +39,7 @@ CMRI::CMRI(unsigned int address, unsigned int input_bits, unsigned int output_bi // parsing state , - _mode(PREAMBLE_1), _rx_index(0), _rx_data_len(0), _init_handler(nullptr) + _mode(PREAMBLE_1), _rx_index(0), _rx_data_len(0), _init_handler(nullptr), _transmit_delay_us(TRANSMIT_DELAY_US) { // clear to zero @@ -59,6 +59,11 @@ void CMRI::set_init_handler(void (*handler)(const uint8_t *, int)) _init_handler = handler; } +void CMRI::set_transmit_delay(unsigned int delay_us) +{ + _transmit_delay_us = delay_us; +} + // reads in serial data, decodes packets // automatically responds to POLL requests // returns packet type so if we got a SET request you know to update your outputs @@ -144,7 +149,7 @@ bool CMRI::set_byte(int pos, char b) void CMRI::transmit() { - delayMicroseconds(50); // a minscule delay to let things recover + delayMicroseconds(_transmit_delay_us); _serial.write(255); _serial.write(255); _serial.write(STX); diff --git a/src/CMRI.h b/src/CMRI.h index 52b453a..dc27258 100644 --- a/src/CMRI.h +++ b/src/CMRI.h @@ -35,6 +35,7 @@ class CMRI CMRI(unsigned int address = 0, unsigned int input_bits = 24, unsigned int output_bits = 48, Stream &serial_class = Serial); void set_address(unsigned int address); void set_init_handler(void (*handler)(const uint8_t *data, int len)); + void set_transmit_delay(unsigned int delay_us); bool process(); bool process_char(char c); @@ -85,6 +86,7 @@ class CMRI char *_tx_buffer; int _rx_data_len; void (*_init_handler)(const uint8_t *, int); + unsigned int _transmit_delay_us; Stream &_serial; @@ -92,6 +94,13 @@ class CMRI int _mode; int _rx_index; +#ifndef TRANSMIT_DELAY_US +// Default RS-485 turnaround delay (microseconds) before transmitting. +// Modern hosts set dH/dL to zero; this is just for transceiver settling. +// Override at compile time or use set_transmit_delay() at runtime. +#define TRANSMIT_DELAY_US 50 +#endif + uint8_t _decode(uint8_t c); // process one character received from serial port }; diff --git a/test/test_cmri/test_main.cpp b/test/test_cmri/test_main.cpp index e4b60aa..e3a9fa5 100644 --- a/test/test_cmri/test_main.cpp +++ b/test/test_cmri/test_main.cpp @@ -194,6 +194,19 @@ void test_preamble_resync_after_garbage(void) TEST_ASSERT_EQUAL_UINT8(CMRI::GET, s.tx[4]); } +// set_transmit_delay overrides the default transmit delay. +void test_set_transmit_delay(void) +{ + Stream s; + CMRI cmri(0, 24, 48, s); + + cmri.set_byte(0, 0x42); + cmri.set_transmit_delay(100); + cmri.transmit(); + + TEST_ASSERT_EQUAL_UINT8(0x42, s.tx[5]); +} + int main(int, char **) { UNITY_BEGIN(); @@ -205,6 +218,7 @@ int main(int, char **) RUN_TEST(test_set_packet_updates_outputs); RUN_TEST(test_address_filtering); RUN_TEST(test_transmit_escapes_control_bytes); + RUN_TEST(test_set_transmit_delay); RUN_TEST(test_preamble_resync_after_garbage); return UNITY_END(); }