X27 Stepper Motor - Issue with zero

Hello fellow makers,

I have limited experience with arduino, but I managed to set up a simple speedometer based on a hall effect sensor and a servo - worked a treat, but it was more of a proof of concept than a finished product.

Now, as part of converting an old motorbike to electric, I want to keep the original speedometer, but due to the fact that I no longer have the old style cable as the back wheel was replaced with a motor, I want to do it with a use of X27 stepper motor.

The plan is to drive the stepper directly from Arduino, feed it hall effect signal that is generated by the electric motor and have Arduino do the maths. Sounds simple.

I am struggling at the very start though and I cannot for the love of me figure out why. I've got an Arduino Nano, I've got an X27 stepper motor and I've got the SwitecX25 library.

Using the build in example, it works fine (the needle travels to zero, then to a set point, and then using the serial input I can make it go wherever I want). So I know my wiring is good, the motor works as it should and all is good.

This is where the problem happens - for now, I want a simple code that will make it go from 0, to max, then back to 0, just like shown in this video (https://www.youtube.com/watch?v=UJKaaRR9W6g&ab_channel=GuyCarpenter), but it just does not work. I tried doing a delay(1000) then going back to zero, but the motor just buzzes and does nothing... Anyone with an idea of what is going wrong?

//----------------------------------------------------------------------
// https://github.com/clearwater/SwitecX25
// 
// This is an example of using the SwitchX25 library.
// It zero's the motor, sets the position to mid-range
// and waits for serial input to indicate new motor positions.
// 
// Open the serial monitor and try entering values 
// between 0 and 944.
// 
// Note that the maximum speed of the motor will be determined
// by how frequently you call update().  If you put a big slow
// serial.println() call in the loop below, the motor will move
// very slowly!
//----------------------------------------------------------------------

#include <SwitecX25.h>

// standard X25.168 range 315 degrees at 1/3 degree steps
#define STEPS (315*3)

// For motors connected to digital pins 4,5,6,7
SwitecX25 motor1(STEPS,4,5,6,7);

void setup(void)
{
  // run the motor against the stops
  motor1.zero();
  // start moving towards the center of the range
  motor1.setPosition(944);
  
  Serial.begin(9600);
  Serial.print("Enter a step position from 0 through ");
  Serial.print(STEPS-1);
  Serial.println(".");
}

void loop(void)
{
  static int nextPos = 0;
  // the motor only moves when you call update
  motor1.update();
  
  if (Serial.available()) {
    char c = Serial.read();
    if (c==10 || c==13) {
      motor1.setPosition(nextPos);
      nextPos = 0;
    } else if (c>='0' && c<='9') {
      nextPos = 10*nextPos + (c-'0');
    }
  }
}

EDIT: I am doing some more testing, and while the issue persists, I have connected a hall effect sensor and using code from here (CB450 K0 Bomber electronic speedo/tach gauge | Honda Twins) it does work, albeit backwards (starts at position 944 and when I put the magnet close, it moves towards 0). I am even more confused - the motor cannot be wired wrong as when I put 0 in the serial input using the example above, it goes to the correct position. What's going on?

Please post a link to the datasheet of the stepper. So far I have never seen a stepper running by the 20 mA that is the rating for those small controllers.
Please post the schematics as well.

Post the example code you used that works.

Hey @Railroader,

Thanks for taking the time. You can find the datasheet of the stepper here - Stepper Datasheet. Plenty of examples online of it being driven directly from Arduino Nano.

As for the schematic, I connected pins marked 1, 2, 3 and 4 as per sketch below to digital pins 4, 5, 6 and 7. That's it, simple as that.

Switec pinout

@wildbill I posted the code that works in the original post above - it works in a sense that it travels from 0 to 945, but then does not go back to zero again (which is what I would like). If I input any value using the serial input, it recognises it correctly.

There is also the second code from the linked forum, I'll add it here. It works in the same way, travels from whatever position it was in to 0, then back to 944. I've wired in a small hall effect sensor and the magnet causes the motor to move, however, it moves right to left (so position changes from 944 to lets say 900), and then defaults back to 944, as opposed to 0.

//----------------------------------------------------------------------
// https://github.com/clearwater/SwitecX25
//
// This is an example of using the SwitchX25 library.
//
// This code has been rewriten from Guy Carpenters X25 example to
// function as a Tachometer for a CB450 K0 Motorcycle (Tach
// gearing ratio of 1:7)
//
// Note that the maximum speed of the motor will be determined
// by how frequently you call update(). If you put a big slow
// serial.println() call in the loop below, the motor will move
// very slowly!
//----------------------------------------------------------------------

// Guy Carpenters X25 library
#include <SwitecX25.h>

// standard X25.168 & X27.168 range 315 degrees at 1/3 degree steps
#define STEPS (315*3) //totaling 945 steps to complete 315 degrees of rotation

// For motors connected to digital pins 4,5,6,7 (on X25 & X27 pin 1-> pin4, pin 2-> pin5, pin3 -> pin6, pin4 -> pin7)
SwitecX25 motor1(STEPS,4,5,6,7);

// digital pin 2 is the hall effect sensor input pin (AT ARDUINO, ADD A 10K RESISTOR BETWEEN PIN 2 AND VIN TO KEEP PIN FROM 'BOUNCING'!!!!)
int hall_pin = 2;

// pick number of rotations to analyze to determin rpm (higher improves accuracy)
float rotations = 1.0;

// Time program has spent in "While" loop per half rotation of tach cable
float WhileTime2;

// each rotation trips hall effect 2 times due to each half of bar magnet triping hall effect sensor once
// correct number of trips per rotation.
float hall_thresh = (rotations*2);

// tach cables rpm result
float rpm_val;

// to mark the first turn of tach cable. due to no previous recording for position of magnet.
int first_run;

// used to record previous state of hall effect sensor
int previous_hall_pin_state;



void setup(void)
{
// run the motor against the stops at start up
motor1.zero();
// start moving towards the 0 value (for my wiring and tach gauge 0 value is at positon 945)
// changing the wiring may change the 0 position to postion 0. If wiring is changed code will need to be updated.
motor1.setPosition(944);

// Mark that first turn of tach cable has NOT happened yet
first_run = 0;

// make the hall pin an input:
pinMode(hall_pin, INPUT);

// initialize serial communication at 9600 bits per second:
// Serial.begin(9600);
}


void loop(void)
{

// nextPos will be the value where the needle will be moved too
// setting nextPos to 0 at begining of loop does not move the motor, it only clears the value to
// prepare it for its new value
static int nextPos = 0;
// the motor only moves when you call update. It will pull the value from the last motor1.setPosition()
// value in your code
motor1.update();

// preallocate values for tach (clear values at begining of new loop for new data and calculations)

// rotations equal 0
float hall_count = 0.0;
// start timer
float start = micros();
// while loop timer
WhileTime2 = 0;
// Marks current status of hall effect sensor (0=ON, 1=OFF)
// NOTE: THIS WAS USED ON THE EARLY DESIGN OF THE CODE. THE CODE HAS CHANGED SINCE THEN, AND
// I AM UNSURE IF THIS MARKER IS STILL NEEDED. WILL LOOK INTO LATER
int on_state = 0;

// counting number of times the hall sensor is tripped
// While code is in "While loop" code in main loop is paused. be sure to include "motorl.update()"
// In "While" loop to keep motor running smoothly

// run "While" loop as long as number of sample rotaions entered in "roataions" above have not been meet
while(hall_count < hall_thresh){ //remember hall_count = 2*rotations

// continue to update motor position even when code is stuck in while loop
motor1.update();

// The amount of time needed for 100rpm reading (used to send needle to 0rpm when readings under 100rpm are received
// or no data is received and time threshold for 100rpm has been exceded for Honda CB450)
// Value is in Micro-Seconds
float hund_rpm_time = ((30000000/10)*hall_thresh);

// The amount of time program has spent in "While" loop for each loop in the "While" loop
WhileTime2 = micros()-start;

// If time in 1 pass through a "While" loop exceeds time for 100rpm minimum
// then begin to send needle to 0rpm position
// NOTE: INCREASE THE "14" FOR FASTER NEEDLE RESPONSE. I'LL LOOK INTO THIS PART OF THE CODE FURTHER IN THE
// FUTURE TO SEE IF THIS EFFECTS ACCURACY
if (WhileTime2 > (hund_rpm_time/14)){
//Sends motor to position 944 (0 rpms for this code)
motor1.setPosition(944);
}

// if magnet is triggering hall effect sensor then...
if (digitalRead(hall_pin)==0){ // hall effect Sending signal (hall_pin = 0)

// if first_run = 0 then the Tach cable has not turned yet. Thus, during previous drive Tach magnet came to rest next
// to hall effect sensor once engine was turned off. reading is false.
if (first_run = 0){
// mark hall effect sensor as previously on (previous_hall_pin_state = 0)
previous_hall_pin_state = 0;
// mark first_run = 1 so this section of code only runs at the very first turn of tach cable only
first_run = 1;

// incriment tach cable rotation value by 1/2 (remember that 2 counts are equal to 1 rotation of the cable)
// TECHNICALLY THE CABLE HAS NOT TURNED AT ALL. INCIMENTING MAY NOT BE NEEDED HERE. HOWEVER, IF INCIMENTING
// HERE IS PRODUCING A FALSE VALUE IT IS ONLY FOR THE 1ST VALUE AND THEN IGNORED. ARITHMETIC ERROR IS UN-NOTICABLE
hall_count+=1.0;
}

/// if hall effect sensor is on now and was previoulsy off then...
if (digitalRead(hall_pin)==0 && previous_hall_pin_state==1){

/// if on_state is equal to 0 (ON) then set on_state equal to 1 (OFF) to prepare for next pass,
// increase hall_count, change previous_hall_pin_state=0 (ON)
if (on_state==0){
on_state = 1;
hall_count+=1.0;
previous_hall_pin_state = 0;
}
}

// if hall_pin = 1, magnet is NOT triggering hall effect sensor then...
} else{

// if you reach this point in code and still first_run = 0
// then Tach cable has not turned yet. And you know that the end of the Tach bar magnet
// did NOT come to rest next to hall effect sensor when engine was last turned off
// mark hall effect sensor as previously off (previous_hall_pin_state = 1)
if (first_run = 0){
previous_hall_pin_state = 1;
// mark first_run = 1 so this section of code only runs at the very first turn of tach cable only
first_run = 1;
}

// if hall effect sensor is off now and was previoulsy on then...
if (digitalRead(hall_pin)==1 && previous_hall_pin_state==0){

//set on_state = 0 (set marker to ON) to prepare for next time hall_pin=0 (ON)
on_state = 0;
// change previous_hall_pin_state=1 (OFF)
previous_hall_pin_state = 1;
}
}

// if number of data gathering cable rotations have been met then exit "while" loop
// and begin doing RPM calculations
if (hall_count>hall_thresh){
break;
}

//continue to update motor position even when code is stuck in while loop
motor1.update();
}

// record current time to compare to start time
float end_time = micros();

// calculate how much time has passed in seconds (dividing microseconds by 1 million gives you seconds)
float time_passed = ((end_time-start)/1000000.0);

// calculate rpm value (hall sensor is tripped twice per rotation, so divide hall_count in half to get
// number of rotations. then divide by the amount of time that passed to get rotations per second.
// multiply by 60 to get rotations per minute
rpm_val = ((hall_count/2)/time_passed)*60.0;

// send value to motor to rotate needle.
// NOTE: CB450 TACH GEARING IS 1:7. SO RPM'S WILL BE (1/7)OF THE MOTORS ACTUAL RPM's

// (for values greater than 1571) situation where tachometer has reached its max amount (Motorcycle about to EXPLODE!)
if(rpm_val > 1571){ //CB450 K0 tach max at 11000rpm (tach ratio of 1:7. 11000/7=1571)
nextPos = 472; // turn gage about 180 deg (X27 turns 945 steps. 945/2=472). (ACTUAL ANGLE FOR CB450 IS SLIGHLTY LESS. WILL WORK OUT LATER)
motor1.setPosition(nextPos); // call needle to rotate to given potition
nextPos = 0; // clear nextPos value to zero for next calculation

//send value to motor to rotate needle (for values between 0 and 1571, between 0 and 11000 rpm)
}else if(rpm_val >= 0 && rpm_val <= 1571){

// nextPos represents "motor potition" or steps. Since 944 is our zero we will work backwards by subtracting he the steps from our zero mark (944).
// first we need to covert our RPM's to steps. Since we are only using 180 degrees of the motor for the CB450 K0 Tach our max steps is 472 (half of 945).
// these 180 degrees will represent a max of 11000 rpms. Since our gearing ratio is 1/7th of the motor that is a max of 11000/7= 1571 rpms of the cable in these 180 degrees.
// so our conversion factor is (472/1571). we multiply our RPM's (rpm_val) by (472/1571) to get the number of steps to subtract from our zero point.
// ..................its not as confusing as I'm explaining. I'm just doing a horrible job.
nextPos = 944-((rpm_val*472)/1571);
motor1.setPosition(nextPos); // call needle to rotate to given potition
nextPos = 0; // clear nextPos value to zero for next calculation

}

}

I did. Thanks! That's really a special stepper.
The datasheet showed an X25 but You use an X27. Any significant difference between them?
Reading the data they tell about a certain "run in" procedure", forced applied in axial direction as well radial direction. Did You read and do that?

Posting code in code tags is well done. However, using Ctrl + T, or "autoformat in the IDE before pasting adds quite some clearance. I'll try to fix it here and take a second look.

Inspecting the code I dislike much of the use of float for variabels being sociable with micros(), or millis(). The normal use is unsigned long.
Using float You will loose bits when time has increased above the level of float. Adding "1" to a large float will not increase the variable.

X27 is just a newer version of X25, I am told they're identical.

The run in procedure I suppose I did by means of attaching a needle (which was a pain in the hoop!).

I don't follow your comment on the floats, but surely that shouldn't matter - the code does work, but for whatever reason it defaults to the wrong position - and seems to be working backwards.

Fine, the same stepper.
Sometimes it's very important to know how a variable is represented, down to the bit. Unsigned long is the type that has the largest range. Float has a lot lower range and cost execution power for no good. I haven't done any calculations but the fact that one error is not reaching the end position gives me this my idea. Suppose the stepper "runs out the scale". Then a not working return is no surprise.
Have You tried making the stepper go from zero, up only a little bit, and then back to zero?

Believe me, I tried! I can the following sequence happen at start up:

  1. Go to zero
  2. Go to any position I choose between 0 and 315 degrees

What I would like to happen is:
3) Go back to zero

And that's just the start up sequence, then I would like to start with my code for displaying the actual speed. I would expect the code I originally posted to do exactly that - the step 3 Go back to zero I understand should be contained within the loop - the initial position is defined as nextPos=0, I don't understand why the motor does not update to this position though...

To make sure I've got it right.... What about this:

  1. Go to zero
  2. Go to position 50 degrees
  3. Go to zero

I deliberately avoid the next question. One thing at the time......

Delay will not help you - you need to call update for the motor to move.

Try using millis and set position to 944 in setup. Call update in loop and check millis also. When it is greater than 10000, set position to zero.

Ok, I got it working - I did a bit more reading and what I needed was a blocking operation,so that it forces the motor to go there. The motor.update operation needs to be looped, so cannot be part of the setup. The code below does what I want, with a small asterisk.

The issue is that when I power up and the motor is already in position zero, it tries to rotate anyway, making a buzzing noise (and causing unnecessary wear and tear) - is there a way to incorporate an if statement there that if the motor is in a zero position already, it can ignore the command?

//----------------------------------------------------------------------
// https://github.com/clearwater/SwitecX25
//
// This is an example of using the SwitchX25 library.
// It zero's the motor, sets the position to mid-range
// and waits for serial input to indicate new motor positions.
//
// Open the serial monitor and try entering values
// between 0 and 944.
//
// Note that the maximum speed of the motor will be determined
// by how frequently you call update().  If you put a big slow
// serial.println() call in the loop below, the motor will move
// very slowly!
//----------------------------------------------------------------------

#include <SwitecX25.h> // adds the library

// standard X25.168 range 315 degrees at 1/3 degree steps
#define STEPS (315*3) // defines the number of steps 

// For motors connected to digital pins 4,5,6,7
SwitecX25 motor1(STEPS, 4, 5, 6, 7); // defines the pin connection

void setup(void)
{
  // run the motor against the stops
  motor1.zero(); // runs the motor to the zero position
  // start moving towards the center of the range
  motor1.setPosition(STEPS);  // defines a position we want the motor to run
  motor1.updateBlocking(); // runs the motor to a predefned position
  motor1.setPosition(0); // defines a position that we want the motor to run, but it will go there only when update is called out

  Serial.begin(9600);
  Serial.print("Enter a step position from 0 through ");
  Serial.print(STEPS - 1);
  Serial.println(".");
}

void loop(void)
{
  // the motor only moves when you call update
  motor1.update();
}

You need some kind of swich, preferably optical, going active when stepper is at zero.

Hi,

They usually have a physical post for the meter needle to knock up against, that is then zero.

If you watch some automotive meters when you turn the ignition ON, the meters do a sweep forward to max, then back to min.
They are over driven position wise to ensure the zero stop.
Their construction, which is different to a "normal" stepper allows them to be "momentarily" overload.
Using physical stops, allows for FAST intialisation.

Tom.... :grinning: :+1: :coffee: :australia:

As long as the stepper torqe doesn't damage the pointer or the axle it will work. New strategy to me.

Hi,

This may help, nice little item.
https://diyodemag.com/education/what_the_tech_gauge_stepper
From a great Australian, free online magazine.

Tom... :grinning: :+1: :coffee: :australia:

This topic can now be marked as closed - thanks a lot Arduino community.

I did a lot of testing and figured it out - turns out, I need to use blocking operations, but not the reset operations. That way, if the motor already is at 0, it doesn't try to go there.

Here is the section of the code that I was after:

  motor1.setPosition(0);
  motor1.updateBlocking();
  motor1.setPosition(STEPS);
  motor1.updateBlocking();
  motor1.setPosition(0);

Maybe it will help someone one day!

Thanks,

Filip

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.