My question is about getting smooth output from a stepper motor through good programming.
Here's a little background:
I've built a GPS speedometer running on a MEGA 2560. It works well for the most part, I'm able to get data from the GPS module to the Arduino, and then out to an OLED display, as well as an analog gauge stepper motor. You can see it working here: Analog and digital Arduino GPS speedometer - YouTube
The video shows a little of this, but the needle can be kind of jumpy. You'll see it jump up and back down, and it does this a lot while decelerating. Obviously this is not actually what is happening. The GPS signal comes in at 5Hz, and I've plotted the output from the GPS. It looks like what I would expect.
Now if I update the motor position only when data comes in, the needle motion will be jumpy. So what I've attempted to do is let my motor position lag behind GPS updates, and interpolate between the last and current GPS values. You'll see this in the "GPS Data Fetching Loop" and "Gauge 1 Update Function".
It seems obvious to me that this interpolation strategy has not worked. What am I doing wrong? I'd appreciate your thoughts.
/////// MAIN LOOP /////////////////////////////////////////////////////////////////////////
void loop()
{
if (timer3 > millis()) timer3 = millis();
if (millis() - timer3 > 8) {
int alpha_a1 = 64; // exponential moving average alpha value
angle1_last = angle1; // save last gauge angle
angle1 = gauge1(); // read gauge value and return angle
angle1 = (angle1*alpha_a1 + angle1_last*(256-alpha_a1))>>8; // calculate exponential moving average
motor1.setPosition(angle1); // send needle angle to motor
Serial.print(0); // serial plotter debugging
Serial.print(" "); // serial plotter debugging
Serial.print(540); // serial plotter debugging
Serial.print(" "); // serial plotter debugging
Serial.println(angle1); // serial plotter debugging
void motorUpdate(); // update motor position of all motors
}
// DISPLAY timer loop
if (timer1 > millis()) timer1 = millis();
if (millis() - timer1 > 100) {
timer1 = millis(); // reset timer1
dispUpdate(); // update OLED display
}
// GPS DATA FETCHING LOOP //
if (GPS.newNMEAreceived()) {
if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false
return; // we can fail to parse a sentence in which case we should just wait for another }
// if millis() or timer wraps around, we'll just reset it
if (timer2 > millis()) timer2 = millis();
if (millis() - timer2 > 200) {
timer2 = millis(); // reset timer2
unsigned long alpha_0 = 128; // filter coefficeint to set speedometer response rate
v_lo = v_hi; // save previous value of velocity
t_lo = t_hi; // save previous time value
lag = t_hi-t_lo; //
v = GPS.speed*1.150779; // fetch velocity from GPS object, convert to MPH
t_hi = micros(); // get current time value
v_100 = (unsigned long)v*100; // x100 to preserve hundredth MPH accuracy
v_hi = (v_100*alpha_0 + v_hi*(256-alpha_0))>>8; //filtered velocity value
// Serial.println(v_hi);
}
}
}
// GAUGE 1 DATA UPDATE FUNCTION //
int gauge1 () {
v_g = map(micros()-lag, t_lo,t_hi,v_lo,v_hi); // interpolate values between GPS data fix
if (v_g < 150 || v_g > 20000) { // bring speeds below 1.5mph and above 200 mph to zero
v_g = 0;
}
int angle = map( v_g, 0, 6000, 0, STEPS1); // calculate angle of gauge
return angle; // return angle of motor
}
// ADAFRUIT GPS INTERRUPT FUNCTION //
// I don't really understand this code, but it works, so don't freaking mess with it
// Interrupt is called once a millisecond, looks for any new GPS data, and stores it
SIGNAL(TIMER0_COMPA_vect) {
char c = GPS.read();
// if you want to debug, this is a good time to do it!
#ifdef UDR0
if (GPSECHO)
if (c) UDR0 = c;
// writing direct to UDR0 is much much faster than Serial.print
// but only one character can be written at a time.
#endif
}
void useInterrupt(boolean v) {
if (v) {
// Timer0 is already used for millis() - we'll just interrupt somewhere
// in the middle and call the "Compare A" function above
OCR0A = 0xAF;
TIMSK0 |= _BV(OCIE0A);
usingInterrupt = true;
} else {
// do not call the interrupt function COMPA anymore
TIMSK0 &= ~_BV(OCIE0A);
usingInterrupt = false;
}
}
// GAUGE POSITION UPDATE FUNCTION //
void motorUpdate ()
{
motor1.update();
}
// OLED DISPLAY UPDATE FUNCTION //
void dispUpdate() {
display.clearDisplay(); //clear buffer
display.setTextSize(2); // text size
display.setCursor(12,1);
display.print("SPD:"); // GPS speed
display.println(v);
display.setTextSize(1); // text size
display.setCursor(12,17);
//display.print("disp updates: "); //counter for debugging
//display.println(testnum);
display.print("v_hi: "); //counter for debugging
display.println(v_hi);
display.display(); //print to display
testnum ++;
}
gauge_feb_4_2020_debug.ino (8.17 KB)