From 8d6aeb8f004168df44d2beac1cf645699b7bb507 Mon Sep 17 00:00:00 2001 From: eddyem Date: Thu, 25 Jun 2020 23:38:39 +0300 Subject: [PATCH] fix USBCAN code --- F0-nolib/usbcan/Makefile | 2 - F0-nolib/usbcan/Readme.md | 17 +-- F0-nolib/usbcan/can.c | 74 ------------- F0-nolib/usbcan/can.h | 19 ---- F0-nolib/usbcan/hardware.c | 53 ++++++--- F0-nolib/usbcan/hardware.h | 19 ++-- F0-nolib/usbcan/main.c | 91 ++++----------- F0-nolib/usbcan/proto.c | 71 ++++-------- F0-nolib/usbcan/proto.h | 7 +- F0-nolib/usbcan/usart.c | 221 ------------------------------------- F0-nolib/usbcan/usart.h | 47 -------- F0-nolib/usbcan/usb.c | 1 - F0-nolib/usbcan/usbcan.bin | Bin 13468 -> 11956 bytes 13 files changed, 99 insertions(+), 523 deletions(-) delete mode 100644 F0-nolib/usbcan/usart.c delete mode 100644 F0-nolib/usbcan/usart.h diff --git a/F0-nolib/usbcan/Makefile b/F0-nolib/usbcan/Makefile index cd31dcb..e000cdf 100644 --- a/F0-nolib/usbcan/Makefile +++ b/F0-nolib/usbcan/Makefile @@ -6,8 +6,6 @@ FAMILY = F0 # MCU code MCU = F042x6 # hardware definitions -DEFS += -DUSARTNUM=1 -#DEFS += -DCHECK_TMOUT #DEFS += -DEBUG # change this linking script depending on particular MCU model, # for example, if you have STM32F103VBT6, you should write: diff --git a/F0-nolib/usbcan/Readme.md b/F0-nolib/usbcan/Readme.md index 1e850f2..21afa5d 100644 --- a/F0-nolib/usbcan/Readme.md +++ b/F0-nolib/usbcan/Readme.md @@ -2,22 +2,9 @@ Simple code for CAN/USB development board Simultaneous work of USB CDC (PL2303 emulation) and CAN Pinout: -PC13 - LED0 -PC14 - LED1 - -PB0..2 - Outputs 1..3 +PB0 - LED0 - short blink when message received +PB1 - LED1 - shine when line OK PB8, PB9 - CAN Rx/Tx -PB14 - BRDaddr0 -PB15 - BRDaddr1 -PA8 - BRDaddr2 - -PA0 - V12 (external 12V voltage) -PA1 - V5 (5V voltage level) - -PA4, PA5 - Inputs 1,2 - -PA9, PA10 - USART1 Tx/Rx - PA11. PA12 - USB DM/DP diff --git a/F0-nolib/usbcan/can.c b/F0-nolib/usbcan/can.c index 902bd95..3e1868d 100644 --- a/F0-nolib/usbcan/can.c +++ b/F0-nolib/usbcan/can.c @@ -23,7 +23,6 @@ #include "can.h" #include "hardware.h" #include "proto.h" -#include "usart.h" #include // memcpy @@ -33,7 +32,6 @@ 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 uint16_t CANID = 0xFFFF; #ifdef EBUG static uint32_t last_err_code = 0; #endif @@ -62,9 +60,6 @@ static int CAN_messagebuf_push(CAN_message *msg){ memcpy(&messages[first_free_idx++], msg, sizeof(CAN_message)); // need to roll? if(first_free_idx == CAN_INMESSAGE_SIZE) first_free_idx = 0; - #ifdef EBUG - //MSG("1st free: "); printu(first_free_idx); NL(); - #endif return 0; } @@ -79,26 +74,11 @@ CAN_message *CAN_messagebuf_pop(){ if(first_nonfree_idx == first_free_idx){ // buffer is empty - refresh it first_nonfree_idx = -1; first_free_idx = 0; -#ifdef EBUG - // MSG("refresh buffer\n"); NL(); -#endif } return msg; } -// get CAN address data from GPIO pins -void readCANID(){ - uint8_t CAN_addr = READ_CAN_INV_ADDR(); - CAN_addr = ~CAN_addr & 0x7; - CANID = (CAN_ID_PREFIX & CAN_ID_MASK) | CAN_addr; -} - -uint16_t getCANID(){ - return CANID; -} - void CAN_reinit(uint16_t speed){ - readCANID(); CAN->TSR |= CAN_TSR_ABRQ0 | CAN_TSR_ABRQ1 | CAN_TSR_ABRQ2; RCC->APB1RSTR |= RCC_APB1RSTR_CANRST; RCC->APB1RSTR &= ~RCC_APB1RSTR_CANRST; @@ -134,7 +114,6 @@ void CAN_setup(uint16_t speed){ else if(speed > 3000) speed = 3000; oldspeed = speed; uint32_t tmout = 16000000; - if(CANID == 0xFFFF) readCANID(); // Configure GPIO: PB8 - CAN_Rx, PB9 - CAN_Tx /* (1) Select AF mode (10) on PB8 and PB9 */ /* (2) AF4 for CAN signals */ @@ -173,14 +152,9 @@ void CAN_setup(uint16_t speed){ CAN->FMR = CAN_FMR_FINIT; /* (7) */ CAN->FA1R = CAN_FA1R_FACT0 | CAN_FA1R_FACT1; /* (8) */ // set to 1 all needed bits of CAN->FFA1R to switch given filters to FIFO1 -#if 0 - CAN->FM1R = CAN_FM1R_FBM0; /* (9) */ - CAN->sFilterRegister[0].FR1 = CANID << 5 | ((BCAST_ID << 5) << 16); /* (10) */ -#else CAN->sFilterRegister[0].FR1 = (1<<21)|(1<<5); // all odd IDs CAN->FFA1R = 2; // filter 1 for FIFO1, filter 0 - for FIFO0 CAN->sFilterRegister[1].FR1 = (1<<21); // all even IDs -#endif CAN->FMR &=~ CAN_FMR_FINIT; /* (12) */ CAN->IER |= CAN_IER_ERRIE | CAN_IER_FOVIE0 | CAN_IER_FOVIE1; /* (13) */ @@ -244,30 +218,6 @@ void can_proc(){ lastFloodTime = Tms; can_send(flood_msg->data, flood_msg->length, flood_msg->ID); } -#if 0 - static uint32_t esr, msr, tsr; - uint32_t msr_now = CAN->MSR & 0xf; - if(esr != CAN->ESR || msr != msr_now || tsr != CAN->TSR){ - MSG("Timestamp: "); - printu(Tms); - NL(); - } - if((CAN->ESR) != esr){ - esr = CAN->ESR; - MSG("CAN->ESR: "); - printuhex(esr); NL(); - } - if(msr_now != msr){ - msr = msr_now; - MSG("CAN->MSR & 0xf: "); - printuhex(msr); NL(); - } - if(CAN->TSR != tsr){ - tsr = CAN->TSR; - MSG("CAN->TSR: "); - printuhex(tsr); NL(); - } -#endif } CAN_status can_send(uint8_t *msg, uint8_t len, uint16_t target_id){ @@ -324,23 +274,6 @@ CAN_status can_send(uint8_t *msg, uint8_t len, uint16_t target_id){ return CAN_OK; } -void can_send_dummy(){ - uint8_t msg = CMD_TOGGLE; - if(CAN_OK != can_send(&msg, 1, TARG_ID)) SEND("Bus busy!\n"); - MSG("CAN->MSR: "); - printuhex(CAN->MSR); NL(); - MSG("CAN->TSR: "); - printuhex(CAN->TSR); NL(); - MSG("CAN->ESR: "); - printuhex(CAN->ESR); NL(); -} - -void can_send_broadcast(){ - uint8_t msg = CMD_BCAST; - if(CAN_OK != can_send(&msg, 1, BCAST_ID)) SEND("Bus busy!\n"); - MSG("Broadcast message sent\n"); -} - void set_flood(CAN_message *msg){ if(!msg) flood_msg = NULL; else{ @@ -354,13 +287,6 @@ static void can_process_fifo(uint8_t fifo_num){ LED_on(LED1); // Turn on LED1 - message received CAN_FIFOMailBox_TypeDef *box = &CAN->sFIFOMailBox[fifo_num]; volatile uint32_t *RFxR = (fifo_num) ? &CAN->RF1R : &CAN->RF0R; - /* - MSG("\nReceive, RDTR="); - #ifdef EBUG - printuhex(box->RDTR); - NL(); - #endif - */ // 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 diff --git a/F0-nolib/usbcan/can.h b/F0-nolib/usbcan/can.h index 2ae474f..2dc8811 100644 --- a/F0-nolib/usbcan/can.h +++ b/F0-nolib/usbcan/can.h @@ -31,18 +31,6 @@ // flood period in milliseconds #define FLOOD_PERIOD_MS 5 -// simple 1-byte commands -#define CMD_TOGGLE (0xDA) -#define CMD_BCAST (0xAD) -// mask heading 8 bits of can ID -#define CAN_ID_MASK (0x7F8) -// prefix to make ID from any number (0..7) -#define CAN_ID_PREFIX (0xAAA) -// "target" ID: num=0 -#define TARG_ID (CAN_ID_PREFIX & CAN_ID_MASK) -// "broadcast" ID: all ones -#define BCAST_ID (0x7FF) - // incoming message buffer size #define CAN_INMESSAGE_SIZE (8) @@ -50,8 +38,6 @@ typedef struct{ uint8_t data[8]; // up to 8 bytes of data uint8_t length; // data length - //uint8_t filterNo; // filter number - //uint8_t fifoNum; // message FIFO number uint16_t ID; // ID of receiver } CAN_message; @@ -65,15 +51,10 @@ typedef enum{ CAN_status CAN_get_status(); -void readCANID(); -uint16_t getCANID(); - 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_send_dummy(); -void can_send_broadcast(); void can_proc(); CAN_message *CAN_messagebuf_pop(); diff --git a/F0-nolib/usbcan/hardware.c b/F0-nolib/usbcan/hardware.c index 37dfe24..ec416a7 100644 --- a/F0-nolib/usbcan/hardware.c +++ b/F0-nolib/usbcan/hardware.c @@ -22,24 +22,17 @@ */ #include "hardware.h" -#include "usart.h" + +uint8_t ledsON = 0; void gpio_setup(void){ - // here we turn on clocking for all periph. - RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_GPIOCEN | RCC_AHBENR_DMAEN; - // Set LEDS (PC13/14) as output - GPIOC->MODER = (GPIOC->MODER & ~(GPIO_MODER_MODER13 | GPIO_MODER_MODER14) - ) | - GPIO_MODER_MODER13_O | GPIO_MODER_MODER14_O; - // PB14(0), PB15(1), PA8(2) - CAN address, pullup inputs - GPIOA->PUPDR = (GPIOA->PUPDR & ~(GPIO_PUPDR_PUPDR8) - ) | - GPIO_PUPDR_PUPDR8_0; - GPIOB->PUPDR = (GPIOB->PUPDR & ~(GPIO_PUPDR_PUPDR14 | GPIO_PUPDR_PUPDR15) - ) | - GPIO_PUPDR_PUPDR14_0 | GPIO_PUPDR_PUPDR15_0; + RCC->AHBENR |= RCC_AHBENR_GPIOBEN; + // Set LEDS (PB0/1) as output pin_set(LED0_port, LED0_pin); // clear LEDs pin_set(LED1_port, LED1_pin); + GPIOB->MODER = (GPIOB->MODER & ~(GPIO_MODER_MODER0 | GPIO_MODER_MODER1) + ) | + GPIO_MODER_MODER0_O | GPIO_MODER_MODER1_O; } void iwdg_setup(){ @@ -70,3 +63,35 @@ void pause_ms(uint32_t pause){ uint32_t Tnxt = Tms + pause; while(Tms < Tnxt) nop(); } + +void Jump2Boot(){ + void (*SysMemBootJump)(void); + volatile uint32_t addr = 0x1FFFC800; + // reset systick + SysTick->CTRL = 0; + // reset clocks + + RCC->APB1RSTR = RCC_APB1RSTR_CECRST | RCC_APB1RSTR_DACRST | RCC_APB1RSTR_PWRRST | RCC_APB1RSTR_CRSRST | + RCC_APB1RSTR_CANRST | RCC_APB1RSTR_USBRST | RCC_APB1RSTR_I2C2RST | RCC_APB1RSTR_I2C1RST | + RCC_APB1RSTR_USART4RST | RCC_APB1RSTR_USART3RST | RCC_APB1RSTR_USART2RST | RCC_APB1RSTR_SPI2RST | + RCC_APB1RSTR_WWDGRST | RCC_APB1RSTR_TIM14RST | +#ifdef STM32F072xB + RCC_APB1RSTR_TIM7RST | RCC_APB1RSTR_TIM6RST | +#endif + RCC_APB1RSTR_TIM3RST | RCC_APB1RSTR_TIM2RST; + RCC->APB2RSTR = RCC_APB2RSTR_DBGMCURST | RCC_APB2RSTR_TIM17RST | RCC_APB2RSTR_TIM16RST | +#ifdef STM32F072xB + RCC_APB2RSTR_TIM15RST | +#endif + RCC_APB2RSTR_USART1RST | RCC_APB2RSTR_SPI1RST | RCC_APB2RSTR_TIM1RST | RCC_APB2RSTR_ADCRST | RCC_APB2RSTR_SYSCFGRST; + RCC->AHBRSTR = 0; + RCC->APB1RSTR = 0; + RCC->APB2RSTR = 0; + // remap memory to 0 (only for STM32F0) + SYSCFG->CFGR1 = 0x01; __DSB(); __ISB(); + SysMemBootJump = (void (*)(void)) (*((uint32_t *)(addr + 4))); + // set main stack pointer + __set_MSP(*((uint32_t *)addr)); + // jump to bootloader + SysMemBootJump(); +} diff --git a/F0-nolib/usbcan/hardware.h b/F0-nolib/usbcan/hardware.h index 1511cff..75c580b 100644 --- a/F0-nolib/usbcan/hardware.h +++ b/F0-nolib/usbcan/hardware.h @@ -33,17 +33,17 @@ #define FORMUSART(X) CONCAT(USART, X) #define USARTX FORMUSART(USARTNUM) -// LEDS: 0 - PC13, 1 - PC14 +// LEDS: 0 - PB0, 1 - PB1 // LED0 -#define LED0_port GPIOC -#define LED0_pin (1<<13) +#define LED0_port GPIOB +#define LED0_pin (1<<0) // LED1 -#define LED1_port GPIOC -#define LED1_pin (1<<14) +#define LED1_port GPIOB +#define LED1_pin (1<<1) -#define LED_blink(x) pin_toggle(x ## _port, x ## _pin) -#define LED_on(x) pin_clear(x ## _port, x ## _pin) -#define LED_off(x) pin_set(x ## _port, x ## _pin) +#define LED_blink(x) do{if(ledsON) pin_toggle(x ## _port, x ## _pin);}while(0) +#define LED_on(x) do{if(ledsON) pin_clear(x ## _port, x ## _pin);}while(0) +#define LED_off(x) do{pin_set(x ## _port, x ## _pin);}while(0) // CAN address - PB14(0), PB15(1), PA8(2) @@ -52,8 +52,11 @@ extern volatile uint32_t Tms; +extern uint8_t ledsON; + void gpio_setup(void); void iwdg_setup(); void pause_ms(uint32_t pause); +void Jump2Boot(); #endif // __HARDWARE_H__ diff --git a/F0-nolib/usbcan/main.c b/F0-nolib/usbcan/main.c index cf9586d..8a6decc 100644 --- a/F0-nolib/usbcan/main.c +++ b/F0-nolib/usbcan/main.c @@ -22,7 +22,6 @@ #include "can.h" #include "hardware.h" #include "proto.h" -#include "usart.h" #include "usb.h" #include "usb_lib.h" @@ -33,27 +32,6 @@ void sys_tick_handler(void){ ++Tms; } -/* -// usb getline -static char *get_USB(){ - static char tmpbuf[512], *curptr = tmpbuf; - static int rest = 511; - uint8_t x = USB_receive((uint8_t*)curptr); - curptr[x] = 0; - if(!x) return NULL; - if(curptr[x-1] == '\n'){ - curptr = tmpbuf; - rest = 511; - return tmpbuf; - } - curptr += x; rest -= x; - if(rest <= 0){ // buffer overflow - curptr = tmpbuf; - rest = 511; - } - return NULL; -}*/ - #define USBBUF 63 // usb getline static char *get_USB(){ @@ -81,15 +59,12 @@ static char *get_USB(){ } curptr += x; rest -= x; if(rest <= 0){ // buffer overflow - usart_send("\nUSB buffer overflow!\n"); transmit_tbuf(); curptr = tmpbuf; rest = USBBUF; } return NULL; } - - int main(void){ uint32_t lastT = 0; uint8_t ctr, len; @@ -98,68 +73,44 @@ int main(void){ sysreset(); SysTick_Config(6000, 1); gpio_setup(); - usart_setup(); - readCANID(); + USB_setup(); CAN_setup(100); - - switchbuff(0); - SEND("Greetings! My address is "); - printuhex(getCANID()); - newline(); - - if(RCC->CSR & RCC_CSR_IWDGRSTF){ // watchdog reset occured - SEND("WDGRESET=1\n"); - } - if(RCC->CSR & RCC_CSR_SFTRSTF){ // software reset occured - SEND("SOFTRESET=1\n"); - } - sendbuf(); RCC->CSR |= RCC_CSR_RMVF; // remove reset flags iwdg_setup(); - USB_setup(); while (1){ IWDG->KR = IWDG_REFRESH; // refresh watchdog - if(lastT > Tms || Tms - lastT > 499){ - LED_blink(LED0); - lastT = Tms; - transmit_tbuf(); // non-blocking transmission of data from UART buffer every 0.5s + if(lastT && (Tms - lastT > 199)){ + LED_off(LED0); + lastT = 0; } can_proc(); usb_proc(); if(CAN_get_status() == CAN_FIFO_OVERRUN){ - register uint8_t o = switchbuff(3); SEND("CAN bus fifo overrun occured!\n"); - sendbuf(); switchbuff(o); + sendbuf(); } can_mesg = CAN_messagebuf_pop(); - if(ShowMsgs && can_mesg && isgood(can_mesg->ID)){ // new data in buff - IWDG->KR = IWDG_REFRESH; - len = can_mesg->length; - printu(Tms); - SEND(" #"); - printuhex(can_mesg->ID); - /*SEND(", filter #"); - printu(can_mesg->filterNo); - SEND(", FIFO #"); - printu(can_mesg->fifoNum);*/ - /*SEND(", len="); - printu(len); - SEND(", data: ");*/ - for(ctr = 0; ctr < len; ++ctr){ - SEND(" "); - printuhex(can_mesg->data[ctr]); + if(can_mesg && isgood(can_mesg->ID)){ + LED_on(LED0); + lastT = Tms; + if(!lastT) lastT = 1; + if(ShowMsgs){ // new data in buff + IWDG->KR = IWDG_REFRESH; + len = can_mesg->length; + printu(Tms); + SEND(" #"); + printuhex(can_mesg->ID); + for(ctr = 0; ctr < len; ++ctr){ + SEND(" "); + printuhex(can_mesg->data[ctr]); + } + newline(); sendbuf(); } - newline(); sendbuf(); - } - if(usartrx()){ // usart1 received data, store in in buffer - usart_getline(&txt); - IWDG->KR = IWDG_REFRESH; - cmd_parser(txt, 0); } if((txt = get_USB())){ IWDG->KR = IWDG_REFRESH; - cmd_parser(txt, 1); + cmd_parser(txt); } } return 0; diff --git a/F0-nolib/usbcan/proto.c b/F0-nolib/usbcan/proto.c index 4bbd13b..95b8ff1 100644 --- a/F0-nolib/usbcan/proto.c +++ b/F0-nolib/usbcan/proto.c @@ -23,7 +23,6 @@ #include "can.h" #include "hardware.h" #include "proto.h" -#include "usart.h" #include "usb.h" #include // strlen @@ -33,32 +32,20 @@ extern volatile uint8_t canerror; uint8_t ShowMsgs = 07; uint16_t Ignore_IDs[IGN_SIZE]; uint8_t IgnSz = 0; -static char buff[UARTBUFSZ+1], *bptr = buff; -static uint8_t blen = 0, USBcmd = 0; +static char buff[BUFSZ+1], *bptr = buff; +static uint8_t blen = 0; void sendbuf(){ IWDG->KR = IWDG_REFRESH; if(blen == 0) return; *bptr = 0; - if(USBcmd) USB_sendstr(buff); - if(USBcmd != 1){ - usart_send(buff); - transmit_tbuf(); - } + USB_sendstr(buff); bptr = buff; blen = 0; } -// 1 - USB, 0 - USART, other number - both -// @return old buff state -uint8_t switchbuff(uint8_t isUSB){ - uint8_t r = USBcmd; - USBcmd = isUSB; - return r; -} - void bufputchar(char ch){ - if(blen > UARTBUFSZ-1){ + if(blen > BUFSZ-1){ sendbuf(); } *bptr++ = ch; @@ -415,9 +402,7 @@ static void add_filter(char *str){ * @param txt - buffer with commands & data * @param isUSB - == 1 if data got from USB */ -void cmd_parser(char *txt, uint8_t isUSB){ - USBcmd = isUSB; - //int16_t L = (int16_t)strlen(txt); +void cmd_parser(char *txt){ char _1st = txt[0]; /* * parse long commands here @@ -447,29 +432,28 @@ void cmd_parser(char *txt, uint8_t isUSB){ } if(txt[1] != '\n') *txt = '?'; // help for wrong message length switch(_1st){ - case 'B': - can_send_broadcast(); - break; - case 'C': - can_send_dummy(); - break; case 'd': IgnSz = 0; break; - case 'G': - SEND("Can address: "); - printuhex(getCANID()); - newline(); + case 'D': + SEND("Go into DFU mode\n"); + sendbuf(); + Jump2Boot(); break; case 'I': CAN_reinit(0); - SEND("Can address: "); - printuhex(getCANID()); - newline(); break; case 'l': list_filters(); break; + case 'o': + ledsON = 0; + LED_off(LED0); + LED_off(LED1); + break; + case 'O': + ledsON = 1; + break; case 'p': print_ign_buf(); break; @@ -489,34 +473,23 @@ void cmd_parser(char *txt, uint8_t isUSB){ printu(Tms); newline(); break; - case 'U': - USND("Test string for USB; a very long string that don't fit into one 64-byte buffer, what will be with it?\n"); - break; - case 'W': - SEND("Test watchdog\n"); - sendbuf(); - pause_ms(5); // a little pause to transmit data - while(1){nop();} - break; default: // help SEND( "'a' - add ID to ignore list (max 10 IDs)\n" "'b' - reinit CAN with given baudrate\n" - "'B' - send broadcast dummy byte\n" - "'C' - send dummy byte over CAN\n" "'d' - delete ignore list\n" + "'D' - activate DFU mode\n" "'f' - add/delete filter, format: bank# FIFO# mode(M/I) num0 [num1 [num2 [num3]]]\n" "'F' - send/clear flood message: F ID byte0 ... byteN\n" - "'G' - get CAN address\n" - "'I' - reinit CAN (with new address)\n" + "'I' - reinit CAN\n" "'l' - list all active filters\n" + "'o' - turn LEDs OFF\n" + "'O' - turn LEDs ON\n" "'p' - print ignore buffer\n" "'P' - pause/resume in packets displaying\n" "'R' - software reset\n" "'s/S' - send data over CAN: s ID byte0 .. byteN\n" - "'T' - gen time from start (ms)\n" - "'U' - send test string over USB\n" - "'W' - test watchdog\n" + "'T' - get time from start (ms)\n" ); break; } diff --git a/F0-nolib/usbcan/proto.h b/F0-nolib/usbcan/proto.h index b6dc5a9..0d74c47 100644 --- a/F0-nolib/usbcan/proto.h +++ b/F0-nolib/usbcan/proto.h @@ -27,6 +27,8 @@ #include "stm32f0.h" #include "hardware.h" +#define BUFSZ (64) + // macro for static strings #define SEND(str) do{addtobuf(str);}while(0) @@ -38,20 +40,19 @@ #define newline() do{bufputchar('\n');}while(0) // newline with buffer sending over USART -#define NL() do{bufputchar('\n'); register uint8_t o = switchbuff(3); sendbuf(); switchbuff(o);}while(0) +#define NL() do{bufputchar('\n'); sendbuf();}while(0) #define IGN_SIZE 10 extern uint16_t Ignore_IDs[IGN_SIZE]; extern uint8_t IgnSz; extern uint8_t ShowMsgs; -void cmd_parser(char *buf, uint8_t isUSB); +void cmd_parser(char *buf); void addtobuf(const char *txt); void bufputchar(char ch); void printu(uint32_t val); void printuhex(uint32_t val); void sendbuf(); -uint8_t switchbuff(uint8_t isUSB); char *omit_spaces(char *buf); char *getnum(char *buf, uint32_t *N); diff --git a/F0-nolib/usbcan/usart.c b/F0-nolib/usbcan/usart.c deleted file mode 100644 index 6a21d35..0000000 --- a/F0-nolib/usbcan/usart.c +++ /dev/null @@ -1,221 +0,0 @@ -/* - * usart.c - * - * Copyright 2017 Edward V. Emelianoff - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ -#include "stm32f0.h" -#include "hardware.h" -#include "usart.h" -#include - -static volatile int idatalen[2] = {0,0}; // received data line length (including '\n') -static volatile int odatalen[2] = {0,0}; - - -static volatile int dlen = 0; // length of data (including '\n') in current buffer -volatile int linerdy = 0, // received data ready - bufovr = 0, // input buffer overfull - txrdy = 1 // transmission done -; - - -static int rbufno = 0, tbufno = 0; // current rbuf/tbuf numbers -static char rbuf[2][UARTBUFSZ], tbuf[2][UARTBUFSZ]; // receive & transmit buffers -static char *recvdata = NULL; - -/** - * return length of received data (without trailing zero - */ -int usart_getline(char **line){ - if(bufovr){ - bufovr = 0; - linerdy = 0; - return 0; - } - *line = recvdata; - linerdy = 0; - return dlen; -} - -// transmit current tbuf and swap buffers -void transmit_tbuf(){ - uint32_t tmout = 16000000; - while(!txrdy){if(--tmout == 0) break;}; // wait for previos buffer transmission - register int l = odatalen[tbufno]; - if(!l) return; - txrdy = 0; - odatalen[tbufno] = 0; -#if USARTNUM == 2 - DMA1_Channel4->CCR &= ~DMA_CCR_EN; - DMA1_Channel4->CMAR = (uint32_t) tbuf[tbufno]; // mem - DMA1_Channel4->CNDTR = l; - DMA1_Channel4->CCR |= DMA_CCR_EN; // start transmission -#elif USARTNUM == 1 - DMA1_Channel2->CCR &= ~DMA_CCR_EN; - DMA1_Channel2->CMAR = (uint32_t) tbuf[tbufno]; // mem - DMA1_Channel2->CNDTR = l; - DMA1_Channel2->CCR |= DMA_CCR_EN; -#else -#error "Not implemented" -#endif - tbufno = !tbufno; -} - -void usart_putchar(const char ch){ - if(odatalen[tbufno] == UARTBUFSZ) transmit_tbuf(); - tbuf[tbufno][odatalen[tbufno]++] = ch; -} - -void usart_send(const char *str){ - uint32_t x = 512; - while(*str && --x){ - if(odatalen[tbufno] == UARTBUFSZ) transmit_tbuf(); - tbuf[tbufno][odatalen[tbufno]++] = *str++; - } -} - -void usart_sendn(const char *str, uint8_t L){ - for(uint8_t i = 0; i < L; ++i){ - if(odatalen[tbufno] == UARTBUFSZ) transmit_tbuf(); - tbuf[tbufno][odatalen[tbufno]++] = *str++; - } -} - -void usart_setup(){ -// Nucleo's USART2 connected to VCP proxy of st-link - uint32_t tmout = 16000000; -#if USARTNUM == 2 - // setup pins: PA2 (Tx - AF1), PA15 (Rx - AF1) - // AF mode (AF1) - GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODER2|GPIO_MODER_MODER15))\ - | (GPIO_MODER_MODER2_AF | GPIO_MODER_MODER15_AF); - GPIOA->AFR[0] = (GPIOA->AFR[0] &~GPIO_AFRH_AFRH2) | 1 << (2 * 4); // PA2 - GPIOA->AFR[1] = (GPIOA->AFR[1] &~GPIO_AFRH_AFRH7) | 1 << (7 * 4); // PA15 - // DMA: Tx - Ch4 - DMA1_Channel4->CPAR = (uint32_t) &USART2->TDR; // 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_5_IRQn, 3); - NVIC_EnableIRQ(DMA1_Channel4_5_IRQn); - NVIC_SetPriority(USART2_IRQn, 0); - // setup usart2 - RCC->APB1ENR |= RCC_APB1ENR_USART2EN; // clock - // oversampling by16, 115200bps (fck=48mHz) - //USART2_BRR = 0x1a1; // 48000000 / 115200 - USART2->BRR = 480000 / 1152; - USART2->CR3 = USART_CR3_DMAT; // enable DMA Tx - USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; // 1start,8data,nstop; enable Rx,Tx,USART - while(!(USART2->ISR & USART_ISR_TC)){if(--tmout == 0) break;} // polling idle frame Transmission - USART2->ICR |= USART_ICR_TCCF; // clear TC flag - USART2->CR1 |= USART_CR1_RXNEIE; - NVIC_EnableIRQ(USART2_IRQn); -// USART1 of main board -#elif USARTNUM == 1 - // PA9 - Tx, PA10 - Rx (AF1) - GPIOA->MODER = (GPIOA->MODER & ~(GPIO_MODER_MODER9 | GPIO_MODER_MODER10))\ - | (GPIO_MODER_MODER9_AF | GPIO_MODER_MODER10_AF); - GPIOA->AFR[1] = (GPIOA->AFR[1] & ~(GPIO_AFRH_AFRH1 | GPIO_AFRH_AFRH2)) | - 1 << (1 * 4) | 1 << (2 * 4); // PA9, PA10 - // USART1 Tx DMA - Channel2 (default value in SYSCFG_CFGR1) - DMA1_Channel2->CPAR = (uint32_t) &USART1->TDR; // periph - DMA1_Channel2->CMAR = (uint32_t) tbuf; // mem - DMA1_Channel2->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_Channel2_3_IRQn, 3); - NVIC_EnableIRQ(DMA1_Channel2_3_IRQn); - NVIC_SetPriority(USART1_IRQn, 0); - // setup usart1 - RCC->APB2ENR |= RCC_APB2ENR_USART1EN; - USART1->BRR = 480000 / 1152; - USART1->CR3 = USART_CR3_DMAT; // enable DMA Tx - USART1->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE; // 1start,8data,nstop; enable Rx,Tx,USART - while(!(USART1->ISR & USART_ISR_TC)){if(--tmout == 0) break;} // polling idle frame Transmission - USART1->ICR |= USART_ICR_TCCF; // clear TC flag - USART1->CR1 |= USART_CR1_RXNEIE; - NVIC_EnableIRQ(USART1_IRQn); -#else -#error "Not implemented" -#endif -} - -#if USARTNUM == 2 -void usart2_isr(){ -// USART1 -#elif USARTNUM == 1 -void usart1_isr(){ -#else -#error "Not implemented" -#endif - #ifdef CHECK_TMOUT - static uint32_t tmout = 0; - #endif - if(USARTX->ISR & USART_ISR_RXNE){ // RX not emty - receive next char - #ifdef CHECK_TMOUT - if(tmout && Tms >= tmout){ // set overflow flag - bufovr = 1; - idatalen[rbufno] = 0; - } - tmout = Tms + TIMEOUT_MS; - if(!tmout) tmout = 1; // prevent 0 - #endif - // read RDR clears flag - uint8_t rb = USARTX->RDR; - USARTX->TDR = rb; - if(idatalen[rbufno] < UARTBUFSZ){ // put next char into buf - rbuf[rbufno][idatalen[rbufno]++] = rb; - if(rb == '\n'){ // got newline - line ready - linerdy = 1; - dlen = idatalen[rbufno]; - recvdata = rbuf[rbufno]; - recvdata[dlen] = 0; - // prepare other buffer - rbufno = !rbufno; - idatalen[rbufno] = 0; - #ifdef CHECK_TMOUT - // clear timeout at line end - tmout = 0; - #endif - } - }else{ // buffer overrun - bufovr = 1; - idatalen[rbufno] = 0; - #ifdef CHECK_TMOUT - tmout = 0; - #endif - } - } -} - -#if USARTNUM == 2 -void dma1_channel4_5_isr(){ - if(DMA1->ISR & DMA_ISR_TCIF4){ // Tx - DMA1->IFCR |= DMA_IFCR_CTCIF4; // clear TC flag - txrdy = 1; - } -} -// USART1 -#elif USARTNUM == 1 -void dma1_channel2_3_isr(){ - if(DMA1->ISR & DMA_ISR_TCIF2){ // Tx - DMA1->IFCR |= DMA_IFCR_CTCIF2; // clear TC flag - txrdy = 1; - } -} -#else -#error "Not implemented" -#endif diff --git a/F0-nolib/usbcan/usart.h b/F0-nolib/usbcan/usart.h deleted file mode 100644 index 3574449..0000000 --- a/F0-nolib/usbcan/usart.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * usart.h - * - * Copyright 2017 Edward V. Emelianoff - * - * 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 2 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, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ -#pragma once -#ifndef __USART_H__ -#define __USART_H__ - -#include "hardware.h" - -// input and output buffers size -#define UARTBUFSZ (64) -// timeout between data bytes -#ifndef TIMEOUT_MS -#define TIMEOUT_MS (1500) -#endif - -#define usartrx() (linerdy) -#define usartovr() (bufovr) - -extern volatile int linerdy, bufovr, txrdy; - -void transmit_tbuf(); -void usart_setup(); -int usart_getline(char **line); -void usart_send(const char *str); -void usart_sendn(const char *str, uint8_t L); -void usart_putchar(const char ch); -void hexdump(uint8_t *arr, uint16_t len); - -#endif // __USART_H__ diff --git a/F0-nolib/usbcan/usb.c b/F0-nolib/usbcan/usb.c index 5727abf..b1f583d 100644 --- a/F0-nolib/usbcan/usb.c +++ b/F0-nolib/usbcan/usb.c @@ -23,7 +23,6 @@ #include "usb.h" #include "usb_lib.h" -#include "usart.h" static volatile uint8_t tx_succesfull = 1; static volatile uint8_t rxNE = 0; diff --git a/F0-nolib/usbcan/usbcan.bin b/F0-nolib/usbcan/usbcan.bin index dafdef477385603b00101e2f60f18ba1109c04c9..cb484e6c56abbef9cedb046d0b3456d88db8b31d 100755 GIT binary patch delta 5579 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 delta 7121 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 RIS4RXGIC-Ac$enX@ZVoah+qH!