Deprecate the CAN controller bus-speed/bus-speed-data properties and rename them to bitrate/bitrate-data to match the terminology used in other CAN devicetree properties and the CAN subsystem API. Signed-off-by: Henrik Brix Andersen <hebad@vestas.com>
308 lines
6.9 KiB
C
308 lines
6.9 KiB
C
/*
|
|
* 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>
|
|
|
|
#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)
|
|
|
|
static struct gpio_dt_spec led_green_gpio = GPIO_DT_SPEC_GET_OR(
|
|
DT_ALIAS(green_led), gpios, {0});
|
|
static struct gpio_dt_spec led_red_gpio = GPIO_DT_SPEC_GET_OR(
|
|
DT_ALIAS(red_led), gpios, {0});
|
|
|
|
static struct gpio_dt_spec button_gpio = GPIO_DT_SPEC_GET_OR(
|
|
DT_ALIAS(sw0), gpios, {0});
|
|
static struct gpio_callback button_callback;
|
|
|
|
struct led_indicator {
|
|
const struct device *dev;
|
|
gpio_pin_t pin;
|
|
};
|
|
|
|
static uint32_t counter;
|
|
|
|
/**
|
|
* @brief Callback for setting LED indicator state.
|
|
*
|
|
* @param value true if the LED indicator shall be turned on, false otherwise.
|
|
* @param arg argument that was passed when LEDs were initialized.
|
|
*/
|
|
static void led_callback(bool value, void *arg)
|
|
{
|
|
struct gpio_dt_spec *led_gpio = arg;
|
|
|
|
if (!led_gpio || !led_gpio->port) {
|
|
return;
|
|
}
|
|
|
|
gpio_pin_set_dt(led_gpio, value);
|
|
}
|
|
|
|
/**
|
|
* @brief Configure LED indicators pins and callbacks.
|
|
*
|
|
* This routine configures the GPIOs for the red and green LEDs (if
|
|
* available).
|
|
*
|
|
* @param nmt CANopenNode NMT object.
|
|
*/
|
|
static void config_leds(CO_NMT_t *nmt)
|
|
{
|
|
int err;
|
|
|
|
if (!led_green_gpio.port) {
|
|
LOG_INF("Green LED not available");
|
|
} else if (!gpio_is_ready_dt(&led_green_gpio)) {
|
|
LOG_ERR("Green LED device not ready");
|
|
led_green_gpio.port = NULL;
|
|
} else {
|
|
err = gpio_pin_configure_dt(&led_green_gpio,
|
|
GPIO_OUTPUT_INACTIVE);
|
|
if (err) {
|
|
LOG_ERR("failed to configure Green LED gpio: %d", err);
|
|
led_green_gpio.port = NULL;
|
|
}
|
|
}
|
|
|
|
if (!led_red_gpio.port) {
|
|
LOG_INF("Red LED not available");
|
|
} else if (!gpio_is_ready_dt(&led_red_gpio)) {
|
|
LOG_ERR("Red LED device not ready");
|
|
led_red_gpio.port = NULL;
|
|
} else {
|
|
err = gpio_pin_configure_dt(&led_red_gpio,
|
|
GPIO_OUTPUT_INACTIVE);
|
|
if (err) {
|
|
LOG_ERR("failed to configure Red LED gpio: %d", err);
|
|
led_red_gpio.port = NULL;
|
|
}
|
|
}
|
|
|
|
canopen_leds_init(nmt,
|
|
led_callback, &led_green_gpio,
|
|
led_callback, &led_red_gpio);
|
|
}
|
|
|
|
/**
|
|
* @brief Button press counter object dictionary handler function.
|
|
*
|
|
* This function is called upon SDO access to the button press counter
|
|
* object (index 0x2102) in the object dictionary.
|
|
*
|
|
* @param odf_arg object dictionary function argument.
|
|
*
|
|
* @return SDO abort code.
|
|
*/
|
|
static CO_SDO_abortCode_t odf_2102(CO_ODF_arg_t *odf_arg)
|
|
{
|
|
uint32_t value;
|
|
|
|
value = CO_getUint32(odf_arg->data);
|
|
|
|
if (odf_arg->reading) {
|
|
return CO_SDO_AB_NONE;
|
|
}
|
|
|
|
if (odf_arg->subIndex != 0U) {
|
|
return CO_SDO_AB_NONE;
|
|
}
|
|
|
|
if (value != 0) {
|
|
/* Preserve old value */
|
|
memcpy(odf_arg->data, odf_arg->ODdataStorage, sizeof(uint32_t));
|
|
return CO_SDO_AB_DATA_TRANSF;
|
|
}
|
|
|
|
LOG_INF("Resetting button press counter");
|
|
counter = 0;
|
|
|
|
return CO_SDO_AB_NONE;
|
|
}
|
|
|
|
/**
|
|
* @brief Button press interrupt callback.
|
|
*
|
|
* @param port GPIO device struct.
|
|
* @param cb GPIO callback struct.
|
|
* @param pins GPIO pin mask that triggered the interrupt.
|
|
*/
|
|
static void button_isr_callback(const struct device *port,
|
|
struct gpio_callback *cb,
|
|
uint32_t pins)
|
|
{
|
|
counter++;
|
|
}
|
|
|
|
/**
|
|
* @brief Configure button GPIO pin and callback.
|
|
*
|
|
* This routine configures the GPIO for the button (if available).
|
|
*/
|
|
static void config_button(void)
|
|
{
|
|
int err;
|
|
|
|
if (button_gpio.port == NULL) {
|
|
LOG_INF("Button not available");
|
|
return;
|
|
}
|
|
|
|
if (!gpio_is_ready_dt(&button_gpio)) {
|
|
LOG_ERR("Button device not ready");
|
|
return;
|
|
}
|
|
|
|
err = gpio_pin_configure_dt(&button_gpio, GPIO_INPUT);
|
|
if (err) {
|
|
LOG_ERR("failed to configure button gpio: %d", err);
|
|
return;
|
|
}
|
|
|
|
gpio_init_callback(&button_callback, button_isr_callback,
|
|
BIT(button_gpio.pin));
|
|
|
|
err = gpio_add_callback(button_gpio.port, &button_callback);
|
|
if (err) {
|
|
LOG_ERR("failed to add button callback: %d", err);
|
|
return;
|
|
}
|
|
|
|
err = gpio_pin_interrupt_configure_dt(&button_gpio,
|
|
GPIO_INT_EDGE_TO_ACTIVE);
|
|
if (err) {
|
|
LOG_ERR("failed to enable button callback: %d", err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
struct canopen_context can;
|
|
uint16_t timeout;
|
|
uint32_t elapsed;
|
|
int64_t timestamp;
|
|
#ifdef CONFIG_CANOPENNODE_STORAGE
|
|
int ret;
|
|
#endif /* CONFIG_CANOPENNODE_STORAGE */
|
|
|
|
can.dev = CAN_INTERFACE;
|
|
if (!device_is_ready(can.dev)) {
|
|
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 */
|
|
|
|
OD_powerOnCounter++;
|
|
|
|
config_button();
|
|
|
|
while (reset != CO_RESET_APP) {
|
|
elapsed = 0U; /* milliseconds */
|
|
|
|
err = CO_init(&can, CONFIG_CANOPEN_NODE_ID, CAN_BITRATE);
|
|
if (err != CO_ERROR_NO) {
|
|
LOG_ERR("CO_init failed (err = %d)", err);
|
|
return 0;
|
|
}
|
|
|
|
LOG_INF("CANopen stack initialized");
|
|
|
|
#ifdef CONFIG_CANOPENNODE_STORAGE
|
|
canopen_storage_attach(CO->SDO[0], CO->em);
|
|
#endif /* CONFIG_CANOPENNODE_STORAGE */
|
|
|
|
config_leds(CO->NMT);
|
|
CO_OD_configure(CO->SDO[0], OD_2102_buttonPressCounter,
|
|
odf_2102, NULL, 0U, 0U);
|
|
|
|
if (IS_ENABLED(CONFIG_CANOPENNODE_PROGRAM_DOWNLOAD)) {
|
|
canopen_program_download_attach(CO->NMT, CO->SDO[0],
|
|
CO->em);
|
|
}
|
|
|
|
CO_CANsetNormalMode(CO->CANmodule[0]);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (reset == CO_RESET_COMM) {
|
|
LOG_INF("Resetting communication");
|
|
}
|
|
}
|
|
|
|
LOG_INF("Resetting device");
|
|
|
|
CO_delete(&can);
|
|
sys_reboot(SYS_REBOOT_COLD);
|
|
}
|