GxEPD2_7C.h 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. // Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare.
  2. // Requires HW SPI and Adafruit_GFX. Caution: these e-papers require 3.3V supply AND data lines!
  3. //
  4. // based on Demo Example from Good Display: http://www.e-paper-display.com/download_list/downloadcategoryid=34&isMode=false.html
  5. //
  6. // Author: Jean-Marc Zingg
  7. //
  8. // Version: see library.properties
  9. //
  10. // Library: https://github.com/ZinggJM/GxEPD2
  11. #ifndef _GxEPD2_7C_H_
  12. #define _GxEPD2_7C_H_
  13. // uncomment next line to use class GFX of library GFX_Root instead of Adafruit_GFX
  14. //#include <GFX.h>
  15. #ifndef ENABLE_GxEPD2_GFX
  16. // default is off
  17. #define ENABLE_GxEPD2_GFX 0
  18. #endif
  19. #if ENABLE_GxEPD2_GFX
  20. #include "GxEPD2_GFX.h"
  21. #define GxEPD2_GFX_BASE_CLASS GxEPD2_GFX
  22. #elif defined(_GFX_H_)
  23. #define GxEPD2_GFX_BASE_CLASS GFX
  24. #else
  25. #include <Adafruit_GFX.h>
  26. #define GxEPD2_GFX_BASE_CLASS Adafruit_GFX
  27. #endif
  28. #include "GxEPD2_EPD.h"
  29. #include "epd3c/GxEPD2_565c.h"
  30. template<typename GxEPD2_Type, const uint16_t page_height>
  31. class GxEPD2_7C : public GxEPD2_GFX_BASE_CLASS
  32. {
  33. public:
  34. GxEPD2_Type epd2;
  35. #if ENABLE_GxEPD2_GFX
  36. GxEPD2_7C(GxEPD2_Type epd2_instance) : GxEPD2_GFX_BASE_CLASS(epd2, GxEPD2_Type::WIDTH, GxEPD2_Type::HEIGHT), epd2(epd2_instance)
  37. #else
  38. GxEPD2_7C(GxEPD2_Type epd2_instance) : GxEPD2_GFX_BASE_CLASS(GxEPD2_Type::WIDTH, GxEPD2_Type::HEIGHT), epd2(epd2_instance)
  39. #endif
  40. {
  41. _page_height = page_height;
  42. _pages = (HEIGHT / _page_height) + ((HEIGHT % _page_height) > 0);
  43. _mirror = false;
  44. _using_partial_mode = false;
  45. _current_page = 0;
  46. setFullWindow();
  47. }
  48. uint16_t pages()
  49. {
  50. return _pages;
  51. }
  52. uint16_t pageHeight()
  53. {
  54. return _page_height;
  55. }
  56. bool mirror(bool m)
  57. {
  58. _swap_ (_mirror, m);
  59. return m;
  60. }
  61. void drawPixel(int16_t x, int16_t y, uint16_t color)
  62. {
  63. if ((x < 0) || (x >= width()) || (y < 0) || (y >= height())) return;
  64. if (_mirror) x = width() - x - 1;
  65. // check rotation, move pixel around if necessary
  66. switch (getRotation())
  67. {
  68. case 1:
  69. _swap_(x, y);
  70. x = WIDTH - x - 1;
  71. break;
  72. case 2:
  73. x = WIDTH - x - 1;
  74. y = HEIGHT - y - 1;
  75. break;
  76. case 3:
  77. _swap_(x, y);
  78. y = HEIGHT - y - 1;
  79. break;
  80. }
  81. // transpose partial window to 0,0
  82. x -= _pw_x;
  83. y -= _pw_y;
  84. // clip to (partial) window
  85. if ((x < 0) || (x >= _pw_w) || (y < 0) || (y >= _pw_h)) return;
  86. // adjust for current page
  87. y -= _current_page * _page_height;
  88. // check if in current page
  89. if ((y < 0) || (y >= _page_height)) return;
  90. uint32_t i = x / 2 + uint32_t(y) * (_pw_w / 2);
  91. uint8_t pv = color7(color);
  92. if (x & 1) _pixel_buffer[i] = (_pixel_buffer[i] & 0xF0) | pv;
  93. else _pixel_buffer[i] = (_pixel_buffer[i] & 0x0F) | (pv << 4);
  94. }
  95. void init(uint32_t serial_diag_bitrate = 0) // = 0 : disabled
  96. {
  97. epd2.init(serial_diag_bitrate);
  98. _using_partial_mode = false;
  99. _current_page = 0;
  100. setFullWindow();
  101. }
  102. // init method with additional parameters:
  103. // initial false for re-init after processor deep sleep wake up, if display power supply was kept
  104. // only relevant for b/w displays with fast partial update
  105. // reset_duration = 20 is default; a value of 2 may help with "clever" reset circuit of newer boards from Waveshare
  106. // pulldown_rst_mode true for alternate RST handling to avoid feeding 5V through RST pin
  107. void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration = 20, bool pulldown_rst_mode = false)
  108. {
  109. epd2.init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
  110. _using_partial_mode = false;
  111. _current_page = 0;
  112. setFullWindow();
  113. }
  114. void fillScreen(uint16_t color)
  115. {
  116. uint8_t pv = color7(color);
  117. uint8_t pv2 = pv | pv << 4;
  118. for (uint32_t x = 0; x < sizeof(_pixel_buffer); x++)
  119. {
  120. _pixel_buffer[x] = pv2;
  121. }
  122. }
  123. // display buffer content to screen, useful for full screen buffer
  124. void display(bool partial_update_mode = false)
  125. {
  126. epd2.writeNative(_pixel_buffer, 0, 0, 0, WIDTH, _page_height);
  127. epd2.refresh(partial_update_mode);
  128. if (!partial_update_mode) epd2.powerOff();
  129. }
  130. // display part of buffer content to screen, useful for full screen buffer
  131. // displayWindow, use parameters according to actual rotation.
  132. // x and w should be multiple of 8, for rotation 0 or 2,
  133. // y and h should be multiple of 8, for rotation 1 or 3,
  134. // else window is increased as needed,
  135. // this is an addressing limitation of the e-paper controllers
  136. void displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
  137. {
  138. x = gx_uint16_min(x, width());
  139. y = gx_uint16_min(y, height());
  140. w = gx_uint16_min(w, width() - x);
  141. h = gx_uint16_min(h, height() - y);
  142. _rotate(x, y, w, h);
  143. epd2.writeNativePart(_pixel_buffer, 0, x, y, WIDTH, _page_height, x, y, w, h);
  144. epd2.refresh(x, y, w, h);
  145. }
  146. void setFullWindow()
  147. {
  148. _using_partial_mode = false;
  149. _pw_x = 0;
  150. _pw_y = 0;
  151. _pw_w = WIDTH;
  152. _pw_h = HEIGHT;
  153. }
  154. // setPartialWindow, use parameters according to actual rotation.
  155. // x and w should be multiple of 8, for rotation 0 or 2,
  156. // y and h should be multiple of 8, for rotation 1 or 3,
  157. // else window is increased as needed,
  158. // this is an addressing limitation of the e-paper controllers
  159. void setPartialWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
  160. {
  161. if (!epd2.hasPartialUpdate) return;
  162. _pw_x = gx_uint16_min(x, width());
  163. _pw_y = gx_uint16_min(y, height());
  164. _pw_w = gx_uint16_min(w, width() - _pw_x);
  165. _pw_h = gx_uint16_min(h, height() - _pw_y);
  166. _rotate(_pw_x, _pw_y, _pw_w, _pw_h);
  167. _using_partial_mode = true;
  168. // make _pw_x, _pw_w multiple of 2
  169. _pw_w += _pw_x % 2;
  170. if (_pw_w % 2 > 0) _pw_w += 2 - _pw_w % 2;
  171. _pw_x -= _pw_x % 2;
  172. }
  173. void firstPage()
  174. {
  175. fillScreen(GxEPD_WHITE);
  176. _current_page = 0;
  177. _second_phase = false;
  178. epd2.setPaged(); // for GxEPD2_565c paged workaround
  179. }
  180. bool nextPage()
  181. {
  182. uint16_t page_ys = _current_page * _page_height;
  183. if (_using_partial_mode)
  184. {
  185. //Serial.print(" nextPage("); Serial.print(_pw_x); Serial.print(", "); Serial.print(_pw_y); Serial.print(", ");
  186. //Serial.print(_pw_w); Serial.print(", "); Serial.print(_pw_h); Serial.print(") P"); Serial.println(_current_page);
  187. uint16_t page_ye = _current_page < (_pages - 1) ? page_ys + _page_height : HEIGHT;
  188. uint16_t dest_ys = _pw_y + page_ys; // transposed
  189. uint16_t dest_ye = gx_uint16_min(_pw_y + _pw_h, _pw_y + page_ye);
  190. if (dest_ye > dest_ys)
  191. {
  192. //Serial.print("writeImage("); Serial.print(_pw_x); Serial.print(", "); Serial.print(dest_ys); Serial.print(", ");
  193. //Serial.print(_pw_w); Serial.print(", "); Serial.print(dest_ye - dest_ys); Serial.println(")");
  194. epd2.writeNative(_pixel_buffer, 0, _pw_x, dest_ys, _pw_w, dest_ye - dest_ys);
  195. }
  196. else
  197. {
  198. //Serial.print("writeImage("); Serial.print(_pw_x); Serial.print(", "); Serial.print(dest_ys); Serial.print(", ");
  199. //Serial.print(_pw_w); Serial.print(", "); Serial.print(dest_ye - dest_ys); Serial.print(") skipped ");
  200. //Serial.print(dest_ys); Serial.print(".."); Serial.println(dest_ye);
  201. }
  202. _current_page++;
  203. if (_current_page == _pages)
  204. {
  205. _current_page = 0;
  206. if (!_second_phase)
  207. {
  208. epd2.refresh(_pw_x, _pw_y, _pw_w, _pw_h);
  209. if (epd2.hasFastPartialUpdate)
  210. {
  211. _second_phase = true;
  212. return true;
  213. }
  214. }
  215. return false;
  216. }
  217. fillScreen(GxEPD_WHITE);
  218. return true;
  219. }
  220. else // full update
  221. {
  222. epd2.writeNative(_pixel_buffer, 0, 0, page_ys, WIDTH, gx_uint16_min(_page_height, HEIGHT - page_ys));
  223. _current_page++;
  224. if (_current_page == _pages)
  225. {
  226. _current_page = 0;
  227. if ((epd2.panel == GxEPD2::GDEW0154Z04) && (_pages > 1))
  228. {
  229. if (!_second_phase)
  230. {
  231. epd2.refresh(false); // full update after first phase
  232. _second_phase = true;
  233. fillScreen(GxEPD_WHITE);
  234. return true;
  235. }
  236. else epd2.refresh(true); // partial update after second phase
  237. } else epd2.refresh(false); // full update after only phase
  238. epd2.powerOff();
  239. return false;
  240. }
  241. fillScreen(GxEPD_WHITE);
  242. return true;
  243. }
  244. }
  245. // GxEPD style paged drawing; drawCallback() is called as many times as needed
  246. void drawPaged(void (*drawCallback)(const void*), const void* pv)
  247. {
  248. if (_using_partial_mode)
  249. {
  250. for (_current_page = 0; _current_page < _pages; _current_page++)
  251. {
  252. uint16_t page_ys = _current_page * _page_height;
  253. uint16_t page_ye = _current_page < (_pages - 1) ? page_ys + _page_height : HEIGHT;
  254. uint16_t dest_ys = _pw_y + page_ys; // transposed
  255. uint16_t dest_ye = gx_uint16_min(_pw_y + _pw_h, _pw_y + page_ye);
  256. if (dest_ye > dest_ys)
  257. {
  258. fillScreen(GxEPD_WHITE);
  259. drawCallback(pv);
  260. epd2.writeNative(_pixel_buffer, 0, _pw_x, dest_ys, _pw_w, dest_ye - dest_ys);
  261. }
  262. }
  263. epd2.refresh(_pw_x, _pw_y, _pw_w, _pw_h);
  264. }
  265. else // full update
  266. {
  267. epd2.setPaged(); // for GxEPD2_154c paged workaround
  268. for (_current_page = 0; _current_page < _pages; _current_page++)
  269. {
  270. uint16_t page_ys = _current_page * _page_height;
  271. fillScreen(GxEPD_WHITE);
  272. drawCallback(pv);
  273. epd2.writeNative(_pixel_buffer, 0, 0, page_ys, WIDTH, gx_uint16_min(_page_height, HEIGHT - page_ys));
  274. }
  275. epd2.refresh(false); // full update
  276. epd2.powerOff();
  277. }
  278. _current_page = 0;
  279. }
  280. void drawInvertedBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, int16_t h, uint16_t color)
  281. {
  282. // taken from Adafruit_GFX.cpp, modified
  283. int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte
  284. uint8_t byte = 0;
  285. for (int16_t j = 0; j < h; j++)
  286. {
  287. for (int16_t i = 0; i < w; i++ )
  288. {
  289. if (i & 7) byte <<= 1;
  290. else
  291. {
  292. #if defined(__AVR) || defined(ESP8266) || defined(ESP32)
  293. byte = pgm_read_byte(&bitmap[j * byteWidth + i / 8]);
  294. #else
  295. byte = bitmap[j * byteWidth + i / 8];
  296. #endif
  297. }
  298. if (!(byte & 0x80))
  299. {
  300. drawPixel(x + i, y + j, color);
  301. }
  302. }
  303. }
  304. }
  305. // Support for Bitmaps (Sprites) to Controller Buffer and to Screen
  306. void clearScreen(uint8_t value = 0xFF) // init controller memory and screen (default white)
  307. {
  308. epd2.clearScreen(value);
  309. }
  310. void writeScreenBuffer(uint8_t value = 0xFF) // init controller memory (default white)
  311. {
  312. epd2.writeScreenBuffer(value);
  313. }
  314. // write to controller memory, without screen refresh; x and w should be multiple of 8
  315. void writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false)
  316. {
  317. epd2.writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm);
  318. }
  319. void writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
  320. int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false)
  321. {
  322. epd2.writeImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm);
  323. }
  324. void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
  325. {
  326. epd2.writeImage(black, color, x, y, w, h, invert, mirror_y, pgm);
  327. }
  328. void writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h)
  329. {
  330. epd2.writeImage(black, color, x, y, w, h, false, false, false);
  331. }
  332. void writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
  333. int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
  334. {
  335. epd2.writeImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm);
  336. }
  337. void writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
  338. int16_t x, int16_t y, int16_t w, int16_t h)
  339. {
  340. epd2.writeImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, false, false, false);
  341. }
  342. // write sprite of native data to controller memory, without screen refresh; x and w should be multiple of 8
  343. void writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
  344. {
  345. epd2.writeNative(data1, data2, x, y, w, h, invert, mirror_y, pgm);
  346. }
  347. // write to controller memory, with screen refresh; x and w should be multiple of 8
  348. void drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false)
  349. {
  350. epd2.drawImage(bitmap, x, y, w, h, invert, mirror_y, pgm);
  351. }
  352. void drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
  353. int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false)
  354. {
  355. epd2.drawImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm);
  356. }
  357. void drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
  358. {
  359. epd2.drawImage(black, color, x, y, w, h, invert, mirror_y, pgm);
  360. }
  361. void drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h)
  362. {
  363. epd2.drawImage(black, color, x, y, w, h, false, false, false);
  364. }
  365. void drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
  366. int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
  367. {
  368. epd2.drawImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm);
  369. }
  370. void drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
  371. int16_t x, int16_t y, int16_t w, int16_t h)
  372. {
  373. epd2.drawImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, false, false, false);
  374. }
  375. // write sprite of native data to controller memory, with screen refresh; x and w should be multiple of 8
  376. void drawNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
  377. {
  378. epd2.drawNative(data1, data2, x, y, w, h, invert, mirror_y, pgm);
  379. }
  380. void refresh(bool partial_update_mode = false) // screen refresh from controller memory to full screen
  381. {
  382. epd2.refresh(partial_update_mode);
  383. if (!partial_update_mode) epd2.powerOff();
  384. }
  385. void refresh(int16_t x, int16_t y, int16_t w, int16_t h) // screen refresh from controller memory, partial screen
  386. {
  387. epd2.refresh(x, y, w, h);
  388. }
  389. // turns off generation of panel driving voltages, avoids screen fading over time
  390. void powerOff()
  391. {
  392. epd2.powerOff();
  393. }
  394. // turns powerOff() and sets controller to deep sleep for minimum power use, ONLY if wakeable by RST (rst >= 0)
  395. void hibernate()
  396. {
  397. epd2.hibernate();
  398. }
  399. private:
  400. template <typename T> static inline void
  401. _swap_(T & a, T & b)
  402. {
  403. T t = a;
  404. a = b;
  405. b = t;
  406. };
  407. static inline uint16_t gx_uint16_min(uint16_t a, uint16_t b)
  408. {
  409. return (a < b ? a : b);
  410. };
  411. static inline uint16_t gx_uint16_max(uint16_t a, uint16_t b)
  412. {
  413. return (a > b ? a : b);
  414. };
  415. void _rotate(uint16_t& x, uint16_t& y, uint16_t& w, uint16_t& h)
  416. {
  417. switch (getRotation())
  418. {
  419. case 1:
  420. _swap_(x, y);
  421. _swap_(w, h);
  422. x = WIDTH - x - w;
  423. break;
  424. case 2:
  425. x = WIDTH - x - w;
  426. y = HEIGHT - y - h;
  427. break;
  428. case 3:
  429. _swap_(x, y);
  430. _swap_(w, h);
  431. y = HEIGHT - y - h;
  432. break;
  433. }
  434. }
  435. uint8_t color7(uint16_t color)
  436. {
  437. static uint16_t _prev_color = GxEPD_BLACK;
  438. static uint8_t _prev_color7 = 0x00; // black
  439. if (color == _prev_color) return _prev_color7;
  440. uint8_t cv7 = 0x00;
  441. switch (color)
  442. {
  443. case GxEPD_BLACK: cv7 = 0x00; break;
  444. case GxEPD_WHITE: cv7 = 0x01; break;
  445. case GxEPD_GREEN: cv7 = 0x02; break;
  446. case GxEPD_BLUE: cv7 = 0x03; break;
  447. case GxEPD_RED: cv7 = 0x04; break;
  448. case GxEPD_YELLOW: cv7 = 0x05; break;
  449. case GxEPD_ORANGE: cv7 = 0x06; break;
  450. default:
  451. {
  452. uint16_t red = color & 0xF800;
  453. uint16_t green = (color & 0x07E0) << 5;
  454. uint16_t blue = (color & 0x001F) << 11;
  455. if ((red < 0x8000) && (green < 0x8000) && (blue < 0x8000)) cv7 = 0x00; // black
  456. else if ((red >= 0x8000) && (green >= 0x8000) && (blue >= 0x8000)) cv7 = 0x01; // white
  457. else if ((red >= 0x8000) && (blue >= 0x8000)) cv7 = red > blue ? 0x04 : 0x03; // red, blue
  458. else if ((green >= 0x8000) && (blue >= 0x8000)) cv7 = green > blue ? 0x02 : 0x03; // green, blue
  459. else if ((red >= 0x8000) && (green >= 0x8000))
  460. {
  461. static const uint16_t y2o_lim = ((GxEPD_YELLOW - GxEPD_ORANGE) / 2 + (GxEPD_ORANGE & 0x07E0)) << 5;
  462. cv7 = green > y2o_lim ? 0x05 : 0x06; // yellow, orange
  463. }
  464. else if (red >= 0x8000) cv7 = 0x04; // red
  465. else if (green >= 0x8000) cv7 = 0x02; // green
  466. else cv7 = 0x03; // blue
  467. }
  468. }
  469. _prev_color = color;
  470. _prev_color7 = cv7;
  471. return cv7;
  472. }
  473. private:
  474. uint8_t _pixel_buffer[(GxEPD2_Type::WIDTH / 2) * page_height];
  475. bool _using_partial_mode, _second_phase, _mirror;
  476. uint16_t _width_bytes, _pixel_bytes;
  477. int16_t _current_page;
  478. uint16_t _pages, _page_height;
  479. uint16_t _pw_x, _pw_y, _pw_w, _pw_h;
  480. };
  481. #endif