# 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](https://lsky.bitnasdaq.vip/D0gBg8.png)
```
┌─────────────┐         ┌──────────────┐         ┌─────────────┐
│   定时器    │ 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（轮询状态）
```c
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 图片数据）
```c
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

## 📚 参考资料

- [STM32F103 参考手册](https://www.st.com/resource/en/reference_manual/rm0008-stm32f101xx-stm32f102xx-stm32f103xx-stm32f105xx-and-stm32f107xx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf)

## 🔄 版本历史

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

---

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