/* * This file is part of the MicroPython project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2024-2026 Angus Gratton * * 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 "py/objstr.h" #include "py/runtime.h" #if MICROPY_PY_MACHINE_CAN #include #include #include "extmod/modmachine.h" #include "extmod/machine_can.h" #include "extmod/machine_can_port.h" #ifndef MICROPY_HW_NUM_CAN #error "Port must provide a definition of MICROPY_HW_NUM_CAN" #endif #define CAN_MSG_RTR (1 << 0) #define CAN_MSG_EXTENDED_ID (1 << 1) #define CAN_MSG_FD_F (1 << 2) #define CAN_MSG_BRS (1 << 3) #define CAN_CLASSIC_MAX_LEN 8 // The port provides implementations of the static machine_can_port.h functions in this file. #include MICROPY_PY_MACHINE_CAN_INCLUDEFILE // Note: not all possible return values of flags() are allowed as triggers #define CAN_IRQ_ALLOWED_TRIGGERS (MP_CAN_IRQ_TX | MP_CAN_IRQ_RX | MP_CAN_IRQ_STATE) // Port can override any of these limits if necessary #ifndef CAN_TSEG1_MIN #define CAN_TSEG1_MIN 1 #endif #ifndef CAN_TSEG1_MAX #define CAN_TSEG1_MAX 16 #endif #ifndef CAN_TSEG2_MIN #define CAN_TSEG2_MIN 1 #endif #ifndef CAN_TSEG2_MAX #define CAN_TSEG2_MAX 8 #endif #ifndef CAN_SJW_MIN #define CAN_SJW_MIN 1 #endif #ifndef CAN_SJW_MAX #define CAN_SJW_MAX 4 #endif #if MICROPY_HW_ENABLE_FDCAN // CAN-FD BRS (Baud Rate Switch) default limits #ifndef CAN_FD_BRS_TSEG1_MIN #define CAN_FD_BRS_TSEG1_MIN 1 #endif #ifndef CAN_FD_BRS_TSEG1_MAX #define CAN_FD_BRS_TSEG1_MAX 32 #endif #ifndef CAN_FD_BRS_TSEG2_MIN #define CAN_FD_BRS_TSEG2_MIN 1 #endif #ifndef CAN_FD_BRS_TSEG2_MAX #define CAN_FD_BRS_TSEG2_MAX 16 #endif #ifndef CAN_FD_BRS_SJW_MIN #define CAN_FD_BRS_SJW_MIN 1 #endif #ifndef CAN_FD_BRS_SJW_MAX #define CAN_FD_BRS_SJW_MAX 16 #endif #endif // MICROPY_HW_ENABLE_FDCAN #ifndef CAN_FILTERS_STD_EXT_SEPARATE // Set if the hardware maintains separate filter indexes for Standard vs Extended IDs #define CAN_FILTERS_STD_EXT_SEPARATE 0 #endif #ifndef CAN_PORT_PRINT_FUNCTION // If this is set then the port should define its own function machine_can_print() #define CAN_PORT_PRINT_FUNCTION 0 #endif // Non port-specific functions follow // Calculate baud rate prescaler, and if necessary also find tseg1, tseg2 // to satisfy sample_point // static int calculate_brp(int bitrate_nom, int f_clock, int *tseg1, int *tseg2, int sample_point, bool is_fd_brs) { bool find_tseg = (*tseg1 == -1); // We need to calculate tseg1, tseg2 // Set min/max limits for Classic CAN int brp_min = CAN_BRP_MIN; int brp_max = CAN_BRP_MAX; int tseg1_min = CAN_TSEG1_MIN; int tseg1_max = CAN_TSEG1_MAX; int tseg2_min = CAN_TSEG2_MIN; int tseg2_max = CAN_TSEG2_MAX; #if MICROPY_HW_ENABLE_FDCAN // If CAN-FD controller, set min/max limits for CAN-FD BRS if (is_fd_brs) { brp_min = CAN_FD_BRS_BRP_MIN; brp_max = CAN_FD_BRS_BRP_MAX; tseg1_min = CAN_FD_BRS_TSEG1_MIN; tseg1_max = CAN_FD_BRS_TSEG1_MAX; tseg2_min = CAN_FD_BRS_TSEG2_MIN; tseg2_max = CAN_FD_BRS_TSEG2_MAX; } #else (void)is_fd_brs; #endif int brp_best = brp_min; int best_bitrate_err = bitrate_nom; // best case deviation from bitrate_nom, start at max int best_sample_err = 10000; // Units of .01%, start at max for (int brp = brp_max; brp >= brp_min; brp--) { unsigned scaled_clock = f_clock / brp; if (find_tseg) { // Find the total number of time quanta that gets closest to the nominal bitrate int ts_total = (scaled_clock + bitrate_nom / 2) / bitrate_nom; if (ts_total < tseg1_min + tseg2_min + 1 || ts_total > tseg1_max + tseg2_max + 1) { // The total time quanta doesn't fit in the allowed range continue; } int bitrate_err = abs((int)bitrate_nom - (int)(scaled_clock / ts_total)); if (bitrate_err > best_bitrate_err) { // This result is worse than our current best bitrate continue; } // Look for tseg1 & tseg2 that come closest to the sample point for (int ts1 = tseg1_min; ts1 <= tseg1_max; ts1++) { int ts2 = ts_total - 1 - ts1; if (ts2 >= tseg2_min && ts2 <= tseg2_max) { int try_sample = 10000 * (1 + ts1) / (1 + ts1 + ts2); // sample point, units of .01% int try_err = abs(try_sample - sample_point * 100); // Priorities for selecting the best: // 1. Smallest bitrate error. // 2. Smallest sample point error. // 3. Shorter (i.e. more total) time quanta (meaning if all else is equal, choose lower brp). if (bitrate_err < best_bitrate_err || try_err <= best_sample_err) { *tseg1 = ts1; *tseg2 = ts2; best_sample_err = try_err; brp_best = brp; best_bitrate_err = bitrate_err; } } } } else { // tseg1 and tseg2 already set, find brp with the lowest bitrate error int ts_total = *tseg1 + *tseg2 + 1; int bitrate_err = abs((int)bitrate_nom - (int)(scaled_clock / ts_total)); if (bitrate_err <= best_bitrate_err) { brp_best = brp; best_bitrate_err = bitrate_err; } } } if (best_bitrate_err == bitrate_nom) { // Didn't find any eligible bitrates mp_raise_ValueError(MP_ERROR_TEXT("unable to calculate BRP for baudrate")); } return brp_best; } // Check a CAN Message ID value is within the valid range, based on supplied flags static void id_range_check(mp_uint_t can_id, mp_uint_t flags) { mp_uint_t max = (flags & CAN_MSG_FLAG_EXT_ID) ? (1 << 29) : (1 << 11); if (can_id >= max) { mp_raise_ValueError(MP_ERROR_TEXT("invalid id")); } } static int machine_can_get_actual_bitrate(machine_can_obj_t *self) { int f_clock = machine_can_port_f_clock(self); return f_clock / self->brp / (1 + self->tseg1 + self->tseg2); } static mp_obj_t machine_can_deinit(mp_obj_t self_in) { machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); // Each CAN instance should have at most one peripheral object assert(self == MP_STATE_PORT(machine_can_objs)[self->can_idx]); self->mp_irq_trigger = 0; self->mp_irq_obj = NULL; if (self->port) { machine_can_update_irqs(self); machine_can_port_deinit(self); self->port = NULL; } return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(machine_can_deinit_obj, machine_can_deinit); static void machine_can_init_helper(machine_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_bitrate, ARG_mode, ARG_sample_point, ARG_sjw, ARG_tseg1, ARG_tseg2}; static const mp_arg_t allowed_args[] = { { MP_QSTR_bitrate, MP_ARG_INT | MP_ARG_REQUIRED }, { MP_QSTR_mode, MP_ARG_INT, {.u_int = MP_CAN_MODE_NORMAL} }, { MP_QSTR_sample_point, MP_ARG_INT, {.u_int = 75} }, { MP_QSTR_sjw, MP_ARG_INT, {.u_int = CAN_SJW_MIN } }, { MP_QSTR_tseg1, MP_ARG_INT, {.u_int = -1} }, { MP_QSTR_tseg2, MP_ARG_INT, {.u_int = -1} }, }; 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); // Verify arguments int bitrate_nom = args[ARG_bitrate].u_int; machine_can_mode_t mode = args[ARG_mode].u_int; if (mode >= MP_CAN_MODE_MAX || !machine_can_port_supports_mode(self, mode)) { mp_raise_ValueError(MP_ERROR_TEXT("mode")); } int sjw = args[ARG_sjw].u_int; if (sjw < CAN_SJW_MIN || sjw > CAN_SJW_MAX) { mp_raise_ValueError(MP_ERROR_TEXT("sjw")); } int tseg1 = args[ARG_tseg1].u_int; int tseg2 = args[ARG_tseg2].u_int; if ((tseg1 != -1 && (tseg1 < CAN_TSEG1_MIN || tseg1 > CAN_TSEG1_MAX)) || (tseg1 == -1 && tseg2 != -1)) { mp_raise_ValueError(MP_ERROR_TEXT("tseg1")); } if ((tseg2 != -1 && (tseg2 < CAN_TSEG2_MIN || tseg2 > CAN_TSEG2_MAX)) || (tseg2 == -1 && tseg1 != -1)) { mp_raise_ValueError(MP_ERROR_TEXT("tseg2")); } int sample_point = args[ARG_sample_point].u_int; if (sample_point <= 0 || sample_point >= 100) { // Probably can make these values more restrictive mp_raise_ValueError(MP_ERROR_TEXT("sample_point")); } int f_clock = machine_can_port_f_clock(self); // This function will also set tseg1 and tseg2 if they are -1 int brp = calculate_brp(bitrate_nom, f_clock, &tseg1, &tseg2, sample_point, false); // Set up the hardware self->tseg1 = tseg1; self->tseg2 = tseg2; self->brp = brp; self->sjw = sjw; self->mode = mode; memset(&self->counters, 0, sizeof(self->counters)); if (self->port != NULL) { machine_can_port_deinit(self); } machine_can_port_init(self); } // Raise an exception if the CAN controller is not initialised static void machine_can_check_initialised(machine_can_obj_t *self) { // Use self->port being allocated as indicator that controller is initialised if (self->port == NULL) { mp_raise_OSError(MP_EINVAL); } } mp_uint_t machine_can_get_index(mp_obj_t identifier) { mp_uint_t can_num; if (mp_obj_is_str(identifier)) { const char *port = mp_obj_str_get_str(identifier); if (0) { #ifdef MICROPY_HW_CAN1_NAME } else if (strcmp(port, MICROPY_HW_CAN1_NAME) == 0) { can_num = 1; #endif #ifdef MICROPY_HW_CAN2_NAME } else if (strcmp(port, MICROPY_HW_CAN2_NAME) == 0) { can_num = 2; #endif #ifdef MICROPY_HW_CAN3_NAME } else if (strcmp(port, MICROPY_HW_CAN3_NAME) == 0) { can_num = 3; #endif } else { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%s) doesn't exist"), port); } } else { can_num = mp_obj_get_int(identifier); } if (can_num < 1 || can_num > MICROPY_HW_NUM_CAN) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) doesn't exist"), can_num); } // check if the CAN is reserved for system use or not if (MICROPY_HW_CAN_IS_RESERVED(can_num)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) is reserved"), can_num); } return can_num - 1; } static mp_obj_t machine_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); // work out port mp_uint_t can_idx = machine_can_get_index(args[0]); machine_can_obj_t *self = MP_STATE_PORT(machine_can_objs)[can_idx]; if (self == NULL) { self = mp_obj_malloc(machine_can_obj_t, &machine_can_type); self->can_idx = can_idx; MP_STATE_PORT(machine_can_objs)[can_idx] = self; } mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); machine_can_init_helper(self, n_args, args, &kw_args); return MP_OBJ_FROM_PTR(self); } static mp_obj_t machine_can_init(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { machine_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); machine_can_init_helper(self, n_args, pos_args, kw_args); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_KW(machine_can_init_obj, 1, machine_can_init); void machine_can_deinit_all(void) { for (int i = 0; i < MICROPY_HW_NUM_CAN; i++) { mp_obj_t can = MP_OBJ_FROM_PTR(MP_STATE_PORT(machine_can_objs)[i]); if (can) { machine_can_deinit(can); MP_STATE_PORT(machine_can_objs)[i] = NULL; } } } static mp_uint_t can_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); machine_can_check_initialised(self); self->mp_irq_trigger = new_trigger; // Call into port layer to update hardware IRQ config machine_can_update_irqs(self); return 0; } static mp_uint_t can_irq_info(mp_obj_t self_in, mp_uint_t info_type) { machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); machine_can_check_initialised(self); if (info_type == MP_IRQ_INFO_FLAGS) { // Call into port layer to get current irq flags return machine_can_port_irq_flags(self); } else if (info_type == MP_IRQ_INFO_TRIGGERS) { return self->mp_irq_trigger; } return 0; } static const mp_irq_methods_t can_irq_methods = { .trigger = can_irq_trigger, .info = can_irq_info, }; static mp_obj_t mp_machine_can_irq(machine_can_obj_t *self, bool any_args, mp_arg_val_t *args) { machine_can_check_initialised(self); if (self->mp_irq_obj == NULL) { self->mp_irq_trigger = 0; self->mp_irq_obj = mp_irq_new(&can_irq_methods, MP_OBJ_FROM_PTR(self)); } if (any_args) { // TODO: refactor this into a helper to save some code size mp_uint_t trigger = args[MP_IRQ_ARG_INIT_trigger].u_int; mp_uint_t not_supported = trigger & ~CAN_IRQ_ALLOWED_TRIGGERS; if (not_supported) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("trigger 0x%08x unsupported"), not_supported); } mp_obj_t handler = args[MP_IRQ_ARG_INIT_handler].u_obj; if (handler != mp_const_none && !mp_obj_is_callable(handler)) { mp_raise_ValueError(MP_ERROR_TEXT("handler must be None or callable")); } self->mp_irq_obj->handler = handler; self->mp_irq_obj->ishard = args[MP_IRQ_ARG_INIT_hard].u_bool; can_irq_trigger(MP_OBJ_FROM_PTR(self), trigger); } return MP_OBJ_FROM_PTR(self->mp_irq_obj); } static mp_obj_t machine_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { machine_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); machine_can_check_initialised(self); enum { ARG_id, ARG_data, ARG_flags, /*ARG_context*/ }; static const mp_arg_t allowed_args[] = { { MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {} }, { MP_QSTR_data, MP_ARG_REQUIRED | MP_ARG_OBJ, {} }, { MP_QSTR_flags, MP_ARG_INT, {.u_int = 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); mp_uint_t can_id = args[ARG_id].u_int; mp_uint_t flags = args[ARG_flags].u_int; mp_buffer_info_t data; mp_get_buffer_raise(args[ARG_data].u_obj, &data, MP_BUFFER_READ); if (data.len > machine_can_port_max_data_len(flags)) { mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("data too long")); } id_range_check(can_id, flags); mp_int_t res = machine_can_port_send(self, can_id, data.buf, data.len, flags); return res >= 0 ? MP_OBJ_NEW_SMALL_INT(res) : mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_KW(machine_can_send_obj, 3, machine_can_send); static mp_obj_t machine_can_cancel_send(mp_obj_t self_in, mp_obj_t arg) { machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); machine_can_check_initialised(self); mp_uint_t idx = mp_obj_get_uint(arg); if (idx >= CAN_TX_QUEUE_LEN) { mp_raise_type(&mp_type_IndexError); } bool res = machine_can_port_cancel_send(self, mp_obj_get_uint(arg)); return mp_obj_new_bool(res); } static MP_DEFINE_CONST_FUN_OBJ_2(machine_can_cancel_send_obj, machine_can_cancel_send); static mp_obj_t machine_can_recv(size_t n_args, const mp_obj_t *args) { machine_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); machine_can_check_initialised(self); uint8_t data[MP_CAN_MAX_LEN]; size_t dlen = sizeof(data); mp_uint_t id = 0; mp_uint_t flags = 0; mp_uint_t errors = 0; mp_obj_array_t *data_memoryview; bool use_arg_as_result = n_args > 1 && args[1] != mp_const_none; mp_obj_list_t *result = mp_obj_list_optional_arg(use_arg_as_result ? args[1] : mp_const_none, RECV_ARG_LEN); mp_obj_t *items = result->items; // Validate the memoryview item if list passed in arg if (use_arg_as_result) { if (!mp_obj_is_type(items[RECV_ARG_DATA], &mp_type_memoryview)) { mp_raise_TypeError(NULL); } } // Call the port-specific receive function if (!machine_can_port_recv(self, data, &dlen, &id, &flags, &errors)) { return mp_const_none; // Nothing to return } // Create memoryview for the result list, if not passed in if (!use_arg_as_result) { // Make the result memoryview large enough for the entire result buffer, not the recv result // ... it will be resized further down // (Potentially wasteful, but means the result can be safely reused as arg to a subsequent call) void *backing_buf = m_malloc(MP_CAN_MAX_LEN); items[RECV_ARG_DATA] = mp_obj_new_memoryview('B', MP_CAN_MAX_LEN, backing_buf); } // TODO: length check any memoryview 'result' that was passed in as arg? // (may not be possible as I don't think memoryview records the size of its backing buffer) data_memoryview = MP_OBJ_TO_PTR(items[RECV_ARG_DATA]); // type of obj was checked already if ((flags & CAN_MSG_FLAG_RTR) == 0) { memcpy(data_memoryview->items, data, dlen); } else { // Remote request, return a memoryview of 0s with correct length memset(data_memoryview->items, 0, dlen); } data_memoryview->len = dlen; items[RECV_ARG_ID] = MP_OBJ_NEW_SMALL_INT(id); items[RECV_ARG_FLAGS] = MP_OBJ_NEW_SMALL_INT(flags); items[RECV_ARG_ERRORS] = MP_OBJ_NEW_SMALL_INT(errors); return MP_OBJ_FROM_PTR(result); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_can_recv_obj, 1, 2, machine_can_recv); static mp_obj_t machine_can_set_filters(mp_obj_t self_in, mp_obj_t filters) { machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); machine_can_check_initialised(self); mp_obj_iter_buf_t iter_buf; int idx = 0; #if CAN_FILTERS_STD_EXT_SEPARATE int idx_ext = 0; #endif mp_obj_t filter; machine_can_port_clear_filters(self); if (filters == mp_const_none) { // Passing 'None' is shortcut for "allow all" - pass standard IDs // via filter 0 and extended IDs via filter 1. machine_can_port_set_filter(self, 0, 0, 0, 0); machine_can_port_set_filter(self, 1, 0, 0, CAN_MSG_FLAG_EXT_ID); } else { // Walk the iterable argument and call machine_can_port_set_filter() // for each item filters = mp_getiter(filters, &iter_buf); while ((filter = mp_iternext(filters)) != MP_OBJ_STOP_ITERATION) { mp_obj_t *args; size_t arg_len; mp_obj_get_array(filter, &arg_len, &args); if (arg_len != 3) { mp_raise_ValueError(MP_ERROR_TEXT("filter items must have length 3")); } mp_uint_t id = mp_obj_get_int(args[0]); mp_uint_t mask = mp_obj_get_int(args[1]); mp_uint_t flags = mp_obj_get_int(args[2]); id_range_check(id, flags); id_range_check(mask, flags); // This function is expected to raise an exception if filter cannot be set machine_can_port_set_filter(self, #if CAN_FILTERS_STD_EXT_SEPARATE (flags & CAN_MSG_FLAG_EXT_ID) ? idx_ext++ : idx++, #else idx++, #endif id, mask, flags ); } } return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_2(machine_can_set_filters_obj, machine_can_set_filters); static mp_obj_t machine_can_state(mp_obj_t self_in) { machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->port == NULL) { return MP_OBJ_NEW_SMALL_INT(MP_CAN_STATE_STOPPED); } return MP_OBJ_NEW_SMALL_INT(machine_can_port_get_state(self)); } static MP_DEFINE_CONST_FUN_OBJ_1(machine_can_state_obj, machine_can_state); static mp_obj_t machine_can_get_counters(size_t n_args, const mp_obj_t *args) { machine_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); machine_can_counters_t *counters = &self->counters; mp_obj_list_t *list = mp_obj_list_optional_arg(n_args > 1 ? args[1] : mp_const_none, 8); machine_can_check_initialised(self); machine_can_port_update_counters(self); // Note: the members of 'counters' are laid out in the same order, // so compiler should be able to infer some kind of loop here... list->items[0] = MP_OBJ_NEW_SMALL_INT(counters->tec); list->items[1] = MP_OBJ_NEW_SMALL_INT(counters->rec); list->items[2] = MP_OBJ_NEW_SMALL_INT(counters->num_warning); list->items[3] = MP_OBJ_NEW_SMALL_INT(counters->num_passive); list->items[4] = MP_OBJ_NEW_SMALL_INT(counters->num_bus_off); list->items[5] = MP_OBJ_NEW_SMALL_INT(counters->tx_pending); list->items[6] = MP_OBJ_NEW_SMALL_INT(counters->rx_pending); list->items[7] = MP_OBJ_NEW_SMALL_INT(counters->rx_overruns); return MP_OBJ_FROM_PTR(list); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_can_get_counters_obj, 1, 2, machine_can_get_counters); static mp_obj_t machine_can_get_timings(size_t n_args, const mp_obj_t *args) { machine_can_obj_t *self = MP_OBJ_TO_PTR(args[0]); mp_obj_list_t *list = mp_obj_list_optional_arg(n_args > 1 ? args[1] : mp_const_none, 6); list->items[0] = MP_OBJ_NEW_SMALL_INT(machine_can_get_actual_bitrate(self)); list->items[1] = MP_OBJ_NEW_SMALL_INT(self->sjw); list->items[2] = MP_OBJ_NEW_SMALL_INT(self->tseg1); list->items[3] = MP_OBJ_NEW_SMALL_INT(self->tseg2); #if MICROPY_HW_ENABLE_FDCAN mp_obj_list_t *fd_list = mp_obj_list_optional_arg(list->items[4], 4); fd_list->items[0] = mp_const_none; // TODO: CAN-FD timings support fd_list->items[1] = mp_const_none; fd_list->items[2] = mp_const_none; fd_list->items[3] = mp_const_none; list->items[4] = MP_OBJ_FROM_PTR(fd_list); #else list->items[4] = mp_const_none; #endif list->items[5] = machine_can_port_get_additional_timings(self, n_args > 1 ? list->items[5] : mp_const_none); return MP_OBJ_FROM_PTR(list); } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_can_get_timings_obj, 1, 2, machine_can_get_timings); static mp_obj_t machine_can_restart(mp_obj_t self_in) { machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); machine_can_check_initialised(self); machine_can_port_restart(self); memset(&self->counters, 0, sizeof(self->counters)); return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(machine_can_restart_obj, machine_can_restart); #if !CAN_PORT_PRINT_FUNCTION static void machine_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_can_obj_t *self = MP_OBJ_TO_PTR(self_in); // We don't store the bitrate argument, instead print the real achieved bitrate int f_clock = machine_can_port_f_clock(self); int actual_bitrate = machine_can_get_actual_bitrate(self); qstr mode; switch (self->mode) { case MP_CAN_MODE_NORMAL: mode = MP_QSTR_MODE_NORMAL; break; case MP_CAN_MODE_SLEEP: mode = MP_QSTR_MODE_SLEEP; break; case MP_CAN_MODE_LOOPBACK: mode = MP_QSTR_MODE_LOOPBACK; break; case MP_CAN_MODE_SILENT: mode = MP_QSTR_MODE_SILENT; break; case MP_CAN_MODE_SILENT_LOOPBACK: default: mode = MP_QSTR_MODE_SILENT_LOOPBACK; break; } mp_printf(print, "CAN(%d, bitrate=%u, mode=CAN.%q, sjw=%u, tseg1=%u, tseg2=%u, f_clock=%u)", self->can_idx + 1, actual_bitrate, mode, self->sjw, self->tseg1, self->tseg2, f_clock); } #endif // CAN.irq(handler, trigger, hard) static mp_obj_t machine_can_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { mp_arg_val_t args[MP_IRQ_ARG_INIT_NUM_ARGS]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_IRQ_ARG_INIT_NUM_ARGS, mp_irq_init_args, args); machine_can_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); bool any_args = n_args > 1 || kw_args->used != 0; return MP_OBJ_FROM_PTR(mp_machine_can_irq(self, any_args, args)); } static MP_DEFINE_CONST_FUN_OBJ_KW(machine_can_irq_obj, 1, machine_can_irq); static const mp_rom_map_elem_t machine_can_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_can_init_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_can_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_can_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&machine_can_send_obj) }, { MP_ROM_QSTR(MP_QSTR_cancel_send), MP_ROM_PTR(&machine_can_cancel_send_obj) }, { MP_ROM_QSTR(MP_QSTR_recv), MP_ROM_PTR(&machine_can_recv_obj) }, { MP_ROM_QSTR(MP_QSTR_set_filters), MP_ROM_PTR(&machine_can_set_filters_obj) }, { MP_ROM_QSTR(MP_QSTR_state), MP_ROM_PTR(&machine_can_state_obj) }, { MP_ROM_QSTR(MP_QSTR_get_counters), MP_ROM_PTR(&machine_can_get_counters_obj) }, { MP_ROM_QSTR(MP_QSTR_get_timings), MP_ROM_PTR(&machine_can_get_timings_obj) }, { MP_ROM_QSTR(MP_QSTR_restart), MP_ROM_PTR(&machine_can_restart_obj) }, // Mode enum constants { MP_ROM_QSTR(MP_QSTR_MODE_NORMAL), MP_ROM_INT(MP_CAN_MODE_NORMAL) }, { MP_ROM_QSTR(MP_QSTR_MODE_SLEEP), MP_ROM_INT(MP_CAN_MODE_SLEEP) }, { MP_ROM_QSTR(MP_QSTR_MODE_LOOPBACK), MP_ROM_INT(MP_CAN_MODE_LOOPBACK) }, { MP_ROM_QSTR(MP_QSTR_MODE_SILENT), MP_ROM_INT(MP_CAN_MODE_SILENT) }, { MP_ROM_QSTR(MP_QSTR_MODE_SILENT_LOOPBACK), MP_ROM_INT(MP_CAN_MODE_SILENT_LOOPBACK) }, // State enum constants { MP_ROM_QSTR(MP_QSTR_STATE_STOPPED), MP_ROM_INT(MP_CAN_STATE_STOPPED) }, { MP_ROM_QSTR(MP_QSTR_STATE_ACTIVE), MP_ROM_INT(MP_CAN_STATE_ACTIVE) }, { MP_ROM_QSTR(MP_QSTR_STATE_WARNING), MP_ROM_INT(MP_CAN_STATE_WARNING) }, { MP_ROM_QSTR(MP_QSTR_STATE_PASSIVE), MP_ROM_INT(MP_CAN_STATE_PASSIVE) }, { MP_ROM_QSTR(MP_QSTR_STATE_BUS_OFF), MP_ROM_INT(MP_CAN_STATE_BUS_OFF) }, // Message Flag enum constants { MP_ROM_QSTR(MP_QSTR_FLAG_RTR), MP_ROM_INT(CAN_MSG_FLAG_RTR) }, { MP_ROM_QSTR(MP_QSTR_FLAG_EXT_ID), MP_ROM_INT(CAN_MSG_FLAG_EXT_ID) }, { MP_ROM_QSTR(MP_QSTR_FLAG_UNORDERED), MP_ROM_INT(CAN_MSG_FLAG_UNORDERED) }, // Receive Error Flag enum constants { MP_ROM_QSTR(MP_QSTR_RECV_ERR_FULL), MP_ROM_INT(CAN_RECV_ERR_FULL) }, { MP_ROM_QSTR(MP_QSTR_RECV_ERR_OVERRUN), MP_ROM_INT(CAN_RECV_ERR_OVERRUN) }, // IRQ enum constants { MP_ROM_QSTR(MP_QSTR_IRQ_RX), MP_ROM_INT(MP_CAN_IRQ_RX) }, { MP_ROM_QSTR(MP_QSTR_IRQ_TX), MP_ROM_INT(MP_CAN_IRQ_TX) }, { MP_ROM_QSTR(MP_QSTR_IRQ_STATE), MP_ROM_INT(MP_CAN_IRQ_STATE) }, { MP_ROM_QSTR(MP_QSTR_IRQ_TX_FAILED), MP_ROM_INT(MP_CAN_IRQ_TX_FAILED) }, { MP_ROM_QSTR(MP_QSTR_IRQ_TX_IDX_SHIFT), MP_ROM_INT(MP_CAN_IRQ_IDX_SHIFT) }, { MP_ROM_QSTR(MP_QSTR_IRQ_TX_IDX_MASK), MP_ROM_INT(MP_CAN_IRQ_IDX_MASK) }, // Other constants { MP_ROM_QSTR(MP_QSTR_TX_QUEUE_LEN), MP_ROM_INT(CAN_TX_QUEUE_LEN) }, { MP_ROM_QSTR(MP_QSTR_FILTERS_MAX), MP_ROM_INT(CAN_HW_MAX_FILTER) }, }; static MP_DEFINE_CONST_DICT(machine_can_locals_dict, machine_can_locals_dict_table); MP_DEFINE_CONST_OBJ_TYPE( machine_can_type, MP_QSTR_CAN, 0, make_new, machine_can_make_new, print, machine_can_print, locals_dict, &machine_can_locals_dict ); MP_REGISTER_ROOT_POINTER(struct _machine_can_obj_t *machine_can_objs[MICROPY_HW_NUM_CAN]); #endif // MICROPY_PY_MACHINE_CAN