From 18d2d79d8c8edc090151a32e2b6cf7c4126907c1 Mon Sep 17 00:00:00 2001 From: Steven Michalske Date: Wed, 3 Feb 2021 17:53:19 -0800 Subject: [PATCH] stm32/can: Add support for a software based CAN message buffer. When using a CAN interface with many messages on the bus; the hardware RX buffers can overflow. This is typically caused by a busy bus, at high data rates, and permissive CAN filters. Our approach was to add software FIFO buffers at the c level CAN ISR and drivers. This allowed for fast reception, bulk python processing, and no dynamic allocations after init. In implementing the SW FIFOs two additional keyword arguments were added to the constructor for HW FIFO configuration. - receive_fifo_locked_mode=True - transmit_fifo_priority=True And two keyword arguments for setting SW buffer sizes to be allocated. - sw_fifo_0_size = 0 - sw_fifo_1_size = 0 Defaulting to 0 messages for no SW FIFO. The overall goals were: - Mimic the UART buffer initialization. - Implement RX buffers at the IRQ level. - Not change the python interface to access the data. - i.e. The same python calls are used in buffered or unbuffered modes. - Design decision was that both CAN FIFOs are either HW or SW per interface. - This was due to the desire to keep the ISR simpler. - The SW buffers can have different sizes for each CAN FIFO. - Each CAN interface may have HW or SW buffers. Signed-off-by: Steven Michalske --- docs/library/pyb.CAN.rst | 22 +++- ports/stm32/can.c | 258 +++++++++++++++++++++++++++++++++++++-- ports/stm32/can.h | 21 +++- ports/stm32/pyb_can.c | 109 +++++++++-------- 4 files changed, 344 insertions(+), 66 deletions(-) diff --git a/docs/library/pyb.CAN.rst b/docs/library/pyb.CAN.rst index 8078e29e0cc8d..f2eb7267f8813 100644 --- a/docs/library/pyb.CAN.rst +++ b/docs/library/pyb.CAN.rst @@ -49,7 +49,7 @@ Class Methods Methods ------- -.. method:: CAN.init(mode, extframe=False, prescaler=100, *, sjw=1, bs1=6, bs2=8, auto_restart=False) +.. method:: CAN.init(mode, extframe=False, prescaler=100, *, receive_fifo_locked_mode=True, transmit_fifo_priority=True, sw_fifo_0_size = 0, sw_fifo_1_size = 0, sjw=1, bs1=6, bs2=8, auto_restart=False) Initialise the CAN bus with the given parameters: @@ -58,6 +58,19 @@ Methods (29 bits); otherwise it uses standard 11 bit identifiers - *prescaler* is used to set the duration of 1 time quanta; the time quanta will be the input clock (PCLK1, see :meth:`pyb.freq()`) divided by the prescaler + - *receive_fifo_locked_mode* controls Receive FIFO locked feature in CAN + controller, see RFLM bit in section 32.9 of the STM32F407 reference manual. If + it is set to False, once a receive FIFO is full the next incoming message will + overwrite the last one. If it is set to True, once a receive FIFO is full, the + next incoming message will be discarded. + - *transmit_fifo_priority* is used to set Transmit FIFO priority. If it is set + to False, priority order is given by the identifier of the message. If it is + set the True, priority order is given by the request order (chronologically). + - *sw_fifo_0_size* and *sw_fifo_1_size* are used to set the desired size of + software FIFOs. When deciding to use software FIFOs, these parameters need + to be set at the same time and both of them should have values >=4, but they + don't need to be same. When deciding not to use software FIFOs, these + parameters should not be touched or should both be set to 0. - *sjw* is the resynchronisation jump width in units of the time quanta; it can be 1, 2, 3, 4 - *bs1* defines the location of the sample point in units of the time quanta; @@ -67,6 +80,13 @@ Methods - *auto_restart* sets whether the controller will automatically try and restart communications after entering the bus-off state; if this is disabled then :meth:`~CAN.restart()` can be used to leave the bus-off state + + When software FIFOs are in use, software FIFOs will be filled by the interrupt + which is triggered by hardware FIFO. This filling operation will continue until + the end of the size of software FIFOs. At the same time, hardware FIFO is still collecting + the data and its behavior will differ according to the *receive_fifo_locked_mode*. + The collected data will be stored into the software FIFOs when there is + available space in it. The time quanta tq is the basic unit of time for the CAN bus. tq is the CAN prescaler value divided by PCLK1 (the frequency of internal peripheral bus 1); diff --git a/ports/stm32/can.c b/ports/stm32/can.c index e5fc55e409bbc..097ea4f69d851 100644 --- a/ports/stm32/can.c +++ b/ports/stm32/can.c @@ -49,7 +49,18 @@ void can_deinit_all(void) { #if !MICROPY_HW_ENABLE_FDCAN -bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart) { +// Declaring static functions used by the CAN hardware to mitigate definition ordering requirements. +STATIC void can_rx_irq_hw_fifo_handler(pyb_can_obj_t *self, uint fifo_id); +STATIC void can_rx_irq_sw_fifo_handler(pyb_can_obj_t *self, uint fifo_id); +STATIC int can_receive_from_hw_fifo(pyb_can_obj_t *self, int fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms); +STATIC int can_receive_from_sw_fifo(pyb_can_obj_t *self, int fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms); +STATIC bool can_any_hardware(pyb_can_obj_t *self, int fifo); +STATIC bool can_any_software(pyb_can_obj_t *self, int fifo); +STATIC bool is_sw_fifo_empty(const byte *rx_state, const sw_fifo_t *sw_fifo); +STATIC void sw_fifo_reset(sw_fifo_t *sw_fifo, byte *rx_state); + +bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart, + bool receive_fifo_locked_mode, bool transmit_fifo_priority, int sw_fifo_0_size, int sw_fifo_1_size) { CAN_InitTypeDef *init = &can_obj->can.Init; init->Mode = mode << 4; // shift-left so modes fit in a small-int init->Prescaler = prescaler; @@ -60,12 +71,13 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ init->ABOM = auto_restart ? ENABLE : DISABLE; init->AWUM = DISABLE; init->NART = DISABLE; - init->RFLM = DISABLE; - init->TXFP = DISABLE; + init->RFLM = receive_fifo_locked_mode ? ENABLE : DISABLE; + init->TXFP = transmit_fifo_priority ? ENABLE : DISABLE; CAN_TypeDef *CANx = NULL; uint32_t sce_irq = 0; const pin_obj_t *pins[2]; + uint32_t sw_fifo_irq_0 = 0, sw_fifo_irq_1 = 0; switch (can_obj->can_id) { #if defined(MICROPY_HW_CAN1_TX) @@ -74,6 +86,8 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ sce_irq = CAN1_SCE_IRQn; pins[0] = MICROPY_HW_CAN1_TX; pins[1] = MICROPY_HW_CAN1_RX; + sw_fifo_irq_0 = CAN1_RX0_IRQn; + sw_fifo_irq_1 = CAN1_RX1_IRQn; __HAL_RCC_CAN1_CLK_ENABLE(); break; #endif @@ -84,6 +98,8 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ sce_irq = CAN2_SCE_IRQn; pins[0] = MICROPY_HW_CAN2_TX; pins[1] = MICROPY_HW_CAN2_RX; + sw_fifo_irq_0 = CAN2_RX0_IRQn; + sw_fifo_irq_1 = CAN2_RX1_IRQn; __HAL_RCC_CAN1_CLK_ENABLE(); // CAN2 is a "slave" and needs CAN1 enabled as well __HAL_RCC_CAN2_CLK_ENABLE(); break; @@ -95,6 +111,8 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ sce_irq = CAN3_SCE_IRQn; pins[0] = MICROPY_HW_CAN3_TX; pins[1] = MICROPY_HW_CAN3_RX; + sw_fifo_irq_0 = CAN3_RX0_IRQn; + sw_fifo_irq_1 = CAN3_RX1_IRQn; __HAL_RCC_CAN3_CLK_ENABLE(); // CAN3 is a "master" and doesn't need CAN1 enabled as well break; #endif @@ -121,6 +139,37 @@ bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_ can_obj->num_error_passive = 0; can_obj->num_bus_off = 0; + // Only either hardware fifo or software fifos is supported for both fifo 0 and fifo 1 + if (sw_fifo_0_size <= 0 || sw_fifo_1_size <= 0) { + can_obj->is_sw_fifo_enabled = false; + + can_obj->can_recv_handler = &can_receive_from_hw_fifo; + can_obj->can_rx_isr_handler = &can_rx_irq_hw_fifo_handler; + can_obj->can_any_handler = &can_any_hardware; + } else { + can_obj->is_sw_fifo_enabled = true; + + can_obj->can_recv_handler = &can_receive_from_sw_fifo; + can_obj->can_rx_isr_handler = &can_rx_irq_sw_fifo_handler; + can_obj->can_any_handler = &can_any_software; + // Init SW FIFO + sw_fifo_reset(&can_obj->sw_fifo[0], &can_obj->rx_state0); + sw_fifo_reset(&can_obj->sw_fifo[1], &can_obj->rx_state1); + + can_obj->sw_fifo[0].size = sw_fifo_0_size; + can_obj->sw_fifo[0].fifo = m_new(CanRxMsgTypeDef, sw_fifo_0_size); + can_obj->sw_fifo[1].size = sw_fifo_1_size; + can_obj->sw_fifo[1].fifo = m_new(CanRxMsgTypeDef, sw_fifo_1_size); + + // Enable Pending interrupt for both fifo 0 and fifo 1 to be able to fill in sw fifo + NVIC_SetPriority(sw_fifo_irq_0, IRQ_PRI_CAN); + NVIC_SetPriority(sw_fifo_irq_1, IRQ_PRI_CAN); + HAL_NVIC_EnableIRQ(sw_fifo_irq_0); + HAL_NVIC_EnableIRQ(sw_fifo_irq_1); + __HAL_CAN_ENABLE_IT(&can_obj->can, CAN_IT_FMP0); + __HAL_CAN_ENABLE_IT(&can_obj->can, CAN_IT_FMP1); + } + __HAL_CAN_ENABLE_IT(&can_obj->can, CAN_IT_ERR | CAN_IT_BOF | CAN_IT_EPV | CAN_IT_EWG); NVIC_SetPriority(sce_irq, IRQ_PRI_CAN); @@ -158,6 +207,9 @@ void can_deinit(pyb_can_obj_t *self) { __HAL_RCC_CAN3_CLK_DISABLE(); #endif } + // Free the memory of software fifo + m_del(CanRxMsgTypeDef, self->sw_fifo[0].fifo, self->sw_fifo[0].size); + m_del(CanRxMsgTypeDef, self->sw_fifo[1].fifo, self->sw_fifo[1].size); } void can_clearfilter(pyb_can_obj_t *self, uint32_t f, uint8_t bank) { @@ -313,47 +365,112 @@ HAL_StatusTypeDef CAN_Transmit(CAN_HandleTypeDef *hcan, uint32_t Timeout) { } STATIC void can_rx_irq_handler(uint can_id, uint fifo_id) { - mp_obj_t callback; pyb_can_obj_t *self; - mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(0); - byte *state; self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1]; + self->can_rx_isr_handler(self, fifo_id); +} + +STATIC void can_rx_irq_hw_fifo_handler(pyb_can_obj_t *self, uint fifo_id) { + mp_obj_t callback; + mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(0); + byte *rx_state; if (fifo_id == CAN_FIFO0) { callback = self->rxcallback0; - state = &self->rx_state0; + rx_state = &self->rx_state0; } else { callback = self->rxcallback1; - state = &self->rx_state1; + rx_state = &self->rx_state1; } - switch (*state) { + switch (*rx_state) { case RX_STATE_FIFO_EMPTY: __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1); irq_reason = MP_OBJ_NEW_SMALL_INT(0); - *state = RX_STATE_MESSAGE_PENDING; + *rx_state = RX_STATE_MESSAGE_PENDING; break; case RX_STATE_MESSAGE_PENDING: __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1); __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FF0 : CAN_FLAG_FF1); irq_reason = MP_OBJ_NEW_SMALL_INT(1); - *state = RX_STATE_FIFO_FULL; + *rx_state = RX_STATE_FIFO_FULL; break; case RX_STATE_FIFO_FULL: __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FOV0 : CAN_IT_FOV1); __HAL_CAN_CLEAR_FLAG(&self->can, (fifo_id == CAN_FIFO0) ? CAN_FLAG_FOV0 : CAN_FLAG_FOV1); irq_reason = MP_OBJ_NEW_SMALL_INT(2); - *state = RX_STATE_FIFO_OVERFLOW; + *rx_state = RX_STATE_FIFO_OVERFLOW; break; case RX_STATE_FIFO_OVERFLOW: - // This should never happen + // The FIFO getting full disables the interrupt, so this state + // should not occur. This state is exited by + // can_receive_from_sw_fifo, where the interrupt is reenabled. break; } pyb_can_handle_callback(self, fifo_id, callback, irq_reason); } +STATIC void can_rx_irq_sw_fifo_handler(pyb_can_obj_t *self, uint fifo_id) { + mp_obj_t callback; + mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(0); + byte *rx_state; + + sw_fifo_t *sw_fifo = &self->sw_fifo[fifo_id]; + + if (fifo_id == CAN_FIFO0) { + callback = self->rxcallback0; + rx_state = &self->rx_state0; + } else { + callback = self->rxcallback1; + rx_state = &self->rx_state1; + } + + do { + switch (*rx_state) { + case RX_STATE_FIFO_EMPTY: + irq_reason = MP_OBJ_NEW_SMALL_INT(0); + *rx_state = RX_STATE_MESSAGE_PENDING; + break; + case RX_STATE_MESSAGE_PENDING: + if (available_space_sw_fifo(*rx_state, sw_fifo) != 1) { + irq_reason = MP_OBJ_NEW_SMALL_INT(0); + } else { // It means only one room in sw fifo + irq_reason = MP_OBJ_NEW_SMALL_INT(1); + *rx_state = RX_STATE_FIFO_FULL; + } + break; + case RX_STATE_FIFO_FULL: + irq_reason = MP_OBJ_NEW_SMALL_INT(2); + *rx_state = RX_STATE_FIFO_OVERFLOW; + __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1); + break; + case RX_STATE_FIFO_OVERFLOW: + // This should never happen + break; + } + + if (*rx_state != RX_STATE_FIFO_OVERFLOW) { + // CAN_RECV_TIMEOUT_MS is short sanity-check timeout because + // we should always have data from the CAN interrupt that triggered this function. + int r = can_receive(&self->can, fifo_id, &sw_fifo->fifo[sw_fifo->write_pos], + sw_fifo->fifo[sw_fifo->write_pos].Data,CAN_RECV_TIMEOUT_MS); + if (r < 0) { + break; // This should never happen; leave loop + } + sw_fifo->write_pos = (sw_fifo->write_pos + 1) % sw_fifo->size; + } + + if (callback != mp_const_none && *rx_state != sw_fifo->previous_rx_state) { + pyb_can_handle_callback(self, fifo_id, callback, irq_reason); + } + + sw_fifo->previous_rx_state = *rx_state; + + } while (__HAL_CAN_MSG_PENDING(&self->can, CAN_FIFO0) && *rx_state != RX_STATE_FIFO_OVERFLOW); +} + STATIC void can_sce_irq_handler(uint can_id) { pyb_can_obj_t *self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1]; if (self) { @@ -369,6 +486,121 @@ STATIC void can_sce_irq_handler(uint can_id) { } } +STATIC int can_receive_from_hw_fifo(pyb_can_obj_t *self, int fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms) { + int ret = can_receive(&self->can, fifo, msg, data, timeout_ms); + + // Manage the rx state machine + if ((fifo == CAN_FIFO0 && self->rxcallback0 != mp_const_none) || + (fifo == CAN_FIFO1 && self->rxcallback1 != mp_const_none)) { + byte *rx_state = (fifo == CAN_FIFO0) ? &self->rx_state0 : &self->rx_state1; + + switch (*rx_state) { + case RX_STATE_FIFO_EMPTY: + break; + case RX_STATE_MESSAGE_PENDING: + if (__HAL_CAN_MSG_PENDING(&self->can, fifo) == 0) { + // Fifo is empty + __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1); + *rx_state = RX_STATE_FIFO_EMPTY; + } + break; + case RX_STATE_FIFO_FULL: + __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1); + *rx_state = RX_STATE_MESSAGE_PENDING; + break; + case RX_STATE_FIFO_OVERFLOW: + __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FOV0 : CAN_IT_FOV1); + __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1); + *rx_state = RX_STATE_MESSAGE_PENDING; + break; + } + } + + return ret; +} + +STATIC int can_receive_from_sw_fifo(pyb_can_obj_t *self, int fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms) { + byte *rx_state = (fifo == CAN_FIFO0) ? &self->rx_state0 : &self->rx_state1; + sw_fifo_t *sw_fifo = &self->sw_fifo[fifo]; + + // Wait for a message to become available, with timeout + uint32_t start = HAL_GetTick(); + while (is_sw_fifo_empty(rx_state,sw_fifo)) { + MICROPY_EVENT_POLL_HOOK + if (HAL_GetTick() - start >= timeout_ms) { + return -MP_ETIMEDOUT; + } + } + + // Receive from software fifo copy to msg and data + memcpy(msg, &sw_fifo->fifo[sw_fifo->read_pos], sizeof(CanRxMsgTypeDef)); + memcpy(data, &sw_fifo->fifo[sw_fifo->read_pos].Data, CAN_DATA_SIZE); + sw_fifo->read_pos = (sw_fifo->read_pos + 1) % sw_fifo->size; + + // It is important to keep this order to avoid losing messages + if (sw_fifo->write_pos == sw_fifo->read_pos) { + *rx_state = RX_STATE_FIFO_EMPTY; + sw_fifo->previous_rx_state = *rx_state; + } + + if (*rx_state == RX_STATE_FIFO_FULL) { + *rx_state = RX_STATE_MESSAGE_PENDING; + sw_fifo->previous_rx_state = *rx_state; + } + + if (*rx_state == RX_STATE_FIFO_OVERFLOW) { + *rx_state = RX_STATE_MESSAGE_PENDING; + sw_fifo->previous_rx_state = *rx_state; + __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1); + } + + return 0; +} + +STATIC bool can_any_hardware(pyb_can_obj_t *self, int fifo) { + uint32_t fifo_id = (fifo == 0) ? CAN_FIFO0 : CAN_FIFO1; + return __HAL_CAN_MSG_PENDING(&self->can, fifo_id) != 0; +} + +STATIC bool can_any_software(pyb_can_obj_t *self, int fifo) { + byte *rx_state = (fifo == CAN_FIFO0) ? &self->rx_state0 : &self->rx_state1; + + sw_fifo_t *sw_fifo = &self->sw_fifo[fifo]; + + if (!is_sw_fifo_empty(rx_state, sw_fifo)) { + return true; + } + return false; +} + +// Return true when SW fifo is empty +STATIC bool is_sw_fifo_empty(const byte *rx_state, const sw_fifo_t *sw_fifo) { + return (*rx_state == RX_STATE_FIFO_EMPTY) && (sw_fifo->write_pos == sw_fifo->read_pos); +} + +STATIC void sw_fifo_reset(sw_fifo_t *sw_fifo, byte *rx_state) { + sw_fifo->read_pos = 0; + sw_fifo->write_pos = 0; + *rx_state = RX_STATE_FIFO_EMPTY; + sw_fifo->previous_rx_state = RX_STATE_FIFO_EMPTY; +} + +uint16_t available_space_sw_fifo(byte rx_state, const sw_fifo_t *sw_fifo) { + uint16_t available_space = sw_fifo->size; + + if (rx_state != RX_STATE_FIFO_EMPTY) { + if (sw_fifo->write_pos > sw_fifo->read_pos) { + available_space = sw_fifo->size - (sw_fifo->write_pos - sw_fifo->read_pos); + } else if (sw_fifo->write_pos == sw_fifo->read_pos) { + available_space = 0; + } else { + available_space = sw_fifo->read_pos - sw_fifo->write_pos; + } + } + + return available_space; +} + #if defined(MICROPY_HW_CAN1_TX) void CAN1_RX0_IRQHandler(void) { IRQ_ENTER(CAN1_RX0_IRQn); diff --git a/ports/stm32/can.h b/ports/stm32/can.h index bb26c0b7e2ec7..ce90995e667d2 100644 --- a/ports/stm32/can.h +++ b/ports/stm32/can.h @@ -46,6 +46,10 @@ #define CanRxMsgTypeDef FDCAN_RxHeaderTypeDef #endif +#define CAN_DATA_SIZE (8) +// Sanity check timeout for recv ISR in SW FIFO mode; data is always expected +#define CAN_RECV_TIMEOUT_MS (10) + enum { CAN_STATE_STOPPED, CAN_STATE_ERROR_ACTIVE, @@ -61,6 +65,14 @@ typedef enum _rx_state_t { RX_STATE_FIFO_OVERFLOW, } rx_state_t; +typedef struct _sw_fifo_t { + CanRxMsgTypeDef *fifo; + uint16_t size; + volatile uint16_t read_pos; + volatile uint16_t write_pos; + byte previous_rx_state; +} sw_fifo_t; + typedef struct _pyb_can_obj_t { mp_obj_base_t base; mp_obj_t rxcallback0; @@ -68,25 +80,32 @@ typedef struct _pyb_can_obj_t { mp_uint_t can_id : 8; bool is_enabled : 1; bool extframe : 1; + bool is_sw_fifo_enabled : 1; byte rx_state0; byte rx_state1; uint16_t num_error_warning; uint16_t num_error_passive; uint16_t num_bus_off; CAN_HandleTypeDef can; + sw_fifo_t sw_fifo[2]; + int (*can_recv_handler)(struct _pyb_can_obj_t *self, int fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms); + void (*can_rx_isr_handler)(struct _pyb_can_obj_t *self, uint fifo_id); + bool (*can_any_handler)(struct _pyb_can_obj_t *self, int fifo_id); } pyb_can_obj_t; extern const mp_obj_type_t pyb_can_type; void can_init0(void); void can_deinit_all(void); -bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart); +bool can_init(pyb_can_obj_t *can_obj, uint32_t mode, uint32_t prescaler, uint32_t sjw, uint32_t bs1, uint32_t bs2, bool auto_restart, + bool receive_fifo_locked_mode, bool transmit_fifo_priority, int sw_fifo_0_size, int sw_fifo_1_size); void can_deinit(pyb_can_obj_t *self); void can_clearfilter(pyb_can_obj_t *self, uint32_t f, uint8_t bank); int can_receive(CAN_HandleTypeDef *can, int fifo, CanRxMsgTypeDef *msg, uint8_t *data, uint32_t timeout_ms); HAL_StatusTypeDef CAN_Transmit(CAN_HandleTypeDef *hcan, uint32_t Timeout); void pyb_can_handle_callback(pyb_can_obj_t *self, uint fifo_id, mp_obj_t callback, mp_obj_t irq_reason); +uint16_t available_space_sw_fifo(byte state, const sw_fifo_t *sw_fifo); #endif // MICROPY_HW_ENABLE_CAN diff --git a/ports/stm32/pyb_can.c b/ports/stm32/pyb_can.c index 224b8f28b0eab..76965da87bef3 100644 --- a/ports/stm32/pyb_can.c +++ b/ports/stm32/pyb_can.c @@ -106,6 +106,10 @@ STATIC uint8_t can2_start_bank = 14; #endif +#define SW_FIFO0_SIZE (0) +#define SW_FIFO1_SIZE (0) +#define SW_FIFO_MIN_SIZE (4) + STATIC void pyb_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(self_in); if (!self->is_enabled) { @@ -142,21 +146,42 @@ STATIC void pyb_can_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ki // init(mode, extframe=False, prescaler=100, *, sjw=1, bs1=6, bs2=8) STATIC mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { - enum { ARG_mode, ARG_extframe, ARG_prescaler, ARG_sjw, ARG_bs1, ARG_bs2, ARG_auto_restart }; + enum { ARG_mode, ARG_extframe, ARG_prescaler, ARG_sjw, ARG_bs1, ARG_bs2, ARG_auto_restart, ARG_receive_fifo_locked_mode, ARG_transmit_fifo_priority, ARG_sw_fifo_0_size, ARG_sw_fifo_1_size }; static const mp_arg_t allowed_args[] = { - { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = CAN_MODE_NORMAL} }, - { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, - { MP_QSTR_prescaler, 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_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_mode, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = CAN_MODE_NORMAL} }, + { MP_QSTR_extframe, MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_prescaler, 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_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_receive_fifo_locked_mode, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_transmit_fifo_priority, MP_ARG_KW_ONLY | MP_ARG_BOOL, {.u_bool = false} }, + { MP_QSTR_sw_fifo_0_size, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = SW_FIFO0_SIZE} }, + { MP_QSTR_sw_fifo_1_size, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = SW_FIFO1_SIZE} }, }; // 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); + // Check for "mix and match" not supported now + if ((args[ARG_sw_fifo_0_size].u_int == 0 && args[ARG_sw_fifo_1_size].u_int != 0) || + (args[ARG_sw_fifo_0_size].u_int != 0 && args[ARG_sw_fifo_1_size].u_int == 0)) { + + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) Mixing HW and SW fifos not supported yet"), self->can_id); + } + + // Check for sw fifo sizes < SW_FIFO_MIN_SIZE, not supported now + if (args[ARG_sw_fifo_0_size].u_int != 0 && args[ARG_sw_fifo_0_size].u_int < SW_FIFO_MIN_SIZE) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) sw_fifo_0_size must be 0 or >= %d"), self->can_id, SW_FIFO_MIN_SIZE); + return mp_const_none; + } + if (args[ARG_sw_fifo_1_size].u_int != 0 && args[ARG_sw_fifo_1_size].u_int < SW_FIFO_MIN_SIZE) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) sw_fifo_1_size must be 0 or >= %d"), self->can_id, SW_FIFO_MIN_SIZE); + return mp_const_none; + } + self->extframe = args[ARG_extframe].u_bool; // set the CAN configuration values @@ -164,7 +189,9 @@ STATIC mp_obj_t pyb_can_init_helper(pyb_can_obj_t *self, size_t n_args, const mp // init CAN (if it fails, it's because the port doesn't exist) if (!can_init(self, args[ARG_mode].u_int, args[ARG_prescaler].u_int, args[ARG_sjw].u_int, - args[ARG_bs1].u_int, args[ARG_bs2].u_int, args[ARG_auto_restart].u_bool)) { + args[ARG_bs1].u_int, args[ARG_bs2].u_int, args[ARG_auto_restart].u_bool, args[ARG_receive_fifo_locked_mode].u_bool, + args[ARG_transmit_fifo_priority].u_bool, args[ARG_sw_fifo_0_size].u_int, args[ARG_sw_fifo_1_size].u_int)) { + mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("CAN(%d) doesn't exist"), self->can_id); } @@ -336,26 +363,29 @@ STATIC mp_obj_t pyb_can_info(size_t n_args, const mp_obj_t *args) { list->items[4] = MP_OBJ_NEW_SMALL_INT(self->num_bus_off); int n_tx_pending = 0x01121223 >> ((can->TSR >> CAN_TSR_TME_Pos & 7) << 2) & 0xf; list->items[5] = MP_OBJ_NEW_SMALL_INT(n_tx_pending); - list->items[6] = MP_OBJ_NEW_SMALL_INT(can->RF0R >> CAN_RF0R_FMP0_Pos & 3); - list->items[7] = MP_OBJ_NEW_SMALL_INT(can->RF1R >> CAN_RF1R_FMP1_Pos & 3); + if (!self->is_sw_fifo_enabled) { + // HW buffering looks at controller registers + list->items[6] = MP_OBJ_NEW_SMALL_INT(can->RF0R >> CAN_RF0R_FMP0_Pos & 3); + list->items[7] = MP_OBJ_NEW_SMALL_INT(can->RF1R >> CAN_RF1R_FMP1_Pos & 3); + } else { + // SW buffering looks in struct + list->items[6] = MP_OBJ_NEW_SMALL_INT(available_space_sw_fifo(self->rx_state0, &self->sw_fifo[0])); + list->items[7] = MP_OBJ_NEW_SMALL_INT(available_space_sw_fifo(self->rx_state1, &self->sw_fifo[1])); + } return MP_OBJ_FROM_PTR(list); #endif } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(pyb_can_info_obj, 1, 2, pyb_can_info); -// any(fifo) - return `True` if any message waiting on the FIFO, else `False` +// any(fifo) - return `True` if any message waiting on the hardware FIFO or software FIFO, else `False` STATIC mp_obj_t pyb_can_any(mp_obj_t self_in, mp_obj_t fifo_in) { pyb_can_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_int_t fifo = mp_obj_get_int(fifo_in); - if (fifo == 0) { - if (__HAL_CAN_MSG_PENDING(&self->can, CAN_FIFO0) != 0) { - return mp_const_true; - } - } else { - if (__HAL_CAN_MSG_PENDING(&self->can, CAN_FIFO1) != 0) { - return mp_const_true; - } + + if (self->can_any_handler(self, fifo)) { + return mp_const_true; } + return mp_const_false; } STATIC MP_DEFINE_CONST_FUN_OBJ_2(pyb_can_any_obj, pyb_can_any); @@ -491,7 +521,7 @@ STATIC mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t * mp_raise_TypeError(NULL); } - int ret = can_receive(&self->can, fifo, &rx_msg, rx_data, args[ARG_timeout].u_int); + int ret = self->can_recv_handler(self, fifo, &rx_msg, rx_data, args[ARG_timeout].u_int); if (ret < 0) { mp_raise_OSError(-ret); } @@ -502,33 +532,6 @@ STATIC mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t * uint32_t rx_dlc = rx_msg.DLC; #endif - // Manage the rx state machine - if ((fifo == CAN_FIFO0 && self->rxcallback0 != mp_const_none) || - (fifo == CAN_FIFO1 && self->rxcallback1 != mp_const_none)) { - byte *state = (fifo == CAN_FIFO0) ? &self->rx_state0 : &self->rx_state1; - - switch (*state) { - case RX_STATE_FIFO_EMPTY: - break; - case RX_STATE_MESSAGE_PENDING: - if (__HAL_CAN_MSG_PENDING(&self->can, fifo) == 0) { - // Fifo is empty - __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FIFO0_PENDING : CAN_IT_FIFO1_PENDING); - *state = RX_STATE_FIFO_EMPTY; - } - break; - case RX_STATE_FIFO_FULL: - __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FIFO0_FULL : CAN_IT_FIFO1_FULL); - *state = RX_STATE_MESSAGE_PENDING; - break; - case RX_STATE_FIFO_OVERFLOW: - __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FIFO0_OVRF : CAN_IT_FIFO1_OVRF); - __HAL_CAN_ENABLE_IT(&self->can, (fifo == CAN_FIFO0) ? CAN_IT_FIFO0_FULL : CAN_IT_FIFO1_FULL); - *state = RX_STATE_MESSAGE_PENDING; - break; - } - } - // 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; @@ -777,7 +780,9 @@ STATIC mp_obj_t pyb_can_rxcallback(mp_obj_t self_in, mp_obj_t fifo_in, mp_obj_t callback = (fifo == 0) ? &self->rxcallback0 : &self->rxcallback1; if (callback_in == mp_const_none) { - __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_PENDING : CAN_IT_FIFO1_PENDING); + if (!self->is_sw_fifo_enabled) { + __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_PENDING : CAN_IT_FIFO1_PENDING); + } __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_FULL : CAN_IT_FIFO1_FULL); __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_OVRF : CAN_IT_FIFO1_OVRF); __HAL_CAN_CLEAR_FLAG(&self->can, (fifo == CAN_FIFO0) ? CAN_FLAG_FIFO0_FULL : CAN_FLAG_FIFO1_FULL); @@ -804,8 +809,10 @@ STATIC mp_obj_t pyb_can_rxcallback(mp_obj_t self_in, mp_obj_t fifo_in, mp_obj_t NVIC_SetPriority(irq, IRQ_PRI_CAN); HAL_NVIC_EnableIRQ(irq); __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_PENDING : CAN_IT_FIFO1_PENDING); - __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_FULL : CAN_IT_FIFO1_FULL); - __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_OVRF : CAN_IT_FIFO1_OVRF); + if (!self->is_sw_fifo_enabled) { + __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_FULL : CAN_IT_FIFO1_FULL); + __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FIFO0_OVRF : CAN_IT_FIFO1_OVRF); + } } return mp_const_none; }