changing the sweep 'resolution' of a stepper motor based on the position of a po

Hey guys,

as part of my first real in depth project i want to incorporate an analog looking gauge that will show the angle of a hydrofoil (aka ‘wedge’) on my boat.
I started with the ‘motorknob’ example in the stepper library and am modifying it to meet my needs.

The hydrofoil moves from a stowed position out of the water(0 degrees) to a ‘functional’ angle that is about 75-90 degrees. those 15 degrees is what i want to precisely control and display it’s position on my gauge. That leaves 74 degrees that is relatively useless on my gauge (not completely useless as i do want to know it’s approx position while it’s getting deployed). I am measuring the angle of my hydrofoil with a linear 10k pot atached to it’s pivot point.

This is the hydrofoil:

and here is how it’ll be mounted on my boat:

Here is a video of the actual gauge’s layout (the plastic piece is just a proxy for the real wedge):

the ‘deploy’ area is only useful to show me that the wedge is moving from 0 degrees to 75. there is no need for me know the precise angle. So in my code i figured i could remap my sensor’s values so that when the pot is is more the 75 degrees it’d ‘sweep’ a larger area on the gauge, increasing accuracy for those 15 degrees. On the flip side, when it’s less the 75 it is represented by a much smaller area of the overall sweep so i adjusted the sensor output with another map function. The problem is, i start to lose ‘sync’ when the pot sweeps back and fourth.

/*
 * WedgeGauge
 */

#include <Stepper.h>

// change this to the number of steps on your motor
#define STEPS (315)

// create an instance of the stepper class, specifying
// the number of steps of the motor and the pins it's
// attached to
Stepper stepper(STEPS, 4, 5, 6, 7);

// the previous reading from the analog input
int previous = 0;

const int numReadings = 10;

int encoderbutton =0;              //for future use
int mtcsetup =0;                     //for future use
int speedthreshold =31;            //for future use
int wedgemin=480;
int wedgemax =520;
int readings[numReadings];      // the readings from the analog input
int index = 0;                  // the index of the current reading
int total = 0;                  // the running total
int average = 0;                // the average
int averageold = 0; 
int inputPin = A0;
int sensorValue = 0;         // the sensor value
int sensorMin = 1023;        // minimum sensor value
int sensorMax = 0;           // maximum sensor value
int sweep =0;

void setup()
{
  // set the speed of the motor to 30 RPMs
  stepper.setSpeed(80);
  // initialize all the readings to 0: 
  for (int thisReading = 0; thisReading < numReadings; thisReading++)
    readings[thisReading] = 0;   
  stepper.step(-650);  //sets the initial home position of the gauge


  //Serial.begin (9600);
}

void loop()
{
  // subtract the last reading:
  total= total - readings[index];         
  // read from the sensor:  
  sweep = analogRead(inputPin); 


  if(sweep >= wedgemin){                                              //functional wedge angle, this is where the pot is when the hydrofoil is at 75 degrees
    sweep = map(sweep,wedgemin,wedgemax, 214, 483);   //the remapped values are representative of where the gauge's needle is.  
                                                                                 // at 214 it is where the 'deploy' sweep transitions to the 'wedge size' position
  }
  else {                                                                        //wedge depoly and retract.  non functional angle
    sweep = map(sweep, 0,wedgemin, 0,214);
  }


  readings[index] = sweep; 

  // add the reading to the total:
  total= total + readings[index];       
  // advance to the next position in the array:  
  index = index + 1;                    

  // if we're at the end of the array...
  if (index >= numReadings)              
    // ...wrap around to the beginning: 
    index = 0;                           

  // calculate the average:
  average = total / numReadings;    

  // get the sensor value
  int val = average;


  if((average >= (averageold+3)) || (average <= (averageold-3))) { //reduces the amount of needle jitter by only move the stepper if the interger changes by 3 or more
    // sensor reading
    stepper.step(val - previous);

    // remember the previous value of the sensor
    previous = val;
    averageold =average;
  }

  //Serial.print ("average:  ");
  //Serial.print (average);
  //Serial.print ("  actual:  ");
  //Serial.println (analogRead(inputPin));

  /* //wedgeSetup below  
   if(mtcsetup == 1){    // record the maximum sensor value
   if (sweep > wedgemax) {
   wedgemax = sweep;
   }
   if (encoderbutton == HIGH){  // pressing the encoder button will set the wedges highest functional angle
   wedgemin =sweep;
   }
   }
   //*/
}

I hope i was clear enough.
i know the issue is in the remap portion but i just don’t know an alternative to fix the sync issue. any ideas on how to correctly code for something like this?

thanks in advance,
'scott

Global variables are automatically initialized to 0. This code:

  for (int thisReading = 0; thisReading < numReadings; thisReading++)
    readings[thisReading] = 0;

is not needed. If you want to leave it, and it doesn’t hurt, you should add { and } around the body (even though they technically aren’t needed.

  stepper.step(-650);  //sets the initial home position of the gauge

Why is it necessary to diddle with the stepper in setup? You need feedback on the stepper motor to determine exactly where home is. Just spinning the motor a bunch doesn’t give you a good home position.

You are taking a reading, and averaging the readings. Each reading gets mucked with before being used to determine the average.

You’d be a lot better of mucking with the average, not the individual readings.

The average should tell you where the wedge is. What it is telling you now is where the needle on the gauge is. That is hardly the same thing, when the goal is a non-linear response.

PaulS:
Global variables are automatically initialized to 0. This code:

  for (int thisReading = 0; thisReading < numReadings; thisReading++)

readings[thisReading] = 0;



is not needed. If you want to leave it, and it doesn't hurt, you should add { and } around the body (even though they technically aren't needed.



stepper.step(-650);  //sets the initial home position of the gauge



Why is it necessary to diddle with the stepper in setup? You need feedback on the stepper motor to determine exactly where home is. Just spinning the motor a bunch doesn't give you a good home position.

You are taking a reading, and averaging the readings. Each reading gets mucked with before being used to determine the average.

You'd be a lot better of mucking with the average, not the individual readings.

The average should tell you where the wedge is. What it is telling you now is where the needle on the gauge is. That is hardly the same thing, when the goal is a non-linear response.

oh… i guess i left out the stepper im using. it has stops at each end. it isn’t capable to spin continuously. so when i step it back 650 steps it makes sure it’s at it’s ‘zero’ position. Im using the same steppers as the oem gauges are using and they do the same thing.
this is the datasheet:
http://clearwater.github.com/gaugette/resources/switec/X25_xxx_01_SP_E-1.pdf

i understand your advice about having the average tell where the wedge is, instead of where the needle is. I thought it was telling me where the wedge was though. since that pot will be hooked up directly to the wedge.

The pot is telling you where the wedge is. The mucking around with the reading (the pot value) that you do is to tell you where the needle should be. Since it is the needle position that you average, the average is not where the wedge is. It is where the needle is.

The value read from the pot should be stored in a variable with a name like wedge, so that you understand that it is not the sweep angle for the pot.

The average calculation should involve wedge, not sweep. The map should take as input average, not sweep. The result should be stored in sweep, which is what should be used to determine the change in needle position (the number of steps) required.

thank you for that. i'll try it out tomorrow. am i on the right track with the if/else mapping idea?

am i on the right track with the if/else mapping idea?

The idea looks right. The only things I'm not sure of is that your limits are correct. I have to assume that you've printed the values of the pot reading as it moves through the range with the wedge, and that you know that they are correct.

When you change how you are computing the average, you may find that they are not. Printing the pot reading again, and the average value, may be necessary to validate that the from ranges are correct.

The to ranges are questionable, too. Not wrong, just that I don't know how you arrived at them. If they work for you, then they are correct.

I've been thinking about what you've said about the pot telling me where the needle is... and it got me thinking. I think a better solution would be for my needle/stepper to tell my wedge where to go. I am going to try to use the rotary encoder to step the dial to the correct position. then my h-bridge will move the wedge to match it.

I'm going to use a 74xx74 flip flop ic to give the arduino 'pulses' for each encoder detent. each pulse will move the dial and then the h-bridge will move the wedge until it meets up with the value mapped a counter value. I think that is a simplified approach for a rookie like me. It may be a better solution since the dial will move much faster then the wedge. so I wont have to wait for the wedge to achieve it's set position to know where it is supposed to be. as a visual indicator i can sequence the led's on the gauge to show if it's in the processes of moving up or down.

I am going to try to use the rotary encoder to step the dial to the correct position. then my h-bridge will move the wedge to match it.

The gauge then won't tell you where the edge actually is, only where you want it to be. You'll also need to look at the LEDs to see if it made it there.

I think that you really should make the gauge show where the edge is. Of course, what you really want to see is where it is relative to where you want it to be, but that would require a different kind of gauge.

well i think that having the led’s cycle to the left or the right would tell me that the wedge was moving, then when the wedge made it to the desired location. the leds would turn off.

I think having a quicker feedback of where i want the wedge to be; instead of where the wedge is at the exact moment, will be ok for my purposes. I’m actually not sure which one i’d prefer to tell you the truth. i think they both have advantages.

I'm actually not sure which one i'd prefer to tell you the truth. i think they both have advantages.

Since it's just software, you can always change your mind, if one approach doesn't work the way you want.

Did you get this to a good resolution with this?

Are you using an X25.168 stepper? If so, it has a sweep of 315 degrees and 3 steps per degree
giving 945 steps between stops, so I would expect you to run the stepper back 945 steps
to zero it. This also probably also implies that the stepper library should be initialized with 945 steps,
not 315, although I'm not sure how the library uses that number.

I have read (but not verified) that the standard stepper library uses a different step pattern
than the X25's, which I would expect to cause problems.

Finally if you are losing sync, the problem might be step interval. If you step
too quickly for your particular needle torque, it will miss steps.
To get maximum speed without losing sync you need to start stepping slowly and
accelerate to full speed, but the exact timing is a factor of the needle you are using.

If this is still a current problem I'd be happy to show you how to solve it with
the SwitecX25 library.

it seems to be working fine. im still testing it though. if you’re the guy who wrote the switec library, i was the one talking to you over on your blog. originally i had the stepper set up to show where the wedge was. I was able to get the default stepper library (and knob to stepper example sketch)to work well enough and easily enough out the box so i just stuck with it.

the gauge isn’t as smooth as in your youtube videos and I’d be interested in implementing your library instead. I just need to understand it a bit better. right now my gauge has a definite stepper feel. Since i’ve since moved on from the pot controlling the stepper, i think it may be easier to implement your library.

I ended up going the with the other solution. having the wedge go to where the needle is. instead of vise versa. this is helpful because the wedge doesn’t move as quickly as the encoder. so i’d be guessing where the wedge would end up if i had the needle represent where the wedge is in it’s travels.

Here is the current working code, I have a mega with a touch interface that sends it presets:

#include <EEPROM.h>
#include <Bounce.h>
#include <Encoder.h>
#include <Stepper.h>


#define BUTTON 8
Bounce bouncer = Bounce( BUTTON, 5 ); 

const int wedgepinLower = 10;
const int wedgepinRaise = 11;
const int wedgepinSpeed = 12;
const int analogInPin = A0;  // Analog input pin that the potentiometer is attached to

int constrValue = 0;
int sensorValue = 0;        // value read from the pot
int wedgepos = 0;        // value output to the PWM (analog out)

int wedgepreset = 0;
int target =0;
int targetold =0;


int preset1 =  478;
int preset2 =  390;
int preset3 =  326;
int preset4 =  238;

//THESE NEED TO BE CALIBRATED WHEN YOU ATTACH YOUR POT TO THE WEDGE
int minwedge =200;            //lowest point the wedge will be functional 
int maxwedge =800;            //highest point the wedge will be functional

//TIME
unsigned long time =0;
long oldPosition  = 0;
int clicks = 0;
int oldclicks =0;
int needlepos =0;
int stepspeed =0;
int buttonState = 0;         // variable for reading the pushbutton status
int buttoncount =0;
int lastButtonState = 0;     // previous state of the button
int release =0;

// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability
Encoder myEnc(2, 3);

//stepper:
// change this to the number of steps on your motor
#define STEPS (105)

// create an instance of the stepper class, specifying
// the number of steps of the motor and the pins it's
// attached to
Stepper stepper(STEPS, 4, 5, 6, 7);




void setup() {
  Serial.begin(9600);

  pinMode(wedgepinLower, OUTPUT);  
  pinMode(wedgepinRaise, OUTPUT);  
  pinMode(wedgepinSpeed, OUTPUT);  

  pinMode(BUTTON, INPUT);     
  stepper.setSpeed(120);

  stepper.step(-650);  //sets the initial home position of the gauge

  // read the analog in value:
  sensorValue = analogRead(analogInPin);            
  // map it to the range of the analog out:
  constrValue = constrain(sensorValue, minwedge, maxwedge);
  wedgepos = map(constrValue, minwedge, maxwedge, 0, 36);  




  // takes the reading of the wedge's current state
  // do this so that, in case of a power reset, you wont have the wedge 
  //traveling to it's home position
  if (sensorValue == 0){
    needlepos = 0;
  }
  if (sensorValue >= minwedge){
    release =1;
    stepper.step(206);
    needlepos = 206;
    stepper.step(wedgepos*8);
    needlepos = ((wedgepos*8)+206);
  }
}

void loop() {
  time = millis();

  
  
  if (Serial.available()>0){
    wedgepreset = Serial.read()-48;
    Serial.print("I received: ");
    Serial.println(wedgepreset);
    Serial.end();
    Serial.begin(9600);

// input from gui on touchscreen
    if (wedgepreset ==1){
      stepper.step(preset1 - needlepos);
      needlepos = preset1;
      wedgepreset = 11;
    }
    if (wedgepreset ==2){
      stepper.step(preset2 - needlepos);
      needlepos = preset2;
      wedgepreset = 11;
    }
    if (wedgepreset ==3){
      stepper.step(preset3 - needlepos);
      needlepos = preset3;
      wedgepreset = 11;
    }
    if (wedgepreset ==4){
      stepper.step(preset4 - needlepos);
      needlepos = preset4;
      wedgepreset = 11;
    }
  
  //user definable presets
    if (wedgepreset ==5){
      preset1 = needlepos;
      wedgepreset = 11;
    }
    if (wedgepreset ==6){
      preset2 = needlepos;
      wedgepreset = 11;
    }
    if (wedgepreset ==7){
      preset3 = needlepos;
      wedgepreset = 11;
    }
    if (wedgepreset ==8){
      preset4 = needlepos;
      wedgepreset = 11;
    }


  }
  // read the analog in value:
  sensorValue = analogRead(analogInPin);            
  constrValue = constrain(sensorValue, minwedge, maxwedge);
  wedgepos = map(constrValue, minwedge, maxwedge, 0, 36);  

  buttonState = digitalRead(BUTTON);
  long newPosition = myEnc.read();
  clicks = newPosition/4;
  if (newPosition != oldPosition) {
    oldPosition = newPosition;
    if (clicks != oldclicks){
      Serial.print ("oldclicks:   ");
      Serial.print (oldclicks);
      Serial.print ("   clicks:   ");
      Serial.print (clicks);
    }  
  } 

  if (oldclicks != clicks){
    Serial.println (clicks-oldclicks);
    if (((clicks-oldclicks) >0)&&(needlepos > 206)){
      stepper.step(-8);
      needlepos = needlepos-8;
    }
    if (((clicks-oldclicks) <0)&&(needlepos >= 206)&&(needlepos < 478)){
      //    stepper.setSpeed(5);
      stepper.step(8);
      needlepos = needlepos+8;
    }
    oldclicks = clicks; 
    Serial.print ("   needlepos:   ");
    Serial.println (needlepos);
  }

  bouncer.update(); 
  int value = bouncer.read();
  if ((value == HIGH)&&(release ==0)) {
    buttoncount++;
    Serial.println (buttoncount);
  } 
  else {
    buttoncount = 0;
  }
  if ( value == LOW) {
    release = 0;
  }
  if (buttoncount >= 200){
    release =1;
    if ((needlepos >= 206)&&(needlepos <= 478)){
      stepper.step(-(needlepos));
      needlepos = 0;
      buttoncount = 0;
    }
    else if (needlepos == 0){
      stepper.step(206);
      needlepos = 206;
      buttoncount = 0;
    }
  }

//H BRIDGE  CONTROLLER
  if (needlepos != ((wedgepos*8)+206)){
    if (needlepos < ((wedgepos*8)+206)&&(target != targetold)){
      target =2;
      digitalWrite (wedgepinLower,LOW);
      digitalWrite (wedgepinRaise,HIGH);
      Serial.println ("^^  RAISE  ^^");
      targetold =target;
    }
    if (needlepos > ((wedgepos*8)+206)&&(target != targetold)){
      target =3;
      digitalWrite (wedgepinLower,HIGH);
      digitalWrite (wedgepinRaise,LOW);
      Serial.println ("vv  LOWER  vv");
      targetold =target;
    }
  }
  if (needlepos == ((wedgepos*8)+206)&&(target != 1)){
    digitalWrite (wedgepinLower,LOW);
    digitalWrite (wedgepinRaise,LOW);
    target =1;
    Serial.println ("TARGET");
  }


}