文章目录
-
- 一、前言
-
- [1.1 技术背景](#1.1 技术背景)
- [1.2 本文目标](#1.2 本文目标)
- [1.3 技术栈](#1.3 技术栈)
- 二、环境准备
-
- [2.1 硬件准备](#2.1 硬件准备)
- [2.2 软件安装](#2.2 软件安装)
-
- [2.2.1 安装STM32CubeIDE](#2.2.1 安装STM32CubeIDE)
- [2.2.2 下载LVGL源码](#2.2.2 下载LVGL源码)
- [2.3 环境配置验证](#2.3 环境配置验证)
- 三、项目创建与配置
-
- [3.1 创建STM32CubeIDE工程](#3.1 创建STM32CubeIDE工程)
- [3.2 配置时钟](#3.2 配置时钟)
- [3.3 配置SPI接口](#3.3 配置SPI接口)
-
- [3.3.1 配置SPI1(LCD)](#3.3.1 配置SPI1(LCD))
- [3.3.2 配置SPI2(触摸屏)](#3.3.2 配置SPI2(触摸屏))
- [3.4 配置FreeRTOS](#3.4 配置FreeRTOS)
- [3.5 生成代码](#3.5 生成代码)
- 四、LVGL移植
-
- [4.1 添加LVGL源码到工程](#4.1 添加LVGL源码到工程)
- [4.2 实现显示驱动](#4.2 实现显示驱动)
- [4.3 实现触摸屏驱动](#4.3 实现触摸屏驱动)
- [4.4 实现智能手表界面](#4.4 实现智能手表界面)
- [4.5 FreeRTOS任务实现](#4.5 FreeRTOS任务实现)
- [4.6 系统架构流程图](#4.6 系统架构流程图)
- 五、编译与下载
-
- [5.1 编译工程](#5.1 编译工程)
- [5.2 下载程序](#5.2 下载程序)
- [5.3 查看运行结果](#5.3 查看运行结果)
- 六、故障排查与问题解决
-
- [6.1 显示问题](#6.1 显示问题)
- [6.2 性能问题](#6.2 性能问题)
- [6.3 内存问题](#6.3 内存问题)
- 七、进阶扩展
-
- [7.1 添加动画效果](#7.1 添加动画效果)
- [7.2 添加主题切换](#7.2 添加主题切换)
- 八、总结
-
- [8.1 核心知识点回顾](#8.1 核心知识点回顾)
- [8.2 扩展学习方向](#8.2 扩展学习方向)
- [8.3 学习资源](#8.3 学习资源)
一、前言
1.1 技术背景
在嵌入式设备中,图形用户界面(GUI)是提升用户体验的关键。传统的嵌入式GUI开发往往面临开发效率低、界面效果差、移植困难等问题。LVGL(Light and Versatile Graphics Library)是一款开源的嵌入式GUI库,具有轻量、美观、易移植等特点,广泛应用于智能手表、智能家居、工业控制等领域。
FreeRTOS是市场上最流行的实时操作系统之一,具有轻量、可移植、功能完善等特点。将LVGL与FreeRTOS结合,可以实现高效的多任务GUI应用开发。
1.2 本文目标
通过本教程,你将学会:
- FreeRTOS开发环境的搭建
- LVGL库的移植与配置
- 智能手表界面的设计与实现
- FreeRTOS与LVGL的集成
- 触摸屏和显示驱动的开发
1.3 技术栈
硬件平台:
- STM32F407VET6(主控芯片)
- ST7789 240x320 TFT LCD(显示屏)
- XPT2046(触摸屏控制器)
- 外部SRAM(显存扩展)
软件环境:
- STM32CubeIDE v1.10.0+
- FreeRTOS v10.4.0+
- LVGL v8.3.0+
- HAL库
二、环境准备
2.1 硬件准备
| 设备 | 数量 | 说明 |
|---|---|---|
| STM32F407VET6开发板 | 1块 | 主控芯片,168MHz |
| ST7789 LCD模块 | 1个 | 2.4寸,240x320分辨率 |
| XPT2046触摸屏 | 1个 | 电阻式触摸屏 |
| 外部SRAM模块 | 1个 | 可选,用于显存扩展 |
| ST-Link V2 | 1个 | 程序下载与调试 |
硬件连接图:
STM32F407VET6 ST7789 LCD
3.3V ─────────── VCC
GND ─────────── GND
PA5 ─────────── SCL (SPI1_SCK)
PA7 ─────────── SDA (SPI1_MOSI)
PA4 ─────────── CS (SPI1_NSS)
PA2 ─────────── RES
PA3 ─────────── DC
PA6 ─────────── BLK (背光)
STM32F407VET6 XPT2046
3.3V ─────────── VCC
GND ─────────── GND
PB13 ─────────── CLK (SPI2_SCK)
PB15 ─────────── DIN (SPI2_MOSI)
PB14 ─────────── DOUT (SPI2_MISO)
PB12 ─────────── CS
PB10 ─────────── PEN (触摸中断)
2.2 软件安装
2.2.1 安装STM32CubeIDE
- 访问ST官网下载STM32CubeIDE:https://www.st.com/en/development-tools/stm32cubeide.html
- 下载并安装(支持Windows、Linux、macOS)
- 安装完成后,下载STM32F4系列的HAL库
2.2.2 下载LVGL源码
- 访问LVGL GitHub仓库:https://github.com/lvgl/lvgl
- 下载v8.3.0版本源码
- 解压到工程目录
LVGL目录结构:
lvgl/
├── src/ # 核心源码
│ ├── core/ # 核心功能
│ ├── draw/ # 绘制引擎
│ ├── extra/ # 扩展功能
│ ├── font/ # 字体
│ ├── hal/ # 硬件抽象层
│ ├── misc/ # 工具函数
│ ├── widgets/ # 控件
│ └── lv_conf_internal.h # 内部配置
├── examples/ # 示例代码
├── docs/ # 文档
└── lvgl.h # 主头文件
2.3 环境配置验证
验证STM32CubeIDE:
- 打开STM32CubeIDE
- 创建新工程,选择STM32F407VET6
- 如果能正常生成代码,说明环境配置正确
验证硬件连接:
- 使用逻辑分析仪或示波器检查SPI信号
- 确认LCD和触摸屏的电源正常
三、项目创建与配置
3.1 创建STM32CubeIDE工程
- 打开STM32CubeIDE
- 点击
File -> New -> STM32 Project - 选择MCU型号:STM32F407VET6
- 输入工程名称:SmartWatch_LVGL
- 选择工程保存路径
3.2 配置时钟
- 打开
.ioc文件(CubeMX配置) - 配置时钟树:
- HSE:8MHz(外部晶振)
- SYSCLK:168MHz
- HCLK:168MHz
- APB1:42MHz
- APB2:84MHz
3.3 配置SPI接口
3.3.1 配置SPI1(LCD)
-
在Pinout视图中,配置SPI1:
- PA5:SPI1_SCK
- PA7:SPI1_MOSI
- PA4:GPIO_Output(软件控制CS)
-
SPI参数配置:
Mode: Full-Duplex Master
Hardware NSS: Disable
Frame Format: Motorola
Data Size: 8 bits
First Bit: MSB First
Clock Polarity: Low
Clock Phase: 1 Edge
Baud Rate: 21 Mbits/s (APB2/4)
3.3.2 配置SPI2(触摸屏)
-
配置SPI2:
- PB13:SPI2_SCK
- PB14:SPI2_MISO
- PB15:SPI2_MOSI
- PB12:GPIO_Output(软件控制CS)
-
SPI参数配置:
Mode: Full-Duplex Master
Hardware NSS: Disable
Frame Format: Motorola
Data Size: 8 bits
First Bit: MSB First
Clock Polarity: Low
Clock Phase: 1 Edge
Baud Rate: 10.5 Mbits/s (APB1/4)
3.4 配置FreeRTOS
- 在CubeMX中,点击
Middleware -> FREERTOS - 选择
Interface为CMSIS_V2 - 配置任务和队列
任务配置:
Task Name Priority Stack Size Entry Function
lvgl_task osPriorityNormal 4096 StartLVGLTask
touch_task osPriorityAboveNormal 1024 StartTouchTask
sensor_task osPriorityNormal 1024 StartSensorTask
3.5 生成代码
- 点击
Project -> Generate Code - 等待代码生成完成
四、LVGL移植
4.1 添加LVGL源码到工程
- 将下载的LVGL源码复制到工程目录
- 在STM32CubeIDE中,右键项目 ->
Refresh - 添加LVGL源文件到编译路径
📄 创建文件:
lv_conf.h
c
/*
* lv_conf.h - LVGL配置文件
*
* 功能:
* - 配置LVGL功能选项
* - 设置内存、显示、输入等参数
*/
#ifndef LV_CONF_H
#define LV_CONF_H
#include <stdint.h>
/* 颜色深度 */
#define LV_COLOR_DEPTH 16
/* 屏幕分辨率 */
#define LV_HOR_RES_MAX 240
#define LV_VER_RES_MAX 320
/* 显示缓冲区 */
#define LV_DISP_DEF_REFR_PERIOD 16
#define LV_DPI_DEF 100
/* 内存配置 */
#define LV_MEM_CUSTOM 0
#if LV_MEM_CUSTOM == 0
#define LV_MEM_SIZE (64U * 1024U)
#endif
/* 任务配置 */
#define LV_USE_PERF_MONITOR 1
#define LV_USE_MEM_MONITOR 1
/* 输入设备 */
#define LV_USE_INDEV_READ_PERIOD 20
/* 控件配置 */
#define LV_USE_ARC 1
#define LV_USE_BAR 1
#define LV_USE_BTN 1
#define LV_USE_BTNMATRIX 1
#define LV_USE_CANVAS 1
#define LV_USE_CHECKBOX 1
#define LV_USE_DROPDOWN 1
#define LV_USE_IMG 1
#define LV_USE_LABEL 1
#define LV_USE_LINE 1
#define LV_USE_ROLLER 1
#define LV_USE_SLIDER 1
#define LV_USE_SWITCH 1
#define LV_USE_TEXTAREA 1
#define LV_USE_TABLE 1
/* 额外控件 */
#define LV_USE_ANIMIMG 1
#define LV_USE_CALENDAR 1
#define LV_USE_CHART 1
#define LV_USE_COLORWHEEL 1
#define LV_USE_IMGBTN 1
#define LV_USE_KEYBOARD 1
#define LV_USE_LED 1
#define LV_USE_LIST 1
#define LV_USE_MENU 1
#define LV_USE_METER 1
#define LV_USE_MSGBOX 1
#define LV_USE_SPINBOX 1
#define LV_USE_SPINNER 1
#define LV_USE_TABVIEW 1
#define LV_USE_TILEVIEW 1
#define LV_USE_WIN 1
/* 主题配置 */
#define LV_USE_THEME_DEFAULT 1
#define LV_THEME_DEFAULT_DARK 1
/* 字体配置 */
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_MONTSERRAT_18 1
#define LV_FONT_MONTSERRAT_20 1
#define LV_FONT_MONTSERRAT_22 1
#define LV_FONT_MONTSERRAT_24 1
/* 日志配置 */
#define LV_USE_LOG 1
#if LV_USE_LOG
#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN
#define LV_LOG_PRINTF 1
#endif
/* 断言配置 */
#define LV_USE_ASSERT_NULL 1
#define LV_USE_ASSERT_MALLOC 1
#define LV_USE_ASSERT_STYLE 0
#define LV_USE_ASSERT_MEM_INTEGRITY 0
/* 编译器配置 */
#define LV_ATTRIBUTE_TICK_INC
#define LV_ATTRIBUTE_TIMER_HANDLER
#define LV_ATTRIBUTE_FLUSH_READY
/* 自定义时钟 */
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "FreeRTOS.h"
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (xTaskGetTickCount())
#endif
#endif /* LV_CONF_H */
4.2 实现显示驱动
📄 创建文件:
Core/Src/lvgl_driver/display_driver.c
c
/*
* display_driver.c - LCD显示驱动
*
* 功能:
* - ST7789 LCD初始化
* - LVGL显示缓冲区配置
* - 屏幕刷新回调函数
*/
#include "display_driver.h"
#include "stm32f4xx_hal.h"
#include "spi.h"
#include "lvgl.h"
/* 引脚定义 */
#define LCD_CS_PIN GPIO_PIN_4
#define LCD_CS_PORT GPIOA
#define LCD_DC_PIN GPIO_PIN_3
#define LCD_DC_PORT GPIOA
#define LCD_RES_PIN GPIO_PIN_2
#define LCD_RES_PORT GPIOA
#define LCD_BL_PIN GPIO_PIN_6
#define LCD_BL_PORT GPIOA
/* LCD命令 */
#define LCD_CMD 0
#define LCD_DATA 1
/* 显示缓冲区 */
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf1[LV_HOR_RES_MAX * 20];
static lv_color_t buf2[LV_HOR_RES_MAX * 20];
/* SPI句柄 */
extern SPI_HandleTypeDef hspi1;
/* 函数声明 */
static void lcd_write_cmd(uint8_t cmd);
static void lcd_write_data(uint8_t data);
static void lcd_write_data_dma(uint8_t *data, uint16_t len);
static void lcd_set_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
static void lcd_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p);
/* LCD写命令 */
static void lcd_write_cmd(uint8_t cmd)
{
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
}
/* LCD写数据 */
static void lcd_write_data(uint8_t data)
{
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, &data, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
}
/* LCD写数据(DMA) */
static void lcd_write_data_dma(uint8_t *data, uint16_t len)
{
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_DMA(&hspi1, data, len);
}
/* 设置显示窗口 */
static void lcd_set_window(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
/* 列地址设置 */
lcd_write_cmd(0x2A);
lcd_write_data(x1 >> 8);
lcd_write_data(x1 & 0xFF);
lcd_write_data(x2 >> 8);
lcd_write_data(x2 & 0xFF);
/* 行地址设置 */
lcd_write_cmd(0x2B);
lcd_write_data(y1 >> 8);
lcd_write_data(y1 & 0xFF);
lcd_write_data(y2 >> 8);
lcd_write_data(y2 & 0xFF);
/* 开始写内存 */
lcd_write_cmd(0x2C);
}
/* LCD初始化 */
void lcd_init(void)
{
/* 硬件复位 */
HAL_GPIO_WritePin(LCD_RES_PORT, LCD_RES_PIN, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(LCD_RES_PORT, LCD_RES_PIN, GPIO_PIN_SET);
HAL_Delay(100);
/* 软件复位 */
lcd_write_cmd(0x01);
HAL_Delay(150);
/* 退出睡眠模式 */
lcd_write_cmd(0x11);
HAL_Delay(120);
/* 设置显示方向 */
lcd_write_cmd(0x36);
lcd_write_data(0x00); /* RGB格式,正常方向 */
/* 设置像素格式 */
lcd_write_cmd(0x3A);
lcd_write_data(0x05); /* 16位色深 */
/* 帧率控制 */
lcd_write_cmd(0xB2);
lcd_write_data(0x0C);
lcd_write_data(0x0C);
lcd_write_data(0x00);
lcd_write_data(0x33);
lcd_write_data(0x33);
/* 电源控制 */
lcd_write_cmd(0xB7);
lcd_write_data(0x35);
/* VCOM设置 */
lcd_write_cmd(0xBB);
lcd_write_data(0x19);
/* LCM控制 */
lcd_write_cmd(0xC0);
lcd_write_data(0x2C);
/* VDV和VRH设置 */
lcd_write_cmd(0xC2);
lcd_write_data(0x01);
/* VRH设置 */
lcd_write_cmd(0xC3);
lcd_write_data(0x12);
/* VDV设置 */
lcd_write_cmd(0xC4);
lcd_write_data(0x20);
/* 帧率控制 */
lcd_write_cmd(0xC6);
lcd_write_data(0x0F);
/* 电源控制 */
lcd_write_cmd(0xD0);
lcd_write_data(0xA4);
lcd_write_data(0xA1);
/* 正极性伽马校正 */
lcd_write_cmd(0xE0);
lcd_write_data(0xD0);
lcd_write_data(0x04);
lcd_write_data(0x0D);
lcd_write_data(0x11);
lcd_write_data(0x13);
lcd_write_data(0x2B);
lcd_write_data(0x3F);
lcd_write_data(0x54);
lcd_write_data(0x4C);
lcd_write_data(0x18);
lcd_write_data(0x0D);
lcd_write_data(0x0B);
lcd_write_data(0x1F);
lcd_write_data(0x23);
/* 负极性伽马校正 */
lcd_write_cmd(0xE1);
lcd_write_data(0xD0);
lcd_write_data(0x04);
lcd_write_data(0x0C);
lcd_write_data(0x11);
lcd_write_data(0x13);
lcd_write_data(0x2C);
lcd_write_data(0x3F);
lcd_write_data(0x44);
lcd_write_data(0x51);
lcd_write_data(0x2F);
lcd_write_data(0x1F);
lcd_write_data(0x1F);
lcd_write_data(0x20);
lcd_write_data(0x23);
/* 打开显示 */
lcd_write_cmd(0x21); /* 反色关闭 */
lcd_write_cmd(0x29); /* 显示开启 */
/* 打开背光 */
HAL_GPIO_WritePin(LCD_BL_PORT, LCD_BL_PIN, GPIO_PIN_SET);
/* 清屏 */
lcd_set_window(0, 0, LV_HOR_RES_MAX - 1, LV_VER_RES_MAX - 1);
for (uint32_t i = 0; i < LV_HOR_RES_MAX * LV_VER_RES_MAX; i++)
{
lcd_write_data(0x00);
lcd_write_data(0x00);
}
}
/* LVGL刷新回调函数 */
static void lcd_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
/* 设置显示窗口 */
lcd_set_window(area->x1, area->y1, area->x2, area->y2);
/* 发送像素数据 */
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
/* 使用DMA传输 */
HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)color_p, w * h * 2);
}
/* DMA传输完成回调 */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi == &hspi1)
{
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
lv_disp_flush_ready(&disp_drv);
}
}
/* 显示驱动初始化 */
lv_disp_drv_t disp_drv;
void lvgl_display_init(void)
{
/* 初始化显示缓冲区 */
lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LV_HOR_RES_MAX * 20);
/* 初始化显示驱动 */
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = LV_HOR_RES_MAX;
disp_drv.ver_res = LV_VER_RES_MAX;
disp_drv.flush_cb = lcd_flush_cb;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
}
📄 创建文件:
Core/Inc/lvgl_driver/display_driver.h
c
/*
* display_driver.h - LCD显示驱动头文件
*/
#ifndef __DISPLAY_DRIVER_H__
#define __DISPLAY_DRIVER_H__
#include "lvgl.h"
/* LCD初始化 */
void lcd_init(void);
/* LVGL显示驱动初始化 */
void lvgl_display_init(void);
#endif /* __DISPLAY_DRIVER_H__ */
4.3 实现触摸屏驱动
📄 创建文件:
Core/Src/lvgl_driver/touch_driver.c
c
/*
* touch_driver.c - XPT2046触摸屏驱动
*
* 功能:
* - XPT2046触摸屏初始化
* - 触摸坐标读取
* - LVGL输入设备注册
*/
#include "touch_driver.h"
#include "stm32f4xx_hal.h"
#include "spi.h"
#include "lvgl.h"
/* 引脚定义 */
#define TOUCH_CS_PIN GPIO_PIN_12
#define TOUCH_CS_PORT GPIOB
#define TOUCH_PEN_PIN GPIO_PIN_10
#define TOUCH_PEN_PORT GPIOB
/* XPT2046命令 */
#define XPT2046_CMD_X 0xD0 /* X轴测量 */
#define XPT2046_CMD_Y 0x90 /* Y轴测量 */
#define XPT2046_CMD_Z1 0xB0 /* Z1压力测量 */
#define XPT2046_CMD_Z2 0xC0 /* Z2压力测量 */
/* 触摸参数 */
#define TOUCH_MIN_X 200
#define TOUCH_MAX_X 3800
#define TOUCH_MIN_Y 200
#define TOUCH_MAX_Y 3800
#define TOUCH_THRESHOLD 100
/* 滤波参数 */
#define TOUCH_FILTER_CNT 5
/* SPI句柄 */
extern SPI_HandleTypeDef hspi2;
/* 输入设备 */
static lv_indev_drv_t indev_drv;
static lv_indev_t *touch_indev;
/* 触摸状态 */
static int32_t touch_x = 0;
static int32_t touch_y = 0;
static uint8_t touch_pressed = 0;
/* 函数声明 */
static uint16_t xpt2046_read_cmd(uint8_t cmd);
static uint8_t xpt2046_read_xy(int32_t *x, int32_t *y);
static void touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data);
/* XPT2046读命令 */
static uint16_t xpt2046_read_cmd(uint8_t cmd)
{
uint8_t rx_buf[2];
uint16_t result;
HAL_GPIO_WritePin(TOUCH_CS_PORT, TOUCH_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi2, &cmd, 1, HAL_MAX_DELAY);
HAL_SPI_Receive(&hspi2, rx_buf, 2, HAL_MAX_DELAY);
HAL_GPIO_WritePin(TOUCH_CS_PORT, TOUCH_CS_PIN, GPIO_PIN_SET);
result = ((rx_buf[0] << 8) | rx_buf[1]) >> 3;
return result;
}
/* XPT2046读取XY坐标 */
static uint8_t xpt2046_read_xy(int32_t *x, int32_t *y)
{
uint16_t x_raw, y_raw;
uint16_t z1, z2;
uint32_t z;
/* 读取压力值 */
z1 = xpt2046_read_cmd(XPT2046_CMD_Z1);
z2 = xpt2046_read_cmd(XPT2046_CMD_Z2);
/* 计算压力 */
if (z1 == 0) return 0;
z = ((uint32_t)xpt2046_read_cmd(XPT2046_CMD_X) * (z2 - z1)) / z1;
/* 检查压力阈值 */
if (z < TOUCH_THRESHOLD) return 0;
/* 多次采样取平均 */
uint32_t x_sum = 0, y_sum = 0;
for (int i = 0; i < TOUCH_FILTER_CNT; i++)
{
x_sum += xpt2046_read_cmd(XPT2046_CMD_X);
y_sum += xpt2046_read_cmd(XPT2046_CMD_Y);
}
x_raw = x_sum / TOUCH_FILTER_CNT;
y_raw = y_sum / TOUCH_FILTER_CNT;
/* 坐标转换 */
*x = (int32_t)((x_raw - TOUCH_MIN_X) * LV_HOR_RES_MAX / (TOUCH_MAX_X - TOUCH_MIN_X));
*y = (int32_t)((y_raw - TOUCH_MIN_Y) * LV_VER_RES_MAX / (TOUCH_MAX_Y - TOUCH_MIN_Y));
/* 边界检查 */
if (*x < 0) *x = 0;
if (*x >= LV_HOR_RES_MAX) *x = LV_HOR_RES_MAX - 1;
if (*y < 0) *y = 0;
if (*y >= LV_VER_RES_MAX) *y = LV_VER_RES_MAX - 1;
return 1;
}
/* LVGL触摸读取回调 */
static void touch_read_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
(void)drv;
/* 检查触摸中断引脚 */
if (HAL_GPIO_ReadPin(TOUCH_PEN_PORT, TOUCH_PEN_PIN) == GPIO_PIN_RESET)
{
if (xpt2046_read_xy(&touch_x, &touch_y))
{
data->point.x = touch_x;
data->point.y = touch_y;
data->state = LV_INDEV_STATE_PRESSED;
touch_pressed = 1;
}
else
{
data->state = LV_INDEV_STATE_RELEASED;
touch_pressed = 0;
}
}
else
{
data->state = LV_INDEV_STATE_RELEASED;
touch_pressed = 0;
}
}
/* 触摸屏初始化 */
void touch_init(void)
{
/* 初始化CS引脚 */
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitStruct.Pin = TOUCH_CS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(TOUCH_CS_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(TOUCH_CS_PORT, TOUCH_CS_PIN, GPIO_PIN_SET);
/* 初始化PEN引脚 */
GPIO_InitStruct.Pin = TOUCH_PEN_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(TOUCH_PEN_PORT, &GPIO_InitStruct);
}
/* LVGL输入设备初始化 */
void lvgl_touch_init(void)
{
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touch_read_cb;
touch_indev = lv_indev_drv_register(&indev_drv);
}
📄 创建文件:
Core/Inc/lvgl_driver/touch_driver.h
c
/*
* touch_driver.h - 触摸屏驱动头文件
*/
#ifndef __TOUCH_DRIVER_H__
#define __TOUCH_DRIVER_H__
#include "lvgl.h"
/* 触摸屏初始化 */
void touch_init(void);
/* LVGL输入设备初始化 */
void lvgl_touch_init(void);
#endif /* __TOUCH_DRIVER_H__ */
4.4 实现智能手表界面
📄 创建文件:
Core/Src/ui/smartwatch_ui.c
c
/*
* smartwatch_ui.c - 智能手表界面
*
* 功能:
* - 主界面设计
* - 时间显示
* - 心率、步数等健康数据
* - 设置菜单
*/
#include "smartwatch_ui.h"
#include "lvgl.h"
#include <stdio.h>
#include <string.h>
/* UI对象 */
static lv_obj_t *main_screen;
static lv_obj_t *time_label;
static lv_obj_t *date_label;
static lv_obj_t *heart_rate_arc;
static lv_obj_t *heart_rate_label;
static lv_obj_t *step_label;
static lv_obj_t *battery_bar;
static lv_obj_t *menu_btn;
/* 样式 */
static lv_style_t style_arc;
static lv_style_t style_text;
/* 数据 */
static uint8_t heart_rate = 72;
static uint32_t step_count = 8542;
static uint8_t battery_level = 85;
/* 函数声明 */
static void create_main_screen(void);
static void update_time_cb(lv_timer_t *timer);
static void update_heart_rate_cb(lv_timer_t *timer);
static void menu_btn_event_cb(lv_event_t *e);
/* 创建智能手表主界面 */
void smartwatch_ui_init(void)
{
/* 初始化样式 */
lv_style_init(&style_arc);
lv_style_set_arc_width(&style_arc, 8);
lv_style_set_arc_color(&style_arc, lv_palette_main(LV_PALETTE_RED));
lv_style_init(&style_text);
lv_style_set_text_font(&style_text, &lv_font_montserrat_24);
lv_style_set_text_color(&style_text, lv_color_white());
/* 创建主界面 */
create_main_screen();
/* 创建定时器更新数据 */
lv_timer_create(update_time_cb, 1000, NULL);
lv_timer_create(update_heart_rate_cb, 2000, NULL);
}
/* 创建主界面 */
static void create_main_screen(void)
{
/* 创建屏幕 */
main_screen = lv_scr_act();
lv_obj_set_style_bg_color(main_screen, lv_color_hex(0x000000), 0);
/* 创建时间标签 */
time_label = lv_label_create(main_screen);
lv_label_set_text(time_label, "12:30");
lv_obj_set_style_text_font(time_label, &lv_font_montserrat_48, 0);
lv_obj_set_style_text_color(time_label, lv_color_white(), 0);
lv_obj_align(time_label, LV_ALIGN_TOP_MID, 0, 20);
/* 创建日期标签 */
date_label = lv_label_create(main_screen);
lv_label_set_text(date_label, "Mon, Apr 13");
lv_obj_set_style_text_font(date_label, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_color(date_label, lv_color_hex(0x888888), 0);
lv_obj_align_to(date_label, time_label, LV_ALIGN_OUT_BOTTOM_MID, 0, 5);
/* 创建心率弧形指示器 */
heart_rate_arc = lv_arc_create(main_screen);
lv_obj_set_size(heart_rate_arc, 100, 100);
lv_obj_align(heart_rate_arc, LV_ALIGN_CENTER, 0, 20);
lv_arc_set_value(heart_rate_arc, heart_rate);
lv_arc_set_range(heart_rate_arc, 40, 180);
lv_obj_set_style_arc_color(heart_rate_arc, lv_palette_main(LV_PALETTE_RED), LV_PART_INDICATOR);
lv_obj_set_style_arc_width(heart_rate_arc, 8, LV_PART_INDICATOR);
/* 创建心率标签 */
heart_rate_label = lv_label_create(heart_rate_arc);
lv_label_set_text_fmt(heart_rate_label, "%d", heart_rate);
lv_obj_set_style_text_font(heart_rate_label, &lv_font_montserrat_28, 0);
lv_obj_set_style_text_color(heart_rate_label, lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_center(heart_rate_label);
/* 创建心率图标 */
lv_obj_t *heart_icon = lv_label_create(main_screen);
lv_label_set_text(heart_icon, LV_SYMBOL_HEART);
lv_obj_set_style_text_color(heart_icon, lv_palette_main(LV_PALETTE_RED), 0);
lv_obj_align_to(heart_icon, heart_rate_arc, LV_ALIGN_OUT_TOP_MID, 0, -5);
/* 创建步数显示 */
step_label = lv_label_create(main_screen);
lv_label_set_text_fmt(step_label, "%s %lu", LV_SYMBOL_GPS, step_count);
lv_obj_set_style_text_font(step_label, &lv_font_montserrat_18, 0);
lv_obj_set_style_text_color(step_label, lv_color_hex(0x00FF00), 0);
lv_obj_align(step_label, LV_ALIGN_BOTTOM_MID, 0, -60);
/* 创建电池指示器 */
battery_bar = lv_bar_create(main_screen);
lv_obj_set_size(battery_bar, 40, 12);
lv_obj_align(battery_bar, LV_ALIGN_TOP_RIGHT, -10, 10);
lv_bar_set_value(battery_bar, battery_level, LV_ANIM_OFF);
lv_obj_set_style_bg_color(battery_bar, lv_color_hex(0x333333), 0);
lv_obj_set_style_bg_color(battery_bar, lv_palette_main(LV_PALETTE_GREEN), LV_PART_INDICATOR);
/* 电池百分比标签 */
lv_obj_t *battery_label = lv_label_create(main_screen);
lv_label_set_text_fmt(battery_label, "%d%%", battery_level);
lv_obj_set_style_text_font(battery_label, &lv_font_montserrat_12, 0);
lv_obj_set_style_text_color(battery_label, lv_color_white(), 0);
lv_obj_align_to(battery_label, battery_bar, LV_ALIGN_OUT_LEFT_MID, -5, 0);
/* 创建菜单按钮 */
menu_btn = lv_btn_create(main_screen);
lv_obj_set_size(menu_btn, 60, 30);
lv_obj_align(menu_btn, LV_ALIGN_BOTTOM_MID, 0, -15);
lv_obj_add_event_cb(menu_btn, menu_btn_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t *btn_label = lv_label_create(menu_btn);
lv_label_set_text(btn_label, "Menu");
lv_obj_center(btn_label);
}
/* 更新时间回调 */
static void update_time_cb(lv_timer_t *timer)
{
(void)timer;
static uint8_t hour = 12;
static uint8_t minute = 30;
static uint8_t second = 0;
second++;
if (second >= 60)
{
second = 0;
minute++;
if (minute >= 60)
{
minute = 0;
hour++;
if (hour >= 24)
{
hour = 0;
}
}
}
lv_label_set_text_fmt(time_label, "%02d:%02d", hour, minute);
}
/* 更新心率回调 */
static void update_heart_rate_cb(lv_timer_t *timer)
{
(void)timer;
/* 模拟心率变化 */
heart_rate = 60 + (rand() % 40);
lv_arc_set_value(heart_rate_arc, heart_rate);
lv_label_set_text_fmt(heart_rate_label, "%d", heart_rate);
/* 根据心率改变颜色 */
if (heart_rate > 120)
{
lv_obj_set_style_arc_color(heart_rate_arc, lv_palette_main(LV_PALETTE_RED), LV_PART_INDICATOR);
lv_obj_set_style_text_color(heart_rate_label, lv_palette_main(LV_PALETTE_RED), 0);
}
else if (heart_rate > 100)
{
lv_obj_set_style_arc_color(heart_rate_arc, lv_palette_main(LV_PALETTE_ORANGE), LV_PART_INDICATOR);
lv_obj_set_style_text_color(heart_rate_label, lv_palette_main(LV_PALETTE_ORANGE), 0);
}
else
{
lv_obj_set_style_arc_color(heart_rate_arc, lv_palette_main(LV_PALETTE_GREEN), LV_PART_INDICATOR);
lv_obj_set_style_text_color(heart_rate_label, lv_palette_main(LV_PALETTE_GREEN), 0);
}
/* 模拟步数增加 */
step_count += rand() % 3;
lv_label_set_text_fmt(step_label, "%s %lu", LV_SYMBOL_GPS, step_count);
}
/* 菜单按钮事件回调 */
static void menu_btn_event_cb(lv_event_t *e)
{
(void)e;
/* 创建菜单界面 */
lv_obj_t *menu_screen = lv_obj_create(NULL);
lv_obj_set_style_bg_color(menu_screen, lv_color_hex(0x000000), 0);
/* 标题 */
lv_obj_t *title = lv_label_create(menu_screen);
lv_label_set_text(title, "Settings");
lv_obj_set_style_text_font(title, &lv_font_montserrat_24, 0);
lv_obj_set_style_text_color(title, lv_color_white(), 0);
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20);
/* 返回按钮 */
lv_obj_t *back_btn = lv_btn_create(menu_screen);
lv_obj_set_size(back_btn, 80, 35);
lv_obj_align(back_btn, LV_ALIGN_BOTTOM_MID, 0, -20);
lv_obj_add_event_cb(back_btn,
[](lv_event_t *e) {
lv_obj_t *scr = lv_event_get_current_target(e);
lv_scr_load(main_screen);
lv_obj_del(lv_obj_get_parent(scr));
}, LV_EVENT_CLICKED, NULL);
lv_obj_t *back_label = lv_label_create(back_btn);
lv_label_set_text(back_label, "Back");
lv_obj_center(back_label);
/* 加载菜单界面 */
lv_scr_load(menu_screen);
}
📄 创建文件:
Core/Inc/ui/smartwatch_ui.h
c
/*
* smartwatch_ui.h - 智能手表界面头文件
*/
#ifndef __SMARTWATCH_UI_H__
#define __SMARTWATCH_UI_H__
/* 初始化智能手表界面 */
void smartwatch_ui_init(void);
#endif /* __SMARTWATCH_UI_H__ */
4.5 FreeRTOS任务实现
📝 修改文件:
Core/Src/freertos.c
c
/*
* freertos.c - FreeRTOS任务实现
*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
#include "lvgl.h"
#include "display_driver.h"
#include "touch_driver.h"
#include "smartwatch_ui.h"
/* 任务句柄 */
osThreadId_t lvglTaskHandle;
osThreadId_t touchTaskHandle;
osThreadId_t sensorTaskHandle;
/* 函数声明 */
void StartLVGLTask(void *argument);
void StartTouchTask(void *argument);
void StartSensorTask(void *argument);
/* LVGL任务 */
void StartLVGLTask(void *argument)
{
(void)argument;
/* 初始化LVGL */
lv_init();
/* 初始化显示驱动 */
lcd_init();
lvgl_display_init();
/* 初始化触摸屏 */
touch_init();
lvgl_touch_init();
/* 创建UI */
smartwatch_ui_init();
/* LVGL主循环 */
for (;;)
{
/* 处理LVGL任务 */
lv_timer_handler();
/* 延时5ms */
osDelay(5);
}
}
/* 触摸任务 */
void StartTouchTask(void *argument)
{
(void)argument;
for (;;)
{
/* 触摸处理在LVGL中完成 */
osDelay(10);
}
}
/* 传感器任务 */
void StartSensorTask(void *argument)
{
(void)argument;
for (;;)
{
/* 读取传感器数据 */
/* 这里可以添加心率传感器、加速度计等 */
osDelay(100);
}
}
/* 提供LVGL时基 */
uint32_t HAL_GetTick(void)
{
return xTaskGetTickCount();
}
4.6 系统架构流程图
应用层
中间件层
驱动层
硬件层
显示
输入
STM32F407
168MHz
ST7789 LCD
240x320
XPT2046
触摸屏
SPI驱动
HAL库
LCD驱动
display_driver.c
触摸驱动
touch_driver.c
FreeRTOS
任务调度
LVGL 8.3
GUI框架
智能手表UI
smartwatch_ui.c
LVGL任务
触摸任务
传感器任务
五、编译与下载
5.1 编译工程
- 在STM32CubeIDE中,点击
Project -> Build All - 等待编译完成
编译输出:
Building target: SmartWatch_LVGL.elf
Invoking: MCU GCC Linker
...
Finished building target: SmartWatch_LVGL.elf
Build complete. 0 errors, 0 warnings.
5.2 下载程序
- 连接ST-Link到开发板
- 点击工具栏的下载按钮
- 等待下载完成
5.3 查看运行结果
预期现象:
- LCD屏幕显示智能手表界面
- 时间每秒更新
- 心率数值动态变化(60-100之间)
- 步数缓慢增加
- 可以点击Menu按钮进入设置界面
- 点击Back按钮返回主界面
六、故障排查与问题解决
6.1 显示问题
问题1:LCD白屏或花屏
错误现象:
- LCD屏幕全白或显示乱码
- 屏幕闪烁
原因分析:
- SPI通信异常
- 初始化序列错误
- 时钟配置不正确
- 电源不稳定
解决方案:
方案1:检查SPI通信
c
// 添加SPI测试代码
uint8_t tx_data = 0x55;
uint8_t rx_data = 0;
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, &tx_data, &rx_data, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
if (rx_data != tx_data) {
printf("SPI communication error!\\n");
}
方案2:检查初始化序列
c
// 在lcd_init()中添加延时
lcd_write_cmd(0x11); // 退出睡眠
HAL_Delay(150); // 增加延时
lcd_write_cmd(0x29); // 打开显示
HAL_Delay(50);
问题2:触摸屏无响应
错误现象:
- 触摸屏幕无反应
- 触摸位置偏移
原因分析:
- 触摸屏校准问题
- SPI通信问题
- 中断配置错误
解决方案:
方案1:校准触摸屏
c
// 在touch_driver.c中添加校准参数
#define TOUCH_MIN_X 200
#define TOUCH_MAX_X 3800
#define TOUCH_MIN_Y 200
#define TOUCH_MAX_Y 3800
// 根据实际测试调整这些值
方案2:检查触摸中断
c
// 检查PEN引脚状态
if (HAL_GPIO_ReadPin(TOUCH_PEN_PORT, TOUCH_PEN_PIN) == GPIO_PIN_RESET) {
printf("Touch detected!\\n");
}
6.2 性能问题
问题3:界面卡顿
错误现象:
- 界面刷新慢
- 动画不流畅
原因分析:
- 显示缓冲区太小
- 任务优先级设置不当
- DMA传输未完成
解决方案:
方案1:优化显示缓冲区
c
// 增大显示缓冲区
static lv_color_t buf1[LV_HOR_RES_MAX * 40]; // 原来是20行
static lv_color_t buf2[LV_HOR_RES_MAX * 40];
方案2:调整任务优先级
c
// 提高LVGL任务优先级
osThreadAttr_t lvglTask_attributes = {
.name = "lvglTask",
.priority = (osPriority_t) osPriorityHigh, // 提高优先级
.stack_size = 4096
};
6.3 内存问题
问题4:内存不足
错误现象:
- 程序崩溃
- LVGL报错
原因分析:
- 堆内存不足
- 任务栈溢出
- 内存泄漏
解决方案:
方案1:调整内存配置
c
// lv_conf.h
#define LV_MEM_SIZE (128U * 1024U) // 增大到128KB
方案2:使用外部SRAM
c
// 配置外部SRAM作为显存
#define LV_MEM_ADR 0x68000000 // FSMC SRAM地址
七、进阶扩展
7.1 添加动画效果
c
/* 创建淡入动画 */
lv_anim_t anim;
lv_anim_init(&anim);
lv_anim_set_var(&anim, obj);
lv_anim_set_values(&anim, 0, 255);
lv_anim_set_duration(&anim, 500);
lv_anim_set_exec_cb(&anim, (lv_anim_exec_xcb_t)lv_obj_set_style_opa);
lv_anim_start(&anim);
7.2 添加主题切换
c
/* 切换深色/浅色主题 */
void switch_theme(bool dark)
{
if (dark) {
lv_theme_default_init(NULL, lv_palette_main(LV_PALETTE_BLUE),
lv_palette_main(LV_PALETTE_RED),
LV_THEME_DEFAULT_DARK,
&lv_font_montserrat_16);
} else {
lv_theme_default_init(NULL, lv_palette_main(LV_PALETTE_BLUE),
lv_palette_main(LV_PALETTE_RED),
false,
&lv_font_montserrat_16);
}
}
八、总结
8.1 核心知识点回顾
- LVGL移植:掌握LVGL的显示和输入设备驱动开发
- FreeRTOS集成:理解多任务环境下GUI的刷新机制
- SPI通信:掌握SPI接口的配置和数据传输
- 触摸屏校准:理解电阻式触摸屏的工作原理和校准方法
8.2 扩展学习方向
- LVGL高级特性:学习动画、样式、事件等高级功能
- 硬件加速:使用DMA2D等硬件加速图形渲染
- 低功耗设计:学习显示屏和触摸的低功耗管理
- 多界面管理:实现复杂的页面切换和导航
8.3 学习资源
官方文档:
官方GitHub: