云计算百科
云计算领域专业知识百科平台

基于 STM32 与LVGL, 7寸QSPI 屏的无人机 MFD 仪表方案实践

目录

  • 引言
  • 项目概述
  • 硬件选型与设计
  • 开发环境搭建
  • 界面设计与实现
  • 核心功能开发
  • 系统集成与主程序
  • 测试与调试
  • 总结与展望
  • 引言

    无人机地面多功能显示器(MFD)是飞行控制的核心交互设备,需实时呈现关键飞行参数并支持快速操作。本方案采用 STM32 微处理器直接通过 QSPI 接口驱动 7 寸液晶屏,集成 CAN FD 总线实现与无人机飞控系统的高速数据交互,通过物理按键完成操作,专注于无人机专用参数显示(剩余电量、电机转速、悬停高度等),兼顾可靠性与实时性。

    项目概述

    核心目标

  • 硬件架构:STM32 通过 QSPI 接口直接驱动 7 寸 800*480 液晶屏,省去 RGB 转换环节,简化电路。
  • 数据交互:通过 CAN FD 总线接收无人机飞控数据(传输速率最高 8Mbps),满足高速数据需求。
  • 显示功能:实现无人机剩余电量、4 路电机转速、悬停高度、飞行模式、GPS 状态等核心参数的可视化显示。
  • 操作方式:通过 3-5 个物理功能按键完成界面切换、参数设置、紧急操作等功能。
  • 性能指标:数据刷新率≥10Hz,界面响应时间≤100ms,连续运行无故障时间≥1000 小时。
  • 硬件选型与设计

    核心控制器选型

    选用STM32H743VIT6作为主控制器,关键参数:

    参数规格适配优势
    内核 ARM Cortex-M7,主频 400MHz 高性能处理能力,支持复杂图形渲染与 CAN FD 实时处理
    外设 2x QSPI 接口(支持 800*480 屏驱动)、2x CAN FD 控制器 原生 QSPI 与 CAN FD 外设,无需额外扩展
    内存 1MB SRAM、2MB Flash 满足 LVGL 图形缓存(800*480@16bit 需约 768KB)与程序存储
    工作温度 -40℃~85℃ 适应无人机户外作业环境

    显示设备选型

    7 寸 800*480 QSPI 接口液晶屏参数:

    参数规格特性
    尺寸 7 英寸 平衡显示面积与便携性
    分辨率 800*480(4:3) 适合多参数分区显示
    接口 QSPI(4 线模式,支持双向数据传输) 直接与 STM32 QSPI 外设连接,传输速率最高 80MHz
    亮度 400cd/m² 户外阳光下可视
    背光 LED(支持 PWM 亮度调节) 可通过 STM32 GPIO 控制功耗
    工作电压 3.3V 与 STM32 供电兼容

    关键外设模块

  • CAN FD 收发器:

    • 型号:TJA1044(支持 CAN FD,最高 8Mbps)
    • 功能:将 STM32 的 CAN FD 信号转换为差分信号,与无人机飞控 CAN 总线连接。
  • 功能按键:

    • 数量:5 个(电源键、菜单键、上 / 下选择键、确认键)
    • 类型:轻触按键(带硬件消抖),下拉输入模式。
  • 电源模块:

    • 输入:DC 12V(无人机供电系统常见电压)
    • 输出:3.3V/2A(给 STM32、液晶屏供电),5V/1A(备用)。
  • 硬件连接设计

    1. STM32 与 QSPI 液晶屏连接
    STM32 引脚功能液晶屏引脚说明
    PB2(QSPI_CLK) 时钟线 CLK QSPI 时钟信号(最高 80MHz)
    PD11(QSPI_NCS) 片选 CS 低电平有效,选中液晶屏
    PF8(QSPI_D0) 数据输入 D0 主机接收 / 从机发送数据
    PF9(QSPI_D1) 数据输出 D1 主机发送 / 从机接收数据
    PF10(QSPI_D2) 数据输入 2 D2 可选,4 线模式增强传输(本方案启用)
    PG12(QSPI_D3) 数据输出 2 D3 可选,4 线模式增强传输(本方案启用)
    PA0 复位 RST 低电平复位液晶屏(默认上拉)
    PA1 背光控制 BL PWM 输出调节亮度(高电平使能)
    3.3V 电源 VCC 液晶屏供电
    GND 接地 GND 共地连接
    2. STM32 与 CAN FD 总线连接
    STM32 引脚功能TJA1044 引脚说明
    PD0(CAN1_RX) 接收数据 RXD CAN FD 接收信号
    PD1(CAN1_TX) 发送数据 TXD CAN FD 发送信号
    3.3V 电源 VCC 收发器供电
    GND 接地 GND 共地连接
    总线接口 CAN_H/CAN_L 连接至无人机飞控 CAN 总线
    3. 功能按键连接
    按键功能STM32 引脚电路设计
    电源键 PC0 下拉输入,按下接 3.3V
    菜单键 PC1 下拉输入,按下接 3.3V
    上选择键 PC2 下拉输入,按下接 3.3V
    下选择键 PC3 下拉输入,按下接 3.3V
    确认键 PC4 下拉输入,按下接 3.3V

    开发环境搭建

    核心工具

    工具名称版本用途
    STM32CubeIDE 1.14.0 代码开发、编译、调试一体化环境
    STM32CubeMX 6.10.0 外设配置(QSPI、CAN FD、GPIO 等)
    LVGL v8.3 嵌入式图形库,适配 800*480 分辨率
    CANoe 11.0 CAN FD 总线数据仿真与测试
    ST-Link V3 程序下载与硬件调试

    环境配置步骤

    1. STM32CubeMX 配置
  • 选择芯片 “STM32H743VITx”,配置核心外设:

    • QSPI:模式 “Memory-mapped”,时钟 80MHz,数据宽度 4 线(D0-D3)。
    • CAN FD:波特率 “500kbps(仲裁段)+8Mbps(数据段)”,使能中断。
    • GPIO:配置 5 个按键引脚为 “输入下拉”,PA1(背光)为 “TIM2_CH1 PWM 输出”。
    • RCC:外部高速时钟(25MHz),系统时钟 400MHz。
    • TIM:TIM6 作为 LVGL 时基(1ms 中断)。
  • 生成工程(STM32CubeIDE 格式),启用 FreeRTOS(用于任务调度)。

  • 2. LVGL 移植配置
  • 下载 LVGL v8.3 源码,复制至工程 “Middlewares/LVGL” 目录。
  • 配置lv_conf.h关键参数:

    c

    运行

    #define LV_HOR_RES_MAX 800
    #define LV_VER_RES_MAX 480
    #define LV_COLOR_DEPTH 16 // 16位色(RGB565)
    #define LV_MEM_SIZE (768 * 1024) // 768KB缓存(刚好容纳一帧800*480@16bit)
    #define LV_TICK_CUSTOM 1 // 使用TIM6作为时基
    #define LV_USE_GAUGE 1 // 启用仪表组件
    #define LV_USE_BAR 1 // 启用进度条组件

  • 编写 QSPI 显示驱动(lv_port_disp.c):
    • 实现disp_init():初始化 QSPI 接口与液晶屏(发送初始化指令)。
    • 实现disp_flush_cb():将 LVGL 的图像数据通过 QSPI 写入液晶屏指定区域。
  • 界面设计与实现(800*480)

    整体布局设计

    针对无人机专用参数,界面划分为 5 个区域:

    区域尺寸位置显示内容
    状态栏 800*30 顶部(0,0) 系统时间、飞行模式(手动 / 自动)、GPS 状态
    主参数区 400*300 左侧(0,30) 悬停高度(大字体)、剩余电量(百分比 + 进度条)
    电机转速区 400*300 右侧(400,30) 4 路电机转速表(圆形仪表,0-100%)
    辅助参数区 800*120 底部(0,330) 飞行速度、电池电压、续航时间预估
    按键提示区 800*30 最底部(0,450) 当前界面可用按键功能提示(如 “菜单键:切换视图”)

    核心仪表实现

    1. 悬停高度显示(主参数区)

    c

    运行

    void altitude_display_init(lv_obj_t *parent) {
    // 创建容器(400*300)
    lv_obj_t *container = lv_obj_create(parent);
    lv_obj_set_size(container, 400, 300);
    lv_obj_align(container, LV_ALIGN_TOP_LEFT, 0, 30);
    lv_obj_set_style_bg_color(container, lv_color_hex(0x000000), LV_PART_MAIN);

    // 高度值标签(大字体)
    lv_obj_t *alt_label = lv_label_create(container);
    lv_obj_set_style_text_font(alt_label, &lv_font_montserrat_60, LV_PART_MAIN);
    lv_obj_set_style_text_color(alt_label, lv_color_hex(0x00FF00), LV_PART_MAIN);
    lv_label_set_text(alt_label, "0.0m");
    lv_obj_align(alt_label, LV_ALIGN_CENTER, 0, -30);

    // 高度单位标签
    lv_obj_t *unit_label = lv_label_create(container);
    lv_obj_set_style_text_font(unit_label, &lv_font_montserrat_24, LV_PART_MAIN);
    lv_label_set_text(unit_label, "悬停高度");
    lv_obj_align(unit_label, LV_ALIGN_CENTER, 0, 30);
    }

    // 更新高度值
    void altitude_update(float height) {
    char buf[10];
    sprintf(buf, "%.1fm", height);
    lv_label_set_text(alt_label, buf);
    // 高度异常(>50m或<0)时变红
    if (height < 0 || height > 50) {
    lv_obj_set_style_text_color(alt_label, lv_color_hex(0xFF0000), LV_PART_MAIN);
    } else {
    lv_obj_set_style_text_color(alt_label, lv_color_hex(0x00FF00), LV_PART_MAIN);
    }
    }

    2. 剩余电量显示(主参数区下方)

    c

    运行

    void battery_display_init(lv_obj_t *parent) {
    // 创建电池容器(400*50)
    lv_obj_t *container = lv_obj_create(parent);
    lv_obj_set_size(container, 400, 50);
    lv_obj_align(container, LV_ALIGN_TOP_LEFT, 0, 330-50); // 主参数区下方

    // 电量进度条
    lv_obj_t *bat_bar = lv_bar_create(container);
    lv_obj_set_size(bat_bar, 200, 20);
    lv_obj_align(bat_bar, LV_ALIGN_CENTER, -50, 0);
    lv_bar_set_range(bat_bar, 0, 100);

    // 电量百分比标签
    lv_obj_t *bat_label = lv_label_create(container);
    lv_obj_set_style_text_font(bat_label, &lv_font_montserrat_20, LV_PART_MAIN);
    lv_label_set_text(bat_label, "100%");
    lv_obj_align(bat_label, LV_ALIGN_CENTER, 80, 0);
    }

    // 更新电量
    void battery_update(uint8_t percentage) {
    lv_bar_set_value(bat_bar, percentage, LV_ANIM_ON);
    char buf[5];
    sprintf(buf, "%d%%", percentage);
    lv_label_set_text(bat_label, buf);
    // 低电量(<20%)时进度条变红
    if (percentage < 20) {
    lv_obj_set_style_bg_color(bat_bar, lv_color_hex(0xFF0000), LV_PART_INDICATOR);
    } else {
    lv_obj_set_style_bg_color(bat_bar, lv_color_hex(0x00FF00), LV_PART_INDICATOR);
    }
    }

    3. 电机转速表(右侧 400*300 区域)

    c

    运行

    void motor_gauge_init(lv_obj_t *parent) {
    // 创建4个电机仪表(2×2布局)
    for (int i = 0; i < 4; i++) {
    lv_obj_t *gauge = lv_gauge_create(parent);
    lv_obj_set_size(gauge, 180, 180);
    // 设置位置(2×2网格)
    int x = (i%2) * 200 + 10;
    int y = (i/2) * 150 + 10;
    lv_obj_align(gauge, LV_ALIGN_TOP_LEFT, 400 + x, 30 + y);

    // 配置仪表范围(0-100%)
    lv_gauge_set_range(gauge, 0, 100);
    lv_gauge_set_critical_value(gauge, 90); // 90%以上为警戒值
    lv_gauge_add_needle_line(gauge, lv_color_hex(0x00FF00), 3, 50); // 绿色指针

    // 电机编号标签
    lv_obj_t *label = lv_label_create(gauge);
    lv_label_set_text_fmt(label, "电机%d", i+1);
    lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, -10);
    }
    }

    // 更新电机转速(0-100%)
    void motor_speed_update(uint8_t motor_id, uint8_t speed) {
    if (motor_id >= 4) return;
    lv_gauge_set_value(motors[motor_id], 0, speed);
    // 超警戒值(>90%)时指针变红
    if (speed > 90) {
    lv_gauge_set_needle_color(motors[motor_id], 0, lv_color_hex(0xFF0000));
    } else {
    lv_gauge_set_needle_color(motors[motor_id], 0, lv_color_hex(0x00FF00));
    }
    }

    核心功能开发

    1. QSPI 液晶屏驱动

    c

    运行

    // 液晶屏初始化(发送QSPI命令)
    void qspi_lcd_init(void) {
    // 复位液晶屏
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);
    HAL_Delay(100);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
    HAL_Delay(100);

    // 初始化QSPI接口(STM32CubeMX已配置,此处发送屏初始化指令)
    qspi_send_cmd(0x11); // 退出睡眠模式
    HAL_Delay(120);
    qspi_send_cmd(0x29); // 开启显示
    qspi_send_cmd(0x36, 0x00); // 设置显示方向
    }

    // 通过QSPI发送命令
    static void qspi_send_cmd(uint8_t cmd, uint8_t data) {
    // 片选使能
    HAL_QSPI_Command(&hqspi, &(QSPI_CommandTypeDef){
    .Instruction = cmd,
    .InstructionMode = QSPI_INSTRUCTION_1_LINE,
    .AddressMode = QSPI_ADDRESS_NONE,
    .DataMode = QSPI_DATA_1_LINE,
    .DummyCycles = 0,
    .NbData = 1,
    .DdrMode = QSPI_DDR_MODE_DISABLE,
    .DdrHoldHalfCycle = QSPI_DDR_HHC_DISABLE,
    .SIOOMode = QSPI_SIOO_INST_EVERY_CMD
    }, HAL_QPSI_TIMEOUT_DEFAULT_VALUE);

    // 发送数据
    HAL_QSPI_Transmit(&hqspi, &data, HAL_QPSI_TIMEOUT_DEFAULT_VALUE);

    // 片选禁用
    HAL_QSPI_Abort(&hqspi);
    }

    // LVGL刷新回调(将图像数据写入液晶屏)
    static void disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
    // 设置液晶屏显示区域(area->x1, area->y1到area->x2, area->y2)
    lcd_set_window(area->x1, area->y1, area->x2, area->y2);

    // 计算数据长度(像素数×2字节/像素)
    uint32_t length = (area->x2 – area->x1 + 1) * (area->y2 – area->y1 + 1) * 2;

    // 通过QSPI发送图像数据
    HAL_QSPI_Command(&hqspi, &(QSPI_CommandTypeDef){
    .Instruction = 0x2C, // 写入GRAM命令
    .InstructionMode = QSPI_INSTRUCTION_1_LINE,
    .AddressMode = QSPI_ADDRESS_NONE,
    .DataMode = QSPI_DATA_4_LINES, // 4线模式加速传输
    .DummyCycles = 0,
    .NbData = length,
    .DdrMode = QSPI_DDR_MODE_DISABLE,
    .DdrHoldHalfCycle = QSPI_DDR_HHC_DISABLE,
    .SIOOMode = QSPI_SIOO_INST_EVERY_CMD
    }, HAL_QPSI_TIMEOUT_DEFAULT_VALUE);

    HAL_QSPI_Transmit(&hqspi, (uint8_t*)color_p, HAL_QPSI_TIMEOUT_DEFAULT_VALUE);
    HAL_QSPI_Abort(&hqspi);

    // 通知LVGL刷新完成
    lv_disp_flush_ready(disp);
    }

    2. CAN FD 数据接收与解析

    c

    运行

    // CAN FD初始化
    void can_fd_init(void) {
    // 过滤器配置(接收所有无人机数据帧)
    CAN_FilterTypeDef filter = {
    .FilterActivation = ENABLE,
    .FilterBank = 0,
    .FilterMode = CAN_FILTERMODE_IDMASK,
    .FilterScale = CAN_FILTERSCALE_32BIT,
    .FilterIdHigh = 0x0000,
    .FilterIdLow = 0x0000,
    .FilterMaskIdHigh = 0x0000,
    .FilterMaskIdLow = 0x0000,
    .FilterFIFOAssignment = CAN_RX_FIFO0
    };
    HAL_CAN_ConfigFilter(&hcan1, &filter);

    // 启动CAN FD并使能接收中断
    HAL_CAN_Start(&hcan1);
    HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
    }

    // CAN FD接收中断回调
    void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {
    CAN_RxHeaderTypeDef rx_header;
    uint8_t rx_data[64]; // CAN FD最大数据长度64字节

    if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data) == HAL_OK) {
    // 根据ID解析数据(无人机自定义协议)
    switch (rx_header.StdId) {
    case 0x101: // 飞行状态帧(高度、电量等)
    parse_flight_status(rx_data, rx_header.DLC);
    break;
    case 0x102: // 电机状态帧
    parse_motor_status(rx_data, rx_header.DLC);
    break;
    // 其他帧类型…
    }
    }
    }

    // 解析飞行状态帧(示例:rx_data[0-3]为高度,rx_data[4]为电量)
    static void parse_flight_status(uint8_t *data, uint8_t len) {
    if (len < 5) return;
    // 高度(float类型,小端格式)
    float height;
    memcpy(&height, data, 4);
    altitude_update(height);

    // 电量(百分比)
    battery_update(data[4]);
    }

    // 解析电机状态帧(示例:rx_data[0-3]为4路电机转速百分比)
    static void parse_motor_status(uint8_t *data, uint8_t len) {
    if (len < 4) return;
    for (int i = 0; i < 4; i++) {
    motor_speed_update(i, data[i]);
    }
    }

    3. 按键处理

    c

    运行

    // 按键扫描任务(FreeRTOS任务)
    void key_scan_task(void *arg) {
    uint8_t key_state[5] = {1,1,1,1,1}; // 初始状态(未按下)
    uint8_t key_prev[5] = {1,1,1,1,1}; // 上一状态

    while (1) {
    // 读取5个按键状态(0=按下,1=未按下)
    key_state[0] = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_0); // 电源键
    key_state[1] = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_1); // 菜单键
    key_state[2] = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_2); // 上选择键
    key_state[3] = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_3); // 下选择键
    key_state[4] = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_4); // 确认键

    // 检测按键下降沿(按下事件)
    for (int i = 0; i < 5; i++) {
    if (key_state[i] == 0 && key_prev[i] == 1) {
    key_handler(i); // 处理按键事件
    }
    key_prev[i] = key_state[i];
    }

    osDelay(20); // 20ms扫描一次,去抖
    }
    }

    // 按键事件处理
    static void key_handler(uint8_t key_id) {
    switch (key_id) {
    case 1: // 菜单键
    ui_switch_view(); // 切换界面视图
    break;
    case 2: // 上选择键
    ui_param_increase(); // 参数增加
    break;
    case 3: // 下选择键
    ui_param_decrease(); // 参数减少
    break;
    case 4: // 确认键
    ui_param_confirm(); // 确认参数
    break;
    // 电源键处理(略)
    }
    }

    系统集成与主程序

    c

    运行

    // 主程序入口
    int main(void) {
    // 硬件初始化
    HAL_Init();
    SystemClock_Config(); // 配置400MHz系统时钟
    MX_GPIO_Init();
    MX_QSPI_Init();
    MX_CAN1_Init();
    MX_TIM6_Init(); // LVGL时基
    MX_FREERTOS_Init(); // 初始化FreeRTOS

    // 外设初始化
    qspi_lcd_init(); // QSPI液晶屏初始化
    can_fd_init(); // CAN FD初始化

    // LVGL初始化
    lv_init();
    lv_port_disp_init(); // 绑定显示驱动
    ui_init(); // 初始化界面元素

    // 创建任务
    osThreadNew(key_scan_task, NULL, &key_scan_task_attributes);
    osThreadNew(ui_refresh_task, NULL, &ui_refresh_task_attributes);

    // 启动调度器
    osKernelStart();

    while (1) {
    // 主循环(FreeRTOS调度器运行后不会执行到这里)
    }
    }

    // 界面刷新任务(100ms一次)
    void ui_refresh_task(void *arg) {
    while (1) {
    lv_task_handler(); // 处理LVGL任务
    osDelay(10); // 10ms一次,保证界面流畅
    }
    }

    测试与调试

    关键测试项

  • QSPI 显示测试:

    • 测试内容:全屏纯色填充(红 / 绿 / 蓝)、灰度渐变、字符显示。
    • 验收标准:无花屏、无拖影,颜色过渡均匀。
  • CAN FD 通信测试:

    • 测试工具:CANoe 仿真无人机数据帧(0x101、0x102 等 ID)。
    • 验收标准:接收成功率 100%,数据解析正确,界面更新延迟 < 100ms。
  • 按键功能测试:

    • 测试内容:连续按键 5000 次,检查是否有误触发或失效。
    • 验收标准:响应率 100%,无粘连现象。
  • 系统稳定性测试:

    • 测试方法:连续运行 24 小时,模拟振动(可选)。
    • 验收标准:无死机、无数据丢失,界面正常刷新。
  • 常见问题解决

    问题原因解决方案
    QSPI 传输数据错误 时钟频率过高(>80MHz)或布线不良 降低 QSPI 时钟至 60MHz,优化 PCB 布线(缩短线长,阻抗匹配)
    CAN FD 接收丢帧 波特率配置错误或 FIFO 溢出 重新校准 CAN FD 波特率,在中断中及时处理数据(减少耗时操作)
    界面卡顿 LVGL 缓存不足或刷新频率过高 确保 LV_MEM_SIZE≥768KB,限制界面元素数量(减少重绘区域)
    按键误触发 无硬件消抖或扫描频率过低 增加 10KΩ 下拉电阻 + 100nF 电容消抖,提高扫描频率至 50Hz

    总结与展望

    本方案基于 STM32 与 QSPI 屏构建了无人机专用 MFD,通过 CAN FD 总线实现高速数据交互,物理按键操作简单可靠,重点呈现了剩余电量、电机转速、悬停高度等核心参数。方案优势在于:

  • 硬件精简:直接 QSPI 驱动液晶屏,省去 RGB 转换模块,降低成本与故障率。
  • 实时性强:CAN FD 总线支持 8Mbps 传输速率,配合 10Hz 界面刷新率,满足无人机数据实时性需求。
  • 专用性突出:界面布局针对无人机参数优化,关键信息一目了然。
  • 未来可扩展方向:

    • 增加数据记录功能(通过 SD 卡存储飞行参数)。
    • 开发多视图切换(如增加姿态仪表、航线规划视图)。
    • 集成无线数传模块(如 4G),支持远程监控。
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 基于 STM32 与LVGL, 7寸QSPI 屏的无人机 MFD 仪表方案实践
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!