Problem creating sinusoidal pulse with servo motor

Hi, The base of my project is essentially a servo motor attached to a tongue depressor so that the tongue depressor sweeps out various angles. I want to modify the Sweep sketch to make the turnaround going back and forth less abrupt, so that the tongue depressor slows down before turning and speeds back up, and slows down before it turns on the other side. So instead of the movement modeling a square wave, I want it to model a sine wave. Any ideas?

Sounds like a perfect fit for the sin() function...

https://www.arduino.cc/en/Reference/Sin

avr_fred:
Sounds like a perfect fit for the sin() function…

https://www.arduino.cc/en/Reference/Sin

Yeah, I’m using that already - my issue is getting it to follow a sine function the whole way through. It works for the first half of the sweep then it returns to its initial position abruptly. I want it to be a smooth oscillation the entire time. Here’s my sketch so far:

#include <Servo.h>

Servo myservo; //my servo

int pos = 0;
int per = PI*pos/100;

void setup() {
myservo.attach(9);
}

void loop() {
for(pos = 0; pos <= 100; pos ++)
{
float pos_sin = sin(per);
myservo.write(40pos_sin);
delay(15);
}
for(pos = 100; pos >=0; pos --)
{
float pos_sin = sin(per);
myservo.write(40
pos_sin);
}
}

The part that isn’t working is the pos = 100 to pos = 0.

The part that isn’t working is the pos = 100 to pos = 0.

Actually, 0 to 100 does not work either, at least in the code you posted. Further, you don’t need the code that counts from 100 to 0. You missed an important detail of how the sin() function works.

When you create a variable like this: “int per = PI*pos/100;” it does not create a value that changes as “pos” changes. It is called a “compile time” declare. The compiler evaluates the expression and since it knows PI and 100, those get used but what about “pos”? You declared it in the line above as “int pos = 0;”. So, all the compiler knows is that “pos” is zero and so “per” becomes zero.

When you program starts, pos equals zero. Since you never write to pos in your code, it exists forever in your program as zero, it never changes in what is called run time. So, you have things that can start out as different values at compile time and be assigned a different value at run time. That’s the behavior you want so you have to calculate the value every time “pos” changes, like this:

for (pos = 0; pos <= 100; pos ++)
{
  per = pos / 100;
  float pos_sin = sin(PI*per);
  myservo.write(40 * pos_sin);
  delay(15);
}

But, this still creates an output value of zero. Why? Because the variables “pos” and “per” are declared as type “int”. If you divide 1 by 100 you get 0.01. But an integer (int) cannot hold a value of 0.01, only whole numbers from -32768 to 32767. So, those variables needs to be type “float”. Knowing now what we’ve learned so far, let’s post your program with those corrections.

#include <Servo.h>

Servo myservo; //my servo

int pos;
float per;
float pos_sin;

void setup() {
  myservo.attach(9);
}
void loop() {
  for (pos = 0; pos <= 100; pos ++)
  {
    per = PI * pos / 100;
    pos_sin = sin(per);
    myservo.write(40 * pos_sin);
    delay(15);
  }
}

While this create something other than a zero value output value to your servo, I doubt it will be what you what. The value required by the call to myservo.write() needs to be in the range of 0 to 180. You’ll end up with 0 to 40. Maybe that’s right, I don’t know but I suspect 90 is what you want.

Getting back to my earlier comment about not needing the 100 to 0 code is due to the fact that there are two pi radians in a circle. If you call the sin() function with that full range, you will get a full positive and negative cycle rather than breaking into two halves as you did with the two for{} loops. If you change your code to make 200 positions out of the full range, you only need one for{} loop:

#include <Servo.h>

Servo myservo; //my servo

int pos;
float per;
float pos_sin;

void setup() {
  myservo.attach(9);
}
void loop() {
  for (pos = 0; pos <= 200; pos ++)
  {
    per = 2 * PI * pos / 200;
    pos_sin = sin(per);
    myservo.write(90 + (90 * pos_sin));
    delay(15);
  }
}

The other tweak there is the “myservo.write(90 + (90 * pos_sin));” which just translates the output to match the range of the servo.

sin() = -1  0  1
servo =  0 90 180

Hi,
I have edited @avr_fred’s code with Serial prints
If you open the plotter and set it to 115200, you will see the plot of the servo signal.

#include <Servo.h>

Servo myservo; //my servo

int pos;
float per;
float pos_sin;
int servo_out;

void setup() {
  Serial.begin(115200);
  myservo.attach(9);
}
void loop() {
  for (pos = 0; pos <= 200; pos ++)
  {
    per = 2 * PI * pos / 200;
    pos_sin = sin(per);
    servo_out = 90 + (90 * pos_sin);
    myservo.write(servo_out);
    Serial.println(servo_out);
    delay(15);
  }
}

Nice job @avr_fred.

Tom… :slight_smile:

avr_fred:
Actually, 0 to 100 does not work either, at least in the code you posted. Further, you don’t need the code that counts from 100 to 0. You missed an important detail of how the sin() function works.

When you create a variable like this: “int per = PI*pos/100;” it does not create a value that changes as “pos” changes. It is called a “compile time” declare. The compiler evaluates the expression and since it knows PI and 100, those get used but what about “pos”? You declared it in the line above as “int pos = 0;”. So, all the compiler knows is that “pos” is zero and so “per” becomes zero.

When you program starts, pos equals zero. Since you never write to pos in your code, it exists forever in your program as zero, it never changes in what is called run time. So, you have things that can start out as different values at compile time and be assigned a different value at run time. That’s the behavior you want so you have to calculate the value every time “pos” changes, like this:

for (pos = 0; pos <= 100; pos ++)

{
  per = pos / 100;
  float pos_sin = sin(PI*per);
  myservo.write(40 * pos_sin);
  delay(15);
}



But, this still creates an output value of zero. Why? Because the variables "pos" and "per" are declared as type "int". If you divide 1 by 100 you get 0.01. But an integer (int) cannot hold a value of 0.01, only whole numbers from -32768 to 32767. So, those variables needs to be type "float". Knowing now what we've learned so far, let's post your program with those corrections.



#include <Servo.h>

Servo myservo; //my servo

int pos;
float per;
float pos_sin;

void setup() {
  myservo.attach(9);
}
void loop() {
  for (pos = 0; pos <= 100; pos ++)
  {
    per = PI * pos / 100;
    pos_sin = sin(per);
    myservo.write(40 * pos_sin);
    delay(15);
  }
}



While this create [u]something[/u] other than a zero value output value to your servo, I doubt it will be what you what. The value required by the call to myservo.write() needs to be in the range of 0 to 180. You'll end up with 0 to 40. Maybe that's right, I don't know but I suspect 90 is what you want.

Getting back to my earlier comment about not needing the 100 to 0 code is due to the fact that there are two pi radians in a circle. If you call the sin() function with that full range, you will get a full positive and negative cycle rather than breaking into two halves as you did with the two for{} loops. If you change your code to make 200 positions out of the full range, you only need one for{} loop:



#include <Servo.h>

Servo myservo; //my servo

int pos;
float per;
float pos_sin;

void setup() {
  myservo.attach(9);
}
void loop() {
  for (pos = 0; pos <= 200; pos ++)
  {
    per = 2 * PI * pos / 200;
    pos_sin = sin(per);
    myservo.write(90 + (90 * pos_sin));
    delay(15);
  }
}



The other tweak there is the "myservo.write(90 + (90 * pos_sin));" which just translates the output to match the range of the servo.



sin() = -1  0  1
servo =  0 90 180

The ‘per’ variable then is only for the increment of the motion of the servo in terms of angular displacement, right?

Also, in the final tweak of “90 + (90 * pos_sin)”, the initial 90 (before the addition) is present only because we want 90 degrees to be the base position of the servo?