Merge branch 'feature/CAN_driver' into 'master'
CAN driver, examples, and docs See merge request idf/esp-idf!2098
This commit is contained in:
commit
c445da47de
36 changed files with 3546 additions and 0 deletions
|
@ -639,6 +639,12 @@ example_test_003_01:
|
||||||
- ESP32
|
- ESP32
|
||||||
- Example_SDIO
|
- Example_SDIO
|
||||||
|
|
||||||
|
example_test_004_01:
|
||||||
|
<<: *example_test_template
|
||||||
|
tags:
|
||||||
|
- ESP32
|
||||||
|
- Example_CAN
|
||||||
|
|
||||||
UT_001_01:
|
UT_001_01:
|
||||||
<<: *unit_test_template
|
<<: *unit_test_template
|
||||||
tags:
|
tags:
|
||||||
|
|
937
components/driver/can.c
Normal file
937
components/driver/can.c
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
|
398
components/driver/include/driver/can.h
Normal file
398
components/driver/include/driver/can.h
Normal file
|
@ -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_*/
|
||||||
|
|
|
@ -26,6 +26,7 @@ PROVIDE ( SPI3 = 0x3ff65000 );
|
||||||
PROVIDE ( SYSCON = 0x3ff66000 );
|
PROVIDE ( SYSCON = 0x3ff66000 );
|
||||||
PROVIDE ( I2C1 = 0x3ff67000 );
|
PROVIDE ( I2C1 = 0x3ff67000 );
|
||||||
PROVIDE ( SDMMC = 0x3ff68000 );
|
PROVIDE ( SDMMC = 0x3ff68000 );
|
||||||
|
PROVIDE ( CAN = 0x3ff6B000 );
|
||||||
PROVIDE ( MCPWM1 = 0x3ff6C000 );
|
PROVIDE ( MCPWM1 = 0x3ff6C000 );
|
||||||
PROVIDE ( I2S1 = 0x3ff6D000 );
|
PROVIDE ( I2S1 = 0x3ff6D000 );
|
||||||
PROVIDE ( UART2 = 0x3ff6E000 );
|
PROVIDE ( UART2 = 0x3ff6E000 );
|
||||||
|
|
211
components/soc/esp32/include/soc/can_struct.h
Normal file
211
components/soc/esp32/include/soc/can_struct.h
Normal file
|
@ -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_ */
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
#define DR_REG_I2C1_EXT_BASE 0x3ff67000
|
#define DR_REG_I2C1_EXT_BASE 0x3ff67000
|
||||||
#define DR_REG_SDMMC_BASE 0x3ff68000
|
#define DR_REG_SDMMC_BASE 0x3ff68000
|
||||||
#define DR_REG_EMAC_BASE 0x3ff69000
|
#define DR_REG_EMAC_BASE 0x3ff69000
|
||||||
|
#define DR_REG_CAN_BASE 0x3ff6B000
|
||||||
#define DR_REG_PWM1_BASE 0x3ff6C000
|
#define DR_REG_PWM1_BASE 0x3ff6C000
|
||||||
#define DR_REG_I2S1_BASE 0x3ff6D000
|
#define DR_REG_I2S1_BASE 0x3ff6D000
|
||||||
#define DR_REG_UART2_BASE 0x3ff6E000
|
#define DR_REG_UART2_BASE 0x3ff6E000
|
||||||
|
|
|
@ -64,6 +64,7 @@ INPUT = \
|
||||||
## Peripherals - API Reference
|
## Peripherals - API Reference
|
||||||
##
|
##
|
||||||
../../components/driver/include/driver/adc.h \
|
../../components/driver/include/driver/adc.h \
|
||||||
|
../../components/driver/include/driver/can.h \
|
||||||
../../components/driver/include/driver/dac.h \
|
../../components/driver/include/driver/dac.h \
|
||||||
../../components/driver/include/driver/gpio.h \
|
../../components/driver/include/driver/gpio.h \
|
||||||
../../components/driver/include/driver/rtc_io.h \
|
../../components/driver/include/driver/rtc_io.h \
|
||||||
|
|
19
docs/_static/diagrams/can/can_acceptance_filter_dual.diag
vendored
Normal file
19
docs/_static/diagrams/can/can_acceptance_filter_dual.diag
vendored
Normal file
|
@ -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];
|
||||||
|
|
||||||
|
}
|
21
docs/_static/diagrams/can/can_acceptance_filter_single.diag
vendored
Normal file
21
docs/_static/diagrams/can/can_acceptance_filter_single.diag
vendored
Normal file
|
@ -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];
|
||||||
|
|
||||||
|
}
|
12
docs/_static/diagrams/can/can_bit_timing.diag
vendored
Normal file
12
docs/_static/diagrams/can/can_bit_timing.diag
vendored
Normal file
|
@ -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];
|
||||||
|
}
|
57
docs/_static/diagrams/can/can_controller_signals.diag
vendored
Normal file
57
docs/_static/diagrams/can/can_controller_signals.diag
vendored
Normal file
|
@ -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"];
|
||||||
|
}
|
31
docs/_static/diagrams/can/can_state_transition.diag
vendored
Normal file
31
docs/_static/diagrams/can/can_state_transition.diag
vendored
Normal file
|
@ -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]
|
||||||
|
}
|
||||||
|
|
604
docs/en/api-reference/peripherals/can.rst
Normal file
604
docs/en/api-reference/peripherals/can.rst
Normal file
|
@ -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
|
|
@ -5,6 +5,7 @@ Peripherals API
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|
||||||
ADC <adc>
|
ADC <adc>
|
||||||
|
CAN <can>
|
||||||
DAC <dac>
|
DAC <dac>
|
||||||
GPIO (including RTC low power I/O) <gpio>
|
GPIO (including RTC low power I/O) <gpio>
|
||||||
I2C <i2c>
|
I2C <i2c>
|
||||||
|
|
1
docs/zh_CN/api-reference/peripherals/can.rst
Normal file
1
docs/zh_CN/api-reference/peripherals/can.rst
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.. include:: ../../../en/api-reference/peripherals/can.rst
|
9
examples/peripherals/can/can_alert_and_recovery/Makefile
Normal file
9
examples/peripherals/can/can_alert_and_recovery/Makefile
Normal file
|
@ -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
|
||||||
|
|
29
examples/peripherals/can/can_alert_and_recovery/README.md
Normal file
29
examples/peripherals/can/can_alert_and_recovery/README.md
Normal file
|
@ -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
|
|
@ -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()
|
|
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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);
|
||||||
|
}
|
|
@ -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.)
|
67
examples/peripherals/can/can_network/README.md
Normal file
67
examples/peripherals/can/can_network/README.md
Normal file
|
@ -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.
|
|
@ -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
|
||||||
|
|
|
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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);
|
||||||
|
}
|
|
@ -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.)
|
|
@ -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
|
||||||
|
|
|
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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);
|
||||||
|
}
|
|
@ -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.)
|
|
@ -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
|
||||||
|
|
|
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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);
|
||||||
|
}
|
|
@ -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.)
|
77
examples/peripherals/can/can_network/example_test.py
Normal file
77
examples/peripherals/can/can_network/example_test.py
Normal file
|
@ -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()
|
9
examples/peripherals/can/can_self_test/Makefile
Normal file
9
examples/peripherals/can/can_self_test/Makefile
Normal file
|
@ -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
|
||||||
|
|
33
examples/peripherals/can/can_self_test/README.md
Normal file
33
examples/peripherals/can/can_self_test/README.md
Normal file
|
@ -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.
|
29
examples/peripherals/can/can_self_test/example_test.py
Normal file
29
examples/peripherals/can/can_self_test/example_test.py
Normal file
|
@ -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()
|
|
@ -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 <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
4
examples/peripherals/can/can_self_test/main/component.mk
Normal file
4
examples/peripherals/can/can_self_test/main/component.mk
Normal file
|
@ -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.)
|
Loading…
Reference in a new issue