F439_CPP_SPI_RA8875_TFT_LCD_04 1.0
STM32F439 SPI RA8875 7-inch TFT display with FRAM-backed touch calibration, built on the STM32_GFX, STM32_RA8875 and STM32_MB85RS64V libraries
Loading...
Searching...
No Matches
entryPointCPP.cpp
1/*
2 * entryPointCPP.cpp
3 *
4 * Created on: Jan 26, 2025
5 * Author: johng
6 */
7
8#include <stdio.h>
9#include <string.h>
10#include <new>
11#include "entryPointCPP.hpp"
12#include "Adafruit_RA8875.h"
13#include "MB85RS64V.hpp"
15#ifdef TOUCH_CALIBRATION_ENABLE
16#include "touchCalibration.hpp"
17#endif
18
20Adafruit_RA8875 *tft = nullptr;
21
23static MB85RS64V *fram = nullptr;
24
26static tsMatrix_t touchMatrix;
27static bool touchMatrixValid = false;
28
59void initTest(SPI_HandleTypeDef *halSPI) {
60 // nothrow: return nullptr on failure instead of throwing, then check it before use
61 tft = new (std::nothrow) Adafruit_RA8875(halSPI, RA8875_CS_GPIO_Port, RA8875_CS_Pin,
62 LCD_RESET_GPIO_Port, LCD_RESET_Pin);
63 if (tft == nullptr) {
64 printf("RA8875 object could not be allocated (out of heap)\r\n");
65 Error_Handler();
66 return;
67 }
68
69 if (tft->begin(RA8875_800x480)) {
70 printf("RA8875 Found\r\n");
71 } else {
72 printf("RA8875 not found (expected chip ID 0x75)\r\n");
73 Error_Handler();
74 return;
75 }
76}
77
104void testLCD(bool buildTest) {
105 if (tft == nullptr) { // initTest() has not run yet; fail loudly instead of dereferencing NULL
106 printf("ERROR: testLCD() called before initTest()\r\n");
107 Error_Handler();
108 }
109
110 if (buildTest) {
111 printf("================ Graphics Mode =============\r\n");
112
113 tft->displayOn(true);
114 HAL_Delay(1);
115 tft->GPIOX(true);
116 HAL_Delay(1);
118 tft->PWM1out(255);
119 HAL_Delay(1);
120
121 tft->graphicsMode();
122
123 printf("FillSreen White\r\n");
125
126 HAL_Delay(1000);
127#ifdef PWM
128 // Play with PWM
129 printf("Start: Play with PWM\r\n");
130
131 for (uint8_t i=255; i!=0; i-=5 )
132 {
133 tft->PWM1out(i);
134 delay(100);
135 }
136 for (uint8_t i=0; i!=255; i+=5 )
137 {
138 tft->PWM1out(i);
139 delay(100);
140 }
141 tft->PWM1out(255);
142
143 printf("END: Play with PWM\r\n");
144 HAL_Delay(2000);
145#endif
146 printf("FillSreen Red\r\n");
148 HAL_Delay(1000);
149 printf("FillSreen Yellow\r\n");
151 HAL_Delay(1000);
152 printf("FillSreen Green\r\n");
154 HAL_Delay(1000);
155 printf("FillSreen Cyan\r\n");
157 HAL_Delay(1000);
158 printf("FillSreen Magenta\r\n");
160 HAL_Delay(1000);
161 printf("FillSreen Black\r\n");
163 HAL_Delay(1000);
164
165 // Try some GFX acceleration!
166 printf("drawCircle\r\n");
167 tft->drawCircle(100, 100, 50, RA8875_BLACK);
168 printf("fillCircle\r\n");
169 tft->fillCircle(100, 100, 49, RA8875_GREEN);
170
171 HAL_Delay(1000);
172
173 printf("fillRect\r\n");
174 tft->fillRect(11, 11, 398, 198, RA8875_BLUE);
175 HAL_Delay(1000);
176 printf("drawRect\r\n");
177 tft->drawRect(10, 10, 400, 200, RA8875_GREEN);
178 HAL_Delay(1000);
179 printf("fillRoundRect\r\n");
180 tft->fillRoundRect(200, 10, 200, 100, 10, RA8875_RED);
181 HAL_Delay(1000);
182 printf("drawPixel\r\n");
183 tft->drawPixel(10,10,RA8875_BLACK);
184 HAL_Delay(1000);
185 printf("drawPixel\r\n");
186 tft->drawPixel(11,11,RA8875_BLACK);
187 HAL_Delay(1000);
188 printf("drawLine\r\n");
189 tft->drawLine(10, 10, 200, 100, RA8875_RED);
190 HAL_Delay(1000);
191 printf("drawTriangle\r\n");
192 tft->drawTriangle(200, 15, 250, 100, 150, 125, RA8875_BLACK);
193 HAL_Delay(1000);
194 printf("fillTriangle\r\n");
195 tft->fillTriangle(200, 16, 249, 99, 151, 124, RA8875_YELLOW);
196 HAL_Delay(1000);
197 printf("drawEllipse\r\n");
198 tft->drawEllipse(300, 100, 100, 40, RA8875_BLACK);
199 HAL_Delay(1000);
200 printf("fillEllipse\r\n");
201 tft->fillEllipse(300, 100, 98, 38, RA8875_GREEN);
202 HAL_Delay(1000);
203 // Argument 5 (curvePart) is a 2-bit value to control each corner (select 0, 1, 2, or 3)
204 printf("drawCurve\r\n");
205 tft->drawCurve(50, 100, 80, 40, 2, RA8875_BLACK);
206 HAL_Delay(1000);
207 printf("fillCurve\r\n");
208 tft->fillCurve(50, 100, 78, 38, 2, RA8875_WHITE);
209 } else {
210 printf("================ Text Mode =============\r\n");
211 tft->displayOn(true);
212 tft->GPIOX(true); // Enable TFT - display enable tied to GPIOX
213 tft->PWM1config(true, RA8875_PWM_CLK_DIV1024); // PWM output for backlight
214 tft->PWM1out(255);
215 printf("fillScreen\r\n");
217
218 /* Switch to text mode */
219 tft->textMode();
220 tft->cursorBlink(32);
221
222
223 /* Set a solid for + bg color ... */
224
225 /* ... or a fore color plus a transparent background */
226
227
228 /* Set the cursor location (in pixels) */
229 tft->textSetCursor(10, 10);
230
231 /* Render some text! */
232 printf("Render lots of Texts\r\n");
233 char string[80] = "Hello, World! ";
235 tft->textWrite(string);
236 HAL_Delay(500);
238 tft->textWrite(string);
239 HAL_Delay(500);
241 tft->textWrite(string);
242 HAL_Delay(500);
244 tft->textWrite(string);
245 HAL_Delay(500);
247 tft->textWrite(string);
248 HAL_Delay(500);
250 tft->textWrite(string);
251
252 HAL_Delay(1000);
253
254 tft->textSetCursor(100, 100);
255 tft->textEnlarge(1);
257 tft->textWrite(string);
258
259 tft->textSetCursor(100, 150);
260 tft->textEnlarge(2);
262 tft->textWrite(string);
263
264 tft->textSetCursor(100, 200);
265 tft->textEnlarge(3);
267 tft->textWrite(string);
268
269 tft->textSetCursor(100, 300);
270 tft->textEnlarge(0);
272 tft->textWrite(string);
273 }
274}
275
300void initFRAM(SPI_HandleTypeDef *halSPI) {
301 // nothrow: return nullptr on failure instead of throwing, then check it before use
302 fram = new (std::nothrow) MB85RS64V(halSPI, MB85RS64_CS_GPIO_Port, MB85RS64_CS_Pin);
303 if (fram == nullptr) {
304 printf("MB85RS64V object could not be allocated (out of heap)\r\n");
305 Error_Handler();
306 return;
307 }
308
309 if (fram->begin()) {
310 // begin() has already checked the ID once. Read it again to print it, and check that read too:
311 // a transfer can complete without error and still return wrong bytes (seen once as 00 00 00 00),
312 // so a mismatch is retried a few times before it is treated as a failure
313 const uint8_t expectedId[MB85RS64V::ID_LENGTH] = { 0x04, 0x7F, 0x03, 0x02 };
314 const int ID_READ_ATTEMPTS = 3;
315 uint8_t id[MB85RS64V::ID_LENGTH] = { 0 };
316
317 for (int attempt = 1; attempt <= ID_READ_ATTEMPTS; attempt++) {
318 if (!fram->readId(id)) {
319 printf("MB85RS64V ID read failed after begin() (HAL status %d), attempt %d of %d\r\n",
320 (int) fram->lastStatus(), attempt, ID_READ_ATTEMPTS);
321 } else if (memcmp(id, expectedId, sizeof(expectedId)) != 0) {
322 printf("MB85RS64V ID read gave %02X %02X %02X %02X (expected 04 7F 03 02), attempt %d of %d\r\n",
323 id[0], id[1], id[2], id[3], attempt, ID_READ_ATTEMPTS);
324 } else {
325 printf("MB85RS64V Found, ID: %02X %02X %02X %02X\r\n", id[0], id[1], id[2], id[3]);
326 return;
327 }
328 HAL_Delay(10);
329 }
330 printf("MB85RS64V ID could not be read correctly after %d attempts\r\n", ID_READ_ATTEMPTS);
331 Error_Handler();
332 return;
333 }
334 printf("MB85RS64V not found (expected ID 04 7F 03 02): check HOLD/WP, CS (PB10) and power\r\n");
335
336 // Show what the chip actually answered, to tell the likely causes apart:
337 // FF FF FF FF = nothing driving MISO (no power, HOLD low, CS not wired), 00 00 00 00 = MISO held low
338 uint8_t id[MB85RS64V::ID_LENGTH] = { 0 };
339 const bool idRead = fram->readId(id);
340 printf(" ID read %s, bytes: %02X %02X %02X %02X, last HAL status %d\r\n", idRead ? "OK" : "FAILED",
341 id[0], id[1], id[2], id[3], (int) fram->lastStatus());
342 printf(" SPI1: mode %s, polarity %s, phase %s, data size %s, NSS %s, prescaler 0x%02lX\r\n",
343 halSPI->Init.Mode == SPI_MODE_MASTER ? "master" : "NOT master",
344 halSPI->Init.CLKPolarity == SPI_POLARITY_LOW ? "low" : "high",
345 halSPI->Init.CLKPhase == SPI_PHASE_1EDGE ? "1 edge" : "2 edge",
346 halSPI->Init.DataSize == SPI_DATASIZE_8BIT ? "8-bit" : "NOT 8-bit",
347 halSPI->Init.NSS == SPI_NSS_SOFT ? "soft" : "NOT soft",
348 (unsigned long) halSPI->Init.BaudRatePrescaler);
349
350 // RDSR does not depend on the chip supporting RDID: its bit 0 always reads 0, so FF means no answer at all,
351 // while a value like 00 means the chip is alive and only the RDID command is the problem
352 uint8_t status = 0xFF;
353 const bool statusRead = fram->readStatus(&status);
354 printf(" Status register read %s: %02X (FF = no answer, 00..8C = chip is answering)\r\n",
355 statusRead ? "OK" : "FAILED", status);
356 Error_Handler();
357 return;
358}
359
377void testFRAM(void) {
378 if (fram == nullptr) {
379 printf("ERROR: testFRAM() called before a successful initFRAM()\r\n");
380 Error_Handler();
381 return;
382 }
383
384 uint8_t before[16];
385 if (!fram->read(0x0000, before, sizeof(before))) {
386 printf("FRAM read failed (HAL status %d)\r\n", (int) fram->lastStatus());
387 Error_Handler();
388 return;
389 }
390 printf("FRAM at 0x0000 before this boot:");
391 for (size_t i = 0; i < sizeof(before); i++) {
392 printf(" %02X", before[i]);
393 }
394 printf("\r\n");
395
396 // The first byte counts boots; the rest is a fixed pattern
397 uint8_t out[16] = { (uint8_t) (before[0] + 1), 0xA5, 0x5A, 0xFF, 0x00, 'M', 'C', 'T', 'F', 'R', 'A', 'M', 1, 2, 3, 4 };
398 if (!fram->write(0x0000, out, sizeof(out))) {
399 printf("FRAM write failed (HAL status %d)\r\n", (int) fram->lastStatus());
400 Error_Handler();
401 return;
402 }
403
404 uint8_t in[16] = { 0 };
405 if (!fram->read(0x0000, in, sizeof(in))) {
406 printf("FRAM read-back failed (HAL status %d)\r\n", (int) fram->lastStatus());
407 Error_Handler();
408 return;
409 }
410 printf("FRAM read back: ");
411 for (size_t i = 0; i < sizeof(in); i++) {
412 printf(" %02X", in[i]);
413 }
414 printf("\r\n");
415
416 for (size_t i = 0; i < sizeof(out); i++) {
417 if (in[i] != out[i]) {
418 printf("FRAM read-back MISMATCH at byte %u\r\n", (unsigned) i);
419 Error_Handler();
420 return;
421 }
422 }
423 printf("FRAM write/read-back OK, boot count %u\r\n", (unsigned) out[0]);
424}
425
443 touchMatrixValid = false;
444
445 if (tft == nullptr || fram == nullptr) {
446 printf("ERROR: initTouchCalibration() needs initTest() and initFRAM() to have run first\r\n");
447 Error_Handler();
448 return false;
449 }
450
451 printf("Reading the stored touch calibration from the FRAM\r\n");
453
454 if (!touchCalibrationLoad(tft, fram, &touchMatrix)) {
455 // touchCalibrationLoad() has already printed the reason
456 printf("No usable touch calibration in the FRAM: define TOUCH_CALIBRATION_ENABLE in main.h and calibrate\r\n");
457 return false;
458 }
459
460 touchMatrixValid = true;
461 printf("Touch calibration read from the FRAM: An=%ld Bn=%ld Cn=%ld Dn=%ld En=%ld Fn=%ld Divider=%ld\r\n",
462 (long) touchMatrix.An, (long) touchMatrix.Bn, (long) touchMatrix.Cn, (long) touchMatrix.Dn,
463 (long) touchMatrix.En, (long) touchMatrix.Fn, (long) touchMatrix.Divider);
464 return true;
465}
466
467#ifdef TOUCH_CALIBRATION_ENABLE
468void testTouch(UART_HandleTypeDef *huart) {
469 const uint32_t DEMO_MS = 15000; // how long the touch-to-draw demo runs
470
471 if (tft == nullptr) {
472 printf("ERROR: testTouch() called before initTest()\r\n");
473 Error_Handler();
474 return;
475 }
476
477 // begin() does not switch the display on; testLCD() does that, but it runs after this function,
478 // so the display and its backlight must be enabled here before anything is drawn
479 tft->displayOn(true);
480 tft->GPIOX(true); // Enable TFT - display enable tied to GPIOX
482 tft->PWM1out(255);
483
484 tft->touchEnable(true);
485
486 if (huart == nullptr) {
487 printf("ERROR: testTouch() needs the terminal UART handle\r\n");
488 Error_Handler();
489 return;
490 }
491
492 if (fram == nullptr) {
493 printf("ERROR: testTouch() needs the FRAM: initFRAM() has not run\r\n");
494 Error_Handler();
495 return;
496 }
498
499 tsMatrix_t matrix;
500
501 // Calibration mode always calibrates and overwrites whatever the FRAM holds
502 if (!touchCalibrationRun(tft, huart, &matrix)) {
503 printf("Touch calibration failed after repeated attempts\r\n");
504 Error_Handler();
505 return;
506 }
507 if (!touchCalibrationSave(tft, fram, &matrix)) {
508 printf("Touch calibration could not be stored: the data read back from the FRAM differs\r\n");
509 Error_Handler();
510 return;
511 }
512 printf("Touch calibration stored in the FRAM\r\n");
513 printf("Touch calibration done: An=%ld Bn=%ld Cn=%ld Dn=%ld En=%ld Fn=%ld Divider=%ld\r\n",
514 (long) matrix.An, (long) matrix.Bn, (long) matrix.Cn, (long) matrix.Dn, (long) matrix.En,
515 (long) matrix.Fn, (long) matrix.Divider);
516
517 printf("Touch the screen: red dots follow your finger for %lu seconds\r\n", (unsigned long) (DEMO_MS / 1000));
519
520 const uint32_t start = HAL_GetTick();
521 while ((HAL_GetTick() - start) < DEMO_MS) {
522 if (tft->touched()) {
523 uint16_t rx = 0;
524 uint16_t ry = 0;
525 tft->touchRead(&rx, &ry);
526
527 tsPoint_t raw = { (int32_t) rx, (int32_t) ry };
528 tsPoint_t pos = { 0, 0 };
529 if (!touchCalibrationApply(&matrix, &raw, &pos)) {
530 printf("Touch conversion failed (divider is zero)\r\n");
531 Error_Handler();
532 return;
533 }
534
535 printf("raw %u,%u -> screen %ld,%ld\r\n", (unsigned) rx, (unsigned) ry, (long) pos.x, (long) pos.y);
536 if (pos.x >= 0 && pos.x < tft->width() && pos.y >= 0 && pos.y < tft->height()) {
537 tft->fillCircle((int16_t) pos.x, (int16_t) pos.y, 4, RA8875_RED);
538 }
539 }
540 HAL_Delay(5);
541 }
542}
543#endif /* TOUCH_CALIBRATION_ENABLE */
#define RA8875_GREEN
Green Color.
#define RA8875_YELLOW
Yellow Color.
#define RA8875_MAGENTA
Magenta Color.
#define RA8875_CYAN
Cyan Color.
#define RA8875_PWM_CLK_DIV1024
See datasheet.
#define RA8875_BLACK
Black Color.
#define RA8875_WHITE
White Color.
#define RA8875_RED
Red Color.
#define RA8875_BLUE
Blue Color.
@ RA8875_800x480
Class that stores state and functions for interacting with the RA8875 display controller.
void GPIOX(boolean on)
void fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color)
boolean touchRead(uint16_t *x, uint16_t *y)
void drawPixel(int16_t x, int16_t y, uint16_t color)
void touchEnable(boolean on)
void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color)
void fillCircle(int16_t x, int16_t y, int16_t r, uint16_t color)
void fillScreen(uint16_t color)
void fillCurve(int16_t xCenter, int16_t yCenter, int16_t longAxis, int16_t shortAxis, uint8_t curvePart, uint16_t color)
void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color)
void fillEllipse(int16_t xCenter, int16_t yCenter, int16_t longAxis, int16_t shortAxis, uint16_t color)
void cursorBlink(uint8_t rate)
boolean begin(enum RA8875sizes s)
void drawCircle(int16_t x, int16_t y, int16_t r, uint16_t color)
void textEnlarge(uint8_t scale)
void textSetCursor(uint16_t x, uint16_t y)
uint16_t height(void)
void PWM1out(uint8_t p)
void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color)
void drawCurve(int16_t xCenter, int16_t yCenter, int16_t longAxis, int16_t shortAxis, uint8_t curvePart, uint16_t color)
void textColor(uint16_t foreColor, uint16_t bgColor)
boolean touched(void)
void displayOn(boolean on)
uint16_t width(void)
void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color)
void textTransparent(uint16_t foreColor)
void PWM1config(boolean on, uint8_t clock)
void textWrite(const char *buffer, uint16_t len=0)
void drawEllipse(int16_t xCenter, int16_t yCenter, int16_t longAxis, int16_t shortAxis, uint16_t color)
Driver for one MB85RS64V SPI FRAM (8192 bytes) on an STM32 HAL SPI bus.
Definition MB85RS64V.hpp:78
bool read(uint16_t address, uint8_t *buffer, size_t length)
Reads bytes from the FRAM array (READ, 03h).
static constexpr size_t ID_LENGTH
Number of bytes returned by readId().
Definition MB85RS64V.hpp:84
bool readStatus(uint8_t *status)
Reads the status register (RDSR, 05h).
HAL_StatusTypeDef lastStatus() const
HAL status of the most recent failed SPI call (HAL_OK if none has failed). Because a failed command i...
bool write(uint16_t address, const uint8_t *data, size_t length)
Writes bytes to the FRAM array (WREN, then WRITE, 02h); a retry repeats both steps.
bool begin()
Checks the SPI setup and the chip's identity, and leaves writes disabled.
bool readId(uint8_t *id)
Reads the four-byte device ID (RDID, 9Fh).
C-callable entry points into the C++ RA8875 display code.
bool initTouchCalibration(void)
Reads the touch calibration stored in the FRAM and reports it on the redirected USART.
void testFRAM(void)
Shows that the FRAM keeps its data, then does a write and read-back test.
void testLCD(bool buildTest)
Runs one of the two display demonstrations.
void initTest(SPI_HandleTypeDef *halSPI)
Creates the display object and starts the RA8875 controller.
void initFRAM(SPI_HandleTypeDef *halSPI)
Creates the FRAM driver object and checks that the chip answers.
void testTouch(UART_HandleTypeDef *huart)
Enables touch, runs the three-point calibration and shows the result.
int32_t y
int32_t x
int32_t Divider
Stores the touch calibration matrix in the MB85RS64V FRAM and checks it when loading.
bool touchCalibrationSave(Adafruit_RA8875 *display, MB85RS64V *fram, tsMatrix_t *matrix)
Saves the calibration matrix to the FRAM and checks it by reading it back.
bool touchCalibrationLoad(Adafruit_RA8875 *display, MB85RS64V *fram, tsMatrix_t *matrix)
Loads the calibration matrix from the FRAM if a valid one is stored.
void touchCalibrationAttachStorage(Adafruit_RA8875 *display, MB85RS64V *fram)
Registers the FRAM read and write callbacks with the display library.
Three-point touch-screen calibration for the RA8875's resistive panel.
bool touchCalibrationRun(Adafruit_RA8875 *display, UART_HandleTypeDef *uart, tsMatrix_t *matrix)
Runs the interactive calibration: draws three targets one at a time, waits for the user to touch each...
bool touchCalibrationApply(const tsMatrix_t *matrix, const tsPoint_t *raw, tsPoint_t *display)
Converts one raw touch reading into a display pixel position.