Display_SH1106_I2C.cpp 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. /*
  2. This program is free software: you can redistribute it and/or modify
  3. it under the terms of the GNU General Public License as published by
  4. the Free Software Foundation, either version 3 of the License, or
  5. (at your option) any later version.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  9. GNU General Public License for more details.
  10. You should have received a copy of the GNU General Public License
  11. along with this program. If not, see <http://www.gnu.org/licenses/>.
  12. */
  13. #include "Display_SH1106_I2C.h"
  14. #include <utility>
  15. #include <AP_HAL/AP_HAL.h>
  16. #include <AP_HAL/I2CDevice.h>
  17. // constructor
  18. Display_SH1106_I2C::Display_SH1106_I2C(AP_HAL::OwnPtr<AP_HAL::Device> dev) :
  19. _dev(std::move(dev))
  20. {
  21. }
  22. Display_SH1106_I2C::~Display_SH1106_I2C()
  23. {
  24. }
  25. Display_SH1106_I2C *Display_SH1106_I2C::probe(AP_HAL::OwnPtr<AP_HAL::Device> dev)
  26. {
  27. Display_SH1106_I2C *driver = new Display_SH1106_I2C(std::move(dev));
  28. if (!driver || !driver->hw_init()) {
  29. delete driver;
  30. return nullptr;
  31. }
  32. return driver;
  33. }
  34. bool Display_SH1106_I2C::hw_init()
  35. {
  36. struct PACKED {
  37. uint8_t reg;
  38. uint8_t seq[26];
  39. } init_seq = { 0x0, {
  40. 0xAE, // Display OFF
  41. 0xA1, // Segment re-map
  42. 0xC8, // COM Output Scan Direction
  43. 0xA8, 0x3F, // MUX Ratio
  44. 0xD5, 0x50, // Display Clock Divide Ratio and Oscillator Frequency: (== +0%)
  45. 0xD3, 0x00, // Display Offset
  46. 0xDB, 0x40, // VCOMH Deselect Level
  47. 0x81, 0xCF, // Contrast Control
  48. 0xAD, 0x8B, // DC-DC Control Mode: 1b (== internal DC-DC enabled) (AKA: Enable charge pump regulator)
  49. 0x40, // Display Start Line
  50. 0xDA, 0x12, // +++ COM Pins hardware configuration
  51. 0xD9, 0xF1, // +++ Pre-charge Period
  52. 0xA4, // +++ Entire Display ON (ignoring RAM): 0b (== OFF)
  53. 0xA6, // +++ Normal/Inverse Display: 0b (== Normal)
  54. 0xAF, // Display ON
  55. 0xB0, // Page Address
  56. 0x02, 0x10 // Column Address
  57. } };
  58. memset(_displaybuffer, 0, SH1106_COLUMNS * SH1106_ROWS_PER_PAGE);
  59. // take i2c bus semaphore
  60. if (!_dev || !_dev->get_semaphore()->take(HAL_SEMAPHORE_BLOCK_FOREVER)) {
  61. return false;
  62. }
  63. // init display
  64. bool success = _dev->transfer((uint8_t *)&init_seq, sizeof(init_seq), nullptr, 0);
  65. // give back i2c semaphore
  66. _dev->get_semaphore()->give();
  67. if (success) {
  68. _need_hw_update = true;
  69. _dev->register_periodic_callback(20000, FUNCTOR_BIND_MEMBER(&Display_SH1106_I2C::_timer, void));
  70. }
  71. return success;
  72. }
  73. void Display_SH1106_I2C::hw_update()
  74. {
  75. _need_hw_update = true;
  76. }
  77. void Display_SH1106_I2C::_timer()
  78. {
  79. if (!_need_hw_update) {
  80. return;
  81. }
  82. _need_hw_update = false;
  83. struct PACKED {
  84. uint8_t reg;
  85. uint8_t column_0_3;
  86. uint8_t column_4_7;
  87. uint8_t page;
  88. } command = { 0x0, 0x2, 0x10, 0xB0 };
  89. struct PACKED {
  90. uint8_t reg;
  91. uint8_t db[SH1106_COLUMNS/2];
  92. } display_buffer = { 0x40, {} };
  93. // write buffer to display
  94. for (uint8_t i = 0; i < (SH1106_ROWS / SH1106_ROWS_PER_PAGE); i++) {
  95. command.page = 0xB0 | (i & 0x0F);
  96. _dev->transfer((uint8_t *)&command, sizeof(command), nullptr, 0);
  97. memcpy(&display_buffer.db[0], &_displaybuffer[i * SH1106_COLUMNS], SH1106_COLUMNS/2);
  98. _dev->transfer((uint8_t *)&display_buffer, SH1106_COLUMNS/2 + 1, nullptr, 0);
  99. memcpy(&display_buffer.db[0], &_displaybuffer[i * SH1106_COLUMNS + SH1106_COLUMNS/2 ], SH1106_COLUMNS/2);
  100. _dev->transfer((uint8_t *)&display_buffer, SH1106_COLUMNS/2 + 1, nullptr, 0);
  101. }
  102. }
  103. void Display_SH1106_I2C::set_pixel(uint16_t x, uint16_t y)
  104. {
  105. // check x, y range
  106. if ((x >= SH1106_COLUMNS) || (y >= SH1106_ROWS)) {
  107. return;
  108. }
  109. // set pixel in buffer
  110. _displaybuffer[x + (y / 8 * SH1106_COLUMNS)] |= 1 << (y % 8);
  111. }
  112. void Display_SH1106_I2C::clear_pixel(uint16_t x, uint16_t y)
  113. {
  114. // check x, y range
  115. if ((x >= SH1106_COLUMNS) || (y >= SH1106_ROWS)) {
  116. return;
  117. }
  118. // clear pixel in buffer
  119. _displaybuffer[x + (y / 8 * SH1106_COLUMNS)] &= ~(1 << (y % 8));
  120. }
  121. void Display_SH1106_I2C::clear_screen()
  122. {
  123. memset(_displaybuffer, 0, SH1106_COLUMNS * SH1106_ROWS_PER_PAGE);
  124. }