参考
esp32c3
80*160屏幕 的 SPI ST7735S LCD
连线
bash
#define LCD_RSTN GPIO_NUM_0
#define LCD_RS GPIO_NUM_1
#define LCD_SPI_MISO GPIO_NUM_2
#define LCD_BL GPIO_NUM_19
#define LCD_SPI_MOSI GPIO_NUM_7
#define LCD_SPI_CLK GPIO_NUM_6
#define LCD_SPI_CS GPIO_NUM_10
bsp
spi
bsp_spi.h
c
#ifndef BSP_SPI_H
#define BSP_SPI_H
#include "stdbool.h"
#include "stddef.h"
#include "stdint.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#define LCD_RSTN GPIO_NUM_0
#define LCD_RS GPIO_NUM_1
#define LCD_SPI_MISO GPIO_NUM_2
#define LCD_BL GPIO_NUM_19
#define LCD_SPI_MOSI GPIO_NUM_7
#define LCD_SPI_CLK GPIO_NUM_6
#define LCD_SPI_CS GPIO_NUM_10
#ifdef __cplusplus
extern "C" {
#endif
void bsp_spi_init(void);
spi_device_handle_t bsp_spi_lcd_device(void);
esp_err_t bsp_spi_lcd_write_command(const uint8_t *data, size_t len);
esp_err_t bsp_spi_lcd_write_data(const uint8_t *data, size_t len);
void bsp_spi_lcd_reset(void);
void bsp_spi_lcd_set_backlight(bool enabled);
#ifdef __cplusplus
}
#endif
#endif
bsp_spi.c
c
#include "bsp_spi.h"
#include "esp_rom_sys.h"
#define LCD_SPI_HOST SPI2_HOST
#define LCD_SPI_CLOCK_HZ (40 * 1000 * 1000)
#define LCD_MAX_TRANSFER_SIZE (320 * 8 * 2)
static spi_device_handle_t lcd_spi_device;
static bool spi_initialized;
static esp_err_t bsp_spi_lcd_write(bool is_data, const uint8_t *data, size_t len)
{
if (!spi_initialized || lcd_spi_device == NULL) {
return ESP_ERR_INVALID_STATE;
}
if (data == NULL || len == 0 || len > LCD_MAX_TRANSFER_SIZE) {
return ESP_ERR_INVALID_ARG;
}
gpio_set_level(LCD_RS, is_data ? 1 : 0);
spi_transaction_t transaction = {
.length = len * 8,
.tx_buffer = data,
};
return spi_device_transmit(lcd_spi_device, &transaction);
}
void bsp_spi_init(void)
{
if (spi_initialized) {
return;
}
const gpio_config_t lcd_gpio_config = {
.pin_bit_mask = (1ULL << LCD_RSTN) | (1ULL << LCD_RS) | (1ULL << LCD_BL),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&lcd_gpio_config));
gpio_set_level(LCD_RSTN, 1);
gpio_set_level(LCD_RS, 0);
gpio_set_level(LCD_BL, 0);
const spi_bus_config_t bus_config = {
.mosi_io_num = LCD_SPI_MOSI,
.miso_io_num = LCD_SPI_MISO,
.sclk_io_num = LCD_SPI_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = LCD_MAX_TRANSFER_SIZE,
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO));
const spi_device_interface_config_t device_config = {
.clock_speed_hz = LCD_SPI_CLOCK_HZ,
.mode = 0,
.spics_io_num = LCD_SPI_CS,
.queue_size = 1,
};
ESP_ERROR_CHECK(spi_bus_add_device(LCD_SPI_HOST, &device_config, &lcd_spi_device));
spi_initialized = true;
}
spi_device_handle_t bsp_spi_lcd_device(void)
{
return lcd_spi_device;
}
esp_err_t bsp_spi_lcd_write_command(const uint8_t *data, size_t len)
{
return bsp_spi_lcd_write(false, data, len);
}
esp_err_t bsp_spi_lcd_write_data(const uint8_t *data, size_t len)
{
return bsp_spi_lcd_write(true, data, len);
}
void bsp_spi_lcd_reset(void)
{
gpio_set_level(LCD_RSTN, 0);
esp_rom_delay_us(20000);
gpio_set_level(LCD_RSTN, 1);
esp_rom_delay_us(120000);
}
void bsp_spi_lcd_set_backlight(bool enabled)
{
gpio_set_level(LCD_BL, enabled ? 1 : 0);
}
oled
font.h font.cpp 文件 和
ESP32 波特律动oled.csdn 中的一样
oled.h
c
#ifndef __OLED_H__
#define __OLED_H__
#include "font.h"
#include "string.h"
typedef enum {
OLED_COLOR_BLACK = 0x0000,
OLED_COLOR_WHITE = 0xFFFF,
OLED_COLOR_RED = 0xF800,
OLED_COLOR_GREEN = 0x07E0,
OLED_COLOR_BLUE = 0x001F,
OLED_COLOR_YELLOW = 0xFFE0,
OLED_COLOR_CYAN = 0x07FF,
OLED_COLOR_MAGENTA = 0xF81F,
OLED_COLOR_ORANGE = 0xFD20,
OLED_COLOR_PURPLE = 0x8010,
OLED_COLOR_PINK = 0xFE19,
OLED_COLOR_GRAY = 0x8410,
OLED_COLOR_LIGHT_GRAY = 0xC618,
OLED_COLOR_DARK_GRAY = 0x4208,
OLED_COLOR_BROWN = 0xA145,
OLED_COLOR_DARK_RED = 0x8800,
OLED_COLOR_DARK_GREEN = 0x0400,
OLED_COLOR_DARK_BLUE = 0x0010,
OLED_COLOR_GOLD = 0xFEA0,
OLED_COLOR_NAVY = 0x000F,
OLED_COLOR_TEAL = 0x0410,
OLED_COLOR_LIME = 0x87E0,
OLED_COLOR_SILVER = 0xC618,
OLED_COLOR_MAROON = 0x7800,
OLED_COLOR_OLIVE = 0x8400,
OLED_COLOR_AQUA = 0x07FF,
OLED_COLOR_FUCHSIA = 0xF81F,
OLED_COLOR_VIOLET = 0xEC1D,
OLED_COLOR_INDIGO = 0x4810,
OLED_COLOR_SKY_BLUE = 0x867D,
OLED_COLOR_LIGHT_BLUE = 0xAEDC,
OLED_COLOR_LIGHT_GREEN = 0x9772,
OLED_COLOR_KHAKI = 0xF751,
OLED_COLOR_CORAL = 0xFBEA,
OLED_COLOR_TOMATO = 0xFB08,
OLED_COLOR_SALMON = 0xFC0E,
OLED_COLOR_TURQUOISE = 0x471A,
OLED_COLOR_SPRING_GREEN = 0x07EF,
OLED_COLOR_SEA_GREEN = 0x2C4A,
OLED_COLOR_NORMAL = OLED_COLOR_WHITE,
OLED_COLOR_REVERSED = 0x10000
} OLED_ColorMode;
/*============================================================================
* LCD 屏幕参数 (ST7735S, SPI 接口, 80x160)
*
* ST7735S 的显存(GRAM)为 132x162, 80x160 可视区域在显存中居中, 所以写入
* 显存时要加上偏移; 若画面出现偏移/镜像/红蓝互换, 只需调整下面这几个宏,
* 不需要改动驱动逻辑.
*==========================================================================*/
#define OLED_WIDTH 80
#define OLED_HEIGHT 160
/* 可视区域在 GRAM 中的偏移: 列 (132-80)/2 = 26, 行 (162-160)/2 = 1 */
#define ST7735_X_OFFSET 26
#define ST7735_Y_OFFSET 1
/* MADCTL(0x36) 参数
* bit7 MY(行镜像) bit6 MX(列镜像) bit5 MV(行列交换) bit3 BGR(红蓝交换)
* 0xC8 = MY|MX|BGR : 竖屏 80x160, 常用配置
* 0x00 = 竖屏, 画面与 0xC8 相差 180 度
* 0x68 = MX|BGR / 0xA8 = MY|BGR : 画面左右或上下翻转
* 画面上下颠倒 -> 改 0x00 ; 左右颠倒 -> 换 0x68 / 0xA8
*/
#define ST7735_MADCTL_VALUE 0xC8
/* 颜色反相: 部分 IPS 模块需要打开反相(0x21)颜色才正常,
* 若显示为"白底黑字"或颜色整体反掉, 把这里改成 0 即可 */
#define ST7735_INVERT_COLORS 1
void OLED_Init(void);
void OLED_DisPlay_On(void);
void OLED_DisPlay_Off(void);
void OLED_SetColorMode(OLED_ColorMode mode);
void OLED_SetBackgroundColor(OLED_ColorMode color);
OLED_ColorMode OLED_Color565(uint8_t red, uint8_t green, uint8_t blue);
void OLED_NewFrame(void);
void OLED_ShowFrame(void);
void OLED_SetPixel(uint16_t x, uint16_t y, OLED_ColorMode color);
void OLED_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, OLED_ColorMode color);
void OLED_DrawRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, OLED_ColorMode color);
void OLED_DrawFilledRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, OLED_ColorMode color);
void OLED_DrawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, OLED_ColorMode color);
void OLED_DrawFilledTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, OLED_ColorMode color);
void OLED_DrawCircle(uint16_t x, uint16_t y, uint16_t r, OLED_ColorMode color);
void OLED_DrawFilledCircle(uint16_t x, uint16_t y, uint16_t r, OLED_ColorMode color);
void OLED_DrawEllipse(uint16_t x, uint16_t y, uint16_t a, uint16_t b, OLED_ColorMode color);
void OLED_DrawImage(uint16_t x, uint16_t y, const Image *img, OLED_ColorMode color);
void OLED_PrintASCIIChar(uint16_t x, uint16_t y, char ch, const ASCIIFont *font, OLED_ColorMode color);
void OLED_PrintASCIIString(uint16_t x, uint16_t y, const char *str, const ASCIIFont *font, OLED_ColorMode color);
void OLED_PrintString(uint16_t x, uint16_t y, const char *str, const Font *font, OLED_ColorMode color);
#endif // __OLED_H__
oled.cpp
c
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include "oled.h"
#include "../spi/bsp_spi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define OLED_PAGE ((OLED_HEIGHT + 7) / 8)
#define LCD_FRAME_ROWS 8
#define ST7735_SWRESET 0x01
#define ST7735_SLPOUT 0x11
#define ST7735_NORON 0x13
#define ST7735_INVOFF 0x20
#define ST7735_INVON 0x21
#define ST7735_DISPOFF 0x28
#define ST7735_DISPON 0x29
#define ST7735_CASET 0x2A
#define ST7735_RASET 0x2B
#define ST7735_RAMWR 0x2C
#define ST7735_MADCTL 0x36
#define ST7735_COLMOD 0x3A
#define ST7735_FRMCTR1 0xB1
#define ST7735_FRMCTR2 0xB2
#define ST7735_FRMCTR3 0xB3
#define ST7735_INVCTR 0xB4
#define ST7735_PWCTR1 0xC0
#define ST7735_PWCTR2 0xC1
#define ST7735_PWCTR3 0xC2
#define ST7735_PWCTR4 0xC3
#define ST7735_PWCTR5 0xC4
#define ST7735_VMCTR1 0xC5
#define ST7735_GMCTRP1 0xE0
#define ST7735_GMCTRN1 0xE1
static uint16_t oled_gram[OLED_HEIGHT][OLED_WIDTH];
static uint8_t lcd_frame_buffer[LCD_FRAME_ROWS][OLED_WIDTH * 2];
static OLED_ColorMode oled_background_color = OLED_COLOR_BLACK;
static void oled_write_command(uint8_t command)
{
ESP_ERROR_CHECK(bsp_spi_lcd_write_command(&command, sizeof(command)));
}
static void oled_write_data(const uint8_t *data, size_t len)
{
ESP_ERROR_CHECK(bsp_spi_lcd_write_data(data, len));
}
static void oled_write_command_data(uint8_t command, const uint8_t *data, size_t len)
{
oled_write_command(command);
if (len != 0) {
oled_write_data(data, len);
}
}
/**
* @brief 设置 ST7735S 的显存写入窗口(自动叠加 80x160 可视区域偏移)
*/
static void st7735_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
const uint16_t start_column = (uint16_t)(x0 + ST7735_X_OFFSET);
const uint16_t end_column = (uint16_t)(x1 + ST7735_X_OFFSET);
const uint16_t start_row = (uint16_t)(y0 + ST7735_Y_OFFSET);
const uint16_t end_row = (uint16_t)(y1 + ST7735_Y_OFFSET);
const uint8_t column_data[] = {
(uint8_t)(start_column >> 8), (uint8_t)start_column,
(uint8_t)(end_column >> 8), (uint8_t)end_column,
};
const uint8_t page_data[] = {
(uint8_t)(start_row >> 8), (uint8_t)start_row,
(uint8_t)(end_row >> 8), (uint8_t)end_row,
};
oled_write_command_data(ST7735_CASET, column_data, sizeof(column_data));
oled_write_command_data(ST7735_RASET, page_data, sizeof(page_data));
}
static void oled_set_pixel_signed(int32_t x, int32_t y, OLED_ColorMode color)
{
if (x < 0 || x >= OLED_WIDTH || y < 0 || y >= OLED_HEIGHT) {
return;
}
oled_gram[y][x] = (uint16_t)(color == OLED_COLOR_REVERSED ? OLED_COLOR_WHITE : color);
}
static void oled_draw_line_signed(int32_t x0, int32_t y0, int32_t x1, int32_t y1,
OLED_ColorMode color)
{
const int32_t dx = abs(x1 - x0);
const int32_t sx = x0 < x1 ? 1 : -1;
const int32_t dy = -abs(y1 - y0);
const int32_t sy = y0 < y1 ? 1 : -1;
int32_t error = dx + dy;
while (true) {
oled_set_pixel_signed(x0, y0, color);
if (x0 == x1 && y0 == y1) {
break;
}
const int32_t error_twice = error * 2;
if (error_twice >= dy) {
error += dy;
x0 += sx;
}
if (error_twice <= dx) {
error += dx;
y0 += sy;
}
}
}
static int32_t clamp_coordinate(int32_t value, int32_t lower, int32_t upper)
{
if (value < lower) {
return lower;
}
if (value > upper) {
return upper;
}
return value;
}
static int64_t triangle_edge(int32_t ax, int32_t ay, int32_t bx, int32_t by,
int32_t px, int32_t py)
{
return (int64_t)(px - ax) * (by - ay) - (int64_t)(py - ay) * (bx - ax);
}
static void oled_draw_block(uint16_t x, uint16_t y, const uint8_t *data,
uint8_t width, uint8_t height, OLED_ColorMode color)
{
if (data == NULL || width == 0 || height == 0) {
return;
}
const OLED_ColorMode foreground_color = color == OLED_COLOR_REVERSED
? OLED_COLOR_BLACK : color;
const OLED_ColorMode background_color = color == OLED_COLOR_REVERSED
? OLED_COLOR_WHITE : oled_background_color;
for (uint16_t column = 0; column < width; column++) {
for (uint16_t row = 0; row < height; row++) {
oled_set_pixel_signed((int32_t)x + column, (int32_t)y + row, background_color);
}
}
for (uint16_t column = 0; column < width; column++) {
for (uint16_t row = 0; row < height; row++) {
const uint16_t data_index = (row / 8) * width + column;
const bool pixel_on = (data[data_index] & (1U << (row % 8))) != 0;
if (pixel_on) {
oled_set_pixel_signed((int32_t)x + column, (int32_t)y + row, foreground_color);
}
}
}
}
void OLED_Init(void)
{
bsp_spi_init();
bsp_spi_lcd_set_backlight(false);
bsp_spi_lcd_reset();
/* 帧率控制: 正常模式/空闲模式/局部模式 */
const uint8_t frame_rate_normal[] = {0x01, 0x2C, 0x2D};
const uint8_t frame_rate_idle[] = {0x01, 0x2C, 0x2D};
const uint8_t frame_rate_partial[] = {0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D};
const uint8_t inversion_control[] = {0x07};
/* 电源控制 */
const uint8_t power_control_1[] = {0xA2, 0x02, 0x84};
const uint8_t power_control_2[] = {0xC5};
const uint8_t power_control_3[] = {0x0A, 0x00};
const uint8_t power_control_4[] = {0x8A, 0x2A};
const uint8_t power_control_5[] = {0x8A, 0xEE};
const uint8_t vcom_control[] = {0x0E};
const uint8_t memory_access_control[] = {ST7735_MADCTL_VALUE};
/* 0x05: 16bit/pixel (RGB565) */
const uint8_t pixel_format[] = {0x05};
const uint8_t positive_gamma[] = {
0x02, 0x1C, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2D,
0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10,
};
const uint8_t negative_gamma[] = {
0x03, 0x1D, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D,
0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10,
};
oled_write_command(ST7735_SWRESET);
vTaskDelay(pdMS_TO_TICKS(150));
oled_write_command(ST7735_SLPOUT);
vTaskDelay(pdMS_TO_TICKS(120));
oled_write_command_data(ST7735_FRMCTR1, frame_rate_normal, sizeof(frame_rate_normal));
oled_write_command_data(ST7735_FRMCTR2, frame_rate_idle, sizeof(frame_rate_idle));
oled_write_command_data(ST7735_FRMCTR3, frame_rate_partial, sizeof(frame_rate_partial));
oled_write_command_data(ST7735_INVCTR, inversion_control, sizeof(inversion_control));
oled_write_command_data(ST7735_PWCTR1, power_control_1, sizeof(power_control_1));
oled_write_command_data(ST7735_PWCTR2, power_control_2, sizeof(power_control_2));
oled_write_command_data(ST7735_PWCTR3, power_control_3, sizeof(power_control_3));
oled_write_command_data(ST7735_PWCTR4, power_control_4, sizeof(power_control_4));
oled_write_command_data(ST7735_PWCTR5, power_control_5, sizeof(power_control_5));
oled_write_command_data(ST7735_VMCTR1, vcom_control, sizeof(vcom_control));
oled_write_command(ST7735_INVERT_COLORS ? ST7735_INVON : ST7735_INVOFF);
oled_write_command_data(ST7735_MADCTL, memory_access_control, sizeof(memory_access_control));
oled_write_command_data(ST7735_COLMOD, pixel_format, sizeof(pixel_format));
oled_write_command_data(ST7735_GMCTRP1, positive_gamma, sizeof(positive_gamma));
oled_write_command_data(ST7735_GMCTRN1, negative_gamma, sizeof(negative_gamma));
oled_write_command(ST7735_NORON);
vTaskDelay(pdMS_TO_TICKS(10));
oled_write_command(ST7735_DISPON);
vTaskDelay(pdMS_TO_TICKS(100));
OLED_NewFrame();
OLED_ShowFrame();
bsp_spi_lcd_set_backlight(true);
}
void OLED_DisPlay_On(void)
{
oled_write_command(ST7735_DISPON);
bsp_spi_lcd_set_backlight(true);
}
void OLED_DisPlay_Off(void)
{
bsp_spi_lcd_set_backlight(false);
oled_write_command(ST7735_DISPOFF);
}
void OLED_SetColorMode(OLED_ColorMode mode)
{
oled_write_command(mode == OLED_COLOR_REVERSED ? ST7735_INVON : ST7735_INVOFF);
}
void OLED_SetBackgroundColor(OLED_ColorMode color)
{
oled_background_color = color == OLED_COLOR_REVERSED ? OLED_COLOR_BLACK : color;
}
OLED_ColorMode OLED_Color565(uint8_t red, uint8_t green, uint8_t blue)
{
return (OLED_ColorMode)(((red & 0xF8) << 8) |
((green & 0xFC) << 3) |
(blue >> 3));
}
void OLED_NewFrame(void)
{
for (uint16_t row = 0; row < OLED_HEIGHT; row++) {
for (uint16_t column = 0; column < OLED_WIDTH; column++) {
oled_gram[row][column] = (uint16_t)oled_background_color;
}
}
}
void OLED_ShowFrame(void)
{
st7735_set_window(0, 0, OLED_WIDTH - 1, OLED_HEIGHT - 1);
oled_write_command(ST7735_RAMWR);
for (uint16_t first_row = 0; first_row < OLED_HEIGHT; first_row += LCD_FRAME_ROWS) {
const uint16_t row_count = (OLED_HEIGHT - first_row < LCD_FRAME_ROWS)
? OLED_HEIGHT - first_row : LCD_FRAME_ROWS;
for (uint16_t row = 0; row < row_count; row++) {
for (uint16_t column = 0; column < OLED_WIDTH; column++) {
const uint16_t rgb565 = oled_gram[first_row + row][column];
lcd_frame_buffer[row][column * 2] = (uint8_t)(rgb565 >> 8);
lcd_frame_buffer[row][column * 2 + 1] = (uint8_t)rgb565;
}
}
oled_write_data(&lcd_frame_buffer[0][0], row_count * OLED_WIDTH * 2);
}
}
void OLED_SetPixel(uint16_t x, uint16_t y, OLED_ColorMode color)
{
oled_set_pixel_signed(x, y, color);
}
void OLED_SetByte_Fine(uint16_t page, uint16_t column, uint8_t data,
uint8_t start, uint8_t end, OLED_ColorMode color)
{
if (page >= OLED_PAGE || column >= OLED_WIDTH || start > end || end >= 8) {
return;
}
const OLED_ColorMode foreground_color = color == OLED_COLOR_REVERSED
? OLED_COLOR_BLACK : color;
const OLED_ColorMode background_color = color == OLED_COLOR_REVERSED
? OLED_COLOR_WHITE : oled_background_color;
for (uint8_t bit = start; bit <= end; bit++) {
oled_set_pixel_signed(column, page * 8 + bit,
(data & (1U << bit)) != 0 ? foreground_color : background_color);
}
}
void OLED_SetByte(uint16_t page, uint16_t column, uint8_t data, OLED_ColorMode color)
{
if (page >= OLED_PAGE || column >= OLED_WIDTH) {
return;
}
const OLED_ColorMode foreground_color = color == OLED_COLOR_REVERSED
? OLED_COLOR_BLACK : color;
const OLED_ColorMode background_color = color == OLED_COLOR_REVERSED
? OLED_COLOR_WHITE : oled_background_color;
for (uint8_t bit = 0; bit < 8; bit++) {
oled_set_pixel_signed(column, page * 8 + bit,
(data & (1U << bit)) != 0 ? foreground_color : background_color);
}
}
void OLED_SetBits_Fine(uint16_t x, uint16_t y, uint8_t data, uint8_t len,
OLED_ColorMode color)
{
if (len == 0) {
return;
}
const OLED_ColorMode foreground_color = color == OLED_COLOR_REVERSED
? OLED_COLOR_BLACK : color;
const OLED_ColorMode background_color = color == OLED_COLOR_REVERSED
? OLED_COLOR_WHITE : oled_background_color;
for (uint8_t bit = 0; bit < len; bit++) {
oled_set_pixel_signed(x, y + bit,
(data & (1U << bit)) != 0 ? foreground_color : background_color);
}
}
void OLED_SetBits(uint16_t x, uint16_t y, uint8_t data, OLED_ColorMode color)
{
OLED_SetBits_Fine(x, y, data, 8, color);
}
void OLED_SetBlock(uint16_t x, uint16_t y, const uint8_t *data,
uint8_t width, uint8_t height, OLED_ColorMode color)
{
oled_draw_block(x, y, data, width, height, color);
}
void OLED_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
OLED_ColorMode color)
{
oled_draw_line_signed(x1, y1, x2, y2, color);
}
void OLED_DrawRectangle(uint16_t x, uint16_t y, uint16_t width, uint16_t height,
OLED_ColorMode color)
{
oled_draw_line_signed(x, y, (int32_t)x + width - 1, y, color);
oled_draw_line_signed(x, (int32_t)y + height - 1, (int32_t)x + width - 1,
(int32_t)y + height - 1, color);
oled_draw_line_signed(x, y, x, (int32_t)y + height - 1, color);
oled_draw_line_signed((int32_t)x + width - 1, y, (int32_t)x + width - 1,
(int32_t)y + height - 1, color);
}
void OLED_DrawFilledRectangle(uint16_t x, uint16_t y, uint16_t width, uint16_t height,
OLED_ColorMode color)
{
if (width == 0 || height == 0) {
return;
}
for (uint16_t row = 0; row < height; row++) {
oled_draw_line_signed(x, (int32_t)y + row, (int32_t)x + width - 1,
(int32_t)y + row, color);
}
}
void OLED_DrawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
uint16_t x3, uint16_t y3, OLED_ColorMode color)
{
oled_draw_line_signed(x1, y1, x2, y2, color);
oled_draw_line_signed(x2, y2, x3, y3, color);
oled_draw_line_signed(x3, y3, x1, y1, color);
}
void OLED_DrawFilledTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2,
uint16_t x3, uint16_t y3, OLED_ColorMode color)
{
const int64_t area = triangle_edge(x1, y1, x2, y2, x3, y3);
if (area == 0) {
OLED_DrawTriangle(x1, y1, x2, y2, x3, y3, color);
return;
}
const int32_t minimum_x = clamp_coordinate((int32_t)std::min({x1, x2, x3}), 0, OLED_WIDTH - 1);
const int32_t maximum_x = clamp_coordinate((int32_t)std::max({x1, x2, x3}), 0, OLED_WIDTH - 1);
const int32_t minimum_y = clamp_coordinate((int32_t)std::min({y1, y2, y3}), 0, OLED_HEIGHT - 1);
const int32_t maximum_y = clamp_coordinate((int32_t)std::max({y1, y2, y3}), 0, OLED_HEIGHT - 1);
for (int32_t y = minimum_y; y <= maximum_y; y++) {
for (int32_t x = minimum_x; x <= maximum_x; x++) {
const int64_t edge1 = triangle_edge(x1, y1, x2, y2, x, y);
const int64_t edge2 = triangle_edge(x2, y2, x3, y3, x, y);
const int64_t edge3 = triangle_edge(x3, y3, x1, y1, x, y);
const bool inside = area > 0
? edge1 >= 0 && edge2 >= 0 && edge3 >= 0
: edge1 <= 0 && edge2 <= 0 && edge3 <= 0;
if (inside) {
oled_set_pixel_signed(x, y, color);
}
}
}
}
void OLED_DrawCircle(uint16_t x, uint16_t y, uint16_t radius, OLED_ColorMode color)
{
int32_t offset_x = 0;
int32_t offset_y = radius;
int32_t decision = 3 - 2 * radius;
while (offset_x <= offset_y) {
oled_set_pixel_signed(x + offset_x, y + offset_y, color);
oled_set_pixel_signed(x - offset_x, y + offset_y, color);
oled_set_pixel_signed(x + offset_x, y - offset_y, color);
oled_set_pixel_signed(x - offset_x, y - offset_y, color);
oled_set_pixel_signed(x + offset_y, y + offset_x, color);
oled_set_pixel_signed(x - offset_y, y + offset_x, color);
oled_set_pixel_signed(x + offset_y, y - offset_x, color);
oled_set_pixel_signed(x - offset_y, y - offset_x, color);
offset_x++;
if (decision > 0) {
offset_y--;
decision += 4 * (offset_x - offset_y) + 10;
} else {
decision += 4 * offset_x + 6;
}
}
}
void OLED_DrawFilledCircle(uint16_t x, uint16_t y, uint16_t radius, OLED_ColorMode color)
{
for (int32_t offset_y = -(int32_t)radius; offset_y <= (int32_t)radius; offset_y++) {
const int32_t horizontal = (int32_t)sqrt((double)radius * radius -
(double)offset_y * offset_y);
oled_draw_line_signed((int32_t)x - horizontal, (int32_t)y + offset_y,
(int32_t)x + horizontal, (int32_t)y + offset_y, color);
}
}
void OLED_DrawEllipse(uint16_t x, uint16_t y, uint16_t radius_x, uint16_t radius_y,
OLED_ColorMode color)
{
if (radius_x == 0 || radius_y == 0) {
return;
}
constexpr double pi = 3.14159265358979323846;
const double step_count = 2.0 * pi * (radius_x + radius_y) / 2.0;
const int32_t steps = step_count < 8 ? 8 : (int32_t)step_count;
for (int32_t step = 0; step < steps; step++) {
const double angle = 2.0 * pi * step / steps;
const int32_t point_x = (int32_t)lround(radius_x * cos(angle));
const int32_t point_y = (int32_t)lround(radius_y * sin(angle));
oled_set_pixel_signed((int32_t)x + point_x, (int32_t)y + point_y, color);
}
}
void OLED_DrawImage(uint16_t x, uint16_t y, const Image *image, OLED_ColorMode color)
{
if (image != NULL) {
oled_draw_block(x, y, image->data, image->w, image->h, color);
}
}
void OLED_PrintASCIIChar(uint16_t x, uint16_t y, char ch, const ASCIIFont *font,
OLED_ColorMode color)
{
if (font == NULL || font->chars == NULL || ch < ' ' || ch > '~') {
return;
}
const uint16_t bytes_per_char = (uint16_t)((font->h + 7) / 8) * font->w;
oled_draw_block(x, y, font->chars + (ch - ' ') * bytes_per_char,
font->w, font->h, color);
}
void OLED_PrintASCIIString(uint16_t x, uint16_t y, const char *str, const ASCIIFont *font,
OLED_ColorMode color)
{
if (str == NULL || font == NULL) {
return;
}
uint16_t current_x = x;
while (*str != '\0') {
OLED_PrintASCIIChar(current_x, y, *str, font, color);
current_x += font->w;
str++;
}
}
static uint8_t oled_utf8_length(const char *string)
{
const uint8_t first_byte = (uint8_t)string[0];
if ((first_byte & 0x80) == 0x00) {
return 1;
}
if ((first_byte & 0xE0) == 0xC0) {
return 2;
}
if ((first_byte & 0xF0) == 0xE0) {
return 3;
}
if ((first_byte & 0xF8) == 0xF0) {
return 4;
}
return 0;
}
void OLED_PrintString(uint16_t x, uint16_t y, const char *str, const Font *font,
OLED_ColorMode color)
{
if (str == NULL || font == NULL || font->chars == NULL || font->ascii == NULL) {
return;
}
uint16_t string_index = 0;
const uint16_t bytes_per_glyph = (uint16_t)((font->h + 7) / 8) * font->w + 4;
while (str[string_index] != '\0') {
const uint8_t utf8_length = oled_utf8_length(str + string_index);
if (utf8_length == 0) {
break;
}
bool found = false;
for (uint8_t glyph_index = 0; glyph_index < font->len; glyph_index++) {
const uint8_t *glyph = font->chars + glyph_index * bytes_per_glyph;
if (memcmp(str + string_index, glyph, utf8_length) == 0) {
oled_draw_block(x, y, glyph + 4, font->w, font->h, color);
x += font->w;
string_index += utf8_length;
found = true;
break;
}
}
if (!found) {
if (utf8_length == 1) {
OLED_PrintASCIIChar(x, y, str[string_index], font->ascii, color);
} else {
OLED_PrintASCIIChar(x, y, ' ', font->ascii, color);
}
x += font->ascii->w;
string_index += utf8_length;
}
}
}
user_oled.h
c
#ifndef __user_oled__
#define __user_oled__
#include "stdint.h"
#include "font.h"
#include "string.h"
#include "oled.h"
/**
* 局部刷新字符串(只刷新 x,y 起 w*h 的矩形区域, 超出屏幕的部分自动裁剪)
* @param x 左上角列坐标
* @param y 左上角行坐标
* @param w 刷新区域宽度(像素)
* @param h 刷新区域高度(像素)
* @param str UTF8 字符串
* @param font 字体(字库中没有的字符回退到 font->ascii)
* @param color 文字颜色, 区域内的背景固定为黑色(OLED_COLOR_REVERSED 时反色)
* @note 一次调用只会设置一次显示窗口(一次 CASET/RASET/RAMWR),
* 像素数据按 8 行分块连续写入; 每个字符只查一次字库.
* 为了不占用堆栈, 内部使用静态缓冲, 因此本函数不可重入.
*/
void LCD_ShowString(uint16_t x, uint16_t y,uint16_t w, uint16_t h,const char *str, const Font *font, OLED_ColorMode color);
#endif
user_oled.cpp
c
#include "user_oled.h"
#include "stdbool.h"
#include "../spi/bsp_spi.h"
#define ST7735_CASET 0x2A
#define ST7735_RASET 0x2B
#define ST7735_RAMWR 0x2C
/* 单次 SPI 传输最多 8 行像素(80 x 8 x 2 = 1280B, 与 bsp_spi.c 的上限一致) */
#define USER_LCD_MAX_TRANSFER_SIZE (OLED_WIDTH * 8 * 2)
#define USER_LCD_BLOCK_ROWS 8
typedef struct {
const uint8_t *data;
uint8_t width;
uint8_t height;
} UserGlyph;
/* 每个字符在显示块中的位置: 字符串只解析一次, 之后逐行按位图填色 */
typedef struct {
const uint8_t *data; // 字模数据(去掉前 4 字节 UTF8 编码)
uint16_t offset_x; // 字符在显示块中的起始列
uint16_t width; // 字宽
uint16_t height; // 字高
} UserGlyphLayout;
/* 多行拼接的像素缓冲, 用来把多行合并成一次 SPI 传输
* 注意: 传输时各行是紧挨着的, 所以行间距要用 clipped_width * 2
* (裁剪后宽度可能小于 OLED_WIDTH), 不能直接用固定宽度做二维数组 */
static uint8_t user_lcd_block_buffer[USER_LCD_BLOCK_ROWS * OLED_WIDTH * 2];
/* 布局表按最坏情况(每字符 1 列宽)取 OLED_WIDTH 项 */
static UserGlyphLayout user_glyph_layout[OLED_WIDTH];
static uint8_t user_utf8_length(const char *string)
{
const uint8_t first_byte = (uint8_t)string[0];
if ((first_byte & 0x80) == 0x00) {
return 1;
}
if ((first_byte & 0xE0) == 0xC0) {
return 2;
}
if ((first_byte & 0xF0) == 0xE0) {
return 3;
}
if ((first_byte & 0xF8) == 0xF0) {
return 4;
}
return 0;
}
static bool user_find_glyph(const char *string, const Font *font,
uint8_t utf8_length, UserGlyph *glyph)
{
const uint16_t bytes_per_glyph = (uint16_t)((font->h + 7) / 8) * font->w + 4;
for (uint8_t glyph_index = 0; glyph_index < font->len; glyph_index++) {
const uint8_t *font_glyph = font->chars + glyph_index * bytes_per_glyph;
if (memcmp(string, font_glyph, utf8_length) == 0) {
glyph->data = font_glyph + 4;
glyph->width = font->w;
glyph->height = font->h;
return true;
}
}
if (utf8_length == 1 && string[0] >= ' ' && string[0] <= '~') {
const uint16_t bytes_per_ascii_char = (uint16_t)((font->ascii->h + 7) / 8) *
font->ascii->w;
glyph->data = font->ascii->chars + (string[0] - ' ') * bytes_per_ascii_char;
} else {
const uint16_t bytes_per_ascii_char = (uint16_t)((font->ascii->h + 7) / 8) *
font->ascii->w;
glyph->data = font->ascii->chars + bytes_per_ascii_char;
}
glyph->width = font->ascii->w;
glyph->height = font->ascii->h;
return true;
}
/**
* @brief 解析字符串, 得到每个字符的位置与字模指针
* @note 每个字符只查一次字库(原实现是每个像素都从头重扫一遍字符串)
* @return 覆盖显示宽度 limit 所需的字符个数
*/
static uint16_t user_build_glyph_layout(const char *string, const Font *font,
uint16_t limit)
{
uint16_t glyph_count = 0;
uint16_t current_x = 0;
while (*string != '\0' && current_x < limit && glyph_count < OLED_WIDTH) {
const uint8_t utf8_length = user_utf8_length(string);
if (utf8_length == 0) {
break;
}
UserGlyph glyph;
if (!user_find_glyph(string, font, utf8_length, &glyph)) {
break;
}
user_glyph_layout[glyph_count].data = glyph.data;
user_glyph_layout[glyph_count].offset_x = current_x;
user_glyph_layout[glyph_count].width = glyph.width;
user_glyph_layout[glyph_count].height = glyph.height;
glyph_count++;
current_x += glyph.width;
string += utf8_length;
}
return glyph_count;
}
/**
* @brief 设置局部刷新窗口(行 sy~ey, 列 sx~ex)
* @note 屏幕为 80x160, 显存为 132x162, 所以窗口坐标要叠加偏移,
* 偏移量在 oled.h 中定义(ST7735_X_OFFSET / ST7735_Y_OFFSET)
*/
void User_LCD_Write_Prepare(uint16_t sy,uint16_t sx,uint16_t ey,uint16_t ex){
const uint16_t start_column = (uint16_t)(sx + ST7735_X_OFFSET);
const uint16_t end_column = (uint16_t)(ex + ST7735_X_OFFSET);
const uint16_t start_row = (uint16_t)(sy + ST7735_Y_OFFSET);
const uint16_t end_row = (uint16_t)(ey + ST7735_Y_OFFSET);
const uint8_t column_data[] = {
(uint8_t)(start_column >> 8), (uint8_t)start_column,
(uint8_t)(end_column >> 8), (uint8_t)end_column,
};
const uint8_t page_data[] = {
(uint8_t)(start_row >> 8), (uint8_t)start_row,
(uint8_t)(end_row >> 8), (uint8_t)end_row,
};
const uint8_t column_command = ST7735_CASET;
const uint8_t page_command = ST7735_RASET;
ESP_ERROR_CHECK(bsp_spi_lcd_write_command(&column_command, sizeof(column_command)));
ESP_ERROR_CHECK(bsp_spi_lcd_write_data(column_data, sizeof(column_data)));
ESP_ERROR_CHECK(bsp_spi_lcd_write_command(&page_command, sizeof(page_command)));
ESP_ERROR_CHECK(bsp_spi_lcd_write_data(page_data, sizeof(page_data)));
}
void User_LCD_WriteRAM(uint8_t *buf, uint32_t size){
while (buf != NULL && size != 0) {
const uint32_t transfer_size = size > USER_LCD_MAX_TRANSFER_SIZE
? USER_LCD_MAX_TRANSFER_SIZE : size;
ESP_ERROR_CHECK(bsp_spi_lcd_write_data(buf, transfer_size));
buf += transfer_size;
size -= transfer_size;
}
}
/**
* @brief 局部刷新字符串
*
* 优化点(输出与优化前逐像素一致):
* 1. 字库只查一次: 先把字符串解析成"字符 -> 位置/字模指针"的布局表,
* 之后逐行按字模位图填色, 不再像原来那样每个像素都从头重扫一遍字符串;
* 2. 整块只写一次窗口: 原来每行都要重设一次 CASET/RASET/RAMWR,
* 现在整个 w*h 块只设一次窗口, 显存一次连续写完(按 8 行分块发送).
*/
void LCD_ShowString(uint16_t x, uint16_t y,uint16_t w, uint16_t h,const char *str, const Font *font, OLED_ColorMode color){
if (str == NULL || font == NULL || font->chars == NULL || font->ascii == NULL ||
font->ascii->chars == NULL || w == 0 || h == 0 || x >= OLED_WIDTH || y >= OLED_HEIGHT) {
return;
}
const uint16_t clipped_width = w > OLED_WIDTH - x ? OLED_WIDTH - x : w;
const uint16_t clipped_height = h > OLED_HEIGHT - y ? OLED_HEIGHT - y : h;
/* 第一步: 解析字符串(每个字符只查一次字库) */
const uint16_t glyph_count = user_build_glyph_layout(str, font, clipped_width);
const uint16_t foreground_color = color == OLED_COLOR_REVERSED
? (uint16_t)OLED_COLOR_BLACK : (uint16_t)color;
const uint16_t background_color = color == OLED_COLOR_REVERSED
? (uint16_t)OLED_COLOR_WHITE : (uint16_t)OLED_COLOR_BLACK;
/* 第二步: 整个块只设一次窗口, 后续数据连续写入显存 */
User_LCD_Write_Prepare(y, x, y + clipped_height - 1, x + clipped_width - 1);
const uint8_t ram_write_command = ST7735_RAMWR;
ESP_ERROR_CHECK(bsp_spi_lcd_write_command(&ram_write_command, sizeof(ram_write_command)));
uint16_t buffered_rows = 0;
for (uint16_t row = 0; row < clipped_height; row++) {
uint8_t *line = user_lcd_block_buffer + buffered_rows * clipped_width * 2;
for (uint16_t column = 0; column < clipped_width; column++) {
line[column * 2] = (uint8_t)(background_color >> 8);
line[column * 2 + 1] = (uint8_t)background_color;
}
for (uint16_t index = 0; index < glyph_count; index++) {
const UserGlyphLayout *glyph = &user_glyph_layout[index];
if (row >= glyph->height || glyph->offset_x >= clipped_width) {
continue;
}
uint16_t pixel_count = glyph->width;
if (glyph->offset_x + pixel_count > clipped_width) {
pixel_count = clipped_width - glyph->offset_x;
}
const uint8_t *glyph_bits = glyph->data + (row / 8) * glyph->width;
const uint8_t bit_mask = (uint8_t)(1U << (row % 8));
for (uint16_t pixel = 0; pixel < pixel_count; pixel++) {
if ((glyph_bits[pixel] & bit_mask) == 0) {
continue;
}
uint8_t *target = line + (glyph->offset_x + pixel) * 2;
target[0] = (uint8_t)(foreground_color >> 8);
target[1] = (uint8_t)foreground_color;
}
}
buffered_rows++;
if (buffered_rows == USER_LCD_BLOCK_ROWS || row + 1 == clipped_height) {
ESP_ERROR_CHECK(bsp_spi_lcd_write_data(user_lcd_block_buffer,
buffered_rows * clipped_width * 2));
buffered_rows = 0;
}
}
}
App.cpp
c
#define OS_GLOBALS
#include "App.h"
#include "Main_constant.h"
#include "user_oled.h"
#include "esp_timer.h"
static const char *TAG = "App";
bool App::Init() {
OLED_Init();
return true;
}
/**
* 约100ms每次
* @param ms
*/
int i = 0;
char msg[50] = { 0 };
void App::Loop(uint32_t ms)
{
static int64_t start = esp_timer_get_time();
static int64_t cost_us = esp_timer_get_time();
static bool mode= true;
start = esp_timer_get_time();
mode=!mode;
if(mode){
//局部刷新 2ms
//屏幕 80x160, 每行 16 像素高, 一行最多 5 个汉字(16x16) 或 10 个 ASCII(8x16)
sprintf(msg, "t:%lld", cost_us);
LCD_ShowString(0, 0, 80, 16,msg, &font16x16, OLED_COLOR_RED);
LCD_ShowString(0, 20, 80, 16,"波特律B", &font16x16, OLED_COLOR_CYAN);
int64_t end = esp_timer_get_time();
cost_us = end - start;
} else{
// 全屏刷新 10ms
OLED_NewFrame();
sprintf(msg, "t:%lld", cost_us);
OLED_PrintString(0, 50, msg, &font16x16, OLED_COLOR_GREEN);
OLED_PrintString(0, 70, "波特律A", &font16x16, OLED_COLOR_CYAN);
OLED_ShowFrame();
int64_t end = esp_timer_get_time();
cost_us = end - start;
}
}
diagram.json
json
{
"version": 1,
"author": "Uri Shaked",
"editor": "wokwi",
"parts": [
{ "type": "wokwi-vcc", "id": "vcc", "top": 192.76, "left": 124.8, "attrs": {} },
{ "type": "wokwi-gnd", "id": "gnd", "top": 192.6, "left": 162.6, "attrs": {} },
{ "type": "board-esp32-c3-devkitm-1", "id": "esp", "top": 200, "left": 0, "attrs": {} },
{ "type": "wokwi-74hc595", "id": "u4", "top": 255, "left": 200, "attrs": {} },
{ "type": "wokwi-74hc595", "id": "u5", "top": 255, "left": 300, "attrs": {} },
{ "type": "wokwi-74hc165", "id": "u6", "top": 295, "left": 200, "attrs": {} },
{ "type": "wokwi-74hc165", "id": "u7", "top": 295, "left": 300, "attrs": {} },
{"type": "wokwi-led-bar-graph", "id": "bar1Led", "top": 310, "left": 216.2, "rotate": 90,"attrs": { "color": "lime" }},
{"type": "wokwi-led-bar-graph", "id": "bar2Led", "top": 310, "left": 316.2, "rotate": 90, "attrs": { "color": "lime" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn0", "top": 400, "left": 154, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn1", "top": 400, "left": 174, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn2", "top": 400, "left": 194, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn3", "top": 400, "left": 214, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn4", "top": 400, "left": 234, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn5", "top": 400, "left": 254, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn6", "top": 400, "left": 274, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn7", "top": 400, "left": 294, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn8", "top": 400, "left": 314, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn9", "top": 400, "left": 334, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn10", "top":400, "left": 354, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn11", "top":400, "left": 374, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn12", "top":400, "left": 394, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn13", "top":400, "left": 414, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn14", "top":400, "left": 434, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-pushbutton-6mm", "id": "btn15", "top":400, "left": 454, "attrs": { "color": "green", "xray": "1" }},
{"type": "wokwi-resistor", "id": "rboot", "rotate": 90, "top": 480, "left": 140, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r0", "rotate": 90, "top": 480, "left": 150, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r1", "rotate": 90, "top": 480, "left": 160, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r2", "rotate": 90, "top": 480, "left": 170, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r3", "rotate": 90, "top": 480, "left": 180, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r4", "rotate": 90, "top": 480, "left": 190, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r5", "rotate": 90, "top": 480, "left": 200, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r6", "rotate": 90, "top": 480, "left": 210, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r7", "rotate": 90, "top": 480, "left": 220, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r8", "rotate": 90, "top": 480, "left": 230, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r9", "rotate": 90, "top": 480, "left": 240, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r10", "rotate": 90, "top": 480, "left": 250, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r11", "rotate": 90, "top": 480, "left": 260, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r12", "rotate": 90, "top": 480, "left": 270, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r13", "rotate": 90, "top": 480, "left": 280, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r14", "rotate": 90, "top": 480, "left": 290, "attrs": { "value": "1000" }},
{"type": "wokwi-resistor", "id": "r15", "rotate": 90, "top": 480, "left": 300, "attrs": { "value": "1000" }},
{ "type": "wokwi-ili9341", "rotate": 90, "id": "lcd", "top": 0, "left": 180, "attrs": {} }
],
"connections": [
[ "vcc:VCC", "u4:MR", "green", [ "v0" ] ],
[ "vcc:VCC", "u5:MR", "green", [ "v0" ] ],
[ "vcc:VCC", "rboot:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r0:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r1:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r2:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r3:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r4:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r5:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r6:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r7:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r8:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r9:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r10:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r11:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r12:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r13:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r14:2", "green", [ "v0" ] ],
[ "vcc:VCC", "r15:2", "green", [ "v0" ] ],
[ "gnd:GND", "u4:OE", "green", [ "v0" ] ],
[ "gnd:GND", "u5:OE", "green", [ "v0" ] ],
[ "gnd:GND", "bar1Led:C1", "green", ["v0" ] ],
[ "gnd:GND", "bar1Led:C2", "green", [ "v0" ] ],
[ "gnd:GND", "bar1Led:C3", "green", [ "v0" ] ],
[ "gnd:GND", "bar1Led:C4", "green", [ "v0" ] ],
[ "gnd:GND", "bar1Led:C5", "green", [ "v0" ] ],
[ "gnd:GND", "bar1Led:C6", "green", [ "v0" ] ],
[ "gnd:GND", "bar1Led:C7", "green", ["v0" ] ],
[ "gnd:GND", "bar1Led:C8", "green", [ "v0" ] ],
[ "gnd:GND", "bar1Led:C9", "green", [ "v0" ] ],
[ "gnd:GND", "bar1Led:C10", "green", ["v0" ] ],
[ "gnd:GND", "bar2Led:C1", "green", ["v0" ] ],
[ "gnd:GND", "bar2Led:C2", "green", [ "v0" ] ],
[ "gnd:GND", "bar2Led:C3", "green", [ "v0" ] ],
[ "gnd:GND", "bar2Led:C4", "green", [ "v0" ] ],
[ "gnd:GND", "bar2Led:C5", "green", [ "v0" ] ],
[ "gnd:GND", "bar2Led:C6", "green", [ "v0" ] ],
[ "gnd:GND", "bar2Led:C7", "green", [ "v0" ] ],
[ "gnd:GND", "bar2Led:C8", "green", [ "v0" ] ],
[ "gnd:GND", "bar2Led:C9", "green", [ "v0" ] ],
[ "gnd:GND", "bar2Led:C10", "green", [ "v0" ] ],
[ "gnd:GND", "btn0:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn1:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn2:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn3:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn4:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn5:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn6:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn7:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn8:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn9:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn10:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn11:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn12:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn13:2.l", "green", [ "v0" ] ],
[ "gnd:GND", "btn14:2.l" , "green", [ "v0" ] ],
[ "gnd:GND", "btn15:2.l" , "green", [ "v0" ] ],
[ "esp:TX", "$serialMonitor:RX", "", [] ],
[ "esp:RX", "$serialMonitor:TX", "", [] ],
[ "esp:18", "u4:SHCP", "green", [ "v0" ] ],
[ "esp:18", "u5:SHCP", "green", [ "v0"] ],
[ "esp:18", "u6:CP", "green", [ "v0" ] ],
[ "esp:18", "u7:CP", "green", [ "v0" ] ],
[ "esp:3", "u4:STCP", "green", ["v0" ] ],
[ "esp:3", "u5:STCP", "green", ["v0" ] ],
[ "esp:3", "u6:PL", "green", ["v0" ] ],
[ "esp:3", "u7:PL", "green", ["v0" ] ],
[ "esp:8", "u4:DS", "green", [ "v0" ] ],
[ "u4:Q7S", "u5:DS", "green", [ "v0" ] ],
[ "u6:Q7", "u7:DS", "green", [ "v0" ] ],
[ "esp:9", "u7:Q7", "green", [ "v0" ] ],
[ "esp:9", "rboot:1", "green", [ "v0" ] ],
[ "u4:Q0", "bar2Led:A1", "green", [ "v0" ] ],
[ "u4:Q1", "bar2Led:A2", "green", [ "v0" ] ],
[ "u4:Q2", "bar2Led:A3", "green", [ "v0" ] ],
[ "u4:Q3", "bar2Led:A4", "green", [ "v0" ] ],
[ "u4:Q4", "bar2Led:A5", "green", [ "v0" ] ],
[ "u4:Q5", "bar2Led:A6", "green", [ "v0" ] ],
[ "u4:Q6", "bar2Led:A7", "green", [ "v0" ] ],
[ "u4:Q7", "bar2Led:A8", "green", [ "v0" ] ],
[ "u5:Q0", "bar2Led:A9", "green", [ "v0" ] ],
[ "u5:Q1", "bar2Led:A10", "green", [ "v0" ] ],
[ "u5:Q2", "bar1Led:A1", "green", [ "v0" ] ],
[ "u5:Q3", "bar1Led:A2", "green", [ "v0" ] ],
[ "u5:Q4", "bar1Led:A3", "green", [ "v0" ] ],
[ "u5:Q5", "bar1Led:A4", "green", [ "v0" ] ],
[ "u5:Q6", "bar1Led:A5", "green", [ "v0"] ],
[ "u5:Q7", "bar1Led:A6", "green", [ "v0" ] ],
[ "u6:D0", "btn0:1.l", "green", [ "v0" ] ],
[ "u6:D1", "btn1:1.l", "green", [ "v0" ] ],
[ "u6:D2", "btn2:1.l", "green", [ "v0" ] ],
[ "u6:D3", "btn3:1.l", "green", [ "v0" ] ],
[ "u6:D4", "btn4:1.l", "green", [ "v0" ] ],
[ "u6:D5", "btn5:1.l", "green", [ "v0" ] ],
[ "u6:D6", "btn6:1.l", "green", [ "v0" ] ],
[ "u6:D7", "btn7:1.l", "green", [ "v0" ] ],
[ "u7:D0", "btn8:1.l", "green", [ "v0" ] ],
[ "u7:D1", "btn9:1.l", "green", [ "v0" ] ],
[ "u7:D2", "btn10:1.l", "green", [ "v0" ] ],
[ "u7:D3", "btn11:1.l", "green", [ "v0" ] ],
[ "u7:D4", "btn12:1.l", "green", [ "v0" ] ],
[ "u7:D5", "btn13:1.l", "green", [ "v0" ] ],
[ "u7:D6", "btn14:1.l", "green", [ "v0" ] ],
[ "u7:D7", "btn15:1.l", "green", [ "v0" ] ],
[ "u6:D0", "r0:1", "green", [ "v0" ] ],
[ "u6:D1", "r1:1", "green", [ "v0" ] ],
[ "u6:D2", "r2:1", "green", [ "v0" ] ],
[ "u6:D3", "r3:1", "green", [ "v0" ] ],
[ "u6:D4", "r4:1", "green", [ "v0" ] ],
[ "u6:D5", "r5:1", "green", [ "v0" ] ],
[ "u6:D6", "r6:1", "green", [ "v0" ] ],
[ "u6:D7", "r7:1", "green", [ "v0" ] ],
[ "u7:D0", "r8:1", "green", [ "v0" ] ],
[ "u7:D1", "r9:1", "green", [ "v0" ] ],
[ "u7:D2", "r10:1", "green", [ "v0" ] ],
[ "u7:D3", "r11:1", "green", [ "v0" ] ],
[ "u7:D4", "r12:1", "green", [ "v0" ] ],
[ "u7:D5", "r13:1", "green", [ "v0" ] ],
[ "u7:D6", "r14:1", "green", [ "v0" ] ],
[ "u7:D7", "r15:1", "green", [ "v0" ] ],
[ "lcd:VCC", "vcc:VCC", "red", [ "v0" ] ],
[ "lcd:GND", "gnd:GND", "black", [ ] ],
[ "lcd:CS", "esp:10", "green", [ "v0" ] ],
[ "lcd:RST", "esp:0", "red", [ "v0" ] ],
[ "lcd:D/C", "esp:1", "red", [ "v0" ] ],
[ "lcd:MOSI", "esp:7", "red", [ "v0" ] ],
[ "lcd:SCK", "esp:6", "red", [ "v0" ] ],
[ "lcd:LED", "esp:19", "red", [ "v0" ] ],
[ "lcd:MISO", "esp:2", "red", [ "v0" ] ]
],
"dependencies": {}
}