Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions examples/init_handler/init_handler.ino
Original file line number Diff line number Diff line change
Expand Up @@ -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 <CMRI.h>
#include <SoftwareSerial.h>

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
Expand All @@ -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);
}
Expand Down
79 changes: 79 additions & 0 deletions examples/init_handler_with_delay/init_handler_with_delay.ino
Original file line number Diff line number Diff line change
@@ -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 <CMRI.h>
#include <SoftwareSerial.h>

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));
}
9 changes: 7 additions & 2 deletions src/CMRI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions src/CMRI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -85,13 +86,21 @@ class CMRI
char *_tx_buffer;
int _rx_data_len;
void (*_init_handler)(const uint8_t *, int);
unsigned int _transmit_delay_us;

Stream &_serial;

// parsing state variables
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
};

Expand Down
14 changes: 14 additions & 0 deletions test/test_cmri/test_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
}