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.
The chip can execute 8,000,000 (assuming 32MHz) instructions per second. The ISRs just take it in turn to do their thing. They've got 1mS (8000 instructions) between each byte arriving from the GPS. Same for each servo and the timer interrupt takes just a few instructions every 8,000 instructions. Oodles of time.

Had a busy day here and now late at night but I'll write some code tomorrow that will hopefully make it clearer.

Mike.
 
The chip can execute 8,000,000 (assuming 32MHz) instructions per second. The ISRs just take it in turn to do their thing. They've got 1mS (8000 instructions) between each byte arriving from the GPS. Same for each servo and the timer interrupt takes just a few instructions every 8,000 instructions. Oodles of time.

Had a busy day here and now late at night but I'll write some code tomorrow that will hopefully make it clearer.

Mike.
Well, your slowly convincing me, and unless anyone disagrees, I'll accept it.

Good, so I'll go back to commenting your CODE from the program at #290 with comments from #307, which I'll post.

P.S. mind that radioactive capsule!
C
 
Well, your slowly convincing me, and unless anyone disagrees, I'll accept it.

Good, so I'll go back to commenting your CODE from the program at #290 with comments from #307, which I'll post.

P.S. mind that radioactive capsule!
C
This was discussed in 2018 at AAC.
It is good you are finally convinced:)
I made in 2018 a test in Oshonsoft simulator and servo interrupt using TIMER1 loaded with servo on time.
The main program ran with 99% speed.
 
This was discussed in 2018 at AAC.
It is good you are finally convinced:)
I made in 2018 a test in Oshonsoft simulator and servo interrupt using TIMER1 loaded with servo on time.
The main program ran with 99% speed.
Hi J,
Yes, understanding every morsel of this project needs a bigger brain than I've got, but luckily, there's search.
Do you mean this one: https://forum.allaboutcircuits.com/...location-pic-in-oshonsoft.148795/post-1291488
If so, I have it loaded, and will look a bit closer along with the timings.

The bit that I needed convincing, is the GPS NMEA sentence input, being sliced into BYTES, when it is only there for 70ish ms from start to finish, spaced 250ms apart.
C
 
Last edited:
If 250ms is enough for parsing a 70 byte msg , then no problem.
Hi J,
To clarify, here is the BUFFER section of the GPS, once in the BUFFER then it stops reading the GPS, and then it is PARSED.
Still ok?
C
 

Attachments

  • Buffer.txt
    2.6 KB · Views: 130
Hi,
Here's M's commented CODE
Watch for errors!
It compiles, but there's no RC1. I haven't found any settings for RC1 output. Perhaps an alternative? I can connect to RA6 which is the 8mHz crystal. EDIT: It appears that we can't get 32mHz pin out!

I've yet to do the WORD bit.
C
 

Attachments

  • 18F4431 32MHz XTL REMOTE_SLAVE 164 290123 1200.txt
    4.7 KB · Views: 128
Last edited:
Whoops, my bad. CCP1 output is on RC2. Change TRISC.2 to be output and you should see pulses on RC2. Or if committed to RC1 then change to CCP2.

Mike.
 
Here's how I would write the UART interrupt,
Code:
    if(RCIE & RCIF){
        uint8_t chr=RCREG;          //get the received character
        if(OERR || FERR){           //neither of these should ever occur.
            CREN=0;                 //this is kinda wishful thinking
            CREN=1;                 //as any data received is corrupt
            strCount=0;             //however, reset everything
            done=0;                 //and hope for the best
            chr=0;                  //ensure this character isn't used
        }else{                      //no errors so use the data
            if(strCount==0 && done==0){ //are we already receiving
                //waiting for $         //no so wait
                if(chr=='$'){           //for $ to appear
                    buff[strCount]=chr; //start receiving
                    strCount++;
                }else if(done==0){      //have we collected a full string?
                    buff[strCount]=chr; //no so carry on storing
                    strCount++;
                    if(chr=='W'){       //have we got the "endOfString" character
                        done=1;         //yes, so set done true
                    }
                }
            }
        }
    }
It will wait for a '$' sign then start filling the buffer (buff) until a 'W' is received.
The main code should monitor done, when done is not zero, process the buffer. No more bytes will go into buffer whilst this is happening.
Once the buffer has been processed, set done=0 and buffCount=0 and wait for done to be non zero again.

Mike.
Edit, changed if(!done) to if(done==0) so less confusing.
 
Here's how I would write the UART interrupt,
Code:
    if(RCIE & RCIF){
        uint8_t chr=RCREG;          //get the received character
        if(OERR || FERR){           //neither of these should ever occur.
            CREN=0;                 //this is kinda wishful thinking
            CREN=1;                 //as any data received is corrupt
            strCount=0;             //however, reset everything
            done=0;                 //and hope for the best
            chr=0;                  //ensure this character isn't used
        }else{                      //no errors so use the data
            if(strCount==0 && done==0){ //are we already receiving
                //waiting for $         //no so wait
                if(chr=='$'){           //for $ to appear
                    buff[strCount]=chr; //start receiving
                    strCount++;
                }else if(done==0){      //have we collected a full string?
                    buff[strCount]=chr; //no so carry on storing
                    strCount++;
                    if(chr=='W'){       //have we got the "endOfString" character
                        done=1;         //yes, so set done true
                    }
                }
            }
        }
    }
It will wait for a '$' sign then start filling the buffer (buff) until a 'W' is received.
The main code should monitor done, when done is not zero, process the buffer. No more bytes will go into buffer whilst this is happening.
Once the buffer has been processed, set done=0 and buffCount=0 and wait for done to be non zero again.

Mike.
Edit, changed if(!done) to if(done==0) so less confusing.
Hi M,
Ok, I'll look at this later, thanks.
C
 
To clarify post #330,
The main loop should do,
Code:
  while(1)
    if(done)
      'process buffer here
      done=0
      strCount=0
    endif
  wend
Note, processing the buffer can also set servo positions if appropriate.

Mike.
 
To clarify post #330,
The main loop should do,
Code:
  while(1)
    if(done)
      'process buffer here
      done=0
      strCount=0
    endif
  wend
Note, processing the buffer can also set servo positions if appropriate.

Mike.
Hi M,
Ok.
Looking at #327, i've put the temp SERVO POS in the WEND WHILE LOOP, is this ok?
C.
 
You can do that however, you've got it wrong,
Code:
servoPos(0) = 1.5 * 900  'SERVO pulse times allowing for Clock speed
servoPos(1) = 1.5 * 900
servoPos(2) = 1.5 * 1000
Should be,
Code:
servoPos(0) = 8000 + 900  'SERVO pulse times allowing for Clock speed
servoPos(1) = 8000 + 900
servoPos(2) = 8000 + 1000
etc.

Also note you need a value for servoPos(8) so it outputs the last value. Just make it 8000 and all should be good.
Note, 8000 is 1mS in clock cycles at 32MHz.
ServoPos can go from 8,000 to 16,000 or from 0 to 7999 plus 8000.

Mike.
 
You can do that however, you've got it wrong,
Code:
servoPos(0) = 1.5 * 900  'SERVO pulse times allowing for Clock speed
servoPos(1) = 1.5 * 900
servoPos(2) = 1.5 * 1000
Should be,
Code:
servoPos(0) = 8000 + 900  'SERVO pulse times allowing for Clock speed
servoPos(1) = 8000 + 900
servoPos(2) = 8000 + 1000
etc.

Also note you need a value for servoPos(8) so it outputs the last value. Just make it 8000 and all should be good.
Note, 8000 is 1mS in clock cycles at 32MHz.
ServoPos can go from 8,000 to 16,000 or from 0 to 7999 plus 8000.

Mike.
Hi M,
Thanks.
End of play today.
C
 
Hi M,
In order to make the set-up section with DIMs etc, I need to know what the variables do.
You've confused me by adding [ wordTemp.W ] What is is and what does it do? I will need to change it to a variable, that I undertsand.

I've never seen [ .W ] before, I'm not sure it's legal.

Your use of 2000 and 4000, isn't accepted, I couldn't get it to compile without it as it was before when there was a WORD variable FRAME.LB and FRAME.LB. Perhaps we can come back to this later?

EDIT: Just looking back at your comments, and #310, where you explain wordtemp.w. I'm still not sure about the .W
C.
 
Last edited:
The .w bit was to mimic the oshonsoft .HB and .LB. .w is not legal in oshonsoft (I think) it's only in C as a union. The later version uses a word variable instead and loads CCPR1L and H into it, does some maths and moves it back.

The use of 2000 and 4000 are just constants that represent 0.25mS and 0.5mS and are definitely required. They can easily be split up into two 8 bit variables to load into the registers - or in the 4000 case added to the word variable.

Why doesn't 'Define num_servos = 10 'not compile?? compile?
You have constants defined above I.E. Define CLOCK_FREQUENCY = 32
What is the difference?
Why does one compile and not the other?

Here's a version that does not use a union and writes the two 8 bit values and makes i a global variable,
Code:
#include <xc.h>
#include "config.c"
#include <stdint.h>
#define _XTAL_FREQ 32000000
#define NUM_SERVOS 10
#define BUFFER 80

uint8_t buff[BUFFER],strCount=0,done=0;

uint16_t wordTemp;
uint32_t ms;
uint8_t count=0,servoCount,i;
uint16_t servoPos[NUM_SERVOS];


void main(void) {
    OSCCON=0b01110000;      //8MHz
    PLLEN=1;                //x4=32MHz
    //setup 1mS interrupt = 8,000,000/16 = 500,000/10 = 50,000 set PR2=49 = 50,000/50 = 1000 = 1mS
    T2CON=0b01001110;       //pre=16 post=10
    PR2=49;
    TMR2IE=1;               //timer 2 interrupts enable
    T1CON=0;                //timer 1 stopped
    for(i=0;i<NUM_SERVOS;i++){
        servoPos[i]=i*1000+8000; //1ms(8000) to 1.875(7/8ths - 15000)ms in 1/8th mS steps
    }
    TRISC=0b11111100;           //CCP0 & 1 output
    PEIE=1;
    GIE=1;
    while(1){
            //adjust servo positions here
    }
}


void __interrupt() inter(void){
    if(TMR2IE && TMR2IF){
        ms++;
        count++;
        if(count>=20){          //start every 20mS
            TMR1=0;             //zero timer 1
            T1CON=1;            //start timer 1
            count=0;
            CCP1CON=0b1000;     //CCP1 pin low and high on match - will be first pulse
            CCPR1L=0x0d;
            CCPR2H=0x07;
            CCP1IE=1;           //enable CCP1 interrupts
            CCP1IF=0;           //ensure interrupt flag is clear
            servoCount=0;       //reset servoCount
            LATC0=1;            //connected to data in of shift register will clock in a high when CCP1 goes high
        }
        TMR2IF=0;
    }
    if(CCP1IE && CCP1IF){
        LATC0=0;                //clear the data in pin
        if(servoCount==9)       //have we done all servos?
            CCP1IE=0;           //yes so no more CCP1 interrupts
        if(CCP1CON==0b1000){    //have we started the 4000 cycle pulse
            CCP1CON=0b1001;     //yes so end the pulse after 0.5mS
            //CCPR1=CCPR1+4000;   //4000 cycles=0.5mS
            wordTemp=CCPR1H*256+CCPR1L;
            wordTemp=wordTemp+4000;
            CCPR1L=wordTemp & 255;
            CCPR1H=wordTemp/256;
        }else{
            CCP1CON=0b1000;     //No so output the timed gap      
            //CCPR1=CCPR1+servoPos[servoCount++]-4000;   //will generate an interrupt when servo time is up
            wordTemp=CCPR1H*256+CCPR1L;
            wordTemp=wordTemp-4000+servoPos[servoCount];
            CCPR1L=wordTemp&255;
            CCPR1H=wordTemp/256;
            servoCount=servoCount+1;
        }
        CCP1IF=0;
    }
    if(RCIE & RCIF){
        uint8_t chr=RCREG;              //get the received character
        if(OERR || FERR){               //neither of these should ever occur.
            CREN=0;                     //this is kinda wishful thinking
            CREN=1;                     //as any data received is corrupt
            strCount=0;                 //however, reset everything
            done=0;                     //and hope for the best
         }else{                         //no errors so use the data
            if(strCount==0 && done==0){ //are we already receiving
                //waiting for $         //no so wait
                if(chr=='$'){           //for $ to appear
                    buff[strCount]=chr; //start receiving
                    strCount++;
                }else if(done==0){      //have we collected a full string?
                    buff[strCount]=chr; //no so carry on storing
                    strCount++;
                    if(chr=='W'){       //have we got the "endOfString" character
                        done=1;         //yes, so set done true
                    }
                }
            }
        }
    }
}

Mike.
Edit, you can of course replace,
CCPR1L=wordTemp&255;
CCPR1H=wordTemp/256;
with
CCPR1L=wordTemp.LB
CCPR1H=wordTemp.HB
Edit2, seems constants aren't defined with Define but Const so
Const NUM_SERVOS=10
and
Const BUFFER=80
should be fine.
Edit3, looks like multiple definition on one line are not allowed so
uint8_t buff[BUFFER],strCount=0,done=0;
will become
Dim buff(BUFFER) as byte
Dim strCount as byte
Dim done as byte
and initialise them prior to enabling interrupts,
strCount=0
done=0
before the line
PEIE=1
Edit4, TRISC will also need to be changed
 
Last edited:
The .w bit was to mimic the oshonsoft .HB and .LB. .w is not legal in oshonsoft (I think) it's only in C as a union. The later version uses a word variable instead and loads CCPR1L and H into it, does some maths and moves it back.

The use of 2000 and 4000 are just constants that represent 0.25mS and 0.5mS and are definitely required. They can easily be split up into two 8 bit variables to load into the registers - or in the 4000 case added to the word variable.

Why doesn't 'Define num_servos = 10 'not compile?? compile?
You have constants defined above I.E. Define CLOCK_FREQUENCY = 32
What is the difference?
Why does one compile and not the other?

Here's a version that does not use a union and writes the two 8 bit values and makes i a global variable,
Code:
#include <xc.h>
#include "config.c"
#include <stdint.h>
#define _XTAL_FREQ 32000000
#define NUM_SERVOS 10
#define BUFFER 80

uint8_t buff[BUFFER],strCount=0,done=0;

uint16_t wordTemp;
uint32_t ms;
uint8_t count=0,servoCount,i;


void main(void) {
    OSCCON=0b01110000;      //8MHz
    PLLEN=1;                //x4=32MHz
    //setup 1mS interrupt = 8,000,000/16 = 500,000/10 = 50,000 set PR2=49 = 50,000/50 = 1000 = 1mS
    T2CON=0b01001110;       //pre=16 post=10
    PR2=49;
    TMR2IE=1;               //timer 2 interrupts enable
    T1CON=0;                //timer 1 stopped
    for(i=0;i<NUM_SERVOS;i++){
        servoPos[i]=i*1000+8000; //1ms(8000) to 1.875(7/8ths - 15000)ms in 1/8th mS steps
    }
    TRISC=0b11111100;           //CCP0 & 1 output
    PEIE=1;
    GIE=1;
    while(1){
            //adjust servo positions here
    }
}


void __interrupt() inter(void){
    if(TMR2IE && TMR2IF){
        ms++;
        count++;
        if(count>=20){          //start every 20mS
            TMR1=0;             //zero timer 1
            T1CON=1;            //start timer 1
            count=0;
            CCP1CON=0b1000;     //CCP1 pin low and high on match - will be first pulse
            CCPR1L=0x0d;
            CCPR2H=0x07;
            CCP1IE=1;           //enable CCP1 interrupts
            CCP1IF=0;           //ensure interrupt flag is clear
            servoCount=0;       //reset servoCount
            LATC0=1;            //connected to data in of shift register will clock in a high when CCP1 goes high
        }
        TMR2IF=0;
    }
    if(CCP1IE && CCP1IF){
        LATC0=0;                //clear the data in pin
        if(servoCount==9)       //have we done all servos?
            CCP1IE=0;           //yes so no more CCP1 interrupts
        if(CCP1CON==0b1000){    //have we started the 4000 cycle pulse
            CCP1CON=0b1001;     //yes so end the pulse after 0.5mS
            //CCPR1=CCPR1+4000;   //4000 cycles=0.5mS
            wordTemp=CCPR1H*256+CCPR1L;
            wordTemp=wordTemp+4000;
            CCPR1L=wordTemp & 255;
            CCPR1H=wordTemp/256;
        }else{
            CCP1CON=0b1000;     //No so output the timed gap      
            //CCPR1=CCPR1+servoPos[servoCount++]-4000;   //will generate an interrupt when servo time is up
            wordTemp=CCPR1H*256+CCPR1L;
            wordTemp=wordTemp-4000+servoPos[servoCount];
            CCPR1L=wordTemp&255;
            CCPR1H=wordTemp/256;
            servoCount=servoCount+1;
        }
        CCP1IF=0;
    }
    if(RCIE & RCIF){
        uint8_t chr=RCREG;              //get the received character
        if(OERR || FERR){               //neither of these should ever occur.
            CREN=0;                     //this is kinda wishful thinking
            CREN=1;                     //as any data received is corrupt
            strCount=0;                 //however, reset everything
            done=0;                     //and hope for the best
         }else{                         //no errors so use the data
            if(strCount==0 && done==0){ //are we already receiving
                //waiting for $         //no so wait
                if(chr=='$'){           //for $ to appear
                    buff[strCount]=chr; //start receiving
                    strCount++;
                }else if(done==0){      //have we collected a full string?
                    buff[strCount]=chr; //no so carry on storing
                    strCount++;
                    if(chr=='W'){       //have we got the "endOfString" character
                        done=1;         //yes, so set done true
                    }
                }
            }
        }
    }
}

Mike.
Edit, you can of course replace,
CCPR1L=wordTemp&255;
CCPR1H=wordTemp/256;
with
CCPR1L=wordTemp.LB
CCPR1H=wordTemp.HB
Edit2, seems constants aren't defined with Define but Const so
Const NUM_SERVOS=10
and
Const BUFFER=80
should be fine.
Edit3, looks like multiple definition on one line are not allowed so
uint8_t buff[BUFFER],strCount=0,done=0;
will become
Dim buff(BUFFER) as byte
Dim strCount as byte
Dim done as byte
and initialise them prior to enabling interrupts,
strCount=0
done=0
before the line
PEIE=1
Edit4, TRISC will also need to be changed
Hi M,
Why doesn't 'Define num_servos = 10 'not compile?? compile?
You have constants defined above I.E. Define CLOCK_FREQUENCY = 32


CLOCK_FREQUENCY is in the OSH list num_servo isn't
-----------------------
Regarding Constants v DIM as word. I can't get constants to work. If we can use DIMs temporarily, and come back to this later, it would be better for me? It's compiling!

I'll have to return to your edits later, the CONST thing has boggled me.
C.
 
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top