STM32实战:基于FreeRTOS的LVGL嵌入式GUI移植(智能手表界面)

文章目录

    • 一、前言
      • [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 查看运行结果)
    • 六、故障排查与问题解决
    • 七、进阶扩展
      • [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
  1. 访问ST官网下载STM32CubeIDE:https://www.st.com/en/development-tools/stm32cubeide.html
  2. 下载并安装(支持Windows、Linux、macOS)
  3. 安装完成后,下载STM32F4系列的HAL库
2.2.2 下载LVGL源码
  1. 访问LVGL GitHub仓库:https://github.com/lvgl/lvgl
  2. 下载v8.3.0版本源码
  3. 解压到工程目录

LVGL目录结构:

复制代码
lvgl/
├── src/                    # 核心源码
│   ├── core/              # 核心功能
│   ├── draw/              # 绘制引擎
│   ├── extra/             # 扩展功能
│   ├── font/              # 字体
│   ├── hal/               # 硬件抽象层
│   ├── misc/              # 工具函数
│   ├── widgets/           # 控件
│   └── lv_conf_internal.h # 内部配置
├── examples/              # 示例代码
├── docs/                  # 文档
└── lvgl.h                 # 主头文件

2.3 环境配置验证

验证STM32CubeIDE:

  1. 打开STM32CubeIDE
  2. 创建新工程,选择STM32F407VET6
  3. 如果能正常生成代码,说明环境配置正确

验证硬件连接:

  1. 使用逻辑分析仪或示波器检查SPI信号
  2. 确认LCD和触摸屏的电源正常

三、项目创建与配置

3.1 创建STM32CubeIDE工程

  1. 打开STM32CubeIDE
  2. 点击 File -> New -> STM32 Project
  3. 选择MCU型号:STM32F407VET6
  4. 输入工程名称:SmartWatch_LVGL
  5. 选择工程保存路径

3.2 配置时钟

  1. 打开 .ioc 文件(CubeMX配置)
  2. 配置时钟树:
    • HSE:8MHz(外部晶振)
    • SYSCLK:168MHz
    • HCLK:168MHz
    • APB1:42MHz
    • APB2:84MHz

3.3 配置SPI接口

3.3.1 配置SPI1(LCD)
  1. 在Pinout视图中,配置SPI1:

    • PA5:SPI1_SCK
    • PA7:SPI1_MOSI
    • PA4:GPIO_Output(软件控制CS)
  2. 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(触摸屏)
  1. 配置SPI2:

    • PB13:SPI2_SCK
    • PB14:SPI2_MISO
    • PB15:SPI2_MOSI
    • PB12:GPIO_Output(软件控制CS)
  2. 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

  1. 在CubeMX中,点击 Middleware -> FREERTOS
  2. 选择 Interface 为CMSIS_V2
  3. 配置任务和队列

任务配置:

复制代码
Task Name          Priority   Stack Size   Entry Function
lvgl_task          osPriorityNormal   4096   StartLVGLTask
touch_task         osPriorityAboveNormal 1024 StartTouchTask
sensor_task        osPriorityNormal   1024   StartSensorTask

3.5 生成代码

  1. 点击 Project -> Generate Code
  2. 等待代码生成完成

四、LVGL移植

4.1 添加LVGL源码到工程

  1. 将下载的LVGL源码复制到工程目录
  2. 在STM32CubeIDE中,右键项目 -> Refresh
  3. 添加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 编译工程

  1. 在STM32CubeIDE中,点击 Project -> Build All
  2. 等待编译完成

编译输出:

复制代码
Building target: SmartWatch_LVGL.elf
Invoking: MCU GCC Linker
...
Finished building target: SmartWatch_LVGL.elf

Build complete. 0 errors, 0 warnings.

5.2 下载程序

  1. 连接ST-Link到开发板
  2. 点击工具栏的下载按钮
  3. 等待下载完成

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 核心知识点回顾

  1. LVGL移植:掌握LVGL的显示和输入设备驱动开发
  2. FreeRTOS集成:理解多任务环境下GUI的刷新机制
  3. SPI通信:掌握SPI接口的配置和数据传输
  4. 触摸屏校准:理解电阻式触摸屏的工作原理和校准方法

8.2 扩展学习方向

  • LVGL高级特性:学习动画、样式、事件等高级功能
  • 硬件加速:使用DMA2D等硬件加速图形渲染
  • 低功耗设计:学习显示屏和触摸的低功耗管理
  • 多界面管理:实现复杂的页面切换和导航

8.3 学习资源

官方文档:

官方GitHub:

相关推荐
Lugas Luo2 小时前
如何利用AI Agent自动分析Linux BSP(Board Support Package)驱动和内核日志
linux·人工智能·嵌入式硬件
振浩微433射频芯片2 小时前
低功耗无线遥控新选择:深度解析VI520R ASK/OOK接收芯片与433MHz方案优势
网络·单片机·嵌入式硬件·物联网·智能家居
leo__5202 小时前
STM32 DMA程序(标准外设库版本)
stm32·单片机·嵌入式硬件
三佛科技-134163842122 小时前
电动牙刷方案开发-基于FT61E131B-RB单片机
单片机·嵌入式硬件·智能家居·pcb工艺
至为芯2 小时前
PY32F071至为芯支持32位ARM内核的高主频MCU微控制器
单片机·嵌入式硬件·mcu
2201_756206342 小时前
STM32L431 下载调试问题排查与解决
stm32·单片机·嵌入式硬件
WeeJot嵌入式2 小时前
【IIC】IIC中断与DMA&状态机编程
单片机·嵌入式硬件
WeeJot嵌入式2 小时前
【OLED】OLED原理&驱动库&取模
stm32·单片机·嵌入式硬件·嵌入式·oled
liuluyang5302 小时前
Synopsys DesignWare APB GPIO (DW_apb_gpio) 模块寄存器详解
stm32·单片机·嵌入式硬件·dw-gpio