note description: "[ Hitachi HD44780 controller interface to modifying VFD's and LCD's ]" legal : "See notice at end of class." status : "See notice at end of class."; author : "Paul Bates (paul.a.bates@gmail.com)" date : "$Date$" revision: "$Revision$" class HD44780_CONTROLLER inherit SAFE_DISPOSABLE export {NONE} is_zombie redefine is_interface_usable end inherit {NONE} SHARED_HD44780_OP_CODES export {NONE} all end create make feature {NONE} -- Initialization make (a_config: like display_config; a_channel: like channel) -- Initializes a HD44780 controller. -- -- `a_config': A display configuration. -- `a_channel': A communication channel to send data through. require a_config_attached: attached a_config a_channel_attached: attached a_channel do display_config := a_config channel := a_channel initialize_defaults ensure display_config_set: display_config = a_config channel_set: channel = a_channel end initialize_defaults -- Initialize default values for the controller. do font := {HD44780_FONT}.font_5x7 buffer_write_mode := {HD44780_BUFFER_SELECT_MODE}.text_data end feature -- Initialization init -- Initialize controller ready for use. This must be called before using the controller. require is_interface_usable: is_interface_usable not_is_initialized: not is_initialized local l_data_length: NATURAL_8 l_font: NATURAL_8 l_lines: NATURAL_8 do -- Set function parameters if channel.is_4bit_channel then l_data_length := {HD44780_OP_CODES}.function_data_length_4bit else l_data_length := {HD44780_OP_CODES}.function_data_length_8bit end inspect font.item when {HD44780_FONT}.font_5x7 then l_font := {HD44780_OP_CODES}.function_font_5x7 when {HD44780_FONT}.font_5x10 then l_font := {HD44780_OP_CODES}.function_font_5x10 end if lines > 1 then l_lines := {HD44780_OP_CODES}.function_lines_multi else l_lines := {HD44780_OP_CODES}.function_lines_single end load_instruction (op_codes.function_set_op (l_data_length, l_lines, l_font)) is_initialized := True -- Update the display/cursor shift parameters update_shift_parameters -- Reset the display, and turn on, which updates the display parameters reset_display -- Setting false to ensure the display parameters are updated. is_shown := False set_is_shown (True) ensure is_initialized: is_initialized end feature {NONE} -- Clean Up safe_dispose (a_disposing: BOOLEAN) -- do set_is_shown (False) set_is_cursor_shown (False) set_is_cursor_blinking_enabled (False) reset_display is_initialized := False ensure then not_is_initialized: not is_initialized end feature -- Access display_config: HD44780_DISPLAY_CONFIG -- The HD44780 display line and column configuration. font: HD44780_FONT -- The HD44780 display font size. feature {HD44780_CONTROLLER, HD44780_CHARACTER_GENERATOR} -- Access buffer_write_mode: HD44780_BUFFER_SELECT_MODE assign set_buffer_write_mode -- The controller's write mode for text and character write modes. -- Text data mode will be sending text to controller for visual output where as character data mode -- will send data to controller's character generator RAM, for manipulating display character bitmaps. feature {NONE} -- Access channel: HD44780_COMM_CHANNEL_I [PRT_CONNECTION] -- The communication channel to use to send display data through. feature {HD44780_CONTROLLER, HD44780_CHARACTER_GENERATOR} -- Element change set_buffer_write_mode (a_mode: like buffer_write_mode) -- Set controllers write buffer mode. -- -- `a_mode': Mode to change controller input to. require is_interface_usable: is_interface_usable local l_old_mode: like buffer_write_mode do l_old_mode := buffer_write_mode buffer_write_mode := a_mode if l_old_mode /= a_mode and a_mode = {HD44780_BUFFER_SELECT_MODE}.text_data then -- Reset DDRAM address to last known set_cursor_position (cursor.column, cursor.line) end ensure buffer_write_mode_set: buffer_write_mode = a_mode end feature -- Measurement lines: NATURAL_8 -- Maximum number of display lines. do Result := display_config.lines end columns: NATURAL_8 -- Maximum number of display columns. do Result := display_config.columns end feature -- Position cursor: HD44780_CURSOR -- Controller's display cursor position do if attached internal_cursor as l_result then Result := l_result else create Result.make (Current) internal_cursor := Result end ensure result_attached: attached Result result_consistent: Result = cursor end feature -- Position setting set_cursor_position (a_column: NATURAL_8; a_line: NATURAL_8) -- Sets the display's cursor to a given position. -- -- `a_column': Column position to set on display cursor to. -- `a_line': Line position to set display cursor to. require is_interface_usable: is_interface_usable a_column_big_enough: a_column > 0 a_column_small_enough: a_column <= columns a_line_big_enough: a_line > 0 a_line_small_enough: a_line <= lines local l_column: like a_column l_address: NATURAL_8 l_instruction: NATURAL_8 do check buffer_write_text_data_mode: buffer_write_mode = {HD44780_BUFFER_SELECT_MODE}.text_data end l_column := a_column l_address := line_ddram_offset (a_line) + (l_column - 1) l_instruction := op_codes.set_ddram_address_op (l_address) load_instruction (l_instruction) cursor.set_column (a_column) cursor.set_line (a_line) ensure cursor_line_set: cursor.line = a_line cursor_column_set: cursor.column = a_column end feature -- Status report is_initialized: BOOLEAN -- Indicates if the controller has been correctly initialized. is_shown: BOOLEAN assign set_is_shown -- Indicates if the display is currently shown (on). is_cursor_shown: BOOLEAN assign set_is_cursor_shown -- Indicates if controller's cursor is visible on the display. is_cursor_blinking_enabled: BOOLEAN assign set_is_cursor_blinking_enabled -- Indicates if controller's blinking cursor is enabled on the display. is_display_shift_enabled: BOOLEAN assign set_is_display_shift_enabled -- Indicates if the display will be shifted as a result of any put operation. is_interface_usable: BOOLEAN -- do Result := Precursor and then channel.is_writable ensure then channel_is_writable: Result implies channel.is_writable end feature -- Status setting set_is_shown (a_show: BOOLEAN) -- Will turn the display on or off based on supplied show state. -- -- `a_show': True to turn on the display; False to turn off. require is_interface_usable: is_interface_usable do if is_shown /= a_show then is_shown := a_show if is_initialized then update_display_parameters end end ensure is_shown_set: is_shown = a_show end set_is_cursor_shown (a_show: BOOLEAN) -- Will turn the display's cursor on or off based on supplied show state. -- -- `a_show': True to turn on the display cursor; False to turn off. require is_interface_usable: is_interface_usable do if is_cursor_shown /= a_show then is_cursor_shown := a_show if is_initialized then update_display_parameters end end ensure is_cursor_shown_set: is_cursor_shown = a_show end set_is_cursor_blinking_enabled (a_enable: BOOLEAN) -- Will enable a blinking or solid cursor based on supplied show state. -- -- `a_enable': True to enable a blinking cursor; False to display as a solid cursor. require is_interface_usable: is_interface_usable do if is_cursor_blinking_enabled /= a_enable then is_cursor_blinking_enabled := a_enable if is_initialized then update_display_parameters end end ensure is_cursor_blinking_enabled_set: is_cursor_blinking_enabled = a_enable end set_is_display_shift_enabled (a_enable: BOOLEAN) -- Will enable display (aposed to cursor) shifting when writing data to the display. -- Note: You should set the cursor to the last column of the display else the shifting -- will immediately shift the displayed character off-screen. -- -- `a_enable': True to enable display shifting; False to use the default cursor shifting. require is_interface_usable: is_interface_usable do if is_display_shift_enabled /= a_enable then is_display_shift_enabled := a_enable if is_initialized then update_shift_parameters end end ensure is_display_shift_enabled_set: is_display_shift_enabled = a_enable end feature -- Helpers character_generator: HD44780_CHARACTER_GENERATOR -- The controller's character generator for manipulating characters data in Character Generator -- RAM (CGRAM). require is_interface_usable: is_interface_usable is_initialize: is_initialized do if attached internal_character_generator as l_result then Result := l_result else create Result.make (Current) internal_character_generator := Result end ensure result_attached: attached Result result_consistent: Result = character_generator end feature -- Basic Operations reset_display -- Clears display and resets cursor to it's home position. require is_interface_usable: is_interface_usable do load_instruction ({HD44780_OP_CODES}.clear_display_op) set_cursor_position (1, 1) end feature {NONE} -- Basic operations update_display_parameters -- Updates display parameters, reflecting the current controllers state. require is_interface_usable: is_interface_usable is_initialized: is_initialized local l_display: NATURAL_8 l_cursor: NATURAL_8 l_blink: NATURAL_8 do -- Set display's visible state. if is_shown then l_display := {HD44780_OP_CODES}.display_on else l_display := {HD44780_OP_CODES}.display_off end -- Set basic ('_') cursor. if is_cursor_shown then l_cursor := {HD44780_OP_CODES}.display_cursor_on -- Set blinking block cursor. if is_cursor_blinking_enabled then l_blink := {HD44780_OP_CODES}.display_blink_cursor_on else l_blink := {HD44780_OP_CODES}.display_blink_cursor_off end else l_cursor := {HD44780_OP_CODES}.display_cursor_off end -- Perform update. load_instruction (op_codes.display_control_op (l_display, l_cursor, l_blink)) end update_shift_parameters -- Updates display/cursor shifting parameters, reflecting the current controllers state. require is_interface_usable: is_interface_usable is_initialized: is_initialized do if is_display_shift_enabled then load_instruction (op_codes.entry_mode_op ({HD44780_OP_CODES}.entry_shift_display, {HD44780_OP_CODES}.entry_move_increment_cursor)) load_instruction (op_codes.cursor_display_shift_op ({HD44780_OP_CODES}.cursor_display_shift_display, {HD44780_OP_CODES}.cursor_display_shift_right)) else load_instruction (op_codes.entry_mode_op ({HD44780_OP_CODES}.entry_shift_none, {HD44780_OP_CODES}.entry_move_increment_cursor)) load_instruction (op_codes.cursor_display_shift_op ({HD44780_OP_CODES}.cursor_display_shift_cursor, {HD44780_OP_CODES}.cursor_display_shift_right)) end end feature -- Output put_data (a_data: NATURAL_8) -- Sends a raw character data value to the controller for display. -- Note: The cursor is moved by one column after write has been completed. Orientation of -- `cursor' movement is based on `shift_mode'. -- -- `a_data': The data character to be sent to display. require is_interface_usable: is_interface_usable is_initialized: is_initialized not_cursor_is_off: not is_display_shift_enabled implies not cursor.is_off do check buffer_write_text_data_mode: buffer_write_mode = {HD44780_BUFFER_SELECT_MODE}.text_data end load_data (a_data) if not is_display_shift_enabled then cursor.column_forth end ensure cursor_moved: not is_display_shift_enabled implies cursor.column = old cursor.column + 1 end put_character (a_char: CHARACTER) -- Sends a character to the controller for display. -- Note: The cursor is moved by one column after write has been completed. Orientation of -- `cursor' movement is based on `shift_mode'. -- -- `a_char': Character to be sent to display. require is_interface_usable: is_interface_usable is_initialized: is_initialized a_char_not_null: a_char /= '%U' not_cursor_is_off: not is_display_shift_enabled implies not cursor.is_off do check buffer_write_text_data_mode: buffer_write_mode = {HD44780_BUFFER_SELECT_MODE}.text_data end put_data (a_char.code.to_natural_8) ensure cursor_moved: not is_display_shift_enabled implies cursor.column = old cursor.column + 1 end put_string (a_string: READABLE_STRING_8) -- Sends a string to the controller for display. -- Note: The cursor is moved by one column after write has been completed. Orientation of -- `cursor' movement is based on `shift_mode'. -- -- `a_string': String to be send to display. require is_interface_usable: is_interface_usable is_initialized: is_initialized a_string_attached: attached a_string not_a_string_is_empty: not a_string.is_empty not_cursor_is_off: not is_display_shift_enabled implies not cursor.is_off a_string_small_enough: not is_display_shift_enabled implies a_string.count <= columns enough_line_space: not is_display_shift_enabled implies (cursor.column - 1) <= (columns - a_string.count) local l_cursor: like cursor l_count, i: INTEGER do check buffer_write_text_data_mode: buffer_write_mode = {HD44780_BUFFER_SELECT_MODE}.text_data end l_count := a_string.count.min (columns) from i := 1 until i > l_count loop load_data (a_string.item (i).code.to_natural_8) i := i + 1 end if not is_display_shift_enabled then l_cursor := cursor if l_cursor.column + l_count < columns then l_cursor.set_column ((l_cursor.column + l_count).to_natural_8) else l_cursor.set_column (columns) end end ensure cursor_moved: not is_display_shift_enabled implies ((old cursor.column + a_string.count) < columns implies cursor.column = (old cursor.column + a_string.count)) or cursor.column = columns end feature {HD44780_CONTROLLER_OPERATOR} -- Communication load_instruction (a_instruction: NATURAL_8) -- Loads a command instruction up to the display. -- -- `a_instruction': An instruction bit code to load on display. require is_interface_usable: is_interface_usable local l_channel: like channel do l_channel := channel l_channel.begin_transaction (True) l_channel.send (a_instruction) l_channel.complete_transaction (True) end load_data (a_data: NATURAL_8) -- Loads data up to the display. -- -- `a_data': The data to be loaded onto display. require is_interface_usable: is_interface_usable local l_channel: like channel do l_channel := channel l_channel.begin_transaction (False) l_channel.send (a_data) l_channel.complete_transaction (False) end feature {HD44780_CONTROLLER_OPERATOR} -- Query line_ddram_offset (a_line: like lines): NATURAL_8 -- Returns DDRAM offset based on a line number. -- -- `a_line': Line to retrieve the DDRAM offset for. -- `Result': The DDRAM offset. require a_line_big_enough: a_line > 0 a_line_small_enough: a_line <= lines do inspect a_line when 1 then Result := 0 when 2 then Result := 64 when 3 then Result := 20 when 4 then Result := 84 else -- All lines should be accounted for so if execution ends up -- here either; the controller can handle more lines or we've -- messed up. check False end Result := 0 end ensure result_is_ddram_address: (Result & {HD44780_OP_CODES}.ddram_address_mask) = Result end feature {NONE} -- Implementation: Internal cache internal_cursor: detachable like cursor -- Cached version of `cursor' -- Note: Do not use directly! note options: stable attribute end internal_character_generator: detachable like character_generator -- Cached version of `character_generator' -- Note: Do not use directly! note options: stable attribute end invariant channel_attached: attached channel columns_big_enough: columns > 0 lines_big_enough: lines > 0 channel_is_writable: is_interface_usable implies channel.is_writable end