From 021c0bdf6b8303f7c9db205f50326ad3926497bf Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Tue, 13 Feb 2024 20:27:10 +0300 Subject: [PATCH] add pre-pre-pre-alpha code (just for fun) --- F1:F103/CAR_CANbus/WindShield/Makefile | 10 + F1:F103/CAR_CANbus/WindShield/Readme | 1 - F1:F103/CAR_CANbus/WindShield/Readme.md | 89 ++++++ F1:F103/CAR_CANbus/WindShield/adc.c | 110 +++++++ F1:F103/CAR_CANbus/WindShield/adc.h | 44 +++ F1:F103/CAR_CANbus/WindShield/can.c | 301 ++++++++++++++++++ F1:F103/CAR_CANbus/WindShield/can.h | 56 ++++ F1:F103/CAR_CANbus/WindShield/flash.c | 189 +++++++++++ F1:F103/CAR_CANbus/WindShield/flash.h | 40 +++ F1:F103/CAR_CANbus/WindShield/hardware.c | 149 +++++++++ F1:F103/CAR_CANbus/WindShield/hardware.h | 67 ++++ F1:F103/CAR_CANbus/WindShield/main.c | 85 +++++ F1:F103/CAR_CANbus/WindShield/openocd.cfg | 6 + F1:F103/CAR_CANbus/WindShield/proto.c | 193 +++++++++++ F1:F103/CAR_CANbus/WindShield/proto.h | 40 +++ F1:F103/CAR_CANbus/WindShield/strfunc.c | 268 ++++++++++++++++ F1:F103/CAR_CANbus/WindShield/strfunc.h | 37 +++ F1:F103/CAR_CANbus/WindShield/usart.c | 152 +++++++++ F1:F103/CAR_CANbus/WindShield/usart.h | 34 ++ F1:F103/CAR_CANbus/WindShield/version.inc | 2 + F1:F103/CAR_CANbus/WindShield/windshield.bin | Bin 0 -> 9508 bytes .../CAR_CANbus/WindShield/windshield.cflags | 1 + .../CAR_CANbus/WindShield/windshield.config | 7 + .../CAR_CANbus/WindShield/windshield.creator | 1 + .../WindShield/windshield.creator.user | 180 +++++++++++ .../windshield.creator.user.cf63021 | 160 ++++++++++ .../CAR_CANbus/WindShield/windshield.cxxflags | 1 + .../CAR_CANbus/WindShield/windshield.files | 15 + .../CAR_CANbus/WindShield/windshield.includes | 7 + 29 files changed, 2244 insertions(+), 1 deletion(-) create mode 100644 F1:F103/CAR_CANbus/WindShield/Makefile delete mode 100644 F1:F103/CAR_CANbus/WindShield/Readme create mode 100644 F1:F103/CAR_CANbus/WindShield/Readme.md create mode 100644 F1:F103/CAR_CANbus/WindShield/adc.c create mode 100644 F1:F103/CAR_CANbus/WindShield/adc.h create mode 100644 F1:F103/CAR_CANbus/WindShield/can.c create mode 100644 F1:F103/CAR_CANbus/WindShield/can.h create mode 100644 F1:F103/CAR_CANbus/WindShield/flash.c create mode 100644 F1:F103/CAR_CANbus/WindShield/flash.h create mode 100644 F1:F103/CAR_CANbus/WindShield/hardware.c create mode 100644 F1:F103/CAR_CANbus/WindShield/hardware.h create mode 100644 F1:F103/CAR_CANbus/WindShield/main.c create mode 100644 F1:F103/CAR_CANbus/WindShield/openocd.cfg create mode 100644 F1:F103/CAR_CANbus/WindShield/proto.c create mode 100644 F1:F103/CAR_CANbus/WindShield/proto.h create mode 100644 F1:F103/CAR_CANbus/WindShield/strfunc.c create mode 100644 F1:F103/CAR_CANbus/WindShield/strfunc.h create mode 100644 F1:F103/CAR_CANbus/WindShield/usart.c create mode 100644 F1:F103/CAR_CANbus/WindShield/usart.h create mode 100644 F1:F103/CAR_CANbus/WindShield/version.inc create mode 100755 F1:F103/CAR_CANbus/WindShield/windshield.bin create mode 100644 F1:F103/CAR_CANbus/WindShield/windshield.cflags create mode 100644 F1:F103/CAR_CANbus/WindShield/windshield.config create mode 100644 F1:F103/CAR_CANbus/WindShield/windshield.creator create mode 100644 F1:F103/CAR_CANbus/WindShield/windshield.creator.user create mode 100644 F1:F103/CAR_CANbus/WindShield/windshield.creator.user.cf63021 create mode 100644 F1:F103/CAR_CANbus/WindShield/windshield.cxxflags create mode 100644 F1:F103/CAR_CANbus/WindShield/windshield.files create mode 100644 F1:F103/CAR_CANbus/WindShield/windshield.includes diff --git a/F1:F103/CAR_CANbus/WindShield/Makefile b/F1:F103/CAR_CANbus/WindShield/Makefile new file mode 100644 index 0000000..633829d --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/Makefile @@ -0,0 +1,10 @@ +BINARY := windshield +# MCU code +MCU ?= F103x6 +# change this linking script depending on particular MCU model, +LDSCRIPT ?= stm32f103x6.ld +DEFINES := -DSTM32F10X_LD +INC_DIR := ../../inc + +include ../../makefile.f1 +include ../../../makefile.stm32 diff --git a/F1:F103/CAR_CANbus/WindShield/Readme b/F1:F103/CAR_CANbus/WindShield/Readme deleted file mode 100644 index 4550cd6..0000000 --- a/F1:F103/CAR_CANbus/WindShield/Readme +++ /dev/null @@ -1 +0,0 @@ -Manage windshields over CAN diff --git a/F1:F103/CAR_CANbus/WindShield/Readme.md b/F1:F103/CAR_CANbus/WindShield/Readme.md new file mode 100644 index 0000000..e6734f4 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/Readme.md @@ -0,0 +1,89 @@ +Manage windshields over CAN +=========================== + +## Pinout + +### Sorted by pin number + +| **Pin #** | **Pin name ** | **function** | **settings** | **comment ** | +| --------- | ------------- | ------------ | ---------------------- | --------------------------------------- | +| 1 | VBAT | 3v3 | | | +| 2 | PC13 | NC | | | +| 3 | PC14 | NC | | | +| 4 | PC15 | NC | | | +| 5 | OCS_IN | 8MHz | default | | +| 6 | OSC_OUT | 8MHz | default | | +| 7 | NRST | NRST conn | | reset MCU | +| 8 | VSSA | gnd | | | +| 9 | VDDA | 3v3 | | | +| 10 | PA0 | Vsen | Ain | motors current | +| 11 | PA1 | Power_EN | slow PP (1) | enable 5V power DC-DC | +| 12 | PA2 | L_UP | slow PP (0) | turn on left up semibridge | +| 13 | PA3 | R_UP | slow PP (0) | turn on right up semibridge | +| 14 | PA4 | NC | | | +| 15 | PA5 | NC | | | +| 16 | PA6 | R_DOWN | TIM3_ch1 PWM out | PWM of right down semibridge | +| 17 | PA7 | L_DOWN | TIM3_ch2 PWM out | PWM of left up semibridge | +| 18 | PB0 | NC | | | +| 19 | PB1 | NC | | | +| 20 | PB2 | NC | | | +| 21 | PB10 | NC | | | +| 22 | PB11 | NC | | | +| 23 | VSS1 | gnd | | | +| 24 | VDD1 | 3v3 | | | +| 25 | PB12 | UP_DIR | in pulldown | high when got command to move window up | +| 26 | PB13 | DOWN_DIR | in pulldown | -//- down (when connected to old wires) | +| 27 | PB14 | UP_BTN | in pullup | button control - move up | +| 28 | PB15 | DOWN_BTN | in pullup | -//- down | +| 29 | PA8 | NC | | | +| 30 | PA9 | USART Tx | AFPP | USART commands | +| 31 | PA10 | USART Rx | AF floating in | (test and so on) | +| 32 | PA11 | DOWN_SW | in pullup | down Hall switch | +| 33 | PA12 | UP_SW | in pullup | upper Hall switch | +| 34 | PA13 | SWDIO | | program/debug | +| 35 | VSS2 | gnd | | | +| 36 | VDD2 | 3v3 | | | +| 37 | PA14 | SWCLK | | program/debug | +| 38 | PA15 | NC | | | +| 39 | PB3 | NC | | | +| 40 | PB4 | NC | | | +| 41 | PB5 | NC | | | +| 42 | PB6 | NC | | | +| 43 | PB7 | NC | | | +| 44 | BOOT0 | BOOT conn | | bootloader activate | +| 45 | PB8 | CAN Rx | in floating or AF flin | CAN bus | +| 46 | PB9 | CAN Tx | AFPP | -//- | +| 47 | VSS3 | gnd | | | +| 48 | VDD3 | 3v3 | | | + + +## Sorted by pin name + +| **Pin #** | **Pin name ** | **function** | **settings** | **comment ** | +| --------- | ------------- | ------------ | ---------------------- | --------------------------------------- | +| 44 | BOOT0 | BOOT conn | | bootloader activate | +| 10 | PA0 | Vsen | Ain | motors current | +| 11 | PA1 | Power_EN | slow PP (1) | enable 5V power DC-DC | +| 12 | PA2 | L_UP | slow PP (0) | turn on left up semibridge | +| 13 | PA3 | R_UP | slow PP (0) | turn on right up semibridge | +| 16 | PA6 | R_DOWN | TIM3_ch1 PWM out | PWM of right down semibridge | +| 17 | PA7 | L_DOWN | TIM3_ch2 PWM out | PWM of left up semibridge | +| 30 | PA9 | USART Tx | AFPP | USART commands | +| 31 | PA10 | USART Rx | AF floating in | (test and so on) | +| 32 | PA11 | DOWN_SW | in pullup | down Hall switch | +| 33 | PA12 | UP_SW | in pullup | upper Hall switch | +| 34 | PA13 | SWDIO | | program/debug | +| 37 | PA14 | SWCLK | | program/debug | +| 45 | PB8 | CAN Rx | in floating or AF flin | CAN bus | +| 46 | PB9 | CAN Tx | AFPP | -//- | +| 25 | PB12 | UP_DIR | in pulldown | high when got command to move window up | +| 26 | PB13 | DOWN_DIR | in pulldown | -//- down (when connected to old wires) | +| 27 | PB14 | UP_BTN | in pullup | button control - move up | +| 28 | PB15 | DOWN_BTN | in pullup | -//- down | +| 5 | OCS_IN | 8MHz | default | | +| 6 | OSC_OUT | 8MHz | default | | +| 7 | NRST | NRST conn | | reset MCU | +| 8 | VSSA | gnd | | | +| 9 | VDDA | 3v3 | | | + +To format pretty tables [use](https://josh.fail/2022/pure-bash-markdown-table-generator/) `markdown-table -5 -s"|" < STM32F103C.md >> Readme.md`. diff --git a/F1:F103/CAR_CANbus/WindShield/adc.c b/F1:F103/CAR_CANbus/WindShield/adc.c new file mode 100644 index 0000000..4e60fd9 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/adc.c @@ -0,0 +1,110 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "adc.h" + +uint16_t ADC_array[NUMBER_OF_ADC_CHANNELS*9]; + +void adc_setup(){ + uint32_t ctr = 0; + // Enable clocking + RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; + RCC->AHBENR |= RCC_AHBENR_DMA1EN; + __DSB(); + // DMA configuration + DMA1_Channel1->CPAR = (uint32_t) (&(ADC1->DR)); + DMA1_Channel1->CMAR = (uint32_t)(ADC_array); + DMA1_Channel1->CNDTR = NUMBER_OF_ADC_CHANNELS * 9; + DMA1_Channel1->CCR = DMA_CCR_MINC | DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 + | DMA_CCR_CIRC | DMA_CCR_PL | DMA_CCR_EN; + RCC->CFGR = (RCC->CFGR & ~(RCC_CFGR_ADCPRE)) | RCC_CFGR_ADCPRE_DIV8; // ADC clock = RCC / 8 + // sampling time - 239.5 cycles for channels 0, 16 and 17 + ADC1->SMPR2 = ADC_SMPR2_SMP0; + ADC1->SMPR1 = ADC_SMPR1_SMP16 | ADC_SMPR1_SMP17; + // sequence order: 0 -> 16 -> 17 + ADC1->SQR3 = (0 << 0) | (16<<5) | (17 << 10); + ADC1->SQR1 = (NUMBER_OF_ADC_CHANNELS - 1) << 20; // amount of conversions + ADC1->CR1 = ADC_CR1_SCAN; // scan mode + // DMA, continuous mode; enable vref & Tsens; enable SWSTART as trigger + ADC1->CR2 = ADC_CR2_DMA | ADC_CR2_TSVREFE | ADC_CR2_CONT | ADC_CR2_EXTSEL | ADC_CR2_EXTTRIG; + // wake up ADC + ADC1->CR2 |= ADC_CR2_ADON; + __DSB(); + // wait for Tstab - at least 1us + while(++ctr < 0xff) nop(); + // calibration + ADC1->CR2 |= ADC_CR2_RSTCAL; + ctr = 0; while((ADC1->CR2 & ADC_CR2_RSTCAL) && ++ctr < 0xfffff); + ADC1->CR2 |= ADC_CR2_CAL; + ctr = 0; while((ADC1->CR2 & ADC_CR2_CAL) && ++ctr < 0xfffff); + // clear possible errors and start + ADC1->SR = 0; + ADC1->CR2 |= ADC_CR2_SWSTART; +} + + +/** + * @brief getADCval - calculate median value for `nch` channel + * @param nch - number of channel + * @return + */ +uint16_t getADCval(int nch){ + int i, addr = nch; +#define PIX_SORT(a,b) { if ((a)>(b)) PIX_SWAP((a),(b)); } +#define PIX_SWAP(a,b) { register uint16_t temp=(a);(a)=(b);(b)=temp; } + uint16_t p[9]; + for(i = 0; i < 9; ++i, addr += NUMBER_OF_ADC_CHANNELS){ // first we should prepare array for optmed + p[i] = ADC_array[addr]; + } + PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ; + PIX_SORT(p[0], p[1]) ; PIX_SORT(p[3], p[4]) ; PIX_SORT(p[6], p[7]) ; + PIX_SORT(p[1], p[2]) ; PIX_SORT(p[4], p[5]) ; PIX_SORT(p[7], p[8]) ; + PIX_SORT(p[0], p[3]) ; PIX_SORT(p[5], p[8]) ; PIX_SORT(p[4], p[7]) ; + PIX_SORT(p[3], p[6]) ; PIX_SORT(p[1], p[4]) ; PIX_SORT(p[2], p[5]) ; + PIX_SORT(p[4], p[7]) ; PIX_SORT(p[4], p[2]) ; PIX_SORT(p[6], p[4]) ; + PIX_SORT(p[4], p[2]) ; + return p[4]; +#undef PIX_SORT +#undef PIX_SWAP +} + +// get voltage @input nch (1/100V) +uint32_t getADCvoltage(int nch){ + uint32_t v = getADCval(nch); + v *= getVdd(); + v /= 0xfff; // 12bit ADC + return v; +} + +// return MCU temperature (degrees of celsius * 10) +int32_t getMCUtemp(){ + // Temp = (V25 - Vsense)/Avg_Slope + 25 + // V_25 = 1.45V, Slope = 4.3e-3 + int32_t Vsense = getVdd() * getADCval(CHTSENS); + int32_t temperature = 593920 - Vsense; // 593920 == 145*4096 + temperature /= 172; // == /(4096*10*4.3e-3), 10 - to convert from *100 to *10 + temperature += 250; + return(temperature); +} + +// return Vdd * 100 (V) +uint32_t getVdd(){ + uint32_t vdd = 120 * 4096; // 1.2V + vdd /= getADCval(CHVDD); + return vdd; +} diff --git a/F1:F103/CAR_CANbus/WindShield/adc.h b/F1:F103/CAR_CANbus/WindShield/adc.h new file mode 100644 index 0000000..31544e6 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/adc.h @@ -0,0 +1,44 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +#define NUMBER_OF_ADC_CHANNELS (3) + +// Vsen voltage channel +#define CHVSEN (0) +// channels for Tsens and Vdd +#define CHTSENS (1) +#define CHVDD (2) + +/** + * @brief ADC_array - array for ADC channels with median filtering: + * 0 - Isensor voltage + * 1 - internal Tsens + * 2 - Vdd + */ +extern uint16_t ADC_array[]; + +void adc_setup(); +int32_t getMCUtemp(); +uint32_t getVdd(); +uint16_t getADCval(int nch); +uint32_t getADCvoltage(int nch); + diff --git a/F1:F103/CAR_CANbus/WindShield/can.c b/F1:F103/CAR_CANbus/WindShield/can.c new file mode 100644 index 0000000..e51343b --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/can.c @@ -0,0 +1,301 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "can.h" +#include "hardware.h" +#include "proto.h" +#include "usart.h" + +// REMAPPED to PB8/PB9!!! + +#include // memcpy + +// circular buffer for received messages +static CAN_message messages[CAN_INMESSAGE_SIZE]; +static uint8_t first_free_idx = 0; // index of first empty cell +static int8_t first_nonfree_idx = -1; // index of first data cell +static uint16_t oldspeed = 100; // speed of last init + +static CAN_status can_status = CAN_STOP; + +static void can_process_fifo(uint8_t fifo_num); + +CAN_status CAN_get_status(){ + int st = can_status; + can_status = CAN_OK; + return st; +} + +// push next message into buffer; return 1 if buffer overfull +static int CAN_messagebuf_push(CAN_message *msg){ + //MSG("Try to push\n"); +#ifdef EBUG + usart_send("push\n"); +#endif + if(first_free_idx == first_nonfree_idx){ +#ifdef EBUG + usart_send("INBUF OVERFULL\n"); +#endif + return 1; // no free space + } + if(first_nonfree_idx < 0) first_nonfree_idx = 0; // first message in empty buffer + memcpy(&messages[first_free_idx++], msg, sizeof(CAN_message)); + // need to roll? + if(first_free_idx == CAN_INMESSAGE_SIZE) first_free_idx = 0; + return 0; +} + +// pop message from buffer +CAN_message *CAN_messagebuf_pop(){ + if(first_nonfree_idx < 0) return NULL; + CAN_message *msg = &messages[first_nonfree_idx++]; + if(first_nonfree_idx == CAN_INMESSAGE_SIZE) first_nonfree_idx = 0; + if(first_nonfree_idx == first_free_idx){ // buffer is empty - refresh it + first_nonfree_idx = -1; + first_free_idx = 0; + } + return msg; +} + +void CAN_reinit(uint16_t speed){ + CAN1->TSR |= CAN_TSR_ABRQ0 | CAN_TSR_ABRQ1 | CAN_TSR_ABRQ2; + RCC->APB1RSTR |= RCC_APB1RSTR_CAN1RST; + RCC->APB1RSTR &= ~RCC_APB1RSTR_CAN1RST; + CAN_setup(speed); +} + +/* +Can filtering: FSCx=0 (CAN1->FS1R) -> 16-bit identifiers +MASK: FBMx=0 (CAN1->FM1R), two filters (n in FR1 and n+1 in FR2) + ID: CAN1->sFilterRegister[x].FRn[0..15] + MASK: CAN1->sFilterRegister[x].FRn[16..31] + FR bits: STID[10:0] RTR IDE EXID[17:15] +LIST: FBMx=1, four filters (n&n+1 in FR1, n+2&n+3 in FR2) + IDn: CAN1->sFilterRegister[x].FRn[0..15] + IDn+1: CAN1->sFilterRegister[x].FRn[16..31] +*/ + +/* +Can timing: main freq - APB (PLL=48MHz) +segment = 1sync + TBS1 + TBS2, sample point is between TBS1 and TBS2, +so if TBS1=4 and TBS2=3, sum=8, bit sampling freq is 48/8 = 6MHz +-> to get 100kbps we need prescaler=60 + 250kbps - 24 + 500kbps - 12 + 1MBps - 6 +*/ + +// speed - in kbps +void CAN_setup(uint16_t speed){ + if(speed == 0) speed = oldspeed; + else if(speed < 50) speed = 50; + else if(speed > 3000) speed = 3000; + oldspeed = speed; + uint32_t tmout = 16000000; + // Configure GPIO: PB8 - CAN_Rx, PB9 - CAN_Tx + /* (1) Select AF mode (10) on PB8 and PB9 */ + /* (2) AF4 for CAN signals */ + RCC->APB2ENR |= RCC_APB2ENR_AFIOEN | RCC_APB2ENR_IOPBEN; + AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_REMAP2; + GPIOB->CRH = 0; + //pin_set(GPIOB, 1<<8); + GPIOB->CRH = (GPIOB->CRH & ~(CRH(8,0xf)|CRH(9,0xf))) | + CRH(8, CNF_FLINPUT | MODE_INPUT) | CRH(9, CNF_AFPP | MODE_NORMAL); + /* Enable the peripheral clock CAN */ + RCC->APB1ENR |= RCC_APB1ENR_CAN1EN; + /* Configure CAN */ + /* (1) Enter CAN init mode to write the configuration */ + /* (2) Wait the init mode entering */ + /* (3) Exit sleep mode */ + /* (4) Normal mode, set timing to 100kb/s: TBS1 = 4, TBS2 = 3, prescaler = 60 */ + /* (5) Leave init mode */ + /* (6) Wait the init mode leaving */ + /* (7) Enter filter init mode, (16-bit + mask, bank 0 for FIFO 0) */ + /* (8) Acivate filter 0 for two IDs */ + /* (9) Identifier list mode */ + /* (10) Set the Id list */ + /* (12) Leave filter init */ + /* (13) Set error interrupts enable (& bus off) */ + CAN1->MCR |= CAN_MCR_INRQ; /* (1) */ + while((CAN1->MSR & CAN_MSR_INAK) != CAN_MSR_INAK) /* (2) */ + if(--tmout == 0) break; + CAN1->MCR &=~ CAN_MCR_SLEEP; /* (3) */ + CAN1->MCR |= CAN_MCR_ABOM; /* allow automatically bus-off */ + + CAN1->BTR = 2 << 20 | 3 << 16 | (4500/speed - 1); //| CAN_BTR_SILM | CAN_BTR_LBKM; /* (4) */ + CAN1->MCR &= ~CAN_MCR_INRQ; /* (5) */ + tmout = 16000000; + while((CAN1->MSR & CAN_MSR_INAK) == CAN_MSR_INAK) /* (6) */ + if(--tmout == 0) break; + // accept ALL + CAN1->FMR = CAN_FMR_FINIT; /* (7) */ + CAN1->FA1R = CAN_FA1R_FACT0 | CAN_FA1R_FACT1; /* (8) */ + // set to 1 all needed bits of CAN1->FFA1R to switch given filters to FIFO1 + CAN1->sFilterRegister[0].FR1 = (1<<21)|(1<<5); // all odd IDs + CAN1->FFA1R = 2; // filter 1 for FIFO1, filter 0 - for FIFO0 + CAN1->sFilterRegister[1].FR1 = (1<<21); // all even IDs + CAN1->FMR &= ~CAN_FMR_FINIT; /* (12) */ + CAN1->IER |= CAN_IER_ERRIE | CAN_IER_FOVIE0 | CAN_IER_FOVIE1 | CAN_IER_BOFIE; /* (13) */ + + /* Configure IT */ + NVIC_SetPriority(USB_LP_CAN1_RX0_IRQn, 0); // RX FIFO0 IRQ + NVIC_SetPriority(CAN1_RX1_IRQn, 0); // RX FIFO1 IRQ + NVIC_SetPriority(CAN1_SCE_IRQn, 0); // RX status changed IRQ + NVIC_EnableIRQ(USB_LP_CAN1_RX0_IRQn); + NVIC_EnableIRQ(CAN1_RX1_IRQn); + NVIC_EnableIRQ(CAN1_SCE_IRQn); + CAN1->MSR = 0; // clear SLAKI, WKUI, ERRI + can_status = CAN_READY; +} + +void can_proc(){ + // check for messages in FIFO0 & FIFO1 + if(CAN1->RF0R & CAN_RF0R_FMP0){ + can_process_fifo(0); + } + if(CAN1->RF1R & CAN_RF1R_FMP1){ + can_process_fifo(1); + } + IWDG->KR = IWDG_REFRESH; + if(CAN1->ESR & (CAN_ESR_BOFF | CAN_ESR_EPVF | CAN_ESR_EWGF)){ // much errors - restart CAN BUS + usart_send("\nToo much errors, restarting CAN!\n"); + // request abort for all mailboxes + CAN1->TSR |= CAN_TSR_ABRQ0 | CAN_TSR_ABRQ1 | CAN_TSR_ABRQ2; + // reset CAN bus + RCC->APB1RSTR |= RCC_APB1RSTR_CAN1RST; + RCC->APB1RSTR &= ~RCC_APB1RSTR_CAN1RST; + CAN_setup(0); + } +} + +CAN_status can_send(uint8_t *msg, uint8_t len, uint16_t target_id){ + uint8_t mailbox = 0; + // check first free mailbox + if(CAN1->TSR & (CAN_TSR_TME)){ + mailbox = (CAN1->TSR & CAN_TSR_CODE) >> 24; + }else{ // no free mailboxes + return CAN_BUSY; + } + CAN_TxMailBox_TypeDef *box = &CAN1->sTxMailBox[mailbox]; + uint32_t lb = 0, hb = 0; + switch(len){ + case 8: + hb |= (uint32_t)msg[7] << 24; + __attribute__((fallthrough)); + case 7: + hb |= (uint32_t)msg[6] << 16; + __attribute__((fallthrough)); + case 6: + hb |= (uint32_t)msg[5] << 8; + __attribute__((fallthrough)); + case 5: + hb |= (uint32_t)msg[4]; + __attribute__((fallthrough)); + case 4: + lb |= (uint32_t)msg[3] << 24; + __attribute__((fallthrough)); + case 3: + lb |= (uint32_t)msg[2] << 16; + __attribute__((fallthrough)); + case 2: + lb |= (uint32_t)msg[1] << 8; + __attribute__((fallthrough)); + default: + lb |= (uint32_t)msg[0]; + } + box->TDLR = lb; + box->TDHR = hb; + box->TDTR = len; + box->TIR = (target_id & 0x7FF) << 21 | CAN_TI0R_TXRQ; + return CAN_OK; +} + +static void can_process_fifo(uint8_t fifo_num){ + if(fifo_num > 1) return; + CAN_FIFOMailBox_TypeDef *box = &CAN1->sFIFOMailBox[fifo_num]; + volatile uint32_t *RFxR = (fifo_num) ? &CAN1->RF1R : &CAN1->RF0R; + // read all + while(*RFxR & CAN_RF0R_FMP0){ // amount of messages pending + // CAN_RDTxR: (16-31) - timestamp, (8-15) - filter match index, (0-3) - data length + /* TODO: check filter match index if more than one ID can receive */ + CAN_message msg; + uint8_t *dat = msg.data; + uint8_t len = box->RDTR & 0x0f; + msg.length = len; + msg.ID = box->RIR >> 21; + //msg.filterNo = (box->RDTR >> 8) & 0xff; + //msg.fifoNum = fifo_num; + if(len){ // message can be without data + uint32_t hb = box->RDHR, lb = box->RDLR; + switch(len){ + case 8: + dat[7] = hb>>24; + __attribute__((fallthrough)); + case 7: + dat[6] = (hb>>16) & 0xff; + __attribute__((fallthrough)); + case 6: + dat[5] = (hb>>8) & 0xff; + __attribute__((fallthrough)); + case 5: + dat[4] = hb & 0xff; + __attribute__((fallthrough)); + case 4: + dat[3] = lb>>24; + __attribute__((fallthrough)); + case 3: + dat[2] = (lb>>16) & 0xff; + __attribute__((fallthrough)); + case 2: + dat[1] = (lb>>8) & 0xff; + __attribute__((fallthrough)); + case 1: + dat[0] = lb & 0xff; + } + } + if(CAN_messagebuf_push(&msg)) return; // error: buffer is full, try later + *RFxR |= CAN_RF0R_RFOM0; // release fifo for access to next message + } + //if(*RFxR & CAN_RF0R_FULL0) *RFxR &= ~CAN_RF0R_FULL0; + *RFxR = 0; // clear FOVR & FULL +} + +void usb_lp_can_rx0_isr(){ // Rx FIFO0 (overrun) + if(CAN1->RF0R & CAN_RF0R_FOVR0){ // FIFO overrun + CAN1->RF0R &= ~CAN_RF0R_FOVR0; + can_status = CAN_FIFO_OVERRUN; + } +} + +void can_rx1_isr(){ // Rx FIFO1 (overrun) + if(CAN1->RF1R & CAN_RF1R_FOVR1){ + CAN1->RF1R &= ~CAN_RF1R_FOVR1; + can_status = CAN_FIFO_OVERRUN; + } +} + +void can_sce_isr(){ // status changed + if(CAN1->MSR & CAN_MSR_ERRI){ // Error + CAN1->MSR &= ~CAN_MSR_ERRI; + // request abort for problem mailbox + if(CAN1->TSR & CAN_TSR_TERR0) CAN1->TSR |= CAN_TSR_ABRQ0; + if(CAN1->TSR & CAN_TSR_TERR1) CAN1->TSR |= CAN_TSR_ABRQ1; + if(CAN1->TSR & CAN_TSR_TERR2) CAN1->TSR |= CAN_TSR_ABRQ2; + can_status = CAN_ERR; + } +} diff --git a/F1:F103/CAR_CANbus/WindShield/can.h b/F1:F103/CAR_CANbus/WindShield/can.h new file mode 100644 index 0000000..77d318b --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/can.h @@ -0,0 +1,56 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +// amount of filter banks in STM32F0 +#define STM32F0FBANKNO 28 +// flood period in milliseconds +#define FLOOD_PERIOD_MS 5 + +// incoming message buffer size +#define CAN_INMESSAGE_SIZE (8) + +// CAN message +typedef struct{ + uint8_t data[8]; // up to 8 bytes of data + uint8_t length; // data length + uint16_t ID; // ID of receiver +} CAN_message; + +typedef enum{ + CAN_STOP, + CAN_READY, + CAN_BUSY, + CAN_OK, + CAN_ERR, + CAN_FIFO_OVERRUN +} CAN_status; + +CAN_status CAN_get_status(); + +void CAN_reinit(uint16_t speed); +void CAN_setup(uint16_t speed); + +CAN_status can_send(uint8_t *msg, uint8_t len, uint16_t target_id); +void can_proc(); +void printCANerr(); + +CAN_message *CAN_messagebuf_pop(); diff --git a/F1:F103/CAR_CANbus/WindShield/flash.c b/F1:F103/CAR_CANbus/WindShield/flash.c new file mode 100644 index 0000000..866f8c2 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/flash.c @@ -0,0 +1,189 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "stm32f1.h" + +#include "flash.h" +#include "strfunc.h" +#include "usart.h" +#include // memcpy + +extern const uint32_t __varsstart, _BLOCKSIZE; +static const uint32_t FLASH_blocksize = (uint32_t)&_BLOCKSIZE; +static uint32_t maxCnum = 1024 / sizeof(user_conf); // can't use blocksize here + +#define USERCONF_INITIALIZER { \ + .userconf_sz = sizeof(user_conf) \ + ,.canspeed = 250 \ + ,.canID = 0xAA \ + } + +static int write2flash(const void*, const void*, uint32_t); +const user_conf *Flash_Data = (const user_conf *)(&__varsstart); +user_conf the_conf = USERCONF_INITIALIZER; + +static int currentconfidx = -1; // index of current configuration + +/** + * @brief binarySearch - binary search in flash for last non-empty cell + * any struct searched should have its sizeof() @ the first field!!! + * @param l - left index + * @param r - right index (should be @1 less than last index!) + * @param start - starting address + * @param stor_size - size of structure to search + * @return index of non-empty cell or -1 + */ +static int binarySearch(int r, const uint8_t *start, int stor_size){ + int l = 0; + while(r >= l){ + int mid = l + (r - l) / 2; + const uint8_t *s = start + mid * stor_size; + if(*((const uint16_t*)s) == stor_size){ + if(*((const uint16_t*)(s + stor_size)) == 0xffff){ // next is free + return mid; + }else{ // element is to the right + l = mid + 1; + } + }else{ // element is to the left + r = mid - 1; + } + } + return -1; // not found +} + +/** + * @brief flashstorage_init - initialization of user conf storage + * run in once @ start + */ +void flashstorage_init(){ + if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ + uint32_t flsz = FLASH_SIZE * 1024; // size in bytes + flsz -= (uint32_t)(&__varsstart) - FLASH_BASE; + maxCnum = flsz / sizeof(user_conf); + } + // -1 if there's no data at all & flash is clear; maxnum-1 if flash is full + currentconfidx = binarySearch((int)maxCnum-2, (const uint8_t*)Flash_Data, sizeof(user_conf)); + if(currentconfidx > -1){ + memcpy(&the_conf, &Flash_Data[currentconfidx], sizeof(user_conf)); + } +} + +// store new configuration +// @return 0 if all OK +int store_userconf(){ + // maxnum - 3 means that there always should be at least one empty record after last data + // for binarySearch() checking that there's nothing more after it! + if(currentconfidx > (int)maxCnum - 3){ // there's no more place + currentconfidx = 0; + if(erase_storage(-1)) return 1; + }else ++currentconfidx; // take next data position (0 - within first run after firmware flashing) + return write2flash((const void*)&Flash_Data[currentconfidx], &the_conf, sizeof(the_conf)); +} + +static int write2flash(const void *start, const void *wrdata, uint32_t stor_size){ + int ret = 0; + if (FLASH->CR & FLASH_CR_LOCK){ // unloch flash + FLASH->KEYR = FLASH_KEY1; + FLASH->KEYR = FLASH_KEY2; + } + while (FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH; + FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; // clear all flags + FLASH->CR |= FLASH_CR_PG; + const uint16_t *data = (const uint16_t*) wrdata; + volatile uint16_t *address = (volatile uint16_t*) start; + uint32_t i, count = (stor_size + 1) / 2; + for(i = 0; i < count; ++i){ + *(volatile uint16_t*)(address + i) = data[i]; + while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH; + if(*(volatile uint16_t*)(address + i) != data[i]){ + usart_send("DON'T MATCH!\n"); + ret = 1; + break; + } + if(FLASH->SR & FLASH_SR_PGERR){ + usart_send("Prog err\n"); + ret = 1; // program error - meet not 0xffff + break; + } + FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; + } + FLASH->CR &= ~(FLASH_CR_PG); + return ret; +} + +// erase Nth page of flash storage (flash should be prepared!) +static int erase_pageN(int N){ + int ret = 0; + FLASH->AR = (uint32_t)Flash_Data + N*FLASH_blocksize; + FLASH->CR |= FLASH_CR_STRT; + while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH; + FLASH->SR = FLASH_SR_EOP; + if(FLASH->SR & FLASH_SR_WRPRTERR){ /* Check Write protection error */ + ret = 1; + FLASH->SR = FLASH_SR_WRPRTERR; /* Clear the flag by software by writing it at 1*/ + } + return ret; +} + +// erase full storage (npage < 0) or its nth page; @return 0 if all OK +int erase_storage(int npage){ + int ret = 0; + uint32_t end = 1, start = 0, flsz = 0; + if(FLASH_SIZE > 0 && FLASH_SIZE < 20000){ + flsz = FLASH_SIZE * 1024; // size in bytes + flsz -= (uint32_t)Flash_Data - FLASH_BASE; + } + end = flsz / FLASH_blocksize; +#ifdef EBUG + usart_send("FLASH_SIZE="); usart_send(u2str(FLASH_SIZE)); + usart_send("\nflsz="); usart_send(u2str(flsz)); + usart_send("\nend="); usart_send(u2str(end)); + usart_send("\ncurrentconfidx="); usart_send(u2str(currentconfidx)); + usart_send("\nmaxCnum="); usart_send(u2str(maxCnum)); + usart_send("\n"); +#endif + if(end == 0 || end >= FLASH_SIZE) return 1; + if(npage > -1){ // erase only one page + if((uint32_t)npage >= end) return 1; + start = npage; + end = start + 1; + } + if((FLASH->CR & FLASH_CR_LOCK) != 0){ + FLASH->KEYR = FLASH_KEY1; + FLASH->KEYR = FLASH_KEY2; + } + while(FLASH->SR & FLASH_SR_BSY) IWDG->KR = IWDG_REFRESH; + FLASH->SR = FLASH_SR_EOP | FLASH_SR_PGERR | FLASH_SR_WRPRTERR; + FLASH->CR |= FLASH_CR_PER; + for(uint32_t i = start; i < end; ++i){ + if(erase_pageN(i)){ + ret = 1; + break; + } + } + FLASH->CR &= ~FLASH_CR_PER; + return ret; +} + +void dump_userconf(){ + usart_send("userconf_sz="); printu(the_conf.userconf_sz); + usart_send("\ncurrentconfidx="); usart_send(u2str(currentconfidx)); + usart_send("\nCAN_speed="); printu(the_conf.canspeed); + usart_send("\nCAN_ID="); printu(the_conf.canID); + usart_send("\n"); +} diff --git a/F1:F103/CAR_CANbus/WindShield/flash.h b/F1:F103/CAR_CANbus/WindShield/flash.h new file mode 100644 index 0000000..4cbe618 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/flash.h @@ -0,0 +1,40 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +#define FLASH_SIZE_REG ((uint32_t)0x1FFFF7E0) +#define FLASH_SIZE *((uint16_t*)FLASH_SIZE_REG) + +/* + * struct to save user configurations + */ +typedef struct __attribute__((packed, aligned(4))){ + uint16_t userconf_sz; // "magick number" + uint16_t canspeed; // CAN bus speed + uint16_t canID; // CAN bus device ID +} user_conf; + +extern user_conf the_conf; + +void flashstorage_init(); +int store_userconf(); +int erase_storage(int npage); +void dump_userconf(); diff --git a/F1:F103/CAR_CANbus/WindShield/hardware.c b/F1:F103/CAR_CANbus/WindShield/hardware.c new file mode 100644 index 0000000..33a8688 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/hardware.c @@ -0,0 +1,149 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "hardware.h" + +/* pinout: + +| **Pin #** | **Pin name ** | **function** | **settings** | **comment ** | +| --------- | ------------- | ------------ | ---------------------- | --------------------------------------- | +| 10 | PA0 | Vsen | Ain | motors current | +| 11 | PA1 | Power_EN | slow PP (1) | enable 5V power DC-DC | +| 12 | PA2 | L_UP | slow PP (0) | turn on left up semibridge | +| 13 | PA3 | R_UP | slow PP (0) | turn on right up semibridge | +| 16 | PA6 | R_DOWN | TIM3_ch1 PWM out | PWM of right down semibridge | +| 17 | PA7 | L_DOWN | TIM3_ch2 PWM out | PWM of left up semibridge | +| 30 | PA9 | USART Tx | AFPP | USART commands | + +| 31 | PA10 | USART Rx | AF floating in | (test and so on) | + +| 32 | PA11 | DOWN_SW | in pullup | down Hall switch | +| 33 | PA12 | UP_SW | in pullup | upper Hall switch | +| 45 | PB8 | CAN Rx | in floating or AF flin | CAN bus | + +| 46 | PB9 | CAN Tx | AFPP | -//- | + +| 25 | PB12 | UP_DIR | in pulldown | high when got command to move window up | +| 26 | PB13 | DOWN_DIR | in pulldown | -//- down (when connected to old wires) | +| 27 | PB14 | UP_BTN | in pullup | button control - move up | +| 28 | PB15 | DOWN_BTN | in pullup | -//- down | + + */ + +// last incr/decr time and minimal time between incr/decr CCRx +static uint32_t PWM_lasttime = 0, PWM_deltat = 1; +static int direction = 0, accel = 0; // rotation direction and acceleration/deceleration + +void gpio_setup(void){ + // PB8 & PB9 (CAN) setup in can.c; PA9 & PA10 (USART) in usart.c; PA6 & PA7 will be configured later + RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN; + // pullups & initial values + GPIOA->ODR = (1<<11) | (1<<11) | (1<<12); + GPIOB->ODR = (1<<14) | (1<<15); + GPIOA->CRL = CRL(0, CNF_ANALOG) | CRL(1, CNF_PPOUTPUT|MODE_SLOW) | CRL(2, CNF_PPOUTPUT|MODE_SLOW) | + CRL(3, CNF_PPOUTPUT|MODE_SLOW); + GPIOA->CRH = CRH(11, CNF_PUDINPUT) | CRH(12, CNF_PUDINPUT); + GPIOB->CRH = CRH(12, CNF_PUDINPUT) | CRH(13, CNF_PUDINPUT) | CRH(14, CNF_PUDINPUT) | CRH(15, CNF_PUDINPUT); + // setup timer: 100 ticks per full PWM range, 2kHz -> 200kHz timer frequency + RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // enable TIM3 clocking + TIM3->CR1 = 0; + TIM3->PSC = (TIM3FREQ/PWMFREQ) - 1; // 359 ticks for 200kHz + TIM3->ARR = PWMMAX - 1; + TIM3->CCR1 = 0; // inactive + TIM3->CCR2 = 0; + // PWM mode 1 (active->inactive) + TIM3->CCMR1 = TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE | + TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE; + // main PWM output + TIM3->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E; +} + +// set minimal pause between successive CCRx increments or decrements; return TRUE if OK +int set_dT(uint32_t d){ + if(d > 1000) return FALSE; + PWM_deltat = d; + return TRUE; +} + +// stop or start rotation in given direction (only if motor stops); return TRUE if OK +// dir == 0 to stop; > 0 to rotate CW (L_UP, R_DOWN), < 0 to rotate CCW (L_DOWN, R_UP) +int motor_ctl(int32_t dir){ + if(TIM3->CR1 & TIM_CR1_CEN){ + if(direction && dir) return FALSE; // motor is moving while trying to move it + if(direction == 0 && dir == 0) return TRUE; // already stopped + } + if(dir == 0){ // stop motor -> deceleration + accel = -1; + PWM_lasttime = Tms; + return TRUE; + } + accel = 1; + if(dir > 0){ // start in positive direction + direction = 1; + set_up(UP_LEFT); + set_pwm(PWM_RIGHT, 1); + }else{ // negative + direction = -1; + set_up(UP_RIGHT); + set_pwm(PWM_LEFT, 1); + } + PWM_lasttime = Tms; + start_pwm(); + return TRUE; +} + +// extremal stop +void motor_break(){ + up_off(); + direction = 0; accel = 0; + stop_pwm(); +} + +// simplest state machine of motor control +void motor_process(){ + if(!direction) return; // motor isn't moving + if(Tms - PWM_lasttime < PWM_deltat) return; + volatile uint16_t *CCRx = (direction > 0) ? &TIM3->CCR1 : &TIM3->CCR2; // current CCRx + if(accel < 0){ // decrement + if(*CCRx == 0){ // stopped + up_off(); + direction = 0; + accel = 0; + stop_pwm(); + return; + } + // TODO: here we should check currents and if failure turn off immediatelly + --*CCRx; + }else{ + // TODO: here we should check currents and increment only if all OK; decrement and change to accel if fail + if(accel){ // acceleration + if(*CCRx == PWMMAX) accel = 0; + else ++*CCRx; + } // else do nothing - moving with constant speed + } + PWM_lasttime = Tms; +} + +void iwdg_setup(){ + uint32_t tmout = 16000000; + RCC->CSR |= RCC_CSR_LSION; + while((RCC->CSR & RCC_CSR_LSIRDY) != RCC_CSR_LSIRDY){if(--tmout == 0) break;} + IWDG->KR = IWDG_START; + IWDG->KR = IWDG_WRITE_ACCESS; + IWDG->PR = IWDG_PR_PR_1; + IWDG->RLR = 1250; + tmout = 16000000; + while(IWDG->SR){if(--tmout == 0) break;} + IWDG->KR = IWDG_REFRESH; +} diff --git a/F1:F103/CAR_CANbus/WindShield/hardware.h b/F1:F103/CAR_CANbus/WindShield/hardware.h new file mode 100644 index 0000000..38e44ce --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/hardware.h @@ -0,0 +1,67 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +// frequency of TIM3 clocking +#define TIM3FREQ (72000000) +// PWM frequency +#define PWMFREQ (200000) +// max PWM value +#define PWMMAX (100) + +#define CONCAT(a,b) a ## b +#define STR_HELPER(s) #s +#define STR(s) STR_HELPER(s) + +#define FORMUSART(X) CONCAT(USART, X) +#define USARTX FORMUSART(USARTNUM) + +// turn ON upper shoulder, x==0 - L_UP, x==1 - R_UP +#define UP_LEFT 0 +#define UP_RIGHT 1 +#define _set_up(x) do{GPIOA->BSRR = (x) ? (1<<3)|((1<<2)<<16) : (1<<2)|((1<<3)<<16); }while(0) +#define set_up(x) _set_up(x) +#define read_upL() (pin_read(GPIOA, 1<<2)) +#define read_upR() (pin_read(GPIOA, 1<<3)) +// turn off upper shoulders +#define up_off() do{pin_clear(GPIOA, (1<<2)|(1<<3));}while(0) +#define start_pwm() do{ TIM3->CR1 = TIM_CR1_CEN; TIM3->EGR |= TIM_EGR_UG; }while(0) +#define stop_pwm() do{ TIM3->CCR1 = 0; TIM3->CCR2 = 0; TIM3->CR1 = 0; }while(0) +#define PWM_LEFT 2 +#define PWM_RIGHT 1 +// set PWM value (0..100), x==1 - R_DOWN, x==2 - L_DOWN +#define _set_pwm(x, n) do{ TIM3->CCR ## x = n; }while(0) +#define set_pwm(x, n) _set_pwm(x, n) +#define _get_pwm(x) (TIM3->CCR ## x) +#define get_pwm(x) _get_pwm(x) + +int set_dT(uint32_t d); + +extern volatile uint32_t Tms; + +void gpio_setup(void); +void iwdg_setup(); + +int motor_ctl(int32_t dir); +void motor_break(); +void motor_process(); + + diff --git a/F1:F103/CAR_CANbus/WindShield/main.c b/F1:F103/CAR_CANbus/WindShield/main.c new file mode 100644 index 0000000..609128a --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/main.c @@ -0,0 +1,85 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "adc.h" +#include "can.h" +#include "flash.h" +#include "hardware.h" +#include "proto.h" +#include "strfunc.h" + +volatile uint32_t Tms = 0; + +/* Called when systick fires */ +void sys_tick_handler(void){ + ++Tms; +} + +int main(void){ + uint32_t lastT = 0; + CAN_message *can_mesg; + StartHSE(); + SysTick_Config(72000); + flashstorage_init(); + gpio_setup(); // should be run before other peripherial setup + adc_setup(); + usart_setup(); + CAN_setup(the_conf.canspeed); + RCC->CSR |= RCC_CSR_RMVF; // remove reset flags +#ifndef EBUG + iwdg_setup(); +#endif + while (1){ + IWDG->KR = IWDG_REFRESH; // refresh watchdog + if(Tms - lastT > 499){ // throw out short messages twice per second + usart_transmit(); + lastT = Tms; + } + can_proc(); + CAN_status st = CAN_get_status(); + if(st == CAN_FIFO_OVERRUN){ + usart_send("CAN bus fifo overrun occured!\n"); + }else if(st == CAN_ERR){ + usart_send("Some CAN error occured\n"); + } + while((can_mesg = CAN_messagebuf_pop())){ + IWDG->KR = IWDG_REFRESH; + // TODO: check ID and process messages + if(flags.can_monitor){ + lastT = Tms; + if(!lastT) lastT = 1; + uint8_t len = can_mesg->length; + printu(Tms); + usart_send(" #"); + printuhex(can_mesg->ID); + for(uint8_t ctr = 0; ctr < len; ++ctr){ + usart_putchar(' '); + printuhex(can_mesg->data[ctr]); + } + usart_putchar('\n'); + } + } + motor_process(); + char *str; + int g = usart_getline(&str); + if(g < 0) usart_send("USART IN buffer overflow!\n"); + else if(g > 0) cmd_parser(str); + } + return 0; +} + diff --git a/F1:F103/CAR_CANbus/WindShield/openocd.cfg b/F1:F103/CAR_CANbus/WindShield/openocd.cfg new file mode 100644 index 0000000..f8936b2 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/openocd.cfg @@ -0,0 +1,6 @@ +# STM32F103C8 "Blue Pill" + +set FLASH_SIZE 0x8000 + +source [find interface/stlink-v2-1.cfg] +source [find target/stm32f1x.cfg] diff --git a/F1:F103/CAR_CANbus/WindShield/proto.c b/F1:F103/CAR_CANbus/WindShield/proto.c new file mode 100644 index 0000000..95b3f93 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/proto.c @@ -0,0 +1,193 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "adc.h" +#include "can.h" +#include "hardware.h" +#include "proto.h" +#include "strfunc.h" +#include "usart.h" +#include "version.inc" + +flags_t flags = { + .can_monitor = 0 +}; + +const char *helpmsg = + "https://github.com/eddyem/stm32samples/tree/master/F1:F103/CAR_CANbus/WindShield build#" BUILD_NUMBER " @ " BUILD_DATE "\n" + "'0' - stop PWM\n" + "'1' - start PWM\n" + "'b' - break motor (extremal stop)\n" + "'d xx' - set dT to xx ms\n" + "'m xx' - move motor right (1), left (-1) or stop (0)\n" + "'p y xx' - set PWM y (l/r) to xx (0..100)" + "'s' - get raw VSEN value\n" + "'t' - get MCU temperature (*10)\n" + "'u y xx' - turn on (xx==1) or off (xx==0) y (l/r) MOSFET\n" + "'v' - get Vdd (*100)\n" + "'M' - CAN bus monitoring on/off\n" + "'R' - software reset\n" + "'T' - get time from start (ms)\n" +; + +static void printans(int res){ + if(res) usart_send("OK"); + else usart_send("FAIL"); +} + +static void usetter(int(*fn)(uint32_t), char* str){ + uint32_t d = 0; + if(str == getnum(str, &d)) printans(FALSE); + else printans(fn(d)); +} +static void isetter(int(*fn)(int32_t), char* str){ + int32_t d = 0; + if(str == getint(str, &d)) printans(FALSE); + else printans(fn(d)); +} +static char pwmch = 0; +static int setdir(char d){ + pwmch = 0; + switch(d){ + case 'r': + case 'l': + pwmch = d; + break; + default: + usart_send("Direction: l/r\n"); + return FALSE; + } + return TRUE; +} +static int pwmvalsetter(uint32_t pwm){ + if(pwm > PWMMAX) return FALSE; + if(pwmch == 'l') set_pwm(PWM_LEFT, pwm); + else if(pwmch == 'r') set_pwm(PWM_RIGHT, pwm); + else return FALSE; + return TRUE; +} +static int upsetter(uint32_t set){ + int L = get_pwm(PWM_LEFT), R = get_pwm(PWM_RIGHT); + switch(pwmch){ + case 'l': + if(L && set) return FALSE; // shortened + if(set) set_up(UP_LEFT); + else up_off(); + break; + case 'r': + if(R && set) return FALSE; + if(set) set_up(UP_RIGHT); + else up_off(); + break; + default: + return FALSE; + } + return TRUE; +} +static int startPWM(){ + int l = read_upL(), r = read_upR(); + if(l && r) return FALSE; // both upper active (impossible case, but what if?) + int L = get_pwm(PWM_LEFT), R = get_pwm(PWM_RIGHT); + if(L && R) return FALSE; // both PWM active + if((l && L) || (r && R)) return FALSE; // shortened + start_pwm(); + return TRUE; +} + +/** + * @brief cmd_parser - command parsing + * @param txt - buffer with commands & data + */ +void cmd_parser(char *txt){ + char _1st = txt[0]; + txt = (char*)omit_spaces(txt + 1); + int proc = 1; + switch(_1st){ // parse long commands here + case 'd': + usetter(set_dT, txt); + break; + case 'm': + isetter(motor_ctl, txt); + break; + case 'p': + if(setdir(*txt)){ + txt = (char*)omit_spaces(txt + 1); + usetter(pwmvalsetter, txt); newline(); + } + usart_send("PWM1="); usart_send(u2str(get_pwm(PWM_RIGHT))); + usart_send("\nPWM2="); usart_send(u2str(get_pwm(PWM_LEFT))); + break; + case 'u': + if(setdir(*txt)){ + txt = (char*)omit_spaces(txt + 1); + usetter(upsetter, txt); newline(); + } + usart_send("UPL="); usart_putchar('0' + read_upL()); + usart_send("\nUPR="); usart_putchar('0' + read_upR()); + break; + default: + proc = 0; + break; + } + if(proc) goto eof; + if(txt[1] != 0) _1st = '?'; // help for wrong message length + switch(_1st){ + case '0': + stop_pwm(); printans(TRUE); + break; + case '1': + printans(startPWM()); + break; + case 'b': + motor_break(); printans(TRUE); + break; + case 's': + usart_send("VSEN="); usart_send(u2str(getADCval(CHVSEN))); + break; + case 't': + usart_send("TMCU="); usart_send(u2str(getMCUtemp())); + break; + case 'v': + usart_send("VDD="); usart_send(u2str(getVdd())); + break; + case 'M': + flags.can_monitor = !flags.can_monitor; + usart_send("CAN monitoring "); + if(flags.can_monitor) usart_send("ON"); + else usart_send("OFF"); + break; + case 'R': + usart_send("Soft reset\n"); + usart_transmit(); + // wait until DMA & USART done + while(!usart_txrdy); + while(!(USART1->SR & USART_SR_TXE)); + USART1->CR1 = 0; // stop USART + NVIC_SystemReset(); + break; + case 'T': + usart_send("Time="); + printu(Tms); + break; + default: // help + usart_send(helpmsg); + break; + } +eof: + newline(); +} diff --git a/F1:F103/CAR_CANbus/WindShield/proto.h b/F1:F103/CAR_CANbus/WindShield/proto.h new file mode 100644 index 0000000..e37b3ad --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/proto.h @@ -0,0 +1,40 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include "hardware.h" + +#define BUFSZ (64) + +typedef struct{ + uint32_t can_monitor : 1; +} flags_t; + +extern flags_t flags; + +#ifdef EBUG +#define DBG(str) do{usart_send(__FILE__ " (L" STR(__LINE__) "): " str); \ + usart_putchar('\n'); usart_transmit(); }while(0) +#else +#define DBG(str) +#endif + + +void cmd_parser(char *buf); diff --git a/F1:F103/CAR_CANbus/WindShield/strfunc.c b/F1:F103/CAR_CANbus/WindShield/strfunc.c new file mode 100644 index 0000000..2387b09 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/strfunc.c @@ -0,0 +1,268 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +/** + * @brief hexdump - dump hex array by 16 bytes in string + * @param sendfun - function to send data + * @param arr - array to dump + * @param len - length of `arr` + */ +void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len){ + char buf[52], *bptr = buf; + for(uint16_t l = 0; l < len; ++l, ++arr){ + for(int16_t j = 1; j > -1; --j){ + register uint8_t half = (*arr >> (4*j)) & 0x0f; + if(half < 10) *bptr++ = half + '0'; + else *bptr++ = half - 10 + 'a'; + } + if(l % 16 == 15){ + *bptr++ = '\n'; + *bptr = 0; + sendfun(buf); + bptr = buf; + }else *bptr++ = ' '; + } + if(bptr != buf){ + *bptr++ = '\n'; + *bptr = 0; + sendfun(buf); + } +} + +/** + * @brief _2str - convert value into string buffer + * @param val - |value| + * @param minus - ==0 if value > 0 + * @return buffer with number + */ +static char *_2str(uint32_t val, uint8_t minus){ + static char strbuf[12]; + char *bufptr = &strbuf[11]; + *bufptr = 0; + if(!val){ + *(--bufptr) = '0'; + }else{ + while(val){ + uint32_t x = val / 10; + *(--bufptr) = (val - 10*x) + '0'; + val = x; + //*(--bufptr) = val % 10 + '0'; + //val /= 10; + } + } + if(minus) *(--bufptr) = '-'; + return bufptr; +} + +// return string with number `val` +char *u2str(uint32_t val){ + return _2str(val, 0); +} +char *i2str(int32_t i){ + uint8_t minus = 0; + uint32_t val; + if(i < 0){ + minus = 1; + val = -i; + }else val = i; + return _2str(val, minus); +} + +/** + * @brief uhex2str - print 32bit unsigned int as hex + * @param val - value + * @return string with number + */ +char *uhex2str(uint32_t val){ + static char buf[12] = "0x"; + int npos = 2; + uint8_t *ptr = (uint8_t*)&val + 3; + int8_t i, j, z=1; + for(i = 0; i < 4; ++i, --ptr){ + if(*ptr == 0){ // omit leading zeros + if(i == 3) z = 0; + if(z) continue; + } + else z = 0; + for(j = 1; j > -1; --j){ + uint8_t half = (*ptr >> (4*j)) & 0x0f; + if(half < 10) buf[npos++] = half + '0'; + else buf[npos++] = half - 10 + 'a'; + } + } + buf[npos] = 0; + return buf; +} + +/** + * @brief omit_spaces - eliminate leading spaces and other trash in string + * @param buf - string + * @return - pointer to first character in `buf` > ' ' + */ +const char *omit_spaces(const char *buf){ + while(*buf){ + if(*buf > ' ') break; + ++buf; + } + return buf; +} + +/** + * @brief getdec - read decimal number & return pointer to next non-number symbol + * @param buf - string + * @param N - number read + * @return Next non-number symbol. In case of overflow return `buf` and N==0xffffffff + */ +static const char *getdec(const char *buf, uint32_t *N){ + char *start = (char*)buf; + uint32_t num = 0; + while(*buf){ + char c = *buf; + if(c < '0' || c > '9'){ + break; + } + if(num > 429496729 || (num == 429496729 && c > '5')){ // overflow + *N = 0xffffff; + return start; + } + num *= 10; + num += c - '0'; + ++buf; + } + *N = num; + return buf; +} +// read hexadecimal number (without 0x prefix!) +static const char *gethex(const char *buf, uint32_t *N){ + const char *start = buf; + uint32_t num = 0; + while(*buf){ + char c = *buf; + uint8_t M = 0; + if(c >= '0' && c <= '9'){ + M = '0'; + }else if(c >= 'A' && c <= 'F'){ + M = 'A' - 10; + }else if(c >= 'a' && c <= 'f'){ + M = 'a' - 10; + } + if(M){ + if(num & 0xf0000000){ // overflow + *N = 0xffffff; + return start; + } + num <<= 4; + num += c - M; + }else{ + break; + } + ++buf; + } + *N = num; + return buf; +} +// read octal number (without 0 prefix!) +static const char *getoct(const char *buf, uint32_t *N){ + const char *start = (char*)buf; + uint32_t num = 0; + while(*buf){ + char c = *buf; + if(c < '0' || c > '7'){ + break; + } + if(num & 0xe0000000){ // overflow + *N = 0xffffff; + return start; + } + num <<= 3; + num += c - '0'; + ++buf; + } + *N = num; + return buf; +} +// read binary number (without b prefix!) +static const char *getbin(const char *buf, uint32_t *N){ + const char *start = (char*)buf; + uint32_t num = 0; + while(*buf){ + char c = *buf; + if(c < '0' || c > '1'){ + break; + } + if(num & 0x80000000){ // overflow + *N = 0xffffff; + return start; + } + num <<= 1; + if(c == '1') num |= 1; + ++buf; + } + *N = num; + return buf; +} + +/** + * @brief getnum - read uint32_t from string (dec, hex or bin: 127, 0x7f, 0b1111111) + * @param buf - buffer with number and so on + * @param N - the number read + * @return pointer to first non-number symbol in buf + * (if it is == buf, there's no number or if *N==0xffffffff there was overflow) + */ +const char *getnum(const char *txt, uint32_t *N){ + const char *nxt = NULL; + const char *s = omit_spaces(txt); + if(*s == '0'){ // hex, oct or 0 + if(s[1] == 'x' || s[1] == 'X'){ // hex + nxt = gethex(s+2, N); + if(nxt == s+2) nxt = (char*)txt; + }else if(s[1] > '0'-1 && s[1] < '8'){ // oct + nxt = getoct(s+1, N); + if(nxt == s+1) nxt = (char*)txt; + }else{ // 0 + nxt = s+1; + *N = 0; + } + }else if(*s == 'b' || *s == 'B'){ + nxt = getbin(s+1, N); + if(nxt == s+1) nxt = (char*)txt; + }else{ + nxt = getdec(s, N); + if(nxt == s) nxt = (char*)txt; + } + return nxt; +} + +// get signed integer +const char *getint(const char *txt, int32_t *I){ + const char *s = omit_spaces(txt); + int32_t sign = 1; + uint32_t U; + if(*s == '-'){ + sign = -1; + ++s; + } + const char *nxt = getnum(s, &U); + if(nxt == s) return txt; + if(U & 0x80000000) return txt; // overfull + *I = sign * (int32_t)U; + return nxt; +} diff --git a/F1:F103/CAR_CANbus/WindShield/strfunc.h b/F1:F103/CAR_CANbus/WindShield/strfunc.h new file mode 100644 index 0000000..5f24d35 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/strfunc.h @@ -0,0 +1,37 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include "usart.h" + +void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len); +char *u2str(uint32_t val); +char *i2str(int32_t i); +char *uhex2str(uint32_t val); +const char *getnum(const char *txt, uint32_t *N); +const char *omit_spaces(const char *buf); +const char *getint(const char *txt, int32_t *I); + +#define newline() do{usart_putchar('\n');}while(0) +#define printu(a) do{usart_send(u2str(a));}while(0) +#define printi(a) do{usart_send(i2str(a));}while(0) +#define printuhex(a) do{usart_send(uhex2str(a));}while(0) diff --git a/F1:F103/CAR_CANbus/WindShield/usart.c b/F1:F103/CAR_CANbus/WindShield/usart.c new file mode 100644 index 0000000..75a342a --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/usart.c @@ -0,0 +1,152 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "stm32f1.h" +#include "usart.h" + +extern volatile uint32_t Tms; +static volatile int idatalen[2] = {0,0}; // received data line length (including '\n') +static volatile int odatalen[2] = {0,0}; + +volatile int usart_txrdy = 1; // transmission done + +static volatile int usart_linerdy = 0 // received data ready + ,dlen = 0 // length of data (including '\n') in current buffer + ,usart_bufovr = 0 // input buffer overfull + +; + +int rbufno = 0, tbufno = 0; // current rbuf/tbuf numbers +static char rbuf[2][UARTBUFSZI], tbuf[2][UARTBUFSZO]; // receive & transmit buffers +static char *recvdata = NULL; + +/** + * return length of received data (without trailing zero) + */ +int usart_getline(char **line){ + if(usart_bufovr){ + usart_bufovr = 0; + usart_linerdy = 0; + return -1; + } + if(!usart_linerdy) return 0; + *line = recvdata; + usart_linerdy = 0; + int x = dlen; + dlen = 0; + return x; +} + +// transmit current tbuf and swap buffers +int usart_transmit(){ + register int l = odatalen[tbufno]; + if(!l) return 0; + uint32_t tmout = 1600000; + while(!usart_txrdy){ + IWDG->KR = IWDG_REFRESH; + if(--tmout == 0) return 0; + }; // wait for previos buffer transmission + usart_txrdy = 0; + odatalen[tbufno] = 0; + DMA1_Channel4->CCR &= ~DMA_CCR_EN; + DMA1_Channel4->CMAR = (uint32_t) tbuf[tbufno]; // mem + DMA1_Channel4->CNDTR = l; + DMA1_Channel4->CCR |= DMA_CCR_EN; + tbufno = !tbufno; + return l; +} + +int usart_putchar(const char ch){ + if(odatalen[tbufno] == UARTBUFSZO){ + if(!usart_transmit()) return 0; + } + tbuf[tbufno][odatalen[tbufno]++] = ch; + return 1; +} + +int usart_send(const char *str){ + int l = 0; + while(*str){ + if(odatalen[tbufno] == UARTBUFSZO){ + if(!usart_transmit()) return 0; + } + tbuf[tbufno][odatalen[tbufno]++] = *str++; + ++l; + } + return l; +} + +/* + * USART speed: baudrate = Fck/(USARTDIV) + * USARTDIV stored in USART->BRR + * + * for 72MHz USARTDIV=72000/f(kboud); so for 115200 USARTDIV=72000/115.2=625 -> BRR=0x271 + * 9600: BRR = 7500 (0x1D4C) + */ + +void usart_setup(){ + uint32_t tmout = 16000000; + // PA9 - Tx, PA10 - Rx + RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_USART1EN | RCC_APB2ENR_AFIOEN; + RCC->AHBENR |= RCC_AHBENR_DMA1EN; + GPIOA->CRH = (GPIOA->CRH & ~(CRH(9,0xf)|CRH(10,0xf))) | + CRH(9, CNF_AFPP|MODE_NORMAL) | CRH(10, CNF_FLINPUT|MODE_INPUT); + // USART1 Tx DMA - Channel4 (Rx - channel 5) + DMA1_Channel4->CPAR = (uint32_t) &USART1->DR; // periph + DMA1_Channel4->CCR |= DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE; // 8bit, mem++, mem->per, transcompl irq + // Tx CNDTR set @ each transmission due to data size + NVIC_SetPriority(DMA1_Channel4_IRQn, 3); + NVIC_EnableIRQ(DMA1_Channel4_IRQn); + NVIC_SetPriority(USART1_IRQn, 0); + // setup usart1 + USART1->BRR = 72000000 / 921600; + USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; // 1start,8data,nstop; enable Rx,Tx,USART + while(!(USART1->SR & USART_SR_TC)){if(--tmout == 0) break;} // polling idle frame Transmission + USART1->SR = 0; // clear flags + USART1->CR1 |= USART_CR1_RXNEIE; // allow Rx IRQ + USART1->CR3 = USART_CR3_DMAT; // enable DMA Tx + NVIC_EnableIRQ(USART1_IRQn); +} + + +void usart1_isr(){ + if(USART1->SR & USART_SR_RXNE){ // RX not emty - receive next char + uint8_t rb = USART1->DR; + if(idatalen[rbufno] < UARTBUFSZI){ // put next char into buf + if(rb == '\n'){ // got newline - line ready + rbuf[rbufno][idatalen[rbufno]] = 0; + usart_linerdy = 1; + dlen = idatalen[rbufno]; + recvdata = rbuf[rbufno]; + // prepare other buffer + rbufno = !rbufno; + idatalen[rbufno] = 0; + }else rbuf[rbufno][idatalen[rbufno]++] = rb; + }else{ // buffer overrun + usart_bufovr = 1; + idatalen[rbufno] = 0; + } + } +} + +void dma1_channel4_isr(){ + if(DMA1->ISR & DMA_ISR_TCIF4){ // Tx + DMA1->IFCR = DMA_IFCR_CTCIF4; // clear TC flag + usart_txrdy = 1; + } +} diff --git a/F1:F103/CAR_CANbus/WindShield/usart.h b/F1:F103/CAR_CANbus/WindShield/usart.h new file mode 100644 index 0000000..a7a4f43 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/usart.h @@ -0,0 +1,34 @@ +/* + * This file is part of the windshield project. + * Copyright 2024 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +// input and output buffers size +#define UARTBUFSZI (256) +#define UARTBUFSZO (1024) + +#define usartrx() (usart_linerdy) +#define usartovr() (usart_bufovr) + +extern volatile int usart_txrdy; + +int usart_transmit(); +void usart_setup(); +int usart_getline(char **line); +int usart_send(const char *str); +int usart_putchar(const char ch); diff --git a/F1:F103/CAR_CANbus/WindShield/version.inc b/F1:F103/CAR_CANbus/WindShield/version.inc new file mode 100644 index 0000000..b4a39d6 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/version.inc @@ -0,0 +1,2 @@ +#define BUILD_NUMBER "10" +#define BUILD_DATE "2024-02-13" diff --git a/F1:F103/CAR_CANbus/WindShield/windshield.bin b/F1:F103/CAR_CANbus/WindShield/windshield.bin new file mode 100755 index 0000000000000000000000000000000000000000..39b9f42b6361e4f822ea74fcdd3f6876df533895 GIT binary patch literal 9508 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 G!TtySpV2J< literal 0 HcmV?d00001 diff --git a/F1:F103/CAR_CANbus/WindShield/windshield.cflags b/F1:F103/CAR_CANbus/WindShield/windshield.cflags new file mode 100644 index 0000000..68d5165 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/windshield.cflags @@ -0,0 +1 @@ +-std=c17 \ No newline at end of file diff --git a/F1:F103/CAR_CANbus/WindShield/windshield.config b/F1:F103/CAR_CANbus/WindShield/windshield.config new file mode 100644 index 0000000..46273ce --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/windshield.config @@ -0,0 +1,7 @@ +// Add predefined macros for your project here. For example: +// #define THE_ANSWER 42 +#define EBUG +#define BLUEPILL +#define STM32F1 +#define STM32F103x6 +#define STM32F10X_LD diff --git a/F1:F103/CAR_CANbus/WindShield/windshield.creator b/F1:F103/CAR_CANbus/WindShield/windshield.creator new file mode 100644 index 0000000..e94cbbd --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/windshield.creator @@ -0,0 +1 @@ +[General] diff --git a/F1:F103/CAR_CANbus/WindShield/windshield.creator.user b/F1:F103/CAR_CANbus/WindShield/windshield.creator.user new file mode 100644 index 0000000..9b8e4b5 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/windshield.creator.user @@ -0,0 +1,180 @@ + + + + + + EnvironmentId + {7bd84e39-ca37-46d3-be9d-99ebea85bc0d} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + KOI8-R + false + 4 + false + 80 + true + true + 1 + 0 + false + true + false + 0 + true + true + 0 + 8 + true + false + 1 + true + false + true + *.md, *.MD, Makefile + false + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + true + true + Builtin.DefaultTidyAndClazy + 8 + true + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop + Desktop + {65a14f9e-e008-4c1b-89df-4eaa4774b6e3} + 0 + 0 + 0 + + /Big/Data/00__Electronics/STM32/F1-nolib/CAR_CANbus/WindShield + + + + all + + true + GenericProjectManager.GenericMakeStep + + 1 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + GenericProjectManager.GenericMakeStep + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + По умолчанию + GenericProjectManager.GenericBuildConfiguration + + 1 + + + 0 + Развёртывание + Развёртывание + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + 0 + true + + + 2 + + false + + ProjectExplorer.CustomExecutableRunConfiguration + + false + true + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/F1:F103/CAR_CANbus/WindShield/windshield.creator.user.cf63021 b/F1:F103/CAR_CANbus/WindShield/windshield.creator.user.cf63021 new file mode 100644 index 0000000..ebefce4 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/windshield.creator.user.cf63021 @@ -0,0 +1,160 @@ + + + + + + EnvironmentId + {cf63021e-ef53-49b0-b03b-2f2570cdf3b6} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + KOI8-R + false + 4 + false + 80 + true + true + 1 + false + false + false + 1 + true + true + 0 + 8 + true + false + 2 + true + true + true + *.md, *.MD, Makefile + true + true + + + + ProjectExplorer.Project.PluginSettings + + + true + true + Builtin.DefaultTidyAndClazy + 4 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop + Desktop + {91347f2c-5221-46a7-80b1-0a054ca02f79} + 0 + 0 + 0 + + /home/eddy/Docs/SAO/ELECTRONICS/STM32/F1-srcs/usbcan + + + + all + + true + GenericProjectManager.GenericMakeStep + + 1 + Сборка + Сборка + ProjectExplorer.BuildSteps.Build + + + + + clean + + true + GenericProjectManager.GenericMakeStep + + 1 + Очистка + Очистка + ProjectExplorer.BuildSteps.Clean + + 2 + false + + false + + По умолчанию + GenericProjectManager.GenericBuildConfiguration + + 1 + + + 0 + Развёртывание + Развёртывание + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + 2 + + ProjectExplorer.CustomExecutableRunConfiguration + + false + true + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/F1:F103/CAR_CANbus/WindShield/windshield.cxxflags b/F1:F103/CAR_CANbus/WindShield/windshield.cxxflags new file mode 100644 index 0000000..6435dfc --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/windshield.cxxflags @@ -0,0 +1 @@ +-std=c++17 \ No newline at end of file diff --git a/F1:F103/CAR_CANbus/WindShield/windshield.files b/F1:F103/CAR_CANbus/WindShield/windshield.files new file mode 100644 index 0000000..57ef708 --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/windshield.files @@ -0,0 +1,15 @@ +adc.c +adc.h +can.c +can.h +flash.c +flash.h +hardware.c +hardware.h +main.c +proto.c +proto.h +strfunc.c +strfunc.h +usart.c +usart.h diff --git a/F1:F103/CAR_CANbus/WindShield/windshield.includes b/F1:F103/CAR_CANbus/WindShield/windshield.includes new file mode 100644 index 0000000..72d756d --- /dev/null +++ b/F1:F103/CAR_CANbus/WindShield/windshield.includes @@ -0,0 +1,7 @@ +. +../../inc +../../inc/Fx +../../inc/cm +../../inc/ld +../../inc/startup +