while(BT_RX_DATA) // If the UART has a character ready
{
char c;
disable_interrupts(INTR_GLOBAL);
c = fgetc(BT); // Read the character
buf_put(&bt_rx, c); // Put it in the buffer
if(c == '\r') // If the character was end-of-line,
{
buf_put(&bt_rx, '\0'); // Store a null
bt_rx_ready = true; // And set the variable for the main program
}
enable_interrupts(INTR_GLOBAL);
}
// In the main loop:
if(bt_rx_ready)
{
process_bt();
}
// and somewhere else
void process_bt(void)
{
// Last char read was a CR; get the buffer data.
// Read until either a CR is found or the buffer is empty
// (or 80 chars have been read, if garbage).
// LF is ignored and dumped.
int8 i;
char c;
char *p;
char *q;
char parse[16];
i = 0;
c = '\0';
p = bbuf;
do
{
c = buf_get(&bt_rx);
if(c > 0x20 && c < 0x7f)
{
if(c >= 0x41 && c <= 0x5A) // Upper case letters
{
c |= 0x20;
}
*p++ = c;
i++;
}
} while(c && c != '\r' && i < 60);
// String with all spaces removed & line feed omitted.
// Ensure it's a terminated string to avoid overruns..
*p++ = '\0';
// Should now have a valid string in bbuf.
// Check it's not junk or too short to be valid first:
if(i < 2) return;
// Have a command string.
//
// The do whatever you do with the GPS string once received..
//
}
Hi R,The GPS stuff is likely the cause of all the jitter! You cannot possibly control servos reliably whilst that long read cycle is happening.
What you can do is check for a received UART character at every 1mS interrupt and put it in a circular buffer if received.
Set a variable if the character was a line feed (0x0A) as that's the end of each GPS sentence.
In your main program, just look for the variable being set and when that happens, read all the characters from the buffer until you get to the line feed, then parse the line.
That means no delays at all in the critical interrupt routines.
This is my circular buffer library, in C. The buffer size should be at least 128, or 256 if the MCU has plenty of RAM.
It will take a bit of translating.....
Header file:
C:/* * File: rj_ringbuffer.h * Author: Robert Jenkins * * Created on 22 October 2018, 16:45 */ #ifndef RJ_RINGBUFFER_H #define RJ_RINGBUFFER_H #ifdef __cplusplus extern "C" { #endif // This must be a power-of-2 value // The mask is the bit size of this value // eg. 32 size, 0x1F mask; five bits set, 2^5 = 32. // 256 size, mask 0xff // 64 size, mask 0x3f // 8 size, mask 0x07 etc. #define RING_BUFFER_SIZE 256 #define RING_BUFFER_MASK 0xff /* In the main program, declare each required buffer * with its own name, like rs232_buffer serial_rx; or rs232_buffer terminal_tx; etc. */ // Limit value to prevent possible buffer wrap. #define RING_BUFFER_LIMIT RING_BUFFER_SIZE - 2 typedef struct rs_bf { unsigned int16 buf_inp; unsigned int16 buf_out; unsigned int8 * inp_ptr; unsigned int8 * out_ptr; unsigned int16 bcount; unsigned int8 buf_dat[RING_BUFFER_SIZE]; } rs232_buffer; // // In each of these, "rs232_buffer *" is replaced by the // name of buffer as declared above, eg. // // buf_put(terminal_tx, c); // i = buf_count(serial_rx); // void buf_clear(rs232_buffer *); int buf_put(rs232_buffer *, char); char buf_get(rs232_buffer *); int buf_count(rs232_buffer *); int buf_put_string(rs232_buffer *, char *); #ifdef __cplusplus } #endif #endif /* RJ_RINGBUFFER_H */
And the actual C program:
C:#include "rj_ringbuffer.h" /* For reference: typedef struct rs_bf { unsigned int16 buf_inp; unsigned int16 buf_out; unsigned int8 * inp_ptr; unsigned int8 * out_ptr; unsigned int16 bcount; unsigned int8 buf_dat[RING_BUFFER_SIZE]; } rs232_buffer; */ void buf_clear(rs232_buffer *b) { b->buf_inp = b->buf_out = b->bcount = 0; b->inp_ptr = b->out_ptr = &(b->buf_dat[0]); } int buf_put(rs232_buffer *b, char c) { if(b->bcount < RING_BUFFER_LIMIT) { b->buf_dat[b->buf_inp++] = c; b->bcount++; b->buf_inp &= RING_BUFFER_MASK; } return b->bcount; } char buf_get(rs232_buffer *b) { char c; if(b->bcount > 0) { c = b->buf_dat[b->buf_out++]; b->bcount--; b->buf_out &= RING_BUFFER_MASK; if(b->buf_inp == b->buf_out) b->bcount = 0; return c; } else return '\0'; } int buf_count(rs232_buffer *b) { return b->bcount; } int buf_put_string(rs232_buffer *b, char *c) { char *p; char l; int i = 0; p = c; while(*p) { l = *p++; buf_put(b, l); i++; } return i; }
Hi M,edit3, can you post your current code that you're currently working with - preferably as a text file.
Hi M,Rather than add anything to the 1mS interrupt and risk frame errors, simply have a UART interrupt. I'll have a go later as it's 7am here and coffee time.
Mike.
Here is a fully commented version,Would you be good enough to explain in detail what your CODE actually does
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; //this is timer 2 period register. I.E. reset after 50 clocks (0 to 49 = 50 counts)
TMR2IE=1; //timer 2 interrupts enable
T1CON=0; //timer 1 stopped
for(uint8_t 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; //enable peripheral interrupts
GIE=1; //enable global interrupts
while(1){ //loop forever because everything is done on interripts
//adjust servo positions here
}
}
void __interrupt() inter(void){
if(TMR2IE && TMR2IF){ //is it a timer 2 interrupt
ms++; //yes, so increment ms variable
count++; //and count
if(count>=20){ //has 20mS passed
TMR1=0; //yes, so zero timer 1
T1CON=1; //and start timer 1
count=0; //reset count
CCP1CON=0b1000; //CCP1 pin low and high on match - will be first pulse
CCPR1=2000; //start pulse in 0.25mS (2000 clock cycles)
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; //clear timer 2 interrupt flag
}
if(CCP1IE && CCP1IF){ //is it a CCP1 interrupt
LATC0=0; //yes, so clear the data in pin as the high has already been clocked in
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
}else{
CCP1CON=0b1000; //No so output the timed gap
CCPR1=CCPR1+servoPos[servoCount++]-4000; //will generate an interrupt when servo time is up
}
CCP1IF=0; //clear CCP1 interrupt flag
}
}
Hi M,Here is a fully commented version,
If you tell me which bits you don't understand then I'll try and explain them better.Code: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; //this is timer 2 period register. I.E. reset after 50 clocks (0 to 49 = 50 counts) TMR2IE=1; //timer 2 interrupts enable T1CON=0; //timer 1 stopped for(uint8_t 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; //enable peripheral interrupts GIE=1; //enable global interrupts while(1){ //loop forever because everything is done on interripts //adjust servo positions here } } void __interrupt() inter(void){ if(TMR2IE && TMR2IF){ //is it a timer 2 interrupt ms++; //yes, so increment ms variable count++; //and count if(count>=20){ //has 20mS passed TMR1=0; //yes, so zero timer 1 T1CON=1; //and start timer 1 count=0; //reset count CCP1CON=0b1000; //CCP1 pin low and high on match - will be first pulse CCPR1=2000; //start pulse in 0.25mS (2000 clock cycles) 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; //clear timer 2 interrupt flag } if(CCP1IE && CCP1IF){ //is it a CCP1 interrupt LATC0=0; //yes, so clear the data in pin as the high has already been clocked in 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 }else{ CCP1CON=0b1000; //No so output the timed gap CCPR1=CCPR1+servoPos[servoCount++]-4000; //will generate an interrupt when servo time is up } CCP1IF=0; //clear CCP1 interrupt flag } }
E.G. do you understand how the pulses are generated?
Mike.
As shown above, you can write a 16 bit value by writing two 8 bit values. Storing in a word generates lots of unnecessary code. From #289 above.All the references to CCPR1 will need to be double writes so,
CCPR1 = 2000
Will need to be,
CCPR1L=0xD0
CCPR1H=0x07
And the other write to CCPR1 changed as posted previously.
#include <xc.h>
#include "config.c"
#include <stdint.h>
#define _XTAL_FREQ 32000000
#define NUM_SERVOS 10
uint16_t servoPos[NUM_SERVOS];
typedef union{
uint16_t w;
struct{
uint8_t lo;
uint8_t hi;
};
}Word;
Word wordTemp;
uint32_t ms;
uint8_t count=0,servoCount;
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(uint8_t 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
//CCPR1=2000; //start pulse in 0.25mS (2000 clock cycles)
CCPR1L=2000 & 255;
CCPR2H=2000/256;
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.w=CCPR1H*256+CCPR1L;
wordTemp.w=wordTemp.w+4000;
CCPR1L=wordTemp.lo;
CCPR1H=wordTemp.hi;
}else{
CCP1CON=0b1000; //No so output the timed gap
//CCPR1=CCPR1+servoPos[servoCount++]-4000; //will generate an interrupt when servo time is up
wordTemp.w=CCPR1H*256+CCPR1L;
wordTemp.w=wordTemp.w-4000+servoPos[servoCount];
CCPR1L=wordTemp.lo;
CCPR1H=wordTemp.hi;
servoCount=servoCount+1;
}
CCP1IF=0;
}
}
wordTemp.w=CCPR1H*256+CCPR1L;
wordTemp.w=wordTemp.w+4000;
CCPR1L=wordTemp.lo;
CCPR1H=wordTemp.hi;
word.HB=CCPR1H
word.LB=CCPR1L
word=word+4000
CCPR1L=word.LB
CCPR2H=word.HB
Hi M,Here's a version that doesn't use CCPR1 as a word variable. It uses a union to mimic word.HB etc.
Code:#include <xc.h> #include "config.c" #include <stdint.h> #define _XTAL_FREQ 32000000 #define NUM_SERVOS 10 uint16_t servoPos[NUM_SERVOS]; typedef union{ uint16_t w; struct{ uint8_t lo; uint8_t hi; }; }Word; Word wordTemp; uint32_t ms; uint8_t count=0,servoCount; 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(uint8_t 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 //CCPR1=2000; //start pulse in 0.25mS (2000 clock cycles) CCPR1L=2000 & 255; CCPR2H=2000/256; 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.w=CCPR1H*256+CCPR1L; wordTemp.w=wordTemp.w+4000; CCPR1L=wordTemp.lo; CCPR1H=wordTemp.hi; }else{ CCP1CON=0b1000; //No so output the timed gap //CCPR1=CCPR1+servoPos[servoCount++]-4000; //will generate an interrupt when servo time is up wordTemp.w=CCPR1H*256+CCPR1L; wordTemp.w=wordTemp.w-4000+servoPos[servoCount]; CCPR1L=wordTemp.lo; CCPR1H=wordTemp.hi; servoCount=servoCount+1; } CCP1IF=0; } }
Mike.
Let's all agree whether I'm correct or not before moving on, and if there's time in the FREE section for the GPS which has 45 characters at 9600.
Hi R,If you look above that, it's what is left of the "frame repeat" period of 20mS.....
Eight servo times could be 16mS with 2mS maximum, or 20mS with 2.5mS maximum as used with some digital servos.
So 4mS "free" with older 1 - 2mS analog servos, or no free time with 0.5 - 2.5mS digital servos.
45 characters at 9600 baud is somewhat over 45 mS. Longer that two complete servo frames.
The GPS data reception MUST run via an interrupt driver routine so it does not block the servo routine.
NO.Am I correct then, that M's 1ms INTERRUPT won't work with the GPS on this PIC?
Hi M,The used time is a small percentage of the available time. The 1mS interrupt does not use 1mS, it uses a few cycles every mS. The servo stuff uses a few cycles when a servo pulse is generated. A usart should use a few cycles when a byte is received. This idea that the chip is being stretched processor wise is nonsense. It can easily do what it's doing and the GPS stuff as well. Where "So 4mS "free" with older 1 - 2mS analog servos, or no free time with 0.5 - 2.5mS digital servos."comes from I have no idea, it's complete nonsense. It's an interrupt - not blocking code!!!!!!!! The "free" time is very close to 100% - let's have a guess at greater than 95%.
Mike.
Then you don't understand. RS232 is sent 1 byte at a time. You receive that byte, save it, check if a whole sentence has been received and flag the main code if it has. You do not wait for the whole sentence - a byte takes ~1mS to be transmitted - it can be received in 1mS by the hardware which then tells you "I've got a byte for you", you then deal with the byte. The only waiting involved is waiting for the hand to go up saying "I've got a byte for you". With the RS232 receive code and servo/timer code it should still be using less than 10% of the available processing time.As far as I have understood, with working GPS, I have to receive a full sentence for $ to W, for my PARSE routine, I don't think it can work by receiving 1 BYTE at a time.
Hi M,Then you don't understand. RS232 is sent 1 byte at a time. You receive that byte, save it, check if a whole sentence has been received and flag the main code if it has. You do not wait for the whole sentence - a byte takes ~1mS to be transmitted - it can be received in 1mS by the hardware which then tells you "I've got a byte for you", you then deal with the byte. The only waiting involved is waiting for the hand to go up saying "I've got a byte for you". With the RS232 receive code and servo/timer code it should still be using less than 10% of the available processing time.
Mike.
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?