mirror of
https://github.com/micropython/micropython.git
synced 2026-04-30 21:00:12 +02:00
6cac2d275d
JavaScript code lint and formatting with Biome / eslint (push) Has been cancelled
Check code formatting / code-formatting (push) Has been cancelled
Check spelling with codespell / codespell (push) Has been cancelled
Build docs / build (push) Has been cancelled
Check examples / embedding (push) Has been cancelled
Package mpremote / build (push) Has been cancelled
.mpy file format and tools / test (push) Has been cancelled
Build ports metadata / build (push) Has been cancelled
alif port / build_alif (alif_ae3_build) (push) Has been cancelled
cc3200 port / build (push) Has been cancelled
esp32 port / build_idf (esp32_build_c2_c5_c6) (push) Has been cancelled
esp32 port / build_idf (esp32_build_cmod_spiram_s2) (push) Has been cancelled
esp32 port / build_idf (esp32_build_p4) (push) Has been cancelled
esp32 port / build_idf (esp32_build_s3_c3) (push) Has been cancelled
esp8266 port / build (push) Has been cancelled
mimxrt port / build (push) Has been cancelled
nrf port / build (push) Has been cancelled
powerpc port / build (push) Has been cancelled
qemu port / build_and_test_arm (bigendian) (push) Has been cancelled
qemu port / build_and_test_arm (sabrelite) (push) Has been cancelled
qemu port / build_and_test_arm (thumb_hardfp) (push) Has been cancelled
qemu port / build_and_test_arm (thumb_softfp) (push) Has been cancelled
qemu port / build_and_test_rv32 (push) Has been cancelled
qemu port / build_and_test_rv64 (push) Has been cancelled
renesas-ra port / build_renesas_ra_board (push) Has been cancelled
rp2 port / build (push) Has been cancelled
samd port / build (push) Has been cancelled
stm32 port / build_stm32 (stm32_misc_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_nucleo_build) (push) Has been cancelled
stm32 port / build_stm32 (stm32_pyb_build) (push) Has been cancelled
unix port / minimal (push) Has been cancelled
unix port / reproducible (push) Has been cancelled
unix port / standard (push) Has been cancelled
unix port / standard_v2 (push) Has been cancelled
unix port / coverage (push) Has been cancelled
unix port / coverage_32bit (push) Has been cancelled
unix port / nanbox (push) Has been cancelled
unix port / longlong (push) Has been cancelled
unix port / float (push) Has been cancelled
unix port / gil_enabled (push) Has been cancelled
unix port / stackless_clang (push) Has been cancelled
unix port / float_clang (push) Has been cancelled
unix port / settrace_stackless (push) Has been cancelled
unix port / repr_b (push) Has been cancelled
unix port / macos (push) Has been cancelled
unix port / qemu_mips (push) Has been cancelled
unix port / qemu_arm (push) Has been cancelled
unix port / qemu_riscv64 (push) Has been cancelled
unix port / sanitize_address (push) Has been cancelled
unix port / sanitize_undefined (push) Has been cancelled
webassembly port / build (push) Has been cancelled
windows port / build-vs (Debug, true, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, true, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Debug, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Debug, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x64, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, dev, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, dev, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, standard, 2017, [15, 16)) (push) Has been cancelled
windows port / build-vs (Release, true, x86, standard, 2019, [16, 17)) (push) Has been cancelled
windows port / build-vs (Release, x64, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x64, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, dev, 2022, [17, 18)) (push) Has been cancelled
windows port / build-vs (Release, x86, standard, 2022, [17, 18)) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, dev) (push) Has been cancelled
windows port / build-mingw (i686, mingw32, standard) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, dev) (push) Has been cancelled
windows port / build-mingw (x86_64, mingw64, standard) (push) Has been cancelled
windows port / cross-build-on-linux (push) Has been cancelled
zephyr port / build (push) Has been cancelled
Python code lint and formatting with ruff / ruff (push) Has been cancelled
Implemented according to API docs in a parent comment. Adds new multi_extmod/machine_can_* tests which pass when testing between NUCLEO_G474RE, NUCLEO_H723ZG and PYBDV11. This work was mostly funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
490 lines
18 KiB
C
490 lines
18 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.
|
|
*/
|
|
|
|
// This file is never compiled standalone, it's included directly from
|
|
// extmod/machine_can.c via MICROPY_PY_MACHINE_CAN_INCLUDEFILE.
|
|
#include <stdbool.h>
|
|
#include "extmod/machine_can_port.h"
|
|
#include "can.h"
|
|
#include "py/runtime.h"
|
|
#include "py/mperrno.h"
|
|
#include "py/mphal.h"
|
|
#include "py/gc.h"
|
|
|
|
#if MICROPY_HW_ENABLE_FDCAN
|
|
#define CAN_BRP_MIN 1
|
|
#define CAN_BRP_MAX 512
|
|
#define CAN_FD_BRS_BRP_MIN 1
|
|
#define CAN_FD_BRS_BRP_MAX 32
|
|
#define CAN_FILTERS_STD_EXT_SEPARATE 1
|
|
|
|
#else // Classic bxCAN
|
|
#define CAN_BRP_MIN 1
|
|
#define CAN_BRP_MAX 1024
|
|
#define CAN_FILTERS_STD_EXT_SEPARATE 0
|
|
#endif
|
|
|
|
#define TX_EMPTY UINT32_MAX
|
|
|
|
struct machine_can_port {
|
|
CAN_HandleTypeDef h;
|
|
uint32_t tx[CAN_TX_QUEUE_LEN]; // ID stored in each hardware tx buffer, or TX_EMPTY if empty
|
|
bool irq_state_pending;
|
|
bool error_passive;
|
|
};
|
|
|
|
// Convert the port agnostic CAN mode to the ST mode
|
|
static uint32_t can_port_mode(machine_can_mode_t mode) {
|
|
switch (mode) {
|
|
case MP_CAN_MODE_NORMAL:
|
|
return CAN_MODE_NORMAL;
|
|
case MP_CAN_MODE_SLEEP:
|
|
return CAN_MODE_SILENT; // Sleep is not an operating mode for ST's peripheral
|
|
case MP_CAN_MODE_LOOPBACK:
|
|
return CAN_MODE_LOOPBACK;
|
|
case MP_CAN_MODE_SILENT:
|
|
return CAN_MODE_SILENT;
|
|
case MP_CAN_MODE_SILENT_LOOPBACK:
|
|
return CAN_MODE_SILENT_LOOPBACK;
|
|
default:
|
|
assert(0); // Mode should have been checked already
|
|
return CAN_MODE_NORMAL;
|
|
}
|
|
}
|
|
|
|
static int machine_can_port_f_clock(const machine_can_obj_t *self) {
|
|
return (int)can_get_source_freq();
|
|
}
|
|
|
|
static bool machine_can_port_supports_mode(const machine_can_obj_t *self, machine_can_mode_t mode) {
|
|
return mode < MP_CAN_MODE_MAX;
|
|
}
|
|
|
|
static mp_uint_t machine_can_port_max_data_len(mp_uint_t flags) {
|
|
#if MICROPY_HW_ENABLE_FDCAN
|
|
if (flags & CAN_MSG_FLAG_FD_F) {
|
|
return 64;
|
|
}
|
|
#endif
|
|
return 8;
|
|
}
|
|
|
|
static void machine_can_port_init(machine_can_obj_t *self) {
|
|
if (!self->port) {
|
|
self->port = m_new(struct machine_can_port, 1);
|
|
}
|
|
memset(self->port, 0, sizeof(struct machine_can_port));
|
|
for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) {
|
|
self->port->tx[i] = TX_EMPTY;
|
|
}
|
|
|
|
bool res = can_init(&self->port->h,
|
|
self->can_idx + 1, // Convert 0-based index to 1-based 'can_id' for lower layer
|
|
CAN_TX_QUEUE,
|
|
can_port_mode(self->mode),
|
|
self->brp,
|
|
self->sjw,
|
|
self->tseg1,
|
|
self->tseg2,
|
|
false); // auto_restart not currently exposed
|
|
|
|
if (!res) {
|
|
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("CAN init failed"));
|
|
}
|
|
}
|
|
|
|
static void machine_can_port_cancel_all_tx(machine_can_obj_t *self) {
|
|
struct machine_can_port *port = self->port;
|
|
can_disable_tx_interrupts(&port->h);
|
|
for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) {
|
|
can_cancel_transmit(&port->h, i);
|
|
port->tx[i] = TX_EMPTY;
|
|
}
|
|
}
|
|
|
|
static void machine_can_port_deinit(machine_can_obj_t *self) {
|
|
machine_can_port_cancel_all_tx(self);
|
|
can_deinit(&self->port->h);
|
|
}
|
|
|
|
static mp_int_t machine_can_port_send(machine_can_obj_t *self, mp_uint_t id, const byte *data, size_t data_len, mp_uint_t flags) {
|
|
int idx_empty = -1; // Empty transmit buffer, where no later index has the same ID message in it
|
|
|
|
// Scan through the current transmit queue to find an eligible buffer for transmit
|
|
for (int i = 0; i < CAN_TX_QUEUE_LEN; i++) {
|
|
uint32_t tx_id = self->port->tx[i];
|
|
if (tx_id == TX_EMPTY) {
|
|
// This slot is empty
|
|
if (idx_empty == -1) {
|
|
// Still have to keep scanning as we might see a later message with the same ID,
|
|
idx_empty = i;
|
|
}
|
|
} else if (tx_id == id && !(flags & CAN_MSG_FLAG_UNORDERED)) {
|
|
// Can't queue a second message with the same ID and guarantee order
|
|
|
|
// (Undocumented hardware limitation - CANFD reference suggests
|
|
// messages with the same ID are sent in buffer index order but
|
|
// testing shows not always the case at least on STM32H7! Unsure if
|
|
// also a limitation of bxCAN or STM32G4, but these only have 3 TX
|
|
// buffers so inserting in buffer index order is likely to run out
|
|
// of buffers relatively quickly anyway...)
|
|
|
|
// Note: currently the driver considers a Standard and an Extended
|
|
// ID with the same numeric value to be the same ID... could fix
|
|
// this, although it's a relatively uncommon case.
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (idx_empty == -1) {
|
|
// No space in transmit queue
|
|
return -1;
|
|
}
|
|
|
|
CanTxMsgTypeDef tx = {
|
|
#if MICROPY_HW_ENABLE_FDCAN
|
|
.MessageMarker = 0,
|
|
.ErrorStateIndicator = FDCAN_ESI_ACTIVE,
|
|
.TxEventFifoControl = FDCAN_NO_TX_EVENTS,
|
|
.Identifier = id, // Range checked by caller
|
|
.IdType = (flags & CAN_MSG_FLAG_EXT_ID) ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID,
|
|
.TxFrameType = (flags & CAN_MSG_FLAG_RTR) ? FDCAN_REMOTE_FRAME : FDCAN_DATA_FRAME,
|
|
.FDFormat = (flags & CAN_MSG_FLAG_FD_F) ? FDCAN_FD_CAN : FDCAN_CLASSIC_CAN,
|
|
.BitRateSwitch = (flags & CAN_MSG_FLAG_BRS) ? FDCAN_BRS_ON : FDCAN_BRS_OFF,
|
|
.DataLength = data_len, // Converted inside can_transmit_buf_index
|
|
#else // Classic
|
|
.StdId = (flags & CAN_MSG_FLAG_EXT_ID) ? 0 : id,
|
|
.ExtId = (flags & CAN_MSG_FLAG_EXT_ID) ? id : 0,
|
|
.IDE = (flags & CAN_MSG_FLAG_EXT_ID) ? CAN_ID_EXT : CAN_ID_STD,
|
|
.RTR = (flags & CAN_MSG_FLAG_RTR) ? CAN_RTR_REMOTE : CAN_RTR_DATA,
|
|
.DLC = data_len,
|
|
#endif
|
|
};
|
|
#if !MICROPY_HW_ENABLE_FDCAN
|
|
assert(data_len <= sizeof(tx.Data)); // Also checked by caller
|
|
memcpy(tx.Data, data, data_len);
|
|
#endif
|
|
|
|
HAL_StatusTypeDef err = can_transmit_buf_index(&self->port->h, idx_empty, &tx, data);
|
|
if (err != HAL_OK) {
|
|
return -1;
|
|
}
|
|
self->port->tx[idx_empty] = id;
|
|
|
|
return idx_empty;
|
|
}
|
|
|
|
static bool machine_can_port_cancel_send(machine_can_obj_t *self, mp_uint_t idx) {
|
|
return can_cancel_transmit(&self->port->h, idx);
|
|
}
|
|
|
|
static bool machine_can_port_recv(machine_can_obj_t *self, void *data, size_t *dlen, mp_uint_t *id, mp_uint_t *flags, mp_uint_t *errors) {
|
|
CAN_HandleTypeDef *can = &self->port->h;
|
|
CanRxMsgTypeDef msg;
|
|
|
|
for (can_rx_fifo_t fifo = CAN_RX_FIFO0; fifo <= CAN_RX_FIFO1; fifo++) {
|
|
if (can_receive(can, fifo, &msg, data, 0) == 0) {
|
|
// CanRxMsgTypeDef is different for Classic vs FD
|
|
#if MICROPY_HW_ENABLE_FDCAN
|
|
*flags = ((msg.IdType == FDCAN_EXTENDED_ID) ? CAN_MSG_FLAG_EXT_ID : 0) |
|
|
((msg.RxFrameType == FDCAN_REMOTE_FRAME) ? CAN_MSG_FLAG_RTR : 0);
|
|
*id = msg.Identifier;
|
|
*dlen = msg.DataLength; // Lower layer has converted to bytes already
|
|
#else
|
|
*flags = (msg.IDE ? CAN_MSG_FLAG_EXT_ID : 0) |
|
|
(msg.RTR ? CAN_MSG_FLAG_RTR : 0);
|
|
*id = msg.IDE ? msg.ExtId : msg.StdId;
|
|
*dlen = msg.DLC;
|
|
#endif
|
|
|
|
*errors = self->rx_error_flags;
|
|
self->rx_error_flags = 0;
|
|
|
|
// Re-enable any interrupts that were disabled in RX IRQ handlers
|
|
can_enable_rx_interrupts(can, fifo, self->mp_irq_trigger & MP_CAN_IRQ_RX);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void machine_can_update_irqs(machine_can_obj_t *self) {
|
|
uint16_t triggers = self->mp_irq_trigger;
|
|
|
|
for (can_rx_fifo_t fifo = CAN_RX_FIFO0; fifo <= CAN_RX_FIFO1; fifo++) {
|
|
if (triggers & MP_CAN_IRQ_RX) {
|
|
can_enable_rx_interrupts(&self->port->h, fifo, true);
|
|
} else {
|
|
can_disable_rx_interrupts(&self->port->h, fifo);
|
|
}
|
|
}
|
|
|
|
// Note: TX complete interrupt is always enabled to manage the internal queue state
|
|
}
|
|
|
|
static void machine_can_port_clear_filters(machine_can_obj_t *self) {
|
|
#if MICROPY_HW_ENABLE_FDCAN
|
|
for (int f = 0; f < CAN_HW_MAX_STD_FILTER; f++) {
|
|
can_clearfilter(&self->port->h, f, false);
|
|
}
|
|
for (int f = 0; f < CAN_HW_MAX_EXT_FILTER; f++) {
|
|
can_clearfilter(&self->port->h, f, true);
|
|
}
|
|
#else
|
|
int bank_offs = (self->can_idx == 1) ? CAN_HW_MAX_FILTER : 0; // CAN2 filters index after CAN1
|
|
for (int f = 0; f < CAN_HW_MAX_FILTER; f++) {
|
|
can_clearfilter(&self->port->h, f + bank_offs, CAN_HW_MAX_FILTER);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#if MICROPY_HW_ENABLE_FDCAN
|
|
static void machine_can_port_set_filter(machine_can_obj_t *self, int filter_idx, mp_uint_t can_id, mp_uint_t mask, mp_uint_t flags) {
|
|
int max_idx = (flags & CAN_MSG_FLAG_EXT_ID) ? CAN_HW_MAX_EXT_FILTER : CAN_HW_MAX_STD_FILTER;
|
|
if (filter_idx >= max_idx) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("too many filters for this ID type"));
|
|
}
|
|
if (flags & ~CAN_MSG_FLAG_EXT_ID) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("flags")); // Only supported flag is for extended ID
|
|
}
|
|
|
|
FDCAN_FilterTypeDef filter = {
|
|
.IdType = (flags & CAN_MSG_FLAG_EXT_ID) ? FDCAN_EXTENDED_ID : FDCAN_STANDARD_ID,
|
|
// FDCAN counts standard and extended id filters separately, but this is
|
|
// already accounted for in filter_idx due to CAN_FILTERS_STD_EXT_SEPARATE.
|
|
.FilterIndex = filter_idx,
|
|
.FilterType = FDCAN_FILTER_MASK,
|
|
// Round-robin between FIFO1 and FIFO0
|
|
.FilterConfig = (filter_idx & 1) ? FDCAN_FILTER_TO_RXFIFO1 : FDCAN_FILTER_TO_RXFIFO0,
|
|
.FilterID1 = can_id,
|
|
.FilterID2 = mask,
|
|
};
|
|
|
|
int r = HAL_FDCAN_ConfigFilter(&self->port->h, &filter);
|
|
assert(r == HAL_OK);
|
|
(void)r;
|
|
}
|
|
#else
|
|
static void machine_can_port_set_filter(machine_can_obj_t *self, int filter_idx, mp_uint_t can_id, mp_uint_t mask, mp_uint_t flags) {
|
|
if (filter_idx >= CAN_HW_MAX_FILTER) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("too many filters"));
|
|
}
|
|
if (flags & ~CAN_MSG_FLAG_EXT_ID) {
|
|
mp_raise_ValueError(MP_ERROR_TEXT("flags")); // Only supported flag is for extended ID
|
|
}
|
|
|
|
if (self->can_idx == 1) {
|
|
filter_idx += CAN_HW_MAX_FILTER; // CAN2 filters index after CAN1
|
|
}
|
|
|
|
CAN_FilterConfTypeDef filter = {
|
|
.FilterActivation = ENABLE,
|
|
.FilterScale = CAN_FILTERSCALE_32BIT,
|
|
.FilterMode = CAN_FILTERMODE_IDMASK,
|
|
.FilterNumber = filter_idx,
|
|
// Apply the filters round-robin to each FIFO, as each filter in bxCAN is
|
|
// associated with only one FIFO.
|
|
.FilterFIFOAssignment = filter_idx % 2,
|
|
.BankNumber = CAN_HW_MAX_FILTER, // Assign same number of filters to CAN2 as CAN1
|
|
};
|
|
|
|
// This somewhat corresponds to STM32 RM Figure 342 "Filter bank scale
|
|
// configuration", although the Reference Manual makes 32-bit mask filters look
|
|
// a lot more complex than they are, then the ST HAL makes it even more
|
|
// complex by only supporting filter configuration via 16-bit halfwords
|
|
// which are re-assembled to full words inside the HAL...
|
|
if (flags & CAN_MSG_FLAG_EXT_ID) {
|
|
filter.FilterIdLow = (can_id << 3) | CAN_ID_EXT;
|
|
filter.FilterIdHigh = can_id >> 13;
|
|
filter.FilterMaskIdLow = (mask << 3) | CAN_ID_EXT;
|
|
filter.FilterMaskIdHigh = mask >> 13;
|
|
} else {
|
|
filter.FilterIdLow = 0;
|
|
filter.FilterIdHigh = can_id << 5;
|
|
filter.FilterMaskIdLow = CAN_ID_EXT; // Set to require CAN_ID_EXT unset in message
|
|
filter.FilterMaskIdHigh = mask << 5;
|
|
}
|
|
|
|
int r = HAL_CAN_ConfigFilter(&self->port->h, &filter);
|
|
assert(r == HAL_OK); // Params should be verified before passing to HAL
|
|
(void)r;
|
|
}
|
|
#endif // MICROPY_HW_ENABLE_FDCAN
|
|
|
|
static machine_can_state_t machine_can_port_get_state(machine_can_obj_t *self) {
|
|
// machine_can_port.h defines MP_CAN_STATE_xxx enums, verify they all match
|
|
// numerically with stm32 can.h CAN_STATE_xxx enums
|
|
MP_STATIC_ASSERT((int)MP_CAN_STATE_STOPPED == (int)CAN_STATE_STOPPED);
|
|
MP_STATIC_ASSERT((int)MP_CAN_STATE_ACTIVE == (int)CAN_STATE_ERROR_ACTIVE);
|
|
MP_STATIC_ASSERT((int)MP_CAN_STATE_WARNING == (int)CAN_STATE_ERROR_WARNING);
|
|
MP_STATIC_ASSERT((int)MP_CAN_STATE_PASSIVE == (int)CAN_STATE_ERROR_PASSIVE);
|
|
MP_STATIC_ASSERT((int)MP_CAN_STATE_BUS_OFF == (int)CAN_STATE_BUS_OFF);
|
|
return (machine_can_state_t)can_get_state(&self->port->h);
|
|
}
|
|
|
|
static void machine_can_port_update_counters(machine_can_obj_t *self) {
|
|
can_counters_t hw_counters;
|
|
struct machine_can_port *port = self->port;
|
|
machine_can_counters_t *counters = &self->counters;
|
|
|
|
can_get_counters(&port->h, &hw_counters);
|
|
|
|
counters->tec = hw_counters.tec;
|
|
counters->rec = hw_counters.rec;
|
|
counters->tx_pending = hw_counters.tx_pending;
|
|
counters->rx_pending = hw_counters.rx_fifo0_pending + hw_counters.rx_fifo1_pending;
|
|
|
|
// Other fields in 'counters' are updated from ISR directly
|
|
}
|
|
|
|
static mp_obj_t machine_can_port_get_additional_timings(machine_can_obj_t *self, mp_obj_t optional_arg) {
|
|
return mp_const_none;
|
|
}
|
|
|
|
static void machine_can_port_restart(machine_can_obj_t *self) {
|
|
// extmod layer has already checked CAN is initialised
|
|
struct machine_can_port *port = self->port;
|
|
machine_can_port_cancel_all_tx(self);
|
|
can_restart(&port->h);
|
|
port->irq_state_pending = false;
|
|
}
|
|
|
|
static bool clear_complete_transfer(machine_can_obj_t *self, int *index, bool *is_success) {
|
|
*index = can_get_transmit_finished(&self->port->h, is_success);
|
|
if (*index == -1) {
|
|
return false;
|
|
}
|
|
self->port->tx[*index] = TX_EMPTY;
|
|
|
|
return true;
|
|
}
|
|
|
|
static mp_uint_t machine_can_port_irq_flags(machine_can_obj_t *self) {
|
|
mp_uint_t flags = 0;
|
|
CAN_HandleTypeDef *can = &self->port->h;
|
|
|
|
if (self->mp_irq_trigger & MP_CAN_IRQ_STATE && self->port->irq_state_pending) {
|
|
flags |= MP_CAN_IRQ_STATE;
|
|
self->port->irq_state_pending = false;
|
|
}
|
|
|
|
// Check for RX
|
|
if (self->mp_irq_trigger & MP_CAN_IRQ_RX) {
|
|
for (can_rx_fifo_t fifo = CAN_RX_FIFO0; fifo <= CAN_RX_FIFO1; fifo++) {
|
|
if (can_is_rx_pending(can, fifo)) {
|
|
flags |= MP_CAN_IRQ_RX;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check for TX done
|
|
if (self->mp_irq_trigger & MP_CAN_IRQ_TX) {
|
|
bool is_success = false;
|
|
int index;
|
|
if (clear_complete_transfer(self, &index, &is_success)) {
|
|
flags |= (mp_uint_t)(index << MP_CAN_IRQ_IDX_SHIFT) | MP_CAN_IRQ_TX;
|
|
if (!is_success) {
|
|
flags |= MP_CAN_IRQ_TX_FAILED;
|
|
}
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
void machine_can_irq_handler(uint can_id, can_int_t interrupt) {
|
|
assert(can_id > 0);
|
|
machine_can_obj_t *self = MP_STATE_PORT(machine_can_objs)[can_id - 1];
|
|
if (self == NULL) {
|
|
return; // Should only hit this code path if pyb.CAN has enabled interrupt
|
|
}
|
|
struct machine_can_port *port = self->port;
|
|
machine_can_counters_t *counters = &self->counters;
|
|
bool call_irq = false;
|
|
bool irq_state = false;
|
|
|
|
switch (interrupt) {
|
|
// RX
|
|
case CAN_INT_FIFO_FULL:
|
|
self->rx_error_flags |= CAN_RECV_ERR_FULL;
|
|
break;
|
|
case CAN_INT_FIFO_OVERFLOW:
|
|
self->rx_error_flags |= CAN_RECV_ERR_OVERRUN;
|
|
counters->rx_overruns++;
|
|
break;
|
|
case CAN_INT_MESSAGE_RECEIVED:
|
|
call_irq = call_irq || (self->mp_irq_trigger & MP_CAN_IRQ_RX);
|
|
break;
|
|
|
|
// Error states
|
|
case CAN_INT_ERR_WARNING:
|
|
if (!port->error_passive) {
|
|
// Only count entering warning state, not leaving it
|
|
counters->num_warning++;
|
|
irq_state = true;
|
|
}
|
|
port->error_passive = false;
|
|
break;
|
|
case CAN_INT_ERR_PASSIVE:
|
|
counters->num_passive++;
|
|
port->error_passive = true;
|
|
irq_state = true;
|
|
break;
|
|
case CAN_INT_ERR_BUS_OFF:
|
|
counters->num_bus_off++;
|
|
irq_state = true;
|
|
port->error_passive = false;
|
|
break;
|
|
|
|
// TX
|
|
case CAN_INT_TX_COMPLETE:
|
|
if (!(self->mp_irq_trigger & MP_CAN_IRQ_TX)) {
|
|
// No TX IRQ, so mark this buffer as free and move on
|
|
int index;
|
|
bool is_success = false;
|
|
clear_complete_transfer(self, &index, &is_success);
|
|
} else {
|
|
// Otherwise, the slot is marked empty after the irq calls flags()
|
|
call_irq = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
assert(0); // Should be unreachable
|
|
}
|
|
|
|
if (irq_state && (self->mp_irq_trigger & MP_CAN_IRQ_STATE)) {
|
|
self->port->irq_state_pending = true;
|
|
call_irq = true;
|
|
}
|
|
|
|
if (call_irq) {
|
|
assert(self->mp_irq_obj != NULL); // Can't set mp_irq_trigger otherwise
|
|
mp_irq_handler(self->mp_irq_obj);
|
|
}
|
|
}
|