Source code for hwtLib.peripheral.displays.hd44780.driver

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from math import ceil

from hwt.code import FsmBuilder, If, Switch
from hwt.hdl.types.bits import HBits
from hwt.hdl.types.enum import HEnum
from hwt.hwIOs.std import HwIODataRdVld, HwIORdVldSync, HwIOSignal, HwIOVectSignal
from hwt.hwIOs.utils import addClkRstn, propagateClkRstn
from hwt.hwModule import HwModule
from hwt.hwParam import HwParam
from hwt.math import log2ceil
from hwt.pyUtils.typingFuture import override
from hwt.synthesizer.rtlLevel.rtlSignal import RtlSignal
from hwtLib.clocking.clkBuilder import ClkBuilder
from hwtLib.handshaked.builder import HsBuilder
from hwtLib.handshaked.storedBurst import HandshakedStoredBurst
from hwtLib.handshaked.streamNode import StreamNode
from hwtLib.peripheral.displays.hd44780.intf import HwIOHd44780


[docs] class HwIOHd44780Cmd(HwIORdVldSync): """ .. hwt-autodoc:: """ @override def hwConfig(self): self.DATA_WIDTH = HwParam(8) @override def hwDeclr(self): self.rs = HwIOSignal() # register select self.rw = HwIOSignal() self.long_wait = HwIOSignal() self.d = HwIOVectSignal(self.DATA_WIDTH) super(HwIOHd44780Cmd, self).hwDeclr()
[docs] class Hd44780CmdBurst(HandshakedStoredBurst): """ .. hwt-autodoc:: """
[docs] def __init__(self): super(Hd44780CmdBurst, self).__init__(HwIOHd44780Cmd) self.DATA = ((0, 0, 0, 0), # an example meaningless data (0, 0, 0, 1))
[docs] @override def set_data(self, hwIO: HwIOHd44780Cmd, d): if d is None: rs, rw, long_wait, d = None, None, None, None else: rs, rw, long_wait, d = d return [ hwIO.rs(rs), hwIO.rw(rw), hwIO.long_wait(long_wait), hwIO.d(d), ]
# http://elm-chan.org/docs/lcd/hd44780_e.html
[docs] class Hd44780Driver(HwModule): """ Controller for Hitachi HD44780 based LCD displays * Manages command delays and IO control * Sends initialization sequence * Translates '\\n' to a jump on next line and clean of line * Translates '\\f' to a clean the display and set cursor to first line first char :attention: The character set of HD44780 is not ASCII, but it is very similar .. hwt-autodoc:: """ @override def hwConfig(self): self.LCD_ROWS = HwParam(2) self.LCD_COLS = HwParam(16) self.LCD_DATA_WIDTH = HwParam(8) self.LCD_FREQ = HwParam(int(270e3)) # frequency of this component to compute timing for LCD control self.CLK_FREQ = HwParam(int(100e6)) @override def hwDeclr(self): addClkRstn(self) with self._hwParamsShared(prefix="LCD_"): self.dataOut = HwIOHd44780()._m() self.dataIn = HwIODataRdVld() self.dataIn.DATA_WIDTH = 8
[docs] def _translate_data_in(self, din: HwIODataRdVld) -> HwIOHd44780Cmd: """ Translates special characters to a LCD commands * '\\n' jump on next line and clean it with ' ' * '\\f' clean the LCD and reset cursor position """ if self.LCD_ROWS > 1: row = self._reg('row', HBits(log2ceil(self.LCD_ROWS - 1)), def_val=0) line_offsets = self._sig("line_offsets", HBits(8)[self.LCD_ROWS], def_val=[ HwIOHd44780.CMD_DDRAM_ADDR_SET(i * self.LCD_COLS) for i in range(self.LCD_ROWS) ]) line_offset = self._sig("line_offset", HBits(8)) line_offset(line_offsets[row]) else: # version without row register raise NotImplementedError() col = self._reg('col', HBits(log2ceil(self.LCD_COLS - 1)), def_val=0) dout = HwIOHd44780Cmd() dout.DATA_WIDTH = self.LCD_DATA_WIDTH self.data_in_translated = dout new_line_fsm_t = HEnum("new_line_fsm_t", [ 'row_incr', 'jmp_on_nextline', "clean_line", "jmp_on_line_start", ]) new_line_fsm = FsmBuilder(self, new_line_fsm_t, "new_line_fsm")\ .Trans(new_line_fsm_t.row_incr, (din.vld & din.data._eq(ord('\n')), new_line_fsm_t.jmp_on_nextline))\ .Trans(new_line_fsm_t.jmp_on_nextline, (dout.rd, new_line_fsm_t.clean_line))\ .Trans(new_line_fsm_t.clean_line, (dout.rd & col._eq(self.LCD_COLS - 1), new_line_fsm_t.jmp_on_line_start))\ .Trans(new_line_fsm_t.jmp_on_line_start, (dout.rd, new_line_fsm_t.row_incr)).stateReg def set_out(d, rs, rw, long_wait): return [ dout.d(d), dout.rs(rs), dout.rw(rw), dout.long_wait(long_wait), ] If(din.vld & din.data._eq(ord('\n')), # jump on next line and clean it with ' ' and jump back on first char Switch(new_line_fsm)\ .Case(new_line_fsm_t.row_incr, row(row + 1), *set_out(None, None, None, None), din.rd(0), dout.vld(0), ).Case(new_line_fsm_t.jmp_on_nextline, col(0), *set_out(line_offset, HwIOHd44780.RS_CONTROL, HwIOHd44780.RW_WRITE, 0), din.rd(0), dout.vld(1), ).Case(new_line_fsm_t.clean_line, If(dout.rd, col(col + 1), ), *set_out(HwIOHd44780.CHAR_MAP[' '], HwIOHd44780.RS_DATA, HwIOHd44780.RW_WRITE, 0), ).Case(new_line_fsm_t.jmp_on_line_start, *set_out(line_offset, HwIOHd44780.RS_CONTROL, HwIOHd44780.RW_WRITE, 0), *StreamNode([din], [dout]).sync(), ), ).Elif(din.vld & din.data._eq(ord('\f')), *set_out(HwIOHd44780.CMD_CLEAR_DISPLAY, HwIOHd44780.RS_CONTROL, HwIOHd44780.RW_WRITE, 1), *StreamNode([din], [dout]).sync(), ).Else( *set_out(din.data, HwIOHd44780.RS_DATA, HwIOHd44780.RW_WRITE, 0), *StreamNode([din], [dout]).sync(), ) return dout
[docs] def _io_core( self, data_in: HwIOHd44780Cmd, cmd_timer_rst, lcd_clk_en: RtlSignal, delay_cmd_half_done: RtlSignal, delay_cmd_done: RtlSignal, delay_cmd_long_done: RtlSignal): st_t = HEnum("st_t", [ "init_delay_after_powerup", "idle", "write", # cmd/data ]) is_long_cmd = self._reg("is_long_cmd") st = FsmBuilder(self, st_t, "st")\ .Trans(st_t.init_delay_after_powerup, (delay_cmd_long_done, st_t.idle))\ .Trans(st_t.idle, (data_in.vld, st_t.write))\ .Trans(st_t.write, ((is_long_cmd & delay_cmd_long_done) | (~is_long_cmd & delay_cmd_done), st_t.idle))\ .stateReg cmd_timer_rst(st._eq(st_t.idle)) data_in.rd(st._eq(st_t.write) & ((delay_cmd_done & ~is_long_cmd) | (delay_cmd_long_done & is_long_cmd))) data_out = self.dataOut data_out.d(data_in.d) data_out.rw(data_in.rw) data_out.rs(data_in.rs) en_in_first_half_period = self._reg("en_in_first_half_period", def_val=1) If(st._eq(st_t.idle), is_long_cmd(data_in.long_wait), en_in_first_half_period(1), ).Elif(en_in_first_half_period & delay_cmd_half_done, en_in_first_half_period(0), ) en_delay = self._reg("en_delay", def_val=0) If(lcd_clk_en, en_delay((st != st_t.idle) & en_in_first_half_period) ) data_out.en(en_delay)
[docs] @override def hwImpl(self): """ * read on 'en' faling edge, write on 'en' rising edge * 'en' max frequency = LCD_FREQ / 10 * rs has to be set at least 1 clk (LCD_FREQ) before rising edge of 'en' """ LCD_DW = self.LCD_DATA_WIDTH assert LCD_DW in (4, 8), LCD_DW cmd_timer_rst = self._sig("cmd_timer_rst") HALF_LCD_PERIOD = ceil(self.CLK_FREQ / (self.LCD_FREQ * 2)) LCD_CLK_PER = 2 * HALF_LCD_PERIOD lcd_clk_en, = ClkBuilder(self, self.clk).timers([ ("lcd_clk_en", LCD_CLK_PER), ]) delay_cmd_half_done, delay_cmd_done, delay_cmd_long_done =\ ClkBuilder(self, self.clk)\ .timers([ # used to signalize that the 'en' should be asserted low ("delay_cmd_half_done", HwIOHd44780.DELAY_CMD // 2), # used to signalize that the processing of command is completed ("delay_cmd_done", HwIOHd44780.DELAY_CMD), # used to signalize that the long command (return home, etc.) is completed ("delay_cmd_long_done", HwIOHd44780.DELAY_RETURN_HOME) ], enableSig=lcd_clk_en, rstSig=cmd_timer_rst ) data_in_tmp = HwIOHd44780Cmd() data_in_tmp.DATA_WIDTH = self.LCD_DATA_WIDTH self.data_in_tmp = data_in_tmp self._io_core(data_in_tmp, cmd_timer_rst, lcd_clk_en, delay_cmd_half_done, delay_cmd_done, delay_cmd_long_done) INIT_SEQUENCE = [ HwIOHd44780.CMD_FUNCTION_SET( HwIOHd44780.DATA_LEN_8b if LCD_DW == 8 else HwIOHd44780.DATA_LEN_4b, self.LCD_ROWS - 1, HwIOHd44780.FONT_5x8), HwIOHd44780.CMD_DISPLAY_CONTROL(1, 0, 0), HwIOHd44780.CMD_ENTRY_MODE_SET(1, 1), ] init_seq = Hd44780CmdBurst() init_seq.DATA_WIDTH = self.LCD_DATA_WIDTH init_seq.DATA = tuple( (HwIOHd44780.RS_CONTROL, HwIOHd44780.RW_WRITE, 0, d) for d in INIT_SEQUENCE ) self.init_seq = init_seq propagateClkRstn(self) data_in = self._translate_data_in(self.dataIn) data_in_tmp(HsBuilder.join_prioritized(self, [init_seq.dataOut, data_in]).end)
if __name__ == "__main__": from hwt.synth import to_rtl_str m = Hd44780Driver() print(to_rtl_str(m))