diff --git a/Core/Src/main.c b/Core/Src/main.c index 80c3e63..77e31aa 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -75,8 +75,8 @@ static void AppTaskStart(void *p_arg) (void)p_arg; /* - * F4作为Modbus RTU从站,触摸屏作为主站。 - * 初始化函数会立即启动USART1的DMA空闲接收。 + * F4作为Modbus RTU从站,触摸屏作为主站 + * 初始化函数会立即启动USART1的DMA空闲接收 */ ModbusSlaveInit(&huart1, MODBUS_SLAVE_DEFAULT_ADDRESS); ModbusRetainedRegistersLoad(); @@ -84,10 +84,6 @@ static void AppTaskStart(void *p_arg) { /* * 维护几个供触摸屏首次联调读取的保持寄存器: - * 4x地址0:器件标识0xF407; - * 4x地址1:系统运行秒数; - * 4x地址2:收到的有效Modbus帧数; - * 4x地址10:触摸屏读写测试值,由协议层保存。 */ (void)ModbusSlaveSetHoldingRegister(//OS时钟 HMI_REG_UPTIME_SECONDS, @@ -111,7 +107,7 @@ static void AppTaskStart(void *p_arg) 6, (uint16_t)ModbusSlaveStatistics.illegalValueCount); -HistorySaveToRegisters(); + HistorySaveToRegisters(); @@ -121,8 +117,7 @@ HistorySaveToRegisters(); /* - * 协议解析和应答在任务上下文完成,中断只接收一帧并设置标志。 - * 每1ms轮询一次,不在串口中断中执行耗时的CRC和寄存器操作。 + * 每1ms轮询一次, */ ModbusSlavePoll(); ModbusRetainedRegistersPoll(); @@ -170,27 +165,12 @@ int main(void) /* USER CODE BEGIN 2 */ - /* uC/OS-II接口通常用返回错误码的方式报告执行结果。 */ INT8U os_err; - /* - * 初始化内核的任务表、就绪表、事件控制块等内部数据。 - * OSInit()只完成初始化,此时任务调度尚未开始。 - */ + OSInit(); - /* - * 创建应用启动任务,各参数依次为: - * 1. 任务入口函数; - * 2. 传给任务的参数(当前不需要,所以为0); - * 3. 初始栈顶地址; - * 4. 任务优先级,数值越小优先级越高; - * 5. 任务ID,本工程暂时与优先级取相同值; - * 6. 栈底地址; - * 7. 栈容量,单位是OS_STK元素而不是字节; - * 8. 用户扩展指针(当前不用); - * 9. 创建选项:允许栈检查并在创建时清零任务栈。 - */ + os_err = OSTaskCreateExt(AppTaskStart, 0, &AppTaskStartStk[APP_TASK_START_STK_SIZE - 1u], @@ -207,10 +187,7 @@ int main(void) Error_Handler(); } - /* - * 启动抢占式调度,内核会运行当前最高优先级的就绪任务。 - * OSStart()成功后不会返回,后面的裸机while循环不会再被执行。 - */ + OSStart(); /* USER CODE END 2 */ diff --git a/Document/my_document/IAR存储空间占用查看方法.md b/Document/my_document/IAR存储空间占用查看方法.md new file mode 100644 index 0000000..2454f4a --- /dev/null +++ b/Document/my_document/IAR存储空间占用查看方法.md @@ -0,0 +1,286 @@ +# IAR 中 Flash、SRAM 和 CCMRAM 占用的查看方法 + +## 1. 从哪里查看 + +IAR 编译、链接完成后,会生成 `.map` 文件。该文件记录了程序代码、常量、全局变量、静态变量、栈以及自定义段的地址和大小。 + +本工程的 Map 文件位于: + +```text +EWARM\Modbus\List\Modbus.map +``` + +如果工程没有生成 Map 文件,可以在 IAR 中依次打开: + +```text +Project → Options → Linker → List +``` + +勾选生成 linker map file 的选项,然后重新执行 `Rebuild All`。 + +查看本工程占用情况时,打开 `Modbus.map`,主要搜索以下三个位置: + +```text +PLACEMENT SUMMARY +Unused ranges +MODULE SUMMARY +``` + +## 2. 本工程的存储区域 + +在 Map 文件的 `PLACEMENT SUMMARY` 中可以看到: + +```text +"P1": place in [from 0x08000000 to 0x080FFFFF] { ro }; + +"P2": place in [from 0x20000000 to 0x2001FFFF] { + rw, block CSTACK, block HEAP }; + +"P3": place in [from 0x10000000 to 0x1000FFFF] { + section .ccmram }; +``` + +各区域含义如下: + +| 区域 | 地址范围 | 容量 | 主要内容 | +|---|---|---:|---| +| P1 | `0x08000000~0x080FFFFF` | 1 MiB | Flash 中的程序代码、常量和变量初始值 | +| P2 | `0x20000000~0x2001FFFF` | 128 KiB | 普通 SRAM 中的全局变量、静态变量和栈 | +| P3 | `0x10000000~0x1000FFFF` | 64 KiB | CCMRAM 中的变量 | + +说明:这里的 `KiB` 按 `1 KiB = 1024 Byte` 计算。 + +## 3. Flash 占用怎么看 + +### 3.1 查看方法 + +在 Map 文件的 `MODULE SUMMARY` 末尾可以看到: + +```text +Grand Total: 33'398 751 163'114 + ro code ro data rw data +``` + +其中: + +- `ro code`:只读程序代码,存放在 Flash; +- `ro data`:只读数据、常量和初始化数据,存放在 Flash; +- `rw data`:运行时可读写数据,主要占用 RAM。 + +本工程的 Flash 占用为: + +```text +Flash占用 = ro code + ro data + = 33,398 + 751 + = 34,149 Byte + ≈ 33.35 KiB +``` + +Flash 总容量为 1 MiB,因此占用率为: + +```text +34,149 ÷ 1,048,576 × 100% +≈ 3.26% +``` + +### 3.2 通过地址验证 + +Map 文件中还可以看到: + +```text +.intvec:0x188 Byte +"P1": 0x83DD Byte +``` + +因此: + +```text +0x188 + 0x83DD = 0x8565 +``` + +即 Flash 已经从 `0x08000000` 使用到 `0x08008564`,下一个空闲地址是 `0x08008565`。 + +`Unused ranges` 中也会显示: + +```text +0x08008565~0x080FFFFF,剩余 0xF7A9B Byte +``` + +## 4. 普通 SRAM 占用怎么看 + +普通 SRAM 对应 `P2`,地址范围是: + +```text +0x20000000~0x2001FFFF +``` + +在 `PLACEMENT SUMMARY` 中,P2 被分为三部分: + +```text +"P2", part 1 of 3: 0xFC +"P2", part 2 of 3: 0x18DD0 +"P2", part 3 of 3: 0x400 +``` + +其中: + +- `part 1`:已经初始化的全局变量和静态变量; +- `part 2`:未初始化或清零的全局变量和静态变量; +- `part 3`:`CSTACK`,即主栈,本工程为 `0x400 = 1024 Byte`。 + +按照链接后占用的地址范围计算: + +```text +普通SRAM占用空间 += 0xFC + 0x18DD0 + 0x400 += 103,116 Byte +≈ 100.70 KiB +``` + +占用率约为: + +```text +103,116 ÷ 131,072 × 100% +≈ 78.67% +``` + +Map 文件的 `Unused ranges` 中显示普通 SRAM 剩余: + +```text +0x20018ECC~0x20018ECF:4 Byte +0x200192D0~0x2001FFFF:0x6D30 Byte +``` + +因此普通 SRAM 总剩余空间为: + +```text +4 + 0x6D30 += 27,956 Byte +≈ 27.30 KiB +``` + +### 4.1 为什么有时会相差几字节 + +`MODULE SUMMARY` 统计的是各变量和数据段本身的大小;`PLACEMENT SUMMARY` 还可能包含为了满足地址对齐而留下的填充空间。 + +本工程普通 SRAM 中的有效读写数据约为 `103,114 Byte`,而链接后实际占用的地址空间为 `103,116 Byte`,两者相差的 `2 Byte` 属于段间对齐产生的空隙。 + +判断变量本身有多大时看 `MODULE SUMMARY`;判断 RAM 是否还能放下新数据时,应结合 `Unused ranges` 查看真正剩余的连续空间。 + +## 5. CCMRAM 占用怎么看 + +CCMRAM 对应 `P3`,地址范围是: + +```text +0x10000000~0x1000FFFF +``` + +Map 文件中显示: + +```text +"P3": 0xEA60 +.ccmram 0x10000000~0x1000EA5F +``` + +因此 CCMRAM 占用为: + +```text +0xEA60 = 60,000 Byte + ≈ 58.59 KiB +``` + +CCMRAM 总容量为 64 KiB,因此占用率为: + +```text +60,000 ÷ 65,536 × 100% +≈ 91.55% +``` + +`Unused ranges` 中显示: + +```text +0x1000EA60~0x1000FFFF:0x15A0 Byte +``` + +所以 CCMRAM 剩余: + +```text +0x15A0 = 5,536 Byte + ≈ 5.41 KiB +``` + +本工程的 CCMRAM 占用已经超过 90%,后续增加 `.ccmram` 数组时需要特别注意。 + +## 6. RAM 合计怎么看 + +RAM 合计应包含: + +```text +RAM合计 = 普通SRAM中的读写数据 + CCMRAM中的读写数据 +``` + +在 `MODULE SUMMARY` 中,本工程的 `Grand Total rw data` 为: + +```text +rw data = 163,114 Byte + ≈ 159.29 KiB +``` + +芯片可用的这两块 RAM 总容量为: + +```text +128 KiB普通SRAM + 64 KiB CCMRAM += 192 KiB += 196,608 Byte +``` + +因此 RAM 合计占用率约为: + +```text +163,114 ÷ 196,608 × 100% +≈ 82.96% +``` + +需要注意,RAM 合计只能说明总体使用量。普通 SRAM 和 CCMRAM 地址不连续,不能因为两者合计还有空间,就认为某个大数组一定能放下。放置新数组前仍要分别检查 P2 和 P3 的剩余连续空间。 + +## 7. 本工程当前占用汇总 + +| 存储区 | 已使用 | 总容量 | 占用率 | 剩余空间 | +|---|---:|---:|---:|---:| +| Flash | 34,149 Byte(33.35 KiB) | 1 MiB | 3.26% | 约 990.65 KiB | +| 普通 SRAM | 约 103,116 Byte(100.70 KiB) | 128 KiB | 78.67% | 约 27.30 KiB | +| CCMRAM | 60,000 Byte(58.59 KiB) | 64 KiB | 91.55% | 约 5.41 KiB | +| RAM 合计 | 163,114 Byte(159.29 KiB) | 192 KiB | 82.96% | 约 32.71 KiB | + +以上数值只对应当前一次编译结果。代码、数组大小、任务栈或链接配置发生变化后,需要重新编译并再次查看 Map 文件。 + +## 8. 如何查某个变量放在哪里 + +在 Map 文件中按 `Ctrl+F` 搜索变量名,可以查看它所属的段、起始地址和大小。 + +可以通过地址判断变量所在区域: + +| 地址开头 | 所在区域 | +|---|---| +| `0x080...` | Flash | +| `0x200...` | 普通 SRAM | +| `0x100...` | CCMRAM | + +例如,本工程的 CCMRAM 数组会显示在: + +```text +.ccmram 0x10000000 ... +``` + +这说明该数组已经被链接到 CCMRAM,而不是普通 SRAM。 + +## 9. 每次编译后的推荐检查步骤 + +1. 执行 `Rebuild All`,确保 Map 文件是最新的。 +2. 搜索 `Grand Total`,查看 `ro code`、`ro data` 和 `rw data`。 +3. 用 `ro code + ro data` 计算 Flash 占用。 +4. 搜索 `"P2"`,查看普通 SRAM 各部分的占用。 +5. 搜索 `"P3"`,查看 CCMRAM 占用。 +6. 搜索 `Unused ranges`,确认各存储区真正剩余的连续空间。 +7. 新增大数组后搜索数组名,确认它被链接到了预期的 RAM 区域。 + diff --git a/Document/my_document/Modbus 0x14收发ADU示例.md b/Document/my_document/Modbus 0x14收发ADU示例.md new file mode 100644 index 0000000..3375f5a --- /dev/null +++ b/Document/my_document/Modbus 0x14收发ADU示例.md @@ -0,0 +1,336 @@ +# Modbus RTU 0x14 收发 ADU 示例 + +## 1. 示例条件 + +本示例使用功能码 `0x14` 读取文件记录,按照当前工程只处理一个子请求的格式编写。 + +```text +从站地址: 1 +引用类型: 0x06 +文件号: 0 +起始记录号: 0 +读取数量: 2个寄存器 +第一个寄存器值: 0x1234 +第二个寄存器值: 0x5678 +``` + +## 2. 请求帧 + +### 2.1 完整请求 ADU + +以下内容可以直接复制到十六进制串口助手发送: + +```text +01 14 07 06 00 00 00 00 00 02 78 E5 +``` + +请求帧长度为: + +```text +12字节 +``` + +### 2.2 请求帧解析 + +| 字节 | 含义 | 说明 | +|---|---|---| +| `01` | 从站地址 | 从站地址为1 | +| `14` | 功能码 | 读取文件记录 | +| `07` | 后续字节数 | 一个子请求固定占7字节 | +| `06` | 引用类型 | Modbus文件记录规定为`0x06` | +| `00 00` | 文件号 | 文件0 | +| `00 00` | 起始记录号 | 从记录0开始 | +| `00 02` | 记录长度 | 读取2个16位寄存器 | +| `78 E5` | CRC16 | CRC低字节`78`在前,高字节`E5`在后 | + +请求帧中参与CRC计算的是: + +```text +01 14 07 06 00 00 00 00 00 02 +``` + +计算得到: + +```text +CRC16 = 0xE578 +``` + +Modbus RTU发送CRC时低字节在前,因此帧尾为: + +```text +78 E5 +``` + +## 3. 正常响应帧 + +假设文件0的记录0和记录1分别保存: + +```text +记录0 = 0x1234 +记录1 = 0x5678 +``` + +### 3.1 完整响应 ADU + +```text +01 14 06 05 06 12 34 56 78 92 FE +``` + +响应帧长度为: + +```text +11字节 +``` + +### 3.2 响应帧解析 + +| 字节 | 含义 | 说明 | +|---|---|---| +| `01` | 从站地址 | 响应来自从站1 | +| `14` | 功能码 | 读取文件记录 | +| `06` |响应数据总字节数 | 后续子响应共6字节 | +| `05` | 子响应长度 | 引用类型1字节加寄存器数据4字节 | +| `06` | 引用类型 | 固定为`0x06` | +| `12 34` | 第一个寄存器 | 值为`0x1234` | +| `56 78` | 第二个寄存器 | 值为`0x5678` | +| `92 FE` | CRC16 | CRC低字节`92`在前,高字节`FE`在后 | + +响应帧中参与CRC计算的是: + +```text +01 14 06 05 06 12 34 56 78 +``` + +计算得到: + +```text +CRC16 = 0xFE92 +``` + +因此响应帧尾发送: + +```text +92 FE +``` + +## 4. 长度计算 + +读取数量为 `quantity` 时,单子请求的请求帧长度固定为: + +```text +12字节 +``` + +正常响应中的字段为: + +```text +子响应长度 = 1 + quantity × 2 +响应数据总字节数 = 2 + quantity × 2 +完整响应ADU长度 = 7 + quantity × 2 +``` + +本例读取2个寄存器: + +```text +子响应长度 = 1 + 2 × 2 = 5字节 +响应数据总字节数 = 2 + 2 × 2 = 6字节 +完整响应ADU长度 = 7 + 2 × 2 = 11字节 +``` + +## 5. 与当前代码的对应关系 + +请求帧中的参数由以下代码解析: + +```c +fileNumber = ModbusGetU16Be(&request[4]); +recordNumber = ModbusGetU16Be(&request[6]); +quantity = ModbusGetU16Be(&request[8]); +``` + +响应帧字段对应: + +```c +ModbusTxFrame[0] = ModbusSlaveAddress; +ModbusTxFrame[1] = 0x14U; +ModbusTxFrame[2] = (uint8_t)(2U + quantity * 2U); +ModbusTxFrame[3] = (uint8_t)(1U + quantity * 2U); +ModbusTxFrame[4] = 0x06U; +``` + +如果后续将文件号改为标准的一基编号,即文件号使用 `1~16`,测试请求中的文件号需要由: + +```text +00 00 +``` + +改为: + +```text +00 01 +``` + +同时重新计算请求帧CRC。 + +## 6. 读取124个寄存器的示例 + +### 6.1 请求条件 + +```text +从站地址: 1 +引用类型: 0x06 +文件号: 0 +起始记录号: 0 +读取数量: 124个寄存器,即0x007C +``` + +### 6.2 完整请求ADU + +以下请求可以直接复制到十六进制串口助手发送: + +```text +01 14 07 06 00 00 00 00 00 7C F8 C5 +``` + +请求帧长度为: + +```text +12字节 +``` + +请求帧解析如下: + +| 字节 | 含义 | +|---|---| +| `01` | 从站地址为1 | +| `14` | 读取文件记录功能码 | +| `07` | 当前子请求包含7个字节 | +| `06` | 引用类型 | +| `00 00` | 文件号0 | +| `00 00` | 从记录0开始读取 | +| `00 7C` | 读取124个寄存器 | +| `F8 C5` | CRC低字节、CRC高字节 | + +参与CRC计算的内容为: + +```text +01 14 07 06 00 00 00 00 00 7C +``` + +计算结果为: + +```text +CRC16 = 0xC5F8 +``` + +因此请求帧末尾按照低字节在前的顺序发送: + +```text +F8 C5 +``` + +### 6.3 正常响应格式 + +读取124个寄存器时,寄存器数据占用: + +```text +124 × 2 = 248字节 = 0xF8字节 +``` + +子响应长度为: + +```text +引用类型1字节 + 寄存器数据248字节 += 249字节 += 0xF9字节 +``` + +响应数据总字节数为: + +```text +子响应长度字段1字节 + 子响应249字节 += 250字节 += 0xFA字节 +``` + +所以正常响应格式为: + +```text +01 14 FA F9 06 [248字节寄存器数据] CRC低 CRC高 +``` + +完整响应ADU长度为: + +```text +从站地址1字节 ++ 功能码1字节 ++ 响应数据总字节数字段1字节 ++ 子响应长度字段1字节 ++ 引用类型1字节 ++ 寄存器数据248字节 ++ CRC 2字节 += 255字节 +``` + +该响应可以放入当前工程的256字节发送缓冲区: + +```c +static uint8_t ModbusTxFrame[256]; +``` + +### 6.4 所有寄存器值均为0时的响应 + +假设读取的124个寄存器全部为 `0x0000`,响应帧的开头为: + +```text +01 14 FA F9 06 +``` + +随后是248个 `00` 数据字节,帧尾CRC为: + +```text +6C 45 +``` + +即: + +```text +01 14 FA F9 06 [248个00] 6C 45 +``` + +该响应的CRC计算值为: + +```text +CRC16 = 0x456C +``` + +Modbus RTU按照低字节在前发送,因此CRC字段为: + +```text +6C 45 +``` + +### 6.5 对应的代码限制 + +发送缓冲区长度为256字节时,单个0x14子请求最多读取124个寄存器,因此数量检查应为: + +```c +if ((quantity == 0U) || (quantity > 124U)) +{ + ModbusSlaveStatistics.illegalValueCount++; + return ModbusBuildException( + request[1], + MODBUS_EX_ILLEGAL_VALUE); +} +``` + +如果每个 `MODBUS_HISTORY_ITEM` 为520字节,即260个16位寄存器,还必须保证本次读取不超过当前文件: + +```c +if (((uint32_t)recordNumber + quantity) > 260UL) +{ + ModbusSlaveStatistics.illegalAddressCount++; + return ModbusBuildException( + request[1], + MODBUS_EX_ILLEGAL_ADDRESS); +} +``` diff --git a/Document/my_document/modbus学习文档.md b/Document/my_document/modbus学习文档.md index ec2e7d1..892884b 100644 --- a/Document/my_document/modbus学习文档.md +++ b/Document/my_document/modbus学习文档.md @@ -544,8 +544,6 @@ FD CA CRC | 0x01 | 非法功能 | 从站不支持该功能码 | | 0x02 | 非法数据地址 | 请求地址不存在或访问范围越界 | | 0x03 | 非法数据值 | 数量、字节数或数据值不合法 | -| 0x04 | 从站设备故障 | 从站执行请求时发生内部错误 | -| 0x06 | 从站设备忙 | 从站暂时不能处理请求 | 例如,从站 1 对 0x03 请求返回非法数据地址: diff --git a/Document/my_document/uCOS-II移植说明.md b/Document/my_document/uCOS-II移植说明.md deleted file mode 100644 index c1e7eb5..0000000 --- a/Document/my_document/uCOS-II移植说明.md +++ /dev/null @@ -1,935 +0,0 @@ -# STM32F407 + IAR 工程 uC/OS-II 移植说明 - -## 1. 文档目的 - -本文记录本项目将 uC/OS-II 移植到 STM32F407IG + IAR 工程的完整过程,并结合当前已经能够正常调试、运行串口与 Modbus RTU 从站的实际代码进行说明。 - -本文不是通用示例,文中目录、文件名、任务优先级、时基和接口均以当前工程为准,主要用于: - -- 回顾本次 uC/OS-II 的移植步骤; -- 理解每个移植文件的作用; -- 在 CubeMX 重新生成代码后检查移植内容是否仍然完整; -- 后续增加任务、信号量、消息队列等功能; -- 排查任务不运行、延时不生效、无法切换任务等问题。 - -当前工程已经完成以下验证: - -- uC/OS-II 可以初始化并启动; -- 应用任务 `AppTaskStart` 可以运行; -- `OSTimeDly()` 和 `OSTimeGet()` 可以正常使用; -- HAL 和 uC/OS-II 共用 SysTick 时基; -- USART1 + DMA 可以正常通信; -- Modbus RTU 从站可以在任务中轮询处理请求。 - ---- - -## 2. 当前工程环境 - -| 项目 | 当前配置 | -|---|---| -| MCU | STM32F407IG | -| CPU 内核 | ARM Cortex-M4 | -| 工程生成工具 | STM32CubeMX | -| 编译器 | IAR Embedded Workbench for ARM | -| IAR 工程配置名 | `Modbus` | -| uC/OS-II 版本 | V2.92.11 | -| 操作系统节拍 | 1000 Hz,即 1 ms/节拍 | -| 串口 | USART1 | -| 串口参数 | 115200 bit/s、8 个有效数据位、偶校验、1 个停止位(8E1) | -| 串口接收 | DMA2 Stream2 Channel4 | -| 串口发送 | DMA2 Stream7 Channel4 | -| 应用任务 | `AppTaskStart` | -| 应用任务优先级 | 5 | -| 应用任务栈 | 256 个 `OS_STK` 单元 | - -IAR 命令行编译工具位于: - -```text -F:\IAR Systems\Embedded Workbench 8.3\common\bin -``` - ---- - -## 3. 移植工作的本质 - -将 uC/OS-II 移植到一个新的 MCU 工程,不只是把源码复制进来。完整移植至少包含以下五部分: - -1. **内核源码**:任务调度、延时、信号量、消息队列等通用功能; -2. **CPU 移植层**:任务栈初始化、临界区、寄存器保存和恢复; -3. **工程配置**:选择启用哪些内核功能、允许多少任务、系统节拍频率等; -4. **硬件连接**:将 SysTick 和 PendSV 正确接入操作系统; -5. **应用启动**:初始化内核、创建任务并启动调度器。 - -它们的关系如下: - -```text -应用任务 AppTaskStart - │ - │ 调用 OSTimeDly、OSTimeGet 等 API - ▼ -uC/OS-II 内核源码 - │ - │ 请求任务切换、初始化任务栈 - ▼ -Cortex-M4 + IAR 移植层 - │ - ├── SysTick:产生操作系统节拍 - └── PendSV:保存旧任务现场并恢复新任务现场 -``` - ---- - -## 4. 工程中的 uC/OS-II 目录 - -当前相关文件位于: - -```text -Middlewares/Third_Party/Micrium/ -├─ Config/ -│ ├─ app_cfg.h -│ ├─ cpu_cfg.h -│ └─ os_cfg.h -├─ uCOS-II/ -│ ├─ Source/ -│ │ ├─ ucos_ii.h -│ │ ├─ os_core.c -│ │ ├─ os_task.c -│ │ ├─ os_time.c -│ │ ├─ os_sem.c -│ │ ├─ os_mutex.c -│ │ ├─ os_q.c -│ │ ├─ os_mbox.c -│ │ ├─ os_flag.c -│ │ ├─ os_mem.c -│ │ └─ os_tmr.c -│ └─ Ports/ARM-Cortex-M4/IAR/ -│ ├─ os_cpu.h -│ ├─ os_cpu_a.asm -│ ├─ os_cpu_c.c -│ └─ os_dbg.c -├─ uC-CPU/ -└─ uC-LIB/ -``` - -### 4.1 各类文件的作用 - -| 文件或目录 | 作用 | -|---|---| -| `ucos_ii.h` | uC/OS-II 总头文件,应用程序通常只需包含它 | -| `os_core.c` | 内核初始化、调度和中断嵌套管理 | -| `os_task.c` | 任务创建、删除、挂起、恢复和栈检查 | -| `os_time.c` | 任务延时及系统节拍计数 | -| `os_sem.c` | 信号量 | -| `os_mutex.c` | 互斥信号量 | -| `os_q.c` | 消息队列 | -| `os_mbox.c` | 邮箱 | -| `os_flag.c` | 事件标志组 | -| `os_mem.c` | 固定大小内存分区 | -| `os_tmr.c` | 软件定时器 | -| `os_cpu.h` | Cortex-M4/IAR 的数据类型、临界区和任务切换宏 | -| `os_cpu_c.c` | 初始任务栈构造、CPU 钩子及 SysTick 接口 | -| `os_cpu_a.asm` | 上下文切换和寄存器保存/恢复的汇编实现 | -| `os_dbg.c` | 向调试器提供内核配置及数据尺寸信息 | -| `os_cfg.h` | 内核功能裁剪和资源数量配置 | -| `app_cfg.h` | 应用任务优先级和栈大小配置 | - -`uC-CPU` 和 `uC-LIB` 目录还提供 Micrium 的 CPU 抽象和通用库头文件。本工程已将相应目录加入头文件搜索路径。 - ---- - -## 5. IAR 工程配置 - -### 5.1 添加源码分组 - -在 IAR 工程中建立 `Micrium` 分组,并添加下列内容。 - -`Micrium Config`: - -```text -app_cfg.h -cpu_cfg.h -os_cfg.h -``` - -`uCOS-II Port`: - -```text -os_cpu_a.asm -os_cpu_c.c -os_dbg.c -``` - -`uCOS-II Source`: - -```text -os_core.c -os_flag.c -os_mbox.c -os_mem.c -os_mutex.c -os_q.c -os_sem.c -os_task.c -os_time.c -os_tmr.c -``` - -即使某项功能在 `os_cfg.h` 中被关闭,对应 `.c` 文件留在工程中通常也不会产生完整功能代码,因为源码内部会根据配置宏进行条件编译。 - -### 5.2 添加头文件搜索路径 - -在 IAR 中进入: - -```text -Project → Options → C/C++ Compiler → Preprocessor -``` - -将以下路径加入 `Additional include directories`: - -```text -$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\Config -$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uCOS-II\Source -$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uCOS-II\Ports\ARM-Cortex-M4\IAR -$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uC-CPU -$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uC-CPU\ARM-Cortex-M4\IAR -$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uC-LIB -``` - -如果编译提示找不到 `ucos_ii.h`、`os_cpu.h`、`cpu.h` 或 `lib_def.h`,应首先检查这些搜索路径。 - ---- - -## 6. 内核和应用配置 - -### 6.1 `app_cfg.h` - -当前应用任务配置为: - -```c -#define APP_TASK_START_PRIO 5u -#define APP_TASK_TEST_PRIO 6u - -#define APP_TASK_START_STK_SIZE 256u -#define APP_TASK_TEST_STK_SIZE 256u -``` - -其中: - -- `APP_TASK_START_PRIO` 是启动任务优先级; -- `APP_TASK_TEST_PRIO` 是预留的测试任务优先级,当前还未创建该任务; -- `APP_TASK_START_STK_SIZE` 是启动任务栈的元素个数,不是字节数; -- Cortex-M4 移植中 `OS_STK` 为 32 位,因此 256 个栈单元约占 1024 字节 RAM。 - -### 6.2 uC/OS-II 的任务优先级规则 - -uC/OS-II V2 中: - -- 数值越小,任务优先级越高; -- 每个任务必须使用不同的优先级; -- 当前 `AppTaskStart` 使用优先级 5; -- 后续可让普通测试任务使用 6、7 等更低优先级; -- 不建议随意占用 0~4,可为以后对实时性要求更高的任务预留。 - -任务优先级 5 和 NVIC 中断优先级 5 是两个不同体系,不能直接比较大小。 - -### 6.3 `os_cfg.h` 的当前关键配置 - -| 配置项 | 当前值 | 含义 | -|---|---:|---| -| `OS_LOWEST_PRIO` | 63 | 应用可用的最低任务优先级范围 | -| `OS_MAX_TASKS` | 10 | 最大应用任务数量 | -| `OS_TICKS_PER_SEC` | 1000 | 每秒 1000 个系统节拍 | -| `OS_TASK_CREATE_EXT_EN` | 1 | 启用 `OSTaskCreateExt()` | -| `OS_TASK_STAT_EN` | 0 | 不启用统计任务 | -| `OS_MUTEX_EN` | 1 | 启用互斥信号量 | -| `OS_Q_EN` | 1 | 启用消息队列 | -| `OS_SEM_EN` | 1 | 启用信号量 | -| `OS_TIME_GET_SET_EN` | 1 | 允许读取和设置系统时间 | -| `OS_TIME_TICK_HOOK_EN` | 1 | 启用节拍钩子 | -| `OS_TMR_EN` | 0 | 不启用软件定时器 | -| `OS_MEM_EN` | 0 | 不启用固定内存分区 | -| `OS_CPU_HOOKS_EN` | 1 | 使用 CPU 移植层钩子 | -| `OS_DEBUG_EN` | 1 | 保留内核调试信息 | - -因为: - -```c -#define OS_TICKS_PER_SEC 1000u -``` - -所以: - -```c -OSTimeDly(1U); /* 延时约 1 ms */ -OSTimeDly(100U); /* 延时约 100 ms */ -OSTimeDly(1000U); /* 延时约 1 s */ -``` - -`OSTimeDly()` 的参数单位是“节拍”,只有在当前 1000 Hz 配置下才刚好等于毫秒。 - ---- - -## 7. SysTick 时基接入 - -### 7.1 为什么 HAL 和 uC/OS-II 都需要 SysTick - -STM32 HAL 使用 SysTick 维护自己的毫秒计数,`HAL_GetTick()`、HAL 超时判断等功能依赖 `HAL_IncTick()`。 - -uC/OS-II 也需要周期节拍,用于: - -- 更新任务延时; -- 唤醒延时到期的任务; -- 更新 `OSTimeGet()` 返回的系统节拍; -- 在需要时触发任务调度。 - -因此,本工程在同一个 `SysTick_Handler()` 中同时服务 HAL 和 uC/OS-II。 - -### 7.2 当前 SysTick 处理 - -`Core/Src/stm32f4xx_it.c` 中的核心代码为: - -```c -void SysTick_Handler(void) -{ - HAL_IncTick(); - - if (OSRunning == OS_TRUE) - { - OS_CPU_SysTickHandler(); - } -} -``` - -必须保留 `OSRunning` 判断。原因是 `HAL_Init()` 会先启动 SysTick,而此时 `OSInit()` 尚未执行。如果在内核未初始化时直接调用 uC/OS-II 的节拍处理,可能访问尚未准备好的内核状态。 - -`OS_CPU_SysTickHandler()` 内部的主要过程是: - -```text -进入 SysTick 中断 - ↓ -OSIntEnter() - ↓ -OSTimeTick():更新延时和系统节拍 - ↓ -OSIntExit():检查是否需要调度更高优先级任务 -``` - -不能只调用 `OS_CPU_SysTickHandler()` 而删除 `HAL_IncTick()`,否则 HAL 的毫秒时基和超时机制会停止。 - ---- - -## 8. PendSV 与任务上下文切换 - -### 8.1 PendSV 的作用 - -SysTick 负责告诉内核“时间过去了一个节拍”,PendSV 才真正执行任务上下文切换。 - -一次任务切换大致包括: - -1. 保存当前任务的 CPU 寄存器; -2. 将当前栈指针保存到任务控制块; -3. 选择当前最高优先级的就绪任务; -4. 取出新任务的栈指针; -5. 恢复新任务的 CPU 寄存器; -6. 从异常返回,新任务继续运行。 - -这些底层工作由 `os_cpu_a.asm` 中的 `OS_CPU_PendSVHandler` 完成。 - -### 8.2 当前工程的特殊接法 - -当前工程不是在 C 语言的空 `PendSV_Handler()` 中调用内核,而是直接修改了 IAR 启动文件: - -```text -EWARM/startup_stm32f407xx.s -``` - -启动文件中声明: - -```asm -EXTERN OS_CPU_PendSVHandler -``` - -并将向量表中的 PendSV 项直接设置为: - -```asm -DCD OS_CPU_PendSVHandler -``` - -所以 `Core/Src/stm32f4xx_it.c` 中虽然还有一个空的 `PendSV_Handler()`,实际 PendSV 向量并不指向它。 - -这是本工程移植中非常关键的一步。若 CubeMX 或其他工具重新生成、替换启动文件,必须确认 PendSV 向量仍然指向 `OS_CPU_PendSVHandler`。否则常见现象是: - -- 第一个任务似乎能够启动; -- 一旦调用 `OSTimeDly()` 或发生任务切换就卡住; -- 多任务无法正常调度。 - -不要同时让向量表指向两个 PendSV 实现,也不要在已有直接向量映射的情况下重复定义另一个同名强符号。 - ---- - -## 9. 在 `main.c` 中启动 uC/OS-II - -### 9.1 包含头文件 - -当前应用包含: - -```c -#include "ucos_ii.h" -#include "modbus_rtu_slave.h" -``` - -`ucos_ii.h` 内部会继续包含 `app_cfg.h`,因此 `main.c` 可以使用任务优先级和栈大小宏。 - -### 9.2 定义任务栈 - -```c -static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; -``` - -任务栈不能定义成任务函数内的普通局部数组,否则函数退出或栈被覆盖后会破坏任务运行现场。这里使用 `static`,使内存在整个程序运行期间一直存在。 - -### 9.3 声明任务函数 - -```c -static void AppTaskStart(void *p_arg); -``` - -uC/OS-II 任务函数必须符合以下形式: - -```c -void TaskName(void *p_arg); -``` - -任务通常包含永久循环,不能执行完后直接返回。 - -### 9.4 正确的初始化顺序 - -本工程顺序为: - -```c -HAL_Init(); -SystemClock_Config(); - -MX_GPIO_Init(); -MX_DMA_Init(); -MX_USB_DEVICE_Init(); -MX_USART1_UART_Init(); - -OSInit(); - -/* 创建应用任务 */ -OSTaskCreateExt(...); - -OSStart(); -``` - -各步骤含义如下: - -1. `HAL_Init()`:初始化 HAL 和 SysTick; -2. `SystemClock_Config()`:配置芯片主频和总线时钟; -3. `MX_xxx_Init()`:初始化任务要使用的外设; -4. `OSInit()`:初始化 uC/OS-II 内核数据结构; -5. `OSTaskCreateExt()`:创建至少一个应用任务; -6. `OSStart()`:启动最高优先级就绪任务。 - -正常情况下 `OSStart()` 不会返回。CubeMX 在后面生成的 `while (1)` 仅作为程序结构保留,操作系统成功启动后不会再执行到那里。 - -### 9.5 当前任务创建代码 - -```c -os_err = OSTaskCreateExt(AppTaskStart, - 0, - &AppTaskStartStk[APP_TASK_START_STK_SIZE - 1u], - APP_TASK_START_PRIO, - APP_TASK_START_PRIO, - &AppTaskStartStk[0], - APP_TASK_START_STK_SIZE, - 0, - (OS_TASK_OPT_STK_CHK | - OS_TASK_OPT_STK_CLR)); - -if (os_err != OS_ERR_NONE) -{ - Error_Handler(); -} -``` - -参数说明: - -| 参数 | 当前传入值 | 作用 | -|---|---|---| -| 任务入口 | `AppTaskStart` | 任务开始执行的函数 | -| 任务参数 | `0` | 传给 `p_arg`,本任务不需要参数 | -| 初始栈顶 | 栈数组最后一个元素 | Cortex-M 栈向低地址增长 | -| 优先级 | `APP_TASK_START_PRIO` | 当前为 5 | -| 任务 ID | 同任务优先级 | 本工程用优先级作为 ID | -| 栈底 | 栈数组第一个元素 | 用于扩展创建和栈检查 | -| 栈大小 | `APP_TASK_START_STK_SIZE` | 256 个栈单元 | -| 扩展数据 | `0` | 当前不使用 | -| 任务选项 | `STK_CHK \| STK_CLR` | 启用栈检查并在创建时清零栈 | - -检查 `os_err` 非常重要。如果任务创建失败却继续调用 `OSStart()`,后续现象不容易定位。 - ---- - -## 10. 当前应用任务与 Modbus 的结合 - -当前启动任务主要代码如下: - -```c -static void AppTaskStart(void *p_arg) -{ - (void)p_arg; - - if (ModbusSlave_Init(&huart1, - MODBUS_SLAVE_DEFAULT_ADDRESS) != HAL_OK) - { - Error_Handler(); - } - - while (1) - { - (void)ModbusSlave_SetHoldingRegister( - HMI_REG_UPTIME_SECONDS, - (uint16_t)(OSTimeGet() / OS_TICKS_PER_SEC)); - - (void)ModbusSlave_SetHoldingRegister( - HMI_REG_RX_FRAME_COUNT, - (uint16_t)g_modbus_slave_stats.valid_frame_count); - - ModbusSlave_Poll(); - OSTimeDly(1U); - } -} -``` - -### 10.1 任务在做什么 - -1. 任务只在开始时调用一次 `ModbusSlave_Init()`; -2. 初始化 USART1 的 Modbus 从站和 DMA 接收; -3. 在永久循环中更新运行时间寄存器; -4. 更新有效接收帧计数寄存器; -5. 调用 `ModbusSlave_Poll()` 在任务上下文中解析已收到的帧; -6. 延时 1 个节拍,把 CPU 让给其他任务或空闲任务。 - -### 10.2 为什么要调用 `OSTimeDly(1U)` - -如果永久循环内没有延时、等待信号量或其他阻塞操作,优先级 5 的任务会一直占用 CPU,使更低优先级任务无法运行。 - -调用 `OSTimeDly(1U)` 后: - -- 当前任务进入延时状态; -- 调度器转而执行其他就绪任务; -- 1 ms 后 SysTick 更新延时计数; -- 当前任务重新进入就绪状态; -- 当它成为最高优先级就绪任务时继续运行。 - ---- - -## 11. 串口中断、DMA 与操作系统任务的关系 - -当前 Modbus 接收采用“中断/DMA 快速接收,任务中解析”的结构: - -```text -USART1 收到数据 - ↓ -DMA 自动搬运到接收缓冲区 - ↓ -串口空闲事件/接收事件中断 - ↓ -HAL_UARTEx_RxEventCallback() - ↓ -ModbusSlave_OnRxEvent() - │ - │ 仅记录数据、帧状态并尽快退出 - ▼ -AppTaskStart 周期执行 ModbusSlave_Poll() - ↓ -CRC 校验、功能码解析、寄存器读写、组织应答 - ↓ -USART1 DMA 发送应答 -``` - -当前回调接口包括: - -```c -HAL_UARTEx_RxEventCallback() - → ModbusSlave_OnRxEvent() - -HAL_UART_TxCpltCallback() - → ModbusSlave_OnTxComplete() - -HAL_UART_ErrorCallback() - → ModbusSlave_OnUartError() -``` - -这种结构的优点是: - -- 中断处理时间短; -- CRC 和协议解析不会长时间占用中断; -- 复杂业务逻辑运行在任务上下文,便于扩展; -- 串口接收与协议处理在执行过程上是异步的。 - -当前串口回调没有直接调用 uC/OS-II 的信号量、队列等服务,因此不需要额外在这些回调外层手工调用 `OSIntEnter()` 和 `OSIntExit()`。 - -如果后续在外设中断中直接调用 `OSSemPost()`、`OSQPost()` 等内核服务,则必须: - -- 确认该服务允许在中断中调用; -- 正确执行 uC/OS-II 的中断进入和退出处理; -- 检查 NVIC 中断优先级是否满足移植层要求; -- 不在中断中调用会阻塞的接口。 - ---- - -## 12. 常用 uC/OS-II 接口解释 - -### 12.1 `OSInit()` - -初始化内核全局变量、就绪表、任务链表,并建立空闲任务等内部对象。必须先调用它,再创建应用任务和启动调度器。 - -### 12.2 `OSTaskCreateExt()` - -创建一个扩展任务。与简化版创建函数相比,它可以传入栈底、栈大小和任务选项,因此可使用栈检查功能。 - -### 12.3 `OSStart()` - -启动调度器,并切换到当前最高优先级的就绪任务。成功后不会返回 `main()` 的普通执行流程。 - -### 12.4 `OSTimeDly()` - -使当前任务延时指定节拍。它是操作系统延时,不是原地空转: - -- 延时期间 CPU 可以执行其他任务; -- 不能在中断服务程序中调用; -- 参数为 0 时通常不会产生期望的延时。 - -### 12.5 `OSTimeGet()` - -读取内核从启动后累计的节拍数。当前节拍频率为 1000 Hz,因此: - -```c -seconds = OSTimeGet() / OS_TICKS_PER_SEC; -``` - -可得到近似运行秒数。计数最终会溢出并回绕,不能把它当作永久不溢出的实时时钟。 - -### 12.6 `OSTaskStkChk()` - -在任务使用了 `OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR` 时,可以检查任务栈使用情况,用于判断栈是否配置过大或过小。 - ---- - -## 13. 后续增加第二个任务 - -### 13.1 定义栈和任务函数 - -```c -static OS_STK AppTaskTestStk[APP_TASK_TEST_STK_SIZE]; - -static void AppTaskTest(void *p_arg) -{ - (void)p_arg; - - while (1) - { - /* 在这里编写周期性测试功能。 */ - - OSTimeDly(100U); - } -} -``` - -### 13.2 创建任务 - -在调用 `OSStart()` 之前增加: - -```c -os_err = OSTaskCreateExt(AppTaskTest, - 0, - &AppTaskTestStk[APP_TASK_TEST_STK_SIZE - 1u], - APP_TASK_TEST_PRIO, - APP_TASK_TEST_PRIO, - &AppTaskTestStk[0], - APP_TASK_TEST_STK_SIZE, - 0, - (OS_TASK_OPT_STK_CHK | - OS_TASK_OPT_STK_CLR)); - -if (os_err != OS_ERR_NONE) -{ - Error_Handler(); -} -``` - -也可以先只创建启动任务,再由启动任务创建其他任务。但无论采用哪种方式,都必须保证: - -- 每个任务有独立且长期有效的栈; -- 每个任务优先级唯一; -- 任务函数不能直接返回; -- 周期任务必须适当延时或等待事件; -- 总任务数不得超过 `OS_MAX_TASKS`。 - ---- - -## 14. CubeMX 重新生成代码时的注意事项 - -### 14.1 能够自动保留的内容 - -CubeMX 的 `Keep User Code when re-generating` 选项只能保留位于标准标记内的代码: - -```c -/* USER CODE BEGIN ... */ -/* USER CODE END ... */ -``` - -本工程下列自定义内容应放在对应的 USER CODE 区域内: - -- `main.c` 中的 uC/OS-II 和 Modbus 头文件; -- 任务栈、任务函数声明; -- `AppTaskStart()`; -- `OSInit()`、任务创建和 `OSStart()`; -- `stm32f4xx_it.c` 中的 `OS_CPU_SysTickHandler()` 调用; -- HAL 串口回调中的 Modbus 接口调用。 - -写在 USER CODE 标记之外的修改可能被覆盖。 - -### 14.2 需要手工重点检查的内容 - -每次 CubeMX 重新生成后,至少检查: - -- `EWARM/startup_stm32f407xx.s` 的 PendSV 向量是否仍指向 `OS_CPU_PendSVHandler`; -- `stm32f4xx_it.c` 是否仍调用 `HAL_IncTick()`; -- `stm32f4xx_it.c` 是否在 `OSRunning` 为真时调用 `OS_CPU_SysTickHandler()`; -- `main.c` 是否仍然调用 `OSInit()`、创建任务并调用 `OSStart()`; -- IAR 工程中的 Micrium 源文件分组是否仍存在; -- IAR 的 Micrium 头文件搜索路径是否仍存在; -- 串口 DMA、NVIC 和回调代码是否仍完整。 - -CubeMX 通常不会主动删除独立放置的 `Middlewares/Third_Party/Micrium` 源文件,但重新生成 IAR 工程文件时,有可能改变 `.ewp` 中手工添加的分组或路径,因此重新生成前应提交 Git 或备份工程。 - ---- - -## 15. 编译和运行验证 - -### 15.1 IAR 图形界面 - -在 IAR 中执行: - -```text -Project → Rebuild All -``` - -确认: - -- 0 errors; -- 0 warnings; -- `os_cpu_a.asm`、`os_cpu_c.c` 和内核源码均参与编译; -- 链接阶段没有重复的中断处理函数。 - -### 15.2 命令行完整编译 - -在工程根目录执行: - -```powershell -& 'F:\IAR Systems\Embedded Workbench 8.3\common\bin\IarBuild.exe' ` - 'EWARM\Modbus.ewp' ` - -build Modbus ` - -log all -``` - -链接映射文件位于: - -```text -EWARM\Modbus\List\Modbus.map -``` - -该文件可以查看代码、只读数据和读写数据的详细占用。 - -### 15.3 调试时可观察的内容 - -建议设置或观察: - -- 在 `AppTaskStart()` 入口设置断点,确认任务已进入; -- `OSRunning` 应在内核启动后变为真; -- `OSTimeGet()` 应持续增加; -- 执行 `OSTimeDly(1U)` 后任务应能再次被唤醒; -- Modbus 运行时间寄存器应每秒变化; -- 串口请求应能收到正常应答。 - ---- - -## 16. 常见故障排查 - -### 16.1 提示找不到 `ucos_ii.h` - -原因通常是 IAR 头文件搜索路径未添加或路径层级错误。检查: - -```text -Middlewares\Third_Party\Micrium\uCOS-II\Source -``` - -### 16.2 `OSStart()` 后卡死或任务不运行 - -依次检查: - -1. 是否先调用了 `OSInit()`; -2. `OSTaskCreateExt()` 返回值是否为 `OS_ERR_NONE`; -3. 任务栈顶是否传入数组最后一个元素; -4. `os_cpu_a.asm` 是否参与编译和链接; -5. 启动文件 PendSV 向量是否指向 `OS_CPU_PendSVHandler`; -6. 任务函数是否包含永久循环。 - -### 16.3 `OSTimeDly()` 后任务再也不运行 - -重点检查: - -- SysTick 中断是否正常进入; -- 是否保留 `OS_CPU_SysTickHandler()`; -- `OSRunning` 是否已经变为真; -- `OS_TICKS_PER_SEC` 是否与实际 SysTick 频率匹配; -- PendSV 向量是否被 CubeMX 恢复成空的 `PendSV_Handler()`。 - -### 16.4 HAL 延时或超时异常 - -检查 `SysTick_Handler()` 中是否仍调用: - -```c -HAL_IncTick(); -``` - -uC/OS-II 和 HAL 共用 SysTick 时,两边的节拍处理都要保留。 - -### 16.5 链接时出现重复的 PendSV 或 SysTick 符号 - -说明同一异常被多个强符号实现。应明确采用一种连接方式。 - -当前工程采用: - -- SysTick 向量指向 `SysTick_Handler()`,其中同时服务 HAL 和 uC/OS-II; -- PendSV 向量直接指向汇编函数 `OS_CPU_PendSVHandler`。 - -### 16.6 任务运行一段时间后进入 HardFault - -常见原因包括: - -- 任务栈过小; -- 使用了错误的 CPU/编译器移植层; -- 栈顶和栈底参数传反; -- 局部数组过大; -- 中断或任务越界写内存; -- 浮点上下文或编译选项与移植层不匹配。 - -应先增加任务栈并使用栈检查,再检查 HardFault 时的栈和寄存器。 - -### 16.7 高优先级任务导致其他任务不运行 - -uC/OS-II 是基于优先级的抢占式内核。若高优先级任务永久循环且从不延时、不挂起、也不等待事件,低优先级任务不会得到运行机会。 - -周期任务中应使用: - -- `OSTimeDly()`; -- 等待信号量; -- 等待消息队列; -- 等待事件标志; -- 或其他会让任务进入非就绪状态的接口。 - -### 16.8 修改了配置宏后出现未定义符号 - -例如打开信号量、队列、统计任务或软件定时器后,应同时确认: - -- 对应源码已加入工程; -- 相关最大数量配置不为 0; -- 所需钩子函数已经实现; -- RAM 和任务数量足够。 - ---- - -## 17. 当前移植的关键文件清单 - -| 文件 | 当前项目中的关键内容 | -|---|---| -| `Core/Src/main.c` | 内核初始化、任务创建、启动调度器、Modbus 应用任务 | -| `Core/Src/stm32f4xx_it.c` | HAL 与 uC/OS-II 共用的 SysTick 处理 | -| `EWARM/startup_stm32f407xx.s` | PendSV 向量直接指向 `OS_CPU_PendSVHandler` | -| `EWARM/Modbus.ewp` | Micrium 源文件分组及头文件搜索路径 | -| `Middlewares/Third_Party/Micrium/Config/app_cfg.h` | 应用任务优先级和栈大小 | -| `Middlewares/Third_Party/Micrium/Config/os_cfg.h` | 内核裁剪、节拍频率和资源数量 | -| `Middlewares/Third_Party/Micrium/uCOS-II/Ports/ARM-Cortex-M4/IAR/os_cpu_a.asm` | 上下文切换 | -| `Middlewares/Third_Party/Micrium/uCOS-II/Ports/ARM-Cortex-M4/IAR/os_cpu_c.c` | 任务栈初始化及 SysTick 接口 | -| `Modbus/Src/modbus_rtu_slave.c` | Modbus RTU 从站协议处理 | - ---- - -## 18. 移植完成检查表 - -### 源码与工程 - -- [ ] uC/OS-II 内核源码已加入 IAR 工程; -- [ ] Cortex-M4/IAR 移植层已加入工程; -- [ ] Micrium 配置文件已加入工程; -- [ ] 所有头文件搜索路径正确; -- [ ] `os_cpu_a.asm` 确实参与编译。 - -### 内核配置 - -- [ ] `OS_TICKS_PER_SEC` 与 SysTick 实际频率一致; -- [ ] `OS_MAX_TASKS` 足够; -- [ ] 每个任务优先级唯一; -- [ ] 所需信号量、队列、互斥量功能已启用; -- [ ] 每个任务栈大小合理。 - -### 中断连接 - -- [ ] SysTick 同时调用 `HAL_IncTick()` 和 `OS_CPU_SysTickHandler()`; -- [ ] 内核未运行时不会调用 OS 节拍处理; -- [ ] PendSV 向量指向 `OS_CPU_PendSVHandler`; -- [ ] 没有重复的 PendSV 强符号。 - -### 应用启动 - -- [ ] 外设初始化先于使用外设的任务; -- [ ] 已调用 `OSInit()`; -- [ ] 至少成功创建一个任务; -- [ ] 已检查任务创建返回值; -- [ ] 已调用 `OSStart()`; -- [ ] 所有任务均包含循环并能主动让出 CPU。 - -### 运行验证 - -- [ ] 工程编译为 0 errors、0 warnings; -- [ ] `AppTaskStart()` 可以进入; -- [ ] `OSRunning` 为真; -- [ ] 系统节拍持续增加; -- [ ] `OSTimeDly()` 后任务可以恢复; -- [ ] USART1 DMA 通信正常; -- [ ] Modbus 请求和应答正常。 - ---- - -## 19. 关于 uC/OS-II 授权 - -当前源码头部的授权说明允许评估、教学或研究等用途。若代码用于正式商业产品,应根据所使用版本核实 Micrium/版权方的商业授权要求,不能仅因为源码可以编译就默认具备商业发布许可。 - ---- - -## 20. 总结 - -本工程的 uC/OS-II 移植可以概括为: - -```text -复制内核和 Cortex-M4/IAR 移植层 - ↓ -加入 IAR 源文件与头文件路径 - ↓ -配置 os_cfg.h 和 app_cfg.h - ↓ -SysTick 同时服务 HAL 与 uC/OS-II - ↓ -启动文件 PendSV 向量接入 OS_CPU_PendSVHandler - ↓ -OSInit → 创建任务 → OSStart - ↓ -在任务中运行 ModbusSlave_Poll 和应用逻辑 -``` - -其中最容易在 CubeMX 重新生成后遗漏的是 **IAR 启动文件中的 PendSV 向量修改**;最容易在增加任务后遇到的问题是 **任务优先级重复、任务栈不足,以及高优先级任务没有延时或等待事件**。 diff --git a/Document/my_document/测试大纲.docx b/Document/my_document/测试大纲.docx index cbb952c..d22b385 100644 Binary files a/Document/my_document/测试大纲.docx and b/Document/my_document/测试大纲.docx differ diff --git a/Document/my_document/测试报告.docx b/Document/my_document/测试报告.docx index 0ef4620..49f2312 100644 Binary files a/Document/my_document/测试报告.docx and b/Document/my_document/测试报告.docx differ diff --git a/Document/my_document/设计方案书.docx b/Document/my_document/设计方案书.docx index cfd0abb..127097a 100644 Binary files a/Document/my_document/设计方案书.docx and b/Document/my_document/设计方案书.docx differ diff --git a/Document/my_document/需求规格书.docx b/Document/my_document/需求规格书.docx index c350e9b..5adc9f0 100644 Binary files a/Document/my_document/需求规格书.docx and b/Document/my_document/需求规格书.docx differ diff --git a/Modbus/Inc/modbus_rtu_slave.h b/Modbus/Inc/modbus_rtu_slave.h index e1c7b49..cd6702c 100644 --- a/Modbus/Inc/modbus_rtu_slave.h +++ b/Modbus/Inc/modbus_rtu_slave.h @@ -2,7 +2,7 @@ #define MODBUS_RTU_SLAVE_H #include "stm32f4xx_hal.h" - +#include #ifdef __cplusplus extern "C" { diff --git a/Modbus/Src/modbus_rtu_slave.c b/Modbus/Src/modbus_rtu_slave.c index ea2e6ed..4bb2ebe 100644 --- a/Modbus/Src/modbus_rtu_slave.c +++ b/Modbus/Src/modbus_rtu_slave.c @@ -337,6 +337,73 @@ static uint16_t ModbusProcessReadHolding(const uint8_t *request,uint16_t request } + +/* +* 返回帧 +*从站地址 | 14 | 总字节数 | 子响应长度 | 06 | 寄存器数据 | CRC +* +* 01 从站地址 +* 14 功能码 +* 06 后续响应数据共6字节 +* 05 当前子响应长度,共5字节 +* 06 引用类型 +* 12 34 第一个寄存器 +* 56 78 第二个寄存器 +*/ +static uint16_t ModbusProcessFile(const uint8_t *request,uint16_t requestLength) +{ + uint16_t fileNumber; + uint16_t recordNumber; + uint16_t quantity; + uint16_t index; + uint16_t value; + uint16_t*p; + if (requestLength != 12U) + { + ModbusSlaveStatistics.illegalValueCount++; + return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); + } + if ((request[2] != 7U) || (request[3] != 0x06U)) + { + ModbusSlaveStatistics.illegalValueCount++; + return ModbusBuildException( request[1],MODBUS_EX_ILLEGAL_VALUE); + } + + quantity = ModbusGetU16Be(&request[8]); + fileNumber = ModbusGetU16Be(&request[4]); + recordNumber = ModbusGetU16Be(&request[6]); + if ((quantity == 0U) || (quantity > 124U))//数量0或者数量大于最大值 + { + ModbusSlaveStatistics.illegalValueCount++; + return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); + } + if (fileNumber>15||recordNumber>=260||((uint32_t)recordNumber + quantity) > 260UL)//地址检查 + { + ModbusSlaveStatistics.illegalAddressCount++; + return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); + } + ModbusTxFrame[0] = ModbusSlaveAddress; + ModbusTxFrame[1] = 0X14; + ModbusTxFrame[2] =(uint8_t)(2U + quantity * 2U); + ModbusTxFrame[3] =(uint8_t)(1U + quantity * 2U); + + ModbusTxFrame[4] = 0X06; + p = (uint16_t*)ModbusHistory; + for (index = 0U; index < quantity; index++) + { + uint16_t address = fileNumber*260+recordNumber + index; + + value = *(p+address); + ModbusTxFrame[5U + index * 2U] = (uint8_t)(value & 0x00FFU); + ModbusTxFrame[6U + index * 2U] = (uint8_t)(value >> 8U); + } + + ModbusAppendCrc(ModbusTxFrame, (uint16_t)(5U + quantity * 2U)); + return (uint16_t)(7U + quantity * 2U); + +} + + /** * @brief 处理写单个保持寄存器功能码 0x06 * @param[in] request RTU 请求帧 @@ -713,6 +780,9 @@ static uint16_t ModbusProcessRequest(const uint8_t *request,uint16_t requestLeng case 0X03U://读保持寄存器 return (isBroadcast != 0U) ? 0U : ModbusProcessReadHolding(request, requestLength); + case 0x14U: + return (isBroadcast != 0U) ? 0U : + ModbusProcessFile(request,requestLength); case 0x05U://写单个线圈 return ModbusProcessWriteSingleCoil(request,