Testing an LCD with the PIC 16F1509 and the XC8 compiler

Nuts and Volts, January 2014 had an article named “The (Nearly) Universal Process Monitor”. The whole magazine had a number of Microchip PIC based articles which is rare these days, as the articles win out to other platforms such as MSP430, Propeller, and Arduino. The article is a pretty nice project but I went to download the code and it’s just a HEX file. Lame! I’ve been guilty of this and I promise never to do it again.

If you’re as disenchanted with building something without modifiable code as I am here is a starting block! I don’t normally program in C but I’m trying to force myself so I can get better. You can trim down many timing delays, and certainly shouldn’t be a problem to optimize my code as I made no effort to do so myself.

When writing the A/D math for this I found some interesting issues; here is one of those: (in pseudo code)

`int_Var1 = 8-bit A/D conversion`
`long_int_Var2 = int_Var1 * 5000`

given int_Var1 = 17  long_int_Var2 should equal 85000 but it didn’t; It came out 19,464 (roll over from 65,536).

However:

`long_int_Var2 = int_Var1`
`long_int_Var2 = long_int_Var2 * 5000  //works like a charm.`

Weird? I tried this in an ANSI C compiler and the first equation worked okay; I guess this is just a quirk about XC8. I somewhat understand why it would act that way and caught it quick but it could easily have been a difficult bug to hunt down.

There is obviously not the greatest amount of resolution in an 8-bit conversion but I was really just testing an LCD I just got off eBay and through this would be a fun way to do so, work on my C skills, and help out anyone wanting a head-start on building their own metering application. If you don’t want to do the math yourself you’ll find you get about 20mV/step resolution (5000mV/256) at 8-bit.

Notes: Refresh is slow, clearing the LCD line #2 is slow because I didn’t like the flash of clearing the whole display… it could have also been made more quick had I not written 16 spaces and just written enough to cover my 7 digits of output. I also should have dropped the LSB of my number because it’s a worthless bit considering my resolution. I will if this gets used on anything other than a breadboard test.

I used a standard 44780 16×2 LCD, a Tautic development board, and a PIC 16F1509 (happens to come with the dev board). For programming I used MPLAB X IDE v1.95 and XC8 v1.21 (free version) and the PICkit 3 programmer. The code is commented enough to figure out the hardware setup.

code (with whatever fixes since wiring the blog) can be found at : https://github.com/chasxmd/iradan.com-2014

at writing it looks like:

```/*
* File: main.c
* Author: Charles M Douvier
* Contact at: http://iradan.com
*
* Created on January 18, 2014, 9:42 AM
*
* Target Device:
* 16F1509 on Tautic 20 pin dev board
*
* Project:
* A/D --> LCD Test
* 8-bit resolution across Vdd to Vref (0-5V)
* for 3.3V operation adjust A/D math
*
* LCD (44780 type) Test with XC8 compiler
* LCD code ported from Mike Pearce's 2001 LCD code for HI-TECH C
* as found on http://www.microchipc.com/
*/```
```#include <xc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <plib.h>```
```//config bits
#pragma config FOSC=INTOSC, WDTE=OFF, PWRTE=OFF, MCLRE=ON, CP=OFF, BOREN=ON, CLKOUTEN=OFF, IESO=OFF, FCMEN=OFF
#pragma config WRT=OFF, STVREN=OFF, LVP=OFF```
`#define _XTAL_FREQ 4000000 //defined for delay`
```int an9_value; //value for a/d
char buf[10]; //buff for iota
long int fvar; //long for format math
long int ones; //left of decm
long int decm; //decimal places
```/*
* LCD RS LATA.5
* LCD EN LATA.4
* LCD DATA4 LATC.0
* LCD DATA5 LATC.1
* LCD DATAT6 LATC.2
* LCD DATA7 LATC.3
* LED LATA.0 for scan rate/heartbeat
*/```
```void lcd_strobe (void) //TOGGLE LCD_EN
{
LATAbits.LATA4 = 0;
__delay_ms(20);
LATAbits.LATA4 = 1;
}```
`/* write a byte to the LCD in 4 bit mode */`
```void lcd_write(unsigned char c)
{
LATC = c >> 4;
lcd_strobe();
LATC = c;
lcd_strobe();
__delay_us(100);
}```
```/*
* Clear and home the LCD
*/```
```void lcd_clear(void)
{
LATAbits.LATA5 = 0;
lcd_write(0x1);
__delay_ms(2);
}```
`/* write a string of chars to the LCD */`
```void lcd_puts(const char * s)
{
LATAbits.LATA5 = 1; // write characters
while(*s)
lcd_write(*s++);
}```
```/*
* Go to the specified position
*/```
```void lcd_goto(unsigned char pos)
{
LATAbits.LATA5 = 0;
lcd_write(0x80+pos);
}```
```/*
* Write 16 spaces on LCD 2 to avoid blanking, (ugly CLEAR effect)
* this is slow but work for my needs
*/```
```void lcd_clrline2(void)
{
lcd_goto(40);
lcd_puts(" ");
lcd_goto(40);
}```
`/* initialise the LCD - put into 4 bit mode */`
```void lcd_init(void)
{
LATAbits.LATA5 = 0; // write control bytes
LATC = 0x03;
__delay_ms(150); //power on delay
lcd_strobe();
__delay_ms(5);
lcd_strobe();
__delay_ms(5);
lcd_strobe();
__delay_ms(5);
LATC = 0x02; // set 4 bit mode
__delay_ms(5);
lcd_strobe();
__delay_ms(5);
lcd_write(0x28); // 4 bit mode, 1/16 duty, 5x8 font
lcd_write(0x08); // display off
lcd_write(0x0C); // display on cursor+blink off
lcd_write(0x06); // entry mode
}```
```int main(void) {
// set up oscillator control register, using internal OSC at 4MHz.
OSCCONbits.IRCF = 0x0d; //set OSCCON IRCF bits to select OSC frequency 4MHz
OSCCONbits.SCS = 0x02; //set the SCS bits to select internal oscillator block```
```TRISCbits.TRISC0 = 0; // output
TRISCbits.TRISC1 = 0; // output
TRISCbits.TRISC2 = 0; // output
TRISCbits.TRISC3 = 0; // output
TRISAbits.TRISA0 = 0; // output
TRISAbits.TRISA4 = 0; // output
TRISAbits.TRISA5 = 0; // output
ANSELCbits.ANSC7 = 1; //...setup on PORTC.7/AN9
LATAbits.LATA0 = 0; //LED Im-Alive test```
`__delay_ms(250); //let the power settle`
```lcd_init();
__delay_ms(10);
lcd_clear();```
```//display test message
lcd_puts("Testing the LCD.");
lcd_goto(40);```
`ADCON0 = 0b00100101; //select AN9 and enable`
```/* ADCON1
* bit 7 ADFM: ADC Result Format Select bit
* 0 = Left justified. Six Least Significant bits of ADRESL are set to ?0? when the conversion result is loaded.
* bit 6-4 ADCS<2:0>: ADC Conversion Clock Select bits
* 110 = FOSC/64
* bit 3-2 Unimplemented: Read as ?0?
* bit 1-0 ADPREF<1:0>: ADC Positive Voltage Reference Configuration bits
* 00 = VREF+ is connected to VDD
*/
ADCON1 = 0b01100000; //left justified, FOSC/64 speed Vref=Vdd```
```while(1)
{
LATAbits.LATA0 = 0; //debugging

lcd_clrline2(); //clear LCD line 2 by writting " " and return```
```__delay_us(5);
GO = 1;
while (GO) continue; //wait for conversion
an9_value = ADRESH; //AN9 value```
``` //format value for LCD read out
//value = AD_value * 5000 (because 5000mV is Vref)
//value = value / 256 (8 bit number)
fvar = an9_value;
fvar = fvar * 5000;
fvar = fvar / 256;
ones = fvar / 1000;
ones = ones % 10;
decm = fvar % 1000;```
`LATAbits.LATA0 = 1; //LED Im-Alive test. I made it through conversion`
```//page 366 of XC8 user guide
itoa(buf,ones,10); //int conv to buffer
lcd_puts(buf); //outputs "1s" place to LCD.
lcd_puts(".");
//page 374 of XC8 user guide
ltoa(buf,decm,10); //long conversion to buffer
tempi=3-tempi; //probably a better way of doing thing
while (tempi) //first figure out how many zeros
{
lcd_puts("0"); //missed 3-string length
tempi=tempi-1; //then send them until done
}
lcd_puts(buf); //output buffer to LCD```
```lcd_puts(" V"); //attach some units for display
//delay
__delay_ms(999); //LCD refresh rate
}
return (EXIT_SUCCESS);
}```

try `#include <plib/plib.h> ` or manually add the path of plib into your MPLABX path settings.