mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-01 11:35:23 +02:00
CMRI RS485 support
This commit is contained in:
parent
71c0c6f8a6
commit
b17356bff2
136
IO_CMRI.cpp
136
IO_CMRI.cpp
@ -19,6 +19,43 @@
|
|||||||
|
|
||||||
#include "IO_CMRI.h"
|
#include "IO_CMRI.h"
|
||||||
|
|
||||||
|
/************************************************************
|
||||||
|
* CMRIbus implementation
|
||||||
|
************************************************************/
|
||||||
|
|
||||||
|
// Constructor for CMRIbus
|
||||||
|
CMRIbus::CMRIbus(uint8_t busNo, HardwareSerial &serial, unsigned long baud, uint16_t cycleTimeMS, VPIN transmitEnablePin) {
|
||||||
|
_busNo = busNo;
|
||||||
|
_serial = &serial;
|
||||||
|
_baud = baud;
|
||||||
|
_cycleTime = cycleTimeMS * 1000UL; // convert from milliseconds to microseconds.
|
||||||
|
_transmitEnablePin = transmitEnablePin;
|
||||||
|
if (_transmitEnablePin != VPIN_NONE) {
|
||||||
|
pinMode(_transmitEnablePin, OUTPUT);
|
||||||
|
ArduinoPins::fastWriteDigital(_transmitEnablePin, 0); // transmitter initially off
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max message length is 256+6=262 bytes.
|
||||||
|
// Each byte is one start bit, 8 data bits and 1 stop bit = 10 bits per byte.
|
||||||
|
// Calculate timeout based on double this time.
|
||||||
|
_timeoutPeriod = 2 * 10 * 262 * 1000UL / (_baud / 1000UL);
|
||||||
|
|
||||||
|
// Calculate the time in microseconds to transmit one byte (10 bits).
|
||||||
|
_byteTransmitTime = 1000000UL * 10 / _baud;
|
||||||
|
// Postdelay is only required if we need to allow for data still being sent when
|
||||||
|
// we want to switch off the transmitter. The flush() method of HardwareSerial
|
||||||
|
// ensures that the data has completed being sent over the line.
|
||||||
|
_postDelay = 0;
|
||||||
|
|
||||||
|
// Add device to HAL device chain
|
||||||
|
IODevice::addDevice(this);
|
||||||
|
|
||||||
|
// Add bus to CMRIbus chain.
|
||||||
|
_nextBus = _busList;
|
||||||
|
_busList = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Main loop function for CMRIbus.
|
// Main loop function for CMRIbus.
|
||||||
// Work through list of nodes. For each node, in separate loop entries
|
// Work through list of nodes. For each node, in separate loop entries
|
||||||
// send initialisation message (once only); then send
|
// send initialisation message (once only); then send
|
||||||
@ -37,7 +74,56 @@ void CMRIbus::_loop(unsigned long currentMicros) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send output data to the bus for nominated CMRInode
|
||||||
|
uint16_t CMRIbus::sendData(CMRInode *node) {
|
||||||
|
uint16_t numDataBytes = (node->getNumOutputs()+7)/8;
|
||||||
|
_serial->write(SYN);
|
||||||
|
_serial->write(SYN);
|
||||||
|
_serial->write(STX);
|
||||||
|
_serial->write(node->getAddress() + 65);
|
||||||
|
_serial->write('T'); // T for Transmit data message
|
||||||
|
uint16_t charsSent = 6; // include header and trailer
|
||||||
|
for (uint8_t index=0; index<numDataBytes; index++) {
|
||||||
|
uint8_t value = node->getOutputStates(index);
|
||||||
|
if (value == DLE || value == STX || value == ETX) {
|
||||||
|
_serial->write(DLE);
|
||||||
|
charsSent++;
|
||||||
|
}
|
||||||
|
_serial->write(value);
|
||||||
|
charsSent++;
|
||||||
|
}
|
||||||
|
_serial->write(ETX);
|
||||||
|
return charsSent; // number of characters sent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send request for input data to nominated CMRInode.
|
||||||
|
uint16_t CMRIbus::requestData(CMRInode *node) {
|
||||||
|
_serial->write(SYN);
|
||||||
|
_serial->write(SYN);
|
||||||
|
_serial->write(STX);
|
||||||
|
_serial->write(node->getAddress() + 65);
|
||||||
|
_serial->write('P'); // P for Poll message
|
||||||
|
_serial->write(ETX);
|
||||||
|
return 6; // number of characters sent
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send initialisation message
|
||||||
|
uint16_t CMRIbus::sendInitialisation(CMRInode *node) {
|
||||||
|
_serial->write(SYN);
|
||||||
|
_serial->write(SYN);
|
||||||
|
_serial->write(STX);
|
||||||
|
_serial->write(node->getAddress() + 65);
|
||||||
|
_serial->write('I'); // I for initialise message
|
||||||
|
_serial->write(node->getType()); // NDP
|
||||||
|
_serial->write((uint8_t)0); // dH
|
||||||
|
_serial->write((uint8_t)0); // dL
|
||||||
|
_serial->write((uint8_t)0); // NS
|
||||||
|
_serial->write(ETX);
|
||||||
|
return 10; // number of characters sent
|
||||||
|
}
|
||||||
|
|
||||||
void CMRIbus::processOutgoing() {
|
void CMRIbus::processOutgoing() {
|
||||||
|
uint16_t charsSent = 0;
|
||||||
if (_currentNode == NULL) {
|
if (_currentNode == NULL) {
|
||||||
// If we're between read/write cycles then don't do anything else.
|
// If we're between read/write cycles then don't do anything else.
|
||||||
if (_currentMicros - _cycleStartTime < _cycleTime) return;
|
if (_currentMicros - _cycleStartTime < _cycleTime) return;
|
||||||
@ -50,19 +136,25 @@ void CMRIbus::processOutgoing() {
|
|||||||
switch (_transmitState) {
|
switch (_transmitState) {
|
||||||
case TD_IDLE:
|
case TD_IDLE:
|
||||||
case TD_INIT:
|
case TD_INIT:
|
||||||
|
enableTransmitter();
|
||||||
if (!_currentNode->isInitialised()) {
|
if (!_currentNode->isInitialised()) {
|
||||||
sendInitialisation(_currentNode);
|
charsSent = sendInitialisation(_currentNode);
|
||||||
_currentNode->setInitialised();
|
_currentNode->setInitialised();
|
||||||
_transmitState = TD_TRANSMIT;
|
_transmitState = TD_TRANSMIT;
|
||||||
|
delayUntil(_currentMicros+_byteTransmitTime*charsSent);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* fallthrough */
|
/* fallthrough */
|
||||||
case TD_TRANSMIT:
|
case TD_TRANSMIT:
|
||||||
sendData(_currentNode);
|
charsSent = sendData(_currentNode);
|
||||||
_transmitState = TD_PROMPT;
|
_transmitState = TD_PROMPT;
|
||||||
|
// Defer next entry for as long as it takes to transmit the characters,
|
||||||
|
// to allow output queue to empty.
|
||||||
|
delayUntil(_currentMicros+_byteTransmitTime*charsSent);
|
||||||
break;
|
break;
|
||||||
case TD_PROMPT:
|
case TD_PROMPT:
|
||||||
requestData(_currentNode);
|
charsSent = requestData(_currentNode);
|
||||||
|
disableTransmitter();
|
||||||
_transmitState = TD_RECEIVE;
|
_transmitState = TD_RECEIVE;
|
||||||
_timeoutStart = _currentMicros; // Start timeout on response
|
_timeoutStart = _currentMicros; // Start timeout on response
|
||||||
break;
|
break;
|
||||||
@ -84,7 +176,7 @@ void CMRIbus::processIncoming() {
|
|||||||
int data = _serial->read();
|
int data = _serial->read();
|
||||||
if (data < 0) return; // No characters to read
|
if (data < 0) return; // No characters to read
|
||||||
|
|
||||||
if (!_currentNode) return; // Not waiting for input, so ignore.
|
if (_transmitState != TD_RECEIVE || !_currentNode) return; // Not waiting for input, so ignore.
|
||||||
|
|
||||||
uint8_t nextState = RD_SYN1; // default to resetting state machine
|
uint8_t nextState = RD_SYN1; // default to resetting state machine
|
||||||
switch(_receiveState) {
|
switch(_receiveState) {
|
||||||
@ -128,6 +220,40 @@ void CMRIbus::processIncoming() {
|
|||||||
_receiveState = nextState;
|
_receiveState = nextState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If configured for half duplex RS485, switch RS485 interface
|
||||||
|
// into transmit mode.
|
||||||
|
void CMRIbus::enableTransmitter() {
|
||||||
|
if (_transmitEnablePin != VPIN_NONE)
|
||||||
|
ArduinoPins::fastWriteDigital(_transmitEnablePin, 1);
|
||||||
|
// Send an extra SYN character to ensure transmitter and
|
||||||
|
// remote receiver have stabilised before we start the packet.
|
||||||
|
_serial->write(SYN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If configured for half duplex RS485, switch RS485 interface
|
||||||
|
// into receive mode.
|
||||||
|
void CMRIbus::disableTransmitter() {
|
||||||
|
// Wait until all data has been transmitted. On the standard
|
||||||
|
// AVR driver, this waits until the FIFO is empty and all
|
||||||
|
// data has been sent over the link.
|
||||||
|
_serial->flush();
|
||||||
|
// If we don't trust the 'flush' function and think the
|
||||||
|
// data's still in transit, then wait a bit longer.
|
||||||
|
if (_postDelay > 0)
|
||||||
|
delayMicroseconds(_postDelay);
|
||||||
|
// Hopefully, we can now safely switch off the transmitter.
|
||||||
|
if (_transmitEnablePin != VPIN_NONE)
|
||||||
|
ArduinoPins::fastWriteDigital(_transmitEnablePin, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link to chain of CMRI bus instances
|
||||||
|
CMRIbus *CMRIbus::_busList = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
/************************************************************
|
||||||
|
* CMRInode implementation
|
||||||
|
************************************************************/
|
||||||
|
|
||||||
// Constructor for CMRInode object
|
// Constructor for CMRInode object
|
||||||
CMRInode::CMRInode(VPIN firstVpin, int nPins, uint8_t busNo, uint8_t address, char type, uint16_t inputs, uint16_t outputs) {
|
CMRInode::CMRInode(VPIN firstVpin, int nPins, uint8_t busNo, uint8_t address, char type, uint16_t inputs, uint16_t outputs) {
|
||||||
_firstVpin = firstVpin;
|
_firstVpin = firstVpin;
|
||||||
@ -173,5 +299,3 @@ CMRInode::CMRInode(VPIN firstVpin, int nPins, uint8_t busNo, uint8_t address, ch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link to chain of CMRI bus instances
|
|
||||||
CMRIbus *CMRIbus::_busList = NULL;
|
|
||||||
|
125
IO_CMRI.h
125
IO_CMRI.h
@ -18,13 +18,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* To define a CMRI bus,
|
* To define a CMRI bus, example syntax:
|
||||||
* CMRIbus::create(bus, Serial3, 19200, cycletime);
|
* CMRIbus::create(bus, serial, baud[, cycletime[, pin]]);
|
||||||
*
|
*
|
||||||
* bus = 0-255
|
* bus = 0-255
|
||||||
* Serial3 = serial port to be used
|
* serial = serial port to be used (e.g. Serial3)
|
||||||
* 19200 = baud rate (min 9600, max 115200)
|
* baud = baud rate (9600, 19200, 28800, 57600 or 115200)
|
||||||
* cycletime = minimum time between successive updates/reads of a node in millisecs
|
* cycletime = minimum time between successive updates/reads of a node in millisecs (default 500ms)
|
||||||
|
* pin = pin number connected to RS485 module's DE and !RE terminals for half-duplex operation (default VPIN_NONE)
|
||||||
*
|
*
|
||||||
* Each bus must use a different serial port.
|
* Each bus must use a different serial port.
|
||||||
*
|
*
|
||||||
@ -36,8 +37,8 @@
|
|||||||
* type = 'M' for SMINI (fixed 24 inputs and 48 outputs)
|
* type = 'M' for SMINI (fixed 24 inputs and 48 outputs)
|
||||||
* 'C' for CPNODE (16 to 144 inputs/outputs in groups of 8)
|
* 'C' for CPNODE (16 to 144 inputs/outputs in groups of 8)
|
||||||
* (other types are not supported at this time).
|
* (other types are not supported at this time).
|
||||||
* inputs = number of inputs (CPNODE)
|
* inputs = number of inputs (CPNODE only)
|
||||||
* outputs = number of outputs (CPNODE)
|
* outputs = number of outputs (CPNODE only)
|
||||||
*
|
*
|
||||||
* Reference: "LCS-9.10.1
|
* Reference: "LCS-9.10.1
|
||||||
* Layout Control Specification: CMRInet Protocol
|
* Layout Control Specification: CMRInet Protocol
|
||||||
@ -168,10 +169,12 @@ private:
|
|||||||
uint8_t _busNo;
|
uint8_t _busNo;
|
||||||
HardwareSerial *_serial;
|
HardwareSerial *_serial;
|
||||||
unsigned long _baud;
|
unsigned long _baud;
|
||||||
|
VPIN _transmitEnablePin = VPIN_NONE;
|
||||||
CMRInode *_nodeListStart = NULL, *_nodeListEnd = NULL;
|
CMRInode *_nodeListStart = NULL, *_nodeListEnd = NULL;
|
||||||
CMRInode *_currentNode = NULL;
|
CMRInode *_currentNode = NULL;
|
||||||
|
|
||||||
// Transmitter state machine states
|
// Transmitter state machine states
|
||||||
enum {TD_IDLE, TD_INIT, TD_TRANSMIT, TD_PROMPT, TD_RECEIVE};
|
enum {TD_IDLE, TD_PRETRANSMIT, TD_INIT, TD_TRANSMIT, TD_PROMPT, TD_RECEIVE};
|
||||||
uint8_t _transmitState = TD_IDLE;
|
uint8_t _transmitState = TD_IDLE;
|
||||||
// Receiver state machine states.
|
// Receiver state machine states.
|
||||||
enum {RD_SYN1, RD_SYN2, RD_STX, RD_ADDR, RD_TYPE,
|
enum {RD_SYN1, RD_SYN2, RD_STX, RD_ADDR, RD_TYPE,
|
||||||
@ -182,8 +185,10 @@ private:
|
|||||||
unsigned long _cycleStartTime = 0;
|
unsigned long _cycleStartTime = 0;
|
||||||
unsigned long _timeoutStart = 0;
|
unsigned long _timeoutStart = 0;
|
||||||
unsigned long _cycleTime; // target time between successive read/write cycles, microseconds
|
unsigned long _cycleTime; // target time between successive read/write cycles, microseconds
|
||||||
uint32_t _timeoutPeriod; // timeout on read responses, in microseconds.
|
unsigned long _timeoutPeriod; // timeout on read responses, in microseconds.
|
||||||
unsigned long _currentMicros; // last value of micros() from _loop function.
|
unsigned long _currentMicros; // last value of micros() from _loop function.
|
||||||
|
unsigned long _postDelay; // delay time after transmission before switching off transmitter (in us)
|
||||||
|
unsigned long _byteTransmitTime; // time in us for transmission of one byte
|
||||||
|
|
||||||
static CMRIbus *_busList; // linked list of defined bus instances
|
static CMRIbus *_busList; // linked list of defined bus instances
|
||||||
|
|
||||||
@ -196,53 +201,23 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static void create(uint8_t busNo, HardwareSerial &serial, unsigned long baud, uint16_t cycleTimeMS = 500) {
|
static void create(uint8_t busNo, HardwareSerial &serial, unsigned long baud, uint16_t cycleTimeMS=500, VPIN transmitEnablePin=VPIN_NONE) {
|
||||||
new CMRIbus(busNo, serial, baud, cycleTimeMS);
|
new CMRIbus(busNo, serial, baud, cycleTimeMS, transmitEnablePin);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new CMRInode to the list of nodes for this bus.
|
|
||||||
void addNode(CMRInode *newNode) {
|
|
||||||
if (!_nodeListStart)
|
|
||||||
_nodeListStart = newNode;
|
|
||||||
if (!_nodeListEnd)
|
|
||||||
_nodeListEnd = newNode;
|
|
||||||
else
|
|
||||||
_nodeListEnd->setNext(newNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
CMRIbus(uint8_t busNo, HardwareSerial &serial, unsigned long baud, uint16_t cycleTimeMS) {
|
|
||||||
_busNo = busNo;
|
|
||||||
_serial = &serial;
|
|
||||||
_baud = baud;
|
|
||||||
_cycleTime = cycleTimeMS * 1000UL; // convert from milliseconds to microseconds.
|
|
||||||
|
|
||||||
// Max message length is 256+6=262 bytes.
|
|
||||||
// Each byte is one start bit, 8 data bits and 1 stop bit = 10 bits per byte.
|
|
||||||
// Calculate timeout based on double this time.
|
|
||||||
_timeoutPeriod = 2 * 10 * 262 * 1000UL / (_baud / 1000);
|
|
||||||
//DIAG(F("Timeout=%l"), _timeoutPeriod);
|
|
||||||
|
|
||||||
// Add device to HAL device chain
|
|
||||||
IODevice::addDevice(this);
|
|
||||||
|
|
||||||
// Add bus to CMRIbus chain.
|
|
||||||
_nextBus = _busList;
|
|
||||||
_busList = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Device-specific initialisation
|
// Device-specific initialisation
|
||||||
void _begin() override {
|
void _begin() override {
|
||||||
// Some sources quote one stop bit, some two.
|
// Some sources quote one stop bit, some two.
|
||||||
_serial->begin(_baud, SERIAL_8N1);
|
_serial->begin(_baud, SERIAL_8N1);
|
||||||
#if defined(DIAG_IO)
|
#if defined(DIAG_IO)
|
||||||
_display();
|
_display();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loop function (overriding IODevice::_loop(unsigned long))
|
||||||
void _loop(unsigned long currentMicros) override;
|
void _loop(unsigned long currentMicros) override;
|
||||||
|
|
||||||
// Display information about the device.
|
// Display information about the device
|
||||||
void _display() override {
|
void _display() override {
|
||||||
DIAG(F("CMRIbus %d configured, speed=%d baud, cycle=%d ms"), _busNo, _baud, _cycleTime/1000);
|
DIAG(F("CMRIbus %d configured, speed=%d baud, cycle=%d ms"), _busNo, _baud, _cycleTime/1000);
|
||||||
}
|
}
|
||||||
@ -256,53 +231,31 @@ protected:
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send output data to the bus for nominated CMRInode
|
// Add new CMRInode to the list of nodes for this bus.
|
||||||
bool sendData(CMRInode *node) {
|
void addNode(CMRInode *newNode) {
|
||||||
uint16_t numDataBytes = (node->getNumOutputs()+7)/8;
|
if (!_nodeListStart)
|
||||||
_serial->write(SYN);
|
_nodeListStart = newNode;
|
||||||
_serial->write(SYN);
|
if (!_nodeListEnd)
|
||||||
_serial->write(STX);
|
_nodeListEnd = newNode;
|
||||||
_serial->write(node->getAddress() + 65);
|
else
|
||||||
_serial->write('T'); // T for Transmit data message
|
_nodeListEnd->setNext(newNode);
|
||||||
for (uint8_t index=0; index<numDataBytes; index++) {
|
|
||||||
uint8_t value = node->getOutputStates(index);
|
|
||||||
if (value == DLE || value == STX || value == ETX) _serial->write(DLE);
|
|
||||||
_serial->write(value);
|
|
||||||
}
|
|
||||||
_serial->write(ETX);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send request for input data to nominated CMRInode.
|
protected:
|
||||||
bool requestData(CMRInode *node) {
|
CMRIbus(uint8_t busNo, HardwareSerial &serial, unsigned long baud, uint16_t cycleTimeMS, VPIN transmitEnablePin);
|
||||||
_serial->write(SYN);
|
uint16_t sendData(CMRInode *node);
|
||||||
_serial->write(SYN);
|
uint16_t requestData(CMRInode *node);
|
||||||
_serial->write(STX);
|
uint16_t sendInitialisation(CMRInode *node);
|
||||||
_serial->write(node->getAddress() + 65);
|
|
||||||
_serial->write('P'); // P for Poll message
|
|
||||||
_serial->write(ETX);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sendInitialisation(CMRInode *node) {
|
|
||||||
_serial->write(SYN);
|
|
||||||
_serial->write(SYN);
|
|
||||||
_serial->write(STX);
|
|
||||||
_serial->write(node->getAddress() + 65);
|
|
||||||
_serial->write('I'); // I for initialise message
|
|
||||||
_serial->write(node->getType()); // NDP
|
|
||||||
_serial->write((uint8_t)0); // dH
|
|
||||||
_serial->write((uint8_t)0); // dL
|
|
||||||
_serial->write((uint8_t)0); // NS
|
|
||||||
_serial->write(ETX);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process any data bytes received from a CMRInode.
|
// Process any data bytes received from a CMRInode.
|
||||||
void processIncoming();
|
void processIncoming();
|
||||||
|
|
||||||
// Process any outgoing traffic that is due.
|
// Process any outgoing traffic that is due.
|
||||||
void processOutgoing();
|
void processOutgoing();
|
||||||
|
// Enable transmitter
|
||||||
|
void enableTransmitter();
|
||||||
|
// Disable transmitter and enable receiver
|
||||||
|
void disableTransmitter();
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
uint8_t getBusNumber() {
|
uint8_t getBusNumber() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user