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.
 
 
 

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