From 6f29257a28daea933859b090f03e97d84edb545b Mon Sep 17 00:00:00 2001 From: Edward Emelianov Date: Fri, 17 Jun 2022 12:12:47 +0300 Subject: [PATCH] 3steppers: fix some bugs, add commands eraseflash and setpos --- F0:F030,F042,F072/3steppersLB/Makefile | 2 +- F0:F030,F042,F072/3steppersLB/Readme.md | 103 ++++++++++--------- F0:F030,F042,F072/3steppersLB/commonproto.c | 63 ++++++------ F0:F030,F042,F072/3steppersLB/flash.c | 6 +- F0:F030,F042,F072/3steppersLB/flash.h | 1 + F0:F030,F042,F072/3steppersLB/steppers.bin | Bin 25296 -> 23644 bytes F0:F030,F042,F072/3steppersLB/steppers.c | 108 +++++++++++++++----- F0:F030,F042,F072/3steppersLB/steppers.h | 16 +-- F0:F030,F042,F072/3steppersLB/strfunct.c | 17 ++- F0:F030,F042,F072/3steppersLB/version.inc | 4 +- 10 files changed, 195 insertions(+), 125 deletions(-) diff --git a/F0:F030,F042,F072/3steppersLB/Makefile b/F0:F030,F042,F072/3steppersLB/Makefile index 57ff91c..0427056 100644 --- a/F0:F030,F042,F072/3steppersLB/Makefile +++ b/F0:F030,F042,F072/3steppersLB/Makefile @@ -5,7 +5,7 @@ BOOTSPEED ?= 57600 FAMILY ?= F0 # MCU code MCU ?= F072xB -DEFS += -DEBUG +#DEFS += -DEBUG # change this linking script depending on particular MCU model, # for example, if you have STM32F103VBT6, you should write: LDSCRIPT ?= stm32f072B.ld diff --git a/F0:F030,F042,F072/3steppersLB/Readme.md b/F0:F030,F042,F072/3steppersLB/Readme.md index 7f457d9..022f240 100644 --- a/F0:F030,F042,F072/3steppersLB/Readme.md +++ b/F0:F030,F042,F072/3steppersLB/Readme.md @@ -142,10 +142,10 @@ Common commands format is cmd[ N[ = val]] where N is command argument (0..127), val is its value Different commands: adc - get ADC values - button - get buttons state + button - get buttons state (return time of last event & ERRCODE == buttonstate) buzzer - change buzzer state (1/0) - esw - get end switches state - ext - external outputs + esw - get end switches state (without number - all, by bytes) + ext - external outputs (without number - all, by bytes; value= 0-off, 1-on, other-toggle) mcut - get MCU T mcuvdd - get MCU Vdd ping - echo given command back @@ -175,6 +175,7 @@ Motors' commands: motreinit - re-init motors after configuration changed relpos - set relative steps, get remaining relslow - set relative steps @ lowest speed + setpos - set/get absolute position (in steps) state - get motor state stop - smooth motor stopping USB-only commands: @@ -185,6 +186,7 @@ USB-only commands: dumperr - dump error codes dumpcmd - dump command codes dumpconf - dump current configuration + eraseflash - erase flash data storage filter - add/modify filter, format: bank# FIFO# mode(M/I) num0 [num1 [num2 [num3]]] getctr - get TIM1/2/3 counters ignbuf - print ignore buffer @@ -208,7 +210,6 @@ bytes descr 7 Hdata dumperr -Find known command: dumperr Error codes: 0 - all OK 1 - wrong parameter's value @@ -219,82 +220,82 @@ Error codes: dumpcmd -Find known command: dumpcmd Commands list: -0 - Different commands: -1 - change relay state (1/0) -2 - change buzzer state (1/0) -3 - get ADC values -4 - get buttons state -5 - get end switches state -6 - get MCU T -7 - get MCU Vdd -8 - reset MCU -9 - get time from start -10 - pwm value -11 - external outputs -12 - save current configuration -13 - minimal encoder ticks per step -14 - maximal encoder ticks per step -15 - set/get microsteps settings -16 - set/get accel/decel (steps/s^2) -17 - set/get max speed (steps per sec) -18 - set/get min speed (steps per sec) -19 - get limiting speed for current microsteps -20 - set/get max steps (from zero) -21 - set/get max encoder's pulses per revolution -22 - set/get motorN flags -23 - end-switches reaction -24 - re-init motors after configuration changed -25 - set/get position (in steps) -26 - set relative steps, get remaining -27 - set relative steps @ lowest speed -28 - emergency stop motor (right now) -29 - smooth motor stopping -30 - emergency stop all motors -31 - find zero position & refresh counters -32 - get motor state -33 - set/get encoder's position +1 - echo given command back +2 - change relay state (1/0) +3 - change buzzer state (1/0) +4 - get ADC values +5 - get buttons state +6 - get end switches state +7 - get MCU T +8 - get MCU Vdd +9 - reset MCU +10 - get time from start +11 - pwm value +12 - external outputs +13 - save current configuration +14 - minimal encoder ticks per step +15 - maximal encoder ticks per step +16 - set/get microsteps settings +17 - set/get accel/decel (steps/s^2) +18 - set/get max speed (steps per sec) +19 - set/get min speed (steps per sec) +20 - get limiting speed for current microsteps +21 - set/get max steps (from zero) +22 - set/get max encoder's pulses per revolution +23 - set/get motorN flags +24 - end-switches reaction +25 - re-init motors after configuration changed +26 - move to/get absolute position (in steps) +27 - set relative steps, get remaining +28 - set relative steps @ lowest speed +29 - emergency stop motor (right now) +30 - smooth motor stopping +31 - emergency stop all motors +32 - find zero position & refresh counters +33 - get motor state +34 - set/get encoder's position +35 - set/get absolute position (in steps) + dumpconf -Find known command: dumpconf -flashsize=64*2048=131072 -userconf_addr=0x08006800 -userconf_idx=-1 // "index of stored conf" +userconf_addr=0x08006000// address from which userconf started +userconf_idx=5 // "index of stored conf" userconf_sz=68 // "magick number" canspeed=100 // default CAN speed canid=170 // identifier (0xaa) microsteps0=32 // microsteps amount per step -accel0=500 // acceleration/deceleration (steps/s^2) -maxspeed0=2000 // max motor speed (steps per second) +accel0=1500 // acceleration/deceleration (steps/s^2) +maxspeed0=1501 // max motor speed (steps per second) minspeed0=20 // min motor speed (steps per second) maxsteps0=500000 // maximal amount of steps encperrev0=4000 // encoders' counts per revolution encperstepmin0=17 // min amount of encoder ticks per one step encperstepmax0=23 // max amount of encoder ticks per one step -motflags0=0x3c // motor's flags +motflags0=0x2f // motor's flags eswreaction0=0 // end-switches reaction (esw_react) microsteps1=32 -accel1=500 +accel1=1500 maxspeed1=2000 minspeed1=20 maxsteps1=500000 encperrev1=4000 encperstepmin1=17 encperstepmax1=23 -motflags1=0x3c +motflags1=0x2f eswreaction1=0 microsteps2=32 -accel2=500 -maxspeed2=2000 +accel2=1500 +maxspeed2=2500 minspeed2=20 maxsteps2=500000 encperrev2=4000 encperstepmin2=17 encperstepmax2=23 -motflags2=0x3c +motflags2=0x2f eswreaction2=0 + Motor flags: bit0 - reversing motor rotation bit1 - reversing encoder rotation diff --git a/F0:F030,F042,F072/3steppersLB/commonproto.c b/F0:F030,F042,F072/3steppersLB/commonproto.c index da8b3ac..76cbb05 100644 --- a/F0:F030,F042,F072/3steppersLB/commonproto.c +++ b/F0:F030,F042,F072/3steppersLB/commonproto.c @@ -27,6 +27,11 @@ #include "strfunct.h" #endif +#define NOPARCHK() do{uint8_t n = PARBASE(par); if(n != CANMESG_NOPAR) return ERR_BADPAR;}while(0) + +#define CHECKN(val, par) do{val = PARBASE(par); \ + if(val > MOTORSNO-1) return ERR_BADPAR;}while(0) + /******* All functions from cmdlist[i].function *******/ static errcodes pingparser(uint8_t _U_ par, int32_t _U_ *val){ @@ -34,6 +39,7 @@ static errcodes pingparser(uint8_t _U_ par, int32_t _U_ *val){ } static errcodes relayparser(uint8_t par, int32_t *val){ + NOPARCHK(); if(ISSETTER(par)){ if(*val) ON(RELAY); else OFF(RELAY); @@ -43,6 +49,7 @@ static errcodes relayparser(uint8_t par, int32_t *val){ } static errcodes buzzerparser(uint8_t par, int32_t *val){ + NOPARCHK(); if(ISSETTER(par)){ if(*val) ON(BUZZER); else OFF(BUZZER); @@ -63,7 +70,7 @@ static errcodes adcparser(uint8_t par, int32_t *val){ static errcodes buttonsparser(uint8_t par, int32_t *val){ uint8_t n = PARBASE(par); if(n > BTNSNO-1){ - par = CANMESG_NOPAR; // the only chance to understand error + *val = CANMESG_NOPAR; // the only chance to understand error return ERR_BADPAR; } return (uint8_t) keystate(n, (uint32_t*)val); @@ -75,39 +82,43 @@ static errcodes eswparser(uint8_t par, int32_t *val){ #error "change the code!!!" #endif uint8_t n = PARBASE(par); - if(n > ESWNO-1){ // all + if(n == CANMESG_NOPAR){ // all *val = 0; uint8_t *arr = (uint8_t*)val; for(int i = 0; i < ESWNO; ++i) *arr++ = ESW_state(i); return ERR_OK; - } + }else if(n > ESWNO - 1) return ERR_BADPAR; *val = (int32_t)ESW_state(n); return ERR_OK; } static errcodes mcutparser(uint8_t _U_ par, int32_t *val){ + NOPARCHK(); *val = getMCUtemp(); return ERR_OK; } static errcodes mcuvddparser(uint8_t _U_ par, int32_t *val){ + NOPARCHK(); *val = getVdd(); return ERR_OK; } static errcodes resetparser(uint8_t _U_ par, int32_t _U_ *val){ + NOPARCHK(); NVIC_SystemReset(); return ERR_OK; } static errcodes timeparser(uint8_t _U_ par, int32_t *val){ + NOPARCHK(); *val = Tms; return ERR_OK; } static errcodes pwmparser(uint8_t par, int32_t *val){ - if(PARBASE(par) > PWMCHMAX && par != CANMESG_NOPAR) return ERR_BADPAR; + NOPARCHK(); #if PWMCHMAX != 0 #error "change the code!!!" #endif @@ -144,7 +155,7 @@ static errcodes extparser(uint8_t par, int32_t *val){ SEND("par="); printu(par); SEND(", n="); bufputchar('0'+n); newline(); #endif - if(n > EXTNO-1){ // all + if(n == CANMESG_NOPAR){ // all #ifdef EBUG SEND("ALL\n"); #endif @@ -157,7 +168,7 @@ static errcodes extparser(uint8_t par, int32_t *val){ arr[i] = EXT_CHK(i); } return ERR_OK; - } + }else if(n > EXTNO-1) return ERR_BADPAR; if(ISSETTER(par)) setextpar((uint8_t)*val, n); *val = (int32_t) EXT_CHK(n); @@ -166,8 +177,7 @@ static errcodes extparser(uint8_t par, int32_t *val){ /******************* START of config parsers *******************/ static errcodes ustepsparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ #if MICROSTEPSMAX > 512 #error "Change the code anywhere!" @@ -184,8 +194,7 @@ static errcodes ustepsparser(uint8_t par, int32_t *val){ } static errcodes encstepsminparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ if(*val < 1 || *val > MAXENCTICKSPERSTEP - 1) return ERR_BADVAL; the_conf.encperstepmin[n] = *val; @@ -195,8 +204,7 @@ static errcodes encstepsminparser(uint8_t par, int32_t *val){ } static errcodes encstepsmaxparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ if(*val < 1 || *val > MAXENCTICKSPERSTEP) return ERR_BADVAL; the_conf.encperstepmax[n] = *val; @@ -206,8 +214,7 @@ static errcodes encstepsmaxparser(uint8_t par, int32_t *val){ } static errcodes accparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ if(*val/the_conf.microsteps[n] > ACCELMAXSTEPS || *val < 1) return ERR_BADVAL; the_conf.accel[n] = *val; @@ -227,8 +234,7 @@ static uint16_t getSPD(uint8_t n, int32_t speed){ } static errcodes maxspdparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ if(*val <= the_conf.minspd[n]) return ERR_BADVAL; the_conf.maxspd[n] = getSPD(n, *val); @@ -238,8 +244,7 @@ static errcodes maxspdparser(uint8_t par, int32_t *val){ } static errcodes minspdparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ if(*val >= the_conf.maxspd[n]) return ERR_BADVAL; the_conf.minspd[n] = getSPD(n, *val); @@ -249,15 +254,13 @@ static errcodes minspdparser(uint8_t par, int32_t *val){ } static errcodes spdlimparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); *val = getSPD(n, 0xffff); return ERR_OK; } static errcodes maxstepsparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ if(*val < 1) return ERR_BADVAL; the_conf.maxsteps[n] = *val; @@ -267,8 +270,7 @@ static errcodes maxstepsparser(uint8_t par, int32_t *val){ } static errcodes encrevparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ if(*val < 1 || *val > MAXENCREV) return ERR_BADVAL; the_conf.encrev[n] = *val; @@ -279,8 +281,7 @@ static errcodes encrevparser(uint8_t par, int32_t *val){ } static errcodes motflagsparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ the_conf.motflags[n] = *((motflags_t*)val); } @@ -290,8 +291,7 @@ static errcodes motflagsparser(uint8_t par, int32_t *val){ // setter of GLOBAL reaction, getter of LOCAL! static errcodes eswreactparser(uint8_t par, int32_t *val){ - uint8_t n = PARBASE(par); - if(n > MOTORSNO-1) return ERR_BADPAR; + uint8_t n; CHECKN(n, par); if(ISSETTER(par)){ if(*val < 0 || *val > ESW_AMOUNT-1) return ERR_BADVAL; the_conf.ESW_reaction[n] = *val; @@ -302,6 +302,7 @@ static errcodes eswreactparser(uint8_t par, int32_t *val){ } static errcodes saveconfparser(uint8_t _U_ par, int32_t _U_ *val){ + NOPARCHK(); if(store_userconf()) return ERR_CANTRUN; return ERR_OK; } @@ -310,13 +311,11 @@ static errcodes saveconfparser(uint8_t _U_ par, int32_t _U_ *val){ /******************* START of motors' parsers *******************/ static errcodes reinitmparser(uint8_t _U_ par, int32_t _U_ *val){ + NOPARCHK(); init_steppers(); return ERR_OK; } -#define CHECKN(val, par) do{val = PARBASE(par); \ - if(val > MOTORSNO-1) return ERR_BADPAR;}while(0) - static errcodes emstopparser(uint8_t par, int32_t _U_ *val){ uint8_t n; CHECKN(n, par); emstopmotor(n); @@ -324,6 +323,7 @@ static errcodes emstopparser(uint8_t par, int32_t _U_ *val){ } static errcodes emstopallparser(uint8_t _U_ par, int32_t _U_ *val){ + NOPARCHK(); for(int i = 0; i < MOTORSNO; ++i) emstopmotor(i); return ERR_OK; @@ -384,7 +384,6 @@ static errcodes gotozeroparser(uint8_t par, _U_ int32_t *val){ return motor_goto0(n); } -#undef CHECKN /******************* END of motors' parsers *******************/ /* diff --git a/F0:F030,F042,F072/3steppersLB/flash.c b/F0:F030,F042,F072/3steppersLB/flash.c index 84de13b..9f958cf 100644 --- a/F0:F030,F042,F072/3steppersLB/flash.c +++ b/F0:F030,F042,F072/3steppersLB/flash.c @@ -112,7 +112,7 @@ int store_userconf(){ // for binarySearch() checking that there's nothing more after it! if(currentconfidx > (int)maxCnum - 3){ // there's no more place currentconfidx = 0; - if(erase_flash(Flash_Data, (&__varsstart))) return 1; + if(erase_flash(Flash_Data, NULL)) 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)); } @@ -241,3 +241,7 @@ void dump_userconf(_U_ char *txt){ } NL(); } + +int erase_storage(){ + return erase_flash(Flash_Data, NULL); +} diff --git a/F0:F030,F042,F072/3steppersLB/flash.h b/F0:F030,F042,F072/3steppersLB/flash.h index 5794a20..07dfd1f 100644 --- a/F0:F030,F042,F072/3steppersLB/flash.h +++ b/F0:F030,F042,F072/3steppersLB/flash.h @@ -79,5 +79,6 @@ extern user_conf the_conf; // global user config (read from FLASH to RAM) void flashstorage_init(); int store_userconf(); void dump_userconf(_U_ char *txt); +int erase_storage(); #endif // __FLASH_H__ diff --git a/F0:F030,F042,F072/3steppersLB/steppers.bin b/F0:F030,F042,F072/3steppersLB/steppers.bin index e50adaf4660a7e70fc004397ff2ac3ffb5670c01..9eb447ed533f1dc4921179faef343922a229bacd 100755 GIT binary patch delta 10809 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 limit } t_stalled; +#ifdef EBUG +static uint8_t stp[MOTORSNO] = {0}; +#endif + static t_stalled stallflags[MOTORSNO]; // motors' direction: 1 for positive, -1 for negative (we need it as could be reverse) @@ -162,6 +166,15 @@ errcodes getremainsteps(uint8_t i, int32_t *position){ // calculate acceleration/deceleration parameters for motor i static void calcacceleration(uint8_t i){ + switch(state[i]){ // do nothing in case of error/stopping + case STP_ERR: + case STP_RELAX: + case STP_STALL: + return; + break; + default: + break; + } int32_t delta = targstppos[i] - stppos[i]; if(delta > 0){ // positive direction if(delta > 2*(int32_t)accdecsteps[i]){ // can move by trapezoid @@ -181,7 +194,10 @@ static void calcacceleration(uint8_t i){ if(the_conf.motflags[i].reverse) MOTOR_CW(i); else MOTOR_CCW(i); } - if(state[i] != STP_MVSLOW) state[i] = STP_ACCEL; + if(state[i] != STP_MVSLOW){ + DBG("->accel"); + state[i] = STP_ACCEL; + } startspeed[i] = curspeed[i]; Taccel[i] = Tms; recalcARR(i); @@ -215,20 +231,21 @@ errcodes motor_absmove(uint8_t i, int32_t newpos){ case STP_RELAX: break; case STP_STALL: + DBG("Move from STALL"); if(dir == stalleddir[i]){ DBG("Move to stalled direction!"); return ERR_CANTRUN; // can't run into stalled direction } break; default: // moving state - DBG("Always moving"); + DBG("Is moving"); return ERR_CANTRUN; } if(newpos > (int32_t)the_conf.maxsteps[i] || newpos < -(int32_t)the_conf.maxsteps[i] || newpos == stppos[i]){ DBG("Too much steps"); return ERR_BADVAL; // too big position or zero } - motdir[i] = dir; + motdir[i] = dir; // should be before limit switch check if(esw_block(i)){ DBG("Block by ESW"); return ERR_CANTRUN; // on end-switch @@ -239,6 +256,7 @@ errcodes motor_absmove(uint8_t i, int32_t newpos){ prevencpos[i] = encoder_position(i); prevstppos[i] = stppos[i]; curspeed[i] = the_conf.minspd[i]; + state[i] = STP_ACCEL; calcacceleration(i); #ifdef EBUG SEND("MOTOR"); bufputchar('0'+i); @@ -259,6 +277,7 @@ errcodes motor_relmove(uint8_t i, int32_t relsteps){ errcodes motor_relslow(uint8_t i, int32_t relsteps){ errcodes e = motor_absmove(i, stppos[i] + relsteps); if(ERR_OK == e){ + DBG("-> MVSLOW"); state[i] = STP_MVSLOW; } return e; @@ -269,6 +288,7 @@ void emstopmotor(uint8_t i){ switch(state[i]){ case STP_ERR: // clear error state case STP_STALL: + DBG("-> RELAX"); state[i] = STP_RELAX; // fallthrough case STP_RELAX: // do nothing in stopping state @@ -301,18 +321,19 @@ void addmicrostep(uint8_t i){ } } if(stopflag[i] || stop_at_pos){ // stop NOW + mottimers[i]->CR1 &= ~TIM_CR1_CEN; // stop timer if(stopflag[i]) targstppos[i] = stppos[i]; // keep position (for keep flag) stopflag[i] = 0; - mottimers[i]->CR1 &= ~TIM_CR1_CEN; // stop timer if(the_conf.motflags[i].donthold) MOTOR_DIS(i); // turn off power if(stallflags[i] == STALL_STOP){ stallflags[i] = STALL_NO; state[i] = STP_STALL; - }else + }else{ state[i] = STP_RELAX; + } #ifdef EBUG - SEND("MOTOR"); bufputchar('0'+i); SEND(" stop @"); printi(stppos[i]); newline(); + stp[i] = 1; #endif } } @@ -361,15 +382,14 @@ static t_stalled chkSTALL(uint8_t i){ SEND("MOTOR"); bufputchar('0'+i); SEND(" Denc="); printi(Denc); SEND(", Dstp="); printu(Dstp); SEND(", speed="); printu(curspeed[i]); #endif - if(++Nstalled[i] > NSTALLEDMAX){ - stopflag[i] = 1; + if(++Nstalled[i] >= NSTALLEDMAX){ Nstalled[i] = 0; #ifdef EBUG SEND(" --- STALL!"); #else SEND("ERRCODE="); bufputchar(ERR_CANTRUN+'0'); SEND("\nstate"); bufputchar(i+'0'); bufputchar('='); - bufputchar(STP_STALL); + bufputchar(STP_STALL + '0'); #endif NL(); stallflags[i] = STALL_STOP; @@ -408,7 +428,19 @@ static t_stalled chkSTALL(uint8_t i){ // check state of i`th stepper static void chkstepper(int i){ int32_t i32; + t_stalled s = chkSTALL(i); + static int32_t oldtargpos[3] = {0}; // target position of previous `keep` call + static uint8_t keepposctr[3] = {0}; // counter of tries to keep current position static uint8_t stopctr[3] = {0}; // counters for encoders/position zeroing after stopping @ esw +#ifdef EBUG + if(stp[i]){ + stp[i] = 0; + // motor state could be changed outside of interrupt, so return it to relax or leave in STALL + if(state[i] != STP_STALL) state[i] = STP_RELAX; + SEND("MOTOR"); bufputchar('0'+i); SEND(" stop @"); printi(stppos[i]); + SEND(", curstate="); printu(state[i]); newline(); + } +#endif switch(state[i]){ case STP_RELAX: // check if need to keep current position if(the_conf.motflags[i].haveencoder){ @@ -425,18 +457,30 @@ static void chkstepper(int i){ if(the_conf.motflags[i].keeppos){ // keep old position diff = targstppos[i] - i32; // check whether we need to change position if(diff){ // try to correct position + if(oldtargpos[i] == targstppos[i]){ + if(++keepposctr[i] >= KEEPPOSMAX){ + DBG("Can't keep position"); + targstppos[i] = i32; + return; // can't keep position for + } + }else{ + oldtargpos[i] = targstppos[i]; + keepposctr[i] = 0; + } #ifdef EBUG SEND("MOTOR"); bufputchar('0'+i); SEND(" curpos="); printi(i32); SEND(", need="); printi(targstppos[i]); NL(); #endif - if(ERR_OK != motor_absmove(i, targstppos[i])) + if(ERR_OK != motor_absmove(i, targstppos[i])){ + DBG("Can't move to target position for keeping"); targstppos[i] = i32; // can't move to target position - forget it + } } } } break; case STP_ACCEL: // acceleration to max speed - if(STALL_NO == chkSTALL(i)){ + if(s == STALL_NO){ //newspeed = curspeed[i] + dV[i]; i32 = the_conf.minspd[i] + (the_conf.accel[i] * (Tms - Taccel[i])) / 1000; if(i32 >= the_conf.maxspd[i]){ // max speed reached -> move with it @@ -450,20 +494,22 @@ static void chkstepper(int i){ curspeed[i] = i32; } recalcARR(i); - } - // check position for triangle profile - if(motdir[i] > 0){ - if(stppos[i] >= decelstartpos[i]){ // reached end of acceleration - TODECEL(); - } - }else{ - if(stppos[i] <= decelstartpos[i]){ - TODECEL(); + // check position for triangle profile + if(motdir[i] > 0){ + if(stppos[i] >= decelstartpos[i]){ // reached end of acceleration + TODECEL(); + } + }else{ + if(stppos[i] <= decelstartpos[i]){ + TODECEL(); + } } + }else if(s == STALL_STOP){ + stopflag[i] = 1; } break; case STP_MOVE: // move @ constant speed until need to decelerate - if(STALL_NO == chkSTALL(i)){ + if(s == STALL_NO){ // check position if(motdir[i] > 0){ if(stppos[i] >= decelstartpos[i]){ // reached start of deceleration @@ -474,10 +520,12 @@ static void chkstepper(int i){ TODECEL(); } } + }else if(s == STALL_STOP){ + stopflag[i] = 1; } break; case STP_DECEL: - if(STALL_NO == chkSTALL(i)){ + if(s == STALL_NO){ //newspeed = curspeed[i] - dV[i]; i32 = startspeed[i] - (the_conf.accel[i] * (Tms - Taccel[i])) / 1000; if(i32 > the_conf.minspd[i]){ @@ -492,10 +540,12 @@ static void chkstepper(int i){ } recalcARR(i); //SEND("spd="); printu(curspeed[i]); SEND(", pos="); printi(stppos[i]); newline(); + }else if(s == STALL_STOP){ + stopflag[i] = 1; } break; case STP_MVSLOW: - chkSTALL(i); + if(s == STALL_STOP) stopflag[i] = 1; break; default: // STALL, ERR -> do nothing, check mvzerostate break; @@ -504,12 +554,13 @@ static void chkstepper(int i){ case M0FAST: if(state[i] == STP_RELAX || state[i] == STP_STALL){ // stopped -> move to + #ifdef EBUG - SEND("M0FAST: motor stopped\n"); + bufputchar('M'); bufputchar('0'+i); SEND("FAST: motor stopped\n"); #endif if(ERR_OK != motor_relslow(i, 1000)){ #ifdef EBUG SEND("Can't move\n"); #endif + DBG("->ERR"); state[i] = STP_ERR; mvzerostate[i] = M0RELAX; ESW_reaction[i] = the_conf.ESW_reaction[i]; @@ -525,11 +576,11 @@ static void chkstepper(int i){ } if((state[i] == STP_RELAX || state[i] == STP_STALL) && ++stopctr[i] > 5){ // wait at least 50ms #ifdef EBUG - SEND("M0SLOW: set position to 0\n"); NL(); + bufputchar('M'); bufputchar('0'+i); SEND("SLOW: motor stopped\n"); #endif ESW_reaction[i] = the_conf.ESW_reaction[i]; prevencpos[i] = encpos[i] = 0; - targstppos[i] = stppos[i] = 0; + prevstppos[i] = targstppos[i] = stppos[i] = 0; enctimers[i]->CNT = 0; // set encoder counter to zero mvzerostate[i] = M0RELAX; } @@ -541,8 +592,9 @@ static void chkstepper(int i){ errcodes motor_goto0(uint8_t i){ errcodes e = motor_absmove(i, -the_conf.maxsteps[i]); - if(ERR_OK != e) return e; - ESW_reaction[i] = ESW_STOPMINUS; + if(ERR_OK != e){ + if(!ESW_state(i)) return e; // not @ limit switch -> error + }else ESW_reaction[i] = ESW_STOPMINUS; mvzerostate[i] = M0FAST; return e; } diff --git a/F0:F030,F042,F072/3steppersLB/steppers.h b/F0:F030,F042,F072/3steppersLB/steppers.h index c6391ab..00c5e22 100644 --- a/F0:F030,F042,F072/3steppersLB/steppers.h +++ b/F0:F030,F042,F072/3steppersLB/steppers.h @@ -27,16 +27,18 @@ #define NSTALLEDMAX (5) // amount of steps to detect stalled state #define STALLEDSTEPS (15) +// amount of tries to keep current position (need for states near problem places) +#define KEEPPOSMAX (10) // stepper states typedef enum{ - STP_RELAX, // no moving - STP_ACCEL, // start moving with acceleration - STP_MOVE, // moving with constant speed - STP_MVSLOW, // moving with slowest constant speed (end of moving) - STP_DECEL, // moving with deceleration - STP_STALL, // stalled - STP_ERR // wrong/error state + STP_RELAX, // 0 - no moving + STP_ACCEL, // 1 - start moving with acceleration + STP_MOVE, // 2 - moving with constant speed + STP_MVSLOW, // 3 - moving with slowest constant speed (end of moving) + STP_DECEL, // 4 - moving with deceleration + STP_STALL, // 5 - stalled + STP_ERR // 6 - wrong/error state } stp_state; // end-switches reaction diff --git a/F0:F030,F042,F072/3steppersLB/strfunct.c b/F0:F030,F042,F072/3steppersLB/strfunct.c index 11b7d4d..fee64b4 100644 --- a/F0:F030,F042,F072/3steppersLB/strfunct.c +++ b/F0:F030,F042,F072/3steppersLB/strfunct.c @@ -423,6 +423,14 @@ void dumperrcodes(_U_ char *txt){ } } +static void eraseflash(_U_ char *txt){ + SEND("Start at "); printu(Tms); + int e = erase_storage(); + SEND("\nStop at "); printu(Tms); newline(); + if(e) SEND("Error\n"); + else SEND("OK\n"); +} + typedef void(*specfpointer)(char *arg); enum{ @@ -443,7 +451,7 @@ enum{ SCMD_WD, SCMD_DUMPERR, SCMD_DUMPCMD, - //SCMD_ST, + SCMD_ERASEFLASH, SCMD_AMOUNT }; @@ -466,7 +474,7 @@ static specfpointer speccmdlist[SCMD_AMOUNT] = { [SCMD_WD] = wdcheck, [SCMD_DUMPCMD] = dumpcmdcodes, [SCMD_DUMPERR] = dumperrcodes, - //[SCMD_ST] = stp_check, + [SCMD_ERASEFLASH] = eraseflash, }; typedef struct{ @@ -526,6 +534,7 @@ static const commands textcommands[] = { {-SCMD_DUMPERR, "dumperr", "dump error codes"}, {-SCMD_DUMPCMD, "dumpcmd", "dump command codes"}, {-SCMD_DUMPCONF, "dumpconf", "dump current configuration"}, + {-SCMD_ERASEFLASH, "eraseflash", "erase flash data storage"}, {-SCMD_FILTER, "filter", "add/modify filter, format: bank# FIFO# mode(M/I) num0 [num1 [num2 [num3]]]"}, {-SCMD_GETCTR, "getctr", "get TIM1/2/3 counters"}, {-SCMD_IGNBUF, "ignbuf", "print ignore buffer"}, @@ -542,7 +551,7 @@ static const commands textcommands[] = { // dump base commands codes (for CAN protocol) void dumpcmdcodes(_U_ char *txt){ SEND("CANbus commands list:\n"); - for(uint16_t i = 0; i < CMD_AMOUNT; ++i){ + for(uint16_t i = 1; i < CMD_AMOUNT; ++i){ printu(i); SEND(" - "); const commands *c = textcommands; @@ -656,7 +665,9 @@ void cmd_parser(char *txt){ par &= 0x7f; if(par != CANMESG_NOPAR) printu(par); bufputchar('='); printi(val); +#ifdef EBUG SEND(" ("); printuhex((uint32_t)val); bufputchar(')'); +#endif if(ERR_OK != retcode){ SEND("\nERRCODE="); printu(retcode); diff --git a/F0:F030,F042,F072/3steppersLB/version.inc b/F0:F030,F042,F072/3steppersLB/version.inc index 5e39d96..a2f289a 100644 --- a/F0:F030,F042,F072/3steppersLB/version.inc +++ b/F0:F030,F042,F072/3steppersLB/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "156" -#define BUILD_DATE "2022-06-10" +#define BUILD_NUMBER "168" +#define BUILD_DATE "2022-06-17"