Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

canio: implement for stm32f405 #3505

Merged
merged 9 commits into from
Oct 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions ports/atmel-samd/common-hal/canio/CAN.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ typedef struct canio_can_obj {
mp_obj_base_t base;
Can *hw;
canio_can_state_t *state;
volatile uint32_t error_warning_state_count;
volatile uint32_t error_passive_state_count;
volatile uint32_t bus_off_state_count;
int baudrate;
uint8_t rx_pin_number:8;
uint8_t tx_pin_number:8;
Expand Down
4 changes: 4 additions & 0 deletions ports/stm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ SRC_STM32 = $(addprefix $(HAL_DIR)/Src/stm32$(MCU_SERIES_LOWER)xx_,\
ll_utils.c \
)

ifeq ($(CIRCUITPY_CANIO),1)
SRC_STM32 += $(HAL_DIR)/Src/stm32$(MCU_SERIES_LOWER)xx_hal_can.c
endif

# Need this to avoid UART linker problems. TODO: rewrite to use registered callbacks.
# Does not exist for F4 and lower
ifeq ($(MCU_VARIANT),$(filter $(MCU_VARIANT),STM32F765xx STM32F767xx STM32F769xx STM32H743xx))
Expand Down
3 changes: 3 additions & 0 deletions ports/stm/boards/feather_stm32f405_express/pins.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,8 @@ STATIC const mp_rom_map_elem_t board_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_SDIO_CLOCK), MP_ROM_PTR(&pin_PC12) },
{ MP_ROM_QSTR(MP_QSTR_SDIO_COMMAND), MP_ROM_PTR(&pin_PD02) },
{ MP_ROM_QSTR(MP_QSTR_SDIO_DATA), MP_ROM_PTR(&sdio_data_tuple) },

{ MP_OBJ_NEW_QSTR(MP_QSTR_CAN_RX), MP_ROM_PTR(&pin_PB09) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_CAN_TX), MP_ROM_PTR(&pin_PB08) },
};
MP_DEFINE_CONST_DICT(board_module_globals, board_module_globals_table);
297 changes: 297 additions & 0 deletions ports/stm/common-hal/canio/CAN.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jeff Epler for Adafruit Industries
*
* 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 <string.h>

#include "py/runtime.h"
#include "py/mperrno.h"

#include "common-hal/canio/CAN.h"
#include "peripherals/periph.h"
#include "shared-bindings/microcontroller/Pin.h"
#include "shared-bindings/util.h"
#include "supervisor/port.h"

STATIC bool reserved_can[MP_ARRAY_SIZE(mcu_can_banks)];

STATIC const mcu_periph_obj_t *find_pin_function(const mcu_periph_obj_t *table, size_t sz, const mcu_pin_obj_t *pin, int periph_index) {
for(size_t i = 0; i<sz; i++, table++) {
if (periph_index != -1 && periph_index != table->periph_index) {
continue;
}
if (pin == table->pin) {
return table;
}
}
return NULL;
}


void common_hal_canio_can_construct(canio_can_obj_t *self, mcu_pin_obj_t *tx, mcu_pin_obj_t *rx, int baudrate, bool loopback, bool silent)
{
#define DIV_ROUND(a, b) (((a) + (b)/2) / (b))
#define DIV_ROUND_UP(a, b) (((a) + (b) - 1) / (b))

const uint8_t can_tx_len = MP_ARRAY_SIZE(mcu_can_tx_list);
const uint8_t can_rx_len = MP_ARRAY_SIZE(mcu_can_rx_list);

const mcu_periph_obj_t *mcu_tx = find_pin_function(mcu_can_tx_list, can_tx_len, tx, -1);
if (!mcu_tx) {
mp_raise_ValueError_varg(translate("Invalid %q pin selection"), MP_QSTR_tx);
}
int periph_index = mcu_tx->periph_index;

const mcu_periph_obj_t *mcu_rx = find_pin_function(mcu_can_rx_list, can_rx_len, rx, periph_index);
if (!mcu_rx) {
mp_raise_ValueError_varg(translate("Invalid %q pin selection"), MP_QSTR_rx);
}

if (reserved_can[periph_index]) {
mp_raise_ValueError(translate("Hardware busy, try alternative pins"));
}

const uint32_t can_frequency = 42000000;
uint32_t clocks_per_bit = DIV_ROUND(can_frequency, baudrate);
uint32_t clocks_to_sample = DIV_ROUND(clocks_per_bit * 7, 8);
uint32_t clocks_after_sample = clocks_per_bit - clocks_to_sample;
uint32_t divisor = MAX(DIV_ROUND_UP(clocks_to_sample, 16), DIV_ROUND_UP(clocks_after_sample, 8));
const uint32_t sjw = 3;

uint32_t tq_per_bit = DIV_ROUND(clocks_per_bit, divisor);
uint32_t tq_to_sample = DIV_ROUND(clocks_to_sample, divisor);
uint32_t tq_after_sample = tq_per_bit - tq_to_sample;

if (divisor > 1023) {
mp_raise_OSError(MP_EINVAL); // baudrate cannot be attained (16kHz or something is lower bound, should never happen)
}

{
GPIO_InitTypeDef GPIO_InitStruct = {
.Pin = pin_mask(tx->number),
.Speed = GPIO_SPEED_FREQ_VERY_HIGH,
.Mode = GPIO_MODE_AF_PP,
.Pull = GPIO_PULLUP,
.Alternate = mcu_tx->altfn_index,
};
HAL_GPIO_Init(pin_port(tx->port), &GPIO_InitStruct);

GPIO_InitStruct.Pin = pin_mask(rx->number);
GPIO_InitStruct.Alternate = mcu_rx->altfn_index;
HAL_GPIO_Init(pin_port(rx->port), &GPIO_InitStruct);
}

CAN_TypeDef *hw = mcu_can_banks[periph_index - 1];

// CAN2 shares resources with CAN1. So we always enable CAN1, then split
// the filter banks equally between them.

__HAL_RCC_CAN1_CLK_ENABLE();

if(hw == CAN2) {
__HAL_RCC_CAN2_CLK_ENABLE();
self->start_filter_bank = 14;
self->end_filter_bank = 28;
self->filter_hw = CAN1;
} else {
self->start_filter_bank = 0;
self->end_filter_bank = 14;
self->filter_hw = hw;
}

CAN_InitTypeDef init = {
.AutoRetransmission = ENABLE,
.AutoBusOff = ENABLE,
.Prescaler = divisor,
.Mode = (loopback ? CAN_MODE_LOOPBACK : 0) | (silent ? CAN_MODE_SILENT_LOOPBACK : 0),
.SyncJumpWidth = (sjw-1) << CAN_BTR_SJW_Pos,
.TimeSeg1 = (tq_to_sample-2) << CAN_BTR_TS1_Pos,
.TimeSeg2 = (tq_after_sample-1) << CAN_BTR_TS2_Pos,
};

self->periph_index = periph_index;
self->silent = silent;
self->loopback = loopback;
self->baudrate = baudrate;

self->handle.Instance = hw;
self->handle.Init = init;
self->handle.State = HAL_CAN_STATE_RESET;

HAL_CAN_Init(&self->handle);

// Set the filter split as 14:14
// COULDDO(@jepler): Dynamically allocate filter banks between CAN1/2
self->filter_hw->FMR |= CAN_FMR_FINIT;
self->filter_hw->FMR = CAN_FMR_FINIT | (14 << CAN_FMR_CAN2SB_Pos);

// Clear every filter enable bit for this can HW
uint32_t fa1r = self->filter_hw->FA1R;
for (int i = self->start_filter_bank; i<self->end_filter_bank; i++) {
fa1r &= ~(1 << i);
}
self->filter_hw->FA1R = fa1r;
CLEAR_BIT(self->filter_hw->FMR, CAN_FMR_FINIT);

HAL_CAN_Start(&self->handle);

reserved_can[periph_index] = true;
}

bool common_hal_canio_can_loopback_get(canio_can_obj_t *self)
{
return self->loopback;
}

int common_hal_canio_can_baudrate_get(canio_can_obj_t *self)
{
return self->baudrate;
}

int common_hal_canio_can_transmit_error_count_get(canio_can_obj_t *self)
{
return (self->handle.Instance->ESR & CAN_ESR_TEC) >> CAN_ESR_TEC_Pos;
}

int common_hal_canio_can_receive_error_count_get(canio_can_obj_t *self)
{
return (self->handle.Instance->ESR & CAN_ESR_REC) >> CAN_ESR_REC_Pos;
}

canio_bus_state_t common_hal_canio_can_state_get(canio_can_obj_t *self) {
uint32_t esr = self->handle.Instance->ESR;
if (READ_BIT(esr, CAN_ESR_BOFF)) {
return BUS_STATE_OFF;
}
if (READ_BIT(esr, CAN_ESR_EPVF)) {
return BUS_STATE_ERROR_PASSIVE;
}
if (READ_BIT(esr, CAN_ESR_EWGF)) {
return BUS_STATE_ERROR_WARNING;
}
return BUS_STATE_ERROR_ACTIVE;
}

void common_hal_canio_can_restart(canio_can_obj_t *self) {
if (!common_hal_canio_can_auto_restart_get(self)) {
// "If ABOM is cleared, the software must initiate the recovering
// sequence by requesting bxCAN to enter and to leave initialization
// mode."
self->handle.Instance->MCR |= CAN_MCR_INRQ;
while ((self->handle.Instance->MSR & CAN_MSR_INAK) == 0) {
}
self->handle.Instance->MCR &= ~CAN_MCR_INRQ;
while ((self->handle.Instance->MSR & CAN_MSR_INAK)) {
}
}
}

bool common_hal_canio_can_auto_restart_get(canio_can_obj_t *self) {
return READ_BIT(self->handle.Instance->MCR, CAN_MCR_ABOM);
}

void common_hal_canio_can_auto_restart_set(canio_can_obj_t *self, bool value) {
if(value) {
SET_BIT(self->handle.Instance->MCR, CAN_MCR_ABOM);
} else {
CLEAR_BIT(self->handle.Instance->MCR, CAN_MCR_ABOM);
}
}

void common_hal_canio_can_send(canio_can_obj_t *self, mp_obj_t message_in)
{
canio_message_obj_t *message = message_in;
uint32_t mailbox;
bool rtr = message->base.type == &canio_remote_transmission_request_type;
CAN_TxHeaderTypeDef header = {
.StdId = message->id,
.ExtId = message->id,
.IDE = message->extended ? CAN_ID_EXT : CAN_ID_STD,
.RTR = rtr ? CAN_RTR_REMOTE : CAN_RTR_DATA,
.DLC = message->size,
};
uint32_t free_level = HAL_CAN_GetTxMailboxesFreeLevel(&self->handle);
if (free_level == 0) {
// There's no free Tx mailbox. We need to cancel some message without
// transmitting it, because once the bus returns to active state it's
// preferable to transmit the newest messages instead of older messages.
//
// We don't strictly guarantee that we abort the oldest Tx request,
// rather we just abort a different index each time. This permits us
// to just track a single cancel index
HAL_CAN_AbortTxRequest(&self->handle, 1 << (self->cancel_mailbox));
self->cancel_mailbox = (self->cancel_mailbox + 1) % 3;
// The abort request may not have completed immediately, so wait for
// the Tx mailbox to become free
do {
free_level = HAL_CAN_GetTxMailboxesFreeLevel(&self->handle);
} while (!free_level);
}
HAL_StatusTypeDef status = HAL_CAN_AddTxMessage(&self->handle, &header, message->data, &mailbox);
if (status != HAL_OK) {
// this is a "shouldn't happen" condition. we don't throw because the
// contract of send() is that it queues the packet to be sent if
// possible and does not signal success or failure to actually send.
return;
}

// wait 8ms (hard coded for now) for TX to occur
uint64_t deadline = port_get_raw_ticks(NULL) + 8;
while (port_get_raw_ticks(NULL) < deadline && HAL_CAN_IsTxMessagePending(&self->handle, 1 << mailbox)) {
RUN_BACKGROUND_TASKS;
}
}

bool common_hal_canio_can_silent_get(canio_can_obj_t *self) {
return self->silent;
}

bool common_hal_canio_can_deinited(canio_can_obj_t *self) {
return !self->handle.Instance;
}

void common_hal_canio_can_check_for_deinit(canio_can_obj_t *self) {
if (common_hal_canio_can_deinited(self)) {
raise_deinited_error();
}
}

void common_hal_canio_can_deinit(canio_can_obj_t *self)
{
if (self->handle.Instance) {
SET_BIT(self->handle.Instance->MCR, CAN_MCR_RESET);
while (READ_BIT(self->handle.Instance->MCR, CAN_MCR_RESET)) {
}
reserved_can[self->periph_index] = 0;
}
self->handle.Instance = NULL;
}

void common_hal_canio_reset(void) {
for (size_t i=0; i<MP_ARRAY_SIZE(mcu_can_banks); i++) {
SET_BIT(mcu_can_banks[i]->MCR, CAN_MCR_RESET);
reserved_can[i] = 0;
}
}
58 changes: 58 additions & 0 deletions ports/stm/common-hal/canio/CAN.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2020 Jeff Epler for Adafruit Industries
*
* 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.
*/

#pragma once

#include "py/obj.h"
#include "shared-bindings/canio/__init__.h"
#include "shared-bindings/canio/CAN.h"
#include "common-hal/microcontroller/Pin.h"
#include "common-hal/canio/__init__.h"
#include "shared-module/canio/Message.h"

#include "stm32f4xx_hal.h"
#include "stm32f4xx_hal_can.h"

#define FILTER_BANK_COUNT (28)

typedef struct canio_can_obj {
mp_obj_base_t base;
CAN_HandleTypeDef handle;
CAN_TypeDef *filter_hw;
int baudrate;
const mcu_pin_obj_t *rx_pin;
const mcu_pin_obj_t *tx_pin;
bool loopback:1;
bool silent:1;
bool auto_restart:1;
bool fifo0_in_use:1;
bool fifo1_in_use:1;
uint8_t periph_index:2;
uint8_t cancel_mailbox;
uint8_t start_filter_bank;
uint8_t end_filter_bank;
long filter_in_use; // bitmask for the 28 filter banks
} canio_can_obj_t;
Loading