/* * This file is part of the 3steppers project. * Copyright 2021 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 "flash.h" #include "hardware.h" #include "steppers.h" #include "strfunct.h" // goto zero stages typedef enum{ M0RELAX, // normal moving M0FAST, // fast move to zero M0SLOW // slowest move from ESW } mvto0state; typedef enum{ STALL_NO, // moving OK STALL_ONCE, // Nstalled < limit STALL_STOP // Nstalled >= 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) static int8_t motdir[MOTORSNO]; // direction of moving when stalled (forbid moving in that direction before go out of position) static int8_t stalleddir[MOTORSNO] = {0}; // current position (in steps) by STP counter static volatile int32_t stppos[MOTORSNO] = {0}; // previous position when check (set to current in start of moving) static int32_t prevstppos[MOTORSNO]; // target stepper position static int32_t targstppos[MOTORSNO] = {0}; // position to start deceleration static int32_t decelstartpos[MOTORSNO]; // current encoder position (4 per ticks) (without TIM->CNT) static volatile int32_t encpos[MOTORSNO] = {0}; // previous encoder position static int32_t prevencpos[MOTORSNO] = {0}; // encoders' ticks per step (calculates @ init) static int32_t encperstep[MOTORSNO]; // ESW reaction - local copy static uint8_t ESW_reaction[MOTORSNO]; // current speed static uint16_t curspeed[MOTORSNO]; static uint16_t startspeed[MOTORSNO]; // speed when deceleration starts // ==1 to stop @ nearest step static uint8_t stopflag[MOTORSNO]; // motor state static stp_state state[MOTORSNO]; // move to zero state static mvto0state mvzerostate[MOTORSNO]; static int8_t Nstalled[MOTORSNO] = {0}; // counter of STALL // lowest ARR value (highest speed), highest (lowest speed) //static uint16_t stphighARR[MOTORSNO]; // microsteps=1<> ustepsshift[i]) - 1; if(ARR < MOTORTIM_ARRMIN) ARR = MOTORTIM_ARRMIN; else if(ARR > 0xffff) ARR = 0xffff; mottimers[i]->ARR = ARR; curspeed[i] = (((PCLK/(MOTORTIM_PSC+1)) / (ARR+1)) >> ustepsshift[i]); // recalculate speed due to new val } // update stepper's settings void update_stepper(uint8_t i){ if(i >= MOTORSNO) return; accdecsteps[i] = (the_conf.maxspd[i] * the_conf.maxspd[i]) / the_conf.accel[i] / 2; ustepsshift[i] = MSB(the_conf.microsteps[i]); encperstep[i] = the_conf.encrev[i] / STEPSPERREV; enctimers[i]->ARR = the_conf.encrev[i]; ESW_reaction[i] = the_conf.ESW_reaction[i]; } // run this function after each steppers parameters changing void init_steppers(){ timers_setup(); // reinit timers & stop them // init variables for(int i = 0; i < MOTORSNO; ++i){ stalleddir[i] = 0; // clear old stall direction stopflag[i] = 0; motdir[i] = 0; curspeed[i] = 0; state[i] = STP_RELAX; if(!the_conf.motflags[i].donthold) MOTOR_EN(i); else MOTOR_DIS(i); update_stepper(i); } } // get absolute position by encoder int32_t encoder_position(uint8_t i){ int32_t pos = encpos[i]; if(the_conf.motflags[i].encreverse) pos -= enctimers[i]->CNT; else pos += enctimers[i]->CNT; return pos; } // set encoder's position, return 0 if false (motor's state != relax) int setencpos(uint8_t i, int32_t position){ if(state[i] != STP_RELAX) return FALSE; int32_t remain = position % the_conf.encrev[i]; if(remain < 0) remain += the_conf.encrev[i]; enctimers[i]->CNT = remain; prevencpos[i] = encpos[i] = position - remain; int32_t curstppos; getpos(i, &curstppos); stppos[i] = targstppos[i] = curstppos; #ifdef EBUG SEND("MOTOR"); bufputchar('0'+i); SEND(", position="); printi(position); SEND(", remain="); printi(remain); SEND(", CNT="); printu(enctimers[i]->CNT); SEND(", pos="); printi(encpos[i]); NL(); #endif return TRUE; } // set absolute position of motor `i` errcodes setmotpos(uint8_t i, int32_t position){ if(state[i] != STP_RELAX) return ERR_CANTRUN; if(position > (int32_t)the_conf.maxsteps[i] || position < -(int32_t)the_conf.maxsteps[i]) return ERR_BADVAL; // too big position or zero if(position == stppos[i]) return ERR_OK; setencpos(i, position * encperstep[i]); return ERR_OK; } // get current position errcodes getpos(uint8_t i, int32_t *position){ if(the_conf.motflags[i].haveencoder){ int32_t p = encoder_position(i); if(p < 0) p -= encperstep[i]>>1; else p += encperstep[i]>>1; p /= encperstep[i]; *position = p; }else *position = stppos[i]; return ERR_OK; } errcodes getremainsteps(uint8_t i, int32_t *position){ *position = targstppos[i] - stppos[i]; return ERR_OK; } // 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 decelstartpos[i] = targstppos[i] - accdecsteps[i]; }else{ // triangle speed profile decelstartpos[i] = stppos[i] + delta/2; } if(the_conf.motflags[i].reverse) MOTOR_CCW(i); else MOTOR_CW(i); }else{ // negative direction delta = -delta; if(delta > 2*(int32_t)accdecsteps[i]){ // can move by trapezoid decelstartpos[i] = targstppos[i] + accdecsteps[i]; }else{ // triangle speed profile decelstartpos[i] = stppos[i] - delta/2; } if(the_conf.motflags[i].reverse) MOTOR_CW(i); else MOTOR_CCW(i); } if(state[i] != STP_MVSLOW){ DBG("->accel"); state[i] = STP_ACCEL; } startspeed[i] = curspeed[i]; Taccel[i] = Tms; recalcARR(i); } // check if end-switch is blocking the moving of i'th motor // @return TRUE if motor can't move static int esw_block(uint8_t i){ int ret = FALSE; if(ESW_state(i)){ // ESW active switch(ESW_reaction[i]){ case ESW_ANYSTOP: // stop motor in any direction ret = TRUE; break; case ESW_STOPMINUS: // stop only @ minus if(motdir[i] == -1) ret = TRUE; break; default: // ESW_IGNORE break; } } return ret; } // move to absolute position errcodes motor_absmove(uint8_t i, int32_t newpos){ //if(i >= MOTORSNO) return ERR_BADPAR; // bad motor number int8_t dir = (newpos > stppos[i]) ? 1 : -1; switch(state[i]){ case STP_ERR: 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("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; // should be before limit switch check if(esw_block(i)){ DBG("Block by ESW"); return ERR_CANTRUN; // on end-switch } Nstalled[i] = (state[i] == STP_STALL) ? -(NSTALLEDMAX*4) : 0; // give some more chances to go out of stall state stopflag[i] = 0; targstppos[i] = 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); SEND(" targstppos="); printi(targstppos[i]); SEND(", decelstart="); printi(decelstartpos[i]); SEND(", accdecsteps="); printu(accdecsteps[i]); NL(); #endif MOTOR_EN(i); mottimers[i]->CR1 |= TIM_CR1_CEN; // start timer return ERR_OK; } // move i'th motor for relsteps errcodes motor_relmove(uint8_t i, int32_t relsteps){ return motor_absmove(i, stppos[i] + 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; } // emergency stop and clear errors 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 return; default: break; } stopflag[i] = 1; } stp_state getmotstate(uint8_t i){ return state[i]; } // count steps @tim 14/15/16 void addmicrostep(uint8_t i){ static volatile uint16_t microsteps[MOTORSNO] = {0}; // current microsteps position if(esw_block(i)) stopflag[i] = 1; // turn on stop flag if end-switch was active if(++microsteps[i] == the_conf.microsteps[i]){ microsteps[i] = 0; stppos[i] += motdir[i]; uint8_t stop_at_pos = 0; if(motdir[i] > 0){ if(stppos[i] >= targstppos[i]){ // reached stop position stop_at_pos = 1; } }else{ if(stppos[i] <= targstppos[i]){ stop_at_pos = 1; } } 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; 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{ state[i] = STP_RELAX; } #ifdef EBUG stp[i] = 1; #endif } } } void encoders_UPD(uint8_t i){ if(enctimers[i]->SR & TIM_SR_UIF){ int8_t d = 1; // positive (-1 - negative) if(enctimers[i]->CR1 & TIM_CR1_DIR) d = -d; // negative if(the_conf.motflags[i].encreverse) d = -d; if(d == 1) encpos[i] += the_conf.encrev[i]; else encpos[i] -= the_conf.encrev[i]; } enctimers[i]->SR = 0; } // check if motor is stalled // @return 0 if moving OK, static t_stalled chkSTALL(uint8_t i){ if(!the_conf.motflags[i].haveencoder) return STALL_NO; int32_t curencpos = encoder_position(i), Denc = curencpos - prevencpos[i]; int32_t curstppos = stppos[i], Dstp = curstppos - prevstppos[i]; int difsign = 1; if(Dstp < 0){ Dstp = -Dstp; difsign = -difsign; } if(Dstp < STALLEDSTEPS){ // didn't move even @ `STALLEDSTEPS` steps stallflags[i] = STALL_NO; return STALL_NO; } if(Denc < 0){ Denc = -Denc; difsign = -difsign; } if(difsign == -1){ // motor and encĪder moves to different sides!!! Denc = -Denc; // init STALL state } prevencpos[i] = curencpos; // TODO: check if it should be here getpos(i, &curstppos); // recalculate current position stppos[i] = curstppos; prevstppos[i] = curstppos; if(Denc < the_conf.encperstepmin[i]*Dstp || the_conf.encperstepmax[i]*Dstp < Denc){ // stall? #ifdef EBUG SEND("MOTOR"); bufputchar('0'+i); SEND(" Denc="); printi(Denc); SEND(", Dstp="); printu(Dstp); SEND(", speed="); printu(curspeed[i]); #endif 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 + '0'); #endif NL(); stallflags[i] = STALL_STOP; stalleddir[i] = motdir[i]; return STALL_STOP; }else{ uint16_t spd = curspeed[i] >> 1; // speed / 2 curspeed[i] = (spd > the_conf.minspd[i]) ? spd : the_conf.minspd[i]; // now recalculate acc/dec parameters calcacceleration(i); #ifdef EBUG SEND(" --- pre-stall, newspeed="); printu(curspeed[i]); NL(); #endif stallflags[i] = STALL_ONCE; return STALL_ONCE; } } Nstalled[i] = 0; return STALL_NO; } #ifdef EBUG #define TODECEL() do{state[i] = STP_DECEL; \ startspeed[i] = curspeed[i]; \ Taccel[i] = Tms; \ SEND("MOTOR"); bufputchar('0'+i); \ SEND(" -> DECEL@"); printi(stppos[i]); SEND(", V="); printu(curspeed[i]); NL(); \ }while(0) #else #define TODECEL() do{state[i] = STP_DECEL; \ startspeed[i] = curspeed[i]; \ Taccel[i] = Tms; \ }while(0) #endif // 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){ getpos(i, &i32); int32_t diff = stppos[i] - i32; // correct `curpos` counter by encoder if(diff){ // correct current stppos by encoder #ifdef EBUG SEND("MOTOR"); bufputchar('0'+i); SEND(" diff="); printi(diff); SEND(", change stppos from "); printi(stppos[i]); SEND(" to "); printi(i32); NL(); #endif stppos[i] = i32; } 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])){ 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(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 curspeed[i] = the_conf.maxspd[i]; state[i] = STP_MOVE; #ifdef EBUG SEND("MOTOR"); bufputchar('0'+i); SEND(" -> MOVE@"); printi(stppos[i]); SEND(", V="); printu(curspeed[i]); NL(); #endif }else{ // increase speed 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(); } } }else if(s == STALL_STOP){ stopflag[i] = 1; } break; case STP_MOVE: // move @ constant speed until need to decelerate if(s == STALL_NO){ // check position if(motdir[i] > 0){ if(stppos[i] >= decelstartpos[i]){ // reached start of deceleration TODECEL(); } }else{ if(stppos[i] <= decelstartpos[i]){ TODECEL(); } } }else if(s == STALL_STOP){ stopflag[i] = 1; } break; case STP_DECEL: 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]){ curspeed[i] = i32; }else{ curspeed[i] = the_conf.minspd[i]; state[i] = STP_MVSLOW; #ifdef EBUG SEND("MOTOR"); bufputchar('0'+i); SEND(" -> MVSLOW@"); printi(stppos[i]); NL(); #endif } 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: if(s == STALL_STOP) stopflag[i] = 1; break; default: // STALL, ERR -> do nothing, check mvzerostate break; } switch(mvzerostate[i]){ case M0FAST: if(state[i] == STP_RELAX || state[i] == STP_STALL){ // stopped -> move to + #ifdef EBUG 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]; }else{ mvzerostate[i] = M0SLOW; stopctr[i] = 0; } } break; case M0SLOW: if(0 == ESW_state(i)){ // moved out of limit switch - can stop emstopmotor(i); } if((state[i] == STP_RELAX || state[i] == STP_STALL) && ++stopctr[i] > 5){ // wait at least 50ms #ifdef EBUG bufputchar('M'); bufputchar('0'+i); SEND("SLOW: motor stopped\n"); #endif ESW_reaction[i] = the_conf.ESW_reaction[i]; prevencpos[i] = encpos[i] = 0; prevstppos[i] = targstppos[i] = stppos[i] = 0; enctimers[i]->CNT = 0; // set encoder counter to zero mvzerostate[i] = M0RELAX; } break; default: // RELAX, STALL: do nothing break; } } errcodes motor_goto0(uint8_t i){ errcodes e = motor_absmove(i, -the_conf.maxsteps[i]); 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; } // smooth motor stopping void stopmotor(uint8_t i){ switch(state[i]){ case STP_MVSLOW: // immeditially stop on slowest speed stopflag[i] = 1; return; break; case STP_MOVE: // stop only in moving states case STP_ACCEL: break; default: // do nothing in other states return; } int32_t newstoppos = stppos[i]; // calculate steps need for stop (we can be @acceleration phase!) int32_t add = (curspeed[i] * curspeed[i]) / the_conf.accel[i] / 2; if(motdir[i] > 0){ newstoppos += add; if(newstoppos < (int32_t)the_conf.maxsteps[i]) targstppos[i] = newstoppos; }else{ newstoppos -= add; if(newstoppos > -((int32_t)the_conf.maxsteps[i])) targstppos[i] = newstoppos; } TODECEL(); } void process_steppers(){ static uint32_t Tlast = 0; if(Tms - Tlast < MOTCHKINTERVAL) return; // hit every 10ms Tlast = Tms; for(int i = 0; i < MOTORSNO; ++i){ chkstepper(i); } } uint8_t geteswreact(uint8_t i){ return ESW_reaction[i]; }