From e51a45040b76e9f312770be58b590dd343873a28 Mon Sep 17 00:00:00 2001 From: eddyem Date: Mon, 13 Apr 2020 21:21:40 +0300 Subject: [PATCH] Add flood & pause commands --- F0-nolib/usbcdc/Makefile | 2 +- F0-nolib/usbcdc/can.c | 24 ++++++++-- F0-nolib/usbcdc/can.h | 8 +++- F0-nolib/usbcdc/main.c | 28 ++++++------ F0-nolib/usbcdc/proto.c | 89 ++++++++++++++++++++----------------- F0-nolib/usbcdc/proto.h | 7 ++- F0-nolib/usbcdc/usbcan.bin | Bin 13888 -> 13504 bytes 7 files changed, 94 insertions(+), 64 deletions(-) diff --git a/F0-nolib/usbcdc/Makefile b/F0-nolib/usbcdc/Makefile index fbd6873..cd31dcb 100644 --- a/F0-nolib/usbcdc/Makefile +++ b/F0-nolib/usbcdc/Makefile @@ -8,7 +8,7 @@ MCU = F042x6 # hardware definitions DEFS += -DUSARTNUM=1 #DEFS += -DCHECK_TMOUT -DEFS += -DEBUG +#DEFS += -DEBUG # change this linking script depending on particular MCU model, # for example, if you have STM32F103VBT6, you should write: LDSCRIPT = stm32f042k.ld diff --git a/F0-nolib/usbcdc/can.c b/F0-nolib/usbcdc/can.c index 2852b4c..91b23e0 100644 --- a/F0-nolib/usbcdc/can.c +++ b/F0-nolib/usbcdc/can.c @@ -41,6 +41,9 @@ static CAN_status can_status = CAN_STOP; static void can_process_fifo(uint8_t fifo_num); +static CAN_message loc_flood_msg; +static CAN_message *flood_msg = NULL; // == loc_flood_msg - to flood + CAN_status CAN_get_status(){ CAN_status st = can_status; // give overrun message only once @@ -205,9 +208,9 @@ void can_proc(){ if(CAN->RF1R & CAN_RF1R_FMP1){ can_process_fifo(1); } + IWDG->KR = IWDG_REFRESH; if(CAN->ESR & (CAN_ESR_BOFF | CAN_ESR_EPVF | CAN_ESR_EWGF)){ // much errors - restart CAN BUS - switchbuff(0); - SEND("\nToo much errors, restarting!\n"); + SEND("\nToo much errors, restarting CAN!\n"); SEND("Receive error counter: "); printu((CAN->ESR & CAN_ESR_REC)>>24); SEND("\nTransmit error counter: "); @@ -236,6 +239,11 @@ void can_proc(){ RCC->APB1RSTR &= ~RCC_APB1RSTR_CANRST; CAN_setup(0); } + static uint32_t lastFloodTime = 0; + if(flood_msg && (Tms - lastFloodTime) > (FLOOD_PERIOD_MS-1)){ // flood every ~5ms + 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; @@ -333,6 +341,14 @@ void can_send_broadcast(){ MSG("Broadcast message sent\n"); } +void set_flood(CAN_message *msg){ + if(!msg) flood_msg = NULL; + else{ + memcpy(&loc_flood_msg, msg, sizeof(CAN_message)); + flood_msg = &loc_flood_msg; + } +} + static void can_process_fifo(uint8_t fifo_num){ if(fifo_num > 1) return; LED_on(LED1); // Turn on LED1 - message received @@ -354,8 +370,8 @@ static void can_process_fifo(uint8_t fifo_num){ uint8_t len = box->RDTR & 0x7; msg.length = len; msg.ID = box->RIR >> 21; - msg.filterNo = (box->RDTR >> 8) & 0xff; - msg.fifoNum = fifo_num; + //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){ diff --git a/F0-nolib/usbcdc/can.h b/F0-nolib/usbcdc/can.h index 6ff83eb..2ae474f 100644 --- a/F0-nolib/usbcdc/can.h +++ b/F0-nolib/usbcdc/can.h @@ -28,6 +28,8 @@ // amount of filter banks in STM32F0 #define STM32F0FBANKNO 28 +// flood period in milliseconds +#define FLOOD_PERIOD_MS 5 // simple 1-byte commands #define CMD_TOGGLE (0xDA) @@ -48,8 +50,8 @@ 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 + //uint8_t filterNo; // filter number + //uint8_t fifoNum; // message FIFO number uint16_t ID; // ID of receiver } CAN_message; @@ -76,4 +78,6 @@ void can_proc(); CAN_message *CAN_messagebuf_pop(); +void set_flood(CAN_message *msg); + #endif // __CAN_H__ diff --git a/F0-nolib/usbcdc/main.c b/F0-nolib/usbcdc/main.c index b791b3b..cf9586d 100644 --- a/F0-nolib/usbcdc/main.c +++ b/F0-nolib/usbcdc/main.c @@ -105,14 +105,15 @@ int main(void){ switchbuff(0); SEND("Greetings! My address is "); printuhex(getCANID()); - NL(); + newline(); if(RCC->CSR & RCC_CSR_IWDGRSTF){ // watchdog reset occured - SEND("WDGRESET=1\n"); NL(); + SEND("WDGRESET=1\n"); } if(RCC->CSR & RCC_CSR_SFTRSTF){ // software reset occured - SEND("SOFTRESET=1\n"); NL(); + SEND("SOFTRESET=1\n"); } + sendbuf(); RCC->CSR |= RCC_CSR_RMVF; // remove reset flags iwdg_setup(); USB_setup(); @@ -127,26 +128,27 @@ int main(void){ can_proc(); usb_proc(); if(CAN_get_status() == CAN_FIFO_OVERRUN){ - switchbuff(3); + register uint8_t o = switchbuff(3); SEND("CAN bus fifo overrun occured!\n"); - newline(); sendbuf(); + sendbuf(); switchbuff(o); } can_mesg = CAN_messagebuf_pop(); - if(can_mesg && isgood(can_mesg->ID)){ // new data in buff + if(ShowMsgs && can_mesg && isgood(can_mesg->ID)){ // new data in buff + IWDG->KR = IWDG_REFRESH; len = can_mesg->length; - switchbuff(3); - SEND("got message, ID="); + printu(Tms); + SEND(" #"); printuhex(can_mesg->ID); - SEND(", filter #"); + /*SEND(", filter #"); printu(can_mesg->filterNo); SEND(", FIFO #"); - printu(can_mesg->fifoNum); - SEND(", len="); + printu(can_mesg->fifoNum);*/ + /*SEND(", len="); printu(len); - SEND(", data: "); + SEND(", data: ");*/ for(ctr = 0; ctr < len; ++ctr){ - printuhex(can_mesg->data[ctr]); SEND(" "); + printuhex(can_mesg->data[ctr]); } newline(); sendbuf(); } diff --git a/F0-nolib/usbcdc/proto.c b/F0-nolib/usbcdc/proto.c index 6521217..7aeba57 100644 --- a/F0-nolib/usbcdc/proto.c +++ b/F0-nolib/usbcdc/proto.c @@ -30,6 +30,7 @@ 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; @@ -49,25 +50,13 @@ void sendbuf(){ } // 1 - USB, 0 - USART, other number - both -void switchbuff(uint8_t isUSB){ +// @return old buff state +uint8_t switchbuff(uint8_t isUSB){ + uint8_t r = USBcmd; USBcmd = isUSB; + return r; } -/* -void memcpy(void *dest, const void *src, int len){ - while(len > 4){ - *(uint32_t*)dest++ = *(uint32_t*)src++; - len -= 4; - } - while(len--) *(uint8_t*)dest++ = *(uint8_t*)src++; -} - -int strlen(const char *txt){ - int l = 0; - while(*txt++) ++l; - return l; -}*/ - void bufputchar(char ch){ if(blen > UARTBUFSZ-1){ sendbuf(); @@ -159,15 +148,14 @@ char *getnum(char *txt, uint32_t *N){ return getdec(txt, N); } - -// send command, format: ID (hex/bin/dec) data bytes (up to 8 bytes, space-delimeted) -TRUE_INLINE void sendCANcommand(char *txt){ - SEND("CAN command with arguments:\n"); +// parse `txt` to CAN_message +static CAN_message *parseCANmsg(char *txt){ + static CAN_message canmsg; + //SEND("CAN command with arguments:\n"); uint32_t N; - uint16_t ID = 0xffff; char *n; - uint8_t data[8]; int ctr = -1; + canmsg.ID = 0xffff; do{ txt = omit_spaces(txt); n = getnum(txt, &N); @@ -175,37 +163,46 @@ TRUE_INLINE void sendCANcommand(char *txt){ txt = n; if(ctr == -1){ if(N > 0x7ff){ - SEND("ID should be 11-bit number!"); - return; + SEND("ID should be 11-bit number!\n"); + return NULL; } - ID = (uint16_t)(N&0x7ff); - SEND("ID="); printuhex(ID); newline(); + canmsg.ID = (uint16_t)(N&0x7ff); + SEND("ID="); printuhex(canmsg.ID); newline(); ctr = 0; continue; } if(ctr > 7){ - SEND("ONLY 8 data bytes allowed!"); - return; + SEND("ONLY 8 data bytes allowed!\n"); + return NULL; } if(N > 0xff){ - SEND("Every data portion is a byte!"); - return; + SEND("Every data portion is a byte!\n"); + return NULL; } - data[ctr++] = (uint8_t)(N&0xff); - printu(N); SEND(", hex: "); - printuhex(N); newline(); + canmsg.data[ctr++] = (uint8_t)(N&0xff); + //printu(N); SEND(", hex: "); + //printuhex(N); newline(); }while(1); - if(*n){ + /*if(*n){ SEND("\nUnusefull data: "); SEND(n); + }*/ + if(canmsg.ID == 0xffff){ + SEND("NO ID given, send nothing!\n"); + return NULL; } - if(ID == 0xffff){ - SEND("NO ID given, send nothing!"); - return; - } + SEND("Message parsed OK\n"); sendbuf(); - N = 1000000; - while(CAN_BUSY == can_send(data, (uint8_t)ctr, ID)){ + canmsg.length = (uint8_t) ctr; + return &canmsg; +} + +// send command, format: ID (hex/bin/dec) data bytes (up to 8 bytes, space-delimeted) +TRUE_INLINE void sendCANcommand(char *txt){ + CAN_message *msg = parseCANmsg(txt); + if(!msg) return; + uint32_t N = 1000000; + while(CAN_BUSY == can_send(msg->data, msg->length, msg->ID)){ if(--N == 0) break; } } @@ -256,6 +253,7 @@ TRUE_INLINE void print_ign_buf(){ SEND("Ignore buffer is empty"); return; } + SEND("Ignored IDs:\n"); for(int i = 0; i < IgnSz; ++i){ printu(i); SEND(": "); @@ -437,6 +435,10 @@ void cmd_parser(char *txt, uint8_t isUSB){ add_filter(txt + 1); goto eof; break; + case 'F': + set_flood(parseCANmsg(txt + 1)); + goto eof; + break; case 's': case 'S': sendCANcommand(txt + 1); @@ -471,6 +473,11 @@ void cmd_parser(char *txt, uint8_t isUSB){ case 'p': print_ign_buf(); break; + case 'P': + ShowMsgs = !ShowMsgs; + if(ShowMsgs) SEND("Resume\n"); + else SEND("Pause\n"); + break; case 'R': SEND("Soft reset\n"); sendbuf(); @@ -499,10 +506,12 @@ void cmd_parser(char *txt, uint8_t isUSB){ "'C' - send dummy byte over CAN\n" "'d' - delete ignore list\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" "'l' - list all active filters\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" diff --git a/F0-nolib/usbcdc/proto.h b/F0-nolib/usbcdc/proto.h index 1f4905e..b6dc5a9 100644 --- a/F0-nolib/usbcdc/proto.h +++ b/F0-nolib/usbcdc/proto.h @@ -38,11 +38,12 @@ #define newline() do{bufputchar('\n');}while(0) // newline with buffer sending over USART -#define NL() do{bufputchar('\n'); switchbuff(0); sendbuf();}while(0) +#define NL() do{bufputchar('\n'); register uint8_t o = switchbuff(3); sendbuf(); switchbuff(o);}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 addtobuf(const char *txt); @@ -50,13 +51,11 @@ void bufputchar(char ch); void printu(uint32_t val); void printuhex(uint32_t val); void sendbuf(); -void switchbuff(uint8_t isUSB); +uint8_t switchbuff(uint8_t isUSB); char *omit_spaces(char *buf); char *getnum(char *buf, uint32_t *N); uint8_t isgood(uint16_t ID); -//int strlen(const char *txt); -//void memcpy(void *dest, const void *src, int len); #endif // __PROTO_H__ diff --git a/F0-nolib/usbcdc/usbcan.bin b/F0-nolib/usbcdc/usbcan.bin index fbe7f64c48e9e4996c6f2485713f8cdeac9d2957..6630c83b8eb22e8f2f082645cde8646b464cbabf 100755 GIT binary patch delta 6569 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 delta 7022 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 z