Multitaking on AST-CAN485?

I have been working with the AST-CAN485 for a few months and found it to be easy to integrate into my multi-node CAN solution. :smiley:

However there is one challenge that I have not been able to overcome. If I only need to transmit CAN messages, I have no problem multitasking but for follower/receiver nodes, I have not found a way to implement multitasking. In the example receiver sketch, this line of code simply locks up the processor until a message is received:

while(can_cmd(&Msg) != CAN_CMD_ACCEPTED);

I have tried several non-blocking variations using the this an other CAN status flags, unsuccessfully.

Is there a way to poll the CAN receiver, say every 100 or 250 milliseconds to see if a message is available to receive?

Thanks!

Doug

I might be wrong, but I don’t think the AST-CAN485 is capable of multitasking. You might be able to fake that using interrupts, but I think these can only execute code one line at a time. I think some PSOC’s are able to multitask but they can’t do it running Arduino code.

Glancing through the datasheet for the AT90CAN128 used on the AST-CAN485 shows that the CAN controller has the ability to interrupt the processor when a valid message object is received by the controller. Section 19.8 describes how to configure the interrupts for the CAN controller and Chapter 8 describes the processor interrupt system. So you should be able to enable an interrupt on receive from the CAN controller and poll the corresponding interrupt flag for the processor, or you could allow the interrupt to redirect program flow to an ISR designed to handle the interrupt. It looks like the interrupt will be vector number 19 at program address 0x0024.

As for the library you’re using, I can’t really speak as to how it functions or implements low-level control of the CAN controller, so you may need to re-write some of the code to take advantage of the things I’ve mentioned above. I would suspect that the library is already doing the polling that I mention, and that’s what’s tying up the processor. Moving the polling outside the library would allow you to add other tasks in between the polls. You might also check the documentation for the library to see if it has a way to add an ISR callback function that could accomplish the same thing. I hope this helps you out.

Mike

So I got curious this afternoon and started reviewing the code for the AST library linked on the Sparkfun page for the AST-CAN485. I don’t think the can_cmd() function is tying up the processor. All that function does is find an available message object (MOb) and sets it to receive any message that comes across the wire. I think it is the while loop around this function that is locking up the processor. The CAN controller on the AT90CAN128 only has 15 available MObs, so if all of them are used up the function returns CAN_CMD_REFUSED. This will cause the while loop to keep going until one frees up.

In the receiver example, I think it is the while loop on the next line that actually ties up the processor until a message is received. The can_get_status() function polls the specified MOb to see if a message has been received. If one hasn’t, then CAN_STATUS_NOT_COMPLETED is returned and the while loop repeats until one has. The can_get_status() function will also disable the MOb when a message is received thereby freeing the MOb up for another call to can_cmd(). So I think by rewriting or expanding the while loops you should be able to write a message receive function that can work concurrently with other tasks.

@mdancer @YellowDog Thanks for your posts and ideas. Its good to find others withing this the CAN485 and I will check this forum more frequently.

I did figure out a way to include multitasking in a CAN485 designed to be a receive CAN messages while also doing other tasks. I have extracted the code below from a sketch that I’ve tested extensively and found to be reliable. I have taken out most all of the code that does not relate to receiving CAN messages. There are lots of ways to improve on what I have done but, for me, its a workable start…


#include <ASTCanLib.h>

#include <at90can_drv.h>

#include <can_compiler.h>

#include <can_drv.h>

#include <can_lib.h>

#include <config.h>

#include <Wire.h>

// Time values to multitask between polling local switches and CAN controller

const long CAN_REFRESH_TIME = 50;

const long CAN_CMD_TIMEOUT = 50;

long CAN_Cmd_Timeout;

// CAN message constants

#define MESSAGE_ID 0 // Message ID

#define MESSAGE_PROTOCOL 1 // CAN protocol (0: CAN 2.0A, 1: CAN 2.0B)

#define MESSAGE_LENGTH 8 // Data length: 8 bytes

#define MESSAGE_RTR 0 // rtr bit

// CAN message object

st_cmd_t Msg;

// Buffer for CAN data

uint8_t Buffer[8] = {};

void setup() {

canInit(500000); // Initialise CAN port. must be before Serial.begin

Serial.begin(115200); // start serial port

Msg.pt_data = &Buffer[0]; // reference message data to buffer

// Initialise CAN packet.

// All of these will be overwritten by a received packet

Msg.ctrl.ide = MESSAGE_PROTOCOL; // Set CAN protocol (0: CAN 2.0A, 1: CAN 2.0B)

Msg.id.ext = MESSAGE_ID; // Set message ID

Msg.dlc = MESSAGE_LENGTH; // Data length: 8 bytes

Msg.ctrl.rtr = MESSAGE_RTR; // Set rtr bit

// Clear the message buffer

clearBuffer(&Buffer[0]);

// Send receive command to the CAN port controller

Msg.cmd = CMD_RX_DATA;

while (can_cmd(&Msg) != CAN_CMD_ACCEPTED);

Serial.println(“System Follower Node #2”);

}

void loop() {

// otherFunctions.poll();

char cmd = checkForCanMsg();

if (cmd != ‘0’) executeCmd(cmd);

}

// setCanRxMode ()

// Configure the CAN peripheral to be in receive mode

void setCanRxMode() {

// Send receive command to the CAN port controller

Msg.cmd = CMD_RX_DATA;

CAN_Cmd_Timeout = millis() + CAN_CMD_TIMEOUT;

U8 canCmdStatus = 0x08; // != to refused or accepted

while ( canCmdStatus != CAN_CMD_ACCEPTED) {

canCmdStatus = can_cmd(&Msg);

if (millis() > CAN_Cmd_Timeout) {

if ( canCmdStatus == CAN_CMD_REFUSED) {

Serial.println(“Error: CAN command refused. Reinitializing CAN peripheral…”);

canInit(500000); // reinitialize the CAN peripheral

}

else {

Serial.println(“Error: CAN command not accepted - timeout”);

}

break;

}

}

}

char checkForCanMsg() {

char cmd = ‘0’;

U8 canStatus = can_get_status(&Msg);

if (canStatus == CAN_STATUS_NOT_COMPLETED) {

// do nothing

}

else if ( canStatus == CAN_STATUS_ERROR ) {

Serial.println(" Error in configuration or in the CAN communication");

setCanRxMode();

}

else {

// Data is now available in the message object

// Print received data to the terminal

serialPrintData(&Msg);

// extract the command

cmd = Msg.pt_data[0];

// Setup for the next receive

// Clear the message buffer

clearBuffer(&Buffer[0]); // clear the rx buffer

// Msg.cmd = CMD_NONE; // clear the CAN status

// can_cmd(&Msg);

// Setup CAN peripheral to receive the next message

setCanRxMode();

}

return (cmd);

}

void serialPrintData(st_cmd_t *msg) {

for (int i = 0; i < msg->dlc; i++) {

Serial.print(char(msg->pt_data*));*
}
Serial.print(“\r\n”);
}
void executeCmd(char cmd) {
switch (cmd) {
case ‘1’:
cabHeatOn((void*)‘R’); // Cabin heating on
break;
case ‘2’:
cabHeatOff((void*)‘R’); // Cabin heating off
break;
case ‘3’:
dhwOn((void*)‘R’); // DHW heating on
break;
case ‘4’:
dhwOff((void*)‘R’); // DHW heating off
break;
default:
Serial.print("Could not understand the command: ");
Serial.println( cmd);
}
}