Guides

Guides here will be focus on the pic18 series microcontroller. All the code here will be compiled using C18 from Microchip. Example here can be tested using breadboard.

I2C using Master Synchronous Serial Port (MSSP) Module

posted Apr 19, 2011, 7:01 PM by Danny Ng   [ updated Apr 19, 2011, 10:53 PM ]


I2C are widely use for to communicate with external peripheral such as port expender, EEPROM, Real Time Clock etc. The Master Synchronous Serial Port (MSSP) module in PIC18 can be used to communicated with I2C peripheral. MSSP module can be configure to work as SPI and I2C. In this example the module will be configured as I2C to communicate with a port expender from Microchip MCP23017. The circuit below is built up on breadboard to test the MSSP function as an I2C module. 
The DIL switch on Port B is used to control the LED on Port A of the IO expender. INTB from the IC is connected to the INT2 pin of the PIC so that the PORTA output of the Port expender can be updated with the value from the DIL switch. The code function will be implemented using 2 interrupt, one at the high priority location for the External interrupt and the other for MSSP module at the low interrupt. MSSP module had to be configured as I2C master mode for this example. The code below shows the setting to initialize MSSP module.

SSPADD = 80;
SSPSTAT = 0x80;
SSPCON1 = 0x28;
SSPCON2 = 0x00;

Function for sending and receiving data for I2C is created according to the required protocol to communicate with MCP23017. The code below shows the function i2cTransmit and i2cRecieve for receiving data and the interrupt routine for the handling of transmission/reception. The 2 function is used to load the data buffer for transmission and reception. The Interrupt function will pass the data accordingly to the MSSP module according to the protocol written in the datasheet of MCP23017.

unsigned char  I2Cbusy = 0;
unsigned char  I2CTransmitArray[20];
unsigned char  I2CTransmitElement;
unsigned char  I2CTransmitCurrent;
unsigned char  I2CReceived;

void i2cTransmit(unsigned char *data, unsigned char Element){
int i = 0;
if(I2Cbusy == 0){
while(i < Element){
I2CTransmitArray[i] = *data;
i++;
data++;
}
I2Cbusy = 1;
I2CTransmitCurrent = 0;
I2CTransmitElement = Element;
SSPCON2bits.SEN = 1;
}
}

void i2cReceive(unsigned address, unsigned regis){
if(I2Cbusy == 0){
I2CTransmitArray[0] = address;
I2CTransmitArray[1] = regis;
I2CTransmitArray[2] = 0; // dummy
I2CTransmitArray[3] = address | 1;
I2Cbusy = 2;
I2CTransmitCurrent = 0;
I2CTransmitElement = 4;
SSPCON2bits.SEN = 1;
}
}
/*****************Low priority interrupt vector **************************/
#pragma code low_vector=0x18
void interrupt_at_low_vector(void)
{
  _asm GOTO low_isr _endasm
}

#pragma code

/*****************Low priority ISR **************************/
#pragma interruptlow low_isr
void low_isr (void)
{
PIR1bits.SSPIF = 0;
if(I2Cbusy ==1){
if(I2CTransmitCurrent < I2CTransmitElement)
SSPBUF = I2CTransmitArray[I2CTransmitCurrent];
else if(I2CTransmitCurrent == I2CTransmitElement)
SSPCON2bits.PEN = 1;
else
I2Cbusy = 0;
}
else{
if(I2CTransmitCurrent == 2)
SSPCON2bits.RSEN = 1;
else if(I2CTransmitCurrent < I2CTransmitElement)
SSPBUF = I2CTransmitArray[I2CTransmitCurrent];
else if(I2CTransmitCurrent == I2CTransmitElement)
SSPCON2bits.RCEN = 1;
else if(I2CTransmitCurrent == I2CTransmitElement+1){
I2CReceived = SSPBUF;
SSPCON2bits.ACKDT = 1;
SSPCON2bits.ACKEN = 1;
}
else if(I2CTransmitCurrent == I2CTransmitElement+2)
SSPCON2bits.PEN = 1;
else
I2Cbusy = 0;
}
I2CTransmitCurrent++;
}

In the interrupt service routine, transmission and reception is differentiated based on the value in I2Cbusy; 1 for transmission and 2 for reception. The device address byte with write flag set need to be send to the module follow by the register address and the data to write. Step for writing N number of data is shown below. The steps below is initiated by the function i2cTransmit.
Steps to write register in MCP23017:
Start Condition -> Device Address + Write flag -> Register Address -> Data 1 -> Data 2 .......-> Data N -> Stop Condition
Below is the example code for the transmission of config1 in the code. The I2Cbusy check is to wait until the whole transmission of the config is done.

unsigned char config1[] = {0x40, 0x00, 0x00, 0xFF};
    i2cTransmit(config1, 4);
while(I2Cbusy != 0);

Reading data from one register location is implemented in for the read function although more than one data continuously can be achieve. Since for this example only 1 read in is required, a Nack is sent to the port expender once the on data is received. Based on the data sheet the step below is used to get data from a register location.
Steps to read register in MCP23017:
Start Condition -> Device Address + Write flag -> Register Address -> Restart Condition ->...
Device Address + Read flag -> Data from MCP23017 ->  NACK from Master ->Stop Condition
Only 1 data at the address will be read from MCP23017. Code below show reading of data from Device with address of 0x40 and register address of 0x13. The data read in will be stored in the variable I2CReceived.
    i2cReceive(0x40, 0x13);
while(I2Cbusy != 0);
output[2] = I2CReceived;

For the example data is read only when interrupt is generated by MCP23017. Connected to the INT2 data will be read when interrupt occur. MCP23017 must be configure to generate interrupt on the change of value on the pin by setting the GPINTEN byte in the register. When interrupt occurs I2C read is called to get the data from the port expender.

#pragma code high_vector=0x08
void interrupt_at_high_vector(void)
{
  _asm GOTO high_isr _endasm
}

#pragma code
/*****************High priority ISR **************************/

#pragma interrupt high_isr
void high_isr (void)
{
INTCON3bits.INT2IF = 0;
i2cReceive(0x40, 0x13);
}

The function of the communication is illustrated by controlling the LED with the switches. Main code of the control is shown below. When there is changes to the input at the interrupt, the LED will be updated with the value on the input ports.

while(1){
if(I2Cbusy == 2){
while(I2Cbusy != 0);
output[2] = I2CReceived;
i2cTransmit(output, 3);
}
}

The pictures below show the changes to the LED according to the switch when the code is downloaded. Note the switch position and the LED.
 All LED Off  All LED Lighted
 On According to Switch On According to Switch
The full code and project files is attached below at the attachments.

Using Timer and Interrupt in C18

posted Jan 29, 2011, 7:48 AM by Danny Ng   [ updated Jun 23, 2011, 12:49 AM ]

The gadget you added is not valid

There are 4 timers in PIC18 which are timer0 to timer3. Timers are used when precise timing event need to be generated. Timers are usually used in conjunction with interrupt to keep the timing accurate. This guide will show an example of using timer0 to count a timer for every second. The circuit used for this example is the same as the circuit used in the PWM guide.
The back light of the LCD will blink on and off every second, while the counter on the LCD will increase until it reaches 199 then reset back to 0. The counting of second is implemented using an interrupt service routine. The guide will be divided in to 2 parts, 1st part dealing with the setting on Timer0 and the second on the interrupt service routine.

Timer

The first part would be setting up the timer to overflow every 1s. Timer overflow occurs when TMR0 go froms 0xFFFF to 0x0000. Since the PIC is running at 8Mhz, the reload needed for the timer can be calculated. To achieve overflow 1s the timer will be set in 16 bit mode. Prescaler of timer0 is set to 64 and the reload value to 34286 or 0x85EE. With this value 1s overflow can be achieved. The calculation below give the time taken for the overflow with TMR0 set to  34286. (1 / (8 000 000 / 4)) * 64 is the time for each count of timer0.
(65 536 - 34 286) * (1 / (8 000 000 / 4)) * 64 = 1
Code below is used to set the timer in to the required reload value and mode.
T0CON = 0x85;
TMR0H = 0x85;
TMR0L = 0xEE;

Interrupt

There are 2 interrupt vector in PIC18, the high and low priority interrupt. Interrupt is settings are configured with INTCON register. IPEN bit in RCON need to be set to enable the interrupt priority levels.
INTCON = 0x20;                
INTCON2 = 0x04;               //TMR0 high priority
RCONbits.IPEN = 1;            //enable priority levels
Since that C18 does not create ISR automatically, a goto instruction must be created at the vector to redirect the interrupt to the appropriate ISR. In this example only the high priority ISR is used, the code below shows the setting for the ISR. A code section is created at 0x08 which is the high interrupt vector to redirect the interrupt to the ISR. Inside the ISR the timer period is reloaded back to 34286 so that 1s interrupt can be achieved. check is set in the ISR to tell the main program to update the LCD. 
void high_isr(void);
/*****************High priority interrupt vector **************************/
#pragma code high_vector=0x08
void interrupt_at_high_vector(void)
{
  _asm GOTO high_isr _endasm
}

#pragma code
/*****************High priority ISR **************************/

#pragma interrupt high_isr
void high_isr (void)
{
if (INTCONbits.TMR0IF){  // Interrupt Check   
INTCONbits.TMR0IF = 0;                 
TMR0H = 0x85;        //Timer Reload to count 1s
TMR0L = 0xEE;                    
second++;
if (second == 200) second = 0;
check = 1;
    }
}
If low priority ISR is needed the below code show the declaration for it. The ISR vector for low priority is at 0x18.
void low_isr(void); 
void high_isr(void);#pragma code low_vector=0x18 
void interrupt_at_low_vector(void) 

_asm GOTO low_isr _endasm 

#pragma code /* return to the default code section */ 
#pragma interruptlow low_isr 
void low_isr (void) 

/* ISR code goes here */ 
}

Overall Program

The overall program is attached together in this guide. Output as shown in the picture above can be observed when the code is compiled and downloaded into the PIC.

Code Generation Tool

There is a Google gadget developed to generate the needed reload value for the register at software page.

PWM using C18

posted Jan 15, 2011, 7:08 PM by Danny Ng   [ updated Feb 8, 2011, 6:43 PM ]

The gadget you added is not valid

The use of pulse width modulation (PWM) is common for the use of controlling power to a particular electrical device. Motor speed control, LED contrast control, power supplies are some of the example usage of PWM. 18 series PIC always come with a CCP module which is capable of generating PWM. The picture below shows an example of using the capture/compare/pwm (CCP) module to control the LED brightness. PWM duty cycle is set accordingly using the value obtain from the potential meter through the ADC with 5v being the brightness and 0v dimmest for the LED.

The test circuit below is constructed to test the PWM module of PIC18f2455. LED of the 2x16 LCD is controlled by a NPN transistor BC547. PWM output is feed through CCP1 for the brightness control. The example below uses the code from ADC and LCD guides.

The first requirement is to set the output port for the CCP1 pin. Setting the tris register to 0 for the bit put the port to output mode.
   TRISCbits.TRISC2 = 0;
Next step would be to set the CCP module to function as a PWM. Timer 2 is tied to the PWM function for the generation of CCP. PR2 is used to determine the Period of the PWM pulse. CCPR1L and bit 4 and 5 of CCP1CON control the duty cycle of the generated PWM. The equation for couting of PWM and duty cycle is given as

PWM Period = [(PR2) + 1] • 4 • TOSC • (TMR2 Prescale Value)

PWM Duty Cycle = (CCPRXL:CCPXCON<5:4>) • TOSC • (TMR2 Prescale Value)

For the example we set the module configuration as below.

T2CON = 0x04;
PR2 = 0xFF;
CCP1CON = 0x3F;
CCPR1L = 0x00;
With this we can obtain a pwm period and maximum duty cycle of 
PWM Period = (255 + 1) * 4 * (1 / (8 * (10^6))) = 0.000128
Max PWM Duty Cycle period = (1024 * (1/(8*10^6))) =0.000128
allowing us to use full 10 bit to control the duty cycle of the PWM. The 10 bit adc value is set in to the corresponding bit for the control of the PWM. Below is the code for the update of pwm duty cycle. Fill attached contain the project for the example.

    ADCValue = ReadADC();
    CCP1CONbits.DC1B0 = ADCValue;
    CCP1CONbits.DC1B1 = ADCValue >> 1;
    CCPR1L = ADCValue >> 2;


Analog to digital converter on PIC18

posted Dec 28, 2010, 6:36 AM by Danny Ng   [ updated Feb 8, 2011, 6:41 PM ]

The gadget you added is not valid

The analog to digital converter (ADC) is a commonly required in most of the projects. Analog voltage measurement can be done using the ADC hardware built in together in a PIC. The picture below show a simple setup for measuring the voltage through the adjustment of the potentiometer. PIC used in this example come with a 10 bit ADC. In the picture the hex value shown is the ADC value acquired from the hardware. After a simple conversion the voltage can be obtain from this value.
A simple circuit is constructed to test the ADC. The potentiometer is use to vary the voltage level on the AN0 pin.

The next step would be to configure the register on the PIC for the ADC to operate. ADCON0 sets the refrence voltage to Vdd and Vss and select AN0 as the input for the ADC. ADCON1 configure the PIN as analog input. Only PIN AN0 is selected as analog input in this example. ADCON2 sets the required time for acquisition. The value for TAD is choose based on the calculation in the datasheet (pic18f2455, page 298-299). ADC module is turn on for it to function.

ADCON0=0;
ADCON1=0x0E;
ADCON2=0xD9;
ADCON0bits.ADON = 1;

A routine for reading of ADC value is created. The acquisition is started by setting the GO bit in ADCON0. Conversion is done when the done bits turn 0. The high and low byte of the ADC results are joined in to a 16 bit data and pass back from the function.

unsigned int ReadADC(void){
unsigned int temp;
ADCON0bits.GO = 1;
while(ADCON0bits.DONE);
temp = ADRESH;
temp = ((temp << 8)| ADRESL );
return temp;
}

A simple formula can be use to calculate the voltage from the ADC result. The result is multiply by the max value of reference voltage and divided by 1024. Doing this will obtain the voltage on the pin AN0.

    fADCValue = ADCValue * 5;
    fADCValue /= (1024);

To display the floating point number conversion to string must be done. The function sprintf is use to format the output so that it can be showed on the LCD. The sprintf function provided by C18 can't be used directly on a floating point number, so the number must 1st be breakup in to the digits left and right of the decimal point. The sprintf is used to join up the 2 parts and the floating point number is display out on the 2x16 lcd.

void Displayfloat(float data){
char LCDOut[16];
long multiplier = 10000;
long lWhole=0; // Stores digits left of decimal
unsigned long ulPart=0; // Stores digits right of decimal
lWhole=(long)((float)data);
ulPart=(long)((float)data*multiplier)-lWhole*multiplier;
sprintf(LCDOut,"Dec: %ld.%04liV", lWhole, ulPart);
XLCDPutRamString(LCDOut);
}

For this example an average of 200 data is taken before the ADC value is shown out on the LCD display. Attached is the project file for this guide.

Auto Baud Rate Based on EUSART module

posted Dec 20, 2010, 6:49 PM by Danny Ng   [ updated Feb 8, 2011, 6:42 PM ]

The gadget you added is not valid

Most of the newer PIC come with the EUSART module which have the function for auto baud rate detection. The features allow a person to set the baud rate at runtime by sending the character "U" or 0x55 to the PIC. With ABDEN (auto baud rate enable) bit set and using BRG clock as a reference, each rising edge occurring on the RX pin is taken as a reference to calculate the SPBRG and SPBRGH value. After the Baud rate is set, the RCIF flag will be set. The receive register must be discarded as it doesn't contain meaningful data. The Image (PIC18F2455 datasheet, page 252) below shows the timing diagram for auto baud rate setup.

A circuit based similar to the UART test is used to demonstrate the capability of auto baud rate generation.Using the code in the project file attached, It will 1st wait for a character "U" to be send over for baud rate calculation. Auto baud is enable using the 3 lines of code below. The UARTReadByte() will read and discard the byte after a successful configuration of baud rate.

//AutoBaud Configuration
BAUDCONbits.WUE = 0;
BAUDCONbits.ABDEN = 1;
UARTReadByte(); // read to clear data

The LCD display will be the same as the image below after a successful program of the microcontroller. It is currently waiting for the character U to set the baud rate.
After receiving U, the LCD will display the value for SPBRGH:SPBRG. In the case of the example, 9600 baud is used and the output is 00CE.
It will send back the 2nd line of the LCD display back to the PC also.
The hex character are display and send to the PC based on the 2 codes below. The lower and upper nibble of the byte must be converted to ASCII code so that it can be displayed to the LCD and the PC.

char ConvertHex(char data){
switch(data){
case 0x0A:
return 'A';
case 0x0B:
return 'B';
case 0x0C:
return 'C';
case 0x0D:
return 'D';
case 0x0E:
return 'E';
case 0x0F:
return 'F';
default:
return (data |0x30);
}
}
void DisplayHex(char data){
char temp = data >> 4;
temp = ConvertHex(temp);
XLCDPut(temp);
    UARTSendByte(temp);  
temp = (data & 0x0F);
temp = ConvertHex(temp);
XLCDPut(temp);
    UARTSendByte(temp);  
}
Data can be sent through the serial port and displayed on the 2nd line of the LCD similar to the example of UART test. The final picture show the web address sent to the LCD.

UART serial communication

posted Dec 10, 2010, 7:50 PM by Danny Ng   [ updated Feb 8, 2011, 6:44 PM ]

The gadget you added is not valid
One of the most basic communication between a PC and microcontroller system is through the Serial Port. Being easy to configure for communication made it a good choice for much of the projects. By setting the correct baud rate on the PIC and PC, Data can be transmitted in between 2 of the device. The Rx and Tx Pins of the EUASRT module are used for the communication. Line driver/receiver must be used to translate the different voltage level used in between the PIC and PC. In this demonstration a MAX 232 is used.  A simple circuit using pic18f2455 based on the schematic shown in the figure is used to test out the communication.
The first step for using UART is by setting the baud rate in the micro controller. It is down by loading the SPBRGH:SPBRG register with the correct value for the desired baud rate. Baud Rate Generator (BRG) will generate clock required to transmit and receive the data. The table below (obtain from 2455 datasheet page 247) show the formula for getting the boaud rate based on the SPBRGH:SPBRG value n. In the example a reload value of 16 is used. The baud generated with the config SYNC = 0, BRG16 = 1, BRGH = 1 will obtain baud rate of 117.647kbps, which is near the 115.2kpbs of the example.
The serial UART in the PIC must be enable by setting the TRIS register of the TX and RX pin to 1, SPEN (serial port enable) of RCSTA to 1, CREN (recieve enable) of RCSTA to 1 and  TXEN (transmit enable) of TXSTA to 1. With the baud rate set, transmitting data to can be done by loading the TXREG signify by 1 on the TXIF when the transmit buffer is empty. When data is received RCIF will be set and data can be read from the RCREG. Based on this some simple function are created for the transmit and receive data from the PC. UARTSendByte is use to send a byte while UARTReadByte is used to get 1 single byte. UARTSendLine will send all the data in an array of char.

void UARTSendByte(char data){
while(PIR1bits.TXIF == 0);
TXREG = data;
}

char UARTReadByte(void){
while(PIR1bits.RCIF == 0);
return RCREG;
}

void UARTSendLine(char *data){
    while(*data)                        
    {       
        UARTSendByte(*data);               
       data++;
    }
}

Based on this a simple project (under attachment) is created to display character sent from pc to the PIC on the LCD. www.ENMCU.com is sent to the LCD for display.




2x16 Character LCD with C18

posted Nov 24, 2010, 6:18 PM by Danny Ng   [ updated Feb 8, 2011, 6:40 PM ]

The gadget you added is not valid


The 2x16 character LCD based on the HD44780 controller are a commonly used for Display purposes on micro controller project. There is a simple way to include the use of LCD in a project when developing with C18. A utility call Application Maestro is provided together with the installation of MPLAB allow the generation of library for LCD. The pin configuration for the port used for Data, RW, RS and EN pin, interface methods (4bit or 8bit) can be selected. Beside that there is 2 mode that can be selected for driving the LCD.

The 1st mode is Blocking which uses delay for displaying of character. In this mode, the RW pin of the LCD is grounded. After passing the required data for display to the LCD, a delay that is longer than the maximum busy period is called. This will ensure that the next command or data is received during the non busy duration.
The 2nd mode uses Busy flag check. During internal processing the the BF(data7) pin of the LCD will be high. Checking on this pin will make sure that the next command will be written at the correct time. The 1st method uses 1 IO port less then the 2nd but losses the function to read data from the LCD. For the example I will use the Busy flag method to drive the LCD. The settings are set as the picture shown above and the code for the LCD is generated.

The code are tried out on PIC18F2455. It is connected according to schematic above.  There will be a 5 files generated from application maestro. xlcd.def, xlcd.h and xlcd.c must be included into the project for compilation. There is a PDF and example file generated also to show the usage of all the function in xlcd.h. By using the code below and adding include the generated files the LCD will display the text in the code. 4 delay functions (XLCDDelay15ms, XLCDDelay4ms, XLCD_Delay500ns and XLCDDelay) have to created by the user for the LCD routine to work properly. The attached PDF file is the similar file generated by application maestro contains details of all the function that can be used with the library.

#include <p18f2455.h>
#include <delays.h>
#include "xlcd.h"

#pragma config FOSC = INTOSCIO_EC
#pragma config WDT = OFF
#pragma config MCLRE = OFF
#pragma config PWRT = ON
#pragma config LVP = OFF

void XLCDDelay15ms (void)
{
    Delay1KTCYx(30);
}
void XLCDDelay4ms (void)
{
    Delay1KTCYx(8);
}
void XLCD_Delay500ns(void)
{
    Nop();
}
void XLCDDelay(void){ //1.5ms delay
    Delay1KTCYx(3);
 }

void main(void){
char data[]="LCD Test";
OSCCON = 0x70;
while(!OSCCONbits.IOFS);
ADCON1=0x0F;  
XLCDInit();  
XLCDPut('E');
XLCDPut('N');
XLCDPut('M');
XLCDPut('C');
XLCDPut('U');
XLCDL2home();
XLCDPutRamString(data);
while(1);
}
With everything connected up the output in the 1st figure can be obtained.

 

Beginning C18 Developement

posted Nov 24, 2010, 6:15 PM by Danny Ng   [ updated Feb 8, 2011, 6:42 PM ]

The gadget you added is not valid
The slides (Getting Started with C18) below contain a simple guide on starting development using C18 and microchip MPLAB IDE.

Getting Started with C18



C18 compiler c018i.o file error

posted Nov 24, 2010, 6:09 PM by Danny Ng   [ updated Feb 8, 2011, 6:43 PM ]

The gadget you added is not valid

If this error arises  - Error - could not find file 'c018i.o' when you try to compile using C18, you need to add the path to the library. Under Project>Build Option>Project, In the build option change the "Show directory for" to Library Search Path and add a new path to the path of C18 LIB folder. In my case its shown as C:\MCC18\LIB. The below 2 screens shots below show the solution for the c018i.o Error. With this the program should be able to compile without any problem.

Step 1:

Step 2:


1-9 of 9