SSD1675.c 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. /**
  2. * Copyright (c) 2017-2018 Tara Keeling
  3. * 2020 Philippe G.
  4. *
  5. * This software is released under the MIT License.
  6. * https://opensource.org/licenses/MIT
  7. */
  8. #include <stdio.h>
  9. #include <string.h>
  10. #include <stdint.h>
  11. #include <stdbool.h>
  12. #include "freertos/FreeRTOS.h"
  13. #include "freertos/task.h"
  14. #include "driver/gpio.h"
  15. #include <esp_log.h>
  16. #include "gds.h"
  17. #include "gds_private.h"
  18. static char TAG[] = "SSD1675";
  19. const unsigned char EPD_lut_full_update[] = {
  20. 0x80,0x60,0x40,0x00,0x00,0x00,0x00, //LUT0: BB: VS 0 ~7
  21. 0x10,0x60,0x20,0x00,0x00,0x00,0x00, //LUT1: BW: VS 0 ~7
  22. 0x80,0x60,0x40,0x00,0x00,0x00,0x00, //LUT2: WB: VS 0 ~7
  23. 0x10,0x60,0x20,0x00,0x00,0x00,0x00, //LUT3: WW: VS 0 ~7
  24. 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT4: VCOM: VS 0 ~7
  25. 0x03,0x03,0x00,0x00,0x02, // TP0 A~D RP0
  26. 0x09,0x09,0x00,0x00,0x02, // TP1 A~D RP1
  27. 0x03,0x03,0x00,0x00,0x02, // TP2 A~D RP2
  28. 0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3
  29. 0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4
  30. 0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5
  31. 0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6
  32. 0x15,0x41,0xA8,0x32,0x30,0x0A,
  33. };
  34. const unsigned char EPD_lut_partial_update[]= { //20 bytes
  35. 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT0: BB: VS 0 ~7
  36. 0x80,0x00,0x00,0x00,0x00,0x00,0x00, //LUT1: BW: VS 0 ~7
  37. 0x40,0x00,0x00,0x00,0x00,0x00,0x00, //LUT2: WB: VS 0 ~7
  38. 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT3: WW: VS 0 ~7
  39. 0x00,0x00,0x00,0x00,0x00,0x00,0x00, //LUT4: VCOM: VS 0 ~7
  40. 0x0A,0x00,0x00,0x00,0x00, // TP0 A~D RP0
  41. 0x00,0x00,0x00,0x00,0x00, // TP1 A~D RP1
  42. 0x00,0x00,0x00,0x00,0x00, // TP2 A~D RP2
  43. 0x00,0x00,0x00,0x00,0x00, // TP3 A~D RP3
  44. 0x00,0x00,0x00,0x00,0x00, // TP4 A~D RP4
  45. 0x00,0x00,0x00,0x00,0x00, // TP5 A~D RP5
  46. 0x00,0x00,0x00,0x00,0x00, // TP6 A~D RP6
  47. 0x15,0x41,0xA8,0x32,0x30,0x0A,
  48. };
  49. struct PrivateSpace {
  50. int ReadyPin;
  51. uint16_t Height;
  52. };
  53. // Functions are not declared to minimize # of lines
  54. void WaitReady( struct GDS_Device* Device) {
  55. struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
  56. if (Private->ReadyPin >= 0) {
  57. int count = 4*1000;
  58. while (gpio_get_level( Private->ReadyPin ) && count) {
  59. vTaskDelay( pdMS_TO_TICKS(100) );
  60. count -= 100;
  61. }
  62. } else {
  63. vTaskDelay( pdMS_TO_TICKS(2000) );
  64. }
  65. }
  66. static void WriteByte( struct GDS_Device* Device, uint8_t Data ) {
  67. Device->WriteData( Device, &Data, 1 );
  68. }
  69. static void SetColumnAddress( struct GDS_Device* Device, uint8_t Start, uint8_t End ) {
  70. // start might be greater than end if we decrement
  71. Device->WriteCommand( Device, 0x44 );
  72. Device->WriteData( Device, &Start, 1 );
  73. Device->WriteData( Device, &End, 1 );
  74. // we obviously want to start ... from the start
  75. Device->WriteCommand( Device, 0x4e );
  76. WriteByte( Device, Start );
  77. }
  78. static void SetRowAddress( struct GDS_Device* Device, uint16_t Start, uint16_t End ) {
  79. // start might be greater than end if we decrement
  80. Device->WriteCommand( Device, 0x45 );
  81. WriteByte( Device, Start );
  82. WriteByte( Device, Start >> 8 );
  83. WriteByte( Device, End );
  84. WriteByte( Device, End >> 8 );
  85. // we obviously want to start ... from the start
  86. Device->WriteCommand( Device, 0x4f );
  87. WriteByte( Device, Start );
  88. WriteByte( Device, Start >> 8 );
  89. }
  90. static void Update( struct GDS_Device* Device ) {
  91. uint8_t *iptr = Device->Framebuffer;
  92. Device->WriteCommand( Device, 0x24 );
  93. // this is awfully slow, but e-ink are slow anyway ...
  94. for (int i = Device->FramebufferSize; --i >= 0;) {
  95. WriteByte( Device, ~*iptr++ );
  96. }
  97. Device->WriteCommand( Device, 0x22 );
  98. WriteByte( Device, 0xC7);
  99. Device->WriteCommand( Device, 0X20 );
  100. WaitReady( Device );
  101. }
  102. // remember that for these ELD drivers W and H are "inverted"
  103. static inline void _DrawPixel( struct GDS_Device* Device, int X, int Y, int Color ) {
  104. uint32_t YBit = ( Y & 0x07 );
  105. Y>>= 3;
  106. uint8_t* FBOffset = Device->Framebuffer + ( ( Y * Device->Width ) + X );
  107. *FBOffset = ( Color == GDS_COLOR_BLACK ) ? *FBOffset & ~BIT( 7-YBit ) : *FBOffset | BIT( 7-YBit );
  108. }
  109. static void ClearWindow( struct GDS_Device* Device, int x1, int y1, int x2, int y2, int Color ) {
  110. for (int r = y1; r <= y2; r++) {
  111. for (int c = x1; c <= x2; c++) {
  112. _DrawPixel( Device, c, r, Color );
  113. }
  114. }
  115. }
  116. static void DrawBitmapCBR(struct GDS_Device* Device, uint8_t *Data, int Width, int Height, int Color ) {
  117. if (!Height) Height = Device->Height;
  118. if (!Width) Width = Device->Width;
  119. // just do row/column swap
  120. for (int r = 0; r < Height; r++) {
  121. uint8_t *optr = Device->Framebuffer + r*Device->Width, *iptr = Data + r;
  122. for (int c = Width; --c >= 0;) {
  123. *optr++ = *iptr;
  124. iptr += Height;
  125. }
  126. }
  127. }
  128. static bool Init( struct GDS_Device* Device ) {
  129. struct PrivateSpace *Private = (struct PrivateSpace*) Device->Private;
  130. // need to re-adjust framebuffer because these are non % 8
  131. Private->Height = Device->Height;
  132. if (Device->Height & 0x07) Device->Height = ((Device->Height >> 3) + 1) << 3;
  133. Device->FramebufferSize = Device->Width * Device->Height / 8;
  134. Device->Framebuffer = calloc(1, Device->FramebufferSize);
  135. NullCheck( Device->Framebuffer, return false );
  136. if (Private->ReadyPin >= 0) {
  137. gpio_pad_select_gpio( Private->ReadyPin );
  138. gpio_set_pull_mode( Private->ReadyPin, GPIO_PULLUP_ONLY);
  139. gpio_set_direction( Private->ReadyPin, GPIO_MODE_INPUT );
  140. }
  141. // soft reset
  142. vTaskDelay(pdMS_TO_TICKS( 2000 ));
  143. Device->WriteCommand( Device, 0x12 );
  144. WaitReady( Device );
  145. Device->WriteCommand( Device, 0x74 );
  146. WriteByte( Device, 0x54 );
  147. Device->WriteCommand( Device, 0x7e );
  148. WriteByte( Device, 0x3B );
  149. Device->WriteCommand( Device, 0x3c );
  150. WriteByte( Device, 0x03 );
  151. Device->WriteCommand( Device, 0x2c );
  152. WriteByte( Device, 0x55 );
  153. Device->WriteCommand( Device, 0x03 );
  154. WriteByte( Device, EPD_lut_full_update[70] );
  155. Device->WriteCommand( Device, 0x04 );
  156. WriteByte( Device, EPD_lut_full_update[71] );
  157. WriteByte( Device, EPD_lut_full_update[72] );
  158. WriteByte( Device, EPD_lut_full_update[73] );
  159. Device->WriteCommand( Device, 0x3a );
  160. WriteByte( Device, EPD_lut_full_update[74] );
  161. Device->WriteCommand( Device, 0x3b );
  162. WriteByte( Device, EPD_lut_full_update[75] );
  163. Device->WriteCommand( Device, 0X32 );
  164. for (int i = 0; i < 70; i++) {
  165. WriteByte( Device, EPD_lut_full_update[i] );
  166. }
  167. // now deal with funny X/Y layout (W and H are "inverted")
  168. Device->WriteCommand( Device, 0x01 );
  169. WriteByte( Device, Device->Width - 1 );
  170. WriteByte( Device, (Device->Width - 1) >> 8 );
  171. WriteByte( Device, (0 << 0) );
  172. /*
  173. Start from 0, Ymax, incX, decY. Starting from X=Height would be difficult
  174. as we would hit the extra bits added because height % 8 != 0 and they are
  175. not written by the DrawPixel. By starting from X=0 we are aligned and
  176. doing incY is like a clockwise 90° rotation (vs otherwise we would virtually
  177. do a counter-clockwise rotation but again have the "border" issue.
  178. */
  179. Device->WriteCommand( Device, 0x11 );
  180. WriteByte( Device, (1 << 2) | (0 << 1) | (1 << 0));
  181. // must be in order with increment/decrement i.e start might be > end if we decrement
  182. SetColumnAddress( Device, 0x0, (Device->Height >> 3) - 1 );
  183. SetRowAddress ( Device, Device->Width - 1, 0 );
  184. WaitReady( Device );
  185. Update( Device );
  186. return true;
  187. }
  188. static const struct GDS_Device SSD1675 = {
  189. .DrawBitmapCBR = DrawBitmapCBR, .ClearWindow = ClearWindow,
  190. .DrawPixelFast = _DrawPixel,
  191. .Update = Update, .Init = Init,
  192. .Mode = GDS_MONO, .Depth = 1,
  193. .Alloc = GDS_ALLOC_NONE,
  194. };
  195. struct GDS_Device* SSD1675_Detect(char *Driver, struct GDS_Device* Device) {
  196. if (!strcasestr(Driver, "SSD1675")) return NULL;
  197. if (!Device) Device = calloc(1, sizeof(struct GDS_Device));
  198. *Device = SSD1675;
  199. char *p;
  200. struct PrivateSpace* Private = (struct PrivateSpace*) Device->Private;
  201. Private->ReadyPin = -1;
  202. if ((p = strcasestr(Driver, "ready")) != NULL) Private->ReadyPin = atoi(strchr(p, '=') + 1);
  203. ESP_LOGI(TAG, "SSD1675 driver with ready GPIO %d", Private->ReadyPin);
  204. return Device;
  205. }