A C++ toolbox repo until the pair uTL/dTL arives
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

537 lines
20 KiB

  1. /*!
  2. * \file drv/liquid_crystal.h
  3. * \brief
  4. * A liquid crystal display driver
  5. *
  6. * \copyright Copyright (C) 2021 Christos Choutouridis <christos@choutouridis.net>
  7. *
  8. * <dl class=\"section copyright\"><dt>License</dt><dd>
  9. * The MIT License (MIT)
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in all
  19. * copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  27. * SOFTWARE.
  28. * </dd></dl>
  29. */
  30. #ifndef TBX_DRV_LIQUID_CRYSTAL_H_
  31. #define TBX_DRV_LIQUID_CRYSTAL_H_
  32. #include <core/core.h>
  33. #include <core/crtp.h>
  34. #include <utils/print.h>
  35. #include <type_traits>
  36. #include <ctime>
  37. namespace tbx {
  38. /*!
  39. * \class liquid_crystal
  40. * \brief
  41. * A CRTP driver class for liquid crystal display with parallel interface,
  42. * based on Hitachi HD44780 (Samsung KS0066U, or compatible).
  43. *
  44. * The driver inherits from Print
  45. * The Implementation requirements are:
  46. * - void bus_impl(data); To set the 4bit/8bit bus
  47. * - void rs_pin_impl(state); To set/clear RS pin
  48. * - void en_pin_impl(state); To set/clear EN pin
  49. * - void power_pin_impl(state); To set/clear PWR pin
  50. * - void bl_pin_impl(state); To set/clear BackLight pin
  51. * - void delay_usec_impl(usec); To provide delay in usec
  52. *
  53. * \tparam Impl_t The derived class
  54. * \tparam Lines The lines of the LCD
  55. * \tparam Columns The coluns of the LCD
  56. * \tparam BusSize The LCD bus size (4 or 8 bits)
  57. */
  58. template <typename Impl_t, size_t Lines, size_t Columns, size_t BusSize =4>
  59. class liquid_crystal : public Print<liquid_crystal<Impl_t, Lines, Columns, BusSize>, char>{
  60. friend Print<liquid_crystal, char>;
  61. _CRTP_IMPL(Impl_t);
  62. static_assert((BusSize == 4) || (BusSize == 8), "BusSize must be either 4 or 8.");
  63. public:
  64. constexpr static size_t lines = Lines;
  65. constexpr static size_t columns = Columns;
  66. /*!
  67. * Public enumerator to be used as argument to the init() function.
  68. * Selects the font size and thus the number of active lines
  69. */
  70. enum class FontSize :uint8_t { dots_5x8, dots_5x10 };
  71. private:
  72. // Commands
  73. constexpr static uint8_t Cmd_cls = 0x01;
  74. constexpr static uint8_t Cmd_RetHome = 0x02;
  75. constexpr static uint8_t Cmd_EntryMode = 0x04;
  76. constexpr static uint8_t Cmd_DispCtrl = 0x08;
  77. constexpr static uint8_t Cmd_Shift = 0x10;
  78. constexpr static uint8_t Cmd_FunSet = 0x20;
  79. constexpr static uint8_t Cmd_SetGRamAddr= 0x40;
  80. constexpr static uint8_t Cmd_SetDRamAddr= 0x80;
  81. /*
  82. * Entry Mode Set -----> 0 0 0 0 0 1 I/D S
  83. * ----------------------------------------------------
  84. * I/D = 1 Increment Curs
  85. * 0 Decrement
  86. * S = 1 Display shift
  87. * 0 Not
  88. */
  89. constexpr static uint8_t Entry_Right = 0x00;
  90. constexpr static uint8_t Entry_Left = 0x02;
  91. constexpr static uint8_t Entry_ShiftInc = 0x01;
  92. constexpr static uint8_t Entry_ShiftDec = 0x00;
  93. /*
  94. * DispOnOffControll --> 0 0 0 0 1 D C B
  95. * -------------------------------------------------
  96. * D = Display On
  97. * C = Cursor On
  98. * B = Blinking On
  99. */
  100. constexpr static uint8_t Display_On = 0x04;
  101. constexpr static uint8_t Display_Off = 0x00;
  102. constexpr static uint8_t Cursor_On = 0x02;
  103. constexpr static uint8_t Cursor_Off = 0x00;
  104. constexpr static uint8_t Blink_On = 0x01;
  105. constexpr static uint8_t Blink_Off = 0x00;
  106. /*
  107. * Cursor/Display Shift --> 0 0 0 1 S/C R/L x x
  108. * ---------------------------------------------------
  109. * S/C = 1 Display Shift
  110. * 0 Cursor Shift
  111. * R/L = 1 Shift Right
  112. * 0 Shift left
  113. */
  114. constexpr static uint8_t DisMove_Display= 0x08;
  115. constexpr static uint8_t DisMove_Cursor = 0x00;
  116. constexpr static uint8_t DisMove_Right = 0x04;
  117. constexpr static uint8_t DisMove_Left = 0x00;
  118. /*
  119. * FunctionSet ------> 0 0 1 DL N F x x
  120. * ---------------------------------------------------
  121. * DL = 1 8bit
  122. * 0 4bit
  123. * N = 1 2 lines
  124. * 0 1 line
  125. * F = 1 5x10 dots
  126. * 0 5x8 dots
  127. */
  128. constexpr static uint8_t FunSet_8bitMode= 0x10;
  129. constexpr static uint8_t FunSet_4bitMode= 0x00;
  130. constexpr static uint8_t FunSet_2Line = 0x08;
  131. constexpr static uint8_t FunSet_1Line = 0x00;
  132. constexpr static uint8_t FunSet_5x10dots= 0x04;
  133. constexpr static uint8_t FunSet_5x8dots = 0x00;
  134. /*!
  135. * \brief
  136. * Helper class to keep track of the display cursor. As we dont read the cursor
  137. * position on the display, in order to implement backspace operation we need to
  138. * keep track the cursor position manually.
  139. */
  140. struct Cursor {
  141. uint8_t inc_x() noexcept {
  142. if (++x_ > Columns) x_ =1;
  143. return x_;
  144. }
  145. uint8_t dec_x() noexcept {
  146. if (--x_ < 1) x_ =Columns;
  147. return x_;
  148. }
  149. uint8_t inc_y () noexcept {
  150. if (++y_ > max_lines) y_ =1;
  151. return y_;
  152. }
  153. uint8_t dec_y () noexcept {
  154. if (--y_ < 1) y_ =max_lines;
  155. return y_;
  156. }
  157. uint8_t operator++() noexcept { return inc_x(); }
  158. uint8_t operator--() noexcept { return dec_x(); }
  159. void set(uint8_t x, uint8_t y) noexcept {
  160. x_ = x;
  161. y_ = y;
  162. }
  163. uint8_t get_x() noexcept { return x_; }
  164. uint8_t get_y() noexcept { return y_; }
  165. void set_lines (uint8_t l) noexcept { max_lines = l; }
  166. private:
  167. uint8_t x_, y_;
  168. uint8_t max_lines;
  169. };
  170. private:
  171. //! \name Implementation requirements
  172. //! @{
  173. void BUS (uint8_t data) { impl().bus_impl(data); }
  174. void RS_Pin (bool state) { impl().rs_pin_impl(state); }
  175. void EN_Pin (bool state) { impl().en_pin_impl(state); }
  176. void PWR_Pin (bool state) { impl().power_pin_impl(state); }
  177. void BL_Pin (bool state) { impl().bl_pin_impl(state); }
  178. void delay_usec(size_t usec) {
  179. impl().delay_usec_impl(usec);
  180. }
  181. //! @}
  182. //! \name Print interface requirements
  183. //! @{
  184. size_t write_impl (const char* str, size_t size) {
  185. size_t ret =0;
  186. while (*str && ret < size) {
  187. putchar (*str++);
  188. ++ret;
  189. }
  190. return ret;
  191. }
  192. size_t write_impl (const char ch) {
  193. return (putchar(ch) == ch) ? 1:0;
  194. }
  195. //! @}
  196. protected:
  197. //! \name Object lifetime
  198. //! @{
  199. liquid_crystal() noexcept = default; //!< Construct from derived only
  200. //~liquid_crystal() = default;
  201. liquid_crystal(const liquid_crystal&) = delete; //!< No copies
  202. liquid_crystal& operator= (const liquid_crystal&) = delete; //!< No copies
  203. //! @}
  204. private:
  205. //! Send enable pulse to display
  206. void pulse_enable () {
  207. EN_Pin(0);
  208. delay_usec (2); // time to settle BUS pin voltages
  209. EN_Pin(1);
  210. delay_usec (2); // >450 [nsec]
  211. EN_Pin(0);
  212. delay_usec (50); // > 37 [usec]
  213. }
  214. //! Writes 4/8bit data to display and pulse the EN pin
  215. //! \param data The data to write
  216. void write_bits (uint8_t data) {
  217. if constexpr (BusSize == 4) BUS (data & 0x0F);
  218. else BUS (data);
  219. pulse_enable ();
  220. }
  221. /*!
  222. * \brief
  223. * Sends commands or character to display by controlling RS pin
  224. * \param data The data to send
  225. * \param mode Character/command mode (RS pin state)
  226. */
  227. void send (uint8_t data, uint8_t mode) {
  228. RS_Pin (mode);
  229. if constexpr (BusSize == 4) {
  230. write_bits (data >> 4);
  231. write_bits (data & 0x0F);
  232. } else {
  233. write_bits (data);
  234. }
  235. }
  236. //! Send a command to display
  237. void command (uint8_t c) { send(c, 0); }
  238. //! Send a character to display
  239. void character (uint8_t c) { send(c, 1); }
  240. public:
  241. //! \name Public API
  242. //! @{
  243. /*!
  244. * \brief
  245. * Initialize the display. After construction the object is valid but reflects the init state
  246. * of display configuration. In order for the display to be functional it needs initialization.
  247. * So the user has to call this function. This function requires a settled environment, so usually
  248. * its called after main().
  249. * \param mode 4bit or 8bit mode
  250. * \param fontSize 5x8 or 5x10 dots font size.
  251. */
  252. void init (FontSize fontSize =FontSize::dots_5x8) {
  253. disp_mode_ = disp_mode_init_; // Set values to LCD's startup configuration
  254. disp_control_ = disp_control_init_;
  255. disp_function_=disp_function_init_;
  256. // Read user configuration
  257. if constexpr (BusSize == 4)
  258. // note: keep this runtime, so the disp_function reflects lcd's configuration state
  259. disp_function_ &= ~FunSet_8bitMode;
  260. else
  261. disp_function_ |= FunSet_8bitMode;
  262. if (fontSize == FontSize::dots_5x10) {
  263. disp_function_ |= FunSet_5x10dots;
  264. disp_function_ &= ~FunSet_2Line;
  265. cursor_.set_lines(Lines>>1);
  266. } else {
  267. disp_function_ &= ~FunSet_5x10dots;
  268. disp_function_ |= FunSet_2Line;
  269. cursor_.set_lines(Lines);
  270. }
  271. // start with All-zeros
  272. BUS (0); EN_Pin(0); RS_Pin(0); BL_Pin(0);
  273. delay_usec(100000);
  274. if constexpr (BusSize == 4) {
  275. // 4bit BUS
  276. write_bits (0x03); // 1t try
  277. delay_usec(20000);
  278. write_bits (0x03); // 2nd try
  279. delay_usec(5000);
  280. write_bits (0x03); // 3rd try
  281. delay_usec(5000);
  282. write_bits (0x02); // We set 4 bit interface
  283. delay_usec (10000);
  284. } else {
  285. // 8bit BUS
  286. write_bits (Cmd_FunSet | disp_function_); // 1st try
  287. delay_usec(20000);
  288. write_bits (Cmd_FunSet | disp_function_); // 2nd try
  289. delay_usec(5000);
  290. write_bits (Cmd_FunSet | disp_function_); // 3rd try
  291. delay_usec(5000);
  292. }
  293. command (Cmd_FunSet | disp_function_); // Finally we set #lines and font size
  294. delay_usec(5000);
  295. command (Cmd_DispCtrl | Display_Off); // Display off
  296. delay_usec(5000);
  297. command (Cmd_cls); // Clear screen
  298. delay_usec(5000);
  299. command (Cmd_EntryMode | disp_mode_); // Entry mode set
  300. delay_usec(5000);
  301. command (Cmd_RetHome); // Return home
  302. delay_usec(10000);
  303. display(true); // Finally display On, done.
  304. cursor_.set(1, 1);
  305. }
  306. //! Utility function to enable/disable power to display. This has an effect IFF there is a
  307. //! power pin on the board
  308. void power (bool en) {
  309. PWR_Pin(en);
  310. }
  311. //! Utility function to enable/disable backlight. This has an effect IFF there is a
  312. //! backlight pin on the board
  313. void backlight (bool en) {
  314. BL_Pin(en);
  315. }
  316. //! Utility function to send on/off command to display.
  317. void display (bool en) {
  318. if (en) disp_control_ |= Display_On;
  319. else disp_control_ &= ~Display_On;
  320. command (Cmd_DispCtrl | disp_control_);
  321. delay_usec(100);
  322. }
  323. //! Utility function to enable/disable display cursor.
  324. void cursor (bool en) {
  325. if (en) disp_control_ |= Cursor_On;
  326. else disp_control_ &= ~Cursor_On;
  327. command (Cmd_DispCtrl | disp_control_);
  328. delay_usec(100);
  329. }
  330. //! Utility function to enable/disable cursor blinking.
  331. void blink (bool en) {
  332. if (en) disp_control_ |= Blink_On;
  333. else disp_control_ &= ~Blink_On;
  334. command (Cmd_DispCtrl | disp_control_);
  335. delay_usec(100);
  336. }
  337. //! Utility function to enable/disable autoscroll.
  338. void autoscroll (bool en) {
  339. if (en) disp_mode_ |= Entry_ShiftInc;
  340. else disp_mode_ &= ~Entry_ShiftInc;
  341. command (Cmd_EntryMode | disp_mode_);
  342. delay_usec(100);
  343. }
  344. /*!
  345. * \brief
  346. * Tool to set display cursor
  347. * \param x The column position (starting with 1)
  348. * \param y The line position (starting with 1 at the top of the display)
  349. */
  350. void set_cursor (uint8_t x, uint8_t y) {
  351. uint8_t offset;
  352. switch (y) {
  353. default:
  354. case 1: offset = 0x0; break;
  355. case 2: offset = 0x40; break;
  356. case 3: offset = 0x0 + Columns; break;
  357. case 4: offset = 0x40 + Columns; break;
  358. }
  359. command( Cmd_SetDRamAddr | offset | (x-1));
  360. cursor_.set(x, y);
  361. }
  362. //! Utility function to set left to right entry mode.
  363. //! \note
  364. //! This is the default
  365. void set_left_to_right () {
  366. disp_mode_ |= Entry_Left;
  367. command (Cmd_EntryMode | disp_mode_);
  368. delay_usec(100);
  369. }
  370. //! Utility function to set right to left entry mode.
  371. void set_right_to_left () {
  372. disp_mode_ &= ~Entry_Left;
  373. command (Cmd_EntryMode | disp_mode_);
  374. delay_usec(100);
  375. }
  376. //! Command to scroll display left one position
  377. void scroll_left () {
  378. command (Cmd_Shift | DisMove_Display | DisMove_Left);
  379. }
  380. //! Command to scroll display right one position
  381. void scroll_right () {
  382. command (Cmd_Shift | DisMove_Display | DisMove_Right);
  383. }
  384. //! Clears the display and return home
  385. void clear() {
  386. command (Cmd_cls);
  387. cursor_.set(1, 1);
  388. delay_usec(2000);
  389. }
  390. //! return home without clearing the display
  391. void home() {
  392. command (Cmd_RetHome);
  393. cursor_.set(1, 1);
  394. delay_usec(2000);
  395. }
  396. /*!
  397. * \brief
  398. * Create custom character and store it to LCD.
  399. * \param location The location to store the character [0..7] allowed
  400. * \param charmap The character map buffer with the font
  401. */
  402. void create_char (uint8_t location, uint8_t charmap[]) {
  403. location &= 0x7; // we only have 8 locations 0-7
  404. command(Cmd_SetGRamAddr | (location << 3));
  405. for (size_t i=0; i<8; ++i) {
  406. character(charmap[i]);
  407. }
  408. }
  409. /*!
  410. * \brief
  411. * Send an ascii character to liquid crystal display.
  412. * \param ch the character to send
  413. * \return the character send.
  414. *
  415. * \note
  416. * This is the driver's "putchar()" functionality to glue.
  417. * Tailor this function to redirect stdout to display.
  418. */
  419. int putchar (int ch) {
  420. // LCD Character dispatcher
  421. switch (ch) {
  422. case 0:
  423. // don't send null termination to device
  424. break;
  425. case '\n':
  426. cursor_.inc_y();
  427. set_cursor (1, cursor_.get_y());
  428. break;
  429. case '\r':
  430. set_cursor (1, cursor_.get_y());
  431. break;
  432. case '\v':
  433. home ();
  434. break;
  435. case '\f':
  436. clear();
  437. break;
  438. case '\b':
  439. --cursor_;
  440. set_cursor (cursor_.get_x(), cursor_.get_y());
  441. character (' ');
  442. --cursor_;
  443. set_cursor (cursor_.get_x(), cursor_.get_y());
  444. break;
  445. default:
  446. character (ch);
  447. ++cursor_;
  448. break;
  449. }
  450. //ANSI C (C99) compatible mode
  451. return ch;
  452. }
  453. //! @}
  454. private:
  455. //! \name Data members
  456. //! @{
  457. //! The init entry mode of the display after power up
  458. static constexpr uint8_t disp_mode_init_ = Entry_Left | Entry_ShiftDec;
  459. //! The init control mode of the display after power up
  460. static constexpr uint8_t disp_control_init_ = Display_Off | Cursor_Off | Blink_Off;
  461. //! The init function set of the display after power up
  462. static constexpr uint8_t disp_function_init_= FunSet_8bitMode | FunSet_1Line | FunSet_5x8dots;
  463. Cursor cursor_{};
  464. uint8_t disp_mode_ {disp_mode_init_};
  465. uint8_t disp_control_ {disp_control_init_};
  466. uint8_t disp_function_ {disp_function_init_};
  467. /*!
  468. * \note
  469. * When the display powers up, it is configured as follows:
  470. * 1. Display clear
  471. * 2. Function set: 0x10
  472. * DL = 1; 8-bit interface data
  473. * N = 0; 1-line display
  474. * F = 0; 5x8 dot character font
  475. * 3. Display on/off control: 0x00
  476. * D = 0; Display off
  477. * C = 0; Cursor off
  478. * B = 0; Blinking off
  479. * 4. Entry mode set: 0x02
  480. * I/D = 1; Increment by 1
  481. * S = 0; No shift
  482. */
  483. //! @}
  484. };
  485. }
  486. #endif /* TBX_DRV_LIQUID_CRYSTAL_H_ */