From 68b04b13bccafb8c1be7232ff7f438c68fca1fdb Mon Sep 17 00:00:00 2001 From: Ihor Nehrutsa Date: Tue, 29 Aug 2023 17:22:27 +0300 Subject: [PATCH 1/3] port/esp32: Add CAN(TWAI) class. Signed-off-by: IhorNehrutsa --- docs/esp32/img/twai_blockdiag.png | Bin 0 -> 10157 bytes docs/esp32/quickref.rst | 24 + examples/esp32_can.py | 73 +++ ports/esp32/machine_can.c | 805 ++++++++++++++++++++++++++++++ ports/esp32/machine_can.h | 78 +++ 5 files changed, 980 insertions(+) create mode 100644 docs/esp32/img/twai_blockdiag.png create mode 100644 examples/esp32_can.py create mode 100644 ports/esp32/machine_can.c create mode 100644 ports/esp32/machine_can.h diff --git a/docs/esp32/img/twai_blockdiag.png b/docs/esp32/img/twai_blockdiag.png new file mode 100644 index 0000000000000000000000000000000000000000..183352ea610de1aa53b149730db94cf068661609 GIT binary patch literal 10157 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 :: + + The CAN driver is based on hardware implementation. + Any available output-capablepins can be used for TX, RX, BUS-OFF, and CLKOUT signal lines. + +.. image:: img/twai_blockdiag.png + +The driver is accessed via the :ref:`machine.CAN ` class:: + + from machine import CAN + can = CAN(0, tx=5, rx=4, mode=CAN.NORMAL, baudrate=500000) + can.setfilter(0, CAN.FILTER_ADDRESS, [0x102]) # set a filter to receive messages with id = 0x102 + can.send([1,2,3], 0x102) # send a message with id 123 + can.recv() # receive message + + can.any() # returns True if there are any message to receive + can.info() # get information about the controller’s error states and TX and RX buffers + can.deinit() # turn off the can bus + can.clear_rx_queue() # clear messages in the FIFO + can.clear_tx_queue() # clear messages in the transmit buffer + Real time clock (RTC) --------------------- diff --git a/examples/esp32_can.py b/examples/esp32_can.py new file mode 100644 index 0000000000000..c7f29635621ba --- /dev/null +++ b/examples/esp32_can.py @@ -0,0 +1,73 @@ +from machine import CAN +import time + + +def send_and_check(can_bus, name, id, expected_result=True, extended=False): + can_bus.clear_tx_queue() + can_bus.clear_rx_queue() + can_bus.send([], id, extframe=extended) + time.sleep_ms(100) + if can_bus.any() == expected_result: + print("{}: OK".format(name)) + if expected_result: + can_bus.recv() + else: + print("{}: FAILED".format(name)) + + +# 4 and 5 pins must be connected to each other, see documentation +dev = CAN( + 0, extframe=False, tx=5, rx=4, mode=CAN.SILENT_LOOPBACK, baudrate=50000, auto_restart=False +) + +# Test send/receive message +print("Loopback Test: no filter - STD") +send_and_check(dev, "No filter", 0x100, True) + +# Set filter1 +print("Loopback Test: one filter - STD") +dev.setfilter(0, CAN.FILTER_ADDRESS, [0x101, 0]) +send_and_check(dev, "Passing Message", 0x101, True) +send_and_check(dev, "Blocked Message", 0x100, False) + +# Set filter2 +print("Loopback Test: second filter - STD") +dev.setfilter(0, CAN.FILTER_ADDRESS, [0x102, 0]) +send_and_check(dev, "Passing Message - Bank 1", 0x102, True) +send_and_check(dev, "Passing Message - Bank 0", 0x101, True) +send_and_check(dev, "Blocked Message", 0x100, False) + +# Remove filter +print("Loopback Test: clear filter - STD") +dev.clearfilter() +send_and_check(dev, "Passing Message - Bank 1", 0x102, True) +send_and_check(dev, "Passing Message - Bank 0", 0x101, True) +send_and_check(dev, "Passing any Message", 0x100, True) + +# Extended message tests +# Move to Extended +dev = CAN( + 0, + extframe=True, + mode=CAN.SILENT_LOOPBACK, + baudrate=CAN.BAUDRATE_500k, + tx_io=18, + rx_io=19, + auto_restart=False, +) + +# Test send/receive message +print("Loopback Test: no filter - Extd") +send_and_check(dev, "No filter", 0x100, True, extended=True) + +# Set filter1 +print("Loopback Test: one filter - Extd") +dev.setfilter(0, CAN.FILTER_ADDRESS, [0x101, 0], extframe=True) +send_and_check(dev, "Passing Message", 0x101, True, extended=True) +send_and_check(dev, "Blocked Message", 0x100, False, extended=True) + +# Remove filter +print("Loopback Test: clear filter - Extd") +dev.clearfilter() +send_and_check(dev, "Passing Message - Bank 0", 0x101, True, extended=True) +send_and_check(dev, "Passing any Message", 0x100, True, extended=True) diff --git a/ports/esp32/machine_can.c b/ports/esp32/machine_can.c new file mode 100644 index 0000000000000..ca95cad3587b3 --- /dev/null +++ b/ports/esp32/machine_can.c @@ -0,0 +1,805 @@ +/* The MIT License (MIT) + * + * Copyright (c) 2019 Musumeci Salvatore + * Copyright (c) 2021 Ihor Nehrutsa + * Copyright (c) 2022 Yuriy Makarov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include + +#include "py/obj.h" +#include "py/objarray.h" +#include "py/binary.h" +#include "py/runtime.h" +#include "py/builtin.h" +#include "py/mphal.h" +#include "py/mperrno.h" +#include "mpconfigport.h" +#include "freertos/task.h" +#include "esp_idf_version.h" + +#include "soc/dport_reg.h" +#include "esp_err.h" +#include "esp_log.h" + +#include "driver/twai.h" +#include "esp_task.h" +#include "machine_can.h" + +#if MICROPY_HW_ENABLE_CAN + +#define CAN_MODE_SILENT_LOOPBACK (0x10) + +// Default baudrate: 500kb +#define CAN_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) +#define CAN_TASK_STACK_SIZE (1024) +#define CAN_DEFAULT_PRESCALER (8) +#define CAN_DEFAULT_SJW (3) +#define CAN_DEFAULT_BS1 (15) +#define CAN_DEFAULT_BS2 (4) +#define CAN_MAX_DATA_FRAME (8) + +/* +// Internal Functions +mp_obj_t esp32_hw_can_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args); +static mp_obj_t esp32_hw_can_init_helper(esp32_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); +static void esp32_hw_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); +*/ +// INTERNAL Deinitialize can +void can_deinit(const esp32_can_obj_t *self) { + check_esp_err(twai_stop()); + check_esp_err(twai_driver_uninstall()); + if (self->irq_handler != NULL) { + vTaskDelete(self->irq_handler); + } + self->config->initialized = false; +} + +// static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS(); +static const twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + +// singleton CAN device object +esp32_can_config_t can_config = { + .general = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_2, GPIO_NUM_4, TWAI_MODE_NORMAL), + .filter = f_config, // TWAI_FILTER_CONFIG_ACCEPT_ALL(), + .timing = TWAI_TIMING_CONFIG_25KBITS(), + .initialized = false +}; + +static esp32_can_obj_t esp32_can_obj = { + {&machine_can_type}, + .config = &can_config +}; + +// INTERNAL FUNCTION Return status information +static twai_status_info_t _esp32_hw_can_get_status() { + twai_status_info_t status; + check_esp_err(twai_get_status_info(&status)); + return status; +} + +static void esp32_hw_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->config->initialized) { + qstr mode; + switch (self->config->general.mode) { + case TWAI_MODE_LISTEN_ONLY: + mode = MP_QSTR_LISTEN; + break; + case TWAI_MODE_NO_ACK: + mode = MP_QSTR_NO_ACK; + break; + case TWAI_MODE_NORMAL: + mode = MP_QSTR_NORMAL; + break; + default: + mode = MP_QSTR_UNKNOWN; + break; + } + mp_printf(print, "CAN(tx=%u, rx=%u, baudrate=%ukb, mode=%q, loopback=%u, extframe=%u)", + self->config->general.tx_io, + self->config->general.rx_io, + self->config->baudrate, + mode, + self->loopback, + self->extframe); + } else { + mp_printf(print, "Device is not initialized"); + } +} + +// INTERNAL FUNCTION FreeRTOS IRQ task +static void esp32_hw_can_irq_task(void *self_in) { + esp32_can_obj_t *self = (esp32_can_obj_t *)self_in; + uint32_t alerts; + + twai_reconfigure_alerts( + TWAI_ALERT_RX_DATA | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_BUS_OFF | TWAI_ALERT_ERR_PASS | + TWAI_ALERT_ABOVE_ERR_WARN | TWAI_ALERT_TX_FAILED | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_BUS_RECOVERED, + NULL + ); + + while (1) { + check_esp_err(twai_read_alerts(&alerts, portMAX_DELAY)); + + if (alerts & TWAI_ALERT_BUS_OFF) { + ++self->num_bus_off; + } + if (alerts & TWAI_ALERT_ERR_PASS) { + ++self->num_error_passive; + } + if (alerts & TWAI_ALERT_ABOVE_ERR_WARN) { + ++self->num_error_warning; + } + + if (alerts & (TWAI_ALERT_TX_FAILED | TWAI_ALERT_TX_SUCCESS)) { + self->last_tx_success = (alerts & TWAI_ALERT_TX_SUCCESS) > 0; + } + + if (alerts & (TWAI_ALERT_BUS_RECOVERED)) { + self->bus_recovery_success = true; + } + + if (self->rxcallback != mp_const_none) { + if (alerts & TWAI_ALERT_RX_DATA) { + uint32_t msgs_to_rx = _esp32_hw_can_get_status().msgs_to_rx; + + if (msgs_to_rx == 1) { + // first message in queue + mp_sched_schedule(self->rxcallback, MP_OBJ_NEW_SMALL_INT(0)); + } else if (msgs_to_rx >= self->config->general.rx_queue_len) { + // queue is full + mp_sched_schedule(self->rxcallback, MP_OBJ_NEW_SMALL_INT(1)); + } + } + if (alerts & TWAI_ALERT_RX_QUEUE_FULL) { + // queue overflow + mp_sched_schedule(self->rxcallback, MP_OBJ_NEW_SMALL_INT(2)); + } + } + } +} + +// init(mode, tx=5, rx=4, baudrate=500000, prescaler=8, sjw=3, bs1=15, bs2=4, auto_restart=False, tx_queue=1, rx_queue=1) +static mp_obj_t esp32_hw_can_init_helper(esp32_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode, ARG_prescaler, ARG_sjw, ARG_bs1, ARG_bs2, ARG_auto_restart, ARG_baudrate, ARG_extframe, + ARG_tx_io, ARG_rx_io, ARG_tx_queue, ARG_rx_queue}; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = TWAI_MODE_NORMAL} }, + { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_prescaler, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_PRESCALER} }, + { MP_QSTR_sjw, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_SJW} }, + { MP_QSTR_bs1, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_BS1} }, + { MP_QSTR_bs2, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_BS2} }, + { MP_QSTR_auto_restart, MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 500000} }, + { MP_QSTR_tx, MP_ARG_INT, {.u_int = 4} }, + { MP_QSTR_rx, MP_ARG_INT, {.u_int = 5} }, + { MP_QSTR_tx_queue, MP_ARG_INT, {.u_int = 1} }, + { MP_QSTR_rx_queue, MP_ARG_INT, {.u_int = 1} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // Configure device + self->config->general.mode = args[ARG_mode].u_int & 0x0F; + self->config->general.tx_io = args[ARG_tx_io].u_int; + self->config->general.rx_io = args[ARG_rx_io].u_int; + self->config->general.clkout_io = TWAI_IO_UNUSED; + self->config->general.bus_off_io = TWAI_IO_UNUSED; + self->config->general.tx_queue_len = args[ARG_tx_queue].u_int; + self->config->general.rx_queue_len = args[ARG_rx_queue].u_int; + self->config->general.alerts_enabled = TWAI_ALERT_AND_LOG || TWAI_ALERT_BELOW_ERR_WARN || TWAI_ALERT_ERR_ACTIVE || TWAI_ALERT_BUS_RECOVERED || + TWAI_ALERT_ABOVE_ERR_WARN || TWAI_ALERT_BUS_ERROR || TWAI_ALERT_ERR_PASS || TWAI_ALERT_BUS_OFF; + self->config->general.clkout_divider = 0; + self->loopback = ((args[ARG_mode].u_int & CAN_MODE_SILENT_LOOPBACK) > 0); + self->extframe = args[ARG_extframe].u_bool; + if (args[ARG_auto_restart].u_bool) { + mp_raise_NotImplementedError("Auto-restart not supported"); + } + self->config->filter = f_config; // TWAI_FILTER_CONFIG_ACCEPT_ALL(); + + // clear errors + self->num_error_warning = 0; + self->num_error_passive = 0; + self->num_bus_off = 0; + + // Calculate CAN nominal bit timing from baudrate if provided + twai_timing_config_t *timing; + switch ((int)args[ARG_baudrate].u_int) { + case 0: + timing = &((twai_timing_config_t) { + .brp = args[ARG_prescaler].u_int, + .sjw = args[ARG_sjw].u_int, + .tseg_1 = args[ARG_bs1].u_int, + .tseg_2 = args[ARG_bs2].u_int, + .triple_sampling = false + }); + break; + #ifdef TWAI_TIMING_CONFIG_1KBITS + case 1000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_1KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_5KBITS + case 5000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_5KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_10KBITS + case 10000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_10KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_12_5KBITS + case 12500: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_12_5KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_16KBITS + case 16000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_16KBITS()); + break; + #endif + #ifdef TWAI_TIMING_CONFIG_20KBITS + case 20000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_20KBITS()); + break; + #endif + case 25000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_25KBITS()); + break; + case 50000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_50KBITS()); + break; + case 100000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_100KBITS()); + break; + case 125000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_125KBITS()); + break; + case 250000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_250KBITS()); + break; + case 500000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_500KBITS()); + break; + case 800000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_800KBITS()); + break; + case 1000000: + timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_1MBITS()); + break; + default: + mp_raise_ValueError("Unable to set baudrate"); + self->config->baudrate = 0; + return mp_const_none; + } + self->config->timing = *timing; + + check_esp_err(twai_driver_install(&self->config->general, &self->config->timing, &self->config->filter)); + check_esp_err(twai_start()); + if (xTaskCreatePinnedToCore(esp32_hw_can_irq_task, "can_irq_task", CAN_TASK_STACK_SIZE, self, CAN_TASK_PRIORITY, (TaskHandle_t *)&self->irq_handler, MP_TASK_COREID) != pdPASS) { + mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create can irq task handler")); + } + self->config->initialized = true; + + return mp_const_none; +} + +// CAN(bus, ...) No argument to get the object +// If no arguments are provided, the initialized object will be returned +static mp_obj_t esp32_hw_can_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // check arguments + mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + if (mp_obj_is_int(args[0]) != true) { + mp_raise_TypeError("bus must be a number"); + } + + // work out port + mp_uint_t can_idx = mp_obj_get_int(args[0]); + if (can_idx != 0) { + mp_raise_msg_varg(&mp_type_ValueError, "CAN(%d) doesn't exist", can_idx); + } + + esp32_can_obj_t *self = &esp32_can_obj; + if (!self->config->initialized || n_args > 1 || n_kw > 0) { + if (self->config->initialized) { + // The caller is requesting a reconfiguration of the hardware + // this can only be done if the hardware is in init mode + can_deinit(self); + } + self->rxcallback = mp_const_none; + self->irq_handler = NULL; + self->rx_state = RX_STATE_FIFO_EMPTY; + + if (n_args > 1 || n_kw > 0) { + // start the peripheral + mp_map_t kw_args; + mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); + esp32_hw_can_init_helper(self, n_args - 1, args + 1, &kw_args); + } + } + return MP_OBJ_FROM_PTR(self); +} + +// init(tx, rx, baudrate, mode=NORMAL, tx_queue=2, rx_queue=5) +static mp_obj_t esp32_hw_can_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); + if (self->config->initialized) { + mp_raise_msg(&mp_type_RuntimeError, "Device is already initialized"); + return mp_const_none; + } + + return esp32_hw_can_init_helper(self, n_args - 1, args + 1, kw_args); +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_init_obj, 4, esp32_hw_can_init); + +// deinit() +static mp_obj_t esp32_hw_can_deinit(const mp_obj_t self_in) { + const esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (self->config->initialized != true) { + mp_raise_msg(&mp_type_RuntimeError, "Device is not initialized"); + return mp_const_none; + } + can_deinit(self); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_deinit_obj, esp32_hw_can_deinit); + +// Force a software restart of the controller, to allow transmission after a bus error +static mp_obj_t esp32_hw_can_restart(mp_obj_t self_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + twai_status_info_t status = _esp32_hw_can_get_status(); + if (!self->config->initialized || status.state != TWAI_STATE_BUS_OFF) { + mp_raise_ValueError(NULL); + } + + self->bus_recovery_success = -1; + check_esp_err(twai_initiate_recovery()); + mp_hal_delay_ms(200); // FIXME: replace it with a smarter solution + + while (self->bus_recovery_success < 0) { + MICROPY_EVENT_POLL_HOOK + } + + if (self->bus_recovery_success) { + check_esp_err(twai_start()); + } else { + mp_raise_OSError(MP_EIO); + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_restart_obj, esp32_hw_can_restart); + +// Get the state of the controller +static mp_obj_t esp32_hw_can_state(mp_obj_t self_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_int_t state = TWAI_STATE_STOPPED; + if (self->config->initialized) { + state = _esp32_hw_can_get_status().state; + } + return mp_obj_new_int(state); +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_state_obj, esp32_hw_can_state); + +// info() -- Get info about error states and TX/RX buffers +static mp_obj_t esp32_hw_can_info(size_t n_args, const mp_obj_t *args) { +/* + esp32_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); + mp_obj_list_t *list; + if (n_args == 1) { + list = MP_OBJ_TO_PTR(mp_obj_new_list(8, NULL)); + } else { + if (!mp_obj_is_type(args[1], &mp_type_list)) { + mp_raise_TypeError(NULL); + } + list = MP_OBJ_TO_PTR(args[1]); + if (list->len < 8) { + mp_raise_ValueError(NULL); + } + } + twai_status_info_t status = _esp32_hw_can_get_status(); + list->items[0] = MP_OBJ_NEW_SMALL_INT(status.tx_error_counter); + list->items[1] = MP_OBJ_NEW_SMALL_INT(status.rx_error_counter); + list->items[2] = MP_OBJ_NEW_SMALL_INT(self->num_error_warning); + list->items[3] = MP_OBJ_NEW_SMALL_INT(self->num_error_passive); + list->items[4] = MP_OBJ_NEW_SMALL_INT(self->num_bus_off); + list->items[5] = MP_OBJ_NEW_SMALL_INT(status.msgs_to_tx); + list->items[6] = MP_OBJ_NEW_SMALL_INT(status.msgs_to_rx); + list->items[7] = mp_const_none; + return MP_OBJ_FROM_PTR(list); +*/ + twai_status_info_t status = _esp32_hw_can_get_status(); + mp_obj_t dict = mp_obj_new_dict(0); + #define dict_key(key) mp_obj_new_str(#key, strlen(#key)) + #define dict_value(key) MP_OBJ_NEW_SMALL_INT(status.key) + #define dict_store(key) mp_obj_dict_store(dict, dict_key(key), dict_value(key)); + dict_store(state); + dict_store(msgs_to_tx); + dict_store(msgs_to_rx); + dict_store(tx_error_counter); + dict_store(rx_error_counter); + dict_store(tx_failed_count); + dict_store(rx_missed_count); + dict_store(arb_lost_count); + dict_store(bus_error_count); + return dict; +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_hw_can_info_obj, 1, 2, esp32_hw_can_info); + +// Get Alert info +static mp_obj_t esp32_hw_can_alert(mp_obj_t self_in) { + uint32_t alerts; + check_esp_err(twai_read_alerts(&alerts, 0)); + return mp_obj_new_int(alerts); +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_alert_obj, esp32_hw_can_alert); + +// any() - return `True` if any message waiting, else `False` +static mp_obj_t esp32_hw_can_any(mp_obj_t self_in) { + twai_status_info_t status = _esp32_hw_can_get_status(); + return mp_obj_new_bool((status.msgs_to_rx) > 0); +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_any_obj, esp32_hw_can_any); + +// send([data], id, *, timeout=0, rtr=false, extframe=false) +static mp_obj_t esp32_hw_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_data, ARG_id, ARG_timeout, ARG_rtr, ARG_extframe }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_rtr, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, + }; + + // parse args + esp32_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // populate message + twai_message_t tx_msg; + + size_t length; + mp_obj_t *items; + mp_obj_get_array(args[ARG_data].u_obj, &length, &items); + if (length > CAN_MAX_DATA_FRAME) { + mp_raise_ValueError("CAN data field too long"); + } + tx_msg.data_length_code = length; + tx_msg.flags = (args[ARG_rtr].u_bool ? TWAI_MSG_FLAG_RTR : TWAI_MSG_FLAG_NONE); + + if (args[ARG_extframe].u_bool) { + tx_msg.identifier = args[ARG_id].u_int & 0x1FFFFFFF; + tx_msg.flags += TWAI_MSG_FLAG_EXTD; + } else { + tx_msg.identifier = args[ARG_id].u_int & 0x7FF; + } + if (self->loopback) { + tx_msg.flags += TWAI_MSG_FLAG_SELF; + } + + for (uint8_t i = 0; i < length; i++) { + tx_msg.data[i] = mp_obj_get_int(items[i]); + } + + if (_esp32_hw_can_get_status().state == TWAI_STATE_RUNNING) { + uint32_t timeout_ms = args[ARG_timeout].u_int; + + if (timeout_ms != 0) { + self->last_tx_success = -1; + uint32_t start = mp_hal_ticks_us(); + check_esp_err(twai_transmit(&tx_msg, pdMS_TO_TICKS(timeout_ms))); + while (self->last_tx_success < 0) { + if (timeout_ms != portMAX_DELAY) { + if (mp_hal_ticks_us() - start >= timeout_ms) { + mp_raise_OSError(MP_ETIMEDOUT); + } + } + MICROPY_EVENT_POLL_HOOK + } + + if (!self->last_tx_success) { + mp_raise_OSError(MP_EIO); + } + } else { + check_esp_err(twai_transmit(&tx_msg, portMAX_DELAY)); + } + + return mp_const_none; + } else { + mp_raise_msg(&mp_type_RuntimeError, "Device is not ready"); + } +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_send_obj, 3, esp32_hw_can_send); + +// recv(list=None, *, timeout=5000) +static mp_obj_t esp32_hw_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_list, ARG_timeout }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_list, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 5000} }, + }; + + // parse args + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + // receive the data + twai_message_t rx_msg; + check_esp_err(twai_receive(&rx_msg, pdMS_TO_TICKS(args[ARG_timeout].u_int))); + uint32_t rx_dlc = rx_msg.data_length_code; + + // Create the tuple, or get the list, that will hold the return values + // Also populate the fourth element, either a new bytes or reuse existing memoryview + mp_obj_t ret_obj = args[ARG_list].u_obj; + mp_obj_t *items; + if (ret_obj == mp_const_none) { + ret_obj = mp_obj_new_tuple(4, NULL); + items = ((mp_obj_tuple_t *)MP_OBJ_TO_PTR(ret_obj))->items; + items[3] = mp_obj_new_bytes(rx_msg.data, rx_dlc); + } else { + // User should provide a list of length at least 4 to hold the values + if (!mp_obj_is_type(ret_obj, &mp_type_list)) { + mp_raise_TypeError(NULL); + } + mp_obj_list_t *list = MP_OBJ_TO_PTR(ret_obj); + if (list->len < 4) { + mp_raise_ValueError(NULL); + } + items = list->items; + // Fourth element must be a memoryview which we assume points to a + // byte-like array which is large enough, and then we resize it inplace + if (!mp_obj_is_type(items[3], &mp_type_memoryview)) { + mp_raise_TypeError(NULL); + } + mp_obj_array_t *mv = MP_OBJ_TO_PTR(items[3]); + if (!(mv->typecode == (MP_OBJ_ARRAY_TYPECODE_FLAG_RW | BYTEARRAY_TYPECODE) || (mv->typecode | 0x20) == (MP_OBJ_ARRAY_TYPECODE_FLAG_RW | 'b'))) { + mp_raise_ValueError(NULL); + } + mv->len = rx_dlc; + memcpy(mv->items, rx_msg.data, rx_dlc); + } + items[0] = MP_OBJ_NEW_SMALL_INT(rx_msg.identifier); + items[1] = rx_msg.extd ? mp_const_true : mp_const_false; + items[2] = rx_msg.rtr ? mp_const_true : mp_const_false; + + // Return the result + return ret_obj; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_recv_obj, 0, esp32_hw_can_recv); + +// Clear filters setting +static mp_obj_t esp32_hw_can_clearfilter(mp_obj_t self_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Defaults from TWAI_FILTER_CONFIG_ACCEPT_ALL + self->config->filter = f_config; // TWAI_FILTER_CONFIG_ACCEPT_ALL(); + + // Apply filter + check_esp_err(twai_stop()); + check_esp_err(twai_driver_uninstall()); + check_esp_err(twai_driver_install( + &self->config->general, + &self->config->timing, + &self->config->filter)); + check_esp_err(twai_start()); + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_clearfilter_obj, esp32_hw_can_clearfilter); + +// bank: 0 only +// mode: FILTER_RAW_SINGLE, FILTER_RAW_DUAL or FILTER_ADDR_SINGLE or FILTER_ADDR_DUAL +// params: [id, mask] +// rtr: ignored if FILTER_RAW +// Set CAN HW filter +static mp_obj_t esp32_hw_can_setfilter(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_bank, ARG_mode, ARG_params, ARG_rtr, ARG_extframe }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_bank, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, + { MP_QSTR_params, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_rtr, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_bool = false} }, + { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, + }; + + // parse args + esp32_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + const int can_idx = args[ARG_bank].u_int; + + if (can_idx != 0) { + mp_raise_msg_varg(&mp_type_ValueError, "Bank (%d) doesn't exist", can_idx); + } + + size_t len; + mp_obj_t *params; + mp_obj_get_array(args[ARG_params].u_obj, &len, ¶ms); + const int mode = args[ARG_mode].u_int; + + uint32_t id = mp_obj_get_int(params[0]); + uint32_t mask = mp_obj_get_int(params[1]); // FIXME: Overflow in case 0xFFFFFFFF for mask + if (mode == FILTER_RAW_SINGLE || mode == FILTER_RAW_DUAL) { + if (len != 2) { + mp_raise_ValueError("params must be a 2-values list"); + } + self->config->filter.single_filter = (mode == FILTER_RAW_SINGLE); + self->config->filter.acceptance_code = id; + self->config->filter.acceptance_mask = mask; + } else { + self->config->filter.single_filter = self->extframe; + // esp32_hw_can_setfilter(self, id, mask, args[ARG_bank].u_int, args[ARG_rtr].u_int); + // Check if bank is allowed + int bank = 0; + if (bank > ((self->extframe && self->config->filter.single_filter) ? 0 : 1)) { + mp_raise_ValueError("CAN filter parameter error"); + } + uint32_t preserve_mask; + int addr = 0; + int rtr = 0; + if (self->extframe) { + addr = (addr & 0x1FFFFFFF) << 3 | (rtr ? 0x04 : 0); + mask = (mask & 0x1FFFFFFF) << 3 | 0x03; + preserve_mask = 0; + } else if (self->config->filter.single_filter) { + addr = (((addr & 0x7FF) << 5) | (rtr ? 0x10 : 0)); + mask = ((mask & 0x7FF) << 5); + mask |= 0xFFFFF000; + preserve_mask = 0; + } else { + addr = (((addr & 0x7FF) << 5) | (rtr ? 0x10 : 0)); + mask = ((mask & 0x7FF) << 5); + preserve_mask = 0xFFFF << (bank == 0 ? 16 : 0); + if ((self->config->filter.acceptance_mask & preserve_mask) == (0xFFFF << (bank == 0 ? 16 : 0))) { + // Other filter accepts all; it will replaced duplicating current filter + addr = addr | (addr << 16); + mask = mask | (mask << 16); + preserve_mask = 0; + } else { + addr = addr << (bank == 1 ? 16 : 0); + mask = mask << (bank == 1 ? 16 : 0); + } + } + self->config->filter.acceptance_code &= preserve_mask; + self->config->filter.acceptance_code |= addr; + self->config->filter.acceptance_mask &= preserve_mask; + self->config->filter.acceptance_mask |= mask; + } + // Apply filter + check_esp_err(twai_stop()); + check_esp_err(twai_driver_uninstall()); + check_esp_err(twai_driver_install( + &self->config->general, + &self->config->timing, + &self->config->filter + )); + check_esp_err(twai_start()); + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_setfilter_obj, 1, esp32_hw_can_setfilter); + +// rxcallback(callable) +static mp_obj_t esp32_hw_can_rxcallback(mp_obj_t self_in, mp_obj_t callback_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (callback_in == mp_const_none) { + // disable callback + self->rxcallback = mp_const_none; + } else if (mp_obj_is_callable(callback_in)) { + // set up interrupt + self->rxcallback = callback_in; + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(esp32_hw_can_rxcallback_obj, esp32_hw_can_rxcallback); + +// Clear TX Queue +static mp_obj_t esp32_hw_can_clear_tx_queue(mp_obj_t self_in) { + return mp_obj_new_bool(twai_clear_transmit_queue() == ESP_OK); +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_clear_tx_queue_obj, esp32_hw_can_clear_tx_queue); + +// Clear RX Queue +static mp_obj_t esp32_hw_can_clear_rx_queue(mp_obj_t self_in) { + return mp_obj_new_bool(twai_clear_receive_queue() == ESP_OK); +} +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_clear_rx_queue_obj, esp32_hw_can_clear_rx_queue); + +static const mp_rom_map_elem_t esp32_can_locals_dict_table[] = { + // CAN_ATTRIBUTES + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_CAN) }, + // Micropython Generic API + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&esp32_hw_can_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_hw_can_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_restart), MP_ROM_PTR(&esp32_hw_can_restart_obj) }, + { MP_ROM_QSTR(MP_QSTR_state), MP_ROM_PTR(&esp32_hw_can_state_obj) }, + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&esp32_hw_can_info_obj) }, + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&esp32_hw_can_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&esp32_hw_can_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&esp32_hw_can_recv_obj) }, + { MP_ROM_QSTR(MP_QSTR_setfilter), MP_ROM_PTR(&esp32_hw_can_setfilter_obj) }, + { MP_ROM_QSTR(MP_QSTR_clearfilter), MP_ROM_PTR(&esp32_hw_can_clearfilter_obj) }, + { MP_ROM_QSTR(MP_QSTR_rxcallback), MP_ROM_PTR(&esp32_hw_can_rxcallback_obj) }, + // ESP32 Specific API + { MP_OBJ_NEW_QSTR(MP_QSTR_clear_tx_queue), MP_ROM_PTR(&esp32_hw_can_clear_tx_queue_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_clear_rx_queue), MP_ROM_PTR(&esp32_hw_can_clear_rx_queue_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_alerts), MP_ROM_PTR(&esp32_hw_can_alert_obj) }, + // CAN_MODE + { MP_ROM_QSTR(MP_QSTR_NORMAL), MP_ROM_INT(TWAI_MODE_NORMAL) }, + { MP_ROM_QSTR(MP_QSTR_LOOPBACK), MP_ROM_INT(TWAI_MODE_NORMAL | CAN_MODE_SILENT_LOOPBACK) }, + { MP_ROM_QSTR(MP_QSTR_SILENT), MP_ROM_INT(TWAI_MODE_NO_ACK) }, +// { MP_ROM_QSTR(MP_QSTR_SILENT_LOOPBACK), MP_ROM_INT(TWAI_MODE_NO_ACK | CAN_MODE_SILENT_LOOPBACK) }, // ESP32 not silent in fact + { MP_ROM_QSTR(MP_QSTR_LISTEN_ONLY), MP_ROM_INT(TWAI_MODE_LISTEN_ONLY) }, +/* esp32 can modes +TWAI_MODE_NORMAL - Normal operating mode where TWAI controller can send/receive/acknowledge messages +TWAI_MODE_NO_ACK - Transmission does not require acknowledgment. Use this mode for self testing. // This mode is useful when self testing the TWAI controller (loopback of transmissions). +TWAI_MODE_LISTEN_ONLY - The TWAI controller will not influence the bus (No transmissions or acknowledgments) but can receive messages. // This mode is suited for bus monitor applications. +*/ +/* stm32 can modes +#define CAN_MODE_NORMAL FDCAN_MODE_NORMAL +#define CAN_MODE_LOOPBACK FDCAN_MODE_EXTERNAL_LOOPBACK +#define CAN_MODE_SILENT FDCAN_MODE_BUS_MONITORING +#define CAN_MODE_SILENT_LOOPBACK FDCAN_MODE_INTERNAL_LOOPBACK +*/ + // CAN_STATE + { MP_ROM_QSTR(MP_QSTR_STOPPED), MP_ROM_INT(TWAI_STATE_STOPPED) }, + { MP_ROM_QSTR(MP_QSTR_ERROR_ACTIVE), MP_ROM_INT(TWAI_STATE_RUNNING) }, + { MP_ROM_QSTR(MP_QSTR_BUS_OFF), MP_ROM_INT(TWAI_STATE_BUS_OFF) }, + { MP_ROM_QSTR(MP_QSTR_RECOVERING), MP_ROM_INT(TWAI_STATE_RECOVERING) }, + // CAN_FILTER_MODE + { MP_ROM_QSTR(MP_QSTR_FILTER_RAW_SINGLE), MP_ROM_INT(FILTER_RAW_SINGLE) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_RAW_DUAL), MP_ROM_INT(FILTER_RAW_DUAL) }, + { MP_ROM_QSTR(MP_QSTR_FILTER_ADDRESS), MP_ROM_INT(FILTER_ADDRESS) }, + // CAN_ALERT + { MP_ROM_QSTR(MP_QSTR_ALERT_TX_IDLE), MP_ROM_INT(TWAI_ALERT_TX_IDLE) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_TX_SUCCESS), MP_ROM_INT(TWAI_ALERT_TX_SUCCESS) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_BELOW_ERR_WARN), MP_ROM_INT(TWAI_ALERT_BELOW_ERR_WARN) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_ERR_ACTIVE), MP_ROM_INT(TWAI_ALERT_ERR_ACTIVE) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_RECOVERY_IN_PROGRESS), MP_ROM_INT(TWAI_ALERT_RECOVERY_IN_PROGRESS) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_BUS_RECOVERED), MP_ROM_INT(TWAI_ALERT_BUS_RECOVERED) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_ARB_LOST), MP_ROM_INT(TWAI_ALERT_ARB_LOST) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_ABOVE_ERR_WARN), MP_ROM_INT(TWAI_ALERT_ABOVE_ERR_WARN) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_BUS_ERROR), MP_ROM_INT(TWAI_ALERT_BUS_ERROR) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_TX_FAILED), MP_ROM_INT(TWAI_ALERT_TX_FAILED) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_RX_QUEUE_FULL), MP_ROM_INT(TWAI_ALERT_RX_QUEUE_FULL) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_ERR_PASS), MP_ROM_INT(TWAI_ALERT_ERR_PASS) }, + { MP_ROM_QSTR(MP_QSTR_ALERT_BUS_OFF), MP_ROM_INT(TWAI_ALERT_BUS_OFF) } +}; +static MP_DEFINE_CONST_DICT(esp32_can_locals_dict, esp32_can_locals_dict_table); + +// Python object definition +MP_DEFINE_CONST_OBJ_TYPE( + machine_can_type, + MP_QSTR_CAN, + MP_TYPE_FLAG_NONE, + make_new, esp32_hw_can_make_new, + print, esp32_hw_can_print, + locals_dict, (mp_obj_dict_t *)&esp32_can_locals_dict + ); + +#endif // MICROPY_HW_ENABLE_CAN diff --git a/ports/esp32/machine_can.h b/ports/esp32/machine_can.h new file mode 100644 index 0000000000000..f88adff0da8d9 --- /dev/null +++ b/ports/esp32/machine_can.h @@ -0,0 +1,78 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Musumeci Salvatore + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#ifndef MICROPY_INCLUDED_ESP32_CAN_H +#define MICROPY_INCLUDED_ESP32_CAN_H + +#include "modmachine.h" +#include "freertos/task.h" + +#include "py/obj.h" + +#if MICROPY_HW_ENABLE_CAN + +#define DEVICE_NAME "CAN" + +typedef enum _filter_mode_t { + FILTER_RAW_SINGLE = 0, + FILTER_RAW_DUAL, + FILTER_ADDRESS +} filter_mode_t; + +typedef struct _esp32_can_config_t { + twai_timing_config_t timing; + twai_filter_config_t filter; + twai_general_config_t general; + uint32_t baudrate; // bit/s + bool initialized; +} esp32_can_config_t; + +typedef struct _esp32_can_obj_t { + mp_obj_base_t base; + esp32_can_config_t *config; + mp_obj_t rxcallback; + TaskHandle_t irq_handler; + byte rx_state; + bool extframe : 1; + bool loopback : 1; + byte last_tx_success : 1; + byte bus_recovery_success : 1; + uint16_t num_error_warning; // FIXME: populate this value somewhere + uint16_t num_error_passive; + uint16_t num_bus_off; +} esp32_can_obj_t; + +typedef enum _rx_state_t { + RX_STATE_FIFO_EMPTY = 0, + RX_STATE_MESSAGE_PENDING, + RX_STATE_FIFO_FULL, + RX_STATE_FIFO_OVERFLOW, +} rx_state_t; + +extern const mp_obj_type_t machine_can_type; + +#endif // MICROPY_HW_ENABLE_CAN + +#endif // MICROPY_INCLUDED_ESP32_CAN_H From 11420251bc212af3daf00f80e1eb268b3fe0cdbf Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Tue, 18 Feb 2025 18:00:07 +0200 Subject: [PATCH 2/3] esp32/boards/sdkconfig.base: Add CONFIG_XTAL_FREQ. CONFIG_XTAL_FREQ_40=y CONFIG_XTAL_FREQ=40 Signed-off-by: Ihor Nehrutsa --- ports/esp32/boards/sdkconfig.base | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ports/esp32/boards/sdkconfig.base b/ports/esp32/boards/sdkconfig.base index 530db427119ca..3e690e4f2152c 100644 --- a/ports/esp32/boards/sdkconfig.base +++ b/ports/esp32/boards/sdkconfig.base @@ -33,6 +33,8 @@ CONFIG_LOG_MAXIMUM_LEVEL_INFO=y # Main XTAL Config # Only on: ESP32 CONFIG_XTAL_FREQ_AUTO=y +CONFIG_XTAL_FREQ_40=y +CONFIG_XTAL_FREQ=40 # ESP System Settings # Only on: ESP32, ESP32S3 From 557d5eef32638df534ca02a7b38177c25c3c3ce9 Mon Sep 17 00:00:00 2001 From: IhorNehrutsa Date: Thu, 13 Feb 2025 22:08:38 +0200 Subject: [PATCH 3/3] port/esp32: Applay TWAI CAN_v2. Co-Authored-By: Sveinung Kval Bakken Signed-off-by: Ihor Nehrutsa --- docs/library/machine.CAN.rst | 408 ++++++++++++++++++++++++++ docs/library/machine.rst | 1 + examples/esp32_can.py | 4 +- ports/esp32/esp32_common.cmake | 1 + ports/esp32/machine_can.c | 518 ++++++++++++++++++++------------- ports/esp32/machine_can.h | 92 ++++-- ports/esp32/modmachine.c | 1 + ports/esp32/modmachine.h | 1 + ports/esp32/mpconfigport.h | 3 +- 9 files changed, 797 insertions(+), 232 deletions(-) create mode 100644 docs/library/machine.CAN.rst diff --git a/docs/library/machine.CAN.rst b/docs/library/machine.CAN.rst new file mode 100644 index 0000000000000..ccddc8b879a76 --- /dev/null +++ b/docs/library/machine.CAN.rst @@ -0,0 +1,408 @@ +.. currentmodule:: machine +.. _machine.CAN: + +class CAN -- Controller Area Network protocol +============================================= + +.. warning:: Currently no MicroPython ports have ``machine.CAN`` controllers. + This is a design document only. + +CAN is a two-wire serial protocol used for reliable real-time message delivery +between one or more nodes connected to a common bus. CAN 2.0 was standardised in +ISO-11898, and is now also known as CAN Classic. + +There is also a newer, backwards compatible, protocol named CAN FD (CAN with +Flexible Data-Rate). + +CAN support requires a controller (often an internal microcontroller +peripheral), and an external transceiver to level-shift the signals onto the CAN +bus. + +The ``machine.CAN`` interface is a *low level basic* CAN messaging interface +that abstracts a CAN controller as an outgoing priority queue for sending +messages, an incoming queue for receiving messages, and mechanisms for reporting +errors. + +.. note:: The forthcoming ``can`` and ``aiocan`` micropython-lib modules are the + recommended way to use CAN with MicroPython. + +Constructor +----------- + +.. class:: CAN(id, **kwargs) + + Construct a CAN controller object of the given id: + + - ``id`` identifies a particular CAN controller object; it is board and port specific. + - ``**kwargs`` are hardware-specific identifiers for the particular + board and port. For example, to set which GPIO pins to use for the + CAN controller. + +Methods +------- + +.. method:: CAN.init(bitrate, mode=CAN.Mode.NORMAL, sample_point=75, sjw=1, tseg1=None, tseg2=None, f_clock=None, auto_retransmit=True) + + Initialise the CAN bus with the given parameters: + + - ``bitrate`` is the desired bus bit rate in bits per second. + - ``mode`` is one of :class:`CAN.Mode` enumerated values, indicating the + desired mode of operation. + + The remaining parameters are optional and relate to CAN bit timings. In many + cases these parameters can be omitted or left at the default values: + + - ``sample_point`` is an integer percentage of the data bit time. It + specifies the position of the bit sample with respect to the whole nominal + bit time. The CAN driver will calculate parameters accordingly. This + parameter is ignored if ``tseg1`` and ``tseg2`` are set. + - ``sjw`` is the resynchronisation jump width in units of time quanta for + nominal bits; it can be a value between 1 and 4 inclusive for classic CAN. + - ``tseg1`` defines the location of the sample point in units of time quanta + for nominal bits; it can be a value between 1 and 16 inclusive for classic + CAN. This is the sum of the ``Prop_Seg`` and ``Phase_Seg1`` phases as + defined in the ISO-11898 standard. If this value is set then ``tseg2`` + must also be set and ``sample_point`` is ignored. + - ``tseg2`` defines the location of the transmit point in units of the time + quanta for nominal bits; it can be a value between 1 and 8 inclusive for + classic CAN. This corresponds to ``Phase_Seg2`` in the ISO-11898 standard. + If this value is set then ``tseg1`` must also be set. + - ``f_clock`` is the system clock frequency for the CAN peripheral, before + any prescaler is applied to achieve a desired bit rate. This can be + omitted on most ports where the value is known to the hardware. + - ``auto_retransmit`` may be set to ``False`` to disable automatic + retransmit of messages in case of errors or lost arbitration. + + If these arguments are specified then the CAN controller is configured + correctly for the desired ``bitrate`` and the specified total number of time + quanta per bit. The ``tseg1`` and ``tseg2`` values override the + ``sample_point`` argument if all of these are supplied. + + .. note:: Individual controller hardware may have additional restrictions on + valid values for these parameters, and will raise a ``ValueError`` + if a given value is not supported. + + .. note:: Specific controller hardware may accept additional optional + parameters for hardware-specific features such as oversampling. + +.. method:: CAN.init_fd(bitrate, sample_point=None, sjw=None, tseg1=None, tseg2=None) + + Initialise CAN FD for controllers which support it. + + .. note:: This method is not present on the class for controllers that only + support CAN Classic. + + This must be called after :func:`CAN.init()`. The parameters all have the + same meanings and behaviour as that function, however they configure the + Bit Rate Switch (BRS) feature of CAN FD. + + When specifying exact timing parameters in units of time quanta, the accepted + ranges are: + + - ``sjw`` can be a value between 1 and 128 inclusive. + - ``tseg1`` can be a value between 2 and 256 inclusive. + - ``tseg2`` can be a value between 2 and 128 inclusive + + .. note:: To avoid the controller temporarily becoming active on the bus + without CAN FD support configured, first call :func:`CAN.init()` + with ``mode`` set to ``CAN.mode.SLEEP``, then call this function to + configure CAN FD, then call :func:`CAN.mode()` to start operation. + + .. note:: Specific controllers may have tighter constraints on allowed + timings. If a value is out of range, the controller will raise a + ``ValueError``. + +.. method:: CAN.set_filters(filters) + + Set receive filters in the CAN controller. ``filters`` should be an iterable, + where each item is a tuple or list with three elements: + + - ``identifier`` is a CAN identifier (int). + - ``bit_mask`` is a bit mask for bits in the CAN identifier field (int). + - ``flags`` is one or more bitwise OR-ed values from + :class:`CAN.MessageFlags` that the incoming message needs to match. Not + all controllers support filtering on all flags, a ``ValueError`` is raised + if an unsupported flag is requested. + + Incoming messages are accepted if the bits masked in ``bit_mask`` match between + the message identifier and the filter ``identifier`` value, and flags set in the + filter match the incoming message. + + All filters are ORed together in the controller. Passing an empty list (``[]``) + for the filters argument disables the receive filter (all messages received.) + + .. note:: If the caller passes a list with more entries than :data:`CAN.filters_max`, + ``ValueError`` will be raised. + +.. data:: CAN.filters_max + + Constant value that reads the maximum number of supported receive filters + for this hardware controller. + +.. method:: CAN.send(identifier, data, flags=0, fifo_equal=True) + + - ``identifier`` is an integer CAN identifier value. + - ``data`` is a bytes object (or similar) containing the CAN message data. + - ``flags`` is OR-ed together flags :class:`CAN.MessageFlags` specifying + properties of the outgoing CAN message (Extended ID, Remote request, etc.) + - ``keep_equal`` is a flag for whether messages with the same identifiers + should be kept in the queue or replaced (see below). + + Write a new CAN message into the controller's hardware transmit queue to be + sent onto the bus. The transmit queue is a priority queue sorted first on CAN + identifier priority (lower numeric identifiers have higher priority), and + second on order of insertion. + + If the queue is full and the new message has higher priority than the lowest + priority message in the queue then the lowest priority message will be + de-queued and replaced. + + If the queue is full and the new message has equal priority to the lowest + priority message in the queue, then the behaviour depends on the + ``keep_equal`` argument: + + - If ``True`` (default), the queue remains unchanged and the new message is + not queued. This allows reliable transmission of messages in FIFO order. + - If ``False``, the oldest matching message in the transmit queue is + de-queued and replaced with the new message. + + The function returns an integer index (1-based) which identifies the queue + entry where the new message was written. This is a hardware-specific index + value that does not reflect which message will be sent next. + + An additional value ``CAN.SEND_FLAG`` is bitwise ORed with the result if the + previously queued message at this entry indexed was de-queued and replaced + with the new message. + + If the hardware queue was full and the new message could not be written into + the queue at all due to priorities, then the return value is ``0``. + + .. note:: This intentionally low-level implementation is designed so the + caller tracks the messages in each hardware queue entry. See the ``can`` and + ``aiocan`` modules for details. + +.. data:: CAN.SEND_FLAG + + Constant bit value that is ORed with the index result from :func:`CAN.send` + if another message was de-queued and replaced with the new message. + +.. data:: CAN.send_max + + Constant, indicating the maximum number of entries in the hardware transmit + queue (this is also the highest possible index returned from ``send``). + +.. method:: CAN.irq_send(callback, hard=False) + + Sets an interrupt ``callback`` function to be called when the controller sends + a message on the bus, or has attempted to send. + + Callback arguments are a tuple or list with four elements: + + - The integer index value that was returned from :func:`CAN.send` when the + corresponding message was queued. + - A :func:`time.ticks_us()` timestamp indicating when the message was + acknowledged sent (or when sending failed). + - Send Status. 0 if the message was sent successfully, or a bitwise OR + of :class:`CAN.SendErrors` flags otherwise. + - Boolean flag, set to ``True`` if the CAN controller hardware has already + re-queued this message to retry sending. ``False`` if sending succeeded, + or if the message was not re-qeueud. + + .. note:: The callback function must not store a reference to the argument. + The same tuple or list object may be reused for each IRQ. + + If ``hard`` is set to True then the callback will be called in "hard" + interrupt context on ports which support this. The parameter is ignored + otherwise. + +.. method:: CAN.irq_recv(callback, hard=False) + + Sets an interrupt ``callback`` function to be called when the controller receives + a message on the bus, or detects an error condition while receiving. + + Callback arguments for a successful receive are a tuple or list with the + following elements: + + - A :func:`time.ticks_us()` timestamp indicating when the + message was received. + - The integer CAN identifier which was received. If receive filters are set, + the identifier will be one that matches. + - Data payload. A bytes object (or equivalent) showing the data portion of the + message. + - Message flags. An integer comprised of bitwise ORed values from + :class:`CAN.MessageFlags`. This indicates metadata about the message. + - Error flags. ``0`` if the message was received successfully, or bitwise + ORed values of :func:`CAN.RecvErrors` otherwise. Note that if error flags + are set then the values of other elements may be ``None``. + + .. note:: The callback function must not store a reference to the argument or + the data payload. The same objects may be reused in future IRQs. + + If ``hard`` is set to True then the callback will be called in "hard" + interrupt context on ports which support this. The parameter is ignored + otherwise. + + Unless not possible due to hardware limitations, the controller will call the + ``irq_recv()`` callback in the same order that messages were received on the + bus. + + .. note:: As a low-level class, :class:`machine.CAN` does not contain a + normal ``recv()`` method. Refer to (planned) ``can`` and ``iocan`` + modules, instead. + +.. method:: CAN.get_state() + + Returns a :class:`CAN.State` value indicating the state of the controller. + +.. method:: CAN.irq_state(callback, hard=False) + + Sets a callback which is called whenever the controller state changes. The + callback argument is the new :class:`CAN.State` value. + + If ``hard`` is set to True then the callback will be called in "hard" + interrupt context on ports which support this. The parameter is ignored + otherwise. + +.. method:: CAN.get_counters([list] /) + + Returns controller's error counter values. The result is a list of eight + values. If the optional ``list`` parameter is specified then the provided + list object is updated and returned as the result, to avoid an allocation. + + The list items are: + + - TEC (Transmit Error Counter) value + - REC (Receive Error Counter) value + - Number of times the controller entered the Error Warning state. + - Number of times the controller entered the Error Passive state. + - Number of times the controller entered the Bus Off state. + - Total number of pending TX messages in the hardware queue. + - Total number of pending RX messages in the hardware queue. + - Number of times an RX overrun occurred. + + .. note:: Depending on the controller, these values may overflow back to 0 after + a certain value. + + .. note:: If a controller doesn't support a particular counter, it will return + ``None`` for that list element. + +.. method:: CAN.get_timings([list]) + + Returns a list of elements indicating the current timings configured in the + CAN controller. This can be used to verify timings for debugging purposes. + The result is a list of six values. If the optional ``list`` parameter is + specified then the provided list object is updated and returned as the + result, to avoid an allocation. + + The list items are: + + - Exact bitrate used by the controller. May vary from ``bitrate`` argument + passed to :func:`CAN.init()` due to quantisation to meet hardware + constraints. + - Resynchronisation jump width (SJW) in units of time quanta for nominal + bits. Has the same meaning as the ``sjw`` parameter of :func:`CAN.init()`. + - Location of the sample point in units of time quanta for nominal bits. Has + the same meaning as the ``tseg1`` parameter of :func:`CAN.init()`. + - Location of the transmit point in units of time quanta for nominal bits. + Has the same meaning as the ``tseg2`` parameter of :func:`CAN.init()`. + - CAN FD timing information. ``None`` for controllers which don't support CAN + FD, or if CAN FD is not initialised. Otherwise, a nested list of four + elements corresponding to the items above but applicable to the CAN FD BRS + feature. + - Optional controller-specific timing information. Depending on the + controller this will either be ``None`` if controller doesn't report any, + or it will be a constant length list whose elements are specific to a + particular hardware controller. + + .. note:: If :func:`CAN.init()` has not been called then this function + still returns a result, but the result is implementation defined + and may not be accurate. + +.. method:: CAN.reset(mode=CAN.Mode.NORMAL) + + Fully resets the CAN controller hardware. Restores values previously set by :func:`CAN.init()`. + + All hardware queues are emptied, error counters and flags reset, etc. + + The controller transitions into the ``mode`` specified by the argument (see :class:`CAN.Mode`). + +.. method:: CAN.restart() + + Restart the CAN controller without resetting its internal state, except for + TEC and REC. Can be used to manually recover from the ``BUS_OFF`` state. + +.. method:: CAN.mode([mode]) + + With an argument, transitions the CAN controller to a new mode of operation, + without resetting it. Argument is one of :func:`CAN.Mode`. Returns the + previous mode of operation. + + Without any arguments, returns the current mode of operation. + + .. note:: Note all modes are supported by all CAN controller hardware. + Passing an unsupported mode value will raise ``ValueError``. + +.. class:: CAN.Mode + + Enumeration class that holds the following constant values (integers) representing controller modes of operation: + + - ``NORMAL`` - CAN controller interacts normally on the bus. + - ``SLEEP`` - CAN controller is asleep in a low power mode. Depending on the + controller, this may support waking the controller and transitioning to + ``NORMAL`` mode if CAN traffic is received. + - ``LOOPBACK`` - A testing mode. The CAN controller is still connected to the + external bus, but will also receive its own transmitted messages and ignore + any ACK errors. + - ``SILENT`` - CAN controller receives messages but does not interact with + the CAN bus (including sending ACKs, errors, etc.) + - ``SILENT_LOOPBACK`` - A testing mode that does not require a CAN bus. The + CAN controller receives its own transmitted messages without interacting + with the CAN bus at all. The CAN TX and RX pins remain idle. + +.. class:: CAN.State + + Enumeration class that holds the following constant values (integers) representing the state of the controller on the bus: + + - ``STOPPED`` - The controller is not interacting with the bus. + - ``ERROR_ACTIVE`` - The controller is in the Error-Active and TEC and REC are both less than 96. + - ``ERROR_WARNING`` - The controller is in the Error-Warning state, meaning + at least one of ``TEC`` or ``REC`` is 96 or greater. + - ``ERROR_PASSIVE`` - The controller is in the Error-Passive state, meaning + at least one of ``TEC`` or ``REC`` is 128 or greater. + - ``BUS_OFF`` - The controller is in the Bus-Off state, meaning ``TEC`` is + greater than 255. It does not currently have any influence on Bus activity. + +.. class:: CAN.MessageFlags + + Enumeration class representing possible conditions of a CAN message, as bitwise ORed together flags. + + - ``RTR`` - Message is a remote transmission request. If this bit is set, the + ``data`` value should be an integer (indicating the length from the ``DLC`` + field), not a bytes object. + - ``EXTENDED_ID`` - Message identifier is Extended (29-bit). If not set, message + identifier is Standard (11-bit). + - ``FD_F``- Message is CAN FD in the FD data format, meaning data payload + can be up to 64 bytes long. Passing this flag to a controller which lacks + CAN FD support will raise ``ValueError``. + - ``BRS`` - For CAN FD controllers and FD data format messages, indicates the + bitrate should be switched during the data phase. + +.. class:: CAN.RecvErrors + + Enumeration class representing possible error statuses when + receiving a CAN message. Multiple values may be ORed together. + + - ``CRC`` - A CRC error occurred. + - ``FORM`` - A form error occurred in a fixed-form bit field. + - ``OVERRUN`` - One or more messages overran the receive hardware + queue and were lost. + - ``ESI`` - The ESI flag of a received CAN FD message was set. + +.. class:: CAN.SendErrors + + Enumeration class representing possible error statuses when + sending a CAN message. Multiple values may be ORed together. + + - ``ARB`` - Arbitration was lost. + - ``NACK`` - Sent message was not ACKed. + - ``ERR`` - A bus error was detected during transmission. diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 76d111f11ef3d..a2de2dd6bf6ed 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -260,6 +260,7 @@ Classes machine.Signal.rst machine.ADC.rst machine.ADCBlock.rst + machine.CAN.rst machine.PWM.rst machine.UART.rst machine.SPI.rst diff --git a/examples/esp32_can.py b/examples/esp32_can.py index c7f29635621ba..bc43e9cae65c6 100644 --- a/examples/esp32_can.py +++ b/examples/esp32_can.py @@ -51,8 +51,8 @@ def send_and_check(can_bus, name, id, expected_result=True, extended=False): extframe=True, mode=CAN.SILENT_LOOPBACK, baudrate=CAN.BAUDRATE_500k, - tx_io=18, - rx_io=19, + tx=18, + rx=19, auto_restart=False, ) diff --git a/ports/esp32/esp32_common.cmake b/ports/esp32/esp32_common.cmake index 6473f04a53500..902d396309dcc 100644 --- a/ports/esp32/esp32_common.cmake +++ b/ports/esp32/esp32_common.cmake @@ -116,6 +116,7 @@ list(APPEND MICROPY_SOURCE_PORT fatfs_port.c help.c machine_bitstream.c + machine_can.c machine_timer.c machine_pin.c machine_touchpad.c diff --git a/ports/esp32/machine_can.c b/ports/esp32/machine_can.c index ca95cad3587b3..e8ef0c0ec3d76 100644 --- a/ports/esp32/machine_can.c +++ b/ports/esp32/machine_can.c @@ -22,8 +22,9 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#include +// #include +#include "mpconfigport.h" #include "py/obj.h" #include "py/objarray.h" #include "py/binary.h" @@ -35,19 +36,22 @@ #include "freertos/task.h" #include "esp_idf_version.h" -#include "soc/dport_reg.h" #include "esp_err.h" #include "esp_log.h" - +#include "soc/soc_caps.h" +#include "soc/clk_tree_defs.h" +#include "soc/twai_periph.h" +#include "hal/twai_types.h" +#include "hal/twai_hal.h" #include "driver/twai.h" #include "esp_task.h" #include "machine_can.h" -#if MICROPY_HW_ENABLE_CAN +#if MICROPY_PY_MACHINE_CAN -#define CAN_MODE_SILENT_LOOPBACK (0x10) +#define debug_printf(...) mp_printf(&mp_plat_print, __VA_ARGS__); mp_printf(&mp_plat_print, " | %d at %s\n", __LINE__, __FILE__); -// Default baudrate: 500kb +// Default bitrate: 500kb #define CAN_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) #define CAN_TASK_STACK_SIZE (1024) #define CAN_DEFAULT_PRESCALER (8) @@ -56,46 +60,38 @@ #define CAN_DEFAULT_BS2 (4) #define CAN_MAX_DATA_FRAME (8) -/* -// Internal Functions -mp_obj_t esp32_hw_can_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args); -static mp_obj_t esp32_hw_can_init_helper(esp32_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); -static void esp32_hw_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind); -*/ -// INTERNAL Deinitialize can -void can_deinit(const esp32_can_obj_t *self) { - check_esp_err(twai_stop()); - check_esp_err(twai_driver_uninstall()); - if (self->irq_handler != NULL) { - vTaskDelete(self->irq_handler); - } - self->config->initialized = false; -} +// #define TWAI_TIMING_CONFIG_20KBITS() {.clk_src = TWAI_CLK_SRC_DEFAULT, .quanta_resolution_hz = 400000, .brp = 0, .tseg_1 = 15, .tseg_2 = 4, .sjw = 3, .triple_sampling = false} -// static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS(); +// static const twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS(); static const twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); // singleton CAN device object esp32_can_config_t can_config = { .general = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_2, GPIO_NUM_4, TWAI_MODE_NORMAL), - .filter = f_config, // TWAI_FILTER_CONFIG_ACCEPT_ALL(), - .timing = TWAI_TIMING_CONFIG_25KBITS(), + .filter = TWAI_FILTER_CONFIG_ACCEPT_ALL(), + .timing = TWAI_TIMING_CONFIG_500KBITS(), .initialized = false }; static esp32_can_obj_t esp32_can_obj = { {&machine_can_type}, - .config = &can_config + .config = &can_config, + .handle = NULL, }; -// INTERNAL FUNCTION Return status information -static twai_status_info_t _esp32_hw_can_get_status() { - twai_status_info_t status; - check_esp_err(twai_get_status_info(&status)); - return status; +// INTERNAL Deinitialize can +void can_deinit(const esp32_can_obj_t *self) { + if (self->handle) { + check_esp_err(twai_stop_v2(self->handle)); + check_esp_err(twai_driver_uninstall_v2(self->handle)); + } + if (self->irq_handler != NULL) { + vTaskDelete(self->irq_handler); + } + self->config->initialized = false; } -static void esp32_hw_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { +static void esp32_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->config->initialized) { @@ -114,10 +110,10 @@ static void esp32_hw_can_print(const mp_print_t *print, mp_obj_t self_in, mp_pri mode = MP_QSTR_UNKNOWN; break; } - mp_printf(print, "CAN(tx=%u, rx=%u, baudrate=%ukb, mode=%q, loopback=%u, extframe=%u)", + mp_printf(print, "CAN(tx=%u, rx=%u, bitrate=%u, mode=%q, loopback=%u, extframe=%u)", self->config->general.tx_io, self->config->general.rx_io, - self->config->baudrate, + self->config->bitrate, mode, self->loopback, self->extframe); @@ -127,19 +123,28 @@ static void esp32_hw_can_print(const mp_print_t *print, mp_obj_t self_in, mp_pri } // INTERNAL FUNCTION FreeRTOS IRQ task -static void esp32_hw_can_irq_task(void *self_in) { - esp32_can_obj_t *self = (esp32_can_obj_t *)self_in; +static void esp32_can_irq_task(void *self_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); uint32_t alerts; - twai_reconfigure_alerts( - TWAI_ALERT_RX_DATA | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_BUS_OFF | TWAI_ALERT_ERR_PASS | - TWAI_ALERT_ABOVE_ERR_WARN | TWAI_ALERT_TX_FAILED | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_BUS_RECOVERED, + check_esp_err(twai_reconfigure_alerts_v2(self->handle, TWAI_ALERT_ALL, + // TWAI_ALERT_RX_DATA | TWAI_ALERT_RX_QUEUE_FULL | TWAI_ALERT_BUS_OFF | TWAI_ALERT_ERR_PASS | + // TWAI_ALERT_ABOVE_ERR_WARN | TWAI_ALERT_TX_FAILED | TWAI_ALERT_TX_SUCCESS | TWAI_ALERT_BUS_RECOVERED, + // TWAI_ALERT_TX_IDLE | TWAI_ALERT_BELOW_ERR_WARN | TWAI_ALERT_ERR_ACTIVE | TWAI_ALERT_RECOVERY_IN_PROGRESS | + // TWAI_ALERT_ARB_LOST | TWAI_ALERT_BUS_ERROR | TWAI_ALERT_RX_FIFO_OVERRUN | TWAI_ALERT_TX_RETRIED | TWAI_ALERT_PERIPH_RESET, NULL - ); + )); while (1) { - check_esp_err(twai_read_alerts(&alerts, portMAX_DELAY)); + check_esp_err(twai_read_alerts_v2(self->handle, &alerts, portMAX_DELAY)); + + if (alerts & TWAI_ALERT_BUS_OFF) { + for (int i = 3; i > 0; i--) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } + twai_initiate_recovery_v2(self->handle); // Needs 128 occurrences of bus free signal + } if (alerts & TWAI_ALERT_BUS_OFF) { ++self->num_bus_off; } @@ -149,50 +154,70 @@ static void esp32_hw_can_irq_task(void *self_in) { if (alerts & TWAI_ALERT_ABOVE_ERR_WARN) { ++self->num_error_warning; } + if (alerts & (TWAI_ALERT_BUS_RECOVERED)) { + self->bus_recovery_success = 1; + } if (alerts & (TWAI_ALERT_TX_FAILED | TWAI_ALERT_TX_SUCCESS)) { self->last_tx_success = (alerts & TWAI_ALERT_TX_SUCCESS) > 0; } - if (alerts & (TWAI_ALERT_BUS_RECOVERED)) { - self->bus_recovery_success = true; + if (self->tx_callback != mp_const_none) { + check_esp_err(twai_get_status_info_v2(self->handle, &self->status)); + if (alerts & TWAI_ALERT_TX_IDLE) { + mp_sched_schedule(self->tx_callback, MP_OBJ_NEW_SMALL_INT(0)); + } + if (alerts & TWAI_ALERT_TX_SUCCESS) { + mp_sched_schedule(self->tx_callback, MP_OBJ_NEW_SMALL_INT(1)); + } + if (alerts & TWAI_ALERT_TX_FAILED) { + mp_sched_schedule(self->tx_callback, MP_OBJ_NEW_SMALL_INT(2)); + } + if (alerts & TWAI_ALERT_TX_RETRIED) { + mp_sched_schedule(self->tx_callback, MP_OBJ_NEW_SMALL_INT(3)); + } } - if (self->rxcallback != mp_const_none) { + if (self->rx_callback != mp_const_none) { if (alerts & TWAI_ALERT_RX_DATA) { - uint32_t msgs_to_rx = _esp32_hw_can_get_status().msgs_to_rx; + check_esp_err(twai_get_status_info_v2(self->handle, &self->status)); + uint32_t msgs_to_rx = self->status.msgs_to_rx; if (msgs_to_rx == 1) { // first message in queue - mp_sched_schedule(self->rxcallback, MP_OBJ_NEW_SMALL_INT(0)); + mp_sched_schedule(self->rx_callback, MP_OBJ_NEW_SMALL_INT(0)); } else if (msgs_to_rx >= self->config->general.rx_queue_len) { // queue is full - mp_sched_schedule(self->rxcallback, MP_OBJ_NEW_SMALL_INT(1)); + mp_sched_schedule(self->rx_callback, MP_OBJ_NEW_SMALL_INT(1)); } } if (alerts & TWAI_ALERT_RX_QUEUE_FULL) { // queue overflow - mp_sched_schedule(self->rxcallback, MP_OBJ_NEW_SMALL_INT(2)); + mp_sched_schedule(self->rx_callback, MP_OBJ_NEW_SMALL_INT(2)); + } + if (alerts & TWAI_ALERT_RX_FIFO_OVERRUN) { + mp_sched_schedule(self->rx_callback, MP_OBJ_NEW_SMALL_INT(3)); } } } } -// init(mode, tx=5, rx=4, baudrate=500000, prescaler=8, sjw=3, bs1=15, bs2=4, auto_restart=False, tx_queue=1, rx_queue=1) -static mp_obj_t esp32_hw_can_init_helper(esp32_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode, ARG_prescaler, ARG_sjw, ARG_bs1, ARG_bs2, ARG_auto_restart, ARG_baudrate, ARG_extframe, - ARG_tx_io, ARG_rx_io, ARG_tx_queue, ARG_rx_queue}; +static mp_obj_t esp32_can_init_helper(esp32_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_mode, ARG_prescaler, ARG_sjw, ARG_bs1, ARG_bs2, ARG_auto_restart, ARG_bitrate, ARG_extframe, + ARG_tx_io, ARG_rx_io, ARG_clkout_io, ARG_bus_off_io, ARG_tx_queue, ARG_rx_queue}; static const mp_arg_t allowed_args[] = { { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = TWAI_MODE_NORMAL} }, - { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, { MP_QSTR_prescaler, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_PRESCALER} }, { MP_QSTR_sjw, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_SJW} }, { MP_QSTR_bs1, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_BS1} }, { MP_QSTR_bs2, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = CAN_DEFAULT_BS2} }, { MP_QSTR_auto_restart, MP_ARG_BOOL, {.u_bool = false} }, - { MP_QSTR_baudrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 500000} }, + { MP_QSTR_bitrate, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 500000} }, + { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, { MP_QSTR_tx, MP_ARG_INT, {.u_int = 4} }, { MP_QSTR_rx, MP_ARG_INT, {.u_int = 5} }, + { MP_QSTR_clkout, MP_ARG_INT, {.u_int = TWAI_IO_UNUSED} }, + { MP_QSTR_bus_off, MP_ARG_INT, {.u_int = TWAI_IO_UNUSED} }, { MP_QSTR_tx_queue, MP_ARG_INT, {.u_int = 1} }, { MP_QSTR_rx_queue, MP_ARG_INT, {.u_int = 1} }, }; @@ -205,111 +230,126 @@ static mp_obj_t esp32_hw_can_init_helper(esp32_can_obj_t *self, size_t n_args, c self->config->general.mode = args[ARG_mode].u_int & 0x0F; self->config->general.tx_io = args[ARG_tx_io].u_int; self->config->general.rx_io = args[ARG_rx_io].u_int; - self->config->general.clkout_io = TWAI_IO_UNUSED; - self->config->general.bus_off_io = TWAI_IO_UNUSED; + self->config->general.clkout_io = args[ARG_clkout_io].u_int; + self->config->general.bus_off_io = args[ARG_bus_off_io].u_int; self->config->general.tx_queue_len = args[ARG_tx_queue].u_int; self->config->general.rx_queue_len = args[ARG_rx_queue].u_int; - self->config->general.alerts_enabled = TWAI_ALERT_AND_LOG || TWAI_ALERT_BELOW_ERR_WARN || TWAI_ALERT_ERR_ACTIVE || TWAI_ALERT_BUS_RECOVERED || - TWAI_ALERT_ABOVE_ERR_WARN || TWAI_ALERT_BUS_ERROR || TWAI_ALERT_ERR_PASS || TWAI_ALERT_BUS_OFF; + self->config->general.alerts_enabled = TWAI_ALERT_ALL; + // TWAI_ALERT_AND_LOG || TWAI_ALERT_BELOW_ERR_WARN || TWAI_ALERT_ERR_ACTIVE || TWAI_ALERT_BUS_RECOVERED || + // TWAI_ALERT_ABOVE_ERR_WARN || TWAI_ALERT_BUS_ERROR || TWAI_ALERT_ERR_PASS || TWAI_ALERT_BUS_OFF; self->config->general.clkout_divider = 0; self->loopback = ((args[ARG_mode].u_int & CAN_MODE_SILENT_LOOPBACK) > 0); self->extframe = args[ARG_extframe].u_bool; if (args[ARG_auto_restart].u_bool) { mp_raise_NotImplementedError("Auto-restart not supported"); } - self->config->filter = f_config; // TWAI_FILTER_CONFIG_ACCEPT_ALL(); + self->config->filter = f_config; // TWAI_FILTER_CONFIG_ACCEPT_ALL(); // // clear errors self->num_error_warning = 0; self->num_error_passive = 0; self->num_bus_off = 0; - // Calculate CAN nominal bit timing from baudrate if provided - twai_timing_config_t *timing; - switch ((int)args[ARG_baudrate].u_int) { + // Calculate CAN nominal bit timing from bitrate if provided + self->config->bitrate = args[ARG_bitrate].u_int; + switch (self->config->bitrate) { case 0: - timing = &((twai_timing_config_t) { - .brp = args[ARG_prescaler].u_int, - .sjw = args[ARG_sjw].u_int, - .tseg_1 = args[ARG_bs1].u_int, - .tseg_2 = args[ARG_bs2].u_int, - .triple_sampling = false - }); + self->config->timing = (twai_timing_config_t) { + .brp = args[ARG_prescaler].u_int, + .sjw = args[ARG_sjw].u_int, + .tseg_1 = args[ARG_bs1].u_int, + .tseg_2 = args[ARG_bs2].u_int, + .triple_sampling = false + }; break; #ifdef TWAI_TIMING_CONFIG_1KBITS case 1000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_1KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_1KBITS(); break; #endif #ifdef TWAI_TIMING_CONFIG_5KBITS case 5000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_5KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_5KBITS(); break; #endif #ifdef TWAI_TIMING_CONFIG_10KBITS case 10000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_10KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_10KBITS(); break; #endif #ifdef TWAI_TIMING_CONFIG_12_5KBITS case 12500: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_12_5KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_12_5KBITS(); break; #endif #ifdef TWAI_TIMING_CONFIG_16KBITS case 16000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_16KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_16KBITS(); break; #endif #ifdef TWAI_TIMING_CONFIG_20KBITS case 20000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_20KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_20KBITS(); break; #endif + #ifdef TWAI_TIMING_CONFIG_25KBITS case 25000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_25KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_25KBITS(); break; + #endif + #ifdef TWAI_TIMING_CONFIG_50KBITS case 50000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_50KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_50KBITS(); break; + #endif + #ifdef TWAI_TIMING_CONFIG_100KBITS case 100000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_100KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_100KBITS(); break; + #endif + #ifdef TWAI_TIMING_CONFIG_125KBITS case 125000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_125KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_125KBITS(); break; + #endif + #ifdef TWAI_TIMING_CONFIG_250KBITS case 250000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_250KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_250KBITS(); break; + #endif + #ifdef TWAI_TIMING_CONFIG_500KBITS case 500000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_500KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_500KBITS(); break; + #endif + #ifdef TWAI_TIMING_CONFIG_800KBITS case 800000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_800KBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_800KBITS(); break; + #endif + #ifdef TWAI_TIMING_CONFIG_1MBITS case 1000000: - timing = &((twai_timing_config_t)TWAI_TIMING_CONFIG_1MBITS()); + self->config->timing = (twai_timing_config_t)TWAI_TIMING_CONFIG_1MBITS(); break; + #endif default: - mp_raise_ValueError("Unable to set baudrate"); - self->config->baudrate = 0; + self->config->bitrate = 0; + mp_raise_ValueError("Unable to set bitrate"); return mp_const_none; } - self->config->timing = *timing; - check_esp_err(twai_driver_install(&self->config->general, &self->config->timing, &self->config->filter)); - check_esp_err(twai_start()); - if (xTaskCreatePinnedToCore(esp32_hw_can_irq_task, "can_irq_task", CAN_TASK_STACK_SIZE, self, CAN_TASK_PRIORITY, (TaskHandle_t *)&self->irq_handler, MP_TASK_COREID) != pdPASS) { + check_esp_err(twai_driver_install_v2(&self->config->general, &self->config->timing, &self->config->filter, &self->handle)); + check_esp_err(twai_start_v2(self->handle)); + if (xTaskCreatePinnedToCore(esp32_can_irq_task, "can_irq_task", CAN_TASK_STACK_SIZE, self, CAN_TASK_PRIORITY, (TaskHandle_t *)&self->irq_handler, MP_TASK_COREID) != pdPASS) { mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("failed to create can irq task handler")); } self->config->initialized = true; - return mp_const_none; } // CAN(bus, ...) No argument to get the object // If no arguments are provided, the initialized object will be returned -static mp_obj_t esp32_hw_can_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { +static mp_obj_t esp32_can_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // check arguments mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); if (mp_obj_is_int(args[0]) != true) { @@ -318,18 +358,20 @@ static mp_obj_t esp32_hw_can_make_new(const mp_obj_type_t *type, size_t n_args, // work out port mp_uint_t can_idx = mp_obj_get_int(args[0]); - if (can_idx != 0) { - mp_raise_msg_varg(&mp_type_ValueError, "CAN(%d) doesn't exist", can_idx); + if (can_idx > SOC_TWAI_CONTROLLER_NUM - 1) { + mp_raise_msg_varg(&mp_type_ValueError, "out of CAN controllers:%d", SOC_TWAI_CONTROLLER_NUM); } esp32_can_obj_t *self = &esp32_can_obj; + self->status.state = STOPPED; if (!self->config->initialized || n_args > 1 || n_kw > 0) { if (self->config->initialized) { // The caller is requesting a reconfiguration of the hardware // this can only be done if the hardware is in init mode can_deinit(self); } - self->rxcallback = mp_const_none; + self->tx_callback = mp_const_none; + self->rx_callback = mp_const_none; self->irq_handler = NULL; self->rx_state = RX_STATE_FIFO_EMPTY; @@ -337,26 +379,25 @@ static mp_obj_t esp32_hw_can_make_new(const mp_obj_type_t *type, size_t n_args, // start the peripheral mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); - esp32_hw_can_init_helper(self, n_args - 1, args + 1, &kw_args); + esp32_can_init_helper(self, n_args - 1, args + 1, &kw_args); } } return MP_OBJ_FROM_PTR(self); } -// init(tx, rx, baudrate, mode=NORMAL, tx_queue=2, rx_queue=5) -static mp_obj_t esp32_hw_can_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { - esp32_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); +// init(tx, rx, bitrate, mode=NORMAL, tx_queue=2, rx_queue=5) +static mp_obj_t esp32_can_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); if (self->config->initialized) { mp_raise_msg(&mp_type_RuntimeError, "Device is already initialized"); return mp_const_none; } - - return esp32_hw_can_init_helper(self, n_args - 1, args + 1, kw_args); + return esp32_can_init_helper(self, n_args - 1, pos_args + 1, kw_args); } -static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_init_obj, 4, esp32_hw_can_init); +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_can_init_obj, 4, esp32_can_init); // deinit() -static mp_obj_t esp32_hw_can_deinit(const mp_obj_t self_in) { +static mp_obj_t esp32_can_deinit(const mp_obj_t self_in) { const esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->config->initialized != true) { mp_raise_msg(&mp_type_RuntimeError, "Device is not initialized"); @@ -365,18 +406,21 @@ static mp_obj_t esp32_hw_can_deinit(const mp_obj_t self_in) { can_deinit(self); return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_deinit_obj, esp32_hw_can_deinit); +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_can_deinit_obj, esp32_can_deinit); +// CAN.restart() // Force a software restart of the controller, to allow transmission after a bus error -static mp_obj_t esp32_hw_can_restart(mp_obj_t self_in) { +static mp_obj_t esp32_can_restart(mp_obj_t self_in) { esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); - twai_status_info_t status = _esp32_hw_can_get_status(); - if (!self->config->initialized || status.state != TWAI_STATE_BUS_OFF) { - mp_raise_ValueError(NULL); + check_esp_err(twai_get_status_info_v2(self->handle, &self->status)); + + if (!self->config->initialized || self->status.state != TWAI_STATE_BUS_OFF) { + return mp_const_none; + // mp_raise_ValueError(NULL); } self->bus_recovery_success = -1; - check_esp_err(twai_initiate_recovery()); + check_esp_err(twai_initiate_recovery_v2(self->handle)); mp_hal_delay_ms(200); // FIXME: replace it with a smarter solution while (self->bus_recovery_success < 0) { @@ -384,56 +428,31 @@ static mp_obj_t esp32_hw_can_restart(mp_obj_t self_in) { } if (self->bus_recovery_success) { - check_esp_err(twai_start()); + check_esp_err(twai_start_v2(self->handle)); } else { mp_raise_OSError(MP_EIO); } return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_restart_obj, esp32_hw_can_restart); +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_can_restart_obj, esp32_can_restart); // Get the state of the controller -static mp_obj_t esp32_hw_can_state(mp_obj_t self_in) { +static mp_obj_t esp32_can_state(mp_obj_t self_in) { esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_int_t state = TWAI_STATE_STOPPED; if (self->config->initialized) { - state = _esp32_hw_can_get_status().state; + check_esp_err(twai_get_status_info_v2(self->handle, &self->status)); } - return mp_obj_new_int(state); + return mp_obj_new_int(self->status.state); } -static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_state_obj, esp32_hw_can_state); +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_can_state_obj, esp32_can_state); // info() -- Get info about error states and TX/RX buffers -static mp_obj_t esp32_hw_can_info(size_t n_args, const mp_obj_t *args) { -/* +static mp_obj_t esp32_can_info(size_t n_args, const mp_obj_t *args) { esp32_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); - mp_obj_list_t *list; - if (n_args == 1) { - list = MP_OBJ_TO_PTR(mp_obj_new_list(8, NULL)); - } else { - if (!mp_obj_is_type(args[1], &mp_type_list)) { - mp_raise_TypeError(NULL); - } - list = MP_OBJ_TO_PTR(args[1]); - if (list->len < 8) { - mp_raise_ValueError(NULL); - } - } - twai_status_info_t status = _esp32_hw_can_get_status(); - list->items[0] = MP_OBJ_NEW_SMALL_INT(status.tx_error_counter); - list->items[1] = MP_OBJ_NEW_SMALL_INT(status.rx_error_counter); - list->items[2] = MP_OBJ_NEW_SMALL_INT(self->num_error_warning); - list->items[3] = MP_OBJ_NEW_SMALL_INT(self->num_error_passive); - list->items[4] = MP_OBJ_NEW_SMALL_INT(self->num_bus_off); - list->items[5] = MP_OBJ_NEW_SMALL_INT(status.msgs_to_tx); - list->items[6] = MP_OBJ_NEW_SMALL_INT(status.msgs_to_rx); - list->items[7] = mp_const_none; - return MP_OBJ_FROM_PTR(list); -*/ - twai_status_info_t status = _esp32_hw_can_get_status(); + check_esp_err(twai_get_status_info_v2(self->handle, &self->status)); mp_obj_t dict = mp_obj_new_dict(0); #define dict_key(key) mp_obj_new_str(#key, strlen(#key)) - #define dict_value(key) MP_OBJ_NEW_SMALL_INT(status.key) + #define dict_value(key) MP_OBJ_NEW_SMALL_INT(self->status.key) #define dict_store(key) mp_obj_dict_store(dict, dict_key(key), dict_value(key)); dict_store(state); dict_store(msgs_to_tx); @@ -446,25 +465,28 @@ static mp_obj_t esp32_hw_can_info(size_t n_args, const mp_obj_t *args) { dict_store(bus_error_count); return dict; } -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_hw_can_info_obj, 1, 2, esp32_hw_can_info); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(esp32_can_info_obj, 1, 2, esp32_can_info); // Get Alert info -static mp_obj_t esp32_hw_can_alert(mp_obj_t self_in) { +static mp_obj_t esp32_can_alert(mp_obj_t self_in) { + const esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); uint32_t alerts; - check_esp_err(twai_read_alerts(&alerts, 0)); + check_esp_err(twai_read_alerts_v2(self->handle, &alerts, 0)); return mp_obj_new_int(alerts); } -static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_alert_obj, esp32_hw_can_alert); +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_can_alert_obj, esp32_can_alert); // any() - return `True` if any message waiting, else `False` -static mp_obj_t esp32_hw_can_any(mp_obj_t self_in) { - twai_status_info_t status = _esp32_hw_can_get_status(); - return mp_obj_new_bool((status.msgs_to_rx) > 0); +static mp_obj_t esp32_can_any(mp_obj_t self_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + check_esp_err(twai_get_status_info_v2(self->handle, &self->status)); + return mp_obj_new_bool((self->status.msgs_to_rx) > 0); } -static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_any_obj, esp32_hw_can_any); +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_can_any_obj, esp32_can_any); // send([data], id, *, timeout=0, rtr=false, extframe=false) -static mp_obj_t esp32_hw_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +// CAN.send(identifier, data, flags=0, fifo_equal=True) +static mp_obj_t esp32_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_data, ARG_id, ARG_timeout, ARG_rtr, ARG_extframe }; static const mp_arg_t allowed_args[] = { { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, @@ -505,13 +527,14 @@ static mp_obj_t esp32_hw_can_send(size_t n_args, const mp_obj_t *pos_args, mp_ma tx_msg.data[i] = mp_obj_get_int(items[i]); } - if (_esp32_hw_can_get_status().state == TWAI_STATE_RUNNING) { + check_esp_err(twai_get_status_info_v2(self->handle, &self->status)); + if (self->status.state == TWAI_STATE_RUNNING) { uint32_t timeout_ms = args[ARG_timeout].u_int; if (timeout_ms != 0) { self->last_tx_success = -1; uint32_t start = mp_hal_ticks_us(); - check_esp_err(twai_transmit(&tx_msg, pdMS_TO_TICKS(timeout_ms))); + check_esp_err(twai_transmit_v2(self->handle, &tx_msg, pdMS_TO_TICKS(timeout_ms))); while (self->last_tx_success < 0) { if (timeout_ms != portMAX_DELAY) { if (mp_hal_ticks_us() - start >= timeout_ms) { @@ -525,7 +548,7 @@ static mp_obj_t esp32_hw_can_send(size_t n_args, const mp_obj_t *pos_args, mp_ma mp_raise_OSError(MP_EIO); } } else { - check_esp_err(twai_transmit(&tx_msg, portMAX_DELAY)); + check_esp_err(twai_transmit_v2(self->handle, &tx_msg, portMAX_DELAY)); } return mp_const_none; @@ -533,10 +556,11 @@ static mp_obj_t esp32_hw_can_send(size_t n_args, const mp_obj_t *pos_args, mp_ma mp_raise_msg(&mp_type_RuntimeError, "Device is not ready"); } } -static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_send_obj, 3, esp32_hw_can_send); +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_can_send_obj, 3, esp32_can_send); -// recv(list=None, *, timeout=5000) -static mp_obj_t esp32_hw_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +// CAN.recv(list=None, *, timeout=5000) +static mp_obj_t esp32_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); enum { ARG_list, ARG_timeout }; static const mp_arg_t allowed_args[] = { { MP_QSTR_list, MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} }, @@ -549,7 +573,7 @@ static mp_obj_t esp32_hw_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_ma // receive the data twai_message_t rx_msg; - check_esp_err(twai_receive(&rx_msg, pdMS_TO_TICKS(args[ARG_timeout].u_int))); + check_esp_err(twai_receive_v2(self->handle, &rx_msg, pdMS_TO_TICKS(args[ARG_timeout].u_int))); uint32_t rx_dlc = rx_msg.data_length_code; // Create the tuple, or get the list, that will hold the return values @@ -589,33 +613,34 @@ static mp_obj_t esp32_hw_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_ma // Return the result return ret_obj; } -static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_recv_obj, 0, esp32_hw_can_recv); +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_can_recv_obj, 1, esp32_can_recv); // Clear filters setting -static mp_obj_t esp32_hw_can_clearfilter(mp_obj_t self_in) { +static mp_obj_t esp32_can_clearfilter(mp_obj_t self_in) { esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); // Defaults from TWAI_FILTER_CONFIG_ACCEPT_ALL - self->config->filter = f_config; // TWAI_FILTER_CONFIG_ACCEPT_ALL(); + self->config->filter = f_config; // TWAI_FILTER_CONFIG_ACCEPT_ALL(); // // Apply filter - check_esp_err(twai_stop()); - check_esp_err(twai_driver_uninstall()); - check_esp_err(twai_driver_install( + check_esp_err(twai_stop_v2(self->handle)); + check_esp_err(twai_driver_uninstall_v2(self->handle)); + check_esp_err(twai_driver_install_v2( &self->config->general, &self->config->timing, - &self->config->filter)); - check_esp_err(twai_start()); + &self->config->filter, &self->handle)); + check_esp_err(twai_start_v2(self->handle)); return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_clearfilter_obj, esp32_hw_can_clearfilter); +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_can_clearfilter_obj, esp32_can_clearfilter); // bank: 0 only // mode: FILTER_RAW_SINGLE, FILTER_RAW_DUAL or FILTER_ADDR_SINGLE or FILTER_ADDR_DUAL // params: [id, mask] // rtr: ignored if FILTER_RAW // Set CAN HW filter -static mp_obj_t esp32_hw_can_setfilter(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { +// CAN.set_filters(filters) +static mp_obj_t esp32_can_set_filters(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_bank, ARG_mode, ARG_params, ARG_rtr, ARG_extframe }; static const mp_arg_t allowed_args[] = { { MP_QSTR_bank, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} }, @@ -651,7 +676,7 @@ static mp_obj_t esp32_hw_can_setfilter(size_t n_args, const mp_obj_t *pos_args, self->config->filter.acceptance_mask = mask; } else { self->config->filter.single_filter = self->extframe; - // esp32_hw_can_setfilter(self, id, mask, args[ARG_bank].u_int, args[ARG_rtr].u_int); + // esp32_can_set_filters(self, id, mask, args[ARG_bank].u_int, args[ARG_rtr].u_int); // Check if bank is allowed int bank = 0; if (bank > ((self->extframe && self->config->filter.single_filter) ? 0 : 1)) { @@ -689,72 +714,128 @@ static mp_obj_t esp32_hw_can_setfilter(size_t n_args, const mp_obj_t *pos_args, self->config->filter.acceptance_mask |= mask; } // Apply filter - check_esp_err(twai_stop()); - check_esp_err(twai_driver_uninstall()); - check_esp_err(twai_driver_install( + if (self->handle) { + check_esp_err(twai_stop_v2(self->handle)); + check_esp_err(twai_driver_uninstall_v2(self->handle)); + } + check_esp_err(twai_driver_install_v2( &self->config->general, &self->config->timing, - &self->config->filter + &self->config->filter, + &self->handle )); - check_esp_err(twai_start()); - + check_esp_err(twai_start_v2(self->handle)); return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_hw_can_setfilter_obj, 1, esp32_hw_can_setfilter); +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_can_set_filters_obj, 1, esp32_can_set_filters); -// rxcallback(callable) -static mp_obj_t esp32_hw_can_rxcallback(mp_obj_t self_in, mp_obj_t callback_in) { +// CAN.irq_recv(callback, hard=False) +static mp_obj_t esp32_can_irq_recv(mp_obj_t self_in, mp_obj_t callback_in) { esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + if (callback_in == mp_const_none) { + // disable callback + self->rx_callback = mp_const_none; + } else if (mp_obj_is_callable(callback_in)) { + // set up interrupt + self->rx_callback = callback_in; + } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(esp32_can_irq_recv_obj, esp32_can_irq_recv); +// CAN.irq_send(callback, hard=False) +static mp_obj_t esp32_can_irq_send(mp_obj_t self_in, mp_obj_t callback_in) { + esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + // mp_obj_t callback_in = NULL; if (callback_in == mp_const_none) { // disable callback - self->rxcallback = mp_const_none; + self->tx_callback = mp_const_none; } else if (mp_obj_is_callable(callback_in)) { // set up interrupt - self->rxcallback = callback_in; + self->tx_callback = callback_in; } + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_2(esp32_can_irq_send_obj, esp32_can_irq_send); +// CAN.get_state() +static mp_obj_t esp32_can_get_state(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { return mp_const_none; } -static MP_DEFINE_CONST_FUN_OBJ_2(esp32_hw_can_rxcallback_obj, esp32_hw_can_rxcallback); +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_can_get_state_obj, 3, esp32_can_get_state); + +// CAN.get_counters([list] /) +static mp_obj_t esp32_can_get_counters(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_can_get_counters_obj, 3, esp32_can_get_counters); + +// CAN.get_timings([list]) +static mp_obj_t esp32_can_get_timings(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_can_get_timings_obj, 3, esp32_can_get_timings); + +// CAN.reset(mode=CAN.Mode.NORMAL) +static mp_obj_t esp32_can_reset(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_can_reset_obj, 3, esp32_can_reset); + +// CAN.mode([mode]) +static mp_obj_t esp32_can_mode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(esp32_can_mode_obj, 3, esp32_can_mode); // Clear TX Queue -static mp_obj_t esp32_hw_can_clear_tx_queue(mp_obj_t self_in) { - return mp_obj_new_bool(twai_clear_transmit_queue() == ESP_OK); +static mp_obj_t esp32_can_clear_tx_queue(mp_obj_t self_in) { + const esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(twai_clear_transmit_queue_v2(self->handle) == ESP_OK); } -static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_clear_tx_queue_obj, esp32_hw_can_clear_tx_queue); +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_can_clear_tx_queue_obj, esp32_can_clear_tx_queue); // Clear RX Queue -static mp_obj_t esp32_hw_can_clear_rx_queue(mp_obj_t self_in) { - return mp_obj_new_bool(twai_clear_receive_queue() == ESP_OK); +static mp_obj_t esp32_can_clear_rx_queue(mp_obj_t self_in) { + const esp32_can_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(twai_clear_receive_queue_v2(self->handle) == ESP_OK); } -static MP_DEFINE_CONST_FUN_OBJ_1(esp32_hw_can_clear_rx_queue_obj, esp32_hw_can_clear_rx_queue); +static MP_DEFINE_CONST_FUN_OBJ_1(esp32_can_clear_rx_queue_obj, esp32_can_clear_rx_queue); static const mp_rom_map_elem_t esp32_can_locals_dict_table[] = { // CAN_ATTRIBUTES { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_CAN) }, // Micropython Generic API - { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&esp32_hw_can_init_obj) }, - { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_hw_can_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_restart), MP_ROM_PTR(&esp32_hw_can_restart_obj) }, - { MP_ROM_QSTR(MP_QSTR_state), MP_ROM_PTR(&esp32_hw_can_state_obj) }, - { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&esp32_hw_can_info_obj) }, - { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&esp32_hw_can_any_obj) }, - { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&esp32_hw_can_send_obj) }, - { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&esp32_hw_can_recv_obj) }, - { MP_ROM_QSTR(MP_QSTR_setfilter), MP_ROM_PTR(&esp32_hw_can_setfilter_obj) }, - { MP_ROM_QSTR(MP_QSTR_clearfilter), MP_ROM_PTR(&esp32_hw_can_clearfilter_obj) }, - { MP_ROM_QSTR(MP_QSTR_rxcallback), MP_ROM_PTR(&esp32_hw_can_rxcallback_obj) }, + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&esp32_can_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_can_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR_restart), MP_ROM_PTR(&esp32_can_restart_obj) }, + { MP_ROM_QSTR(MP_QSTR_state), MP_ROM_PTR(&esp32_can_state_obj) }, + { MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&esp32_can_info_obj) }, + { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&esp32_can_any_obj) }, + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&esp32_can_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&esp32_can_recv_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq_send), MP_ROM_PTR(&esp32_can_irq_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_irq_recv), MP_ROM_PTR(&esp32_can_irq_recv_obj) }, + { MP_ROM_QSTR(MP_QSTR_set_filters), MP_ROM_PTR(&esp32_can_set_filters_obj) }, + { MP_ROM_QSTR(MP_QSTR_clearfilter), MP_ROM_PTR(&esp32_can_clearfilter_obj) }, + + { MP_ROM_QSTR(MP_QSTR_get_state), MP_ROM_PTR(&esp32_can_get_state_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_counters), MP_ROM_PTR(&esp32_can_get_counters_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_timings), MP_ROM_PTR(&esp32_can_get_timings_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&esp32_can_reset_obj) }, + { MP_ROM_QSTR(MP_QSTR_mode), MP_ROM_PTR(&esp32_can_mode_obj) }, // ESP32 Specific API - { MP_OBJ_NEW_QSTR(MP_QSTR_clear_tx_queue), MP_ROM_PTR(&esp32_hw_can_clear_tx_queue_obj) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_clear_rx_queue), MP_ROM_PTR(&esp32_hw_can_clear_rx_queue_obj) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_get_alerts), MP_ROM_PTR(&esp32_hw_can_alert_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_clear_tx_queue), MP_ROM_PTR(&esp32_can_clear_tx_queue_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_clear_rx_queue), MP_ROM_PTR(&esp32_can_clear_rx_queue_obj) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_get_alerts), MP_ROM_PTR(&esp32_can_alert_obj) }, // CAN_MODE - { MP_ROM_QSTR(MP_QSTR_NORMAL), MP_ROM_INT(TWAI_MODE_NORMAL) }, - { MP_ROM_QSTR(MP_QSTR_LOOPBACK), MP_ROM_INT(TWAI_MODE_NORMAL | CAN_MODE_SILENT_LOOPBACK) }, - { MP_ROM_QSTR(MP_QSTR_SILENT), MP_ROM_INT(TWAI_MODE_NO_ACK) }, -// { MP_ROM_QSTR(MP_QSTR_SILENT_LOOPBACK), MP_ROM_INT(TWAI_MODE_NO_ACK | CAN_MODE_SILENT_LOOPBACK) }, // ESP32 not silent in fact - { MP_ROM_QSTR(MP_QSTR_LISTEN_ONLY), MP_ROM_INT(TWAI_MODE_LISTEN_ONLY) }, + // class CAN.Mode + { MP_ROM_QSTR(MP_QSTR_NORMAL), MP_ROM_INT(MODE_NORMAL) }, + { MP_ROM_QSTR(MP_QSTR_SLEEP), MP_ROM_INT(MODE_SLEEP) }, + { MP_ROM_QSTR(MP_QSTR_LOOPBACK), MP_ROM_INT(MODE_LOOPBACK) }, + { MP_ROM_QSTR(MP_QSTR_SILENT), MP_ROM_INT(MODE_SILENT) }, + { MP_ROM_QSTR(MP_QSTR_SILENT_LOOPBACK), MP_ROM_INT(MODE_SILENT_LOOPBACK) }, + { MP_ROM_QSTR(MP_QSTR_LISTEN_ONLY), MP_ROM_INT(MODE_LISTEN_ONLY) }, /* esp32 can modes TWAI_MODE_NORMAL - Normal operating mode where TWAI controller can send/receive/acknowledge messages TWAI_MODE_NO_ACK - Transmission does not require acknowledgment. Use this mode for self testing. // This mode is useful when self testing the TWAI controller (loopback of transmissions). @@ -767,15 +848,34 @@ TWAI_MODE_LISTEN_ONLY - The TWAI controller will not influence the bus (No trans #define CAN_MODE_SILENT_LOOPBACK FDCAN_MODE_INTERNAL_LOOPBACK */ // CAN_STATE + // class CAN.State { MP_ROM_QSTR(MP_QSTR_STOPPED), MP_ROM_INT(TWAI_STATE_STOPPED) }, { MP_ROM_QSTR(MP_QSTR_ERROR_ACTIVE), MP_ROM_INT(TWAI_STATE_RUNNING) }, + { MP_ROM_QSTR(MP_QSTR_ERROR_WARNING), MP_ROM_INT(-1) }, + { MP_ROM_QSTR(MP_QSTR_ERROR_PASSIVE), MP_ROM_INT(-1) }, { MP_ROM_QSTR(MP_QSTR_BUS_OFF), MP_ROM_INT(TWAI_STATE_BUS_OFF) }, - { MP_ROM_QSTR(MP_QSTR_RECOVERING), MP_ROM_INT(TWAI_STATE_RECOVERING) }, + { MP_ROM_QSTR(MP_QSTR_RECOVERING), MP_ROM_INT(TWAI_STATE_RECOVERING) }, // esp32 specific + + // class CAN.MessageFlags + { MP_ROM_QSTR(MP_QSTR_RTR), MP_ROM_INT(RTR) }, + { MP_ROM_QSTR(MP_QSTR_EXTENDED_ID), MP_ROM_INT(EXTENDED_ID) }, + { MP_ROM_QSTR(MP_QSTR_FD_F), MP_ROM_INT(FD_F) }, + { MP_ROM_QSTR(MP_QSTR_BRS), MP_ROM_INT(BRS) }, + // class CAN.RecvErrors + { MP_ROM_QSTR(MP_QSTR_CRC), MP_ROM_INT(CRC) }, + { MP_ROM_QSTR(MP_QSTR_FORM), MP_ROM_INT(FORM) }, + { MP_ROM_QSTR(MP_QSTR_OVERRUN), MP_ROM_INT(OVERRUN) }, + { MP_ROM_QSTR(MP_QSTR_ESI), MP_ROM_INT(ESI) }, + // class CAN.SendErrors + { MP_ROM_QSTR(MP_QSTR_ARB), MP_ROM_INT(ARB) }, + { MP_ROM_QSTR(MP_QSTR_NACK), MP_ROM_INT(NACK) }, + { MP_ROM_QSTR(MP_QSTR_ERR), MP_ROM_INT(ERR) }, // CAN_FILTER_MODE { MP_ROM_QSTR(MP_QSTR_FILTER_RAW_SINGLE), MP_ROM_INT(FILTER_RAW_SINGLE) }, { MP_ROM_QSTR(MP_QSTR_FILTER_RAW_DUAL), MP_ROM_INT(FILTER_RAW_DUAL) }, { MP_ROM_QSTR(MP_QSTR_FILTER_ADDRESS), MP_ROM_INT(FILTER_ADDRESS) }, // CAN_ALERT + { MP_ROM_QSTR(MP_QSTR_ALERT_ALL), MP_ROM_INT(TWAI_ALERT_ALL) }, { MP_ROM_QSTR(MP_QSTR_ALERT_TX_IDLE), MP_ROM_INT(TWAI_ALERT_TX_IDLE) }, { MP_ROM_QSTR(MP_QSTR_ALERT_TX_SUCCESS), MP_ROM_INT(TWAI_ALERT_TX_SUCCESS) }, { MP_ROM_QSTR(MP_QSTR_ALERT_BELOW_ERR_WARN), MP_ROM_INT(TWAI_ALERT_BELOW_ERR_WARN) }, @@ -797,9 +897,9 @@ MP_DEFINE_CONST_OBJ_TYPE( machine_can_type, MP_QSTR_CAN, MP_TYPE_FLAG_NONE, - make_new, esp32_hw_can_make_new, - print, esp32_hw_can_print, + make_new, esp32_can_make_new, + print, esp32_can_print, locals_dict, (mp_obj_dict_t *)&esp32_can_locals_dict ); -#endif // MICROPY_HW_ENABLE_CAN +#endif // MICROPY_PY_MACHINE_CAN diff --git a/ports/esp32/machine_can.h b/ports/esp32/machine_can.h index f88adff0da8d9..752396130a613 100644 --- a/ports/esp32/machine_can.h +++ b/ports/esp32/machine_can.h @@ -23,36 +23,93 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -#ifndef MICROPY_INCLUDED_ESP32_CAN_H -#define MICROPY_INCLUDED_ESP32_CAN_H - +#ifndef MICROPY_INCLUDED_ESP32_MACHINE_CAN_H +#define MICROPY_INCLUDED_ESP32_MACHINE_CAN_H +/* #include "modmachine.h" #include "freertos/task.h" +#include "mpconfigport.h" #include "py/obj.h" +*/ +#if MICROPY_PY_MACHINE_CAN -#if MICROPY_HW_ENABLE_CAN +#define CAN_MODE_SILENT_LOOPBACK (0x10) -#define DEVICE_NAME "CAN" +typedef enum { + MODE_NORMAL = TWAI_MODE_NORMAL, + MODE_SLEEP = -1, + MODE_LOOPBACK = -2, // TWAI_MODE_NORMAL | CAN_MODE_SILENT_LOOPBACK, + MODE_SILENT = TWAI_MODE_NO_ACK, + MODE_SILENT_LOOPBACK = -3, + MODE_LISTEN_ONLY = TWAI_MODE_LISTEN_ONLY, // esp32 specific +} can_mode_t; -typedef enum _filter_mode_t { - FILTER_RAW_SINGLE = 0, +typedef enum { + FILTER_RAW_SINGLE = 1, FILTER_RAW_DUAL, FILTER_ADDRESS } filter_mode_t; -typedef struct _esp32_can_config_t { +typedef enum { + RX_STATE_FIFO_EMPTY = 1, + RX_STATE_MESSAGE_PENDING, + RX_STATE_FIFO_FULL, + RX_STATE_FIFO_OVERFLOW, +} rx_state_t; + +typedef enum { + NOT_INITIATED = TWAI_STATE_STOPPED - 1, + STOPPED = TWAI_STATE_STOPPED, + RUNNING = TWAI_STATE_RUNNING, + BUS_OFF = TWAI_STATE_BUS_OFF, + RECOVERING = TWAI_STATE_RECOVERING, +} state_t; + +typedef enum { + ERROR = -1, + /* + ERROR_ACTIVE = TWAI_ERROR_ACTIVE, + ERROR_WARNING = TWAI_ERROR_WARNING, + ERROR_PASSIVE = TWAI_ERROR_PASSIVE, + ERROR_BUS_OFF = TWAI_ERROR_BUS_OFF, + */ +} error_state_t; + + +typedef enum { + RTR = 1, + EXTENDED_ID, + FD_F, + BRS, +} message_flags_t; + +typedef enum { + CRC = 1, + FORM, + OVERRUN, + ESI, +} recv_errors_t; + +typedef enum { + ARB = 1, + NACK, + ERR, +} send_errors_t; + +typedef struct { twai_timing_config_t timing; twai_filter_config_t filter; twai_general_config_t general; - uint32_t baudrate; // bit/s + uint32_t bitrate; // bit/s bool initialized; } esp32_can_config_t; -typedef struct _esp32_can_obj_t { +typedef struct { mp_obj_base_t base; esp32_can_config_t *config; - mp_obj_t rxcallback; + mp_obj_t rx_callback; + mp_obj_t tx_callback; TaskHandle_t irq_handler; byte rx_state; bool extframe : 1; @@ -62,17 +119,12 @@ typedef struct _esp32_can_obj_t { uint16_t num_error_warning; // FIXME: populate this value somewhere uint16_t num_error_passive; uint16_t num_bus_off; + twai_handle_t handle; + twai_status_info_t status; } esp32_can_obj_t; -typedef enum _rx_state_t { - RX_STATE_FIFO_EMPTY = 0, - RX_STATE_MESSAGE_PENDING, - RX_STATE_FIFO_FULL, - RX_STATE_FIFO_OVERFLOW, -} rx_state_t; - extern const mp_obj_type_t machine_can_type; -#endif // MICROPY_HW_ENABLE_CAN +#endif // MICROPY_PY_MACHINE_CAN -#endif // MICROPY_INCLUDED_ESP32_CAN_H +#endif // MICROPY_INCLUDED_ESP32_MACHINE_CAN_H diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index 0c1b94d02d940..2d39a22d8057e 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -54,6 +54,7 @@ #define MICROPY_PY_MACHINE_EXTRA_GLOBALS \ { MP_ROM_QSTR(MP_QSTR_sleep), MP_ROM_PTR(&machine_lightsleep_obj) }, \ \ + { MP_ROM_QSTR(MP_QSTR_CAN), MP_ROM_PTR(&machine_can_type) }, \ { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, \ MICROPY_PY_MACHINE_SDCARD_ENTRY \ { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, \ diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h index 9a5e6a566eb83..b1e6c7bf42d7d 100644 --- a/ports/esp32/modmachine.h +++ b/ports/esp32/modmachine.h @@ -12,6 +12,7 @@ typedef enum { extern const mp_obj_type_t machine_touchpad_type; extern const mp_obj_type_t machine_dac_type; extern const mp_obj_type_t machine_sdcard_type; +extern const mp_obj_type_t machine_can_type; void machine_init(void); void machine_deinit(void); diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index b5b7d63a56333..5396cedcd6603 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -62,7 +62,7 @@ #define MICROPY_STACK_CHECK_MARGIN (1024) #define MICROPY_ENABLE_EMERGENCY_EXCEPTION_BUF (1) #define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) -#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NORMAL + 1) #define MICROPY_WARNINGS (1) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) #define MICROPY_STREAMS_POSIX_API (1) @@ -153,6 +153,7 @@ #define MICROPY_PY_MACHINE_UART_INCLUDEFILE "ports/esp32/machine_uart.c" #define MICROPY_PY_MACHINE_UART_SENDBREAK (1) #define MICROPY_PY_MACHINE_UART_IRQ (1) +#define MICROPY_PY_MACHINE_CAN (1) #define MICROPY_PY_MACHINE_WDT (1) #define MICROPY_PY_MACHINE_WDT_INCLUDEFILE "ports/esp32/machine_wdt.c" #define MICROPY_PY_NETWORK (1)