diff options
Diffstat (limited to 'src/stm32/can.c')
-rw-r--r-- | src/stm32/can.c | 440 |
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 +} |