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.

Junebug Servo C18 code.

Status
Not open for further replies.

Pommie

Well-Known Member
Most Helpful Member
On another thread someone asked about C18 servo code and I wrote a simple version. It occurred to me that it wouldn't be too difficult to make this work on the Junebug and so here it is. This code reads the potentiometer on AN1 (VR1) and sets the position of a servo on RB3 to match it.

Switches 1,2,3 & 8 on.
Code:
#include <p18f1320.h>
#pragma config WDT = OFF, LVP = OFF, OSC = INTIO2
#define ServoPin LATBbits.LATB3
#define ServoTris TRISBbits.TRISB3

void main(void){
int ServoPos;
    OSCCON=0x70;                //Osc=8MHz
    ADCON0=0b00000101;          //A2D on and select AN1
    ADCON1=0x7d;                //A1 = analogue
    ADCON2=0b10110101;          //Right justify - Fosc/16
    ServoTris=0;                //make servo pin output
    ServoPin=0;                 //Servo output off
    CCP1CON=0b00001011;         //Special event trigger
    T1CON=0b10010001;           //Timer 1 on with Pre=2
    ServoPos=1500;              //set servo to mid position
    CCPR1=ServoPos;             //set CCP initial value   
    while(1){
        while(!PIR1bits.CCP1IF);    //wait for CCP interrupt bit
        ServoPin=0;                 //end pulse
        CCPR1=20000-ServoPos;       //Off time = 20mS - Servo Time
        PIR1bits.CCP1IF=0;          //clear int flag
        ADCON0bits.GO=1;            //start conversion
        while(ADCON0bits.GO);       //Wait for it to complete
        ServoPos=ADRES+1000;        //Pos will be 1mS to 2.023mS
        while(!PIR1bits.CCP1IF);    //wait for int flag
        ServoPin=1;                 //start pulse
        CCPR1=ServoPos;             //Servo time in uS
        PIR1bits.CCP1IF=0;          //clear int flag
    }
}

Have fun.

Mike.
Project file attached.
 
Last edited:
Your code looks great, I'm starting to be able to read and understand C18.

I've noticed many C18 programs use
#include <p18Cxxxx.h>
 
Last edited by a moderator:
What are C header files for?

The inclusion of 18cxxxx is done to get the proper definitions for your target pic (given that it is an 18).

This is the first few lines of 18cxxxx.h

Code:
#ifndef _P18CXXX_H
#define _P18CXXX_H

#if defined(__18C242)
#include <p18c242.h>
#elif defined(__18C252)
#include <p18c252.h>
#elif defined(__18C442)
#include <p18c442.h>
#elif defined(__18C452)
#include <p18c452.h>

So, by picking your target as say and 18F1320, then p18f1320.h will get included. Why don't we just include p18f1320.h directly.
Well suppose you change targets to "say" a 18F4620, then you have to edit the include line. This is just the way it is done for proper coding. ;)

So what is in p18f1320.h

It will hold the definitions for all the memory locations (TRISA, LATA, etc) that you will be using in the program. With the header files, the compiler doesn't have to have "hard coded" things built in. Microchip can build a new pic with things in slightly different places and they just create a new header file and the compiler can find where they are.

I highly recommend that you go view the header files. Mine are in c:\MCC18\h

:)
 
Last edited by a moderator:
I had some trouble with my MPlab when I tested the code. The project wouldn't build due to some linker script error. Gonna try on another laptop. I've just built a servo tester with a 16F684, and asm code from a Myke Predko book. Not much more than a pot and the pic. I'm in the process of building a R/C helicopter, and needed something to center the servoes, in the absence of a radio system. A switch to make the circuit output exactly 1,5 ms for center position would be nice. A two position switch, to choose between center lock and normal operation. Would also be good if the pic outputs 1,5 ms until the pot reaches center position. That would avoid wrecking the servo gears if the switch is moved when the pot is at either end. (if the servo stalls because of mechanical limitis in the model)

Another use is for sending signals to an electronic speed controller (ESC). I've done that with an ESC for a brushless motor. Most ESCs woun't arm before you send them a zero throttle signal, 1,0 ms. Wouldn't harm if the servo tester just sent 1,0 ms until the pot is at zero throttle after boot up. And maybe a blinking LED if the pot is at > zero throttle.
My sport R/C helicopter turned into a attack helicopter the other day. Spinning up at full speed on top of a table inside my garage. Falling down, chopping a big chunk of the table plate, while I was running for my life...
 
Last edited by a moderator:
Watch out with powering the Servo with your USB port. Using a powered hub will help as they can deliver the current a servo motor will need.
 
Mike,

May I suggest an interrupt version of that code for newcomers to study? The ISR vector setup isn't quite as intuitive as it is in some other languages.

Regards...
 
Mike said:
Mike,

May I suggest an interrupt version of that code for newcomers to study? The ISR vector setup isn't quite as intuitive as it is in some other languages.

Regards...

Might have a go at that tomorrow. Good to see you back, been anywhere nice?

Mike.
 
Pommie,

Thanks for the code. I have ordered a junebug and one of my future projects will need this exact code.

Corse, I still need to go through the tuts and learn C! But it still lets me see actual code and I tend to learn better from examples that I can dissect.
 
Odin said:
I'm in the process of building a R/C helicopter, and needed something to center the servoes, in the absence of a radio system. A switch to make the circuit output exactly 1,5 ms for center position would be nice. A two position switch, to choose between center lock and normal operation. Would also be good if the pic outputs 1,5 ms until the pot reaches center position. That would avoid wrecking the servo gears if the switch is moved when the pot is at either end. (if the servo stalls because of mechanical limitis in the model)

Here is a version that will go fully left if button 1 pressed, center if button 2 pressed and fully right if button 3 pressed. If you press all 3 buttons together it will take up the position defined by the pot.

I also changed it to use interrupts because, as Mike said, it isn't as intuitive as one would imagine.

Mike.
Code:
#include <p18f1320.h>
#pragma config WDT = OFF, LVP = OFF, OSC = INTIO2

#define ServoPin LATBbits.LATB3
#define ServoTris TRISBbits.TRISB3

void ccp1_isr();                    

volatile int ServoPos;          //used to hold the servo position

#pragma code low_vector=0x18    //setup the ISR vector
void low_interrupt (){
  _asm GOTO ccp1_isr _endasm    //jump to interrupt handler
}
#pragma code

#pragma interruptlow ccp1_isr   //the ISR
void ccp1_isr(){
    if(ServoPin==1){            //will be 1 if we are at end of pulse
        ServoPin=0;             //turn off servo output
        CCPR1=20000-ServoPos;   //Off time = 20mS - Servo Time
    }
    else{
        ServoPin=1;             //turn on servo output
        CCPR1=ServoPos;         //On time 
    }
    PIR1bits.CCP1IF=0;          //clear int flag
}
#pragma code

void main(){
    OSCCON=0x70;                //Osc=8MHz
    ADCON0=0b00000101;          //A2D on and select AN1
    ADCON1=0x7d;                //A1 = analogue
    ADCON2=0b10110101;          //Right justify - Fosc/16
    ServoTris=0;                //make bit 0 output
    ServoPin=0;                 //Servo output off
    CCP1CON=0b00001011;         //Special event trigger
    T1CON=0b10010001;           //Timer 1 on with Pre=2
    ServoPos=1500;              //set servo to mid position
    CCPR1=ServoPos;             //set CCP initial value   
    PIE1bits.CCP1IE=1;          //enable CCP1 interrupt
    INTCONbits.PEIE=1;          //enable peripheral interrupts
    INTCONbits.GIE=1;           //enable glogal interrupts
    INTCON2bits.RBPU=0;         //enable port b week pullups
    while(1){                   //loop forever
    static char Mode=0;             //0 = use ADC, 1=fixed pos
    static char Keys;               //holds previous key values
    char OldKeys,Edges;             //local variables
        while(!ServoPin);           //Wait for start of servo pulse
        while(ServoPin);            //wait for end - makes for good debounce
        OldKeys=Keys;               //make a copy of keys
        Keys=PORTB&0b00100101;      //get switch state
        Keys^=0b00100101;           //make pressed keys = 1
        if(Keys==0b00100101)        //all 3 pressed?
            Mode=0;                 //yes, set to use ADC input
        Edges=(Keys^OldKeys);       //keep only keys that have changed
        Edges&=Keys;                //keep only new key presses - not key releases
        if(Mode==0){                //are we using the pot?
            ADCON0bits.GO=1;        //yes, start conversion
            while(ADCON0bits.GO);   //Wait for it to complete
            ServoPos=ADRES+1000;    //Pos will be 1mS to 2.023mS
        }
        if(Edges!=0)                //any key pressed
            Mode=1;                 //switch to fixed mode
        if(Edges==0b00000001)       //Key 1 pressed
            ServoPos=1000;          //set servo fully left
        if(Edges==0b00000100)       //key 2?
            ServoPos=1500;          //set center
        if(Edges==0b00100000)       //key 3?
            ServoPos=2000;          //set fully right
    }
}
 
Last edited:
Thanks Pommie, I'm starting to C the light. :)

Now I've got to learn what pragma do, I originally thought it was for CONFIG now it also seems to be like ORG
 
Very nice Mike (Pommie). Again, excellent comments for newcomer and veteran alike.

The 18F1320 Data Sheet seems to indicate that the CCP/ECCP module "special event trigger" mode automatically starts an ADC acquisition and conversion by setting the ADCON0bits.GO bit for you if the ADC module is turned on. Does that mean you could eliminate setting the ADCON0bits.GO bit? Would that require moving the ADC conversion into the ISR (to avoid timing problems)? Would there be any advantage doing it that way (in the ISR)?

Code:
void ccp1_isr()
{ if(ServoPin = !ServoPin)    // if toggled ServoPin = 1
    CCPR1 = ServoPos;         // setup "on" time match value
  else                        // else
    CCPR1 = 20000 - ServoPos  // setup "off" time match value
  while(ADCON0bits.GO);       // wait for ADC conversion
  ServoPot=ADRES+1000;        // <-- new variable
  PIR1bits.CCP1IF=0;          // clear CCP1 interrupt flag bit
}
 
Last edited:
I hadn't realised that the ADC would automatically be triggered. On the 16 series this only happens on CCP2 (when 2 CCP modules are available) and I had assumed it would be the same for the 18 series.

I just commented out the setting of the Go bit and it still works. The "special event trigger" does start the conversion.

Mike.
 
Is there really that few people experimenting with the Junebug that nobody found this post interesting?
Work has been hectic lately and I haven't had much time to play. Saw the post, but couldn't do anything with it till today.

Here's an asm port of Pommie's first posted program:
Code:
	list	p=18F1320
	include	<p18F1320.inc>
	CONFIG	OSC=INTIO2,WDT=OFF,MCLRE=ON,LVP=OFF

	cblock	0x00
		ServoPosH,ServoPosL,src1,src2,dst1,dst2
	endc

	org	0x0000
init	movlw	0x70
	movwf	OSCCON			;Osc=8MHz
	movlw	b'00000101'		;A2D on and select AN1
	movwf	ADCON0
	movlw	b'01111101'		;A1 = analog
	movwf	ADCON1
	movlw	b'10110101'		;Right justify - Fosc/16
	movwf	ADCON2
	bcf	TRISB,3			;make servo pin output
	bcf	LATB,3			;Servo output off
	movlw	b'00001011'		;Special event trigger
	movwf	CCP1CON
	movlw	b'10010001'		;Timer 1 on with Pre=2
	movwf	T1CON
	movlw	0x05			;set servo to mid position
	movwf	ServoPosH
	movwf	CCPR1H			;and set CCP initial value
	movlw	0xdc
	movwf	ServoPosL
	movwf	CCPR1L
	
main	btfss	PIR1,CCP1IF		;wait for CCP interrupt bit
	goto	main
	bcf	LATB,3			;end pulse
	movlw	0x4e			;Off time = 20ms - Servo Time
	movwf	src1
	movlw	0x20
	movwf	src2
	movff	ServoPosH,dst1
	movff	ServoPosL,dst2
	call	sub16
	movff	dst1,CCPR1H
	movff	dst2,CCPR1L
	bcf	PIR1,CCP1IF		;clear int flag
	bsf	ADCON0,GO		;start conversion
conv	btfsc	ADCON0,DONE		;Wait for it to complete
	goto	conv
	movff	ADRESH,src1		;Pos will be 1ms to 2.023ms
	movff	ADRESL,src2
	movlw	0x03
	movwf	dst1
	movlw	0xe8
	movwf	dst2
	call	add16
	movff	dst1,ServoPosH
	movff	dst2,ServoPosL
wait	btfss	PIR1,CCP1IF		;wait for int flag
	goto	wait
	bsf	LATB,3			;start pulse
	movff	ServoPosH,CCPR1H	;Servo time in uS
	movff	ServoPosL,CCPR1L
	bcf	PIR1,CCP1IF		;clear int flag
	goto	main

sub16	movf	src2,W
	subwf	dst2,F
	movf	src1,W
	btfss	STATUS,C
	incf	src1,W
	subwf	dst1,F
	return

add16	movf	src2,W
	addwf	dst2,F
	movf	src1,W
	btfsc	STATUS,C
	incf	src1,W
	addwf	dst1,F
	return

	end
 
Last edited:
Nicely done Futz. Are you going to do the interrupt version as well?

One little suggestion to make it more readable. Use word variables with Lo and Hi on the end instead of 1 & 2, use the . with decimal numbers and make use of the low and high directives.
Code:
;so this
	movlw	0x03
	movwf	dst1
	movlw	0xe8
	movwf	dst2
	call	add16
;becomes
	movlw	low(.1000)
	movwf	dstLo
	movlw	high(.1000)
	movwf	dstHi
	call	add16

Mike.
 
Pommie said:
Nicely done Futz. Are you going to do the interrupt version as well?
The port is done, but the buttons aren't working yet. I have to go through it (maybe tomorrow) and see what I did wrong. Most likely I got one of the bit tests backward and it's branching the wrong way. The potentiometer/servo part works fine though.

One little suggestion to make it more readable. Use word variables with Lo and Hi on the end instead of 1 & 2,
Those 1 & 2 things came from Chuck McMannis, the guy I stole those 16-bit add/subtract code snips from. I didn't change a thing. I prefer to use H and L suffixes. I'll change that in the second port.

use the . with decimal numbers and make use of the low and high directives.
Mike.
I used a decimal number? :eek: If so, I sure didn't mean to. I usually think in hex. :D
EDIT: Ah, I see what you're talking about.

Good stuff. I'll put those suggestions into future programs. Thanks! :D
 
Last edited:
Pommie said:
Are you going to do the interrupt version as well?
Finally found the problem. Here's the asm port of Pommie's second program:
Code:
	list	p=18F1320
	include	<p18F1320.inc>
	CONFIG	OSC=INTIO2,WDT=OFF,MCLRE=ON,LVP=OFF

	cblock	0x00
		Mode,Keys,OldKeys,Edges,ServoPosH,ServoPosL
		srcH,srcL,dstH,dstL
	endc

	org	0x00
	goto	init

	org	0x18
isr	btfss	PORTB,3			;the ISR
	goto	else1
	bcf	LATB,3			;turn off servo output
	movlw	high(.20000)		;Off time = 20ms - Servo Time
	movwf	srcH
	movlw	low(.20000)
	movwf	srcL
	movff	ServoPosH,dstH
	movff	ServoPosL,dstL
	call	sub16
	movff	dstH,CCPR1H
	movff	dstL,CCPR1L
	goto	isr_done
else1	bsf	LATB,3			;turn on servo output
	movff	ServoPosH,CCPR1H	;On time
	movff	ServoPosL,CCPR1L
isr_done
	bcf	PIR1,CCP1IF		;clear int flag
	retfie	FAST
	
init	movlw	0x70
	movwf	OSCCON			;Osc=8MHz
	movlw	b'00000101'		;A2D on and select AN1
	movwf	ADCON0
	movlw	b'01111101'		;A1 = analog
	movwf	ADCON1
	movlw	b'10110101'		;Right justify - Fosc/16
	movwf	ADCON2
	bcf	TRISB,3			;make servo pin output
	bcf	LATB,3			;Servo output off
	movlw	b'00001011'		;Special event trigger
	movwf	CCP1CON
	movlw	b'10010001'		;Timer 1 on with Pre=2
	movwf	T1CON
	movlw	high(.1500)		;set servo to mid position
	movwf	ServoPosH
	movwf	CCPR1H			;and set CCP initial value
	movlw	low(.1500)
	movwf	ServoPosL
	movwf	CCPR1L
	bsf	PIE1,CCP1IE		;enable CCP1 interrupt
	bsf	INTCON,PEIE		;enable peripheral interrupts
	bsf	INTCON,GIE		;enable global interrupts
	bcf	INTCON2,RBPU		;enable PORTB weak pullups
	
main	btfss	PORTB,3			;Wait for start of servo pulse
	goto	main
wait2	btfsc	PORTB,3			;wait for end - makes for good debounce
	goto	wait2
	movff	Keys,OldKeys		;make a copy of keys
	movf	PORTB,W			;get switch state
	andlw	b'00100101'
	movwf	Keys
	movlw	b'00100101'		;make pressed keys = 1
	xorwf	Keys,F
	movlw	b'00100101'		;all 3 pressed?
	cpfseq	Keys
	goto	not3
	clrf	Mode			;yes, set to use ADC input
not3	movf	Keys,W			;keep only keys that have changed
	xorwf	OldKeys,W
	movwf	Edges
	andwf	Keys,W			;keep only new key presses - not key releases
	movwf	Edges
	tstfsz	Mode			;are we using the pot?
	goto	nomode
	bsf	ADCON0,GO		;yes, start conversion
waitcv	btfsc	ADCON0,DONE		;wait for it to complete
	goto	waitcv
	movff	ADRESH,srcH		;Pos will be 1ms to 2.023ms
	movff	ADRESL,srcL
	movlw	high(.1000)
	movwf	dstH
	movlw	low(.1000)
	movwf	dstL
	call	add16
	movff	dstH,ServoPosH
	movff	dstL,ServoPosL
nomode	tstfsz	Edges			;any key pressed
	goto	mode1			;go switch to fixed mode
	goto	mode0
mode1	movlw	1			;switch to fixed mode
	movwf	Mode
mode0	movlw	1
	subwf	Edges,W			;key 1 pressed
	btfss	STATUS,Z
	goto	key2
	movlw	high(.1000)		;set servo fully left
	movwf	ServoPosH
	movlw	low(.1000)
	movwf	ServoPosL
	goto	again
key2	movlw	0x04			;key 2?
	subwf	Edges,W
	btfss	STATUS,Z
	goto	key3
	movlw	high(.1500)		;set center
	movwf	ServoPosH
	movlw	low(.1500)
	movwf	ServoPosL
	goto	again
key3	movlw	0x20			;key 3?
	subwf	Edges,W
	btfss	STATUS,Z
	goto	again
	movlw	high(.2000)		;set fully right
	movwf	ServoPosH
	movlw	low(.2000)
	movwf	ServoPosL	
again	goto	main

sub16	movf	srcL,W
	subwf	dstL,F
	movf	srcH,W
	btfss	STATUS,C
	incf	srcH,W
	subwf	dstH,F
	return

add16	movf	srcL,W
	addwf	dstL,F
	movf	srcH,W
	btfsc	STATUS,C
	incf	srcH,W
	addwf	dstH,F
	return

	end

and here's the first one again, modified with the suggestions from a few posts ago:
Code:
	list	p=18F1320
	include	<p18F1320.inc>
	CONFIG	OSC=INTIO2,WDT=OFF,MCLRE=ON,LVP=OFF

	cblock	0x00
		ServoPosH,ServoPosL,srcH,srcL,dstH,dstL
	endc

	org		0x0000
init	movlw	0x70
	movwf	OSCCON			;Osc=8MHz
	movlw	b'00000101'		;A2D on and select AN1
	movwf	ADCON0
	movlw	b'01111101'		;A1 = analog
	movwf	ADCON1
	movlw	b'10110101'		;Right justify - Fosc/16
	movwf	ADCON2
	bcf	TRISB,3			;make servo pin output
	bcf	LATB,3			;Servo output off
	movlw	b'00001011'		;Special event trigger
	movwf	CCP1CON
	movlw	b'10010001'		;Timer 1 on with Pre=2
	movwf	T1CON
	movlw	high(.1500)		;set servo to mid position
	movwf	ServoPosH
	movwf	CCPR1H			;and set CCP initial value
	movlw	low(.1500)
	movwf	ServoPosL
	movwf	CCPR1L
	
main	btfss	PIR1,CCP1IF		;wait for CCP interrupt bit
	goto	main
	bcf	LATB,3			;end pulse
	movlw	high(.20000)		;Off time = 20ms - Servo Time
	movwf	srcH
	movlw	low(.20000)
	movwf	srcL
	movff	ServoPosH,dstH
	movff	ServoPosL,dstL
	call	sub16
	movff	dstH,CCPR1H
	movff	dstL,CCPR1L
	bcf	PIR1,CCP1IF		;clear int flag
	bsf	ADCON0,GO		;start conversion
conv	btfsc	ADCON0,DONE		;Wait for it to complete
	goto	conv
	movff	ADRESH,srcH		;Pos will be 1ms to 2.023ms
	movff	ADRESL,srcL
	movlw	high(.1000)
	movwf	dstH
	movlw	low(.1000)
	movwf	dstL
	call	add16
	movff	dstH,ServoPosH
	movff	dstL,ServoPosL
wait	btfss	PIR1,CCP1IF		;wait for int flag
	goto	wait
	bsf	LATB,3			;start pulse
	movff	ServoPosH,CCPR1H	;Servo time in uS
	movff	ServoPosL,CCPR1L
	bcf	PIR1,CCP1IF		;clear int flag
	goto	main

sub16	movf	srcL,W
	subwf	dstL,F
	movf	srcH,W
	btfss	STATUS,C
	incf	srcH,W
	subwf	dstH,F
	return

add16	movf	srcL,W
	addwf	dstL,F
	movf	srcH,W
	btfsc	STATUS,C
	incf	srcH,W
	addwf	dstH,F
	return

	end
 
Last edited:
blueroomelectronics said:
Watch out with powering the Servo with your USB port.

Found out the hard way... No damage though. I'm using a separate 5 V powersupply for the servo now.
 
Status
Not open for further replies.

Latest threads

New Articles From Microcontroller Tips

Back
Top