Best practice for controlling servos without library

If for whatever reasons you don't want to use libraries, what is the most robust go-to practice of controlling servos? The "industry standard" so to say. Right now I'm using "millis()" for counting control signal period and "delayMicroseconds()" to measure pulse width. I also experimented with using "micros()" for pulse width, but I've read that this method is too fragile for precise control.

If you would need to create a code from scratch for a servo control, how would you approach it?

What benefit do you see in controlling/monitoring Servo by your own discrete codes?

Pure curiosity and it helps me understanding things better, it's for a personal project.

Don't believe everything you read.

Just one servo?

Hmm, pure.

You may know that you can read all the source code for anything that ends up in a project you compile, and it is all on you machine somewhere.

If a real programmer were to "create a code from scratch for a servo control", she would probably use counter/timer peripherals and end up with something that might look quite a bit like the servo library code.

So read it, marvel and learn.

a7

2 Likes

I would pick a servo library, look at its source code, see how it did it, and go from there.

1 Like

yeah, just one

The answer is quite dependent on what else the code must do/tolerate. Interrupts for serial, for millis(), and anything else are quite likely to disrupt even the most carefully crafted pulse generating code; using a hardware timer might help, but is again dependent upon approach.

1 Like

If you need "precise control" as you mentioned then I would use hardware timers but as @alto777 mentioned you will probably just wind up recreating the servo library.

1 Like

Sounds to me like you're trying to avoid incorporating code written by others, probably for legal reasons?

1. Make a semicircle dial (0 deg to 180 deg) with 10 degrees intervals and mark them properly. Position the shaft of te Servo (assume: SG90) at the center of the semicircle.


2. Create a sketch to generate a 50 Hz Fast PWM signal using TC1 of Arduino UNO.

3. Connect a Pot with UNO to manually control the duty cycle of the PWM signal of Step-2.

4. Connect a scope to measure the duty cycle of the PWM signal. You can also compute it.

5. Uplaod the sketch of Step-2.

6. Adjust the duty cycle of the PWM signal so that the horn of Servo (Assume: SG90) aligns with 0 degree of the dial.

7. Repeat Step-6 for each 10 degree incremental advance of the horn of the Servo.

8. Record and post Rotational_degree vs PWM_Duty_Cycle of the Servo.

9. Finally, convert the record of Step-8 into Programming Codes so that you can control the shaft position using a software command rather than using the hardware Pot.

You can e.g. use Timer1 from the Atmega328 (Arduino Uno) to generate a servo signal. It will output on Arduino Uno pin 9.

Here is the example sketch:

#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
  DDRB |= (1 << PB1); // Set PB1 as output (pin 9 on your Arduino Uno)
  TCCR1A |= (1 << COM1A1) | (1 << WGM11); // Fast PWM, non-inverting mode
  TCCR1B |= (1 << WGM13) | (1 << WGM12) | (1 << CS11); // Fast PWM, prescaler = 8
  ICR1 = 39999; //20ms PWM period
  while (1) {
    OCR1A = 1999; // Set position to 0 degrees
    _delay_ms(1000);
    OCR1A = 2999; // Set position to 90 degrees
    _delay_ms(1000);
    OCR1A = 3999; // Set position to 180 degrees
    _delay_ms(1000);
  }
}

It will of course occupy Timer1, so you cannot use it for something else.

The Atmega328PB is a lot more friendly for this, as it has two more 16 bit timers, that you could set up this way.

There is no Ardino style setup() and loop() functions.

Good eye. Your point?

There doesn't have to be because it uses the higher level main function.
Everything up to the while statement is the setup function in the normal IDE, and everything after the while statement is the equivalent of the loop function.

However, the main function is supposed to return a int variable and no such variable is set to return anything. It then probably returns a null which is why you can get away with this error.

Here you go.
And....from 256 to 656 bytes

void setup() {
  DDRB |= (1 << PB1); // Set PB1 as output (pin 9 on your Arduino Uno)
  TCCR1A = (1 << COM1A1) | (1 << WGM11); // Fast PWM, non-inverting mode
  TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); // Fast PWM, prescaler = 8
  ICR1 = 39999; //20ms PWM period
}

void loop() {
  OCR1A = 1999; // Set position to 0 degrees
  delay(1000);
  OCR1A = 2999; // Set position to 90 degrees
  delay(1000);
  OCR1A = 3999; // Set position to 180 degrees
  delay(1000);
}

And the 3x growth is because? I would expect that for one, the millisecond timer is now running, but I doubt that is all; what else has been stood up behind the Arduino curtain?

I would do in the following way to comply with #12 using UNOR3.

#define OC1A 9
volatile bool flagOcA = false;

void setup()
{
  Serial.begin(9600);
  pinMode(OC1A, OUTPUT); //Ch-A
  //-------------------------------
  TCCR1A = 0x00;   //reset
  TCCR1B = 0x00;   //TC1 reset and OFF
  //fOC1A/B = clckSys/(N*(1+ICR1); Mode-14 FPWM; OCR1A controls duty cycle
  // 50 Hz = 16000000/(8*(1+ICR1) N=8, (8,64,256,1024)==> ICR1 = 39999
  TCCR1A |= bit(WGM11); //Mode-14 Fast PWM
  TCCR1B |= bit(WGM13) | bit(WGM12);    //Mode-14 Fast PWM
  bitSet(TCCR1A, COM1A1);
  bitClear(TCCR1A, COM1A0);  //Non-invert: HIGH-LOW
  ICR1 = 39999;   // TOP for 50 Hz frequnecy
  OCR1A = 4000;  //= 10% duty cycle - initial
  TCNT1 = 0;
  TIMSK1 |= (1 << OCIE1A);  //TC1 Ch-A output compare interrupt enable
  TCCR1B |= bit(CS11);//TC1 start with prescaler, N = 8;
}

void loop() //codes to adjust duty cycle using Pot
{
  if (flagOcA == true)
  {
    int y = analogRead(A0);  //Pot is with A0-pin
    OCR1A = map(y, 0, 1023, 0, 39999); //0% to 100%
    flagOcA = false;
    delay(200); //for smooth control of duty cycle
  }
}

ISR(TIMER1_COMPA_vect)
{
  flagOcA = true;
}

Why not starting the Timer1 (TC1) after the initialization of all parameters?

TCCR1B = (1 << WGM13) | (1 << WGM12);
 ICR1 = 39999; //20ms PWM period
TCCR1B |= (1 << CS11); // start TC1 for Fast PWM, prescaler = 8