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 int tempi; //to add leadign zeros..
/* * 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 TRISCbits.TRISC7 = 1; //analog input 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=strlen(buf); //uh, adding leading zeros.. 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); }