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. |
Guides
I2C using Master Synchronous Serial Port (MSSP) 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:
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:
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.
The full code and project files is attached below at the attachments. |
Using Timer and Interrupt in C18
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. TimerThe 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; InterruptThere 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 ProgramThe 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 ToolThere is a Google gadget developed to generate the needed reload value for the register at software page. |
PWM using C18
![]() 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; 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 ADCValue = ReadADC(); CCP1CONbits.DC1B0 = ADCValue; CCP1CONbits.DC1B1 = ADCValue >> 1; CCPR1L = ADCValue >> 2; |
Analog to digital converter on PIC18
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;
unsigned int ReadADC(void){ unsigned int temp; ADCON0bits.GO = 1; while(ADCON0bits.DONE); temp = ADRESH; temp = ((temp << 8)| ADRESL ); return temp; }
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
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); } |
UART serial communication
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
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.
With everything connected up the output in the 1st figure can be obtained. |
Beginning C18 Developement
The slides (Getting Started with C18) below contain a simple guide on starting development using C18 and microchip MPLAB IDE. ![]() |
C18 compiler c018i.o file error
1-9 of 9