DTU LED 项目

基于 STM32F103ZE 的数据传输单元(DTU)项目,集成了 LCD 显示、按键控制、RS485 Modbus 通信等功能。

📋 项目简介

本项目是一个完整的 DTU 设备固件,实现了屏幕显示、按键交互、远程通信等核心功能。设备通过 RS485 接口与上位机进行 Modbus RTU 协议通信,支持图片传输、按键上报、状态监控等功能。

🎯 主要功能

  • LCD 显示:160×160 像素黑白 LCD,UC1698U 控制器,支持图形和文本显示
  • 按键控制:基于 MultiButton 库的按键检测,支持多种按键事件
  • RS485 通信:Modbus RTU 协议,DMA 接收模式,波特率 700000
  • LED 指示:系统运行状态指示灯
  • 看门狗保护:独立看门狗(IWDG)防止系统死机
  • Flash 存储:支持 BMP 图片数据存储到 Flash(扇区 126,地址 0x0803F000)
  • 定时器管理:5ms 周期定时器,用于系统任务调度

🔧 硬件平台

  • MCU:STM32F103ZE(Cortex-M3,512KB Flash,64KB RAM)
  • 系统时钟:72MHz(PLL 倍频)
  • LCD:160×160 像素,UC1698U 控制器
  • 通信接口:RS485(USART2,TX=PA2,RX=PA3,DE=PA1)
  • 按键:支持多按键输入(具体引脚见 Drivers/BSP/KEY/key.c)
  • LED:系统运行指示灯

📁 项目结构

LED/
├── Drivers/                    # 驱动层
│   ├── BSP/                   # 板级支持包
│   │   ├── 160160D/          # LCD 显示驱动
│   │   ├── KEY/              # 按键驱动(MultiButton)
│   │   ├── LED/              # LED 驱动
│   │   ├── RS485/            # RS485 通信驱动
│   │   ├── WDOG/             # 看门狗驱动
│   │   └── TIM/              # 定时器驱动
│   ├── CMSIS/                # CMSIS 核心文件
│   ├── STM32F1xx_HAL_Driver/ # STM32 HAL 库
│   └── SYSTEM/               # 系统核心驱动
│       ├── sys/              # 系统配置(时钟、中断等)
│       ├── delay/            # 延时函数
│       └── usart/            # 串口驱动(调试用)
├── Middlewares/              # 中间件层
│   └── Modbus/               # Modbus RTU 协议实现
│       ├── MODBUS.c/h       # Modbus 主处理
│       └── CRC16.c/h        # CRC16 校验
├── Users/                     # 用户应用层
│   ├── main.c               # 主程序入口
│   ├── File_Handle.c        # Flash 文件处理
│   └── config.h             # 配置文件
├── Projects/                 # 工程文件
│   └── MDK-ARM/             # Keil MDK-ARM 工程
├── Output/                   # 编译输出(仅保留 .hex)
└── readme.md                 # 本文件

🚀 快速开始

开发环境要求

  • IDE:Keil MDK-ARM V5.06 或更高版本
  • 编译器:ARM Compiler 5/6
  • 调试器:ST-Link、J-Link 等
  • Pack:Keil.STM32F1xx_DFP.2.4.1 或更高版本

编译步骤

  1. 打开 Projects/MDK-ARM/atk_f103.uvprojx 工程文件
  2. 选择目标设备:STM32F103ZE
  3. 编译工程(F7 或 Project → Build Target)
  4. 编译成功后,Output/ 目录下会生成 DTU.hex 文件

烧录程序

  1. 连接 ST-Link 或其他调试器到目标板
  2. 在 Keil 中点击 Download(F8)或使用外部烧录工具
  3. 烧录 Output/DTU.hex 文件到 MCU

📡 通信协议

Modbus RTU 配置

  • 协议:Modbus RTU
  • 主站地址:0x01
  • 波特率:700000
  • 数据位:8
  • 停止位:1
  • 校验位:无
  • 接收模式:DMA ReceiveToIdle(不定长帧接收)

通信功能

  • 实时数据上报:定时上报系统状态
  • 按键事件上报:按键按下时上报按键值
  • 图片传输:接收并显示远程发送的 BMP 图片数据
  • 屏幕刷新:支持远程刷新屏幕显示内容

🔄 工作流程

本项目核心功能是按键检测上报和图像接收显示,主要逻辑在 MODBUS.c 文件中实现。

系统整体架构

image-20260125153417020

┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│   定时器    │ 5ms中断  │   按键驱动    │ 检测按键 │   主循环    │
│  (5ms周期)  │────────>│ (MultiButton)│────────>│  (main.c)   │
└─────────────┘         └──────────────┘         └──────┬──────┘
                                 │──────────────────────│
                                 │ RS485_Process
                                 ▼
┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│   RS485     │ DMA接收 │  MODBUS.c    │ 处理数据 │   LCD显示     │
│  (USART2)   │────────>│  (主处理)    │────────>│  (160160D)   │
└─────────────┘         └──────┬───────┘         └─────────────┘
                               │
                 ┌─────────────┼─────────────┐
                 │ 发送命令    │             │ 解析数据控制
                 ▼             │             ▼
          ┌─────────────┐      │      ┌─────────────┐
          │   上位机    │      │      │    LED      │
          │  (138端)    │      │      │  (状态指示) │
          └─────────────┘      │      └─────────────┘
                               │

说明:MODBUS.c 接收 RS485 数据后解析;普通图片帧中的 info[0..3] 为 LED 状态,经 LED_Printf() 更新灯状态;LOGO 图片不含 LED 控制。

按键检测与上报流程

1. 按键检测流程

定时器中断 (5ms)
    │
    ├─> Button_Ticks()          // MultiButton 库扫描按键
    │
    └─> 检测到按键按下
            │
            ├─> Key_ProcessCallback()  // 按键回调函数
            │       │
            │       ├─> BackLight_ON()      // 打开背光
            │       ├─> KeyRun_Disp(1)      // 显示按键指示
            │       └─> keyStatus = key_type // 更新按键状态
            │
            └─> 主循环检测 keyStatus
                    │
                    └─> RS485_Process(keyStatus, ConnectFlg)

2. 按键上报流程

RS485_Process() 函数
    │
    ├─> 检测到按键 (key != KEY_NONE)
    │       │
    │       └─> 保存到 key_bk(静态变量,保持状态)
    │
    ├─> 检查发送条件
    │       │
    │       ├─> CMD_ACK == SET        // 上次命令已应答
    │       └─> ACK_OverTime == SET   // 或应答超时
    │
    └─> 满足条件时发送按键命令
            │
            ├─> RefreshScreen(KEY_CMD, key_bk)
            │       │
            │       ├─> RS485_RefreshScreenCMD(0x03, KEY_CMD, key)
            │       │       │
            │       │       ├─> 构建命令帧(6字节):
            │       │       │   [0] = 0x01 (从站地址)
            │       │       │   [1] = 0x03 (功能码)
            │       │       │   [2] = 0x00 (KEY_CMD)
            │       │       │   [3] = key  (按键值)
            │       │       │   [4-5] = CRC16校验
            │       │       │
            │       │       └─> RS485_SendBuff() 发送
            │       │
            │       └─> 重置轮询状态
            │
            └─> key_bk = KEY_NONE  // 清除按键备份

按键命令帧格式(6字节):

┌──────┬──────┬──────┬──────┬──────┬──────┐
│ 0x01 │ 0x03 │ 0x00 │ key  │ CRC  │ CRC  │
│地址  │功能码 │按键  │按键值 │ 高   │ 低   │
└──────┴──────┴──────┴──────┴──────┴──────┘

图像接收与显示流程

1. 数据接收流程

RS485 DMA 接收 (ReceiveToIdle)
    │
    ├─> 接收到一帧数据(3208字节)
    │       │
    │       └─> HAL_UARTEx_RxEventCallback()
    │               │
    │               ├─> RS485REG.NewMessageFlag = SET
    │               └─> 重新启动 DMA 接收
    │
    └─> 主循环检测 NewMessageFlag
            │
            └─> RS485_Process() 处理新消息

2. 数据解析与显示流程

RS485_Process() 检测到新消息
    │
    ├─> NewMessageFlag == SET
    │       │
    │       ├─> ConnectFlg = 1  // 设置连接标志
    │       │
    │       ├─> CRC 校验
    │       │       │
    │       │       ├─> 校验通过
    │       │       │       │
    │       │       │       ├─> 提取数据:
    │       │       │       │   Picture[3200] = DR[0:3199]    // 图片数据
    │       │       │       │   info[8] = DR[3200:3207]        // 命令信息
    │       │       │       │
    │       │       │       ├─> 解析 info: 
    │       │       │       │   
    │       │       │       │   info[4-5] = 图片类型标识
    │       │       │       │
    │       │       │       └─> 判断图片类型
    │       │       │               │
    │       │       │               ├─> LOGO 图片 (info[4]==0x89 && info[5]==0x45)
    │       │       │               │       │─>info[0-1] = 图片宽度 (16位)
    │       │       │               │       │─>info[2-3] = 图片高度 (16位)
    │       │       │               │       ├─> 复制到 logo 结构体
    │       │       │               │       ├─> BMP_SAVE2False()  // 保存到 Flash
    │       │       │               │       ├─> ClearScreen()     // 清屏
    │       │       │               │       ├─> LOGO_Printf()     // 显示 LOGO
    │       │       │               │       └─> delay_ms(2000)    // 延时 2 秒
    │       │       │               │
    │       │       │               └─> 普通图片
    │       │       │                       │─>info[0-3] = LED 状态 
    │       │       │                       ├─> LED_Printf(LED_BUFF)  // 更新 LED 状态
    │       │       │                       └─> ScreenPrintf(Picture) // 显示图片
    │       │       │
    │       │       └─> 校验失败
    │       │               │
    │       │               ├─> CRC_ERR_COUNT++
    │       │               └─> 连续 3 次错误 → RS485_DMA_init() 重新初始化
    │       │
    │       └─> 清除标志并设置 CMD_ACK = SET
    │
    └─> 继续处理其他任务

接收数据帧格式(3208字节):

┌─────────────────────┬──────────────────┐
│   Picture[3200]     │    info[8]       │
│   图片点阵数据       │    命令信息       │
└─────────────────────┴──────────────────┘
         │                      │
         │                      ├─> info[0-1]: 宽度
         │                      ├─> info[2-3]: 高度
         │                      ├─> info[4-5]: 类型标识
         │                      └─> info[6-7]: LED状态(普通图片)
         │
         └─> 3200 字节 BMP 点阵数据

实时数据轮询流程

定时器中断 (5ms)
    │
    └─> Process_Count()
            │
            ├─> RT_count++              // 实时轮询计数递增
            └─> ACK_count++ (如果 ACK_COUNT_ABLE == SET)
                    │
                    └─> 主循环检查
                            │
                            ├─> RT_count > 55 (约 275ms)
                            │       │
                            │       └─> RefreshScreen(RT_CMD, 0)  // 发送实时刷新命令
                            │
                            └─> ACK_count > 40 (约 200ms)
                                    │
                                    └─> ACK_OverTime = SET  // 应答超时
                                            │
                                            └─> 连续 5 次超时 → 重新初始化 RS485

完整工作流程图

┌─────────────────────────────────────────────────────────────┐
│                    系统初始化                                │
│  - HAL_Init()                                               │
│  - RS485_DMA_init()  // 启动 DMA 接收                        │
│  - Process_Init()   // 初始化轮询状态                        │
│  - NL_LOGO_Printf() // 显示 Flash 中的 LOGO                  │
└──────────────────────┬──────────────────────────────────────┘
                       │
                       ▼
        ┌──────────────────────────────┐
        │      主循环 (while(1))        │
        └──────────────┬───────────────┘
                       │
        ┌──────────────┴───────────────┐
        │                              │
        ▼                              ▼
┌───────────────┐            ┌───────────────┐
│  定时器中断    │            │  RS485_Process│
│   (5ms周期)   │            │   (主处理)     │
└───────┬───────┘            └───────┬───────┘
        │                            │
        ├─> Button_Ticks()           ├─> 检查新消息
        ├─> Process_Count()          │   (NewMessageFlag)
        └─> 其他任务                 │
                                    ├─> CRC 校验
                                    │
                                    ├─> 解析数据
                                    │   ├─> LOGO 图片 → 保存 Flash + 显示
                                    │   └─> 普通图片 → 显示图片 + LED
                                    │
                                    ├─> 检查按键上报
                                    │   └─> 发送按键命令 (KEY_CMD)
                                    │
                                    └─> 检查定时刷新
                                        └─> 发送实时命令 (RT_CMD)

关键数据结构

RS485_POLL_TYPE(轮询状态)

typedef struct {
    volatile uint8_t  RT_count;        // 实时命令计数(0-55,约275ms)
    volatile uint16_t CMD_ACK;          // 命令应答标志(SET=可发送)
    volatile uint8_t  ACK_COUNT_ABLE;   // 应答计时使能
    volatile uint8_t  ACK_count;        // 应答等待计数(0-40,约200ms)
    volatile uint8_t  ACK_OverTime;     // 本次应答超时标志
    volatile uint8_t  ACK_OverTimeCnt;  // 连续超时次数(0-5)
} RS485_POLL_TYPE;

logo_type(LOGO 图片数据)

typedef struct {
    uint8_t  bmpdata[3200];  // BMP 点阵数据
    uint32_t biWidth;        // 图片宽度(像素)
    uint32_t biHeight;       // 图片高度(像素)
} logo_type;

关键时间参数

参数 值 说明
定时器周期 5ms 系统基础时钟周期
实时轮询周期 55×5ms = 275ms 定时发送实时刷新命令
应答超时时间 40×5ms = 200ms 等待应答的最大时间
连续超时上限 5 次 超过则重新初始化 RS485
CRC 错误上限 3 次 连续错误则重新初始化 RS485
LOGO 显示时间 2000ms LOGO 图片显示持续时间

错误处理机制

  1. CRC 校验失败:

    • 累计错误计数 CRC_ERR_COUNT
    • 连续 3 次错误 → 重新初始化 RS485 DMA 接收
  2. 应答超时:

    • 发送命令后启动应答计时(ACK_COUNT_ABLE = SET)
    • 200ms 内未收到应答 → 设置超时标志
    • 连续 5 次超时 → 清除连接标志,重新初始化 RS485
  3. 通信恢复:

    • 重新初始化后,CMD_ACK = SET,允许重新发送命令
    • 系统自动恢复通信流程

🔌 引脚分配

以下引脚定义与 led.c、key.c、rs485.c、160160D.C 中的配置一致。

LED 灯(共 20 个)

所有 LED 均为低电平有效(输出低电平点亮,高电平熄灭)。推挽输出,上拉,高速。

LED ID 宏/用途 端口 引脚 说明
0 LED_POW PC PC8 电源指示灯
1 LED_RUN PC PC9 运行指示灯
2 — PA PA9 通用 LED
3 — PA PA11 通用 LED
4 — PA PA15 通用 LED
5 — PC PC11 通用 LED
6 — PD PD0 通用 LED
7 — PD PD2 通用 LED
8 — PD PD4 通用 LED
9 — PD PD6 通用 LED
10 — PC PC7 通用 LED
11 — PA PA8 通用 LED
12 — PA PA10 通用 LED
13 — PA PA12 通用 LED
14 — PC PC10 通用 LED
15 — PC PC12 通用 LED
16 — PD PD1 通用 LED
17 — PD PD3 通用 LED
18 — PD PD5 通用 LED
19 — PD PD7 通用 LED
  • 代码位置:Drivers/BSP/LED/led.c 中 led_config[] 数组。
  • API:LED_On(id) / LED_Off(id) / LED_Toggle(id);电源灯 LED_POW(x),运行灯 LED_Toggle(LED_RUN)。

按键(共 8 个)

按键为输入,无上拉/下拉(由外部电路决定)。按下为低电平,释放为高电平。

按键类型 端口 引脚 说明
KEY_ENTER PD PD12 确认键
KEY_UP PD PD11 上键
KEY_DOWN PD PD10 下键
KEY_LEFT PD PD13 左键
KEY_RIGHT PD PD8 右键
KEY_ESC PB PB15 取消键
KEY_ADD PB PB3 加键(需禁用 JTAG,见下)
KEY_RESET PD PD15 复位键
  • 代码位置:Drivers/BSP/KEY/key.c 中 key_configs[] 数组。
  • JTAG 说明:使用 PB3 时,Key_Init() 中会调用 __HAL_AFIO_REMAP_SWJ_NOJTAG() 禁用 JTAG、保留 SWD,以释放 PA15、PB3、PB4 作 GPIO。调试请使用 SWD(PA13/PA14)。

RS485 通信

信号 端口 引脚 说明
TX PA PA2 USART2_TX
RX PA PA3 USART2_RX
DE PA PA1 收发使能,低电平发送、高电平接收

LCD 显示(160160D / UC1698U)

信号 端口 引脚 说明
背光 PB PB0 背光控制
RST PB PB10 复位
CS PB PB11 片选
RD PB PB12 读使能
WR PB PB13 写使能
CD PB PB14 命令/数据选择
D0~D7 PE PE8~PE15 8 位并行数据总线

⚙️ 系统配置

时钟配置

  • 系统时钟:72MHz
  • AHB 时钟:72MHz
  • APB1 时钟:36MHz
  • APB2 时钟:72MHz

定时器配置

  • 定时器周期:5ms
  • 中断优先级:组 2,抢占优先级 1,子优先级 0

看门狗配置

  • 类型:独立看门狗(IWDG)
  • 喂狗周期:5ms(在定时器中断中喂狗)

系统保护

  • 自动复位:系统运行时间超过 5000 秒(约 83 分钟)自动复位,防止长时间运行异常

📝 代码规范

本项目遵循以下代码规范:

  • 注释风格:Doxygen 风格注释(@file、@brief、@param、@retval 等)
  • 命名规范:驼峰命名法(函数、变量),宏定义全大写
  • 文件组织:按功能模块划分,驱动层、中间件层、应用层分离

🔍 主要模块说明

LCD 显示模块(160160D)

  • 驱动 UC1698U 控制器
  • 支持 4K 色(RGB444)和 64K 色(RGB565)
  • 提供字符显示、图形显示、LOGO 显示等功能

按键模块(KEY)

  • 基于 MultiButton 库
  • 支持单击、长按、双击等事件
  • 按键事件通过回调函数处理

RS485 通信模块

  • DMA 接收模式(主工作模式)
  • 中断接收模式(错误恢复)
  • 支持不定长帧接收(ReceiveToIdle)

Modbus 模块

  • Modbus RTU 协议实现
  • 支持实时数据上报、按键上报
  • 支持图片数据传输和屏幕刷新

Flash 存储模块

  • BMP 图片数据存储(地址:0x0803F000)
  • 扇区大小:2KB
  • 支持读取和写入操作

🐛 调试说明

调试接口

  • SWD 接口:SWDIO、SWCLK(标准 STM32 SWD 接口)
  • 串口调试:可通过 USART1 进行调试输出(如需要)

常见问题

  1. 编译错误:检查 Pack 是否安装正确,设备型号是否为 STM32F103ZE
  2. 烧录失败:检查调试器连接,确认目标板供电正常
  3. 通信异常:检查 RS485 接线,确认波特率配置正确
  4. 显示异常:检查 LCD 接线,确认背光控制正常

📄 许可证

本项目由阜阳师范大学物电学院开发。

👥 作者

  • 开发单位:阜阳师范大学物电学院
  • 版本:V0.1
  • 日期:2026.1.19

📚 参考资料

🔄 版本历史

  • V0.1(2026.1.19)
    • 初始版本
    • 实现 LCD 显示、按键控制、RS485 Modbus 通信等基础功能
    • 添加看门狗保护和系统自动复位功能

注意:本项目仅供学习和研究使用。使用前请仔细阅读代码注释,确保硬件连接正确。

S
Description
DTU 项目中的 LCD 驱动板程序
Readme 18 MiB
Languages
C 99.5%
Assembly 0.5%