C18 compiler for PIC

08-Feb-2012

Home
Setup the C18 compiler

 

 

My Resources, PIC C18 code

This shows example programs to use with the C18 compiler. Note that this must be used with a PIC 18 series.

Further documents may be found from the 'Docs' directory that the C18 compiler is installed in, e.g. C:\MCC\docs.

The documents may also be viewed from here and are the following:

Getting started with MPLAB and C18

C18 User Guide

C18 Libraries

PIC18F4550 Datasheet

Presentation on introduction to PIC

 

Contents (on this page)

Setting the configuration bits

Set an LED on a port to on

Flash an LED

Latches

Reading Inputs

A/D Converters

Setting interrupts

Using interrupts to control a servo

Read and Write to EEPROM

RS232 Serial communication

Read an encoder

Pulse Width Modulation

 

Setting the configuration bits

These are set to tell which PIC is used, the PIC hardware what oscillators to expect and if a watchdog timer is installed etc. The include file should be changed to your particular PIC.

For now, just use the bits set below:

#include <p18F4550.h>
#pragma config FOSC = INTOSC_EC
#pragma config PWRT = OFF
#pragma config BOR = OFF
#pragma config MCLRE = ON
#pragma config PBADEN = OFF
#pragma config ICPRT = OFF
#pragma config LVP = OFF
#pragma config WDT = OFF,DEBUG=OFF

Note that if an external oscillator is installed, the FOSC will need to be changed:

#pragma config FOSC = HS

To find out the specific configuration bits for your PIC:

In MPLAB, select: Help-->Topics-->PIC18Config Settings

Set LED on port to on

/* Sets LED on port E0 to on */
<<include the configuration bits here>>
 
void main (void)
{
 
     TRISE=0; //Set port E to outputs
     PORTE=1;
}

Flash led

To flash an LED, you must use a delay. The code for the delay is in the include file 'Delays.h'.

The commands are:
Delay1TCY Delay one instruction cycle.
Delay10TCYx Delay in multiples of 10 instruction cycles.
Delay100TCYx Delay in multiples of 100 instruction cycles.
Delay1KTCYx Delay in multiples of 1,000 instruction cycles.
Delay10KTCYx Delay in multiples of 10,000 instruction cycles.

The format of the command is followed by a number in brackets (number of times to repeat the delay):

e.g. to delay for 50 instruction cycles, use:

Delay1TCY(50);

or

Delay10TCYx(5);

Code to flash an LED

/* Sets LED on port E0 to on */
<<include the configuration bits here>>
#include <delays.h>

void main (void)
{
    TRISE=0; //Set port E to outputs
    while(1)
    {
        PORTE=1;
        Delay10KTCYx(100);
        PORTE=0;
        Delay10KTCYx(100);
    }
}

Latches

To control the output of a single bit, use the latch LATportbits.LATportbit.

E.g. To set bit 4 of port E, LATEbits.LATE4=1

The example below alternately flashes LEDs on bits 0 and 1 of port E:

<<include the configuration bits here>>
#include <delays.h>

void main (void)
{
    TRISE=0; //Set port E to outputs
    while(1)
    {

        LATEbits.LATE0=1;
        LATEbits.LATE1=0;
        Delay10KTCYx(100);
        LATEbits.LATE0=0;
        LATEbits.LATE1=1;
        Delay10KTCYx(100);
    }
}

A clearer way of programming is to use '#define' where the port can be given a useful name. This also works for setting individual 'TRIS' bits too as in:

#define trisled1 TRISDbits.TRISD0
#define led1 LATDbits.LATD0
trisled1=0; //Sets led port to output
led1=1; //Turns on LED

Reading inputs

Inputs can be read, first, set the direction of the pins to input, e.g.

TRISB=0b11111111; Sets all pins on port B to input

Read the pins e.g.

If (PORTB&0b1) .....   Will act when bit 0 of PORTB is high

A clearer way of programming is to use '#define' where the port can be given a useful name. This also works for setting individual 'TRIS' bits too as in:

#define sw1 PORTBbits.RB3
#define trissw1 TRISBbits.TRISB3
trissw1=1; //Sets to input
//Test switch high
if (sw1==1) …

Pullups

Internal pull-up resistors can be enabled by resetting bit 6 of INTCON2 thus:

INTCON2=INTCON2&0B0111111; //Set pullups on port B

For details of why INTCON2 is used etc, see section 10.2 (page 114) of the PIC18F4550 datasheet.

A/D converters

The A/D converter is built in to most PIC microcontrollers. Port A is usually configurable as an analogue port to take a voltage between 0 to 5V. This example sets port A0 and A1 to be analogue input ports. Note, the libraries available with the C18 compiler do not seem to work properly, it is better to modify the bits directly as in my code, the 'adc.h' file is not necessary.

<<include the configuration bits here>>

void setupADC(void)

{
//Set port A bits to input
TRISA=0b11;
//Set FOSC/32, this is detailed in section 21.0, page 261 of the PIC18f4550 datasheet
ADCON2bits.ADCS0=0;
ADCON2bits.ADCS1=1;
ADCON2bits.ADCS2=0;

//Set voltage reference and port A0 and A1 as analogue only, this is detailed in section 21.0, page 260 of the PIC18f4550 datasheet
ADCON1=0b00001101;


ADCON0bits.ADON=1; //Turn on ADC
}

unsigned int readADC(void)
{
unsigned char adh,adl;
unsigned int result;

//Read port A0
ADCON0bits.CHS0=0;
ADCON0bits.CHS1=0;
ADCON0bits.CHS2=0;
ADCON0bits.CHS3=0;
//CHANNEL 0 set, note, to read Port A1, ADCON0bits.CHS0=1, page 259 of the PIC18f4550 datasheet
ADCON0bits.GO=1;
while (ADCON0bits.GO);

//Convert high and low bytes to a 16 bit value
adl= ADRESL;
adh= ADRESH;
result=adh*256;
result=result+adl;
return result;
}

 

Read / write to EEPROM

When writing to the EEPROM, first disable the interrupts. The address and data bytes are set followed by writing 0x55 and then 0xaa to EECON2.

The details are in section 7.0 of the PIC18F4550 datasheet.

void writeeeprom(unsigned char addr,unsigned char byte)
{
unsigned char i=INTCONbits.GIEH;
INTCONbits.GIEH = 0; //disable interrupts
EECON1bits.EEPGD=0; //Write to EEPROM
EECON1bits.CFGS=0; //EEPROM not config bits
EECON1bits.WREN=1; //Allows write

EEADR=addr;
EEDATA=byte;
EECON2=0x55;
EECON2=0xaa;
EECON1bits.WR=1;
while(EECON1bits.WR); //Wait until written
//while(!PIR2bits.EEIF);
//PIR2bits.EEIF=0;
EECON1bits.WREN=0; //No more write
INTCONbits.GIEH = i; //restore interrupts
}

 


unsigned char readeeprom(unsigned char addr)
{
EECON1bits.CFGS=0; //EEPROM not config bits
EECON1bits.EEPGD=0;
EEADR=addr;
EECON1bits.RD=1;
return (unsigned int) EEDATA;
}

 

If a 16 bit integer needs to be written and read into EEPROM, use the following code:
 

void writeeeprom16(char addr,unsigned int val)
{
char h,l;
h=(char)(val/256);
l=val-(h*256);
writeeeprom(addr,l);
writeeeprom(addr+1,h);
}


unsigned int readeeprom16(char addr)
{

//unsigned int value;
l=readeeprom(addr);
h=readeeprom(addr+1);
//hi=48;li=57;
value=(h*256)+l;
return value;
}

 


 

Setting an interrupt

The interrupts are called from either a timer, or from a change in input port. This example shows how to set a timer (using the 'timers' library), and set an interrupt to be called by this. The code is incomplete but shows the extras that must be included for it to work.

The OpenTimer command found in the C18 library sets timer 0 to be 16 bit with a post scalar of 4, i.e. if a 40MHz crystal is used, this gives a cycle frequency of 10Mhz or 0.1u seconds. Now 16 bits means it counts from 0 to 65535 before rolling over and generating an interrupt, i.e. this is 65536 x 0.1 u secs = 6.5536 milli-secs. With a post scalar of 4, this gives an interrupt time of 6.5536 milli-secs x 4 = 0.0262 seconds.

Presentation on interrupts

The OpenTimer command takes the following parameters:

TIMER_INT_ON Interrupt enabled

TIMER_INT_OFF Interrupt disabled

Timer Width:

T0_8BIT 8-bit mode

T0_16BIT 16-bit mode

Clock Source:

T0_SOURCE_EXT External clock source (I/O pin)

T0_SOURCE_INT Internal clock source (TOSC)

External Clock Trigger (for T0_SOURCE_EXT):

T0_EDGE_FALL External clock on falling edge

T0_EDGE_RISE External clock on rising edge

Prescale Value:

T0_PS_1_1 1:1 prescale

T0_PS_1_2 1:2 prescale

T0_PS_1_4 1:4 prescale

T0_PS_1_8 1:8 prescale

T0_PS_1_16 1:16 prescale

T0_PS_1_32 1:32 prescale

T0_PS_1_64 1:64 prescale

T0_PS_1_128 1:128 prescale

T0_PS_1_256 1:256 prescale

The following calculations are for a 20Mhz clock and are in seconds:

 

 An Excel program to calculate the timings is here (put the clock frequency in cell A1).


#include <p18F4550.h>
#include <timers.h>

#pragma config FOSC = INTOSC_EC
#pragma config PWRT = OFF
#pragma config BOR = OFF
#pragma config MCLRE = ON
#pragma config PBADEN = OFF
#pragma config ICPRT = OFF
#pragma config LVP = OFF
#pragma config WDT = OFF,DEBUG=OFF

void set_timer(void)
{
//Ensure the oscillator is running at a specific speed
OSCCONbits.IRCF2=1;
OSCCONbits.IRCF1=1;
OSCCONbits.IRCF0=1;

OpenTimer0(TIMER_INT_ON & T0_16BIT & T0_SOURCE_INT & T0_PS_1_256);
INTCONbits.GIEH = 1; //enable interrupts
}

//----------------------------------------------------------------------------
// High priority interrupt routine
#pragma code
#pragma interrupt InterruptHandlerHigh

void InterruptHandlerHigh ()
{ //Every 0.026 secs
if (INTCONbits.TMR0IF)
{ //check for TMR0 overflow
INTCONbits.TMR0IF = 0; //clear interrupt flag
WriteTimer0(65000);
//Code used by the interrupt goes here
LATCbits.LATC0 =!LATCbits.LATC0;
}
}
#pragma code InterruptVectorHigh = 0x08
void InterruptVectorHigh (void)
{
_asm
goto InterruptHandlerHigh //jump to interrupt routine
_endasm
}


void main (void)
{
TRISC=0;
set_timer();
// WriteTimer0(0);
while(1){

//interrupt will run now

}
}

 

Using two interrupts to control a servo

The interrupt time can be accurately set using the WriteTimer0(value) command, where, for a 40Mhz crystal and a pre/post scalar of 1:1, value will represent time in intervals of 0.0000001 seconds.

An RC servo motor needs a pulse of between 1-2ms every 20ms (see http://www.seattlerobotics.org/guide/servos.html ).

Here, Timer1 triggers every 20ms, sets an output pin and enables timer0. When timer0 triggers, it resets the output pin and disables itself.

In the program below, Timer1 triggers every 20ms and timer 0 triggers every 't' msecs: 


#include <p18F2520.h>
#include <timers.h>

#pragma config OSC = HS
#pragma config PWRT = OFF
#pragma config MCLRE = ON
#pragma config PBADEN = OFF
#pragma config LVP = OFF
#pragma config WDT = OFF,DEBUG=OFF

#define SERVO_PULSE_HIGH LATCbits.LATC0 =1
#define SERVO_PULSE_LOW LATCbits.LATC0 =0
#define CONVERT_mS_PULSE 10000.0

unsigned int timer0;
float t;

void set_timer(void)
{
//40Mhz clock, each increment 0.0000001 secs (interrupt at overflow of 65536)
OpenTimer0(TIMER_INT_ON & T0_16BIT & T0_SOURCE_INT & T0_PS_1_1);
OpenTimer1(TIMER_INT_ON & T1_16BIT_RW & T1_SOURCE_INT & T1_PS_1_4 & T1_OSC1EN_OFF);
T1CONbits.TMR1CS = 0; //Timer 1Clock internal source
WriteTimer1(15536);
//Every 20ms.
//since PS=1:4 increment time is 0.0004ms
//20 / 0.0004 = 50000
// 65536 - 50000 = 15536
INTCONbits.GIEH = 1; /* Enable global Interupt */
}

//----------------------------------------------------------------------------
// High priority interrupt routine
#pragma code
#pragma interrupt InterruptHandlerHigh

void
InterruptHandlerHigh ()
{
if (INTCONbits.TMR0IF)
{
LATCbits.LATC0 =0; //Servo pulse low
T0CONbits.TMR0ON = 0; //Timer0 off
}
//Timer 1 (slow)
//Every 20msecs
if (PIR1bits.TMR1IF)
{
WriteTimer1(15536);
 // reset interrupt event flag timer1
     PIR1bits.TMR1IF = 0;
LATCbits.LATC0 =1;
timer0=(unsigned int)(65536-(t*CONVERT_mS_PULSE));
WriteTimer0(timer0);
   // enable timer0
     T0CONbits.TMR0ON = 1;
INTCONbits.TMR0IF = 0; //clear interrupt flag
}
}

#pragma code InterruptVectorHigh = 0x08
void
InterruptVectorHigh (void)
{
_asm
goto InterruptHandlerHigh //jump to interrupt routine
_endasm
}

void main (void)
{
TRISC=0;
t=2.5; //Length of pulse in ms
set_timer();
 

while(1);

}

 

RS232 Serial communication

PC's usually use asynchronous communication (see http://en.wikipedia.org/wiki/Asynchronous_serial_communication) The baud rate is set using the 'spbrg' register using the formula baud=Fosc/(64*(spbrg+1). So if spbrg=64,the baud rate is almost 9600 using a 40MHz crystal as in the code below:

#include <usart.h>

#include <stdio.h>

#pragma config OSC = HS

#pragma config PWRT = OFF

#pragma config MCLRE = ON

#pragma config PBADEN = OFF

#pragma config LVP = OFF

#pragma config WDT = OFF,DEBUG=OFF

 

void main() {

char x[10];

int n=0;

int a;

TRISC=0;

OpenUSART(USART_TX_INT_OFF & USART_RX_INT_ON &

USART_ASYNCH_MODE & USART_EIGHT_BIT &

USART_CONT_RX & USART_BRGH_LOW, 64);

printf("Hello, world!\n");

getsUSART(x,5 );

x[5]=0;

a=atoi(x);

a++;

printf("%d",a);

while(1);

}

 

Read an encoder

A two channel encoder consists of two light sensors slightly offset (90 degrees phase difference).

The trick with reading an encoder is to count the edge of an input pulse.

Counting both the rising and falling edges can double the number of counts per revolution (increase resolution).

If a two output encoder is used with both A and B channels, the direction of rotation can be determined. The A channel can be used as the rotation count whilst the B channel can determine direction by its high or low state. This is done by counting the edge of the A channel and reading if the B channel is high or low when the edge is counted.

The resolution can be increased fourfold by counting on both the rising and falling edges of the A and B channels, the other channel state determines direction, e.g. if a rising edge is detected on the B channel, the state of A determines its direction.

Presentation on encoders

This code snippet assumes the A and B channels are connected to Port B bits 3 and 4 and shows both rising and falling edge counting using one channel only:

int read_encoder(void)
{
static unsigned char prev_b3=0;
static int encl=0;
if (PORTB & 0b1000)
if ((PORTB & 0b1000)&&(!prev_b3))
{
if (PORTB & 0b10000) encl++;else encl--;
prev_b3=1;
}
if ((!(PORTB & 0b1000))&&(prev_b3))
{
if (PORTB & 0b10000) encl--;else encl++;
prev_b3=0;
}
return encl;

}

Pulse Width Modulation

This uses the library 'pwm.h'

/* Sets the PWM value on CCP1 to 50% duty cycle*/
#include <p18F4550.h>
#include <pwm.h>
#include<timers.h>

#pragma config FOSC = INTOSC_EC

#pragma config PWRT = OFF
#pragma config BOR = OFF
#pragma config MCLRE = ON
#pragma config PBADEN = OFF
#pragma config ICPRT = OFF
#pragma config LVP = OFF
#pragma config WDT = OFF,DEBUG=OFF

void main (void)
{

TRISC=0;
OpenTimer2(TIMER_INT_OFF & T2_PS_1_1 & T2_POST_1_1);
OpenPWM1(0xff);
SetOutputPWM1(SINGLE_OUT,PWM_MODE_1);
SetDCPWM1(512);
while(1)
{

}
}

 

Ultrasonics (needs interrupt as above)

This uses the SRF04 from Robot Electronics


char ultrasonic(void) //returns 1 is readings ready, else 0
{
static char usstate=0; //state machine state
static int usonic_count1=0,usonicwait1=0;
char ready=0;

switch (usstate)
{
case 17://go on to next sensor, or wait 10ms before retrigger
usonicwait1++;
if (usonicwait1>20000) usstate=0;
break;
case 16:
usonic3=usonic_count1;
usonicwait1=0;
usstate=17;
ready=1;
break;
case 15:
usonic_count1++;
if (!(PORTD & 0b100000)) usstate=16;
if (usonic_count1>800){usonic3=0;usstate=0;}//echo pulse not lowering after >30ms, restart
break;
case 14:
usonic_count1=0;
usonicwait1++;
if (PORTD & 0b100000) usstate=15;
if (usonicwait1>200) {usstate=0;usonic3=0;}//echo pulse not coming, restart
break;
case 13:
usonicwait1++;
if (usonicwait1>5)
{
LATDbits.LATD4 =0; //send trigger pulse for 50 interrupt
usstate=14;
}
break;
case 12:
LATDbits.LATD4 =1; //high trigger pulse
usonicwait1=0;
usstate=13;
break;
case 11://go on to next sensor, or wait 10ms before retrigger
usonicwait1++;
if (usonicwait1>20000) usstate=12;
break;
case 10:
usonic2=usonic_count1;
usonicwait1=0;
usstate=11;
break;
case 9:
usonic_count1++;
if (!(PORTD & 0b1000)) usstate=10;
if (usonic_count1>800){usonic2=0;usstate=0;}//echo pulse not lowering after >30ms, restart
break;
case 8:
usonic_count1=0;
usonicwait1++;
if (PORTD & 0b1000) usstate=9;
if (usonicwait1>200) {usstate=0;usonic2=0;}//echo pulse not coming, restart
break;
case 7:
usonicwait1++;
if (usonicwait1>5)
{
LATDbits.LATD2 =0; //send trigger pulse for 50 interrupt
usstate=8;
}
break;
case 6:
LATDbits.LATD2 =1; //high trigger pulse
usonicwait1=0;
usstate=7;
break;
case 5://go on to next sensor, or wait 10ms before retrigger
usstate=6;
//usonicwait1++;
//if (usonicwait1>20000) usstate=0;
break;
case 4:
usonic1=usonic_count1;
usonicwait1=0;
usstate=5;
break;
case 3:
usonic_count1++;
if (!(PORTD & 0b10)) usstate=4;
if (usonic_count1>800){usonic1=0;usstate=0;}//echo pulse not lowering after >30ms, restart
break;
case 2:
usonic_count1=0;
usonicwait1++;
if (PORTD & 0b10) usstate=3;
if (usonicwait1>200) {usstate=0;usonic1=0;}//echo pulse not coming, restart
break;
case 1:
usonicwait1++;
if (usonicwait1>5)
{
LATDbits.LATD0 =0; //send trigger pulse for 50 interrupt
usstate=2;
}
break;
case 0:
LATDbits.LATD0 =1; //high trigger pulse
usonicwait1=0;
usstate=1;
break;
}//case
return ready;
}

 

 

 

     

Home | Setup the C18 compiler

This site was last updated 02/08/12