Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Hardware ESC 8xSERVO CONTROL on PIC (Oshonsoft BASIC)

Status
Not open for further replies.
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.
 
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 R,
I looked up 6xCH PICs in Microchip MAPS, but couldn't find any. I'll have another look later.

I searched the usual places for you PIC, but no.

For now, I've switched to simple PIN ON/OFF as earlier suggested, which may be fine. I have an existing program, that needs changing a bit, that I'm doing.
Thanks, c.
 
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 looked up 6xCH PICs in Microchip MAPS, but couldn't find any. I'll have another look later.

I searched the usual places for your PIC, but no.

For now, I've switched to simple PIN ON/OFF as earlier suggested, which may be fine. I have an existing program, that needs changing a bit, that I'm doing.
Thanks, c.
 
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.
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.
 
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.
Hi All,
I appreciate the PIC search, but I have pairs of existing PCBs, the REMOTE 18F4431 is for Motor/Servocontrol, and is PIN specific. I doubt that a substitute PIC that fits the bill could be found conveniently.

I've switched to the attached SW :( program, which simulates ok.
Let's see how this fairs first.
Thanks, C
 

Attachments

  • 18f4431 32mhz XTL REMOTE PWM 090522 1100.bas
    2.9 KB · Views: 315
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..
 
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..
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.

Mike.
 
OK, a full working program using the principle I described above; One timer and one CCP only, for (up to) eight independent servo outputs.

It's in CCS C for a 16F446 (as that's the one I had to hand).
You will have to work out the basic equivalent and register setups etc. yourself to do the same things, but it proves how simple the concept is.

The size is far more due to the comments and spacing that actual program code!

ps. It's by no means optimised, I was more bothered about clarity in how it works.

C:
/*
 * 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);
}
 
Last edited:
And the results for proof of proper functionality, using the servo times set in the demo above.
It is running in a physical device, monitoring with a Hantek scope.

I'm always using pin C0 as the upper trace for sync, then each one from C1-C7 in order to show the various times and positions for those outputs.

Ouputs 0 and 1:
Servo_0_1.jpg


Outputs 0 and 2:
Servo_0_2.jpg


Outputs 0 and 3:
Servo_0_3.jpg


Outputs 0 and 4:
Servo_0_4.jpg



Outputs 0 and 5:
Servo_0_5.jpg



Outputs 0 and 6:
Servo_0_6.jpg



Outputs 0 and 7:
Servo_0_7.jpg
 
Last edited:
Hi all,
I've edited my reply in #13.
This looks perfect 'R'
It's going to take me a long time to convert it to BASIC, but I'll let you know how well it works.

Meanwhile, I'll carry on testing the SW version I have.
Thanks.
C
 
Hi,
The Oshonsoft program is at #45 and the 'C' program is at #48.
I have attached an oscillator view of the first 4xCH.

The #45 program is very difficult for me to convert to BASIC (and it also it has an INCLUDE), Is this the same for 'C' programmers when faced with #45?

If anyone can read and understand both both programs, can they let me know if either use SW or HW and how much, so I can see the difference please? I suspect they are very similar, and onlyuse the HW TIMER. If this is the case then they are probaly the same.
C
 

Attachments

  • Osc.jpg
    Osc.jpg
    375.2 KB · Views: 307
The includes are not needed, they are in by habit.

The two work very differently.

1: Mine does not change the timer, that free runs.
2: It uses a CCP register to set the time intervals, by adding each to whats read from the timer.
3: The servo times are stored in an array, so the output update sequence at each CCP interrupt is exactly the same length, minimising jitter.

It has seven lines for the "reset" sequence and then just seven lines at each CCP interrupt.

Looking at the oshonsoft info, it has arrays.
There is no reason to have long drawn out code that will have variable execution time, in something that is supposed to be timing with high accuracy. Use an array and a fixed routine.
Definition:
Dim servo_times(10) As Word

Accessing the value:
servo_times(servo)

It does appear that you have to configure the CCP by writing directly to the registers, though; it seem oshonsoft has very minimal support for PIC built-in peripherals.

PIC built-in peripherals are what makes them so versatile, but hardly anything is provided for in the compiler.

A good reason for not using it, to me! The CCS one supports almost every type of PIC hardware peripheral.
 
If you can wait, I'll see if I can put into Basic for you.
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 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 R,
As I look though the CODE, I can almost see what's happening, and I like the 'nothing in the LOOP' part. The LOOP will be calculating what to do.

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.

It looks good, and I wish I could read it in 'C'
Thanks.
C
 
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?
Hi I,
That would be excellent :)

The processor is 18F4431.

At your leasure sir, I can be testing SERVOs with #45 CODE.
C.
 
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.
 
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.
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).
 
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).
Hi R,
Sounds good!

This is part of a project, here and AAC.
We (Forum members) have been working on the main PIC 18F46K20 and setting up peripherals, compass, alitimeter etc, which is almost ready. PIC 1 sends Joystick etc DATA to this PIC which controls the PWM outputs.

It could be used to control many remote things, but in my case it is for a Tricopter.
Cheers, C
 
Last edited:
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top