Merged in ziggurat29/nuttx/stm32l4_i2c_lcd_mjkdz_001 (pull request #35)

correct bugs and add enhancements to pcf8574 lcd backpack driver
This commit is contained in:
Gregory Nutt 2016-05-29 14:02:39 -06:00
commit 22044edd12
3 changed files with 261 additions and 32 deletions

View File

@ -63,9 +63,9 @@
/* timing characteristics of the LCD interface */
#define DELAY_US_NYBBLE0 200
#define DELAY_US_NYBBLE1 100
#define DELAY_US_WRITE 35
#define DELAY_US_NYBBLE0 20
#define DELAY_US_NYBBLE1 10
#define DELAY_US_WRITE 40
#define DELAY_US_HOMECLEAR 1500
/* HD44780 commands */
@ -86,6 +86,8 @@
# define lcdvdbg(x...)
#endif
#define MAX_OPENCNT (255) /* Limit of uint8_t */
/****************************************************************************
* Private Types
****************************************************************************/
@ -95,6 +97,8 @@ struct pcf8574_lcd_dev_s
FAR struct i2c_master_s *i2c; /* I2C interface */
struct pcf8574_lcd_backpack_config_s cfg; /* gpio configuration */
uint8_t bl_bit; /* current backlight bit */
uint8_t refs; /* Number of references */
uint8_t unlinked; /* We are unlinked, so teardown on last close */
sem_t sem_excl; /* mutex */
};
@ -117,12 +121,16 @@ static ssize_t pcf8574_lcd_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t pcf8574_lcd_write(FAR struct file *filep,
FAR const char *buffer, size_t buflen);
static off_t pcf8574_lcd_seek(FAR struct file *filep, off_t offset, int whence);
static int pcf8574_lcd_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
#ifndef CONFIG_DISABLE_POLL
static int pcf8574lcd_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
#endif
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int pcf8574_lcd_unlink(FAR struct inode *inode);
#endif
/****************************************************************************
* Private Data
@ -134,12 +142,14 @@ static const struct file_operations g_pcf8574_lcd_fops =
pcf8574_lcd_close, /* close */
pcf8574_lcd_read, /* read */
pcf8574_lcd_write, /* write */
0, /* seek */
pcf8574_lcd_seek, /* seek */
pcf8574_lcd_ioctl, /* ioctl */
#ifndef CONFIG_DISABLE_POLL
pcf8574lcd_poll, /* poll */
#endif
0 /* unlink */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
pcf8574_lcd_unlink /* unlink */
#endif
};
/****************************************************************************
@ -259,7 +269,7 @@ static inline uint8_t rc2addr(FAR struct pcf8574_lcd_dev_s *priv,
* of first line, and fourth line is a continuation of second.
*/
return (row - 2) * 0x40 + (col - priv->cfg.cols);
return (row - 2) * 0x40 + (col + priv->cfg.cols);
}
}
@ -383,9 +393,14 @@ static void latch_nybble(FAR struct pcf8574_lcd_dev_s *priv, uint8_t nybble,
en_bit = 1 << priv->cfg.en;
rs_bit = rs ? (1 << priv->cfg.rs) : 0;
/* Put the nybble, preserving backlight, reset R/~W and set EN and maybe RS */
/* Put the nybble, preserving backlight, reset R/~W and maybe RS */
lcddata = prepare_nybble(priv, nybble) | priv->bl_bit | en_bit | rs_bit;
lcddata = prepare_nybble(priv, nybble) | priv->bl_bit | rs_bit;
pca8574_write(priv, lcddata);
/* Now set EN */
lcddata |= en_bit;
pca8574_write(priv, lcddata);
up_udelay(DELAY_US_NYBBLE0); /* setup */
@ -419,9 +434,14 @@ static uint8_t load_nybble(FAR struct pcf8574_lcd_dev_s *priv, bool rs)
rs_bit = rs ? (1 << priv->cfg.rs) : 0;
rw_bit = 1 << priv->cfg.rw;
/* Put highs on the data lines, preserve, set R/~W and set EN and maybe RS */
/* Put highs on the data lines, preserve, set R/~W and maybe RS */
lcddata = prepare_nybble(priv, 0x0f) | priv->bl_bit | en_bit | rw_bit | rs_bit;
lcddata = prepare_nybble(priv, 0x0f) | priv->bl_bit | rw_bit | rs_bit;
pca8574_write(priv, lcddata);
/* Now set EN */
lcddata |= en_bit;
pca8574_write(priv, lcddata);
up_udelay(DELAY_US_NYBBLE0); /* setup */
@ -538,7 +558,7 @@ static void lcd_init(FAR struct pcf8574_lcd_dev_s *priv)
{
/* Wait for more than 15 ms after Vcc for the LCD to stabilize */
usleep(20000);
usleep(50000);
/* Perform the init sequence. This sequence of commands is constructed so
* that it will get the device into nybble mode irrespective of what state
@ -548,7 +568,7 @@ static void lcd_init(FAR struct pcf8574_lcd_dev_s *priv)
* the remainder of operations.
*/
/* Send Command 0x30, set 8-bit mode, and wait > 4.1 ms*/
/* Send Command 0x30, set 8-bit mode, and wait > 4.1 ms */
latch_nybble(priv, 0x30>>4, false);
usleep(5000);
@ -556,15 +576,17 @@ static void lcd_init(FAR struct pcf8574_lcd_dev_s *priv)
/* Send Command 0x30, set 8-bit mode, and wait > 100 us */
latch_nybble(priv, 0x30>>4, false);
usleep(200);
usleep(5000);
/* Send Command 0x30, set 8-bit mode */
latch_nybble(priv, 0x30>>4, false);
usleep(200);
/* now Function set: Set interface to be 4 bits long (only 1 cycle write for the first time). */
latch_nybble(priv, 0x20>>4, false);
usleep(5000);
/* Function set: DL=0;Interface is 4 bits, N=1 (2 Lines), F=0 (5x8 dots font) */
@ -988,6 +1010,53 @@ static int lcd_getstream(FAR struct lib_instream_s *instream)
return EOF;
}
/****************************************************************************
* Name: lcd_fpos_to_curpos
*
* Description:
* Convert a file logical offset to a screen cursor pos (row,col). This
* discounts 'synthesized' line feeds at the end of screen lines.
*
****************************************************************************/
static void lcd_fpos_to_curpos(FAR struct pcf8574_lcd_dev_s *priv,
off_t fpos, uint8_t *row, uint8_t *col, bool* onlf)
{
int virtcols;
virtcols = (priv->cfg.cols + 1);
/* Determine if this is a 'virtual' position (on the synthetic LF) */
*onlf = (priv->cfg.cols == fpos % virtcols);
/* Adjust off any preceding synthetic LF's to get linear position */
fpos -= fpos / virtcols;
/* Compute row/col from linear position */
*row = fpos / priv->cfg.cols;
*col = fpos % priv->cfg.cols;
}
/****************************************************************************
* Name: lcd_curpos_to_fpos
*
* Description:
* Convert a screen cursor pos (row,col) to a file logical offset. This
* includes 'synthesized' line feeds at the end of screen lines.
*
****************************************************************************/
static void lcd_curpos_to_fpos(FAR struct pcf8574_lcd_dev_s *priv,
uint8_t row, uint8_t col, off_t* fpos)
{
/* the logical file position is the linear position plus any synthetic LF */
*fpos = (row * priv->cfg.cols) + col + row;
}
/****************************************************************************
* Name: pcf8574_lcd_open
*
@ -998,6 +1067,22 @@ static int lcd_getstream(FAR struct lib_instream_s *instream)
static int pcf8574_lcd_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private;
/* Increment the reference count */
sem_wait(&priv->sem_excl);
if (priv->refs == MAX_OPENCNT)
{
return -EMFILE;
}
else
{
priv->refs++;
}
sem_post(&priv->sem_excl);
return OK;
}
@ -1011,7 +1096,35 @@ static int pcf8574_lcd_open(FAR struct file *filep)
static int pcf8574_lcd_close(FAR struct file *filep)
{
return OK;
FAR struct inode *inode = filep->f_inode;
FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private;
int ret;
/* Decrement the reference count */
sem_wait(&priv->sem_excl);
if (priv->refs == 0)
{
ret = -EIO;
}
else
{
priv->refs--;
/* If we had previously unlinked, but there were open references at the
* time, we need to do the final teardown now.
*/
if (priv->refs == 0 && priv->unlinked)
{
/* We have no real teardown at present */
}
ret = OK;
}
sem_post(&priv->sem_excl);
return ret;
}
/****************************************************************************
@ -1019,7 +1132,8 @@ static int pcf8574_lcd_close(FAR struct file *filep)
*
* Description:
* This simply reads as much of the display memory as possible. This is
* probably not very interesting.
* generally not very interesting, but we do it in a way that allows us to
* 'cat' the LCD contents via the shell.
*
****************************************************************************/
@ -1032,22 +1146,44 @@ static ssize_t pcf8574_lcd_read(FAR struct file *filep, FAR char *buffer,
uint8_t addr;
uint8_t row;
uint8_t col;
bool onlf;
sem_wait(&priv->sem_excl);
/* Get current cursor position so we can restore it */
(void)lcd_read_busy_addr(priv, &addr);
addr2rc(priv, addr, &row, &col);
/* Just read the entire display into the given buffer, as much as possible */
/* Convert file position to row/col address and position DDADDR there */
lcd_fpos_to_curpos(priv, filep->f_pos, &row, &col, &onlf);
lcd_set_curpos(priv, row, col);
/* Read as much of the display as possible */
nIdx = 0;
row = 0;
col = 0;
while (nIdx < buflen && row < priv->cfg.rows)
{
/* Synthesize end-of-line LF and advance to start of next row */
if (onlf)
{
/* Synthesize LF for all but last row */
if ( row < priv->cfg.rows-1)
{
buffer[nIdx] = '\x0a';
onlf = false;
++filep->f_pos;
++nIdx;
}
++row;
col = 0;
continue;
}
/* If we are at start of line we will need to update DDRAM address */
if (0 == col)
{
lcd_set_curpos(priv, row, 0);
@ -1055,12 +1191,15 @@ static ssize_t pcf8574_lcd_read(FAR struct file *filep, FAR char *buffer,
buffer[nIdx] = lcd_getdata(priv);
++filep->f_pos;
++nIdx;
++col;
/* If we are now at the end of a line, we setup for the synthetic LF */
if (priv->cfg.cols == col)
{
++row;
col = 0;
onlf = true;
}
}
@ -1214,10 +1353,62 @@ static ssize_t pcf8574_lcd_write(FAR struct file *filep,
}
}
/* Wherever we wound up, update our logical file pos to reflect it */
lcd_curpos_to_fpos(priv, row, col, &filep->f_pos);
sem_post(&priv->sem_excl);
return buflen;
}
/****************************************************************************
* Name: pcf8574_lcd_seek
*
* Description:
* Seek the logical file pointer to the specified position. This is
* probably not very interesting except possibly for (SEEK_SET, 0) to
* rewind the pointer for a subsequent read().
* The file pointer is logical, and includes synthesized LF chars at the
* end of the display lines.
*
****************************************************************************/
static off_t pcf8574_lcd_seek(FAR struct file *filep, off_t offset, int whence)
{
FAR struct inode *inode = filep->f_inode;
FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private;
int maxpos;
sem_wait(&priv->sem_excl);
maxpos = priv->cfg.rows * priv->cfg.cols + (priv->cfg.rows - 1);
switch (whence)
{
case SEEK_CUR:
filep->f_pos += offset;
if (filep->f_pos > maxpos)
filep->f_pos = maxpos;
break;
case SEEK_SET:
filep->f_pos = offset;
if (filep->f_pos > maxpos)
filep->f_pos = maxpos;
break;
case SEEK_END:
filep->f_pos = maxpos;
break;
default:
/* Return EINVAL if the whence argument is invalid */
filep->f_pos = -EINVAL;
}
sem_post(&priv->sem_excl);
return filep->f_pos;
}
/****************************************************************************
* Name: pcf8574_lcd_ioctl
*
@ -1337,6 +1528,32 @@ static int pcf8574lcd_poll(FAR struct file *filep, FAR struct pollfd *fds,
}
#endif
/****************************************************************************
* Name: pcf8574_lcd_unlink
****************************************************************************/
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int pcf8574_lcd_unlink(FAR struct inode *inode)
{
FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private;
int ret = OK;
sem_wait(&priv->sem_excl);
priv->unlinked = true;
/* If there are no open references to the driver then tear it down now */
if (priv->refs == 0)
{
/* We have no real teardown at present */
ret = OK;
}
sem_post(&priv->sem_excl);
return ret;
}
#endif
/****************************************************************************
* Public Functions
****************************************************************************/
@ -1383,6 +1600,8 @@ int pcf8574_lcd_backpack_register(FAR const char *devpath,
priv->i2c = i2c;
priv->cfg = *cfg;
priv->bl_bit = priv->cfg.bl_active_high ? 0 : (1 << priv->cfg.bl);
priv->refs = 0;
priv->unlinked = false;
sem_init(&priv->sem_excl, 0, 1);
/* Initialize */

View File

@ -225,7 +225,7 @@ Example of programming a character image:
Now character '\x04' will display as an 'up arrow'.
Note, you might consider avoiding the use of code point 0x00 unless
absolutely needed, because the embedded nul character can cause
you absolutely need it, because the embedded nul character can cause
problems. The driver, and write() apis are binary, and unaffected,
but things like printf() and puts() assume C-style strings, and are
affected.
@ -237,9 +237,16 @@ Troubleshooting
bus timeouts that suggest a non-responsive slave.
* Check your board wiring and configuration specification. Buzz
out the lines if you have to.
* Con't forget to check the 'contrast' potentiometer. The voltage
at the central wiper should be approximately 0.29 V. The useful
range of voltages at this pin is very narrow, and outside that
range there will be nothing visible on the display, so most of the
turn range of the pot is non-useful. Much of human life has been
wasted in the rediscovery of this farcically idiotic
* Remember to set the (ros,cols) geometry in pcf8574_lcd_backpack_config_s
before registration of the driver, since this cannot be determined
programmatically.
* If the driver registration step seems to 'hang' it could be the I2C
driver performing retries due to no response from the LCD backpack. Check
the address. Turning on debug output for I2C can help make this visible.
* Don't forget to check the 'contrast' potentiometer. The voltage at the
central wiper should be approximately 0.3 V - 2.4 V, but the actual value
is is dependent on the physics of the attached LCD module. The useful
range of voltages at this pin for any given LCD is quite narrow, and
outside that range there will be nothing visible on the display, so most
of the turn range of the pot is non-useful. It's less 'contrast' and
more 'LCD segment drive bias'.

View File

@ -58,12 +58,15 @@
* Pretty much anything on the market except 4x40, which really consists of two
* separate 2x40 controllers, and the I2C backpack doesn't support those due
* to the second 'E' line being needed.
* Additionally, you still need to set the (row,col) geometry explicitly, since
* there is not a means of determining this dynamically.
* Consider these 'informative'.
* XXX Note, actual testing has been done on LCD_I2C_BACKPACK_CFG_MJKDZ only,
* the others come from online research.
* XXX Note, actual testing has been done on LCD_I2C_BACKPACK_CFG_MJKDZ
* and LCD_I2C_BACKPACK_CFG_SAINSMART only, the others come from online
* research.
*/
/* board marked 'mjkdz' */
/* board marked 'mjkdz' and Arduino-IIC-LCD GY-LCD-V1 */
#define LCD_I2C_BACKPACK_CFG_MJKDZ {0x20,4,5,6,0,1,2,3,7,false,0,0}