Initial WIP for TFT (not working yet)

This commit is contained in:
Justin C. Miller
2025-03-30 23:00:03 -07:00
parent 090616f8b4
commit c422864bc2
7 changed files with 1482 additions and 0 deletions

View File

@@ -53,6 +53,8 @@ target_sources(edmfd PUBLIC
src/edmfd/main.cc src/edmfd/main.cc
src/edmfd/mcp23017.cc src/edmfd/mcp23017.cc
src/edmfd/screen.cc src/edmfd/screen.cc
src/edmfd/spi.cc
src/edmfd/tft.cc
src/edmfd/usb_descriptors.cc src/edmfd/usb_descriptors.cc
src/edmfd/vendor.cc src/edmfd/vendor.cc
) )
@@ -66,6 +68,7 @@ target_link_libraries(edmfd PUBLIC
pico_stdio_uart pico_stdio_uart
pico_unique_id pico_unique_id
hardware_i2c hardware_i2c
hardware_spi
tinyusb_device tinyusb_device
tinyusb_board tinyusb_board
) )

View File

@@ -14,6 +14,8 @@
#include "hid.hh" #include "hid.hh"
#include "i2c.hh" #include "i2c.hh"
#include "mcp23017.hh" #include "mcp23017.hh"
#include "spi.hh"
#include "tft.hh"
using mcp23017::reg; using mcp23017::reg;
@@ -27,11 +29,13 @@ extern "C" {
} }
static constexpr unsigned i2c_baud = 400 * 1000; static constexpr unsigned i2c_baud = 400 * 1000;
static constexpr unsigned spi_baud = 24 * 1000 * 1000;
static constexpr unsigned hid_update_ms = 1000/20; // 20Hz static constexpr unsigned hid_update_ms = 1000/20; // 20Hz
static constexpr unsigned bank_leds = 0; static constexpr unsigned bank_leds = 0;
static constexpr unsigned bank_buttons = 1; static constexpr unsigned bank_buttons = 1;
static constexpr unsigned buttons_per_group = 5; static constexpr unsigned buttons_per_group = 5;
static constexpr unsigned button_group_count = 2; static constexpr unsigned button_group_count = 2;
static const unsigned button_group_irqs[] = {4, 7}; static const unsigned button_group_irqs[] = {4, 7};
@@ -102,6 +106,10 @@ int main(void)
log::info("board initialized"); log::info("board initialized");
i2c::init(i2c_baud); i2c::init(i2c_baud);
spi::init(spi_baud);
tft disp {9, 10};
disp.init();
// set up interrupt handlers // set up interrupt handlers
for (int pin : button_group_irqs) { for (int pin : button_group_irqs) {

47
src/edmfd/spi.cc Normal file
View File

@@ -0,0 +1,47 @@
#include "hardware/spi.h"
#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "logging/log.hh"
#include "spi.hh"
namespace spi {
cs::cs(unsigned pin) :
pin {pin}
{
asm volatile("nop \n nop \n nop");
gpio_put(pin, 0); // Active low
asm volatile("nop \n nop \n nop");
}
cs::~cs()
{
asm volatile("nop \n nop \n nop");
gpio_put(pin, 1);
asm volatile("nop \n nop \n nop");
}
void init(unsigned baud)
{
baud = spi_init(spi_default, baud);
gpio_set_function(PICO_DEFAULT_SPI_SCK_PIN, GPIO_FUNC_SPI);
gpio_set_function(PICO_DEFAULT_SPI_TX_PIN, GPIO_FUNC_SPI);
gpio_set_function(PICO_DEFAULT_SPI_RX_PIN, GPIO_FUNC_SPI);
// Make the SPI pins available to picotool
bi_decl(bi_3pins_with_func(PICO_DEFAULT_SPI_RX_PIN, PICO_DEFAULT_SPI_TX_PIN, PICO_DEFAULT_SPI_SCK_PIN, GPIO_FUNC_SPI));
//spi_set_format(spi_default, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
log::info("waiting for spi...");
while(!spi_is_writable(spi_default));
log::info("spi initialized at %.1fMHz, SCK=%d TX=%d RX=%d", baud/1000000.f,
PICO_DEFAULT_SPI_RX_PIN, PICO_DEFAULT_SPI_TX_PIN, PICO_DEFAULT_SPI_SCK_PIN);
}
} // namespace spi

17
src/edmfd/spi.hh Normal file
View File

@@ -0,0 +1,17 @@
#pragma once
namespace spi {
void init(unsigned baud);
// RAII chip select
class cs
{
public:
cs(unsigned pin);
~cs();
private:
unsigned pin;
};
} // namespace spi

1204
src/edmfd/startup_banner.hh Normal file

File diff suppressed because it is too large Load Diff

185
src/edmfd/tft.cc Normal file
View File

@@ -0,0 +1,185 @@
#include "hardware/spi.h"
#include "pico/stdlib.h"
#include "logging/log.hh"
#include "spi.hh"
#include "tft.hh"
#include "startup_banner.hh"
enum class cmd : uint8_t {
// Organized by datasheet table page
// Page 105
nop = 0x00, // p.114, NOP
swreset = 0x01, // p.115, Software reset
slpin = 0x10, // p.131, Sleep in
slpout = 0x11, // p.133, Sleep out
// Page 106
invoff = 0x20, // p.137, Display inversion off
invon = 0x21, // p.138, Display inversion on
dispoff = 0x28, // p.142, Display off
dispon = 0x29, // p.143, Display on
caset = 0x2a, // p.144, Column address set
paset = 0x2b, // p.146, Page address set
ramwr = 0x2c, // p.148, Write VRAM
// Page 107
teoff = 0x34, // p.155, Tear effect line off
teon = 0x35, // p.156, Tear effect line on
madctl = 0x36, // p.157, Memory access control
colmod = 0x3a, // p.164, Color mode
tesl = 0x44, // p.167, Set tear scanline
// Page 110
setosc = 0xb0, // p.200, Set internal oscillator
setpower = 0xb1, // p.201, Set power control
setdisplay = 0xb2, // p.205, Set display control
setrgb = 0xb3, // p.207, Set RGB I/F
setcyc = 0xb4, // p.210, Set display cycle
setvcom = 0xb6, // p.214, Set VCOM voltage
setextc = 0xb9, // p.217, Enter extension command
// Page 111
setstba = 0xc0, // p.218, Set source option
setpanel = 0xcc, // p.224, Set panel characteristics
// Page 112
setgamma = 0xe0, // p.225, Set gamma
};
namespace {
const uint8_t init_cmds[] = {
// SWRESET Soft reset, then delay 10 ms
uint8_t(cmd::swreset), 0x80 + 100 / 5,
// SETEXTC? Set extension command
uint8_t(cmd::setextc), 3, 0xFF, 0x83, 0x57,
// No command, just delay 300 ms (This was 0xff)
uint8_t(cmd::nop), 0x80 + 500 / 5,
// SETRGB -- ?? 0x80 enables SDO pin (0x00 disables)
uint8_t(cmd::setrgb), 4, 0x80, 0x00, 0x06, 0x06,
// SETVCOM -1.52V
uint8_t(cmd::setvcom), 1, 0x25,
// SETOSC Normal mode 70Hz, Idle mode 55 Hz
uint8_t(cmd::setosc), 1, 0x68,
// SETPANEL BGR, Gate direction swapped
uint8_t(cmd::setpanel), 1, 0x05,
// SETPOWER Not deep standby, BT, VSPR, VSNR, AP, FS
uint8_t(cmd::setpower), 6, 0x00, 0x15, 0x1C, 0x1C, 0x83, 0xAA,
// SETSTBA OPON normal, OPON idle, STBA, STBA, STBA, GEN
uint8_t(cmd::setstba), 6, 0x50, 0x50, 0x01, 0x3C, 0x1E, 0x08,
// SETCYC NW 2, RTN, DIV, DUM, DUM, GDON, GDOFF
uint8_t(cmd::setcyc), 7, 0x02, 0x40, 0x00, 0x2A, 0x2A, 0x0D, 0x78,
// SETGAMMA
uint8_t(cmd::setgamma), 34, 0x02, 0x0A, 0x11, 0x1d, 0x23, 0x35, 0x41, 0x4b, 0x4b, 0x42,
0x3A, 0x27, 0x1B, 0x08, 0x09, 0x03, 0x02, 0x0A, 0x11, 0x1d,
0x23, 0x35, 0x41, 0x4b, 0x4b, 0x42, 0x3A, 0x27, 0x1B, 0x08,
0x09, 0x03, 0x00, 0x01,
// COLMOD 16 bit color
uint8_t(cmd::colmod), 1, 0x55,
// MADCTL
uint8_t(cmd::madctl), 1, 0x00, //0xC0,
// TEON TW off
uint8_t(cmd::teon), 1, 0x00,
// TESL (TEARLINE?)
uint8_t(cmd::tesl), 2, 0x00, 0x02,
// SLPOUT Exit Sleep, then delay 150 ms
uint8_t(cmd::slpout), 0x80 + 150 / 5,
// DISPON Main screen turn on, delay 50 ms
uint8_t(cmd::dispon), 0x80 + 50 / 5,
// End of commands
0, 0
};
} // namespace
void tft::init()
{
gpio_init(m_cs);
gpio_set_dir(m_cs, GPIO_OUT);
gpio_put(m_cs, 1); // Initialize to 1 (unselected)
gpio_init(m_dc);
gpio_set_dir(m_dc, GPIO_OUT);
gpio_put(m_dc, 1); // Initialize to 1 (data mode)
{
spi::cs {m_cs};
uint8_t buf[64];
uint8_t const *commands = init_cmds;
const uint8_t *end = init_cmds + sizeof(init_cmds);
while (commands < end) {
cmd command = static_cast<cmd>(*commands++);
uint8_t nargs = *commands++;
if (command == cmd::nop && !nargs) break;
// If high bit is set, this is a delay
unsigned delay = (nargs & 0x80) ? (unsigned(nargs & 0x7f) * 5) : 0;
nargs = (nargs & 0x80) ? 0 : (nargs & 0x7f);
for (unsigned i = 0; i < nargs; ++i)
buf[i] = *commands++;
if (command != cmd::nop)
write_command(command, buf, nargs);
if (delay) {
log::trace("Delay %dms", delay);
sleep_ms(delay);
}
}
}
{
spi::cs {m_cs};
uint16_t w = 480/3;
uint16_t h = 320/3;
unsigned n = w*h;
uint16_t color = __builtin_bswap16(startup_color);
//color = 0xffff;
color = 0xf800;
log::info("Drawing %dx%d square, color %04x", w, h, color);
set_draw_rect(0, 0, w, h);
for (unsigned i = 0; i < n; ++i)
spi_write_blocking(spi_default, reinterpret_cast<uint8_t*>(&color), sizeof(color));
log::trace("Wrote SPI %d data bytes", n);
}
}
void tft::write_command(cmd command, uint8_t *args, unsigned args_len)
{
{
spi::cs {m_dc}; // Command Mode
spi_write_blocking(spi_default, reinterpret_cast<uint8_t*>(&command), sizeof(command));
}
if (args_len)
spi_write_blocking(spi_default, args, args_len);
log::trace("Wrote SPI command %02x, %d data bytes", command, args_len);
}
void tft::set_draw_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
{
uint16_t xargs[2] = { __builtin_bswap16(x), __builtin_bswap16(x + w - 1) };
uint16_t yargs[2] = { __builtin_bswap16(y), __builtin_bswap16(y + h - 1) };
write_command(cmd::caset, reinterpret_cast<uint8_t*>(&xargs), sizeof(xargs));
write_command(cmd::paset, reinterpret_cast<uint8_t*>(&yargs), sizeof(yargs));
write_command(cmd::ramwr);
}

18
src/edmfd/tft.hh Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
enum class cmd : uint8_t;
class tft
{
public:
tft(unsigned cs, unsigned dc) :
m_cs {cs}, m_dc {dc} {}
void init();
private:
void write_command(cmd command, uint8_t *args = nullptr, unsigned args_len = 0);
void set_draw_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
unsigned m_cs, m_dc;
};