|
- /*!
- * \file drv/liquid_crystal.h
- * \brief
- * A liquid crystal display driver
- *
- * \copyright Copyright (C) 2021 Christos Choutouridis <christos@choutouridis.net>
- *
- * <dl class=\"section copyright\"><dt>License</dt><dd>
- * The MIT License (MIT)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- * </dd></dl>
- */
-
- #ifndef TBX_DRV_LIQUID_CRYSTAL_H_
- #define TBX_DRV_LIQUID_CRYSTAL_H_
-
- #include <core/core.h>
- #include <core/crtp.h>
- #include <utils/print.h>
-
- #include <type_traits>
- #include <ctime>
-
- namespace tbx {
-
- /*!
- * \class liquid_crystal
- * \brief
- * A CRTP driver class for liquid crystal display with parallel interface,
- * based on Hitachi HD44780 (Samsung KS0066U, or compatible).
- *
- * The driver inherits from Print
- * The Implementation requirements are:
- * - void bus_impl(data); To set the 4bit/8bit bus
- * - void rs_pin_impl(state); To set/clear RS pin
- * - void en_pin_impl(state); To set/clear EN pin
- * - void power_pin_impl(state); To set/clear PWR pin
- * - void bl_pin_impl(state); To set/clear BackLight pin
- * - void delay_usec_impl(usec); To provide delay in usec
- *
- * \tparam Impl_t The derived class
- * \tparam Lines The lines of the LCD
- * \tparam Columns The coluns of the LCD
- * \tparam BusSize The LCD bus size (4 or 8 bits)
- */
- template <typename Impl_t, size_t Lines, size_t Columns, size_t BusSize =4>
- class liquid_crystal : public Print<liquid_crystal<Impl_t, Lines, Columns, BusSize>, char>{
- friend Print<liquid_crystal, char>;
- _CRTP_IMPL(Impl_t);
- static_assert((BusSize == 4) || (BusSize == 8), "BusSize must be either 4 or 8.");
-
- public:
- constexpr static size_t lines = Lines;
- constexpr static size_t columns = Columns;
- /*!
- * Public enumerator to be used as argument to the init() function.
- * Selects the font size and thus the number of active lines
- */
- enum class FontSize :uint8_t { dots_5x8, dots_5x10 };
- private:
-
- // Commands
- constexpr static uint8_t Cmd_cls = 0x01;
- constexpr static uint8_t Cmd_RetHome = 0x02;
- constexpr static uint8_t Cmd_EntryMode = 0x04;
- constexpr static uint8_t Cmd_DispCtrl = 0x08;
- constexpr static uint8_t Cmd_Shift = 0x10;
- constexpr static uint8_t Cmd_FunSet = 0x20;
- constexpr static uint8_t Cmd_SetGRamAddr= 0x40;
- constexpr static uint8_t Cmd_SetDRamAddr= 0x80;
-
- /*
- * Entry Mode Set -----> 0 0 0 0 0 1 I/D S
- * ----------------------------------------------------
- * I/D = 1 Increment Curs
- * 0 Decrement
- * S = 1 Display shift
- * 0 Not
- */
- constexpr static uint8_t Entry_Right = 0x00;
- constexpr static uint8_t Entry_Left = 0x02;
- constexpr static uint8_t Entry_ShiftInc = 0x01;
- constexpr static uint8_t Entry_ShiftDec = 0x00;
-
- /*
- * DispOnOffControll --> 0 0 0 0 1 D C B
- * -------------------------------------------------
- * D = Display On
- * C = Cursor On
- * B = Blinking On
- */
- constexpr static uint8_t Display_On = 0x04;
- constexpr static uint8_t Display_Off = 0x00;
- constexpr static uint8_t Cursor_On = 0x02;
- constexpr static uint8_t Cursor_Off = 0x00;
- constexpr static uint8_t Blink_On = 0x01;
- constexpr static uint8_t Blink_Off = 0x00;
-
- /*
- * Cursor/Display Shift --> 0 0 0 1 S/C R/L x x
- * ---------------------------------------------------
- * S/C = 1 Display Shift
- * 0 Cursor Shift
- * R/L = 1 Shift Right
- * 0 Shift left
- */
- constexpr static uint8_t DisMove_Display= 0x08;
- constexpr static uint8_t DisMove_Cursor = 0x00;
- constexpr static uint8_t DisMove_Right = 0x04;
- constexpr static uint8_t DisMove_Left = 0x00;
-
- /*
- * FunctionSet ------> 0 0 1 DL N F x x
- * ---------------------------------------------------
- * DL = 1 8bit
- * 0 4bit
- * N = 1 2 lines
- * 0 1 line
- * F = 1 5x10 dots
- * 0 5x8 dots
- */
- constexpr static uint8_t FunSet_8bitMode= 0x10;
- constexpr static uint8_t FunSet_4bitMode= 0x00;
- constexpr static uint8_t FunSet_2Line = 0x08;
- constexpr static uint8_t FunSet_1Line = 0x00;
- constexpr static uint8_t FunSet_5x10dots= 0x04;
- constexpr static uint8_t FunSet_5x8dots = 0x00;
-
- /*!
- * \brief
- * Helper class to keep track of the display cursor. As we dont read the cursor
- * position on the display, in order to implement backspace operation we need to
- * keep track the cursor position manually.
- */
- struct Cursor {
- uint8_t inc_x() noexcept {
- if (++x_ > Columns) x_ =1;
- return x_;
- }
- uint8_t dec_x() noexcept {
- if (--x_ < 1) x_ =Columns;
- return x_;
- }
- uint8_t inc_y () noexcept {
- if (++y_ > max_lines) y_ =1;
- return y_;
- }
- uint8_t dec_y () noexcept {
- if (--y_ < 1) y_ =max_lines;
- return y_;
- }
- uint8_t operator++() noexcept { return inc_x(); }
- uint8_t operator--() noexcept { return dec_x(); }
- void set(uint8_t x, uint8_t y) noexcept {
- x_ = x;
- y_ = y;
- }
- uint8_t get_x() noexcept { return x_; }
- uint8_t get_y() noexcept { return y_; }
- void set_lines (uint8_t l) noexcept { max_lines = l; }
- private:
- uint8_t x_, y_;
- uint8_t max_lines;
- };
- private:
- //! \name Implementation requirements
- //! @{
- void BUS (uint8_t data) { impl().bus_impl(data); }
- void RS_Pin (bool state) { impl().rs_pin_impl(state); }
- void EN_Pin (bool state) { impl().en_pin_impl(state); }
- void PWR_Pin (bool state) { impl().power_pin_impl(state); }
- void BL_Pin (bool state) { impl().bl_pin_impl(state); }
- void delay_usec(size_t usec) {
- impl().delay_usec_impl(usec);
- }
- //! @}
-
- //! \name Print interface requirements
- //! @{
- size_t write_impl (const char* str, size_t size) {
- size_t ret =0;
- while (*str && ret < size) {
- putchar (*str++);
- ++ret;
- }
- return ret;
- }
- size_t write_impl (const char ch) {
- return (putchar(ch) == ch) ? 1:0;
- }
- //! @}
-
- protected:
- //! \name Object lifetime
- //! @{
- liquid_crystal() noexcept = default; //!< Construct from derived only
- //~liquid_crystal() = default;
- liquid_crystal(const liquid_crystal&) = delete; //!< No copies
- liquid_crystal& operator= (const liquid_crystal&) = delete; //!< No copies
- //! @}
-
- private:
- //! Send enable pulse to display
- void pulse_enable () {
- EN_Pin(0);
- delay_usec (2); // time to settle BUS pin voltages
- EN_Pin(1);
- delay_usec (2); // >450 [nsec]
- EN_Pin(0);
- delay_usec (50); // > 37 [usec]
- }
-
- //! Writes 4/8bit data to display and pulse the EN pin
- //! \param data The data to write
- void write_bits (uint8_t data) {
- if constexpr (BusSize == 4) BUS (data & 0x0F);
- else BUS (data);
- pulse_enable ();
- }
-
- /*!
- * \brief
- * Sends commands or character to display by controlling RS pin
- * \param data The data to send
- * \param mode Character/command mode (RS pin state)
- */
- void send (uint8_t data, uint8_t mode) {
- RS_Pin (mode);
- if constexpr (BusSize == 4) {
- write_bits (data >> 4);
- write_bits (data & 0x0F);
- } else {
- write_bits (data);
- }
- }
- //! Send a command to display
- void command (uint8_t c) { send(c, 0); }
- //! Send a character to display
- void character (uint8_t c) { send(c, 1); }
-
- public:
- //! \name Public API
- //! @{
-
- /*!
- * \brief
- * Initialize the display. After construction the object is valid but reflects the init state
- * of display configuration. In order for the display to be functional it needs initialization.
- * So the user has to call this function. This function requires a settled environment, so usually
- * its called after main().
- * \param mode 4bit or 8bit mode
- * \param fontSize 5x8 or 5x10 dots font size.
- */
- void init (FontSize fontSize =FontSize::dots_5x8) {
- disp_mode_ = disp_mode_init_; // Set values to LCD's startup configuration
- disp_control_ = disp_control_init_;
- disp_function_=disp_function_init_;
-
- // Read user configuration
- if constexpr (BusSize == 4)
- // note: keep this runtime, so the disp_function reflects lcd's configuration state
- disp_function_ &= ~FunSet_8bitMode;
- else
- disp_function_ |= FunSet_8bitMode;
-
- if (fontSize == FontSize::dots_5x10) {
- disp_function_ |= FunSet_5x10dots;
- disp_function_ &= ~FunSet_2Line;
- cursor_.set_lines(Lines>>1);
- } else {
- disp_function_ &= ~FunSet_5x10dots;
- disp_function_ |= FunSet_2Line;
- cursor_.set_lines(Lines);
- }
-
- // start with All-zeros
- BUS (0); EN_Pin(0); RS_Pin(0); BL_Pin(0);
-
- delay_usec(100000);
- if constexpr (BusSize == 4) {
- // 4bit BUS
- write_bits (0x03); // 1t try
- delay_usec(20000);
- write_bits (0x03); // 2nd try
- delay_usec(5000);
- write_bits (0x03); // 3rd try
- delay_usec(5000);
- write_bits (0x02); // We set 4 bit interface
- delay_usec (10000);
- } else {
- // 8bit BUS
- write_bits (Cmd_FunSet | disp_function_); // 1st try
- delay_usec(20000);
- write_bits (Cmd_FunSet | disp_function_); // 2nd try
- delay_usec(5000);
- write_bits (Cmd_FunSet | disp_function_); // 3rd try
- delay_usec(5000);
- }
- command (Cmd_FunSet | disp_function_); // Finally we set #lines and font size
- delay_usec(5000);
-
- command (Cmd_DispCtrl | Display_Off); // Display off
- delay_usec(5000);
- command (Cmd_cls); // Clear screen
- delay_usec(5000);
- command (Cmd_EntryMode | disp_mode_); // Entry mode set
- delay_usec(5000);
-
- command (Cmd_RetHome); // Return home
- delay_usec(10000);
- display(true); // Finally display On, done.
- cursor_.set(1, 1);
- }
-
- //! Utility function to enable/disable power to display. This has an effect IFF there is a
- //! power pin on the board
- void power (bool en) {
- PWR_Pin(en);
- }
-
- //! Utility function to enable/disable backlight. This has an effect IFF there is a
- //! backlight pin on the board
- void backlight (bool en) {
- BL_Pin(en);
- }
-
- //! Utility function to send on/off command to display.
- void display (bool en) {
- if (en) disp_control_ |= Display_On;
- else disp_control_ &= ~Display_On;
-
- command (Cmd_DispCtrl | disp_control_);
- delay_usec(100);
- }
-
- //! Utility function to enable/disable display cursor.
- void cursor (bool en) {
- if (en) disp_control_ |= Cursor_On;
- else disp_control_ &= ~Cursor_On;
-
- command (Cmd_DispCtrl | disp_control_);
- delay_usec(100);
- }
-
- //! Utility function to enable/disable cursor blinking.
- void blink (bool en) {
- if (en) disp_control_ |= Blink_On;
- else disp_control_ &= ~Blink_On;
-
- command (Cmd_DispCtrl | disp_control_);
- delay_usec(100);
- }
-
- //! Utility function to enable/disable autoscroll.
- void autoscroll (bool en) {
- if (en) disp_mode_ |= Entry_ShiftInc;
- else disp_mode_ &= ~Entry_ShiftInc;
-
- command (Cmd_EntryMode | disp_mode_);
- delay_usec(100);
- }
-
- /*!
- * \brief
- * Tool to set display cursor
- * \param x The column position (starting with 1)
- * \param y The line position (starting with 1 at the top of the display)
- */
- void set_cursor (uint8_t x, uint8_t y) {
- uint8_t offset;
- switch (y) {
- default:
- case 1: offset = 0x0; break;
- case 2: offset = 0x40; break;
- case 3: offset = 0x0 + Columns; break;
- case 4: offset = 0x40 + Columns; break;
- }
- command( Cmd_SetDRamAddr | offset | (x-1));
- cursor_.set(x, y);
- }
-
- //! Utility function to set left to right entry mode.
- //! \note
- //! This is the default
- void set_left_to_right () {
- disp_mode_ |= Entry_Left;
- command (Cmd_EntryMode | disp_mode_);
- delay_usec(100);
- }
-
- //! Utility function to set right to left entry mode.
- void set_right_to_left () {
- disp_mode_ &= ~Entry_Left;
- command (Cmd_EntryMode | disp_mode_);
- delay_usec(100);
- }
-
- //! Command to scroll display left one position
- void scroll_left () {
- command (Cmd_Shift | DisMove_Display | DisMove_Left);
- }
-
- //! Command to scroll display right one position
- void scroll_right () {
- command (Cmd_Shift | DisMove_Display | DisMove_Right);
- }
-
- //! Clears the display and return home
- void clear() {
- command (Cmd_cls);
- cursor_.set(1, 1);
- delay_usec(2000);
- }
-
- //! return home without clearing the display
- void home() {
- command (Cmd_RetHome);
- cursor_.set(1, 1);
- delay_usec(2000);
- }
-
- /*!
- * \brief
- * Create custom character and store it to LCD.
- * \param location The location to store the character [0..7] allowed
- * \param charmap The character map buffer with the font
- */
- void create_char (uint8_t location, uint8_t charmap[]) {
- location &= 0x7; // we only have 8 locations 0-7
- command(Cmd_SetGRamAddr | (location << 3));
- for (size_t i=0; i<8; ++i) {
- character(charmap[i]);
- }
- }
-
- /*!
- * \brief
- * Send an ascii character to liquid crystal display.
- * \param ch the character to send
- * \return the character send.
- *
- * \note
- * This is the driver's "putchar()" functionality to glue.
- * Tailor this function to redirect stdout to display.
- */
- int putchar (int ch) {
- // LCD Character dispatcher
- switch (ch) {
- case 0:
- // don't send null termination to device
- break;
- case '\n':
- cursor_.inc_y();
- set_cursor (1, cursor_.get_y());
- break;
- case '\r':
- set_cursor (1, cursor_.get_y());
- break;
- case '\v':
- home ();
- break;
- case '\f':
- clear();
- break;
- case '\b':
- --cursor_;
- set_cursor (cursor_.get_x(), cursor_.get_y());
- character (' ');
- --cursor_;
- set_cursor (cursor_.get_x(), cursor_.get_y());
- break;
- default:
- character (ch);
- ++cursor_;
- break;
- }
- //ANSI C (C99) compatible mode
- return ch;
- }
- //! @}
-
- private:
- //! \name Data members
- //! @{
-
- //! The init entry mode of the display after power up
- static constexpr uint8_t disp_mode_init_ = Entry_Left | Entry_ShiftDec;
- //! The init control mode of the display after power up
- static constexpr uint8_t disp_control_init_ = Display_Off | Cursor_Off | Blink_Off;
- //! The init function set of the display after power up
- static constexpr uint8_t disp_function_init_= FunSet_8bitMode | FunSet_1Line | FunSet_5x8dots;
-
- Cursor cursor_{};
- uint8_t disp_mode_ {disp_mode_init_};
- uint8_t disp_control_ {disp_control_init_};
- uint8_t disp_function_ {disp_function_init_};
- /*!
- * \note
- * When the display powers up, it is configured as follows:
- * 1. Display clear
- * 2. Function set: 0x10
- * DL = 1; 8-bit interface data
- * N = 0; 1-line display
- * F = 0; 5x8 dot character font
- * 3. Display on/off control: 0x00
- * D = 0; Display off
- * C = 0; Cursor off
- * B = 0; Blinking off
- * 4. Entry mode set: 0x02
- * I/D = 1; Increment by 1
- * S = 0; No shift
- */
-
- //! @}
- };
-
- }
-
- #endif /* TBX_DRV_LIQUID_CRYSTAL_H_ */
|