Subscribe via RSS
15Apr/124

First CAN Node: LEDs and Sensors

Despite my layout being tiny, I've decided that my layout will need two nodes. One will do the sensing of trains + management of LEDs and the other will control the throttle, points and isolation. I've now finished the building and wiring of the first node. One note for below: I've used Frame IDs to differentiate the messages across the network; unique numbers are used to define the message type and each node will have filters applied to only receive nodes that are specifically for them.

Detecting Trains

Train sensing has been implemented via light sensors. I needed 24 sensors around the track and decided that multiplexing was the only way to read them all. 3x 4051 ICs came in handy and were configured as per the schematic below. You can find a better explanation on light sensor implementation in the article here.

LED+Sensors CAN Node

I've simplified the code for reading the sensors as I'll ensure all of them have sufficient lighting and therefore 'stable' values. The circuit waits a second after power-up to read the initial sensor values. It then marks each one as 'active' if the value is greater than 20 'points' above the initial value. If a reading comes in lower then the initially read value then that value is lowered accordingly. I've also created a special command (frame id: 0x333) to allow a reset of all sensor values (specifically if ambient light proves a problem.)

The node sends out the sensor values and then pauses 100ms. The CAN packet with sensor data has the frame ID 0x111 and then fills three bytes with the 24 sensors states. A '1' is used for occupied and a '0' for vacant. The message will be received by any node on the network with the appropriate filters set.

Controlling LEDs

To control the LEDs I've re-used the previous MAX7219 ICs that I had lying around. There's a much more in-depth article here on how to use this chip. The MAX7219 requires three wires to the Arduino... but I'd already used up all the digital pins. Fortunately you can use the Analog pins just as easily.

LEDs on new layout

LEDs on new layout

As the MAX7219 can receive bytes per row to control the LEDs I simply pass through the data from the CAN bus when appropriate. It would only take one CAN message as they contain 8 bytes of data. The Frame ID for this message is 0x222. You can see that I'm being pretty wasteful with Frame IDs but, fortunately, I don't have many specific messages to send.

Completed Sensor+LED Node

So, it was soldered and wired... quite a mess really. The CAN Controller was on-board but there was no 'direct communications' interface to the PC. This meant I had to pop the chip out (atmega328) each time I wanted to change the code... tedious and dangerous!! I managed to bend pins and confuse myself every now and then. Don't do this at home!

SENSOR+LED Node

Controller Node

I've got a third node hooked up to the computer via my trusty old Arduino Mega. This node transmits data from the CAN Bus to the PC via the standard serial-over-USB connection. A .NET application then shows the layout and allows you to interact with the LEDs and sensors accordingly. It shows the status of the sensors at any point in time and allows you to create rules in a sequence which can determine the speed and direction of the train.

The physical design of this node is simply the Arduino Mega plus the CAN schematic as seen in my previous blog post on implementing the MCP2515 controller.

  if (CAN.buffer0DataWaiting()) {
    CAN.readDATA_ff_0(&length,frame_data,&frame_id, &ext, &filter);
    Serial.write("X");
    Serial.write(frame_data[0]);
    Serial.write(frame_data[1]);
    Serial.write(frame_data[2]);
  }
  
  while (Serial.available() >= 9) {
    // read the incoming byte:
    b = Serial.read();
    if (b == 'L') {
      for (int i = 0; i < 8; i++) frame_data[i] = Serial.read();
      frame_id = 0x222;
      CAN.load_ff_0(8, &frame_id, frame_data, false);
    } else if (b == 'D') {
      for (int i = 0; i < 8; i++) frame_data[i] = Serial.read();
      frame_id = 0x666;
      CAN.load_ff_0(8, &frame_id, frame_data, false);
      //printBuf(frame_id, frame_data);
    } else if (b == 'R') {
      for (int i = 0; i < 8; i++) frame_data[i] = Serial.read();
      frame_id = 0x333;
      CAN.load_ff_0(8, &frame_id, frame_data, false);      
    } else if (b == 'P') {
      for (int i = 0; i < 8; i++) frame_data[i] = Serial.read();
      frame_id = 0x777;
      CAN.load_ff_0(8, &frame_id, frame_data, false);            
    }
  }

LED+Sensor Node Code

The sensor code consisted of an initial sensor read and then constant sensor checking. The sensors were then determined to be either 'occupied' or 'vacant' and then the data was sent. There are no 'smarts' here... the sensor data is simply hammered over the network.

The receive buffers are also checked for a Frame ID of 0x222 or 0x333. The filters are set appropriately to ensure that only these messages trigger the interrupts. The LED data is sent to the MAX7219 controller on the 0x222 message and the sensors are reset on 0x333.

void loop() {	
  send_data[0] = 0;
  send_data[1] = 0;
  send_data[2] = 0;
  for (sens = 0; sens < 8; sens++) {
    setOutputBit(sens);
    sensor_read = readInput(0);
    if (sensor_read < sensor[sens]) sensor[sens] = sensor_read;
    send_data[0] |= (((20 + sensor[sens])      <= sensor_read) << sens);
    sensor_read = readInput(1);
    if (sensor_read < sensor[sens]) sensor[sens] = sensor_read;
    send_data[1] |= (((20 + sensor[sens + 8])  <= sensor_read) << sens);
    sensor_read = readInput(2);
    if (sensor_read < sensor[sens]) sensor[sens] = sensor_read;
    send_data[2] |= (((20 + sensor[sens + 16]) <= sensor_read) << sens);
  }
  frame_id = 0x111;
  CAN.load_ff_0(3, &frame_id, send_data, false);

  frame_data[0] = 0;

  data1 = CAN.buffer0DataWaiting();
  data2 = CAN.buffer1DataWaiting();

  if (data1 || data2) {
    if (data1) CAN.readDATA_ff_0(&length, frame_data, &frame_id, &ext, &filter);
    if (data2) CAN.readDATA_ff_1(&length, frame_data, &frame_id, &ext, &filter);

    if (frame_id == 0x222) {
      for (int i = 0; i < 8; i++) lcl.setRow(0, i, frame_data[i]);
    } else if (frame_id == 0x333) {
      for (sens = 0; sens < 8; sens++) {
        setOutputBit(sens);
        sensor[sens] = readInput(0);
        sensor[sens + 8] = readInput(1);
        sensor[sens + 16] = readInput(2);
      }   
    }
 }
 delay(100);
}

Controller Node Code

The controller was the middle-man for translating PC code to the CAN bus and vice-versa. Sensor data from the CAN bus was converted to serial data with a leading 'X' byte. On the contrary LED commands were received from the PC with a leading 'L' byte. The following 8 bytes were sent as a single message onto the CAN bus once this character was seen.

    using System.IO.Ports;
    SerialPort _serialPort;

    private void SetupComPort() {
        _serialPort = new SerialPort("COM3", 9600, Parity.None, 8, StopBits.One);
        _serialPort.Handshake = Handshake.None;
        _serialPort.DataReceived += new SerialDataReceivedEventHandler(HandleData);
        _serialPort.Open();
    }

    private void cb_CheckedChanged(object sender, EventArgs e)
    {
        Byte[] bytesToSend = new Byte[9];
        bytesToSend[0] = (byte)'L';
        for (int i = 0; i < 8; i++) {
            for (int z = 0; z < 8; z++) bytesToSend[i+1] |= (byte)((LEDs[(i*8) + z].Checked ? 1 : 0) << z);
        }
        _serialPort.Write(bytesToSend, 0, 9);
    }

    private void UpdateSpeed()
    {
        Byte[] bytesToSend = new Byte[9];
        bytesToSend[0] = (byte)'D';
        bytesToSend[1] = (byte)(direction ? 1 : 0);
        bytesToSend[2] = (byte)hSpeed.Value;
        bytesToSend[3] = (byte)points[0]; //for loop? probably should.
        bytesToSend[4] = (byte)points[1];
        bytesToSend[5] = (byte)points[2];
        bytesToSend[6] = (byte)points[3];
        bytesToSend[7] = (byte)points[4];
        bytesToSend[8] = (byte)points[5];
        _serialPort.Write(bytesToSend, 0, 9);
        Console.WriteLine("Sent: D" + bytesToSend[1] + "-" + bytesToSend[2]);
    }

Notes learnt from hooking all this up

  • CAN Filters work, but you can still see messages
    On the CAN controller you can set filters to only allow specific messages through. It actually turns out that, regardless of the filter being set, if you constantly poll either buffer for a message that, if there has been a message sent, it will be available and can be received from the network. Filters simply prevent the message from triggering the 'message waiting in buffer' interrupts. So, the best bet is that you check for a message in a buffer first rather than simply constantly attempt to receive messages. Let the CAN IC tell you that a message is waiting!
  • You can easily spam a CAN network
    Sending too many messages over the network will delay or prevent transmission of other nodes' messages. One node can send too many messages over the network preventing another from successfully getting messages into a third nodes' buffers. It's all down to how quickly the third node can receive the messages. If both receiving buffers are full then the message will fly on past and not be seen.
  • Inserting and removing ICs
    PLEASE only do this when you need to and do this with care! Lever them out from both ends and DO NOT just use your fingers. Insert them evenly as well. It's too damn easy to destroy these delicate and expensive components.

...next will be a node to control the thottle, isolated blocks and point-servos.