Worked example: Servo controlled by potentiometer

This is the worked example we looked at in today’s class of a potentiometer controlling the angle of a servo motor (via dsPIC30F4011 microcontroller) which has a paper arrow attached to it.

      • The potentiometer voltage, which varies between 0V and 5V is connected to AN0 (pin 2).
      • The servo angle is controlled by a PWM signal from PWM1H (pin 37). The period of the PWM waveform is 20ms and the expected range of pulse widths is approximately 1-2ms.
      • An LED connected to RDO (pin 23) is turned on when the potentiometer voltage exceeds 2.5V and turned off otherwise.
      • Each time the potentiometer voltage is read (as a number between 0 and 1023) the value is transmitted serially via the UART (using printf).
//
// dsPIC30F4011 potentiometer and servo example
// Written by Ted Burke
// Last updated 2-10-2012
//

#include <xc.h>
#include <libpic30.h>
#include <stdio.h>

// Configuration settings
_FOSC(CSW_FSCM_OFF & FRC_PLL16); // Fosc=16x7.5MHz, Fcy=30MHz
_FWDT(WDT_OFF);                  // Watchdog timer off
_FBORPOR(MCLR_DIS);              // Disable reset pin

// Function prototypes
unsigned int read_analog_channel(int n);

int main()
{
	int voltage = 0;

	// Make all port D pins outputs
	TRISD = 0;

	// Configure AN0-AN8 as analog inputs
	ADCON3bits.ADCS = 15;  // Tad = 266ns, conversion time is 12*Tad
	ADCON1bits.ADON = 1;   // Turn ADC ON

	// Configure PWM for free running mode
	//
	//   PWM period = Tcy * prescale * PTPER = 0.33ns * 64 * PTPER
	//   PWM pulse width = (Tcy/2) * prescale * PDCx
	//
	PWMCON1 = 0x00FF;     // Enable all PWM pairs in complementary mode
	PTCONbits.PTCKPS = 3; // prescale=1:64 (0=1:1, 1=1:4, 2=1:16, 3=1:64)
	PTPER = 9375;         // 20ms PWM period (15-bit period value)
	PDC1 = 1406;          // 1.5ms pulse width on PWM channel 1
	PTCONbits.PTEN = 1;   // Enable PWM time base

	// Setup UART
	U1BRG = 48;            // 38400 baud @ 30 MIPS
	U1MODEbits.UARTEN = 1; // Enable UART

	while(1)
	{
		// Read analog voltage from AN0 (pin 2)
		// The number returned (between 0 and 1023)
		// represents a voltage in the range 0-5V.
		voltage = read_analog_channel(0);

		// If the voltage is greater than 2.5V
		// turn on the LED on RD0 (pin 23),
		// otherwise turn it off.
		if (voltage < 512) _LATD0 = 0;
		else _LATD0 = 1;

		// Display the voltage reading on screen
		printf("Voltage = %d\n", voltage);

		// Calculate and update the servo angle
		// based on potentiometer voltage on AN0.
		// In this case we want voltage values
		// between 0 and 1023 to be mapped onto
		// PDC1 values between 600 and 1800.
		PDC1 = 600 + (1800-600)*(voltage/1023.0);

		// 100ms delay
		__delay32(3000000);
	}

	return 0;
}

// This function reads a single sample from the specified
// analog input. It should take less than 5us when the
// microcontroller is running at 30 MIPS.
// The dsPIC30F4011 has a 10-bit ADC, so the value
// returned is between 0 and 1023 inclusive.
unsigned int read_analog_channel(int channel)
{
	ADCHS = channel;          // Select the requested channel
	ADCON1bits.SAMP = 1;      // Start sampling
	__delay32(30);            // 1us delay @ 30 MIPS
	ADCON1bits.SAMP = 0;      // Start Converting
	while (!ADCON1bits.DONE); // Should take 12 * Tad = 3.2us
	return ADCBUF0;
}

The program above is for Microchip’s XC16 compiler. I’m not using MPLAB, so this is the simple script I use to build it (assuming you have XC16 installed, just save this script as “build.bat” in the same folder as “main.c” and run it in a command window by typing “build”):

REM build.bat - commands to build a hex file from main.c

xc16-gcc main.c -mcpu=30F4011 -Wl,--script=p30F4011.gld
if errorlevel 0 xc16-bin2hex a.out

The circuit diagram is shown below:

Here’s a photo of the actual circuit (courtesy of Shane Conlan – thanks Shane!):

Advertisements
This entry was posted in Uncategorized and tagged , , , , , . Bookmark the permalink.

2 Responses to Worked example: Servo controlled by potentiometer

  1. gapguillen says:

    Hi Ted, greetings from Mexico City and I apologize for my English. I like this project and I used the compiler XC16 and MPLAB X IDE and I made one modification to PDC1 for focus my HITEC servo.
    My servo is working very well and I would like to modify your code to take control of two servos with two potentiometers and my questions are: Can I do it or not? … What should I modify at your code to take control of two servos in PWM1H and PWM2H? … Have a nice day

    • batchloaf says:

      Hi gapguillen,

      There’s not much to it really – you just need to add in a few extra lines to read two channels instead of one and to control PDC2 as well as PDC1. Here’s a modified version of the main function that should do it:

      int main()
      {
          int voltage1 = 0;
          int voltage2 = 0;
       
          // Make all port D pins outputs
          TRISD = 0;
       
          // Configure AN0-AN8 as analog inputs
          ADCON3bits.ADCS = 15;  // Tad = 266ns, conversion time is 12*Tad
          ADCON1bits.ADON = 1;   // Turn ADC ON
       
          // Configure PWM for free running mode
          //
          //   PWM period = Tcy * prescale * PTPER = 0.33ns * 64 * PTPER
          //   PWM pulse width = (Tcy/2) * prescale * PDCx
          //
          PWMCON1 = 0x00FF;     // Enable all PWM pairs in complementary mode
          PTCONbits.PTCKPS = 3; // prescale=1:64 (0=1:1, 1=1:4, 2=1:16, 3=1:64)
          PTPER = 9375;         // 20ms PWM period (15-bit period value)
          PDC1 = 1406;          // 1.5ms pulse width on PWM channel 1
          PDC2 = 1406;          // 1.5ms pulse width on PWM channel 2
          PTCONbits.PTEN = 1;   // Enable PWM time base
       
          // Setup UART
          U1BRG = 48;            // 38400 baud @ 30 MIPS
          U1MODEbits.UARTEN = 1; // Enable UART
       
          while(1)
          {
              // Read analog voltages from AN0 (pin 2)
              // and AN1 (pin 3).
              // Each number returned (between 0 and 1023)
              // represents a voltage in the range 0-5V.
              voltage1 = read_analog_channel(0);
              voltage2 = read_analog_channel(1);
       
              // If the voltage is greater than 2.5V
              // turn on the LED on RD0 (pin 23),
              // otherwise turn it off.
              if (voltage1 < 512) _LATD0 = 0;
              else _LATD0 = 1;
       
              // Display the voltage reading on screen
              printf("voltage1 = %d, voltage2 = %d\n", voltage1, voltage2);
       
              // Calculate and update the servo angle
              // based on potentiometer voltage on AN0.
              // In this case we want voltage values
              // between 0 and 1023 to be mapped onto
              // PDC1 values between 600 and 1800.
              PDC1 = 600 + (1800-600)*(voltage1/1023.0);
              PDC2 = 600 + (1800-600)*(voltage2/1023.0);
       
              // 100ms delay
              __delay32(3000000);
          }
       
          return 0;
      }
      

      Be warned though, I didn’t have time to test the above code so there may still be some bugs to be worked out!

      Hope that helps.
      Ted

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s