// =================== PINOUT ===================
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
#define TFT_SCK 13
#define TFT_MOSI 11
#define FT_SDA A4
#define FT_SCL A5
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
// =================== COLOR ===================
#define COLOR_BLACK 0x0000
#define COLOR_WHITE 0xFFFF
#define COLOR_BLUE 0x001F
#define COLOR_NAVY 0x0010
#define COLOR_YELLOW 0xFFE0
#define COLOR_RED 0xF800
#define COLOR_GRAY 0x8410
#define COLOR_CYAN 0x07FF
#define COLOR_GREEN 0x07E0
#define COLOR_LIGHTGREY 0xC618
#define COLOR_LIGHTBLUE 0x6D9D
// =================== SPI BARE METAL ===================
void spi_init() {
DDRB |= (1<<PB3) | (1<<PB5); // MOSI, SCK
pinMode(TFT_CS, OUTPUT);
pinMode(TFT_DC, OUTPUT);
pinMode(TFT_RST, OUTPUT);
SPCR = (1<<SPE)|(1<<MSTR);
}
void spi_write(uint8_t d) {
SPDR = d;
while (!(SPSR & (1<<SPIF)));
}
// =================== ILI9341 BARE METAL ===================
void tft_write_command(uint8_t cmd) {
digitalWrite(TFT_DC, LOW);
digitalWrite(TFT_CS, LOW);
spi_write(cmd);
digitalWrite(TFT_CS, HIGH);
}
void tft_write_data(uint8_t data) {
digitalWrite(TFT_DC, HIGH);
digitalWrite(TFT_CS, LOW);
spi_write(data);
digitalWrite(TFT_CS, HIGH);
}
void tft_write_data16(uint16_t data) {
tft_write_data(data >> 8);
tft_write_data(data & 0xFF);
}
void tft_reset() {
digitalWrite(TFT_RST, LOW); delay(10);
digitalWrite(TFT_RST, HIGH); delay(10);
}
void tft_init() {
tft_reset();
tft_write_command(0x01); delay(5); // Software reset
tft_write_command(0x28); // Display OFF
tft_write_command(0xCF); tft_write_data(0x00); tft_write_data(0xC1); tft_write_data(0x30);
tft_write_command(0xED); tft_write_data(0x64); tft_write_data(0x03); tft_write_data(0x12); tft_write_data(0x81);
tft_write_command(0xE8); tft_write_data(0x85); tft_write_data(0x00); tft_write_data(0x78);
tft_write_command(0xCB); tft_write_data(0x39); tft_write_data(0x2C); tft_write_data(0x00); tft_write_data(0x34); tft_write_data(0x02);
tft_write_command(0xF7); tft_write_data(0x20);
tft_write_command(0xEA); tft_write_data(0x00); tft_write_data(0x00);
tft_write_command(0xC0); tft_write_data(0x23); // Power control
tft_write_command(0xC1); tft_write_data(0x10); // Power control
tft_write_command(0xC5); tft_write_data(0x3e); tft_write_data(0x28); // VCOM
tft_write_command(0xC7); tft_write_data(0x86);
tft_write_command(0x36); tft_write_data(0x48); // Memory Access Control
tft_write_command(0x3A); tft_write_data(0x55); // Pixel Format
tft_write_command(0xB1); tft_write_data(0x00); tft_write_data(0x18);
tft_write_command(0xB6); tft_write_data(0x08); tft_write_data(0x82); tft_write_data(0x27);
tft_write_command(0xF2); tft_write_data(0x00);
tft_write_command(0x26); tft_write_data(0x01);
tft_write_command(0xE0);
tft_write_data(0x0F); tft_write_data(0x31); tft_write_data(0x2B); tft_write_data(0x0C);
tft_write_data(0x0E); tft_write_data(0x08); tft_write_data(0x4E); tft_write_data(0xF1);
tft_write_data(0x37); tft_write_data(0x07); tft_write_data(0x10); tft_write_data(0x03);
tft_write_data(0x0E); tft_write_data(0x09); tft_write_data(0x00);
tft_write_command(0xE1);
tft_write_data(0x00); tft_write_data(0x0E); tft_write_data(0x14); tft_write_data(0x03);
tft_write_data(0x11); tft_write_data(0x07); tft_write_data(0x31); tft_write_data(0xC1);
tft_write_data(0x48); tft_write_data(0x08); tft_write_data(0x0F); tft_write_data(0x0C);
tft_write_data(0x31); tft_write_data(0x36); tft_write_data(0x0F);
tft_write_command(0x11); delay(120); // Sleep out
tft_write_command(0x29); delay(10); // Display ON
}
void tft_set_addr_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
tft_write_command(0x2A);
tft_write_data(x0 >> 8); tft_write_data(x0 & 0xFF);
tft_write_data(x1 >> 8); tft_write_data(x1 & 0xFF);
tft_write_command(0x2B);
tft_write_data(y0 >> 8); tft_write_data(y0 & 0xFF);
tft_write_data(y1 >> 8); tft_write_data(y1 & 0xFF);
tft_write_command(0x2C);
}
void tft_draw_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
for (uint16_t i = 0; i < w; i++) {
tft_set_addr_window(x+i, y, x+i, y); tft_write_data16(color);
tft_set_addr_window(x+i, y+h-1, x+i, y+h-1); tft_write_data16(color);
}
for (uint16_t i = 0; i < h; i++) {
tft_set_addr_window(x, y+i, x, y+i); tft_write_data16(color);
tft_set_addr_window(x+w-1, y+i, x+w-1, y+i); tft_write_data16(color);
}
}
void tft_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
uint8_t hi = color >> 8;
uint8_t lo = color & 0xFF;
uint32_t total = (uint32_t)w * h;
tft_set_addr_window(x, y, x + w - 1, y + h - 1);
// Optimasi: Set DC dan CS sekali
PORTB |= (1 << PB1); // DC HIGH (misal pin 9 => PB1)
PORTB &= ~(1 << PB2); // CS LOW (misal pin 10 => PB2)
for (uint32_t i = 0; i < total; i++) {
SPDR = hi;
while (!(SPSR & (1 << SPIF)));
SPDR = lo;
while (!(SPSR & (1 << SPIF)));
}
PORTB |= (1 << PB2); // CS HIGH
}
// =================== I2C BARE METAL ===================
void i2c_init() {
TWSR = 0;
TWBR = ((F_CPU/100000)-16)/2;
}
void i2c_start() {
TWCR = (1<<TWSTA)|(1<<TWEN)|(1<<TWINT);
while (!(TWCR & (1<<TWINT)));
}
void i2c_stop() {
TWCR = (1<<TWSTO)|(1<<TWEN)|(1<<TWINT);
delayMicroseconds(10);
}
void i2c_write(uint8_t data) {
TWDR = data;
TWCR = (1<<TWEN)|(1<<TWINT);
while (!(TWCR & (1<<TWINT)));
}
uint8_t i2c_read_ack() {
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA);
while (!(TWCR & (1<<TWINT)));
return TWDR;
}
uint8_t i2c_read_nack() {
TWCR = (1<<TWEN)|(1<<TWINT);
while (!(TWCR & (1<<TWINT)));
return TWDR;
}
// =================== FT6206/FT6236 TOUCH ===================
#define FT_ADDR 0x38
bool ft_read_touch(uint16_t *x, uint16_t *y) {
i2c_start();
i2c_write((FT_ADDR << 1) | 0); // write
i2c_write(0x02); // Touch data reg
i2c_start();
i2c_write((FT_ADDR << 1) | 1); // read
uint8_t td = i2c_read_ack(); // Number of touch points
uint8_t xh = i2c_read_ack();
uint8_t xl = i2c_read_ack();
uint8_t yh = i2c_read_ack();
uint8_t yl = i2c_read_nack();
i2c_stop();
if (td == 0) return false;
*x = ((xh & 0x0F) << 8) | xl;
*y = ((yh & 0x0F) << 8) | yl;
// Orientasi layar kamu (ubah sesuai arah fisik display-mu)
*x = SCREEN_WIDTH - *x;
*y = SCREEN_HEIGHT - *y;
return true;
}
// =================== FONT 5x7 ===================
const uint8_t font5x7[][5] = {
{0x00,0x00,0x00,0x00,0x00}, // 32: space
{0x00,0x00,0x5F,0x00,0x00}, // 33: !
{0x00,0x07,0x00,0x07,0x00}, // 34: "
{0x14,0x7F,0x14,0x7F,0x14}, // 35: #
{0x24,0x2A,0x7F,0x2A,0x12}, // 36: $
{0x23,0x13,0x08,0x64,0x62}, // 37: %
{0x36,0x49,0x55,0x22,0x50}, // 38: &
{0x00,0x05,0x03,0x00,0x00}, // 39: '
{0x00,0x1C,0x22,0x41,0x00}, // 40: (
{0x00,0x41,0x22,0x1C,0x00}, // 41: )
{0x14,0x08,0x3E,0x08,0x14}, // 42: *
{0x08,0x08,0x3E,0x08,0x08}, // 43: +
{0x00,0x50,0x30,0x00,0x00}, // 44: ,
{0x08,0x08,0x08,0x08,0x08}, // 45: -
{0x00,0x60,0x60,0x00,0x00}, // 46: .
{0x20,0x10,0x08,0x04,0x02}, // 47: /
{0x3E,0x51,0x49,0x45,0x3E}, // 48: 0
{0x00,0x42,0x7F,0x40,0x00}, // 49: 1
{0x42,0x61,0x51,0x49,0x46}, // 50: 2
{0x21,0x41,0x45,0x4B,0x31}, // 51: 3
{0x18,0x14,0x12,0x7F,0x10}, // 52: 4
{0x27,0x45,0x45,0x45,0x39}, // 53: 5
{0x3C,0x4A,0x49,0x49,0x30}, // 54: 6
{0x01,0x71,0x09,0x05,0x03}, // 55: 7
{0x36,0x49,0x49,0x49,0x36}, // 56: 8
{0x06,0x49,0x49,0x29,0x1E}, // 57: 9
{0x00,0x36,0x36,0x00,0x00}, // 58: :
{0x00,0x56,0x36,0x00,0x00}, // 59: ;
{0x08,0x14,0x22,0x41,0x00}, // 60: <
{0x14,0x14,0x14,0x14,0x14}, // 61: =
{0x00,0x41,0x22,0x14,0x08}, // 62: >
{0x02,0x01,0x51,0x09,0x06}, // 63: ?
{0x32,0x49,0x79,0x41,0x3E}, // 64: @
{0x7E,0x11,0x11,0x11,0x7E}, // 65: A
{0x7F,0x49,0x49,0x49,0x36}, // 66: B
{0x3E,0x41,0x41,0x41,0x22}, // 67: C
{0x7F,0x41,0x41,0x22,0x1C}, // 68: D
{0x7F,0x49,0x49,0x49,0x41}, // 69: E
{0x7F,0x09,0x09,0x09,0x01}, // 70: F
{0x3E,0x41,0x49,0x49,0x7A}, // 71: G
{0x7F,0x08,0x08,0x08,0x7F}, // 72: H
{0x00,0x41,0x7F,0x41,0x00}, // 73: I
{0x20,0x40,0x41,0x3F,0x01}, // 74: J
{0x7F,0x08,0x14,0x22,0x41}, // 75: K
{0x7F,0x40,0x40,0x40,0x40}, // 76: L
{0x7F,0x02,0x0C,0x02,0x7F}, // 77: M
{0x7F,0x04,0x08,0x10,0x7F}, // 78: N
{0x3E,0x41,0x41,0x41,0x3E}, // 79: O
{0x7F,0x09,0x09,0x09,0x06}, // 80: P
{0x3E,0x41,0x51,0x21,0x5E}, // 81: Q
{0x7F,0x09,0x19,0x29,0x46}, // 82: R
{0x46,0x49,0x49,0x49,0x31}, // 83: S
{0x01,0x01,0x7F,0x01,0x01}, // 84: T
{0x3F,0x40,0x40,0x40,0x3F}, // 85: U
{0x1F,0x20,0x40,0x20,0x1F}, // 86: V
{0x3F,0x40,0x38,0x40,0x3F}, // 87: W
{0x63,0x14,0x08,0x14,0x63}, // 88: X
{0x07,0x08,0x70,0x08,0x07}, // 89: Y
{0x61,0x51,0x49,0x45,0x43} // 90: Z
};
// =================== PRIMITIF UI ===================
void tft_draw_char(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg = 0xFFFF, uint8_t scale=1, bool transparent_bg = false) {
if (c < 32 || c > 90) c = '?';
const uint8_t* chr = font5x7[c - 32];
// Hitung ukuran area karakter
uint8_t char_w = 5 * scale;
uint8_t char_h = 7 * scale;
// Set area hanya sekali
tft_set_addr_window(x, y, x + char_w - 1, y + char_h - 1);
for (uint8_t j = 0; j < 7; j++) { // Baris
for (uint8_t sy = 0; sy < scale; sy++) {
for (uint8_t i = 0; i < 5; i++) { // Kolom
uint8_t line = chr[i];
bool pixel_on = (line >> j) & 0x01;
for (uint8_t sx = 0; sx < scale; sx++) {
if (pixel_on) {
tft_write_data16(color);
} else if (!transparent_bg) {
tft_write_data16(bg);
} else {
tft_write_data16(bg); // atau bisa di-skip jika hardware mendukung
}
}
}
}
}
}
void tft_draw(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg = 0xFFFF, uint8_t scale = 1, bool transparent_bg = false) {
if (c < 32 || c > 90) c = '?';
const uint8_t* chr = font5x7[c - 32];
for (uint8_t i = 0; i < 5; i++) {
uint8_t line = chr[i];
for (uint8_t j = 0; j < 7; j++) {
bool pixel_on = (line & 0x01);
if (pixel_on || !transparent_bg) {
uint16_t col = pixel_on ? color : bg;
for (uint8_t sx = 0; sx < scale; sx++) {
for (uint8_t sy = 0; sy < scale; sy++) {
uint16_t px = x + i * scale + sx;
uint16_t py = y + j * scale + sy;
tft_set_addr_window(px, py, px, py);
tft_write_data16(col);
}
}
}
line >>= 1;
}
}
}
void tft_draw_text(uint16_t x, uint16_t y, const char* s, uint16_t color, uint16_t bg, uint8_t scale=1) {
while (*s) {
tft_draw_char(x, y, *s, color, bg, scale, false);
x += 6*scale;
s++;
}
}
void tft_draw_text_transparent(uint16_t x, uint16_t y, const char* s, uint16_t color, uint16_t bg, uint8_t scale=1) {
while (*s) {
tft_draw(x, y, *s, color, bg, scale, true);
x += 6*scale;
s++;
}
}
void tft_draw_rounded_box(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint8_t r, uint16_t fill, uint16_t outline, bool highlight = false) {
// Gambar bagian tengah (tengah + sisi datar)
tft_fill_rect(x + r, y, w - 2 * r, h, fill); // Tengah
tft_fill_rect(x, y + r, r, h - 2 * r, fill); // Kiri
tft_fill_rect(x + w - r, y + r, r, h - 2 * r, fill); // Kanan
// Gambar sudut bulat lebih efisien
for (int16_t dy = 0; dy < r; dy++) {
int16_t dx_limit = sqrt(r * r - dy * dy);
// Baris horizontal untuk setiap sudut bulat
// Kiri Atas dan Kanan Atas
if (dx_limit > 0) {
// Kiri Atas
tft_set_addr_window(x + r - dx_limit, y + r - dy, x + r - 1, y + r - dy);
for (int16_t dx = dx_limit; dx > 0; dx--) {
tft_write_data16(fill);
}
// Kanan Atas
tft_set_addr_window(x + w - r, y + r - dy, x + w - r + dx_limit - 1, y + r - dy);
for (int16_t dx = 0; dx < dx_limit; dx++) {
tft_write_data16(fill);
}
}
// Kiri Bawah dan Kanan Bawah
if (dx_limit > 0) {
// Kiri Bawah
tft_set_addr_window(x + r - dx_limit, y + h - r + dy - 1, x + r - 1, y + h - r + dy - 1);
for (int16_t dx = dx_limit; dx > 0; dx--) {
tft_write_data16(fill);
}
// Kanan Bawah
tft_set_addr_window(x + w - r, y + h - r + dy - 1, x + w - r + dx_limit - 1, y + h - r + dy - 1);
for (int16_t dx = 0; dx < dx_limit; dx++) {
tft_write_data16(fill);
}
}
}
// Highlight jika perlu
if (highlight) {
tft_draw_rect(x - 2, y - 2, w + 4, h + 4, COLOR_YELLOW);
}
}
const char* menu_labels[2][5] = {
// Indonesia
{
"GULA DARAH",
"SPO2 & BPM",
"SPIROMETER",
"BREATHALYZER",
"BAHASA"
},
// English
{
"BLOOD GLUCOSE",
"SPO2 & BPM",
"SPIROMETER",
"BREATHALYZER",
"LANGUAGE"
}
};
const char* lang_labels[2] = {"INDONESIA", "ENGLISH"};
enum State { STATE_SPLASH, STATE_MENU, STATE_LANG, STATE_SENSOR };
State state = STATE_SPLASH;
uint8_t lang_id = 0;
int selected_menu = -1;
int selected_lang = -1;
int current_sensor = -1;
void draw_filled_circle(int x0, int y0, int r, uint16_t color) {
for (int y = -r; y <= r; y++) {
int w = sqrt(r * r - y * y);
tft_fill_rect(x0 - w, y0 + y, 2 * w + 1, 1, color);
}
}
void draw_plus_logo(int centerX, int centerY, int barLength, int barWidth, int radius, uint16_t color) {
int halfBar = barLength / 2;
int halfWidth = barWidth / 2;
// Horizontal bar
tft_fill_rect(centerX - halfBar, centerY - halfWidth, barLength, barWidth, color);
// Vertical bar
tft_fill_rect(centerX - halfWidth, centerY - halfBar, barWidth, barLength, color);
// Rounded corners (4 titik ujung)
draw_filled_circle(centerX - halfBar, centerY, radius, color); // kiri
draw_filled_circle(centerX + halfBar, centerY, radius, color); // kanan
draw_filled_circle(centerX, centerY - halfBar, radius, color); // atas
draw_filled_circle(centerX, centerY + halfBar, radius, color); // bawah
}
void show_splash() {
tft_fill_rect(65, 30, 110, 110, COLOR_WHITE);
draw_plus_logo(120, 70, 50, 20, 10, COLOR_LIGHTBLUE);
// Tulisan besar (tetap dengan background biru transparan)
tft_draw_text_transparent(72, 115, "MED-PLAY", COLOR_BLACK, COLOR_BLUE, 2);
// Tulisan kecil biasa
tft_draw_text(35, 220, "MEDICAL DISPLAY", COLOR_WHITE, COLOR_BLACK, 2);
delay(1500);
state = STATE_MENU;
}
void draw_menu(int highlight = -1) {
for (int i = 0; i < 5; i++) {
int y = 40 + i*48;
tft_draw_rounded_box(20, y, 200, 40, 12, (i==highlight)?COLOR_BLUE:COLOR_WHITE, COLOR_BLACK, (i==highlight));
tft_draw_text(40, y+12, menu_labels[lang_id][i], (i==highlight)?COLOR_WHITE:COLOR_BLACK, (i==highlight)?COLOR_BLUE:COLOR_WHITE, 2);
}
}
void draw_lang_menu(int highlight = -1) {
tft_draw_text(30, 40, "PILIH BAHASA", COLOR_WHITE, COLOR_BLACK, 2);
// Indonesia
tft_draw_rounded_box(40, 90, 160, 50, 14, (highlight==0)?COLOR_BLUE:COLOR_WHITE, COLOR_NAVY, (highlight==0));
tft_draw_text(70, 105, lang_labels[0], (highlight==0)?COLOR_WHITE:COLOR_NAVY, (highlight==0)?COLOR_BLUE:COLOR_WHITE, 2);
// English
tft_draw_rounded_box(40, 170, 160, 50, 14, (highlight==1)?COLOR_BLUE:COLOR_WHITE, COLOR_NAVY, (highlight==1));
tft_draw_text(70, 185, lang_labels[1], (highlight==1)?COLOR_WHITE:COLOR_NAVY, (highlight==1)?COLOR_BLUE:COLOR_WHITE, 2);
// Back
tft_draw_rounded_box(10, 270, 60, 30, 8, COLOR_WHITE, COLOR_NAVY);
tft_draw_text(20, 280, "BACK", COLOR_NAVY, COLOR_WHITE, 1);
}
void draw_sensor_page(int idx) {
tft_draw_text(20, 30, menu_labels[lang_id][idx], COLOR_WHITE, COLOR_BLACK, 2);
tft_draw_rounded_box(30, 70, 180, 120, 16, COLOR_LIGHTGREY, COLOR_NAVY);
tft_draw_text(50, 90, "GRAPH", COLOR_BLACK, COLOR_LIGHTGREY, 1);
tft_draw_rounded_box(10, 270, 60, 30, 8, COLOR_WHITE, COLOR_NAVY);
tft_draw_text(20, 280, "BACK", COLOR_BLACK, COLOR_WHITE, 1);
}
bool inBox(int x, int y, int bx, int by, int bw, int bh) {
return (x >= bx && x <= bx+bw && y >= by && y <= by+bh);
}
void handleMenuTouch() {
uint16_t x, y;
if (!ft_read_touch(&x, &y)) return;
for (int i = 0; i < 5; i++) {
int by = 40 + i*48;
if (inBox(x, y, 20, by, 200, 40)) {
draw_menu(i);
delay(120);
if (i == 4) { // Bahasa
state = STATE_LANG;
tft_draw_rounded_box(0, 0, 240, 320, 50, COLOR_BLACK, COLOR_BLACK, false);
draw_lang_menu();
} else {
state = STATE_SENSOR;
current_sensor = i;
tft_draw_rounded_box(0, 0, 240, 320, 50, COLOR_BLACK, COLOR_BLACK, false);
draw_sensor_page(i);
}
return;
}
}
}
void handleLangTouch() {
uint16_t x, y;
if (!ft_read_touch(&x, &y)) return;
if (inBox(x, y, 40, 90, 160, 50)) {
lang_id = 0;
draw_lang_menu(0);
delay(120);
state = STATE_MENU;
tft_draw_rounded_box(0, 0, 240, 320, 50, COLOR_BLACK, COLOR_BLACK, false);
draw_menu();
return;
}
if (inBox(x, y, 40, 170, 160, 50)) {
lang_id = 1;
draw_lang_menu(1);
delay(120);
state = STATE_MENU;
tft_draw_rounded_box(0, 0, 240, 320, 50, COLOR_BLACK, COLOR_BLACK, false);
draw_menu();
return;
}
if (inBox(x, y, 10, 270, 60, 30)) {
state = STATE_MENU;
tft_draw_rounded_box(0, 0, 240, 320, 50, COLOR_BLACK, COLOR_BLACK, false);
draw_menu();
return;
}
}
void handleSensorTouch() {
uint16_t x, y;
if (!ft_read_touch(&x, &y)) return;
if (inBox(x, y, 10, 270, 60, 30)) {
state = STATE_MENU;
tft_draw_rounded_box(0, 0, 240, 320, 50, COLOR_BLACK, COLOR_BLACK, false);
draw_menu();
return;
}
}
void setup() {
spi_init();
i2c_init();
pinMode(TFT_CS, OUTPUT);
pinMode(TFT_DC, OUTPUT);
pinMode(TFT_RST, OUTPUT);
tft_init();
show_splash();
tft_draw_rounded_box(0, 0, 240, 320, 50, COLOR_BLACK, COLOR_BLACK, false);
draw_menu();
}
void loop() {
switch (state) {
case STATE_MENU:
handleMenuTouch();
break;
case STATE_LANG:
handleLangTouch();
break;
case STATE_SENSOR:
handleSensorTouch();
break;
default:
break;
}
delay(10);
}