Files
basic1/lib/st7789/st7789.c
T

467 lines
12 KiB
C

/*
* Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* ST7789 TFT LCD Display Driver
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hardware/gpio.h"
#include "hardware/spi.h"
#include "hardware/pwm.h"
#include "pico/binary_info.h"
#include "pico/stdlib.h"
#include "st7789.h"
// ST7789 Commands
#define ST7789_NOP 0x00
#define ST7789_SWRESET 0x01
#define ST7789_RDDID 0x04
#define ST7789_RDDST 0x09
#define ST7789_SLPIN 0x10
#define ST7789_SLPOUT 0x11
#define ST7789_PTLON 0x12
#define ST7789_NORON 0x13
#define ST7789_INVOFF 0x20
#define ST7789_INVON 0x21
#define ST7789_DISPOFF 0x28
#define ST7789_DISPON 0x29
#define ST7789_CASET 0x2A
#define ST7789_RASET 0x2B
#define ST7789_RAMWR 0x2C
#define ST7789_RAMRD 0x2E
#define ST7789_PTLAR 0x30
#define ST7789_VSCRDEF 0x33
#define ST7789_MADCTL 0x36
#define ST7789_VSCRSADD 0x37
#define ST7789_COLMOD 0x3A
#define ST7789_MADCTL_MY 0x80
#define ST7789_MADCTL_MX 0x40
#define ST7789_MADCTL_MV 0x20
#define ST7789_MADCTL_ML 0x10
#define ST7789_MADCTL_RGB 0x00
#define ST7789_RDID1 0xDA
#define ST7789_RDID2 0xDB
#define ST7789_RDID3 0xDC
#define ST7789_RDID4 0xDD
static const struct st7789_config *config;
static uint16_t width;
static uint16_t height;
static uint16_t x_offset;
static uint16_t y_offset;
// Backlight control
static uint pwm_slice;
static uint pwm_channel;
static uint8_t current_brightness = 100;
static bool pwm_initialized = false;
static inline void cs_select() {
if (config->gpio_cs >= 0) {
asm volatile("nop \n nop \n nop");
gpio_put(config->gpio_cs, 0);
asm volatile("nop \n nop \n nop");
}
}
static inline void cs_deselect() {
if (config->gpio_cs >= 0) {
asm volatile("nop \n nop \n nop");
gpio_put(config->gpio_cs, 1);
asm volatile("nop \n nop \n nop");
}
}
static inline void dc_command() {
asm volatile("nop \n nop \n nop");
gpio_put(config->gpio_dc, 0);
asm volatile("nop \n nop \n nop");
}
static inline void dc_data() {
asm volatile("nop \n nop \n nop");
gpio_put(config->gpio_dc, 1);
asm volatile("nop \n nop \n nop");
}
static inline void reset_pulse() {
gpio_put(config->gpio_rst, 1);
sleep_ms(5);
gpio_put(config->gpio_rst, 0);
sleep_ms(20);
gpio_put(config->gpio_rst, 1);
sleep_ms(150);
}
static void write_command(uint8_t cmd) {
dc_command();
cs_select();
spi_write_blocking(config->spi, &cmd, 1);
cs_deselect();
}
static void write_data(const uint8_t *data, size_t len) {
dc_data();
cs_select();
spi_write_blocking(config->spi, data, len);
cs_deselect();
}
static void write_command_with_data(uint8_t cmd, const uint8_t *data, size_t len) {
write_command(cmd);
write_data(data, len);
}
static void set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
uint8_t data[4];
// Add offsets for display positioning
x0 += x_offset;
x1 += x_offset;
y0 += y_offset;
y1 += y_offset;
// Column address set
data[0] = (x0 >> 8) & 0xFF;
data[1] = x0 & 0xFF;
data[2] = (x1 >> 8) & 0xFF;
data[3] = x1 & 0xFF;
write_command_with_data(ST7789_CASET, data, 4);
// Row address set
data[0] = (y0 >> 8) & 0xFF;
data[1] = y0 & 0xFF;
data[2] = (y1 >> 8) & 0xFF;
data[3] = y1 & 0xFF;
write_command_with_data(ST7789_RASET, data, 4);
// Write to RAM
write_command(ST7789_RAMWR);
}
void st7789_init(const struct st7789_config *c, uint16_t w, uint16_t h) {
config = c;
width = w;
height = h;
// Set offsets for 240x240 display on ST7789
// The Adafruit 1.54" TFT needs these offsets
x_offset = 0;
y_offset = 80; // 240x240 display with 80 pixel row offset
// Initialize SPI
spi_init(config->spi, 62500 * 1000); // 62.5 MHz
gpio_set_function(config->gpio_din, GPIO_FUNC_SPI);
gpio_set_function(config->gpio_clk, GPIO_FUNC_SPI);
// Initialize CS pin
if (config->gpio_cs >= 0) {
gpio_init(config->gpio_cs);
gpio_set_dir(config->gpio_cs, GPIO_OUT);
gpio_put(config->gpio_cs, 1);
}
// Initialize DC pin
gpio_init(config->gpio_dc);
gpio_set_dir(config->gpio_dc, GPIO_OUT);
// Initialize RST pin
gpio_init(config->gpio_rst);
gpio_set_dir(config->gpio_rst, GPIO_OUT);
// Initialize backlight pin with PWM
if (config->gpio_bl >= 0) {
gpio_set_function(config->gpio_bl, GPIO_FUNC_PWM);
pwm_slice = pwm_gpio_to_slice_num(config->gpio_bl);
pwm_channel = pwm_gpio_to_channel(config->gpio_bl);
// Set PWM frequency to ~1kHz
pwm_set_wrap(pwm_slice, 65535);
pwm_set_chan_level(pwm_slice, pwm_channel, 65535); // 100% duty cycle
pwm_set_enabled(pwm_slice, true);
pwm_initialized = true;
}
// Reset display
reset_pulse();
// Software reset
write_command(ST7789_SWRESET);
sleep_ms(150);
// Out of sleep mode
write_command(ST7789_SLPOUT);
sleep_ms(10);
// Set color mode to 16-bit (RGB565)
uint8_t colmod_data = 0x05;
write_command_with_data(ST7789_COLMOD, &colmod_data, 1);
sleep_ms(10);
// Memory data access control
uint8_t madctl_data = ST7789_MADCTL_MX | ST7789_MADCTL_MY | ST7789_MADCTL_RGB;
write_command_with_data(ST7789_MADCTL, &madctl_data, 1);
// Normal display mode on
write_command(ST7789_NORON);
sleep_ms(10);
// Display on
write_command(ST7789_DISPON);
sleep_ms(10);
}
void st7789_fill(uint16_t color) {
set_window(0, 0, width - 1, height - 1);
dc_data();
cs_select();
// Send color data in chunks for better performance
uint8_t data[2] = {(color >> 8) & 0xFF, color & 0xFF};
uint32_t pixel_count = width * height;
// Send pixels in batches
uint8_t buffer[256]; // 128 pixels at a time
for (int i = 0; i < 128; i++) {
buffer[i * 2] = data[0];
buffer[i * 2 + 1] = data[1];
}
uint32_t full_chunks = pixel_count / 128;
uint32_t remaining = pixel_count % 128;
for (uint32_t i = 0; i < full_chunks; i++) {
spi_write_blocking(config->spi, buffer, 256);
}
if (remaining > 0) {
spi_write_blocking(config->spi, buffer, remaining * 2);
}
cs_deselect();
}
void st7789_put(uint16_t color) {
uint8_t data[2] = {(color >> 8) & 0xFF, color & 0xFF};
dc_data();
cs_select();
spi_write_blocking(config->spi, data, 2);
cs_deselect();
}
void st7789_set_cursor(uint16_t x, uint16_t y) {
set_window(x, y, width - 1, height - 1);
}
void st7789_write(const uint16_t *data, size_t len) {
dc_data();
cs_select();
for (size_t i = 0; i < len; i++) {
uint8_t bytes[2] = {(data[i] >> 8) & 0xFF, data[i] & 0xFF};
spi_write_blocking(config->spi, bytes, 2);
}
cs_deselect();
}
void st7789_vertical_scroll(uint16_t row) {
uint8_t data[2] = {(row >> 8) & 0xFF, row & 0xFF};
write_command_with_data(ST7789_VSCRSADD, data, 2);
}
void st7789_draw_pixel(uint16_t x, uint16_t y, uint16_t color) {
if (x >= width || y >= height) return;
set_window(x, y, x, y);
uint8_t data[2] = {(color >> 8) & 0xFF, color & 0xFF};
dc_data();
cs_select();
spi_write_blocking(config->spi, data, 2);
cs_deselect();
}
void st7789_draw_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
// Top and bottom horizontal lines
st7789_fill_rect(x, y, w, 1, color);
st7789_fill_rect(x, y + h - 1, w, 1, color);
// Left and right vertical lines
st7789_fill_rect(x, y, 1, h, color);
st7789_fill_rect(x + w - 1, y, 1, h, color);
}
void st7789_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
if (x >= width || y >= height) return;
if (x + w > width) w = width - x;
if (y + h > height) h = height - y;
set_window(x, y, x + w - 1, y + h - 1);
dc_data();
cs_select();
uint8_t data[2] = {(color >> 8) & 0xFF, color & 0xFF};
uint32_t pixel_count = w * h;
// Send pixels in batches
uint8_t buffer[256];
for (int i = 0; i < 128; i++) {
buffer[i * 2] = data[0];
buffer[i * 2 + 1] = data[1];
}
uint32_t full_chunks = pixel_count / 128;
uint32_t remaining = pixel_count % 128;
for (uint32_t i = 0; i < full_chunks; i++) {
spi_write_blocking(config->spi, buffer, 256);
}
if (remaining > 0) {
spi_write_blocking(config->spi, buffer, remaining * 2);
}
cs_deselect();
}
void st7789_draw_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) {
int16_t f = 1 - r;
int16_t ddF_x = 1;
int16_t ddF_y = -2 * r;
int16_t x = 0;
int16_t y = r;
st7789_draw_pixel(x0, y0 + r, color);
st7789_draw_pixel(x0, y0 - r, color);
st7789_draw_pixel(x0 + r, y0, color);
st7789_draw_pixel(x0 - r, y0, color);
while (x < y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
st7789_draw_pixel(x0 + x, y0 + y, color);
st7789_draw_pixel(x0 - x, y0 + y, color);
st7789_draw_pixel(x0 + x, y0 - y, color);
st7789_draw_pixel(x0 - x, y0 - y, color);
st7789_draw_pixel(x0 + y, y0 + x, color);
st7789_draw_pixel(x0 - y, y0 + x, color);
st7789_draw_pixel(x0 + y, y0 - x, color);
st7789_draw_pixel(x0 - y, y0 - x, color);
}
}
void st7789_fill_circle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) {
int16_t f = 1 - r;
int16_t ddF_x = 1;
int16_t ddF_y = -2 * r;
int16_t x = 0;
int16_t y = r;
st7789_fill_rect(x0 - r, y0, 2 * r + 1, 1, color);
while (x < y) {
if (f >= 0) {
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
st7789_fill_rect(x0 - x, y0 + y, 2 * x + 1, 1, color);
st7789_fill_rect(x0 - x, y0 - y, 2 * x + 1, 1, color);
st7789_fill_rect(x0 - y, y0 + x, 2 * y + 1, 1, color);
st7789_fill_rect(x0 - y, y0 - x, 2 * y + 1, 1, color);
}
}
void st7789_draw_line(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) {
int16_t dx = abs(x1 - x0);
int16_t dy = abs(y1 - y0);
int16_t sx = (x0 < x1) ? 1 : -1;
int16_t sy = (y0 < y1) ? 1 : -1;
int16_t err = dx - dy;
while (1) {
st7789_draw_pixel(x0, y0, color);
if (x0 == x1 && y0 == y1) break;
int16_t e2 = 2 * err;
if (e2 > -dy) {
err -= dy;
x0 += sx;
}
if (e2 < dx) {
err += dx;
y0 += sy;
}
}
}
void st7789_set_brightness(uint8_t brightness) {
if (!pwm_initialized || config->gpio_bl < 0) return;
// Clamp brightness
if (brightness > 100) brightness = 100;
current_brightness = brightness;
// Convert 0-100 to 0-65535
uint16_t level = (uint16_t)((brightness * 65535) / 100);
pwm_set_chan_level(pwm_slice, pwm_channel, level);
}
uint8_t st7789_get_brightness(void) {
return current_brightness;
}
void st7789_sleep(void) {
// Turn off backlight
if (pwm_initialized && config->gpio_bl >= 0) {
pwm_set_chan_level(pwm_slice, pwm_channel, 0);
}
// Display off
write_command(ST7789_DISPOFF);
sleep_ms(10);
// Sleep in
write_command(ST7789_SLPIN);
sleep_ms(120);
}
void st7789_wake(void) {
// Sleep out
write_command(ST7789_SLPOUT);
sleep_ms(120);
// Display on
write_command(ST7789_DISPON);
sleep_ms(10);
// Restore brightness
st7789_set_brightness(current_brightness);
}