mirror of
https://github.com/micropython/micropython.git
synced 2026-04-30 21:00:12 +02:00
022570445b
API is different to the original machine.CAN proposal, as numerous shortcomings were found during initial implementation. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
750 lines
28 KiB
C
750 lines
28 KiB
C
/*
|
|
* 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 <stdlib.h>
|
|
#include <string.h>
|
|
#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
|