/********************************************************************** Copyright (C) 2018 MisfitTech LLC, All rights reserved. MisfitTech uses a dual license model that allows the software to be used under a standard GPL open source license, or a commercial license. The standard GPL license requires that all software statically linked with MisfitTec Code is also distributed under the same GPL V2 license terms. Details of both license options follow: - Open source licensing - MisfitTech is a free download and may be used, modified, evaluated and distributed without charge provided the user adheres to version two of the GNU General Public License (GPL) and does not remove the copyright notice or this text. The GPL V2 text is available on the gnu.org web site - Commercial licensing - Businesses and individuals that for commercial or other reasons cannot comply with the terms of the GPL V2 license must obtain a low cost commercial license before incorporating MisfitTech code into proprietary software for distribution in any form. Commercial licenses can be purchased from www.misfittech.net and do not require any source files to be changed. This code is distributed in the hope that it will be useful. You cannot use MisfitTech's code unless you agree that you use the software 'as is'. MisfitTech's code is provided WITHOUT ANY WARRANTY; without even the implied warranties of NON-INFRINGEMENT, MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. MisfitTech LLC disclaims all conditions and terms, be they implied, expressed, or statutory. Written by Trampas Stern for MisfitTech. Misfit Tech invests time and resources providing this open source code, please support MisfitTech and open-source hardware by purchasing products from MisfitTech, www.misifittech.net! *********************************************************************/ #include "calibration.h" #include "Flash.h" #include "nonvolatile.h" #include "board.h" //for divide with rounding macro #include "utils.h" static uint16_t getTableIndex(uint16_t value) { int32_t x; x=((int32_t)value*CALIBRATION_TABLE_SIZE)/CALIBRATION_STEPS; //the divide is a floor not a round which is what we want return (uint16_t)x; } static uint16_t interp(Angle x1, Angle y1, Angle x2, Angle y2, Angle x) { int32_t dx,dy,dx2,y; dx=x2-x1; dy=y2-y1; dx2=x-x1; y=(int32_t)y1+DIVIDE_WITH_ROUND((dx2*dy),dx); if (y<0) { y=y+CALIBRATION_STEPS; } return (uint16_t)y; } static void printData(int32_t *data, int32_t n) { int32_t i; Serial.print("\n\r"); for (i=0; iFastCal.angle[x]; }else { return reverseLookup(encoderAngle); } #else return reverseLookup(encoderAngle) #endif } Angle CalibrationTable::reverseLookup(Angle encoderAngle) { int32_t i=0; int32_t a1,a2; int32_t x; int16_t y; int32_t min,max; min=(uint16_t)table[0].value; max=min; for (i=0; imax) { max=x; } } x=(uint16_t)encoderAngle; if (xCALIBRATION_STEPS/2) { if (a1=a1 && x<=a2) || (x>=a2 && x<=a1) ) { //LOG("%d", i); // inerpolate results and return //LOG("%d %d %d",a1,a2,x); //LOG("%d,%d",(i*CALIBRATION_MAX)/CALIBRATION_TABLE_SIZE,((i+2)*CALIBRATION_MAX)/CALIBRATION_TABLE_SIZE); y=interp(a1, DIVIDE_WITH_ROUND((i*CALIBRATION_STEPS),CALIBRATION_TABLE_SIZE), a2, DIVIDE_WITH_ROUND( ((i+1)*CALIBRATION_STEPS),CALIBRATION_TABLE_SIZE), x); return y; } i++; } ERROR("WE did some thing wrong"); } void CalibrationTable::smoothTable(void) { uint16_t b[]={1,2,4,5,4,2,1}; uint16_t sum_b=19; //sum of b filter int32_t data[CALIBRATION_TABLE_SIZE]; int32_t table2[CALIBRATION_TABLE_SIZE]; int32_t i; int32_t offset=0; int32_t startNum; //first lets handle the wrap around in the table for (i=0; i0 && offset==0) { if(((uint16_t)table[i-1].value-(uint16_t)table[i].value) <-32768) { offset=-65536; } if (((uint16_t)table[i-1].value-(uint16_t)table[i].value) > 32768) { offset=65536; } } table2[i]=(int32_t)((uint16_t)table[i].value)+offset; } //Serial.print("after wrap\n\r"); //printData(table2,CALIBRATION_TABLE_SIZE); //remove the starting offset and compensate table for index startNum=table2[0]; for (i=0; i=CALIBRATION_TABLE_SIZE) { ix=ix-CALIBRATION_TABLE_SIZE; } if (i==0) { LOG("index %d",ix); } sum=sum+table2[ix]*b[ib]; ib++; } sum=DIVIDE_WITH_ROUND(sum,sum_b); data[i]=sum; } //Serial.print("after filter\n\r"); //printData(data,CALIBRATION_TABLE_SIZE); //add in offset and the phase compenstation for (i=0; i=65536) { data[i]=data[i]-65536; } } //Serial.print("after wrap added\n\r"); //printData(data,CALIBRATION_TABLE_SIZE); //save new table for (i=0; iCalibrationTable,sizeof(data)); createFastCal(); LOG("after writting status is %d",data.status); loadFromFlash(); } void CalibrationTable::loadFromFlash(void) { FlashCalData_t data; int i; LOG("Reading Calbiration to Flash"); memcpy(&data, &NVM->CalibrationTable,sizeof(data)); for (i=0; iCalibrationTable.status); return NVM->CalibrationTable.status; } void CalibrationTable::createFastCal(void) { #ifdef NZS_FAST_CAL int32_t i; uint16_t cs=0; uint16_t data[256]; int32_t j; j=0; cs=0; LOG("setting fast calibration"); for (i=0; i<16384; i++) { uint16_t x; x=reverseLookup(i*4); data[j]=x; j++; if (j>=256) { flashWrite(&NVM->FastCal.angle[i-255],data,256*sizeof(uint16_t)); //LOG("Wrote fastcal at index %d-%d", i-255, i); j=0; } cs+=x; } //update the checksum flashWrite(&NVM->FastCal.checkSum,&cs,sizeof(uint16_t)); fastCalVaild=true; //this is a quick test /* for (i=0; i<16384; i++) { LOG("fast Cal %d,%d,%d",i,NVM->FastCal.angle[i],(uint32_t)reverseLookup(i*4)); } */ #endif } void CalibrationTable::updateFastCal(void) { #ifdef NZS_FAST_CAL int32_t i; uint16_t cs=0; uint16_t data[256]; int32_t j; bool NonZero=false; for (i=0; i<16384; i++) { cs+=NVM->FastCal.angle[i]; if (cs != 0) { NonZero=true; } } if (cs!=NVM->FastCal.checkSum || NonZero==false) { createFastCal(); } else { LOG("fast cal is valid"); fastCalVaild=true; } #endif } void CalibrationTable::init(void) { int i; if (true == flashGood()) { loadFromFlash(); updateFastCal(); }else { for (i=0; iCALIBRATION_STEPS/2) { dist=dist-CALIBRATION_STEPS; } //if our distance is larger than size between calibration points in table we will ignore this sample if (dist>CALIBRATION_STEPS/CALIBRATION_TABLE_SIZE) { //spans two or more table calibration points for this implementation we will not use lastIndex=(int32_t)index; lastValue=value; return; } //now lets see if the values are above and below a table calibration point dist= abs(getTableIndex(lastAngle)-getTableIndex(actualAngle)); if (dist != 0) //if the two indexs into table are not the same it spans a calibration point in table. { //the two span a set calibation table point. uint16_t newValue; newValue=interp(lastAngle, lastEncoderValue, actualAngle, encoderValue, getTableIndex(actualAngle)*(CALIBRATION_STEPS/CALIBRATION_TABLE_SIZE)) //this new value is our best guess as to the correct calibration value. updateTableValue(getTableIndex(actualAngle),newValue); } else { //we should calibate the table value for the point the closest } } lastAngle=(int32_t)actualAngle; lastEncoderValue=encoderValue; } #endif //when we are microstepping and are in between steps the probability the stepper motor did not move // is high. That is the actualAngle will be correct but the encoderValue will be behind due to not having enough torque to move motor. // Therefore we only want to update the calibration on whole steps where we have highest probability of things being correct. void CalibrationTable::updateTable(Angle actualAngle, Angle encoderValue) { int32_t dist, index; Angle tableAngle; index = getTableIndex((uint32_t)actualAngle+CALIBRATION_STEPS/CALIBRATION_TABLE_SIZE/2); //add half of distance to next entry to round to closest table index tableAngle=(index*CALIBRATION_STEPS)/CALIBRATION_TABLE_SIZE; //calculate the angle for this index dist=tableAngle-actualAngle; //distance to calibration table angle //LOG("Dist is %d",dist); if (abs(dist)=CALIBRATION_TABLE_SIZE) { indexHigh -= CALIBRATION_TABLE_SIZE; } //LOG("AngleLow %d, AngleHigh %d",angleLow,angleHigh); //LOG("TableLow %u, TableHigh %d",(uint16_t)table[indexLow].value,(uint16_t)table[indexHigh].value); y1=table[indexLow].value; y2=table[indexHigh].value; //handle the wrap condition if (abs(y2-y1)>CALIBRATION_STEPS/2) { if (y2=CALIBRATION_STEPS) { value=value-CALIBRATION_STEPS; } err=table[indexLow].error; if (table[indexHigh].error > err) { err=table[indexHigh].error; } if (table[indexLow].error == CALIBRATION_ERROR_NOT_SET || table[indexHigh].error == CALIBRATION_ERROR_NOT_SET) { err=CALIBRATION_ERROR_NOT_SET; } ptrData->value=value; ptrData->error=err; return 0; } Angle CalibrationTable::getCal(Angle actualAngle) { CalData_t data; getValue(actualAngle, &data); return data.value; }