aboutsummaryrefslogtreecommitdiffstats
path: root/src/stm32/can.c
diff options
context:
space:
mode:
authorbondus <liquidpontus@yahoo.se>2020-06-25 00:59:38 +0200
committerGitHub <noreply@github.com>2020-06-24 18:59:38 -0400
commit7a8e9591e324637d80e4bfd18f1b75774c521dda (patch)
treecec73f407c920e9547d963c815477230178d0255 /src/stm32/can.c
parent7cab732ae9baf5cf6da716f06dc82942426a1712 (diff)
downloadkutter-7a8e9591e324637d80e4bfd18f1b75774c521dda.tar.gz
kutter-7a8e9591e324637d80e4bfd18f1b75774c521dda.tar.xz
kutter-7a8e9591e324637d80e4bfd18f1b75774c521dda.zip
stm32: Improved CAN support for STM32 (#2976)
Reworked the STM32F0 CAN bus implementation. It's more robust and higher performance. Added support for function remapping to different pins. API is emulating an STM32F0. Improved and ported CAN bus to STM32F0, F1 and F4. Signed-off-by: Pontus Borg <glpontus@gmail.com>
Diffstat (limited to 'src/stm32/can.c')
-rw-r--r--src/stm32/can.c440
1 files changed, 440 insertions, 0 deletions
diff --git a/src/stm32/can.c b/src/stm32/can.c
new file mode 100644
index 00000000..7e3d75f1
--- /dev/null
+++ b/src/stm32/can.c
@@ -0,0 +1,440 @@
+/*
+ * Serial over CAN emulation for STM32 boards.
+ *
+ * Copyright (C) 2019 Eug Krashtan <eug.krashtan@gmail.com>
+ * Copyright (C) 2020 Pontus Borg <glpontus@gmail.com>
+ * This file may be distributed under the terms of the GNU GPLv3 license.
+ *
+ */
+
+#include "autoconf.h" //
+#include "board/armcm_boot.h" // armcm_enable_irq
+#include "board/serial_irq.h" // serial_rx_byte
+#include "command.h" // DECL_CONSTANT_STR
+#include "internal.h" // enable_pclock
+#include "sched.h" // DECL_INIT
+#include <string.h>
+#include "can.h"
+
+#if (CONFIG_CAN_PINS_PA11_PA12)
+DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PA11,PA12");
+#define GPIO_Rx GPIO('A', 11)
+#define GPIO_Tx GPIO('A', 12)
+#endif
+#if (CONFIG_CAN_PINS_PB8_PB9)
+DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB8,PB9");
+#define GPIO_Rx GPIO('B', 8)
+#define GPIO_Tx GPIO('B', 9)
+#endif
+#if (CONFIG_CAN_PINS_PI8_PH13)
+DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PI8,PH13");
+#define GPIO_Rx GPIO('I', 8)
+#define GPIO_Tx GPIO('H', 13)
+#endif
+#if (CONFIG_CAN_PINS_PB5_PB6)
+DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB5,PB6");
+#define GPIO_Rx GPIO('B', 5)
+#define GPIO_Tx GPIO('B', 6)
+#endif
+#if (CONFIG_CAN_PINS_PB12_PB13)
+DECL_CONSTANT_STR("RESERVE_PINS_CAN", "PB12,PB13");
+#define GPIO_Rx GPIO('B', 12)
+#define GPIO_Tx GPIO('B', 13)
+#endif
+
+#if (CONFIG_MACH_STM32F0)
+#define SOC_CAN CAN
+#define CAN_RX0_IRQn CEC_CAN_IRQn
+#define CAN_RX1_IRQn CEC_CAN_IRQn
+#define CAN_TX_IRQn CEC_CAN_IRQn
+#define CAN_SCE_IRQn CEC_CAN_IRQn
+#define CAN_FUNCTION GPIO_FUNCTION(4) // Alternative function mapping number
+#endif
+
+#if (CONFIG_MACH_STM32F1)
+#define SOC_CAN CAN1
+#define CAN_RX0_IRQn CAN1_RX0_IRQn
+#define CAN_RX1_IRQn CAN1_RX1_IRQn
+#define CAN_TX_IRQn CAN1_TX_IRQn
+#define CAN_SCE_IRQn CAN1_SCE_IRQn
+#define CAN_FUNCTION GPIO_FUNCTION(9) // Alternative function mapping number
+#endif
+
+
+#if (CONFIG_MACH_STM32F4)
+#warning CAN on STM32F4 is untested
+#if (CONFIG_CAN_PINS_PA11_PA12 ||
+ CONFIG_CAN_PINS_PB8_PB9 ||
+ CONFIG_CAN_PINS_PI8_PH13)
+#define SOC_CAN CAN1
+#define CAN_RX0_IRQn CAN1_RX0_IRQn
+#define CAN_RX1_IRQn CAN1_RX1_IRQn
+#define CAN_TX_IRQn CAN1_TX_IRQn
+#define CAN_SCE_IRQn CAN1_SCE_IRQn
+#elsif ((CONFIG_CAN_PINS_PB5_PB6 || CONFIG_CAN_PINS_PB12_PB13)
+#define SOC_CAN CAN2
+#define CAN_RX0_IRQn CAN2_RX0_IRQn
+#define CAN_RX1_IRQn CAN2_RX1_IRQn
+#define CAN_TX_IRQn CAN2_TX_IRQn
+#define CAN_SCE_IRQn CAN2_SCE_IRQn
+#else
+#error Uknown pins for STMF32F4 CAN
+#endif
+
+#define CAN_FUNCTION GPIO_FUNCTION(9) // Alternative function mapping number
+#endif
+
+
+#ifndef SOC_CAN
+#error No known CAN device for configured MCU
+#endif
+
+
+// TXFP makes packets posted to the TX mboxes transmit in chronologcal order
+// ABOM makes the hardware automatically leave bus-off state
+#define MCR_FLAGS (CAN_MCR_TXFP | CAN_MCR_ABOM)
+
+#define CAN_FILTER_NUMBER 0
+
+static uint16_t MyCanId = 0;
+
+static int can_find_empty_tx_mbox(void) {
+ uint32_t tsr = SOC_CAN->TSR;
+ if(tsr & CAN_TSR_TME0) return 0;
+ if(tsr & CAN_TSR_TME1) return 1;
+ if(tsr & CAN_TSR_TME2) return 2;
+ return -1;
+}
+
+static void can_transmit_mbox(uint32_t id, int mbox, uint32_t dlc, uint8_t *pkt)
+{
+ CAN_TxMailBox_TypeDef *mb = &SOC_CAN->sTxMailBox[mbox];
+ /* Set up the Id */
+ mb->TIR &= CAN_TI0R_TXRQ;
+ mb->TIR |= (id << CAN_TI0R_STID_Pos);
+
+ /* Set up the DLC */
+ mb->TDTR &= 0xFFFFFFF0U;
+ mb->TDTR |= (dlc & 0xFU);
+
+ /* Set up the data field */
+ if(pkt) {
+ mb->TDLR = ((uint32_t)pkt[3] << 24) |
+ ((uint32_t)pkt[2] << 16) |
+ ((uint32_t)pkt[1] << 8) |
+ ((uint32_t)pkt[0] << 0);
+ mb->TDHR = ((uint32_t)pkt[7] << 24) |
+ ((uint32_t)pkt[6] << 16) |
+ ((uint32_t)pkt[5] << 8) |
+ ((uint32_t)pkt[4] << 0);
+ }
+
+ /* Request transmission */
+ __sync_synchronize(); // disable write optimization
+ mb->TIR |= CAN_TI0R_TXRQ;
+
+}
+
+// Blocking transmit function, it can race with the IRQ driven TX handler.
+// This should(tm) not happen
+static void can_transmit(uint32_t id, uint32_t dlc, uint8_t *pkt)
+{
+ int mbox = -1;
+
+ do {
+ mbox = can_find_empty_tx_mbox();
+ } while(mbox < 0);
+
+ can_transmit_mbox(id, mbox, dlc, pkt);
+}
+
+// Convert Unique 96-bit value into 48 bit representation
+static void pack_uuid(uint8_t* u)
+{
+ for(int i=0; i<SHORT_UUID_LEN; i++) {
+ u[i] = *((uint8_t*)(UID_BASE+i)) ^
+ *((uint8_t*)(UID_BASE+i+SHORT_UUID_LEN));
+ }
+}
+
+static void can_uuid_resp(void)
+{
+ uint8_t short_uuid[SHORT_UUID_LEN];
+ pack_uuid(short_uuid);
+ can_transmit(PKT_ID_UUID_RESP, SHORT_UUID_LEN, short_uuid);
+}
+
+static void get_rx_data(uint8_t* buf, unsigned int mbox)
+{
+ uint32_t rdlr = SOC_CAN->sFIFOMailBox[mbox].RDLR;
+ buf[0] = (rdlr >> 0) & 0xff;
+ buf[1] = (rdlr >> 8) & 0xff;
+ buf[2] = (rdlr >> 16) & 0xff;
+ buf[3] = (rdlr >> 24) & 0xff;
+ uint32_t rdhr = SOC_CAN->sFIFOMailBox[mbox].RDHR;
+ buf[4] = (rdhr >> 0) & 0xff;
+ buf[5] = (rdhr >> 8) & 0xff;
+ buf[6] = (rdhr >> 16) & 0xff;
+ buf[7] = (rdhr >> 24) & 0xff;
+}
+
+// Return true if more data is available to send or mailboxes are full
+int CAN_TxIrq(void) {
+ int txdata = 1;
+
+ // TODO: We need some kind of error handling?
+
+ while(txdata) {
+ int mbox = can_find_empty_tx_mbox();
+ if(mbox < 0) {
+ // All mboxes full, wait for next IRQ
+ return 1;
+ }
+ int i=0;
+ uint8_t databuf[8];
+ for (;i<8;i++)
+ {
+ if(serial_get_tx_byte(&(databuf[i])) == -1) {
+ txdata = 0;
+ break;
+ }
+ }
+ if (i>0) {
+ can_transmit_mbox(MyCanId+1, mbox, i, databuf);
+ }
+ }
+ return txdata;
+}
+
+void CAN_RxCpltCallback(unsigned int mbox)
+{
+ CAN_FIFOMailBox_TypeDef* mb = &SOC_CAN->sFIFOMailBox[mbox];
+ uint32_t id = (mb->RIR >> CAN_RI0R_STID_Pos) & 0x7FF;
+ uint8_t dlc = mb->RDTR & CAN_RDT0R_DLC;
+ uint8_t databuf[8];
+
+ if(!MyCanId) { // If serial not assigned yet
+ if(id==PKT_ID_UUID && dlc == 0) {
+ // Just inform host about my UUID
+ can_uuid_resp();
+ } else if (id == PKT_ID_SET) {
+ uint8_t short_uuid[SHORT_UUID_LEN];
+ pack_uuid(short_uuid);
+
+ // compare my UUID with packet to check if this packet mine
+ get_rx_data(databuf, mbox);
+ if (memcmp(&(databuf[2]), short_uuid, SHORT_UUID_LEN) == 0) {
+ memcpy(&MyCanId, databuf, sizeof(uint16_t));
+ /* Set new filter values */
+ uint32_t filternbrbitpos = (1U) << CAN_FILTER_NUMBER;
+ SOC_CAN->FA1R &= ~(filternbrbitpos);
+ /* Personal ID */
+ SOC_CAN->sFilterRegister[CAN_FILTER_NUMBER].FR1 =
+ ((uint32_t)(MyCanId<<5) << 16U);
+ /* Catch reset command */
+ SOC_CAN->sFilterRegister[CAN_FILTER_NUMBER].FR2 =
+ ((uint32_t)(PKT_ID_UUID<<5) << 16U);
+ /* Filter activation */
+ SOC_CAN->FA1R |= filternbrbitpos;
+ /* Leave the initialisation mode for the filter */
+ SOC_CAN->FMR &= ~(CAN_FMR_FINIT);
+ }
+ }
+ } else {
+ if (id == MyCanId) {
+ // compare my UUID with packet to check if this packet mine
+ if(dlc == 0) {
+ // empty packet == ping request
+ can_transmit(MyCanId+1, 0, NULL);
+ } else {
+ get_rx_data(databuf, mbox);
+ for(int i=0; i < dlc; i++ ) {
+ serial_rx_byte(databuf[i]);
+ }
+ }
+ }
+ else if (id == PKT_ID_UUID && dlc > 0)
+ {
+ get_rx_data(databuf, mbox);
+ if (memcmp(databuf, &MyCanId, 2) == 0)
+ {
+ // Reset from host
+ NVIC_SystemReset();
+ }
+ }
+ }
+}
+
+/**
+ * @brief This function handles CAN global interrupts
+ */
+void
+CAN_IRQHandler(void)
+{
+ // RX
+ if (SOC_CAN->RF0R & CAN_RF0R_FMP0) {
+ // Mailbox 0
+ while(SOC_CAN->RF0R & CAN_RF0R_FMP0) {
+ CAN_RxCpltCallback(0);
+ SOC_CAN->RF0R |= CAN_RF0R_RFOM0;
+ }
+ }
+ if (SOC_CAN->RF1R & CAN_RF1R_FMP1) {
+ // Mailbox 1
+ while(SOC_CAN->RF1R & CAN_RF1R_FMP1) {
+ CAN_RxCpltCallback(1);
+ SOC_CAN->RF1R |= CAN_RF1R_RFOM1;
+ }
+ }
+
+ /* Check Overrun flag for FIFO0 */
+ if(SOC_CAN->RF0R & CAN_RF0R_FOVR0)
+ {
+ /* Clear FIFO0 Overrun Flag */
+ SOC_CAN->RF0R |= CAN_RF0R_FOVR0;
+ }
+ /* Check Overrun flag for FIFO1 */
+ if(SOC_CAN->RF1R & CAN_RF1R_FOVR1)
+ {
+ /* Clear FIFO1 Overrun Flag */
+ SOC_CAN->RF1R |= CAN_RF1R_FOVR1;
+ }
+
+ // TX
+ if(SOC_CAN->IER & CAN_IER_TMEIE) { // TX IRQ enabled
+ if(!CAN_TxIrq())
+ SOC_CAN->IER &= ~CAN_IER_TMEIE; // Disable TXIRQ
+ }
+}
+
+static inline const uint32_t
+make_btr(uint32_t sjw, // Sync jump width, ... hmm
+ uint32_t time_seg1, // time segment before sample point, 1 .. 16
+ uint32_t time_seg2, // time segment after sample point, 1 .. 8
+ uint32_t brp) // Baud rate prescaler, 1 .. 1024
+{
+ return
+ ((uint32_t)(sjw-1)) << CAN_BTR_SJW_Pos
+ | ((uint32_t)(time_seg1-1)) << CAN_BTR_TS1_Pos
+ | ((uint32_t)(time_seg2-1)) << CAN_BTR_TS2_Pos
+ | ((uint32_t)(brp - 1)) << CAN_BTR_BRP_Pos;
+}
+
+
+static inline const uint32_t
+compute_btr(uint32_t pclock, uint32_t bitrate) {
+
+ /*
+ Some equations:
+ Tpclock = 1 / pclock
+ Tq = brp * Tpclock
+ Tbs1 = Tq * TS1
+ Tbs2 = Tq * TS2
+ NominalBitTime = Tq + Tbs1 + Tbs2
+ BaudRate = 1/NominalBitTime
+
+ Bit value sample point is after Tq+Tbs1. Ideal sample point
+ is at 87.5% of NominalBitTime
+
+ Use the lowest brp where ts1 and ts2 are in valid range
+ */
+
+ uint32_t bit_clocks = pclock / bitrate; // clock ticks per bit
+
+ uint32_t sjw = 2;
+ uint32_t qs;
+ // Find number of time quantas that gives us the exact wanted bit time
+ for(qs = 18; qs > 9; qs --) {
+ // check that bit_clocks / quantas is an integer
+ uint32_t brp_rem = bit_clocks % qs;
+ if(brp_rem == 0)
+ break;
+ }
+ uint32_t brp = bit_clocks / qs;
+ uint32_t time_seg2 = qs / 8; // sample at ~87.5%
+ uint32_t time_seg1 = qs - (1 + time_seg2);
+
+ return make_btr(sjw, time_seg1, time_seg2, brp);
+}
+
+void
+can_init(void)
+{
+ enable_pclock((uint32_t)SOC_CAN);
+
+ gpio_peripheral(GPIO_Rx, CAN_FUNCTION, 1);
+ gpio_peripheral(GPIO_Tx, CAN_FUNCTION, 0);
+
+ uint32_t pclock = get_pclock_frequency((uint32_t)SOC_CAN);
+
+ uint32_t btr = compute_btr(pclock, CONFIG_SERIAL_BAUD);
+
+ /*##-1- Configure the CAN #######################################*/
+
+ /* Exit from sleep mode */
+ SOC_CAN->MCR &= ~(CAN_MCR_SLEEP);
+ /* Request initialisation */
+ SOC_CAN->MCR |= CAN_MCR_INRQ;
+ /* Wait the acknowledge */
+ while( !(SOC_CAN->MSR & CAN_MSR_INAK) );
+
+ SOC_CAN->MCR |= MCR_FLAGS;
+ SOC_CAN->BTR = btr;
+
+ /* Request leave initialisation */
+ SOC_CAN->MCR &= ~(CAN_MCR_INRQ);
+ /* Wait the acknowledge */
+ while( SOC_CAN->MSR & CAN_MSR_INAK );
+
+ /*##-2- Configure the CAN Filter #######################################*/
+ uint32_t filternbrbitpos = (1U) << CAN_FILTER_NUMBER;
+
+ /* Select the start slave bank */
+ SOC_CAN->FMR |= CAN_FMR_FINIT;
+ /* Initialisation mode for the filter */
+ SOC_CAN->FA1R &= ~(filternbrbitpos);
+
+ SOC_CAN->sFilterRegister[CAN_FILTER_NUMBER].FR1 =
+ ((uint32_t)(PKT_ID_UUID<<5) << 16U);
+ SOC_CAN->sFilterRegister[CAN_FILTER_NUMBER].FR2 =
+ ((uint32_t)(PKT_ID_SET<<5) << 16U);
+
+ /*Identifier list mode for the filter*/
+ SOC_CAN->FM1R |= filternbrbitpos;
+ /* 32-bit scale for the filter */
+ SOC_CAN->FS1R |= filternbrbitpos;
+
+ /* FIFO 0 assignation for the filter */
+ SOC_CAN->FFA1R &= ~(filternbrbitpos);
+
+ /* Filter activation */
+ SOC_CAN->FA1R |= filternbrbitpos;
+ /* Leave the initialisation mode for the filter */
+ SOC_CAN->FMR &= ~(CAN_FMR_FINIT);
+
+ /*##-3- Configure Interrupts #################################*/
+
+ SOC_CAN->IER |= (CAN_IER_FMPIE0 | CAN_IER_FMPIE1); // RX mailbox IRQ
+
+ armcm_enable_irq(CAN_IRQHandler, CAN_RX0_IRQn, 0);
+ if(CAN_RX0_IRQn != CAN_RX1_IRQn)
+ armcm_enable_irq(CAN_IRQHandler, CAN_RX1_IRQn, 0);
+ if(CAN_RX0_IRQn != CAN_TX_IRQn)
+ armcm_enable_irq(CAN_IRQHandler, CAN_TX_IRQn, 0);
+ // TODO: CAN_SCE_IRQ?n
+
+
+ /*##-4- Say Hello #################################*/
+ can_uuid_resp();
+}
+DECL_INIT(can_init);
+
+void
+serial_enable_tx_irq(void)
+{
+ if(MyCanId == 0)
+ // Serial port not initialized
+ return;
+
+ SOC_CAN->IER |= CAN_IER_TMEIE; // TX mailbox IRQ
+}