Jump to content
jsobell

Lcd_driver.h - Problems... And A Solution

Recommended Posts

Well, after struggling for two days with bizzare issues with a simple LCD display, I finally found the problem.

I'm not sure if this is common to all PICs, and it's been many years since I did any amount of PIC programming, but I had extreme problems interfacing to the display due to the way the template is coded for reading data back in 4bit mode.

 

If you choose to use a single port to write to a display, it makes sense to set all of the ports to one port, in my case PortD. The problem arises that if you use a function such as:

data |= d & 0xF0;

then the existing value is read from the port, then written back. Unfortunately, if there are output pins on that port then the data read does not reflect the existing values, so your existing outputs get stomped on.

Using the latch register (latd) in place of the port (portd) fixes the problem, but the latch can't be used to read the values when the port is reversed for status information, so two different variables are required to specify the port for reading (portd) and writing (latd).

 

I've modified the template to reflect this change, and would appreciate someone checking to see if this makes sense or if I've completely missed the point and was misusing the template in the first place (Dave?). I can't test it with the other pin setups (8 bit mode and 4bit lo nibble), but I assume those sections are now corrected too.

 

Ah well, at last my display is alive... Now to find something to use it for.

 

Cheers,

Jason

 

 

p.s. A file attach option on the forum might be nice...

------------Code follows-----------

////////////////////////////////////////////////////////////////////////////
// LCD with HD44780 drive chip
////////////////////////////////////////////////////////////////////////////
// Author(s): David Hobday, Pavel Baranov
// Date 15 November 2004
//
// Copyright (c) 2004-2005 Pavel Baranov
// Copyright (c) 2004-2005 David Hobday
// Copyright (c) 2004 Andrew Smallridge
//
//
// How to use - by David Hobday
// ============================
// These must be define before template header file is included
// Remember when using LCD in 4 bit mode you must connect to the LCDs DB4-DB7 pins
//
// *** For a list of functions, see the END of this file.
//
// The time delays used in the code should mean that it will work on PIC18 with 
// 40MHz clock without any changes.
//
// The example below (which would work on a PIC16F84) operates the display 
// in 4bit mode and requires the following connections:
// RS  to RA3
// R/W to RA2
// E   to RA1
// DB0 to None
// DB1 to None
// DB2 to None
// DB3 to None
// DB4 to RB4
// DB5 to RB5
// DB6 to RB6
// DB7 to RB7
//
//
// Add the following after #include <system.h> in you source file:
//
//#define LCD_ARGS 	2,		/* Interface type: mode 0 = 8bit, 1 = 4bit(low nibble), 2 = 4bit(upper nibble) */ \
//		1, 					/* Use busy signal: 1 = use busy, 0 = use time delays */\
//		LATD, PORTD, TRISD, /* Write Data port, Read Data port, and data port tris register */ \
//		LATD, TRISD, 		/* Control port and control port tris register */ \
//		3,					/* Bit number of control port is connected to RS */ \
//		2,					/* Bit number of control port is connected to RW */ \
//		1					/* Bit number of control port is connected to Enable */
//
//#include <lcd_driver.h> // include the LCD template code
//
//
// Revisions
// =========
//
// V1.10 David Hobday 03/03/2005
// =============================
// 1) Improved documentation in file
// 2) Changed delays to delay_10us for usage on target with clock <= 4MHz
// 3) Changed template to make more friendly and obvious
// 
// V1.11 18/03/2005
// David Hobday
// 1) Fixed operation with PIC18 - template arguments of incorrect type
// 2) Tested with PicDem2Plus board (4MHz PIC16F877/PIC18F452)
// 3) Added lcd_gotoxy function.
// 4) Added option to use display busy bit or time delays
// 5) Added overloaded lprintf function for ROM string
// 6) Added overloaded lprintf function to output numbers supported formats:
//  "%d" - decimal
//  "%X" - hex
//  "%b" - binart
//  example: display binary number six digits, '0' as file character.
//  lprintf( "val:%06b", numb );
// 7) Other improvements/cleanup.
//
//
// V1.11 David Hobday 25/03/2005
// =============================
// 1) Fixed bug with lprintf( "test:%d", 0 ); not printing a 0.
// 2) Added a few more comments in lprintf( const char*, int ) code
// 
//
// V1.12 Jason Sobell 03/07/2005
// =============================
// 1) Fixed bug when data and control ports are the same (introduced ReadDataPort)
// 
////////////////////////////////////////////////////////////////////////////

char writeDelayType;

////////////////////////////////////////////////////////////////////////////
// LCD Commands ( Refer to LCD Data Sheet )
////////////////////////////////////////////////////////////////////////////
#define clear_lcd         		0x01 // Clear Display
#define return_home       		0x02 // Cursor to Home position
#define entry_mode       		 0x06 // Normal entry mode
#define entry_mode_rev   		 0x04 // Normal entry mode  -reverse direction
#define entry_mode_scroll 		0x07 // - with shift
#define entry_mode_scroll_rev	0x05 // reverse direction

#define system_set_8_bit 		 0x38 // 8 bit data mode 2 line ( 5x7 font )
#define system_set_4_bit 		 0x28 // 4 bit data mode 2 line ( 5x7 font )
#define system_set_reset 		 0x30 // Reset code
#define display_on       		 0x0C // Display ON - 2 line mode
#define display_off       		0x08 // Display off
#define set_dd_line1     		 0x80 // Line 1 position 1
#define set_dd_line2     		 0xC0 // Line 2 position 1
#define set_dd_ram       		 0x80 // Line 1 position 1
#define write_data       		 0x00 // With RS = 1
#define cursor_on         		0x0E // Switch Cursor ON
#define cursor_off       		 0x0C // Switch Cursor OFF
#define cursor_blink_on   		0x0F // Cursor plus blink
#define cursor_shift_right		0x14 // Move cursor right
#define cursor_shift_left 		0x10 // Move cursor left
#define display_shift_right		0x1C // Scroll display right
#define display_shift_left		0x18 // Scroll display left

#define WriteNoDelay	1
#define WriteControlled	0

// Interface type
#define LCD_8_BIT_MODE 0
#define LCD_4_BIT_LO_NIB_MODE 1
#define LCD_4_BIT_HI_NIB_MODE 2

// These macros make susequent code more readable, but can seem a little confusing
#define _LCD_RawWriteNibble LCD_RawWriteNibble	<InterfaceType, UseBusy, DataPort, ReadDataPort, Data_PortTris, CtrlPort, Ctrl_PortTris, RS, RW, E>
#define _LCD_RawWriteNibbleInline  LCD_RawWriteNibbleInline	<InterfaceType, UseBusy, DataPort, ReadDataPort, Data_PortTris, CtrlPort, Ctrl_PortTris, RS, RW, E>
#define _LCD_Read			LCD_Read			<InterfaceType, UseBusy, DataPort, ReadDataPort, Data_PortTris, CtrlPort, Ctrl_PortTris, RS, RW, E>
#define _LCD_WaitForNotBusy	LCD_WaitForNotBusy	<InterfaceType, UseBusy, DataPort, ReadDataPort, Data_PortTris, CtrlPort, Ctrl_PortTris, RS, RW, E>
#define _LCD_Write			LCD_Write			<InterfaceType, UseBusy, DataPort, ReadDataPort, Data_PortTris, CtrlPort, Ctrl_PortTris, RS, RW, E>
#define _LCD_FunctionMode	LCD_FunctionMode	<InterfaceType, UseBusy, DataPort, ReadDataPort, Data_PortTris, CtrlPort, Ctrl_PortTris, RS, RW, E>
#define _LCD_DataMode		LCD_DataMode		<InterfaceType, UseBusy, DataPort, ReadDataPort, Data_PortTris, CtrlPort, Ctrl_PortTris, RS, RW, E>
#define _LCD_RawWrite		LCD_RawWrite		<InterfaceType, UseBusy, DataPort, ReadDataPort, Data_PortTris, CtrlPort, Ctrl_PortTris, RS, RW, E>
#define _LCD_ClockOut		LCD_ClockOut		<InterfaceType, UseBusy, DataPort, ReadDataPort, Data_PortTris, CtrlPort, Ctrl_PortTris, RS, RW, E>

#define _LCD_TEMPL		template <	unsigned char InterfaceType,\
								unsigned char UseBusy,\
								unsigned int DataPort, unsigned int ReadDataPort, unsigned int Data_PortTris,\
								unsigned int CtrlPort, unsigned int Ctrl_PortTris,\
								unsigned char RS, unsigned char RW, unsigned char E>

_LCD_TEMPL
inline void LCD_FunctionMode( void )
{
volatile bit rs@CtrlPort.RS = 0;
}

_LCD_TEMPL
inline void LCD_DataMode( void )
{
volatile bit rs@CtrlPort.RS = 1;
}


inline void LCD_CycleMakeupDelay()
{
// Enable cycle time must be > 1000ns total for both reading and writing
// LCD_SetupDelay + LCD_EnablePulseDelay + LCD_HoldupDelay + LCD_CycleMakeupDelay >= 1000ns
//       200      +          500         +       100       +          200         >= 1000ns

// This delay is required to meet the Sharp data sheet total cycle time of > 1000ns
// @40MHz this is 2 instructions
asm nop 
asm nop	
}


inline void LCD_EnablePulseDelay()
{		
// PWEH > 460ns on Sharp data sheet
// @40MHz this is 5 instructions
asm nop 
asm nop
asm nop
asm nop
asm nop
}

inline void LCD_SetupDelay()
{
// tAS > 140ns min on Sharp data sheet
// @40MHz this is 2 instructions
asm nop 
asm nop
}

inline void LCD_HoldupDelay()
{
// tAS > 10ns min on Sharp data sheet
// @40MHz this is 1 instructions
asm nop
}



_LCD_TEMPL
char LCD_Read()
{
char d; 
volatile unsigned char data@DataPort, tris@Data_PortTris, readdata@ReadDataPort;
volatile bit rw@CtrlPort.RW, e@CtrlPort.E;

if( InterfaceType == LCD_4_BIT_HI_NIB_MODE )
{
	// upper nibble input
	tris |= 0xF0;
	rw = 1; // set reading mode
	// first high nibble	
	LCD_SetupDelay();
	e = 1;
	LCD_EnablePulseDelay();
	// d = data & 0xF0; // read data
	d = readdata & 0xF0; // read data
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();

	// then low nibble
	LCD_SetupDelay();
	e = 1;
	LCD_EnablePulseDelay();
	//d |= data >> 4;
	d |= readdata >> 4;
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();
}


if( InterfaceType == LCD_4_BIT_LO_NIB_MODE )
{
	// lower nibble input	
	tris |= 0x0F;
	rw = 1; // set reading mode
	// first high nibble
	LCD_SetupDelay();
	e = 1;
	LCD_EnablePulseDelay();
	d = readdata << 4;
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();

	// then low nibble
	LCD_SetupDelay();
	e = 1;
	LCD_EnablePulseDelay();	
	d |= readdata & 0x0F;
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();
}


if( InterfaceType == LCD_8_BIT_MODE )
{
	// port input	
	tris = 0xFF;
	rw = 1; // set reading mode
	LCD_SetupDelay();
	e = 1;
	LCD_EnablePulseDelay();
	d = readdata;
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();
}

return d;
}

_LCD_TEMPL
void LCD_RawWriteNibble( char d )
{
// Note: this function is duplicate below, but declared inline.
// this is to reduce stack depth usage
// Note: this function is above, but declared inline.
// this is to reduce stack depth usage
//volatile unsigned char data@DataPort, tris@Data_PortTris;
volatile unsigned char data@DataPort, tris@Data_PortTris;
volatile bit rw@CtrlPort.RW, e@CtrlPort.E;

if( InterfaceType == LCD_4_BIT_HI_NIB_MODE )
{
	// port upper nibble output
	rw = 0; // set writing mode
	LCD_SetupDelay();
	tris &= 0x0F;
	data &= 0x0F;
	data |= d & 0xF0;
	e = 1;
	LCD_EnablePulseDelay();
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();
}

if( InterfaceType == LCD_4_BIT_LO_NIB_MODE )
{
	// port upper nibble output
	rw = 0; // set writing mode
	LCD_SetupDelay();
	tris &= 0xF0;
	data &= 0xF0;
	data |= d >> 4;
	e = 1;
	LCD_EnablePulseDelay();
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();
}	
}

_LCD_TEMPL
inline void LCD_RawWriteNibbleInline( char d )
{
// Note: this function is above, but declared inline.
// this is to reduce stack depth usage
volatile unsigned char data@DataPort, tris@Data_PortTris;
volatile bit rw@CtrlPort.RW, e@CtrlPort.E;

if( InterfaceType == LCD_4_BIT_HI_NIB_MODE )
{
	// port upper nibble output
	rw = 0; // set writing mode
	LCD_SetupDelay();
	tris &= 0x0F;
	data &= 0x0F;
	data |= d & 0xF0;
	e = 1;
	LCD_EnablePulseDelay();
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();
}

if( InterfaceType == LCD_4_BIT_LO_NIB_MODE )
{
	// port upper nibble output
	rw = 0; // set writing mode
	LCD_SetupDelay();
	tris &= 0xF0;
	data &= 0xF0;
	data |= d >> 4;
	e = 1;
	LCD_EnablePulseDelay();
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();
}	
}


_LCD_TEMPL
void LCD_RawWrite( char d )
{		
volatile unsigned char tris@Data_PortTris, data@DataPort;
volatile bit rw@CtrlPort.RW, e@CtrlPort.E;

if( InterfaceType == LCD_4_BIT_HI_NIB_MODE  )
{
	// output upper nibble, then lower nibble
	bit flag = 0;
	do
	{			
		_LCD_RawWriteNibbleInline( d );
		flag = !flag;
		d <<= 4;
	}
	while( flag );
}	

if( InterfaceType == LCD_4_BIT_LO_NIB_MODE )
{
	// output upper nibble, then lower nibble
	bit flag = 0;
	do
	{			
		_LCD_RawWriteNibbleInline( d );
		flag = !flag;
		d <<= 4;
	}
	while( flag );
}	


if( InterfaceType == LCD_8_BIT_MODE )
{
	// port b output
	rw = 0; // set writing mode
	tris = 0x00;		
	data = d;
	LCD_SetupDelay();
	e = 1;
	LCD_EnablePulseDelay();
	e = 0;
	LCD_HoldupDelay();
	LCD_CycleMakeupDelay();
}
}

_LCD_TEMPL
inline void LCD_WaitForNotBusy()
{
// volatile bit rs@CtrlPort.RS; bit old_RS = rs;	
volatile bit rs@CtrlPort.RS; bit old_RS = rs;	
rs = 0;
while( _LCD_Read() & 0x80 ); // wait while busy set
rs = old_RS;
}

_LCD_TEMPL
void LCD_Write(char d )
{
volatile bit rs@CtrlPort.RS;

if( UseBusy == 1 )
{	
	// wait until display Not busy before sending next data
	if ( writeDelayType == WriteControlled )
		_LCD_WaitForNotBusy();

	_LCD_RawWrite( d );
}
else
{		
		_LCD_RawWrite( d );

	// give time to complete
	if ( writeDelayType == WriteControlled )
	{
		if( !rs && (d == return_home || d == clear_lcd) )
			delay_ms( 2 ); // return_home takes more time than other instructions to execute
		else
			delay_10us( 5 ); // 50us - enough time for normal command execution - clear and home need longer!!			
	}
}
}

_LCD_TEMPL
void LCD_Clear()
{
_LCD_FunctionMode();
_LCD_Write( clear_lcd ); // clear display
_LCD_Write( return_home );
}

_LCD_TEMPL
void LCD_Setup( void )
{
// set control port bits used to output
volatile bit trisRS@Ctrl_PortTris.RS, trisRW@Ctrl_PortTris.RW, trisE@Ctrl_PortTris.E;

trisRS = 0;
trisRW = 0;
trisE = 0;

writeDelayType = WriteNoDelay; // no delays in data writes

delay_ms(16); // Power up delay

_LCD_FunctionMode();

if( InterfaceType == LCD_4_BIT_HI_NIB_MODE )
{
	// Reset sequence as described in data sheets
	_LCD_RawWriteNibble( system_set_reset ); 
	delay_ms(5); // min delay here of 4.1 ms
	_LCD_RawWriteNibble( system_set_reset );
	delay_10us(100); // min delay here of 100us
	_LCD_RawWriteNibble( system_set_reset );
   
	// LCD busy flag is valid from this point onwards
	if( UseBusy == 1 )
		_LCD_WaitForNotBusy();
	else
		delay_10us( 5 ); // standard command delay time

	_LCD_RawWriteNibble( system_set_4_bit );

	if( UseBusy == 1 )
		_LCD_WaitForNotBusy();
	else
		delay_10us( 5 ); // standard command delay time

	writeDelayType = WriteControlled;
	_LCD_Write( system_set_4_bit );
}

if( InterfaceType == LCD_4_BIT_LO_NIB_MODE )
{
	// Reset sequence as described in data sheets
	_LCD_RawWriteNibble( system_set_reset ); 
	delay_ms(5); // min delay here of 4.1 ms
	_LCD_RawWriteNibble( system_set_reset );
	delay_10us(100); // min delay here of 100us
	_LCD_RawWriteNibble( system_set_reset );
   
	// LCD busy flag is valid from this point onwards
	if( UseBusy == 1 )
		_LCD_WaitForNotBusy();
	else
		delay_10us( 5 ); // standard command delay time

	_LCD_RawWriteNibble( system_set_4_bit );

	if( UseBusy == 1 )
		_LCD_WaitForNotBusy();
	else
		delay_10us( 5 ); // standard command delay time

	writeDelayType = WriteControlled;
	_LCD_Write( system_set_4_bit );
}

if( InterfaceType == LCD_8_BIT_MODE )
{
	// Reset sequence as described in data sheets
	_LCD_RawWrite( system_set_reset ); 
	delay_ms(5); // min delay here of 4.1 ms
	_LCD_RawWrite( system_set_reset );
	delay_10us(10); // min delay here of 100us
	_LCD_RawWrite( system_set_reset );
   
	// busy flag is valid from this point onwards
	if( UseBusy == 1 )
		_LCD_WaitForNotBusy();
	else
		delay_10us( 5 ); // standard command delay time

	_LCD_RawWrite( system_set_8_bit );	

	if( UseBusy == 1 )
		_LCD_WaitForNotBusy();
	else
		delay_10us( 5 ); // standard command delay time

	writeDelayType = WriteControlled; // use busy
}

_LCD_Write( entry_mode );
_LCD_Write( display_on );
_LCD_Write( set_dd_ram );
}

_LCD_TEMPL
void LCD_Printf( const char *lcdptr )
{
char pi = 0, c;
_LCD_DataMode();
   while( 1 )
   {
	c = lcdptr[pi++];
	if ( !c )
		return;
	if ( c == '\n' )
	{
		_LCD_FunctionMode();
		// move to start second line
		_LCD_Write( set_dd_ram + 0x40 );
		_LCD_DataMode();
	}
	else
		_LCD_Write( c );// Display on LCD
}
}

_LCD_TEMPL
void LCD_Printf( rom char *lcdptr )
{
char pi = 0, c;
_LCD_DataMode();
   while( 1 )
   {
	c = lcdptr[pi++];
	if ( !c )
		return;
	if ( c == '\n' )
	{
		_LCD_FunctionMode();
		// move to start second line
		_LCD_Write( set_dd_ram + 0x40 ); 
		_LCD_DataMode();
	}
	else
		_LCD_Write( c );// Display on LCD
}
}

_LCD_TEMPL
void LCD_Printf( const char *lcdptr, int val )
{
unsigned char pi = 0, bi, c, fill, baseOrBits, sign, mask;
unsigned char buff[ 10 ]; // max length allow is 9
bit pad;

_LCD_DataMode();
   while( 1 )
   {
	c = lcdptr[pi++]; if ( !c ) return;

	switch( c )
	{
	case '\n':
		_LCD_FunctionMode();
		// move to start second line
		_LCD_Write( set_dd_ram + 0x40 );
		_LCD_DataMode();
		break;
	case '%':
		c = lcdptr[pi++]; if ( !c ) return;

		// Next character if zero indicates that we should zero fill output
		if ( c == '0' )
		{
			fill = '0';
			c = lcdptr[pi++]; if ( !c ) return;
		}
		else
			fill = ' ';

		// Next character if valid digit indicates field width
		if( c > '0' && c <= '9' )
		{
			pad = 1;
			bi = c - 48;;				
			c = lcdptr[pi++]; if ( !c ) return;
		}
		else
		{
			pad = 0;
			bi = sizeof( buff ) - 1;
		}


		// Next character indicates the radix (number base)
		sign = 0;
		switch( c )
		{
		case 'd':
			if( val < 0 ) sign = '-';
		case 'u':
			baseOrBits = 10; // base ten, divide by ten per digit
			break;			
		case 'X':
			baseOrBits = 4; // base 16, requires a 4 bit shift per digit
			mask = 0x0F;
			break;
		case 'b':
			baseOrBits = 1; // base 16, requires a 1 bit shift per digit
			mask = 0x01;
			break;
		default:
			return; // no radix
		}

		// null terminate, then reverse fill string
		buff[ bi ] = '\0';

		bit first = true;				
		while( bi )
		{
			bi--;
			if( val || first )
			{
				first = false;

				if( baseOrBits == 10 )
				{
					c = (unsigned char)(val % baseOrBits);
					val /= baseOrBits;

					if( c & 0x80 ) 
						c = 0 - c; // negative numbers
				}
				else
				{
					c = val & mask;
					val = ((unsigned int)val) >> baseOrBits;
				}						


				if( c > 9 )
					c += 55; // convert to hex digits character A-F
				else
					c += 48; // convert to digit character 0-9
			}
			else
			{
				if( sign && (bi == 0 || fill != '0') )
				{
					c = sign;
					sign = 0;
				}
				else
					c = fill;
			}

			buff[ bi ] = c;

			if( pad == 0 && val == 0 && sign == 0 )
				break;
		}
		// output string to display
		while( 1 )
		{
			c = buff[ bi ];
			if( !c ) break;
			_LCD_Write( c );// Display on LCD
			bi++;
		}
		break;
	default:
		_LCD_Write( c );// Display on LCD
		break;
	}
}
}

_LCD_TEMPL
void LCD_GotoXy( char x, char y )
{
_LCD_FunctionMode();
unsigned char offset = (y << 6) + x;
_LCD_Write( set_dd_ram + offset );
}

_LCD_TEMPL
void LCD_Function( char func )
{
_LCD_FunctionMode();
_LCD_Write( func );
}

////////////////////////////////////////////////////////////////////////////
// Helpers that hide template arguments
////////////////////////////////////////////////////////////////////////////
// low level functions
#define lcd_write		LCD_Write<LCD_ARGS>
#define lcd_waitfornotbusy LCD_WaitForNotBusy<LCD_ARGS>
#define lcd_read		LCD_Read<LCD_ARGS>
#define lcd_funcmode	LCD_FunctionMode<LCD_ARGS>
#define lcd_datamode	LCD_DataMode<LCD_ARGS>


// high level functions - these all set function or data mode as required
#define lcd_setup		LCD_Setup<LCD_ARGS>
#define lprintf			LCD_Printf<LCD_ARGS>
#define lcd_clear		LCD_Clear<LCD_ARGS>
#define lcd_gotoxy		LCD_GotoXy<LCD_ARGS>
#define lcd_function 	LCD_Function<LCD_ARGS>

Share this post


Link to post
Share on other sites

I have had a look at your listing but I cannot see where you made the changes. Can you point them out?

 

Regards, Andrew

Share this post


Link to post
Share on other sites

Sorry, I should have marked them.

 

Search for ReadDataPort, which is the variable/template option I added throughout.

 

I also changed the suggested configuration options:

//#define LCD_ARGS     2,        /* Interface type: mode 0 = 8bit, 1 = 4bit(low nibble), 2 = 4bit(upper nibble) */ \
//        1,                     /* Use busy signal: 1 = use busy, 0 = use time delays */\
//        LATD, PORTD, TRISD, /* Write Data port, Read Data port, and data port tris register */ \
//        LATD, TRISD,         /* Control port and control port tris register */ \
//        3,                    /* Bit number of control port is connected to RS */ \
//        2,                    /* Bit number of control port is connected to RW */ \
//        1                    /* Bit number of control port is connected to Enable */

 

I suspect this is all a PIC18x specific issue, as the PIC16 doesn't use the concept of a latch register.

I also believe it shows a problem in the SourceBoost emulator which seems to always reflect the last byte written to the port, hence why the LCD program works fine with the LCD display plugin in emulation mode but not in practise.

 

Cheers,

Jason

Share this post


Link to post
Share on other sites

Hi Jason,

 

I have had a (brief) look into this. It appears to me that the original code in the driver was correct and that you might have hit a compiler bug (applogies in advance to the PICANT team if I am wrong).

 

data |= d & 0xF0;

 

The result of this operation should be [/code]date = data | (d & 0xF0);[/code] but I suspect the result was [/code]data = (data | d) & 0xF0;[/code]

 

In the scenario where a PIC16F uses a single port for LCD data and control there is a possibililty of hitting a read-modify-write problem. If the output control lines to the LCD are heavily loaded (not normally the case but someone may have added a LED or some other load) then the read of the port could see one of more of the control lines appear low when in reality they are high or visa versa. In this case the LCD driver code should be modified to deal with this corner case by storing a copy of the control lines in RAM and using these values to set the output port bits.

 

Regards, Andrew

Share this post


Link to post
Share on other sites
In the scenario where a PIC16F uses a single port for LCD data and control there is a possibililty of hitting a read-modify-write problem. If the output control lines to the LCD are heavily loaded (not normally the case but someone may have added a LED or some other load) then the read of the port could see one of more of the control lines appear low when in reality they are high or visa versa. In this case the LCD driver code should be modified to deal with this corner case by storing a copy of the control lines in RAM and using these values to set the output port bits.

 

From what I can see in the spec sheet, the latch register gets around the 'high load' issue. When I run the ICD2 emulator, it does indeed read the pins as being low, even when they are actually high (and that's with a 1.5K resistor to the base of a transistor!), so the latch register is the only one with the current state shown correctly on the PIC18.

 

I do remember the old 'store a copy of the register in RAM' issue on the PIC17 series, and I assume that's one reason the latch was introduced on the PIC18.

Most posts regarding this from MicroChip state that the latch should be used instead of the port register, so unless problems arise with the PIC16 then I suspect my solution may be the 'officially correct' method.

 

I've looked at the code created, and the following appears:

		data |= d & 0xF0;
012A  0EF0     	 MOVLW 0xF0
012C  1411     	 ANDWF LCD_RawWri_00005_arg_d, W
012E  128C     	 IORWF LCD_RawWri_00005_1_data, F

 

This looks correct, as a single read is being performed from variable d, with a single immediate OR to the destination port (data), so this is not where the problem lies (although it was a good potential problem point).

 

Once the char to int casting bug is fixed then I'd like to contribute back another fix for the led_driver.h that allows display of signed and unsigned values correctly.

Who is managing this stuff? Should I email the changes to someone instead of posting them in here?

 

Cheers,

Jason

Share this post


Link to post
Share on other sites

The library is meant to be target independant.

To use the LAT register for the control port instead of the port (in this example for PORTA):

 

#define LCD_ARGS 	1,	/* Interface type: mode 0 = 8bit, 1 = 4bit(low nibble), 2 = 4bit(upper nibble) */ \
	1, 				/* Use busy signal: 1 = use busy, 0 = use time delays */\
	PORTD, TRISD, 	/* Data port and data port tris register */ \
	LATA, TRISA, 	/* Control port and control port tris register */ \
	3,				/* Bit number of control port is connected to RS */ \
	2,				/* Bit number of control port is connected to RW */ \
	1 				/* Bit number of control port is connected to Enable */

 

then I'd like to contribute back another fix for the led_driver.h that allows display of signed and unsigned values correctly.

 

What is this issue ?

 

Who is managing this stuff? Should I email the changes to someone instead of posting them in here?

 

The code is managed as part of the distribution. If you would like to submit changes please send to support, and we will review the changes.

 

Regards

Dave

Share this post


Link to post
Share on other sites
What is this issue ?

 

It's been posted to the 'Bug Reports' section here, and seems to me to be a fundamental and serious bug (which is why an acknowledgement/confirmation/"no Jason, you're an idiot" response would be nice :) )

 

*Edit*

Ah, the bug in the LCD code is that if you try and display an unsigned integer using %u then it is always displayed as signed (only without the '-' symbol).

I wanted to display numbers >32768, so had to fix it.

 

I'll email you the fixes instead of posting in here.

*/Edit*

 

Cheers,

Jason

Edited by jsobell

Share this post


Link to post
Share on other sites
The library is meant to be target independant.

To use the LAT register for the control port instead of the port (in this example for PORTA):

 

#define LCD_ARGS     1,    /* Interface type: mode 0 = 8bit, 1 = 4bit(low nibble), 2 = 4bit(upper nibble) */ \
        1,                 /* Use busy signal: 1 = use busy, 0 = use time delays */\
        PORTD, TRISD,     /* Data port and data port tris register */ \
        LATA, TRISA,     /* Control port and control port tris register */ \
        3,                /* Bit number of control port is connected to RS */ \
        2,                /* Bit number of control port is connected to RW */ \
        1                 /* Bit number of control port is connected to Enable */

 

Obviously, but this does not work if Data and Control are on the same port.

The problem is in modifying the Data port in nibbles, where 'OR'ing in a new value will cause an internal Read/Write to set the new bits.

When the Read is performed, it reads the control bits as being zero, despite the fact they are actually high. Therefore, you *must* use the latch or you must OR in the saved state of the control pins, otherwise they will be forced back to low.

 

Target independence is only possible if you allow for the fact that PIC18s and PIC16s handle their ports differently. Check the Microchip data sheets and see at the port circuitry for the differences.

 

Cheers,

Jason

Share this post


Link to post
Share on other sites
The library is meant to be target independant.

 

Obviously, but this does not work if Data and Control are on the same port.

The problem is in modifying the Data port in nibbles, where 'OR'ing in a new value will cause an internal Read/Write to set the new bits.

When the Read is performed, it reads the control bits as being zero, despite the fact they are actually high. Therefore, you *must* use the latch or you must OR in the saved state of the control pins, otherwise they will be forced back to low.

 

The problem is more basic than this. The driver needs to protect the contents of output bits that are not part of the LCD data bus irrespective of the bits being used by the LCD or some other function. To OR in the state of the control bits in this case only makes sense if all bits are on the same port as the data. This can be a challenge for a general driver as it has no knowledge of how the programmer (such as yourself) are talking to the other bits on the port. Simulating the latch register of the 18F family for the 16F only makes sense if the other code written by other developers also uses the same emulated latch. The lat registers are are great help for the 18F family but you also need to attempt solve the general case for those processors without these registers which is a challenge because of the read-modify write problem of that processor class. However you have the case where some developers will write directly to the port and the other case where a bit configured to be an output is deliberately read as an input. Although a corner case - it is a valid technique.

 

Target independence is only possible if you allow for the fact that PIC18s and PIC16s handle their ports differently.

 

I think Dave was saying is that target independance is the GOAL of the driver .

Share this post


Link to post
Share on other sites
The problem is more basic than this. The driver needs to protect the contents of output bits that are not part of the LCD data bus irrespective of the bits being used by the LCD or some other function. To OR in the state of the control bits in this case only makes sense if all bits are on the same port as the data. This can be a challenge for a general driver as it has no knowledge of how the programmer (such as yourself) are talking to the other bits on the port. Simulating the latch register of the 18F family for the 16F only makes sense if the other code written by other developers also uses the same emulated latch. The lat registers are are great help for the 18F family but you also need to attempt solve the general case for those processors without these registers which is a challenge because of the read-modify write problem of that processor class. However you have the case where some developers will write directly to the port and the other case where a bit configured to be an output is deliberately read as an input.  Although a corner case - it is a valid technique.

 

Hmm, I think this is a mute point, as the latter problems you describe are unsolvable on the PIC16, and is the reason the latch was introduced on the PIC18.

 

I think Dave was saying is that target independance is the GOAL of the driver.

 

Yes, I understand that, but Dave's response was to suggest simply replacing 'PortA' with 'LatA', but this does not work in the common scenario where the port is shared. I posted a very simple solution for this situation that works with PIC16s with separate control/data ports, and with PIC18s with either separate or combined. Hence we have a solution that is still target independant, but works in more situations than before.

 

If someone can code up the solution to the PIC16 using a shared port, then that's great, and will only involve some compiler conditional code adding a few lines and one variable. I would do it myself, but I don't have any PIC16s at the moment (and the emulator is not consistent with the hardware port read/writes).

Whatever happens, it is not going to resolve the issue you mention above where a developer writes directly to the port, but we know we can't do anything about this, so it's probably best to simply document it in the template and let the developer beware.

 

Cheers,

Jason

Share this post


Link to post
Share on other sites

There's still nothing changed in the provided source to address this issue, and given that it's fairly standard to use a single 8-bit port for LCD display control, perhaps it should be rectified in the SourceBoost supplied version?

 

At the moment I have to replace the installed lcd_driver.h after every upgrade with my own to fix the bug.

 

Cheers,

Jason

Share this post


Link to post
Share on other sites

Jason,

 

There's still nothing changed in the provided source to address this issue, and given that it's fairly standard to use a single 8-bit port for LCD display control, perhaps it should be rectified in the SourceBoost supplied version?

 

At the moment I have to replace the installed lcd_driver.h after every upgrade with my own to fix the bug.

 

There has been alot mentioned in this thread, which issue(s) are you refering to?

Changed the boostc LCD sample to a single port:

RD6 RS

RD5 R/W

RD6 E

RD0 DB4

RD1 DB5

RD2 DB6

RD3 DB7

 

and it all worked fine.

 

Regards

Dave

Share this post


Link to post
Share on other sites

On which PIC chip?

 

As I've already shown several times, this is PIC16 only, and does not work on the PIC18 with it's latch register, hence why I emailed you version that worked on both.

 

I get this strange feeling of deja-vu...

 

Please see my posting from "Jul 5 2005, 02:42 PM"

 

Cheers,

Jason

Share this post


Link to post
Share on other sites

Jason,

 

As I've already shown several times, this is PIC16 only, and does not work on the PIC18 with it's latch register, hence why I emailed you version that worked on both.

 

Let me try and get this straight;

Wth PIC18 we don't have a problem, we can use the LAT registers which aren't affected by a pins drive characteristics and external loading.

 

With PIC16 pin characteristics and external loading mean that a value can be read that is different to that which was written. If this is the case then we have a shadow register that holds the last value written. Any code in our program that uses this port(s) must also use the shadow register(s).

 

Please re-mail your modified version of the code for further review.

 

BTW: Another thought I had about the LCD code was to make all data pins and control pins individually port selectable. That way you would not be stuck with using a particular port nibble for data. This would be much more complicated if shadow registers where used for each port.

 

Regards

Dave

Share this post


Link to post
Share on other sites
Let me try and get this straight;

Wth PIC18 we don't have a problem, we can use the LAT registers which aren't affected by a pins drive characteristics and external loading.

 

With PIC16 pin characteristics and external loading mean that a value can be read that is different to that which was written. If this is the case then we have a shadow register that holds the last value written. Any code in our program that uses this port(s) must also use the shadow register(s).

 

That's almost correct :) On the PIC18 we can't use the latch to read the data (such as status bits), as it always reflects the last written value, so we still need to use the data register.

 

All in all, we need to specify three ports in the setup, which is what the code I'm emailing you does. It's annoying to have to change the configuration options and add a parameter, creating a minor break in backwards compatability, but there has to be a way to specify separate data read port and data write ports.

 

Cheers,

Jason

Share this post


Link to post
Share on other sites

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...

×
×
  • Create New...