Currently the CANopenNode stack used by Zephyr is extremely out of date which the team is well aware of:
https://github.com/zephyrproject-rtos/zephyr/issues/70285
I have the fork from the National Taiwan University running as discussed in that thread but am having issues converting Zephyr's CANopenNode example to be compatible with it. The main file from the original Zephyr example that I've already edited is as follows:
/*
* Copyright (c) 2019 Vestas Wind Systems A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/settings/settings.h>
#include <canopennode.h>
#include <zephyr/drivers/can.h> // for the change bitrate function
#include "nvent_inc/stm32_backboard_predator.h"
// THREAD SUPPORT *************************************************************
#include <zephyr/device.h>
#include <zephyr/sys/printk.h>
#include <zephyr/sys/__assert.h>
#include <string.h>
/* size of stack area used by each thread */
#define STACKSIZE 1024
/* scheduling priority used by each thread */
#define PRIORITY 7
void helper_main(void *p0, void *p1, void *p2);
K_THREAD_DEFINE(helper_main_id, STACKSIZE, helper_main, NULL, NULL, NULL,
PRIORITY, 0, 0);
// ****************************************************************************
#define LOG_LEVEL CONFIG_CANOPEN_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(app);
#define CAN_INTERFACE DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus))
#define CAN_BITRATE (DT_PROP_OR(DT_CHOSEN(zephyr_canbus), bitrate, \
DT_PROP_OR(DT_CHOSEN(zephyr_canbus), bus_speed, \
CONFIG_CAN_DEFAULT_BITRATE)) / 1000)
/* default values for CO_CANopenInit() */
#define NMT_CONTROL \
CO_NMT_STARTUP_TO_OPERATIONAL \
| CO_NMT_ERR_ON_ERR_REG | CO_ERR_REG_GENERIC_ERR | CO_ERR_REG_COMMUNICATION
#define FIRST_HB_TIME 500
#define SDO_SRV_TIMEOUT_TIME 1000
#define SDO_CLI_TIMEOUT_TIME 500
#define SDO_CLI_BLOCK false
#define OD_STATUS_BITS NULL
#define idle() while(0x01){flush_logs();}
/* Global variables and objects */
//extern CO_t* CO; // CANopen object
void
change_bitrate(
uint32_t bitrate,
uint32_t samplerate)
{
struct can_timing timing;
uint32_t err_count = 0x00;
const struct device *const can_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus));
int ret;
ret = can_calc_timing(can_dev, &timing, bitrate, samplerate);
if (ret > 0) {
LOG_ERR("CBR::Sample-Point error: %d\n", ret);
++err_count;
}
if (ret < 0) {
LOG_ERR("CBR::Failed to calc a valid timing\n");
return;
}
ret = can_stop(can_dev);
if (ret != 0) {
LOG_ERR("CBR::Failed to stop CAN controller\n");
++err_count;
}
ret = can_set_timing(can_dev, &timing);
if (ret != 0) {
LOG_ERR("CBR::Failed to set timing\n");
++err_count;
}
ret = can_start(can_dev);
if (ret != 0) {
LOG_ERR("CBR::Failed to start CAN controller\n");
++err_count;
}
if (!err_count) {
LOG_INF("CAN rate set to %dbps\n", bitrate);
} // end if
} // end change_bitrate
void
helper_main(
void *p0,
void *p1,
void *p2)
{
(void)p0, (void)p1, (void)p2; // To quiet unused variables warning
LOG_INF("nVent task thread launched...");
flush_logs();
/*
if (stm32_backboard_inits() != _NO_ERROR) {
LOG_ERR("Error from stm32_backboard_inits");
} // end if
*/
#ifdef __USE_MEMFAULT_CLI
// Don't start the shell until all boot messages are printed
k_sleep(K_MSEC(5000));
console_init();
#endif // __USE_MEMFAULT_CLI
while(0x01) {
#ifdef __USE_MEMFAULT_CLI
shell_loop();
#endif // __USE_MEMFAULT_CLI
if (stm32_adc_poll() != _NO_ERROR) {
LOG_ERR("Failure to do ADC conversions");
} // end if
#ifndef DISABLE_I2C
if (stm32_i2c_poll() != _NO_ERROR) {
LOG_ERR("Failure to read I2C bus");
} // end if
#endif // DISABLE_I2C
// WARNING: These now require a parameter
//
// C:\nVent_work\zephyr_root\zephyr_repo\zephyrproject\zephyr\modules\canopennode\CO_driver_target.h
// line 236
CO_LOCK_OD(CO->CANmodule);
update_objdict();
CO_UNLOCK_OD(CO->CANmodule);
k_sleep(K_MSEC(100));
} // end while
}
/**
* @brief Main application entry point.
*
* The main application thread is responsible for initializing the
* CANopen stack and doing the non real-time processing.
*/
int main(void)
{
CO_NMT_reset_cmd_t reset = CO_RESET_NOT;
CO_ReturnError_t err;
uint32_t errInfo;
uint32_t heapMemoryUsed;
const struct device *CANptr = NULL;
uint8_t pendingNodeId = 10; // read from dip switches or nonvolatile memory, configurable by LSS slave
uint8_t activeNodeId = 10; // Copied from CO_pendingNodeId in the communication reset section
uint16_t pendingBitRate = 1000; // read from dip switches or nonvolatile memory, configurable by LSS slave
CO_config_t *config_ptr = NULL;
uint32_t timeout;
uint32_t elapsed;
//int64_t timestamp;
uint32_t timestamp;
uint32_t timestamp_delta;
uint32_t timeDifference_us;
#ifdef CONFIG_CANOPENNODE_STORAGE
int ret;
#endif /* CONFIG_CANOPENNODE_STORAGE */
if (stm32_backboard_inits() != _NO_ERROR) {
LOG_ERR("Error from stm32_backboard_inits");
} // end if
flush_logs();
k_thread_start(helper_main_id);
CANptr = CAN_INTERFACE;
if (!device_is_ready(CANptr)) {
//if (!device_is_ready(CAN_INTERFACE)) {
LOG_ERR("CAN interface not ready");
return 0;
}
#ifdef CONFIG_CANOPENNODE_STORAGE
ret = settings_subsys_init();
if (ret) {
LOG_ERR("failed to initialize settings subsystem (err = %d)",
ret);
return 0;
}
ret = settings_load();
if (ret) {
LOG_ERR("failed to load settings (err = %d)", ret);
return 0;
}
#endif /* CONFIG_CANOPENNODE_STORAGE */
CO = CO_new(config_ptr, &heapMemoryUsed);
if (CO == NULL) {
LOG_ERR("Error: Can't allocate memory for CAN stack");
return 0;
} else {
LOG_INF("Allocated %u bytes for CANopen objects", heapMemoryUsed);
}
while (reset != CO_RESET_APP) {
elapsed = 0U; // milliseconds
LOG_INF("CANopen stack initialization starting");
// Wait rt_thread.
CO->CANmodule->CANnormal = false;
// Enter CAN configuration.
CO_CANsetConfigurationMode((void*)CANptr);
CO_CANmodule_disable(CO->CANmodule);
// initialize CANopen
err = CO_CANinit(CO, (void*)CANptr, pendingBitRate);
if (err != CO_ERROR_NO) {
LOG_ERR("Error: CAN initialization failed: %d", err);
return 0;
}
errInfo = 0;
err = CO_CANopenInit(CO, // CANopen object */
NULL, // alternate NMT */
NULL, // alternate em */
OD, // Object dictionary */
OD_STATUS_BITS, // Optional OD_statusBits */
NMT_CONTROL, // CO_NMT_control_t */
FIRST_HB_TIME, // firstHBTime_ms */
SDO_SRV_TIMEOUT_TIME, // SDOserverTimeoutTime_ms */
SDO_CLI_TIMEOUT_TIME, // SDOclientTimeoutTime_ms */
SDO_CLI_BLOCK, // SDOclientBlockTransfer */
activeNodeId, &errInfo);
if (err != CO_ERROR_NO && err != CO_ERROR_NODE_ID_UNCONFIGURED_LSS) {
if (err == CO_ERROR_OD_PARAMETERS) {
LOG_ERR("Error: Object Dictionary entry 0x%X", errInfo);
} else {
LOG_ERR("Error: CANopen initialization failed: %d", err);
}
return 0;
}
err = CO_CANopenInitPDO(CO, CO->em, OD, activeNodeId, &errInfo);
if (err != CO_ERROR_NO) {
if (err == CO_ERROR_OD_PARAMETERS) {
LOG_ERR("Error: Object Dictionary entry 0x%X", errInfo);
} else {
LOG_ERR("Error: PDO initialization failed: %d", err);
}
return 0;
}
/* Configure CANopen callbacks, etc */
if (!CO->nodeIdUnconfigured) {
LOG_INF("CANopenNode - Node-id %d initialized", activeNodeId);
} else {
LOG_INF("CANopenNode - Node-id not initialized");
}
// start CAN
CO_CANsetNormalMode(CO->CANmodule);
reset = CO_RESET_NOT;
LOG_INF("CANopenNode - Running...");
flush_logs();
while (reset == CO_RESET_NOT) {
// loop for normal program execution
// get time difference since last function call
timeDifference_us = 500;
timestamp = k_cycle_get_32();
// CANopen process
reset = CO_process(CO, false, elapsed, &timeDifference_us);
// Nonblocking application code may go here.
if (timeDifference_us > 0) {
k_usleep(timeDifference_us);
timestamp_delta = k_cycle_get_32() - timestamp;
elapsed = k_cyc_to_us_near32(timestamp_delta);
} else {
// Do not sleep, more processing to be
//done by the stack.
//
elapsed = 0U;
/* Process automatic storage */
/* optional sleep for short time */
}
} // end while
LOG_INF("Resetting device");
// delete objects from memory
CO_CANsetConfigurationMode((void*)&CANptr);
CO_delete(CO);
LOG_INF("CANopenNode finished");
//CO_delete(&can);
sys_reboot(SYS_REBOOT_COLD);
}
The core loop from the original Zephyr demo project is as follows:
while (true) {
timeout = 1U; // default timeout in milliseconds
timestamp = k_uptime_get();
reset = CO_process(CO, (uint16_t)elapsed, &timeout);
if (reset != CO_RESET_NOT) {
break;
}
if (timeout > 0) {
//CO_LOCK_OD();
//OD_buttonPressCounter = counter;
//CO_UNLOCK_OD();
#ifdef CONFIG_CANOPENNODE_STORAGE
ret = canopen_storage_save(
CANOPEN_STORAGE_EEPROM);
if (ret) {
LOG_ERR("failed to save EEPROM");
}
#endif // CONFIG_CANOPENNODE_STORAGE
//
// Try to sleep for as long as the
// stack requested and calculate the
// exact time elapsed.
//
k_sleep(K_MSEC(timeout));
elapsed = (uint32_t)k_uptime_delta(×tamp);
} else {
//
// Do not sleep, more processing to be
//done by the stack.
//
elapsed = 0U;
}
} // end while-true
So basically the CO_process function is supposed to be updating a time variable that you use to put the CAN thread to sleep. I created my updated version based on code I saw in the sync_thread function in canopennode.c as this seems to be doing the same process. However, although things were fine in the original version of CANopen that Zephyr uses, now with this setup I get an <wrn> canopen_driver: TX buffer full, discarding message with ID 0x70A warning every time the stack tries to send out the heartbeat, unless I disable the k_usleep call. So as long as I don't try to switch to another thread the stack is fine, I get the heartbeats and I can read/write the object dictionary, but the second I try to put this thread to sleep I get buffer errors. Not sure what to look at here, and if I put in a trap to try and catch CO->CANtx->bufferFull as true, I never catch this state in the main loop. Also, if I put a breakpoint in on the k_usleep call and tell the code to run from the breakpoint over and over at a slow pace, I won't get the buffer errors either.
update_objdicta long-running function? I'm wondering if any of your other threads are callingCO_LOCK_ODand not letting it go, or not letting it go quickly allowing the buffer to fill. Otherwise, it is hard to tell what other threads are doing that's stopping CAN messages from going out.