MR16/User/device/lcd_driver/lcd.c
2025-11-29 17:30:50 +08:00

743 lines
25 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
LCD驱动
高效:
--------控制引脚配置,屏幕属性配置完成后即可使用
可扩展:
--------添加你喜欢的颜色,
--------在lcd_library.h添加你的自定义点阵库-使用LCD_DrawBitmap驱动任意位图
兼容:
--------LSB与MSB顺序的字表
所有绘制函数使用示例:
LCD_DrawPoint(0,0,BLUE);
LCD_DrawChar(10, 10, 'A', RED, WHITE);
LCD_DrawLine(10, 10, 100, 50, RED);
LCD_DrawRectangle(10,10,50,100,GREEN);
LCD_DrawHollowCircle(200,50,16,RED);
LCD_DrawSolidCircle(200,100,16,RED);
LCD_DrawString(0,0,"MR16",MEDIUMORCHID,32,LSB);
extern const unsigned char logo_M[];
LCD_DrawBitmap(logo_M,70,70,64,64,MEDIUMORCHID,MSB);
*/
/* Includes ----------------------------------------------------------------- */
#include "device/lcd_driver/lcd.h"
#include "device/device.h"
#include "device/lcd_driver/lcd_lib.h"
#include "bsp/spi.h"
#include <stdlib.h>
#include <stdio.h>
/* USER INCLUDE BEGIN */
/* USER INCLUDE END */
/* Private define ----------------------------------------------------------- */
/* USER DEFINE BEGIN */
/* USER DEFINE END */
/* Private macro ------------------------------------------------------------ */
/* Private typedef ---------------------------------------------------------- */
/* Private variables -------------------------------------------------------- */
static LCD_Orientation_t lcd_orientation = LCD_ORIENTATION_PORTRAIT; // 当前屏幕方向
/* Private function -------------------------------------------------------- */
/**
* 写命令到LCD
*
* @param cmd 要写入的命令
*
* @note 此函数用于向LCD发送控制命令。通过设置数据/命令选择引脚为命令模式,
* 并通过SPI接口发送命令。
*/
static int8_t LCD_WriteCommand(uint8_t cmd) {
LCD_DC_LOW(); // 设置数据/命令选择引脚为命令模式
LCD_CS_LOW(); // 使能SPI片选
BSP_SPI_Transmit(BSP_SPI_LCD, &cmd, 1, false); // 通过SPI发送命令
LCD_CS_HIGH(); // 禁用SPI片选
return DEVICE_OK;
}
/**
* 写数据到LCD
*
* @param data 要写入的数据
*
* @note 此函数用于向LCD发送数据。通过设置数据/命令选择引脚为数据模式,
* 并通过SPI接口发送数据。
*/
static int8_t LCD_WriteData(uint8_t data) {
LCD_DC_HIGH(); // 设置数据/命令选择引脚为数据模式
LCD_CS_LOW(); // 使能SPI片选
BSP_SPI_Transmit(BSP_SPI_LCD, &data, 1, false); // 通过SPI发送数据
LCD_CS_HIGH(); // 禁用SPI片选
return DEVICE_OK;
}
/**
* 使用 DMA 写多个数据到 LCD
*
* @param data 数据缓冲区指针
* @param size 数据大小(字节数)
*
* @note 此函数用于通过DMA快速发送大量数据到LCD。适用于数据量较大的场景
* 提高传输效率。
*/
static int8_t LCD_WriteDataBuffer_DMA(uint8_t *data, uint16_t size) {
LCD_DC_HIGH(); // 设置数据/命令选择引脚为数据模式
LCD_CS_LOW(); // 使能SPI片选
BSP_SPI_Transmit(BSP_SPI_LCD, data, size, true); // 通过SPI发送数据
while(BSP_SPI_GetState(BSP_SPI_LCD) != HAL_SPI_STATE_READY); // 等待SPI传输完成
LCD_CS_HIGH(); // 禁用SPI片选
return DEVICE_OK;
}
/**
* 修改原来的 LCD_WriteDataBuffer增加 DMA 支持
*
* @param data 数据缓冲区指针
* @param size 数据大小(字节数)
*
* @note 此函数根据数据量大小选择使用DMA或普通SPI传输。如果数据量大于64字节
* 使用DMA传输否则使用普通SPI传输。
*/
static int8_t LCD_WriteDataBuffer(uint8_t *data, uint16_t size) {
if (size > 64) { // 如果数据量较大,使用 DMA
LCD_WriteDataBuffer_DMA(data, size);
} else { // 否则使用普通传输
LCD_DC_HIGH(); // 设置数据/命令选择引脚为数据模式
LCD_CS_LOW(); // 使能SPI片选
BSP_SPI_Transmit(BSP_SPI_LCD, data, size, false); // 通过SPI发送数据
LCD_CS_HIGH(); // 禁用SPI片选
}
return DEVICE_OK;
}
/**
* 根据屏幕方向映射坐标
*
* @param x 输入的X坐标
* @param y 输入的Y坐标
* @param mx 映射后的X坐标输出
* @param my 映射后的Y坐标输出
*
* @note 此函数根据当前屏幕方向,将输入的坐标映射到实际的屏幕坐标。
*/
static int8_t LCD_MapCoords(uint16_t x, uint16_t y, uint16_t *mx, uint16_t *my) {
switch (lcd_orientation) {
case LCD_ORIENTATION_PORTRAIT: // 0°
*mx = x;
*my = y;
break;
case LCD_ORIENTATION_LANDSCAPE: // 90°顺时针
*mx = y;
*my = LCD_HEIGHT - 1 - x;
break;
case LCD_ORIENTATION_LANDSCAPE_INVERTED: // 90°逆时针
*mx = LCD_WIDTH - 1 - y;
*my = x;
break;
case LCD_ORIENTATION_PORTRAIT_INVERTED: // 180°
*mx = LCD_WIDTH - 1 - x;
*my = LCD_HEIGHT - 1 - y;
break;
default:
*mx = x;
*my = y;
break;
}
return DEVICE_OK;
}
/**
* 设置LCD绘图窗口
*
* @param x 窗口起始X坐标
* @param y 窗口起始Y坐标
* @param w 窗口宽度
* @param h 窗口高度
*
* @note 此函数用于设置LCD的绘图窗口指定后续绘图操作的区域。
*/
static int8_t LCD_SetAddressWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
uint16_t x_start = x + X_OFFSET; // 计算窗口起始X坐标
uint16_t x_end = x_start + w - 1; // 计算窗口结束X坐标
uint16_t y_start = y + Y_OFFSET; // 计算窗口起始Y坐标
uint16_t y_end = y_start + h - 1; // 计算窗口结束Y坐标
LCD_WriteCommand(0x2A); // 列地址设置
uint8_t data_x[] = {x_start >> 8, x_start & 0xFF, x_end >> 8, x_end & 0xFF};
LCD_WriteDataBuffer(data_x, sizeof(data_x));
LCD_WriteCommand(0x2B); // 行地址设置
uint8_t data_y[] = {y_start >> 8, y_start & 0xFF, y_end >> 8, y_end & 0xFF};
LCD_WriteDataBuffer(data_y, sizeof(data_y));
LCD_WriteCommand(0x2C); // 内存写入
return DEVICE_OK;
}
/* Exported functions ------------------------------------------------------- */
/**
* 初始化LCD
*
* @param orientation 屏幕方向(竖屏、横屏等)
*
* @note 此函数用于初始化LCD显示屏设置屏幕方向、像素格式、伽马校正等参数。
* 根据传入的屏幕方向参数,调整屏幕的显示方向。
*/
int8_t LCD_Init(LCD_Orientation_t orientation) {
lcd_orientation = orientation; // 设置屏幕方向
LCD_RST_LOW(); // 复位引脚低电平
HAL_Delay(50); // 延时
LCD_RST_HIGH(); // 复位引脚高电平
HAL_Delay(50); // 延时
LCD_WriteCommand(0x36); // 内存数据访问控制
switch (orientation) {
case LCD_ORIENTATION_PORTRAIT: // 竖屏模式
LCD_WriteData(0x08); // MY=1, MX=0, MV=0, ML=0, BGR=0
break;
case LCD_ORIENTATION_LANDSCAPE: // 横屏模式90°顺时针旋转
LCD_WriteData(0x60); // MY=0, MX=1, MV=1, ML=0, BGR=0
break;
case LCD_ORIENTATION_LANDSCAPE_INVERTED: // 横屏模式90°逆时针旋转
LCD_WriteData(0xA0); // MY=1, MX=1, MV=1, ML=0, BGR=0
break;
case LCD_ORIENTATION_PORTRAIT_INVERTED: // 竖屏模式180°旋转
LCD_WriteData(0xC8); // MY=1, MX=1, MV=0, ML=0, BGR=0
break;
default:
// LCD_WriteData(0x08); // 默认竖屏模式
break;
}
LCD_WriteCommand(0x3A); // 接口像素格式
LCD_WriteData(0x05); // 16位色
LCD_WriteCommand(0xB2); // 前廊设置
uint8_t porch[] = {0x0C, 0x0C, 0x00, 0x33, 0x33};
LCD_WriteDataBuffer(porch, sizeof(porch));
LCD_WriteCommand(0xB7); // 门控设置
LCD_WriteData(0x35);
LCD_WriteCommand(0xBB); // VCOM设置
LCD_WriteData(0x19);
LCD_WriteCommand(0xC0); // LCM控制
LCD_WriteData(0x2C);
LCD_WriteCommand(0xC2); // VDV和VRH命令使能
LCD_WriteData(0x01);
LCD_WriteCommand(0xC3); // VRH设置
LCD_WriteData(0x12);
LCD_WriteCommand(0xC4); // VDV设置
LCD_WriteData(0x20);
LCD_WriteCommand(0xC6); // 帧率控制
LCD_WriteData(0x0F);
LCD_WriteCommand(0xD0); // 电源控制1
LCD_WriteData(0xA4);
LCD_WriteData(0xA1);
LCD_WriteCommand(0xE0); // 正电压伽马控制
uint8_t gamma_pos[] = {0xD0, 0x04, 0x0D, 0x11, 0x13, 0x2B, 0x3F, 0x54, 0x4C, 0x18, 0x0D, 0x0B, 0x1F, 0x23};
LCD_WriteDataBuffer(gamma_pos, sizeof(gamma_pos));
LCD_WriteCommand(0xE1); // 负电压伽马控制
uint8_t gamma_neg[] = {0xD0, 0x04, 0x0C, 0x11, 0x13, 0x2C, 0x3F, 0x44, 0x51, 0x2F, 0x1F, 0x1F, 0x20, 0x23};
LCD_WriteDataBuffer(gamma_neg, sizeof(gamma_neg));
LCD_WriteCommand(0x21); // 显示反转开启
LCD_WriteCommand(0x11); // 退出睡眠模式
HAL_Delay(120); // 延时
LCD_WriteCommand(0x29); // 显示开启
return DEVICE_OK;
}
/**
* 清屏函数
*
* @param color 清屏颜色RGB565格式
*
* @note 此函数用于将整个LCD屏幕填充为指定颜色。
*/
int8_t LCD_Clear(uint16_t color) {
uint8_t color_data[] = {color >> 8, color & 0xFF}; // 将颜色转换为字节数组
LCD_SetAddressWindow(0, 0, LCD_WIDTH, LCD_HEIGHT); // 设置整个屏幕为绘制窗口
// 创建一个缓冲区,用于存储一行的颜色数据
uint32_t row_size = LCD_WIDTH * 2; // 每行像素占用 2 字节
uint8_t *row_buffer = (uint8_t *)malloc(row_size);
if (row_buffer == NULL) return DEVICE_ERR_NULL; // 分配失败,直接返回
// 填充缓冲区为目标颜色
for (uint32_t i = 0; i < row_size; i += 2) {
row_buffer[i] = color_data[0];
row_buffer[i + 1] = color_data[1];
}
// 按行传输数据,覆盖整个屏幕
for (uint32_t y = 0; y < LCD_HEIGHT; y++) {
LCD_WriteDataBuffer_DMA(row_buffer, row_size);
}
free(row_buffer); // 释放缓冲区
return DEVICE_OK;
}
/**
* 绘制单个像素点
*
* @param x X坐标
* @param y Y坐标
* @param color 像素颜色RGB565格式
*
* @note 此函数用于在指定位置绘制一个像素点。
*/
int8_t LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color) {
uint16_t mx, my;
LCD_MapCoords(x, y, &mx, &my); // 根据屏幕方向映射坐标
LCD_SetAddressWindow(mx, my, 1, 1); // 设置绘制窗口为单个像素
uint8_t color_data[] = { (uint8_t)(color >> 8), (uint8_t)(color & 0xFF) }; // 将颜色转换为字节数组
LCD_WriteDataBuffer(color_data, 2); // 写入像素数据
return DEVICE_OK;
}
/**
* 绘制直线
*
* @param x0 起始X坐标
* @param y0 起始Y坐标
* @param x1 终止X坐标
* @param y1 终止Y坐标
* @param color 直线颜色RGB565格式
*
* @note 此函数使用Bresenham算法绘制直线。
*/
int8_t LCD_DrawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) {
int dx = x1 - x0; // 计算X方向增量
int dy = y1 - y0; // 计算Y方向增量
int sx = (dx >= 0) ? 1 : -1; // X方向步长
int sy = (dy >= 0) ? 1 : -1; // Y方向步长
dx = dx >= 0 ? dx : -dx; // 取绝对值
dy = dy >= 0 ? dy : -dy; // 取绝对值
if (dx == 0 && dy == 0) { // 单点
LCD_DrawPoint((uint16_t)x0, (uint16_t)y0, color);
return DEVICE_OK;
}
if (dx > dy) { // X方向增量大于Y方向增量
int err = dx / 2; // 初始化误差
int x = x0;
int y = y0;
for (int i = 0; i <= dx; i++) {
LCD_DrawPoint((uint16_t)x, (uint16_t)y, color); // 绘制当前点
x += sx; // 更新X坐标
err -= dy; // 更新误差
if (err < 0) {
y += sy; // 更新Y坐标
err += dx; // 更新误差
}
}
} else { // Y方向增量大于X方向增量
int err = dy / 2; // 初始化误差
int x = x0;
int y = y0;
for (int i = 0; i <= dy; i++) {
LCD_DrawPoint((uint16_t)x, (uint16_t)y, color); // 绘制当前点
y += sy; // 更新Y坐标
err -= dx; // 更新误差
if (err < 0) {
x += sx; // 更新X坐标
err += dy; // 更新误差
}
}
}
return DEVICE_OK;
}
/**
* 绘制矩形
*
* @param x1 矩形左上角X坐标
* @param y1 矩形左上角Y坐标
* @param x2 矩形右下角X坐标
* @param y2 矩形右下角Y坐标
* @param color 矩形颜色RGB565格式
*
* @note 此函数通过绘制四条边来绘制矩形。
*/
int8_t LCD_DrawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {
LCD_DrawLine(x1, y1, x2, y1, color); // 上边
LCD_DrawLine(x1, y1, x1, y2, color); // 左边
LCD_DrawLine(x1, y2, x2, y2, color); // 下边
LCD_DrawLine(x2, y1, x2, y2, color); // 右边
return DEVICE_OK;
}
int8_t LCD_DrawSolidRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {
int8_t a;
if(y1>y2) a=1;
else a=-1;
while(y1!=y2) {
LCD_DrawLine(x1, y1, x2, y1, color); // 上边
LCD_DrawLine(x1, y2, x2, y2, color); // 下边
y1-=a;y2+=a;
}
return DEVICE_OK;
}
/**
* 绘制空心圆
*
* @param x0 圆心X坐标
* @param y0 圆心Y坐标
* @param r 圆的半径
* @param color 圆的颜色RGB565格式
*
* @note 此函数使用中点圆算法绘制空心圆。
*/
int8_t LCD_DrawHollowCircle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) {
int a = 0; // X方向增量
int b = r; // Y方向增量
while (a <= b) {
LCD_DrawPoint(x0 - b, y0 - a, color); // 第3象限
LCD_DrawPoint(x0 + b, y0 - a, color); // 第0象限
LCD_DrawPoint(x0 - a, y0 + b, color); // 第1象限
LCD_DrawPoint(x0 - a, y0 - b, color); // 第2象限
LCD_DrawPoint(x0 + b, y0 + a, color); // 第4象限
LCD_DrawPoint(x0 + a, y0 - b, color); // 第5象限
LCD_DrawPoint(x0 + a, y0 + b, color); // 第6象限
LCD_DrawPoint(x0 - b, y0 + a, color); // 第7象限
a++; // 更新X方向增量
if ((a * a + b * b) > (r * r)) { // 判断是否超出半径范围
b--; // 更新Y方向增量
}
}
return DEVICE_OK;
}
/**
* 绘制实心圆
*
* @param x0 圆心X坐标
* @param y0 圆心Y坐标
* @param r 圆的半径
* @param color 圆的颜色RGB565格式
*
* @note 此函数使用中点圆算法绘制实心圆,通过填充水平线段实现。
*/
int8_t LCD_DrawSolidCircle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) {
int a = 0; // X方向增量
int b = r; // Y方向增量
int r2 = r * r; // 预计算半径的平方
while (a <= b) {
// 绘制8个对称点并填充水平线段
LCD_DrawLine(x0 - b, y0 - a, x0 + b, y0 - a, color); // 第3象限
LCD_DrawLine(x0 - b, y0 + a, x0 + b, y0 + a, color); // 第4象限
LCD_DrawLine(x0 - a, y0 - b, x0 + a, y0 - b, color); // 第2象限
LCD_DrawLine(x0 - a, y0 + b, x0 + a, y0 + b, color); // 第6象限
a++; // 更新X方向增量
if ((a * a + b * b) > r2) { // 判断是否超出半径范围
b--; // 更新Y方向增量
}
}
return DEVICE_OK;
}
/**
* 绘制字符
*
* @param x 字符起始X坐标
* @param y 字符起始Y坐标
* @param ch 要绘制的字符
* @param color 字符颜色RGB565格式
* @param font_size 字体大小12或32
* @param bit_order 位顺序LSB或MSB
*
* @note 此函数根据字体大小和位顺序绘制单个字符。
*/
int8_t LCD_DrawChar(uint16_t x, uint16_t y, char ch, uint16_t color, uint8_t font_size, LCD_BitOrder_t bit_order) {
if (ch < ' ' || ch > '~') { // 检查字符是否在可打印范围内
return DEVICE_ERR;
}
uint8_t index = ch - ' '; // 计算字符索引
const uint8_t *font_data=NULL;
uint8_t char_width=0;
uint8_t char_height=0;
uint8_t bytesPerRow=0;
switch (font_size) {
case 12:// 12x6 字体ascii_1206特殊位映射 bit5..bit0
#ifdef ASCII_1206
font_data = (const uint8_t *)ascii_1206[index];
char_width = 6;
char_height = 12;
for (uint8_t row = 0; row < char_height; row++) {
for (uint8_t col = 0; col < char_width; col++) {
uint16_t pixel_x = x + col;
uint16_t pixel_y = y + row;
uint8_t bit_value;
if (bit_order == MSB) { // MSB 优先项目约定6 位放在 bit5..bit0
bit_value = (font_data[row] >> (5 - col)) & 0x01;
} else { // LSB 优先
bit_value = (font_data[row] >> col) & 0x01;
}
if (bit_value) {
LCD_DrawPoint(pixel_x, pixel_y, color);
}
}
}
#endif
break;
case 16:// 16x8 字体ascii_1608按行存储每行 1 字节MSB-first 常规映射)
#ifdef ASCII_1608
font_data = (const uint8_t *)ascii_1608[index];
char_width = 8;
char_height = 16;
for (uint8_t row = 0; row < char_height; row++) {
uint8_t row_byte = font_data[row];
for (uint8_t col = 0; col < char_width; col++) {
uint16_t pixel_x = x + col;
uint16_t pixel_y = y + row;
uint8_t bit_value;
if (bit_order == MSB) {
bit_value = (row_byte >> (7 - col)) & 0x01; // MSB-first: bit7 -> col0
} else {
bit_value = (row_byte >> col) & 0x01; // LSB-first
}
if (bit_value) {
LCD_DrawPoint(pixel_x, pixel_y, color);
}
}
}
#endif
break;
case 24:// 24x12 字体ascii_2412按行存储每行 2 字节)
#ifdef ASCII_2412
font_data = (const uint8_t *)ascii_2412[index];
char_width = 12;
char_height = 24;
bytesPerRow = (char_width + 7) / 8; // =2
for (uint8_t row = 0; row < char_height; row++) {
for (uint8_t col = 0; col < char_width; col++) {
uint16_t pixel_x = x + col;
uint16_t pixel_y = y + row;
uint8_t byte_index = col / 8;
uint8_t b = font_data[row * bytesPerRow + byte_index];
uint8_t bit_value;
if (bit_order == MSB) {
bit_value = (b >> (7 - (col % 8))) & 0x01;
} else {
bit_value = (b >> (col % 8)) & 0x01;
}
if (bit_value) {
LCD_DrawPoint(pixel_x, pixel_y, color);
}
}
}
#endif
break;
case 32:// 32x16 字体ascii_3216按行存储每行 2 字节)
#ifdef ASCII_3216
font_data = (const uint8_t *)ascii_3216[index];
char_width = 16;
char_height = 32;
bytesPerRow = (char_width + 7) / 8; // =2
for (uint8_t row = 0; row < char_height; row++) {
for (uint8_t col = 0; col < char_width; col++) {
uint16_t pixel_x = x + col;
uint16_t pixel_y = y + row;
uint8_t byte_index = col / 8;
uint8_t b = font_data[row * bytesPerRow + byte_index];
uint8_t bit_value;
if (bit_order == MSB) {
bit_value = (b >> (7 - (col % 8))) & 0x01;
} else {
bit_value = (b >> (col % 8)) & 0x01;
}
if (bit_value) {
LCD_DrawPoint(pixel_x, pixel_y, color);
}
}
}
#endif
break;
default:
return DEVICE_ERR;
}
return DEVICE_OK;
}
/**
* 绘制字符串
*
* @param x 字符串起始X坐标
* @param y 字符串起始Y坐标
* @param str 要绘制的字符串
* @param color 字符颜色RGB565格式
* @param font_size 字体大小12或32
* @param bit_order 位顺序LSB或MSB
*
* @note 此函数逐字符绘制字符串,支持换行。
*/
int8_t LCD_DrawString(uint16_t x, uint16_t y, const char *str, uint16_t color, uint8_t font_size, LCD_BitOrder_t bit_order) {
uint16_t cursor_x = x;
uint16_t cursor_y = y;
uint8_t char_width, char_height, x_spacing, y_spacing;
switch (font_size) {
case 12:
#ifdef ASCII_1206
char_width = 6;
char_height = 12;
x_spacing = 7; // 推荐间距:宽度+1
y_spacing = 13; // 行间距:高度+1
#endif
break;
case 16:
#ifdef ASCII_1608
char_width = 8;
char_height = 16;
x_spacing = 9;
y_spacing = 17;
#endif
break;
case 24:
#ifdef ASCII_2412
char_width = 12;
char_height = 24;
x_spacing = 13;
y_spacing = 25;
#endif
break;
case 32:
#ifdef ASCII_3216
char_width = 16;
char_height = 32;
x_spacing = 17;
y_spacing = 33;
#endif
break;
default:
return DEVICE_ERR;// 不支持的字体大小
}
while (*str) {
if (*str == '\n') {
cursor_x = x;
cursor_y += y_spacing;
str++;
continue;
}
LCD_DrawChar(cursor_x, cursor_y, *str, color, font_size, bit_order);
cursor_x += x_spacing;
str++;
}
}
/**
* 绘制整数
*
* @param x 起始X坐标
* @param y 起始Y坐标
* @param num 要绘制的整数
* @param color 数字颜色RGB565格式
* @param font_size 字体大小12或32
* @param bit_order 位顺序LSB或MSB
*
* @note 此函数将整数转换为字符串后调用LCD_DrawString绘制。
*/
int8_t LCD_DrawInteger(uint16_t x, uint16_t y, int32_t num, uint16_t color, uint8_t font_size, LCD_BitOrder_t bit_order) {
char buffer[12]; // 缓冲区足够存储32位整数的字符串表示
snprintf(buffer, sizeof(buffer), "%d", num); // 将整数转换为字符串
LCD_DrawString(x, y, buffer, color, font_size, bit_order); // 调用字符串绘制函数
return DEVICE_OK;
}
/**
* 绘制浮点数
*
* @param x 起始X坐标
* @param y 起始Y坐标
* @param num 要绘制的浮点数
* @param decimal_places 小数点后保留的位数
* @param color 数字颜色RGB565格式
* @param font_size 字体大小12或32
* @param bit_order 位顺序LSB或MSB
*
* @note 此函数将浮点数转换为字符串后调用LCD_DrawString绘制。
*/
int8_t LCD_DrawFloat(uint16_t x, uint16_t y, float num, uint8_t decimal_places, uint16_t color, uint8_t font_size, LCD_BitOrder_t bit_order) {
char buffer[20]; // 缓冲区,足够存储浮点数的字符串表示
snprintf(buffer, sizeof(buffer), "%.*f", decimal_places, num); // 将浮点数转换为字符串
LCD_DrawString(x, y, buffer, color, font_size, bit_order); // 调用字符串绘制函数
return DEVICE_OK;
}
/**
* 绘制位图
*
* @param bitmap 位图数据指针
* @param x 位图起始X坐标
* @param y 位图起始Y坐标
* @param width 位图宽度(像素)
* @param height 位图高度(像素)
* @param color 绘制颜色RGB565格式
*
* @note 此函数逐像素绘制位图,适用于简单的位图显示。如果需要更高性能,可以优化为按行发送。
*/
int8_t LCD_DrawBitmap(const uint8_t *bitmap, uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t color, LCD_BitOrder_t bit_order) {
if (bitmap == NULL) return DEVICE_ERR_NULL; // 检查位图指针是否为空
uint16_t bytesPerRow = (width + 7) / 8; // 每行的字节数
for (uint16_t row = 0; row < height; row++) { // 遍历每一行
const uint8_t *row_ptr = bitmap + (uint32_t)row * bytesPerRow; // 当前行的起始指针
for (uint16_t byte_i = 0; byte_i < bytesPerRow; byte_i++) { // 遍历每一行的字节
uint8_t b = row_ptr[byte_i]; // 当前字节
for (uint8_t bit = 0; bit < 8; bit++) { // 遍历每个字节的位
uint16_t col = (uint16_t)byte_i * 8 + bit; // 计算当前像素的列坐标
if (col >= width) break; // 如果超出宽度范围,跳过
uint8_t pixel_on = 0;
if (bit_order == MSB) {
// MSB-first字节内最高位(0x80)对应当前字节块的最左像素
pixel_on = (b & (0x80 >> bit)) ? 1 : 0;
} else {
// LSB-first字节内最低位(0x01)对应当前字节块的最左像素
pixel_on = (b & (1U << bit)) ? 1 : 0;
}
if (pixel_on) { // 如果当前位为1则绘制像素
LCD_DrawPoint((uint16_t)(x + col), (uint16_t)(y + row), color);
}
}
}
}
return DEVICE_OK;
}