├── examples ├── susic_256_outputs │ └── susic_256_outputs.ino ├── many_outputs │ └── many_outputs.ino ├── rs485_rx_and_tx │ └── rs485_rx_and_tx.ino ├── many_inputs │ └── many_inputs.ino ├── rs485_cmri_hello_world │ └── rs485_cmri_hello_world.ino ├── multiple_nodes │ └── multiple_nodes.ino ├── hello_world │ └── hello_world.ino ├── multiple_nodes_inputting │ └── multiple_nodes_inputting.ino └── inputs_and_outputs │ └── inputs_and_outputs.ino ├── library.properties ├── keywords.txt ├── library.json ├── .gitattributes ├── CMRI.h ├── .gitignore ├── CMRI.cpp └── README.md /examples/susic_256_outputs/susic_256_outputs.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | CMRI cmri(0, 0, 256); // address 0, 0 inputs, 256 outputs 4 | 5 | void setup() { 6 | Serial.begin(9600, SERIAL_8N2); // make sure this matches your speed set in JMRI 7 | pinMode(2, OUTPUT); 8 | pinMode(3, OUTPUT); 9 | } 10 | 11 | void loop() { 12 | // 1: main processing node of cmri library 13 | cmri.process(); 14 | 15 | // 2: update output. Reads bit 0 of T packet and sets the LED to this 16 | digitalWrite(2, cmri.get_bit(0)); 17 | digitalWrite(3, cmri.get_bit(255)); 18 | } 19 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=CMRI 2 | version=1.5.1 3 | author=Michael Adams 4 | maintainer=Michael Adams 5 | sentence=A library for interfacing Arduino with the C/MRI computer control system for model railroads. 6 | paragraph=This library allows you to easily interface your Arduino with JMRI (Java Model Railroad Interface) by emulating Bruce Chubb's Computer/Model Railroad Interface (C/MRI) System. It provides a simple API to handle GET, SET, and POLL requests from JMRI automatically, with support for up to 2048 digital lines. 7 | category=Communication 8 | url=https://github.com/madleech/ArduinoCMRI 9 | architectures=* 10 | includes=CMRI.h -------------------------------------------------------------------------------- /examples/many_outputs/many_outputs.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define LATCH 8 4 | #define CLOCK 12 5 | #define DATA 11 6 | 7 | CMRI cmri; // defaults to a SMINI with address 0. SMINI = 24 inputs, 48 outputs 8 | 9 | void setup() { 10 | Serial.begin(9600, SERIAL_8N2); // make sure this matches your speed set in JMRI 11 | pinMode(LATCH, OUTPUT); 12 | pinMode(CLOCK, OUTPUT); 13 | pinMode(DATA, OUTPUT); 14 | } 15 | 16 | void loop() { 17 | // 1: main processing node of cmri library 18 | cmri.process(); 19 | 20 | // 2: update output. Reads bit 0 of T packet and sets the LED to this 21 | digitalWrite(LATCH, LOW); 22 | shiftOut(DATA, CLOCK, MSBFIRST, cmri.get_byte(0)); 23 | digitalWrite(LATCH, HIGH); 24 | } 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/rs485_rx_and_tx/rs485_rx_and_tx.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define CMRI_ADDR 0 5 | 6 | #define DE_PIN 2 7 | #define LED_PIN 3 8 | 9 | Auto485 bus(DE_PIN); // Arduino pin 2 -> MAX485 DE and RE pins 10 | CMRI cmri(CMRI_ADDR, 24, 48, bus); // defaults to a SMINI with address 0. SMINI = 24 inputs, 48 outputs 11 | 12 | void setup() { 13 | bus.begin(9600, SERIAL_8N2); // open RS485 bus at 9600bps 14 | pinMode(LED_PIN, OUTPUT); 15 | } 16 | 17 | void loop() { 18 | // 1: main processing node of cmri library 19 | cmri.process(); 20 | 21 | // 2: update output. Reads bit 0 of T packet and sets the LED to this 22 | digitalWrite(LED_PIN, cmri.get_bit(0)); 23 | 24 | // 3: update input. Flips a bit back and forth every second or so 25 | cmri.set_bit(0, (millis() / 1000) % 2 == 0); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /examples/many_inputs/many_inputs.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // pins for a 168/368 based Arduino 5 | #define SS 10 6 | #define MOSI 11 /* not used */ 7 | #define MISO 12 8 | #define CLOCK 13 9 | 10 | CMRI cmri; // defaults to a SMINI with address 0. SMINI = 24 inputs, 48 outputs 11 | 12 | void setup() { 13 | Serial.begin(9600, SERIAL_8N2); // make sure this matches your speed set in JMRI 14 | SPI.begin(); 15 | } 16 | 17 | void loop() { 18 | // 1: main processing node of cmri library 19 | cmri.process(); 20 | 21 | // 2: toggle the SS pin 22 | digitalWrite(SS, HIGH); 23 | delay(1); // wait while data CD4021 loads in data 24 | digitalWrite(SS, LOW); 25 | 26 | // 3: update input status in CMRI, will get sent to PC next time we're asked 27 | cmri.set_byte(0, SPI.transfer(0x00 /* dummy output value */)); 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Syntax Coloring Map for CMRI library 3 | ####################################### 4 | 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | CMRI KEYWORD1 10 | 11 | ####################################### 12 | # Methods and Functions (KEYWORD2) 13 | ####################################### 14 | 15 | set_address KEYWORD2 16 | set_length KEYWORD2 17 | process KEYWORD2 18 | process_char KEYWORD2 19 | transmit KEYWORD2 20 | get_bit KEYWORD2 21 | get_byte KEYWORD2 22 | set_bit KEYWORD2 23 | set_byte KEYWORD2 24 | decode KEYWORD2 25 | valid KEYWORD2 26 | packet_type KEYWORD2 27 | 28 | ####################################### 29 | # Constants (LITERAL1) 30 | ####################################### 31 | 32 | MAX LITERAL1 33 | INIT LITERAL1 34 | SET LITERAL1 35 | GET LITERAL1 36 | POLL LITERAL1 37 | STX LITERAL1 38 | ETX LITERAL1 39 | 40 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CMRI", 3 | "version": "1.5.1", 4 | "description": "A library for interfacing Arduino with the C/MRI computer control system for model railroads. This library allows you to easily interface your Arduino with JMRI (Java Model Railroad Interface) by emulating Bruce Chubb's Computer/Model Railroad Interface (C/MRI) System. It provides a simple API to handle GET, SET, and POLL requests from JMRI automatically, with support for up to 2048 digital lines.", 5 | "keywords": "CMRI, JMRI, model-railroad, arduino, communication", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/madleech/ArduinoCMRI.git" 9 | }, 10 | "authors": [ 11 | { 12 | "name": "Michael Adams", 13 | "url": "http://www.michael.net.nz", 14 | "maintainer": true 15 | } 16 | ], 17 | "license": "MIT", 18 | "frameworks": "arduino", 19 | "platforms": "*", 20 | "headers": "CMRI.h", 21 | "build": { 22 | "srcDir": ".", 23 | "includeDir": "." 24 | } 25 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.c text=auto 19 | *.cpp text=auto 20 | *.cxx text=auto 21 | *.h text=auto 22 | *.hxx text=auto 23 | *.py text=auto 24 | *.rb text=auto 25 | *.java text=auto 26 | *.html text=auto 27 | *.htm text=auto 28 | *.css text=auto 29 | *.scss text=auto 30 | *.sass text=auto 31 | *.less text=auto 32 | *.js text=auto 33 | *.lisp text=auto 34 | *.clj text=auto 35 | *.sql text=auto 36 | *.php text=auto 37 | *.lua text=auto 38 | *.m text=auto 39 | *.asm text=auto 40 | *.erl text=auto 41 | *.fs text=auto 42 | *.fsx text=auto 43 | *.hs text=auto 44 | 45 | *.csproj text=auto merge=union 46 | *.vbproj text=auto merge=union 47 | *.fsproj text=auto merge=union 48 | *.dbproj text=auto merge=union 49 | *.sln text=auto eol=crlf merge=union 50 | -------------------------------------------------------------------------------- /examples/rs485_cmri_hello_world/rs485_cmri_hello_world.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * C/MRI -> JMRI via RS485 3 | * ======================= 4 | * Uses an RS485 bus to transparently talk support multiple ArduinoCMRI nodes on one bus. 5 | * By passing in an Auto485 object to the CMRI constructor, we are able to automatically 6 | * control the DE and RE pins on our RS485 bus transceiver. 7 | * 8 | * Sets up pin 13 (LED) as an output, pin 12 as an input, and attaches both to 9 | * the first output/input bits of the emulated SMINI interface. 10 | * 11 | * To set up in JMRI, follow instructions in hello_world. 12 | * 13 | */ 14 | 15 | #include 16 | #include 17 | 18 | Auto485 bus(2); // Arduino pin 2 -> MAX485 DE and RE pins 19 | CMRI cmri(0, 24, 48, bus); // sets up an SMINI with address 0. SMINI = 24 inputs, 48 outputs 20 | 21 | void setup() { 22 | bus.begin(9600, SERIAL_8N2); // open the RS485 bus at 9600bps 23 | pinMode(12, INPUT); digitalWrite(12, HIGH); 24 | pinMode(13, OUTPUT); 25 | } 26 | 27 | void loop() { 28 | // 1: main processing node of cmri library 29 | cmri.process(); 30 | 31 | // 2: update output. Reads bit 0 of T packet and sets the LED to this 32 | digitalWrite(13, cmri.get_bit(0)); 33 | 34 | // 3: update inputs 35 | cmri.set_bit(0, !digitalRead(12)); 36 | } 37 | 38 | -------------------------------------------------------------------------------- /examples/multiple_nodes/multiple_nodes.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * An example of driving multiple C/MRI nodes on a single Arduino 3 | * ============================================================== 4 | * Sets up pins 2 and 3 as outputs, and attaches each to a different SMINI node. 5 | * 6 | * To set up in JMRI: 7 | * 1: Create a new connection, 8 | * - type = C/MRI, 9 | * - connection = Serial, 10 | * - port = , 11 | * - speed = 9600 12 | * 2: Click 'Configure C/MRI nodes' and click 'Add Node' to create a new SMINI node 13 | * 3: In the same window, type in '1' as the address and click 'Add Node' to add a second SMINI node 14 | * 4: Click 'Done' 15 | * 5: Restart J/MRI and it should say "Serial: using Serial on COM" - congratulations! 16 | * 6: Open Tools > Tables > Lights and click 'Add' 17 | * 7: Add a new light at hardware address 0001, then click 'Create' 18 | * 8: Enter hardware address 1001 and click 'Create'. This will create a second light. 19 | * 8: Click the 'Off' state button to turn each LED on. Congratulations! 20 | */ 21 | 22 | #include 23 | 24 | CMRI cmri0(0); // first SMINI, 24 inputs, 48 outputs 25 | CMRI cmri1(1); // second SMINI, another 24 inputs and another 48 outputs 26 | 27 | void setup() { 28 | Serial.begin(9600, SERIAL_8N2); // make sure this matches your speed set in JMRI 29 | pinMode(2, OUTPUT); 30 | pinMode(3, OUTPUT); 31 | } 32 | 33 | char c; 34 | void loop() { 35 | // 1: main processing node of cmri library 36 | while (Serial.available() > 0) 37 | { 38 | c = Serial.read(); 39 | cmri0.process_char(c); 40 | cmri1.process_char(c); 41 | } 42 | 43 | // 2: update outputs. 44 | digitalWrite(2, cmri0.get_bit(0)); 45 | digitalWrite(3, cmri1.get_bit(0)); 46 | } 47 | -------------------------------------------------------------------------------- /examples/hello_world/hello_world.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * A trivial C/MRI -> JMRI interface 3 | * ================================= 4 | * Sets up pin 13 (LED) as an output, and attaches it to the first output bit 5 | * of the emulated SMINI interface. 6 | * 7 | * To set up in JMRI: 8 | * 1: Create a new connection, 9 | * - type = C/MRI, 10 | * - connection = Serial, 11 | * - port = , 12 | * - speed = 9600 13 | * 2: Click 'Configure C/MRI nodes' and create a new SMINI node 14 | * 3: Click 'Add Node' and then 'Done' 15 | * 4: Restart J/MRI and it should say "Serial: using Serial on COM" - congratulations! 16 | * 5: Open Tools > Tables > Lights and click 'Add' 17 | * 6: Add a new light at hardware address 1, then click 'Create' and close the window. Ignore the save message. 18 | * 7: Click the 'Off' state button to turn the LED on. Congratulations! 19 | * 20 | * Debugging: 21 | * Open the CMRI > CMRI Monitor window to check what is getting sent. 22 | * With 'Show raw data' turned on the output looks like: 23 | * [41 54 01 00 00 00 00 00] Transmit ua=0 OB=1 0 0 0 0 0 24 | * 25 | * 0x41 = 65 = A = address 0 26 | * 0x54 = 84 = T = transmit, i.e. PC -> C/MRI 27 | * 0x01 = 0b00000001 = turn on the 1st bit 28 | * 0x00 = 0b00000000 = all other bits off 29 | */ 30 | 31 | #include 32 | 33 | CMRI cmri; // defaults to a SMINI with address 0. SMINI = 24 inputs, 48 outputs 34 | 35 | void setup() { 36 | Serial.begin(9600, SERIAL_8N2); // make sure this matches your speed set in JMRI 37 | pinMode(13, OUTPUT); 38 | } 39 | 40 | void loop() { 41 | // 1: main processing node of cmri library 42 | cmri.process(); 43 | 44 | // 2: update output. Reads bit 0 of T packet and sets the LED to this 45 | digitalWrite(13, cmri.get_bit(0)); 46 | } 47 | 48 | -------------------------------------------------------------------------------- /examples/multiple_nodes_inputting/multiple_nodes_inputting.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * An example of driving multiple C/MRI nodes on a single Arduino 3 | * ============================================================== 4 | * Sets up pins 2 and 3 as outputs, and attaches each to a different SMINI node. 5 | * 6 | * To set up in JMRI: 7 | * 1: Create a new connection, 8 | * - type = C/MRI, 9 | * - connection = Serial, 10 | * - port = , 11 | * - speed = 9600 12 | * 2: Click 'Configure C/MRI nodes' and click 'Add Node' to create a new SMINI node 13 | * 3: In the same window, type in '1' as the address and click 'Add Node' to add a second SMINI node 14 | * 4: Click 'Done' 15 | * 5: Restart J/MRI and it should say "Serial: using Serial on COM" - congratulations! 16 | * 6: Open Tools > Tables > Lights and click 'Add' 17 | * 7: Add a new light at hardware address 0001, then click 'Create' 18 | * 8: Enter hardware address 1001 and click 'Create'. This will create a second light. 19 | * 8: Click the 'Off' state button to turn each LED on. Congratulations! 20 | */ 21 | 22 | #include 23 | 24 | CMRI cmri0(0); // first SMINI, 24 inputs, 48 outputs 25 | CMRI cmri1(1); // second SMINI, another 24 inputs and another 48 outputs 26 | 27 | void setup() { 28 | Serial.begin(9600, SERIAL_8N2); // make sure this matches your speed set in JMRI 29 | 30 | cmri0.set_bit(0, HIGH); // system name CS0001 31 | cmri0.set_bit(22, LOW); // system name CS0023 32 | cmri0.set_bit(23, HIGH); // system name CS0024 33 | 34 | cmri1.set_bit(0, HIGH); // system name CS1001 35 | cmri1.set_bit(1, HIGH); // system name CS1002 36 | cmri1.set_bit(22, HIGH); // system name CS10023 37 | } 38 | 39 | char c; 40 | void loop() { 41 | // 1: main processing node of cmri library 42 | while (Serial.available() > 0) 43 | { 44 | c = Serial.read(); 45 | cmri0.process_char(c); 46 | cmri1.process_char(c); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/inputs_and_outputs/inputs_and_outputs.ino: -------------------------------------------------------------------------------- 1 | /** 2 | * An example of C/MRI inputs and outputs 3 | * ====================================== 4 | * Sets up pins 2-5 as an outputs with LEDs, and pins 6-9 as inputs with pullups. 5 | * 6 | * 1: Set up a JMRI connection, see hello_world, steps 1-4 7 | * 2: Open Tools > Tables > Lights and click 'Add' 8 | * 3: Add a new light at hardware address 1, then click 'Create'. 9 | * 4: Repeat for hardware address 2, 3, 4 and close the window. Ignore the save message. 10 | * 5: Click on 'Sensors' and set up new sensors for hardware address 1, 2, 3 and 4. 11 | * 6: You'll notice the TX and RX LEDs burst into life. This is JMRI polling the state of our sensors. 12 | * 7: Ground pin 6, you'll see sensor #1 go Active, while the rest are Inactive. 13 | * 8: Switch to Lights and play around with the State buttons. Congratulations! 14 | * 15 | * Debugging: 16 | * Open the CMRI > CMRI Monitor window to check what is getting sent and received. 17 | * With 'Show raw data' turned on the output looks like: 18 | * [41 50] Poll ua=0 19 | * [41 52 01 00 00] Receive ua=0 IB=1 0 0 20 | * 21 | * 0x41 = 65 = A = address 0 22 | * 0x50 = 80 = P = poll, i.e. PC asking C/MRI to transmit its state back to PC 23 | * 24 | * 0x41 = 65 = A = address 0 25 | * 0x52 = 82 = R = receive, i.e. PC receiving state data from C/MRI 26 | * 0x01 = 0b00000001 = 1st bit is high 27 | * 0x00 = 0b00000000 = all other bits off 28 | */ 29 | 30 | #include 31 | 32 | CMRI cmri; // defaults to a SMINI with address 0. SMINI = 24 inputs, 48 outputs 33 | 34 | void setup() { 35 | Serial.begin(9600, SERIAL_8N2); // make sure this matches your speed set in JMRI 36 | for (int i=2; i<=5; i++) { pinMode(i, OUTPUT); } 37 | for (int i=6; i<=9; i++) { pinMode(i, INPUT); digitalWrite(i, HIGH); } 38 | } 39 | 40 | void loop() { 41 | // 1: build up a packet 42 | cmri.process(); 43 | 44 | // 2: update outputs 45 | digitalWrite(2, cmri.get_bit(0)); 46 | digitalWrite(3, cmri.get_bit(1)); 47 | digitalWrite(4, cmri.get_bit(2)); 48 | digitalWrite(5, cmri.get_bit(3)); 49 | 50 | // 3: update inputs (invert digitalRead due to the pullups) 51 | cmri.set_bit(0, !digitalRead(6)); 52 | cmri.set_bit(1, !digitalRead(7)); 53 | cmri.set_bit(2, !digitalRead(8)); 54 | cmri.set_bit(3, !digitalRead(9)); 55 | } 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /CMRI.h: -------------------------------------------------------------------------------- 1 | /* 2 | CMRI - a small library for Arduino to interface with the C/MRI 3 | computer control system for model railroads 4 | Copyright (C) 2012 Michael Adams (www.michael.net.nz) 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #ifndef CMRI_h 27 | #define CMRI_h 28 | 29 | #define _CMRI_VERSION 1.5.1 // version of this library 30 | #include 31 | 32 | class CMRI 33 | { 34 | public: 35 | CMRI(unsigned int address = 0, unsigned int input_bits = 24, unsigned int output_bits = 48, Stream& serial_class = Serial); 36 | void set_address(unsigned int address); 37 | 38 | bool process(); 39 | bool process_char(char c); 40 | void transmit(); 41 | 42 | bool get_bit(int n); 43 | char get_byte(int n); 44 | 45 | bool set_bit(int n, bool b); 46 | bool set_byte(int n, char b); 47 | 48 | enum { 49 | MAX = 258, // max packet length in bytes (64 i/o cards @ 32 bits each + packet type and address bytes) 50 | INIT = 'I', // PC is telling us stuff we don't really care about 51 | SET = 'T', // as in TX from the PC => Arduino, PC is SETing our status 52 | GET = 'R', // as in TX from Arduino => PC, PC is GETing our status 53 | POLL = 'P', // PC wants to know our status 54 | NOOP = 0x00, // do nothing 55 | STX = 0x02, // start byte 56 | ETX = 0x03, // end byte 57 | ESC = 0x10, // escape byte 58 | }; 59 | 60 | private: 61 | enum {PREAMBLE_1,PREAMBLE_2,PREAMBLE_3,DECODE_ADDR,DECODE_CMD,DECODE_DATA,DECODE_ESC_DATA,IGNORE_CMD,IGNORE_DATA,IGNORE_ESC_DATA,POSTAMBLE_SET,POSTAMBLE_POLL,POSTAMBLE_OTHER}; 62 | 63 | int _address; 64 | int _rx_length; 65 | int _tx_length; 66 | char _rx_packet_type; 67 | char* _rx_buffer; 68 | char* _tx_buffer; 69 | 70 | Stream& _serial; 71 | 72 | // parsing state variables 73 | int _mode; 74 | int _rx_index; 75 | 76 | uint8_t _decode(uint8_t c); // process one character received from serial port 77 | }; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ################# 3 | ## Eclipse 4 | ################# 5 | 6 | *.pydevproject 7 | .project 8 | .metadata 9 | bin/** 10 | tmp/** 11 | tmp/**/* 12 | *.tmp 13 | *.bak 14 | *.swp 15 | *~.nib 16 | local.properties 17 | .classpath 18 | .settings/ 19 | .loadpath 20 | 21 | # External tool builders 22 | .externalToolBuilders/ 23 | 24 | # Locally stored "Eclipse launch configurations" 25 | *.launch 26 | 27 | # CDT-specific 28 | .cproject 29 | 30 | # PDT-specific 31 | .buildpath 32 | 33 | 34 | ################# 35 | ## Visual Studio 36 | ################# 37 | 38 | ## Ignore Visual Studio temporary files, build results, and 39 | ## files generated by popular Visual Studio add-ons. 40 | 41 | # User-specific files 42 | *.suo 43 | *.user 44 | *.sln.docstates 45 | 46 | # Build results 47 | **/[Dd]ebug/ 48 | **/[Rr]elease/ 49 | *_i.c 50 | *_p.c 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.vspscc 65 | .builds 66 | **/*.dotCover 67 | 68 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 69 | #**/packages/ 70 | 71 | # Visual C++ cache files 72 | ipch/ 73 | *.aps 74 | *.ncb 75 | *.opensdf 76 | *.sdf 77 | 78 | # Visual Studio profiler 79 | *.psess 80 | *.vsp 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper* 84 | 85 | # Installshield output folder 86 | [Ee]xpress 87 | 88 | # DocProject is a documentation generator add-in 89 | DocProject/buildhelp/ 90 | DocProject/Help/*.HxT 91 | DocProject/Help/*.HxC 92 | DocProject/Help/*.hhc 93 | DocProject/Help/*.hhk 94 | DocProject/Help/*.hhp 95 | DocProject/Help/Html2 96 | DocProject/Help/html 97 | 98 | # Click-Once directory 99 | publish 100 | 101 | # Others 102 | [Bb]in 103 | [Oo]bj 104 | sql 105 | TestResults 106 | *.Cache 107 | ClientBin 108 | stylecop.* 109 | ~$* 110 | *.dbmdl 111 | Generated_Code #added for RIA/Silverlight projects 112 | 113 | # Backup & report files from converting an old project file to a newer 114 | # Visual Studio version. Backup files are not needed, because we have git ;-) 115 | _UpgradeReport_Files/ 116 | Backup*/ 117 | UpgradeLog*.XML 118 | 119 | 120 | 121 | ############ 122 | ## Windows 123 | ############ 124 | 125 | # Windows image file caches 126 | Thumbs.db 127 | 128 | # Folder config file 129 | Desktop.ini 130 | 131 | 132 | ############# 133 | ## Python 134 | ############# 135 | 136 | *.py[co] 137 | 138 | # Packages 139 | *.egg 140 | *.egg-info 141 | dist 142 | build 143 | eggs 144 | parts 145 | bin 146 | var 147 | sdist 148 | develop-eggs 149 | .installed.cfg 150 | 151 | # Installer logs 152 | pip-log.txt 153 | 154 | # Unit test / coverage reports 155 | .coverage 156 | .tox 157 | 158 | #Translations 159 | *.mo 160 | 161 | #Mr Developer 162 | .mr.developer.cfg 163 | 164 | # Mac crap 165 | .DS_Store 166 | -------------------------------------------------------------------------------- /CMRI.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | CMRI - a small library for Arduino to interface with the C/MRI 3 | computer control system for model railroads 4 | Copyright (C) 2012 Michael Adams (www.michael.net.nz) 5 | All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a 8 | copy of this software and associated documentation files (the "Software"), 9 | to deal in the Software without restriction, including without limitation 10 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 11 | and/or sell copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #include "CMRI.h" 27 | #include 28 | 29 | CMRI::CMRI(unsigned int address, unsigned int input_bits, unsigned int output_bits, Stream &serial_class) 30 | // store details 31 | : _address(address) 32 | , _rx_length((output_bits + 7) / 8) 33 | , _tx_length((input_bits + 7) / 8) 34 | 35 | // set state 36 | , _rx_buffer((char *) malloc(_rx_length)) 37 | , _tx_buffer((char *) malloc(_tx_length)) 38 | 39 | , _serial(serial_class) 40 | 41 | // parsing state 42 | , _mode(PREAMBLE_1) 43 | , _rx_index(0) 44 | 45 | { 46 | // clear to zero 47 | for(int i=0; i<_rx_length; i++) 48 | _rx_buffer[i] = 0; 49 | for(int i=0; i<_tx_length; i++) 50 | _tx_buffer[i] = 0; 51 | } 52 | 53 | void CMRI::set_address(unsigned int address) 54 | { 55 | _address = address; 56 | } 57 | 58 | // reads in serial data, decodes packets 59 | // automatically responds to POLL requests 60 | // returns packet type so if we got a SET request you know to update your outputs 61 | bool CMRI::process() 62 | { 63 | while (_serial.available() > 0) 64 | { 65 | if (process_char(_serial.read())) 66 | { 67 | return true; 68 | } 69 | } 70 | return false; 71 | } 72 | 73 | bool CMRI::process_char(char c) 74 | { 75 | // if it's a SET that's fine do nothing 76 | // if it's an INIT that's also fine, we don't really care 77 | // if it's a GET, well, do nothing since it must be someone else replying 78 | // if it's a POLL then reply straight away with our data 79 | switch (_decode(c)) 80 | { 81 | case POLL: 82 | transmit(); 83 | return true; 84 | 85 | case SET: 86 | return true; 87 | 88 | default: 89 | return false; 90 | } 91 | } 92 | 93 | 94 | 95 | // public methods 96 | 97 | bool CMRI::get_bit(int pos) 98 | { 99 | // 1: divide index by 8 to get byte offset 100 | char c = get_byte(pos / 8); 101 | // 2: return bit at that location 102 | return (bool) ((c >> (pos % 8)) & 0x01); 103 | } 104 | 105 | char CMRI::get_byte(int pos) 106 | { 107 | if (pos >= _rx_length) 108 | return 0; // out of bounds 109 | else 110 | return _rx_buffer[pos]; 111 | } 112 | 113 | bool CMRI::set_bit(int pos, bool bit) 114 | { 115 | if ((pos + 7) / 8 >= _tx_length) 116 | return false; // out of bounds 117 | else 118 | { 119 | int index = pos / 8; 120 | _tx_buffer[index] = bit 121 | ? _tx_buffer[index] | 1 << pos % 8 // if bit=1, then OR it 122 | : _tx_buffer[index] & ~(1 << pos % 8) // if bit=0, then NAND it 123 | ; 124 | return true; 125 | } 126 | } 127 | 128 | bool CMRI::set_byte(int pos, char b) 129 | { 130 | if (pos >= _tx_length) 131 | return false; // out of bounds 132 | else 133 | { 134 | _tx_buffer[pos] = b; 135 | return true; 136 | } 137 | } 138 | 139 | 140 | void CMRI::transmit() 141 | { 142 | delayMicroseconds(50); //a minscule delay to let things recover 143 | _serial.write(255); 144 | _serial.write(255); 145 | _serial.write(STX); 146 | _serial.write(65 + _address); 147 | _serial.write(GET); 148 | for (int i=0; i<_tx_length; i++) 149 | { 150 | if (_tx_buffer[i] == ETX) 151 | _serial.write(ESC); // escape because this looks like an STX bit (very basic protocol) 152 | if (_tx_buffer[i] == ESC) 153 | _serial.write(ESC); // escape because this looks like an escape bit (very basic protocol) 154 | _serial.write(_tx_buffer[i]); 155 | } 156 | _serial.write(ETX); 157 | _serial.flush(); 158 | } 159 | 160 | // Private methods 161 | uint8_t CMRI::_decode(uint8_t c) 162 | { 163 | switch(_mode) 164 | { 165 | case PREAMBLE_1: 166 | _rx_index = 0; 167 | if (c == 0xFF) 168 | _mode = PREAMBLE_2; 169 | break; 170 | 171 | case PREAMBLE_2: 172 | if (c == 0xFF) 173 | _mode = PREAMBLE_3; 174 | else 175 | _mode = PREAMBLE_1; 176 | break; 177 | 178 | case PREAMBLE_3: 179 | if (c == STX) 180 | _mode = DECODE_ADDR; 181 | else 182 | _mode = PREAMBLE_1; 183 | break; 184 | 185 | case DECODE_ADDR: 186 | if (c == 'A' + _address) 187 | _mode = DECODE_CMD; 188 | else if (c >= 'A') 189 | _mode = IGNORE_CMD; 190 | else 191 | _mode = PREAMBLE_1; 192 | break; 193 | 194 | case DECODE_CMD: 195 | if (c == SET) 196 | _mode = DECODE_DATA; 197 | else if (c == POLL) 198 | goto POSTAMBLE_POLL; 199 | else 200 | _mode = POSTAMBLE_OTHER; 201 | break; 202 | 203 | case IGNORE_CMD: 204 | _mode = IGNORE_DATA; 205 | break; 206 | 207 | case DECODE_DATA: 208 | if (c == ESC) 209 | _mode = DECODE_ESC_DATA; 210 | else if (c == ETX) 211 | goto POSTAMBLE_SET; 212 | else if (_rx_index >= _rx_length) 213 | { } 214 | else 215 | _rx_buffer[_rx_index++] = c; 216 | break; 217 | 218 | case DECODE_ESC_DATA: 219 | if (_rx_index >= _rx_length) 220 | { } 221 | else 222 | _rx_buffer[_rx_index++] = c; 223 | _mode = DECODE_DATA; 224 | break; 225 | 226 | case IGNORE_DATA: 227 | if (c == ESC) 228 | _mode = IGNORE_ESC_DATA; 229 | else if (c == ETX) 230 | goto POSTAMBLE_IGNORE; 231 | break; 232 | 233 | case IGNORE_ESC_DATA: 234 | _mode = IGNORE_DATA; 235 | break; 236 | 237 | case POSTAMBLE_OTHER: 238 | _mode = PREAMBLE_1; 239 | break; 240 | } 241 | return NOOP; 242 | 243 | POSTAMBLE_SET: 244 | _mode = PREAMBLE_1; 245 | return SET; 246 | 247 | POSTAMBLE_POLL: 248 | _mode = PREAMBLE_1; 249 | return POLL; 250 | 251 | POSTAMBLE_IGNORE: 252 | _mode = PREAMBLE_1; 253 | return NOOP; 254 | } 255 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ArduinoCMRI 2 | =========== 3 | 4 | * Author: Michael Adams () 5 | * Copyright (C) 2012 Michael D K Adams. 6 | * Released under the MIT license. 7 | 8 | ArduinoCMRI is an library for connecting your Arduino to your model railroad. It lets you easily interface lights, switches, servos, and other inputs and outputs with [JMRI][1], the Java Model Railroad Interface. It does this by emulating Bruce Chubb's [Computer/Model Railroad Interface][2] (C/MRI) System, an I/O system designed for interfacing model railroads with computers. 9 | 10 | By emulating the C/MRI system you get maximum flexibility to tailor your solution to fit your needs, and you can reuse hardware you already own. A single Arduino can easily control hundreds of signals and points on your railroad! 11 | 12 | Features: 13 | * Simple API that handles GET, SET, and POLL requests from JMRI automatically. 14 | * Easy access to input and output data. 15 | * Emulates an SMINI up to a SUSIC with up to 2048 digital lines available. 16 | * Error tolerant. 17 | 18 | For documentation and examples please see the main [project blog][3]. 19 | 20 | [1]: http://jmri.org/ 21 | [2]: http://www.jlcenterprises.net/ 22 | [3]: http://utrainia.michael.net.nz/tag/cmri 23 | 24 | Requirements 25 | ------------ 26 | * JMRI -- http://jmri.org/ 27 | * An Arduino -- http://arduino.cc/ -- More modern Arduino boards (such as the Uno) are supported now! 28 | 29 | Installation 30 | ------------ 31 | Download the ZIP archive (https://github.com/madleech/ArduinoCMRI/zipball/master) and extract it to your Arduino folder under `libraries/CMRI`. 32 | 33 | Restart the Arduino IDE and you should see in File > Examples > CMRI entires for hello world and an input/output example. 34 | 35 | 36 | Code Examples 37 | ------------- 38 | Here is the 'hello\_world' example program, included in the download: 39 | 40 | #include 41 | 42 | CMRI cmri; 43 | 44 | void setup() { 45 | Serial.begin(9600, SERIAL_8N2); // SERIAL_8N2 to match what JMRI expects CMRI hardware to use 46 | pinMode(13, OUTPUT); 47 | } 48 | 49 | void loop() { 50 | cmri.process(); 51 | digitalWrite(13, cmri.get_bit(0)); 52 | } 53 | 54 | This connects a Light in JMRI to the built in LED on your Arduino. Toggle the light in JMRI and your Arduino will light up. 55 | 56 | The code is pretty simple. `#include ` includes the library, while `CMRI cmri();` creates a new CMRI object with default values (address = 0, 24 inputs to PC, and 48 outputs from PC). The `Serial.begin` and `pinMode` lines set up our serial port and tell the LED pin that it's going to be an output. 57 | 58 | The main loop is where the magic happens. `cmri.process()` receives data from the PC and processes it automatically, responding to the PC if required, and updating it's internal state ready for us to read the data back out. 59 | 60 | To access the data we sent it, we use the `cmri.get_bit(n)` function to get the value of bit number *n*. 61 | 62 | If you wanted to extend this demo to transmit data back to the PC, all you need to do is connect your digital inputs to `cmri.set_bit(n, value)` calls, and the `cmri.process()` function will take care of the rest. 63 | 64 | Documentation 65 | ------------- 66 | **CMRI(unsigned int address = 0, unsigned int input\_bits = 24, unsigned int output\_bits = 48)** 67 | Creates a new CMRI object. The default values will create a device that matches the capabilities of an SMINI node. If you want to bind to a different node address, or address more or less inputs, you can alter it here. The maximum combined number of addressable inputs and outputs is 2048 (C/MRI limitation). The library will work fine with any number of inputs and outputs, it will simply ignore out-of-range data. 68 | 69 | **void set\_address(unsigned int address)** 70 | Sets the address of the C/MRI node. 71 | 72 | **char process()** 73 | Reads in available data from the serial port and acts accordingly: 74 | * For POLL requests, it replies with the current state of the input data. 75 | * For INIT requests, it does nothing. 76 | * For SET/TRANSMIT (T) requests, it updates the output data. 77 | 78 | Return value is NULL for no valid packet received, or one of CMRI::INIT, CMRI::SET, CMRI::POLL depending on the packet type received. 79 | 80 | **bool process\_char(char c)** 81 | Similar to the CMRI::process method, but lets you manage the serial data yourself. Use this if you are processing more than 1 CMRI node in a system. 82 | 83 | Return value is true if a valid packet has been received and processing of it has finished. Otherwise it returns false. 84 | 85 | **void transmit()** 86 | Transmits the current state of the input data back to the PC. Creates a CMRI::GET packet. 87 | 88 | **bool get\_bit(int n)** 89 | Reads a bit from of the last valid input data received. Use this to update your signals, points, etc. 90 | 91 | **char get\_byte(int n)** 92 | Reads an entire byte from the input buffer. Use this with shiftOut and some shift registers to vastly expand your I/O capabilities. 93 | 94 | **bool set\_bit(int n, bool b)** 95 | Updates the output buffer to the specified value. Data will be transmitted to the PC either when transmit() is called, or when the next POLL packet is received. 96 | 97 | **bool set\_byte(int n, char b)** 98 | Updates an entire byte of the output buffer. Use this with shiftIn and some shift registers to add many extra digital inputs to your system. 99 | 100 | 101 | Troubleshooting 102 | --------------- 103 | **JMRI reports: unrecognized rep: "50 aa 00" etc / no responses to POLL requests** 104 | Make sure your `Serial.begin(...)` line is `Serial.begin(9600, SERIAL_8N2)`. Real C/MRI hardware uses 8 data bits, no parity, and *2* stop bits, which is different to regular serial which only has 1 stop bit. Most (i.e. cheap) USB to serial adapters will usually ignore the differences, however some are more rigorous in their parsing of serial data and won't work unless the Arduino is actually transmitting using 2 stop bits. Hence the `SERIAL_8N2` in the `Serial.begin(...)` line. 105 | 106 | Protocol 107 | -------- 108 | The C/MRI protocol is fairly simple, however [documentation][4] can be difficult to come across. 109 | 110 | The system usually runs over a serial bus (although an ISA bus version was also produced), and uses the slightly obscure protocol of 8 databits, no parity, and 2 stop bits. Luckily we can (usually, see above) get away with just using the default 8n1 @ 9600 setting that Arduino's use by default and USB takes care of the rest. 111 | 112 | Each packet is framed as follows: `0xFF 0xFF 0x02` which is two 255 bits, and one STX (start) bit. The data then follows, and is closed by an ETX packet, `0x03`. 113 | 114 | The first two bytes of data are always the address, and the packet type. 115 | 116 | Address = decimal 65 + address, which corresponds to uppercase `A` for address = 0, `B` = 1, etc. 117 | 118 | Packet type = uppercase ASCII `I`, `T`, `R`, or `P`, representing INIT, SET, GET and POLL respectively. 119 | 120 | For GET and SET packet types, the data bytes then follow. These are just plain binary, however since 0x03 may represent the ETX end frame, any sequence of 0x03 in the data stream must be escaped by a preceeding 0x10 byte. 121 | 122 | So a SET operation counting up in binary would look like: 123 | `[41 54 00 00 00 00 00 00]` 124 | `[41 54 01 00 00 00 00 00]` 125 | `[41 54 02 00 00 00 00 00]` 126 | `[41 54 10 03 00 00 00 00 00]` -- notice the escape byte in this one 127 | `[41 54 04 00 00 00 00 00]` 128 | 129 | The only documentation I could find for the STX and ETX byte values was by reading the JMRI source code. 130 | 131 | [4]: http://home.roadrunner.com/~jimngage/TRACTRONICS/MicroController/mr89c52f.htm 132 | 133 | License 134 | ------- 135 | Copyright (c) 2012 Michael D K Adams. http://www.michael.net.nz 136 | 137 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 138 | 139 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 140 | 141 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 142 | 143 | --------------------------------------------------------------------------------