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.
Hi,
Here's the program so far:

=======================================
This loops forever
While 1
'adjust servo positions here
Wend
---------------------------------------------------------
Is this line correct:
PIE1.CCP1IE = 1 'enable CCP1 interrupts
PIR1.CCP1IF = 0 'ensure interrupt flag is clear
servocount = 0 'reset servoCount
LATC.0 = 1 'connected to data in of shift register will clock in a high when CCP1 goes high
Endif
'PIE1.TMR2IF = 0 'WARNING: Variable argument expected here

Endif

-------------------------------------------------------------------

It is not INTERRUPTING!
==============================================
 

Attachments

  • 18F4431 32MHz XTL REMOTE_SLAVE 164 260123 1000.bas
    4 KB · Views: 268
Hi,
Just spotted this:
=====================================
16.3 Capture Mode
In Capture mode, CCPR1H:CCPR1L captures the
16-bit value of the TMR1 register when an event
occurs on pin RC2/CCP1. An event is defined as
one of the following:
================================
Our CODE uses TMR2 instead?

EDIT: My eyes are getting crossed, bit I think I can see the need for TMR2, but it's not clear.
C
 
Last edited:
This is code I think will work with a 164. RC0 should go to the data input (pins 1&2) and RC1 to clock (pin 8). The servos go to Qa to Qe.
Not tested as no hardware, comments welcome.
Code:
#include <xc.h>
#include "config.c"
#include <stdint.h>
#define _XTAL_FREQ 32000000
#define NUM_SERVOS 10

uint16_t servoPos[NUM_SERVOS];
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)
            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
        }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;
    }
}
It (I hope) outputs a 0.5mS pulse every servoTime.

Mike.
Edit, added a millisecond interrupt as it's useful.
Is CCPR1= 2000 valid statement or should CCPR1H and CCPR1L be used?
 
jjw, if one looks at the system variables in Oshonsoft, CCPR1 is defined as a byte, location same as CCPR1L. CCPR1H is also defined as a byte, at the next higher address. Since CCPR1 is defined as a byte in Oshonsoft by default, one can only do byte operations against it and have to use CCPR1H and CCPR1L to used word values...
CCPR1 = 2000 is an invalid operation.
I checked this with PIC18 IDE.
registers.jpg
 
Last edited:
jjw, if one looks at the system variables in Oshonsoft, CCPR1 is defined as a byte, location same as CCPR1L. CCPR1H is also defined as a byte, at the next higher address. Since CCPR1 is defined as a byte in Oshonsoft by default, one can only do byte operations against it and have to use CCPR1H and CCPR1L to used word values...
CCPR1 = 2000 is an invalid operation.
I checked this with PIC18 IDE.
View attachment 140158
Hi S,
I have changed the program to write 2x BYTEs, is it ok to write the 2x BYTEs in sequence timing wise? Are there any issues with this.
C
 
Hi P,
Here is what AI understands about this 164 program:
Is it's understanding correct?
If so, then it will help me figure out exactly what's supposed to happen.
C.
=============================================
This appears to be C code for a program that controls servo motors. The main function sets up the system clock, configures Timer 2 to generate an interrupt every 1ms, and initializes an array "servoPos" with the target positions for the servos. The program enters an infinite loop where the servo positions can be adjusted. The interrupt service routine (inter) is responsible for counting the number of milliseconds that have passed, and for generating the pulses that control the servos. The CCP1CON and CCPR1 registers are used to generate the pulses, and the servoCount variable keeps track of which servo is currently being controlled. The program also uses a shift register to control multiple servos with a single microcontroller pin. The program uses the TMR2IF and CCP1IF flags to check if an interrupt has occurred and the CCP1IE and TMR2IE to enable the interrupt.
 
Hi,
I've now got #271 to INTERRUPT by adding [ PIR1.TMR2IF = 1 '1 = TMR2 to PR2 match occurred (must be cleared in software) ]

EDIT: RC0 DATA) is flashing, but RC1 (clock is not)
C.
 
Last edited:
Been away for the last few days and writing this reply on my phone would have been painful.

The
Code:
PIE1.TMR2IF = 0  'Variable argument expected here??????????????
conversion is weird as it (the A.I.) got it right earlier,
Code:
If PIE1.TMR2IE And PIR1.TMR2IF Then
So, yes it should be PIR1.TMR2IF.

All the references to CCPR1 will need to be double writes so,
Code:
CCPR1 = 2000

Will need to be,
CCPR1L=0xD0
CCPR1H=0x07

And the other write to CCPR1 changed as posted previously.

Mike.
edit, 2000 decimal is 0x07D0 hex.
edit2, the code uses timer 2 for the 1mS interrupt and timer 1 for the servo timing via CCP1 in compare mode.
edit3, can you post your current code that you're currently working with - preferably as a text file.
edit4, no idea what is set/clear in the config values but one of those bits moves CCP1 to port B so it won't appear on PortC.1
 
Last edited:
Been away for the last few days and writing this reply on my phone would have been painful.

The
Code:
PIE1.TMR2IF = 0  'Variable argument expected here??????????????
conversion is weird as it (the A.I.) got it right earlier,
Code:
If PIE1.TMR2IE And PIR1.TMR2IF Then
So, yes it should be PIR1.TMR2IF.

All the references to CCPR1 will need to be double writes so,
Code:
CCPR1 = 2000

Will need to be,
CCPR1L=0xD0
CCPR1H=0x07

And the other write to CCPR1 changed as posted previously.

Mike.
edit, 2000 decimal is 0x07D0 hex.
edit2, the code uses timer 2 for the 1mS interrupt and timer 1 for the servo timing via CCP1 in compare mode.
edit3, can you post your current code that you're currently working with - preferably as a text file.
edit4, no idea what is set/clear in the config values but one of those bits moves CCP1 to port B so it won't appear on PortC.1
Hi M,
Glad you're back!

The INTERRUPT is now at the [ If PIE1.CCP1IE And PIR1.CCP1IF Then ] and passing through.
no idea what is set/clear in the config values but one of those bits moves CCP1 to port B so it won't appear on PortC.1

I don't see any PORTB or C, so I assume that there should be a line like [PORTC = SERVOPOS] or similar somewhere. As you say, there is nothing appearing at PORTC.1.

As a text file? Do you mean like this?
'18F4431 32MHz XTL REMOTE_SLAVE 164 280123 0900
Define CONFIG1L = 0x00
Define CONFIG1H = 0x06 '8mHz XTL x4 =32mHz
Define CONFIG2L = 0x0c
Define CONFIG2H = 0x20
Define CONFIG3L = 0x04
Define CONFIG3H = 0x80
Define CONFIG4L = 0x80 'Set for HVP
Define CONFIG4H = 0x00
Define CONFIG5L = 0x0f
Define CONFIG5H = 0xc0
Define CONFIG6L = 0x0f
Define CONFIG6H = 0xe0
Define CONFIG7L = 0x0f
Define CONFIG7H = 0x40

'' Define oscillator frequency
'#define _xtal_freq 32000000

Define CLOCK_FREQUENCY = 32
Define SINGLE_DECIMAL_PLACES = 2
Define STRING_MAX_LENGTH = 20

Define SIMULATION_WAITMS_VALUE = 1 'Comment in for SIM out for PIC

TRISA = %11011000 '7=OSC, 6=OSC, 4=QEIB 3=QEIA 2=TEMP SEROUT
TRISB = %00000000
TRISC = %01000110 '6=1-slave4431_cs, 2=MOSI 1=74HC164 CLK.
TRISD = %00000000 '6=led, 7=led, 5=synch
TRISE = %00000000

'LATB = %00000000 'SERVO ouput PORT [ No more ]!!!!!!!!

ANSEL0 = 1 '[7:0]1 = Analog input: 1 = Analog input (Used later): REGISTER 21.6
ANSEL1.ANS8 = 0 '[0]Analog Input Function Select bit: 0 = Digital I/O

Disable Low
Disable High

'Define num_servos = 10 'not compile??
Dim num_servos As Byte 'BYTE ??
num_servos = 10
Dim i As Word '??????????

Dim servoPos(10) As Word
Dim ms As Long
Dim Cnt As Byte 'servocount As Byte

Dim frame As Word
frame = 0x07d0 '2000

Dim cycle As Word
cycle = 0x0fa0 '4000

Dim servocount As Byte '???????????

Enable High 'This is set for SERVOS
Enable Low 'This is set for GPS later

main_loop: '\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/

OSCCON = %01110000 '& h70
'pllen = 1 'x4=32MHz "#define CONFIG1H = 0x06 'HS oscillator, PLL enabled (clock frequency=4 x FOSC1)#define CONFIG1H = 0x06 'HS oscillator, PLL enabled (clock frequency=4 x FOSC1)"
'setup 1mS interrupt = 8,000,000/16 = 500,000/10 = 50,000 set PR2=49 = 50,000/50 = 1000 = 1mS
T2CON = %01001110 '& h4e
PR2 = 49
PIE1.TMR2IE = 1 'timer 2 interrupts enable
PIE1.CCP1IE = 1

PIR1.TMR2IF = 1 '1 = TMR2 to PR2 match occurred (must be cleared in software)£

T1CON = 0 'timer 1 stopped

For i = 0 To num_servos - 1
servoPos(i) = i * 1000 + 8000 '1ms(8000) to 1.875(7/8ths - 15000)ms in 1/8th mS steps
Next i

TRISC = %1111100 '& hfc 'CCP0 & 1 output
INTCON.PEIE = 1
INTCON.GIE = 1
While 1
'adjust servo positions here
servoPos(0) = 1.5 * 900 'SERVO pulse times allowing for Clock speed
servoPos(1) = 1.5 * 900
servoPos(2) = 1.5 * 1000
servoPos(3) = 1.5 * 1100
servoPos(4) = 1.5 * 1200
servoPos(5) = 1.5 * 1300
servoPos(6) = 1.5 * 1400
servoPos(7) = 1.5 * 1600
Wend

Goto main_loop

End

'sub inter()
On High Interrupt
Save System

If PIE1.TMR2IE And PIR1.TMR2IF Then
ms = ms + 1
Cnt = Cnt + 1
If Cnt >= 20 Then 'start every 20mS
TMR1 = 0 'zero timer 1
T1CON = 1 'start timer 1
Cnt = 0
CCP1CON = %00001000 '& h8 'CCP1 pin low and high on match - will be first pulse


CCPR1L = frame.LB '2000 'start pulse in 0.25mS (2000 clock cycles)
CCPR1H = frame.HB

PIE1.CCP1IE = 1 'enable CCP1 interrupts
PIR1.CCP1IF = 0 'ensure interrupt flag is clear
servocount = 0 'reset servoCount
LATC.0 = 1 'connected to data in of shift register will clock in a high when CCP1 goes high
Endif
PIR1.TMR2IF = 0 ' 0 = No TMR2 to PR2 match occurred
Endif


If PIE1.CCP1IE And PIR1.CCP1IF Then
LATC.0 = 0 'clear the data in pin
If servocount = 9 Then 'have we done all servos?
PIE1.CCP1IE = 0 'yes so no more CCP1 interrupts
Endif
If CCP1CON = %00001000 Then '& h8 Then 'have we started the 4000 cycle pulse
CCP1CON = %00001001 '& h9 'yes so end the pulse after 0.5mS

CCPR1L = CCPR1L + cycle.LB '4000 cycles=0.5mS
CCPR1H = CCPR1H + cycle.HB
Else
CCP1CON = %00001000 '& h8 'No so output the timed gap

CCPR1L = CCPR1L + servoPos(servocount) - cycle.LB + servocount 'will generate an interrupt when servo time is up
CCPR1H = CCPR1H + servoPos(servocount) - cycle.HB + servocount

servocount = servocount + 1
Endif
PIR1.CCP1IF = 0
Endif
Resume

'The code uses timer 2 For the 1ms Interrupt And timer 1 For the servo timing via ccp1 in compare mode.

'No idea what is set / clear in the config values but one of those bits moves ccp1 To
'port b so it won't appear on PortC.1

C
 
Hi M,
Regarding the AI, getting it wrong!
The AI is learning, so it has to have feedback for it to learn. If I knew how to give it this complicated feedback, I would.
C
 
Hi M,
Thinking about this EVERY 1mS INTERRUPT!
The GPS ITERRUPT needs time to read a sentence, will this be possible?
C
 
The chip is executing 8,000,000 instructions per second so in 1mS it executes 8,000 instructions. As long as the GPS (usart) interrupt doesn't wait for the whole sentence then it'll all be fine. All the above interrupts are very short, it spends most of the time in the while loop.

Mike.
 
The chip is executing 8,000,000 instructions per second so in 1mS it executes 8,000 instructions. As long as the GPS (usart) interrupt doesn't wait for the whole sentence then it'll all be fine. All the above interrupts are very short, it spends most of the time in the while loop.

Mike.
Hi M,
The GPS waits for a whole sentence.
C
 
Assuming the GPS is at 9600 baud then you get a character approximately every mS. Read the character (using an interrupt), put it in a buffer, if a complete sentence has been received then deal with it, otherwise, return from the interrupt and don't block the rest of the program.

I can now see why you need multiple chips.

Mike.
 
Assuming the GPS is at 9600 baud then you get a character approximately every mS. Read the character (using an interrupt), put it in a buffer, if a complete sentence has been received then deal with it, otherwise, return from the interrupt and don't block the rest of the program.

I can now see why you need multiple chips.

Mike.
Hi M,
A full GPS sentence is app 70 characters, this has been shortened to e,g,
[$GNRMC,111111.11,A,4807.03811,N,01131.00011,W ]
So $ to W, The INTERRUPT triggers on $ and must be read to W in one go.
It is then in a buffer, so the program carries on while PARSING.

P.S It is not a proper GPS chip buffer INTERRUPT, but the best that has been developed.
C
 
Hi M,
Over the development of this project, I've had to change chips, and all sorts of ideas too.

One of these ideas relates to the GPS clock. It is very accurate, so times can be co-ordinated on different PCBs, which would be handy. (There are 2x PCBS that 'talk' to each other)

If everything was time controlled by the GPS clock, would it be possible to have INTERRUPTS based on GPS timing, instead of the PIC ones?
C
 
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;
}
 
Last edited:
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top