The Arduino Uno Q is a very powerful and feature rich device, however, some features are tricky to implement, TFT displays and SD cards included.
It took me a few days to determine what libraries to use to get my TFT display to work.
This is the display I wanted to use, since it was already in the usual Arduino shield configuration.
This code is a combination of DIYables example code, and MCUFRIEND_kbv example code.
/*
Arduino Uno Q with DIYables 3.5 inch 480 x 240 TFT display
CapperLabs 11 January 2026
*** To Do ***
*/
#include <Arduino.h>
#include <Arduino_RouterBridge.h>
#include <Adafruit_GFX.h> // Core graphics library
#include <DIYables_TFT_Shield.h>
#include <SD.h>
#include <Fonts/FreeSansBold24pt7b.h>
#define MAGENTA DIYables_TFT::colorRGB(255, 0, 255)
#define ORANGE DIYables_TFT::colorRGB(255, 165, 0)
#define PINK DIYables_TFT::colorRGB(255, 192, 203)
#define VIOLET DIYables_TFT::colorRGB(148, 0, 211)
#define TURQUOISE DIYables_TFT::colorRGB(64, 224, 208)
#define YELLOW DIYables_TFT::colorRGB(255, 255, 40)
#define BLACK 0x0000
#define RED 0xF800
#define BLUE 0x001F
#define GREEN 0x07E0
#define WHITE 0xFFFF
#define GREY 0x8410
#define BUFFPIXEL 20 // Buffer size remains the same
DIYables_TFT_ILI9486_Shield TFT_display;
#define SD_CS 10
File bmpFile;
uint16_t SCREEN_WIDTH;
uint16_t SCREEN_HEIGHT;
void showmsgXY(int x, int y, int sz, const GFXfont *f, const char *msg)
{
int16_t x1, y1;
uint16_t wid, ht;
TFT_display.setFont(f);
TFT_display.setCursor(x, y);
TFT_display.setTextColor(GREEN);
TFT_display.setTextSize(sz);
TFT_display.print(msg);
}
void setup() {
Bridge.begin();
Monitor.begin();
if (!SD.begin(SD_CS)) {
Monitor.println("SD card initialization failed!");
return;
}
Monitor.println("SD card initialized successfully.");
Monitor.println(F("TFT Test Jan2026"));
TFT_display.begin();
TFT_display.setFont(&FreeSansBold24pt7b);
// Set the rotation (0 to 3)
TFT_display.setRotation(1); // Rotate screen 90 degrees
// After rotation, update screen dimensions
SCREEN_WIDTH = TFT_display.width();
SCREEN_HEIGHT = TFT_display.height();
TFT_display.fillScreen(BLACK);
// Outlined round rectangle (Full Screen)
TFT_display.drawRoundRect(5, 5, 470, 310, 15, RED);
showmsgXY(130, 120, 1, &FreeSansBold24pt7b, "TFT Test");
showmsgXY(130, 200, 1, &FreeSansBold24pt7b, "Jan 2026");
// Show a .bmp file from SD card
// Get image dimensions
uint32_t imgWidth, imgHeight;
if (getBMPDimensions("mybmp.bmp", imgWidth, imgHeight)) {
Monitor.print("BMP Image Width: ");
Monitor.println(imgWidth);
Monitor.print("BMP Image Height: ");
Monitor.println(imgHeight);
} else {
Monitor.println("Failed to get BMP dimensions");
}
// Center the image based on its dimensions
int x = (SCREEN_WIDTH - imgWidth) / 2;
int y = (SCREEN_HEIGHT - imgHeight) / 2;
drawBMP("mybmpf.bmp", x, 220); // Draw image at calculated position
delay(2000);
}
void loop(void) {
delay(2000);
TFT_display.fillScreen(BLACK);
TFT_display.drawRoundRect(5, 5, 470, 310, 15, RED);
showmsgXY(130, 120, 1, &FreeSansBold24pt7b, "TFT Test");
showmsgXY(170, 200, 1, &FreeSansBold24pt7b, "Jan 2026");
drawBMP("mybmp.bmp", 120, 220); // Draw image at calculated position
delay(2000);
}
// Helper functions to read BMP file header
uint16_t read16(File &f) {
uint16_t result;
result = f.read();
result |= (f.read() << 8);
return result;
}
uint32_t read32(File &f) {
uint32_t result;
result = f.read();
result |= ((uint32_t)f.read() << 8);
result |= ((uint32_t)f.read() << 16);
result |= ((uint32_t)f.read() << 24);
return result;
}
// Function to read a signed 32-bit integer
int32_t readS32(File &f) {
int32_t result;
result = f.read();
result |= ((uint32_t)f.read() << 8);
result |= ((uint32_t)f.read() << 16);
result |= ((uint32_t)f.read() << 24);
return result;
}
// Function to draw BMP from SD card
void drawBMP(const char *filename, int x, int y) {
bmpFile = SD.open(filename);
if (!bmpFile) {
Monitor.println("File not found");
return;
}
if (read16(bmpFile) != 0x4D42) { // Check BMP signature
Monitor.println("Not a BMP file");
bmpFile.close();
return;
}
// Skip unnecessary BMP header details
Monitor.println("BMP signature OK");
uint32_t fileSize = read32(bmpFile);
Monitor.print("File Size: ");
Monitor.println(fileSize);
read32(bmpFile); // Reserved bytes (skip)
uint32_t imageOffset = read32(bmpFile); // Start of image data
Monitor.print("Image Data Offset: ");
Monitor.println(imageOffset);
uint32_t dibHeaderSize = read32(bmpFile); // DIB header size
Monitor.print("DIB Header Size: ");
Monitor.println(dibHeaderSize);
// Now read the width and height of the image
uint32_t bmpWidth = read32(bmpFile);
int32_t bmpHeight = readS32(bmpFile); // Read as signed 32-bit integer
Monitor.print("Image Width: ");
Monitor.println(bmpWidth);
Monitor.print("Image Height: ");
Monitor.println(bmpHeight);
bool topDown = true; // Flag to check if the image is top-down
if (bmpHeight < 0) {
bmpHeight = -bmpHeight; // Make height positive for processing
topDown = true; // Mark the BMP as top-down
}
if (read16(bmpFile) != 1) { // Planes must be 1
Monitor.println("Invalid BMP file");
bmpFile.close();
return;
}
uint16_t depth = read16(bmpFile); // Color depth
Monitor.print("Bit Depth: ");
Monitor.println(depth);
if (depth != 24) { // Only 24-bit BMP supported
Monitor.println("Only 24-bit BMP is supported");
bmpFile.close();
return;
}
if (read32(bmpFile) != 0) { // No compression
Monitor.println("Unsupported BMP compression");
bmpFile.close();
return;
}
// Move to the start of the image data
bmpFile.seek(imageOffset);
uint8_t sdbuffer[3 * BUFFPIXEL]; // Buffer for 20 pixels (3 bytes per pixel)
uint16_t color;
uint32_t rowSize = (bmpWidth * 3 + 3) & ~3; // BMP rows are padded to 4-byte boundaries
// Adjust x and y if image is larger than screen
if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) {
Monitor.println("Image position out of screen bounds");
return;
}
uint32_t maxRow = min(bmpHeight, SCREEN_HEIGHT - y);
uint32_t maxCol = min(bmpWidth, SCREEN_WIDTH - x);
// Draw the image
for (uint32_t row = 0; row < maxRow; row++) {
int32_t rowPos = topDown ? row : bmpHeight - 1 - row; // Adjust for top-down BMPs
uint32_t filePosition = imageOffset + rowPos * rowSize;
bmpFile.seek(filePosition); // Move to the correct row
for (uint32_t col = 0; col < maxCol; col += BUFFPIXEL) {
uint32_t pixelsToRead = min(BUFFPIXEL, maxCol - col); // Avoid reading beyond row width
bmpFile.read(sdbuffer, 3 * pixelsToRead); // Read multiple pixels at once
for (uint32_t i = 0; i < pixelsToRead; i++) {
uint8_t b = sdbuffer[i * 3];
uint8_t g = sdbuffer[i * 3 + 1];
uint8_t r = sdbuffer[i * 3 + 2];
color = DIYables_TFT::colorRGB(r, g, b);
// Draw pixel on screen if within bounds
if ((x + col + i) < SCREEN_WIDTH && (y + row) < SCREEN_HEIGHT) {
TFT_display.drawPixel(x + col + i, y + row, color);
}
}
}
}
bmpFile.close(); // Close file when done
Monitor.println("Finished drawing BMP");
}
// Function to get BMP image dimensions
bool getBMPDimensions(const char *filename, uint32_t &width, uint32_t &height) {
File bmpFile = SD.open(filename);
if (!bmpFile) {
Monitor.println("File not found");
return false;
}
if (read16(bmpFile) != 0x4D42) { // Check BMP signature
Monitor.println("Not a BMP file");
bmpFile.close();
return false;
}
read32(bmpFile); // Skip file size
read32(bmpFile); // Skip reserved bytes
uint32_t imageOffset = read32(bmpFile); // Start of image data
uint32_t dibHeaderSize = read32(bmpFile); // DIB header size
// Read the width and height of the image
width = read32(bmpFile);
int32_t bmpHeight = readS32(bmpFile); // May be negative for top-down images
height = (bmpHeight < 0) ? -bmpHeight : bmpHeight;
bmpFile.close(); // Close the file
return true; // Success
delay(20000);
}The way I approached this task was to configure two systems, one with an Arduino Uno and a TFT display, and the second with an Arduino Uno Q with a TFT display. Then I tried to compile and run each of the example programs included with the various TFT and GFX libraries I had installed into the Arduino IDE on each system to see what features would compile, and which would not compile.
When an example file successfully compiled as received, I saved that file with an extension "WorksWithUno" or "WorksWithQ" as a first pass.
After that, I combined code from each working model to get the display working with correct colors and fonts, and the SD file system working while displaying a local bitmap file on the screen.
I also needed to install the Arduino serial monitor bridge code and change all the serial.println("") code to Monitor.print("") code needed for the Q interface to App Lab.
There was one compile error I fixed by editing this file:
\AppData\Local\Arduino15\libraries\SD\src/utility/SdFatUtil.h:40:15: error: conflicting declaration 'int __bss_end'
extern int __bss_end;
I changed it to:
extern char __bss_end[]; and it compiled.
The library MCUFRIEND_kbv works well on the UNO, but will not compile for the Q.
Now that I have a working TFT display, I will try and share data across the "bridge" between the Python program and the Arduino sketch....to be continued...







Comments