espidf驱动SPI ST7735S LCD

参考

ESP32 波特律动oled.csdn

香橙派zero3配个iic屏显示ip.csdn

蚂蚁S9矿板PS 配个iic屏显示ip.csdn

espidf的iic的SSD1306的oled.csdn

espidf的SPI的ILI9341的LCD.csdn

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": {}
}
相关推荐
kaixin_啊啊1 小时前
Codex论文辅助全流程
人工智能·笔记·学习·ai·大模型
愚公移山填海1 小时前
【无标题】
笔记·python·学习·pycharm
边境悍匪2 小时前
蜗牛学苑 Java 智能体学习 Day41|ElementPlus 布局、Vue‑Router 登录、Axios 思维导图复盘
java·vue.js·学习
(Charon)2 小时前
【Kafka】消息队列学习(二):Topic、Partition与Offset,消息到底存在哪里
分布式·学习·kafka
彧azz2 小时前
Git 学习指南:从入门到进阶的完整实践手册
git·学习
彧azz11 小时前
图的存储结构详解:邻接矩阵的原理、实现与应用
开发语言·数据结构·学习·php
平头哥AI11 小时前
Day 22 _ 包装错误别丢链_%w、errors.Is 与 errors.As
android·服务器·学习·golang·go
一阵寒风11 小时前
CAM智能化助力PCB 智能制造-培训体系第六章.项目开发学习
学习·制造
传奇开心果编程12 小时前
【Xilem 0.4 基础语法学与练】第15课:状态管理与 memoize 性能优化
学习·rust·前端框架