| Author | SHA1 | Message | Date |
|---|---|---|---|
|
|
dd37a5345c |
清理旧查表残留
Signed-off-by: hanyongwei <2043702190@qq.com> |
2 weeks ago |
|
|
62b49681a3 |
修改为定点数Q24,采用查形状表+线性插值
Signed-off-by: hanyongwei <2043702190@qq.com> |
2 weeks ago |
|
|
92742fffc1 |
增加了注释
Signed-off-by: hanyongwei <2043702190@qq.com> |
2 weeks ago |
|
|
5bd7cb9c53 |
删掉二分降峰后重复判断逻辑
Signed-off-by: hanyongwei <2043702190@qq.com> |
2 weeks ago |
|
|
1d799e89d5 |
补充了部分注释
Signed-off-by: hanyongwei <2043702190@qq.com> |
2 weeks ago |
|
|
27140c2ddb | 修改部分过长的变量命名 | 2 weeks ago |
|
|
b86e343c92 | 清理冗余设计3 | 2 weeks ago |
|
|
ad2a6c7d76 | 清理部分冗余设计2 | 2 weeks ago |
|
|
5deb2ab693 | 清理部分冗余设计 | 2 weeks ago |
|
|
183bd22770 | 修复起始脉冲重复输出 | 3 weeks ago |
|
|
2073601b84 | 简化无用的代码 | 3 weeks ago |
|
|
1941ee6b05 |
修复ACT时间切段异常
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
e825e45a36 |
修复脉冲个数异常无法触发异常码05的问题
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
8ebd1b7382 |
修复输出起始频率不对
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
87ade7a5fd |
修正起始频率
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
c93c9b4e77 |
修复S曲线规划和正弦曲线规划
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
e00bd1bcaa |
修复起始频率大于目标频率时从起始频率减速
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
4cc13d4a75 |
增加EXT或者脉冲发送完成中间判断,跳过无效段并换向要减速到0
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
96fe4e6461 |
增加故障码判断
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
f2e8d00daf |
修复bkp存储失败问题
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
2f3ae275d9 |
最后一个脉冲下降沿强制拉低电平,避免出现极窄的脉冲
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
854f0e9cb9 |
修复bsk存储,改为稀疏映射,使用数组存储数据
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
fabb0f833f |
改用bkp存储,存储公共参数、段参数、记录信息
Signed-off-by: hanyongwei <2043702190@qq.com> |
3 weeks ago |
|
|
4b93dd0f63 | 删掉modbus的01,0F功能码 | 4 weeks ago |
|
|
cda1eac2af | 增加故障诊断 | 1 month ago |
|
|
dacd97c62a | 增加动态修改频率功能 | 1 month ago |
|
|
584280033e | 移除对os节拍的依赖,纯脉冲计数改频 | 1 month ago |
|
|
5386a08534 | 用运动学公式计算下一拍频率 | 1 month ago |
|
|
6ff53fdf14 | 改为按脉冲累加发送,不再按照1ms改频 | 1 month ago |
| @@ -15,11 +15,8 @@ iar/modbus/Exe/ | |||||
| iar/modbus/List/ | iar/modbus/List/ | ||||
| iar/modbus/Obj/ | iar/modbus/Obj/ | ||||
| iar/Backup of modbus.ewp | iar/Backup of modbus.ewp | ||||
| host/ | |||||
| iar/settings/modbus.reggroups | iar/settings/modbus.reggroups | ||||
| build-plsr_host-Desktop_Qt_5_6_3_MinGW_32bit-Debug/ | build-plsr_host-Desktop_Qt_5_6_3_MinGW_32bit-Debug/ | ||||
| plsr/test/ | plsr/test/ | ||||
| tools/ | tools/ | ||||
| .cursor/ | |||||
| 3. 定位控制手册.pdf | |||||
| .cursor/ | |||||
| @@ -1,11 +1,26 @@ | |||||
| /** | /** | ||||
| ****************************************************************************** | ****************************************************************************** | ||||
| * @file main.c | * @file main.c | ||||
| * @brief 初始化系统时钟配置、初始化GPIO、初始化串口、启动任务 | |||||
| * @brief 应用入口:时钟/GPIO/串口/定时器初始化,创建 uCOS-II 任务并启动调度 | |||||
| * | * | ||||
| * @note Modbus / PLSR / 参数块管理 / 指令接口 | |||||
| * @note PLSR: Y0(PF6)=TIM10 / Y1(PF8)=TIM13 / Y2(PF7)=TIM11 脉冲,Y3(PF9)=方向 | |||||
| * @details 本文件是下位机固件的启动枢纽,不实现业务算法,只完成: | |||||
| * 1) HAL / 系统时钟 / SysTick 优先级 | |||||
| * 2) 外设:GPIO、USART1(Modbus RTU)、TIM2(PLSR 相关) | |||||
| * 3) PlsrInit:脉冲驱动、运行控制、指令监控等模块初始化 | |||||
| * 4) OSInit 后创建三个业务任务,再 OSStart 进入多任务 | |||||
| * | * | ||||
| * 任务分工(优先级数字越小越高优先级,与 uCOS-II 一致) | |||||
| * PlsrTask prio=3 — 运行控制节拍、CommandPoll、监控刷新 | |||||
| * ModbusSlaveTask prio=4 — RS485 从站收发、保持寄存器访问 | |||||
| * PlsrParamBlockTask prio=5 — 分帧参数收齐后 Apply + 持久化落盘 | |||||
| * | |||||
| * 引脚约定(PLSR 脉冲口) | |||||
| * Y0(PF6)=TIM10 / Y1(PF8)=TIM13 / Y2(PF7)=TIM11 脉冲输出 | |||||
| * Y3(PF9)=方向电平 | |||||
| * | |||||
| * 注意 | |||||
| * PlsrParamBlockInit 内部创建信号量,必须在 OSInit() 之后调用。 | |||||
| * OSStart() 之后理论上不会返回;末尾 while(1) 仅作防护。 | |||||
| ****************************************************************************** | ****************************************************************************** | ||||
| */ | */ | ||||
| @@ -18,14 +33,15 @@ | |||||
| #include "plsr.h" | #include "plsr.h" | ||||
| #include "plsr_param.h" | #include "plsr_param.h" | ||||
| /* ---------- 任务栈与优先级(数字越小优先级越高) ---------- */ | |||||
| #define MODBUS_TASK_STACK_SIZE 512 | #define MODBUS_TASK_STACK_SIZE 512 | ||||
| #define MODBUS_TASK_PRIO 3 | |||||
| #define MODBUS_TASK_PRIO 4 /* Modbus 从站:低于 PLSR,避免抢占脉冲路径过久 */ | |||||
| #define PLSR_TASK_STACK_SIZE 512 | #define PLSR_TASK_STACK_SIZE 512 | ||||
| #define PLSR_TASK_PRIO 4 | |||||
| #define PLSR_TASK_PRIO 3 /* 运行控制最高业务优先级 */ | |||||
| #define PARAM_BLOCK_STACK_SIZE 256 | #define PARAM_BLOCK_STACK_SIZE 256 | ||||
| #define PARAM_BLOCK_TASK_PRIO 5 | |||||
| #define PARAM_BLOCK_TASK_PRIO 5 /* 参数 Apply/落盘可稍慢,不挡运动 */ | |||||
| OS_STK ModbusTaskStk[MODBUS_TASK_STACK_SIZE]; | OS_STK ModbusTaskStk[MODBUS_TASK_STACK_SIZE]; | ||||
| OS_STK PlsrTaskStk[PLSR_TASK_STACK_SIZE]; | OS_STK PlsrTaskStk[PLSR_TASK_STACK_SIZE]; | ||||
| @@ -33,34 +49,44 @@ OS_STK ParamBlockTaskStk[PARAM_BLOCK_STACK_SIZE]; | |||||
| void SystemClock_Config(void); | void SystemClock_Config(void); | ||||
| /** | |||||
| * @brief 复位后入口:硬件就绪 → 模块 Init → 建任务 → 启动 OS | |||||
| * @note 不返回;调度器接管 CPU | |||||
| */ | |||||
| int main(void) | int main(void) | ||||
| { | { | ||||
| HAL_Init(); | HAL_Init(); | ||||
| /* SysTick 优先级须低于 PendSV,供 uCOS 节拍与上下文切换 */ | |||||
| HAL_NVIC_SetPriority(SysTick_IRQn, 14, 0); | HAL_NVIC_SetPriority(SysTick_IRQn, 14, 0); | ||||
| HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0); | HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0); | ||||
| SystemClock_Config(); | SystemClock_Config(); | ||||
| /* 配置 Cortex-M SysTick 为 OS 节拍源(与 OS_TICKS_PER_SEC 匹配) */ | |||||
| OS_CPU_SysTickInit(168000000 / OS_TICKS_PER_SEC); | OS_CPU_SysTickInit(168000000 / OS_TICKS_PER_SEC); | ||||
| MX_GPIO_Init(); | MX_GPIO_Init(); | ||||
| MX_USART1_UART_Init(); | |||||
| MX_USART1_UART_Init(); /* Modbus RTU 物理层 */ | |||||
| MX_TIM2_Init(); | MX_TIM2_Init(); | ||||
| PlsrInit(); | |||||
| PlsrInit(); /* 脉冲驱动 / 运行控制 / 指令接口等 */ | |||||
| OSInit(); | OSInit(); | ||||
| PlsrParamBlockInit(); /* 依赖 OS,须在 OSInit 之后 */ | |||||
| OSTaskCreateExt(ModbusSlaveTask, | |||||
| NULL, | |||||
| &ModbusTaskStk[MODBUS_TASK_STACK_SIZE - 1], | |||||
| MODBUS_TASK_PRIO, | |||||
| MODBUS_TASK_PRIO, | |||||
| &ModbusTaskStk[0], | |||||
| MODBUS_TASK_STACK_SIZE, | |||||
| NULL, | |||||
| /* 参数块信号量依赖 OS,必须放在 OSInit 之后 */ | |||||
| PlsrParamBlockInit(); | |||||
| /* Modbus 从站任务:解析 FC03/06/10,维护保持寄存器映像 */ | |||||
| OSTaskCreateExt(ModbusSlaveTask,//任务函数入口,任务实体函数 | |||||
| NULL,//传递给任务函数的入口参数 | |||||
| &ModbusTaskStk[MODBUS_TASK_STACK_SIZE - 1],//栈向下增长,栈从高地址往低地址生长 | |||||
| MODBUS_TASK_PRIO,//任务优先级 | |||||
| MODBUS_TASK_PRIO,//任务id,一般直接等于优先级;可以不等于优先级,但不能冲突 | |||||
| &ModbusTaskStk[0],//栈数组的起始地址,数组下标 0 的地址 | |||||
| MODBUS_TASK_STACK_SIZE,//栈的元素数量,不是字节数 | |||||
| NULL,//TCB 扩展指针 | |||||
| OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); | OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); | ||||
| //开启栈用量统计;任务创建的时候,把整个栈内存全部清零;保存 FPU 浮点寄存器 | |||||
| /* PLSR 主任务:TickMs / CommandPoll / 监控发布 */ | |||||
| OSTaskCreateExt(PlsrTask, | OSTaskCreateExt(PlsrTask, | ||||
| NULL, | NULL, | ||||
| &PlsrTaskStk[PLSR_TASK_STACK_SIZE - 1], | &PlsrTaskStk[PLSR_TASK_STACK_SIZE - 1], | ||||
| @@ -70,7 +96,11 @@ int main(void) | |||||
| PLSR_TASK_STACK_SIZE, | PLSR_TASK_STACK_SIZE, | ||||
| NULL, | NULL, | ||||
| OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); | OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); | ||||
| //开启栈用量统计;任务创建的时候,把整个栈内存全部清零;保存 FPU 浮点寄存器 | |||||
| /* | |||||
| * 参数块任务:Pend → ApplyFromHold(校验)→ 通过则 SaveParamsFromHold。 | |||||
| * 分帧写完后由 OnHoldWrite Post;Save 落 BKP,Export 仅供读回钳位结果。 | |||||
| */ | |||||
| OSTaskCreateExt(PlsrParamBlockTask, | OSTaskCreateExt(PlsrParamBlockTask, | ||||
| NULL, | NULL, | ||||
| &ParamBlockTaskStk[PARAM_BLOCK_STACK_SIZE - 1], | &ParamBlockTaskStk[PARAM_BLOCK_STACK_SIZE - 1], | ||||
| @@ -81,13 +111,18 @@ int main(void) | |||||
| NULL, | NULL, | ||||
| OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); | OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); | ||||
| OSStart(); | |||||
| OSStart(); /* 启动多任务;正常情况下不再返回 */ | |||||
| while (1) | while (1) | ||||
| { | { | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * @brief 系统时钟:HSE(12MHz) → PLL → 168MHz SYSCLK | |||||
| * @note 须与 OS_CPU_SysTickInit、pulse_driver 使用的主频常量一致 | |||||
| * @note PLLM 须等于晶振 MHz:12MHz 用 12;误用 8 会使 SYSCLK=252MHz,脉冲频率偏 1.5 倍 | |||||
| */ | |||||
| void SystemClock_Config(void) | void SystemClock_Config(void) | ||||
| { | { | ||||
| RCC_OscInitTypeDef RCC_OscInitStruct = {0}; | RCC_OscInitTypeDef RCC_OscInitStruct = {0}; | ||||
| @@ -100,28 +135,31 @@ void SystemClock_Config(void) | |||||
| RCC_OscInitStruct.HSEState = RCC_HSE_ON; | RCC_OscInitStruct.HSEState = RCC_HSE_ON; | ||||
| RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; | RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; | ||||
| RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; | RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; | ||||
| RCC_OscInitStruct.PLL.PLLM = 12; | |||||
| RCC_OscInitStruct.PLL.PLLM = 12; /* HSE=12MHz → VCO in = 1MHz */ | |||||
| RCC_OscInitStruct.PLL.PLLN = 336; | RCC_OscInitStruct.PLL.PLLN = 336; | ||||
| RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; | RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; | ||||
| RCC_OscInitStruct.PLL.PLLQ = 4; | |||||
| RCC_OscInitStruct.PLL.PLLQ = 4;/*不准改成7*/ | |||||
| if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) | if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) | ||||
| { | { | ||||
| Error_Handler(); | Error_Handler(); | ||||
| } | } | ||||
| RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK | |||||
| |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; | |||||
| RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | |||||
| | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; | |||||
| RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; | RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; | ||||
| RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; | RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; | ||||
| RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; | RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; | ||||
| RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; | RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; | ||||
| if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) | if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) | ||||
| { | { | ||||
| Error_Handler(); | Error_Handler(); | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * @brief 致命错误停机(时钟配置失败等) | |||||
| * @note 生产可用 LED 闪烁;此处死循环便于调试挂起 | |||||
| */ | |||||
| void Error_Handler(void) | void Error_Handler(void) | ||||
| { | { | ||||
| __disable_irq(); | __disable_irq(); | ||||
| @@ -129,7 +167,3 @@ void Error_Handler(void) | |||||
| { | { | ||||
| } | } | ||||
| } | } | ||||
| #ifdef USE_FULL_ASSERT | |||||
| #endif | |||||
| @@ -225,12 +225,12 @@ void TIM2_IRQHandler(void) | |||||
| } | } | ||||
| /** | /** | ||||
| * @brief TIM3:PLSR WAIT/ACT 1ms 时基 | |||||
| * @brief TIM5:PLSR 单次延时到期(换向 / WAIT / ACT) | |||||
| */ | */ | ||||
| void TIM3_IRQHandler(void) | |||||
| void TIM5_IRQHandler(void) | |||||
| { | { | ||||
| OSIntEnter(); | OSIntEnter(); | ||||
| PlsrSignalIoOnTim3Irq(); | |||||
| PlsrSignalIoOnTim5Irq(); | |||||
| OSIntExit(); | OSIntExit(); | ||||
| } | } | ||||
| @@ -359,7 +359,6 @@ | |||||
| <state>$PROJ_DIR$\..\plsr\param</state> | <state>$PROJ_DIR$\..\plsr\param</state> | ||||
| <state>$PROJ_DIR$\..\plsr\persist</state> | <state>$PROJ_DIR$\..\plsr\persist</state> | ||||
| <state>$PROJ_DIR$\..\plsr\pulse_driver</state> | <state>$PROJ_DIR$\..\plsr\pulse_driver</state> | ||||
| <state>$PROJ_DIR$\..\plsr\path_plan</state> | |||||
| <state>$PROJ_DIR$\..\plsr\accel_curve</state> | <state>$PROJ_DIR$\..\plsr\accel_curve</state> | ||||
| <state>$PROJ_DIR$\..\plsr\run_control</state> | <state>$PROJ_DIR$\..\plsr\run_control</state> | ||||
| <state>$PROJ_DIR$\..\plsr\command</state> | <state>$PROJ_DIR$\..\plsr\command</state> | ||||
| @@ -2841,27 +2840,6 @@ | |||||
| </group> | </group> | ||||
| <group> | <group> | ||||
| <name>modbus</name> | <name>modbus</name> | ||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_common.h</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_data.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_data.h</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_fc.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_fc.h</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_port.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_port.h</name> | |||||
| </file> | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name> | <name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name> | ||||
| </file> | </file> | ||||
| @@ -2904,15 +2882,6 @@ | |||||
| <name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name> | <name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name> | ||||
| </file> | </file> | ||||
| </group> | </group> | ||||
| <group> | |||||
| <name>path_plan</name> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\path_plan\plsr_path_plan.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\path_plan\plsr_path_plan.h</name> | |||||
| </file> | |||||
| </group> | |||||
| <group> | <group> | ||||
| <name>accel_curve</name> | <name>accel_curve</name> | ||||
| <file> | <file> | ||||
| @@ -3074,31 +3074,82 @@ | |||||
| <group> | <group> | ||||
| <name>modbus</name> | <name>modbus</name> | ||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\modbus\modbus_common.h</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_data.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_data.h</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_fc.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_fc.h</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_port.c</name> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name> | |||||
| </file> | </file> | ||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\modbus\modbus_port.h</name> | |||||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.h</name> | |||||
| </file> | </file> | ||||
| </group> | |||||
| <group> | |||||
| <name>plsr</name> | |||||
| <group> | |||||
| <name>accel_curve</name> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.h</name> | |||||
| </file> | |||||
| </group> | |||||
| <group> | |||||
| <name>command</name> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\command\plsr_command.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\command\plsr_command.h</name> | |||||
| </file> | |||||
| </group> | |||||
| <group> | |||||
| <name>param</name> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\param\plsr_param.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\param\plsr_param.h</name> | |||||
| </file> | |||||
| </group> | |||||
| <group> | |||||
| <name>persist</name> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\persist\plsr_persist.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\persist\plsr_persist.h</name> | |||||
| </file> | |||||
| </group> | |||||
| <group> | |||||
| <name>pulse_driver</name> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name> | |||||
| </file> | |||||
| </group> | |||||
| <group> | |||||
| <name>run_control</name> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.h</name> | |||||
| </file> | |||||
| </group> | |||||
| <group> | |||||
| <name>signal_io</name> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\signal_io\plsr_signal_io.c</name> | |||||
| </file> | |||||
| <file> | |||||
| <name>$PROJ_DIR$\..\plsr\signal_io\plsr_signal_io.h</name> | |||||
| </file> | |||||
| </group> | |||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name> | |||||
| <name>$PROJ_DIR$\..\plsr\plsr.c</name> | |||||
| </file> | </file> | ||||
| <file> | <file> | ||||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.h</name> | |||||
| <name>$PROJ_DIR$\..\plsr\plsr.h</name> | |||||
| </file> | </file> | ||||
| </group> | </group> | ||||
| <group> | <group> | ||||
| @@ -1,30 +0,0 @@ | |||||
| /** | |||||
| * @file modbus_common.h | |||||
| * @brief Modbus RTU 从站模块间私有常量与共享声明 | |||||
| */ | |||||
| #ifndef MODBUS_COMMON_H | |||||
| #define MODBUS_COMMON_H | |||||
| #include "modbus_rtu.h" | |||||
| /* 功能码 */ | |||||
| #define READ_COILS 0x01U | |||||
| #define READ_HOLDING_REGS 0x03U | |||||
| #define WRITE_SINGLE_REG 0x06U | |||||
| #define WRITE_MULTI_COILS 0x0FU | |||||
| #define WRITE_MULTI_REGS 0x10U | |||||
| /* 地址映射失败时的哨兵值 */ | |||||
| #define MODBUS_ADDR_INVALID 0xFFFFU | |||||
| /* 异常码 01:非法功能 */ | |||||
| #define ILLEGAL_FUNCTION 0x01U | |||||
| /* 异常码 02:非法数据地址 */ | |||||
| #define ILLEGAL_DATA_ADDRESS 0x02U | |||||
| /* 异常码 03:非法数据值 */ | |||||
| #define ILLEGAL_DATA_VALUE 0x03U | |||||
| #endif | |||||
| @@ -1,194 +0,0 @@ | |||||
| /** | |||||
| * @file modbus_data.c | |||||
| * @brief 线圈/保持寄存器数据映像处理 | |||||
| */ | |||||
| #include "modbus_data.h" | |||||
| #include "modbus_common.h" | |||||
| #include <string.h> | |||||
| /* 线圈数据映像,一个线圈对应一个字节 */ | |||||
| uint8_t g_coil_buf[COIL_BUFFER_SIZE] = {0}; | |||||
| /* 保持寄存器数据映像 */ | |||||
| uint16_t g_hold_reg[HOLD_REG_BUFFER_SIZE] = {0}; | |||||
| /*============================================================================*/ | |||||
| /* 初始化 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 初始化线圈与保持寄存器映像 | |||||
| * @retval None | |||||
| */ | |||||
| void ModbusDataInit(void) | |||||
| { | |||||
| memset(g_coil_buf, 0, sizeof(g_coil_buf)); | |||||
| memset(g_hold_reg, 0, sizeof(g_hold_reg)); | |||||
| // for (int i = 0; i< 9999; i++) | |||||
| // { | |||||
| // g_coil_buf[i] = i % 2; | |||||
| // g_hold_reg[i] = 9999-i; | |||||
| // } | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 地址映射与区间校验 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 将线圈地址映射为本地映像下标 | |||||
| * @param[in] addr 线圈地址 | |||||
| * @retval 合法时返回下标;非法返回 MODBUS_ADDR_INVALID | |||||
| */ | |||||
| uint16_t ModbusMapCoilAddr(uint16_t addr) | |||||
| { | |||||
| return (addr <= MODBUS_MB_MAXIMUM) ? addr : MODBUS_ADDR_INVALID; | |||||
| } | |||||
| /** | |||||
| * @brief 将保持寄存器地址映射为本地映像下标 | |||||
| * @param[in] addr 寄存器地址 | |||||
| * @retval 合法时返回下标;非法返回 MODBUS_ADDR_INVALID | |||||
| */ | |||||
| uint16_t ModbusMapRegAddr(uint16_t addr) | |||||
| { | |||||
| return (addr <= MODBUS_MB_MAXIMUM) ? addr : MODBUS_ADDR_INVALID; | |||||
| } | |||||
| /** | |||||
| * @brief 校验线圈连续区间是否全部合法 | |||||
| * @param[in] addr 起始地址 | |||||
| * @param[in] qty 数量(须 ≥ 1) | |||||
| * @retval 1 合法;0 非法 | |||||
| */ | |||||
| uint8_t ModbusIsCoilRangeValid(uint16_t addr, uint16_t qty) | |||||
| { | |||||
| uint32_t endAddr; | |||||
| if ((qty == 0U) || (ModbusMapCoilAddr(addr) == MODBUS_ADDR_INVALID)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| endAddr = (uint32_t)addr + (uint32_t)qty - 1U; | |||||
| if (endAddr > (uint32_t)MODBUS_MB_MAXIMUM) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return 1U; | |||||
| } | |||||
| /** | |||||
| * @brief 校验保持寄存器连续区间是否全部合法 | |||||
| * @param[in] addr 起始地址 | |||||
| * @param[in] qty 数量(须 ≥ 1) | |||||
| * @retval 1 合法;0 非法 | |||||
| */ | |||||
| uint8_t ModbusIsRegRangeValid(uint16_t addr, uint16_t qty) | |||||
| { | |||||
| uint32_t endAddr; | |||||
| if ((qty == 0U) || (ModbusMapRegAddr(addr) == MODBUS_ADDR_INVALID)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| endAddr = (uint32_t)addr + (uint32_t)qty - 1U; | |||||
| if (endAddr > (uint32_t)MODBUS_MB_MAXIMUM) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return 1U; | |||||
| } | |||||
| /** | |||||
| * @brief 从缓冲读取大端序 uint16 | |||||
| * @param[in] data 至少 2字节,高字节在前 | |||||
| * @retval 解析得到的 16位无符号值 | |||||
| */ | |||||
| uint16_t ReadU16Be(const uint8_t *data) | |||||
| { | |||||
| return (uint16_t)(((uint16_t)data[0] << 8) | data[1]); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 公共映像访问 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 读线圈映像 | |||||
| * @param[in] addr 线圈地址 | |||||
| * @retval 1 ON;0 OFF 或地址非法 | |||||
| */ | |||||
| uint8_t ReadCoil(uint16_t addr) | |||||
| { | |||||
| uint16_t offset; | |||||
| offset = ModbusMapCoilAddr(addr); | |||||
| if (offset == MODBUS_ADDR_INVALID) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return g_coil_buf[offset] ? 1U : 0U; | |||||
| } | |||||
| /** | |||||
| * @brief 写线圈映像 | |||||
| * @param[in] addr 线圈地址 | |||||
| * @param[in] val 非 0 写 ON,0 写 OFF | |||||
| * @retval None | |||||
| */ | |||||
| void WriteCoil(uint16_t addr, uint8_t val) | |||||
| { | |||||
| uint16_t offset; | |||||
| offset = ModbusMapCoilAddr(addr); | |||||
| if (offset == MODBUS_ADDR_INVALID) | |||||
| { | |||||
| return; | |||||
| } | |||||
| g_coil_buf[offset] = val ? 1U : 0U; | |||||
| } | |||||
| /** | |||||
| * @brief 读保持寄存器映像 | |||||
| * @param[in] addr 寄存器地址 | |||||
| * @retval 寄存器值;地址非法时返回 0 | |||||
| */ | |||||
| uint16_t ReadHoldReg(uint16_t addr) | |||||
| { | |||||
| uint16_t offset; | |||||
| offset = ModbusMapRegAddr(addr); | |||||
| if (offset == MODBUS_ADDR_INVALID) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return g_hold_reg[offset]; | |||||
| } | |||||
| /** | |||||
| * @brief 写保持寄存器映像 | |||||
| * @param[in] addr 寄存器地址 | |||||
| * @param[in] val 写入值 | |||||
| * @retval None | |||||
| */ | |||||
| void WriteHoldReg(uint16_t addr, uint16_t val) | |||||
| { | |||||
| uint16_t offset; | |||||
| offset = ModbusMapRegAddr(addr); | |||||
| if (offset == MODBUS_ADDR_INVALID) | |||||
| { | |||||
| return; | |||||
| } | |||||
| g_hold_reg[offset] = val; | |||||
| } | |||||
| @@ -1,59 +0,0 @@ | |||||
| /** | |||||
| * @file modbus_data.h | |||||
| * @brief 线圈 / 保持寄存器数据映像与地址校验 | |||||
| */ | |||||
| #ifndef MODBUS_DATA_H | |||||
| #define MODBUS_DATA_H | |||||
| #include <stdint.h> | |||||
| /** | |||||
| * @brief 初始化线圈与保持寄存器映像 | |||||
| * @details 清零线圈;清零寄存器 | |||||
| * @retval None | |||||
| */ | |||||
| void ModbusDataInit(void); | |||||
| /** | |||||
| * @brief 将线圈地址映射为本地下标 | |||||
| * @param[in] addr 协议线圈地址 | |||||
| * @retval 合法时返回下标 | |||||
| * @retval MODBUS_ADDR_INVALID 地址非法 | |||||
| */ | |||||
| uint16_t ModbusMapCoilAddr(uint16_t addr); | |||||
| /** | |||||
| * @brief 将保持寄存器地址映射为本地下标 | |||||
| * @param[in] addr 协议寄存器地址 | |||||
| * @retval 合法时返回下标 | |||||
| * @retval MODBUS_ADDR_INVALID 地址非法 | |||||
| */ | |||||
| uint16_t ModbusMapRegAddr(uint16_t addr); | |||||
| /** | |||||
| * @brief 校验线圈连续区间是否合法 | |||||
| * @param[in] addr 起始地址 | |||||
| * @param[in] qty 数量(须 ≥ 1) | |||||
| * @retval 1 合法 | |||||
| * @retval 0 非法 | |||||
| */ | |||||
| uint8_t ModbusIsCoilRangeValid(uint16_t addr, uint16_t qty); | |||||
| /** | |||||
| * @brief 校验保持寄存器连续区间是否合法 | |||||
| * @param[in] addr 起始地址 | |||||
| * @param[in] qty 数量(须 ≥ 1) | |||||
| * @retval 1 合法 | |||||
| * @retval 0 非法 | |||||
| */ | |||||
| uint8_t ModbusIsRegRangeValid(uint16_t addr, uint16_t qty); | |||||
| /** | |||||
| * @brief 从缓冲读取大端序 uint16 | |||||
| * @param[in] data 至少 2 字节(高字节在前) | |||||
| * @retval 解析得到的 16 位无符号值 | |||||
| */ | |||||
| uint16_t ReadU16Be(const uint8_t *data); | |||||
| #endif | |||||
| @@ -1,424 +0,0 @@ | |||||
| /** | |||||
| * @file modbus_fc.c | |||||
| * @brief Modbus 功能码 01/03/06/0F/10 与异常应答实现 | |||||
| */ | |||||
| #include "modbus_fc.h" | |||||
| #include "modbus_common.h" | |||||
| #include "modbus_data.h" | |||||
| #include "modbus_port.h" | |||||
| #include "plsr_param.h" | |||||
| #include <string.h> | |||||
| /*应答帧组帧缓冲 */ | |||||
| static uint8_t g_modbus_tx_buf[MODBUS_TX_BUF_LEN]; | |||||
| /* 0 = 广播禁止回帧;1 = 正常回帧 */ | |||||
| static uint8_t g_modbus_reply_enable = 1U; | |||||
| /* 当前请求从站地址(异常帧使用) */ | |||||
| static uint8_t g_modbus_current_addr = MODBUS_SLAVE_ADDR; | |||||
| static void AppendCrcAndSend(uint8_t *pdu, uint16_t pduLen); | |||||
| static void SendException(uint8_t funcCode, uint8_t exCode); | |||||
| static void ReadCoils(const uint8_t *frame, uint16_t len); | |||||
| static void ReadHoldingRegs(const uint8_t *frame, uint16_t len); | |||||
| static void WriteSingleReg(const uint8_t *frame, uint16_t len); | |||||
| static void WriteMultiCoils(const uint8_t *frame, uint16_t len); | |||||
| static void WriteMultiRegs(const uint8_t *frame, uint16_t len); | |||||
| /** | |||||
| * @brief 拼装响应帧 | |||||
| * @param[in] slaveAddr 请求中的从站地址 | |||||
| * @param[in] replyEnable 0 = 广播不回帧;1 = 正常回帧 | |||||
| */ | |||||
| void ReplyContext(uint8_t slaveAddr, uint8_t replyEnable) | |||||
| { | |||||
| g_modbus_current_addr = slaveAddr; | |||||
| g_modbus_reply_enable = replyEnable; | |||||
| } | |||||
| /** | |||||
| * @brief 按功能码分发到对应处理函数 | |||||
| * @param[in] frame 完整 ADU(含 CRC) | |||||
| * @param[in] len ADU 长度 | |||||
| */ | |||||
| void ProcessRequest(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint8_t funcCode; | |||||
| if ((frame == NULL) || (len < 4U)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| funcCode = frame[1]; | |||||
| switch (funcCode) | |||||
| { | |||||
| case READ_COILS: | |||||
| ReadCoils(frame, len); | |||||
| break; | |||||
| case READ_HOLDING_REGS: | |||||
| ReadHoldingRegs(frame, len); | |||||
| break; | |||||
| case WRITE_SINGLE_REG: | |||||
| WriteSingleReg(frame, len); | |||||
| break; | |||||
| case WRITE_MULTI_COILS: | |||||
| WriteMultiCoils(frame, len); | |||||
| break; | |||||
| case WRITE_MULTI_REGS: | |||||
| WriteMultiRegs(frame, len); | |||||
| break; | |||||
| default: | |||||
| SendException(funcCode, ILLEGAL_FUNCTION); | |||||
| break; | |||||
| } | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 功能码 01:读线圈 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 处理读线圈请求(0x01) | |||||
| * @param[in] frame 完整请求 ADU | |||||
| * @param[in] len 合法请求固定为 8 | |||||
| */ | |||||
| static void ReadCoils(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint16_t startAddr; | |||||
| uint16_t quantity; | |||||
| uint16_t byteCount; | |||||
| uint16_t i; | |||||
| uint8_t bitMask; | |||||
| if (len != 8U) | |||||
| { | |||||
| SendException(READ_COILS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| startAddr = ReadU16Be(&frame[2]); | |||||
| quantity = ReadU16Be(&frame[4]); | |||||
| if ((quantity < 1U) || (quantity > MAX_READ_COILS)) | |||||
| { | |||||
| SendException(READ_COILS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| if (ModbusIsCoilRangeValid(startAddr, quantity) == 0U) | |||||
| { | |||||
| SendException(READ_COILS, ILLEGAL_DATA_ADDRESS); | |||||
| return; | |||||
| } | |||||
| byteCount = (uint16_t)((quantity + 7U) / 8U); | |||||
| g_modbus_tx_buf[0] = frame[0]; | |||||
| g_modbus_tx_buf[1] = READ_COILS; | |||||
| g_modbus_tx_buf[2] = (uint8_t)byteCount; | |||||
| memset(&g_modbus_tx_buf[3], 0, byteCount); | |||||
| for (i = 0U; i < quantity; i++) | |||||
| { | |||||
| if (ReadCoil((uint16_t)(startAddr + i)) != 0U) | |||||
| { | |||||
| bitMask = (uint8_t)(1U << (i % 8U)); | |||||
| g_modbus_tx_buf[3U + (i / 8U)] |= bitMask; | |||||
| } | |||||
| } | |||||
| AppendCrcAndSend(g_modbus_tx_buf, (uint16_t)(3U + byteCount)); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 功能码 03:读保持寄存器 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 处理读保持寄存器请求(0x03) | |||||
| * @param[in] frame 完整请求 ADU | |||||
| * @param[in] len 合法请求固定为 8 | |||||
| */ | |||||
| static void ReadHoldingRegs(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint16_t startAddr; | |||||
| uint16_t quantity; | |||||
| uint16_t byteCount; | |||||
| uint16_t i; | |||||
| uint16_t regVal; | |||||
| if (len != 8U) | |||||
| { | |||||
| SendException(READ_HOLDING_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| startAddr = ReadU16Be(&frame[2]); | |||||
| quantity = ReadU16Be(&frame[4]); | |||||
| if ((quantity < 1U) || (quantity > MAX_READ_REGS)) | |||||
| { | |||||
| SendException(READ_HOLDING_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| if (ModbusIsRegRangeValid(startAddr, quantity) == 0U) | |||||
| { | |||||
| SendException(READ_HOLDING_REGS, ILLEGAL_DATA_ADDRESS); | |||||
| return; | |||||
| } | |||||
| byteCount = (uint16_t)(quantity * 2U); | |||||
| g_modbus_tx_buf[0] = frame[0]; | |||||
| g_modbus_tx_buf[1] = READ_HOLDING_REGS; | |||||
| g_modbus_tx_buf[2] = (uint8_t)byteCount; | |||||
| for (i = 0U; i < quantity; i++) | |||||
| { | |||||
| regVal = ReadHoldReg((uint16_t)(startAddr + i)); | |||||
| g_modbus_tx_buf[3U + (i * 2U)] = (uint8_t)(regVal >> 8); | |||||
| g_modbus_tx_buf[4U + (i * 2U)] = (uint8_t)(regVal & 0xFFU); | |||||
| } | |||||
| AppendCrcAndSend(g_modbus_tx_buf, (uint16_t)(3U + byteCount)); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 功能码 06:写单个保持寄存器 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 处理写单个保持寄存器请求(0x06) | |||||
| * @param[in] frame 完整请求 ADU | |||||
| * @param[in] len 合法请求固定为 8 | |||||
| * | |||||
| * 读写都经 WriteHoldReg / ReadHoldReg,落在 g_hold_reg[]。 | |||||
| * 应答回显地址与写入值。 | |||||
| */ | |||||
| static void WriteSingleReg(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint16_t addr; | |||||
| uint16_t value; | |||||
| if (len != 8U) | |||||
| { | |||||
| SendException(WRITE_SINGLE_REG, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| addr = ReadU16Be(&frame[2]); | |||||
| value = ReadU16Be(&frame[4]); | |||||
| if (ModbusIsRegRangeValid(addr, 1U) == 0U) | |||||
| { | |||||
| SendException(WRITE_SINGLE_REG, ILLEGAL_DATA_ADDRESS); | |||||
| return; | |||||
| } | |||||
| WriteHoldReg(addr, value); | |||||
| g_modbus_tx_buf[0] = frame[0]; | |||||
| g_modbus_tx_buf[1] = WRITE_SINGLE_REG; | |||||
| g_modbus_tx_buf[2] = frame[2]; | |||||
| g_modbus_tx_buf[3] = frame[3]; | |||||
| g_modbus_tx_buf[4] = frame[4]; | |||||
| g_modbus_tx_buf[5] = frame[5]; | |||||
| AppendCrcAndSend(g_modbus_tx_buf, 6U); | |||||
| PlsrParamBlockOnHoldWrite(addr, 1U); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 功能码 0F:写多个线圈 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 处理写多个线圈请求(0x0F) | |||||
| * @param[in] frame 完整请求 ADU | |||||
| * @param[in] len 等于 7 + ByteCount + 2 | |||||
| * @retval None | |||||
| */ | |||||
| static void WriteMultiCoils(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint16_t startAddr; | |||||
| uint16_t quantity; | |||||
| uint8_t byteCount; | |||||
| uint16_t expectedLen; | |||||
| uint16_t i; | |||||
| uint8_t bitMask; | |||||
| uint8_t coilVal; | |||||
| if (len < 9U) | |||||
| { | |||||
| SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| startAddr = ReadU16Be(&frame[2]); | |||||
| quantity = ReadU16Be(&frame[4]); | |||||
| byteCount = frame[6]; | |||||
| if ((quantity < 1U) || (quantity > MAX_WRITE_COILS)) | |||||
| { | |||||
| SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| if (byteCount != (uint8_t)((quantity + 7U) / 8U)) | |||||
| { | |||||
| SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| expectedLen = (uint16_t)(7U + byteCount + 2U); | |||||
| if (len != expectedLen) | |||||
| { | |||||
| SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| if (ModbusIsCoilRangeValid(startAddr, quantity) == 0U) | |||||
| { | |||||
| SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_ADDRESS); | |||||
| return; | |||||
| } | |||||
| for (i = 0U; i < quantity; i++) | |||||
| { | |||||
| bitMask = (uint8_t)(1U << (i % 8U)); | |||||
| coilVal = (frame[7U + (i / 8U)] & bitMask) ? 1U : 0U; | |||||
| WriteCoil((uint16_t)(startAddr + i), coilVal); | |||||
| } | |||||
| g_modbus_tx_buf[0] = frame[0]; | |||||
| g_modbus_tx_buf[1] = WRITE_MULTI_COILS; | |||||
| g_modbus_tx_buf[2] = frame[2]; | |||||
| g_modbus_tx_buf[3] = frame[3]; | |||||
| g_modbus_tx_buf[4] = frame[4]; | |||||
| g_modbus_tx_buf[5] = frame[5]; | |||||
| AppendCrcAndSend(g_modbus_tx_buf, 6U); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 功能码 10:写多个保持寄存器 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 处理写多个保持寄存器请求(0x10) | |||||
| * @param[in] frame 完整请求 ADU | |||||
| * @param[in] len 等于 7 + ByteCount + 2 | |||||
| * @retval None | |||||
| */ | |||||
| static void WriteMultiRegs(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint16_t startAddr; | |||||
| uint16_t quantity; | |||||
| uint8_t byteCount; | |||||
| uint16_t expectedLen; | |||||
| uint16_t i; | |||||
| if (len < 9U) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| startAddr = ReadU16Be(&frame[2]); | |||||
| quantity = ReadU16Be(&frame[4]); | |||||
| byteCount = frame[6]; | |||||
| if ((quantity < 1U) || (quantity > MAX_WRITE_REGS)) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| if (byteCount != (uint8_t)(quantity * 2U)) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| expectedLen = (uint16_t)(7U + byteCount + 2U); | |||||
| if (len != expectedLen) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| if (ModbusIsRegRangeValid(startAddr, quantity) == 0U) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_ADDRESS); | |||||
| return; | |||||
| } | |||||
| for (i = 0U; i < quantity; i++) | |||||
| { | |||||
| WriteHoldReg((uint16_t)(startAddr + i), | |||||
| ReadU16Be(&frame[7U + (i * 2U)])); | |||||
| } | |||||
| /* 先回 FC10 应答,再做分帧计数(避免应答被后续逻辑拖住) */ | |||||
| g_modbus_tx_buf[0] = frame[0]; | |||||
| g_modbus_tx_buf[1] = WRITE_MULTI_REGS; | |||||
| g_modbus_tx_buf[2] = frame[2]; | |||||
| g_modbus_tx_buf[3] = frame[3]; | |||||
| g_modbus_tx_buf[4] = frame[4]; | |||||
| g_modbus_tx_buf[5] = frame[5]; | |||||
| AppendCrcAndSend(g_modbus_tx_buf, 6U); | |||||
| PlsrParamBlockOnHoldWrite(startAddr, quantity); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 异常应答与发送 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 组并发送异常应答 | |||||
| * @param[in] funcCode 原功能码(未置位 0x80) | |||||
| * @param[in] exCode 异常码 01/02/03 | |||||
| */ | |||||
| static void SendException(uint8_t funcCode, uint8_t exCode) | |||||
| { | |||||
| g_modbus_tx_buf[0] = g_modbus_current_addr; | |||||
| g_modbus_tx_buf[1] = (uint8_t)(funcCode | 0x80U); | |||||
| g_modbus_tx_buf[2] = exCode; | |||||
| AppendCrcAndSend(g_modbus_tx_buf, 3U); | |||||
| LedErrorBlink(); | |||||
| } | |||||
| /* 添加CRC */ | |||||
| static void AppendCrcAndSend(uint8_t *pdu, uint16_t pduLen) | |||||
| { | |||||
| uint16_t crc; | |||||
| if ((pdu == NULL) || (pduLen == 0U) || | |||||
| ((uint16_t)(pduLen + 2U) > MODBUS_TX_BUF_LEN)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| if (g_modbus_reply_enable == 0U) | |||||
| { | |||||
| return; | |||||
| } | |||||
| crc = ModbusCrc16(pdu, pduLen); | |||||
| pdu[pduLen] = (uint8_t)(crc & 0xFFU); | |||||
| pdu[pduLen + 1U] = (uint8_t)(crc >> 8); | |||||
| SendData(pdu, (uint16_t)(pduLen + 2U)); | |||||
| } | |||||
| @@ -1,25 +0,0 @@ | |||||
| /** | |||||
| * @file modbus_fc.h | |||||
| * @brief 功能码处理与异常应答 | |||||
| */ | |||||
| #ifndef MODBUS_FC_H | |||||
| #define MODBUS_FC_H | |||||
| #include <stdint.h> | |||||
| /** | |||||
| * @brief 拼装响应帧 | |||||
| * @param[in] slaveAddr 请求中的从站地址(异常帧地址字段) | |||||
| * @param[in] replyEnable 0=广播不回帧;1=正常回帧 | |||||
| */ | |||||
| void ReplyContext(uint8_t slaveAddr, uint8_t replyEnable); | |||||
| /** | |||||
| * @brief 按功能码分发并处理请求 | |||||
| * @param[in] frame 完整ADU(含 CRC) | |||||
| * @param[in] len ADU长度(字节) | |||||
| */ | |||||
| void ProcessRequest(const uint8_t *frame, uint16_t len); | |||||
| #endif | |||||
| @@ -1,188 +0,0 @@ | |||||
| /** | |||||
| * @file modbus_port.c | |||||
| * @author lve | |||||
| * @brief 链路端口实现(收字节、空闲定帧、发送) | |||||
| * @version 0.1.2 | |||||
| * @date 2026/7/31 | |||||
| * @note 从站任务取帧处理后,通过 SendData 阻塞回发 | |||||
| * @copyright Copyright (c) 2026 | |||||
| */ | |||||
| #include "modbus_port.h" | |||||
| #include "modbus_common.h" | |||||
| #include "gpio.h" | |||||
| #include "plsr.h" | |||||
| #include "plsr_pulse_driver.h" | |||||
| uint8_t g_modbus_rx_buf[MODBUS_RX_BUF_LEN] = {0}; | |||||
| volatile uint16_t g_modbus_rx_len = 0U; | |||||
| OS_EVENT *g_modbus_rx_sem = (OS_EVENT *)0; | |||||
| static uint8_t g_rxByte; | |||||
| static volatile uint8_t g_modbus_frame_pending = 0U; | |||||
| static volatile uint8_t g_modbus_tim_armed = 0U; | |||||
| static void StopFrameTimer(void); | |||||
| static void FlushUartRx(void); | |||||
| static void RestartReceive(void); | |||||
| void LedErrorBlink(void) | |||||
| { | |||||
| /* 指示灯已取消,保留空函数以兼容调用点 */ | |||||
| } | |||||
| static void StopFrameTimer(void) | |||||
| { | |||||
| (void)HAL_TIM_Base_Stop_IT(&htim2); | |||||
| g_modbus_tim_armed = 0U; | |||||
| __HAL_TIM_SET_COUNTER(&htim2, 0U); | |||||
| __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); | |||||
| } | |||||
| static void FlushUartRx(void) | |||||
| { | |||||
| __HAL_UART_CLEAR_OREFLAG(&huart1); | |||||
| __HAL_UART_CLEAR_NEFLAG(&huart1); | |||||
| __HAL_UART_CLEAR_FEFLAG(&huart1); | |||||
| while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET) | |||||
| { | |||||
| (void)huart1.Instance->DR; | |||||
| } | |||||
| } | |||||
| void PortClearFramePending(void) | |||||
| { | |||||
| g_modbus_frame_pending = 0U; | |||||
| } | |||||
| static void RestartReceive(void) | |||||
| { | |||||
| FlushUartRx(); | |||||
| huart1.RxState = HAL_UART_STATE_READY; | |||||
| (void)HAL_UART_Receive_IT(&huart1, &g_rxByte, 1U); | |||||
| } | |||||
| void ModbusPortInit(void) | |||||
| { | |||||
| g_modbus_rx_len = 0U; | |||||
| g_modbus_frame_pending = 0U; | |||||
| g_modbus_tim_armed = 0U; | |||||
| g_modbus_rx_sem = OSSemCreate(0U); | |||||
| RestartReceive(); | |||||
| } | |||||
| void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) | |||||
| { | |||||
| if (huart->Instance == USART1) | |||||
| { | |||||
| uint32_t gapTicks; | |||||
| if (g_modbus_frame_pending == 0U) | |||||
| { | |||||
| /* 1.5T 字符间隙:判定上一帧已结束,丢弃半包后重新组帧 */ | |||||
| if (g_modbus_tim_armed != 0U) | |||||
| { | |||||
| gapTicks = __HAL_TIM_GET_COUNTER(&htim2); | |||||
| if (gapTicks >= (uint32_t)MODBUS_T15_TICKS) | |||||
| { | |||||
| g_modbus_rx_len = 0U; | |||||
| } | |||||
| } | |||||
| if (g_modbus_rx_len < MODBUS_RX_BUF_LEN) | |||||
| { | |||||
| g_modbus_rx_buf[g_modbus_rx_len] = g_rxByte; | |||||
| g_modbus_rx_len++; | |||||
| } | |||||
| } | |||||
| __HAL_TIM_SET_COUNTER(&htim2, 0U); | |||||
| if (g_modbus_tim_armed == 0U) | |||||
| { | |||||
| g_modbus_tim_armed = 1U; | |||||
| __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); | |||||
| (void)HAL_TIM_Base_Start_IT(&htim2); | |||||
| } | |||||
| (void)HAL_UART_Receive_IT(&huart1, &g_rxByte, 1U); | |||||
| } | |||||
| } | |||||
| void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) | |||||
| { | |||||
| if (huart->Instance == USART1) | |||||
| { | |||||
| RestartReceive(); | |||||
| } | |||||
| } | |||||
| void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) | |||||
| { | |||||
| if (htim->Instance == TIM2) | |||||
| { | |||||
| (void)HAL_TIM_Base_Stop_IT(&htim2); | |||||
| g_modbus_tim_armed = 0U; | |||||
| if ((g_modbus_rx_sem != (OS_EVENT *)0) && | |||||
| (g_modbus_rx_len > 0U) && | |||||
| (g_modbus_frame_pending == 0U)) | |||||
| { | |||||
| g_modbus_frame_pending = 1U; | |||||
| (void)OSSemPost(g_modbus_rx_sem); | |||||
| } | |||||
| } | |||||
| else if (PlsrPulseDriverIsPulseTim(htim->Instance) != 0U) | |||||
| { | |||||
| /* 每个 PWM 周期溢出 = 1 个脉冲(TIM10/11/13) */ | |||||
| PlsrOnPulseIsr(); | |||||
| } | |||||
| } | |||||
| uint16_t ModbusCrc16(uint8_t *data, uint16_t len) | |||||
| { | |||||
| uint16_t crc = 0xFFFFU; | |||||
| uint16_t i; | |||||
| uint16_t j; | |||||
| for (i = 0U; i < len; i++) | |||||
| { | |||||
| crc ^= data[i]; | |||||
| for (j = 0U; j < 8U; j++) | |||||
| { | |||||
| if ((crc & 0x0001U) != 0U) | |||||
| { | |||||
| crc = (uint16_t)((crc >> 1) ^ 0xA001U); | |||||
| } | |||||
| else | |||||
| { | |||||
| crc >>= 1; | |||||
| } | |||||
| } | |||||
| } | |||||
| return crc; | |||||
| } | |||||
| void SendData(uint8_t *buf, uint16_t len) | |||||
| { | |||||
| OS_CPU_SR cpu_sr; | |||||
| volatile uint32_t turnaround; | |||||
| (void)HAL_UART_AbortReceive_IT(&huart1); | |||||
| StopFrameTimer(); | |||||
| OS_ENTER_CRITICAL(); | |||||
| g_modbus_rx_len = 0U; | |||||
| OS_EXIT_CRITICAL(); | |||||
| (void)HAL_UART_Transmit(&huart1, buf, len, 200U); | |||||
| while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET) | |||||
| { | |||||
| } | |||||
| for (turnaround = 0U; turnaround < 8000U; turnaround++) | |||||
| { | |||||
| } | |||||
| FlushUartRx(); | |||||
| RestartReceive(); | |||||
| } | |||||
| @@ -1,17 +0,0 @@ | |||||
| /** | |||||
| * @file modbus_port.h | |||||
| * @brief Modbus RTU 链路端口:UART/TIM 定帧/CRC/发送 | |||||
| */ | |||||
| #ifndef MODBUS_PORT_H | |||||
| #define MODBUS_PORT_H | |||||
| #include <stdint.h> | |||||
| void PortClearFramePending(void); | |||||
| void ModbusPortInit(void); | |||||
| /* 保留空实现,兼容旧调用点(已取消指示灯) */ | |||||
| void LedErrorBlink(void); | |||||
| #endif | |||||
| @@ -8,13 +8,52 @@ | |||||
| */ | */ | ||||
| #include "modbus_rtu.h" | #include "modbus_rtu.h" | ||||
| #include "modbus_data.h" | |||||
| #include "modbus_port.h" | |||||
| #include "modbus_fc.h" | |||||
| #include <string.h> | #include <string.h> | ||||
| #include "plsr.h" | |||||
| #include "plsr_pulse_driver.h" | |||||
| #include "gpio.h" | |||||
| #include "plsr_param.h" | |||||
| #include "plsr_command.h" | |||||
| #include "plsr_persist.h" | |||||
| static uint8_t g_modbus_proc_buf[MODBUS_RX_BUF_LEN]; | static uint8_t g_modbus_proc_buf[MODBUS_RX_BUF_LEN]; | ||||
| /* 保持寄存器数据映像 */ | |||||
| uint16_t g_hold_reg[HOLD_REG_BUFFER_SIZE] = {0}; | |||||
| /*应答帧组帧缓冲 */ | |||||
| static uint8_t g_modbus_tx_buf[MODBUS_TX_BUF_LEN]; | |||||
| /* 0 = 广播禁止回帧;1 = 正常回帧 */ | |||||
| static uint8_t g_modbus_reply_enable = 1U; | |||||
| /* 当前请求从站地址(异常帧使用) */ | |||||
| static uint8_t g_modbus_current_addr = MODBUS_SLAVE_ADDR; | |||||
| uint8_t g_modbus_rx_buf[MODBUS_RX_BUF_LEN] = {0}; | |||||
| volatile uint16_t g_modbus_rx_len = 0U; | |||||
| OS_EVENT *g_modbus_rx_sem = (OS_EVENT *)0; | |||||
| static uint8_t g_rxByte; | |||||
| static volatile uint8_t g_modbus_frame_pending = 0U; | |||||
| static volatile uint8_t g_modbus_tim_armed = 0U; | |||||
| static void AppendCrcAndSend(uint8_t *pdu, uint16_t pduLen); | |||||
| static void SendException(uint8_t funcCode, uint8_t exCode); | |||||
| static void ReadHoldingRegs(const uint8_t *frame, uint16_t len); | |||||
| static void WriteSingleReg(const uint8_t *frame, uint16_t len); | |||||
| static void WriteMultiRegs(const uint8_t *frame, uint16_t len); | |||||
| static void StopFrameTimer(void); | |||||
| static void FlushUartRx(void); | |||||
| static void RestartReceive(void); | |||||
| static uint16_t ModbusCrc16(uint8_t *data, uint16_t len); | |||||
| static void SendData(uint8_t *buf, uint16_t len); | |||||
| //static void ModbusDataInit(void); | |||||
| static uint16_t ModbusMapRegAddr(uint16_t addr); | |||||
| static uint8_t ModbusIsRegRangeValid(uint16_t addr, uint16_t qty); | |||||
| static uint16_t ReadU16Be(const uint8_t *data); | |||||
| static void ReplyContext(uint8_t slaveAddr, uint8_t replyEnable); | |||||
| static void ProcessRequest(const uint8_t *frame, uint16_t len); | |||||
| /** | /** | ||||
| * @brief 从站任务:阻塞等待完整帧,校验后分发处理 | * @brief 从站任务:阻塞等待完整帧,校验后分发处理 | ||||
| */ | */ | ||||
| @@ -29,7 +68,12 @@ void ModbusSlaveTask(void *pArg) | |||||
| (void)pArg; | (void)pArg; | ||||
| ModbusDataInit(); | |||||
| /* | |||||
| * ModbusDataInit 会清零 g_hold_reg;上电后仅从 BKP 恢复参数/监控到 hold, | |||||
| * 不有点多此一举,直接不初始化g_hold_reg。 | |||||
| */ | |||||
| // ModbusDataInit(); | |||||
| // PlsrPersistLoad(); | |||||
| ModbusPortInit(); | ModbusPortInit(); | ||||
| while (1) | while (1) | ||||
| @@ -48,12 +92,12 @@ void ModbusSlaveTask(void *pArg) | |||||
| } | } | ||||
| memcpy(g_modbus_proc_buf, g_modbus_rx_buf, frameLen); | memcpy(g_modbus_proc_buf, g_modbus_rx_buf, frameLen); | ||||
| g_modbus_rx_len = 0U; | g_modbus_rx_len = 0U; | ||||
| PortClearFramePending(); | |||||
| /*清除帧挂起*/ | |||||
| g_modbus_frame_pending = 0U; | |||||
| OS_EXIT_CRITICAL(); | OS_EXIT_CRITICAL(); | ||||
| if (frameLen < 4U) | if (frameLen < 4U) | ||||
| { | { | ||||
| LedErrorBlink(); | |||||
| continue; | continue; | ||||
| } | } | ||||
| @@ -62,7 +106,6 @@ void ModbusSlaveTask(void *pArg) | |||||
| crcCalc = ModbusCrc16(g_modbus_proc_buf, (uint16_t)(frameLen - 2U)); | crcCalc = ModbusCrc16(g_modbus_proc_buf, (uint16_t)(frameLen - 2U)); | ||||
| if (crcRecv != crcCalc) | if (crcRecv != crcCalc) | ||||
| { | { | ||||
| LedErrorBlink(); | |||||
| continue; | continue; | ||||
| } | } | ||||
| @@ -76,3 +119,570 @@ void ModbusSlaveTask(void *pArg) | |||||
| ProcessRequest(g_modbus_proc_buf, frameLen); | ProcessRequest(g_modbus_proc_buf, frameLen); | ||||
| } | } | ||||
| } | } | ||||
| /*============================================================================*/ | |||||
| /* 初始化 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 初始化保持寄存器映像 | |||||
| * @retval None | |||||
| */ | |||||
| //void ModbusDataInit(void) | |||||
| //{ | |||||
| // memset(g_hold_reg, 0, sizeof(g_hold_reg)); | |||||
| //} | |||||
| /*============================================================================*/ | |||||
| /* 地址映射与区间校验 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 将保持寄存器地址映射为本地映像下标 | |||||
| * @param[in] addr 寄存器地址 | |||||
| * @retval 合法时返回下标;非法返回 MODBUS_ADDR_INVALID | |||||
| */ | |||||
| uint16_t ModbusMapRegAddr(uint16_t addr) | |||||
| { | |||||
| return (addr <= MODBUS_MB_MAXIMUM) ? addr : MODBUS_ADDR_INVALID; | |||||
| } | |||||
| /** | |||||
| * @brief 校验保持寄存器连续区间是否全部合法 | |||||
| * @param[in] addr 起始地址 | |||||
| * @param[in] qty 数量(须 ≥ 1) | |||||
| * @retval 1 合法;0 非法 | |||||
| */ | |||||
| uint8_t ModbusIsRegRangeValid(uint16_t addr, uint16_t qty) | |||||
| { | |||||
| uint32_t endAddr; | |||||
| if ((qty == 0U) || (ModbusMapRegAddr(addr) == MODBUS_ADDR_INVALID)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| endAddr = (uint32_t)addr + (uint32_t)qty - 1U; | |||||
| if (endAddr > (uint32_t)MODBUS_MB_MAXIMUM) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return 1U; | |||||
| } | |||||
| /** | |||||
| * @brief 从缓冲读取大端序 uint16 | |||||
| * @param[in] data 至少 2字节,高字节在前 | |||||
| * @retval 解析得到的 16位无符号值 | |||||
| */ | |||||
| uint16_t ReadU16Be(const uint8_t *data) | |||||
| { | |||||
| return (uint16_t)(((uint16_t)data[0] << 8) | data[1]); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 公共映像访问 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 读保持寄存器映像 | |||||
| * @param[in] addr 寄存器地址 | |||||
| * @retval 寄存器值;地址非法时返回 0 | |||||
| */ | |||||
| uint16_t ReadHoldReg(uint16_t addr) | |||||
| { | |||||
| uint16_t offset; | |||||
| offset = ModbusMapRegAddr(addr); | |||||
| if (offset == MODBUS_ADDR_INVALID) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return g_hold_reg[offset]; | |||||
| } | |||||
| /** | |||||
| * @brief 写保持寄存器映像 | |||||
| * @param[in] addr 寄存器地址 | |||||
| * @param[in] val 写入值 | |||||
| * @retval None | |||||
| */ | |||||
| void WriteHoldReg(uint16_t addr, uint16_t val) | |||||
| { | |||||
| uint16_t offset; | |||||
| offset = ModbusMapRegAddr(addr); | |||||
| if (offset == MODBUS_ADDR_INVALID) | |||||
| { | |||||
| return; | |||||
| } | |||||
| g_hold_reg[offset] = val; | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 请求处理 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 拼装响应帧 | |||||
| * @param[in] slaveAddr 请求中的从站地址 | |||||
| * @param[in] replyEnable 0 = 广播不回帧;1 = 正常回帧 | |||||
| */ | |||||
| void ReplyContext(uint8_t slaveAddr, uint8_t replyEnable) | |||||
| { | |||||
| g_modbus_current_addr = slaveAddr; | |||||
| g_modbus_reply_enable = replyEnable; | |||||
| } | |||||
| /** | |||||
| * @brief 按功能码分发到对应处理函数 | |||||
| * @param[in] frame 完整 ADU(含 CRC) | |||||
| * @param[in] len ADU 长度 | |||||
| */ | |||||
| void ProcessRequest(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint8_t funcCode; | |||||
| if ((frame == NULL) || (len < 4U)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| funcCode = frame[1]; | |||||
| switch (funcCode) | |||||
| { | |||||
| case READ_HOLDING_REGS: | |||||
| ReadHoldingRegs(frame, len); | |||||
| break; | |||||
| case WRITE_SINGLE_REG: | |||||
| WriteSingleReg(frame, len); | |||||
| break; | |||||
| case WRITE_MULTI_REGS: | |||||
| WriteMultiRegs(frame, len); | |||||
| break; | |||||
| default: | |||||
| SendException(funcCode, ILLEGAL_FUNCTION); | |||||
| break; | |||||
| } | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 功能码 03:读保持寄存器 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 处理读保持寄存器请求(0x03) | |||||
| * @param[in] frame 完整请求 ADU | |||||
| * @param[in] len 合法请求固定为 8 | |||||
| */ | |||||
| static void ReadHoldingRegs(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint16_t startAddr; | |||||
| uint16_t quantity; | |||||
| uint16_t byteCount; | |||||
| uint16_t i; | |||||
| uint16_t regVal; | |||||
| if (len != 8U) | |||||
| { | |||||
| SendException(READ_HOLDING_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| startAddr = ReadU16Be(&frame[2]); | |||||
| quantity = ReadU16Be(&frame[4]); | |||||
| if ((quantity < 1U) || (quantity > MAX_READ_REGS)) | |||||
| { | |||||
| SendException(READ_HOLDING_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| if (ModbusIsRegRangeValid(startAddr, quantity) == 0U) | |||||
| { | |||||
| SendException(READ_HOLDING_REGS, ILLEGAL_DATA_ADDRESS); | |||||
| return; | |||||
| } | |||||
| byteCount = (uint16_t)(quantity * 2U); | |||||
| g_modbus_tx_buf[0] = frame[0]; | |||||
| g_modbus_tx_buf[1] = READ_HOLDING_REGS; | |||||
| g_modbus_tx_buf[2] = (uint8_t)byteCount; | |||||
| for (i = 0U; i < quantity; i++) | |||||
| { | |||||
| regVal = ReadHoldReg((uint16_t)(startAddr + i)); | |||||
| g_modbus_tx_buf[3U + (i * 2U)] = (uint8_t)(regVal >> 8); | |||||
| g_modbus_tx_buf[4U + (i * 2U)] = (uint8_t)(regVal & 0xFFU); | |||||
| } | |||||
| AppendCrcAndSend(g_modbus_tx_buf, (uint16_t)(3U + byteCount)); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 功能码 06:写单个保持寄存器 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 处理写单个保持寄存器请求(0x06) | |||||
| * @param[in] frame 完整请求 ADU | |||||
| * @param[in] len 合法请求固定为 8 | |||||
| * | |||||
| * 读写都经 WriteHoldReg / ReadHoldReg,落在 g_hold_reg[]。 | |||||
| * 应答回显地址与写入值。 | |||||
| */ | |||||
| static void WriteSingleReg(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint16_t addr; | |||||
| uint16_t value; | |||||
| if (len != 8U) | |||||
| { | |||||
| SendException(WRITE_SINGLE_REG, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| addr = ReadU16Be(&frame[2]); | |||||
| value = ReadU16Be(&frame[4]); | |||||
| if (ModbusIsRegRangeValid(addr, 1U) == 0U) | |||||
| { | |||||
| SendException(WRITE_SINGLE_REG, ILLEGAL_DATA_ADDRESS); | |||||
| return; | |||||
| } | |||||
| WriteHoldReg(addr, value); | |||||
| g_modbus_tx_buf[0] = frame[0]; | |||||
| g_modbus_tx_buf[1] = WRITE_SINGLE_REG; | |||||
| g_modbus_tx_buf[2] = frame[2]; | |||||
| g_modbus_tx_buf[3] = frame[3]; | |||||
| g_modbus_tx_buf[4] = frame[4]; | |||||
| g_modbus_tx_buf[5] = frame[5]; | |||||
| AppendCrcAndSend(g_modbus_tx_buf, 6U); | |||||
| PlsrParamBlockOnHoldWrite(addr, 1U); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 功能码 10:写多个保持寄存器 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 处理写多个保持寄存器请求(0x10) | |||||
| * @param[in] frame 完整请求 ADU | |||||
| * @param[in] len 等于 7 + ByteCount + 2 | |||||
| * @retval None | |||||
| */ | |||||
| static void WriteMultiRegs(const uint8_t *frame, uint16_t len) | |||||
| { | |||||
| uint16_t startAddr; | |||||
| uint16_t quantity; | |||||
| uint8_t byteCount; | |||||
| uint16_t expectedLen; | |||||
| uint16_t i; | |||||
| if (len < 9U) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| startAddr = ReadU16Be(&frame[2]); | |||||
| quantity = ReadU16Be(&frame[4]); | |||||
| byteCount = frame[6]; | |||||
| if ((quantity < 1U) || (quantity > MAX_WRITE_REGS)) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| if (byteCount != (uint8_t)(quantity * 2U)) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| expectedLen = (uint16_t)(7U + byteCount + 2U); | |||||
| if (len != expectedLen) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE); | |||||
| return; | |||||
| } | |||||
| if (ModbusIsRegRangeValid(startAddr, quantity) == 0U) | |||||
| { | |||||
| SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_ADDRESS); | |||||
| return; | |||||
| } | |||||
| for (i = 0U; i < quantity; i++) | |||||
| { | |||||
| WriteHoldReg((uint16_t)(startAddr + i), | |||||
| ReadU16Be(&frame[7U + (i * 2U)])); | |||||
| } | |||||
| /* 先回 FC10 应答,再做分帧计数(避免应答被后续逻辑拖住) */ | |||||
| g_modbus_tx_buf[0] = frame[0]; | |||||
| g_modbus_tx_buf[1] = WRITE_MULTI_REGS; | |||||
| g_modbus_tx_buf[2] = frame[2]; | |||||
| g_modbus_tx_buf[3] = frame[3]; | |||||
| g_modbus_tx_buf[4] = frame[4]; | |||||
| g_modbus_tx_buf[5] = frame[5]; | |||||
| AppendCrcAndSend(g_modbus_tx_buf, 6U); | |||||
| /*统计接受帧数,收满释放信号量*/ | |||||
| PlsrParamBlockOnHoldWrite(startAddr, quantity); | |||||
| /* 动态改频率 */ | |||||
| PlsrCommandOnSegFreqHoldWrite(startAddr, quantity); | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 异常应答与发送 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 组并发送异常应答 | |||||
| * @param[in] funcCode 原功能码(未置位 0x80) | |||||
| * @param[in] exCode 异常码 01/02/03 | |||||
| */ | |||||
| static void SendException(uint8_t funcCode, uint8_t exCode) | |||||
| { | |||||
| g_modbus_tx_buf[0] = g_modbus_current_addr; | |||||
| g_modbus_tx_buf[1] = (uint8_t)(funcCode | 0x80U); | |||||
| g_modbus_tx_buf[2] = exCode; | |||||
| AppendCrcAndSend(g_modbus_tx_buf, 3U); | |||||
| } | |||||
| /** | |||||
| * @brief 添加CRC | |||||
| * @param[in] frame PDU | |||||
| * @param[in] len 根据协议规定传入对应字节数 | |||||
| * @retval None | |||||
| */ | |||||
| static void AppendCrcAndSend(uint8_t *pdu, uint16_t pduLen) | |||||
| { | |||||
| uint16_t crc; | |||||
| if ((pdu == NULL) || (pduLen == 0U) || | |||||
| ((uint16_t)(pduLen + 2U) > MODBUS_TX_BUF_LEN)) | |||||
| { | |||||
| return; | |||||
| } | |||||
| if (g_modbus_reply_enable == 0U) | |||||
| { | |||||
| return; | |||||
| } | |||||
| crc = ModbusCrc16(pdu, pduLen); | |||||
| pdu[pduLen] = (uint8_t)(crc & 0xFFU); | |||||
| pdu[pduLen + 1U] = (uint8_t)(crc >> 8); | |||||
| SendData(pdu, (uint16_t)(pduLen + 2U)); | |||||
| } | |||||
| /** | |||||
| * @brief 停止帧定时器 | |||||
| * @retval None | |||||
| */ | |||||
| static void StopFrameTimer(void) | |||||
| { | |||||
| (void)HAL_TIM_Base_Stop_IT(&htim2); | |||||
| g_modbus_tim_armed = 0U; | |||||
| __HAL_TIM_SET_COUNTER(&htim2, 0U); | |||||
| __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); | |||||
| } | |||||
| /** | |||||
| * @brief 清掉串口接收标志 | |||||
| * @retval None | |||||
| */ | |||||
| static void FlushUartRx(void) | |||||
| { | |||||
| __HAL_UART_CLEAR_OREFLAG(&huart1); | |||||
| __HAL_UART_CLEAR_NEFLAG(&huart1); | |||||
| __HAL_UART_CLEAR_FEFLAG(&huart1); | |||||
| while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET) | |||||
| { | |||||
| (void)huart1.Instance->DR; | |||||
| } | |||||
| } | |||||
| /** | |||||
| * @brief 重启串口接收 | |||||
| * @retval None | |||||
| */ | |||||
| static void RestartReceive(void) | |||||
| { | |||||
| FlushUartRx(); | |||||
| huart1.RxState = HAL_UART_STATE_READY; | |||||
| (void)HAL_UART_Receive_IT(&huart1, &g_rxByte, 1U); | |||||
| } | |||||
| /** | |||||
| * @brief modbus串口初始化 | |||||
| * @retval None | |||||
| */ | |||||
| void ModbusPortInit(void) | |||||
| { | |||||
| g_modbus_rx_len = 0U; | |||||
| g_modbus_frame_pending = 0U; | |||||
| g_modbus_tim_armed = 0U; | |||||
| g_modbus_rx_sem = OSSemCreate(0U); | |||||
| RestartReceive(); | |||||
| } | |||||
| /** | |||||
| * @brief 串口接收中断回调 | |||||
| * @param[in] huart 串口句柄 | |||||
| * @retval None | |||||
| */ | |||||
| void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) | |||||
| { | |||||
| if (huart->Instance == USART1) | |||||
| { | |||||
| uint32_t gapTicks; | |||||
| if (g_modbus_frame_pending == 0U) | |||||
| { | |||||
| /* 1.5T 字符间隙:判定上一帧已结束,丢弃半包后重新组帧 */ | |||||
| if (g_modbus_tim_armed != 0U) | |||||
| { | |||||
| gapTicks = __HAL_TIM_GET_COUNTER(&htim2); | |||||
| if (gapTicks >= (uint32_t)MODBUS_T15_TICKS) | |||||
| { | |||||
| g_modbus_rx_len = 0U; | |||||
| } | |||||
| } | |||||
| if (g_modbus_rx_len < MODBUS_RX_BUF_LEN) | |||||
| { | |||||
| g_modbus_rx_buf[g_modbus_rx_len] = g_rxByte; | |||||
| g_modbus_rx_len++; | |||||
| } | |||||
| } | |||||
| __HAL_TIM_SET_COUNTER(&htim2, 0U); | |||||
| if (g_modbus_tim_armed == 0U) | |||||
| { | |||||
| g_modbus_tim_armed = 1U; | |||||
| __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE); | |||||
| (void)HAL_TIM_Base_Start_IT(&htim2); | |||||
| } | |||||
| (void)HAL_UART_Receive_IT(&huart1, &g_rxByte, 1U); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * @brief 串口异常回调 | |||||
| * @param[in] huart 串口句柄 | |||||
| * @retval None | |||||
| */ | |||||
| void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) | |||||
| { | |||||
| if (huart->Instance == USART1) | |||||
| { | |||||
| RestartReceive(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * @brief 定时器更新(溢出)中断回调函数 | |||||
| * @param[in] htim 定时器句柄指针 | |||||
| * @retval None | |||||
| */ | |||||
| void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) | |||||
| { | |||||
| if (htim->Instance == TIM2) | |||||
| { | |||||
| (void)HAL_TIM_Base_Stop_IT(&htim2); | |||||
| g_modbus_tim_armed = 0U; | |||||
| if ((g_modbus_rx_sem != (OS_EVENT *)0) && | |||||
| (g_modbus_rx_len > 0U) && | |||||
| (g_modbus_frame_pending == 0U)) | |||||
| { | |||||
| g_modbus_frame_pending = 1U; | |||||
| (void)OSSemPost(g_modbus_rx_sem); | |||||
| } | |||||
| } | |||||
| else if (PlsrPulseDriverIsPulseTim(htim->Instance) != 0U) | |||||
| { | |||||
| /* 每个 PWM 周期溢出 = 1 个脉冲(TIM10/11/13) */ | |||||
| PlsrOnPulseIsr(); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * @brief CRC16校验 | |||||
| * @param[in] data 数据 | |||||
| * @param[in] len 字节长度 | |||||
| * @retval crc | |||||
| */ | |||||
| uint16_t ModbusCrc16(uint8_t *data, uint16_t len) | |||||
| { | |||||
| uint16_t crc = 0xFFFFU; | |||||
| uint16_t i; | |||||
| uint16_t j; | |||||
| for (i = 0U; i < len; i++) | |||||
| { | |||||
| crc ^= data[i]; | |||||
| for (j = 0U; j < 8U; j++) | |||||
| { | |||||
| if ((crc & 0x0001U) != 0U) | |||||
| { | |||||
| crc = (uint16_t)((crc >> 1) ^ 0xA001U); | |||||
| } | |||||
| else | |||||
| { | |||||
| crc >>= 1; | |||||
| } | |||||
| } | |||||
| } | |||||
| return crc; | |||||
| } | |||||
| /** | |||||
| * @brief 发送数据 | |||||
| * @param[in] buf 待发送数据 | |||||
| * @param[in] len 待发送数据字节长度 | |||||
| * @retval None | |||||
| */ | |||||
| void SendData(uint8_t *buf, uint16_t len) | |||||
| { | |||||
| OS_CPU_SR cpu_sr; | |||||
| volatile uint32_t turnaround; | |||||
| (void)HAL_UART_AbortReceive_IT(&huart1); | |||||
| StopFrameTimer(); | |||||
| OS_ENTER_CRITICAL(); | |||||
| g_modbus_rx_len = 0U; | |||||
| OS_EXIT_CRITICAL(); | |||||
| (void)HAL_UART_Transmit(&huart1, buf, len, 200U); | |||||
| while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET) | |||||
| { | |||||
| } | |||||
| for (turnaround = 0U; turnaround < 8000U; turnaround++) | |||||
| { | |||||
| } | |||||
| FlushUartRx(); | |||||
| RestartReceive(); | |||||
| } | |||||
| @@ -36,23 +36,15 @@ | |||||
| /* 地址范围(M0/D0 起,地址直接当下标) */ | /* 地址范围(M0/D0 起,地址直接当下标) */ | ||||
| /*============================================================================*/ | /*============================================================================*/ | ||||
| /* 线圈/保持寄存器最大协议地址 */ | |||||
| /* 保持寄存器最大协议地址 */ | |||||
| #define MODBUS_MB_MAXIMUM 0x3000U | #define MODBUS_MB_MAXIMUM 0x3000U | ||||
| /* 实际能存储的最大数据个数 */ | /* 实际能存储的最大数据个数 */ | ||||
| #define MODBUS_POINT_COUNT (MODBUS_MB_MAXIMUM + 1U) | #define MODBUS_POINT_COUNT (MODBUS_MB_MAXIMUM + 1U) | ||||
| /* 线圈最大协议地址 */ | |||||
| #define MODBUS_MAX_COIL_ADDR MODBUS_MB_MAXIMUM | |||||
| /*保持寄存器最大协议地址 */ | /*保持寄存器最大协议地址 */ | ||||
| #define MODBUS_MAX_REG_ADDR MODBUS_MB_MAXIMUM | #define MODBUS_MAX_REG_ADDR MODBUS_MB_MAXIMUM | ||||
| /* 线圈起始地址、数量、末位地址 */ | |||||
| #define MODBUS_M_COIL_BASE 0x0000U | |||||
| #define MODBUS_M_COIL_COUNT MODBUS_POINT_COUNT | |||||
| #define MODBUS_M_COIL_LAST MODBUS_MB_MAXIMUM | |||||
| /* 保持寄存器起始地址、数量、末位地址*/ | /* 保持寄存器起始地址、数量、末位地址*/ | ||||
| #define MODBUS_D_REG_BASE 0x0000U | #define MODBUS_D_REG_BASE 0x0000U | ||||
| #define MODBUS_D_REG_COUNT MODBUS_POINT_COUNT | #define MODBUS_D_REG_COUNT MODBUS_POINT_COUNT | ||||
| @@ -62,42 +54,34 @@ | |||||
| /* Modbus 应用层数量上限 */ | /* Modbus 应用层数量上限 */ | ||||
| /*============================================================================*/ | /*============================================================================*/ | ||||
| /* 单次可读线圈数量上限 */ | |||||
| #define MAX_READ_COILS 2000U | |||||
| /* 单次可读保持寄存器数量上限 */ | /* 单次可读保持寄存器数量上限 */ | ||||
| #define MAX_READ_REGS 125U | #define MAX_READ_REGS 125U | ||||
| /* 单次可写线圈数量上限 */ | |||||
| #define MAX_WRITE_COILS 1968U | |||||
| /* 单次可写保持寄存器数量上限 */ | /* 单次可写保持寄存器数量上限 */ | ||||
| #define MAX_WRITE_REGS 123U | #define MAX_WRITE_REGS 123U | ||||
| /* 线圈映像数组长度 */ | |||||
| #define COIL_BUFFER_SIZE MODBUS_POINT_COUNT | |||||
| /* 保持寄存器映像数组长度 */ | /* 保持寄存器映像数组长度 */ | ||||
| #define HOLD_REG_BUFFER_SIZE MODBUS_POINT_COUNT | #define HOLD_REG_BUFFER_SIZE MODBUS_POINT_COUNT | ||||
| extern UART_HandleTypeDef huart1; | extern UART_HandleTypeDef huart1; | ||||
| extern TIM_HandleTypeDef htim2; | extern TIM_HandleTypeDef htim2; | ||||
| /* 接收原始请求帧的缓冲区、任务侧拷贝到处理缓冲后清空长度 | |||||
| */ | |||||
| extern uint8_t g_modbus_rx_buf[MODBUS_RX_BUF_LEN]; | |||||
| /* 功能码 */ | |||||
| #define READ_HOLDING_REGS 0x03U | |||||
| #define WRITE_SINGLE_REG 0x06U | |||||
| #define WRITE_MULTI_REGS 0x10U | |||||
| /* 前接收缓冲中已收到的字节数,注意临界区 */ | |||||
| extern volatile uint16_t g_modbus_rx_len; | |||||
| /* 地址映射失败时的哨兵值 */ | |||||
| #define MODBUS_ADDR_INVALID 0xFFFFU | |||||
| /* TIM2 空闲超时后 OSSemPost;ModbusSlaveTask 中 OSSemPend */ | |||||
| extern OS_EVENT *g_modbus_rx_sem; | |||||
| /* 异常码 01:非法功能 */ | |||||
| #define ILLEGAL_FUNCTION 0x01U | |||||
| /* 线圈数据映像 */ | |||||
| extern uint8_t g_coil_buf[COIL_BUFFER_SIZE]; | |||||
| /* 异常码 02:非法数据地址 */ | |||||
| #define ILLEGAL_DATA_ADDRESS 0x02U | |||||
| /* 保持寄存器数据映像 */ | |||||
| extern uint16_t g_hold_reg[HOLD_REG_BUFFER_SIZE]; | |||||
| /* 异常码 03:非法数据值 */ | |||||
| #define ILLEGAL_DATA_VALUE 0x03U | |||||
| /*============================================================================*/ | /*============================================================================*/ | ||||
| /* 公共函数 */ | /* 公共函数 */ | ||||
| @@ -112,46 +96,6 @@ extern uint16_t g_hold_reg[HOLD_REG_BUFFER_SIZE]; | |||||
| */ | */ | ||||
| void ModbusSlaveTask(void *pArg); | void ModbusSlaveTask(void *pArg); | ||||
| /** | |||||
| * @brief 计算 Modbus RTU CRC16 | |||||
| * @param[in] data 待校验数据指针(不含CRC字段) | |||||
| * @param[in] len 数据长度(字节) | |||||
| * @retval CRC16 | |||||
| * @note 低字节在前、高字节在后 | |||||
| */ | |||||
| uint16_t ModbusCrc16(uint8_t *data, uint16_t len); | |||||
| /** | |||||
| * @brief 阻塞发送一帧完整 ADU | |||||
| * @details 发送前中止接收并清空接收长度,发送完成后等待 TC,短延时换向, | |||||
| * 再恢复中断接收,避免自发自收污染缓冲。 | |||||
| * @param[in] buf 待发送缓冲区(含 CRC) | |||||
| * @param[in] len 发送长度(字节) | |||||
| * @retval None | |||||
| */ | |||||
| void SendData(uint8_t *buf, uint16_t len); | |||||
| /** | |||||
| * @brief 读线圈映像 | |||||
| * @param[in] addr 协议线圈地址 | |||||
| * @retval 1 线圈为 ON | |||||
| * @retval 0 线圈为 OFF,或地址非法 | |||||
| */ | |||||
| uint8_t ReadCoil(uint16_t addr); | |||||
| /** | |||||
| * @brief 写线圈映像 | |||||
| * @param[in] addr 协议线圈地址 | |||||
| * @param[in] val 非0写ON,0写OFF;地址非法则忽略 | |||||
| * @retval None | |||||
| */ | |||||
| void WriteCoil(uint16_t addr, uint8_t val); | |||||
| /** | |||||
| * @brief 读保持寄存器映像 | |||||
| * @param[in] addr 协议寄存器地址 | |||||
| * @retval 寄存器值;地址非法时返回 0 | |||||
| */ | |||||
| uint16_t ReadHoldReg(uint16_t addr); | uint16_t ReadHoldReg(uint16_t addr); | ||||
| /** | /** | ||||
| @@ -161,4 +105,9 @@ uint16_t ReadHoldReg(uint16_t addr); | |||||
| */ | */ | ||||
| void WriteHoldReg(uint16_t addr, uint16_t val); | void WriteHoldReg(uint16_t addr, uint16_t val); | ||||
| /** | |||||
| * @brief 端口初始化 | |||||
| */ | |||||
| void ModbusPortInit(void); | |||||
| #endif | #endif | ||||
| @@ -1,12 +1,9 @@ | |||||
| /** | /** | ||||
| * @file plsr_accel_curve.h | * @file plsr_accel_curve.h | ||||
| * @brief 预估加/匀/减脉冲;相内按时间走直线/S/正弦 | |||||
| * @brief 脉冲域加/匀/减规划与取频(运动学:v^2 - v0^2 = 2ax) | |||||
| * | * | ||||
| * 1ms 改频:脉冲数按 t=0..T-1 实际取频累加;到不了目标频则降峰值走三角(无匀速)。 | |||||
| * 匀速仅当加速+减速脉冲之和 < 总脉冲(真正到达目标频)时出现。 | |||||
| * 斜率按公共参数:K_acc=默认速度/加速时间,K_dec=默认速度/减速时间(减 0,不用起/止速)。 | |||||
| * 小频差时 T 可能 <1ms,无可见斜坡属正常。 | |||||
| * 开段端点由运行层 ResolveStart/EndHz 解析后再传入 Plan(Plan 不解释策略 0)。 | |||||
| * @details 按起/峰/止频率、斜率时间与总脉冲规划三相脉冲预算,并在脉冲序号上取频。 | |||||
| * 不读 wait_type,不碰 TIM/GPIO。 | |||||
| */ | */ | ||||
| #ifndef PLSR_ACCEL_CURVE_H | #ifndef PLSR_ACCEL_CURVE_H | ||||
| #define PLSR_ACCEL_CURVE_H | #define PLSR_ACCEL_CURVE_H | ||||
| @@ -14,120 +11,90 @@ | |||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #include "plsr_param.h" | #include "plsr_param.h" | ||||
| typedef struct { | |||||
| uint32_t acc_n; | |||||
| uint32_t const_n; | |||||
| uint32_t dec_n; | |||||
| uint32_t f_cur; | |||||
| uint32_t f_tgt; | |||||
| uint32_t f_end; | |||||
| uint32_t t_acc_ms; | |||||
| uint32_t t_dec_ms; | |||||
| uint32_t t_decel_start_ms; /* 反向规划:段内时间轴上进入减速的时刻 */ | |||||
| PlsrAccelMode_e mode; | |||||
| } PlsrAccelPlan_t; | |||||
| typedef enum { | |||||
| PLSR_CURVE_PHASE_ACC = 0, | |||||
| PLSR_CURVE_PHASE_CONST, | |||||
| PLSR_CURVE_PHASE_DEC | |||||
| } PlsrCurvePhase_e; | |||||
| uint32_t PlsrAccelCurveClampFreq(uint32_t freq_hz); | |||||
| /** | /** | ||||
| * 起跳频率:f_jump = sqrt(加速度_Hz/s)。 | |||||
| * 起速/止速为 0 时,第一个脉冲(或最后一个脉冲)应以此频率输出, | |||||
| * 使脉冲周期恰好等于加速度产生该频率所需的时间。 | |||||
| * 单段脉冲域规划结果(PlsrAccelCurvePlan 输出) | |||||
| * run_control 进入减速时使用局部规划副本,不会改写这里的整段规划预算。 | |||||
| */ | */ | ||||
| uint32_t PlsrAccelCurveJumpFreq(uint32_t f_from, uint32_t f_to, | |||||
| uint32_t t_ms); | |||||
| typedef struct { | |||||
| uint32_t accel_pulses; /* 入口到目标频率需要的脉冲数 */ | |||||
| uint32_t const_pulses; /* 保持目标频率的脉冲数 */ | |||||
| uint32_t decel_pulses; /* 目标到出口频率需要的脉冲数 */ | |||||
| uint32_t start_freq_hz; /* 本段实际入口频率 */ | |||||
| uint32_t target_freq_hz; /* 实际目标频率,短段可能被降低 */ | |||||
| uint32_t end_freq_hz; /* 本段出口频率 */ | |||||
| uint32_t accel_rate_hz_per_s; /* 升高频率时使用的变化率 */ | |||||
| uint32_t decel_rate_hz_per_s; /* 降低频率时使用的变化率 */ | |||||
| PlsrAccelMode_e curve_mode; /* 直线 / S / 正弦 */ | |||||
| } PlsrAccelPlan_t; | |||||
| /** | |||||
| * 配置起速 → 规划用真实起点频率。 | |||||
| * f_cfg>=1:原样(钳位);f_cfg==0:按斜率算起跳频率(从静止爬升)。 | |||||
| * 由运行层在开段前调用;Plan 只吃已经解析好的端点。 | |||||
| */ | |||||
| uint32_t PlsrAccelCurveResolveStartHz(uint32_t f_cfg, | |||||
| uint32_t f_tgt, | |||||
| uint32_t default_spd, | |||||
| uint32_t start_spd_ref, | |||||
| uint32_t end_spd_ref, | |||||
| uint32_t accel_ms, | |||||
| uint32_t decel_ms); | |||||
| /** 配置起速 → 规划入口:起速>目标用起速;否则目标与起跳比较取其一 */ | |||||
| uint32_t PlsrAccelCurveResolveStartHz(uint32_t configured_freq_hz, | |||||
| uint32_t target_freq_hz, | |||||
| uint32_t default_speed_hz, | |||||
| uint32_t accel_time_ms, | |||||
| uint32_t decel_time_ms); | |||||
| /** | |||||
| * 配置止速 → 规划用真实终点频率。 | |||||
| * f_cfg>=1:原样(钳位);f_cfg==0:按斜率算落地频率(避免 1Hz 拖尾)。 | |||||
| * 段末是否停表由运行层决定,与本函数无关。 | |||||
| */ | |||||
| uint32_t PlsrAccelCurveResolveEndHz(uint32_t f_cfg, | |||||
| uint32_t f_tgt, | |||||
| uint32_t default_spd, | |||||
| uint32_t start_spd_ref, | |||||
| uint32_t end_spd_ref, | |||||
| uint32_t accel_ms, | |||||
| uint32_t decel_ms); | |||||
| /** 配置止速 → 规划出口:止速>目标用止速;否则目标与起跳比较取其一(不默认到 0) */ | |||||
| uint32_t PlsrAccelCurveResolveEndHz(uint32_t configured_freq_hz, | |||||
| uint32_t target_freq_hz, | |||||
| uint32_t default_speed_hz, | |||||
| uint32_t accel_time_ms, | |||||
| uint32_t decel_time_ms); | |||||
| /** | /** | ||||
| * 斜坡上按 mode 取频(µs 时间轴)。ISR 可用:O(1)。 | |||||
| * f = f0+(f1-f0)*shape(t/T) | |||||
| * 规划一整段脉冲域三相预算与实际峰值 | |||||
| * 脉冲不够到达目标频率时取消匀速段,再降低实际目标频率。 | |||||
| */ | */ | ||||
| uint32_t PlsrAccelCurveFreqOnRampUs(uint32_t f0, | |||||
| uint32_t f1, | |||||
| uint32_t t_us, | |||||
| uint32_t T_us, | |||||
| PlsrAccelMode_e mode); | |||||
| /** | |||||
| * 与 TIM3 1ms 改频一致的斜坡取频(规划/运行共用)。 | |||||
| */ | |||||
| uint32_t PlsrAccelCurveFreqOnRampMs(uint32_t f0, | |||||
| uint32_t f1, | |||||
| uint32_t t_ms, | |||||
| uint32_t T_ms, | |||||
| PlsrAccelMode_e mode); | |||||
| void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan, | |||||
| uint32_t total_pulses, | |||||
| uint32_t start_freq_hz, | |||||
| uint32_t target_freq_hz, | |||||
| uint32_t end_freq_hz, | |||||
| uint32_t default_speed_hz, | |||||
| uint32_t accel_time_ms, | |||||
| uint32_t decel_time_ms, | |||||
| PlsrAccelMode_e curve_mode); | |||||
| /** | |||||
| * 斜坡第一拍频率(起速为 0 时用自洽点,避免按 1Hz 整秒吃光加速相)。 | |||||
| * ISR 可用:O(1)。 | |||||
| */ | |||||
| uint32_t PlsrAccelCurveFirstPulseFreq(uint32_t f0, | |||||
| uint32_t f1, | |||||
| uint32_t T_us, | |||||
| PlsrAccelMode_e mode); | |||||
| /** 按已完成脉冲数取频(任务/预览;ISR 热路径用 PlsrAccelNextFreq) */ | |||||
| uint32_t PlsrAccelCurveFreqAtPulse(const PlsrAccelPlan_t *plan, | |||||
| uint32_t completed_seg_pulses); | |||||
| /** | /** | ||||
| * 减速落地频率:f_end>=1 即止速;f_end==0 返回 0(停表,不用 1Hz 冒充)。 | |||||
| * 单相逐脉冲运行态(ISR 热路径,每来一个 UPDATE 调 Step 一次) | |||||
| * 直线:每次按目标平方值逐步逼近下一频率 | |||||
| * S/正弦:时间累计 + 257 点 Q24 形状表线性插值 | |||||
| */ | */ | ||||
| uint32_t PlsrAccelCurveLandFreq(uint32_t f_end, | |||||
| uint32_t f_peak, | |||||
| uint32_t T_ms, | |||||
| PlsrAccelMode_e mode); | |||||
| typedef struct { | |||||
| uint32_t cur_freq_hz; /* 当前输出频率 */ | |||||
| uint32_t start_freq_hz; /* 当前相起点频率 */ | |||||
| uint32_t end_freq_hz; /* 当前相终点频率 */ | |||||
| uint32_t accel_hz_per_s; /* 当前相使用的变化率 */ | |||||
| uint32_t completed_pulses; /* 当前相已经步进的脉冲数 */ | |||||
| uint32_t total_pulses; /* 当前相计划步进的脉冲数 */ | |||||
| uint32_t elapsed_time_us; /* 时间曲线已经经过的时间 */ | |||||
| uint32_t jerk_time_us; /* S 曲线单个 jerk 阶段时间 */ | |||||
| uint32_t const_accel_time_us; /* S 曲线恒加速阶段时间 */ | |||||
| uint32_t ramp_time_us; /* 整个斜坡时间 */ | |||||
| uint8_t freq_rising; /* 1=频率升高,0=降低 */ | |||||
| uint8_t is_active; /* 1=当前相仍在运行 */ | |||||
| PlsrAccelMode_e curve_mode; /* 直线 / S / 正弦 */ | |||||
| } PlsrAccelRuntime_t; | |||||
| /** 拟合斜坡时间(ms),使离散模型脉冲数尽量等于 max_pulses(任务上下文) */ | |||||
| uint32_t PlsrAccelCurveFitRampTimeMs(uint32_t f_from, | |||||
| uint32_t f_to, | |||||
| uint32_t max_pulses, | |||||
| uint32_t t_prefer_ms, | |||||
| PlsrAccelMode_e mode); | |||||
| /* 当前段唯一的曲线规划和逐拍状态,run_control 直接使用,不再另存副本。 */ | |||||
| extern PlsrAccelPlan_t g_plsr_accel_plan; /* 本段三相规划结果 */ | |||||
| extern PlsrAccelRuntime_t g_plsr_accel_runtime; /* 当前相 ISR 逐拍状态 */ | |||||
| void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan, | |||||
| uint32_t total_pulses, | |||||
| uint32_t f_cur, | |||||
| uint32_t f_tgt, | |||||
| uint32_t f_end, | |||||
| uint32_t default_spd, | |||||
| uint32_t start_spd_ref, | |||||
| uint32_t end_spd_ref, | |||||
| uint32_t accel_ms, | |||||
| uint32_t decel_ms, | |||||
| PlsrAccelMode_e mode); | |||||
| /** 进入加速相:起→峰;S/正弦在 ISR 内形状表取频 */ | |||||
| void PlsrAccelBeginAccel(PlsrAccelRuntime_t *runtime, | |||||
| const PlsrAccelPlan_t *plan); | |||||
| /** 进入减速相:峰→止;S/正弦在 ISR 内形状表取频 */ | |||||
| void PlsrAccelBeginDecel(PlsrAccelRuntime_t *runtime, | |||||
| const PlsrAccelPlan_t *plan); | |||||
| /** 进入匀速相:钉峰值,ISR 不再改频 */ | |||||
| void PlsrAccelBeginConstSpeed(PlsrAccelRuntime_t *runtime, | |||||
| const PlsrAccelPlan_t *plan); | |||||
| /** | |||||
| * 1ms 改频:按段内绝对时间取频(相界由 t_decel_start_ms 决定,不用脉冲域)。 | |||||
| */ | |||||
| uint32_t PlsrAccelCurveFreqAtSegTime(const PlsrAccelPlan_t *plan, | |||||
| uint32_t seg_elapsed_ms); | |||||
| /** 本相前进 1 脉冲,返回下一拍命令频率 */ | |||||
| uint32_t PlsrAccelNextFreq(PlsrAccelRuntime_t *runtime); | |||||
| #endif | #endif | ||||
| @@ -1,84 +1,236 @@ | |||||
| /** | /** | ||||
| * @file plsr_command.c | * @file plsr_command.c | ||||
| * @brief 指令接口:0x3000 控制位 → 运行控制;监控写回 0x2000~0x2006 | |||||
| * @brief Modbus 指令接口实现 | |||||
| * | |||||
| * @details 模块职责 | |||||
| * 将 Modbus 控制/监控寄存器与 PLSR 运行层对接:解析 0x3000 控制位、 | |||||
| * 周期刷新 0x2000~0x2006、维护故障码镜像、处理运行中动态写频。 | |||||
| * | |||||
| * 故障码数据路径 | |||||
| * 1. run_control 等模块调用 PlsrCommandSetFault(n) → 更新 s_fault + 写 0x2006 | |||||
| * 2. Poll 末尾 PublishMonitor 重发 s_fault,保证寄存器与镜像一致 | |||||
| * 3. 仅改寄存器不改 s_fault 会被下一周期 Poll 覆盖 | |||||
| * 4. START / CLR 控制位处理时 ClearFault(),表示用户确认恢复正常 | |||||
| * | |||||
| * 动态改频路径 | |||||
| * FC10 写当前段 FREQ → OnSegFreqHoldWrite → s_live_freq_req=1 | |||||
| * → Poll 调 PlsrChangeFreq → run_control 重规划剩余脉冲 | |||||
| */ | */ | ||||
| #include "plsr_command.h" | #include "plsr_command.h" | ||||
| #include "plsr_param.h" | #include "plsr_param.h" | ||||
| #include "plsr_run_control.h" | #include "plsr_run_control.h" | ||||
| #include "plsr_pulse_driver.h" | |||||
| #include "plsr.h" | #include "plsr.h" | ||||
| #include "plsr_persist.h" | |||||
| #include "modbus_rtu.h" | #include "modbus_rtu.h" | ||||
| static uint16_t s_fault = PLSR_ERR_NONE; | |||||
| /* | |||||
| * 模块静态状态 | |||||
| * s_fault — 故障码镜像,与 0x2006 同步;告警码持久直到 START/CLR | |||||
| * s_live_freq_req — 运行中写频请求标志,Poll 消费后清 0 | |||||
| * s_live_freq_hz — 待应用的 new_tgt_hz | |||||
| */ | |||||
| static uint16_t s_fault = PLSR_ERR_NONE; /* 故障码镜像,与 0x2006 同步 */ | |||||
| static volatile uint8_t s_live_freq_req; /* 1=运行中改频待 Poll 消费 */ | |||||
| static volatile uint32_t s_live_freq_hz; /* 待应用的新目标频率 Hz */ | |||||
| /** | |||||
| * @brief 写 32 位值到相邻两个modbus保持寄存器 | |||||
| * @param addr 低字寄存器地址 | |||||
| * @param value 32 位值 | |||||
| */ | |||||
| static void PlsrCommandWriteDWord(uint16_t addr, uint32_t value) | static void PlsrCommandWriteDWord(uint16_t addr, uint32_t value) | ||||
| { | { | ||||
| WriteHoldReg(addr, (uint16_t)(value & 0xFFFFU)); | WriteHoldReg(addr, (uint16_t)(value & 0xFFFFU)); | ||||
| WriteHoldReg((uint16_t)(addr + 1U), (uint16_t)((value >> 16) & 0xFFFFU)); | WriteHoldReg((uint16_t)(addr + 1U), (uint16_t)((value >> 16) & 0xFFFFU)); | ||||
| } | } | ||||
| /** | |||||
| * @brief 刷新全部监控寄存器 0x2000~0x2006到modbus保持寄存器 | |||||
| * @note 调用时机:Init 末、Poll 每周期末 | |||||
| * @note 数据源:RunControl / Plsr 任务 + s_fault 镜像 | |||||
| */ | |||||
| static void PlsrCommandPublishMonitor(void) | static void PlsrCommandPublishMonitor(void) | ||||
| { | { | ||||
| PlsrCommandWriteDWord(PLSR_MON_ACC_PULSE_L, (uint32_t)PlsrGetAccPulse()); | |||||
| PlsrCommandWriteDWord(PLSR_MON_FREQ_L, PlsrRunControlGetCurFreq()); | |||||
| WriteHoldReg(PLSR_MON_RUN_STATUS, PlsrIsBusy() ? 1U : 0U); | |||||
| WriteHoldReg(PLSR_MON_CUR_SEG, PlsrRunControlGetCurSeg()); | |||||
| PlsrCommandWriteDWord(PLSR_MON_ACC_PULSE_L, (uint32_t)g_plsr_accumulated_pulses); | |||||
| WriteHoldReg(PLSR_MON_ERR, s_fault); | WriteHoldReg(PLSR_MON_ERR, s_fault); | ||||
| if (g_plsr_busy != 0U) | |||||
| { | |||||
| PlsrCommandWriteDWord(PLSR_MON_FREQ_L, g_plsr_output_freq_hz); /* 0x2002/03 当前频 */ | |||||
| WriteHoldReg(PLSR_MON_RUN_STATUS, 1U); /* 0x2004 运行中 */ | |||||
| WriteHoldReg(PLSR_MON_CUR_SEG, (uint16_t)(g_plsr_cur_seg_idx + 1U)); /* 0x2005 1-based */ | |||||
| } | |||||
| else | |||||
| { | |||||
| /* 空闲/停止:运行、段、频率一律清零;累计与故障码单独维护 */ | |||||
| PlsrCommandWriteDWord(PLSR_MON_FREQ_L, 0U); | |||||
| WriteHoldReg(PLSR_MON_RUN_STATUS, 0U); | |||||
| WriteHoldReg(PLSR_MON_CUR_SEG, 0U); | |||||
| } | |||||
| } | } | ||||
| /** @brief 置故障码 */ | |||||
| void PlsrCommandSetFault(uint16_t code) | void PlsrCommandSetFault(uint16_t code) | ||||
| { | { | ||||
| s_fault = code; | s_fault = code; | ||||
| WriteHoldReg(PLSR_MON_ERR, code); | WriteHoldReg(PLSR_MON_ERR, code); | ||||
| } | } | ||||
| /** @brief 清故障码 */ | |||||
| void PlsrCommandClearFault(void) | void PlsrCommandClearFault(void) | ||||
| { | { | ||||
| s_fault = PLSR_ERR_NONE; | s_fault = PLSR_ERR_NONE; | ||||
| WriteHoldReg(PLSR_MON_ERR, PLSR_ERR_NONE); | WriteHoldReg(PLSR_MON_ERR, PLSR_ERR_NONE); | ||||
| } | } | ||||
| /** @brief persist模块读故障码 */ | |||||
| uint16_t PlsrCommandGetFault(void) | uint16_t PlsrCommandGetFault(void) | ||||
| { | { | ||||
| return s_fault; | return s_fault; | ||||
| } | } | ||||
| /** @brief 参数/重复启动类故障是否禁止 START */ | |||||
| uint8_t PlsrCommandIsStartBlocked(void) | |||||
| { | |||||
| if ((s_fault >= PLSR_ERR_PARAM_BLOCK_MIN) && | |||||
| (s_fault <= PLSR_ERR_PARAM_BLOCK_MAX)) | |||||
| { | |||||
| return 1U; | |||||
| } | |||||
| return 0U; | |||||
| } | |||||
| /** @brief 指令模块初始化 */ | |||||
| void PlsrCommandInit(void) | void PlsrCommandInit(void) | ||||
| { | { | ||||
| WriteHoldReg(PLSR_CTRL_REG, 0U); | WriteHoldReg(PLSR_CTRL_REG, 0U); | ||||
| PlsrCommandClearFault(); | |||||
| s_live_freq_req = 0U; | |||||
| s_live_freq_hz = 0U; | |||||
| /* 不清故障:上电由 PersistRestoreMonitor 恢复;无镜像时 s_fault 默认为 0 */ | |||||
| PlsrCommandPublishMonitor(); | PlsrCommandPublishMonitor(); | ||||
| } | } | ||||
| /** | |||||
| * @brief 运行中 FC10 写段频率 | |||||
| * @note 限制:quantity==2、Busy、地址对齐 FREQ_L、当前改频段,当前运行段 | |||||
| * @note 频率非法写 0x04 且不更新段表、不请求 ChangeFreq | |||||
| */ | |||||
| void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity) | |||||
| { | |||||
| uint16_t cur_seg; /* 当前运行段(1-based) */ | |||||
| uint16_t seg; /* 本次写入对应的段下标(0-based) */ | |||||
| uint16_t offset; | |||||
| uint32_t freqency; | |||||
| if (quantity != 2U) | |||||
| { | |||||
| return; /* 须为频率双字 */ | |||||
| } | |||||
| if (g_plsr_busy == 0U) | |||||
| { | |||||
| return; /* 空闲改频走参数块,不走本路径 */ | |||||
| } | |||||
| if (start_addr < PLSR_REG_SEG1_BASE) | |||||
| { | |||||
| return; | |||||
| } | |||||
| offset = (uint16_t)(start_addr - PLSR_REG_SEG1_BASE); | |||||
| if ((offset % PLSR_SEG_STRIDE) != PLSR_SEG_OFF_FREQ_L) | |||||
| { | |||||
| return; /* 地址须对齐该段 FREQ_L */ | |||||
| } | |||||
| seg = (uint16_t)(offset / PLSR_SEG_STRIDE); | |||||
| if (seg >= PLSR_SEG_MAX) | |||||
| { | |||||
| return; | |||||
| } | |||||
| cur_seg = (uint16_t)(g_plsr_cur_seg_idx + 1U); | |||||
| if ((cur_seg < 1U) || ((uint16_t)(cur_seg - 1U) != seg)) | |||||
| { | |||||
| return; /* 仅允许改当前运行段 */ | |||||
| } | |||||
| freqency = (uint32_t)ReadHoldReg(start_addr) | | |||||
| ((uint32_t)ReadHoldReg((uint16_t)(start_addr + 1U)) << 16); | |||||
| if ((freqency < 1U) || (freqency > 100000U)) | |||||
| { | |||||
| PlsrCommandSetFault(PLSR_ERR_FREQ_ILLEGAL); /* 非法:不改段表、不请求 */ | |||||
| return; | |||||
| } | |||||
| g_plsr_segs[seg].freq_hz = (int32_t)freqency; /* 更新运行段表 */ | |||||
| PlsrPersistSaveSegFreqToBkp(seg, freqency); /* 仅该段频率落 BKP */ | |||||
| s_live_freq_hz = freqency; | |||||
| s_live_freq_req = 1U; /* 交 Poll → ChangeFreq */ | |||||
| } | |||||
| /** | |||||
| * @brief 周期轮询 | |||||
| * | |||||
| * 控制位处理顺序(同周期多 bit 时): | |||||
| * 1. 读 ctrl 后立即写 0(避免重复触发) | |||||
| * 2. STOP — 急停 | |||||
| * 3. CLR — 清累计 + ClearFault | |||||
| * 4. START — 忙则 0x0C;否则 Apply 校验 → 0x04~0x0C 故障码则拒启 → Stop+Start | |||||
| * 动态改频在控制位之后、PublishMonitor 之前执行。 | |||||
| */ | |||||
| void PlsrCommandPoll(void) | void PlsrCommandPoll(void) | ||||
| { | { | ||||
| uint16_t ctrl = ReadHoldReg(PLSR_CTRL_REG); | uint16_t ctrl = ReadHoldReg(PLSR_CTRL_REG); | ||||
| uint8_t skip_monitor = 0U; /* 精确等待时跳过本拍监控刷新 */ | |||||
| if (ctrl != 0U) | if (ctrl != 0U) | ||||
| { | { | ||||
| /* 上位机置位,从站处理后立刻清零 */ | |||||
| WriteHoldReg(PLSR_CTRL_REG, 0U); | |||||
| WriteHoldReg(PLSR_CTRL_REG, 0U); /* 立即清,避免重复触发 */ | |||||
| if ((ctrl & PLSR_CTRL_BIT_STOP) != 0U) | if ((ctrl & PLSR_CTRL_BIT_STOP) != 0U) | ||||
| { | { | ||||
| PlsrStop(); | |||||
| PlsrStop(); /* 急停 */ | |||||
| } | } | ||||
| if ((ctrl & PLSR_CTRL_BIT_CLR) != 0U) | if ((ctrl & PLSR_CTRL_BIT_CLR) != 0U) | ||||
| { | { | ||||
| PlsrClearAccPulse(); | |||||
| PlsrCommandClearFault(); | |||||
| PlsrClearAccPulse(); /* 清累计 + 绝对原点 */ | |||||
| PlsrCommandClearFault(); /* 同步清故障码 */ | |||||
| } | } | ||||
| /* 同时有停止位时不启动 */ | |||||
| /* START:同拍有 STOP 则不启 */ | |||||
| if (((ctrl & PLSR_CTRL_BIT_START) != 0U) && | if (((ctrl & PLSR_CTRL_BIT_START) != 0U) && | ||||
| ((ctrl & PLSR_CTRL_BIT_STOP) == 0U)) | ((ctrl & PLSR_CTRL_BIT_STOP) == 0U)) | ||||
| { | { | ||||
| (void)PlsrParamBlockApplyFromHold(); | |||||
| PlsrCommandClearFault(); | |||||
| PlsrStop(); | |||||
| (void)PlsrStart(PlsrParamGetStartSeg()); | |||||
| if (g_plsr_busy != 0U) | |||||
| { | |||||
| PlsrCommandSetFault(PLSR_ERR_DUP_START); /* 0x0C 重复启动 */ | |||||
| } | |||||
| else if (PlsrCommandIsStartBlocked() != 0U) | |||||
| { | |||||
| PlsrStop(); /* 0x04~0x0B:拒启,仅停 */ | |||||
| } | |||||
| else | |||||
| { | |||||
| PlsrStop(); /* 先清残留再启 */ | |||||
| (void)PlsrStart(PlsrParamGetStartSeg()); | |||||
| if (PlsrRunControlIsPreciseWait() != 0U) | |||||
| { | |||||
| skip_monitor = 1U; /* 换向/WAIT 精确等 TIM5 */ | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| PlsrCommandPublishMonitor(); | |||||
| if (s_live_freq_req != 0U) | |||||
| { | |||||
| uint32_t freq = s_live_freq_hz; | |||||
| s_live_freq_req = 0U; | |||||
| (void)PlsrChangeFreq(freq); /* 重规划剩余脉冲 */ | |||||
| } | |||||
| if (skip_monitor == 0U) | |||||
| { | |||||
| PlsrCommandPublishMonitor(); /* 回写 0x2000~0x2006 */ | |||||
| } | |||||
| } | } | ||||
| @@ -1,37 +1,111 @@ | |||||
| /** | /** | ||||
| * @file plsr_command.h | * @file plsr_command.h | ||||
| * @brief 指令接口模块 | |||||
| * @brief Modbus 指令接口:监控回写与控制位解析 | |||||
| * | * | ||||
| * 监控区 0x2000~0x2006:下位机刷新,上位机 0x03 读。 | |||||
| * 控制寄存器 0x3000:上位机置位,下位机处理后清零。 | |||||
| * @details 模块职责 | |||||
| * 维护故障码镜像 s_fault,周期性将运行状态写入监控保持寄存器; | |||||
| * 解析控制寄存器 0x3000 的 START/STOP/CLR 位并驱动 PlsrStart/Stop/Clear; | |||||
| * 处理运行中 FC10 写当前段频率的双字,触发 ChangeFreq。 | |||||
| * | |||||
| * 与其它模块关系 | |||||
| * - modbus_rtu :ReadHoldReg / WriteHoldReg 底层访问 | |||||
| * - plsr_run_control :GetCurSeg / GetCurFreq / IsBusy 数据源 | |||||
| * - plsr_param :ApplyFromHold、段表 FREQ 偏移常量 | |||||
| * - plsr.h :PlsrStart / Stop / ChangeFreq / ClearAccPulse | |||||
| * | |||||
| * 关键数据流 | |||||
| * 上位机写 0x3000 → Poll 读 ctrl → 清 0 → 执行 STOP/CLR/START → PublishMonitor | |||||
| * run_control 诊断 → SetFault(n) → 写 0x2006;Poll 每周期重发 s_fault | |||||
| * FC10 写段频 → OnSegFreqHoldWrite → s_live_freq_req → Poll → ChangeFreq | |||||
| * | |||||
| * 寄存器映射(保持寄存器,Modbus 0x03/0x06/0x10) | |||||
| * 监控区 0x2000~0x2006:下位机刷新,上位机只读 | |||||
| * 控制区 0x3000 :上位机置位,从站处理后清零 | |||||
| */ | */ | ||||
| #ifndef PLSR_COMMAND_H | #ifndef PLSR_COMMAND_H | ||||
| #define PLSR_COMMAND_H | #define PLSR_COMMAND_H | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| /* ---------- 监控保持寄存器(双字低地址存低字) ---------- */ | |||||
| #define PLSR_MON_ACC_PULSE_L 0x2000U /* 脉冲累计,有符号补码 */ | |||||
| /* ---------- 监控保持寄存器(双字低地址存低 16 位,高地址存高 16 位) ---------- */ | |||||
| #define PLSR_MON_ACC_PULSE_L 0x2000U /* 脉冲累计 SDW,有符号补码 */ | |||||
| #define PLSR_MON_ACC_PULSE_H 0x2001U | #define PLSR_MON_ACC_PULSE_H 0x2001U | ||||
| #define PLSR_MON_FREQ_L 0x2002U /* 当前发送频率 Hz */ | |||||
| #define PLSR_MON_FREQ_L 0x2002U /* 当前发送频率 Hz,UDW */ | |||||
| #define PLSR_MON_FREQ_H 0x2003U | #define PLSR_MON_FREQ_H 0x2003U | ||||
| #define PLSR_MON_RUN_STATUS 0x2004U /* 0空闲 1运行 */ | |||||
| #define PLSR_MON_CUR_SEG 0x2005U /* 当前执行段 1-based */ | |||||
| #define PLSR_MON_ERR 0x2006U /* 错误码 */ | |||||
| #define PLSR_MON_RUN_STATUS 0x2004U /* 0=空闲,1=运行中 */ | |||||
| #define PLSR_MON_CUR_SEG 0x2005U /* 当前执行段号,1-based;空闲为 0 */ | |||||
| #define PLSR_MON_ERR 0x2006U /* 故障码,见 PLSR_ERR_* */ | |||||
| /* ---------- 控制寄存器(读/写,处理后由从站清 0) ---------- */ | |||||
| /* ---------- 控制寄存器(读/写;从站处理后写回 0) ---------- */ | |||||
| #define PLSR_CTRL_REG 0x3000U | #define PLSR_CTRL_REG 0x3000U | ||||
| #define PLSR_CTRL_BIT_START (1U << 0) /* 启动脉冲 */ | |||||
| #define PLSR_CTRL_BIT_STOP (1U << 1) /* 停止脉冲 */ | |||||
| #define PLSR_CTRL_BIT_CLR (1U << 2) /* 清除状态信息 */ | |||||
| #define PLSR_CTRL_BIT_START (1U << 0) /* 置 1:Apply 参数后启动 */ | |||||
| #define PLSR_CTRL_BIT_STOP (1U << 1) /* 置 1:急停 */ | |||||
| #define PLSR_CTRL_BIT_CLR (1U << 2) /* 置 1:清零累计脉冲与故障码 */ | |||||
| #define PLSR_ERR_NONE 0U | |||||
| #define PLSR_ERR_SEG_FREQ 1U /* 段频率非法(非 0 且越界,或解析后不在 1~100k) */ | |||||
| /* | |||||
| * 故障码定义(写入 0x2006,须经 PlsrCommandSetFault,勿直接只写寄存器) | |||||
| */ | |||||
| #define PLSR_ERR_NONE 0x00U //无故障 | |||||
| #define PLSR_ERR_PULSE_CNT 0x01U //实际脉冲数与段目标不一致 | |||||
| #define PLSR_ERR_FREQ_UNREACH 0x02U //规划峰值达不到设定目标(三角) | |||||
| #define PLSR_ERR_CURVE_MISMATCH 0x03U //加减速相内脉冲与规划预算不符 | |||||
| #define PLSR_ERR_FREQ_ILLEGAL 0x04U //段频率 ∉ [1, 100000] Hz(非 0) | |||||
| #define PLSR_ERR_PULSE_RANGE 0x05U //脉冲个数 ∉ int32 合法范围 | |||||
| #define PLSR_ERR_SEG_COUNT 0x06U //总段数 ∉ [1, 10] | |||||
| #define PLSR_ERR_START_SEG 0x07U //起始段 ∉ [1, 10] | |||||
| #define PLSR_ERR_START_END_SPD 0x08U //起/止速非0且 ∉ [1, 100000] Hz | |||||
| #define PLSR_ERR_PULSE_Y 0x09U //脉冲端子 ∉ {Y0,Y1,Y2} | |||||
| #define PLSR_ERR_DIR_Y 0x0AU //方向端子 ≠ Y3 | |||||
| #define PLSR_ERR_DEFAULT_SPD 0x0BU //默认速度 ∉ [1, 100000] Hz | |||||
| #define PLSR_ERR_DUP_START 0x0CU //运行中重复 START | |||||
| /** 参数类门禁故障码下限 */ | |||||
| #define PLSR_ERR_PARAM_BLOCK_MIN PLSR_ERR_FREQ_ILLEGAL | |||||
| /** 参数类门禁故障码上限 */ | |||||
| #define PLSR_ERR_PARAM_BLOCK_MAX PLSR_ERR_DUP_START | |||||
| /** | |||||
| * @brief 指令模块初始化 | |||||
| * @note 调用时机:PlsrInit 末、PlsrTask 首次延迟后再次调用 | |||||
| * @note 副作用:清 0x3000、清故障、刷新全部监控寄存器 | |||||
| */ | |||||
| void PlsrCommandInit(void); | void PlsrCommandInit(void); | ||||
| /** | |||||
| * @brief 周期轮询控制位并刷新监控 | |||||
| * @note 调用时机:PlsrTask 每 ~1ms | |||||
| * @note 副作用:可能触发 Start/Stop/Clear/ChangeFreq,写 0x2000~0x2006 | |||||
| */ | |||||
| void PlsrCommandPoll(void); | void PlsrCommandPoll(void); | ||||
| /** | |||||
| * @brief 置故障码并立即写 0x2006 | |||||
| * @param code PLSR_ERR_* 之一 | |||||
| * @note 调用时机:run_control 诊断点;PublishMonitor 也会重发 s_fault | |||||
| * @note 注意:更新 s_fault 镜像,告警码不自动清除(须 START 或 CLR) | |||||
| */ | |||||
| void PlsrCommandSetFault(uint16_t code); | void PlsrCommandSetFault(uint16_t code); | ||||
| /** | |||||
| * @brief 清故障为 0 | |||||
| * @note 调用时机:START 前、CLR 控制位(Init 不再清,以便上电恢复) | |||||
| */ | |||||
| void PlsrCommandClearFault(void); | void PlsrCommandClearFault(void); | ||||
| /** @return 当前故障码镜像(与 0x2006 一致) */ | |||||
| uint16_t PlsrCommandGetFault(void); | uint16_t PlsrCommandGetFault(void); | ||||
| /** | |||||
| * @brief 是否因参数/重复启动类故障禁止 START | |||||
| * @return 1=故障码为 0x04~0x0C;0=可尝试启动 | |||||
| */ | |||||
| uint8_t PlsrCommandIsStartBlocked(void); | |||||
| /** | |||||
| * @brief FC10 写段频率双字后的钩子 | |||||
| * @param start_addr 写入起始保持寄存器地址 | |||||
| * @param quantity 写入字数,须为 2 | |||||
| */ | |||||
| void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity); | |||||
| #endif | #endif | ||||
| @@ -1,113 +1,67 @@ | |||||
| /** | /** | ||||
| * @file plsr_param.c | * @file plsr_param.c | ||||
| * @brief 参数块管理模块实现(运行映像 + 分帧导入/导出) | |||||
| * @brief 参数块管理实现:运行映像 + 分帧导入/导出 + ParamBlockTask | |||||
| * | |||||
| * @details 模块职责 | |||||
| * 维护公共参数 g_plsr_config 和段参数 g_plsr_segs[],实现寄存器与 | |||||
| * 运行参数之间的双向转换,以及「5+N 分帧」收齐后的异步导入 | |||||
| * | |||||
| * 与其它模块关系 | |||||
| * Modbus FC10 应答路径调用 OnHoldWrite;START 也会同步 ApplyFromHold; | |||||
| * Persist:校验全部通过才 SaveParamsFromHold;失败只写故障码,不存BKP; | |||||
| * 非上电不再 ExportToHold(hold 保留上位机写入值,由故障码提示修正)。 | |||||
| */ | */ | ||||
| #include "plsr_param.h" | #include "plsr_param.h" | ||||
| #include "plsr_persist.h" | #include "plsr_persist.h" | ||||
| #include "plsr_command.h" | |||||
| #include "modbus_rtu.h" | #include "modbus_rtu.h" | ||||
| #include "ucos_ii.h" | #include "ucos_ii.h" | ||||
| #include <string.h> | |||||
| static PlsrCfg_t s_cfg; | |||||
| static PlsrSeg_t s_seg[PLSR_SEG_MAX]; | |||||
| PlsrCfg_t g_plsr_config; /* 公共参数运行映像 */ | |||||
| PlsrSeg_t g_plsr_segs[PLSR_SEG_MAX]; /* 段表运行映像(最多 PLSR_SEG_MAX) */ | |||||
| static OS_EVENT *s_param_sem; | |||||
| static volatile uint16_t s_expect_frames; | |||||
| static volatile uint16_t s_recv_frames; | |||||
| static volatile uint8_t s_xfer_active; | |||||
| /*============================================================================*/ | |||||
| /* 运行映像 */ | |||||
| /*============================================================================*/ | |||||
| void PlsrParamInitDefault(void) | |||||
| { | |||||
| uint16_t i; | |||||
| memset(&s_cfg, 0, sizeof(s_cfg)); | |||||
| memset(s_seg, 0, sizeof(s_seg)); | |||||
| /* 与上位机默认约定一致;真正运行参数由 ApplyFromHold 覆盖 */ | |||||
| s_cfg.pulse_y = 0U; | |||||
| s_cfg.dir_y = 3U; | |||||
| s_cfg.wait_x_sel = 0U; | |||||
| s_cfg.ext_x_sel = 0U; | |||||
| s_cfg.send_mode = PLSR_SEND_COMPLETE; | |||||
| s_cfg.dir_delay_ms = 10U; | |||||
| s_cfg.dir_logic = PLSR_DIR_LOGIC_POS; | |||||
| s_cfg.accel_mode = PLSR_ACCEL_LINEAR; | |||||
| s_cfg.run_mode = PLSR_POS_RELATIVE; | |||||
| s_cfg.seg_count = 0U; | |||||
| s_cfg.start_seg = 1U; | |||||
| s_cfg.default_speed = 1000UL; | |||||
| s_cfg.start_speed = 0UL; | |||||
| s_cfg.end_speed = 0UL; | |||||
| s_cfg.accel_ms = 100U; | |||||
| s_cfg.decel_ms = 100U; | |||||
| for (i = 0U; i < PLSR_SEG_MAX; i++) | |||||
| { | |||||
| s_seg[i].freq_hz = 0; | |||||
| s_seg[i].pulse_cnt = 0; | |||||
| s_seg[i].wait_type = PLSR_WAIT_TIME; | |||||
| s_seg[i].wait_ms = 0U; | |||||
| s_seg[i].act_ms = 0U; | |||||
| s_seg[i].jump_seg = 0U; | |||||
| } | |||||
| } | |||||
| PlsrCfg_t *PlsrParamGetCfg(void) | |||||
| { | |||||
| return &s_cfg; | |||||
| } | |||||
| PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based) | |||||
| { | |||||
| if (seg_0based >= PLSR_SEG_MAX) | |||||
| { | |||||
| seg_0based = 0U; | |||||
| } | |||||
| return &s_seg[seg_0based]; | |||||
| } | |||||
| static OS_EVENT *s_param_sem; /* 分帧收齐后唤醒 ParamBlockTask */ | |||||
| static volatile uint16_t s_expect_frames; /* 本批期望总帧数 = 5 + 段数 */ | |||||
| static volatile uint16_t s_recv_frames; /* 已收帧计数 */ | |||||
| static volatile uint8_t s_xfer_active; /* 1=正在接收一批分帧 */ | |||||
| /** @return 有效段数,软钳到 [0, PLSR_SEG_MAX] */ | |||||
| uint16_t PlsrParamGetSegCount(void) | uint16_t PlsrParamGetSegCount(void) | ||||
| { | { | ||||
| if (s_cfg.seg_count < 1U) | |||||
| if (g_plsr_config.seg_count < 1U) | |||||
| { | { | ||||
| return 0U; | |||||
| return 0U; /* 无有效段 */ | |||||
| } | } | ||||
| if (s_cfg.seg_count > PLSR_SEG_MAX) | |||||
| if (g_plsr_config.seg_count > PLSR_SEG_MAX) | |||||
| { | { | ||||
| return (uint16_t)PLSR_SEG_MAX; | |||||
| return (uint16_t)PLSR_SEG_MAX; /* 超上限钳住 */ | |||||
| } | } | ||||
| return s_cfg.seg_count; | |||||
| return g_plsr_config.seg_count; | |||||
| } | } | ||||
| /** | |||||
| * @brief 启动段 1-based,软钳到 [1, seg_count] | |||||
| */ | |||||
| uint16_t PlsrParamGetStartSeg(void) | uint16_t PlsrParamGetStartSeg(void) | ||||
| { | { | ||||
| uint16_t n = PlsrParamGetSegCount(); | uint16_t n = PlsrParamGetSegCount(); | ||||
| if (n < 1U) | if (n < 1U) | ||||
| { | { | ||||
| return 1U; | |||||
| return 1U; /* 无段时仍返回 1 */ | |||||
| } | } | ||||
| if (s_cfg.start_seg < 1U) | |||||
| if (g_plsr_config.start_seg < 1U) | |||||
| { | { | ||||
| return 1U; | return 1U; | ||||
| } | } | ||||
| /* TODO:起始段非法时输出故障码 */ | |||||
| if (s_cfg.start_seg > n) | |||||
| if (g_plsr_config.start_seg > n) | |||||
| { | { | ||||
| return n; | |||||
| return n; /* 超出则钳到末段 */ | |||||
| } | } | ||||
| return s_cfg.start_seg; | |||||
| return g_plsr_config.start_seg; | |||||
| } | } | ||||
| /*============================================================================*/ | |||||
| /* 分帧导入 / 导出 */ | |||||
| /*============================================================================*/ | |||||
| /** 读相邻两寄存器拼成无符号 32 位(低字在前) */ | |||||
| /** 读相邻两寄存器拼成无符号 32 位(低地址=低 16 位) */ | |||||
| static uint32_t PlsrParamReadDWord(uint16_t addr) | static uint32_t PlsrParamReadDWord(uint16_t addr) | ||||
| { | { | ||||
| uint16_t low = ReadHoldReg(addr); | uint16_t low = ReadHoldReg(addr); | ||||
| @@ -115,17 +69,30 @@ static uint32_t PlsrParamReadDWord(uint16_t addr) | |||||
| return ((uint32_t)high << 16) | (uint32_t)low; | return ((uint32_t)high << 16) | (uint32_t)low; | ||||
| } | } | ||||
| static void PlsrParamWriteDWord(uint16_t addr, uint32_t value) | |||||
| { | |||||
| WriteHoldReg(addr, (uint16_t)(value & 0xFFFFU)); | |||||
| WriteHoldReg((uint16_t)(addr + 1U), (uint16_t)((value >> 16) & 0xFFFFU)); | |||||
| } | |||||
| static int32_t PlsrParamReadSignedDWord(uint16_t addr) | |||||
| /** | |||||
| * @brief 判断本次 FC10 是否为公共帧 2~5 | |||||
| * @return 1=是;0=否 | |||||
| */ | |||||
| static uint8_t PlsrParamIsCommonTailFrameWrite(uint16_t start_addr, uint16_t quantity) | |||||
| { | { | ||||
| return (int32_t)PlsrParamReadDWord(addr); | |||||
| if (quantity < PLSR_PARAM_COMMON_DWORD_WORDS) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| if ((start_addr == PLSR_REG_COMMON_F2_BASE) || | |||||
| (start_addr == PLSR_REG_COMMON_F3_BASE) || | |||||
| (start_addr == PLSR_REG_COMMON_F4_BASE) || | |||||
| (start_addr == PLSR_REG_COMMON_F5_BASE)) | |||||
| { | |||||
| return 1U; | |||||
| } | |||||
| return 0U; | |||||
| } | } | ||||
| /** | |||||
| * @brief 判断本次 FC10 是否为段参数 | |||||
| * @return 1=是段帧;0=否 | |||||
| */ | |||||
| static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity) | static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity) | ||||
| { | { | ||||
| uint16_t i; | uint16_t i; | ||||
| @@ -146,132 +113,185 @@ static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity) | |||||
| return 0U; | return 0U; | ||||
| } | } | ||||
| static void PlsrParamSetXferStatus(uint16_t st, uint16_t fail_frame) | |||||
| /*============================================================================*/ | |||||
| /* 参数校验辅助 */ | |||||
| /*============================================================================*/ | |||||
| #define PLSR_PARAM_SPD_MIN_HZ 1U | |||||
| #define PLSR_PARAM_SPD_MAX_HZ 100000U | |||||
| /** 速度 Hz 是否落在 [1, 100k](默认速度/段频非 0) */ | |||||
| static uint8_t PlsrParamIsSpeedHzValid(uint32_t hz) | |||||
| { | |||||
| return ((hz >= PLSR_PARAM_SPD_MIN_HZ) && (hz <= PLSR_PARAM_SPD_MAX_HZ)) ? 1U : 0U; | |||||
| } | |||||
| /** | |||||
| * 起速/止速:0 合法; | |||||
| * 非 0 须在 1~100kHz。 | |||||
| */ | |||||
| static uint8_t PlsrParamIsStartEndSpeedValid(uint32_t hz) | |||||
| { | { | ||||
| WriteHoldReg(PLSR_PARAM_XFER_STATUS_REG, st); | |||||
| WriteHoldReg(PLSR_PARAM_XFER_FAIL_FRAME_REG, fail_frame); | |||||
| WriteHoldReg(PLSR_PARAM_XFER_RECV_REG, s_recv_frames); | |||||
| if (hz == 0U) | |||||
| { | |||||
| return 1U; | |||||
| } | |||||
| return PlsrParamIsSpeedHzValid(hz); | |||||
| } | } | ||||
| /** 段频率:0=用默认速度;非 0 须在 1~100kHz */ | |||||
| static uint8_t PlsrParamIsSegFreqValid(int32_t freq_hz) | |||||
| { | |||||
| if (freq_hz == 0) | |||||
| { | |||||
| return 1U; | |||||
| } | |||||
| if (freq_hz < 0) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return PlsrParamIsSpeedHzValid((uint32_t)freq_hz); | |||||
| } | |||||
| /** 脉冲个数 SDW:须在 int32 可安全取绝对值的范围内(排除 INT_MIN) */ | |||||
| static uint8_t PlsrParamIsPulseCntValid(int32_t pulse_cnt) | |||||
| { | |||||
| /* INT_MIN 无法安全取反成正的目标脉冲数,报 0x05 */ | |||||
| if (pulse_cnt == (int32_t)(-2147483647L - 1L)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| return 1U; | |||||
| } | |||||
| /*============================================================================*/ | |||||
| /* 分帧导入 / 导出 */ | |||||
| /*============================================================================*/ | |||||
| /** | |||||
| * @brief 保持寄存器 → 运行映像,边拷贝边校验 | |||||
| * | |||||
| * @details 校验失败时仍完成拷贝,故障码写入 0x2006; | |||||
| * 多项非法时仅保留校验过程中最后一个故障码。 | |||||
| * @return 1=全部通过;0=存在门禁故障(不落存BKP) | |||||
| */ | |||||
| uint8_t PlsrParamBlockApplyFromHold(void) | uint8_t PlsrParamBlockApplyFromHold(void) | ||||
| { | { | ||||
| uint16_t i; | uint16_t i; | ||||
| uint16_t n; | uint16_t n; | ||||
| uint16_t base; | uint16_t base; | ||||
| uint16_t m; | |||||
| PlsrSeg_t *seg; | |||||
| /* | |||||
| * 从保持寄存器组装运行映像。 | |||||
| * TODO:段数/频率/脉冲/端子冲突/跳转非法 → 故障码(当前仅裁剪) | |||||
| */ | |||||
| s_cfg.pulse_y = ReadHoldReg(PLSR_REG_PULSE_Y); | |||||
| if (s_cfg.pulse_y > 2U) | |||||
| uint16_t fault = PLSR_ERR_NONE; | |||||
| g_plsr_config.pulse_y = ReadHoldReg(PLSR_REG_PULSE_Y); /* 脉冲端子 Y0~Y2 */ | |||||
| if (g_plsr_config.pulse_y > 2U) | |||||
| { | { | ||||
| s_cfg.pulse_y = 0U; | |||||
| fault = PLSR_ERR_PULSE_Y; /* 0x07:端子非法 */ | |||||
| } | } | ||||
| s_cfg.dir_y = 3U; /* 方向固定 Y3,忽略上位机其它值 */ | |||||
| WriteHoldReg(PLSR_REG_DIR_Y, 3U); | |||||
| s_cfg.wait_x_sel = ReadHoldReg(PLSR_REG_WAIT_X) ? 1U : 0U; | |||||
| s_cfg.ext_x_sel = ReadHoldReg(PLSR_REG_EXT_X) ? 1U : 0U; | |||||
| s_cfg.send_mode = (PlsrSendMode_e)(ReadHoldReg(PLSR_REG_SEND_MODE) ? 1U : 0U); | |||||
| s_cfg.dir_delay_ms = ReadHoldReg(PLSR_REG_DIR_DELAY); | |||||
| s_cfg.dir_logic = (ReadHoldReg(PLSR_REG_DIR_LOGIC) != 0U) ? | |||||
| PLSR_DIR_LOGIC_NEG : PLSR_DIR_LOGIC_POS; | |||||
| m = ReadHoldReg(PLSR_REG_ACCEL_MODE); | |||||
| if (m > (uint16_t)PLSR_ACCEL_SINE) | |||||
| g_plsr_config.dir_y = ReadHoldReg(PLSR_REG_DIR_Y); /* 方向端子须为 Y3 */ | |||||
| if (g_plsr_config.dir_y != 3U) | |||||
| { | { | ||||
| m = (uint16_t)PLSR_ACCEL_LINEAR; | |||||
| fault = PLSR_ERR_DIR_Y; /* 0x08 */ | |||||
| } | } | ||||
| s_cfg.accel_mode = (PlsrAccelMode_e)m; | |||||
| s_cfg.run_mode = (ReadHoldReg(PLSR_REG_RUN_MODE) != 0U) ? | |||||
| PLSR_POS_ABSOLUTE : PLSR_POS_RELATIVE; | |||||
| g_plsr_config.wait_x_sel = ReadHoldReg(PLSR_REG_WAIT_X) ? 1U : 0U; /* 0=X4,1=X5 */ | |||||
| g_plsr_config.ext_x_sel = ReadHoldReg(PLSR_REG_EXT_X) ? 1U : 0U; /* 0=X4,1=X5 */ | |||||
| g_plsr_config.send_mode = (PlsrSendMode_e)(ReadHoldReg(PLSR_REG_SEND_MODE) ? 1U : 0U); | |||||
| g_plsr_config.dir_delay_ms = ReadHoldReg(PLSR_REG_DIR_DELAY); /* 换向延时 ms */ | |||||
| g_plsr_config.dir_logic = (ReadHoldReg(PLSR_REG_DIR_LOGIC) != 0U) ? | |||||
| PLSR_DIR_LOGIC_NEG : PLSR_DIR_LOGIC_POS; /* 方向电平逻辑 */ | |||||
| g_plsr_config.accel_mode = (PlsrAccelMode_e)ReadHoldReg(PLSR_REG_ACCEL_MODE); | |||||
| g_plsr_config.run_mode = (ReadHoldReg(PLSR_REG_RUN_MODE) != 0U) ? | |||||
| PLSR_POS_ABSOLUTE : PLSR_POS_RELATIVE; /* 绝对/相对 */ | |||||
| n = ReadHoldReg(PLSR_REG_SEG_COUNT); | n = ReadHoldReg(PLSR_REG_SEG_COUNT); | ||||
| if (n > PLSR_SEG_MAX) | |||||
| g_plsr_config.seg_count = n; | |||||
| if ((n < 1U) || (n > PLSR_SEG_MAX)) | |||||
| { | { | ||||
| n = (uint16_t)PLSR_SEG_MAX; | |||||
| fault = PLSR_ERR_SEG_COUNT; /* 0x09:段数越界 */ | |||||
| } | } | ||||
| s_cfg.seg_count = n; | |||||
| s_cfg.start_seg = ReadHoldReg(PLSR_REG_START_SEG); | |||||
| if (s_cfg.start_seg < 1U) | |||||
| g_plsr_config.start_seg = ReadHoldReg(PLSR_REG_START_SEG); /* 1-based 启动段 */ | |||||
| if ((n >= 1U) && (n <= PLSR_SEG_MAX)) | |||||
| { | |||||
| if ((g_plsr_config.start_seg < 1U) || (g_plsr_config.start_seg > n)) | |||||
| { | |||||
| fault = PLSR_ERR_START_SEG; /* 0x0A:启动段越界 */ | |||||
| } | |||||
| } | |||||
| else if (g_plsr_config.start_seg < 1U) | |||||
| { | { | ||||
| s_cfg.start_seg = 1U; | |||||
| fault = PLSR_ERR_START_SEG; | |||||
| } | } | ||||
| if ((n >= 1U) && (s_cfg.start_seg > n)) | |||||
| g_plsr_config.default_speed = PlsrParamReadDWord(PLSR_REG_DEFAULT_SPD_L); | |||||
| if (PlsrParamIsSpeedHzValid(g_plsr_config.default_speed) == 0U) | |||||
| { | { | ||||
| s_cfg.start_seg = n; | |||||
| fault = PLSR_ERR_DEFAULT_SPD; /* 0x0B:默认速度非法 */ | |||||
| } | } | ||||
| s_cfg.default_speed = PlsrParamReadDWord(PLSR_REG_DEFAULT_SPD_L); | |||||
| s_cfg.start_speed = PlsrParamReadDWord(PLSR_REG_START_SPD_L); | |||||
| s_cfg.end_speed = PlsrParamReadDWord(PLSR_REG_END_SPD_L); | |||||
| s_cfg.accel_ms = ReadHoldReg(PLSR_REG_ACCEL_MS); | |||||
| s_cfg.decel_ms = ReadHoldReg(PLSR_REG_DECEL_MS); | |||||
| g_plsr_config.start_speed = PlsrParamReadDWord(PLSR_REG_START_SPD_L); /* 起跳频,0 合法 */ | |||||
| if (PlsrParamIsStartEndSpeedValid(g_plsr_config.start_speed) == 0U) | |||||
| { | |||||
| fault = PLSR_ERR_START_END_SPD; | |||||
| } | |||||
| for (i = 0U; i < n; i++) | |||||
| g_plsr_config.end_speed = PlsrParamReadDWord(PLSR_REG_END_SPD_L); /* 止速,0 合法 */ | |||||
| if (PlsrParamIsStartEndSpeedValid(g_plsr_config.end_speed) == 0U) | |||||
| { | { | ||||
| seg = &s_seg[i]; | |||||
| base = (uint16_t)(PLSR_REG_SEG1_BASE + i * PLSR_SEG_STRIDE); | |||||
| seg->freq_hz = PlsrParamReadSignedDWord((uint16_t)(base + PLSR_SEG_OFF_FREQ_L)); | |||||
| seg->pulse_cnt = PlsrParamReadSignedDWord((uint16_t)(base + PLSR_SEG_OFF_PULSE_L)); | |||||
| fault = PLSR_ERR_START_END_SPD; | |||||
| } | |||||
| g_plsr_config.accel_ms = ReadHoldReg(PLSR_REG_ACCEL_MS); /* 加速参考时间 */ | |||||
| g_plsr_config.decel_ms = ReadHoldReg(PLSR_REG_DECEL_MS); /* 减速参考时间 */ | |||||
| if ((n >= 1U) && (n <= PLSR_SEG_MAX)) | |||||
| { | |||||
| for (i = 0U; i < n; i++) | |||||
| { | { | ||||
| uint16_t w = ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT)); | |||||
| if (w > 4U) | |||||
| base = (uint16_t)(PLSR_REG_SEG1_BASE + i * PLSR_SEG_STRIDE); | |||||
| g_plsr_segs[i].freq_hz = | |||||
| (int32_t)PlsrParamReadDWord((uint16_t)(base + PLSR_SEG_OFF_FREQ_L)); | |||||
| if (PlsrParamIsSegFreqValid(g_plsr_segs[i].freq_hz) == 0U) | |||||
| { | |||||
| fault = PLSR_ERR_FREQ_ILLEGAL; /* 0x04:段频率非法 */ | |||||
| } | |||||
| g_plsr_segs[i].pulse_cnt = | |||||
| (int32_t)PlsrParamReadDWord((uint16_t)(base + PLSR_SEG_OFF_PULSE_L)); | |||||
| if (PlsrParamIsPulseCntValid(g_plsr_segs[i].pulse_cnt) == 0U) | |||||
| { | { | ||||
| w = 0U; | |||||
| fault = PLSR_ERR_PULSE_RANGE; /* 0x05:脉冲数 INT_MIN */ | |||||
| } | } | ||||
| seg->wait_type = (PlsrWaitType_e)w; | |||||
| g_plsr_segs[i].wait_type = | |||||
| (PlsrWaitType_e)ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT)); | |||||
| g_plsr_segs[i].wait_ms = | |||||
| ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT_MS)); /* 段后等待 ms */ | |||||
| g_plsr_segs[i].act_ms = | |||||
| ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_ACT_MS)); /* ACT 定时 ms */ | |||||
| g_plsr_segs[i].jump_seg = | |||||
| ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_JUMP)); /* 跳转目标段 */ | |||||
| } | } | ||||
| seg->wait_ms = ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT_MS)); | |||||
| seg->act_ms = ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_ACT_MS)); | |||||
| seg->jump_seg = ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_JUMP)); | |||||
| } | } | ||||
| PlsrPersistNotifyCommonChanged(); | |||||
| return 1U; | |||||
| } | |||||
| void PlsrParamBlockExportToHold(void) | |||||
| { | |||||
| uint16_t i; | |||||
| uint16_t base; | |||||
| PlsrSeg_t *seg; | |||||
| WriteHoldReg(PLSR_REG_PULSE_Y, s_cfg.pulse_y); | |||||
| WriteHoldReg(PLSR_REG_DIR_Y, s_cfg.dir_y); | |||||
| WriteHoldReg(PLSR_REG_WAIT_X, s_cfg.wait_x_sel); | |||||
| WriteHoldReg(PLSR_REG_EXT_X, s_cfg.ext_x_sel); | |||||
| WriteHoldReg(PLSR_REG_SEND_MODE, (uint16_t)s_cfg.send_mode); | |||||
| WriteHoldReg(PLSR_REG_DIR_DELAY, s_cfg.dir_delay_ms); | |||||
| WriteHoldReg(PLSR_REG_DIR_LOGIC, (uint16_t)s_cfg.dir_logic); | |||||
| WriteHoldReg(PLSR_REG_ACCEL_MODE, (uint16_t)s_cfg.accel_mode); | |||||
| WriteHoldReg(PLSR_REG_RUN_MODE, (uint16_t)s_cfg.run_mode); | |||||
| WriteHoldReg(PLSR_REG_SEG_COUNT, s_cfg.seg_count); | |||||
| WriteHoldReg(PLSR_REG_START_SEG, s_cfg.start_seg); | |||||
| PlsrParamWriteDWord(PLSR_REG_DEFAULT_SPD_L, s_cfg.default_speed); | |||||
| PlsrParamWriteDWord(PLSR_REG_START_SPD_L, s_cfg.start_speed); | |||||
| WriteHoldReg(0x100FU, 0U); | |||||
| PlsrParamWriteDWord(PLSR_REG_END_SPD_L, s_cfg.end_speed); | |||||
| WriteHoldReg(PLSR_REG_ACCEL_MS, s_cfg.accel_ms); | |||||
| WriteHoldReg(PLSR_REG_DECEL_MS, s_cfg.decel_ms); | |||||
| PlsrCommandSetFault(fault); /* 写 0x2006 镜像 */ | |||||
| for (i = 0U; i < PLSR_SEG_MAX; i++) | |||||
| if (fault == PLSR_ERR_NONE) | |||||
| { | { | ||||
| seg = &s_seg[i]; | |||||
| base = (uint16_t)(PLSR_REG_SEG1_BASE + i * PLSR_SEG_STRIDE); | |||||
| PlsrParamWriteDWord((uint16_t)(base + PLSR_SEG_OFF_FREQ_L), (uint32_t)seg->freq_hz); | |||||
| PlsrParamWriteDWord((uint16_t)(base + PLSR_SEG_OFF_PULSE_L), (uint32_t)seg->pulse_cnt); | |||||
| WriteHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT), (uint16_t)seg->wait_type); | |||||
| WriteHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT_MS), seg->wait_ms); | |||||
| WriteHoldReg((uint16_t)(base + PLSR_SEG_OFF_ACT_MS), seg->act_ms); | |||||
| WriteHoldReg((uint16_t)(base + PLSR_SEG_OFF_JUMP), seg->jump_seg); | |||||
| return 1U; /* 可落存 BKP */ | |||||
| } | } | ||||
| return 0U; | |||||
| } | } | ||||
| /** | |||||
| * @brief 统计接受帧数,收满释放信号量 | |||||
| * | |||||
| * 公共帧1@0x1000~0x100A,expect=5+N,recv=1 | |||||
| * 传输中公共帧2~5 / 段帧 → recv++ | |||||
| * recv>=expect → 清 active,Post 信号量 | |||||
| * 运行中只写频率双字不走本路径 | |||||
| */ | |||||
| void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity) | void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity) | ||||
| { | { | ||||
| if (quantity == 0U) | if (quantity == 0U) | ||||
| @@ -279,69 +299,62 @@ void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity) | |||||
| return; | return; | ||||
| } | } | ||||
| /* | |||||
| * 公共帧:起始 0x1000 且覆盖到 0x1013 → 开启传输,期望帧=1+段数。 | |||||
| * 段帧:传输中且写到某段基址 → 帧计数+1。 | |||||
| * 收齐后 post 信号量,由 ParamBlockTask 执行 Apply。 | |||||
| */ | |||||
| if ((start_addr == PLSR_REG_PULSE_Y) && | |||||
| (quantity >= (uint16_t)(PLSR_REG_DECEL_MS - PLSR_REG_PULSE_Y + 1U))) | |||||
| if ((start_addr == PLSR_REG_COMMON_F1_BASE) && | |||||
| (quantity >= PLSR_PARAM_COMMON_FRAME1_WORDS)) | |||||
| { | { | ||||
| /* 公共帧 1:开新一批,期望 5+段数 */ | |||||
| uint16_t seg_n = ReadHoldReg(PLSR_REG_SEG_COUNT); | uint16_t seg_n = ReadHoldReg(PLSR_REG_SEG_COUNT); | ||||
| if (seg_n > PLSR_SEG_MAX) | if (seg_n > PLSR_SEG_MAX) | ||||
| { | { | ||||
| seg_n = (uint16_t)PLSR_SEG_MAX; | |||||
| seg_n = (uint16_t)PLSR_SEG_MAX; /* 计数软钳,避免 expect 溢出 */ | |||||
| } | } | ||||
| s_expect_frames = (uint16_t)(1U + seg_n); | |||||
| s_recv_frames = 1U; | |||||
| s_xfer_active = 1U; | |||||
| PlsrParamSetXferStatus(PLSR_PARAM_XFER_BUSY, 0U); | |||||
| s_expect_frames = (uint16_t)(PLSR_PARAM_COMMON_FRAMES + seg_n); | |||||
| s_recv_frames = 1U; /* 帧 1 已计入 */ | |||||
| s_xfer_active = 1U; /* 开始收批 */ | |||||
| } | } | ||||
| else if ((s_xfer_active != 0U) && | else if ((s_xfer_active != 0U) && | ||||
| (PlsrParamIsSegFrameWrite(start_addr, quantity) != 0U)) | |||||
| ((PlsrParamIsCommonTailFrameWrite(start_addr, quantity) != 0U) || | |||||
| (PlsrParamIsSegFrameWrite(start_addr, quantity) != 0U))) | |||||
| { | { | ||||
| s_recv_frames++; | |||||
| WriteHoldReg(PLSR_PARAM_XFER_RECV_REG, s_recv_frames); | |||||
| s_recv_frames++; /* 公共帧 2~5 或段帧 +1 */ | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| return; | |||||
| return; /* 非本批帧 / 未开批 → 忽略 */ | |||||
| } | } | ||||
| if ((s_xfer_active != 0U) && (s_recv_frames >= s_expect_frames)) | if ((s_xfer_active != 0U) && (s_recv_frames >= s_expect_frames)) | ||||
| { | { | ||||
| s_xfer_active = 0U; | |||||
| s_xfer_active = 0U; /* 收齐 */ | |||||
| if (s_param_sem != (OS_EVENT *)0) | if (s_param_sem != (OS_EVENT *)0) | ||||
| { | { | ||||
| (void)OSSemPost(s_param_sem); | |||||
| } | |||||
| else if (PlsrParamBlockApplyFromHold() != 0U) | |||||
| { | |||||
| PlsrParamSetXferStatus(PLSR_PARAM_XFER_OK, 0U); | |||||
| } | |||||
| else | |||||
| { | |||||
| PlsrParamSetXferStatus(PLSR_PARAM_XFER_FAIL, s_recv_frames); | |||||
| (void)OSSemPost(s_param_sem); /* 唤醒 ParamBlockTask */ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| /** @brief 创建信号量并置清空接收计数 */ | |||||
| void PlsrParamBlockInit(void) | void PlsrParamBlockInit(void) | ||||
| { | { | ||||
| s_param_sem = OSSemCreate(0U); | s_param_sem = OSSemCreate(0U); | ||||
| s_expect_frames = 0U; | s_expect_frames = 0U; | ||||
| s_recv_frames = 0U; | s_recv_frames = 0U; | ||||
| s_xfer_active = 0U; | s_xfer_active = 0U; | ||||
| PlsrParamSetXferStatus(PLSR_PARAM_XFER_IDLE, 0U); | |||||
| } | } | ||||
| /** | |||||
| * @brief 参数块任务 | |||||
| * | |||||
| * OSSemPend → ApplyFromHold(校验+写 0x2006)→ 通过则 SaveParamsFromHold。 | |||||
| * 校验失败:不存 BKP,hold 保留上位机原值,由故障码提示修正。 | |||||
| */ | |||||
| void PlsrParamBlockTask(void *pArg) | void PlsrParamBlockTask(void *pArg) | ||||
| { | { | ||||
| INT8U err; | INT8U err; | ||||
| (void)pArg; | (void)pArg; | ||||
| for (;;) | |||||
| while(1) | |||||
| { | { | ||||
| (void)OSSemPend(s_param_sem, 0U, &err); | (void)OSSemPend(s_param_sem, 0U, &err); | ||||
| if (err != OS_ERR_NONE) | if (err != OS_ERR_NONE) | ||||
| @@ -351,12 +364,7 @@ void PlsrParamBlockTask(void *pArg) | |||||
| if (PlsrParamBlockApplyFromHold() != 0U) | if (PlsrParamBlockApplyFromHold() != 0U) | ||||
| { | { | ||||
| PlsrParamSetXferStatus(PLSR_PARAM_XFER_OK, 0U); | |||||
| PlsrPersistPoll(); /* 分帧收齐后尽快落盘,不等 PlsrTask */ | |||||
| } | |||||
| else | |||||
| { | |||||
| PlsrParamSetXferStatus(PLSR_PARAM_XFER_FAIL, s_recv_frames); | |||||
| PlsrPersistSaveParamsFromHold(); /* 仅全部校验通过才存 BKP */ | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -1,140 +1,210 @@ | |||||
| /** | /** | ||||
| * @file plsr_param.h | * @file plsr_param.h | ||||
| * @brief 参数块管理模块:寄存器地址、运行结构体、分帧导入/导出 | |||||
| * @brief 参数块管理:保持寄存器映射、运行映像结构体、分帧导入/导出 | |||||
| * | * | ||||
| * 协议概要: | |||||
| * - 公共帧:0x1000~0x1013(20 字),0x1009=段数 N,预期总帧=1+N | |||||
| * - 段帧:0x1100+(i)*0x10,每段 8 字 | |||||
| * - 传输状态:0x0FFC/0x0FFD/0x0FFE | |||||
| * @details 模块职责 | |||||
| * 1. 定义公共参数区 0x1000~0x1013、段表区 0x1100+i*0x10 的地址与字段语义; | |||||
| * 2. 维护 g_plsr_config / g_plsr_segs[]; | |||||
| * 3. 支持上位机「5 公共帧 + N 段帧」分帧 FC10 写入:收齐后 ApplyFromHold; | |||||
| * 4. 上电由 PersistLoad 填 hold;ExportToHold 仅保留作调试接口。 | |||||
| * | |||||
| * 与其它模块关系 | |||||
| * - modbus_rtu / modbus_data :ReadHoldReg / WriteHoldReg | |||||
| * - plsr_command :启动前 ApplyFromHold;运行中修改当前段 freq_hz | |||||
| * - plsr_run_control :GetCfg / GetSeg / GetSegCount / GetStartSeg | |||||
| * - plsr_persist :拷贝成功后存入BKP;动态改频存频率进BKP | |||||
| * - PlsrParamBlockTask :独立 uC/OS 任务,等信号量后 Apply + 整包存 BKP | |||||
| * | |||||
| * 关键数据流 | |||||
| * 上位机写公共帧1@0x1000 → OnHoldWrite 开传输 expect=5+N,recv=1 | |||||
| * 再写公共帧2~5 → recv++;写段帧@0x1100+ → recv++;收齐 → OSSemPost | |||||
| * ParamBlockTask → ApplyFromHold(校验)→ 通过则 SaveParamsFromHold → 状态 OK/FAIL | |||||
| * 上电:PersistLoad(BKP→hold) → ApplyFromHold(hold→结构体) | |||||
| * | |||||
| * 协议概要 | |||||
| * - 公共 5 帧: | |||||
| * 1: 0x1000~0x100A(11 字) | |||||
| * 2: 0x100B~0x100C(默认速度) | |||||
| * 3: 0x100D~0x100E(起速) | |||||
| * 4: 0x1010~0x1011(止速;跳过 0x100F) | |||||
| * 5: 0x1012~0x1013(加减速时间) | |||||
| * - 段帧:0x1100+(i)*0x10,每段 8 字 | |||||
| * - 预期总帧 = PLSR_PARAM_COMMON_FRAMES + N | |||||
| * - 传输状态:0x0FFC 已收帧 / 0x0FFD 失败帧 / 0x0FFE 状态 | |||||
| */ | */ | ||||
| #ifndef PLSR_PARAM_H | #ifndef PLSR_PARAM_H | ||||
| #define PLSR_PARAM_H | #define PLSR_PARAM_H | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| /** 段表最大段数;与上位机 PLSR_SEG_MAX、寄存器布局一致 */ | |||||
| #define PLSR_SEG_MAX 10U | #define PLSR_SEG_MAX 10U | ||||
| /* ---------- 公共参数保持寄存器 ---------- */ | |||||
| #define PLSR_REG_PULSE_Y 0x1000U /* 0=Y0 1=Y1 2=Y2 */ | |||||
| #define PLSR_REG_DIR_Y 0x1001U /* 固定写 3=Y3 */ | |||||
| #define PLSR_REG_WAIT_X 0x1002U /* WAIT 输入选择 */ | |||||
| #define PLSR_REG_EXT_X 0x1003U /* EXT 输入选择 */ | |||||
| #define PLSR_REG_SEND_MODE 0x1004U /* 0完成 1后续 */ | |||||
| /* ---------- 公共参数保持寄存器(0x1000 起,共 20 字到 0x1013) ---------- */ | |||||
| #define PLSR_REG_PULSE_Y 0x1000U /* 脉冲端子:0=Y0 1=Y1 2=Y2 */ | |||||
| #define PLSR_REG_DIR_Y 0x1001U /* 方向端子:固件强制 3=Y3 */ | |||||
| #define PLSR_REG_WAIT_X 0x1002U /* WAIT 输入选择:0=X4 1=X5 */ | |||||
| #define PLSR_REG_EXT_X 0x1003U /* EXT 输入选择:0=X4 1=X5 */ | |||||
| #define PLSR_REG_SEND_MODE 0x1004U /* 发送方式:0完成 1后续 */ | |||||
| #define PLSR_REG_DIR_DELAY 0x1005U /* 方向建立延时 ms */ | #define PLSR_REG_DIR_DELAY 0x1005U /* 方向建立延时 ms */ | ||||
| #define PLSR_REG_DIR_LOGIC 0x1006U /* 0正逻辑 1负逻辑 */ | |||||
| #define PLSR_REG_ACCEL_MODE 0x1007U /* 0直线 1-S 2正弦 */ | |||||
| #define PLSR_REG_RUN_MODE 0x1008U /* 0相对 1绝对 */ | |||||
| #define PLSR_REG_SEG_COUNT 0x1009U /* 脉冲总段数 N */ | |||||
| #define PLSR_REG_START_SEG 0x100AU /* 起始执行段 1-based */ | |||||
| #define PLSR_REG_DEFAULT_SPD_L 0x100BU /* 默认速度 DW 低 */ | |||||
| #define PLSR_REG_DIR_LOGIC 0x1006U /* 方向逻辑:0正 1负 */ | |||||
| #define PLSR_REG_ACCEL_MODE 0x1007U /* 曲线:0直线 1=S 2正弦 */ | |||||
| #define PLSR_REG_RUN_MODE 0x1008U /* 定位:0相对 1绝对 */ | |||||
| #define PLSR_REG_SEG_COUNT 0x1009U /* 脉冲总段数 N(决定 expect=5+N) */ | |||||
| #define PLSR_REG_START_SEG 0x100AU /* 启动段号 1-based */ | |||||
| #define PLSR_REG_DEFAULT_SPD_L 0x100BU /* 默认速度 Hz,双字低 */ | |||||
| #define PLSR_REG_DEFAULT_SPD_H 0x100CU | #define PLSR_REG_DEFAULT_SPD_H 0x100CU | ||||
| #define PLSR_REG_START_SPD_L 0x100DU /* 起速 DW */ | |||||
| #define PLSR_REG_START_SPD_L 0x100DU /* 起速 Hz */ | |||||
| #define PLSR_REG_START_SPD_H 0x100EU | #define PLSR_REG_START_SPD_H 0x100EU | ||||
| /* 0x100F 保留,写帧填 0 */ | /* 0x100F 保留,写帧填 0 */ | ||||
| #define PLSR_REG_END_SPD_L 0x1010U /* 止速 DW */ | |||||
| #define PLSR_REG_END_SPD_L 0x1010U /* 止速 Hz */ | |||||
| #define PLSR_REG_END_SPD_H 0x1011U | #define PLSR_REG_END_SPD_H 0x1011U | ||||
| #define PLSR_REG_ACCEL_MS 0x1012U | |||||
| #define PLSR_REG_DECEL_MS 0x1013U | |||||
| /* ---------- 段参数:第 i 段基址 = 0x1100 + i*0x10 ---------- */ | |||||
| #define PLSR_REG_ACCEL_MS 0x1012U /* 默认速度加速时间 ms */ | |||||
| #define PLSR_REG_DECEL_MS 0x1013U /* 默认速度减速时间 ms */ | |||||
| /* ---------- 公共参数分帧(5 帧 + N 段帧) ---------- */ | |||||
| #define PLSR_PARAM_COMMON_FRAMES 5U | |||||
| #define PLSR_PARAM_COMMON_FRAME1_WORDS 11U /* 0x1000~0x100A */ | |||||
| #define PLSR_PARAM_COMMON_DWORD_WORDS 2U /* 双字帧字数 */ | |||||
| #define PLSR_REG_COMMON_F1_BASE PLSR_REG_PULSE_Y /* 0x1000 */ | |||||
| #define PLSR_REG_COMMON_F1_END PLSR_REG_START_SEG /* 0x100A */ | |||||
| #define PLSR_REG_COMMON_F2_BASE PLSR_REG_DEFAULT_SPD_L /* 0x100B */ | |||||
| #define PLSR_REG_COMMON_F3_BASE PLSR_REG_START_SPD_L /* 0x100D */ | |||||
| #define PLSR_REG_COMMON_F4_BASE PLSR_REG_END_SPD_L /* 0x1010 */ | |||||
| #define PLSR_REG_COMMON_F5_BASE PLSR_REG_ACCEL_MS /* 0x1012 */ | |||||
| /* ---------- 段参数:第 i 段基址 = 0x1100 + i*0x10,每段 8 字 ---------- */ | |||||
| #define PLSR_REG_SEG1_BASE 0x1100U | #define PLSR_REG_SEG1_BASE 0x1100U | ||||
| #define PLSR_SEG_STRIDE 0x0010U | #define PLSR_SEG_STRIDE 0x0010U | ||||
| #define PLSR_SEG_OFF_FREQ_L 0U /* 频率 SDW */ | |||||
| #define PLSR_SEG_OFF_FREQ_L 0U /* 目标频率 SDW;0=用 default_speed */ | |||||
| #define PLSR_SEG_OFF_FREQ_H 1U | #define PLSR_SEG_OFF_FREQ_H 1U | ||||
| #define PLSR_SEG_OFF_PULSE_L 2U /* 脉冲数 SDW,负=反转 */ | |||||
| #define PLSR_SEG_OFF_PULSE_L 2U /* 脉冲数/绝对坐标 SDW,负=反转 */ | |||||
| #define PLSR_SEG_OFF_PULSE_H 3U | #define PLSR_SEG_OFF_PULSE_H 3U | ||||
| #define PLSR_SEG_OFF_WAIT 4U /* 等待类型 */ | |||||
| #define PLSR_SEG_OFF_WAIT_MS 5U | |||||
| #define PLSR_SEG_OFF_ACT_MS 6U | |||||
| #define PLSR_SEG_OFF_JUMP 7U /* 0=末段停 / 非末段接下一段;1~N=跳转目标段号 */ | |||||
| /* 分帧传输状态(上位机可轮询) */ | |||||
| #define PLSR_PARAM_XFER_RECV_REG 0x0FFCU | |||||
| #define PLSR_PARAM_XFER_FAIL_FRAME_REG 0x0FFDU | |||||
| #define PLSR_PARAM_XFER_STATUS_REG 0x0FFEU | |||||
| #define PLSR_PARAM_XFER_IDLE 0U | |||||
| #define PLSR_PARAM_XFER_BUSY 1U | |||||
| #define PLSR_PARAM_XFER_OK 2U | |||||
| #define PLSR_PARAM_XFER_FAIL 3U | |||||
| #define PLSR_SEG_OFF_WAIT 4U /* PlsrWaitType_e */ | |||||
| #define PLSR_SEG_OFF_WAIT_MS 5U /* WAIT 时间 ms */ | |||||
| #define PLSR_SEG_OFF_ACT_MS 6U /* ACT 时间 ms */ | |||||
| #define PLSR_SEG_OFF_JUMP 7U /* 0=顺序/末段停;1~N=跳转目标段号 */ | |||||
| /** 方向逻辑:正转时方向脚电平 */ | |||||
| typedef enum { | typedef enum { | ||||
| PLSR_DIR_LOGIC_POS = 0, /* 正转→方向高,反转→低 */ | PLSR_DIR_LOGIC_POS = 0, /* 正转→方向高,反转→低 */ | ||||
| PLSR_DIR_LOGIC_NEG = 1 /* 相反 */ | PLSR_DIR_LOGIC_NEG = 1 /* 相反 */ | ||||
| } PlsrDirLogic_e; | } PlsrDirLogic_e; | ||||
| /** | |||||
| * 发送方式(与等待类型一起决定本段 f_end,并影响是否允许同向不停表衔接) | |||||
| * COMPLETE:EXT_OR_DONE 同向时匀速跑完;反向或其它等待类型规划到止速 | |||||
| * FOLLOW:仅 EXT_OR_DONE 同向可规划到下一段目标并允许不停表衔接;反向同其它等待 | |||||
| */ | |||||
| typedef enum { | typedef enum { | ||||
| PLSR_SEND_COMPLETE = 0, /* 完成:上一段发完后,本段开头再加减速到本段频率 */ | |||||
| PLSR_SEND_FOLLOW = 1 /* 后续:上一段发完时频率已到本段目标;同向可不停表 */ | |||||
| PLSR_SEND_COMPLETE = 0, | |||||
| PLSR_SEND_FOLLOW = 1 | |||||
| } PlsrSendMode_e; | } PlsrSendMode_e; | ||||
| /** 加减速曲线形状 */ | |||||
| typedef enum { | typedef enum { | ||||
| PLSR_ACCEL_LINEAR = 0, | |||||
| PLSR_ACCEL_S = 1, | |||||
| PLSR_ACCEL_SINE = 2 | |||||
| PLSR_ACCEL_LINEAR = 0, /* f^2 = f0^2 ± 2an */ | |||||
| PLSR_ACCEL_S = 1, /* 时间域七段 S:加速度梯形 1:2:1 */ | |||||
| PLSR_ACCEL_SINE = 2 /* 时间域 raised-cosine */ | |||||
| } PlsrAccelMode_e; | } PlsrAccelMode_e; | ||||
| /** | |||||
| * 定位模式 | |||||
| * RELATIVE:pulse_cnt = 本段相对位移(可负) | |||||
| * ABSOLUTE:pulse_cnt = 相对「本次启动原点」的目标坐标 | |||||
| */ | |||||
| typedef enum { | typedef enum { | ||||
| PLSR_POS_RELATIVE = 0, /* 脉冲数=相对位移 */ | |||||
| PLSR_POS_ABSOLUTE = 1 /* 脉冲数=绝对坐标 */ | |||||
| PLSR_POS_RELATIVE = 0, | |||||
| PLSR_POS_ABSOLUTE = 1 | |||||
| } PlsrPosMode_e; | } PlsrPosMode_e; | ||||
| /** | |||||
| * 段末/段内等待类型 | |||||
| * TIME/SIGNAL/ACT/EXT:完成/后续均规划到止速;并阻止 FOLLOW 不停表衔接 | |||||
| * EXT_OR_DONE:与下一段同向时后续可接目标/完成匀速跑完并可 FOLLOW 衔接; | |||||
| * 与下一段反向时规划同其它等待(减速到止速,下一段从起速再爬) | |||||
| */ | |||||
| typedef enum { | typedef enum { | ||||
| PLSR_WAIT_TIME = 0, /* 发完后等 wait_ms,再跳转 */ | |||||
| PLSR_WAIT_SIGNAL = 1, /* 发完后等 WAIT 口下降沿(EXTI),再跳转 */ | |||||
| PLSR_WAIT_ACT = 2, /* ACT:满 act_ms 从当前频率切下一段;脉冲先完则等到点 */ | |||||
| PLSR_WAIT_EXT = 3, /* 运行中 EXT 下降沿中途切段;发完后仍等 EXT */ | |||||
| PLSR_WAIT_EXT_OR_DONE = 4 /* EXT 沿或脉冲发完,先到先切段 */ | |||||
| PLSR_WAIT_TIME = 0, /* 发完后等 wait_ms(0 按 1ms),再跳转 */ | |||||
| PLSR_WAIT_SIGNAL = 1, /* 发完后等 WAIT 口下降沿 */ | |||||
| PLSR_WAIT_ACT = 2, /* 开跑后满 act_ms 从当前频切下一段;脉冲先完则等到点 */ | |||||
| PLSR_WAIT_EXT = 3, /* 运行中 EXT 沿中途切段;发完后仍等 EXT */ | |||||
| PLSR_WAIT_EXT_OR_DONE = 4 /* EXT 沿或脉冲发完,先到先切 */ | |||||
| } PlsrWaitType_e; | } PlsrWaitType_e; | ||||
| /** 公共运行参数(运动侧只读此映像,不直接啃寄存器) */ | |||||
| /** | |||||
| * 公共运行参数映像 | |||||
| */ | |||||
| typedef struct { | typedef struct { | ||||
| uint16_t pulse_y; /* 0=Y0/PF6/TIM10 1=Y1/PF8/TIM13 2=Y2/PF7/TIM11 */ | |||||
| uint16_t dir_y; /* 固定 3=Y3,不可选 */ | |||||
| uint16_t wait_x_sel; | |||||
| uint16_t pulse_y; /* 0=Y0/TIM10 1=Y1/TIM13 2=Y2/TIM11 */ | |||||
| uint16_t dir_y; /* 固定 3=Y3 */ | |||||
| uint16_t wait_x_sel; /* 0=X4 1=X5 */ | |||||
| uint16_t ext_x_sel; | uint16_t ext_x_sel; | ||||
| PlsrSendMode_e send_mode; | PlsrSendMode_e send_mode; | ||||
| uint16_t dir_delay_ms; | uint16_t dir_delay_ms; | ||||
| PlsrDirLogic_e dir_logic; | PlsrDirLogic_e dir_logic; | ||||
| PlsrAccelMode_e accel_mode; | PlsrAccelMode_e accel_mode; | ||||
| PlsrPosMode_e run_mode; | PlsrPosMode_e run_mode; | ||||
| uint16_t seg_count; | |||||
| uint16_t seg_count; /* 有效段数 0~PLSR_SEG_MAX */ | |||||
| uint16_t start_seg; /* 1-based */ | uint16_t start_seg; /* 1-based */ | ||||
| uint32_t default_speed; /* 脉冲默认速度 Hz:斜率 K=默认速度/加减速时间;段频为 0 时也作段目标 */ | |||||
| uint32_t start_speed; /* 起速Hz;0=从静止起跳(运行层 ResolveStartHz) */ | |||||
| uint32_t end_speed; /* 止速Hz;0=落地频率后停表(运行层 ResolveEndHz) */ | |||||
| uint16_t accel_ms; /* 默认速度加速时间 ms(定加速斜率) */ | |||||
| uint16_t decel_ms; /* 默认速度减速时间 ms(定减速斜率) */ | |||||
| uint32_t default_speed; /* Hz;段频为 0 时作段目标;并定斜率 */ | |||||
| uint32_t start_speed; /* 起速;0=运行层 ResolveStartHz 起跳 */ | |||||
| uint32_t end_speed; /* 止速;0=ResolveEndHz 落地后停表 */ | |||||
| uint16_t accel_ms; | |||||
| uint16_t decel_ms; | |||||
| } PlsrCfg_t; | } PlsrCfg_t; | ||||
| /** 单段运行参数 */ | /** 单段运行参数 */ | ||||
| typedef struct { | typedef struct { | ||||
| int32_t freq_hz; /* 0=用默认速度;须解析后在 1~100k,否则故障 */ | |||||
| int32_t freq_hz; /* 0=用默认速度;解析后须在 1~100k,否则 0x04 */ | |||||
| int32_t pulse_cnt; /* 可负=反向;0=本段不发 */ | int32_t pulse_cnt; /* 可负=反向;0=本段不发 */ | ||||
| PlsrWaitType_e wait_type; | PlsrWaitType_e wait_type; | ||||
| uint16_t wait_ms; | uint16_t wait_ms; | ||||
| uint16_t act_ms; | uint16_t act_ms; | ||||
| uint16_t jump_seg; /* 0=末段结束 / 非末段则顺序下一段;1~N=跳转目标段号 */ | |||||
| uint16_t jump_seg; /* 0=顺序/末段结束;1~N=跳转 */ | |||||
| } PlsrSeg_t; | } PlsrSeg_t; | ||||
| /* ---------- 运行映像访问 ---------- */ | |||||
| void PlsrParamInitDefault(void); | |||||
| PlsrCfg_t *PlsrParamGetCfg(void); | |||||
| PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based); | |||||
| /* ---------- 运行参数 ---------- */ | |||||
| extern PlsrCfg_t g_plsr_config; | |||||
| extern PlsrSeg_t g_plsr_segs[PLSR_SEG_MAX]; | |||||
| /** @return 有效段数,钳制到 [0, PLSR_SEG_MAX] */ | |||||
| uint16_t PlsrParamGetSegCount(void); | uint16_t PlsrParamGetSegCount(void); | ||||
| /** | |||||
| * @return 启动段 1-based,钳制到 [1, seg_count] | |||||
| * @note 非法起始段当前仅裁剪,TODO 可报故障码 | |||||
| */ | |||||
| uint16_t PlsrParamGetStartSeg(void); | uint16_t PlsrParamGetStartSeg(void); | ||||
| /* ---------- 分帧导入 / 导出 / 管理任务 ---------- */ | /* ---------- 分帧导入 / 导出 / 管理任务 ---------- */ | ||||
| /** OSInit 之后调用,创建分帧应用信号量 */ | |||||
| /** | |||||
| * @brief 创建分帧应用信号量,清传输状态 | |||||
| * @note 调用时机:OSInit 之后、任务创建前 | |||||
| */ | |||||
| void PlsrParamBlockInit(void); | void PlsrParamBlockInit(void); | ||||
| /** 等信号量后 hold→结构体 */ | |||||
| /** | |||||
| * @brief 参数块任务:阻塞等信号量 → ApplyFromHold(校验)→ 通过则 SaveParamsFromHold | |||||
| * @param pArg 未使用 | |||||
| */ | |||||
| void PlsrParamBlockTask(void *pArg); | void PlsrParamBlockTask(void *pArg); | ||||
| /** FC10 应答后由 Modbus 调用:分帧计数,收齐则 post 信号量 */ | |||||
| /** | |||||
| * @brief FC10 写保持寄存器后由 Modbus 回调 | |||||
| * @param start_addr 起始地址 | |||||
| * @param quantity 字数 | |||||
| * @note 公共帧开传输;段帧累加;收齐 post 信号量 | |||||
| */ | |||||
| void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity); | void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity); | ||||
| /** 寄存器→运行结构体;成功返回 1(完整校验/故障码待实现) */ | |||||
| /** | |||||
| * @brief 保持寄存器 → g_plsr_config / g_plsr_segs,边拷贝边校验 | |||||
| * @return 1=全部通过;0=存在门禁故障 | |||||
| */ | |||||
| uint8_t PlsrParamBlockApplyFromHold(void); | uint8_t PlsrParamBlockApplyFromHold(void); | ||||
| /** 运行结构体→寄存器(上电默认、上位机读回) */ | |||||
| void PlsrParamBlockExportToHold(void); | |||||
| #endif | #endif | ||||
| @@ -1,69 +0,0 @@ | |||||
| /** | |||||
| * @file plsr_path_plan.c | |||||
| * @brief 多段路径规划实现(只做决策,不碰 TIM/GPIO) | |||||
| */ | |||||
| #include "plsr_path_plan.h" | |||||
| #include "plsr_param.h" | |||||
| int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based) | |||||
| { | |||||
| PlsrSeg_t *seg = PlsrParamGetSeg(cur_seg_0based); | |||||
| uint16_t n = PlsrParamGetSegCount(); | |||||
| uint16_t jump = seg->jump_seg; | |||||
| /* | |||||
| * 跳转目标(1-based): | |||||
| * 0 → 末段:结束;非末段:顺序下一段(cur+1) | |||||
| * 1..N → 跳到对应段(可顺序、回跳循环、任意段) | |||||
| * 其它 → 结束 | |||||
| */ | |||||
| if (jump == 0U) | |||||
| { | |||||
| /* 非末段写 0:视为顺序下一档,不是停机 */ | |||||
| if ((n >= 1U) && (cur_seg_0based + 1U < n)) | |||||
| { | |||||
| return (int16_t)(cur_seg_0based + 1U); | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| if ((jump >= 1U) && (jump <= n)) | |||||
| { | |||||
| return (int16_t)(jump - 1U); | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse) | |||||
| { | |||||
| PlsrSeg_t *seg = PlsrParamGetSeg(seg_0based); | |||||
| PlsrCfg_t *cfg = PlsrParamGetCfg(); | |||||
| int32_t move; | |||||
| if (cfg->run_mode == PLSR_POS_ABSOLUTE) | |||||
| { | |||||
| move = seg->pulse_cnt - acc_pulse; | |||||
| } | |||||
| else | |||||
| { | |||||
| move = seg->pulse_cnt; | |||||
| } | |||||
| return (move >= 0) ? 1U : 0U; | |||||
| } | |||||
| uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based) | |||||
| { | |||||
| PlsrSeg_t *seg = PlsrParamGetSeg(seg_0based); | |||||
| /* 仅段后 WAIT 时间;0 按 1ms(手册);信号/ACT/EXT 由 run_control 处理 */ | |||||
| if (seg->wait_type == PLSR_WAIT_TIME) | |||||
| { | |||||
| return (seg->wait_ms == 0U) ? 1U : seg->wait_ms; | |||||
| } | |||||
| (void)seg_0based; | |||||
| return 0U; | |||||
| } | |||||
| int16_t PlsrPathPlanResolveAfterSeg(uint16_t cur_seg_0based) | |||||
| { | |||||
| return PlsrPathPlanNextSeg(cur_seg_0based); | |||||
| } | |||||
| @@ -1,37 +0,0 @@ | |||||
| /** | |||||
| * @file plsr_path_plan.h | |||||
| * @brief 多段路径规划:段序、跳转、段间等待、相对/绝对方向判定 | |||||
| * | |||||
| * 不直接驱动硬件;由运行控制查询后执行。 | |||||
| * 等待类型由 run_control + signal_io 执行(时间用 TIM3,信号用 EXTI)。 | |||||
| */ | |||||
| #ifndef PLSR_PATH_PLAN_H | |||||
| #define PLSR_PATH_PLAN_H | |||||
| #include <stdint.h> | |||||
| /** | |||||
| * @brief 解析下一段索引 | |||||
| * @param cur_seg_0based 当前段 0-based | |||||
| * @return 下一段 0-based;无下一段或非法跳转返回 -1 | |||||
| * @note jump=0:末段→结束;非末段→顺序下一段 | |||||
| * jump=1~N:跳到对应段(可循环) | |||||
| */ | |||||
| int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based); | |||||
| /** | |||||
| * @brief 判断该段运动方向 | |||||
| * @param acc_pulse 当前累计脉冲(绝对模式算位移用) | |||||
| * @return 1=正向,0=反向 | |||||
| */ | |||||
| uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse); | |||||
| /** | |||||
| * @brief 段结束后 WAIT 时间(ms);仅 WAIT_TIME 有效,0 按 1ms | |||||
| */ | |||||
| uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based); | |||||
| /** 段结束(或等待结束)后解析下一段,语义同 NextSeg */ | |||||
| int16_t PlsrPathPlanResolveAfterSeg(uint16_t cur_seg_0based); | |||||
| #endif | |||||
| @@ -1,6 +1,13 @@ | |||||
| /** | /** | ||||
| * @file plsr.c | * @file plsr.c | ||||
| * @brief PLSR 门面实现:转发到运行控制,并提供周期任务 | |||||
| * @brief PLSR 顶层初始化和周期任务 | |||||
| * | |||||
| * @details 按顺序初始化各模块,以及运行 PlsrTask 周期任务。 | |||||
| * | |||||
| * 关键数据流 | |||||
| * PlsrInit 顺序:PersistLoad(BKP→hold) → ApplyFromHold(hold→结构体) → … | |||||
| * ModbusDataInit 清 hold 后:再次 PersistLoad(BKP→hold),不再 ExportToHold | |||||
| * PlsrTask 循环:TickMs → DebouncePoll → CommandPoll → TickMonitor → WakePend | |||||
| */ | */ | ||||
| #include "plsr.h" | #include "plsr.h" | ||||
| #include "plsr_pulse_driver.h" | #include "plsr_pulse_driver.h" | ||||
| @@ -11,117 +18,75 @@ | |||||
| #include "plsr_signal_io.h" | #include "plsr_signal_io.h" | ||||
| #include "ucos_ii.h" | #include "ucos_ii.h" | ||||
| /** | |||||
| * @brief 子系统初始化 | |||||
| * | |||||
| * 顺序要点 | |||||
| * PersistLoad → Apply:BKP → hold → 结构体(BKP 内参数已合法); | |||||
| * 非上电不再把结构体 Export 回 hold;Modbus 清零后由 BKP 再填 hold。 | |||||
| */ | |||||
| void PlsrInit(void) | void PlsrInit(void) | ||||
| { | { | ||||
| PlsrParamInitDefault(); | |||||
| PlsrPersistLoad(); /* 有 Flash 记录则覆盖公共参数;段表仍用默认 */ | |||||
| PlsrPersistInit(); /* 使能 BKPSRAM 写访问,否则存BKP无效 */ | |||||
| PlsrPersistLoad(); /* BKPSRAM → g_hold_reg */ | |||||
| (void)PlsrParamBlockApplyFromHold(); /* hold → 运行参数 + 校验 */ | |||||
| PlsrPulseDriverInit(); | PlsrPulseDriverInit(); | ||||
| PlsrSignalIoInit(); | PlsrSignalIoInit(); | ||||
| PlsrRunControlInit(); | PlsrRunControlInit(); | ||||
| PlsrParamBlockExportToHold(); /* 上电把运行映像摊到保持寄存器 */ | |||||
| PlsrPersistRestoreRuntime(); /* hold 0x2000 累计 → run_control */ | |||||
| PlsrCommandInit(); | PlsrCommandInit(); | ||||
| PlsrPersistRestoreMonitor(); /* 恢复故障码;0x3000/运行态强制空闲 */ | |||||
| PlsrPersistTickMonitor(); /* 监控区 hold+BKP 与运行态对齐 */ | |||||
| } | } | ||||
| uint8_t PlsrStart(uint16_t start_seg) | |||||
| { | |||||
| if (start_seg == 0U) | |||||
| { | |||||
| start_seg = PlsrParamGetStartSeg(); | |||||
| } | |||||
| return PlsrRunControlStart(start_seg); | |||||
| } | |||||
| void PlsrStop(void) | |||||
| { | |||||
| PlsrRunControlStop(); | |||||
| } | |||||
| uint8_t PlsrChangeFreq(uint32_t new_tgt_hz) | |||||
| { | |||||
| return PlsrRunControlChangeFreq(new_tgt_hz); | |||||
| } | |||||
| uint8_t PlsrIsBusy(void) | |||||
| { | |||||
| return PlsrRunControlIsBusy(); | |||||
| } | |||||
| int32_t PlsrGetAccPulse(void) | |||||
| { | |||||
| return PlsrRunControlGetAccPulse(); | |||||
| } | |||||
| void PlsrClearAccPulse(void) | |||||
| { | |||||
| PlsrRunControlClearAccPulse(); | |||||
| } | |||||
| void PlsrOnPulseIsr(void) | |||||
| { | |||||
| PlsrRunControlOnPulseIsr(); | |||||
| } | |||||
| /** | |||||
| * @brief PLSR 主周期任务 | |||||
| * @param pArg 未使用 | |||||
| * | |||||
| * 任务内局部状态: | |||||
| * s_booted — 首次延迟导出完成标志 | |||||
| * s_was_busy — 仅 LOOP_TEST:上一拍 busy,用于检测「刚结束」边沿 | |||||
| * s_idle_ticks — 仅 LOOP_TEST:空闲累计 tick,满 OS_TICKS_PER_SEC 自动再启 | |||||
| */ | |||||
| void PlsrTask(void *pArg) | void PlsrTask(void *pArg) | ||||
| { | { | ||||
| static uint8_t s_booted = 0U; | |||||
| #if (PLSR_LOOP_TEST_EN != 0) | |||||
| static uint8_t s_was_busy = 0U; | |||||
| static uint16_t s_idle_ticks = 0U; | |||||
| #endif | |||||
| static uint8_t s_booted = 0U; /* 1=已完成首次延迟对齐(等 Modbus 填 hold) */ | |||||
| (void)pArg; | (void)pArg; | ||||
| for (;;) | |||||
| PlsrRunControlWakeInit(); /* OS 已启动,创建 WAIT/ACT 到期唤醒信号量 */ | |||||
| while(1) | |||||
| { | { | ||||
| /* 首次进入:等 Modbus 清寄存器后再导出运行映像,避免被冲掉 */ | |||||
| /* | |||||
| * 首次进入:ModbusSlaveTask 已 DataInit 清 hold 并 PersistLoad 填回 BKP 参数。 | |||||
| * 此处仅恢复监控/指令侧,不再 Export 结构体到 hold。 | |||||
| */ | |||||
| if (s_booted == 0U) | if (s_booted == 0U) | ||||
| { | { | ||||
| OSTimeDly(30U); /* ~300ms @ 100Hz tick */ | |||||
| PlsrParamBlockExportToHold(); | |||||
| OSTimeDly(3U); /* ~30ms @ 100Hz tick;让出 CPU 给 Modbus 完成初始化 */ | |||||
| PlsrCommandInit(); | PlsrCommandInit(); | ||||
| PlsrPersistRestoreMonitor(); | |||||
| PlsrPersistTickMonitor(); | |||||
| s_booted = 1U; | s_booted = 1U; | ||||
| #if (PLSR_LOOP_TEST_EN != 0) | |||||
| s_was_busy = 0U; | |||||
| s_idle_ticks = 0U; | |||||
| #endif | |||||
| } | } | ||||
| PlsrCommandPoll(); /* 收 START/STOP 等,回写忙闲状态 */ | |||||
| PlsrPersistPoll(); /* 公共参数有改且空闲则追加写入 Flash */ | |||||
| PlsrRunControlTickMs(); /* 方向延时、段间等待到期处理 */ | |||||
| PlsrRunControlTickMs(); /* 处理延时/外部信号输入,优先:WAIT/换向到期立刻开表 */ | |||||
| PlsrSignalIoDebouncePoll(); /* OS 节拍消抖确认 */ | |||||
| PlsrCommandPoll(); /* 收 START/STOP/CLR,回写 0x2000~0x2006 监控 ,决定是否开pwm输出*/ | |||||
| PlsrPersistTickMonitor(); /* 监控区:运行态→hold→BKP(每 OS 节拍) */ | |||||
| #if (PLSR_LOOP_TEST_EN != 0) | |||||
| /* 调试:空闲满 1s 自动从第 1 段再启 */ | |||||
| if (s_booted != 0U) | |||||
| /* | |||||
| * 换向 / WAIT 时间:WakePend(0) 只等 TIM5 Post(墙钟≈设定 ms)。 | |||||
| * 其它:超时 1 OS tick,兼顾 EXT 轮询。 | |||||
| */ | |||||
| if (PlsrRunControlIsPreciseWait() != 0U) | |||||
| { | { | ||||
| uint8_t busy = PlsrIsBusy(); | |||||
| if ((s_was_busy != 0U) && (busy == 0U)) | |||||
| { | |||||
| s_idle_ticks = 0U; | |||||
| } | |||||
| if (busy == 0U) | |||||
| { | |||||
| s_idle_ticks++; | |||||
| if (s_idle_ticks >= (uint16_t)OS_TICKS_PER_SEC) | |||||
| { | |||||
| s_idle_ticks = 0U; | |||||
| if (PlsrParamGetSegCount() >= 1U) | |||||
| { | |||||
| (void)PlsrStart(1U); | |||||
| } | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| s_idle_ticks = 0U; | |||||
| } | |||||
| s_was_busy = busy; | |||||
| PlsrRunControlWakePend(0U); | |||||
| } | |||||
| else | |||||
| { | |||||
| PlsrRunControlWakePend(1U); | |||||
| } | } | ||||
| #endif | |||||
| OSTimeDly(1U); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,47 +1,83 @@ | |||||
| /** | /** | ||||
| * @file plsr.h | * @file plsr.h | ||||
| * @brief PLSR 对外门面:初始化、周期任务、启停与状态查询 | |||||
| * @brief PLSR 多段脉冲输出子系统公共接口 | |||||
| * | * | ||||
| * 内部模块:参数块 / 公共参数持久化 / 指令接口 / 路径规划 / 加减速曲线 / 运行控制 / 脉冲驱动 | |||||
| * @details 模块职责 | |||||
| * 本文件只声明公共入口与共享运行状态。初始化和周期任务实现在 plsr.c; | |||||
| * 启停和脉冲中断直接实现在 run_control.c,不再经过 plsr.c 转发。 | |||||
| * | |||||
| * 与其它模块关系 | |||||
| * - plsr_param :运行参数映像(公共 + 段表),Modbus 保持寄存器 ↔ 结构体 | |||||
| * - plsr_persist :BKPSRAM 掉电保存(公共+段表、动态频率、监控区) | |||||
| * - plsr_command :Modbus 监控区 0x2000~0x2006、控制区 0x3000 指令解析 | |||||
| * - plsr_run_control:段状态机、等待策略、加减速执行、故障诊断 | |||||
| * - plsr_pulse_driver:TIM PWM 脉冲 + Y3 方向 GPIO | |||||
| * - plsr_signal_io :WAIT/EXT 外部输入、TIM5 延时 | |||||
| * | |||||
| * 关键数据流 | |||||
| * 上电:PlsrInit → PersistLoad(BKP→hold) → ApplyFromHold → … | |||||
| * →(ModbusDataInit 后再次 PersistLoad 填 hold) | |||||
| * 写参:分帧收齐 → ParamBlockTask Apply(校验)→ 通过则 SaveParamsFromHold | |||||
| */ | */ | ||||
| #ifndef PLSR_H | #ifndef PLSR_H | ||||
| #define PLSR_H | #define PLSR_H | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #include "plsr_param.h" | |||||
| /* 1=第1段跑完后停约1s再循环(仅调试用,正式产品保持 0) */ | |||||
| #ifndef PLSR_LOOP_TEST_EN | |||||
| #define PLSR_LOOP_TEST_EN 0 | |||||
| #endif | |||||
| /* run_control 拥有的共享运行状态;段下标从 0 开始。 */ | |||||
| extern volatile uint8_t g_plsr_busy; /* 运行忙标志(上位机监控) */ | |||||
| extern volatile uint16_t g_plsr_cur_seg_idx; /* 当前段下标(0-based) */ | |||||
| extern volatile int32_t g_plsr_accumulated_pulses; /* 全局累计脉冲(有符号) */ | |||||
| #include "plsr_param.h" | |||||
| /** 初始化参数映像、脉冲驱动、运行控制、指令寄存器 */ | |||||
| /** | |||||
| * @brief plsr任务初始化 | |||||
| */ | |||||
| void PlsrInit(void); | void PlsrInit(void); | ||||
| /** uC/OS 任务:周期处理指令 + 运行控制节拍 */ | |||||
| /** | |||||
| * @brief uC/OS-II 周期任务入口 | |||||
| * @param pArg 任务参数 | |||||
| */ | |||||
| void PlsrTask(void *pArg); | void PlsrTask(void *pArg); | ||||
| /** | /** | ||||
| * @brief 启动多段脉冲 | |||||
| * @param start_seg 起始段号 1-based;传 0 则用参数块中的起始段 | |||||
| * @return 1=已启动,0=忙或无有效段 | |||||
| * @brief 启动多段脉冲序列 | |||||
| * @param start_seg 起始段号,1-based;传 0 则使用参数块中的 start_seg | |||||
| * @return 1=已成功置忙并开段;0=当前忙、无有效段或首段频率非法(写故障码 0x04) | |||||
| * @note 调用时机:应用层或 CommandPoll 收到 START 位后 | |||||
| * @note 副作用:清外部沿锁存、设置绝对坐标原点、进入 RC_DIR_WAIT 或 RC_RUN | |||||
| */ | */ | ||||
| uint8_t PlsrStart(uint16_t start_seg); | uint8_t PlsrStart(uint16_t start_seg); | ||||
| /** 急停:关 PWM、清方向、退出运行 */ | |||||
| /** | |||||
| * @brief 急停 | |||||
| * @note 调用时机:CommandPoll 收到 STOP 位,或应用层主动停止 | |||||
| * @note 副作用:关 PWM、解锁 PSC、清方向、复位运行状态机与 ACT/WAIT 标志 | |||||
| */ | |||||
| void PlsrStop(void); | void PlsrStop(void); | ||||
| /** | /** | ||||
| * @brief 运行中动态改目标频率(当前输出频率 → new_tgt,按 accel_mode 过渡) | |||||
| * @return 1=已重规划,0=非运行或频率非法 | |||||
| * @brief 运行中动态修改当前段目标频率 | |||||
| * @param new_tgt_hz 新目标频率 Hz,须在 [1, 100000] | |||||
| * @return 1=已按剩余脉冲重规划;0=非 RC_RUN、频率非法或本段已发完 | |||||
| * @note 调用时机:CommandPoll 处理运行中 FC10 写频,或直接调用 | |||||
| * @note 副作用:以当前输出频率为起点、剩余脉冲为总长重新 PlanSeg;非法频率写 0x04 | |||||
| */ | */ | ||||
| uint8_t PlsrChangeFreq(uint32_t new_tgt_hz); | uint8_t PlsrChangeFreq(uint32_t new_tgt_hz); | ||||
| uint8_t PlsrIsBusy(void); /**< 1=运动中 */ | |||||
| int32_t PlsrGetAccPulse(void); /**< 累计脉冲(绝对坐标用) */ | |||||
| /** | |||||
| * @brief 清零累计脉冲与绝对坐标原点 | |||||
| * @note 调用时机:CommandPoll 收到 CLR 控制位 | |||||
| * @note 同时清除累计脉冲和绝对模式原点,下次 Start 重新锁定原点 | |||||
| */ | |||||
| void PlsrClearAccPulse(void); | void PlsrClearAccPulse(void); | ||||
| /** TIM UPDATE 中断入口(每发出一个脉冲调用一次) */ | |||||
| /** | |||||
| * @brief 脉冲 TIM UPDATE 中断入口 | |||||
| * @note 调用时机:每发出一个脉冲边沿,由 stm32f4xx_it 中对应 TIM 回调转发 | |||||
| * @note 副作用:累计本段脉冲和坐标、逐拍改频、处理段末/ACT 边界 | |||||
| */ | |||||
| void PlsrOnPulseIsr(void); | void PlsrOnPulseIsr(void); | ||||
| #endif | #endif | ||||
| @@ -1,15 +1,27 @@ | |||||
| /** | /** | ||||
| * @file plsr_pulse_driver.h | * @file plsr_pulse_driver.h | ||||
| * @brief 脉冲输出驱动(方案书 5.1):TIM 脉冲 + 方向 GPIO | |||||
| * @brief 脉冲输出驱动(方案书 5.1):TIM PWM 脉冲 + 方向 GPIO | |||||
| * | * | ||||
| * 脉冲端子(由参数 pulse_y 选择;端子编号与 TIM 编号交叉): | |||||
| * @details 模块职责 | |||||
| * 按公共参数 pulse_y 选择 Y0/Y1/Y2 之一输出方波脉冲,方向固定走 Y3。 | |||||
| * 段内尽量锁定 PSC、只改 ARR,使加减速改频在周期边界生效,减少毛刺与多计边沿。 | |||||
| * ISR 热路径只写预装载 ARR/CCR;必须换 PSC 时挂起,由任务 ApplyPending 处理。 | |||||
| * | |||||
| * 与其它模块关系 | |||||
| * - plsr_param :读 pulse_y / dir_logic | |||||
| * - plsr_run_control :Start / SetFreq / SetFreqIsr / Stop / 方向 / PSC 锁 | |||||
| * - stm32f4xx_it :TIM UPDATE → PlsrOnPulseIsr;IsPulseTim 识别脉冲 TIM | |||||
| * | |||||
| * 端子与 TIM 映射(端子编号与 TIM 编号交叉,勿按序号假设) | |||||
| * Y0 → PF6 / TIM10_CH1 AF3 (APB2 Timer 168MHz) | * Y0 → PF6 / TIM10_CH1 AF3 (APB2 Timer 168MHz) | ||||
| * Y1 → PF8 / TIM13_CH1 AF9 (APB1 Timer 84MHz) | * Y1 → PF8 / TIM13_CH1 AF9 (APB1 Timer 84MHz) | ||||
| * Y2 → PF7 / TIM11_CH1 AF3 (APB2 Timer 168MHz) | * Y2 → PF7 / TIM11_CH1 AF3 (APB2 Timer 168MHz) | ||||
| * 方向固定 Y3 → PF9 GPIO(不可选) | |||||
| * Y3 → PF9 GPIO 方向(固定,不可选) | |||||
| * | * | ||||
| * 频率 0:停止该路 PWM;>0 时按 Hz 输出。 | |||||
| * 段内锁定 PSC、只改 ARR;改频请求在脉冲周期边界生效,减少毛刺。 | |||||
| * 频率约定 | |||||
| * 0 :停止该路 PWM(空闲 Forced inactive 拉低) | |||||
| * 1~100k:按 Hz 输出;超出由 Calc 路径钳到 100k | |||||
| * 波形:空闲/开跑起点低;运行中 PWM1 每周期先高后低 | |||||
| */ | */ | ||||
| #ifndef PLSR_PULSE_DRIVER_H | #ifndef PLSR_PULSE_DRIVER_H | ||||
| #define PLSR_PULSE_DRIVER_H | #define PLSR_PULSE_DRIVER_H | ||||
| @@ -17,32 +29,116 @@ | |||||
| #include <stdint.h> | #include <stdint.h> | ||||
| #include "stm32f4xx_hal.h" | #include "stm32f4xx_hal.h" | ||||
| /** HAL 句柄:由本模块 Init 填充,中断向量里转发到 HAL_TIM_IRQHandler */ | |||||
| extern TIM_HandleTypeDef htim10; | extern TIM_HandleTypeDef htim10; | ||||
| extern TIM_HandleTypeDef htim11; | extern TIM_HandleTypeDef htim11; | ||||
| extern TIM_HandleTypeDef htim13; | extern TIM_HandleTypeDef htim13; | ||||
| /* 驱动拥有的真实输出状态,run_control 直接读取,不再保存第二份。 */ | |||||
| extern volatile uint32_t g_plsr_output_freq_hz; /* 当前输出频率 Hz */ | |||||
| extern volatile uint8_t g_plsr_pwm_running; /* 1=PWM 正在跑 */ | |||||
| extern uint8_t g_plsr_direction_forward; /* 1=正转方向语义 */ | |||||
| extern uint8_t g_plsr_direction_valid; /* 1=方向脚已有效写出 */ | |||||
| /** | |||||
| * @brief 初始化三路脉冲 TIM + 方向 GPIO | |||||
| * @note 调用时机:PlsrInit,在 RunControl 之前 | |||||
| * @note 副作用:Y0/Y1/Y2 空闲拉低、计数器关;Y3 输出低 | |||||
| */ | |||||
| void PlsrPulseDriverInit(void); | void PlsrPulseDriverInit(void); | ||||
| /** | |||||
| * @brief 按正/反向与 dir_logic 写 Y3 方向脚 | |||||
| * @param forward 1=正转语义,0=反转 | |||||
| */ | |||||
| void PlsrPulseDriverSetDir(uint8_t forward); | void PlsrPulseDriverSetDir(uint8_t forward); | ||||
| /** @brief 方向脚拉低(收尾/急停) */ | |||||
| void PlsrPulseDriverClearDir(void); | void PlsrPulseDriverClearDir(void); | ||||
| /** 按本段 [f_min,f_max] 锁定 PSC,整段只改 ARR */ | |||||
| /** | |||||
| * @brief 按本段频率跨度 [f_min,f_max] 尝试锁定单一 PSC | |||||
| * @note 若单一 PSC 无法同时覆盖两端(如 1Hz~很高频),自动不锁,改由 SetFreq 自适应 | |||||
| * @note 后续同向不停表开段时禁止重锁(由 run_control 在 pwm_on 时跳过调用) | |||||
| */ | |||||
| void PlsrPulseDriverLockPscRange(uint32_t f_min_hz, uint32_t f_max_hz); | void PlsrPulseDriverLockPscRange(uint32_t f_min_hz, uint32_t f_max_hz); | ||||
| /** @brief 解除 PSC 锁定(FinishAll / Stop) */ | |||||
| void PlsrPulseDriverUnlockPsc(void); | void PlsrPulseDriverUnlockPsc(void); | ||||
| /** | |||||
| * @brief 开沿顺序:ARR/CCR → CNT=0 → UG → CCxE → CEN(CEN 后立刻有效电平) | |||||
| * @note 换向延时到期应在 TIM5 延时中断内调用;勿经任务调度再开表 | |||||
| */ | |||||
| void PlsrPulseDriverStart(uint32_t freq_hz); | void PlsrPulseDriverStart(uint32_t freq_hz); | ||||
| void PlsrPulseDriverStartFreeRun(uint32_t freq_hz); | |||||
| /** | |||||
| * @brief 换向延时期间装载 ARR/CCR,Forced 低且 CEN 关 | |||||
| * @note 到期在 ISR 里 Start:CNT=0+UG+CEN,首沿对齐延时结束 | |||||
| */ | |||||
| void PlsrPulseDriverPrepare(uint32_t freq_hz); | |||||
| /** | |||||
| * @brief 任务上下文改频:PSC 不变则预装载 ARR;须换 PSC 则停表重装 | |||||
| * @note 频率或量化结果未变则立即返回 | |||||
| */ | |||||
| void PlsrPulseDriverSetFreq(uint32_t freq_hz); | void PlsrPulseDriverSetFreq(uint32_t freq_hz); | ||||
| /** 请求改频(ISR 周期边界再落到硬件) */ | |||||
| void PlsrPulseDriverRequestFreq(uint32_t freq_hz); | |||||
| /** 在更新中断里调用:应用挂起频率 */ | |||||
| /** | |||||
| * @brief 脉冲 ISR 改频:仅写 ARR/CCR(预装载,下一拍生效) | |||||
| * @note PSC 不变时直接改;必须换 PSC 则挂起,由任务 ApplyPending | |||||
| * @note 频率未变或 ARR 未变则立即返回(匀速热路径) | |||||
| */ | |||||
| void PlsrPulseDriverSetFreqIsr(uint32_t freq_hz); | |||||
| /** | |||||
| * @brief 消费挂起改频(TickMs / 更新路径) | |||||
| * @note s_req_freq==0 时 Stop;否则 SetFreq | |||||
| */ | |||||
| void PlsrPulseDriverApplyPending(void); | void PlsrPulseDriverApplyPending(void); | ||||
| /** @brief 丢弃未生效的改频请求(进匀速锁定时用) */ | |||||
| void PlsrPulseDriverClearPending(void); | |||||
| /** | |||||
| * @brief 取消冷启动首拍 ARR 保护(进入匀速、无首拍改频时调用) | |||||
| */ | |||||
| void PlsrPulseDriverClearStartPeriodProtect(void); | |||||
| /** | |||||
| * @brief 停止三路脉冲 TIM,空闲 Forced inactive 拉低 | |||||
| * @note 不清方向脚;不清 PSC 锁(由上层 Unlock) | |||||
| */ | |||||
| void PlsrPulseDriverStop(void); | void PlsrPulseDriverStop(void); | ||||
| /** 当前拍结束后停止计数,避免多出一个上升沿 */ | |||||
| void PlsrPulseDriverArmOnePulseStop(void); | |||||
| /** 取消单脉冲停表(后续模式同向衔接时用) */ | |||||
| /** | |||||
| * @brief ACT 到期后收完当前脉冲、锁低输出,并在本周期末停止计数 | |||||
| * @note 可在周期任意位置调用:高电平等下降沿,低电平立即 Forced inactive | |||||
| */ | |||||
| void PlsrPulseDriverArmActStop(void); | |||||
| /** | |||||
| * @brief 段末停表:OPM 等周期结束 + CC1 下降沿立刻 Forced 拉低 | |||||
| * @note 在已计入末拍上升沿的 UPDATE ISR 中调用 | |||||
| */ | |||||
| void PlsrPulseDriverArmSegEndStop(void); | |||||
| /** | |||||
| * @brief 取消单脉冲停表(后续模式同向衔接 / CutSeg keep 时) | |||||
| */ | |||||
| void PlsrPulseDriverClearOnePulseStop(void); | void PlsrPulseDriverClearOnePulseStop(void); | ||||
| /** | |||||
| * @brief 立即将当前活动路输出强制拉低(Forced inactive),不关其它路 | |||||
| * @note ISR 安全;OPM 段末 UPDATE 后 CNT 回零时 PWM1 可能短暂为高,须先于收尾调用 | |||||
| */ | |||||
| void PlsrPulseDriverHoldOutputLow(void); | |||||
| /** | |||||
| * @brief 判断 instance 是否为本模块脉冲 TIM | |||||
| * @return 1=TIM10/11/13;0=否 | |||||
| * @note 供 stm32f4xx_it 过滤 UPDATE 回调 | |||||
| */ | |||||
| uint8_t PlsrPulseDriverIsPulseTim(TIM_TypeDef *instance); | uint8_t PlsrPulseDriverIsPulseTim(TIM_TypeDef *instance); | ||||
| #endif | #endif | ||||
| @@ -1,6 +1,6 @@ | |||||
| /** | /** | ||||
| * @file plsr_run_control.h | * @file plsr_run_control.h | ||||
| * @brief 运行控制:完成方式 / 后续方式(加减速形状见 accel_mode) | |||||
| * @brief 运行控制:多段脉冲状态机(开段 → 发脉冲 → 等待/跳转 → 收尾) | |||||
| */ | */ | ||||
| #ifndef PLSR_RUN_CONTROL_H | #ifndef PLSR_RUN_CONTROL_H | ||||
| #define PLSR_RUN_CONTROL_H | #define PLSR_RUN_CONTROL_H | ||||
| @@ -8,16 +8,27 @@ | |||||
| #include <stdint.h> | #include <stdint.h> | ||||
| void PlsrRunControlInit(void); | void PlsrRunControlInit(void); | ||||
| uint8_t PlsrRunControlStart(uint16_t start_seg_1based); | |||||
| void PlsrRunControlStop(void); | |||||
| uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz); | |||||
| /** @brief 上电从 BKPSRAM 恢复累计脉冲(不清绝对原点锁以外的运行态) */ | |||||
| void PlsrRunControlRestoreAccPulse(int32_t acc_pulse); | |||||
| void PlsrRunControlTickMs(void); | void PlsrRunControlTickMs(void); | ||||
| void PlsrRunControlOn1ms(void); /* TIM3 1ms:改频 + WAIT/ACT 时基 */ | |||||
| void PlsrRunControlOnPulseIsr(void); /* 脉冲 UPDATE:只计脉冲 */ | |||||
| uint8_t PlsrRunControlIsBusy(void); | |||||
| int32_t PlsrRunControlGetAccPulse(void); | |||||
| void PlsrRunControlClearAccPulse(void); | |||||
| uint16_t PlsrRunControlGetCurSeg(void); | |||||
| uint32_t PlsrRunControlGetCurFreq(void); | |||||
| /** @brief TIM5 单次延时到期(换向 / WAIT / ACT 剩余) */ | |||||
| void PlsrRunControlOnDelayTimer(void); | |||||
| /** | |||||
| * @brief 创建任务唤醒信号量(须在 OSInit 之后、首次 Pend/Post 之前) | |||||
| */ | |||||
| void PlsrRunControlWakeInit(void); | |||||
| /** | |||||
| * @brief PlsrTask 休眠:被 ISR Post 立刻醒,或最多等 timeout_ticks 个 OS 节拍 | |||||
| * @param timeout_ticks 与 OSSemPend 相同;0=一直等到 Post;常用 1 | |||||
| */ | |||||
| void PlsrRunControlWakePend(uint16_t timeout_ticks); | |||||
| /** | |||||
| * @brief 是否在 TIM5 单次延时等待(换向 / WAIT 时间) | |||||
| * @note 此时 WakePend(0) 只等到期 Post | |||||
| */ | |||||
| uint8_t PlsrRunControlIsPreciseWait(void); | |||||
| #endif | #endif | ||||
| @@ -1,75 +1,133 @@ | |||||
| /** | /** | ||||
| * @file plsr_signal_io.c | * @file plsr_signal_io.c | ||||
| * @brief WAIT/EXT:EXTI 下降沿触发(X4=PB5、X5=PG12) | |||||
| * @brief WAIT/EXT 输入 + TIM5 单次延时 + OS 节拍消抖 | |||||
| * | * | ||||
| * 输入上拉,空闲为高;飞线接地产生下降沿。 | |||||
| * 触发只认 EXTI,不靠平时扫口。沿到来后等 20ms 再读一次: | |||||
| * 仍为低才锁存(消抖,并滤掉 Y0 脉冲耦合出的尖峰)。 | |||||
| * @details 实现要点 | |||||
| * 【EXTI 输入】 | |||||
| * X4=PB5 → EXTI9_5;X5=PG12 → EXTI15_10。 | |||||
| * 下降沿触发 OnFalling:仅 pending=1,不在 ISR 里判有效(避免抖动/毛刺)。 | |||||
| * | |||||
| * 【消抖】 | |||||
| * DebouncePoll(任务上下文,约 10ms 一次): | |||||
| * pending 且首次 → 记 arm_tick; | |||||
| * 满 PLSR_SIGNAL_DEBOUNCE_MS(20ms)且 PinIsLow → fell=1。 | |||||
| * 上升沿恢复高电平则 Confirm 末尾不置 fell(pending 被清掉)。 | |||||
| * | |||||
| * 【TIM5 单次延时】 | |||||
| * 32 位 TIM5,APB1 84MHz;仅在有延时时开表(URS+OPM)。 | |||||
| * PSC=8399 → 10kHz(1 计数 = 100µs);ARR = delay_ms×10 − 1。 | |||||
| * 一次 UPDATE 溢出 = delay_ms;中断内 Cancel 并 PlsrRunControlOnDelayTimer()。 | |||||
| * | |||||
| * 【与 run_control 配合】 | |||||
| * ScheduleDelayMs :方向延时 / WAIT 时间 / ACT 定时 | |||||
| * TakeExtFalling :RUN 中 EXT 切段、WAIT_COND 等 | |||||
| * TakeWaitFalling :WAIT 信号段后等待 | |||||
| * ClearEdges :EXT 段开跑前丢旧沿 | |||||
| * ClearPending :纯 EXT 段末保留 fell、清 pending | |||||
| * DelayRemainMs :ACT 脉冲先完后续装剩余时间 | |||||
| */ | */ | ||||
| #include "plsr_signal_io.h" | #include "plsr_signal_io.h" | ||||
| #include "plsr_param.h" | #include "plsr_param.h" | ||||
| #include "plsr_run_control.h" | #include "plsr_run_control.h" | ||||
| #include "main.h" | #include "main.h" | ||||
| #include "ucos_ii.h" | |||||
| /* ---------- 硬件引脚 ---------- */ | |||||
| #define PLSR_X4_PORT GPIOB | #define PLSR_X4_PORT GPIOB | ||||
| #define PLSR_X4_PIN GPIO_PIN_5 | |||||
| #define PLSR_X4_PIN GPIO_PIN_5 /* X4:WAIT/EXT 可选 */ | |||||
| #define PLSR_X5_PORT GPIOG | #define PLSR_X5_PORT GPIOG | ||||
| #define PLSR_X5_PIN GPIO_PIN_12 | |||||
| #define PLSR_X5_PIN GPIO_PIN_12 /* X5:WAIT/EXT 可选 */ | |||||
| #define PLSR_TIM3_PSC 83U | |||||
| #define PLSR_TIM3_ARR 999U | |||||
| /* | |||||
| * TIM5 时基:84MHz / (8399+1) = 10kHz → 100µs/格。 | |||||
| * PSC=8399 落在 16 位内 | |||||
| */ | |||||
| #define PLSR_TIM5_PSC_100US 8399U | |||||
| #define PLSR_TIM5_TICKS_PER_MS 10U /* 10×100µs = 1ms */ | |||||
| #define PLSR_DELAY_MS_MAX 65535U | |||||
| /** OS 节拍消抖窗口(ms),与 PlsrTask tick 配合 */ | |||||
| #define PLSR_SIGNAL_DEBOUNCE_MS 20U | #define PLSR_SIGNAL_DEBOUNCE_MS 20U | ||||
| /** 单路 X 口消抖状态(pending / fell 由 ISR 与任务共享,volatile) */ | |||||
| typedef struct { | typedef struct { | ||||
| volatile uint8_t pending; /* EXTI 下降沿,待 20ms 确认 */ | |||||
| volatile uint8_t wait_ms; | |||||
| volatile uint8_t fell; /* 确认后的下降沿,读清 */ | |||||
| volatile uint8_t pending; /* 1=EXTI 已见下降沿,待 Confirm */ | |||||
| volatile uint8_t stamp_ok; /* 1=已记 arm_tick,正在计时 */ | |||||
| volatile INT32U arm_tick; /* 消抖起点 OS tick */ | |||||
| volatile uint8_t fell; /* 1=消抖确认的有效下降沿,Take* 消费 */ | |||||
| GPIO_TypeDef *port; | GPIO_TypeDef *port; | ||||
| uint16_t pin; | uint16_t pin; | ||||
| } PlsrEdge_t; | } PlsrEdge_t; | ||||
| static volatile uint32_t s_ms; | |||||
| static PlsrEdge_t s_db_x4; | |||||
| static PlsrEdge_t s_db_x5; | |||||
| static PlsrEdge_t s_debounce_x4; /* X4 消抖状态 */ | |||||
| static PlsrEdge_t s_debounce_x5; /* X5 消抖状态 */ | |||||
| static volatile uint8_t s_delay_armed; /* 1=TIM5 单次延时正在计时 */ | |||||
| /** 毫秒 → OS tick 数(向上取整,至少 1 tick) */ | |||||
| static INT32U PlsrSignalIoMsToTicks(uint32_t ms) | |||||
| { | |||||
| INT32U t; | |||||
| static TIM_HandleTypeDef s_htim3; | |||||
| if (ms == 0U) | |||||
| { | |||||
| return 1U; | |||||
| } | |||||
| t = ((INT32U)ms * (INT32U)OS_TICKS_PER_SEC + 999U) / 1000U; | |||||
| if (t < 1U) | |||||
| { | |||||
| t = 1U; | |||||
| } | |||||
| return t; | |||||
| } | |||||
| static uint8_t PlsrSignalIoPinIsLow(const PlsrEdge_t *db) | |||||
| /** @return 1=脚当前为低(有效按下/接地) */ | |||||
| static uint8_t PlsrSignalIoPinIsLow(const PlsrEdge_t *debounce) | |||||
| { | { | ||||
| return (HAL_GPIO_ReadPin(db->port, db->pin) == GPIO_PIN_RESET) ? 1U : 0U; | |||||
| return (HAL_GPIO_ReadPin(debounce->port, debounce->pin) == GPIO_PIN_RESET) ? 1U : 0U; | |||||
| } | } | ||||
| static void PlsrSignalIoOnFalling(PlsrEdge_t *db) | |||||
| /** EXTI 下降沿:置 pending,重置计时标志消抖在 DebouncePoll 完成 */ | |||||
| static void PlsrSignalIoOnFalling(PlsrEdge_t *debounce) | |||||
| { | { | ||||
| db->pending = 1U; | |||||
| db->wait_ms = 0U; | |||||
| debounce->pending = 1U; | |||||
| debounce->stamp_ok = 0U; | |||||
| } | } | ||||
| static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *db) | |||||
| /** | |||||
| * @brief 单路消抖确认(DebouncePoll 每 tick 调用) | |||||
| * @note 满 debounce_ms 仍低 → fell;否则丢弃 pending | |||||
| */ | |||||
| static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *debounce) | |||||
| { | { | ||||
| if (db->pending == 0U) | |||||
| INT32U need; | |||||
| if (debounce->pending == 0U) | |||||
| { | { | ||||
| return; | return; | ||||
| } | } | ||||
| if (db->wait_ms < 0xFFU) | |||||
| if (debounce->stamp_ok == 0U) | |||||
| { | { | ||||
| db->wait_ms++; | |||||
| debounce->arm_tick = OSTimeGet(); | |||||
| debounce->stamp_ok = 1U; | |||||
| return; | |||||
| } | } | ||||
| if (db->wait_ms < PLSR_SIGNAL_DEBOUNCE_MS) | |||||
| need = PlsrSignalIoMsToTicks(PLSR_SIGNAL_DEBOUNCE_MS); | |||||
| if ((OSTimeGet() - debounce->arm_tick) < need) | |||||
| { | { | ||||
| return; | return; | ||||
| } | } | ||||
| db->pending = 0U; | |||||
| /* 尖峰/耦合在 20ms 后已回到高,不算触发;飞线接地保持为低才算 */ | |||||
| if (PlsrSignalIoPinIsLow(db) != 0U) | |||||
| debounce->pending = 0U; | |||||
| debounce->stamp_ok = 0U; | |||||
| if (PlsrSignalIoPinIsLow(debounce) != 0U) | |||||
| { | { | ||||
| db->fell = 1U; | |||||
| debounce->fell = 1U; | |||||
| } | } | ||||
| } | } | ||||
| /** X4/X5:下降沿 EXTI + 上拉;先关 NVIC 再配 GPIO,避免配置期间误触发 */ | |||||
| static void PlsrSignalIoGpioExtiInit(void) | static void PlsrSignalIoGpioExtiInit(void) | ||||
| { | { | ||||
| GPIO_InitTypeDef gpio = {0}; | GPIO_InitTypeDef gpio = {0}; | ||||
| @@ -87,7 +145,6 @@ static void PlsrSignalIoGpioExtiInit(void) | |||||
| gpio.Pin = PLSR_X4_PIN; | gpio.Pin = PLSR_X4_PIN; | ||||
| HAL_GPIO_Init(PLSR_X4_PORT, &gpio); | HAL_GPIO_Init(PLSR_X4_PORT, &gpio); | ||||
| gpio.Pin = PLSR_X5_PIN; | gpio.Pin = PLSR_X5_PIN; | ||||
| HAL_GPIO_Init(PLSR_X5_PORT, &gpio); | HAL_GPIO_Init(PLSR_X5_PORT, &gpio); | ||||
| @@ -102,147 +159,221 @@ static void PlsrSignalIoGpioExtiInit(void) | |||||
| HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); | HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); | ||||
| } | } | ||||
| static void PlsrSignalIoTim3Init(void) | |||||
| /** TIM5:预配 PSC/最大 ARR,默认停表;ScheduleDelayMs 时再装 ARR 并 OPM 启动 */ | |||||
| static void PlsrSignalIoTim5Init(void) | |||||
| { | { | ||||
| __HAL_RCC_TIM3_CLK_ENABLE(); | |||||
| s_htim3.Instance = TIM3; | |||||
| s_htim3.Init.Prescaler = PLSR_TIM3_PSC; | |||||
| s_htim3.Init.CounterMode = TIM_COUNTERMODE_UP; | |||||
| s_htim3.Init.Period = PLSR_TIM3_ARR; | |||||
| s_htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; | |||||
| s_htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; | |||||
| (void)HAL_TIM_Base_Init(&s_htim3); | |||||
| HAL_NVIC_SetPriority(TIM3_IRQn, 9, 0); | |||||
| HAL_NVIC_EnableIRQ(TIM3_IRQn); | |||||
| (void)HAL_TIM_Base_Start_IT(&s_htim3); | |||||
| __HAL_RCC_TIM5_CLK_ENABLE(); | |||||
| TIM5->CR1 = 0U; /* 停表:向上计、关 OPM */ | |||||
| TIM5->DIER = 0U; /* 关 UPDATE 中断 */ | |||||
| TIM5->SR = 0U; /* 清状态标志 */ | |||||
| TIM5->PSC = PLSR_TIM5_PSC_100US; /* PSC=8399 → 10kHz(100µs/格),非 1MHz */ | |||||
| TIM5->ARR = 0xFFFFFFFFUL; /* 预装最大 ARR,真正延时在 Schedule 重写 */ | |||||
| TIM5->CNT = 0U; | |||||
| TIM5->EGR = TIM_EGR_UG; /* UG:PSC 立刻生效(会置 UIF) */ | |||||
| TIM5->SR = 0U; /* 清 UG 带来的 UIF */ | |||||
| HAL_NVIC_SetPriority(TIM5_IRQn, 7, 0); | |||||
| HAL_NVIC_EnableIRQ(TIM5_IRQn); | |||||
| } | } | ||||
| /** @brief 信号 IO 初始化:消抖状态 + EXTI + TIM5 */ | |||||
| void PlsrSignalIoInit(void) | void PlsrSignalIoInit(void) | ||||
| { | { | ||||
| s_ms = 0U; | |||||
| s_db_x4.port = PLSR_X4_PORT; | |||||
| s_db_x4.pin = PLSR_X4_PIN; | |||||
| s_db_x4.pending = 0U; | |||||
| s_db_x4.wait_ms = 0U; | |||||
| s_db_x4.fell = 0U; | |||||
| s_db_x5.port = PLSR_X5_PORT; | |||||
| s_db_x5.pin = PLSR_X5_PIN; | |||||
| s_db_x5.pending = 0U; | |||||
| s_db_x5.wait_ms = 0U; | |||||
| s_db_x5.fell = 0U; | |||||
| s_delay_armed = 0U; | |||||
| s_debounce_x4.port = PLSR_X4_PORT; | |||||
| s_debounce_x4.pin = PLSR_X4_PIN; | |||||
| s_debounce_x4.pending = 0U; | |||||
| s_debounce_x4.stamp_ok = 0U; | |||||
| s_debounce_x4.arm_tick = 0U; | |||||
| s_debounce_x4.fell = 0U; | |||||
| s_debounce_x5.port = PLSR_X5_PORT; | |||||
| s_debounce_x5.pin = PLSR_X5_PIN; | |||||
| s_debounce_x5.pending = 0U; | |||||
| s_debounce_x5.stamp_ok = 0U; | |||||
| s_debounce_x5.arm_tick = 0U; | |||||
| s_debounce_x5.fell = 0U; | |||||
| PlsrSignalIoGpioExtiInit(); | PlsrSignalIoGpioExtiInit(); | ||||
| PlsrSignalIoTim3Init(); | |||||
| /* 配置 EXTI 时可能打出假沿,丢掉 */ | |||||
| PlsrSignalIoTim5Init(); | |||||
| /* 配完 NVIC 后再清一次,避免配置期误沿 */ | |||||
| __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X4_PIN); | __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X4_PIN); | ||||
| __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X5_PIN); | __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X5_PIN); | ||||
| s_db_x4.fell = 0U; | |||||
| s_db_x4.pending = 0U; | |||||
| s_db_x5.fell = 0U; | |||||
| s_db_x5.pending = 0U; | |||||
| s_debounce_x4.fell = 0U; | |||||
| s_debounce_x4.pending = 0U; | |||||
| s_debounce_x5.fell = 0U; | |||||
| s_debounce_x5.pending = 0U; | |||||
| } | } | ||||
| uint32_t PlsrSignalIoGetMs(void) | |||||
| /** @brief 装 TIM5 单次延时(ms),OPM 到期进 OnTim5Irq */ | |||||
| void PlsrSignalIoScheduleDelayMs(uint32_t delay_ms) | |||||
| { | { | ||||
| return s_ms; | |||||
| } | |||||
| uint32_t ticks; | |||||
| uint32_t PlsrSignalIoDeadlineFromNow(uint32_t ms) | |||||
| { | |||||
| if (ms == 0U) | |||||
| if (delay_ms < 1U) | |||||
| { | { | ||||
| return s_ms; | |||||
| delay_ms = 1U; | |||||
| } | } | ||||
| return s_ms + ms; | |||||
| if (delay_ms > PLSR_DELAY_MS_MAX) | |||||
| { | |||||
| delay_ms = PLSR_DELAY_MS_MAX; | |||||
| } | |||||
| /* N ms → N×10 个 100µs 格;OPM 计满一次 UPDATE 即到期 */ | |||||
| ticks = delay_ms * PLSR_TIM5_TICKS_PER_MS; | |||||
| TIM5->CR1 = 0U; | |||||
| TIM5->DIER = 0U; | |||||
| TIM5->SR = 0U; | |||||
| TIM5->PSC = PLSR_TIM5_PSC_100US; | |||||
| TIM5->ARR = ticks - 1U; | |||||
| TIM5->CR1 = TIM_CR1_URS; /* 仅溢出产生 UPDATE */ | |||||
| TIM5->EGR = TIM_EGR_UG; /* PSC/ARR 立刻生效 */ | |||||
| TIM5->SR = 0U; /* 清 UG 带来的 UIF */ | |||||
| TIM5->CNT = 0U; | |||||
| TIM5->DIER = TIM_DIER_UIE; | |||||
| s_delay_armed = 1U; | |||||
| TIM5->CR1 = (uint32_t)(TIM_CR1_CEN | TIM_CR1_OPM | TIM_CR1_URS); /* 开表单次 */ | |||||
| } | } | ||||
| uint8_t PlsrSignalIoMsReached(uint32_t deadline_ms) | |||||
| /** @brief 取消 TIM5 延时并清武装 */ | |||||
| void PlsrSignalIoCancelDelay(void) | |||||
| { | { | ||||
| return ((int32_t)(s_ms - deadline_ms) >= 0) ? 1U : 0U; | |||||
| TIM5->CR1 = 0U; | |||||
| TIM5->DIER = 0U; | |||||
| TIM5->SR = 0U; | |||||
| s_delay_armed = 0U; | |||||
| } | } | ||||
| void PlsrSignalIoClearEdges(void) | |||||
| /** @brief 查询 TIM5 剩余延时 ms(未武装或已停返回 0) */ | |||||
| uint32_t PlsrSignalIoDelayRemainMs(void) | |||||
| { | { | ||||
| s_db_x4.fell = 0U; | |||||
| s_db_x4.pending = 0U; | |||||
| s_db_x5.fell = 0U; | |||||
| s_db_x5.pending = 0U; | |||||
| uint32_t arr; | |||||
| uint32_t cnt; | |||||
| uint32_t left_ticks; | |||||
| if (s_delay_armed == 0U) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| if ((TIM5->CR1 & TIM_CR1_CEN) == 0U) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| arr = TIM5->ARR; | |||||
| cnt = TIM5->CNT; | |||||
| if (cnt >= (arr + 1U)) | |||||
| { | |||||
| return 0U; | |||||
| } | |||||
| left_ticks = (arr + 1U) - cnt; | |||||
| /*会有个小bug。假定计数剩余个数655个,则要计数65.5ms, | |||||
| 该函数会向下取整,导致返回65ms。再次进来计数时就会误差0.5ms。理论最大误差1ms。*/ | |||||
| return left_ticks / PLSR_TIM5_TICKS_PER_MS; | |||||
| } | } | ||||
| void PlsrSignalIoClearPending(void) | |||||
| /** @brief OS 节拍消抖:确认 X4/X5 有效下降沿 */ | |||||
| void PlsrSignalIoDebouncePoll(void) | |||||
| { | { | ||||
| s_db_x4.pending = 0U; | |||||
| s_db_x5.pending = 0U; | |||||
| PlsrSignalIoDebounceConfirm(&s_debounce_x4); | |||||
| PlsrSignalIoDebounceConfirm(&s_debounce_x5); | |||||
| } | } | ||||
| static PlsrEdge_t *PlsrSignalIoDbBySel(uint16_t sel_01) | |||||
| /** @brief 丢弃两侧沿与 pending(EXT 段开跑前) */ | |||||
| void PlsrSignalIoClearEdges(void) | |||||
| { | { | ||||
| return (sel_01 != 0U) ? &s_db_x5 : &s_db_x4; | |||||
| s_debounce_x4.fell = 0U; | |||||
| s_debounce_x4.pending = 0U; | |||||
| s_debounce_x4.stamp_ok = 0U; | |||||
| s_debounce_x5.fell = 0U; | |||||
| s_debounce_x5.pending = 0U; | |||||
| s_debounce_x5.stamp_ok = 0U; | |||||
| } | } | ||||
| uint8_t PlsrSignalIoWaitIsOn(void) | |||||
| /** @brief 仅清 pending,保留已确认 fell(纯 EXT 段末) */ | |||||
| void PlsrSignalIoClearPending(void) | |||||
| { | { | ||||
| PlsrCfg_t *cfg = PlsrParamGetCfg(); | |||||
| return PlsrSignalIoPinIsLow(PlsrSignalIoDbBySel(cfg->wait_x_sel)); | |||||
| s_debounce_x4.pending = 0U; | |||||
| s_debounce_x4.stamp_ok = 0U; | |||||
| s_debounce_x5.pending = 0U; | |||||
| s_debounce_x5.stamp_ok = 0U; | |||||
| } | } | ||||
| /** @brief 取 WAIT 口有效下降沿(读后清) */ | |||||
| uint8_t PlsrSignalIoTakeWaitFalling(void) | uint8_t PlsrSignalIoTakeWaitFalling(void) | ||||
| { | { | ||||
| PlsrCfg_t *cfg = PlsrParamGetCfg(); | |||||
| PlsrEdge_t *db = PlsrSignalIoDbBySel(cfg->wait_x_sel); | |||||
| if (db->fell != 0U) | |||||
| /* wait_x_sel:0=X4,1=X5 */ | |||||
| if (g_plsr_config.wait_x_sel == 0U) | |||||
| { | |||||
| if (s_debounce_x4.fell != 0U) | |||||
| { | |||||
| s_debounce_x4.fell = 0U; | |||||
| return 1U; | |||||
| } | |||||
| } | |||||
| else if (s_debounce_x5.fell != 0U) | |||||
| { | { | ||||
| db->fell = 0U; | |||||
| s_debounce_x5.fell = 0U; | |||||
| return 1U; | return 1U; | ||||
| } | } | ||||
| return 0U; | return 0U; | ||||
| } | } | ||||
| /** @brief 取 EXT 口有效下降沿(读后清) */ | |||||
| uint8_t PlsrSignalIoTakeExtFalling(void) | uint8_t PlsrSignalIoTakeExtFalling(void) | ||||
| { | { | ||||
| PlsrCfg_t *cfg = PlsrParamGetCfg(); | |||||
| PlsrEdge_t *db = PlsrSignalIoDbBySel(cfg->ext_x_sel); | |||||
| if (db->fell != 0U) | |||||
| /* ext_x_sel:0=X4,1=X5 */ | |||||
| if (g_plsr_config.ext_x_sel == 0U) | |||||
| { | |||||
| if (s_debounce_x4.fell != 0U) | |||||
| { | |||||
| s_debounce_x4.fell = 0U; | |||||
| return 1U; | |||||
| } | |||||
| } | |||||
| else if (s_debounce_x5.fell != 0U) | |||||
| { | { | ||||
| db->fell = 0U; | |||||
| s_debounce_x5.fell = 0U; | |||||
| return 1U; | return 1U; | ||||
| } | } | ||||
| return 0U; | return 0U; | ||||
| } | } | ||||
| void PlsrSignalIoOnTim3Irq(void) | |||||
| /** @brief TIM5 UPDATE:到期回调 run_control */ | |||||
| void PlsrSignalIoOnTim5Irq(void) | |||||
| { | { | ||||
| if ((TIM3->SR & TIM_SR_UIF) != 0U) | |||||
| if ((TIM5->SR & TIM_SR_UIF) != 0U) | |||||
| { | { | ||||
| TIM3->SR = (uint32_t)~TIM_SR_UIF; | |||||
| s_ms++; | |||||
| PlsrSignalIoDebounceConfirm(&s_db_x4); | |||||
| PlsrSignalIoDebounceConfirm(&s_db_x5); | |||||
| PlsrRunControlOn1ms(); | |||||
| TIM5->SR = (uint32_t)~TIM_SR_UIF; | |||||
| TIM5->DIER = 0U; | |||||
| TIM5->CR1 = 0U; | |||||
| if (s_delay_armed != 0U) | |||||
| { | |||||
| s_delay_armed = 0U; | |||||
| PlsrRunControlOnDelayTimer(); /* 方向/WAIT/ACT 到期 */ | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| /** @brief EXTI9_5:X4 下降沿 → pending */ | |||||
| void PlsrSignalIoOnExti9_5(void) | void PlsrSignalIoOnExti9_5(void) | ||||
| { | { | ||||
| if (__HAL_GPIO_EXTI_GET_IT(PLSR_X4_PIN) != 0U) | if (__HAL_GPIO_EXTI_GET_IT(PLSR_X4_PIN) != 0U) | ||||
| { | { | ||||
| __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X4_PIN); | __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X4_PIN); | ||||
| PlsrSignalIoOnFalling(&s_db_x4); | |||||
| PlsrSignalIoOnFalling(&s_debounce_x4); | |||||
| } | } | ||||
| } | } | ||||
| /** @brief EXTI15_10:X5 下降沿 → pending */ | |||||
| void PlsrSignalIoOnExti15_10(void) | void PlsrSignalIoOnExti15_10(void) | ||||
| { | { | ||||
| if (__HAL_GPIO_EXTI_GET_IT(PLSR_X5_PIN) != 0U) | if (__HAL_GPIO_EXTI_GET_IT(PLSR_X5_PIN) != 0U) | ||||
| { | { | ||||
| __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X5_PIN); | __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X5_PIN); | ||||
| PlsrSignalIoOnFalling(&s_db_x5); | |||||
| PlsrSignalIoOnFalling(&s_debounce_x5); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,48 +1,90 @@ | |||||
| /** | /** | ||||
| * @file plsr_signal_io.h | * @file plsr_signal_io.h | ||||
| * @brief 等待/EXT 输入(X4=PB5、X5=PG12) | |||||
| * @brief WAIT/EXT 外部输入与 TIM5 单次延时 | |||||
| * | * | ||||
| * 公共参数:wait_x_sel / ext_x_sel:0=X4,1=X5。 | |||||
| * 上拉空闲为高;飞线接地产生下降沿,由 EXTI 触发(不轮询)。 | |||||
| * @details 模块职责 | |||||
| * 1. 配置 X4=PB5、X5=PG12 为下降沿 EXTI(内部上拉,空闲高;飞线/触点接地出有效沿); | |||||
| * 2. 消抖:EXTI 仅置 pending,任务侧 DebouncePoll 按 OS 节拍确认约 20ms 仍低 → fell; | |||||
| * 3. TIM5(32 位)按需单次计时:100µs 时基,ARR=ms×10−1,一次 UPDATE = 设定 ms | |||||
| * (最长 65535ms);到期回调 run_control::OnDelayTimer。 | |||||
| * | |||||
| * 硬件与参数映射 | |||||
| * X4 (PB5, EXTI9_5) / X5 (PG12, EXTI15_10) | |||||
| * wait_x_sel (0x1002) → 0=X4,1=X5,供 WAIT 信号等待(PLSR_WAIT_SIGNAL) | |||||
| * ext_x_sel (0x1003) → 0=X4,1=X5,供 EXT 切段(PLSR_WAIT_EXT / EXT_OR_DONE) | |||||
| * WAIT 与 EXT 可独立选口,共用同一套消抖状态机。 | |||||
| * | |||||
| * TIM5 单次延时用途 | |||||
| * - 方向切换延时 dir_delay_ms(RC_DIR_WAIT → Start) | |||||
| * - WAIT 时间 PLSR_WAIT_TIME 段后等待 | |||||
| * - ACT 时间 PLSR_WAIT_ACT 段内定时(到期 OPM 收当前拍再切段) | |||||
| * - ACT 脉冲先完但定时未到:DelayRemainMs 续装剩余 ms | |||||
| */ | */ | ||||
| #ifndef PLSR_SIGNAL_IO_H | #ifndef PLSR_SIGNAL_IO_H | ||||
| #define PLSR_SIGNAL_IO_H | #define PLSR_SIGNAL_IO_H | ||||
| #include <stdint.h> | #include <stdint.h> | ||||
| /** | |||||
| * @brief 初始化 X4/X5 EXTI 消抖状态与 TIM5(不启动延时) | |||||
| */ | |||||
| void PlsrSignalIoInit(void); | void PlsrSignalIoInit(void); | ||||
| /** TIM3 毫秒计数(自由运行,约 1ms/格) */ | |||||
| uint32_t PlsrSignalIoGetMs(void); | |||||
| /** | |||||
| * @brief TIM5 单次延时(ms),到期 OnTim5Irq → PlsrRunControlOnDelayTimer | |||||
| * @param delay_ms 0 按 1ms;上限 65535ms | |||||
| * @note 重入会先 Cancel 再重装;OPM 计满自动停表 | |||||
| */ | |||||
| void PlsrSignalIoScheduleDelayMs(uint32_t delay_ms); | |||||
| /** @brief 停止 TIM5 单次延时,s_delay_armed=0 */ | |||||
| void PlsrSignalIoCancelDelay(void); | |||||
| /** deadline = GetMs()+ms;ms=0 则立即到期 */ | |||||
| uint32_t PlsrSignalIoDeadlineFromNow(uint32_t ms); | |||||
| /** | |||||
| * @brief 读 TIM5 单次延时剩余 ms | |||||
| * @note ACT 脉冲先完、定时仍走时,用于续装剩余 ACT 时间 | |||||
| */ | |||||
| uint32_t PlsrSignalIoDelayRemainMs(void); | |||||
| /** 1=已到或过期(含无符号环绕) */ | |||||
| uint8_t PlsrSignalIoMsReached(uint32_t deadline_ms); | |||||
| /** | |||||
| * @brief OS 节拍消抖轮询(X4/X5 各跑一遍 Confirm) | |||||
| * @note 由 PlsrTask 每 tick 调用;满 PLSR_SIGNAL_DEBOUNCE_MS 且仍低才置 fell | |||||
| */ | |||||
| void PlsrSignalIoDebouncePoll(void); | |||||
| /** 清 WAIT/EXT 下降沿锁存(包含已确认的 fell) */ | |||||
| /** | |||||
| * @brief 清 X4/X5 的 pending 与 fell(开跑 EXT 段、FinishAll 时用) | |||||
| * @note 丢弃尚未确认的沿与已锁存沿,防误切段 | |||||
| */ | |||||
| void PlsrSignalIoClearEdges(void); | void PlsrSignalIoClearEdges(void); | ||||
| /** 只清消抖 pending,保留已确认的 fell(发完后过滤脉冲耦合假沿用) */ | |||||
| /** | |||||
| * @brief 只清 pending/stamp,保留已确认的 fell | |||||
| * @note 纯 EXT 段末:清消抖中的假沿,已确认沿留给 TakeExtFalling | |||||
| */ | |||||
| void PlsrSignalIoClearPending(void); | void PlsrSignalIoClearPending(void); | ||||
| /** WAIT 口当前是否为低(已接地)。仅段末判断※2 时读一次,不作周期轮询触发 */ | |||||
| uint8_t PlsrSignalIoWaitIsOn(void); | |||||
| /** | /** | ||||
| * WAIT:消耗一次已确认的 EXTI 下降沿。 | |||||
| * ※2 段中飞线接地并保持 → 发完立刻下一段;※3 未触发则一直等。 | |||||
| * @brief 消费 WAIT 口(wait_x_sel)上已确认的下降沿 | |||||
| * @return 1=本次取到有效沿并已清零 fell;0=无沿 | |||||
| * @note 仅认消抖后的 fell,不读当前 GPIO 电平(防虚低误跳) | |||||
| */ | */ | ||||
| uint8_t PlsrSignalIoTakeWaitFalling(void); | uint8_t PlsrSignalIoTakeWaitFalling(void); | ||||
| /** | /** | ||||
| * EXT:消耗一次 EXTI 下降沿锁存。一次按压只切一次段。 | |||||
| * @brief 消费 EXT 口(ext_x_sel)上已确认的下降沿 | |||||
| * @return 1=本次取到有效沿并已清零 fell;0=无沿 | |||||
| * @note RUN 中 EXT/EXT_OR_DONE 切段、WAIT_COND 等路径共用 | |||||
| */ | */ | ||||
| uint8_t PlsrSignalIoTakeExtFalling(void); | uint8_t PlsrSignalIoTakeExtFalling(void); | ||||
| void PlsrSignalIoOnTim3Irq(void); | |||||
| void PlsrSignalIoOnExti9_5(void); | |||||
| void PlsrSignalIoOnExti15_10(void); | |||||
| /** @brief TIM5 UPDATE 中断入口:单次延时到期 → run_control */ | |||||
| void PlsrSignalIoOnTim5Irq(void); | |||||
| /** @brief EXTI9_5 中断入口(X4 / PB5) */ | |||||
| void PlsrSignalIoOnExti9_5(void); | |||||
| /** @brief EXTI15_10 中断入口(X5 / PG12) */ | |||||
| void PlsrSignalIoOnExti15_10(void); | |||||
| #endif | #endif | ||||