IMPORTANT
This demo uses the following components:
- BT817 Controller PCB
- NHD-FT81x-SHIELD for Arduino
- Arduino Uno
- 40-pin breakout board
NOTES
LCD needs to initialize first before initializing EVE. 2 SPI interfaces are needed. In this demo, native SPI pins and bit banging are used for the 2 SPI interfaces.
OUTPUT
/******************************************************************************
*
Copyright (c) 2026 - Newhaven Display International, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
* NHD_4_0_480480AF_ASXP_CTP Example Code
*
* Description: Arduino example code for the Newhaven Display
* NHD-4.0-480480AF-ASXP-CTP
*
* The demo showcases a GUI with interactive menus. The menus include:
* - Clock
* - Settings
* - Temperature Control
*
* !! IMPORTANT !!
* The demo uses the following components:
* - BT817 EVE Controller PCB
* - NHD-FT81x-SHIELD for Arduino
* - Arduino Uno
* - 40-pin breakout board
*
* Pinout for LCD init:
* - (NHD-4.0-480480AF-ASXP-CTP) RESET -> A2 (Arduino Uno)
* - (NHD-4.0-480480AF-ASXP-CTP) CS -> 2 (Arduino Uno)
* - (NHD-4.0-480480AF-ASXP-CTP) DC -> 4 (Arduino Uno)
* - (NHD-4.0-480480AF-ASXP-CTP) SCL -> 6 (Arduino Uno)
* - (NHD-4.0-480480AF-ASXP-CTP) SDA -> 7 (Arduino Uno)
*
* Libraries Required:
* - SPI.h - From Arduino
* - Gameduino 2
*
*
* Notes:
* - LCD needs to initialize first before initializing EVE. 2 SPI interfaces are needed.
* In this demo, native SPI pins and bit banging are used for the 2 SPI interfaces.
*
*
******************************************************************************/
#include <SPI.h>
#include <GD2.h>
// Pinout
#define RST A2
#define CS 2
#define DC 4
#define SCL 6
#define SDA 7
// Resolution for the display
int _hsize = 480;
int _vsize = 480;
int _screen = 0;
// Store temperature value for thermostat screen
unsigned long _temperature = 0;
// Brightness value for PWM
unsigned long _brightness = 65478;
// Start clock at 11:53
unsigned int hours = 11;
unsigned int minutes = 53;
unsigned int seconds = 30;
unsigned long previousMillis = 0;
const long interval = 1000;
// Initialize Embedded Video Controller
void init_480X480() {
digitalWrite(RST, LOW);
delay(20);
digitalWrite(RST, HIGH);
delay(100);
_lcd_init();
// Horizontal timing (pixels)
GD.wr16(REG_HSIZE, 480); // Active pixels
GD.wr16(REG_HCYCLE, 790); // 10 + 150 + 480 + 150
GD.wr16(REG_HSYNC0, 0); // HSYNC start
GD.wr16(REG_HSYNC1, 10); // HSYNC end (width = 10)
GD.wr16(REG_HOFFSET, 160); // HSYNC(10) + HBP(150)
// Vertical timing (lines)
GD.wr16(REG_VSIZE, 480); // Active lines
GD.wr16(REG_VCYCLE, 530); // 10 + 20 + 480 + 20
GD.wr16(REG_VSYNC0, 0); // VSYNC start
GD.wr16(REG_VSYNC1, 10); // VSYNC end (width = 10)
GD.wr16(REG_VOFFSET, 30); // VSYNC(10) + VBP(20)
// Pixel clock and signal polarity
GD.wr16(REG_PCLK, 2);
GD.wr16(REG_SWIZZLE, 0);
GD.wr16(REG_PCLK_POL, 0);
GD.wr16(REG_CSPREAD, 0);
GD.wr16(REG_DITHER, 1);
GD.wr16(REG_ROTATE, 0);
GD.wr16(REG_PWM_HZ, 5000);
GD.swap();
}
// Bit bang SPI command for LCD initialization
void _lcd_com(unsigned char c) {
digitalWrite(CS, LOW);
digitalWrite(DC, LOW);
for (int i = 0; i < 8; i++) {
if ((c & 0x80) == 0x80) {
digitalWrite(SDA, HIGH);
} else {
digitalWrite(SDA, LOW);
}
c = (c << 1); // Shift byte
digitalWrite(SCL, HIGH);
digitalWrite(SCL, LOW);
digitalWrite(SCL, HIGH);
}
digitalWrite(CS, HIGH);
}
// Bit bang SPI data for LCD initialization
void _lcd_dat(unsigned char d) {
digitalWrite(CS, LOW);
digitalWrite(DC, HIGH);
for (int i = 0; i < 8; i++) {
if ((d & 0x80) == 0x80) {
digitalWrite(SDA, HIGH);
} else {
digitalWrite(SDA, LOW);
}
d = (d << 1); // Shift byte
digitalWrite(SCL, HIGH);
digitalWrite(SCL, LOW);
digitalWrite(SCL, HIGH);
}
digitalWrite(CS, HIGH);
}
// Initializate LCD TFT
void _lcd_init() {
_lcd_com(0xFF);
_lcd_dat(0x77);
_lcd_dat(0x01);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x13);
_lcd_com(0xEF);
_lcd_dat(0x08);
_lcd_com(0xFF);
_lcd_dat(0x77);
_lcd_dat(0x01);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x10);
_lcd_com(0xC0);
_lcd_dat(0x3B);
_lcd_dat(0x00);
_lcd_com(0xC1);
_lcd_dat(0x0D);
_lcd_dat(0x02);
_lcd_com(0xC2);
_lcd_dat(0x21);
_lcd_dat(0x08);
_lcd_com(0xC7);
_lcd_dat(0x00);
_lcd_com(0xCC);
_lcd_dat(0x18);
_lcd_com(0xB0);
_lcd_dat(0x00);
_lcd_dat(0x13);
_lcd_dat(0x1E);
_lcd_dat(0x0E);
_lcd_dat(0x11);
_lcd_dat(0x05);
_lcd_dat(0x09);
_lcd_dat(0x07);
_lcd_dat(0x07);
_lcd_dat(0x23);
_lcd_dat(0x04);
_lcd_dat(0x12);
_lcd_dat(0x0F);
_lcd_dat(0xA7);
_lcd_dat(0x2C);
_lcd_dat(0x18);
_lcd_com(0xB1);
_lcd_dat(0x00);
_lcd_dat(0x14);
_lcd_dat(0x1B);
_lcd_dat(0x0E);
_lcd_dat(0x11);
_lcd_dat(0x06);
_lcd_dat(0x06);
_lcd_dat(0x08);
_lcd_dat(0x07);
_lcd_dat(0x20);
_lcd_dat(0x04);
_lcd_dat(0x12);
_lcd_dat(0x11);
_lcd_dat(0xA5);
_lcd_dat(0x2F);
_lcd_dat(0x18);
_lcd_com(0xFF);
_lcd_dat(0x77);
_lcd_dat(0x01);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x11);
_lcd_com(0xB0);
_lcd_dat(0x60);
_lcd_com(0xB1);
_lcd_dat(0x31);
_lcd_com(0xB2);
_lcd_dat(0x8A);
_lcd_com(0xB3);
_lcd_dat(0x80);
_lcd_com(0xB5);
_lcd_dat(0x4B);
_lcd_com(0xB7);
_lcd_dat(0x85);
_lcd_com(0xB8);
_lcd_dat(0x21);
_lcd_com(0xC0);
_lcd_dat(0x07);
_lcd_com(0xC1);
_lcd_dat(0x78);
_lcd_com(0xC2);
_lcd_dat(0x78);
_lcd_com(0xE0);
_lcd_dat(0x00);
_lcd_dat(0x1B);
_lcd_dat(0x02);
_lcd_com(0xE1);
_lcd_dat(0x08);
_lcd_dat(0xA0);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x07);
_lcd_dat(0xA0);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x44);
_lcd_dat(0x44);
_lcd_com(0xE2);
_lcd_dat(0x11);
_lcd_dat(0x11);
_lcd_dat(0x44);
_lcd_dat(0x44);
_lcd_dat(0xED);
_lcd_dat(0xA0);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0xEC);
_lcd_dat(0xA0);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_com(0xE3);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x11);
_lcd_dat(0x11);
_lcd_com(0xE4);
_lcd_dat(0x44);
_lcd_dat(0x44);
_lcd_com(0xE5);
_lcd_dat(0x0A);
_lcd_dat(0xE9);
_lcd_dat(0xD8);
_lcd_dat(0xA0);
_lcd_dat(0x0C);
_lcd_dat(0xEB);
_lcd_dat(0xD8);
_lcd_dat(0xA0);
_lcd_dat(0x0E);
_lcd_dat(0xED);
_lcd_dat(0xD8);
_lcd_dat(0xA0);
_lcd_dat(0x10);
_lcd_dat(0xEF);
_lcd_dat(0xD8);
_lcd_dat(0xA0);
_lcd_com(0xE6);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x11);
_lcd_dat(0x11);
_lcd_com(0xE7);
_lcd_dat(0x44);
_lcd_dat(0x44);
_lcd_com(0xE8);
_lcd_dat(0x09);
_lcd_dat(0xE8);
_lcd_dat(0xD8);
_lcd_dat(0xA0);
_lcd_dat(0x0B);
_lcd_dat(0xEA);
_lcd_dat(0xD8);
_lcd_dat(0xA0);
_lcd_dat(0x0D);
_lcd_dat(0xEC);
_lcd_dat(0xD8);
_lcd_dat(0xA0);
_lcd_dat(0x0F);
_lcd_dat(0xEE);
_lcd_dat(0xD8);
_lcd_dat(0xA0);
_lcd_com(0xEB);
_lcd_dat(0x02);
_lcd_dat(0x00);
_lcd_dat(0xE4);
_lcd_dat(0xE4);
_lcd_dat(0x88);
_lcd_dat(0x00);
_lcd_dat(0x40);
_lcd_com(0xEC);
_lcd_dat(0x3C);
_lcd_dat(0x00);
_lcd_com(0xED);
_lcd_dat(0xAB);
_lcd_dat(0x89);
_lcd_dat(0x76);
_lcd_dat(0x54);
_lcd_dat(0x02);
_lcd_dat(0xFF);
_lcd_dat(0xFF);
_lcd_dat(0xFF);
_lcd_dat(0xFF);
_lcd_dat(0xFF);
_lcd_dat(0xFF);
_lcd_dat(0x20);
_lcd_dat(0x45);
_lcd_dat(0x67);
_lcd_dat(0x98);
_lcd_dat(0xBA);
_lcd_com(0xEF);
_lcd_dat(0x08);
_lcd_dat(0x08);
_lcd_dat(0x08);
_lcd_dat(0x45);
_lcd_dat(0x3F);
_lcd_dat(0x54);
_lcd_com(0xFF);
_lcd_dat(0x77);
_lcd_dat(0x01);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x13);
_lcd_com(0xE8);
_lcd_dat(0x00);
_lcd_dat(0x0E);
_lcd_com(0xFF);
_lcd_dat(0x77);
_lcd_dat(0x01);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_com(0x11);
delay(120);
_lcd_com(0xFF);
_lcd_dat(0x77);
_lcd_dat(0x01);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x13);
_lcd_com(0xE8);
_lcd_dat(0x00);
_lcd_dat(0x0C);
delay(10);
_lcd_com(0xE8);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_com(0xFF);
_lcd_dat(0x77);
_lcd_dat(0x01);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_dat(0x00);
_lcd_com(0x3A); // 565RGB 55 16bit,666RGB 66 18bit,24bit 77
_lcd_dat(0x77);
_lcd_com(0x29);
_lcd_com(0x36);
_lcd_dat(0x00);
}
// Main Menu Draw
void mainMenu() {
// Clear screen every refresh
GD.ClearColorRGB(0x00);
GD.Clear();
// Set main color to white
GD.ColorRGB(0xffffffff);
// Draw home button
GD.cmd_fgcolor(0x218521);
GD.Tag(10);
GD.cmd_button(0, 0, 80, (_vsize) / 2 - 3, 30, OPT_FLAT | OPT_CENTER, "");
house_icon(25, 110);
// Draw settings button
GD.cmd_fgcolor(0x0d5e0d);
GD.Tag(2);
GD.cmd_button(0, (_vsize / 2) + 3, 80, (_vsize / 2) - 3, 30, OPT_FLAT | OPT_CENTER, "");
settings_icon(25, 340);
GD.get_inputs();
if (GD.inputs.tag == 10 && _screen != 0) {
_screen = 0;
} else if (GD.inputs.tag == 2 && _screen != 1) {
_screen = 1;
}
if (_screen == 0) {
// Draw clock and date
GD.cmd_text((_hsize / 2) + 45, _vsize - 140, 31, OPT_CENTER, "TUESDAY");
GD.cmd_text((_hsize / 2) + 45, _vsize - 100, 29, OPT_CENTER, "12/2/2025");
GD.ColorRGB(0xffffffff);
GD.cmd_bgcolor(0x00);
GD.cmd_clock((_hsize / 2) + 45, (_vsize / 2) - 60, 120, OPT_FLAT | OPT_NOBACK, hours, minutes, seconds, 0);
GD.ColorRGB(0xffffffff);
} else if (_screen == 1) {
// Draw settings menu
GD.Tag(3);
GD.cmd_button((_hsize / 2) - 80, (_vsize / 2) - 150, 250, 80, 28, OPT_FLAT | OPT_CENTER, "Temperature Settings");
GD.Tag(4);
GD.cmd_button((_hsize / 2) - 80, (_vsize / 2) - 50, 250, 80, 28, OPT_FLAT | OPT_CENTER, "Display Settings");
GD.get_inputs();
if (GD.inputs.tag == 3) {
_screen = 2;
} else if (GD.inputs.tag == 4) {
_screen = 3;
}
GD.ColorRGB(0xffffffff);
} else if (_screen == 2) {
// Temperature control screen
if ((GD.inputs.track_tag & 0xff) == 5 && GD.inputs.track_val > 0)
_temperature = GD.inputs.track_val;
_temperature < 10000 ? _temperature = 10000 : _temperature = _temperature;
_temperature > (65478 - 10000) ? _temperature = (65478 - 10000) : _temperature = _temperature;
// Draw custom gauge dial
GD.Tag(5);
GD.cmd_fgcolor(0x00);
GD.cmd_dial((_hsize / 2) + 40, (_vsize / 2), 170, OPT_FLAT, _temperature);
GD.cmd_track((_hsize / 2) + 40, (_vsize / 2), 1, 1, 5);
int tick_x = (_hsize / 2) + 40;
int tick_y = (_vsize / 2);
int tickend_x = (_hsize / 2) + 40;
int tickend_y = (_vsize / 2);
GD.Begin(LINES);
for (int i = 0; i < 31; i++) {
i % 5 == 0 ? GD.LineWidth(16 * 3) : GD.LineWidth(16 * 1);
GD.polar(tick_x, tick_y, 150, (6.5 + i) * 1515);
GD.polar(tickend_x, tickend_y, 130, (6.5 + i) * 1515);
GD.Vertex2ii(280 + tick_x, 240 + tick_y);
GD.Vertex2ii(280 + tickend_x, 240 + tickend_y);
}
int bg_color = map(_temperature, 68, 65478, 0, 255);
byte R = bg_color;
byte G = 0xAE;
byte B = 255 - bg_color;
uint32_t final_bg = R;
final_bg <<= 8;
final_bg += G;
final_bg <<= 8;
final_bg += B;
GD.ColorRGB(final_bg);
GD.Begin(POINTS);
GD.PointSize(16 * 110);
GD.Vertex2ii((_hsize / 2) + 40, (_vsize / 2));
GD.ColorRGB(0x00);
GD.PointSize(16 * 80);
GD.Vertex2ii((_hsize / 2) + 40, (_vsize / 2));
GD.ColorRGB(0xffffffff);
int block_x_1 = 0;
int block_y_1 = 0;
int block_x_2 = 0;
int block_y_2 = 0;
int center_x = 0;
int center_y = 0;
GD.polar(center_x, center_y, 10, 6.5 * 1515);
GD.polar(block_x_1, block_y_1, 150, 5.25 * 1515);
GD.polar(block_x_2, block_y_2, 150, (6.5 + 32) * 1515);
GD.Begin(LINES);
GD.ColorRGB(0x0);
GD.LineWidth(16 * 10);
GD.Vertex2ii(290 + center_x, 240 + center_y);
GD.Vertex2ii(290 + block_x_1, 240 + block_y_1);
GD.Vertex2ii(290 + center_x, 240 + center_y);
GD.Vertex2ii(290 + block_x_2, 240 + block_y_2);
GD.LineWidth(16 * 24);
GD.Vertex2ii(230, 330);
GD.Vertex2ii(340, 330);
int converted_temp = map(_temperature, 68, 65478, 58, 83);
GD.ColorRGB(0xffffffff);
GD.cmd_number((_hsize / 2) + 40, (_vsize / 2), 31, OPT_CENTER, converted_temp);
GD.ColorRGB(0xffffffff);
} else if (_screen == 3) {
// Display brightness screen
int converted_brightness = map(_brightness, 68, 65478, 0, 128);
if ((GD.inputs.track_tag & 0xff) == 6 && GD.inputs.track_val > 0) {
_brightness = GD.inputs.track_val;
GD.wr(REG_PWM_DUTY, converted_brightness);
}
GD.Tag(6);
GD.cmd_slider((_hsize / 2) - 85, 170, 230, 22, 0, converted_brightness, 128);
GD.cmd_track((_hsize / 2) - 85, 170, 230, 22, 6);
GD.cmd_text((_hsize / 2) + 45, 120, 31, OPT_CENTER, "Brightness");
}
GD.ColorRGB(0xffffffff);
clock();
GD.swap();
}
void clock() {
// Capture the current time provided by the Arduino since boot
unsigned long currentMillis = millis();
// Check if 1000 milliseconds have passed since the last time we updated
if (currentMillis - previousMillis >= interval) {
// Save the time we last updated the clock
previousMillis = currentMillis;
// Increment the seconds counter
seconds++;
// Check if seconds reached 60
if (seconds >= 60) {
seconds = 0;
minutes++;
// Check if minutes reached 60
if (minutes >= 60) {
minutes = 0;
hours++;
// Check if hours reached 24 (handle 24-hour cycle)
if (hours >= 24) {
hours = 0;
}
}
}
}
}
void house_icon(uint16_t x, uint16_t y) {
GD.Begin(RECTS);
GD.ColorRGB(0xffffffff);
GD.Vertex2ii(x, y);
GD.Vertex2ii(x + 30, y + 30);
GD.ColorRGB(0x218521);
GD.Vertex2ii(x + 10, y + 15);
GD.Vertex2ii(x + 20, y + 31);
GD.Begin(LINES);
GD.ColorRGB(0xffffffff);
GD.LineWidth(16 * 5);
GD.Vertex2ii(x + 15, y - 5);
GD.Vertex2ii(x - 5, y + 5);
GD.Vertex2ii(x + 15, y - 5);
GD.Vertex2ii(x + 35, y + 5);
}
void settings_icon(uint16_t x, uint16_t y) {
GD.PointSize(16 * 22);
GD.Begin(POINTS);
GD.ColorRGB(0xffffffff);
GD.Vertex2ii(x + 15, y + 15);
GD.Begin(RECTS);
GD.ColorRGB(0x0d5e0d);
GD.Vertex2ii(x + 10, y - 10);
GD.Vertex2ii(x + 20, y + 10);
GD.ColorRGB(0xffffffff);
GD.Vertex2ii(x + 10, y + 30);
GD.Vertex2ii(x + 20, y + 40);
}
void setup() {
Serial.begin(115200);
pinMode(RST, OUTPUT);
pinMode(CS, OUTPUT);
pinMode(DC, OUTPUT);
pinMode(SCL, OUTPUT);
pinMode(SDA, OUTPUT);
digitalWrite(RST, HIGH);
digitalWrite(CS, HIGH);
digitalWrite(DC, HIGH);
digitalWrite(SCL, HIGH);
digitalWrite(SDA, HIGH);
GD.begin(0, 10, 5);
GD.Clear();
init_480X480();
GD.Clear();
GD.swap();
}
void loop() {
// Put menu function in main loop to update continously
mainMenu();
}