0f5c7f741eefa4737c85b962672021a128507a9d
DTU LCD 项目
基于 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 或更高版本
编译步骤
- 打开
Projects/MDK-ARM/atk_f103.uvprojx工程文件 - 选择目标设备:
STM32F103ZE - 编译工程(F7 或 Project → Build Target)
- 编译成功后,
Output/目录下会生成DTU.hex文件
烧录程序
- 连接 ST-Link 或其他调试器到目标板
- 在 Keil 中点击 Download(F8)或使用外部烧录工具
- 烧录
Output/DTU.hex文件到 MCU
📡 通信协议
Modbus RTU 配置
- 协议:Modbus RTU
- 主站地址:0x01
- 波特率:700000
- 数据位:8
- 停止位:1
- 校验位:无
- 接收模式:DMA ReceiveToIdle(不定长帧接收)
通信功能
- 实时数据上报:定时上报系统状态
- 按键事件上报:按键按下时上报按键值
- 图片传输:接收并显示远程发送的 BMP 图片数据
- 屏幕刷新:支持远程刷新屏幕显示内容
🔄 工作流程
本项目核心功能是按键检测上报和图像接收显示,主要逻辑在 MODBUS.c 文件中实现。
系统整体架构
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ 定时器 │ 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 图片显示持续时间 |
错误处理机制
-
CRC 校验失败:
- 累计错误计数
CRC_ERR_COUNT - 连续 3 次错误 → 重新初始化 RS485 DMA 接收
- 累计错误计数
-
应答超时:
- 发送命令后启动应答计时(
ACK_COUNT_ABLE = SET) - 200ms 内未收到应答 → 设置超时标志
- 连续 5 次超时 → 清除连接标志,重新初始化 RS485
- 发送命令后启动应答计时(
-
通信恢复:
- 重新初始化后,
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 进行调试输出(如需要)
常见问题
- 编译错误:检查 Pack 是否安装正确,设备型号是否为 STM32F103ZE
- 烧录失败:检查调试器连接,确认目标板供电正常
- 通信异常:检查 RS485 接线,确认波特率配置正确
- 显示异常:检查 LCD 接线,确认背光控制正常
📄 许可证
本项目由阜阳师范大学物电学院开发。
👥 作者
- 开发单位:阜阳师范大学物电学院
- 版本:V0.1
- 日期:2026.1.19
📚 参考资料
🔄 版本历史
- V0.1(2026.1.19)
- 初始版本
- 实现 LCD 显示、按键控制、RS485 Modbus 通信等基础功能
- 添加看门狗保护和系统自动复位功能
注意:本项目仅供学习和研究使用。使用前请仔细阅读代码注释,确保硬件连接正确。
Description
Languages
C
99.5%
Assembly
0.5%
