Tutorial: Arduino Motor/Stepper/Servo Shield – Part 2: Timed Servo Moves

You have decided: More than 52% voted in Part 1 that the next topic should be Timed Servo Moves. So here we go :-).

This is about how to move the servos over time, instead of moving it to the given position as fast as possible. I’m using a linear approach here: moving the servos linearly over time.

Moving Servo Motors

Moving Servo Motors

The Principle

Say I want to move the servo position from 0 to 200. But instead of doing this as fast as possible, I want to do this in 1 second. The typical servo PWM period is 20 ms (50 Hz), so I can change or increment the position every 20 ms, such as starting from time t. That would make 200/50=4 steps per 20 ms.

  1. t+0: position 0
  2. t+20 ms: position 4
  3. t+40 ms: position 8
  4. t+1000 ms: position 200

One approach would be to do this in a loop, simply waiting for 20 ms, then move to the new position. But this would be a waste of microcontroller time. Instead, I’m using a periodic timer interrupts with Triggers.

Trigger Component

The Trigger component implements an infrastructure to call functions at a given time in the future. The functionality is available with the Trigger component available on GitHub. So I’m adding the Trigger component to my ‘Servo’ project I have created in my earlier post:

Trigger Component

Trigger Component

The Trigger component maintains a list of ‘trigger callbacks’, and if they expire, the callback gets called. The data structure in the Trigger component is simple: a time (in ticks) value, plus the function pointer for the callback:

typedef struct TriggerDesc {
  word triggerTime;                          /*!< trigger 'time' in ticks */
  void (*callback)(void);                    /*!< callback function */
} TriggerDesc;

static TriggerDesc TriggerList[2];           /*!< Array of triggers */

All the triggers are in an array of such Trigger descriptors.

With AddTrigger() a trigger gets added to the list:

void TRG1_AddTrigger(byte trigger, word incTicks, void (*callback)(void))
{
  EnterCritical();
  TriggerList[trigger].triggerTime = incTicks;
  TriggerList[trigger].callback = callback;
  ExitCritical();
}

The first argument is a trigger ID which is an index into the array. To keep things simple, no access-out-of-bound check is performed. But as AddTrigger() could be called from an interrupt, the code is guarded as critical section.

The AddTick() method needs to be called with a given frequency (e.g. every 10 ms). It decrements the triggerTime tick counter, and if it is expiring, it calls the call back function pointers in CheckCallbacks() if they have expired:

void TRG1_AddTick(void)
{
  /* This method is usually called from a periodic timer interrupt! */
  uint8_t i;

  for(i=0;i
    EnterCritical();
    if (TriggerList[i].triggerTime!=0) { /* prevent underflow */
      TriggerList[i].triggerTime--;
    }
    ExitCritical();
  } /* for */
  while(CheckCallbacks()) {
    /* while we have callbacks, re-iterate the list as this may have added new triggers at the current time */
  }
}

The frequency of the trigger tick in milliseconds is specified in the component properties:

Trigger TickPeriodMs

Trigger TickPeriodMs

Timer Interrupt

In case I have an RTOS which already has an event or callback for its tick timer, then I can use the ‘RTOS’ setting, and I do not need a dedicated timer interrupt. As I’m using a bare-metal (no RTOS) project here, I adding a TimerInt component to my project with a new TimerUnit_LDD (I cannot re-use the one for the Servo PWM):

New TimerUnit_LDD

New TimerUnit_LDD

I keep the default of 10 ms trigger tick time, so I configure the timer to call the interrupt handler every 10 ms:

Timer configured for 10 ms

Timer configured for 10 ms

Adding Ticks from the Timer Interrupt

Time to generate code:

Generating Code

Generating Code

Then double-click on the OnInterrupt() event:

OnInterrupt Event

OnInterrupt Event

This jumps to the event hook method inside Events.c:

OnInterrupt in Events

OnInterrupt in Events

Now I add a call to AddTick() to the timer interrupt event. One way is to use drag&drop instead of writing the code:

drag and drop of method name

drag and drop of method name

This adds my method call:

TRG1_AddTick() added

TRG1_AddTick() added

Timed Moves in the Servo

For each Servo, I enable the ‘Timed Move’ feature and link it to the Trigger module:

Configured Timed Move for Servos

Configured Timed Move for Servos

Each of the servos is using its own Trigger ID. So I need to add them to the trigger configuration. For this I need to edit the Trigger Events string list:

Configuring Trigger Event ID List

Configuring Trigger Event ID List

The trigger ID’s are named after the component names. As I’m using SERVO1 and SERVO2, I add them to the list, one item per line:

Added Servo Trigger IDs

Added Servo Trigger IDs

Time to generate the Processor Expert code so my Trigger ID’s get created in TRG1.h. The triggers have the component prefix (TRG1_) added:

Trigger IDs Created

Trigger IDs Created

I’m going to use these IDs in my source code to move the servos.

Moving the Servo

With the ‘Timed Move’ enabled, the Servo component offers a MovePos() method:

MovePos() Servo Method

MovePos() Servo Method

What it does in MovePos() to set up the data structure with information about how many steps it has to do, what is the final (target) PWM and how much it has to change the PWM for each step:

/*!
 * \brief Moves a servo to a given position over a given time.
 * \param pos Position where to move the servo, must be in servo boundaries (in us)
 * \param timeMs Time to be used for the move, in milliseconds
**/
void SERVO1_MovePos(byte pos, int16_t timeMs)
{
  /* calculate required steps*/
  Servo.cnt = timeMs/SERVO1_MOVE_TRIGGER_MS;
  if(Servo.cnt < 1) {
    Servo.cnt = 1; /* avoid division by zero */
  }
  Servo.targetPWMus = SERVO1_POS8_TO_PWM(pos);
  /* calculate increment / decrement per step */
  Servo.pwmDeltaPerStep=(int16_t)((Servo.targetPWMus-Servo.currPWMus)/Servo.cnt);
  DoSteps();
}

The real move is done in DoSteps(): it changes the PWM, prepares for the next step and sets up the trigger:

static void DoSteps(void)
{
  /* Write new duty cycle */
#if SERVO1_INVERTED_PWM
  Pwm1_SetDutyUS((uint16_t)(SERVO1_PWM_PERIOD_US-(Servo.currPWMus+Servo.pwmDeltaPerStep)));
#else
  Pwm1_SetDutyUS((uint16_t)(Servo.currPWMus+Servo.pwmDeltaPerStep));
#endif
  /* update current duty cycle value */
  Servo.currPWMus += Servo.pwmDeltaPerStep;
  Servo.cnt--;
  /* if necessary, call next step by trigger */
  if(Servo.cnt>0) {
    TRG1_AddTrigger(TRG1_SERVO1, SERVO1_MOVE_TRIGGER_TICKS, DoSteps);
  }
}

This means that the trigger will call DoSteps() through the Trigger periodic timer interrupt every SERVO1_MOVE_TRIGGER_TICKS which is set to 20 ms. As a result the servo will move during the given time as specified by MovePos() to the new position.

Example Code

With this, the following source will move the servo from the current position to the position 100, taking one second (1000 ms):

SERVO1_MovePos(200, 1000);

So here is the extended example code: it first moves trough the servo position, and then uses ‘timed’ moves:

void APP_Run(void) {
  uint16_t pos;

  /* move each servo from 0 to 255 */
  for(pos=0;pos<256;pos++) {
    SERVO1_SetPos(pos);
    SERVO2_SetPos(pos);
    WAIT1_Waitms(50);
  }
  /* Use timed move */
  for(;;) {
    SERVO1_MovePos(0, 4000);
    SERVO2_MovePos(0, 1000);
    WAIT1_Waitms(8000); /* wait some time */
    SERVO1_MovePos(150, 2000);
    SERVO2_MovePos(150, 500);
    WAIT1_Waitms(4000); /* wait some time */
  }
}

The code is using WAIT_Waitms() between the moves to give the servos enough time to move.

💡 The latest version of the Servo component sources on GitHub features a method IsMoving() which returns TRUE if the trigger is still working on moving the servo.

The project discussed here is available on GitHub here.

List of Tutorials

  1. Tutorial: Arduino Motor/Stepper/Servo Shield – Part 1: Servos
  2. Tutorial: Arduino Motor/Stepper/Servo Shield – Part 2: Timed Servo Moves
  3. Tutorial: Arduino Motor/Stepper/Servo Shield – Part 3: 74HCT595 Shift Register

Happy Moving 🙂

What’s Next?

Now it is up to you to vote what should be the next topic 🙂 :

6 thoughts on “Tutorial: Arduino Motor/Stepper/Servo Shield – Part 2: Timed Servo Moves

  1. Erich, great post!, i can’t wait to test servos in my board. I must note there is an error in the webpage. The text format is a mess. Your website has been a great help for me. Thank you!

    Like

    • Hi Juan,
      Thanks for pointin to the format mess. WordPress does this sometimes to me if I copy-paste source text. I need to be more careful the next time. It should look ok now.
      Thanks!

      Like

  2. Pingback: Tutorial: Arduino Motor/Stepper/Servo Shield – Part 1: Servos | MCU on Eclipse

  3. Pingback: Tutorial: Arduino Motor/Stepper/Servo Shield – Part 3: 74HCT595 Shift Register | MCU on Eclipse

  4. This tutorial is great for a periodic interrupt that has a constant rate, but my project requires a programmable interrupt. For my project I read some external interfaces, and those determine how long I need to assert an output. My though was to use the TimerInt component, and then configure the different modes for the different timing ranges. Then I can pole in inputs, set the mode, set the period, enable the component (start the timer), enable the event and start the output. In the interrupt event I could turn off my output and disable the component. (Hope that wasn’t confusing, I’m open to suggestions.)

    I am of course able to add the component to the project, but once I setup the “Interrupt period” to be a list of values, configuring each of the 15 modes to be one of the ranges. Once I click okay, I get an error. “ERROR: Inherited component does not support this feature: Runtime setting type list.”

    Do you have a tutorial that explains how to configure the TimerInt component and the TimerUnit_LDD referenced component for dynamic interrupt periods?

    Thanks in advance!

    Like

What do you think?

This site uses Akismet to reduce spam. Learn how your comment data is processed.