3

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(&timestamp);
            } 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.

1
  • Is update_objdict a long-running function? I'm wondering if any of your other threads are calling CO_LOCK_OD and 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. Commented Aug 23 at 3:58

1 Answer 1

1

So the issue was that the frame being sent out is the heartbeat frame, and if there is no NMT master to ack it, then the stack isn't treating the frame as being sent anyway, so the buffer is overflowing with the heartbeat messages. If a sniffer is active on the bus, the warnings go away. I ended up editing the code to change that warning to "warn once" so I don't keep getting that spammed out when only one CAN device is active on the bus.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.