PlatformIO package of the Teensy core framework compatible with GCC 10 & C++20
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.

ssd1351.h 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. // All speed gains and original optimized library for the ILI9341 are credit to Paul Stoffregen.
  2. /***************************************************
  3. This is adapted from the library for the Adafruit ILI9341 display, but for the SSD1351.
  4. ----> https://www.adafruit.com/products/1673
  5. Check out the links above for our tutorials and wiring diagrams
  6. These displays use SPI to communicate, 4 or 5 pins are required to
  7. interface (RST is optional)
  8. Adafruit invests time and resources providing this open source code,
  9. please support Adafruit and open-source hardware by purchasing
  10. products from Adafruit!
  11. Written by Limor Fried/Ladyada for Adafruit Industries.
  12. MIT license, all text above must be included in any redistribution
  13. ****************************************************/
  14. #if !defined(TEENSYDUINO) || !defined(__arm__)
  15. #error "Sorry, this optimized library only works on 32 bit Teensy. Use Adafruit_SSD1351 for others."
  16. #elif defined(__MKL26Z64__)
  17. #error "Sorry, this optimized library doesn't work with Teensy LC. Use Adafruit_SSD1351."
  18. #endif
  19. #pragma once
  20. #include <Arduino.h>
  21. #include <array>
  22. #include <string>
  23. #include <SPI.h>
  24. #include "color.h"
  25. #include "buffer.h"
  26. #include "gfxfont.h"
  27. #include "Fonts/all_fonts.h"
  28. extern "C" {
  29. int _getpid(){ return -1;}
  30. int _kill(int pid, int sig){ return -1; }
  31. int _write(){return -1;}
  32. }
  33. #ifndef swap
  34. template <typename T> void __attribute__((always_inline)) swap(T &a, T &b) {
  35. T t = a;
  36. a = b;
  37. b = t;
  38. }
  39. #endif
  40. // Magical member templating magic to make special members for buffering / non-buffering more readable.
  41. // Taken from http://lists.boost.org/Archives/boost/2014/08/215954.php
  42. #define REQUIRES(...) typename std::enable_if<(__VA_ARGS__), int>::type = 0
  43. #define MEMBER_REQUIRES(...) template<bool HiddenMemberBool=true, REQUIRES(HiddenMemberBool && (__VA_ARGS__))>
  44. namespace ssd1351 {
  45. // Use 18mhz SPI as that seems about the fastest my version of the display can deal with. For some reason this still breaks
  46. // when overclocking the teensy, insights into this would be highly appreciated.
  47. // To work around it, you can define SLOW_SPI before including this, in which case the SPI speed is reduced to 15MHz.
  48. // This slows down the display communication quite a lot - but at least it allows running this on an overclocked teensy.
  49. #ifndef SPICLOCK
  50. #ifdef SLOW_SPI
  51. #define SPICLOCK 15000000
  52. #else
  53. #define SPICLOCK 18000000
  54. #endif
  55. #endif
  56. #define CMD_COMMAND_LOCK 0xFD
  57. // These two bytes are used to issue some display lock commands for the init. I don't know what they do, but they seem necessary.
  58. #define COMMAND_LOCK_INIT1 0x12 // "Unlock OLED driver IC MCU interface from entering command"
  59. #define COMMAND_LOCK_INIT2 0xB1 // "Command A2,B1,B3,BB,BE accessible if in unlock state"
  60. #define CMD_DISPLAY_SLEEP 0xAE // Set display to sleep mode
  61. #define CMD_DISPLAY_WAKE 0xAF // Wake up display from sleep mode
  62. #define CMD_CLOCK_DIVIDER 0xB3 // Set clock divider and display frequency
  63. #define CMD_REMAP 0xA0 // Remap various display settings, like hardware mapping and most importantly color mode
  64. #define CMD_START_LINE 0xA1 // Set display start line, needs to be set to 96 for 128x96 displays
  65. #define CMD_DISPLAY_OFFSET 0xA2 // Set display offset (hardware dependent, needs to be set to 0)
  66. #define CMD_FUNCTION_SELECTION 0xAB // Used to activate/deactivate internal voltage regulator
  67. #define CMD_NORMAL_MODE 0xA6 // Normal display mode (display contents of video RAM)
  68. #define INTERNAL_VREG 0x01 // internal voltage regulator, other value is never used
  69. #define CMD_COLUMN_ADDRESS 0x15 // Set start and end column of active video RAM area
  70. #define CMD_ROW_ADDRESS 0x75 // Set start and end row of active video RAM area
  71. #define CMD_WRITE_TO_RAM 0x5C // Start writing to the video ram. After this, color data can be sent.
  72. #define CMD_NOOP 0xAD // Sometimes used as a last command - doesn't do anything.
  73. // Text alignments
  74. static const uint8_t ALIGN_LEFT = 0;
  75. static const uint8_t ALIGN_CENTER = 1;
  76. static const uint8_t ALIGN_RIGHT = 2;
  77. static const uint8_t HIGH_COLOR = 0;
  78. static const uint8_t LOW_COLOR = 1;
  79. const auto black = RGB();
  80. static const SPISettings spi_settings(SPICLOCK, MSBFIRST, SPI_MODE0);
  81. struct Rect {
  82. int16_t x;
  83. int16_t y;
  84. int16_t w;
  85. int16_t h;
  86. };
  87. template <typename C, typename B, int W = 128, int H = 128>
  88. class SSD1351 : public Print {
  89. public:
  90. SSD1351(
  91. uint8_t _cs = 10,
  92. uint8_t _dc = 15,
  93. uint8_t _reset = 14,
  94. uint8_t _mosi=11,
  95. uint8_t _sclk=13
  96. ) : cs(_cs), dc(_dc), reset(_reset), mosi(_mosi), sclk(_sclk) {}
  97. void begin() {
  98. // Initialize the display. This validates the used pins for hardware SPI use,
  99. // goes through the displays init routine and sets some important options like
  100. // color depth and display size.
  101. // Only size and color depth are settable - everything else is hardcoded.
  102. // verify SPI pins are valid;
  103. if (SPI.pinIsMOSI(mosi) && SPI.pinIsSCK(sclk)) {
  104. SPI.setMOSI(mosi);
  105. SPI.setSCK(sclk);
  106. } else {
  107. Serial.println("SPI pins are invalid.");
  108. return;
  109. }
  110. SPI.begin();
  111. #ifdef KINETISK
  112. if (SPI.pinIsChipSelect(cs, dc)) {
  113. // Configure both cs and dc as chip selects, which allows triggering them extremely fast
  114. // pcs_data and pcs_command contain the bitmasks used when setting the pin states.
  115. pcs_data = SPI.setCS(cs);
  116. pcs_command = pcs_data | SPI.setCS(dc);
  117. } else {
  118. Serial.println("CS and DC need to be special chip select pins.");
  119. pcs_data = 0;
  120. pcs_command = 0;
  121. return;
  122. }
  123. #elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
  124. _csport = portOutputRegister(cs);
  125. _cspinmask = digitalPinToBitMask(cs);
  126. pinMode(cs, OUTPUT);
  127. DIRECT_WRITE_HIGH(_csport, _cspinmask);
  128. _spi_tcr_current = IMXRT_LPSPI4_S.TCR; // get the current TCR value
  129. // TODO: Need to setup DC to actually work.
  130. if (SPI.pinIsChipSelect(dc)) {
  131. SPI.setCS(dc);
  132. _dcport = 0;
  133. _dcpinmask = 0;
  134. } else {
  135. _dcport = portOutputRegister(dc);
  136. _dcpinmask = digitalPinToBitMask(dc);
  137. pinMode(dc, OUTPUT);
  138. DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
  139. }
  140. maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(7));
  141. #endif
  142. // toggle reset low to reset
  143. if (reset < 255) {
  144. pinMode(reset, OUTPUT);
  145. digitalWrite(reset, HIGH);
  146. delay(10);
  147. digitalWrite(reset, LOW);
  148. delay(10);
  149. digitalWrite(reset, HIGH);
  150. delay(10);
  151. }
  152. delay(30);
  153. beginSPITransaction();
  154. // Set display command lock settings - they have something to do with when the display can receive which commands,
  155. // but I don't exactly understand what the implications are.
  156. sendCommandAndContinue(CMD_COMMAND_LOCK);
  157. sendDataAndContinue(COMMAND_LOCK_INIT1);
  158. sendCommandAndContinue(CMD_COMMAND_LOCK);
  159. sendDataAndContinue(COMMAND_LOCK_INIT2);
  160. sendCommandAndContinue(CMD_DISPLAY_SLEEP);
  161. sendCommandAndContinue(CMD_CLOCK_DIVIDER);
  162. // First 4 bits (1111) are the display frequency (highest), last 4 bits (0001) are the clock divider (lowest)
  163. sendDataAndContinue(0xF1);
  164. // Set various mapping settings (0x74 = 0111 0100 for low color mode and 0xB4 (1011 0100) for high color mode)
  165. // 01: color mode (01 is 64k colors, 10 is 262k colors)
  166. // 1: COM pins split (hardware dependent)
  167. // 1: COM Scan direction up to down (hardware dependent)
  168. // 0: Reserved
  169. // 1: Colour sequence C -> B -> A instead of A -> B -> C
  170. // 0: Column layout mapping (hardware dependent)
  171. // 0: Horizontal address increment mode (x is increased after each write and wraps)
  172. setColorDepth();
  173. // Set start line - this needs to be 0 for a 128x128 display and 96 for a 128x96 display
  174. sendCommandAndContinue(CMD_START_LINE);
  175. sendDataAndContinue(H == 128 ? 0 : 96);
  176. // Set display offset - this is always zero
  177. sendCommandAndContinue(CMD_DISPLAY_OFFSET);
  178. sendDataAndContinue(0);
  179. // Select the internal voltage regulator
  180. sendCommandAndContinue(CMD_FUNCTION_SELECTION);
  181. sendDataAndContinue(INTERNAL_VREG);
  182. // Set display to normal operation and leave sleep mode
  183. sendCommandAndContinue(CMD_NORMAL_MODE);
  184. sendLastCommand(CMD_DISPLAY_WAKE);
  185. endSPITransaction();
  186. }
  187. void sleep(bool enable) {
  188. beginSPITransaction();
  189. sendLastCommand(enable ? CMD_DISPLAY_SLEEP : CMD_DISPLAY_WAKE);
  190. endSPITransaction();
  191. }
  192. void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, const C &color) {
  193. drawFastHLine(x, y, w, color);
  194. drawFastHLine(x, y + h - 1, w, color);
  195. drawFastVLine(x, y, h, color);
  196. drawFastVLine(x + w - 1, y, h, color);
  197. }
  198. void drawCircle(int16_t x0, int16_t y0, int16_t r, const C &color) {
  199. int16_t f = 1 - r;
  200. int16_t ddF_x = 1;
  201. int16_t ddF_y = -2 * r;
  202. int16_t x = 0;
  203. int16_t y = r;
  204. drawPixel(x0 , y0+r, color);
  205. drawPixel(x0 , y0-r, color);
  206. drawPixel(x0+r, y0 , color);
  207. drawPixel(x0-r, y0 , color);
  208. while (x<y) {
  209. if (f >= 0) {
  210. y--;
  211. ddF_y += 2;
  212. f += ddF_y;
  213. }
  214. x++;
  215. ddF_x += 2;
  216. f += ddF_x;
  217. drawPixel(x0 + x, y0 + y, color);
  218. drawPixel(x0 - x, y0 + y, color);
  219. drawPixel(x0 + x, y0 - y, color);
  220. drawPixel(x0 - x, y0 - y, color);
  221. drawPixel(x0 + y, y0 + x, color);
  222. drawPixel(x0 - y, y0 + x, color);
  223. drawPixel(x0 + y, y0 - x, color);
  224. drawPixel(x0 - y, y0 - x, color);
  225. }
  226. }
  227. void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, const C &color) {
  228. int16_t f = 1 - r;
  229. int16_t ddF_x = 1;
  230. int16_t ddF_y = -2 * r;
  231. int16_t x = 0;
  232. int16_t y = r;
  233. while (x<y) {
  234. if (f >= 0) {
  235. y--;
  236. ddF_y += 2;
  237. f += ddF_y;
  238. }
  239. x++;
  240. ddF_x += 2;
  241. f += ddF_x;
  242. if (cornername & 0x4) {
  243. drawPixel(x0 + x, y0 + y, color);
  244. drawPixel(x0 + y, y0 + x, color);
  245. }
  246. if (cornername & 0x2) {
  247. drawPixel(x0 + x, y0 - y, color);
  248. drawPixel(x0 + y, y0 - x, color);
  249. }
  250. if (cornername & 0x8) {
  251. drawPixel(x0 - y, y0 + x, color);
  252. drawPixel(x0 - x, y0 + y, color);
  253. }
  254. if (cornername & 0x1) {
  255. drawPixel(x0 - y, y0 - x, color);
  256. drawPixel(x0 - x, y0 - y, color);
  257. }
  258. }
  259. }
  260. void fillCircle(int16_t x0, int16_t y0, int16_t r, const C &color) {
  261. drawFastVLine(x0, y0-r, 2*r+1, color);
  262. fillCircleHelper(x0, y0, r, 3, 0, color);
  263. }
  264. void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, const C &color) {
  265. int16_t f = 1 - r;
  266. int16_t ddF_x = 1;
  267. int16_t ddF_y = -2 * r;
  268. int16_t x = 0;
  269. int16_t y = r;
  270. while (x<y) {
  271. if (f >= 0) {
  272. y--;
  273. ddF_y += 2;
  274. f += ddF_y;
  275. }
  276. x++;
  277. ddF_x += 2;
  278. f += ddF_x;
  279. if (cornername & 0x1) {
  280. drawFastVLine(x0+x, y0-y, 2*y+1+delta, color);
  281. drawFastVLine(x0+y, y0-x, 2*x+1+delta, color);
  282. }
  283. if (cornername & 0x2) {
  284. drawFastVLine(x0-x, y0-y, 2*y+1+delta, color);
  285. drawFastVLine(x0-y, y0-x, 2*x+1+delta, color);
  286. }
  287. }
  288. }
  289. void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, const C &color) {
  290. drawLine(x0, y0, x1, y1, color);
  291. drawLine(x1, y1, x2, y2, color);
  292. drawLine(x2, y2, x0, y0, color);
  293. }
  294. void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, const C &color) {
  295. int16_t a, b, y, last;
  296. // Sort coordinates by Y order (y2 >= y1 >= y0)
  297. if (y0 > y1) {
  298. swap(y0, y1); swap(x0, x1);
  299. }
  300. if (y1 > y2) {
  301. swap(y2, y1); swap(x2, x1);
  302. }
  303. if (y0 > y1) {
  304. swap(y0, y1); swap(x0, x1);
  305. }
  306. if(y0 == y2) { // Handle awkward all-on-same-line case as its own thing
  307. a = b = x0;
  308. if(x1 < a) a = x1;
  309. else if(x1 > b) b = x1;
  310. if(x2 < a) a = x2;
  311. else if(x2 > b) b = x2;
  312. drawFastHLine(a, y0, b-a+1, color);
  313. return;
  314. }
  315. int16_t
  316. dx01 = x1 - x0,
  317. dy01 = y1 - y0,
  318. dx02 = x2 - x0,
  319. dy02 = y2 - y0,
  320. dx12 = x2 - x1,
  321. dy12 = y2 - y1,
  322. sa = 0,
  323. sb = 0;
  324. // For upper part of triangle, find scanline crossings for segments
  325. // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1
  326. // is included here (and second loop will be skipped, avoiding a /0
  327. // error there), otherwise scanline y1 is skipped here and handled
  328. // in the second loop...which also avoids a /0 error here if y0=y1
  329. // (flat-topped triangle).
  330. if(y1 == y2) {
  331. last = y1; // Include y1 scanline
  332. } else {
  333. last = y1-1; // Skip it
  334. }
  335. for(y = y0; y <= last; y++) {
  336. a = x0 + sa / dy01;
  337. b = x0 + sb / dy02;
  338. sa += dx01;
  339. sb += dx02;
  340. /* longhand:
  341. a = x0 + (x1 - x0) * (y - y0) / (y1 - y0);
  342. b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
  343. */
  344. if(a > b) {
  345. swap(a,b);
  346. }
  347. drawFastHLine(a, y, b - a + 1, color);
  348. }
  349. // For lower part of triangle, find scanline crossings for segments
  350. // 0-2 and 1-2. This loop is skipped if y1=y2.
  351. sa = dx12 * (y - y1);
  352. sb = dx02 * (y - y0);
  353. for(; y <= y2; y++) {
  354. a = x1 + sa / dy12;
  355. b = x0 + sb / dy02;
  356. sa += dx12;
  357. sb += dx02;
  358. /* longhand:
  359. a = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
  360. b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
  361. */
  362. if(a > b) {
  363. swap(a,b);
  364. }
  365. drawFastHLine(a, y, b-a+1, color);
  366. }
  367. }
  368. void drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, const C &color) {
  369. drawFastHLine(x + r, y, w - 2 * r, color); // Top
  370. drawFastHLine(x + r, y + h - 1, w - 2 * r, color); // Bottom
  371. drawFastVLine(x, y + r , h - 2 * r, color); // Left
  372. drawFastVLine(x + w - 1, y + r, h - 2 * r, color); // Right
  373. // draw four corners
  374. drawCircleHelper(x + r, y + r, r, 1, color);
  375. drawCircleHelper(x + w - r - 1, y + r, r, 2, color);
  376. drawCircleHelper(x + w - r - 1, y + h - r - 1, r, 4, color);
  377. drawCircleHelper(x + r, y + h - r - 1, r, 8, color);
  378. }
  379. void fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, const C &color) {
  380. fillRect(x + r, y, w - 2 * r, h, color);
  381. // draw four corners
  382. fillCircleHelper(x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color);
  383. fillCircleHelper(x + r, y + r, r, 2, h - 2 * r - 1, color);
  384. }
  385. void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, const C &color) {
  386. int16_t i, j, byteWidth = (w + 7) / 8;
  387. for(j = 0; j < h; j++) {
  388. for(i = 0; i < w; i++) {
  389. if(pgm_read_byte(bitmap + j * byteWidth + i / 8) & (128 >> (i & 7))) {
  390. drawPixel(x + i, y + j, color);
  391. }
  392. }
  393. }
  394. }
  395. static int16_t getWidth(void) { return W; }
  396. static int16_t getHeight(void) { return H; }
  397. // Text methods
  398. void setCursor(int16_t x, int16_t y) {
  399. cursor_x = x;
  400. line_start_x = x;
  401. cursor_y = y;
  402. }
  403. // get current cursor position (get rotation safe maximum values, using: width() for x, height() for y)
  404. int16_t getCursorX() const {
  405. return cursor_x;
  406. }
  407. int16_t getCursorY() const {
  408. return cursor_y;
  409. }
  410. void setTextColor(C color) {
  411. // Setting just a text color implies a transparent background, which is implied
  412. // by using the same color for background and foreground.
  413. text_color = text_bg_color = color;
  414. }
  415. void setTextColor(const C &foreground, const C &background) {
  416. text_color = foreground;
  417. text_bg_color = background;
  418. }
  419. void setTextSize(uint8_t new_size) {
  420. text_size = (new_size > 0) ? new_size : 1;
  421. }
  422. void setTextWrap(boolean _wrap) {
  423. wrap = _wrap;
  424. }
  425. void cp437(bool use_cp437 = true) {
  426. cp437 = use_cp437;
  427. }
  428. void setFont(const GFXfont &new_font) {
  429. font = (GFXfont *)&new_font;
  430. }
  431. void drawText(const char *str, int16_t x, int16_t y, uint8_t align=ALIGN_LEFT) {
  432. uint8_t string_length = strlen(str);
  433. Rect bounds = getTextBounds(str, x, y);
  434. // Store previous settings for cursor and wrapping
  435. uint8_t prev_cursor_x = cursor_x;
  436. uint8_t prev_cursor_y = cursor_y;
  437. bool prevWrap = wrap;
  438. // Set cursor and disable wrapping
  439. switch(align) {
  440. case ALIGN_LEFT:
  441. cursor_x = x;
  442. break;
  443. case ALIGN_CENTER:
  444. cursor_x = x - bounds.w / 2 - (bounds.x - x);
  445. break;
  446. case ALIGN_RIGHT:
  447. cursor_x = x - bounds.w;
  448. break;
  449. }
  450. cursor_y = y;
  451. wrap = false;
  452. print(str);
  453. // Restore previous cursor and wrap setting
  454. cursor_x = prev_cursor_x;
  455. cursor_y = prev_cursor_y;
  456. wrap = prevWrap;
  457. }
  458. uint16_t getTextWidth(const char *str) {
  459. return getTextBounds(str, 0, 0).w;
  460. }
  461. // Pass string and a cursor position, returns UL corner and W,H.
  462. Rect getTextBounds(const char *str, int16_t x, int16_t y) {
  463. uint8_t c; // Current character
  464. GFXglyph *glyph;
  465. uint8_t first = font->first;
  466. uint8_t last = font->last;
  467. int16_t min_x = W;
  468. int16_t min_y = H;
  469. int16_t max_x = -1;
  470. int16_t max_y = -1;
  471. // Bounding box coordinates for the current glyph
  472. int16_t glyph_x1 = 0;
  473. int16_t glyph_y1 = 0;
  474. int16_t glyph_x2 = 0;
  475. int16_t glyph_y2 = 0;
  476. while((c = *str++)) {
  477. if(c != '\n') { // Not a newline
  478. if((c == '\r') || (c < font->first) || (c > font->last)) {
  479. // Char not present in current font
  480. continue;
  481. }
  482. c -= font->first;
  483. glyph = &(font->glyph[c]);
  484. if(wrap && (x + (glyph->xOffset + glyph->width) * text_size >= W)) {
  485. // Line wrap
  486. x = line_start_x; // Reset x to 0
  487. y += font->yAdvance; // Advance y by 1 line
  488. }
  489. glyph_x1 = x + glyph->xOffset * text_size;
  490. glyph_y1 = y + glyph->yOffset * text_size;
  491. glyph_x2 = glyph_x1 + glyph->width * text_size - 1;
  492. glyph_y2 = glyph_y1 + glyph->height * text_size - 1;
  493. if(glyph_x1 < min_x) {
  494. min_x = glyph_x1;
  495. }
  496. if(glyph_y1 < min_y) {
  497. min_y = glyph_y1;
  498. }
  499. if(glyph_x2 > max_x) {
  500. max_x = glyph_x2;
  501. }
  502. if(glyph_y2 > max_y) {
  503. max_y = glyph_y2;
  504. }
  505. if ((font != &TomThumb) || *(str + 1)) {
  506. x += glyph->xAdvance * text_size;
  507. }
  508. } else { // Newline
  509. x = line_start_x; // Reset x
  510. y += font->yAdvance; // Advance y by 1 line
  511. }
  512. }
  513. return {min_x, min_y, max_x - min_x + 1, max_y - min_y + 1};
  514. }
  515. size_t write(uint8_t c) {
  516. if(!font) {
  517. return 1;
  518. }
  519. if(c == '\n') {
  520. cursor_x = line_start_x;
  521. cursor_y += (int16_t)text_size * (uint8_t)font->yAdvance;
  522. } else if(c != '\r') {
  523. uint8_t first = font->first;
  524. if((c >= first) && (c <= font->last)) {
  525. uint8_t c2 = c - font->first;
  526. GFXglyph *glyph = &(font->glyph[c2]);
  527. uint8_t w = glyph->width;
  528. uint8_t h = glyph->height;
  529. if((w > 0) && (h > 0)) { // Is there an associated bitmap?
  530. int16_t xo = glyph->xOffset; // sic
  531. if(wrap && ((cursor_x + text_size * (xo + w)) >= W)) {
  532. // Drawing character would go off right edge; wrap to new line
  533. cursor_x = line_start_x;
  534. cursor_y += (int16_t)text_size * font->yAdvance;
  535. }
  536. drawChar(cursor_x, cursor_y, c, text_color, text_bg_color, text_size);
  537. }
  538. cursor_x += glyph->xAdvance * (int16_t)text_size;
  539. }
  540. }
  541. return 1;
  542. }
  543. void drawChar(int16_t x, int16_t y, unsigned char c, C color, C bg, uint8_t size) {
  544. if (!font) {
  545. return;
  546. }
  547. // Character is assumed previously filtered by write() to eliminate
  548. // newlines, returns, non-printable characters, etc. Calling drawChar()
  549. // directly with 'bad' characters of font may cause mayhem!
  550. c -= font->first;
  551. GFXglyph *glyph = &(font->glyph[c]);
  552. uint8_t *bitmap = font->bitmap;
  553. uint16_t bo = glyph->bitmapOffset;
  554. uint8_t w = glyph->width;
  555. uint8_t h = glyph->height;
  556. int8_t xo = glyph->xOffset;
  557. int8_t yo = glyph->yOffset;
  558. uint8_t xx = 0;
  559. uint8_t yy = 0;
  560. uint8_t bits = 0;
  561. uint8_t bit = 0;
  562. int16_t xo16 = 0;
  563. int16_t yo16 = 0;
  564. if(size > 1) {
  565. xo16 = xo;
  566. yo16 = yo;
  567. }
  568. for(yy=0; yy<h; yy++) {
  569. for(xx=0; xx<w; xx++) {
  570. if(!(bit++ & 7)) {
  571. bits = bitmap[bo++];
  572. }
  573. if(bits & 0x80) {
  574. if(size == 1) {
  575. drawPixel(x+xo+xx, y+yo+yy, color);
  576. } else {
  577. fillRect(x+(xo16+xx)*size, y+(yo16+yy)*size, size, size, color);
  578. }
  579. }
  580. bits <<= 1;
  581. }
  582. }
  583. }
  584. void sendCommand(uint8_t commandByte, const uint8_t *dataBytes, uint8_t numDataBytes) {
  585. beginSPITransaction();
  586. if (numDataBytes) {
  587. sendCommandAndContinue(commandByte);
  588. for (uint8_t i=0; i<(numDataBytes-1); i++) {
  589. sendDataAndContinue(*dataBytes++); // Send the data bytes
  590. }
  591. sendLastData(*dataBytes);
  592. } else {
  593. sendLastCommand(commandByte);
  594. }
  595. endSPITransaction();
  596. }
  597. // Yeah, this is somewhere between silly and crazy.
  598. // Suggestions on how to include the implementations that work without getting rid of MEMBER_REQUIRES are more than welcome.
  599. #include "ssd1351_highcolor.inl"
  600. #include "ssd1351_lowcolor.inl"
  601. #include "ssd1351_indexedcolor.inl"
  602. #include "ssd1351_nobuffer.inl"
  603. #include "ssd1351_singlebuffer.inl"
  604. private:
  605. typedef std::array<C, W * H> ArrayType;
  606. MEMBER_REQUIRES(std::is_same<B, SingleBuffer>::value)
  607. __attribute__((always_inline)) ArrayType& frontBuffer() {
  608. static ArrayType buffer;
  609. return buffer;
  610. }
  611. // Pins
  612. uint8_t cs;
  613. uint8_t dc;
  614. uint8_t reset;
  615. uint8_t mosi;
  616. uint8_t sclk;
  617. // Magical registers (I think?) to make toggling DC pin super fast.
  618. uint8_t pcs_data, pcs_command;
  619. int16_t cursor_x;
  620. int16_t cursor_y;
  621. int16_t line_start_x;
  622. C text_color = black;
  623. C text_bg_color = black;
  624. uint8_t text_size = 1;
  625. bool wrap = true; // If set, 'wrap' text at right edge of display
  626. bool _cp437 = false; // If set, use correct CP437 charset (default is off)
  627. GFXfont *font = (GFXfont *)&TomThumb;
  628. // Teensy 3.x processors
  629. void __attribute__((always_inline)) setVideoRamPosition(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
  630. // Sets the active video RAM area of the display. After sending this command
  631. // (and sending the 'write to ram' command), color data can be sent do the display without
  632. // having to set the x/y address for each pixel. After each pixel, the display will internally
  633. // increment to point to the next pixel:
  634. // x0,y0 -> x0+1, y0, ..., x1,y0, x0,y0+1, x0+1,y0+1, ..., x1,y1
  635. sendCommandAndContinue(CMD_COLUMN_ADDRESS);
  636. sendDataAndContinue(x0);
  637. sendDataAndContinue(x1);
  638. sendCommandAndContinue(CMD_ROW_ADDRESS);
  639. sendDataAndContinue(y0);
  640. sendDataAndContinue(y1);
  641. }
  642. // ****
  643. // Low-level data pushing functions
  644. // ****
  645. #ifdef KINETISK
  646. void beginSPITransaction() __attribute__((always_inline)) {
  647. SPI.beginTransaction(spi_settings);
  648. }
  649. void endSPITransaction() __attribute__((always_inline)) {
  650. SPI.endTransaction();
  651. }
  652. void __attribute__((always_inline)) waitFifoNotFull() {
  653. uint32_t sr;
  654. uint32_t tmp __attribute__((unused));
  655. do {
  656. sr = KINETISK_SPI0.SR;
  657. if (sr & 0xF0) tmp = KINETISK_SPI0.POPR; // drain RX FIFO
  658. } while ((sr & (15 << 12)) > (3 << 12));
  659. }
  660. void __attribute__((always_inline)) waitTransmitComplete(uint32_t mcr) {
  661. uint32_t tmp __attribute__((unused));
  662. while (1) {
  663. uint32_t sr = KINETISK_SPI0.SR;
  664. if (sr & SPI_SR_EOQF) break; // wait for last transmit
  665. if (sr & 0xF0) tmp = KINETISK_SPI0.POPR;
  666. }
  667. KINETISK_SPI0.SR = SPI_SR_EOQF;
  668. SPI0_MCR = mcr;
  669. while (KINETISK_SPI0.SR & 0xF0) {
  670. tmp = KINETISK_SPI0.POPR;
  671. }
  672. }
  673. void __attribute__((always_inline)) sendCommandAndContinue(uint8_t command) {
  674. KINETISK_SPI0.PUSHR = command | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
  675. waitFifoNotFull();
  676. }
  677. void __attribute__((always_inline)) sendLastCommand(uint8_t command) {
  678. uint32_t mcr = SPI0_MCR;
  679. KINETISK_SPI0.PUSHR = command | (pcs_command << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ;
  680. waitTransmitComplete(mcr);
  681. }
  682. void __attribute__((always_inline)) sendDataAndContinue(uint8_t data) {
  683. KINETISK_SPI0.PUSHR = data | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_CONT;
  684. waitFifoNotFull();
  685. }
  686. void __attribute__((always_inline)) sendLastData(uint8_t data) {
  687. uint32_t mcr = SPI0_MCR;
  688. KINETISK_SPI0.PUSHR = data | (pcs_data << 16) | SPI_PUSHR_CTAS(0) | SPI_PUSHR_EOQ;
  689. waitTransmitComplete(mcr);
  690. }
  691. void __attribute__((always_inline)) sendDataAndContinue16(uint16_t data) {
  692. KINETISK_SPI0.PUSHR = data | (pcs_data << 16) | SPI_PUSHR_CTAS(1) | SPI_PUSHR_CONT;
  693. waitFifoNotFull();
  694. }
  695. void __attribute__((always_inline)) sendLastData16(uint16_t data) {
  696. uint32_t mcr = SPI0_MCR;
  697. KINETISK_SPI0.PUSHR = data | (pcs_data << 16) | SPI_PUSHR_CTAS(1) | SPI_PUSHR_EOQ;
  698. waitTransmitComplete(mcr);
  699. }
  700. #elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
  701. uint32_t _cspinmask;
  702. volatile uint32_t *_csport;
  703. uint32_t _spi_tcr_current;
  704. uint32_t _dcpinmask;
  705. uint8_t _pending_rx_count = 0;
  706. volatile uint32_t *_dcport;
  707. void __attribute__((always_inline)) DIRECT_WRITE_LOW(volatile uint32_t * base, uint32_t mask) __attribute__((always_inline)) {
  708. *(base+34) = mask;
  709. }
  710. void __attribute__((always_inline)) DIRECT_WRITE_HIGH(volatile uint32_t * base, uint32_t mask) __attribute__((always_inline)) {
  711. *(base+33) = mask;
  712. }
  713. void __attribute__((always_inline)) waitFifoNotFull() {
  714. uint32_t tmp __attribute__((unused));
  715. do {
  716. if ((IMXRT_LPSPI4_S.RSR & LPSPI_RSR_RXEMPTY) == 0) {
  717. tmp = IMXRT_LPSPI4_S.RDR; // Read any pending RX bytes in
  718. if (_pending_rx_count) _pending_rx_count--; //decrement count of bytes still levt
  719. }
  720. } while ((IMXRT_LPSPI4_S.SR & LPSPI_SR_TDF) == 0) ;
  721. }
  722. void __attribute__((always_inline)) waitTransmitComplete() {
  723. uint32_t tmp __attribute__((unused));
  724. while (_pending_rx_count) {
  725. if ((IMXRT_LPSPI4_S.RSR & LPSPI_RSR_RXEMPTY) == 0) {
  726. tmp = IMXRT_LPSPI4_S.RDR; // Read any pending RX bytes in
  727. _pending_rx_count--; //decrement count of bytes still levt
  728. }
  729. }
  730. IMXRT_LPSPI4_S.CR = LPSPI_CR_MEN | LPSPI_CR_RRF; // Clear RX FIFO
  731. }
  732. void __attribute__((always_inline)) waitTransmitComplete(uint32_t mcr) {
  733. waitTransmitComplete();
  734. }
  735. #define TCR_MASK (LPSPI_TCR_PCS(3) | LPSPI_TCR_FRAMESZ(31) | LPSPI_TCR_CONT | LPSPI_TCR_RXMSK )
  736. void maybeUpdateTCR(uint32_t requested_tcr_state) /*__attribute__((always_inline)) */ {
  737. if ((_spi_tcr_current & TCR_MASK) != requested_tcr_state) {
  738. bool dc_state_change = (_spi_tcr_current & LPSPI_TCR_PCS(3)) != (requested_tcr_state & LPSPI_TCR_PCS(3));
  739. _spi_tcr_current = (_spi_tcr_current & ~TCR_MASK) | requested_tcr_state ;
  740. // only output when Transfer queue is empty.
  741. if (!dc_state_change || !_dcpinmask) {
  742. while ((IMXRT_LPSPI4_S.FSR & 0x1f) ) ;
  743. IMXRT_LPSPI4_S.TCR = _spi_tcr_current; // update the TCR
  744. } else {
  745. waitTransmitComplete();
  746. if (requested_tcr_state & LPSPI_TCR_PCS(3)) DIRECT_WRITE_HIGH(_dcport, _dcpinmask);
  747. else DIRECT_WRITE_LOW(_dcport, _dcpinmask);
  748. IMXRT_LPSPI4_S.TCR = _spi_tcr_current & ~(LPSPI_TCR_PCS(3) | LPSPI_TCR_CONT); // go ahead and update TCR anyway?
  749. }
  750. }
  751. }
  752. void beginSPITransaction() __attribute__((always_inline)) {
  753. SPI.beginTransaction(spi_settings);
  754. if (_csport) {
  755. DIRECT_WRITE_LOW(_csport, _cspinmask);
  756. }
  757. }
  758. void endSPITransaction() __attribute__((always_inline)) {
  759. if (_csport)
  760. DIRECT_WRITE_HIGH(_csport, _cspinmask);
  761. SPI.endTransaction();
  762. }
  763. void __attribute__((always_inline)) sendCommandAndContinue(uint8_t command) {
  764. maybeUpdateTCR(LPSPI_TCR_PCS(0) | LPSPI_TCR_FRAMESZ(7) /*| LPSPI_TCR_CONT*/);
  765. IMXRT_LPSPI4_S.TDR = command;
  766. _pending_rx_count++; //
  767. waitFifoNotFull();
  768. }
  769. void __attribute__((always_inline)) sendLastCommand(uint8_t command) {
  770. maybeUpdateTCR(LPSPI_TCR_PCS(0) | LPSPI_TCR_FRAMESZ(7) /*| LPSPI_TCR_CONT*/);
  771. IMXRT_LPSPI4_S.TDR = command;
  772. _pending_rx_count++; //
  773. waitTransmitComplete();
  774. }
  775. void __attribute__((always_inline)) sendDataAndContinue(uint8_t data) {
  776. maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(7) | LPSPI_TCR_CONT);
  777. IMXRT_LPSPI4_S.TDR = data;
  778. _pending_rx_count++; //
  779. waitFifoNotFull();
  780. }
  781. void __attribute__((always_inline)) sendLastData(uint8_t data) {
  782. maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(7));
  783. IMXRT_LPSPI4_S.TDR = data;
  784. _pending_rx_count++; //
  785. waitTransmitComplete();
  786. }
  787. void __attribute__((always_inline)) sendDataAndContinue16(uint16_t data) {
  788. maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(15) | LPSPI_TCR_CONT);
  789. IMXRT_LPSPI4_S.TDR = data;
  790. _pending_rx_count++; //
  791. waitFifoNotFull();
  792. }
  793. void __attribute__((always_inline)) sendLastData16(uint16_t data) {
  794. maybeUpdateTCR(LPSPI_TCR_PCS(1) | LPSPI_TCR_FRAMESZ(15));
  795. IMXRT_LPSPI4_S.TDR = data;
  796. _pending_rx_count++; //
  797. waitTransmitComplete();
  798. }
  799. #endif
  800. };
  801. }