esp32课设记录(二)lcd屏显示文字与照片
取模软件链接:
链接: 百度网盘 请输入提取码 提取码: 1234
课设要求如图所示,因此需要在esp32显示文字和照片。在上个文章中我已经写了按键相关内容。这篇主要描述怎么显示文字和照片。我使用的是ESP-IDF库。
本项目使用的是基于ST7789驱动芯片的LCD屏幕,通过SPI接口与ESP32通信。这是一个彩色TFT显示屏,具有以下特点:
分辨率:128×160像素
色深:16位色(65536色)
通信方式:SPI(串行外设接口)
控制引脚定义:
#define LCD_PIN_SCL 5 // SPI时钟信号
#define LCD_PIN_SDA 4 // SPI数据信号
#define LCD_PIN_RES 0 // 复位信号
#define LCD_PIN_DC 3 // 数据/命令选择信号
#define LCD_PIN_CS 1 // 片选信号
#define LCD_PIN_BL 2 // 背光控制信号
初始化
感觉和stm32的标准库蛮像的。里面的函数都是ESP-IDF库自带的。
LCD的初始化过程非常关键,按照以下顺序执行:
1.配置背光为输出模式,输出高电平。
2. 配置并初始化SPI总线
各参数详细说明:
sclk_io_num:
指定用于SPI时钟信号的GPIO引脚号
该时钟信号同步数据传输,决定了通信速度
mosi_io_num:
Master Out Slave In,主设备输出/从设备输入
ESP32通过这根线发送数据到LCD
miso_io_num:
Master In Slave Out,主设备输入/从设备输出
设为-1表示不使用此功能
LCD显示通常是单向通信,不需要从LCD读取数据
quadwp_io_num & quadhd_io_num:
用于四线SPI模式的额外数据线
设为-1表示使用标准SPI而非四线模式
max_transfer_sz:
单次SPI传输的最大字节数
设置为整个屏幕的数据量:宽×高×每像素字节数
16位色彩深度,每像素2字节,故乘以2
3. 配置LCD面板IO
各参数详细说明:
dc_gpio_num = LCD_PIN_DC - 数据/命令选择线,区分发送给LCD的是命令还是数据
cs_gpio_num = LCD_PIN_CS - 片选信号线,当有多个SPI设备时,用于选择LCD
pclk_hz = LCD_PIXEL_CLOCK_HZ - 时钟频率,决定数据传输速率,此处为20MHz
spi_mode = 0 - SPI模式,定义时钟极性和相位:模式0:空闲时时钟为低,在上升沿采样
lcd_cmd_bits = 8 - 命令位宽,LCD控制命令的位数
lcd_param_bits = 8 - 参数位宽,命令参数的位数
trans_queue_depth = 10 - 传输队列深度,可以排队等待的传输请求数量,提高吞吐量
4. 创建ST7789面板控制器
.reset_gpio_num = LCD_PIN_RES
指定用于重置LCD控制器的GPIO引脚。初始化过程中,ESP32会通过这个引脚发送复位信号,使LCD控制器恢复到已知初始状态。通常是先将此引脚拉低一段时间(10-100ms),然后拉高,触发硬件复位。
.rgb_endian = LCD_RGB_ENDIAN_RGB
设置RGB颜色数据的字节顺序
LCD_RGB_ENDIAN_RGB:标准RGB格式,红色分量在高位
LCD_RGB_ENDIAN_BGR:BGR格式,蓝色分量在高位
.bits_per_pixel = 16
设置每个像素的位深度。值=16:表示使用RGB565颜色格式(红色5位,绿色6位,蓝色5位)。
数据结构:16位色模式下,每个像素占用2个字节内存。
显示能力:支持65536种颜色
5. 初始化面板并配置显示参数
6. 分配显示缓冲区并清空屏幕
// LCD相关句柄
esp_lcd_panel_handle_t panel_handle = NULL;
uint16_t *lcd_buffer = NULL;// 初始化背光,就是搞高电平ovo
void backlight_init(void)
{gpio_config_t io_conf = {.pin_bit_mask = (1ULL << LCD_PIN_BL),.mode = GPIO_MODE_OUTPUT,.pull_up_en = GPIO_PULLUP_DISABLE,.pull_down_en = GPIO_PULLDOWN_DISABLE,.intr_type = GPIO_INTR_DISABLE,};gpio_config(&io_conf);// 打开背光(根据接线可能需要调整电平)gpio_set_level(LCD_PIN_BL, 1);
}// 清除屏幕上的文本区域
void clear_text_area(uint16_t *buffer, int width, int height)
{// 使用黑色填充文本区域for (int y = 0; y < height; y++){for (int x = 0; x < width; x++){buffer[y * width + x] = 0x0000; // 黑色}}
}void lcd_init(void) {// 1. 初始化背光backlight_init();// 2. 配置并初始化SPI总线// SPI总线配置spi_bus_config_t buscfg = {.sclk_io_num = LCD_PIN_SCL,.mosi_io_num = LCD_PIN_SDA,.miso_io_num = -1, // 不使用MISO.quadwp_io_num = -1,.quadhd_io_num = -1,.max_transfer_sz = LCD_H_RES * LCD_V_RES * 2, // 16bit/pixel};ESP_ERROR_CHECK(spi_bus_initialize(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));// 3. 配置LCD面板IOesp_lcd_panel_io_handle_t io_handle = NULL;esp_lcd_panel_io_spi_config_t io_config = {.dc_gpio_num = LCD_PIN_DC,.cs_gpio_num = LCD_PIN_CS,.pclk_hz = LCD_PIXEL_CLOCK_HZ,.lcd_cmd_bits = 8,.lcd_param_bits = 8,.spi_mode = 0,.trans_queue_depth = 10,};ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_SPI_HOST, &io_config, &io_handle));// 4. 创建ST7789面板控制器esp_lcd_panel_dev_config_t panel_config = {.reset_gpio_num = LCD_PIN_RES,.rgb_endian = LCD_RGB_ENDIAN_RGB, // 使用RGB格式.bits_per_pixel = 16,};// 5. 初始化面板并配置显示参数// 初始化LCD面板ESP_LOGI(TAG, "正在初始化LCD面板");ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));// 初始化LCD面板ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));// 应用正确的显示设置ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, false)); // 不交换XYESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, false, false)); // 不镜像ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, false)); // 不反转颜色// 开启显示ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));// 6. 分配显示缓冲区并清空屏幕// 分配显示缓冲区lcd_buffer = heap_caps_malloc(LCD_H_RES * LCD_V_RES * sizeof(uint16_t), MALLOC_CAP_DMA);if (!lcd_buffer){ESP_LOGE(TAG, "内存分配失败");return;}// 清空屏幕clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);
}// 释放LCD资源
void lcd_deinit(void)
{if (lcd_buffer){free(lcd_buffer);lcd_buffer = NULL;}
}
显示图片
我的图片显示使用的是大端、rgb565、水平扫描。
用image2lcd软件,配置如图:
取模软件链接:
链接: 百度网盘 请输入提取码 提取码: 1234
可以先用这张测试图片测试,我是网上下载了一个三原色图并且加了个圈,只要这张图所有颜色显示对了,结果包对的!!!!
我在图片显示这边卡了很久很久,最后发现是大端小端的问题,这个一定要注意!!!!
小技巧:当图片一直颜色不对的时候,可以排列组合n种图片显示方式,比如rgb/bgr,大端/小端等等,每隔1s切换一次,最后看第几次显示的是对的。
把数据粘贴过去,变成单独文件。然后直接主函数调用就行。
#include "wyh_pic.h"const unsigned char gImage_wyh_pic[32768] = {0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,0XFF,......
decode_image(gImage_wyh_pic, lcd_buffer, LCD_H_RES, LCD_V_RES);
显示汉字
用取模软件取模,我的设置是:阴码,逐行,逆向,十六进制,C51格式。
取模软件链接:
链接: 百度网盘 请输入提取码 提取码: 1234
由于我要显示的中文字符实在太多太多了,有大几十个,而且经常会有复用的情况。于是我用了一种简单高效的字库查表方式。
先定义一个结构体:
// 中文字符结构体
typedef struct
{uint16_t unicode; // Unicode编码const uint8_t *bitmap; // 16x16点阵数据
} chinese_char_t;
Unicode编码:每个汉字的唯一标识符,如"王"的Unicode是0x738B
点阵数据:16×16像素的点阵,每个汉字占用32字节(16行×每行2字节)
然后复制粘贴取模结果。看我右边预览就知道有多少中文字符了。比如:
static const uint8_t char_jie[] = {0x08, 0x01, 0x08, 0x02, 0xC8, 0x3F, 0x08, 0x00, 0xBF, 0x10, 0x08, 0x09, 0xE8, 0x7F, 0x08, 0x02,0x18, 0x02, 0xEC, 0x7F, 0x0B, 0x11, 0x88, 0x10, 0x08, 0x09, 0x08, 0x06, 0x8A, 0x19, 0x64, 0x20 /*"接",13*/
};
之后构建字符表:
static const chinese_char_t chinese_chars[] = {{0x5DF2, char_yi}, // 已{0x8FDE, char_lian}, // 连{0x63A5, char_jie}, // 接// ...更多字符
};#define NUM_CHINESE_CHARS (sizeof(chinese_chars) / sizeof(chinese_char_t))
//自动计算中文字库数组中包含的汉字数量
最后编写一个查询函数,输入unicode码就能找到汉字的取模结果
// 查找中文字符点阵数据
const uint8_t *get_chinese_char_bitmap(uint16_t unicode)
{for (int i = 0; i < NUM_CHINESE_CHARS; i++){if (chinese_chars[i].unicode == unicode){return chinese_chars[i].bitmap;}}return NULL; // 未找到对应字符
}
最后这样调用就行了
draw_string(lcd_buffer, 0, 0, "设备信息", 0xFFFF, LCD_H_RES);
我之后的功能函数会自动处理转换,可以看到相当方便,扩展也很简单。
功能函数
一些函数,适配屏幕,可以直接调用。主要包含中文显示,ASCII显示,图片显示功能。
// 显示屏参数
#define LCD_H_RES 128 // 水平分辨率
#define LCD_V_RES 160 // 垂直分辨率
#define LCD_PIXEL_CLOCK_HZ (20 * 1000 * 1000) // 20MHz SPI时钟
#define LCD_SPI_HOST SPI2_HOST// 添加图片尺寸常量
#define IMAGE_WIDTH 128 // 图片宽度
#define IMAGE_HEIGHT 128 // 图片高度// 字体设置
#define FONT_WIDTH 6 // 英文字符宽度
#define FONT_HEIGHT 8 // 英文字符高度
#define CHINESE_FONT_WIDTH 16 // 中文字符宽度
#define CHINESE_FONT_HEIGHT 16 // 中文字符高度
#define TEXT_LINE_MAX 19 // 最大行数 (160/8 - 1)
#define TEXT_COLUMN_MAX 21 // 最大列数 (128/6)// LCD相关句柄
extern esp_lcd_panel_handle_t panel_handle;
extern uint16_t *lcd_buffer;// 颜色格式转换函数 ,大端
uint16_t convert_color_format(uint8_t byte1, uint8_t byte2)
{return (byte1 << 8) | byte2;
}// 解码图像函数
void decode_image(const unsigned char *src_data, uint16_t *dest_buffer, int width, int height)
{// 使用定义的图片尺寸常量const int img_width = IMAGE_WIDTH;const int img_height = IMAGE_HEIGHT;// 清空缓冲区为黑色for (int i = 0; i < width * height; i++){dest_buffer[i] = 0x0000; // 黑色}// 根据显示屏和图像尺寸计算居中位置int start_x = (width - img_width) / 2;int start_y = (height - img_height) / 2;if (start_x < 0)start_x = 0;if (start_y < 0)start_y = 0;// 复制图像数据到缓冲区的居中位置for (int y = 0; y < img_height && y + start_y < height; y++){for (int x = 0; x < img_width && x + start_x < width; x++){// 计算源位置 - 水平扫描模式int src_pos = (y * img_width + x) * 2; // 每个像素2字节// 读取像素数据uint8_t byte1 = src_data[src_pos];uint8_t byte2 = src_data[src_pos + 1];// 转换颜色格式 - 使用正确的格式uint16_t pixel = convert_color_format(byte1, byte2);// 计算目标缓冲区位置int dest_pos = (y + start_y) * width + (x + start_x);// 写入像素数据dest_buffer[dest_pos] = pixel;}}
}// 绘制单个字符函数 (简单ASCII字体)
void draw_char(uint16_t *buffer, int x, int y, char ch, uint16_t color, int width)
{// 简单ASCII字体定义 (仅支持基本ASCII)// 这是一个非常简单的5x7字体,每个字节代表一行,每位代表一个像素static const unsigned char font5x7[][5] = {{0x00, 0x00, 0x00, 0x00, 0x00}, // 空格{0x00, 0x00, 0x5F, 0x00, 0x00}, // !{0x00, 0x07, 0x00, 0x07, 0x00}, // "{0x14, 0x7F, 0x14, 0x7F, 0x14}, // #{0x24, 0x2A, 0x7F, 0x2A, 0x12}, // ${0x23, 0x13, 0x08, 0x64, 0x62}, // %{0x36, 0x49, 0x55, 0x22, 0x50}, // &{0x00, 0x05, 0x03, 0x00, 0x00}, // '{0x00, 0x1C, 0x22, 0x41, 0x00}, // ({0x00, 0x41, 0x22, 0x1C, 0x00}, // ){0x08, 0x2A, 0x1C, 0x2A, 0x08}, // *{0x08, 0x08, 0x3E, 0x08, 0x08}, // +{0x00, 0x50, 0x30, 0x00, 0x00}, // ,{0x08, 0x08, 0x08, 0x08, 0x08}, // -{0x00, 0x60, 0x60, 0x00, 0x00}, // .{0x20, 0x10, 0x08, 0x04, 0x02}, // /{0x3E, 0x51, 0x49, 0x45, 0x3E}, // 0{0x00, 0x42, 0x7F, 0x40, 0x00}, // 1{0x42, 0x61, 0x51, 0x49, 0x46}, // 2{0x21, 0x41, 0x45, 0x4B, 0x31}, // 3{0x18, 0x14, 0x12, 0x7F, 0x10}, // 4{0x27, 0x45, 0x45, 0x45, 0x39}, // 5{0x3C, 0x4A, 0x49, 0x49, 0x30}, // 6{0x01, 0x71, 0x09, 0x05, 0x03}, // 7{0x36, 0x49, 0x49, 0x49, 0x36}, // 8{0x06, 0x49, 0x49, 0x29, 0x1E}, // 9{0x00, 0x36, 0x36, 0x00, 0x00}, // :{0x00, 0x56, 0x36, 0x00, 0x00}, // ;{0x00, 0x08, 0x14, 0x22, 0x41}, // <{0x14, 0x14, 0x14, 0x14, 0x14}, // ={0x41, 0x22, 0x14, 0x08, 0x00}, // >{0x02, 0x01, 0x51, 0x09, 0x06}, // ?{0x32, 0x49, 0x79, 0x41, 0x3E}, // @{0x7E, 0x11, 0x11, 0x11, 0x7E}, // A{0x7F, 0x49, 0x49, 0x49, 0x36}, // B{0x3E, 0x41, 0x41, 0x41, 0x22}, // C{0x7F, 0x41, 0x41, 0x22, 0x1C}, // D{0x7F, 0x49, 0x49, 0x49, 0x41}, // E{0x7F, 0x09, 0x09, 0x01, 0x01}, // F{0x3E, 0x41, 0x41, 0x49, 0x3A}, // G{0x7F, 0x08, 0x08, 0x08, 0x7F}, // H{0x00, 0x41, 0x7F, 0x41, 0x00}, // I{0x20, 0x40, 0x41, 0x3F, 0x01}, // J{0x7F, 0x08, 0x14, 0x22, 0x41}, // K{0x7F, 0x40, 0x40, 0x40, 0x40}, // L{0x7F, 0x02, 0x04, 0x02, 0x7F}, // M{0x7F, 0x04, 0x08, 0x10, 0x7F}, // N{0x3E, 0x41, 0x41, 0x41, 0x3E}, // O{0x7F, 0x09, 0x09, 0x09, 0x06}, // P{0x3E, 0x41, 0x51, 0x21, 0x5E}, // Q{0x7F, 0x09, 0x19, 0x29, 0x46}, // R{0x46, 0x49, 0x49, 0x49, 0x31}, // S{0x01, 0x01, 0x7F, 0x01, 0x01}, // T{0x3F, 0x40, 0x40, 0x40, 0x3F}, // U{0x1F, 0x20, 0x40, 0x20, 0x1F}, // V{0x7F, 0x20, 0x18, 0x20, 0x7F}, // W{0x63, 0x14, 0x08, 0x14, 0x63}, // X{0x03, 0x04, 0x78, 0x04, 0x03}, // Y{0x61, 0x51, 0x49, 0x45, 0x43}, // Z{0x00, 0x00, 0x7F, 0x41, 0x41}, // [{0x02, 0x04, 0x08, 0x10, 0x20}, // '\'{0x41, 0x41, 0x7F, 0x00, 0x00}, // ]{0x04, 0x02, 0x01, 0x02, 0x04}, // ^{0x40, 0x40, 0x40, 0x40, 0x40}, // _{0x00, 0x01, 0x02, 0x04, 0x00}, // `{0x20, 0x54, 0x54, 0x54, 0x78}, // a{0x7F, 0x48, 0x44, 0x44, 0x38}, // b{0x38, 0x44, 0x44, 0x44, 0x20}, // c{0x38, 0x44, 0x44, 0x48, 0x7F}, // d{0x38, 0x54, 0x54, 0x54, 0x18}, // e{0x08, 0x7E, 0x09, 0x01, 0x02}, // f{0x08, 0x14, 0x54, 0x54, 0x3C}, // g{0x7F, 0x08, 0x04, 0x04, 0x78}, // H{0x00, 0x44, 0x7D, 0x40, 0x00}, // i{0x20, 0x40, 0x44, 0x3D, 0x00}, // j{0x00, 0x7F, 0x10, 0x28, 0x44}, // k{0x00, 0x41, 0x7F, 0x40, 0x00}, // l{0x7C, 0x04, 0x18, 0x04, 0x78}, // m{0x7C, 0x08, 0x04, 0x04, 0x78}, // n{0x38, 0x44, 0x44, 0x44, 0x38}, // o{0x7C, 0x14, 0x14, 0x14, 0x08}, // p{0x08, 0x14, 0x14, 0x18, 0x7C}, // q{0x7C, 0x08, 0x04, 0x04, 0x08}, // r{0x48, 0x54, 0x54, 0x54, 0x20}, // s{0x04, 0x3F, 0x44, 0x40, 0x20}, // t{0x3C, 0x40, 0x40, 0x20, 0x7C}, // u{0x1C, 0x20, 0x40, 0x20, 0x1C}, // v{0x3C, 0x40, 0x30, 0x40, 0x3C}, // w{0x44, 0x28, 0x10, 0x28, 0x44}, // x{0x0C, 0x50, 0x50, 0x50, 0x3C}, // y{0x44, 0x64, 0x54, 0x4C, 0x44}, // z{0x00, 0x08, 0x36, 0x41, 0x00}, // {{0x00, 0x00, 0x7F, 0x00, 0x00}, // |{0x00, 0x41, 0x36, 0x08, 0x00}, // }{0x08, 0x08, 0x2A, 0x1C, 0x08}, // →{0x08, 0x1C, 0x2A, 0x08, 0x08} // ←};// 确保字符在有效范围内if (ch < ' ' || ch > '~' + 2)ch = '?';// 计算在字体数组中的索引int idx = ch - ' ';// 在指定位置绘制字符for (int row = 0; row < 7; row++){for (int col = 0; col < 5; col++){if (font5x7[idx][col] & (1 << row)){// 如果这个像素需要绘制int pos = (y + row) * width + (x + col);if (pos < width * LCD_V_RES){buffer[pos] = color;}}}}
}// 绘制中文字符函数 (16x16点阵)
void draw_chinese_char(uint16_t *buffer, int x, int y, uint16_t unicode, uint16_t color, int width)
{// 获取字符点阵数据const uint8_t *bitmap = get_chinese_char_bitmap(unicode);if (bitmap == NULL){// 如果找不到字符,绘制一个方块表示缺失for (int row = 0; row < 16; row++){for (int col = 0; col < 16; col++){if (row == 0 || row == 15 || col == 0 || col == 15){int pos = (y + row) * width + (x + col);if (pos < width * LCD_V_RES){buffer[pos] = color;}}}}return;}// 绘制16x16点阵 - 修改为低位在前的模式for (int row = 0; row < 16; row++){for (int col = 0; col < 16; col++){// 计算点阵中的位置,每行2个字节uint8_t byte = bitmap[row * 2 + col / 8];uint8_t bit = col % 8; // 修改为低位在前(从低位到高位)if (byte & (1 << bit)){// 如果这个像素需要绘制int pos = (y + row) * width + (x + col);if (pos < width * LCD_V_RES){buffer[pos] = color;}}}}
}// 修改字符串绘制函数以支持中文
void draw_string(uint16_t *buffer, int x, int y, const char *str, uint16_t color, int width)
{int cursor_x = x;// 使用中文字符高度作为统一行高int line_height = CHINESE_FONT_HEIGHT;while (*str){// 处理换行if (*str == '\n'){cursor_x = x;y += line_height;if (y >= LCD_V_RES - line_height){break; // 超出屏幕底部}str++;continue;}// 检查是否是UTF-8编码的中文if ((*str & 0xE0) == 0xE0 && (*(str + 1) & 0x80) && (*(str + 2) & 0x80)){// 3字节UTF-8,提取Unicode码点uint16_t unicode = (((*str & 0x0F) << 12) |((*(str + 1) & 0x3F) << 6) |(*(str + 2) & 0x3F));// 绘制中文字符draw_chinese_char(buffer, cursor_x, y, unicode, color, width);cursor_x += CHINESE_FONT_WIDTH; // 使用常量而非硬编码值str += 3; // 跳过3个UTF-8字节// 检查是否需要换行if (cursor_x >= width - CHINESE_FONT_WIDTH){cursor_x = x;y += line_height; // 使用统一行高if (y >= LCD_V_RES - line_height){break; // 超出屏幕底部}}}else{// ASCII字符draw_char(buffer, cursor_x, y, *str, color, width);cursor_x += FONT_WIDTH;str++;// 检查是否需要换行if (cursor_x >= width - FONT_WIDTH){cursor_x = x;y += line_height; // 使用统一行高而非FONT_HEIGHTif (y >= LCD_V_RES - line_height){break; // 超出屏幕底部}}}}
}
main函数集合
LCD显示遵循以下基本流程:
1.初始化LCD
2.清空缓冲区
3.向缓冲区绘制内容
4.将缓冲区内容刷新到LCD
void display_example(void)
{// 1. 初始化LCD(通常在app_main开始时调用一次)lcd_init();// 2. 清空屏幕缓冲区clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);// 3. 向缓冲区绘制内容// 3.1 显示英文文本 - 参数:缓冲区、x坐标、y坐标、文本、颜色、屏幕宽度draw_string(lcd_buffer, 0, 0, "Hello World!", 0xFFFF, LCD_H_RES); // 白色文字// 3.2 显示中文文本(自动处理UTF-8编码)draw_string(lcd_buffer, 0, FONT_HEIGHT + 2, "你好,世界!", 0xFFFF, LCD_H_RES);// 3.3 显示带颜色的文本 - 0xF800是红色(RGB565格式)draw_string(lcd_buffer, 0, FONT_HEIGHT*2 + 4, "Red Text", 0xF800, LCD_H_RES);// 3.4 在特定位置显示内容(例如底部)draw_string(lcd_buffer, 0, LCD_V_RES - FONT_HEIGHT - 2, "Bottom Text", 0x07E0, LCD_H_RES); // 绿色// 4. 将缓冲区内容刷新到LCD显示esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);// 等待2秒vTaskDelay(2000 / portTICK_PERIOD_MS);// 显示图片示例clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);decode_image(gImage_wyh_pic, lcd_buffer, LCD_H_RES, LCD_V_RES);esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);// 等待2秒vTaskDelay(2000 / portTICK_PERIOD_MS);// 显示图片并在图片上添加文字clear_text_area(lcd_buffer, LCD_H_RES, LCD_V_RES);decode_image(gImage_wyh_pic, lcd_buffer, LCD_H_RES, LCD_V_RES);// 在图片上方添加标题(黑色背景白色文字,更容易看清)for (int y = 0; y < FONT_HEIGHT + 4; y++) {for (int x = 0; x < LCD_H_RES; x++) {lcd_buffer[y * LCD_H_RES + x] = 0x0000; // 黑色背景}}draw_string(lcd_buffer, 0, 2, "带标题的图片", 0xFFFF, LCD_H_RES);// 刷新显示esp_lcd_panel_draw_bitmap(panel_handle, 0, 0, LCD_H_RES, LCD_V_RES, lcd_buffer);
}
成功!示例
下一篇会介绍mqtt。
相关文章:
esp32课设记录(二)lcd屏显示文字与照片
取模软件链接: 链接: 百度网盘 请输入提取码 提取码: 1234 课设要求如图所示,因此需要在esp32显示文字和照片。在上个文章中我已经写了按键相关内容。这篇主要描述怎么显示文字和照片。我使用的是ESP-IDF库。 本项目使用的是基于ST7789驱动芯片的LCD屏幕…...
Open CASCADE学习|几何体切片处理:OpenMP与OSD_Parallel并行方案深度解析
在三维建模与仿真领域,几何体切片处理是CAE前处理、3D打印路径规划、医学影像分析等场景的关键技术。其核心目标是将三维模型沿特定方向离散为二维截面集合,便于后续分析或制造。OpenCASCADE作为开源几何内核,提供高效的布尔运算与几何算法&a…...
【Android】从Choreographer到UI渲染(二)
【Android】从Choreographer到UI渲染(二) Google 在 2012 年推出的 Project Butter(黄油计划)是 Android 系统发展史上的重要里程碑,旨在解决长期存在的 UI 卡顿、响应延迟等问题,提升用户体验。 在 Androi…...
板凳-------Mysql cookbook学习 (三)
1.22 使长输出行更具可读性 mysql> show full columns from limbs; ------------------------------------------------------------------------------------------------------------- | Field | Type | Collation | Null | Key | Default | Extra | Pri…...
济南国网数字化培训班学习笔记-第三组-2-电力通信光缆网认知
电力通信光缆网认知 光缆网架构现状 基础底座 电路系统是高度复杂,实时性、安全性、可靠性要求极高的巨系统,必须建设专用通信网 相伴相生 电力系统是由发电、输电、变电、配电、用电等一次设施,及保障其正常运行的保护、自动化、通信等…...
黑灰产业链深度解析
黑灰产业链深度解析 大家好,欢迎来到「黑产档案」。本频道专注于反诈教育宣传,通过深度拆解黑灰产业链的运作逻辑,帮助公众识别骗局、规避风险。本节课将聚焦产业链的核心环节,揭示其背后的灰色生态。 一、黑灰产的定义与范畴 要…...
golang选项设计模式
选项设计模式 有时候一个函数会有很多参数,为了方便函数的使用,我们会给希望给一些参数设定默认值,调用时只需要传与默认值不同的参数即可,类似于 python 里面的默认参数和字典参数,虽然 golang 里面既没有默认参数也…...
方案精读:104页DeepSeek金融银行核算流程场景部署建设方案【附全文阅读】
DeepSeek,金融银行核算流程的革新方案! 这份方案专为金融银行从业者打造,旨在解决传统核算流程的难题。当下,金融银行核算面临效率低、错误率高、合规压力大等挑战,DeepSeek 方案正是应对之策。 该方案运用人工智能和大数据技术,实现数据采集、清洗自动化,智能核算对账,…...
【MySQL】02.数据库基础
1. 数据库的引入 之前存储数据用文件就可以了,为什么还要弄个数据库? 文件存储存在安全性问题,文件不利于数据查询和管理,文件不利于存储海量数据,文件在程序中控制不方便。而为了解决上述问题,专家们设计出更加利于…...
STM32项目实战:ADC采集
STM32F103C8T6的ADC配置。PB0对应的是ADC1的通道8。在标准库中,需要初始化ADC,设置通道,时钟,转换模式等。需要配置GPIOB的第0脚为模拟输入模式,然后配置ADC1的通道8,设置转换周期和触发方式。 接下来是I2C…...
《AI语音模型:MiniMax Speech-02》
开场:AI 语音界的震撼弹 在 AI 语音技术的激烈竞争赛道上,MiniMax Speech - 02 的出现宛如一颗震撼弹,瞬间引爆了整个行业。不久前,一则消息在全球 AI 领域引起轩然大波:MiniMax 的新一代语音大模型 Speech - 02&#…...
基于LabVIEW的双音多频系统设计
目录 1 系统设计概述 双音多频(Dual-Tone Multi-Frequency, DTMF)信号是一种广泛应用于电话系统中的音频信号,通过不同的频率组合表示不同的按键。每个按键对应两个频率,一个低频和一个高频,共同组成独特的信号。在虚拟仪器技术快速发展的背景下,利用LabVIEW等图形化编程…...
快速生成角色背景设定:基于Next.js的AI辅助工具开发实践
引言 在游戏开发、小说创作和角色扮演(RP)中,角色背景设定(Headcanon)的构建往往耗时耗力。传统方法依赖手动编写,容易陷入思维定式。本文将分享如何利用Next.js和Tailwind CSS开发一个高效的AI角色设定生…...
轻量级视频剪辑方案:FFmpeg图形化工具体验
FFmpeg小白助手是基于开源FFmpeg开发的本地化视频处理软件,采用绿色免安装设计,解压后即可直接运行。该工具主要面向普通用户的日常音视频处理需求,通过简洁的图形界面降低了FFmpeg的使用门槛。 功能特性 基础编辑功能 格式转换:…...
主成分分析的应用之sklearn.decomposition模块的PCA函数
主成分分析的应用之sklearn.decomposition模块的PCA函数 一、模型建立整体步骤 二、数据 2297.86 589.62 474.74 164.19 290.91 626.21 295.20 199.03 2262.19 571.69 461.25 185.90 337.83 604.78 354.66 198.96 2303.29 589.99 516.21 236.55 403.92 730.05 438.41 225.80 …...
Java基于数组的阻塞队列实现详解
在多线程编程中,阻塞队列是一种非常有用的工具,它可以在生产者和消费者之间提供一个缓冲区,使得生产者可以往队列中添加数据,而消费者可以从队列中取出数据。当队列满时,生产者会被阻塞直到有空间可用;当队…...
ngx_http_random_index_module 模块概述
一、使用场景 随机内容分发 当同一目录下存放多份等价内容(如多张轮播图、不同版本静态页面等)时,可通过随机索引实现负载均衡或流量分散。A/B 测试 通过目录请求自动随机分配用户到不同测试组,无需后端逻辑参与。动态“首页”选…...
你引入的lodash充分利用了吗?
#开发中,发现自己只有cloneDeep的时候才想起来用这个库的便利,搜索了项目内代码,发现大家基本也是这样,其实我们错过了很多好东西# cloneDeep 深拷贝 var objects [{ a: 1 }, { b: 2 }];var deep _.cloneDeep(objects); conso…...
Python爬虫基础
本篇内容中,我们主要分享一些爬虫的前置知识,主要知识点有: 爬虫的概念和作用爬虫的流程【重要】http相关的复习 http和https概念和区别浏览器访问一个网址的过程爬虫中常用的请求头、响应头常见的响应状态码 浏览器自带开发者工具的使用 爬…...
飞帆控件:on_post_get 接口配置
在网页中写一个接口是很基础的要求。 今天我们介绍一个工具,不用写代码,配置即可。 先上链接: on_post_gethttps://fvi.cn/798来看看控件的配置: 使用这个控件,在网页中写 post/get 接口可以告别代码。或许能做到初…...
C++笔试题(金山科技新未来训练营):
题目分布: 17道单选(每题3分)3道多选题(全对3分,部分对1分)2道编程题(每一道20分)。 不过题目太多,就记得一部分了: 单选题: static变量的初始…...
Selenium-Java版(css表达式)
css表达式 前言 根据 tag名、id、class 选择元素 tag名 #id .class 选择子元素和后代元素 定义 语法 根据属性选择 验证CSS Selector 组选择 按次序选择子节点 父元素的第n个子节点 父元素的倒数第n个子节点 父元素的第几个某类型的子节点 父元素的…...
19. 结合Selenium和YAML对页面实例化PO对象改造
19. 结合Selenium和YAML对页面实例化PO对象改造 一、架构升级核心思路 1.1 改造核心目标 # 原始PO模式:显式定义元素定位 username (id, ctl00_MainContent_username)# 改造后PO模式:动态属性访问 self.username.send_keys(Tester) # 自动触发元素定…...
MySQL——5、基本查询
表的增删改查 1、Create1.1、单行数据全列插入1.2、多行数据指定列插入1.3、插入否则更新1.4、替换 2、Retrieve2.1、select列2.2、where条件2.3、结果排序2.4、筛选分页结果 3、Update4、Delete4.1、删除数据4.2、截断表 5、插入查询结果6、聚合函数7、group by子句的使用8、实…...
ngx_http_referer_module 模块概述
一、使用场景 防盗链 仅允许本站或特定域名的页面直接引用图片、视频等资源,拒绝第三方网站直接嵌入。流量控制 阻止来自社交媒体、搜索引擎或未知来源的大量自动化抓取。安全审计 简易记录并过滤可疑 Referer,以减少非法请求。 注意 Referer 头可被伪造…...
Go语言--语法基础5--基本数据类型--类型转换
Go 编程语言中 if 条件语句的语法如下: 1、基本形式 if 布尔表达式 { /* 在布尔表达式为 true 时执行 */ } If 在布尔表达式为 true 时,其后紧跟的语句块执行,如果为 false 则 不执行。 package main import "fmt" …...
用golang实现二叉搜索树(BST)
目录 一、概念、性质二、二叉搜索树的实现1. 结构2. 查找3. 插入4. 删除5. 中序遍历 中序前驱/后继结点 一、概念、性质 二叉搜索树(Binary Search Tree),简写BST,又称为二叉查找树 它满足: 空树是一颗二叉搜索树对…...
基于FPGA的电子万年历系统开发,包含各模块testbench
目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于FPGA的电子万年历系统开发,包含各模块testbench。主要包含以下核心模块: 时钟控制模块:提供系统基准时钟和计时功能。 日历计算模块:…...
上位机知识篇---Web
文章目录 前言 前言 本文简单介绍了Web。...
2025 ISCC 练武赛Pwn-wp(含附件)
前言 去年个人赛报名了忘记打了(笑), 所以这应该算是我第一次参加ISCC, 体验也是非常非常非常非常的cha(第四声)!!! 主办方也是非常幽默,pwn和web都是公用容器,那web最后都被当成玩具玩坏了 下面是这次练武题的pwn所有附件 通过网盘分享的文件…...
LeetCode Hot100刷题——除自身以外数组的乘积
238. 除自身以外数组的乘积 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&a…...
IDEA - Windows IDEA 代码块展开与折叠(基础折叠操作、高级折叠操作)
一、基础折叠操作 折叠当前代码块:Ctrl - # 操作方式按下 【Ctrl】 键,再按下 【-】 键展开当前代码块:Ctrl # 操作方式按下 【Ctrl】 键,再按下 【】 键折叠所有代码块:Ctrl Shift - # 操作方式按下 【Ctrl】…...
javaSE.Map
Map存储映射关系。键值对。key-value 左边值映射着右边的值,左边相当于钥匙,开到右边的门获取得到信息。 get👇put 是否存在该key👇containsKey() map.values👇 entrySet👇 entrySet()获取map中所有的键…...
Python Requests库完全指南:从入门到精通
引言 在Python的生态系统中,requests库以其简洁优雅的API设计和强大的功能,成为HTTP请求处理领域的标杆工具。无论是数据爬虫开发、API接口调用,还是自动化测试场景,requests都能将复杂的网络交互简化为几行可读性极高的代码。相…...
1.QPushBotton 以及 对象树
目录 1. 创建第一个Qt程序 1.1 初始化设置 🍐 选择存储位置 🍊 Kit 🍋 类信息 🍌 项目管理 1.2 代码 🍉 main.cpp 🍇widget.h 🍓 widget.cpp 1.3 .pro文件 🍈 常见模块…...
GO语言语法---For循环、break、continue
文章目录 1. 基本for循环(类似其他语言的while)2. 经典for循环(初始化;条件;后续操作)3. 无限循环4. 使用break和continue5 . 带标签的循环(可用于break/continue指定循环)1、break带标签2、continue带标签…...
网络编程-select(二)
一、I/O多路复用 1、为什么要多路复用 之前开启多线程能实时接收数据,并且也不是一次性连接服务。但毕竟是一请求一连接,每有一个客户端向服务端发起请求,就会创建一个线程,当请求达到上千上万,就会创建上千上万的线…...
2025年PMP 学习十九 第12章 项目采购管理
2025年PMP 学习十九 第12章 项目采购管理 序号过程过程组1规划采购管理规划2实施采购执行3控制采购监控4合同管理- 文章目录 2025年PMP 学习十九 第12章 项目采购管理12 项目采购管理建立战略合作伙伴关系的意义:细化采购步骤 12.1 规划采购管理1. **定义与作用**2…...
10.11 LangGraph多角色Agent开发实战:生产级AI系统架构与性能优化全解析
LangGraph 项目:High-level API for Multi-actor Agents 关键词:LangGraph 多角色 Agent, 状态管理, 持久化机制, 工作流编排, 生产级 AI 系统 1. LangGraph 设计哲学与架构演进 LangGraph 是 LangChain 生态中首个面向 多角色协作 Agent 的高阶 API 框架,其核心设计思想可…...
计算机网络概要
⽹络相关基础知识 协议 两设备之间使⽤光电信号传输信息数据 要想传递不同信息 那么⼆者ᳵ就需要约定好的数据格式 层 封装 继承 多态是计算机的性质 它们⽀持了软硬件分层的实现 同层协议可以ᳵ接通信 同层协议ᳵ不直接通信 是各⾃调⽤下层提供的结构能⼒完成通信 分层…...
Visual Studio已更新为17.14+集成deepseek实现高效编程
01 Visual Studio 2022 v17.14。 此更新侧重于全面提供出色的开发人员体验,侧重于稳定性和安全性以及 AI 改进。 02 GPT-4o代码完成模式 我们非常高兴地宣布,新的 GPT-4o Copilot 代码完成模型现已在 Visual Studio 17.14 中为 GitHub Copilot 用户…...
axios的基本使用
1. Axios概述 Axios 是一个基于 Promise 的 HTTP 客户端库,专为浏览器和 Node.js 设计,用来发送AJAX请求。可以通过npm install -g axios安装axios库。Axios有以下特征: 跨平台兼容性:同一套代码可运行于浏览器和 Node.js。在浏…...
【第三十六周】LoRA 微调方法
LoRA 摘要Abstract文章信息引言方法LoRA的原理LoRA在Transformer中的应用补充其他细节 实验与分析LoRA的使用论文实验结果分析 总结 摘要 本篇博客介绍了LoRA(Low-Rank Adaptation),这是一种面向大规模预训练语言模型的参数高效微调方法&…...
fcQCA模糊集定性比较分析法-学习笔记
模糊集定性比较分析(fsQCA,Fuzzy-set Qualitative Comparative Analysis) 是一种结合了定性和定量元素的研究方法,用于分析中小样本数据中的复杂因果关系。 1. 理解基础概念 QCA的核心思想: 基于集合论和布尔代数&a…...
基于WebRTC的实时语音对话系统:从语音识别到AI回复
基于WebRTC的实时语音对话系统:从语音识别到AI回复 在当今数字化时代,实时语音交互已成为人机界面的重要组成部分。本文将深入探讨一个基于WebRTC技术的实时语音对话系统,该系统集成了语音识别(ASR)、大语言模型(LLM)和语音合成(TTS)技术&am…...
Text2SQL:自助式数据报表开发---0517
Text2SQL技术 早期阶段:依赖于人工编写的规则模板来匹配自然语言和SQL语句之间的对应关系 机器学习阶段:采用序列到序列模型等机器学习方法来学习自然语言与SQL之间的关系 LLM阶段:借助LLM强大的语言理解和代码生成能力,利用提示…...
关于 Web 漏洞原理与利用:1. SQL 注入(SQLi)
一、原理: 拼接 SQL 语句导致注入 SQL 注入的根本原因是:开发者将用户的输入和 SQL 语句直接拼接在一起,没有任何过滤或校验,最终被数据库“当作语句”执行了。 这就像是我们给数据库写了一封信,结果攻击者在我们的…...
【NLP 75、如何通过API调用智谱大模型】
事事忘记,事事等待,事事自愈 —— 25.5.18 一、调用智谱大模型 zhipuai.model_api.invoke():调用智谱 AI 的大模型(如 ChatGLM)进行文本生成或推理,支持同步请求。 参数列表 参数名类型是否必需默认值说…...
【RabbitMQ】 RabbitMQ高级特性(二)
文章目录 一、重试机制1.1、重试配置1.2、配置交换机&队列1.3、发送消息1.4、消费消息1.5、运行程序1.6、 手动确认 二、TTL2.1、设置消息的TTL2.2、设置队列的TTL2.3、两者区别 三 、死信队列6.1 死信的概念3.2 代码示例3.2.1、声明队列和交换机3.2.2、正常队列绑定死信交…...
EMQX开源版安装指南:Linux/Windows全攻略
EMQX开源版安装教程-linux/windows 因最近自己需要使用MQTT,需要搭建一个MQTT服务器,所以想到了很久以前用到的EMQX。但是当时的EMQX使用的是开源版的,在官网可以直接下载。而现在再次打开官网时发现怎么也找不大开源版本了,所以…...