I seem to vaguely remember MicroChip bringing out a PIC with specific hardware for feeding servos, or at least announcing it - but I've no idea what number it was, or if it ever went into production.
Follow along with the video below to see how to install our site as a web app on your home screen.
Note: This feature may not be available in some browsers.
Hi R,I have been watching this thread with interest..
There is another way to drive servo's with a pic. There is a pic with quite a few CCP modules.. But if I remember they only had three timers... I will check and see.
THEN!! I will make a module that you can call in oshonsoft... It might take a day or so. You problem may be getting hold of such silicone at the moment... pic18f23j13 has 7 CCP modules Which IS supported in the Oshonsoft pic18 IDE..
Hi N and R,I have been watching this thread with interest..
There is another way to drive servo's with a pic. There is a pic with quite a few CCP modules.. But if I remember they only had three timers... I will check and see.
THEN!! I will make a module that you can call in oshonsoft... It might take a day or so. You problem may be getting hold of such silicone at the moment... pic18f23j13 has 7 CCP modules Which IS supported in the Oshonsoft pic18 IDE..
Ian, if the chip has PPS then you can use 1 CCP module and switch it's output to the relevant pin.I have been watching this thread with interest..
There is another way to drive servo's with a pic. There is a pic with quite a few CCP modules.. But if I remember they only had three timers... I will check and see.
THEN!! I will make a module that you can call in oshonsoft... It might take a day or so. You problem may be getting hold of such silicone at the moment... pic18f23j13 has 7 CCP modules Which IS supported in the Oshonsoft pic18 IDE..
Hi All,Ian, if the chip has PPS then you can use 1 CCP module and switch it's output to the relevant pin.
You'll only need timer 1.
CCP1 tends to be remappable to PORTB. So you could do 8 outputs on that port.
Does it have to be an 18F pic?
Mike.
I did a similar thing a long time ago but had to write the port value on interrupt which caused jitter (1/2 cycles). With PPS, accuracy could be cycle perfect and any jitter eliminated.Once upon a long while ago, Me and Jpanhault set to doing a high precsion servo driver using the compare function of the CCP module rather than the PWM function.. With great success,I might add, we got better than 16bit precision. He was a radio controlled airplane enthusiast..
/*
* File: servo_pwm_test.c
* Author: Robert
*
* Created on 08 May 2022, 20:40
*/
#include <16F18446.h>
#DEVICE PIC16F18446 ICD=1
#USE DELAY(internal=32MHz)
#USE FAST_IO(A)
#USE FAST_IO(B)
#USE FAST_IO(C)
#include <stdio.h>
#include <stdlib.h>
unsigned int16 ticks; // Counter for use in clock interrupt
int1 flag_20mS;
int8 index;
unsigned int16 servo_times[8];
unsigned int16 time_now;
unsigned int16 time_set;
unsigned int8 shift_reg;
//
// This is only used to set the flag to restart a new cycle every so often.
//
#INT_TIMER1
void t1_interrupt_routine(void) {
//
// Approx. 60Hz
//
ticks = ticks + 1;
// Trigger new cycle every other tick,
// to get greater than 20mS sequence interval.
if(ticks > 1) {
ticks = 0;
flag_20mS = 1;
}
}
#INT_CCP1
void ccp1_interrupt_routine(void) {
//
// This is executed at each new compare
//
// Get the timer present count for reference
time_now = get_timer1();
if(index < 8) { // All servos already done?
// No, do next servo
// Increment index for next servo time value
index = index + 1;
shift_reg <<= 1; // Shift the bit left one place.
// shift_reg = shift_reg + shift reg would do the same.
output_c(shift_reg); // Write the shift register bits to the port
time_set = time_now + servo_times[index]; // And set the duration for this output
CCP_1 = time_set; // Write the compare time to CCP 1 register.
}
else {
output_c(0); // Turn off all servo bits.
// And do nothing with timing - the interrupt would be
// triggered again after a full counter cycle, in ~15mS
// Which again will just clear the outputs, unless a new
// sequence has been started by the 20mS flag.
}
}
/*
*
*/
int main(int argc, char** argv) {
set_tris_c(0); // Port C all outputs
setup_adc(ADC_OFF);
// OSC = 32 MHz.
// Timer source: Internal = osc / 4; 8 MHz.
// T1 = A 16 bit timer used as the timebase
// Divide by 8 prescale = 1MHz input clock
// With 16 bit count, overflow will occur approx. every 16mS, 60Hz.
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
// Without an output pin defined, just an interrupt from CCP
setup_ccp1(CCP_COMPARE_TOGGLE);
//
// With this timing, 1mS = 1000 counts;
// servo 1mS to 2mS = 1000 to 2000,
// with 1500 as mid point.
//
//
//
// For demonstration, I'm setting the servo times
// to various fixed values.
//
// Set them from your program as needed.
//
servo_times[0] = 1500; // Port bit 0, Mid position
servo_times[1] = 1100; // Port bit 1, Near min position
servo_times[2] = 1900; // Port bit 2, Near max position
servo_times[3] = 1300; // Port bit 3, Under mid position
servo_times[4] = 1700; // Port bit 4, Over mid position
servo_times[5] = 1200; // Port bit 5, etc
servo_times[6] = 1800; // Port bit 6
servo_times[7] = 1400; // Port bit 7
// And enable the interrupts for the hardware used
enable_interrupts(GLOBAL);
enable_interrupts(INT_TIMER1);
enable_interrupts(INT_CCP1);
// Main loop
for(;;) {
if(flag_20mS == 1) {
// Set up new cycle
index = 0;
shift_reg = 1; // Set a bit that will be at port bit 0.
time_now = get_timer1(); // Get the timer present count
output_c(shift_reg); // Write the shift register bits to the port
time_set = time_now + servo_times[index]; // And set the first duration
CCP_1 = time_set; // Write the compare time to CCP 1 register.
// Reset the new cycle flag after it has been seen.
flag_20mS = 0;
}
//
// Rest of the program here onwards.
// No delay or wait loops!
//
}
return (0);
}
Hi Ian,If you can wait, I'll see if I can put into Basic for you.
/*
* File: servo_pwm_test_v2.c
* Author: Robert Jenkins
*
* Created on 09 May 2022, 09:45
*
*
* Better optimised version, more consistent output timing and
* no timer interrupt or anything needed in main program section,
* beyond the initialisations.
*
* The main program loop can have delays or wait loops,
* as long as interrupts are never blocked.
*
*/
#include <16F18446.h>
#DEVICE PIC16F18446 ICD=1
#USE DELAY(internal=32MHz)
#USE FAST_IO(A)
#USE FAST_IO(B)
#USE FAST_IO(C)
int8 index;
unsigned int16 servo_times[8];
unsigned int16 time_now;
unsigned int16 time_set;
unsigned int8 shift_reg;
#INT_CCP1
void ccp1_interrupt_routine(void) {
//
// This is executed at each new compare
//
shift_reg <<= 1; // Shift the bit left one place.
// shift_reg = shift_reg + shift reg would do the same.
output_c(shift_reg); // Write the shift register bits to the port
// Increment index for next servo time value
index = index + 1;
if(index < 8) { // All servos already done?
// No, do next servo
// Set the duration for this output, as a new offset
// from the last compare time.
time_set = time_set + servo_times[index];
CCP_1 = time_set; // Write the compare time to CCP 1 register.
}
else {
// No output needed as the port is already updated.
// output_c(0); // Turn off all servo bits.
// Add a fixed time increment for delays before
// restarting the sequence.
time_set = time_set + 1500; // add 1.5mS for the next interrupt
// Update CCP
CCP_1 = time_set;
if(index > 15) {
// 15 * average 1.5mS = 22.5mS, a reasonable frame repeat time
// Set up for a new sequence
index = 0;
shift_reg = 1; // Set a bit that will be at port bit 0.
time_now = get_timer1(); // Get the timer present count
time_set = time_now + servo_times[index]; // And set the first duration
output_c(shift_reg); // Write the shift register bits to the port
CCP_1 = time_set; // Write the compare time to CCP 1 register.
}
}
}
/*
*
*/
int main(int argc, char** argv) {
set_tris_c(0); // Port C all outputs
setup_adc(ADC_OFF);
// OSC = 32 MHz.
// Timer source: Internal = osc / 4; 8 MHz.
// T1 = A 16 bit timer used as the timebase
// Divide by 8 prescale = 1MHz input clock
// With 16 bit count, overflow will occur approx. every 16mS, 60Hz.
setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);
// Without an output pin defined, just an interrupt from CCP
setup_ccp1(CCP_COMPARE_TOGGLE);
//
// With this timing, 1mS = 1000 counts;
// servo 1mS to 2mS = 1000 to 2000,
// with 1500 as mid point.
//
//
//
// For demonstration, I'm setting the servo times
// to various fixed values.
//
// Set them from your program as needed.
//
servo_times[0] = 1500; // Port bit 0, Mid position
servo_times[1] = 1100; // Port bit 1, Near min position
servo_times[2] = 1900; // Port bit 2, Near max position
servo_times[3] = 1300; // Port bit 3, Under mid position
servo_times[4] = 1700; // Port bit 4, Over mid position
servo_times[5] = 1200; // Port bit 5, etc
servo_times[6] = 1800; // Port bit 6
servo_times[7] = 1400; // Port bit 7
// And enable the interrupts for the hardware used
// Timer 1 is free running after initialisation & only used as
// the reference for CCP1, so no time interrupt required.
enable_interrupts(GLOBAL);
enable_interrupts(INT_CCP1);
// Main loop
for(;;) {
//
// Nothing needed here for servos, it's all in the CCP interrupt.
//
// Rest of the program here onwards.
//
}
return (0);
}
Hi R,Hi Ian,
an updated one, slightly better timing though possibly slightly harder to understand but in other ways simpler, it needs nothing in the main program & no timer interrupt, though the timer still needs initialising.
C:/* * File: servo_pwm_test_v2.c * Author: Robert Jenkins * * Created on 09 May 2022, 09:45 * * * Better optimised version, more consistent output timing and * no timer interrupt or anything needed in main program section, * beyond the initialisations. * * The main program loop can have delays or wait loops, * as long as interrupts are never blocked. * */ #include <16F18446.h> #DEVICE PIC16F18446 ICD=1 #USE DELAY(internal=32MHz) #USE FAST_IO(A) #USE FAST_IO(B) #USE FAST_IO(C) int8 index; unsigned int16 servo_times[8]; unsigned int16 time_now; unsigned int16 time_set; unsigned int8 shift_reg; #INT_CCP1 void ccp1_interrupt_routine(void) { // // This is executed at each new compare // shift_reg <<= 1; // Shift the bit left one place. // shift_reg = shift_reg + shift reg would do the same. output_c(shift_reg); // Write the shift register bits to the port // Increment index for next servo time value index = index + 1; if(index < 8) { // All servos already done? // No, do next servo // Set the duration for this output, as a new offset // from the last compare time. time_set = time_set + servo_times[index]; CCP_1 = time_set; // Write the compare time to CCP 1 register. } else { // No output needed as the port is already updated. // output_c(0); // Turn off all servo bits. // Add a fixed time increment for delays before // restarting the sequence. time_set = time_set + 1500; // add 1.5mS for the next interrupt // Update CCP CCP_1 = time_set; if(index > 15) { // 15 * average 1.5mS = 22.5mS, a reasonable frame repeat time // Set up for a new sequence index = 0; shift_reg = 1; // Set a bit that will be at port bit 0. time_now = get_timer1(); // Get the timer present count time_set = time_now + servo_times[index]; // And set the first duration output_c(shift_reg); // Write the shift register bits to the port CCP_1 = time_set; // Write the compare time to CCP 1 register. } } } /* * */ int main(int argc, char** argv) { set_tris_c(0); // Port C all outputs setup_adc(ADC_OFF); // OSC = 32 MHz. // Timer source: Internal = osc / 4; 8 MHz. // T1 = A 16 bit timer used as the timebase // Divide by 8 prescale = 1MHz input clock // With 16 bit count, overflow will occur approx. every 16mS, 60Hz. setup_timer_1(T1_INTERNAL | T1_DIV_BY_8); // Without an output pin defined, just an interrupt from CCP setup_ccp1(CCP_COMPARE_TOGGLE); // // With this timing, 1mS = 1000 counts; // servo 1mS to 2mS = 1000 to 2000, // with 1500 as mid point. // // // // For demonstration, I'm setting the servo times // to various fixed values. // // Set them from your program as needed. // servo_times[0] = 1500; // Port bit 0, Mid position servo_times[1] = 1100; // Port bit 1, Near min position servo_times[2] = 1900; // Port bit 2, Near max position servo_times[3] = 1300; // Port bit 3, Under mid position servo_times[4] = 1700; // Port bit 4, Over mid position servo_times[5] = 1200; // Port bit 5, etc servo_times[6] = 1800; // Port bit 6 servo_times[7] = 1400; // Port bit 7 // And enable the interrupts for the hardware used // Timer 1 is free running after initialisation & only used as // the reference for CCP1, so no time interrupt required. enable_interrupts(GLOBAL); enable_interrupts(INT_CCP1); // Main loop for(;;) { // // Nothing needed here for servos, it's all in the CCP interrupt. // // Rest of the program here onwards. // } return (0); }
Hi I,Hi C.. If you can wait, I'll see if I can put into Basic for you.
One question??? What processor are you using The pic16f18446 isn't listed?
Though so..The processor is 18F4431.
I don't have any of those, but it has the same 16 bit timer1 and CCP1 references timer1 for the compare time, just like the 16F446 I used for my test setup, so it should be able to work just the same on that device.The processor is 18F4431.
timer1 is set up to run with a 1MHz clock, so giving 1uS steps to the servo output pulse durations.It also appears that you have the Osc set to 32mHz (good), with I think a second timer for the 'PROCEDURE' (I think that's what it is?)
The 18F4431 LOOP calculation will be receiving MOTOR/SERVO instructions constantly, so I hope that this can work with that.
Hi R,timer1 is set up to run with a 1MHz clock, so giving 1uS steps to the servo output pulse durations.
It's only used as a timing reference source for the CCP1 module.
That must not be messed with by your program, but you can use any other timers as you wish, including for regular interrupts etc.
All the servo control stuff, other than the one-off setup at the start of the program, is done in the CCP1 "Compare" interrupt.
As long as you never block interrupts it will work completely in the background with virtually no impact on whatever else the program is doing - it will just steal a few microseconds every 1 - 2mS for an update.
You can change any or all of the servo pulse time settings as and when needed, they will be used whenever the servo is next updated.
(If you use other interrupts, CCP1 should be given highest priority, if possible).