blob: 59825dc31901b386792990e0067f06a566c19b76 [file] [log] [blame]
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
""" Text-based LCD module driver for LCM2004."""
# servo libs
import hw_driver
# commands
LCD_CLEAR_DISPLAY = 0x01
LCD_RETURN_HOME = 0x02
LCD_ENTRY_MODE_SET = 0x04
LCD_DISPLAY_CONTROL = 0x08
LCD_CURSOR_SHIFT = 0x10
LCD_FUNCTION_SET = 0x20
LCD_SET_CGRAM_ADDR = 0x40
LCD_SET_DDRAM_ADDR = 0x80
# entry mode
LCD_ENTRY_RIGHT = 0x00
LCD_ENTRY_LEFT = 0x02
LCD_ENTRY_SHIFT_INCREMENT = 0x01
LCD_ENTRY_SHIFT_DECREMENT = 0x00
# on/off control
LCD_DISPLAY_ON = 0x04
LCD_DISPLAY_OFF = 0x00
LCD_CURSOR_ON = 0x02
LCD_CURSOR_OFF = 0x00
LCD_BLINK_ON = 0x01
LCD_BLINK_OFF = 0x00
# display/cursor shift
LCD_DISPLAY_MOVE = 0x08
LCD_CURSOR_MOVE = 0x00
LCD_MOVE_RIGHT = 0x04
LCD_MOVE_LEFT = 0x00
# function set
LCD_8BIT_MODE = 0x10
LCD_4BIT_MODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x10_DOTS = 0x04
LCD_5x8_DOTS = 0x00
# backlight control
LCD_BACKLIGHT_ON = 0x08
LCD_BACKLIGHT_OFF = 0x00
# instruction and data registers
LCD_REGISTER_CMD = 0
LCD_REGISTER_DATA = 1
# EN pin
LCD_EN_BIT = 0b00000100
# data used in the initial stage of LCM
LCD_DATA1_AS_INIT = 0x30
LCD_DATA2_AS_INIT = 0x20
# Devices shared among driver objects:
# (interface instance, child) => Lcm2004Device instance
lcm2004_devices = {}
class LcmError(hw_driver.HwDriverError):
"""Error occurred accessing LCM2004."""
pass
class Lcm2004Device(object):
"""Defines a LCM2004 device shared among many LCM2004 drivers.
Public Attributes:
backlight_value: value indicating if a device has backlight.
"""
def __init__(self):
self.backlight_value = LCD_BACKLIGHT_ON
class lcm2004(hw_driver.HwDriver):
"""Provides drv=lcm2004 control."""
# the number of column and row
COLUMN = 20
ROW = 4
ROW_ADDR_OFFSET = [0x00, 0x40, 0x14, 0x54]
def __init__(self, interface, params):
"""Constructor.
Args:
interface: interface object to handle low-level communication.
params: dictionary of params needed to perform operations on the device.
All items are strings initially but should be cast to types detailed
below.
Mandatory Params:
child: integer, 7-bit i2c child address
Optional Params:
N/A
"""
super(lcm2004, self).__init__(interface, params)
device_key = (interface, self._get_child())
if device_key not in lcm2004_devices:
self._device = Lcm2004Device()
lcm2004_devices[device_key] = self._device
self._init()
else:
self._device = lcm2004_devices[device_key]
def _init(self):
"""Initiates LCM.
The initial process refers the figure 24
in the page 46 of HD44780U datasheet.
"""
# start in 8bit mode, try to set 4 bit mode
self._write_expander(LCD_DATA1_AS_INIT)
# second try
self._write_expander(LCD_DATA1_AS_INIT)
# set to 4-bit interface
self._write_expander(LCD_DATA2_AS_INIT)
# function set
self._command(LCD_FUNCTION_SET | LCD_4BIT_MODE | LCD_2LINE | LCD_5x8_DOTS)
# turn on the display with cursor
self._command(LCD_DISPLAY_CONTROL | LCD_DISPLAY_ON | LCD_CURSOR_ON
| LCD_BLINK_OFF)
# entry mode set
self._command(LCD_ENTRY_MODE_SET | LCD_ENTRY_LEFT
| LCD_ENTRY_SHIFT_DECREMENT)
self._home()
self._clear()
def _check_8bit(self, v):
"""Checks if v uses only lower 8 bits."""
if v & 0xFF != v:
raise LcmError('0x%x is not 8-bit' % v)
def _get_child(self):
"""Checks and return needed params to call driver.
Returns:
child: 7-bit i2c address
Raises:
LcmError: If the 'child' doesn't exist.
"""
if 'child' not in self._params:
raise LcmError('Missing child address "child"')
child = int(self._params['child'], 0)
return child
def _write_byte(self, byte):
"""Writes one byte to PCF8574(remote IO expander).
Args:
byte: One byte sent to IIC bus.
"""
self._check_8bit(byte)
self._interface.wr_rd(self._get_child(), [byte], 0)
def _write_expander(self, byte):
"""Writes the byte to expander.
By controlling the high/low of the En pin(bit 2),
the byte will be sent to the LCM.
The difference between _write_byte() & _write_expander() is:
The _write_byte(): byte -IIC-> PCF8574.
The _write_expander(): byte -IIC-> PCF8574 -Serial-> LCM.
The _write_expander() sends the byte to LCM by pulling a pulse
of the EN pin.
Args:
byte: 8 bits layout
bit 7 - 4: High or low nibble of the data, which
comes from the 2nd paramter of _send().
bit 3: Backlight on(1)/off(0).
bit 2: The En pin high(1)/low(0).
bit 1: Read from(1)/Write to(0) LCM.
bit 0: Data(1)/command(0) mode.
"""
self._write_byte(byte)
# Pull a pulse of EN pin
self._write_byte(byte | LCD_EN_BIT)
self._write_byte(byte & ~LCD_EN_BIT)
def _send(self, data, mode):
"""Sends one byte data to either instruction or data register.
Because of byte layout describing in _write_expander(),
we need to separate the "data" byte into high nibble,
and low nibble and send them in order. Besides that,
we need to set backlight on/off and data/instruction bit
before sending the nibble.
Args:
data: One byte.
mode: LCD_REGISTER_CMD or LCD_REGISTER_DATA.
"""
high_nibble = data & 0xF0
low_nibble = (data << 4) & 0xF0
backlight = self._device.backlight_value
self._write_expander(high_nibble | backlight | mode)
self._write_expander(low_nibble | backlight | mode)
def _command(self, data):
"""Sends command to instruction register.
Args:
data: One byte command.
"""
self._send(data, LCD_REGISTER_CMD)
def _clear(self):
"""Cleans full screen."""
self._command(LCD_CLEAR_DISPLAY)
def _home(self):
"""Sets cursor to column 0 and row 0."""
self._command(LCD_RETURN_HOME)
def _backlight_on(self):
"""Turns on backlight."""
self._device.backlight_value = LCD_BACKLIGHT_ON
self._write_byte(LCD_BACKLIGHT_ON)
def _backlight_off(self):
"""Turns off backlight."""
self._device.backlight_value = LCD_BACKLIGHT_OFF
self._write_byte(LCD_BACKLIGHT_OFF)
def _Set_lcm_text(self, text):
"""Prints text to LCM.
Args:
text: string value to be printed on the LCM.
"""
if len(text) > lcm2004.COLUMN:
raise LcmError('The text length is larger than %d.' % lcm2004.COLUMN)
for c in text:
self._send(ord(c), LCD_REGISTER_DATA)
def _Set_lcm_row(self, row_number):
"""Positions LCM cursor.
Args:
row_number: range from 0 to 3.
"""
row = int(row_number)
if row >= lcm2004.ROW:
raise LcmError('Row number(%d) is out of range(0-3).' % row)
self._command(LCD_SET_DDRAM_ADDR
| (lcm2004.ROW_ADDR_OFFSET[min(row, lcm2004.ROW - 1)]))
def _Set_lcm_cmd(self, cmd):
"""Sends command to LCM.
Args:
cmd: clear | home | bklon | bkloff.
"""
if cmd == 'clear':
self._clear()
elif cmd == 'home':
self._home()
elif cmd == 'bklon':
self._backlight_on()
elif cmd == 'bkloff':
self._backlight_off()
else:
raise LcmError('Unsupported command(%s).' % cmd)