4. 显示数字和变量
任务说明
本案例需要实现LCD屏幕上的动态数据刷新功能:将整数和浮点数变量转换为字符串,并显示在TFT液晶屏的指定位置。核心目标是编写两个转换函数(整数转字符串、浮点数转字符串),配合已有的LCD显示字符串函数,实现变量数值的实时更新显示。
硬件连接与初始化
本案例基于ST7789驱动芯片的TFT LCD模块(240×320像素),通过SPI接口与MCU连接。硬件连接如下表所示:
核心代码实现
以下代码实现了整数和浮点数到字符串的转换,以及LCD上的动态刷新功能。转换函数不依赖标准库中的sprintf,采用手动转换方式以适配嵌入式环境。
// ============ lcd_display_var.h ============
#ifndef LCD_DISPLAY_VAR_H
#define LCD_DISPLAY_VAR_H
#include "stm32f1xx_hal.h"
#include <stdint.h>
// LCD尺寸定义
#define LCD_WIDTH 240
#define LCD_HEIGHT 320
// 颜色定义(RGB565)
#define COLOR_WHITE 0xFFFF
#define COLOR_BLACK 0x0000
#define COLOR_BLUE 0x001F
#define COLOR_RED 0xF800
// 初始化LCD(SPI、GPIO、ST7789初始化序列)
void LCD_Init(void);
// 设置显示窗口
void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
// 填充颜色数据
void LCD_FillColor(uint16_t color, uint32_t count);
// 在指定位置显示字符串
// x: 起始列(0~239), y: 起始行(0~319)
// str: 要显示的字符串, color: 字体颜色, bg: 背景色
void LCD_ShowString(uint16_t x, uint16_t y, char *str, uint16_t color, uint16_t bg);
// 整数转字符串(支持负数)
// num: 输入整数, str: 输出缓冲区(需≥12字节)
void IntToStr(int32_t num, char *str);
// 浮点数转字符串(支持负数,保留decimals位小数)
// num: 输入浮点数, str: 输出缓冲区(需≥20字节), decimals: 小数位数(0~6)
void FloatToStr(float num, char *str, uint8_t decimals);
#endif
// ============ lcd_display_var.c ============
#include "lcd_display_var.h"
// SPI句柄(由外部提供)
extern SPI_HandleTypeDef hspi1;
// 控制引脚定义
#define LCD_DC_PIN GPIO_PIN_0
#define LCD_DC_PORT GPIOB
#define LCD_CS_PIN GPIO_PIN_1
#define LCD_CS_PORT GPIOB
#define LCD_RST_PIN GPIO_PIN_2
#define LCD_RST_PORT GPIOB
// 底层发送命令
static void LCD_SendCmd(uint8_t cmd) {
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_RESET); // DC=0 命令
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET); // CS=0 使能
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET); // CS=1 释放
}
// 底层发送数据
static void LCD_SendData(uint8_t data) {
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET); // DC=1 数据
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &data, 1, 100);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
}
// 初始化LCD
void LCD_Init(void) {
// 复位
HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_RESET);
HAL_Delay(20);
HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_SET);
HAL_Delay(150);
// ST7789初始化序列(简化版)
LCD_SendCmd(0x01); // 软件复位
HAL_Delay(150);
LCD_SendCmd(0x11); // 退出睡眠模式
HAL_Delay(200);
LCD_SendCmd(0x3A); // 像素格式
LCD_SendData(0x55); // 16位色
LCD_SendCmd(0x36); // 内存数据访问控制
LCD_SendData(0x00); // 正常方向
LCD_SendCmd(0x29); // 开启显示
}
// 设置显示窗口
void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
LCD_SendCmd(0x2A); // 列地址设置
LCD_SendData(x0 >> 8);
LCD_SendData(x0 & 0xFF);
LCD_SendData(x1 >> 8);
LCD_SendData(x1 & 0xFF);
LCD_SendCmd(0x2B); // 行地址设置
LCD_SendData(y0 >> 8);
LCD_SendData(y0 & 0xFF);
LCD_SendData(y1 >> 8);
LCD_SendData(y1 & 0xFF);
LCD_SendCmd(0x2C); // 开始写入内存
}
// 填充颜色数据
void LCD_FillColor(uint16_t color, uint32_t count) {
uint8_t hi = color >> 8;
uint8_t lo = color & 0xFF;
uint8_t data[2];
data[0] = hi;
data[1] = lo;
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
for (uint32_t i = 0; i < count; i++) {
HAL_SPI_Transmit(&hspi1, data, 2, 100);
}
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
}
// 显示字符串(使用8x16点阵字体)
void LCD_ShowString(uint16_t x, uint16_t y, char *str, uint16_t color, uint16_t bg) {
// 外部字体表:8x16 ASCII字符点阵
extern const uint8_t asc2_8x16[95][16];
while (*str) {
char c = *str;
if (c < 0x20 || c > 0x7E) { // 仅支持可打印ASCII
c = ' ';
}
// 设置字符窗口(8像素宽,16像素高)
LCD_SetWindow(x, y, x + 7, y + 15);
const uint8_t *glyph = asc2_8x16[c - 0x20];
// 逐行写入
for (int row = 0; row < 16; row++) {
uint8_t line = glyph[row];
for (int col = 0; col < 8; col++) {
uint16_t pixel = (line & (0x80 >> col)) ? color : bg;
uint8_t hi = pixel >> 8;
uint8_t lo = pixel & 0xFF;
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &hi, 1, 100);
HAL_SPI_Transmit(&hspi1, &lo, 1, 100);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
}
}
x += 8; // 移动到下一个字符位置
if (x + 8 > LCD_WIDTH) break; // 超出右边界则停止
str++;
}
}
// 整数转字符串
void IntToStr(int32_t num, char *str) {
char buffer[12];
int i = 0;
int is_negative = 0;
if (num == 0) {
str[0] = '0';
str[1] = '\0';
return;
}
if (num < 0) {
is_negative = 1;
num = -num; // 注意:INT32_MIN会溢出,但此处简化处理
}
while (num > 0) {
buffer[i++] = '0' + (num % 10);
num /= 10;
}
int j = 0;
if (is_negative) {
str[j++] = '-';
}
while (i > 0) {
str[j++] = buffer[--i];
}
str[j] = '\0';
}
// 浮点数转字符串
void FloatToStr(float num, char *str, uint8_t decimals) {
if (decimals > 6) decimals = 6;
// 处理负数
int is_negative = 0;
if (num < 0) {
is_negative = 1;
num = -num;
}
// 提取整数部分
int32_t int_part = (int32_t)num;
// 提取小数部分(四舍五入)
float frac = num - (float)int_part;
int32_t multiplier = 1;
for (uint8_t i = 0; i < decimals; i++) {
multiplier *= 10;
}
int32_t frac_part = (int32_t)(frac * multiplier + 0.5f);
// 处理进位(如0.999 -> 1.000)
if (frac_part >= multiplier) {
int_part++;
frac_part = 0;
}
// 转换整数部分
char int_str[12];
IntToStr(int_part, int_str);
// 组合结果
char *p = str;
if (is_negative) {
*p++ = '-';
}
// 复制整数部分
char *q = int_str;
while (*q) {
*p++ = *q++;
}
// 添加小数部分
if (decimals > 0) {
*p++ = '.';
char frac_str[7];
for (uint8_t i = 0; i < decimals; i++) {
frac_str[decimals - 1 - i] = '0' + (frac_part % 10);
frac_part /= 10;
}
frac_str[decimals] = '\0';
q = frac_str;
while (*q) {
*p++ = *q++;
}
}
*p = '\0';
}
主程序示例
以下代码演示了如何调用上述函数,实现两个动态变量的刷新显示:
// ============ main.c 关键部分 ============
#include "lcd_display_var.h"
// 8x16 ASCII字体表(仅列出前3个字符作为示意,完整表需包含95个字符)
const uint8_t asc2_8x16[95][16] = {
// 空格 (0x20)
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
// '!' (0x21)
{0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,
0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00},
// '"' (0x22)
{0x00,0x66,0x66,0x66,0x24,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},
// ... 完整字体表需包含全部95个ASCII可打印字符
};
// 模拟传感器数据(实际应用中由ADC或外设读取)
int32_t sensor_value = 0;
float temperature = 25.5f;
void SystemClock_Config(void); // 系统时钟配置(略)
int main(void) {
HAL_Init();
SystemClock_Config();
// SPI1初始化(略,需配置为10MHz主机模式)
// GPIO初始化(略,需配置PB0/PB1/PB2为输出)
LCD_Init();
// 清屏为黑色
LCD_SetWindow(0, 0, 239, 319);
LCD_FillColor(COLOR_BLACK, 240 * 320);
// 显示静态标签
LCD_ShowString(10, 20, "Sensor:", COLOR_WHITE, COLOR_BLACK);
LCD_ShowString(10, 50, "Temp:", COLOR_WHITE, COLOR_BLACK);
char display_buf[20];
while (1) {
// 模拟数据更新(实际应用中读取真实传感器)
sensor_value += 10;
if (sensor_value > 1000) sensor_value = 0;
temperature += 0.1f;
if (temperature > 30.0f) temperature = 20.0f;
// 显示整数变量(清除旧值区域后写入新值)
IntToStr(sensor_value, display_buf);
LCD_ShowString(90, 20, " ", COLOR_WHITE, COLOR_BLACK); // 清除旧值
LCD_ShowString(90, 20, display_buf, COLOR_YELLOW, COLOR_BLACK);
// 显示浮点变量(保留1位小数)
FloatToStr(temperature, display_buf, 1);
LCD_ShowString(90, 50, " ", COLOR_WHITE, COLOR_BLACK); // 清除旧值
LCD_ShowString(90, 50, display_buf, COLOR_CYAN, COLOR_BLACK);
HAL_Delay(500); // 500ms刷新一次
}
}
验证方法
按照以下步骤验证功能:
- 编译并烧录程序到MCU,确保SPI接线正确(SCLK→PA5,MOSI→PA7,DC→PB0,CS→PB1,RST→PB2,共用GND)。
- 上电后观察LCD屏幕:第一行显示“Sensor:”后跟不断递增的整数(0→10→20→...→1000循环),第二行显示“Temp:”后跟20.0到30.0之间递增的浮点数(保留1位小数)。
- 数值每500ms更新一次,旧值被正确清除,无残影或乱码。
- 判定标准:整数从0递增到1000后回绕到0重新开始;浮点数从20.0递增到30.0后回绕到20.0;所有字符清晰可辨,无重叠或花屏现象。
预期输出示例(代码逻辑可唯一推出的结果):
Sensor: 120
Temp: 26.3
其中“120”为整数变量当前值,“26.3”为浮点变量当前值(保留1位小数)。实际显示数值随时间变化,但格式始终与此一致。