* drivers/lcd/st7032.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdlib.h>
#include <errno.h>
#include <debug.h>
#include <string.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/signal.h>
#include <nuttx/ascii.h>
#include <nuttx/fs/fs.h>
#include <nuttx/lcd/slcd_codec.h>
#include <nuttx/lcd/slcd_ioctl.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/lcd/st7032.h>
#ifndef CONFIG_LIBC_SLCDCODEC
# error please also select Library Routines, Segment LCD CODEC
#endif
#if defined(CONFIG_I2C) && defined(CONFIG_LCD_ST7032)
* Pre-processor Definitions
****************************************************************************/
#ifndef CONFIG_ST7032_I2C_FREQ
# define CONFIG_ST7032_I2C_FREQ 400000
#endif
* Private Types
****************************************************************************/
struct st7032_dev_s
{
FAR struct i2c_master_s *i2c;
uint8_t row;
uint8_t col;
uint8_t buffer[ST7032_MAX_ROW * ST7032_MAX_COL];
bool pendscroll;
mutex_t lock;
};
* Private Function Prototypes
****************************************************************************/
static inline void st7032_write_inst(FAR struct st7032_dev_s *priv,
uint8_t cmd);
static inline void st7032_write_data(FAR struct st7032_dev_s *priv,
uint8_t value);
static inline void st7032_setcontrast(FAR struct st7032_dev_s *priv,
int8_t contrast);
static void lcd_scroll_up(FAR struct st7032_dev_s *priv);
static ssize_t st7032_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t st7032_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static off_t st7032_seek(FAR struct file *filep, off_t offset, int whence);
static int st7032_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
* Private Data
****************************************************************************/
static const struct file_operations g_st7032fops =
{
NULL,
NULL,
st7032_read,
st7032_write,
st7032_seek,
st7032_ioctl,
};
* Private Functions
****************************************************************************/
* Name: st7032_write_inst
*
* Description:
* Write an Instruction command to ST7032
*
****************************************************************************/
static inline void st7032_write_inst(FAR struct st7032_dev_s *priv,
uint8_t cmd)
{
struct i2c_msg_s msg;
uint8_t data[2];
int ret;
data[0] = 0x00;
data[1] = cmd;
msg.frequency = CONFIG_ST7032_I2C_FREQ;
msg.addr = ST7032_I2C_ADDR;
msg.flags = 0;
msg.buffer = (FAR uint8_t *) data;
msg.length = 2;
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
if (ret < 0)
{
lcderr("ERROR: I2C_TRANSFER failed: %d\n", ret);
}
nxsig_usleep(30);
}
* Name: st7032_write_data
*
* Description:
* Write a Data command to ST7032
*
****************************************************************************/
static inline void st7032_write_data(FAR struct st7032_dev_s *priv,
uint8_t value)
{
struct i2c_msg_s msg;
uint8_t data[2];
int ret;
data[0] = ST7032_CTRLBIT_RS;
data[1] = value;
msg.frequency = CONFIG_ST7032_I2C_FREQ;
msg.addr = ST7032_I2C_ADDR;
msg.flags = 0;
msg.buffer = (FAR uint8_t *) data;
msg.length = 2;
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
if (ret < 0)
{
lcderr("ERROR: I2C_TRANSFER failed: %d\n", ret);
}
nxsig_usleep(30);
}
static inline void st7032_setcontrast(FAR struct st7032_dev_s *priv,
int8_t contrast)
{
if (contrast < ST7032_CONTRAST_MIN)
{
contrast = ST7032_CONTRAST_MIN;
}
else if (contrast > ST7032_CONTRAST_MAX)
{
contrast = ST7032_CONTRAST_MAX;
}
st7032_write_inst(priv, ST7032_CONTRAST_SET | (contrast & 0x0f));
st7032_write_inst(priv, (contrast >> 4) | ST7032_POWER_ICON_CTRL_SET |
POWER_ICON_BOST_CTRL_BON);
}
* Name: lcd_getdata
*
* Description:
* Simulate reading data from LCD, we are reading from internal buffer
*
****************************************************************************/
static inline uint8_t lcd_getdata(FAR struct st7032_dev_s *priv)
{
uint8_t data;
data = priv->buffer[priv->row * priv->col];
return data;
}
* Name: rc2addr
*
* Description:
* This converts a row/column pair to a screen memory address.
*
****************************************************************************/
static inline uint8_t rc2addr(FAR struct st7032_dev_s *priv)
{
return priv->row * 0x40 + priv->col;
}
* Name: addr2rc
*
* Description:
* This converts a screen memory address to a row/column pair.
*
****************************************************************************/
static inline void addr2rc(FAR struct st7032_dev_s *priv, uint8_t addr,
FAR uint8_t *row, FAR uint8_t *col)
{
*row = addr / 0x40;
*col = addr % 0x40;
}
* Name: lcd_set_curpos
*
* Description:
* This sets the cursor position based on row, column addressing.
*
* Input Parameters:
* priv - device instance
*
****************************************************************************/
static void lcd_set_curpos(FAR struct st7032_dev_s *priv)
{
uint8_t addr;
addr = rc2addr(priv);
st7032_write_inst(priv, ST7032_SET_DDRAM_ADDR | addr);
}
* Name: lcd_putdata
*
* Description:
* Write a byte to the LCD and update column/row position
*
****************************************************************************/
static inline void lcd_putdata(FAR struct st7032_dev_s *priv, uint8_t data)
{
st7032_write_data(priv, data);
priv->buffer[priv->col * priv->row] = data;
priv->col++;
if (priv->col >= ST7032_MAX_COL)
{
priv->col = 0;
priv->row++;
}
if (priv->row >= ST7032_MAX_ROW)
{
priv->pendscroll = true;
priv->row = ST7032_MAX_ROW - 1;
}
lcd_set_curpos(priv);
}
* Name: lcd_scroll_up
*
* Description:
* Scroll the display up, and clear the new (last) line.
*
****************************************************************************/
static void lcd_scroll_up(FAR struct st7032_dev_s *priv)
{
FAR uint8_t *data;
int currow;
int curcol;
data = kmm_malloc(ST7032_MAX_COL);
if (NULL == data)
{
lcdinfo("Failed to allocate buffer in lcd_scroll_up()\n");
return;
}
st7032_write_inst(priv, ST7032_CLEAR_DISPLAY);
for (currow = 1; currow < ST7032_MAX_ROW; ++currow)
{
priv->row = currow;
for (curcol = 0; curcol < ST7032_MAX_COL; ++curcol)
{
priv->col = curcol;
data[curcol] = lcd_getdata(priv);
}
priv->col = 0;
priv->row = currow - 1;
lcd_set_curpos(priv);
for (curcol = 0; curcol < ST7032_MAX_COL; ++curcol)
{
lcd_putdata(priv, data[curcol]);
}
}
priv->col = 0;
priv->row = ST7032_MAX_ROW - 1;
lcd_set_curpos(priv);
for (curcol = 0; curcol < ST7032_MAX_COL; ++curcol)
{
lcd_putdata(priv, ' ');
}
priv->col = 0;
priv->row = ST7032_MAX_ROW - 1;
lcd_set_curpos(priv);
kmm_free(data);
}
* Name: lcd_codec_action
*
* Description:
* Perform an 'action' as per the Segment LCD codec.
*
* Input Parameters:
* priv - device instance
* code - SLCD code action code
* count - count param for those actions that take it
*
****************************************************************************/
static void lcd_codec_action(FAR struct st7032_dev_s *priv,
enum slcdcode_e code, uint8_t count)
{
switch (code)
{
case SLCDCODE_BACKDEL:
{
if (count <= 0)
{
break;
}
else
{
if (count > priv->col)
{
count = priv->col;
}
priv->col = priv->col - count;
lcd_set_curpos(priv);
}
}
case SLCDCODE_FWDDEL:
{
if (count <= 0)
{
break;
}
else
{
uint8_t start;
uint8_t end;
uint8_t i;
uint8_t data;
start = priv->col + count;
if (start >= ST7032_MAX_COL)
{
break;
}
end = start + count;
if (end > ST7032_MAX_COL)
{
end = ST7032_MAX_COL;
}
for (i = priv->col; i < end; ++start, ++i)
{
priv->col = start;
lcd_set_curpos(priv);
data = lcd_getdata(priv);
priv->col = i;
lcd_set_curpos(priv);
lcd_putdata(priv, data);
}
for (; i < ST7032_MAX_COL; ++i)
{
lcd_putdata(priv, ' ');
}
lcd_set_curpos(priv);
}
}
break;
case SLCDCODE_ERASE:
if (count > 0)
{
uint8_t end;
uint8_t i;
end = priv->col + count;
if (end > ST7032_MAX_COL)
{
end = ST7032_MAX_COL;
}
for (i = priv->col; i < end; ++i)
{
lcd_putdata(priv, ' ');
}
lcd_set_curpos(priv);
}
break;
case SLCDCODE_CLEAR:
{
st7032_write_inst(priv, ST7032_CLEAR_DISPLAY);
}
break;
case SLCDCODE_ERASEEOL:
{
uint8_t i;
for (i = priv->col; i < ST7032_MAX_COL; ++i)
{
lcd_putdata(priv, ' ');
}
lcd_set_curpos(priv);
}
break;
case SLCDCODE_LEFT:
{
if (count > priv->col)
{
priv->col = 0;
}
else
{
priv->col -= count;
}
lcd_set_curpos(priv);
}
break;
case SLCDCODE_RIGHT:
{
priv->col += count;
if (priv->col >= ST7032_MAX_COL)
{
priv->col = ST7032_MAX_COL - 1;
}
lcd_set_curpos(priv);
}
break;
case SLCDCODE_UP:
{
if (count > priv->row)
{
priv->row = 0;
}
else
{
priv->row -= count;
}
lcd_set_curpos(priv);
}
break;
case SLCDCODE_DOWN:
{
priv->row += count;
if (priv->row >= ST7032_MAX_ROW)
{
priv->row = ST7032_MAX_ROW - 1;
}
lcd_set_curpos(priv);
}
break;
case SLCDCODE_HOME:
{
priv->col = 0;
lcd_set_curpos(priv);
}
break;
case SLCDCODE_END:
{
priv->col = ST7032_MAX_COL - 1;
lcd_set_curpos(priv);
}
break;
case SLCDCODE_PAGEUP:
case SLCDCODE_PAGEDOWN:
break;
case SLCDCODE_BLINKSTART:
st7032_write_inst(priv, ST7032_DISPLAY_ON_OFF | DISPLAY_ON_OFF_D |
DISPLAY_ON_OFF_C | DISPLAY_ON_OFF_B);
break;
case SLCDCODE_BLINKEND:
case SLCDCODE_BLINKOFF:
st7032_write_inst(priv, ST7032_DISPLAY_ON_OFF | DISPLAY_ON_OFF_D |
DISPLAY_ON_OFF_C);
break;
default:
case SLCDCODE_NORMAL:
break;
}
}
* Name: lcd_init
*
* Description:
* perform the initialization sequence to get the LCD into a known state.
*
****************************************************************************/
static void lcd_init(FAR struct st7032_dev_s *priv)
{
uint8_t data;
data = ST7032_FUNCTION_SET | FUNCTION_SET_DL | FUNCTION_SET_N |
FUNCTION_SET_IS;
st7032_write_inst(priv, data);
data = ST7032_INT_OSC_FREQ | INT_OSC_FREQ_BS | INT_OSC_FREQ_F2;
st7032_write_inst(priv, data);
data = ST7032_POWER_ICON_CTRL_SET | POWER_ICON_BOST_CTRL_ION;
st7032_write_inst(priv, data);
st7032_setcontrast(priv, DEFAULT_CONTRAST);
data = ST7032_FOLLOWER_CTRL | FOLLOWER_CTRL_FON | FOLLOWER_CTRL_RAB2;
st7032_write_inst(priv, data);
data = ST7032_DISPLAY_ON_OFF | DISPLAY_ON_OFF_D | DISPLAY_ON_OFF_C |
DISPLAY_ON_OFF_B;
st7032_write_inst(priv, data);
data = ST7032_ENTRY_MODE_SET | ENTRY_MODE_SET_ID;
st7032_write_inst(priv, data);
data = ST7032_CLEAR_DISPLAY;
st7032_write_inst(priv, data);
}
* 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 st7032_dev_s *priv,
uint8_t row, uint8_t col, FAR off_t *fpos)
{
*fpos = (row * ST7032_MAX_COL) + col + row;
}
* Name: st7032_read
****************************************************************************/
static ssize_t st7032_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
return -ENOSYS;
}
* Name: st7032_write
****************************************************************************/
static ssize_t st7032_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen)
{
FAR struct inode *inode = filep->f_inode;
FAR struct st7032_dev_s *priv = inode->i_private;
struct lib_meminstream_s instream;
struct slcdstate_s state;
enum slcdret_e result;
uint8_t ch;
uint8_t count;
nxmutex_lock(&priv->lock);
lib_meminstream(&instream, buffer, buflen);
memset(&state, 0, sizeof(struct slcdstate_s));
while ((result = slcd_decode(&instream.common,
&state, &ch, &count)) != SLCDRET_EOF)
{
if (priv->pendscroll)
{
lcd_scroll_up(priv);
priv->pendscroll = false;
}
if (result == SLCDRET_CHAR)
{
if (ch == ASCII_TAB)
{
* TAB?
*/
st7032_write_inst(priv, ST7032_DISPLAY_ON_OFF |
DISPLAY_ON_OFF_D | DISPLAY_ON_OFF_C |
DISPLAY_ON_OFF_B);
}
else if (ch == ASCII_VT)
{
}
else if (ch == ASCII_FF)
{
}
else if (ch == ASCII_CR)
{
priv->col = 0;
lcd_set_curpos(priv);
}
else if (ch == ASCII_SO)
{
st7032_write_inst(priv, ST7032_DISPLAY_ON_OFF |
DISPLAY_ON_OFF_D);
}
else if (ch == ASCII_SI)
{
lcd_init(priv);
priv->row = 0;
priv->col = 0;
}
else if (ch == ASCII_LF)
{
priv->row += 1;
if (priv->row >= ST7032_MAX_ROW)
{
priv->pendscroll = true;
priv->row = ST7032_MAX_ROW - 1;
}
priv->col = 0;
lcd_set_curpos(priv);
}
else if (ch == ASCII_BS)
{
lcd_codec_action(priv, SLCDCODE_BACKDEL, 1);
}
else if (ch == ASCII_DEL)
{
lcd_codec_action(priv, SLCDCODE_FWDDEL, 1);
}
else
{
lcd_putdata(priv, ch);
}
}
else
{
lcd_codec_action(priv, (enum slcdcode_e)ch, count);
}
}
lcd_curpos_to_fpos(priv, priv->row, priv->col, &filep->f_pos);
nxmutex_unlock(&priv->lock);
return buflen;
}
* Name: st7032_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 st7032_seek(FAR struct file *filep, off_t offset, int whence)
{
FAR struct inode *inode = filep->f_inode;
FAR struct st7032_dev_s *priv =
inode->i_private;
off_t maxpos;
off_t pos;
nxmutex_lock(&priv->lock);
maxpos = ST7032_MAX_ROW * ST7032_MAX_COL + (ST7032_MAX_ROW - 1);
pos = filep->f_pos;
switch (whence)
{
case SEEK_CUR:
pos += offset;
if (pos > maxpos)
{
pos = maxpos;
}
else if (pos < 0)
{
pos = 0;
}
filep->f_pos = pos;
break;
case SEEK_SET:
pos = offset;
if (pos > maxpos)
{
pos = maxpos;
}
else if (pos < 0)
{
pos = 0;
}
filep->f_pos = pos;
break;
case SEEK_END:
pos = maxpos + offset;
if (pos > maxpos)
{
pos = maxpos;
}
else if (pos < 0)
{
pos = 0;
}
filep->f_pos = pos;
break;
default:
pos = (off_t)-EINVAL;
break;
}
nxmutex_unlock(&priv->lock);
return pos;
}
* Name: st7032_ioctl
*
* Description:
* Perform device operations that are outside the standard I/O model.
*
****************************************************************************/
static int st7032_ioctl(FAR struct file *filep, int cmd,
unsigned long arg)
{
switch (cmd)
{
case SLCDIOC_GETATTRIBUTES:
{
FAR struct inode *inode = filep->f_inode;
FAR struct slcd_attributes_s *attr =
(FAR struct slcd_attributes_s *)((uintptr_t)arg);
lcdinfo("SLCDIOC_GETATTRIBUTES:\n");
if (!attr)
{
return -EINVAL;
}
attr->nrows = ST7032_MAX_ROW;
attr->ncolumns = ST7032_MAX_COL;
attr->nbars = 0;
attr->maxcontrast = 0;
attr->maxbrightness = 1;
}
break;
case SLCDIOC_CURPOS:
{
FAR struct inode *inode = filep->f_inode;
FAR struct st7032_dev_s *priv =
inode->i_private;
FAR struct slcd_curpos_s *attr =
(FAR struct slcd_curpos_s *)((uintptr_t)arg);
attr->row = priv->row;
attr->column = priv->col;
}
break;
case SLCDIOC_GETBRIGHTNESS:
{
FAR struct inode *inode = filep->f_inode;
FAR struct st7032_dev_s *priv =
inode->i_private;
nxmutex_lock(&priv->lock);
*(FAR int *)((uintptr_t)arg) = 1;
nxmutex_unlock(&priv->lock);
}
break;
case SLCDIOC_SETBRIGHTNESS:
{
FAR struct inode *inode = filep->f_inode;
FAR struct st7032_dev_s *priv =
inode->i_private;
nxmutex_lock(&priv->lock);
nxmutex_unlock(&priv->lock);
}
break;
case SLCDIOC_SETBAR:
case SLCDIOC_GETCONTRAST:
case SLCDIOC_SETCONTRAST:
default:
return -ENOTTY;
}
return OK;
}
* Public Functions
****************************************************************************/
* Name: st7032_register
*
* Description:
* Register the ST7032 character device as 'devpath'
*
* Input Parameters:
* devpath - The full path to the driver to register. E.g., "/dev/temp0"
* i2c - An instance of the I2C interface to use to communicate with
* ST7032
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int st7032_register(FAR const char *devpath, FAR struct i2c_master_s *i2c)
{
FAR struct st7032_dev_s *priv;
int ret;
priv = kmm_malloc(sizeof(struct st7032_dev_s));
if (!priv)
{
snerr("ERROR: Failed to allocate instance\n");
return -ENOMEM;
}
priv->i2c = i2c;
priv->col = 0;
priv->row = 0;
priv->pendscroll = false;
nxmutex_init(&priv->lock);
lcd_init(priv);
ret = register_driver(devpath, &g_st7032fops, 0666, priv);
if (ret < 0)
{
snerr("ERROR: Failed to register driver: %d\n", ret);
nxmutex_destroy(&priv->lock);
kmm_free(priv);
}
return ret;
}
#endif