diff --git a/F1:F103/BISS_C_encoders/99-myHW.rules b/F1:F103/BISS_C_encoders/99-myHW.rules new file mode 100644 index 0000000..983dee6 --- /dev/null +++ b/F1:F103/BISS_C_encoders/99-myHW.rules @@ -0,0 +1,4 @@ +ACTION=="add", DRIVERS=="usb", ENV{USB_IDS}="%s{idVendor}:%s{idProduct}" +ACTION=="add", ENV{USB_IDS}=="067b:2303", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty" +ACTION=="add", ENV{USB_IDS}=="0483:5740", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty" + diff --git a/F1:F103/BISS_C_encoders/encoders.bin b/F1:F103/BISS_C_encoders/encoders.bin index a55eb97..3806056 100755 Binary files a/F1:F103/BISS_C_encoders/encoders.bin and b/F1:F103/BISS_C_encoders/encoders.bin differ diff --git a/F1:F103/BISS_C_encoders/encoders.files b/F1:F103/BISS_C_encoders/encoders.files index 9bc5db9..42d3791 100644 --- a/F1:F103/BISS_C_encoders/encoders.files +++ b/F1:F103/BISS_C_encoders/encoders.files @@ -1,3 +1,5 @@ +flash.c +flash.h hardware.c hardware.h main.c @@ -5,6 +7,8 @@ proto.c proto.h ringbuffer.c ringbuffer.h +spi.c +spi.h strfunc.c strfunc.h usart.c diff --git a/F1:F103/BISS_C_encoders/flash.c b/F1:F103/BISS_C_encoders/flash.c new file mode 100644 index 0000000..5a64626 --- /dev/null +++ b/F1:F103/BISS_C_encoders/flash.c @@ -0,0 +1,196 @@ +/* + * This file is part of the encoders project. + * Copyright 2025 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 "usb_dev.h" // printout +#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) \ + } + +static int write2flash(const void*, const void*, uint32_t); +const user_conf *Flash_Data = (const user_conf *)(&__varsstart); +user_conf the_conf = USERCONF_INITIALIZER; + +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; +#ifdef EBUG + CMDWR("mid/l/r="); + CMDWR(u2str(mid)); CMDWR("/"); + CMDWR(u2str(l)); CMDWR("/"); + CMDWR(u2str(r)); CMDWR("/"); + CMDn(); +#endif + 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); + } +#ifdef EBUG + CMDWR("INIT\n"); +#endif + // -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)); + } +#ifdef EBUG + CMDWR("currentconfidx="); CMDWR(i2str(currentconfidx)); CMDn(); +#endif +} + +// 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]){ + CMDWR("DON'T MATCH!\n"); + ret = 1; + break; + } + if(FLASH->SR & FLASH_SR_PGERR){ + CMDWR("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; +#ifdef EBUG + CMDWR("Erase page #"); CMDWR(u2str(N)); CMDn(); +#endif + 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 + CMDWR("FLASH_SIZE="); CMDWR(u2str(FLASH_SIZE)); + CMDWR("\nflsz="); CMDWR(u2str(flsz)); + CMDWR("\nend="); CMDWR(u2str(end)); + CMDWR("\ncurrentconfidx="); CMDWR(u2str(currentconfidx)); + CMDWR("\nmaxCnum="); CMDWR(u2str(maxCnum)); + CMDn(); +#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; +} + diff --git a/F1:F103/BISS_C_encoders/flash.h b/F1:F103/BISS_C_encoders/flash.h new file mode 100644 index 0000000..fb7c002 --- /dev/null +++ b/F1:F103/BISS_C_encoders/flash.h @@ -0,0 +1,46 @@ +/* + * This file is part of the encoders project. + * Copyright 2025 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 "usb_descr.h" + +#define FLASH_SIZE_REG ((uint32_t)0x1FFFF7E0) +#define FLASH_SIZE *((uint16_t*)FLASH_SIZE_REG) + +// maximal size (in letters) of iInterface for settings +#define MAX_IINTERFACE_SZ (16) + +/* + * struct to save user configurations + */ +typedef struct __attribute__((packed, aligned(4))){ + uint16_t userconf_sz; // "magick number" + uint16_t iInterface[bTotNumEndpoints][MAX_IINTERFACE_SZ]; // hryunikod! + uint8_t iIlengths[bTotNumEndpoints]; +} user_conf; + +extern user_conf the_conf; +extern int currentconfidx; + +void flashstorage_init(); +int store_userconf(); +int erase_storage(int npage); + + diff --git a/F1:F103/BISS_C_encoders/hardware.c b/F1:F103/BISS_C_encoders/hardware.c index f28ac75..f24d2d1 100644 --- a/F1:F103/BISS_C_encoders/hardware.c +++ b/F1:F103/BISS_C_encoders/hardware.c @@ -16,21 +16,27 @@ */ #include "hardware.h" +#include "spi.h" static inline void gpio_setup(){ // Enable clocks to the GPIO subsystems (PB for ADC), turn on AFIO clocking to disable SWD/JTAG - RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN; + RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN; // turn off SWJ/JTAG // AFIO->MAPR = AFIO_MAPR_SWJ_CFG_DISABLE; AFIO->MAPR = AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // for PA15 // Set led as opendrain output GPIOC->CRH |= CRH(13, CNF_ODOUTPUT|MODE_SLOW); + GPIOA->CRL = CRL(5, CNF_AFPP|MODE_FAST) | CRL(6, CNF_FLINPUT); // USB pullup (PA15) - pushpull output GPIOA->CRH = CRH(15, CNF_PPOUTPUT|MODE_SLOW); + GPIOB->CRH = CRH(13, CNF_AFPP|MODE_FAST) | CRH(14, CNF_FLINPUT); } void hw_setup(){ gpio_setup(); + // setup both SPI channels + spi_setup(1); + spi_setup(2); } #ifndef EBUG diff --git a/F1:F103/BISS_C_encoders/main.c b/F1:F103/BISS_C_encoders/main.c index 4087e90..33299cd 100644 --- a/F1:F103/BISS_C_encoders/main.c +++ b/F1:F103/BISS_C_encoders/main.c @@ -16,8 +16,10 @@ * along with this program. If not, see . */ +#include "flash.h" #include "hardware.h" #include "proto.h" +#include "spi.h" #include "strfunc.h" #include "usart.h" #include "usb_dev.h" @@ -32,7 +34,9 @@ void sys_tick_handler(void){ int main(){ char inbuff[RBINSZ]; uint32_t lastT = 0, lastS = 0; + uint8_t encbuf[ENCODER_BUFSZ]; StartHSE(); + flashstorage_init(); hw_setup(); USBPU_OFF(); SysTick_Config(72000); @@ -79,6 +83,9 @@ int main(){ CMDWR(inbuff); CMDWR("'\n"); } + if(spi_read_enc(0, encbuf)){ // send encoder data + hexdump(I_X, encbuf, ENCODER_BUFSZ); + } } if(CDCready[I_Y]){ int l = USB_receivestr(I_Y, inbuff, RBINSZ); @@ -88,6 +95,9 @@ int main(){ CMDWR(inbuff); CMDWR("'\n"); } + if(spi_read_enc(1, encbuf)){ // send encoder data + hexdump(I_Y, encbuf, ENCODER_BUFSZ); + } } } return 0; diff --git a/F1:F103/BISS_C_encoders/openocd.cfg b/F1:F103/BISS_C_encoders/openocd.cfg index bdab2a1..a43a499 100644 --- a/F1:F103/BISS_C_encoders/openocd.cfg +++ b/F1:F103/BISS_C_encoders/openocd.cfg @@ -1,4 +1,94 @@ set FLASH_SIZE 0x10000 -source [find interface/stlink-v2-1.cfg] -source [find target/stm32f1x.cfg] +source [find interface/stlink.cfg] + + +# script for stm32f1x family + +# +# stm32 devices support both JTAG and SWD transports. +# +source [find target/swj-dp.tcl] +source [find mem_helper.tcl] + +if { [info exists CHIPNAME] } { + set _CHIPNAME $CHIPNAME +} else { + set _CHIPNAME stm32f1x +} + +set _ENDIAN little + +# Work-area is a space in RAM used for flash programming +# By default use 4kB (as found on some STM32F100s) +if { [info exists WORKAREASIZE] } { + set _WORKAREASIZE $WORKAREASIZE +} else { + set _WORKAREASIZE 0x1000 +} + +# Allow overriding the Flash bank size +if { [info exists FLASH_SIZE] } { + set _FLASH_SIZE $FLASH_SIZE +} else { + # autodetect size + set _FLASH_SIZE 0 +} + +#jtag scan chain +if { [info exists CPUTAPID] } { + set _CPUTAPID $CPUTAPID +} else { + if { [using_jtag] } { + # See STM Document RM0008 Section 26.6.3 + set _CPUTAPID 0x2ba01477 + } { + # this is the SW-DP tap id not the jtag tap id + set _CPUTAPID 0x2ba01477 + } +} + +swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID +dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu + +if {[using_jtag]} { + jtag newtap $_CHIPNAME bs -irlen 5 +} + +set _TARGETNAME $_CHIPNAME.cpu +target create $_TARGETNAME cortex_m -endian $_ENDIAN -dap $_CHIPNAME.dap + +$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0 + +# flash size will be probed +set _FLASHNAME $_CHIPNAME.flash +flash bank $_FLASHNAME stm32f1x 0x08000000 $_FLASH_SIZE 0 0 $_TARGETNAME + +# JTAG speed should be <= F_CPU/6. F_CPU after reset is 8MHz, so use F_JTAG = 1MHz +adapter_khz 1000 + +adapter_nsrst_delay 100 +if {[using_jtag]} { + jtag_ntrst_delay 100 +} + +reset_config srst_nogate + +if {![using_hla]} { + # if srst is not fitted use SYSRESETREQ to + # perform a soft reset + cortex_m reset_config sysresetreq +} + +$_TARGETNAME configure -event examine-end { + # DBGMCU_CR |= DBG_WWDG_STOP | DBG_IWDG_STOP | + # DBG_STANDBY | DBG_STOP | DBG_SLEEP + mmw 0xE0042004 0x00000307 0 +} + +$_TARGETNAME configure -event trace-config { + # Set TRACE_IOEN; TRACE_MODE is set to async; when using sync + # change this value accordingly to configure trace pins + # assignment + mmw 0xE0042004 0x00000020 0 +} diff --git a/F1:F103/BISS_C_encoders/openocd.cfg_ b/F1:F103/BISS_C_encoders/openocd.cfg_ new file mode 100644 index 0000000..7533dd1 --- /dev/null +++ b/F1:F103/BISS_C_encoders/openocd.cfg_ @@ -0,0 +1,4 @@ +set FLASH_SIZE 0x10000 + +source [find interface/stlink.cfg] +source [find target/stm32f1x.cfg] diff --git a/F1:F103/BISS_C_encoders/proto.c b/F1:F103/BISS_C_encoders/proto.c index 28a772f..659e88b 100644 --- a/F1:F103/BISS_C_encoders/proto.c +++ b/F1:F103/BISS_C_encoders/proto.c @@ -18,20 +18,13 @@ #include +#include "flash.h" #include "proto.h" +#include "spi.h" #include "strfunc.h" #include "usb_dev.h" #include "version.inc" -// commands indexes -typedef enum{ - C_dummy, - C_help, - C_sendX, - C_sendY, - C_AMOUNT -} cmd_e; - // error codes typedef enum { ERR_OK, // no errors @@ -50,6 +43,27 @@ static const char* const errors[ERR_AMOUNT] = { [ERR_SILENCE] = "", }; +// commands indexes +typedef enum{ + C_dummy, + C_help, + C_sendX, + C_sendY, + C_setiface1, + C_setiface2, + C_setiface3, + C_dumpconf, + C_erasestorage, + C_storeconf, + C_reboot, + C_fin, + C_encstart, + C_spistat, + C_spiinit, + C_spideinit, + C_AMOUNT +} cmd_e; + // command handler (idx - index of command in list, par - all after equal sign in "cmd = par") typedef errcode_e (*handler_t)(cmd_e idx, char *par); @@ -65,6 +79,7 @@ static const funcdescr_t commands[C_AMOUNT]; CMDWR(i2str(ival)); CMDn(); return ERR_SILENCE; }while(0) static errcode_e help(cmd_e idx, char* par); +static errcode_e dumpconf(cmd_e idx, char *par); static errcode_e dummy(cmd_e idx, char *par){ static int32_t val = -111; @@ -98,13 +113,132 @@ static errcode_e sendenc(cmd_e idx, char *par){ return ERR_OK; } +static errcode_e setiface(cmd_e idx, char *par){ + if(idx < C_setiface1 || idx >= C_setiface1 + bTotNumEndpoints) return ERR_BADCMD; + idx -= C_setiface1; // now it is an index of iIlengths + if(par && *par){ + int l = strlen(par); + DBGs(i2str(l)); + if(l > MAX_IINTERFACE_SZ) return ERR_BADPAR; // too long + the_conf.iIlengths[idx] = (uint8_t) l * 2; + char *ptr = (char*)the_conf.iInterface[idx]; + for(int i = 0; i < l; ++i){ // make fucking hryunicode + *ptr++ = *par++; + *ptr++ = 0; + } + } + // getter + int l = the_conf.iIlengths[idx] / 2; + char *ptr = (char*)the_conf.iInterface[idx]; + CMDWR(commands[idx + C_setiface1].cmd); + CMDWR("="); + for(int i = 0; i < l; ++i){ + USB_putbyte(I_CMD, *ptr); + ptr += 2; + } + CMDn(); + return ERR_SILENCE; +} + +static errcode_e erasestor(cmd_e _U_ idx, char *par){ + int32_t npage = -1; + if(par){ + if(par == getint(par, &npage)) return ERR_BADPAR; + } + if(erase_storage(npage)) return ERR_FAIL; + return ERR_OK; +} + +static errcode_e storeconf(_U_ cmd_e idx, _U_ char *par){ + if(store_userconf()) return ERR_FAIL; + return ERR_OK; +} + +static errcode_e reboot(_U_ cmd_e idx, _U_ char *par){ + NVIC_SystemReset(); + return ERR_OK; // never reached +} + +static errcode_e fini(_U_ cmd_e idx, _U_ char *par){ + flashstorage_init(); + return ERR_OK; // never reached +} + +static errcode_e encstart(_U_ cmd_e idx, _U_ char *par){ + if(!spi_start_enc(0)) return ERR_FAIL; + if(!spi_start_enc(1)) return ERR_FAIL; + return ERR_OK; +} + +static errcode_e spistat(_U_ cmd_e idx, _U_ char *par){ + for(int i = 1; i < 3; ++i){ + USB_sendstr(I_CMD, "SPI"); + USB_putbyte(I_CMD, '0' + i); + USB_sendstr(I_CMD, ": "); + switch(spi_status[i]){ + case SPI_NOTREADY: + USB_sendstr(I_CMD, "not ready"); + break; + case SPI_BUSY: + USB_sendstr(I_CMD, "busy"); + break; + case SPI_READY: + USB_sendstr(I_CMD, "ready"); + break; + } + newline(I_CMD); + } + return ERR_SILENCE; +} + +static errcode_e spiinit(_U_ cmd_e idx, _U_ char *par){ + for(int i = 1; i < 3; ++i){ + USB_sendstr(I_CMD, "Init SPI"); + USB_putbyte(I_CMD, '0' + i); + newline(I_CMD); + spi_setup(i); + } + return ERR_SILENCE; +} + +static errcode_e spideinit(_U_ cmd_e idx, _U_ char *par){ + for(int i = 1; i < 3; ++i){ + USB_sendstr(I_CMD, "DEinit SPI"); + USB_putbyte(I_CMD, '0' + i); + newline(I_CMD); + spi_deinit(i); + } + return ERR_SILENCE; +} + // text commands static const funcdescr_t commands[C_AMOUNT] = { [C_dummy] = {"dummy", dummy}, [C_help] = {"help", help}, [C_sendX] = {"sendx", sendenc}, [C_sendY] = {"sendy", sendenc}, - }; + [C_setiface1] = {"setiface1", setiface}, + [C_setiface2] = {"setiface2", setiface}, + [C_setiface3] = {"setiface3", setiface}, + [C_dumpconf] = {"dumpconf", dumpconf}, + [C_erasestorage] = {"erasestorage", erasestor}, + [C_storeconf] = {"storeconf", storeconf}, + [C_reboot] = {"reboot", reboot}, + [C_fin] = {"fin", fini}, + [C_encstart] = {"readenc", encstart}, + [C_spistat] = {"spistat", spistat}, + [C_spiinit] = {"spiinit", spiinit}, + [C_spideinit] = {"spideinit", spideinit}, +}; + +static errcode_e dumpconf(cmd_e _U_ idx, char _U_ *par){ + CMDWR("userconf_sz="); CMDWR(u2str(the_conf.userconf_sz)); + CMDWR("\ncurrentconfidx="); CMDWR(i2str(currentconfidx)); + CMDn(); + for(int i = 0; i < bTotNumEndpoints; ++i) + setiface(C_setiface1 + i, NULL); + return ERR_SILENCE; +} typedef struct{ int idx; // command index (if < 0 - just display `help` as grouping header) @@ -113,18 +247,31 @@ typedef struct{ // SHOUL be sorted and grouped static const help_t helpmessages[] = { + {-1, "Configuration"}, + {C_dumpconf, "dump current configuration"}, + {C_erasestorage, "erase full storage or current page (=n)"}, + {C_setiface1, "set name of first (command) interface"}, + {C_setiface2, "set name of second (axis X) interface"}, + {C_setiface3, "set name of third (axis Y) interface"}, + {C_storeconf, "store configuration in flash memory"}, {-1, "Different commands"}, {C_dummy, "dummy integer setter/getter"}, + {C_encstart, "start reading encoders"}, {C_help, "show this help"}, - {-1, "Debug commands"}, - {C_sendX, "send text string to X encoder"}, - {C_sendY, "send text string to Y encoder"}, + {C_reboot, "reboot MCU"}, + {C_spideinit, "deinit SPI"}, + {C_spiinit, "init SPI"}, + {C_spistat, "get status of both SPI interfaces"}, + {-1, "Debug"}, + {C_sendX, "send text string to X encoder's terminal"}, + {C_sendY, "send text string to Y encoder's terminal"}, + {C_fin, "reinit flash"}, {-1, NULL}, }; static errcode_e help(_U_ cmd_e idx, _U_ char* par){ - CMDWRn("https://github.com/eddyem/stm32samples/tree/master/F1:F103/ build #" BUILD_NUMBER " @ " BUILD_DATE); - CMDWRn("commands format: command[=setter]\\n"); + CMDWRn("https://github.com/eddyem/stm32samples/tree/master/F1:F103/BISS_C_encoders build #" BUILD_NUMBER " @ " BUILD_DATE); + CMDWRn("\ncommands format: 'command[=setter]\\n'"); const help_t *c = helpmessages; while(c->help){ if(c->idx < 0){ // header @@ -139,7 +286,7 @@ static errcode_e help(_U_ cmd_e idx, _U_ char* par){ CMDn(); ++c; } - return ERR_OK; + return ERR_SILENCE; } // *cmd is "command" for commands/getters or "parameter=value" for setters @@ -149,7 +296,7 @@ void parse_cmd(char *cmd){ char *cmdstart = omit_spaces(cmd), *parstart = NULL; if(!cmdstart) goto retn; char *ptr = cmdstart; - while(*ptr > '@') ++ptr; + while(*ptr > ' ' && *ptr != '=') ++ptr; if(*ptr && *ptr <= ' '){ // there's spaces after command *ptr++ = 0; ptr = omit_spaces(ptr); diff --git a/F1:F103/BISS_C_encoders/spi.c b/F1:F103/BISS_C_encoders/spi.c new file mode 100644 index 0000000..4c83255 --- /dev/null +++ b/F1:F103/BISS_C_encoders/spi.c @@ -0,0 +1,174 @@ +/* + * This file is part of the encoders project. + * Copyright 2025 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 "spi.h" +#include // memcpy +#include "usb_dev.h" +#ifdef EBUG +#include "strfunc.h" +#endif + +#define CHKIDX(idx) do{if(idx == 0 || idx > AMOUNT_OF_SPI) return;}while(0) +#define CHKIDXR(idx) do{if(idx == 0 || idx > AMOUNT_OF_SPI) return 0;}while(0) + +spiStatus spi_status[AMOUNT_OF_SPI+1] = {0, SPI_NOTREADY, SPI_NOTREADY}; +static volatile SPI_TypeDef* const SPIs[AMOUNT_OF_SPI+1] = {NULL, SPI1, SPI2}; +static volatile DMA_Channel_TypeDef * const DMAs[AMOUNT_OF_SPI+1] = {NULL, DMA1_Channel2, DMA1_Channel4}; +#define WAITX(x) do{volatile uint32_t wctr = 0; while((x) && (++wctr < 360000)) IWDG->KR = IWDG_REFRESH; if(wctr==360000){ DBG("timeout"); return 0;}}while(0) + +static uint8_t encoderbuf[AMOUNT_OF_SPI][ENCODER_BUFSZ] = {0}; +static uint8_t freshdata[AMOUNT_OF_SPI] = {0}; + +// init SPI to work RX-only with DMA +// SPI1 (PA5/PA6): DMA1_Channel2 +// SPI2 (PB13/PB14): DMA1_Channel4 +void spi_setup(uint8_t idx){ + CHKIDX(idx); + volatile SPI_TypeDef *SPI = SPIs[idx]; + SPI->CR1 = 0; // clear EN + SPI->CR2 = 0; + RCC->AHBENR |= RCC_AHBENR_DMA1EN; + volatile DMA_Channel_TypeDef *DMA = DMAs[idx]; + if(idx == 1){ // PA5/PA6; 72MHz + RCC->APB2RSTR = RCC_APB2RSTR_SPI1RST; + RCC->APB2RSTR = 0; // clear reset + GPIOA->CRL = (GPIOA->CRL & ~(GPIO_CRL_CNF5 | GPIO_CRL_CNF6)) + | CRL(5, CNF_AFPP|MODE_FAST) | CRL(6, CNF_FLINPUT); + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; + SPI->CR1 = SPI_CR1_BR_0 | SPI_CR1_BR_2; // Fpclk/64 + NVIC_EnableIRQ(DMA1_Channel2_IRQn); // enable Rx interrupt + }else if(idx == 2){ // PB12..PB15; 36MHz + RCC->APB1RSTR = RCC_APB1RSTR_SPI2RST; + RCC->APB1RSTR = 0; + GPIOB->CRH = (GPIOB->CRH & ~(GPIO_CRH_CNF13 | GPIO_CRH_CNF14)) + | CRH(13, CNF_AFPP|MODE_FAST) | CRH(14, CNF_FLINPUT); + RCC->APB1ENR |= RCC_APB1ENR_SPI2EN; + SPI->CR1 = SPI_CR1_BR_2; // Fpclk/32 + NVIC_EnableIRQ(DMA1_Channel4_IRQn); + }else return; // err + // Baudrate = 0b110 - fpclk/128 + SPI->CR1 |= SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_RXONLY; + SPI->CR2 = SPI_CR2_RXDMAEN; + DMA->CPAR = (uint32_t)&(SPI->DR); + DMA->CCR = DMA_CCR_MINC | DMA_CCR_TCIE | DMA_CCR_TEIE; // mem inc, hw->mem, Rx complete and error interrupt + spi_status[idx] = SPI_READY; + DBG("SPI works"); +} + +void spi_onoff(uint8_t idx, uint8_t on){ + CHKIDX(idx); + volatile SPI_TypeDef *SPI = SPIs[idx]; + if(on){ + DBGs(u2str(idx)); + DBG("turn on SPI"); + SPI->CR1 |= SPI_CR1_SPE; + spi_status[idx] = SPI_BUSY; + }else{ + SPI->CR1 &= ~SPI_CR1_SPE; + spi_status[idx] = SPI_READY; + } +} + +// turn off given SPI channel and release GPIO +void spi_deinit(uint8_t idx){ + CHKIDX(idx); + DBG("deinit SPI"); + volatile SPI_TypeDef *SPI = SPIs[idx]; + SPI->CR1 = 0; + SPI->CR2 = 0; + if(idx == 1){ + RCC->APB2ENR &= ~RCC_APB2ENR_SPI1EN; + GPIOA->CRL &= ~(GPIO_CRL_CNF5 | GPIO_CRL_CNF6); + }else if(idx == 2){ + RCC->APB1ENR &= ~RCC_APB1ENR_SPI2EN; + GPIOB->CRH &= ~(GPIO_CRH_CNF13 | GPIO_CRH_CNF14); + } + spi_status[idx] = SPI_NOTREADY; +} + +int spi_waitbsy(uint8_t idx){ + CHKIDXR(idx); + DBGs(u2str(idx)); + DBG("wait busy"); + WAITX(SPIs[idx]->SR & SPI_SR_BSY); + return 1; +} + +// just copy last read encoder value into `buf` +// @return TRUE if got fresh data +int spi_read_enc(uint8_t encno, uint8_t buf[8]){ + if(encno > 1 || !freshdata[encno]) return FALSE; + DBGs(u2str(encno)); DBG("Read encoder data"); + memcpy(buf, encoderbuf[encno], ENCODER_BUFSZ); + freshdata[encno] = 0; // clear fresh status + return TRUE; +} + +// start encoder reading over DMA +// @return FALSE if SPI is busy +// here `encodernum` is 0 (SPI1) or 1 (SPI2), not 1/2 as SPI index! +int spi_start_enc(int encodernum){ + int spiidx = encodernum + 1; + DBG("start enc"); + if(spiidx < 1 || spiidx > AMOUNT_OF_SPI) return FALSE; + if(spi_status[spiidx] != SPI_READY) return FALSE; + if(!spi_waitbsy(spiidx)) return FALSE; + if(SPI1->CR1 & SPI_CR1_SPE) DBG("spi1 works!"); + if(SPI2->CR1 & SPI_CR1_SPE) DBG("spi2 works!"); + volatile DMA_Channel_TypeDef *DMA = DMAs[spiidx]; + DMA->CMAR = (uint32_t) encoderbuf[encodernum]; + DMA->CNDTR = ENCODER_BUFSZ; + DBG("turn on spi"); + spi_onoff(spiidx, 1); + DMA->CCR |= DMA_CCR_EN; + return TRUE; +} + +// SSI got fresh data +void dma1_channel2_isr(){ + if(DMA1->ISR & DMA_ISR_TEIF2){ + DMA1->IFCR = DMA_IFCR_CTEIF2; + } + if(DMA1->ISR & DMA_ISR_TCIF2){ + //uint32_t ctr = TIM2->CNT; + DMA1->IFCR = DMA_IFCR_CTCIF2; + freshdata[0] = 1; + //encoderbuf[5] = (ctr >> 16) & 0xff; + //encoderbuf[6] = (ctr >> 8 ) & 0xff; + //encoderbuf[7] = (ctr >> 0 ) & 0xff; + } + spi_onoff(1, 0); + // turn off DMA + DMA1_Channel2->CCR &= ~DMA_CCR_EN; +} + +void dma1_channel4_isr(){ + if(DMA1->ISR & DMA_ISR_TEIF4){ + DMA1->IFCR = DMA_IFCR_CTEIF4; + } + if(DMA1->ISR & DMA_ISR_TCIF4){ + DMA1->IFCR = DMA_IFCR_CTCIF4; + freshdata[1] = 1; + } + spi_onoff(2, 0); + // turn off DMA + DMA1_Channel4->CCR &= ~DMA_CCR_EN; +} + diff --git a/F1:F103/BISS_C_encoders/spi.h b/F1:F103/BISS_C_encoders/spi.h new file mode 100644 index 0000000..333af93 --- /dev/null +++ b/F1:F103/BISS_C_encoders/spi.h @@ -0,0 +1,22 @@ + +#pragma once +#include + +#define AMOUNT_OF_SPI (2) + +#define ENCODER_BUFSZ (8) + +typedef enum{ + SPI_NOTREADY, + SPI_READY, + SPI_BUSY +} spiStatus; + +extern spiStatus spi_status[AMOUNT_OF_SPI+1]; + +void spi_onoff(uint8_t idx, uint8_t on); +void spi_deinit(uint8_t idx); +void spi_setup(uint8_t idx); +int spi_waitbsy(uint8_t idx); +int spi_start_enc(int encodernum); +int spi_read_enc(uint8_t encno, uint8_t buf[8]); diff --git a/F1:F103/BISS_C_encoders/strfunc.c b/F1:F103/BISS_C_encoders/strfunc.c index 594a2cb..20850e6 100644 --- a/F1:F103/BISS_C_encoders/strfunc.c +++ b/F1:F103/BISS_C_encoders/strfunc.c @@ -18,14 +18,15 @@ #include #include +#include "usb_dev.h" /** * @brief hexdump - dump hex array by 16 bytes in string - * @param sendfun - function to send data + * @param ifno - number of interface * @param arr - array to dump * @param len - length of `arr` */ -void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len){ +void hexdump(int ifno, 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){ @@ -36,14 +37,14 @@ void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len){ if(l % 16 == 15){ *bptr++ = '\n'; *bptr = 0; - sendfun(buf); + USB_sendstr(ifno, buf); bptr = buf; }else *bptr++ = ' '; } if(bptr != buf){ *bptr++ = '\n'; *bptr = 0; - sendfun(buf); + USB_sendstr(ifno, buf); } } diff --git a/F1:F103/BISS_C_encoders/strfunc.h b/F1:F103/BISS_C_encoders/strfunc.h index a31e0bd..44637a2 100644 --- a/F1:F103/BISS_C_encoders/strfunc.h +++ b/F1:F103/BISS_C_encoders/strfunc.h @@ -20,7 +20,7 @@ #include -void hexdump(int (*sendfun)(const char *s), uint8_t *arr, uint16_t len); +void hexdump(int ifno, uint8_t *arr, uint16_t len); char *u2str(uint32_t val); char *i2str(int32_t i); char *uhex2str(uint32_t val); diff --git a/F1:F103/BISS_C_encoders/usb_descr.c b/F1:F103/BISS_C_encoders/usb_descr.c index ad67b50..6237867 100644 --- a/F1:F103/BISS_C_encoders/usb_descr.c +++ b/F1:F103/BISS_C_encoders/usb_descr.c @@ -15,6 +15,9 @@ * along with this program. If not, see . */ +#include + +#include "flash.h" #include "usb_descr.h" #undef DBG @@ -166,18 +169,28 @@ _USB_LANG_ID_(LD, LANG_US); _USB_STRING_(SD, u"0.0.1"); _USB_STRING_(MD, u"eddy@sao.ru"); _USB_STRING_(PD, u"USB BISS-C encoders controller"); -_USB_STRING_(ID1, u"encoder_cmd"); -_USB_STRING_(ID2, u"encoder_X"); -_USB_STRING_(ID3, u"encoder_Y"); + +// iInterface will change on initialisation by config +#define _USB_IIDESCR_(str) {sizeof(str), 0x03, str} +typedef struct{ + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bString[MAX_IINTERFACE_SZ]; +}iidescr_t; +static iidescr_t iids[bTotNumEndpoints] = { + _USB_IIDESCR_(u"encoder_cmd"), + _USB_IIDESCR_(u"encoder_X"), + _USB_IIDESCR_(u"encoder_Y"), +}; static const void* const StringDescriptor[iDESCR_AMOUNT] = { [iLANGUAGE_DESCR] = &LD, [iMANUFACTURER_DESCR] = &MD, [iPRODUCT_DESCR] = &PD, [iSERIAL_DESCR] = &SD, - [iINTERFACE_DESCR1] = &ID1, - [iINTERFACE_DESCR2] = &ID2, - [iINTERFACE_DESCR3] = &ID3, + [iINTERFACE_DESCR1] = &iids[0], + [iINTERFACE_DESCR2] = &iids[1], + [iINTERFACE_DESCR3] = &iids[2], }; static void wr0(const uint8_t *buf, uint16_t size, uint16_t askedsize){ @@ -246,3 +259,14 @@ void get_descriptor(config_pack_t *pack){ break; } } + +// change values of iInterface by content of global config +void setup_interfaces(){ + for(int i = 0; i < bTotNumEndpoints; ++i){ + if(the_conf.iIlengths[i]){ + iids[i].bLength = the_conf.iIlengths[i]; + memcpy(iids[i].bString, the_conf.iInterface[i], the_conf.iIlengths[i]); + } + iids[i].bDescriptorType = 0x03; + } +} diff --git a/F1:F103/BISS_C_encoders/usb_descr.h b/F1:F103/BISS_C_encoders/usb_descr.h index f364a50..b2778f5 100644 --- a/F1:F103/BISS_C_encoders/usb_descr.h +++ b/F1:F103/BISS_C_encoders/usb_descr.h @@ -62,3 +62,4 @@ enum{ }; void get_descriptor(config_pack_t *pack); +void setup_interfaces(); diff --git a/F1:F103/BISS_C_encoders/usb_lib.c b/F1:F103/BISS_C_encoders/usb_lib.c index 7c6d6b5..2779402 100644 --- a/F1:F103/BISS_C_encoders/usb_lib.c +++ b/F1:F103/BISS_C_encoders/usb_lib.c @@ -401,6 +401,7 @@ void USB_setup(){ USB->BCDR |= USB_BCDR_DPPU; NVIC_EnableIRQ(USB_IRQn); #endif + setup_interfaces(); } diff --git a/F1:F103/BISS_C_encoders/version.inc b/F1:F103/BISS_C_encoders/version.inc index 6b6881f..35655c8 100644 --- a/F1:F103/BISS_C_encoders/version.inc +++ b/F1:F103/BISS_C_encoders/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "19" -#define BUILD_DATE "2025-03-25" +#define BUILD_NUMBER "63" +#define BUILD_DATE "2025-03-26"