Note: This article was updated in February 2020.

NoCAN networks are managed by a Raspberry Pi, which can usually provide a pretty accurate time source, thanks to NTP. In that situation, if some nodes need date/time information, they could subscribe to a channel called "rtc" (for Real Time Clock) and then you can push the date/time information to the nodes on a regular basis. On a Rasberry-Pi, it could be as simple as typing the following command:

nocanc publish "rtc" `date -u -Iseconds`

The above example assumes that you are sending time in UTC using ISO 8601 format and that your nodes can process that information. You could, of course, use another format.

Oops no Internet

NTP is fine if you have Internet access, but it won't work if your NoCAN network is managed by a Raspberry-Pi that has no such connection. Luckily, it turns out that you can configure any CANZERO node into an RTC clock. Simply set the initial time/date at the beginning and then let the node keep track of time for you and broadcast that information to your Raspberry-Pi and the rest of your NoCAN network.

The CANZERO comes with a 32.756kHz crystal that can be used to drive the internal RTC of the SAMD21G18 32-bit onboard MCU. And there is even an Arduino library for that. In the Arduino IDE, simply go to Sketch > Include Library > Manage Libraries... and search for RTCZero by Arduino. Install the library.

We will now create a sketch that uses two NoCAN channels:

  • rtc: this channel will broadcast the time every second.
  • rtc/set: this channel will be used to set up the current date and time, typically once at the start.

The code is a bit long because of the addition of functions to parse date/time strings in ISO 8601 format:

#include <nocan.h>
#include <RTCZero.h>

NocanChannelId cid;
NocanChannelId scid;
RTCZero rtc;
byte last_second = 0;

int string_UTC(char *dest, byte year, byte month, byte day, byte hour, byte minute, byte second);

void setup() {
  // put your setup code here, to run once:
  Nocan.open();
  Nocan.registerChannel("rtc", &cid);
  Nocan.registerChannel("rtc/set",&scid);
  Nocan.subscribeChannel(scid);

  rtc.begin(); // initialize RTC to defaults 1:00 on Jan 1, 2001
  rtc.setTime(1, 0, 0);
  rtc.setDate(1, 1, 1);
}

void loop() {
  // put your main code here, to run repeatedly:
  NocanMessage msg;

  if (Nocan.receivePending())
  {
    Nocan.receiveMessage(&msg);
    // We don't need to check the channel_id here since we only subscribed to one channel.
    if (msg.data_len==20)
      update_rtc_from_string_UTC(msg.data);
  }
  else
  {
    if (last_second != rtc.getSeconds())
    {
      last_second = rtc.getSeconds();

      msg.channel_id = cid;
      msg.data_len = 20;
      string_UTC(msg.data, rtc.getYear(), rtc.getMonth(), rtc.getDay(), rtc.getHours(), rtc.getMinutes(), rtc.getSeconds());
      Nocan.publishMessage(msg);
    }
  }
}

#define DIGH(x) ((x)/10+'0')
#define DIGL(x) ((x)%10+'0')

int string_UTC(unsigned char *dest, byte year, byte month, byte day, byte hour, byte minute, byte second)
{
  // ISO 8601 format is 2011-08-30T13:22:53Z
  dest[0] = '2';
  dest[1] = '0';
  dest[2] = DIGH(year);
  dest[3] = DIGL(year);
  dest[4] = '-';
  dest[5] = DIGH(month);
  dest[6] = DIGL(month);
  dest[7] = '-';
  dest[8] = DIGH(day);
  dest[9] = DIGL(day);
  dest[10] = 'T';
  dest[11] = DIGH(hour);
  dest[12] = DIGL(hour);
  dest[13] = ':';
  dest[14] = DIGH(minute);
  dest[15] = DIGL(minute);
  dest[16] = ':';
  dest[17] = DIGH(second);
  dest[18] = DIGL(second);
  dest[19] = 'Z';
  return 20;
}

int to_num(const unsigned char *src)
{
  int r;
  if ((src[0]<'0') || (src[0]>'9'))
    return -1;
  r = (src[0]-'0')*10;
  if ((src[1]<'0') || (src[1]>'9'))
    return -1;
  return r + (src[1]-'0');
}

int update_rtc_from_string_UTC(const unsigned char *src)
{
  int year, month, day, hour, minute, second;
  if ((year = to_num(src+2))<0) return -1;
  if ((month = to_num(src+5))<0) return -1;
  if ((day = to_num(src+8))<0) return -1;

  if ((hour = to_num(src+11))<0) return -1;
  if ((minute = to_num(src+14))<0) return -1;
  if ((second = to_num(src+17))<0) return -1;

  rtc.setTime(hour, minute, second);
  rtc.setDate(year, month, day);
  return 0;
}

To keep things simple, we left error checking to the minimum in the above example. The above sketch is just an example. In your application, the node does not need to broadcast time every second.

You can upload the Arduino sketch directly from the Arduino IDE as described here.

Once the node is up and running, you can set the RTC of the node with the following command:

nocanc publish "rtc/set" `date -u -Iseconds`

After that, you can read the RTC clock of your CANZERO node with the following command:

nocanc read-channel "rtc" 

Alternative ways to upload the sketch

If you don't want or can't use the Arduino sketch upload feature mentioned above, you can upload the sketch manually as follows:

  1. In the Arduino IDE, generate the firmware by selecting Sketch > Export Compiled Binary.
  2. Next, locate the compiled binary by selecting Sketch > Show Sketch Folder, and look for the file ending with .omzlo_canzero.hex.
  3. You have two options for the manual sketch upload:
    • You can use the nocanc tool to upload this firmware to the target node as details in our big NoCAN tutorial.
    • If you don't like the command line, you can also use the new NoCAN web interface we announced in a previous post, and simply drag and drop the firmware file to upload it, as shown on the video below.

NoCAN is cool!

Spread the word, follow us on Twitter or on our Facebook page.