Servo Control in any GPIO
In this example CCS is used with TI compiler. The code will make possible to control a Servo position in any GPIO by using a Timer timeout interrupt. We use Timer5 in periodic mode.
Like any project remember to configure the system clock. In this case i use 80Mhz clock.
First we need to configure and enable the Timer. We set the timer to timeout at every 5uS in periodic mode and register the interrupt handler.
/* Timer setup*/void TimerBegin(){ /* * 200Khz - 5uS. (um servo precisa de um pulse de 1000uS a 2000uS ou 1mS a 2mS) * 200-400 */ //We set the load value so the timer interrupts each 1ms uint32_t Period; Period = 400; SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER5); SysCtlDelay(3); /* Configure the timer as periodic, by omission it's in count down mode. It counts from the load value to 0 and then resets back to the load value. REMEMBER: You need to configure the timer before setting the load and match */ TimerConfigure(TIMER5_BASE, TIMER_CFG_PERIODIC); TimerLoadSet(TIMER5_BASE, TIMER_A, Period -1); TimerIntRegister(TIMER5_BASE, TIMER_A, ServoInterrupt); /* Enable the timeout interrupt. In count down mode it's when the timer reaches 0 and resets back to load. In count up mode it's when the timer reaches load and resets back to 0. */ TimerIntEnable(TIMER5_BASE, TIMER_TIMA_TIMEOUT); TimerEnable(TIMER5_BASE, TIMER_A);}
Now before seing the interrupt, let's see some functions that attach the servo control into a pin and then another that changes the servo pos in a pin.
For that we some variables. I made it so you can only have 6 servos.
volatile uint32_t ServoCount = 0; uint32_t ServoBase[6]; uint32_t ServoPin[6]; uint32_t ServoPos[6]; uint32_t ServoPosTemp[6]; uint32_t ServoNumber = 0;
Then we have a function to attack a servo to a pin, called ServoAttach(). This simply enables the pin chosen as output and adds it to the variables.
int32_t ServoAttach(uint32_t peripheral, uint32_t base, uint32_t pin){ //Remember to check if you aren't controling more servos than the arrays can handle if(ServoNumber < 6){ //Enable the GPIO you want to use SysCtlPeripheralEnable(peripheral); SysCtlDelay(3); //Set pin as output GPIOPinTypeGPIOOutput(base,pin); //Save which GPIO and pin you want to use ServoBase[ServoNumber] = base; ServoPin[ServoNumber] = pin; //Increment variable so we know we have 1 more servo ServoNumber++; } else return -1; return 0;}
After that, we have ServoWrite(). It takes the value you want for the servo and the servo you want to change. The servo can be a value from 0-6. The servo can be a value from 0-200, being 0 the most left position and 200 the most right. The true value for the servo is from about 200-399 so you need to add 199 to the value given trough parameter. The value is then saved in the correct position of the array, ServoPos.
/* * This function changes the servopos * It takes values from 0 to 200 and converts them to the real values of 200-400. * It saves the position in a auxiliar array */ int32_t ServoWrite(uint32_t value, uint32_t pos){ //Check if the values are correct if(value > 200) return -1; //Increment 199 since the real values are from 200 to 400, not 0 to 200 value = value + 199; //Save value in array for the interrupt ServoPosTemp[pos] = value; return 0;}
Now finally, the interrupt handler.
Like all handlers, remember to clear the flags. The interrupt handler increments a counter variable that we will declare has global as follow:
void ServoInterrupt(){ //Remember to clear the interrupt flags uint32_t status=0; status = TimerIntStatus(TIMER5_BASE,true); TimerIntClear(TIMER5_BASE,status); /* * This increments the counter * The counter works like a timer in PWM mode in count up mode. * The value 4000 sets the wave period. * Then we have the "match" values that set the positive pulse width, * those are the values saved in the array ServoPos. The outputs start as HIGH and go to LOW * when the match value is reached */ ServoCount++; uint32_t i; if(ServoCount > 4000) ServoCount = 0; /* * This loads the next values into the ServoPos array * It needs to happen in the begining of a period */ for(i=0; i < ServoNumber; i++){ ServoPos[i] = ServoPosTemp[i]; } for(i=0; i < ServoNumber; i++){ if(ServoCount > ServoPos[i]) GPIOPinWrite(ServoBase[i],ServoPin[i], 0); else GPIOPinWrite(ServoBase[i],ServoPin[i], ServoPin[i]); }}
The counter is incremented until it reaches 4000. At 4001 it rolls back to 0. Why? Well the Servo needs a 50Hz signal, like a 50Hz PWM signal. Remember we have the timer calling the interrupt every 5uS? Well 1/50=0.02s, and 5uS*4000=0.02s. It kinda working like a timer in PWM, count up mode.
Now why would we need that? The Servo position depends on the positive pulse time, usually it's from 1mS to 2mS, 1mS is the most left and 2mS is the most right. With a interrupt every 5uS we have a resolution of 5uS, making it possible about 200 different positions.
Inside the interrupt we check the counter and the position we want for each servo in each pin. We start with the pin HIGH and then when the value is reached, like the match value in a timer, the pin goes to LOW.
By doing this we are creating a 50Hz PWM in the pins of our choice with a variable duty by just using 1 timer. And this way the pin used doesn't need to be connected to a timer or PWM module. It can be any GPIO pin
Then just apply all that in the main and there it is, control any Servo in any pin. You can increase if you want the array size to control more servos, but remember that will make the interrupt slower. Notice also that the outputs aren't syncronized, since the outputs are controlled with a for and not at the same time.
/* * This code was made in CCS v6 and using TivaWare. All from Texas Instruments * * The code was made to control servos in multiple pins with the help on 1 timer. * The timer interrupts every 5uS and increments a counter variable. This variable works as load value that sets the period. *For 50Hz frequency the value needs to be 4000 (5uS * 4000 = 0.2mS) * There's a array called ServoPos that works like a match value that controls the positive pulse width of the PWM. * * The arrays size are made so you can have 6 pins but you can change that. Remember that the more servos you use, *the more slow the interrupt is. * * The array ServoPosTemp is needed to save the next position to be loaded in the begining of the period of the PWM * * * Luís Afonso */#include <stdint.h>#include <stdbool.h>#include "stdlib.h"#include "inc/hw_ints.h"#include "inc/hw_memmap.h"#include "inc/hw_uart.h"#include "inc/hw_gpio.h"#include "inc/hw_timer.h"#include "inc/hw_types.h"#include "driverlib/pin_map.h"#include "driverlib/rom.h"#include "driverlib/rom_map.h"#include "driverlib/debug.h"#include "driverlib/systick.h"#include "driverlib/interrupt.h"#include "driverlib/sysctl.h"#include "driverlib/uart.h"#include "driverlib/udma.h"#include "driverlib/gpio.h"#include "driverlib/timer.h"#include <string.h>volatile uint32_t ServoCount = 0; uint32_t ServoBase[6]; uint32_t ServoPin[6]; uint32_t ServoPos[6]; uint32_t ServoPosTemp[6]; uint32_t ServoNumber = 0;void ServoInterrupt(){ //Remember to clear the interrupt flags uint32_t status=0; status = TimerIntStatus(TIMER5_BASE,true); TimerIntClear(TIMER5_BASE,status); /* * This increments the counter * The counter works like a timer in PWM mode in count up mode. * The value 4000 sets the wave period. * Then we have the "match" values that set the positive pulse width, * those are the values saved in the array ServoPos. The outputs start as HIGH and go to LOW * when the match value is reached */ ServoCount++; uint32_t i; if(ServoCount > 4000) ServoCount = 0; /* * This loads the next values into the ServoPos array * It needs to happen in the begining of a period */ for(i=0; i < ServoNumber; i++){ ServoPos[i] = ServoPosTemp[i]; } for(i=0; i < ServoNumber; i++){ if(ServoCount > ServoPos[i]) GPIOPinWrite(ServoBase[i],ServoPin[i], 0); else GPIOPinWrite(ServoBase[i],ServoPin[i], ServoPin[i]); }}/* * This function changes the servopos * It takes values from 0 to 200 and converts them to the real values of 200-400. * It saves the position in a auxiliar array */ int32_t ServoWrite(uint32_t value, uint32_t pos){ //Check if the values are correct if(value > 200) return -1; //Increment 199 since the real values are from 200 to 400, not 0 to 200 value = value + 199; //Save value in array for the interrupt ServoPosTemp[pos] = value; return 0;} int32_t ServoAttach(uint32_t peripheral, uint32_t base, uint32_t pin){ //Remember to check if you aren't controling more servos than the arrays can handle if(ServoNumber < 6){ //Enable the GPIO you want to use SysCtlPeripheralEnable(peripheral); SysCtlDelay(3); //Set pin as output GPIOPinTypeGPIOOutput(base,pin); //Save which GPIO and pin you want to use ServoBase[ServoNumber] = base; ServoPin[ServoNumber] = pin; //Increment variable so we know we have 1 more servo ServoNumber++; } else return -1; return 0;}/* Timer setup*/void TimerBegin(){ /* * 200Khz - 5uS. (um servo precisa de um pulse de 1000uS a 2000uS ou 1mS a 2mS) * 200-400 */ //We set the load value so the timer interrupts each 1ms uint32_t Period; Period = 400; SysCtlPeripheralEnable(SYSCTL_PERIPH_TIMER5); SysCtlDelay(3); /* Configure the timer as periodic, by omission it's in count down mode. It counts from the load value to 0 and then resets back to the load value. REMEMBER: You need to configure the timer before setting the load and match */ TimerConfigure(TIMER5_BASE, TIMER_CFG_PERIODIC); TimerLoadSet(TIMER5_BASE, TIMER_A, Period -1); TimerIntRegister(TIMER5_BASE, TIMER_A, ServoInterrupt); /* Enable the timeout interrupt. In count down mode it's when the timer reaches 0 and resets back to load. In count up mode it's when the timer reaches load and resets back to 0. */ TimerIntEnable(TIMER5_BASE, TIMER_TIMA_TIMEOUT); TimerEnable(TIMER5_BASE, TIMER_A);}int main(void) { SysCtlClockSet(SYSCTL_SYSDIV_2_5|SYSCTL_USE_PLL|SYSCTL_OSC_MAIN|SYSCTL_XTAL_16MHZ); ServoAttach(SYSCTL_PERIPH_GPIOF,GPIO_PORTF_BASE,GPIO_PIN_1); TimerBegin(); int i=0; while(1){ for(i=0; i < 200 ; i++){ ServoWrite(i,0); SysCtlDelay(300000); } ServoWrite(0,0); SysCtlDelay(3000000); } //return 0;}