From 1d2727f4c8f9a6ef5fffd8e7f2dd86500cc8284b Mon Sep 17 00:00:00 2001 From: Darian Leung Date: Mon, 18 Dec 2017 20:32:29 +0800 Subject: [PATCH] CAN Driver The following commit contains the first version of the ESP32 CAN Driver. closes #544 --- .gitlab-ci.yml | 6 + components/driver/can.c | 937 ++++++++++++++++++ components/driver/include/driver/can.h | 398 ++++++++ components/esp32/ld/esp32.peripherals.ld | 1 + components/soc/esp32/include/soc/can_struct.h | 211 ++++ components/soc/esp32/include/soc/soc.h | 1 + docs/Doxyfile | 1 + .../can/can_acceptance_filter_dual.diag | 19 + .../can/can_acceptance_filter_single.diag | 21 + docs/_static/diagrams/can/can_bit_timing.diag | 12 + .../diagrams/can/can_controller_signals.diag | 57 ++ .../diagrams/can/can_state_transition.diag | 31 + docs/en/api-reference/peripherals/can.rst | 604 +++++++++++ docs/en/api-reference/peripherals/index.rst | 1 + docs/zh_CN/api-reference/peripherals/can.rst | 1 + .../can/can_alert_and_recovery/Makefile | 9 + .../can/can_alert_and_recovery/README.md | 29 + .../can_alert_and_recovery/example_test.py | 29 + .../can_alert_and_recovery_example_main.c | 153 +++ .../can_alert_and_recovery/main/component.mk | 4 + .../peripherals/can/can_network/README.md | 67 ++ .../can_network_listen_only/Makefile | 9 + .../can_network_example_listen_only_main.c | 123 +++ .../can_network_listen_only/main/component.mk | 4 + .../can_network/can_network_master/Makefile | 9 + .../main/can_network_example_master_main.c | 235 +++++ .../can_network_master/main/component.mk | 4 + .../can_network/can_network_slave/Makefile | 9 + .../main/can_network_example_slave_main.c | 264 +++++ .../can_network_slave/main/component.mk | 4 + .../can/can_network/example_test.py | 77 ++ .../peripherals/can/can_self_test/Makefile | 9 + .../peripherals/can/can_self_test/README.md | 33 + .../can/can_self_test/example_test.py | 29 + .../main/can_self_test_example_main.c | 141 +++ .../can/can_self_test/main/component.mk | 4 + 36 files changed, 3546 insertions(+) create mode 100644 components/driver/can.c create mode 100644 components/driver/include/driver/can.h create mode 100644 components/soc/esp32/include/soc/can_struct.h create mode 100644 docs/_static/diagrams/can/can_acceptance_filter_dual.diag create mode 100644 docs/_static/diagrams/can/can_acceptance_filter_single.diag create mode 100644 docs/_static/diagrams/can/can_bit_timing.diag create mode 100644 docs/_static/diagrams/can/can_controller_signals.diag create mode 100644 docs/_static/diagrams/can/can_state_transition.diag create mode 100644 docs/en/api-reference/peripherals/can.rst create mode 100644 docs/zh_CN/api-reference/peripherals/can.rst create mode 100644 examples/peripherals/can/can_alert_and_recovery/Makefile create mode 100644 examples/peripherals/can/can_alert_and_recovery/README.md create mode 100644 examples/peripherals/can/can_alert_and_recovery/example_test.py create mode 100644 examples/peripherals/can/can_alert_and_recovery/main/can_alert_and_recovery_example_main.c create mode 100644 examples/peripherals/can/can_alert_and_recovery/main/component.mk create mode 100644 examples/peripherals/can/can_network/README.md create mode 100644 examples/peripherals/can/can_network/can_network_listen_only/Makefile create mode 100644 examples/peripherals/can/can_network/can_network_listen_only/main/can_network_example_listen_only_main.c create mode 100644 examples/peripherals/can/can_network/can_network_listen_only/main/component.mk create mode 100644 examples/peripherals/can/can_network/can_network_master/Makefile create mode 100644 examples/peripherals/can/can_network/can_network_master/main/can_network_example_master_main.c create mode 100644 examples/peripherals/can/can_network/can_network_master/main/component.mk create mode 100644 examples/peripherals/can/can_network/can_network_slave/Makefile create mode 100644 examples/peripherals/can/can_network/can_network_slave/main/can_network_example_slave_main.c create mode 100644 examples/peripherals/can/can_network/can_network_slave/main/component.mk create mode 100644 examples/peripherals/can/can_network/example_test.py create mode 100644 examples/peripherals/can/can_self_test/Makefile create mode 100644 examples/peripherals/can/can_self_test/README.md create mode 100644 examples/peripherals/can/can_self_test/example_test.py create mode 100644 examples/peripherals/can/can_self_test/main/can_self_test_example_main.c create mode 100644 examples/peripherals/can/can_self_test/main/component.mk diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c282e5672..e7c7a20ce 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -639,6 +639,12 @@ example_test_003_01: - ESP32 - Example_SDIO +example_test_004_01: + <<: *example_test_template + tags: + - ESP32 + - Example_CAN + UT_001_01: <<: *unit_test_template tags: diff --git a/components/driver/can.c b/components/driver/can.c new file mode 100644 index 000000000..139d8a49a --- /dev/null +++ b/components/driver/can.c @@ -0,0 +1,937 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "esp_types.h" +#include "esp_log.h" +#include "esp_intr_alloc.h" +#include "soc/dport_reg.h" +#include "soc/can_struct.h" +#include "driver/gpio.h" +#include "driver/periph_ctrl.h" +#include "driver/can.h" + +/* ---------------------------- Definitions --------------------------------- */ +//Internal Macros +#define CAN_CHECK(cond, ret_val) ({ \ + if (!(cond)) { \ + return (ret_val); \ + } \ +}) +#define CAN_CHECK_FROM_CRIT(cond, ret_val) ({ \ + if (!(cond)) { \ + CAN_EXIT_CRITICAL(); \ + return ret_val; \ + } \ +}) +#define CAN_SET_FLAG(var, mask) ((var) |= (mask)) +#define CAN_RESET_FLAG(var, mask) ((var) &= ~(mask)) +#define CAN_TAG "CAN" + +//Driver default config/values +#define DRIVER_DEFAULT_EWL 96 //Default Error Warning Limit value +#define DRIVER_DEFAULT_TEC 0 //TX Error Counter starting value +#define DRIVER_DEFAULT_REC 0 //RX Error Counter starting value +#define DRIVER_DEFAULT_CLKOUT_DIV 14 //APB CLK divided by two +#define DRIVER_DEFAULT_INTERRUPTS 0xE7 //Exclude data overrun +#define DRIVER_DEFAULT_ERR_PASS_CNT 128 //Error counter threshold for error passive + +//Command Bit Masks +#define CMD_TX_REQ 0x01 //Transmission Request +#define CMD_ABORT_TX 0x02 //Abort Transmission +#define CMD_RELEASE_RX_BUFF 0x04 //Release Receive Buffer +#define CMD_CLR_DATA_OVRN 0x08 //Clear Data Overrun +#define CMD_SELF_RX_REQ 0x10 //Self Reception Request +#define CMD_TX_SINGLE_SHOT 0x03 //Single Shot Transmission +#define CMD_SELF_RX_SINGLE_SHOT 0x12 //Single Shot Self Reception + +//Control flags +#define CTRL_FLAG_STOPPED 0x001 //CAN peripheral in stopped state +#define CTRL_FLAG_RECOVERING 0x002 //Bus is undergoing bus recovery +#define CTRL_FLAG_ERR_WARN 0x004 //TEC or REC is >= error warning limit +#define CTRL_FLAG_ERR_PASSIVE 0x008 //TEC or REC is >= 128 +#define CTRL_FLAG_BUS_OFF 0x010 //Bus-off due to TEC >= 256 +#define CTRL_FLAG_TX_BUFF_OCCUPIED 0x020 //Transmit buffer is occupied +#define CTRL_FLAG_SELF_TEST 0x040 //Configured to Self Test Mode +#define CTRL_FLAG_LISTEN_ONLY 0x080 //Configured to Listen Only Mode + +//Constants use for frame formatting and parsing +#define FRAME_MAX_LEN 13 //EFF with 8 bytes of data +#define FRAME_MAX_DATA_LEN 8 //Max data bytes allowed in CAN2.0 +#define FRAME_EXTD_ID_LEN 4 //EFF ID requires 4 bytes (29bit) +#define FRAME_STD_ID_LEN 2 //SFF ID requires 2 bytes (11bit) +#define FRAME_INFO_LEN 1 //Frame info requires 1 byte + +#define ALERT_LOG_LEVEL_WARNING CAN_ALERT_ARB_LOST //Alerts above and including this level use ESP_LOGW +#define ALERT_LOG_LEVEL_ERROR CAN_ALERT_TX_FAILED //Alerts above and including this level use ESP_LOGE + +/* ------------------ Typedefs, structures, and variables ------------------- */ + +/* Formatted frame structure has identical layout as TX/RX buffer registers. + This allows for direct copy to/from TX/RX buffer. The two reserved bits in TX + buffer are used in the frame structure to store the self_reception and + single_shot flags. */ +typedef union { + struct { + struct { + uint8_t dlc: 4; //Data length code (0 to 8) of the frame + uint8_t self_reception: 1; //This frame should be transmitted using self reception command + uint8_t single_shot: 1; //This frame should be transmitted using single shot command + uint8_t rtr: 1; //This frame is a remote transmission request + uint8_t frame_format: 1; //Format of the frame (1 = extended, 0 = standard) + }; + union { + struct { + uint8_t id[FRAME_STD_ID_LEN]; //11 bit standard frame identifier + uint8_t data[FRAME_MAX_DATA_LEN]; //Data bytes (0 to 8) + uint8_t reserved8[2]; + } standard; + struct { + uint8_t id[FRAME_EXTD_ID_LEN]; //29 bit extended frame identifier + uint8_t data[FRAME_MAX_DATA_LEN]; //Data bytes (0 to 8) + } extended; + }; + }; + uint8_t bytes[FRAME_MAX_LEN]; +} can_frame_t; + +//Control structure for CAN driver +typedef struct { + //Control and status members + uint32_t control_flags; + uint32_t rx_missed_count; + uint32_t tx_failed_count; + uint32_t arb_lost_count; + uint32_t bus_error_count; + intr_handle_t isr_handle; + //TX and RX + QueueHandle_t tx_queue; + QueueHandle_t rx_queue; + int tx_msg_count; + int rx_msg_count; + //Alerts + SemaphoreHandle_t alert_semphr; + uint32_t alerts_enabled; + uint32_t alerts_triggered; +} can_obj_t; + +static can_obj_t *p_can_obj = NULL; +static portMUX_TYPE can_spinlock = portMUX_INITIALIZER_UNLOCKED; +#define CAN_ENTER_CRITICAL() portENTER_CRITICAL(&can_spinlock) +#define CAN_EXIT_CRITICAL() portEXIT_CRITICAL(&can_spinlock) + +/* ------------------- Configuration Register Functions---------------------- */ + +static inline esp_err_t can_enter_reset_mode() +{ + /* Enter reset mode (required to write to configuration registers). Reset mode + also prevents all CAN activity on the current module and is automatically + set upon entering a BUS-OFF condition. */ + CAN.mode_reg.reset = 1; //Set reset mode bit + CAN_CHECK(CAN.mode_reg.reset == 1, ESP_ERR_INVALID_STATE); //Check bit was set + return ESP_OK; +} + +static inline esp_err_t can_exit_reset_mode() +{ + /* Exiting reset mode will return the CAN module to operating mode. Reset mode + must also be exited in order to trigger BUS-OFF recovery sequence. */ + CAN.mode_reg.reset = 0; //Exit reset mode + CAN_CHECK(CAN.mode_reg.reset == 0, ESP_ERR_INVALID_STATE); //Check bit was reset + return ESP_OK; +} + +static inline void can_config_pelican() +{ + //Use PeliCAN address layout. Exposes extra registers + CAN.clock_divider_reg.can_mode = 1; +} + +static inline void can_config_mode(can_mode_t mode) +{ + //Configure CAN mode of operation + can_mode_reg_t mode_reg; + mode_reg.val = CAN.mode_reg.val; //Get current value of mode register + if (mode == CAN_MODE_NO_ACK) { + mode_reg.self_test = 1; + mode_reg.listen_only = 0; + } else if (mode == CAN_MODE_LISTEN_ONLY) { + mode_reg.self_test = 0; + mode_reg.listen_only = 1; + } else { + //Default to normal operating mode + mode_reg.self_test = 0; + mode_reg.listen_only = 0; + } + CAN.mode_reg.val = mode_reg.val; //Write back modified value to register +} + +static inline void can_config_interrupts(uint32_t interrupts) +{ + //Enable interrupt sources + CAN.interrupt_enable_reg.val = interrupts; +} + +static inline void can_config_bus_timing(uint32_t brp, uint32_t sjw, uint32_t tseg_1, uint32_t tseg_2, bool triple_sampling) +{ + /* Configure bus/bit timing of CAN peripheral. + - BRP (even from 2 to 128) divide APB to CAN system clock (T_scl) + - SJW (1 to 4) is number of T_scl to shorten/lengthen for bit synchronization + - TSEG_1 (1 to 16) is number of T_scl in a bit time before sample point + - TSEG_2 (1 to 8) is number of T_scl in a bit time after sample point + - triple_sampling will cause each bit time to be sampled 3 times*/ + can_bus_tim_0_reg_t timing_reg_0; + can_bus_tim_1_reg_t timing_reg_1; + timing_reg_0.baud_rate_prescaler = (brp / 2) - 1; + timing_reg_0.sync_jump_width = sjw - 1; + timing_reg_1.time_seg_1 = tseg_1 - 1; + timing_reg_1.time_seg_2 = tseg_2 - 1; + timing_reg_1.sampling = triple_sampling; + CAN.bus_timing_0_reg.val = timing_reg_0.val; + CAN.bus_timing_1_reg.val = timing_reg_1.val; +} + +static inline void can_config_error(int err_warn_lim, int rx_err_cnt, int tx_err_cnt) +{ + /* Set error warning limit, RX error counter, and TX error counter. Note that + forcibly setting RX/TX error counters will incur the expected status changes + and interrupts as soon as reset mode exits. */ + if (err_warn_lim >= 0 && err_warn_lim <= UINT8_MAX) { + //Defaults to 96 after hardware reset. + CAN.error_warning_limit_reg.byte = err_warn_lim; + } + if (rx_err_cnt >= 0 && rx_err_cnt <= UINT8_MAX) { + //Defaults to 0 after hardware reset. + CAN.rx_error_counter_reg.byte = rx_err_cnt; + } + if (tx_err_cnt >= 0 && tx_err_cnt <= UINT8_MAX) { + //Defaults to 0 after hardware reset, and 127 after BUS-OFF event + CAN.tx_error_counter_reg.byte = tx_err_cnt; + } +} + +static inline void can_config_acceptance_filter(uint32_t code, uint32_t mask, bool single_filter) +{ + //Set filter mode + CAN.mode_reg.acceptance_filter = (single_filter) ? 1 : 0; + //Swap code and mask to match big endian registers + uint32_t code_swapped = __builtin_bswap32(code); + uint32_t mask_swapped = __builtin_bswap32(mask); + for (int i = 0; i < 4; i++) { + CAN.acceptance_filter.code_reg[i].byte = ((code_swapped >> (i * 8)) & 0xFF); + CAN.acceptance_filter.mask_reg[i].byte = ((mask_swapped >> (i * 8)) & 0xFF); + } +} + +static inline void can_config_clk_out(uint32_t divider) +{ + /* Configure CLKOUT. CLKOUT is a pre-scaled version of APB CLK. Divider can be + 1, or any even number from 2 to 14. Set to out of range value (0) to disable + CLKOUT. */ + can_clk_div_reg_t clock_divider_reg; + clock_divider_reg.val = CAN.clock_divider_reg.val; + if (divider >= 2 && divider <= 14) { + clock_divider_reg.clock_off = 0; + clock_divider_reg.clock_divider = (divider / 2) - 1; + } else if (divider == 1) { + clock_divider_reg.clock_off = 0; + clock_divider_reg.clock_divider = 7; + } else { + clock_divider_reg.clock_off = 1; + clock_divider_reg.clock_divider = 0; + } + CAN.clock_divider_reg.val = clock_divider_reg.val; +} + +/* ---------------------- Runtime Register Functions------------------------- */ + +static inline void can_set_command(uint8_t commands) +{ + CAN.command_reg.val = commands; +} + +static void can_set_tx_buffer_and_transmit(can_frame_t *frame) +{ + //Copy frame structure into TX buffer registers + for (int i = 0; i < FRAME_MAX_LEN; i++) { + CAN.tx_rx_buffer[i].val = frame->bytes[i]; + } + + //Set correct transmit command + uint8_t command; + if (frame->self_reception) { + command = (frame->single_shot) ? CMD_SELF_RX_SINGLE_SHOT : CMD_SELF_RX_REQ; + } else { + command = (frame->single_shot) ? CMD_TX_SINGLE_SHOT : CMD_TX_REQ; + } + can_set_command(command); +} + +static inline uint32_t can_get_status() +{ + return CAN.status_reg.val; +} + +static inline uint32_t can_get_interrupt_reason() +{ + return CAN.interrupt_reg.val; +} + +static inline uint32_t can_get_arbitration_lost_capture() +{ + return CAN.arbitration_lost_captue_reg.val; + //Todo: ALC read only to re-arm arb lost interrupt. Add function to decode ALC +} + +static inline uint32_t can_get_error_code_capture() +{ + return CAN.error_code_capture_reg.val; + //Todo: ECC read only to re-arm bus error interrupt. Add function to decode ECC +} + +static inline void can_get_error_counters(uint32_t *tx_error_cnt, uint32_t *rx_error_cnt) +{ + if (tx_error_cnt != NULL) { + *tx_error_cnt = CAN.tx_error_counter_reg.byte; + } + if (rx_error_cnt != NULL) { + *rx_error_cnt = CAN.rx_error_counter_reg.byte; + } +} + +static inline void can_get_rx_buffer_and_clear(can_frame_t *frame) +{ + //Copy RX buffer registers into frame structure + for (int i = 0; i < FRAME_MAX_LEN; i++) { + frame->bytes[i] = CAN.tx_rx_buffer[i].val; + } + //Clear RX buffer + can_set_command(CMD_RELEASE_RX_BUFF); +} + +static inline uint32_t can_get_rx_message_counter() +{ + return CAN.rx_message_counter_reg.val; +} + +/* -------------------- Interrupt and Alert Handlers ------------------------ */ + +static void can_alert_handler(uint32_t alert_code, int *alert_req) +{ + if (p_can_obj->alerts_enabled & alert_code) { + //Signify alert has occurred + CAN_SET_FLAG(p_can_obj->alerts_triggered, alert_code); + *alert_req = 1; + if (p_can_obj->alerts_enabled & CAN_ALERT_AND_LOG) { + if (alert_code >= ALERT_LOG_LEVEL_ERROR) { + ESP_EARLY_LOGE(CAN_TAG, "Alert %d", alert_code); + } else if (alert_code >= ALERT_LOG_LEVEL_WARNING) { + ESP_EARLY_LOGW(CAN_TAG, "Alert %d", alert_code); + } else { + ESP_EARLY_LOGI(CAN_TAG, "Alert %d", alert_code); + } + } + } +} + +static void can_intr_handler_err_warn(can_status_reg_t *status, BaseType_t *task_woken, int *alert_req) +{ + if (status->bus) { + if (status->error) { + //Bus-Off condition. TEC should set and held at 127, REC should be 0, reset mode entered + CAN_SET_FLAG(p_can_obj->control_flags, CTRL_FLAG_BUS_OFF); + /* Note: REC is still allowed to increase during bus-off. REC > err_warn + can prevent "bus recovery complete" interrupt from occurring. Set to + listen only mode to freeze REC. */ + can_config_mode(CAN_MODE_LISTEN_ONLY); + can_alert_handler(CAN_ALERT_BUS_OFF, alert_req); + } else { + //Bus-recovery in progress. TEC has dropped below error warning limit + can_alert_handler(CAN_ALERT_RECOVERY_IN_PROGRESS, alert_req); + } + } else { + if (status->error) { + //TEC or REC surpassed error warning limit + CAN_SET_FLAG(p_can_obj->control_flags, CTRL_FLAG_ERR_WARN); + can_alert_handler(CAN_ALERT_ABOVE_ERR_WARN, alert_req); + } else if (p_can_obj->control_flags & CTRL_FLAG_RECOVERING) { + //Bus recovery complete. + can_enter_reset_mode(); + //Reset and set flags to the equivalent of the stopped state + CAN_RESET_FLAG(p_can_obj->control_flags, CTRL_FLAG_RECOVERING | CTRL_FLAG_ERR_WARN | + CTRL_FLAG_ERR_PASSIVE | CTRL_FLAG_BUS_OFF | + CTRL_FLAG_TX_BUFF_OCCUPIED); + CAN_SET_FLAG(p_can_obj->control_flags, CTRL_FLAG_STOPPED); + can_alert_handler(CAN_ALERT_BUS_RECOVERED, alert_req); + } else { + //TEC and REC are both below error warning + CAN_RESET_FLAG(p_can_obj->control_flags, CTRL_FLAG_ERR_WARN); + can_alert_handler(CAN_ALERT_BELOW_ERR_WARN, alert_req); + } + } +} + +static void can_intr_handler_err_passive(int *alert_req) +{ + uint32_t tec, rec; + can_get_error_counters(&tec, &rec); + if (tec >= DRIVER_DEFAULT_ERR_PASS_CNT || rec >= DRIVER_DEFAULT_ERR_PASS_CNT) { + //Entered error passive + CAN_SET_FLAG(p_can_obj->control_flags, CTRL_FLAG_ERR_PASSIVE); + can_alert_handler(CAN_ALERT_ERR_PASS, alert_req); + } else { + //Returned to error active + CAN_RESET_FLAG(p_can_obj->control_flags, CTRL_FLAG_ERR_PASSIVE); + can_alert_handler(CAN_ALERT_ERR_ACTIVE, alert_req); + } +} + +static void can_intr_handler_bus_err(int *alert_req) +{ + // ECC register is read to re-arm bus error interrupt. ECC is not used + (void) can_get_error_code_capture(); + p_can_obj->bus_error_count++; + can_alert_handler(CAN_ALERT_BUS_ERROR, alert_req); +} + +static void can_intr_handler_arb_lost(int *alert_req) +{ + //ALC register is read to re-arm arb lost interrupt. ALC is not used + (void) can_get_arbitration_lost_capture(); + p_can_obj->arb_lost_count++; + can_alert_handler(CAN_ALERT_ARB_LOST, alert_req); +} + +static void can_intr_handler_rx(BaseType_t *task_woken, int *alert_req) +{ + can_rx_msg_cnt_reg_t msg_count_reg; + msg_count_reg.val = can_get_rx_message_counter(); + + for (int i = 0; i < msg_count_reg.rx_message_counter; i++) { + can_frame_t frame; + can_get_rx_buffer_and_clear(&frame); + //Copy frame into RX Queue + if (xQueueSendFromISR(p_can_obj->rx_queue, &frame, task_woken) == pdTRUE) { + p_can_obj->rx_msg_count++; + } else { + p_can_obj->rx_missed_count++; + can_alert_handler(CAN_ALERT_RX_QUEUE_FULL, alert_req); + } + } +} + +static void can_intr_handler_tx(can_status_reg_t *status, int *alert_req) +{ + //Handle previously transmitted frame + if (status->tx_complete) { + can_alert_handler(CAN_ALERT_TX_SUCCESS, alert_req); + } else { + p_can_obj->tx_failed_count++; + can_alert_handler(CAN_ALERT_TX_FAILED, alert_req); + } + + //Update TX message count + p_can_obj->tx_msg_count--; + configASSERT(p_can_obj->tx_msg_count >= 0); //Sanity check + + //Check if there are more frames to transmit + if (p_can_obj->tx_msg_count > 0 && p_can_obj->tx_queue != NULL) { + can_frame_t frame; + configASSERT(xQueueReceiveFromISR(p_can_obj->tx_queue, &frame, NULL) == pdTRUE); + can_set_tx_buffer_and_transmit(&frame); + } else { + //No more frames to transmit + CAN_RESET_FLAG(p_can_obj->control_flags, CTRL_FLAG_TX_BUFF_OCCUPIED); + can_alert_handler(CAN_ALERT_TX_IDLE, alert_req); + } +} + +static void can_intr_handler_main(void *arg) +{ + BaseType_t task_woken = pdFALSE; + int alert_req = 0; + can_status_reg_t status; + can_intr_reg_t intr_reason; + + CAN_ENTER_CRITICAL(); + status.val = can_get_status(); + intr_reason.val = (p_can_obj != NULL) ? can_get_interrupt_reason() : 0; //Incase intr occurs whilst driver is being uninstalled + + //Handle error counter related interrupts + if (intr_reason.err_warn) { + //Triggers when Bus-Status or Error-status bits change + can_intr_handler_err_warn(&status, &task_woken, &alert_req); + } + if (intr_reason.err_passive) { + //Triggers when entering/returning error passive/active state + can_intr_handler_err_passive(&alert_req); + } + + //Handle other error interrupts + if (intr_reason.bus_err) { + //Triggers when an error (Bit, Stuff, CRC, Form, ACK) occurs on the CAN bus + can_intr_handler_bus_err(&alert_req); + } + if (intr_reason.arb_lost) { + //Triggers when arbitration is lost + can_intr_handler_arb_lost(&alert_req); + } + //Todo: Check data overrun bug where interrupt does not trigger even when enabled + + //Handle TX/RX interrupts + if (intr_reason.rx) { + //Triggers when RX buffer has one or more frames. Disabled if RX Queue length = 0 + can_intr_handler_rx(&task_woken, &alert_req); + } + if (intr_reason.tx) { + //Triggers when TX buffer becomes free after a transmission + can_intr_handler_tx(&status, &alert_req); + } + /* Todo: Check possible bug where transmitting self reception request then + clearing rx buffer will cancel the transmission. */ + CAN_EXIT_CRITICAL(); + + if (p_can_obj->alert_semphr != NULL && alert_req) { + //Give semaphore if alerts were triggered + xSemaphoreGiveFromISR(p_can_obj->alert_semphr, &task_woken); + } + if (task_woken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +/* ---------------------- Frame and GPIO functions ------------------------- */ + +static void can_format_frame(uint32_t id, uint8_t dlc, const uint8_t *data, uint32_t flags, can_frame_t *tx_frame) +{ + /* This function encodes a message into a frame structure. The frame structure has + an identical layout to the TX buffer, allowing the frame structure to be directly + copied into TX buffer. */ + //Set frame information + tx_frame->dlc = dlc; + tx_frame->rtr = (flags & CAN_MSG_FLAG_RTR) ? 1 : 0; + tx_frame->frame_format = (flags & CAN_MSG_FLAG_EXTD) ? 1 : 0; + tx_frame->self_reception = (flags & CAN_MSG_FLAG_SELF) ? 1 : 0; + tx_frame->single_shot = (flags & CAN_MSG_FLAG_SS) ? 1 : 0; + + //Set ID + int id_len = (flags & CAN_MSG_FLAG_EXTD) ? FRAME_EXTD_ID_LEN : FRAME_STD_ID_LEN; + uint8_t *id_buffer = (flags & CAN_MSG_FLAG_EXTD) ? tx_frame->extended.id : tx_frame->standard.id; + //Split ID into 4 or 2 bytes, and turn into big-endian with left alignment (<< 3 or 5) + uint32_t id_temp = (flags & CAN_MSG_FLAG_EXTD) ? __builtin_bswap32((id & CAN_EXTD_ID_MASK) << 3) : //((id << 3) >> 8*(3-i)) + __builtin_bswap16((id & CAN_STD_ID_MASK) << 5); //((id << 5) >> 8*(1-i)) + for (int i = 0; i < id_len; i++) { + id_buffer[i] = (id_temp >> (8 * i)) & 0xFF; //Copy big-endian ID byte by byte + } + + //Set Data. + uint8_t *data_buffer = (flags & CAN_MSG_FLAG_EXTD) ? tx_frame->extended.data : tx_frame->standard.data; + for (int i = 0; (i < dlc) && (i < FRAME_MAX_DATA_LEN); i++) { //Handle case where dlc is > 8 + data_buffer[i] = data[i]; + } +} + +static void can_parse_frame(can_frame_t *rx_frame, uint32_t *id, uint8_t *dlc, uint8_t *data, uint32_t *flags) +{ + //This function decodes a frame structure into it's constituent components. + + //Copy frame information + *dlc = rx_frame->dlc; + *flags = 0; + *flags |= (rx_frame->dlc > FRAME_MAX_DATA_LEN) ? CAN_MSG_FLAG_DLC_NON_COMP : 0; + *flags |= (rx_frame->rtr) ? CAN_MSG_FLAG_RTR : 0; + *flags |= (rx_frame->frame_format) ? CAN_MSG_FLAG_EXTD : 0; + + //Copy ID + int id_len = (rx_frame->frame_format) ? FRAME_EXTD_ID_LEN : FRAME_STD_ID_LEN; + uint8_t *id_buffer = (rx_frame->frame_format) ? rx_frame->extended.id : rx_frame->standard.id; + uint32_t id_temp = 0; + for (int i = 0; i < id_len; i++) { + id_temp |= id_buffer[i] << (8 * i); //Copy big-endian ID byte by byte + } + //Revert endianness of 4 or 2 byte ID, and shift into 29 or 11 bit ID + id_temp = (rx_frame->frame_format) ? (__builtin_bswap32(id_temp) >> 3) : //((byte[i] << 8*(3-i)) >> 3) + (__builtin_bswap16(id_temp) >> 5); //((byte[i] << 8*(1-i)) >> 5) + *id = id_temp & ((rx_frame->frame_format) ? CAN_EXTD_ID_MASK : CAN_STD_ID_MASK); + + //Copy data + uint8_t *data_buffer = (rx_frame->frame_format) ? rx_frame->extended.data : rx_frame->standard.data; + for (int i = 0; (i < rx_frame->dlc) && (i < FRAME_MAX_DATA_LEN); i++) { + data[i] = data_buffer[i]; + } + //Set remaining bytes of data to 0 + for (int i = rx_frame->dlc; i < FRAME_MAX_DATA_LEN; i++) { + data[i] = 0; + } +} + +static void can_configure_gpio(gpio_num_t tx, gpio_num_t rx, gpio_num_t clkout, gpio_num_t bus_status) +{ + //Set TX pin + gpio_set_pull_mode(tx, GPIO_FLOATING); + gpio_matrix_out(tx, CAN_TX_IDX, false, false); + gpio_pad_select_gpio(tx); + + //Set RX pin + gpio_set_pull_mode(rx, GPIO_FLOATING); + gpio_matrix_in(rx, CAN_RX_IDX, false); + gpio_pad_select_gpio(rx); + + //Configure output clock pin (Optional) + if (clkout >= 0 && clkout < GPIO_NUM_MAX) { + gpio_set_pull_mode(clkout, GPIO_FLOATING); + gpio_matrix_out(clkout, CAN_CLKOUT_IDX, false, false); + gpio_pad_select_gpio(clkout); + } + + //Configure bus status pin (Optional) + if (bus_status >= 0 && bus_status < GPIO_NUM_MAX) { + gpio_set_pull_mode(bus_status, GPIO_FLOATING); + gpio_matrix_out(bus_status, CAN_BUS_OFF_ON_IDX, false, false); + gpio_pad_select_gpio(bus_status); + } +} + +/* ---------------------------- Public Functions ---------------------------- */ + +esp_err_t can_driver_install(const can_general_config_t *g_config, const can_timing_config_t *t_config, const can_filter_config_t *f_config) +{ + //Check arguments and state + CAN_CHECK(p_can_obj == NULL, ESP_ERR_INVALID_STATE); //Check is driver is already installed + CAN_CHECK(g_config != NULL, ESP_ERR_INVALID_ARG); + CAN_CHECK(t_config != NULL, ESP_ERR_INVALID_ARG); + CAN_CHECK(f_config != NULL, ESP_ERR_INVALID_ARG); + CAN_CHECK(g_config->rx_queue_len > 0, ESP_ERR_INVALID_ARG); + CAN_CHECK(g_config->tx_io >= 0 && g_config->tx_io < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG); + CAN_CHECK(g_config->rx_io >= 0 && g_config->rx_io < GPIO_NUM_MAX, ESP_ERR_INVALID_ARG); + esp_err_t ret; + + //Initialize CAN object + p_can_obj = calloc(1, sizeof(can_obj_t)); + CAN_CHECK(p_can_obj != NULL, ESP_ERR_NO_MEM); + p_can_obj->tx_queue = (g_config->tx_queue_len > 0) ? xQueueCreate(g_config->tx_queue_len, sizeof(can_frame_t)) : NULL; + p_can_obj->rx_queue = xQueueCreate(g_config->rx_queue_len, sizeof(can_frame_t)); + p_can_obj->alert_semphr = xSemaphoreCreateBinary(); + if ((g_config->tx_queue_len > 0 && p_can_obj->tx_queue == NULL) || + p_can_obj->rx_queue == NULL || p_can_obj->alert_semphr == NULL) { + ret = ESP_ERR_NO_MEM; + goto err; + } + p_can_obj->control_flags = CTRL_FLAG_STOPPED; + p_can_obj->control_flags |= (g_config->mode == CAN_MODE_NO_ACK) ? CTRL_FLAG_SELF_TEST : 0; + p_can_obj->control_flags |= (g_config->mode == CAN_MODE_LISTEN_ONLY) ? CTRL_FLAG_LISTEN_ONLY : 0; + p_can_obj->tx_msg_count = 0; + p_can_obj->rx_msg_count = 0; + p_can_obj->tx_failed_count = 0; + p_can_obj->rx_missed_count = 0; + p_can_obj->arb_lost_count = 0; + p_can_obj->bus_error_count = 0; + p_can_obj->alerts_enabled = g_config->alerts_enabled; + p_can_obj->alerts_triggered = 0; + + CAN_ENTER_CRITICAL(); + //Initialize CAN peripheral + periph_module_enable(PERIPH_CAN_MODULE); //Enable APB CLK to CAN peripheral + configASSERT(can_enter_reset_mode() == ESP_OK); //Must enter reset mode to write to config registers + can_config_pelican(); //Use PeliCAN addresses + /* Note: REC is allowed to increase even in reset mode. Listen only mode + will freeze REC. The desired mode will be set when can_start() is called. */ + can_config_mode(CAN_MODE_LISTEN_ONLY); + can_config_interrupts(DRIVER_DEFAULT_INTERRUPTS); + can_config_bus_timing(t_config->brp, t_config->sjw, t_config->tseg_1, t_config->tseg_2, t_config->triple_sampling); + can_config_error(DRIVER_DEFAULT_EWL, DRIVER_DEFAULT_REC, DRIVER_DEFAULT_TEC); + can_config_acceptance_filter(f_config->acceptance_code, f_config->acceptance_mask, f_config->single_filter); + can_config_clk_out(g_config->clkout_divider); + //Allocate GPIO and Interrupts + can_configure_gpio(g_config->tx_io, g_config->rx_io, g_config->clkout_io, g_config->bus_off_io); + (void) can_get_interrupt_reason(); //Read interrupt reg to clear it before allocating ISR + ESP_ERROR_CHECK(esp_intr_alloc(ETS_CAN_INTR_SOURCE, 0, can_intr_handler_main, NULL, &p_can_obj->isr_handle)); + CAN_EXIT_CRITICAL(); + //Todo: Allow interrupt to be registered to specified CPU + + //CAN module is still in reset mode, users need to call can_start() afterwards + return ESP_OK; + + err: + //Cleanup and return error + if (p_can_obj != NULL) { + if (p_can_obj->tx_queue != NULL) { + vQueueDelete(p_can_obj->tx_queue); + p_can_obj->tx_queue = NULL; + } + if (p_can_obj->rx_queue != NULL) { + vQueueDelete(p_can_obj->rx_queue); + p_can_obj->rx_queue = NULL; + } + if (p_can_obj->alert_semphr != NULL) { + vSemaphoreDelete(p_can_obj->alert_semphr); + p_can_obj->alert_semphr = NULL; + } + free(p_can_obj); + } + return ret; +} + +esp_err_t can_driver_uninstall() +{ + //Check state + CAN_ENTER_CRITICAL(); + CAN_CHECK_FROM_CRIT(p_can_obj != NULL, ESP_ERR_INVALID_STATE); + CAN_CHECK_FROM_CRIT(p_can_obj->control_flags & (CTRL_FLAG_STOPPED | CTRL_FLAG_BUS_OFF), ESP_ERR_INVALID_STATE); + + //Clear registers + configASSERT(can_enter_reset_mode() == ESP_OK); //Enter reset mode to stop any CAN bus activity + (void) can_get_interrupt_reason(); + (void) can_get_arbitration_lost_capture(); + (void) can_get_error_code_capture(); + + ESP_ERROR_CHECK(esp_intr_free(p_can_obj->isr_handle)); //Free interrupt + periph_module_disable(PERIPH_CAN_MODULE); //Disable CAN peripheral + //Delete queues, semaphores + if (p_can_obj->tx_queue != NULL) { + vQueueDelete(p_can_obj->tx_queue); + } + vQueueDelete(p_can_obj->rx_queue); + vSemaphoreDelete(p_can_obj->alert_semphr); + free(p_can_obj); //Free can driver object + CAN_EXIT_CRITICAL(); + + return ESP_OK; +} + +esp_err_t can_start() +{ + //Check state + CAN_ENTER_CRITICAL(); + CAN_CHECK_FROM_CRIT(p_can_obj != NULL, ESP_ERR_INVALID_STATE); + CAN_CHECK_FROM_CRIT(p_can_obj->control_flags & CTRL_FLAG_STOPPED, ESP_ERR_INVALID_STATE); + + //Reset RX queue, and RX message count + xQueueReset(p_can_obj->rx_queue); + p_can_obj->rx_msg_count = 0; + configASSERT(can_enter_reset_mode() == ESP_OK); //Should already be in bus-off mode, set again to make sure + + //Currently in listen only mode, need to set to mode specified by configuration + can_mode_t mode; + if (p_can_obj->control_flags & CTRL_FLAG_SELF_TEST) { + mode = CAN_MODE_NO_ACK; + } else if (p_can_obj->control_flags & CTRL_FLAG_LISTEN_ONLY) { + mode = CAN_MODE_LISTEN_ONLY; + } else { + mode = CAN_MODE_NORMAL; + } + can_config_mode(mode); //Set mode + (void) can_get_interrupt_reason(); //Clear interrupt register + configASSERT(can_exit_reset_mode() == ESP_OK); + + CAN_RESET_FLAG(p_can_obj->control_flags, CTRL_FLAG_STOPPED); + CAN_EXIT_CRITICAL(); + return ESP_OK; +} + +esp_err_t can_stop() +{ + //Check state + CAN_ENTER_CRITICAL(); + CAN_CHECK_FROM_CRIT(p_can_obj != NULL, ESP_ERR_INVALID_STATE); + CAN_CHECK_FROM_CRIT(!(p_can_obj->control_flags & (CTRL_FLAG_STOPPED | CTRL_FLAG_BUS_OFF)), ESP_ERR_INVALID_STATE); + + //Clear interrupts and reset flags + configASSERT(can_enter_reset_mode() == ESP_OK); + (void) can_get_interrupt_reason(); //Read interrupt register to clear interrupts + can_config_mode(CAN_MODE_LISTEN_ONLY); //Set to listen only mode to freeze REC + CAN_RESET_FLAG(p_can_obj->control_flags, CTRL_FLAG_TX_BUFF_OCCUPIED); + CAN_SET_FLAG(p_can_obj->control_flags, CTRL_FLAG_STOPPED); + + //Reset TX Queue and message count + if (p_can_obj->tx_queue != NULL) { + xQueueReset(p_can_obj->tx_queue); + } + p_can_obj->tx_msg_count = 0; + + CAN_EXIT_CRITICAL(); + + return ESP_OK; +} + +esp_err_t can_transmit(const can_message_t *message, TickType_t ticks_to_wait) +{ + //Check arguments + CAN_CHECK(p_can_obj != NULL, ESP_ERR_INVALID_STATE); + CAN_CHECK(message != NULL, ESP_ERR_INVALID_ARG); + CAN_CHECK((message->data_length_code <= FRAME_MAX_DATA_LEN) || (message->flags & CAN_MSG_FLAG_DLC_NON_COMP), ESP_ERR_INVALID_ARG); + + CAN_ENTER_CRITICAL(); + //Check State + CAN_CHECK_FROM_CRIT(!(p_can_obj->control_flags & CTRL_FLAG_LISTEN_ONLY), ESP_ERR_NOT_SUPPORTED); + CAN_CHECK_FROM_CRIT(!(p_can_obj->control_flags & (CTRL_FLAG_STOPPED | CTRL_FLAG_BUS_OFF)), ESP_ERR_INVALID_STATE); + //Format frame + esp_err_t ret = ESP_FAIL; + can_frame_t tx_frame; + can_format_frame(message->identifier, message->data_length_code, message->data, message->flags, &tx_frame); + //Check if frame can be sent immediately + if ((p_can_obj->tx_msg_count == 0) && !(p_can_obj->control_flags & CTRL_FLAG_TX_BUFF_OCCUPIED)) { + //No other frames waiting to transmit. Bypass queue and transmit immediately + can_set_tx_buffer_and_transmit(&tx_frame); + p_can_obj->tx_msg_count++; + CAN_SET_FLAG(p_can_obj->control_flags, CTRL_FLAG_TX_BUFF_OCCUPIED); + ret = ESP_OK; + } + CAN_EXIT_CRITICAL(); + + if (ret != ESP_OK) { + if (p_can_obj->tx_queue == NULL) { + //TX Queue is disabled and TX buffer is occupied, message was not sent + ret = ESP_FAIL; + } else if (xQueueSend(p_can_obj->tx_queue, &tx_frame, ticks_to_wait) == pdTRUE) { + //Copied to TX Queue + CAN_ENTER_CRITICAL(); + if (p_can_obj->control_flags & (CTRL_FLAG_STOPPED | CTRL_FLAG_STOPPED)) { + //TX queue was reset (due to stop/bus_off), remove copied frame from queue to prevent transmission + configASSERT(xQueueReceive(p_can_obj->tx_queue, &tx_frame, 0) == pdTRUE); + ret = ESP_ERR_INVALID_STATE; + } else if ((p_can_obj->tx_msg_count == 0) && !(p_can_obj->control_flags & CTRL_FLAG_TX_BUFF_OCCUPIED)) { + //TX buffer was freed during copy, manually trigger transmission + configASSERT(xQueueReceive(p_can_obj->tx_queue, &tx_frame, 0) == pdTRUE); + can_set_tx_buffer_and_transmit(&tx_frame); + p_can_obj->tx_msg_count++; + CAN_SET_FLAG(p_can_obj->control_flags, CTRL_FLAG_TX_BUFF_OCCUPIED); + ret = ESP_OK; + } else { + //Frame was copied to queue, waiting to be transmitted + p_can_obj->tx_msg_count++; + ret = ESP_OK; + } + CAN_EXIT_CRITICAL(); + } else { + //Timed out waiting for free space on TX queue + ret = ESP_ERR_TIMEOUT; + } + } + return ret; +} + +esp_err_t can_receive(can_message_t *message, TickType_t ticks_to_wait) +{ + //Check arguments and state + CAN_CHECK(p_can_obj != NULL, ESP_ERR_INVALID_STATE); + CAN_CHECK(message != NULL, ESP_ERR_INVALID_ARG); + + //Get frame from RX Queue or RX Buffer + can_frame_t rx_frame; + if (xQueueReceive(p_can_obj->rx_queue, &rx_frame, ticks_to_wait) != pdTRUE) { + return ESP_ERR_TIMEOUT; + } + + CAN_ENTER_CRITICAL(); + p_can_obj->rx_msg_count--; + CAN_EXIT_CRITICAL(); + + //Decode frame + can_parse_frame(&rx_frame, &(message->identifier), &(message->data_length_code), message->data, &(message->flags)); + return ESP_OK; +} + +esp_err_t can_read_alerts(uint32_t *alerts, TickType_t ticks_to_wait) +{ + //Check arguments and state + CAN_CHECK(p_can_obj != NULL, ESP_ERR_INVALID_STATE); + CAN_CHECK(alerts != NULL, ESP_ERR_INVALID_ARG); + + //Wait for an alert to occur + if (xSemaphoreTake(p_can_obj->alert_semphr, ticks_to_wait) == pdTRUE) { + CAN_ENTER_CRITICAL(); + *alerts = p_can_obj->alerts_triggered; + p_can_obj->alerts_triggered = 0; //Clear triggered alerts + CAN_EXIT_CRITICAL(); + return ESP_OK; + } else { + *alerts = 0; + return ESP_ERR_TIMEOUT; + } +} + +esp_err_t can_reconfigure_alerts(uint32_t alerts_enabled, uint32_t *current_alerts) +{ + CAN_CHECK(p_can_obj != NULL, ESP_ERR_INVALID_STATE); + CAN_ENTER_CRITICAL(); + uint32_t cur_alerts; + cur_alerts = can_read_alerts(&cur_alerts, 0); //Clear any unhandled alerts + p_can_obj->alerts_enabled = alerts_enabled; //Update enabled alerts + CAN_EXIT_CRITICAL(); + + if (current_alerts != NULL) { + *current_alerts = cur_alerts; + } + return ESP_OK; +} + +esp_err_t can_initiate_recovery() +{ + CAN_ENTER_CRITICAL(); + //Check state + CAN_CHECK_FROM_CRIT(p_can_obj != NULL, ESP_ERR_INVALID_STATE); + CAN_CHECK_FROM_CRIT(p_can_obj->control_flags & CTRL_FLAG_BUS_OFF, ESP_ERR_INVALID_STATE); + CAN_CHECK_FROM_CRIT(!(p_can_obj->control_flags & CTRL_FLAG_RECOVERING), ESP_ERR_INVALID_STATE); + + //Reset TX Queue/Counters + if (p_can_obj->tx_queue != NULL) { + xQueueReset(p_can_obj->tx_queue); + } + p_can_obj->tx_msg_count = 0; + CAN_RESET_FLAG(p_can_obj->control_flags, CTRL_FLAG_TX_BUFF_OCCUPIED); + CAN_SET_FLAG(p_can_obj->control_flags, CTRL_FLAG_RECOVERING); + + //Trigger start of recovery process + configASSERT(can_exit_reset_mode() == ESP_OK); + CAN_EXIT_CRITICAL(); + + return ESP_OK; +} + +esp_err_t can_get_status_info(can_status_info_t *status_info) +{ + //Check parameters and state + CAN_CHECK(p_can_obj != NULL, ESP_ERR_INVALID_STATE); + CAN_CHECK(status_info != NULL, ESP_ERR_INVALID_ARG); + + CAN_ENTER_CRITICAL(); + uint32_t tec, rec; + can_get_error_counters(&tec, &rec); + status_info->tx_error_counter = tec; + status_info->rx_error_counter = rec; + status_info->msgs_to_tx = p_can_obj->tx_msg_count; + status_info->msgs_to_rx = p_can_obj->rx_msg_count; + status_info->tx_failed_count = p_can_obj->tx_failed_count; + status_info->rx_missed_count = p_can_obj->rx_missed_count; + status_info->arb_lost_count = p_can_obj->arb_lost_count; + status_info->bus_error_count = p_can_obj->bus_error_count; + if (p_can_obj->control_flags & CTRL_FLAG_RECOVERING) { + status_info->state = CAN_STATE_RECOVERING; + } else if (p_can_obj->control_flags & CTRL_FLAG_BUS_OFF) { + status_info->state = CAN_STATE_BUS_OFF; + } else if (p_can_obj->control_flags & CTRL_FLAG_STOPPED) { + status_info->state = CAN_STATE_STOPPED; + } else { + status_info->state = CAN_STATE_RUNNING; + } + CAN_EXIT_CRITICAL(); + + return ESP_OK; +} + diff --git a/components/driver/include/driver/can.h b/components/driver/include/driver/can.h new file mode 100644 index 000000000..5ec272ca4 --- /dev/null +++ b/components/driver/include/driver/can.h @@ -0,0 +1,398 @@ +// Copyright 2015-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _DRIVER_CAN_H_ +#define _DRIVER_CAN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_types.h" +#include "esp_intr.h" +#include "esp_err.h" +#include "gpio.h" + +/* -------------------- Default initializers and flags ---------------------- */ +/** @cond */ //Doxy command to hide preprocessor definitions from docs +/** + * @brief Initializer macro for general configuration structure. + * + * This initializer macros allows the TX GPIO, RX GPIO, and operating mode to be + * configured. The other members of the general configuration structure are + * assigned default values. + */ +#define CAN_GENERAL_CONFIG_DEFAULT(tx_io_num, rx_io_num, op_mode) {.mode = op_mode, .tx_io = tx_io_num, .rx_io = rx_io_num, \ + .clkout_io = CAN_IO_UNUSED, .bus_off_io = CAN_IO_UNUSED, \ + .tx_queue_len = 5, .rx_queue_len = 5, \ + .alerts_enabled = CAN_ALERT_NONE, .clkout_divider = 0, } + +/** + * @brief Initializer macros for timing configuration structure + * + * The following initializer macros offer commonly found bit rates. + */ +#define CAN_TIMING_CONFIG_25KBITS() {.brp = 128, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .triple_sampling = false} +#define CAN_TIMING_CONFIG_50KBITS() {.brp = 80, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false} +#define CAN_TIMING_CONFIG_100KBITS() {.brp = 40, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false} +#define CAN_TIMING_CONFIG_125KBITS() {.brp = 32, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false} +#define CAN_TIMING_CONFIG_250KBITS() {.brp = 16, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false} +#define CAN_TIMING_CONFIG_500KBITS() {.brp = 8, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false} +#define CAN_TIMING_CONFIG_800KBITS() {.brp = 4, .tseg_1 = 16, .tseg_2 = 8, .sjw = 3, .triple_sampling = false} +#define CAN_TIMING_CONFIG_1MBITS() {.brp = 4, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false} + +/** + * @brief Initializer macro for filter configuration to accept all IDs + */ +#define CAN_FILTER_CONFIG_ACCEPT_ALL() {.acceptance_code = 0, .acceptance_mask = 0xFFFFFFFF, .single_filter = true} + +/** + * @brief Alert flags + * + * The following flags represents the various kind of alerts available in + * the CAN driver. These flags can be used when configuring/reconfiguring + * alerts, or when calling can_read_alerts(). + * + * @note The CAN_ALERT_AND_LOG flag is not an actual alert, but will configure + * the CAN driver to log to UART when an enabled alert occurs. + */ +#define CAN_ALERT_TX_IDLE 0x0001 /**< Alert(1): No more messages to transmit */ +#define CAN_ALERT_TX_SUCCESS 0x0002 /**< Alert(2): The previous transmission was successful */ +#define CAN_ALERT_BELOW_ERR_WARN 0x0004 /**< Alert(4): Both error counters have dropped below error warning limit */ +#define CAN_ALERT_ERR_ACTIVE 0x0008 /**< Alert(8): CAN controller has become error active */ +#define CAN_ALERT_RECOVERY_IN_PROGRESS 0x0010 /**< Alert(16): CAN controller is undergoing bus recovery */ +#define CAN_ALERT_BUS_RECOVERED 0x0020 /**< Alert(32): CAN controller has successfully completed bus recovery */ +#define CAN_ALERT_ARB_LOST 0x0040 /**< Alert(64): The previous transmission lost arbitration */ +#define CAN_ALERT_ABOVE_ERR_WARN 0x0080 /**< Alert(128): One of the error counters have exceeded the error warning limit */ +#define CAN_ALERT_BUS_ERROR 0x0100 /**< Alert(256): A (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus */ +#define CAN_ALERT_TX_FAILED 0x0200 /**< Alert(512): The previous transmission has failed (for single shot transmission) */ +#define CAN_ALERT_RX_QUEUE_FULL 0x0400 /**< Alert(1024): The RX queue is full causing a frame to be lost */ +#define CAN_ALERT_ERR_PASS 0x0800 /**< Alert(2048): CAN controller has become error passive */ +#define CAN_ALERT_BUS_OFF 0x1000 /**< Alert(4096): Bus-off condition occurred. CAN controller can no longer influence bus */ +#define CAN_ALERT_ALL 0x1FFF /**< Bit mask to enable all alerts during configuration */ +#define CAN_ALERT_NONE 0x0000 /**< Bit mask to disable all alerts during configuration */ +#define CAN_ALERT_AND_LOG 0x2000 /**< Bit mask to enable alerts to also be logged when they occur */ + +/** + * @brief Message flags + * + * The message flags are used to indicate the type of message transmitted/received. + * Some flags also specify the type of transmission. + */ +#define CAN_MSG_FLAG_NONE 0x00 /**< No message flags (Standard Frame Format) */ +#define CAN_MSG_FLAG_EXTD 0x01 /**< Extended Frame Format (29bit ID) */ +#define CAN_MSG_FLAG_RTR 0x02 /**< Message is a Remote Transmit Request */ +#define CAN_MSG_FLAG_SS 0x04 /**< Transmit as a Single Shot Transmission */ +#define CAN_MSG_FLAG_SELF 0x08 /**< Transmit as a Self Reception Request */ +#define CAN_MSG_FLAG_DLC_NON_COMP 0x10 /**< Message's Data length code is larger than 8. This will break compliance with CAN2.0B */ + +/** + * @brief Miscellaneous macros + */ +#define CAN_EXTD_ID_MASK 0x1FFFFFFF /**< Bit mask for 29 bit Extended Frame Format ID */ +#define CAN_STD_ID_MASK 0x7FF /**< Bit mask for 11 bit Standard Frame Format ID */ +#define CAN_MAX_DATA_LEN 8 /**< Maximum number of data bytes in a CAN2.0B frame */ +#define CAN_IO_UNUSED (-1) /**< Marks GPIO as unused in CAN configuration */ +/** @endcond */ + +/* ----------------------- Enum and Struct Definitions ---------------------- */ + +/** + * @brief CAN driver operating modes + */ +typedef enum { + CAN_MODE_NORMAL, /**< Normal operating mode where CAN controller can send/receive/acknowledge messages */ + CAN_MODE_NO_ACK, /**< Transmission does not require acknowledgment. Use this mode for self testing */ + CAN_MODE_LISTEN_ONLY, /**< The CAN controller will not influence the bus (No transmissions or acknowledgments) but can receive messages */ +} can_mode_t; + +/** + * @brief CAN driver states + */ +typedef enum { + CAN_STATE_STOPPED, /**< Stopped state. The CAN controller will not participate in any CAN bus activities */ + CAN_STATE_RUNNING, /**< Running state. The CAN controller can transmit and receive messages */ + CAN_STATE_BUS_OFF, /**< Bus-off state. The CAN controller cannot participate in bus activities until it has recovered */ + CAN_STATE_RECOVERING, /**< Recovering state. The CAN controller is undergoing bus recovery */ +} can_state_t; + +/** + * @brief Structure for general configuration of the CAN driver + * + * @note Macro initializers are available for this structure + */ +typedef struct { + can_mode_t mode; /**< Mode of CAN controller */ + gpio_num_t tx_io; /**< Transmit GPIO number */ + gpio_num_t rx_io; /**< Receive GPIO number */ + gpio_num_t clkout_io; /**< CLKOUT GPIO number (optional, set to -1 if unused) */ + gpio_num_t bus_off_io; /**< Bus off indicator GPIO number (optional, set to -1 if unused) */ + uint32_t tx_queue_len; /**< Number of messages TX queue can hold (set to 0 to disable TX Queue) */ + uint32_t rx_queue_len; /**< Number of messages RX queue can hold */ + uint32_t alerts_enabled; /**< Bit field of alerts to enable (see documentation) */ + uint32_t clkout_divider; /**< CLKOUT divider. Can be 1 or any even number from 2 to 14 (optional, set to 0 if unused) */ +} can_general_config_t; + +/** + * @brief Structure for bit timing configuration of the CAN driver + * + * @note Macro initializers are available for this structure + */ +typedef struct { + uint8_t brp; /**< Baudrate prescaler (APB clock divider, even number from 2 to 128) */ + uint8_t tseg_1; /**< Timing segment 1 (Number of time quanta, between 1 to 16) */ + uint8_t tseg_2; /**< Timing segment 2 (Number of time quanta, 1 to 8) */ + uint8_t sjw; /**< Synchronization Jump Width (Max time quanta jump for synchronize from 1 to 4) */ + bool triple_sampling; /**< Enables triple sampling when the CAN controller samples a bit */ +} can_timing_config_t; + +/** + * @brief Structure for acceptance filter configuration of the CAN driver (see documentation) + * + * @note Macro initializers are available for this structure + */ +typedef struct { + uint32_t acceptance_code; /**< 32-bit acceptance code */ + uint32_t acceptance_mask; /**< 32-bit acceptance mask */ + bool single_filter; /**< Use Single Filter Mode (see documentation) */ +} can_filter_config_t; + +/** + * @brief Structure to store status information of CAN driver + */ +typedef struct { + can_state_t state; /**< Current state of CAN controller (Stopped/Running/Bus-Off/Recovery) */ + uint32_t msgs_to_tx; /**< Number of messages queued for transmission or awaiting transmission completion */ + uint32_t msgs_to_rx; /**< Number of messages in RX queue waiting to be read */ + uint32_t tx_error_counter; /**< Current value of Transmit Error Counter */ + uint32_t rx_error_counter; /**< Current value of Receive Error Counter */ + uint32_t tx_failed_count; /**< Number of messages that failed transmissions */ + uint32_t rx_missed_count; /**< Number of messages that were lost due to a full RX queue */ + uint32_t arb_lost_count; /**< Number of instances arbitration was lost */ + uint32_t bus_error_count; /**< Number of instances a bus error has occurred */ +} can_status_info_t; + +/** + * @brief Structure to store a CAN message + * + * @note The flags member is used to control the message type, and transmission + * type (see documentation for message flags) + */ +typedef struct { + uint32_t flags; /**< Bit field of message flags indicates frame/transmission type (see documentation) */ + uint32_t identifier; /**< 11 or 29 bit identifier */ + uint8_t data_length_code; /**< Data length code */ + uint8_t data[CAN_MAX_DATA_LEN]; /**< Data bytes (not relevant in RTR frame) */ +} can_message_t; + +/* ----------------------------- Public API -------------------------------- */ + +/** + * @brief Install CAN driver + * + * This function installs the CAN driver using three configuration structures. + * The required memory is allocated and the CAN driver is placed in the stopped + * state after running this function. + * + * @param[in] g_config General configuration structure + * @param[in] t_config Timing configuration structure + * @param[in] f_config Filter configuration structure + * + * @note Macro initializers are available for the configuration structures (see documentation) + * + * @note To reinstall the CAN driver, call can_driver_uninstall() first + * + * @return + * - ESP_OK: Successfully installed CAN driver + * - ESP_ERR_INVALID_ARG: Arguments are invalid + * - ESP_ERR_NO_MEM: Insufficient memory + * - ESP_ERR_INVALID_STATE: Driver is already installed + */ +esp_err_t can_driver_install(const can_general_config_t *g_config, const can_timing_config_t *t_config, const can_filter_config_t *f_config); + +/** + * @brief Uninstall the CAN driver + * + * This function uninstalls the CAN driver, freeing the memory utilized by the + * driver. This function can only be called when the driver is in the stopped + * state or the bus-off state. + * + * @warning The application must ensure that no tasks are blocked on TX/RX + * queues or alerts when this function is called. + * + * @return + * - ESP_OK: Successfully uninstalled CAN driver + * - ESP_ERR_INVALID_STATE: Driver is not in stopped/bus-off state, or is not installed + */ +esp_err_t can_driver_uninstall(); + +/** + * @brief Start the CAN driver + * + * This function starts the CAN driver, putting the CAN driver into the running + * state. This allows the CAN driver to participate in CAN bus activities such + * as transmitting/receiving messages. The RX queue is reset in this function, + * clearing any unread messages. This function can only be called when the CAN + * driver is in the stopped state. + * + * @return + * - ESP_OK: CAN driver is now running + * - ESP_ERR_INVALID_STATE: Driver is not in stopped state, or is not installed + */ +esp_err_t can_start(); + +/** + * @brief Stop the CAN driver + * + * This function stops the CAN driver, preventing any further message from being + * transmitted or received until can_start() is called. Any messages in the TX + * queue are cleared. Any messages in the RX queue should be read by the + * application after this function is called. This function can only be called + * when the CAN driver is in the running state. + * + * @warning A message currently being transmitted/received on the CAN bus will + * be ceased immediately. This may lead to other CAN nodes interpreting + * the unfinished message as an error. + * + * @return + * - ESP_OK: CAN driver is now Stopped + * - ESP_ERR_INVALID_STATE: Driver is not in running state, or is not installed + */ +esp_err_t can_stop(); + +/** + * @brief Transmit a CAN message + * + * This function queues a CAN message for transmission. Transmission will start + * immediately if no other messages are queued for transmission. If the TX queue + * is full, this function will block until more space becomes available or until + * it timesout. If the TX queue is disabled (TX queue length = 0 in configuration), + * this function will return immediately if another message is undergoing + * transmission. This function can only be called when the CAN driver is in the + * running state and cannot be called under Listen Only Mode. + * + * @param[in] message Message to transmit + * @param[in] ticks_to_wait Number of FreeRTOS ticks to block on the TX queue + * + * @note This function does not guarantee that the transmission is successful. + * The TX_SUCCESS/TX_FAILED alert can be enabled to alert the application + * upon the success/failure of a transmission. + * + * @note The TX_IDLE alert can be used to alert the application when no other + * messages are awaiting transmission. + * + * @return + * - ESP_OK: Transmission successfully queued/initiated + * - ESP_ERR_INVALID_ARG: Arguments are invalid + * - ESP_ERR_TIMEOUT: Timed out waiting for space on TX queue + * - ESP_FAIL: TX queue is disabled and another message is currently transmitting + * - ESP_ERR_INVALID_STATE: CAN driver is not in running state, or is not installed + * - ESP_ERR_NOT_SUPPORTED: Listen Only Mode does not support transmissions + */ +esp_err_t can_transmit(const can_message_t *message, TickType_t ticks_to_wait); + +/** + * @brief Receive a CAN message + * + * This function receives a message from the RX queue. The flags field of the + * message structure will indicate the type of message received. This function + * will block if there are no messages in the RX queue + * + * @param[out] message Received message + * @param[in] ticks_to_wait Number of FreeRTOS ticks to block on RX queue + * + * @warning The flags field of the received message should be checked to determine + * if the received message contains any data bytes. + * + * @return + * - ESP_OK: Message successfully received from RX queue + * - ESP_ERR_TIMEOUT: Timed out waiting for message + * - ESP_ERR_INVALID_ARG: Arguments are invalid + * - ESP_ERR_INVALID_STATE: CAN driver is not installed + */ +esp_err_t can_receive(can_message_t *message, TickType_t ticks_to_wait); + +/** + * @brief Read CAN driver alerts + * + * This function will read the alerts raised by the CAN driver. If no alert has + * been when this function is called, this function will block until an alert + * occurs or until it timeouts. + * + * @param[out] alerts Bit field of raised alerts (see documentation for alert flags) + * @param[in] ticks_to_wait Number of FreeRTOS ticks to block for alert + * + * @note Multiple alerts can be raised simultaneously. The application should + * check for all alerts that have been enabled. + * + * @return + * - ESP_OK: Alerts read + * - ESP_ERR_TIMEOUT: Timed out waiting for alerts + * - ESP_ERR_INVALID_ARG: Arguments are invalid + * - ESP_ERR_INVALID_STATE: CAN driver is not installed + */ +esp_err_t can_read_alerts(uint32_t *alerts, TickType_t ticks_to_wait); + +/** + * @brief Reconfigure which alerts are enabled + * + * This function reconfigures which alerts are enabled. If there are alerts + * which have not been read whilst reconfiguring, this function can read those + * alerts. + * + * @param[in] alerts_enabled Bit field of alerts to enable (see documentation for alert flags) + * @param[out] current_alerts Bit field of currently raised alerts. Set to NULL if unused + * + * @return + * - ESP_OK: Alerts reconfigured + * - ESP_ERR_INVALID_STATE: CAN driver is not installed + */ +esp_err_t can_reconfigure_alerts(uint32_t alerts_enabled, uint32_t *current_alerts); + +/** + * @brief Start the bus recovery process + * + * This function initiates the bus recovery process when the CAN driver is in + * the bus-off state. Once initiated, the CAN driver will enter the recovering + * state and wait for 128 occurrences of the bus-free signal on the CAN bus + * before returning to the stopped state. This function will reset the TX queue, + * clearing any messages pending transmission. + * + * @note The BUS_RECOVERED alert can be enabled to alert the application when + * the bus recovery process completes. + * + * @return + * - ESP_OK: Bus recovery started + * - ESP_ERR_INVALID_STATE: CAN driver is not in the bus-off state, or is not installed + */ +esp_err_t can_initiate_recovery(); + +/** + * @brief Get current status information of the CAN driver + * + * @param[out] status_info Status information + * + * @return + * - ESP_OK: Status information retrieved + * - ESP_ERR_INVALID_ARG: Arguments are invalid + * - ESP_ERR_INVALID_STATE: CAN driver is not installed + */ +esp_err_t can_get_status_info(can_status_info_t *status_info); + +#ifdef __cplusplus +} +#endif + +#endif /*_DRIVER_CAN_H_*/ + diff --git a/components/esp32/ld/esp32.peripherals.ld b/components/esp32/ld/esp32.peripherals.ld index 3403ee8a8..2dce0e0fc 100644 --- a/components/esp32/ld/esp32.peripherals.ld +++ b/components/esp32/ld/esp32.peripherals.ld @@ -26,6 +26,7 @@ PROVIDE ( SPI3 = 0x3ff65000 ); PROVIDE ( SYSCON = 0x3ff66000 ); PROVIDE ( I2C1 = 0x3ff67000 ); PROVIDE ( SDMMC = 0x3ff68000 ); +PROVIDE ( CAN = 0x3ff6B000 ); PROVIDE ( MCPWM1 = 0x3ff6C000 ); PROVIDE ( I2S1 = 0x3ff6D000 ); PROVIDE ( UART2 = 0x3ff6E000 ); diff --git a/components/soc/esp32/include/soc/can_struct.h b/components/soc/esp32/include/soc/can_struct.h new file mode 100644 index 000000000..3f566b135 --- /dev/null +++ b/components/soc/esp32/include/soc/can_struct.h @@ -0,0 +1,211 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _SOC_CAN_STRUCT_H_ +#define _SOC_CAN_STRUCT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* -------------------------- Register Definitions -------------------------- */ + +/* The CAN peripheral's registers are 8bits, however the ESP32 can only access + * peripheral registers every 32bits. Therefore each CAN register is mapped to + * the least significant byte of every 32bits. + */ +typedef union { + struct { + uint32_t byte: 8; /* LSB */ + uint32_t reserved24: 24; /* Internal Reserved */ + }; + uint32_t val; +} can_reg_t; + +typedef union { + struct { + uint32_t reset: 1; /* MOD.0 Reset Mode */ + uint32_t listen_only: 1; /* MOD.1 Listen Only Mode */ + uint32_t self_test: 1; /* MOD.2 Self Test Mode */ + uint32_t acceptance_filter: 1; /* MOD.3 Acceptance Filter Mode */ + uint32_t reserved28: 28; /* Internal Reserved. MOD.4 Sleep Mode not supported */ + }; + uint32_t val; +} can_mode_reg_t; + +typedef union { + struct { + uint32_t tx_req: 1; /* CMR.0 Transmission Request */ + uint32_t abort_tx: 1; /* CMR.1 Abort Transmission */ + uint32_t release_rx_buff: 1; /* CMR.2 Release Receive Buffer */ + uint32_t clear_data_overrun: 1; /* CMR.3 Clear Data Overrun */ + uint32_t self_rx_req: 1; /* CMR.4 Self Reception Request */ + uint32_t reserved27: 27; /* Internal Reserved */ + }; + uint32_t val; +} can_cmd_reg_t; + +typedef union { + struct { + uint32_t rx_buff: 1; /* SR.0 Receive Buffer Status */ + uint32_t data_overrun: 1; /* SR.1 Data Overrun Status */ + uint32_t tx_buff: 1; /* SR.2 Transmit Buffer Status */ + uint32_t tx_complete: 1; /* SR.3 Transmission Complete Status */ + uint32_t rx: 1; /* SR.4 Receive Status */ + uint32_t tx: 1; /* SR.5 Transmit Status */ + uint32_t error: 1; /* SR.6 Error Status */ + uint32_t bus: 1; /* SR.7 Bus Status */ + uint32_t reserved24: 24; /* Internal Reserved */ + }; + uint32_t val; +} can_status_reg_t; + +typedef union { + struct { + uint32_t rx: 1; /* IR.0 Receive Interrupt */ + uint32_t tx: 1; /* IR.1 Transmit Interrupt */ + uint32_t err_warn: 1; /* IR.2 Error Interrupt */ + uint32_t data_overrun: 1; /* IR.3 Data Overrun Interrupt */ + uint32_t reserved1: 1; /* Internal Reserved (Wake-up not supported) */ + uint32_t err_passive: 1; /* IR.5 Error Passive Interrupt */ + uint32_t arb_lost: 1; /* IR.6 Arbitration Lost Interrupt */ + uint32_t bus_err: 1; /* IR.7 Bus Error Interrupt */ + uint32_t reserved24: 24; /* Internal Reserved */ + }; + uint32_t val; +} can_intr_reg_t; + +typedef union { + struct { + uint32_t rx: 1; /* IER.0 Receive Interrupt Enable */ + uint32_t tx: 1; /* IER.1 Transmit Interrupt Enable */ + uint32_t err_warn: 1; /* IER.2 Error Interrupt Enable */ + uint32_t data_overrun: 1; /* IER.3 Data Overrun Interrupt Enable */ + uint32_t reserved1: 1; /* Internal Reserved (Wake-up not supported) */ + uint32_t err_passive: 1; /* IER.5 Error Passive Interrupt Enable */ + uint32_t arb_lost: 1; /* IER.6 Arbitration Lost Interrupt Enable */ + uint32_t bus_err: 1; /* IER.7 Bus Error Interrupt Enable */ + uint32_t reserved24: 24; /* Internal Reserved */ + }; + uint32_t val; +} can_intr_en_reg_t; + +typedef union { + struct { + uint32_t baud_rate_prescaler: 6; /* BTR0[5:0] Baud Rate Prescaler */ + uint32_t sync_jump_width: 2; /* BTR0[7:6] Synchronization Jump Width*/ + uint32_t reserved24: 24; /* Internal Reserved */ + }; + uint32_t val; +} can_bus_tim_0_reg_t; + +typedef union { + struct { + uint32_t time_seg_1: 4; /* BTR1[3:0] Timing Segment 1 */ + uint32_t time_seg_2: 3; /* BTR1[6:4] Timing Segment 2 */ + uint32_t sampling: 1; /* BTR1.7 Sampling*/ + uint32_t reserved24: 24; /* Internal Reserved */ + }; + uint32_t val; +} can_bus_tim_1_reg_t; + +typedef union { + struct { + uint32_t arbitration_lost_capture: 5; /* ALC[4:0] Arbitration lost capture */ + uint32_t reserved27: 27; /* Internal Reserved */ + }; + uint32_t val; +} can_arb_lost_cap_reg_t; + +typedef union { + struct { + uint32_t segment: 5; /* ECC[4:0] Error Code Segment 0 to 5 */ + uint32_t direction: 1; /* ECC.5 Error Direction (TX/RX) */ + uint32_t error_code: 2; /* ECC[7:6] Error Code */ + uint32_t reserved24: 24; /* Internal Reserved */ + }; + uint32_t val; +} can_err_code_cap_reg_t; + +typedef struct { + can_reg_t code_reg[4]; + can_reg_t mask_reg[4]; + uint32_t reserved32[5]; +} can_acc_filter_t; + +typedef union { + struct { + uint32_t rx_message_counter: 5; /* RMC[4:0] RX Message Counter */ + uint32_t reserved27: 27; /* Internal Reserved */ + }; + uint32_t val; +} can_rx_msg_cnt_reg_t; + +typedef union { + struct { + uint32_t clock_divider: 3; /* CDR[2:0] CLKOUT frequency selector based of fOSC */ + uint32_t clock_off: 1; /* CDR.3 CLKOUT enable/disable */ + uint32_t reserved3: 3; /* Internal Reserved. RXINTEN and CBP not supported */ + uint32_t can_mode: 1; /* CDR.7 BasicCAN:0 PeliCAN:1 */ + uint32_t reserved24: 24; /* Internal Reserved */ + }; + uint32_t val; +} can_clk_div_reg_t; + +/* ---------------------------- Register Layout ------------------------------ */ + +typedef volatile struct { + //Configuration and Control Registers + can_mode_reg_t mode_reg; /* Address 0 */ + can_cmd_reg_t command_reg; /* Address 1 */ + can_status_reg_t status_reg; /* Address 2 */ + can_intr_reg_t interrupt_reg; /* Address 3 */ + can_intr_en_reg_t interrupt_enable_reg; /* Address 4 */ + uint32_t reserved_05; /* Address 5 */ + can_bus_tim_0_reg_t bus_timing_0_reg; /* Address 6 */ + can_bus_tim_1_reg_t bus_timing_1_reg; /* Address 7 */ + uint32_t reserved_08; /* Address 8 (Output control not supported) */ + uint32_t reserved_09; /* Address 9 (Test Register not supported) */ + uint32_t reserved_10; /* Address 10 */ + + //Capture and Counter Registers + can_arb_lost_cap_reg_t arbitration_lost_captue_reg; /* Address 11 */ + can_err_code_cap_reg_t error_code_capture_reg; /* Address 12 */ + can_reg_t error_warning_limit_reg; /* EWLR[7:0] Error Warning Limit: Address 13 */ + can_reg_t rx_error_counter_reg; /* RXERR[7:0] Receive Error Counter: Address 14 */ + can_reg_t tx_error_counter_reg; /* TXERR[7:0] Transmit Error Counter: Address 15 */ + + //Shared Registers (TX Buff/RX Buff/Acc Filter) + union { + can_acc_filter_t acceptance_filter; + can_reg_t tx_rx_buffer[13]; + }; /* Address 16-28 TX/RX Buffer and Acc Filter*/; + + //Misc Registers + can_rx_msg_cnt_reg_t rx_message_counter_reg; /* Address 29 */ + can_reg_t reserved_30; /* Address 30 (RX Buffer Start Address not supported) */ + can_clk_div_reg_t clock_divider_reg; /* Address 31 */ + + //Start of RX FIFO +} can_dev_t; + +_Static_assert(sizeof(can_dev_t) == 128, "CAN registers should be 32 * 4 bytes"); + +extern can_dev_t CAN; + +#ifdef __cplusplus +} +#endif + +#endif /* _SOC_CAN_STRUCT_H_ */ + diff --git a/components/soc/esp32/include/soc/soc.h b/components/soc/esp32/include/soc/soc.h index 660abbdb3..3ac5248c8 100644 --- a/components/soc/esp32/include/soc/soc.h +++ b/components/soc/esp32/include/soc/soc.h @@ -120,6 +120,7 @@ #define DR_REG_I2C1_EXT_BASE 0x3ff67000 #define DR_REG_SDMMC_BASE 0x3ff68000 #define DR_REG_EMAC_BASE 0x3ff69000 +#define DR_REG_CAN_BASE 0x3ff6B000 #define DR_REG_PWM1_BASE 0x3ff6C000 #define DR_REG_I2S1_BASE 0x3ff6D000 #define DR_REG_UART2_BASE 0x3ff6E000 diff --git a/docs/Doxyfile b/docs/Doxyfile index 396963ea1..643ae2e29 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -64,6 +64,7 @@ INPUT = \ ## Peripherals - API Reference ## ../../components/driver/include/driver/adc.h \ + ../../components/driver/include/driver/can.h \ ../../components/driver/include/driver/dac.h \ ../../components/driver/include/driver/gpio.h \ ../../components/driver/include/driver/rtc_io.h \ diff --git a/docs/_static/diagrams/can/can_acceptance_filter_dual.diag b/docs/_static/diagrams/can/can_acceptance_filter_dual.diag new file mode 100644 index 000000000..b6db5c63a --- /dev/null +++ b/docs/_static/diagrams/can/can_acceptance_filter_dual.diag @@ -0,0 +1,19 @@ +#Diagram of CAN Controller Acceptance Filter (Dual) Configuration + +packetdiag can_acceptance_filter_dual { + node_width = 30 + node_height = 35 + default_fontsize = 15 + colwidth = 32 + + 0-3: "F1 DB1[0:3]" [color = lightyellow]; + 4: "F2\nRTR" [color = lightyellow]; + 5-15: "F2 11-bit ID" [color = lightyellow]; + 16-19: "F1 DB1[4:7]" [color = lightyellow]; + 20: "F1\nRTR" [color = lightyellow]; + 21-31: "F1 11-bit ID" [color = lightyellow]; + + 32-47: "F2 29-bit ID [13:28]" [color = lightblue]; + 48-63: "F1 29-bit ID [13:28]" [color = lightblue]; + +} \ No newline at end of file diff --git a/docs/_static/diagrams/can/can_acceptance_filter_single.diag b/docs/_static/diagrams/can/can_acceptance_filter_single.diag new file mode 100644 index 000000000..996d20cbe --- /dev/null +++ b/docs/_static/diagrams/can/can_acceptance_filter_single.diag @@ -0,0 +1,21 @@ +#Diagram of CAN Controller Acceptance Filter (Single) Configuration + +packetdiag can_acceptance_filter_single { + node_width = 30 + node_height = 35 + default_fontsize = 15 + colwidth = 32 + + #Single Filter Standard Frame Format + 0-7: Data Byte 2 [color = lightyellow]; + 8-15: Data Byte 1 [color = lightyellow]; + 16-19: Unsed [color = lightgrey]; + 20: RTR [color = lightyellow]; + 21-31: 11 bit ID [color = lightyellow]; + + #Single Filter Extended Frame Format + 32-33: Unused [color = lightgrey]; + 34: RTR [color = lightblue]; + 35-63: 29 bit ID [color = lightblue]; + +} \ No newline at end of file diff --git a/docs/_static/diagrams/can/can_bit_timing.diag b/docs/_static/diagrams/can/can_bit_timing.diag new file mode 100644 index 000000000..2422ae87a --- /dev/null +++ b/docs/_static/diagrams/can/can_bit_timing.diag @@ -0,0 +1,12 @@ +#Example of bit timing configuration for 500KBPS + +packetdiag can_bit_timing_diag{ + node_width = 40 + node_height = 35 + default_fontsize = 15 + colwidth = 20 + + 0: "Sync" [color = lightgrey]; + 1-14: "Tseg1 = 15" [color = lightblue]; + 15-19: "Tseg2 = 4" [color = lightyellow]; +} \ No newline at end of file diff --git a/docs/_static/diagrams/can/can_controller_signals.diag b/docs/_static/diagrams/can/can_controller_signals.diag new file mode 100644 index 000000000..670213ef4 --- /dev/null +++ b/docs/_static/diagrams/can/can_controller_signals.diag @@ -0,0 +1,57 @@ +#Diagram of CAN controller signal lines + +blockdiag can_controller_signals_diagram { + + orientation = portrait; + span_width = 80; + + #Column 1 nodes + can[label = "CAN Controller", fontsize = 15, shape = roundedbox]; + + #Column 2 nodes + tx[label = "TX", shape = endpoint]; + rx[label = "RX", shape = endpoint]; + bus_off[label = "BUS-OFF", shape = endpoint]; + clkout[label = "CLKOUT", shape = endpoint]; + + #Column 3 nodes + hide1 [shape = none]; + hide2 [shape = none]; + hide3 [shape = none]; + hide4 [shape = none]; + + group { + orientation = portrait; + color = none; + + #Group column 1 nodes vertically + can; + } + group { + orientation = portrait; + color = none; + + #Group column 2 nodes vertically + tx; rx; bus_off; clkout; + } + group { + orientation = portrait; + color = none; + label = "GPIO Matrix"; + fontsize = 20; + shape = line; + + #Group column 3 nodes vertically + hide1; hide2; hide3; hide4; + } + + can -> tx [folded]; + can -> rx [folded]; + can -> bus_off [folded]; + can -> clkout [folded]; + + tx -> hide1 [folded]; + rx -> hide2 [folded]; + bus_off -> hide3 [folded, label = "Optional"]; + clkout -> hide4 [folded, label = "Optional"]; +} \ No newline at end of file diff --git a/docs/_static/diagrams/can/can_state_transition.diag b/docs/_static/diagrams/can/can_state_transition.diag new file mode 100644 index 000000000..a70b9ae57 --- /dev/null +++ b/docs/_static/diagrams/can/can_state_transition.diag @@ -0,0 +1,31 @@ +#State transition diagram of the CAN Driver + +blockdiag can_state_transition_diagram { + + orientation = landscape; + default_fontsize = 18; + node_width = 180; + node_height = 40; + span_width = 100; + span_height = 40; + + #First Row + bus_off [label = "Bus-Off"]; + recovering [label = "Recovering"]; + #Second Row + uninstalled [label = "Uninstalled"]; + stopped [label = "Stopped"]; + running [label = "Running"]; + app_start[label = "Entry", shape = beginpoint]; + + bus_off -> uninstalled [folded, thick, fontsize = 14, label = "F"]; + bus_off -> recovering [thick, fontsize = 14, label = "G"]; + recovering -> stopped [folded, thick, color = blue, fontsize = 14, label = "H"]; + + uninstalled <-> stopped [thick, fontsize = 14, label = "A/B"]; + stopped <-> running [thick, fontsize = 14, label = "C/D"]; + running -> bus_off [folded, thick, color = red, fontsize = 14, label = "E"]; + + app_start -> uninstalled [folded, style = dashed] +} + diff --git a/docs/en/api-reference/peripherals/can.rst b/docs/en/api-reference/peripherals/can.rst new file mode 100644 index 000000000..ece926d88 --- /dev/null +++ b/docs/en/api-reference/peripherals/can.rst @@ -0,0 +1,604 @@ +Controller Area Network (CAN) +============================= + +.. _CAN Protocol License Conditions: http://www.bosch-semiconductors.com/media/ip_modules/pdf_2/can_protocol/bosch_can_protocol_license_conditions.pdf + +.. warning:: + Please note that the ESP32 includes a CAN peripheral. The CAN Protocol is + protected by the intellectual property rights of Robert Bosch GmbH. Therefore + a license is required for any implementation of the CAN Protocol + (see `CAN Protocol License Conditions`_). **Since the selling price of the + ESP32 includes no such royalty fee, Espressif hereby disclaims any liability or + obligation regarding the CAN Protocol license. Users of the CAN Protocol via + the ESP32's CAN peripheral should contact Robert Bosch GmbH directly for the + necessary license.** + + +.. -------------------------------- Overview ----------------------------------- + +Overview +-------- + +The ESP32's peripherals contains a CAN Controller that supports Standard Frame +Format (16-bit ID) and Extended Frame Format (29-bit ID) of the CAN2.0B specification. + +.. warning:: + The ESP32 CAN controller is not compatible with CAN FD frames and will interpret + such frames as errors. + +This programming guide is split into the following sections: + + 1. :ref:`basic-can-concepts` + + 2. :ref:`signals-lines-and-transceiver` + + 3. :ref:`configuration` + + 4. :ref:`driver-operation` + + 5. :ref:`examples` + + +.. --------------------------- Basic CAN Concepts ------------------------------ + +.. _basic-can-concepts: + +Basic CAN Concepts +------------------ + +.. note:: + The following section only covers the basic aspects of CAN. For full details, + see the CAN2.0B specification + +The CAN protocol is a multi-master, multi-cast communication protocol with error +detection/signalling and inbuilt message prioritization. The CAN protocol is +commonly used as a communication bus in automotive applications. + +**Multi-master:** Any node in a CAN bus is allowed initiate the transfer of data. + +**Multi-cast:** When a node transmits a message, all nodes are able to receive +the message (broadcast). However some nodes can selective choose which messages +to accept via the use of acceptance filtering (multi-cast). + +**Error Detection and Signalling:** Every CAN node will constantly monitor the +CAN bus. When any node detects an error, it will signal the error by transmitting an error +frame. Other nodes will receive the error frame and transmit their own error frames +in response. This will result in an error detection being propagated to all nodes on +the bus. + +**Message Priorities:** If two nodes attempt to transmit simultaneously, the +node transmitting the message with the lower ID will win arbitration. All other +nodes will become receivers ensuring there is at most one transmitter at any time. + +CAN Message Frames +^^^^^^^^^^^^^^^^^^ + +The CAN2.0B specification contains two frame formats known as **Extended Frame** +and **Standard Frame** which contain 29-bit IDs and 11-bit IDs respectively. +A CAN message consists of the following components + + - 29-bit or 11-bit ID + - Data Length Code (DLC) between 0 to 8 + - Up to 8 bytes of data (should match DLC) + +Error States and Counters +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The CAN2.0B specification implements fault confinement by requiring every CAN node +to maintain two internal error counters known as the **Transmit Error Counter (TEC)** +and the **Receive Error Counter (REC)**. The two error counters are used to determine +a CAN node's **error state**, and the counters are incremented and decremented +following a set of rules (see CAN2.0B specification). These error states are known +as **Error Active**, **Error Passive**, and **Bus-Off**. + +**Error Active:** A CAN node is Error Active when **both TEC and REC are less +than 128** and indicates a CAN node is operating normally. Error Active nodes are +allowed to participate in CAN bus activities, and will actively signal any error +conditions it detects by transmitting an **Active Error Flag** over the CAN bus. + +**Error Passive:** A CAN node is Error Passive when **either the TEC or REC becomes +greater than or equal to 128**. Error Passive nodes are still able to take part in +CAN bus activities, but will instead transmit a **Passive Error Flag** upon +detection of an error. + +**Bus-Off:** A CAN node becomes Bus-Off when the **TEC becomes greater than or equal +to 256**. A Bus-Off node is unable take part in CAN bus activity and will remain so +until it undergoes bus recovery. + + +.. ---------------------- Signal Lines and Transceiver ------------------------- + +.. _signals-lines-and-transceiver: + +Signals Lines and Transceiver +----------------------------- + +The CAN controller does not contain a internal transceiver and therefore +**requires an external transceiver** to operate. The type of external transceiver will +depend on the application's physical layer specification (e.g. using SN65HVD23X +transceivers for ISO 11898-2 compatibility). + +The CAN controller's interface consists of 4 signal lines known as **TX, RX, BUS-OFF, +and CLKOUT**. These four signal lines can be routed through the GPIO Matrix to GPIOs. + +.. blockdiag:: ../../../_static/diagrams/can/can_controller_signals.diag + :caption: Signal lines of the CAN controller + :align: center + +**TX and RX:** The TX and RX signal lines are required to interface with an +external CAN transceiver. Both signal lines represent/interpret a dominant bit +as a low logic level (0V), and a recessive bit as a high logic level (3.3V). + +**BUS-OFF:** The BUS-OFF signal line is **optional** and is set to a low logic level +(0V) whenever the CAN controller reaches a bus-off state. The BUS-OFF signal line +is set to a high logic level (3.3V) otherwise. + +**CLKOUT:** The CLKOUT signal line is **optional** and outputs a prescaled version +of the CAN controller's source clock (APB Clock). + +.. note:: + An external transceiver **must internally tie the TX input and the RX output** + such that a change in logic level to the TX signal line can be observed on the + RX line. Failing to do so will cause the CAN controller to interpret differences + in logic levels between the two signal lines as a lost in arbitration or a + bit error. + + +.. ------------------------------ Configuration -------------------------------- + +.. _configuration: + +Configuration +------------- + +Operating Modes +^^^^^^^^^^^^^^^ + +The CAN driver supports the following modes of operations: + +**Normal Mode:** The normal operating mode allows the CAN controller to take part +in bus activities such as transmitting and receiving messages/error frames. +Acknowledgement from another CAN node is required when transmitting message frames. + +**No Ack Mode:** The No Acknowledgement mode is similar to normal mode, however +acknowledgements are not required when transmitting message frames. This mode is +useful when self testing the CAN controller. + +**Listen Only Mode:** This mode will prevent the CAN controller from taking part +in bus activities. Therefore transmissions of messages/acknowledgement/error frames +will be disabled. However the the CAN controller will still be able to receive +messages (without acknowledging). This mode is suited for applications such as +CAN bus monitoring. + +Alerts +^^^^^^ + +The CAN driver contains an alert feature which is used to notify the application +level of certain CAN driver events. Alerts are selectively enabled when the +CAN driver is installed, but can be reconfigured during runtime by calling +:cpp:func:`can_reconfigure_alerts`. The application can then wait for any enabled +alerts to occur by calling :cpp:func:`can_read_alerts`. The CAN driver supports +the following alerts: + ++------------------------------------+------------------------------------------------------------------------+ +| Alert | Description | ++====================================+=============================================+==========================+ +| ``CAN_ALERT_TX_IDLE`` | No more messages queued for transmission | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_TX_SUCCESS`` | The previous transmission was successful | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_BELOW_ERR_WARN`` | Both error counters have dropped below error warning limit | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_ERR_ACTIVE`` | CAN controller has become error active | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_RECOVERY_IN_PROGRESS`` | CAN controller is undergoing bus recovery | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_BUS_RECOVERED`` | CAN controller has successfully completed bus recovery | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_ARB_LOST`` | The previous transmission lost arbitration | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_ABOVE_ERR_WARN`` | One of the error counters have exceeded the error warning limit | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_BUS_ERROR`` | A (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_TX_FAILED`` | The previous transmission has failed | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_RX_QUEUE_FULL`` | The RX queue is full causing a received frame to be lost | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_ERR_PASS`` | CAN controller has become error passive | ++------------------------------------+------------------------------------------------------------------------+ +| ``CAN_ALERT_BUS_OFF`` | Bus-off condition occurred. CAN controller can no longer influence bus | ++------------------------------------+------------------------------------------------------------------------+ + +.. note:: + The **error warning limit** can be used to preemptively warn the application + of bus errors before the error passive state is reached. By default the CAN + driver sets the **error warning limit** to **96**. The ``CAN_ALERT_ABOVE_ERR_WARN`` + is raised when the TEC or REC becomes larger then or equal to the error warning + limit. The ``CAN_ALERT_BELOW_ERR_WARN`` is raised when both TEC and REC return + back to values below **96**. + +.. note:: + When enabling alerts, the ``CAN_ALERT_AND_LOG`` flag can be used to cause the + CAN driver to log any raised alerts to UART. The ``CAN_ALERT_ALL`` and + ``CAN_ALERT_NONE`` macros can also be used to enable/disable all alerts during + configuration/reconfiguration. + +Bit Timing +^^^^^^^^^^ + +The operating bit rate of the CAN controller is configured using the +:cpp:type:`can_timing_config_t` structure. The period of each bit is made up of +multiple **time quanta**, and the period of a **time quanta** is determined by a +prescaled version of the CAN controller's source clock. A single bit contains the +following segments in the following order: + + 1. The **Synchronization Segment** consists of a single time quanta + 2. **Timing Segment 1** consists of 1 to 16 time quanta before sample point + 3. **Timing Segment 2** consists of 1 to 8 time quanta after sample point + +The **Baudrate Prescaler** is used to determine the period of each time quanta by +dividing the CAN controller's source clock (80 MHz APB clock). The ``brp`` can be +**any even number from 2 to 128**. + +.. packetdiag:: ../../../_static/diagrams/can/can_bit_timing.diag + :caption: Bit timing configuration for 500kbit/s given BRP = 8 + :align: center + +The sample point of a bit is located on the intersection of Timing Segment 1 and +2. Enabling **Triple Sampling** will cause 3 time quanta to be sampled per bit +instead of 1 (extra samples are located at the tail end of Timing Segment 1). + +The **Synchronization Jump Width** is used to determined the maximum number of +time quanta a single bit time can be lengthened/shortened for synchronization +purposes. ``sjw`` can **range from 1 to 4**. + +.. note:: + Multiple combinations of ``brp``, ``tseg_1``, ``tseg_2``, and ``sjw`` can + achieve the same bit rate. Users should tune these values to the physical + characteristics of their CAN bus by taking into account factors such as + **propagation delay, node information processing time, and phase errors**. + +Bit timing **macro initializers** are also available for commonly used CAN bus bit rates. +The following macro initiliazers are provided by the CAN driver. + + - ``CAN_TIMING_CONFIG_25KBITS()`` + - ``CAN_TIMING_CONFIG_50KBITS()`` + - ``CAN_TIMING_CONFIG_100KBITS()`` + - ``CAN_TIMING_CONFIG_125KBITS()`` + - ``CAN_TIMING_CONFIG_250KBITS()`` + - ``CAN_TIMING_CONFIG_500KBITS()`` + - ``CAN_TIMING_CONFIG_800KBITS()`` + - ``CAN_TIMING_CONFIG_1MBITS()`` + +Acceptance Filter +^^^^^^^^^^^^^^^^^ + +The CAN controller contains a hardware acceptance filter which can be used to +filter CAN messages of a particular ID. A node that filters out a message +**will not receive the message, but will still acknowledge it**. Acceptances +filters can make a node more efficient by filtering out messages sent over the +CAN bus that are irrelevant to the CAN node in question. The CAN controller's +acceptance filter is configured using two 32-bit values within :cpp:type:`can_filter_config_t` +known as the **acceptance code** and the **acceptance mask**. + +The **acceptance code** specifies the bit sequence which a message's ID, RTR, and +data bytes must match in order for the message to be received by the CAN +controller. The **acceptance mask** is a bit sequence specifying which bits of +the acceptance code can be ignored. This allows for a messages of different IDs +to be accepted by a single acceptance code. + +The acceptance filter can be used under **Single or Dual Filter Mode**. +Single Filter Mode will use the acceptance code and mask to define a single +filter. This allows for the first two data bytes of a standard frame to be filtered, +or the entirety of an extended frame's 29-bit ID. The following diagram illustrates +how the 32-bit acceptance code and mask will be interpreted under Single Filter Mode +(Note: The yellow and blue fields represent standard and extended CAN frames respectively). + +.. packetdiag:: ../../../_static/diagrams/can/can_acceptance_filter_single.diag + :caption: Bit layout of single filter mode (Right side MSBit) + :align: center + +**Dual Filter Mode** will use the acceptance code and mask to define two separate +filters allowing for increased flexibility of ID's to accept, but does not allow +for all 29-bits of an extended ID to be filtered. The following diagram illustrates +how the 32-bit acceptance code and mask will be interpreted under **Dual Filter Mode** +(Note: The yellow and blue fields represent standard and extended CAN frames respectively). + +.. packetdiag:: ../../../_static/diagrams/can/can_acceptance_filter_dual.diag + :caption: Bit layout of dual filter mode (Right side MSBit) + :align: center + +Disabling TX Queue +^^^^^^^^^^^^^^^^^^ + +The TX queue can be disabled during configuration by setting the ``tx_queue_len`` +member of :cpp:type:`can_general_config_t` to ``0``. This will allow applications +that do not require message transmission to save a small amount of memory when +using the CAN driver. + + +.. -------------------------------- CAN Driver --------------------------------- + +.. _driver-operation: + +Driver Operation +---------------- + +The CAN driver is designed with distinct states and strict rules regarding the +functions or conditions that trigger a state transition. The following diagram +illustrates the various states and their transitions. + +.. blockdiag:: ../../../_static/diagrams/can/can_state_transition.diag + :caption: State transition diagram of the CAN driver (see table below) + :align: center + ++-------+------------------------+------------------------------------+ +| Label | Transition | Action/Condition | ++=======+========================+====================================+ +| A | Uninstalled -> Stopped | :cpp:func:`can_driver_install` | ++-------+------------------------+------------------------------------+ +| B | Stopped -> Uninstalled | :cpp:func:`can_driver_uninstall` | ++-------+------------------------+------------------------------------+ +| C | Stopped -> Running | :cpp:func:`can_start` | ++-------+------------------------+------------------------------------+ +| D | Running -> Stopped | :cpp:func:`can_stop` | ++-------+------------------------+------------------------------------+ +| E | Running -> Bus-Off | Transmit Error Counter >= 256 | ++-------+------------------------+------------------------------------+ +| F | Bus-Off -> Uninstalled | :cpp:func:`can_driver_uninstall` | ++-------+------------------------+------------------------------------+ +| G | Bus-Off -> Recovering | :cpp:func:`can_initiate_recovery` | ++-------+------------------------+------------------------------------+ +| H | Recovering -> Stopped | 128 occurrences of bus-free signal | ++-------+------------------------+------------------------------------+ + +Driver States +^^^^^^^^^^^^^ + +**Uninstalled**: In the uninstalled state, no memory is allocated for the driver +and the CAN controller is powered OFF. + +**Stopped**: In this state, the CAN controller is powered ON and the CAN driver +has been installed. However the CAN controller will be unable to take part in +any CAN bus activities such as transmitting, receiving, or acknowledging messages. + +**Running**: In the running state, the CAN controller is able to take part in +bus activities. Therefore messages can be transmitted/received/acknowledged. +Furthermore the CAN controller will be able to transmit error frames upon detection +of errors on the CAN bus. + +**Bus-Off**: The bus-off state is automatically entered when the CAN controller's +Transmit Error Counter becomes greater than or equal to 256 (see CAN2.0B specification +regarding error counter rules). The bus-off state indicates the occurrence of severe +errors on the CAN bus or in the CAN controller. Whilst in the bus-off state, the +CAN controller will be unable to take part in any CAN bus activities. To exit +the bus-off state, the CAN controller must undergo the bus recovery process. + +**Recovering**: The recovering state is entered when the CAN driver undergoes +bus recovery. The CAN driver/controller will remain in the recovering state until +the 128 occurrences of the bus-free signal (see CAN2.0B specification) is observed +on the CAN bus. + +Message Flags +^^^^^^^^^^^^^ + +The CAN driver distinguishes different types of CAN messages by using the message +flags in the ``flags`` field of :cpp:type:`can_message_t`. These flags help +distinguish whether a message is in standard or extended format, an RTR, and the +type of transmission to use when transmitting such a message. The CAN driver +supports the following flags: + ++-------------------------------+---------------------------------------------------------------+ +| Flag | Description | ++===============================+===============================================================+ +| ``CAN_MSG_FLAG_EXTD`` | Message is in Extended Frame Format (29bit ID) | ++-------------------------------+---------------------------------------------------------------+ +| ``CAN_MSG_FLAG_RTR`` | Message is a Remote Transmit Request | ++-------------------------------+---------------------------------------------------------------+ +| ``CAN_MSG_FLAG_SS`` | Transmit message using Single Shot Transmission (Message will | +| | note be retransmitted upon error or loss of arbitration) | ++-------------------------------+---------------------------------------------------------------+ +| ``CAN_MSG_FLAG_SELF`` | Transmit message using Self Reception Request (Transmitted | +| | message will also received by the same node) | ++-------------------------------+---------------------------------------------------------------+ +| ``CAN_MSG_FLAG_DLC_NON_COMP`` | Message's Data length code is larger than 8. This | +| | will break compliance with CAN2.0B | ++-------------------------------+---------------------------------------------------------------+ + +.. note:: + The ``CAN_MSG_FLAG_NONE`` flag can be used for Standard Frame Format messages + + +.. -------------------------------- Examples ----------------------------------- + +.. _examples: + +Examples +-------- + +Configuration & Installation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following code snippet demonstrates how to configure, install, and start the +CAN driver via the use of the various configuration structures, macro initializers, +the :cpp:func:`can_driver_install` function, and the :cpp:func:`can_start` function. + +.. code-block:: c + + #include "driver/gpio.h" + #include "driver/can.h" + + void app_main() + { + //Initialize configuration structures using macro initializers + can_general_config_t g_config = CAN_GENERAL_CONFIG_DEFAULT(GPIO_NUM_21, GPIO_NUM_22, CAN_MODE_NORMAL); + can_timing_config_t t_config = CAN_TIMING_CONFIG_500KBITS(); + can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL(); + + //Install CAN driver + if (can_driver_install(&g_config, &t_config, &f_config) == ESP_OK) { + printf("Driver installed\n"); + } else { + printf("Failed to install driver\n"); + return; + } + + //Start CAN driver + if (can_start() == ESP_OK) { + printf("Driver started\n"); + } else { + printf("Failed to start driver\n"); + return; + } + + ... + + } + +The usage of macro initializers are not mandatory and each of the configuration +structures can be manually. + +Message Transmission +^^^^^^^^^^^^^^^^^^^^ + +The following code snippet demonstrates how to transmit a message via the usage +of the :cpp:type:`can_message_t` type and :cpp:func:`can_transmit` function. + +.. code-block:: c + + #include "driver/can.h" + + ... + + //Configure message to transmit + can_message_t message; + message.identifier = 0xAAAA; + message.flags = CAN_MSG_FLAG_EXTD; + message.data_length_code = 4; + for (int i = 0; i < 4; i++) { + message.data[i] = 0; + } + + //Queue message for transmission + if (can_transmit(&message, pdMS_TO_TICKS(1000)) == ESP_OK) { + printf("Message queued for transmission\n"); + } else { + printf("Failed to queue message for transmission\n"); + } + +Message Reception +^^^^^^^^^^^^^^^^^ + +The following code snippet demonstrates how to receive a message via the usage +of the :cpp:type:`can_message_t` type and :cpp:func:`can_receive` function. + +.. code-block:: c + + #include "driver/can.h" + + ... + + //Wait for message to be received + can_message_t message; + if (can_receive(&message, pdMS_TO_TICKS(10000)) == ESP_OK) { + printf("Message received\n"); + } else { + printf("Failed to receive message\n"); + return; + } + + //Process received message + if (message.flags & CAN_MSG_FLAG_EXTD) { + printf("Message is in Extended Format\n"); + } else { + printf("Message is in Standard Format\n"); + } + printf("ID is %d\n", message.identifier); + if (!(message.flags & CAN_MSG_FLAG_RTR)) { + for (int i = 0; i < message.data_length_code; i++) { + printf("Data byte %d = %d\n", i, message.data[i]); + } + } + +Reconfiguring and Reading Alerts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The following code snippet demonstrates how to reconfigure and read CAN driver +alerts via the use of the :cpp:func:`can_reconfigure_alerts` and +:cpp:func:`can_read_alerts` functions. + +.. code-block:: c + + #include "driver/can.h" + + ... + + //Reconfigure alerts to detect Error Passive and Bus-Off error states + uint32_t alerts_to_enable = CAN_ALERT_ERR_PASS | CAN_ALERT_BUS_OFF; + if (can_reconfigure_alerts(alerts_to_enable, NULL) == ESP_OK) { + printf("Alerts reconfigured\n"); + } else { + printf("Failed to reconfigure alerts"); + } + + //Block indefinitely until an alert occurs + uint32_t alerts_triggered; + can_read_alerts(&alerts_triggered, portMAX_DELAY); + +Stop and Uninstall +^^^^^^^^^^^^^^^^^^ + +The following code demonstrates how to stop and uninstall the CAN driver via the +use of the :cpp:func:`can_stop` and :cpp:func:`can_driver_uninstall` functions. + +.. code-block:: c + + #include "driver/can.h" + + ... + + //Stop the CAN driver + if (can_stop() == ESP_OK) { + printf("Driver stopped\n"); + } else { + printf("Failed to stop driver\n"); + return; + } + + //Uninstall the CAN driver + if (can_driver_uninstall() == ESP_OK) { + printf("Driver uninstalled\n"); + } else { + printf("Failed to uninstall driver\n"); + return; + } + + +Application Examples +^^^^^^^^^^^^^^^^^^^^ + +**Network Example:** The CAN Network example demonstrates communication between +two ESP32s using the CAN driver API. One CAN node acts as a network master initiate +and ceasing the transfer of a data from another CAN node acting as a network slave. +The example can be found via :example:`examples/peripheral/can/can_network`. + +**Alert and Recovery Example:** This example demonstrates how to use the CAN driver's +alert and bus recovery API. The example purposely introduces errors on the CAN +bus to put the CAN controller into the Bus-Off state. An alert is used to detect +the Bus-Off state and trigger the bus recovery process. The example can be found +via :example:`examples/peripheral/can/can_alert_and_recovery`. + +**Self Test Example:** This example uses the No Acknowledge Mode and Self Reception +Request to cause the CAN controller to send and simultaneously receive a series +of messages. This example can be used to verify if the connections between the CAN +controller and the external transceiver are working correctly. The example can be +found via :example:`examples/peripheral/can/can_self_test`. + + +.. ---------------------------- API Reference ---------------------------------- + +API Reference +------------- + +.. include:: /_build/inc/can.inc \ No newline at end of file diff --git a/docs/en/api-reference/peripherals/index.rst b/docs/en/api-reference/peripherals/index.rst index 930e12a7b..c10254e81 100644 --- a/docs/en/api-reference/peripherals/index.rst +++ b/docs/en/api-reference/peripherals/index.rst @@ -5,6 +5,7 @@ Peripherals API :maxdepth: 1 ADC + CAN DAC GPIO (including RTC low power I/O) I2C diff --git a/docs/zh_CN/api-reference/peripherals/can.rst b/docs/zh_CN/api-reference/peripherals/can.rst new file mode 100644 index 000000000..803dcfd8e --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/can.rst @@ -0,0 +1 @@ +.. include:: ../../../en/api-reference/peripherals/can.rst \ No newline at end of file diff --git a/examples/peripherals/can/can_alert_and_recovery/Makefile b/examples/peripherals/can/can_alert_and_recovery/Makefile new file mode 100644 index 000000000..1edd763c5 --- /dev/null +++ b/examples/peripherals/can/can_alert_and_recovery/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := can_alert_and_recovery_example + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/can/can_alert_and_recovery/README.md b/examples/peripherals/can/can_alert_and_recovery/README.md new file mode 100644 index 000000000..b54d6e914 --- /dev/null +++ b/examples/peripherals/can/can_alert_and_recovery/README.md @@ -0,0 +1,29 @@ +# CAN Alert and Recovery Example + +## Overview +The CAN Alert and Recovery Example demonstrates the usage of alerts and bus +recovery in the CAN driver. This example **requires only a single ESP32 module +to run**. + +The CAN Alert and Recovery Example will do the following... + +1. Initialize and start the CAN driver on the ESP32 module +2. Repeatedly transmit messages (no acknowledgement required) +3. Reconfigure alerts to detect bus-off state +4. Purposely trigger errors on transmissions +5. Detect Bus Off condition +6. Initiate bus recovery +7. Deinitialize CAN driver on ESP32 module + +## External Transceiver and Pin Assignment +The CAN controller in the ESP32 **does not contain an internal transceiver**. +Therefore users are responsible for providing an external transceiver compatible +with the physical layer specifications of their target ISO standard (such as +SN65HVD23X transceivers for ISO 11898-2 compatibility) + +The CAN controller in the ESP32 represents dominant bits to the transceiver as +logic low, and recessive bits as logic high. The Alert and Recovery Example +utilizes the following default pin assignments + +* TX Pin is routed to GPIO21 +* RX Pin is routed to GPIO22 diff --git a/examples/peripherals/can/can_alert_and_recovery/example_test.py b/examples/peripherals/can/can_alert_and_recovery/example_test.py new file mode 100644 index 000000000..e94de8ff6 --- /dev/null +++ b/examples/peripherals/can/can_alert_and_recovery/example_test.py @@ -0,0 +1,29 @@ +#Need Python 3 string formatting functions +from __future__ import print_function + +import re +import os +import sys +# The test cause is dependent on the Tiny Test Framework. Ensure the +# `TEST_FW_PATH` environment variable is set to `$IDF_PATH/tools/tiny-test-fw` +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) +import TinyFW +import IDF + +# CAN Self Test Example constants +STR_EXPECT = ("CAN Alert and Recovery: Driver installed", "CAN Alert and Recovery: Driver uninstalled") +EXPECT_TIMEOUT = 20 + +@IDF.idf_example_test(env_tag='Example_CAN') +def test_can_alert_and_recovery_example(env, extra_data): + #Get device under test, flash and start example. "dut4" must be defined in EnvConfig + dut = env.get_dut('dut4', 'examples/peripherals/can/can_alert_and_recovery') + dut.start_app() + + for string in STR_EXPECT: + dut.expect(string, timeout = EXPECT_TIMEOUT) + +if __name__ == '__main__': + test_can_alert_and_recovery_example() diff --git a/examples/peripherals/can/can_alert_and_recovery/main/can_alert_and_recovery_example_main.c b/examples/peripherals/can/can_alert_and_recovery/main/can_alert_and_recovery_example_main.c new file mode 100644 index 000000000..fab6d4bb0 --- /dev/null +++ b/examples/peripherals/can/can_alert_and_recovery/main/can_alert_and_recovery_example_main.c @@ -0,0 +1,153 @@ +/* CAN Alert and Recovery Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* + * The following example demonstrates how to use the alert and bus recovery + * features of the CAN driver. The example will do the following: + * 1) Install and start the CAN driver + * 2) Have the TX task periodically broadcast messages expecting no ACK + * 3) Reconfigure alerts to detect bus-off state + * 4) Trigger bus errors by inverting TX GPIO + * 5) Initiate bus-off recovery and wait for completion + * 6) Uninstall CAN driver + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/can.h" + +/* --------------------- Definitions and static variables ------------------ */ +//Example Configuration +#define TX_GPIO_NUM 21 +#define RX_GPIO_NUM 22 +#define TX_TASK_PRIO 9 +#define CTRL_TASK_PRIO 10 +#define ERR_DELAY_US 800 //Approximate time for arbitration phase at 25KBPS +#define ERR_PERIOD_US 80 //Approximate time for two bits at 25KBPS +#define EXAMPLE_TAG "CAN Alert and Recovery" + +static const can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL(); +static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS(); +static const can_general_config_t g_config = CAN_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, CAN_MODE_NO_ACK); +static const can_message_t tx_msg = {.identifier = 0, .data_length_code = 0, .flags = CAN_MSG_FLAG_NONE}; + +static SemaphoreHandle_t tx_task_sem; +static SemaphoreHandle_t ctrl_task_sem; +static bool trigger_tx_error = false; + +/* --------------------------- Tasks and Functions -------------------------- */ + +static void invert_tx_bits(bool enable) +{ + if (enable) { + //Inverts output of TX to trigger errors + gpio_matrix_out(TX_GPIO_NUM, CAN_TX_IDX, true, false); + } else { + //Returns TX to default settings + gpio_matrix_out(TX_GPIO_NUM, CAN_TX_IDX, false, false); + } +} + +static void tx_task(void *arg) +{ + xSemaphoreTake(tx_task_sem, portMAX_DELAY); + while (1) { + if (can_transmit(&tx_msg, 0) == ESP_ERR_INVALID_STATE) { + break; //Exit TX task when bus-off state is reached + } + if (trigger_tx_error) { + //Trigger a bit error in transmission by inverting GPIO + ets_delay_us(ERR_DELAY_US); //Wait until arbitration phase is over + invert_tx_bits(true); //Trigger bit error for a few bits + ets_delay_us(ERR_PERIOD_US); + invert_tx_bits(false); + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + vTaskDelete(NULL); +} + +static void ctrl_task(void *arg) +{ + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + ESP_ERROR_CHECK(can_start()); + ESP_LOGI(EXAMPLE_TAG, "Driver started"); + ESP_LOGI(EXAMPLE_TAG, "Starting transmissions"); + xSemaphoreGive(tx_task_sem); //Start transmit task + + //Prepare to trigger errors, reconfigure alerts to detect change in error state + can_reconfigure_alerts(CAN_ALERT_ABOVE_ERR_WARN | CAN_ALERT_ERR_PASS | CAN_ALERT_BUS_OFF, NULL); + for (int i = 3; i > 0; i--) { + ESP_LOGW(EXAMPLE_TAG, "Trigger TX errors in %d", i); + vTaskDelay(pdMS_TO_TICKS(1000)); + } + ESP_LOGI(EXAMPLE_TAG, "Trigger errors"); + trigger_tx_error = true; + + while (1) { + uint32_t alerts; + can_read_alerts(&alerts, portMAX_DELAY); + if (alerts & CAN_ALERT_ABOVE_ERR_WARN) { + ESP_LOGI(EXAMPLE_TAG, "Surpassed Error Warning Limit"); + } + if (alerts & CAN_ALERT_ERR_PASS) { + ESP_LOGI(EXAMPLE_TAG, "Entered Error Passive state"); + } + if (alerts & CAN_ALERT_BUS_OFF) { + ESP_LOGI(EXAMPLE_TAG, "Bus Off state"); + //Prepare to initiate bus recovery, reconfigure alerts to detect bus recovery completion + can_reconfigure_alerts(CAN_ALERT_BUS_RECOVERED, NULL); + for (int i = 3; i > 0; i--) { + ESP_LOGW(EXAMPLE_TAG, "Initiate bus recovery in %d", i); + vTaskDelay(pdMS_TO_TICKS(1000)); + } + can_initiate_recovery(); //Needs 128 occurrences of bus free signal + ESP_LOGI(EXAMPLE_TAG, "Initiate bus recovery"); + } + if (alerts & CAN_ALERT_BUS_RECOVERED) { + //Bus recovery was successful, exit control task to uninstall driver + ESP_LOGI(EXAMPLE_TAG, "Bus Recovered"); + break; + } + } + //No need call can_stop(), bus recovery will return to stopped state + xSemaphoreGive(ctrl_task_sem); + vTaskDelete(NULL); +} + +void app_main() +{ + tx_task_sem = xSemaphoreCreateBinary(); + ctrl_task_sem = xSemaphoreCreateBinary(); + + xTaskCreatePinnedToCore(tx_task, "CAN_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY); + xTaskCreatePinnedToCore(ctrl_task, "CAN_ctrl", 4096, NULL, CTRL_TASK_PRIO, NULL, tskNO_AFFINITY); + + //Install CAN driver + ESP_ERROR_CHECK(can_driver_install(&g_config, &t_config, & f_config)); + ESP_LOGI(EXAMPLE_TAG, "Driver installed"); + + xSemaphoreGive(ctrl_task_sem); //Start control task + vTaskDelay(pdMS_TO_TICKS(100)); + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); //Wait for completion + + //Uninstall CAN driver + ESP_ERROR_CHECK(can_driver_uninstall()); + ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled"); + + //Cleanup + vSemaphoreDelete(tx_task_sem); + vSemaphoreDelete(ctrl_task_sem); +} diff --git a/examples/peripherals/can/can_alert_and_recovery/main/component.mk b/examples/peripherals/can/can_alert_and_recovery/main/component.mk new file mode 100644 index 000000000..b4fa72791 --- /dev/null +++ b/examples/peripherals/can/can_alert_and_recovery/main/component.mk @@ -0,0 +1,4 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/peripherals/can/can_network/README.md b/examples/peripherals/can/can_network/README.md new file mode 100644 index 000000000..71fbd6427 --- /dev/null +++ b/examples/peripherals/can/can_network/README.md @@ -0,0 +1,67 @@ +# CAN Network Example + +## Overview +The CAN Network Example demonstrates communication between two ESP32 modules (master +and slave) using the CAN2.0B protocol. CAN is a multi-master protocol, therefore +the concept of master/slave in this example refers to which node initiates +and stops the transfer of a stream of data messages. The example also includes +an optional **Listen Only module** which can passively receive the CAN messages +sent between the master and slave module without participating in any CAN bus activity. + +The CAN Network Example will execute the following steps over multiple iterations: + +1. Both master and slave go through initialization process +2. The master repeatedly sends **PING** messages until it receives a **PING_RESP** +from the slave. The slave will only send a **PING_RESP** message when it receives +a **PING** message from the master. +3. Once the master has received the **PING_RESP** from the slave, it will send a +**START_CMD** message to the slave. +4. Upon receiving the **START_CMD** message, the slave will start transmitting +**DATA** messages until the master sends a **STOP_CMD**. The master will send +the **STOP_CMD** after receiving N **DATA** messages from the slave (N = 50 by +default). +5. When the slave receives the **STOP_CMD**, it will confirm that it has stopped +by sending a **STOP_RESP** message to the master. + +## External Transceiver and Pin Assignment +The CAN controller in the ESP32 **does not contain an internal transceiver**. +Therefore users are responsible for providing an external transceiver compatible +with the physical layer specifications of their target ISO standard (such as +SN65HVD23X transceivers for ISO 11898-2 compatibility) + +The CAN controller in the ESP32 represents dominant bits to the transceiver as +logic low, and recessive bits as logic high. The Network Example utilizes the +following default pin assignments + +* TX Pin is routed to GPIO21 +* RX Pin is routed to GPIO22 + +The following diagram illustrates an example network + +~~~~ + ---------- ---------- -------------- + | Master | | Slave | | Listen Only | + | | | | | | + | 21 22 | | 21 22 | | 21 22 | + ---------- ---------- -------------- + | | | | | | + | | | | | | + ---------- ---------- ---------- + | D R | | D R | | D R | + | | | | | | + | VP230 | | VP230 | | VP230 | + | | | | | | + | H L | | H L | | H L | + ---------- ---------- ---------- + | | | | | | + | | | | | | + |--x------|-----x------|-----x------|--| H + | | | + |---------x------------x------------x--| L + +~~~~ + +## Note +If there appears to be no activity on the CAN bus when running the example, users +can try running the `can_self_test` example to verify if their transceivers are +wired properly. \ No newline at end of file diff --git a/examples/peripherals/can/can_network/can_network_listen_only/Makefile b/examples/peripherals/can/can_network/can_network_listen_only/Makefile new file mode 100644 index 000000000..94d598041 --- /dev/null +++ b/examples/peripherals/can/can_network/can_network_listen_only/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := can_network_listen_only + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/can/can_network/can_network_listen_only/main/can_network_example_listen_only_main.c b/examples/peripherals/can/can_network/can_network_listen_only/main/can_network_example_listen_only_main.c new file mode 100644 index 000000000..5309bbd0b --- /dev/null +++ b/examples/peripherals/can/can_network/can_network_listen_only/main/can_network_example_listen_only_main.c @@ -0,0 +1,123 @@ +/* CAN Network Listen Only Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* + * The following example demonstrates a Listen Only node in a CAN network. The + * Listen Only node will not take part in any CAN bus activity (no acknowledgments + * and no error frames). This example will execute multiple iterations, with each + * iteration the Listen Only node will do the following: + * 1) Listen for ping and ping response + * 2) Listen for start command + * 3) Listen for data messages + * 4) Listen for stop and stop response + */ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "driver/can.h" + +/* --------------------- Definitions and static variables ------------------ */ +//Example Configuration +#define NO_OF_ITERS 3 +#define RX_TASK_PRIO 9 +#define TX_GPIO_NUM 21 +#define RX_GPIO_NUM 22 +#define EXAMPLE_TAG "CAN Listen Only" + +#define ID_MASTER_STOP_CMD 0x0A0 +#define ID_MASTER_START_CMD 0x0A1 +#define ID_MASTER_PING 0x0A2 +#define ID_SLAVE_STOP_RESP 0x0B0 +#define ID_SLAVE_DATA 0x0B1 +#define ID_SLAVE_PING_RESP 0x0B2 + +static const can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL(); +static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS(); +//Set TX queue length to 0 due to listen only mode +static const can_general_config_t g_config = {.mode = CAN_MODE_LISTEN_ONLY, + .tx_io = TX_GPIO_NUM, .rx_io = RX_GPIO_NUM, + .clkout_io = CAN_IO_UNUSED, .bus_off_io = CAN_IO_UNUSED, + .tx_queue_len = 0, .rx_queue_len = 5, + .alerts_enabled = CAN_ALERT_NONE, + .clkout_divider = 0}; + +static SemaphoreHandle_t rx_sem; + +/* --------------------------- Tasks and Functions -------------------------- */ + +static void can_receive_task(void *arg) +{ + xSemaphoreTake(rx_sem, portMAX_DELAY); + bool start_cmd = false; + bool stop_resp = false; + uint32_t iterations = 0; + + while (iterations < NO_OF_ITERS) { + can_message_t rx_msg; + can_receive(&rx_msg, portMAX_DELAY); + if (rx_msg.identifier == ID_MASTER_PING) { + ESP_LOGI(EXAMPLE_TAG, "Received master ping"); + } else if (rx_msg.identifier == ID_SLAVE_PING_RESP) { + ESP_LOGI(EXAMPLE_TAG, "Received slave ping response"); + } else if (rx_msg.identifier == ID_MASTER_START_CMD) { + ESP_LOGI(EXAMPLE_TAG, "Received master start command"); + start_cmd = true; + } else if (rx_msg.identifier == ID_SLAVE_DATA) { + uint32_t data = 0; + for (int i = 0; i < rx_msg.data_length_code; i++) { + data |= (rx_msg.data[i] << (i * 8)); + } + ESP_LOGI(EXAMPLE_TAG, "Received data value %d", data); + } else if (rx_msg.identifier == ID_MASTER_STOP_CMD) { + ESP_LOGI(EXAMPLE_TAG, "Received master stop command"); + } else if (rx_msg.identifier == ID_SLAVE_STOP_RESP) { + ESP_LOGI(EXAMPLE_TAG, "Received slave stop response"); + stop_resp = true; + } + if (start_cmd && stop_resp) { + //Each iteration is complete after a start command and stop response is received + iterations++; + start_cmd = 0; + stop_resp = 0; + } + } + + xSemaphoreGive(rx_sem); + vTaskDelete(NULL); +} + +void app_main() +{ + rx_sem = xSemaphoreCreateBinary(); + xTaskCreatePinnedToCore(can_receive_task, "CAN_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY); + + //Install and start CAN driver + ESP_ERROR_CHECK(can_driver_install(&g_config, &t_config, &f_config)); + ESP_LOGI(EXAMPLE_TAG, "Driver installed"); + ESP_ERROR_CHECK(can_start()); + ESP_LOGI(EXAMPLE_TAG, "Driver started"); + + xSemaphoreGive(rx_sem); //Start RX task + vTaskDelay(pdMS_TO_TICKS(100)); + xSemaphoreTake(rx_sem, portMAX_DELAY); //Wait for RX task to complete + + //Stop and uninstall CAN driver + ESP_ERROR_CHECK(can_stop()); + ESP_LOGI(EXAMPLE_TAG, "Driver stopped"); + ESP_ERROR_CHECK(can_driver_uninstall()); + ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled"); + + //Cleanup + vSemaphoreDelete(rx_sem); +} diff --git a/examples/peripherals/can/can_network/can_network_listen_only/main/component.mk b/examples/peripherals/can/can_network/can_network_listen_only/main/component.mk new file mode 100644 index 000000000..b4fa72791 --- /dev/null +++ b/examples/peripherals/can/can_network/can_network_listen_only/main/component.mk @@ -0,0 +1,4 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/peripherals/can/can_network/can_network_master/Makefile b/examples/peripherals/can/can_network/can_network_master/Makefile new file mode 100644 index 000000000..23fbfb5da --- /dev/null +++ b/examples/peripherals/can/can_network/can_network_master/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := can_network_master + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/can/can_network/can_network_master/main/can_network_example_master_main.c b/examples/peripherals/can/can_network/can_network_master/main/can_network_example_master_main.c new file mode 100644 index 000000000..6e98a3403 --- /dev/null +++ b/examples/peripherals/can/can_network/can_network_master/main/can_network_example_master_main.c @@ -0,0 +1,235 @@ +/* CAN Network Master Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* + * The following example demonstrates a master node in a CAN network. The master + * node is responsible for initiating and stopping the transfer of data messages. + * The example will execute multiple iterations, with each iteration the master + * node will do the following: + * 1) Start the CAN driver + * 2) Repeatedly send ping messages until a ping response from slave is received + * 3) Send start command to slave and receive data messages from slave + * 4) Send stop command to slave and wait for stop response from slave + * 5) Stop the CAN driver + */ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "driver/can.h" + +/* --------------------- Definitions and static variables ------------------ */ +//Example Configuration +#define PING_PERIOD_MS 250 +#define NO_OF_DATA_MSGS 50 +#define NO_OF_ITERS 3 +#define ITER_DELAY_MS 1000 +#define RX_TASK_PRIO 8 +#define TX_TASK_PRIO 9 +#define CTRL_TSK_PRIO 10 +#define TX_GPIO_NUM 21 +#define RX_GPIO_NUM 22 +#define EXAMPLE_TAG "CAN Master" + +#define ID_MASTER_STOP_CMD 0x0A0 +#define ID_MASTER_START_CMD 0x0A1 +#define ID_MASTER_PING 0x0A2 +#define ID_SLAVE_STOP_RESP 0x0B0 +#define ID_SLAVE_DATA 0x0B1 +#define ID_SLAVE_PING_RESP 0x0B2 + +typedef enum { + TX_SEND_PINGS, + TX_SEND_START_CMD, + TX_SEND_STOP_CMD, + TX_TASK_EXIT, +} tx_task_action_t; + +typedef enum { + RX_RECEIVE_PING_RESP, + RX_RECEIVE_DATA, + RX_RECEIVE_STOP_RESP, + RX_TASK_EXIT, +} rx_task_action_t; + +static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS(); +static const can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL(); +static const can_general_config_t g_config = CAN_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, CAN_MODE_NORMAL); + +static const can_message_t ping_message = {.identifier = ID_MASTER_PING, .data_length_code = 0, + .flags = CAN_MSG_FLAG_SS, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}}; +static const can_message_t start_message = {.identifier = ID_MASTER_START_CMD, .data_length_code = 0, + .flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}}; +static const can_message_t stop_message = {.identifier = ID_MASTER_STOP_CMD, .data_length_code = 0, + .flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}}; + +static QueueHandle_t tx_task_queue; +static QueueHandle_t rx_task_queue; +static SemaphoreHandle_t stop_ping_sem; +static SemaphoreHandle_t ctrl_task_sem; +static SemaphoreHandle_t done_sem; + +/* --------------------------- Tasks and Functions -------------------------- */ + +static void can_receive_task(void *arg) +{ + while (1) { + rx_task_action_t action; + xQueueReceive(rx_task_queue, &action, portMAX_DELAY); + + if (action == RX_RECEIVE_PING_RESP) { + //Listen for ping response from slave + while (1) { + can_message_t rx_msg; + can_receive(&rx_msg, portMAX_DELAY); + if (rx_msg.identifier == ID_SLAVE_PING_RESP) { + xSemaphoreGive(stop_ping_sem); + xSemaphoreGive(ctrl_task_sem); + break; + } + } + } else if (action == RX_RECEIVE_DATA) { + //Receive data messages from slave + uint32_t data_msgs_rec = 0; + while (data_msgs_rec < NO_OF_DATA_MSGS) { + can_message_t rx_msg; + can_receive(&rx_msg, portMAX_DELAY); + if (rx_msg.identifier == ID_SLAVE_DATA) { + uint32_t data = 0; + for (int i = 0; i < rx_msg.data_length_code; i++) { + data |= (rx_msg.data[i] << (i * 8)); + } + ESP_LOGI(EXAMPLE_TAG, "Received data value %d", data); + data_msgs_rec ++; + } + } + xSemaphoreGive(ctrl_task_sem); + } else if (action == RX_RECEIVE_STOP_RESP) { + //Listen for stop response from slave + while (1) { + can_message_t rx_msg; + can_receive(&rx_msg, portMAX_DELAY); + if (rx_msg.identifier == ID_SLAVE_STOP_RESP) { + xSemaphoreGive(ctrl_task_sem); + break; + } + } + } else if (action == RX_TASK_EXIT) { + break; + } + } + vTaskDelete(NULL); +} + +static void can_transmit_task(void *arg) { + while (1) { + tx_task_action_t action; + xQueueReceive(tx_task_queue, &action, portMAX_DELAY); + + if (action == TX_SEND_PINGS) { + //Repeatedly transmit pings + ESP_LOGI(EXAMPLE_TAG, "Transmitting ping"); + while (xSemaphoreTake(stop_ping_sem, 0) != pdTRUE) { + can_transmit(&ping_message, portMAX_DELAY); + vTaskDelay(pdMS_TO_TICKS(PING_PERIOD_MS)); + } + } else if (action == TX_SEND_START_CMD) { + //Transmit start command to slave + can_transmit(&start_message, portMAX_DELAY); + ESP_LOGI(EXAMPLE_TAG, "Transmitted start command"); + } else if (action == TX_SEND_STOP_CMD) { + //Transmit stop command to slave + can_transmit(&stop_message, portMAX_DELAY); + ESP_LOGI(EXAMPLE_TAG, "Transmitted stop command"); + } else if (action == TX_TASK_EXIT) { + break; + } + } + vTaskDelete(NULL); +} + +void can_control_task(void *arg) { + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + tx_task_action_t tx_action; + rx_task_action_t rx_action; + + for (int iter = 0; iter < NO_OF_ITERS; iter++) { + ESP_ERROR_CHECK(can_start()); + ESP_LOGI(EXAMPLE_TAG, "Driver started"); + + //Start transmitting pings, and listen for ping response + tx_action = TX_SEND_PINGS; + rx_action = RX_RECEIVE_PING_RESP; + xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY); + xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY); + + //Send Start command to slave, and receive data messages + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + tx_action = TX_SEND_START_CMD; + rx_action = RX_RECEIVE_DATA; + xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY); + xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY); + + //Send Stop command to slave when enough data messages have been received. Wait for stop response + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + tx_action = TX_SEND_STOP_CMD; + rx_action = RX_RECEIVE_STOP_RESP; + xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY); + xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY); + + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + ESP_ERROR_CHECK(can_stop()); + ESP_LOGI(EXAMPLE_TAG, "Driver stopped"); + vTaskDelay(pdMS_TO_TICKS(ITER_DELAY_MS)); + } + //Stop TX and RX tasks + tx_action = TX_TASK_EXIT; + rx_action = RX_TASK_EXIT; + xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY); + xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY); + + //Delete Control task + xSemaphoreGive(done_sem); + vTaskDelete(NULL); +} + +void app_main() +{ + //Create tasks, queues, and semaphores + rx_task_queue = xQueueCreate(1, sizeof(rx_task_action_t)); + tx_task_queue = xQueueCreate(1, sizeof(tx_task_action_t)); + ctrl_task_sem = xSemaphoreCreateBinary(); + stop_ping_sem = xSemaphoreCreateBinary(); + done_sem = xSemaphoreCreateBinary(); + xTaskCreatePinnedToCore(can_receive_task, "CAN_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY); + xTaskCreatePinnedToCore(can_transmit_task, "CAN_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY); + xTaskCreatePinnedToCore(can_control_task, "CAN_ctrl", 4096, NULL, CTRL_TSK_PRIO, NULL, tskNO_AFFINITY); + + //Install CAN driver + ESP_ERROR_CHECK(can_driver_install(&g_config, &t_config, &f_config)); + ESP_LOGI(EXAMPLE_TAG, "Driver installed"); + + xSemaphoreGive(ctrl_task_sem); //Start control task + xSemaphoreTake(done_sem, portMAX_DELAY); //Wait for completion + + //Uninstall CAN driver + ESP_ERROR_CHECK(can_driver_uninstall()); + ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled"); + + //Cleanup + vQueueDelete(rx_task_queue); + vQueueDelete(tx_task_queue); + vSemaphoreDelete(ctrl_task_sem); + vSemaphoreDelete(stop_ping_sem); + vSemaphoreDelete(done_sem); +} diff --git a/examples/peripherals/can/can_network/can_network_master/main/component.mk b/examples/peripherals/can/can_network/can_network_master/main/component.mk new file mode 100644 index 000000000..b4fa72791 --- /dev/null +++ b/examples/peripherals/can/can_network/can_network_master/main/component.mk @@ -0,0 +1,4 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/peripherals/can/can_network/can_network_slave/Makefile b/examples/peripherals/can/can_network/can_network_slave/Makefile new file mode 100644 index 000000000..2ee82b21d --- /dev/null +++ b/examples/peripherals/can/can_network/can_network_slave/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := can_network_slave + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/can/can_network/can_network_slave/main/can_network_example_slave_main.c b/examples/peripherals/can/can_network/can_network_slave/main/can_network_example_slave_main.c new file mode 100644 index 000000000..7c4a793bd --- /dev/null +++ b/examples/peripherals/can/can_network/can_network_slave/main/can_network_example_slave_main.c @@ -0,0 +1,264 @@ +/* CAN Network Slave Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* + * The following example demonstrates a slave node in a CAN network. The slave + * node is responsible for sending data messages to the master. The example will + * execute multiple iterations, with each iteration the slave node will do the + * following: + * 1) Start the CAN driver + * 2) Listen for ping messages from master, and send ping response + * 3) Listen for start command from master + * 4) Send data messages to master and listen for stop command + * 5) Send stop response to master + * 6) Stop the CAN driver + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "driver/can.h" + +/* --------------------- Definitions and static variables ------------------ */ +//Example Configuration +#define DATA_PERIOD_MS 50 +#define NO_OF_ITERS 3 +#define ITER_DELAY_MS 1000 +#define RX_TASK_PRIO 8 //Receiving task priority +#define TX_TASK_PRIO 9 //Sending task priority +#define CTRL_TSK_PRIO 10 //Control task priority +#define TX_GPIO_NUM 21 +#define RX_GPIO_NUM 22 +#define EXAMPLE_TAG "CAN Slave" + +#define ID_MASTER_STOP_CMD 0x0A0 +#define ID_MASTER_START_CMD 0x0A1 +#define ID_MASTER_PING 0x0A2 +#define ID_SLAVE_STOP_RESP 0x0B0 +#define ID_SLAVE_DATA 0x0B1 +#define ID_SLAVE_PING_RESP 0x0B2 + +typedef enum { + TX_SEND_PING_RESP, + TX_SEND_DATA, + TX_SEND_STOP_RESP, + TX_TASK_EXIT, +} tx_task_action_t; + +typedef enum { + RX_RECEIVE_PING, + RX_RECEIVE_START_CMD, + RX_RECEIVE_STOP_CMD, + RX_TASK_EXIT, +} rx_task_action_t; + +static const can_general_config_t g_config = CAN_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, CAN_MODE_NORMAL); +static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS(); +static const can_filter_config_t f_config = CAN_FILTER_CONFIG_ACCEPT_ALL(); +static const can_message_t ping_resp = {.identifier = ID_SLAVE_PING_RESP, .data_length_code = 0, + .flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}}; +static const can_message_t stop_resp = {.identifier = ID_SLAVE_STOP_RESP, .data_length_code = 0, + .flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}}; +//Data bytes of data message will be initialized in the transmit task +static can_message_t data_message = {.identifier = ID_SLAVE_DATA, .data_length_code = 4, + .flags = CAN_MSG_FLAG_NONE, .data = {0, 0 , 0 , 0 ,0 ,0 ,0 ,0}}; + +static QueueHandle_t tx_task_queue; +static QueueHandle_t rx_task_queue; +static SemaphoreHandle_t ctrl_task_sem; +static SemaphoreHandle_t stop_data_sem; +static SemaphoreHandle_t done_sem; + +/* --------------------------- Tasks and Functions -------------------------- */ + +static void can_receive_task(void *arg) +{ + while (1) { + rx_task_action_t action; + xQueueReceive(rx_task_queue, &action, portMAX_DELAY); + if (action == RX_RECEIVE_PING) { + //Listen for pings from master + can_message_t rx_msg; + while (1) { + can_receive(&rx_msg, portMAX_DELAY); + if (rx_msg.identifier == ID_MASTER_PING) { + xSemaphoreGive(ctrl_task_sem); + break; + } + } + } else if (action == RX_RECEIVE_START_CMD) { + //Listen for start command from master + can_message_t rx_msg; + while (1) { + can_receive(&rx_msg, portMAX_DELAY); + if (rx_msg.identifier == ID_MASTER_START_CMD) { + xSemaphoreGive(ctrl_task_sem); + break; + } + } + } else if (action == RX_RECEIVE_STOP_CMD) { + //Listen for stop command from master + can_message_t rx_msg; + while (1) { + can_receive(&rx_msg, portMAX_DELAY); + if (rx_msg.identifier == ID_MASTER_STOP_CMD) { + xSemaphoreGive(stop_data_sem); + xSemaphoreGive(ctrl_task_sem); + break; + } + } + } else if (action == RX_TASK_EXIT) { + break; + } + } + vTaskDelete(NULL); +} + +static void can_transmit_task(void *arg) +{ + while (1) { + tx_task_action_t action; + xQueueReceive(tx_task_queue, &action, portMAX_DELAY); + + if (action == TX_SEND_PING_RESP) { + //Transmit ping response to master + can_transmit(&ping_resp, portMAX_DELAY); + ESP_LOGI(EXAMPLE_TAG, "Transmitted ping response"); + xSemaphoreGive(ctrl_task_sem); + } else if (action == TX_SEND_DATA) { + //Transmit data messages until stop command is received + ESP_LOGI(EXAMPLE_TAG, "Start transmitting data"); + while (1) { + //FreeRTOS tick count used to simulate sensor data + uint32_t sensor_data = xTaskGetTickCount(); + for (int i = 0; i < 4; i++) { + data_message.data[i] = (sensor_data >> (i * 8)) & 0xFF; + } + can_transmit(&data_message, portMAX_DELAY); + ESP_LOGI(EXAMPLE_TAG, "Transmitted data value %d", sensor_data); + vTaskDelay(pdMS_TO_TICKS(DATA_PERIOD_MS)); + if (xSemaphoreTake(stop_data_sem, 0) == pdTRUE) { + break; + } + } + } else if (action == TX_SEND_STOP_RESP) { + //Transmit stop response to master + can_transmit(&stop_resp, portMAX_DELAY); + ESP_LOGI(EXAMPLE_TAG, "Transmitted stop response"); + xSemaphoreGive(ctrl_task_sem); + } else if (action == TX_TASK_EXIT) { + break; + } + } + vTaskDelete(NULL); +} + +static void can_control_task(void *arg) +{ + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + tx_task_action_t tx_action; + rx_task_action_t rx_action; + + for (int iter = 0; iter < NO_OF_ITERS; iter++) { + ESP_ERROR_CHECK(can_start()); + ESP_LOGI(EXAMPLE_TAG, "Driver started"); + + //Listen of pings from master + rx_action = RX_RECEIVE_PING; + xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY); + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + + //Send ping response + tx_action = TX_SEND_PING_RESP; + xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY); + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + + //Listen for start command + rx_action = RX_RECEIVE_START_CMD; + xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY); + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + + //Start sending data messages and listen for stop command + tx_action = TX_SEND_DATA; + rx_action = RX_RECEIVE_STOP_CMD; + xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY); + xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY); + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + + //Send stop response + tx_action = TX_SEND_STOP_RESP; + xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY); + xSemaphoreTake(ctrl_task_sem, portMAX_DELAY); + + //Wait for bus to become free + can_status_info_t status_info; + can_get_status_info(&status_info); + while (status_info.msgs_to_tx > 0) { + vTaskDelay(pdMS_TO_TICKS(100)); + can_get_status_info(&status_info); + } + + ESP_ERROR_CHECK(can_stop()); + ESP_LOGI(EXAMPLE_TAG, "Driver stopped"); + vTaskDelay(pdMS_TO_TICKS(ITER_DELAY_MS)); + } + + //Stop TX and RX tasks + tx_action = TX_TASK_EXIT; + rx_action = RX_TASK_EXIT; + xQueueSend(tx_task_queue, &tx_action, portMAX_DELAY); + xQueueSend(rx_task_queue, &rx_action, portMAX_DELAY); + + //Delete Control task + xSemaphoreGive(done_sem); + vTaskDelete(NULL); +} + +void app_main() +{ + //Add short delay to allow master it to initialize first + for (int i = 3; i > 0; i--) { + printf("Slave starting in %d\n", i); + vTaskDelay(pdMS_TO_TICKS(1000)); + } + + + //Create semaphores and tasks + tx_task_queue = xQueueCreate(1, sizeof(tx_task_action_t)); + rx_task_queue = xQueueCreate(1, sizeof(rx_task_action_t)); + ctrl_task_sem = xSemaphoreCreateBinary(); + stop_data_sem = xSemaphoreCreateBinary();; + done_sem = xSemaphoreCreateBinary();; + xTaskCreatePinnedToCore(can_receive_task, "CAN_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY); + xTaskCreatePinnedToCore(can_transmit_task, "CAN_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY); + xTaskCreatePinnedToCore(can_control_task, "CAN_ctrl", 4096, NULL, CTRL_TSK_PRIO, NULL, tskNO_AFFINITY); + + //Install CAN driver, trigger tasks to start + ESP_ERROR_CHECK(can_driver_install(&g_config, &t_config, &f_config)); + ESP_LOGI(EXAMPLE_TAG, "Driver installed"); + + xSemaphoreGive(ctrl_task_sem); //Start Control task + xSemaphoreTake(done_sem, portMAX_DELAY); //Wait for tasks to complete + + //Uninstall CAN driver + ESP_ERROR_CHECK(can_driver_uninstall()); + ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled"); + + //Cleanup + vSemaphoreDelete(ctrl_task_sem); + vSemaphoreDelete(stop_data_sem); + vSemaphoreDelete(done_sem); + vQueueDelete(tx_task_queue); + vQueueDelete(rx_task_queue); +} diff --git a/examples/peripherals/can/can_network/can_network_slave/main/component.mk b/examples/peripherals/can/can_network/can_network_slave/main/component.mk new file mode 100644 index 000000000..b4fa72791 --- /dev/null +++ b/examples/peripherals/can/can_network/can_network_slave/main/component.mk @@ -0,0 +1,4 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/peripherals/can/can_network/example_test.py b/examples/peripherals/can/can_network/example_test.py new file mode 100644 index 000000000..517ec5fe6 --- /dev/null +++ b/examples/peripherals/can/can_network/example_test.py @@ -0,0 +1,77 @@ +#Need Python 3 string formatting functions +from __future__ import print_function + +import re +import os +import sys +import time +from threading import Thread +# The test cause is dependent on the Tiny Test Framework. Ensure the +# `TEST_FW_PATH` environment variable is set to `$IDF_PATH/tools/tiny-test-fw` +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) +import TinyFW +import IDF + +#Define tuple of strings to expect for each DUT. +master_expect = ("CAN Master: Driver installed", "CAN Master: Driver uninstalled") +slave_expect = ("CAN Slave: Driver installed", "CAN Slave: Driver uninstalled") +listen_only_expect = ("CAN Listen Only: Driver installed", "Listen Only: Driver uninstalled") + +def dut_thread_callback(**kwargs): + #Parse keyword arguments + dut = kwargs['dut'] #Get DUT from kwargs + expected = kwargs['expected'] + result = kwargs['result'] #Get result[out] from kwargs. MUST be of mutable type e.g. list + + #Must reset again as flashing during start_app will reset multiple times, causing unexpected results + dut.reset() + + for string in expected: + dut.expect(string, 20) + + #Mark thread has run to completion without any exceptions + result[0] = True + +@IDF.idf_example_test(env_tag='Example_CAN') +def test_can_network_example(env, extra_data): + + #Get device under test. "dut1", "dut2", and "dut3" must be properly defined in EnvConfig + dut_master = env.get_dut("dut1", "examples/peripherals/can/can_network/can_network_master") + dut_slave = env.get_dut("dut2", "examples/peripherals/can/can_network/can_network_slave") + dut_listen_only = env.get_dut("dut3", "examples/peripherals/can/can_network/can_network_listen_only") + + #Flash app onto each DUT, each DUT is reset again at the start of each thread + dut_master.start_app() + dut_slave.start_app() + dut_listen_only.start_app() + + #Create dict of keyword arguments for each dut + results = [[False], [False], [False]] + master_kwargs = {"dut" : dut_master, "result" : results[0], "expected" : master_expect} + slave_kwargs = {"dut" : dut_slave, "result" : results[1], "expected" : slave_expect} + listen_only_kwargs = {"dut" : dut_listen_only, "result" : results[2], "expected" : listen_only_expect} + + #Create thread for each dut + dut_master_thread = Thread(target = dut_thread_callback, name = "Master Thread", kwargs = master_kwargs) + dut_slave_thread = Thread(target = dut_thread_callback, name = "Slave Thread", kwargs = slave_kwargs) + dut_listen_only_thread = Thread(target = dut_thread_callback, name = "Listen Only Thread", kwargs = listen_only_kwargs) + + #Start each thread + dut_listen_only_thread.start() + dut_master_thread.start() + dut_slave_thread.start() + + #Wait for threads to complete + dut_listen_only_thread.join() + dut_master_thread.join() + dut_slave_thread.join() + + #check each thread ran to completion + for result in results: + if result[0] != True: + raise Exception("One or more threads did not run successfully") + +if __name__ == '__main__': + test_can_network_example() diff --git a/examples/peripherals/can/can_self_test/Makefile b/examples/peripherals/can/can_self_test/Makefile new file mode 100644 index 000000000..dfde37859 --- /dev/null +++ b/examples/peripherals/can/can_self_test/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := can_self_test_example + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/peripherals/can/can_self_test/README.md b/examples/peripherals/can/can_self_test/README.md new file mode 100644 index 000000000..13e5e52e5 --- /dev/null +++ b/examples/peripherals/can/can_self_test/README.md @@ -0,0 +1,33 @@ +# CAN Self Test Example + +## Overview +The CAN Self Test Example demonstrates the self testing capabilities of the +ESP32 CAN peripheral and **only requires a single ESP32 module to run**. +The Self Test Example can be used to verify that the wiring between the ESP32 +and an external transceiver operates correctly. + +The CAN Self Test Example will do the following over multiple iterations: + +1. Start the CAN driver +2. Simultaneously transmit and receive messages using the self reception request. +3. Stop the CAN driver + +## External Transceiver and Pin Assignment +The CAN controller in the ESP32 **does not contain an internal transceiver**. +Therefore users are responsible for providing an external transceiver compatible +with the physical layer specifications of their target ISO standard (such as +SN65HVD23X transceivers for ISO 11898-2 compatibility) + +The CAN controller in the ESP32 represents dominant bits to the transceiver as +logic low, and recessive bits as logic high. The Self Test Example utilizes the +following default pin assignments + +* TX Pin is routed to GPIO21 +* RX Pin is routed to GPIO22 + +## Note +If the Self Test Example does not receive any messages, it is likely that the +wiring between the ESP32 and the external transceiver is incorrect. To verify +that the CAN controller in the ESP32 is operating correctly, users can bypass +the external transceiver by connecting the TX Pin directly to the RX Pin when +running the Self Test Example. diff --git a/examples/peripherals/can/can_self_test/example_test.py b/examples/peripherals/can/can_self_test/example_test.py new file mode 100644 index 000000000..370cdd951 --- /dev/null +++ b/examples/peripherals/can/can_self_test/example_test.py @@ -0,0 +1,29 @@ +#Need Python 3 string formatting functions +from __future__ import print_function + +import re +import os +import sys +# The test cause is dependent on the Tiny Test Framework. Ensure the +# `TEST_FW_PATH` environment variable is set to `$IDF_PATH/tools/tiny-test-fw` +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) +import TinyFW +import IDF + +# CAN Self Test Example constants +STR_EXPECT = ("CAN Self Test: Driver installed", "CAN Self Test: Driver uninstalled") +EXPECT_TIMEOUT = 20 + +@IDF.idf_example_test(env_tag='Example_CAN') +def test_can_self_test_example(env, extra_data): + #Get device under test, flash and start example. "dut4" must be defined in EnvConfig + dut = env.get_dut('dut4', 'examples/peripherals/can/can_self_test') + dut.start_app() + + for string in STR_EXPECT: + dut.expect(string, timeout = EXPECT_TIMEOUT) + +if __name__ == '__main__': + test_can_self_test_example() diff --git a/examples/peripherals/can/can_self_test/main/can_self_test_example_main.c b/examples/peripherals/can/can_self_test/main/can_self_test_example_main.c new file mode 100644 index 000000000..34af55943 --- /dev/null +++ b/examples/peripherals/can/can_self_test/main/can_self_test_example_main.c @@ -0,0 +1,141 @@ +/* CAN Self Test Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ + +/* + * The following example demonstrates the self testing capabilities of the CAN + * peripheral by utilizing the No Acknowledgment Mode and Self Reception Request + * capabilities. This example can be used to verify that the CAN peripheral and + * its connections to the external transceiver operates without issue. The example + * will execute multiple iterations, each iteration will do the following: + * 1) Start the CAN driver + * 2) Transmit and receive 100 messages using self reception request + * 3) Stop the CAN driver + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include "esp_log.h" +#include "driver/can.h" + +/* --------------------- Definitions and static variables ------------------ */ + +//Example Configurations +#define NO_OF_MSGS 100 +#define NO_OF_ITERS 3 +#define TX_GPIO_NUM 21 +#define RX_GPIO_NUM 22 +#define TX_TASK_PRIO 8 //Sending task priority +#define RX_TASK_PRIO 9 //Receiving task priority +#define CTRL_TSK_PRIO 10 //Control task priority +#define MSG_ID 0x555 //11 bit standard format ID +#define EXAMPLE_TAG "CAN Self Test" + +static const can_timing_config_t t_config = CAN_TIMING_CONFIG_25KBITS(); +//Filter all other IDs except MSG_ID +static const can_filter_config_t f_config = {.acceptance_code = (MSG_ID << 21), + .acceptance_mask = ~(CAN_STD_ID_MASK << 21), + .single_filter = true}; +//Set to NO_ACK mode due to self testing with single module +static const can_general_config_t g_config = CAN_GENERAL_CONFIG_DEFAULT(TX_GPIO_NUM, RX_GPIO_NUM, CAN_MODE_NO_ACK); + +static SemaphoreHandle_t tx_sem; +static SemaphoreHandle_t rx_sem; +static SemaphoreHandle_t ctrl_sem; +static SemaphoreHandle_t done_sem; + +/* --------------------------- Tasks and Functions -------------------------- */ + +static void can_transmit_task(void *arg) +{ + can_message_t tx_msg = {.data_length_code = 1, .identifier = MSG_ID, .flags = CAN_MSG_FLAG_SELF}; + for (int iter = 0; iter < NO_OF_ITERS; iter++) { + xSemaphoreTake(tx_sem, portMAX_DELAY); + for (int i = 0; i < NO_OF_MSGS; i++) { + //Transmit messages using self reception request + tx_msg.data[0] = i; + ESP_ERROR_CHECK(can_transmit(&tx_msg, portMAX_DELAY)); + vTaskDelay(pdMS_TO_TICKS(10)); + } + } + vTaskDelete(NULL); +} + +static void can_receive_task(void *arg) +{ + can_message_t rx_message; + for (int iter = 0; iter < NO_OF_ITERS; iter++) { + xSemaphoreTake(rx_sem, portMAX_DELAY); + for (int i = 0; i < NO_OF_MSGS; i++) { + //Receive message and print message data + ESP_ERROR_CHECK(can_receive(&rx_message, portMAX_DELAY)) + ESP_LOGI(EXAMPLE_TAG, "Msg received - Data = %d", rx_message.data[0]); + } + //Indicate to control task all messages received for this iteration + xSemaphoreGive(ctrl_sem); + } + vTaskDelete(NULL); +} + +static void can_control_task(void *arg) +{ + xSemaphoreTake(ctrl_sem, portMAX_DELAY); + for (int iter = 0; iter < NO_OF_ITERS; iter++) { + //Start CAN Driver for this iteration + ESP_ERROR_CHECK(can_start()); + ESP_LOGI(EXAMPLE_TAG, "Driver started"); + + //Trigger TX and RX tasks to start transmitting/receiving + xSemaphoreGive(rx_sem); + xSemaphoreGive(tx_sem); + xSemaphoreTake(ctrl_sem, portMAX_DELAY); //Wait for TX and RX tasks to finish iteration + + ESP_ERROR_CHECK(can_stop()); //Stop the CAN Driver + ESP_LOGI(EXAMPLE_TAG, "Driver stopped"); + vTaskDelay(pdMS_TO_TICKS(100)); //Delay then start next iteration + } + xSemaphoreGive(done_sem); + vTaskDelete(NULL); +} + +void app_main() +{ + //Create tasks and synchronization primitives + tx_sem = xSemaphoreCreateBinary(); + rx_sem = xSemaphoreCreateBinary(); + ctrl_sem = xSemaphoreCreateBinary(); + done_sem = xSemaphoreCreateBinary(); + + xTaskCreatePinnedToCore(can_control_task, "CAN_ctrl", 4096, NULL, CTRL_TSK_PRIO, NULL, tskNO_AFFINITY); + xTaskCreatePinnedToCore(can_receive_task, "CAN_rx", 4096, NULL, RX_TASK_PRIO, NULL, tskNO_AFFINITY); + xTaskCreatePinnedToCore(can_transmit_task, "CAN_tx", 4096, NULL, TX_TASK_PRIO, NULL, tskNO_AFFINITY); + + //Install CAN driver + ESP_ERROR_CHECK(can_driver_install(&g_config, & t_config, &f_config)); + ESP_LOGI(EXAMPLE_TAG, "Driver installed"); + + //Start control task + xSemaphoreGive(ctrl_sem); + //Wait for all iterations and tasks to complete running + xSemaphoreTake(done_sem, portMAX_DELAY); + + //Uninstall CAN driver + ESP_ERROR_CHECK(can_driver_uninstall()); + ESP_LOGI(EXAMPLE_TAG, "Driver uninstalled"); + + //Cleanup + vSemaphoreDelete(tx_sem); + vSemaphoreDelete(rx_sem); + vSemaphoreDelete(ctrl_sem); + vQueueDelete(done_sem); +} + diff --git a/examples/peripherals/can/can_self_test/main/component.mk b/examples/peripherals/can/can_self_test/main/component.mk new file mode 100644 index 000000000..b4fa72791 --- /dev/null +++ b/examples/peripherals/can/can_self_test/main/component.mk @@ -0,0 +1,4 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)