| @@ -1,11 +1,26 @@ | |||
| /** | |||
| ****************************************************************************** | |||
| * @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_param.h" | |||
| /* ---------- 任务栈与优先级(数字越小优先级越高) ---------- */ | |||
| #define MODBUS_TASK_STACK_SIZE 512 | |||
| #define MODBUS_TASK_PRIO 4 | |||
| #define MODBUS_TASK_PRIO 4 /* Modbus 从站:低于 PLSR,避免抢占脉冲路径过久 */ | |||
| #define PLSR_TASK_STACK_SIZE 512 | |||
| #define PLSR_TASK_PRIO 3 | |||
| #define PLSR_TASK_PRIO 3 /* 运行控制最高业务优先级 */ | |||
| #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 PlsrTaskStk[PLSR_TASK_STACK_SIZE]; | |||
| @@ -33,24 +49,32 @@ OS_STK ParamBlockTaskStk[PARAM_BLOCK_STACK_SIZE]; | |||
| void SystemClock_Config(void); | |||
| /** | |||
| * @brief 复位后入口:硬件就绪 → 模块 Init → 建任务 → 启动 OS | |||
| * @note 不返回;调度器接管 CPU | |||
| */ | |||
| int main(void) | |||
| { | |||
| HAL_Init(); | |||
| /* SysTick 优先级须低于 PendSV,供 uCOS 节拍与上下文切换 */ | |||
| HAL_NVIC_SetPriority(SysTick_IRQn, 14, 0); | |||
| HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0); | |||
| SystemClock_Config(); | |||
| /* 配置 Cortex-M SysTick 为 OS 节拍源(与 OS_TICKS_PER_SEC 匹配) */ | |||
| OS_CPU_SysTickInit(168000000 / OS_TICKS_PER_SEC); | |||
| MX_GPIO_Init(); | |||
| MX_USART1_UART_Init(); | |||
| MX_USART1_UART_Init(); /* Modbus RTU 物理层 */ | |||
| MX_TIM2_Init(); | |||
| PlsrInit(); | |||
| PlsrInit(); /* 脉冲驱动 / 运行控制 / 指令接口等 */ | |||
| OSInit(); | |||
| PlsrParamBlockInit(); /* 依赖 OS,须在 OSInit 之后 */ | |||
| /* 参数块信号量依赖 OS,必须放在 OSInit 之后 */ | |||
| PlsrParamBlockInit(); | |||
| /* Modbus 从站任务:解析 FC03/06/10,维护保持寄存器映像 */ | |||
| OSTaskCreateExt(ModbusSlaveTask, | |||
| NULL, | |||
| &ModbusTaskStk[MODBUS_TASK_STACK_SIZE - 1], | |||
| @@ -61,6 +85,7 @@ int main(void) | |||
| NULL, | |||
| OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); | |||
| /* PLSR 主任务:TickMs / CommandPoll / 监控发布 */ | |||
| OSTaskCreateExt(PlsrTask, | |||
| NULL, | |||
| &PlsrTaskStk[PLSR_TASK_STACK_SIZE - 1], | |||
| @@ -71,6 +96,10 @@ int main(void) | |||
| NULL, | |||
| OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); | |||
| /* | |||
| * 参数块任务:Pend 信号量 → ApplyFromHold → PersistPoll。 | |||
| * 分帧写完后由 OnHoldWrite Post,尽快落盘,不堵在 PlsrTask 里。 | |||
| */ | |||
| OSTaskCreateExt(PlsrParamBlockTask, | |||
| NULL, | |||
| &ParamBlockTaskStk[PARAM_BLOCK_STACK_SIZE - 1], | |||
| @@ -81,13 +110,17 @@ int main(void) | |||
| NULL, | |||
| OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); | |||
| OSStart(); | |||
| OSStart(); /* 启动多任务;正常情况下不再返回 */ | |||
| while (1) | |||
| { | |||
| } | |||
| } | |||
| /** | |||
| * @brief 系统时钟:HSE → PLL → 168MHz SYSCLK(APM32F407 / STM32F4 类) | |||
| * @note 须与 OS_CPU_SysTickInit 使用的主频常量一致 | |||
| */ | |||
| void SystemClock_Config(void) | |||
| { | |||
| RCC_OscInitTypeDef RCC_OscInitStruct = {0}; | |||
| @@ -109,19 +142,22 @@ void SystemClock_Config(void) | |||
| 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.AHBCLKDivider = RCC_SYSCLK_DIV1; | |||
| RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; | |||
| RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; | |||
| if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) | |||
| { | |||
| Error_Handler(); | |||
| } | |||
| } | |||
| /** | |||
| * @brief 致命错误停机(时钟配置失败等) | |||
| * @note 生产可用 LED 闪烁;此处死循环便于调试挂起 | |||
| */ | |||
| void Error_Handler(void) | |||
| { | |||
| __disable_irq(); | |||
| @@ -129,7 +165,3 @@ void Error_Handler(void) | |||
| { | |||
| } | |||
| } | |||
| #ifdef USE_FULL_ASSERT | |||
| #endif | |||
| @@ -2841,27 +2841,6 @@ | |||
| </group> | |||
| <group> | |||
| <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> | |||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name> | |||
| </file> | |||
| @@ -2871,37 +2850,31 @@ | |||
| </group> | |||
| <group> | |||
| <name>plsr</name> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\plsr.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\plsr.h</name> | |||
| </file> | |||
| <group> | |||
| <name>param</name> | |||
| <name>accel_curve</name> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\param\plsr_param.c</name> | |||
| <name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\param\plsr_param.h</name> | |||
| <name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.h</name> | |||
| </file> | |||
| </group> | |||
| <group> | |||
| <name>persist</name> | |||
| <name>command</name> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\persist\plsr_persist.c</name> | |||
| <name>$PROJ_DIR$\..\plsr\command\plsr_command.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\persist\plsr_persist.h</name> | |||
| <name>$PROJ_DIR$\..\plsr\command\plsr_command.h</name> | |||
| </file> | |||
| </group> | |||
| <group> | |||
| <name>pulse_driver</name> | |||
| <name>param</name> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.c</name> | |||
| <name>$PROJ_DIR$\..\plsr\param\plsr_param.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name> | |||
| <name>$PROJ_DIR$\..\plsr\param\plsr_param.h</name> | |||
| </file> | |||
| </group> | |||
| <group> | |||
| @@ -2914,30 +2887,30 @@ | |||
| </file> | |||
| </group> | |||
| <group> | |||
| <name>accel_curve</name> | |||
| <name>persist</name> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.c</name> | |||
| <name>$PROJ_DIR$\..\plsr\persist\plsr_persist.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.h</name> | |||
| <name>$PROJ_DIR$\..\plsr\persist\plsr_persist.h</name> | |||
| </file> | |||
| </group> | |||
| <group> | |||
| <name>run_control</name> | |||
| <name>pulse_driver</name> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.c</name> | |||
| <name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.h</name> | |||
| <name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name> | |||
| </file> | |||
| </group> | |||
| <group> | |||
| <name>command</name> | |||
| <name>run_control</name> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\command\plsr_command.c</name> | |||
| <name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\command\plsr_command.h</name> | |||
| <name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.h</name> | |||
| </file> | |||
| </group> | |||
| <group> | |||
| @@ -2949,6 +2922,12 @@ | |||
| <name>$PROJ_DIR$\..\plsr\signal_io\plsr_signal_io.h</name> | |||
| </file> | |||
| </group> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\plsr.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\plsr\plsr.h</name> | |||
| </file> | |||
| </group> | |||
| <group> | |||
| <name>ucos</name> | |||
| @@ -3074,31 +3074,91 @@ | |||
| <group> | |||
| <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> | |||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\modbus\modbus_port.h</name> | |||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.h</name> | |||
| </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>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> | |||
| <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> | |||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name> | |||
| <name>$PROJ_DIR$\..\plsr\plsr.c</name> | |||
| </file> | |||
| <file> | |||
| <name>$PROJ_DIR$\..\modbus\modbus_rtu.h</name> | |||
| <name>$PROJ_DIR$\..\plsr\plsr.h</name> | |||
| </file> | |||
| </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,426 +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 "plsr_command.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); | |||
| 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); | |||
| 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,51 @@ | |||
| */ | |||
| #include "modbus_rtu.h" | |||
| #include "modbus_data.h" | |||
| #include "modbus_port.h" | |||
| #include "modbus_fc.h" | |||
| #include <string.h> | |||
| #include "plsr.h" | |||
| #include "plsr_pulse_driver.h" | |||
| #include "gpio.h" | |||
| #include "plsr_param.h" | |||
| #include "plsr_command.h" | |||
| 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 从站任务:阻塞等待完整帧,校验后分发处理 | |||
| */ | |||
| @@ -48,12 +86,12 @@ void ModbusSlaveTask(void *pArg) | |||
| } | |||
| memcpy(g_modbus_proc_buf, g_modbus_rx_buf, frameLen); | |||
| g_modbus_rx_len = 0U; | |||
| PortClearFramePending(); | |||
| /*清除帧挂起*/ | |||
| g_modbus_frame_pending = 0U; | |||
| OS_EXIT_CRITICAL(); | |||
| if (frameLen < 4U) | |||
| { | |||
| LedErrorBlink(); | |||
| continue; | |||
| } | |||
| @@ -62,7 +100,6 @@ void ModbusSlaveTask(void *pArg) | |||
| crcCalc = ModbusCrc16(g_modbus_proc_buf, (uint16_t)(frameLen - 2U)); | |||
| if (crcRecv != crcCalc) | |||
| { | |||
| LedErrorBlink(); | |||
| continue; | |||
| } | |||
| @@ -76,3 +113,568 @@ void ModbusSlaveTask(void *pArg) | |||
| 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 起,地址直接当下标) */ | |||
| /*============================================================================*/ | |||
| /* 线圈/保持寄存器最大协议地址 */ | |||
| /* 保持寄存器最大协议地址 */ | |||
| #define MODBUS_MB_MAXIMUM 0x3000U | |||
| /* 实际能存储的最大数据个数 */ | |||
| #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_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_COUNT MODBUS_POINT_COUNT | |||
| @@ -62,42 +54,34 @@ | |||
| /* Modbus 应用层数量上限 */ | |||
| /*============================================================================*/ | |||
| /* 单次可读线圈数量上限 */ | |||
| #define MAX_READ_COILS 2000U | |||
| /* 单次可读保持寄存器数量上限 */ | |||
| #define MAX_READ_REGS 125U | |||
| /* 单次可写线圈数量上限 */ | |||
| #define MAX_WRITE_COILS 1968U | |||
| /* 单次可写保持寄存器数量上限 */ | |||
| #define MAX_WRITE_REGS 123U | |||
| /* 线圈映像数组长度 */ | |||
| #define COIL_BUFFER_SIZE MODBUS_POINT_COUNT | |||
| /* 保持寄存器映像数组长度 */ | |||
| #define HOLD_REG_BUFFER_SIZE MODBUS_POINT_COUNT | |||
| extern UART_HandleTypeDef huart1; | |||
| 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); | |||
| /** | |||
| * @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); | |||
| /** | |||
| @@ -161,4 +105,9 @@ uint16_t ReadHoldReg(uint16_t addr); | |||
| */ | |||
| void WriteHoldReg(uint16_t addr, uint16_t val); | |||
| /** | |||
| * @brief 端口初始化 | |||
| */ | |||
| void ModbusPortInit(void); | |||
| #endif | |||
| @@ -1,13 +1,20 @@ | |||
| /** | |||
| * @file plsr_accel_curve.c | |||
| * @brief 脉冲域规划/取频:f^2 = f0^2 + 2*a*n(直线);S/正弦按脉冲进度 shape | |||
| * @brief 脉冲域规划/取频实现:直线 f^2=f0^2±2an;S/正弦按脉冲进度 shape | |||
| * | |||
| * 脉冲预算不够到目标频时:降低峰值(三角,无匀速)。 | |||
| * @details 模块职责 | |||
| * 实现 plsr_accel_curve.h 全部 API。核心路径: | |||
| * Plan → 估 acc_n/dec_n → 不够则 FitPeak 降峰; | |||
| * PulseRtBeginAcc/Dec/Const + Step 供 ISR 每脉冲改频。 | |||
| * | |||
| * 关键约束 | |||
| * 频率钳制上限 100000Hz;运行门禁非法频率由 command/run_control 报 0x04。 | |||
| * 脉冲预算不够到目标频时:降低峰值(三角,无匀速),调用方写故障 0x02。 | |||
| */ | |||
| #include "plsr_accel_curve.h" | |||
| #include <stdint.h> | |||
| /*频率大于100k时暂时固定到100k,后面增加报错*/ | |||
| /* 频率大于 100k 时暂时钳到 100k;启动门禁另报 0x04 */ | |||
| uint32_t PlsrAccelCurveClampFreq(uint32_t freq_hz) | |||
| { | |||
| if (freq_hz > 100000U) | |||
| @@ -17,6 +24,11 @@ uint32_t PlsrAccelCurveClampFreq(uint32_t freq_hz) | |||
| return freq_hz; | |||
| } | |||
| /*============================================================================*/ | |||
| /* 整数开方 / 斜率时间 */ | |||
| /*============================================================================*/ | |||
| /** @brief |a-b| */ | |||
| static uint32_t PlsrAccelCurveAbsDiff(uint32_t a, uint32_t b) | |||
| { | |||
| return (a >= b) ? (a - b) : (b - a); | |||
| @@ -856,6 +868,15 @@ static uint32_t PlsrAccelCurveFitPeak(uint32_t total_pulses, | |||
| return f_cur - best; | |||
| } | |||
| /** | |||
| * @brief 规划一整段脉冲域(见 plsr_accel_curve.h) | |||
| * | |||
| * 算法步骤: | |||
| * 1. 钳频、解析 f_cur/f_end 若仍 <1; | |||
| * 2. 由 default_spd 与 accel/decel_ms 算 a_acc/a_dec(无默认速则频差回退); | |||
| * 3. PulsesForRamp 估 acc_n/dec_n;若和 > total → FitPeak 降峰重估; | |||
| * 4. 写出 plan 三相预算与 t_* 预览字段。 | |||
| */ | |||
| void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan, | |||
| uint32_t total_pulses, | |||
| uint32_t f_cur, | |||
| @@ -1030,6 +1051,10 @@ void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan, | |||
| plan->t_decel_start_ms = t_acc + PlsrAccelCurveConstTimeMs(plan->const_n, f_peak); | |||
| } | |||
| /** | |||
| * @brief 按已完成脉冲数取频(见 plsr_accel_curve.h) | |||
| * @note 加速:Kinematic 或 Shape+Lerp;匀速:f_tgt;减速:同理 | |||
| */ | |||
| uint32_t PlsrAccelCurveFreqAtPulse(const PlsrAccelPlan_t *plan, | |||
| uint32_t pulse_done) | |||
| { | |||
| @@ -1120,6 +1145,13 @@ uint32_t PlsrAccelCurveFreqAtSegTime(const PlsrAccelPlan_t *plan, | |||
| return PlsrAccelCurveFreqAtPulse(plan, approx_n); | |||
| } | |||
| /*============================================================================*/ | |||
| /* 逐拍运行态(ISR) */ | |||
| /*============================================================================*/ | |||
| /** | |||
| * @brief 为 S/正弦准备 Q24 步进:(1<<24)/N 商余,避免每拍除法 | |||
| */ | |||
| static void PlsrAccelPulseRtSetupShape(PlsrAccelPulseRt_t *rt, uint32_t n_total) | |||
| { | |||
| if (n_total < 1U) | |||
| @@ -1135,6 +1167,7 @@ static void PlsrAccelPulseRtSetupShape(PlsrAccelPulseRt_t *rt, uint32_t n_total) | |||
| rt->rem_acc = 0U; | |||
| } | |||
| /** @brief 进入加速相(见 plsr_accel_curve.h) */ | |||
| void PlsrAccelPulseRtBeginAcc(PlsrAccelPulseRt_t *rt, | |||
| const PlsrAccelPlan_t *plan) | |||
| { | |||
| @@ -1154,6 +1187,7 @@ void PlsrAccelPulseRtBeginAcc(PlsrAccelPulseRt_t *rt, | |||
| PlsrAccelPulseRtSetupShape(rt, rt->n_total); | |||
| } | |||
| /** @brief 进入减速相(见 plsr_accel_curve.h) */ | |||
| void PlsrAccelPulseRtBeginDec(PlsrAccelPulseRt_t *rt, | |||
| const PlsrAccelPlan_t *plan) | |||
| { | |||
| @@ -1173,6 +1207,7 @@ void PlsrAccelPulseRtBeginDec(PlsrAccelPulseRt_t *rt, | |||
| PlsrAccelPulseRtSetupShape(rt, rt->n_total); | |||
| } | |||
| /** @brief 进入匀速相(见 plsr_accel_curve.h) */ | |||
| void PlsrAccelPulseRtBeginConst(PlsrAccelPulseRt_t *rt, | |||
| const PlsrAccelPlan_t *plan) | |||
| { | |||
| @@ -1279,6 +1314,9 @@ static uint32_t PlsrAccelPulseRtSquareStep(PlsrAccelPulseRt_t *rt) | |||
| return PlsrAccelCurveClampFreq(f); | |||
| } | |||
| /** | |||
| * @brief S/正弦:累加 Q24 进度 → shape 千分比 → Lerp(f0,f1) | |||
| */ | |||
| static uint32_t PlsrAccelPulseRtShapeStep(PlsrAccelPulseRt_t *rt) | |||
| { | |||
| uint32_t ratio; | |||
| @@ -1320,6 +1358,9 @@ static uint32_t PlsrAccelPulseRtShapeStep(PlsrAccelPulseRt_t *rt) | |||
| return PlsrAccelCurveClampFreq(f); | |||
| } | |||
| /** | |||
| * @brief 本相前进一脉冲(见 plsr_accel_curve.h) | |||
| */ | |||
| uint32_t PlsrAccelPulseRtStep(PlsrAccelPulseRt_t *rt) | |||
| { | |||
| if (rt == (PlsrAccelPulseRt_t *)0) | |||
| @@ -1,11 +1,21 @@ | |||
| /** | |||
| * @file plsr_accel_curve.h | |||
| * @brief 脉冲域加/匀/减规划与取频(v^2 - v0^2 = 2ax) | |||
| * @brief 脉冲域加/匀/减规划与取频(运动学:v^2 - v0^2 = 2ax) | |||
| * | |||
| * 加速度 a = 默认速度*1000/加减速时间(Hz/s)。 | |||
| * 脉冲序号 n 上:f(n) = sqrt(f0^2 + 2*a*n);初速为 0 即 f0=0。 | |||
| * 匀速仅当加速+减速脉冲之和 < 总脉冲(真正到达目标频)时出现。 | |||
| * 开段端点由运行层 ResolveStart/EndHz 解析后再传入 Plan。 | |||
| * @details 模块职责 | |||
| * 纯算法层:根据起/峰/止频率、斜率时间与总脉冲,规划 acc_n/const_n/dec_n, | |||
| * 并在脉冲序号上取频。不读 wait_type,不碰 TIM/GPIO。 | |||
| * | |||
| * 核心约定 | |||
| * - 加速度 a ≈ default_speed * 1000 / accel_ms(Hz/s);起/止速只定端点 | |||
| * - 直线:f(n) = sqrt(f0^2 ± 2*a*n);S/正弦:按脉冲进度 shape 插值 | |||
| * - 匀速仅当 acc_n+dec_n < total(真正到达目标峰)时出现 | |||
| * - 脉冲不够到设定峰:FitPeak 降峰(三角),调用方可写故障 0x02 | |||
| * - 开段端点由运行层 ResolveStart/EndHz 解析为真实 Hz(>=1)后再 Plan | |||
| * | |||
| * 与其它模块关系 | |||
| * - plsr_run_control :PlanSeg → Plan;ISR → PulseRtStep;Refresh → FreqAtPulse | |||
| * - plsr_param :accel_mode / default_speed / accel_ms / decel_ms | |||
| */ | |||
| #ifndef PLSR_ACCEL_CURVE_H | |||
| #define PLSR_ACCEL_CURVE_H | |||
| @@ -13,6 +23,13 @@ | |||
| #include <stdint.h> | |||
| #include "plsr_param.h" | |||
| /** | |||
| * 一段脉冲域规划结果(Plan 填出,运行层只读/EnterDecel 可改写 dec_n) | |||
| * acc_n/const_n/dec_n — 三相脉冲预算 | |||
| * f_cur / f_tgt / f_end — 实际起速、峰值、止速(可能已 FitPeak) | |||
| * a_acc / a_dec — Hz/s | |||
| * t_*_ms — 斜率时间估算(预览/兼容;运行热路径不依赖) | |||
| */ | |||
| typedef struct { | |||
| uint32_t acc_n; | |||
| uint32_t const_n; | |||
| @@ -20,31 +37,37 @@ typedef struct { | |||
| uint32_t f_cur; | |||
| uint32_t f_tgt; | |||
| uint32_t f_end; | |||
| uint32_t a_acc; /* 加速 a(Hz/s) */ | |||
| uint32_t a_dec; /* 减速 a(Hz/s) */ | |||
| uint32_t t_acc_ms; /* 斜率时间估算(预览/兼容,运行不依赖) */ | |||
| uint32_t a_acc; | |||
| uint32_t a_dec; | |||
| uint32_t t_acc_ms; | |||
| uint32_t t_dec_ms; | |||
| uint32_t t_decel_start_ms; /* 估算:进减速时刻(预览用) */ | |||
| uint32_t t_decel_start_ms; | |||
| PlsrAccelMode_e mode; | |||
| } PlsrAccelPlan_t; | |||
| /** 曲线相枚举(预留/文档;运行层用自有 RunPhase_e) */ | |||
| typedef enum { | |||
| PLSR_CURVE_PHASE_ACC = 0, | |||
| PLSR_CURVE_PHASE_CONST, | |||
| PLSR_CURVE_PHASE_DEC | |||
| } PlsrCurvePhase_e; | |||
| /** | |||
| * @brief 频率钳到上限 100000Hz(不抬下限) | |||
| * @note 非法低频门禁由 command/run_control 写 0x04,本函数不做 | |||
| */ | |||
| uint32_t PlsrAccelCurveClampFreq(uint32_t freq_hz); | |||
| /** | |||
| * 起跳/步进频率:f = sqrt(f_from^2 + 2a),a = |f_to-f_from|*1000/t_ms。 | |||
| * f_from=0 时退化为 sqrt(2a);超过 f_to 则钳到 f_to。 | |||
| * @brief 起跳/步进频率:f = sqrt(f_from^2 + 2a),a = |f_to-f_from|*1000/t_ms | |||
| * @note f_from=0 退化为 sqrt(2a);超过 f_to 钳到 f_to;t_ms=0 直接返回 f_to | |||
| */ | |||
| uint32_t PlsrAccelCurveJumpFreq(uint32_t f_from, uint32_t f_to, | |||
| uint32_t t_ms); | |||
| /** | |||
| * 配置起速 → 规划用真实起点:一律 JumpFreq(f_cfg, f_tgt, t_acc)。 | |||
| * @brief 配置起速 → 规划用真实起点:JumpFreq(f_cfg, f_tgt, t_acc) | |||
| * @note 起速 0 与非 0 统一走 Jump,避免配置 0 时从 0Hz 开跑 | |||
| */ | |||
| uint32_t PlsrAccelCurveResolveStartHz(uint32_t f_cfg, | |||
| uint32_t f_tgt, | |||
| @@ -55,8 +78,8 @@ uint32_t PlsrAccelCurveResolveStartHz(uint32_t f_cfg, | |||
| uint32_t decel_ms); | |||
| /** | |||
| * 配置止速 → 规划用真实终点。 | |||
| * f_cfg<=1:按落地频率估计(避免 1Hz 拖尾);否则 JumpFreq。 | |||
| * @brief 配置止速 → 规划用真实终点 | |||
| * @note f_cfg<=1:按落地频率估计(避免 1Hz 拖尾);否则 JumpFreq | |||
| */ | |||
| uint32_t PlsrAccelCurveResolveEndHz(uint32_t f_cfg, | |||
| uint32_t f_tgt, | |||
| @@ -66,34 +89,54 @@ uint32_t PlsrAccelCurveResolveEndHz(uint32_t f_cfg, | |||
| uint32_t accel_ms, | |||
| uint32_t decel_ms); | |||
| /** | |||
| * @brief 斜坡取频(µs 时间轴,O(1)) | |||
| * @note 直线:代数插值;S/正弦:shape(t/T) 再 Lerp。主要用于估计/兼容,非 ISR 主路径 | |||
| */ | |||
| uint32_t PlsrAccelCurveFreqOnRampUs(uint32_t f0, | |||
| uint32_t f1, | |||
| uint32_t t_us, | |||
| uint32_t T_us, | |||
| PlsrAccelMode_e mode); | |||
| /** @brief ms 接口,内部转 µs 调 FreqOnRampUs */ | |||
| uint32_t PlsrAccelCurveFreqOnRampMs(uint32_t f0, | |||
| uint32_t f1, | |||
| uint32_t t_ms, | |||
| uint32_t T_ms, | |||
| PlsrAccelMode_e mode); | |||
| /** | |||
| * @brief 斜坡第一拍自洽频率(起速=0 时避免整周期 1Hz 吃光加速时间) | |||
| */ | |||
| uint32_t PlsrAccelCurveFirstPulseFreq(uint32_t f0, | |||
| uint32_t f1, | |||
| uint32_t T_us, | |||
| PlsrAccelMode_e mode); | |||
| /** | |||
| * @brief 减速落地频率:f_end>=1 返回止速;f_end==0 返回 0(停表) | |||
| */ | |||
| uint32_t PlsrAccelCurveLandFreq(uint32_t f_end, | |||
| uint32_t f_peak, | |||
| uint32_t T_ms, | |||
| PlsrAccelMode_e mode); | |||
| /** | |||
| * @brief 拟合斜坡时间 T,使离散脉冲估计尽量等于 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); | |||
| /** | |||
| * @brief 规划一整段脉冲域三相预算与实际峰值 | |||
| * @param total_pulses 本段总脉冲 | |||
| * @param f_cur/f_tgt/f_end 起/目标峰/止(真实 Hz,由策略层解析) | |||
| * @note 不够爬升则 FitPeak;结果写入 *plan | |||
| */ | |||
| void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan, | |||
| uint32_t total_pulses, | |||
| uint32_t f_cur, | |||
| @@ -107,18 +150,20 @@ void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan, | |||
| PlsrAccelMode_e mode); | |||
| /** | |||
| * 按已完成脉冲数取频(任务/预览可用)。 | |||
| * 运行热路径请用 PlsrAccelPulseRt(逐拍 ±1 / Q24,可进 ISR)。 | |||
| * @brief 按已完成脉冲数取频(任务/预览) | |||
| * @note 运行热路径请用 PlsrAccelPulseRt(逐拍 ±1 / Q24,可进 ISR) | |||
| */ | |||
| uint32_t PlsrAccelCurveFreqAtPulse(const PlsrAccelPlan_t *plan, | |||
| uint32_t pulse_done); | |||
| /** @deprecated 时间轴取频;保留给旧预览,运行请用脉冲域 */ | |||
| /** | |||
| * @deprecated 时间轴取频;保留给旧预览,运行请用脉冲域 | |||
| */ | |||
| uint32_t PlsrAccelCurveFreqAtSegTime(const PlsrAccelPlan_t *plan, | |||
| uint32_t seg_elapsed_ms); | |||
| /** | |||
| * 逐拍运行态(ISR 热路径): | |||
| * 逐拍运行态(ISR 热路径) | |||
| * - 直线:上一拍频率 ±1 逼近 sqrt(f0^2±2an),不全量 Isqrt64 | |||
| * - S/正弦:Begin 时算 Q24 步长,逐拍商余累加,不做 index/total 除法 | |||
| */ | |||
| @@ -138,13 +183,22 @@ typedef struct { | |||
| PlsrAccelMode_e mode; | |||
| } PlsrAccelPulseRt_t; | |||
| /** @brief 进入加速相运行态(n=0,active 视 acc_n) */ | |||
| void PlsrAccelPulseRtBeginAcc(PlsrAccelPulseRt_t *rt, | |||
| const PlsrAccelPlan_t *plan); | |||
| /** @brief 进入减速相运行态 */ | |||
| void PlsrAccelPulseRtBeginDec(PlsrAccelPulseRt_t *rt, | |||
| const PlsrAccelPlan_t *plan); | |||
| /** @brief 进入匀速:active=0,复用 f_tgt */ | |||
| void PlsrAccelPulseRtBeginConst(PlsrAccelPulseRt_t *rt, | |||
| const PlsrAccelPlan_t *plan); | |||
| /** 本相前进 1 脉冲,返回下一拍命令频率(匀速直接返回缓存 f) */ | |||
| /** | |||
| * @brief 本相前进 1 脉冲,返回下一拍命令频率 | |||
| * @note 匀速直接返回缓存 f;走完 n_total 后钳到 f1 并清 active | |||
| */ | |||
| uint32_t PlsrAccelPulseRtStep(PlsrAccelPulseRt_t *rt); | |||
| #endif | |||
| @@ -1,6 +1,20 @@ | |||
| /** | |||
| * @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_param.h" | |||
| @@ -8,16 +22,33 @@ | |||
| #include "plsr.h" | |||
| #include "modbus_rtu.h" | |||
| /* | |||
| * 模块静态状态 | |||
| * 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; | |||
| static volatile uint8_t s_live_freq_req; | |||
| static volatile uint32_t s_live_freq_hz; | |||
| /** | |||
| * @brief 写 32 位值到相邻两个保持寄存器(低字在前) | |||
| * @param addr 低字寄存器地址 | |||
| * @param value 32 位值 | |||
| * @note 副作用:WriteHoldReg 两次 | |||
| */ | |||
| static void PlsrCommandWriteDWord(uint16_t addr, uint32_t value) | |||
| { | |||
| WriteHoldReg(addr, (uint16_t)(value & 0xFFFFU)); | |||
| WriteHoldReg((uint16_t)(addr + 1U), (uint16_t)((value >> 16) & 0xFFFFU)); | |||
| } | |||
| /** | |||
| * @brief 刷新全部监控寄存器 0x2000~0x2006 | |||
| * @note 调用时机:Init 末、Poll 每周期末 | |||
| * @note 数据源:RunControl / Plsr 门面 API + s_fault 镜像 | |||
| */ | |||
| static void PlsrCommandPublishMonitor(void) | |||
| { | |||
| PlsrCommandWriteDWord(PLSR_MON_ACC_PULSE_L, (uint32_t)PlsrGetAccPulse()); | |||
| @@ -27,23 +58,27 @@ static void PlsrCommandPublishMonitor(void) | |||
| WriteHoldReg(PLSR_MON_ERR, s_fault); | |||
| } | |||
| /** @brief 置故障(见 plsr_command.h) */ | |||
| void PlsrCommandSetFault(uint16_t code) | |||
| { | |||
| s_fault = code; | |||
| WriteHoldReg(PLSR_MON_ERR, code); | |||
| } | |||
| /** @brief 清故障(见 plsr_command.h) */ | |||
| void PlsrCommandClearFault(void) | |||
| { | |||
| s_fault = PLSR_ERR_NONE; | |||
| WriteHoldReg(PLSR_MON_ERR, PLSR_ERR_NONE); | |||
| } | |||
| /** @brief 读故障镜像(见 plsr_command.h) */ | |||
| uint16_t PlsrCommandGetFault(void) | |||
| { | |||
| return s_fault; | |||
| } | |||
| /** @brief 指令模块初始化(见 plsr_command.h) */ | |||
| void PlsrCommandInit(void) | |||
| { | |||
| WriteHoldReg(PLSR_CTRL_REG, 0U); | |||
| @@ -54,8 +89,9 @@ void PlsrCommandInit(void) | |||
| } | |||
| /** | |||
| * 上位机只改当前段频率:地址必须是当前段 FREQ_L,且一次写 2 字。 | |||
| * 更新 s_seg[].freq_hz,置位由 Poll 调 ChangeFreq。 | |||
| * @brief 运行中 FC10 写段频率钩子(见 plsr_command.h) | |||
| * @note 门禁:quantity==2、Busy、地址对齐 FREQ_L、seg0==当前段 | |||
| * @note 频率非法写 0x04 且不更新段表、不请求 ChangeFreq | |||
| */ | |||
| void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity) | |||
| { | |||
| @@ -97,6 +133,7 @@ void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity) | |||
| f = (uint32_t)ReadHoldReg(start_addr) | | |||
| ((uint32_t)ReadHoldReg((uint16_t)(start_addr + 1U)) << 16); | |||
| /* 运行中写频也做 0x04 门禁:非法不改段表、不请求 ChangeFreq */ | |||
| if ((f < 1U) || (f > 100000U)) | |||
| { | |||
| PlsrCommandSetFault(PLSR_ERR_FREQ_ILLEGAL); | |||
| @@ -113,14 +150,23 @@ void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity) | |||
| s_live_freq_req = 1U; | |||
| } | |||
| /** | |||
| * @brief 周期轮询(见 plsr_command.h) | |||
| * | |||
| * 控制位处理顺序(同周期多 bit 时): | |||
| * 1. 读 ctrl 后立即写 0(避免重复触发) | |||
| * 2. STOP — 急停 | |||
| * 3. CLR — 清累计 + ClearFault | |||
| * 4. START — 仅当无 STOP 位:Apply → ClearFault → Stop → Start | |||
| * 动态改频在控制位之后、PublishMonitor 之前执行。 | |||
| */ | |||
| void PlsrCommandPoll(void) | |||
| { | |||
| uint16_t ctrl = ReadHoldReg(PLSR_CTRL_REG); | |||
| if (ctrl != 0U) | |||
| { | |||
| /* 上位机置位,从站处理后立刻清零 */ | |||
| WriteHoldReg(PLSR_CTRL_REG, 0U); | |||
| WriteHoldReg(PLSR_CTRL_REG, 0U); /* 从站「读-改-清」协议:处理后清零 */ | |||
| if ((ctrl & PLSR_CTRL_BIT_STOP) != 0U) | |||
| { | |||
| @@ -130,7 +176,7 @@ void PlsrCommandPoll(void) | |||
| if ((ctrl & PLSR_CTRL_BIT_CLR) != 0U) | |||
| { | |||
| PlsrClearAccPulse(); | |||
| PlsrCommandClearFault(); | |||
| PlsrCommandClearFault(); /* 清零累计时同步清 0x2006 */ | |||
| } | |||
| /* 同时有停止位时不启动 */ | |||
| @@ -138,6 +184,7 @@ void PlsrCommandPoll(void) | |||
| ((ctrl & PLSR_CTRL_BIT_STOP) == 0U)) | |||
| { | |||
| (void)PlsrParamBlockApplyFromHold(); | |||
| /* 启动视为正常入口:先写故障码 0,再 Stop+Start;非法频率由 Start 改写为 4 */ | |||
| PlsrCommandClearFault(); | |||
| PlsrStop(); | |||
| (void)PlsrStart(PlsrParamGetStartSeg()); | |||
| @@ -1,44 +1,101 @@ | |||
| /** | |||
| * @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 | |||
| #define PLSR_COMMAND_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_FREQ_L 0x2002U /* 当前发送频率 Hz */ | |||
| #define PLSR_MON_FREQ_L 0x2002U /* 当前发送频率 Hz,UDW */ | |||
| #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_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 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~100k)(告警,禁止启动) */ | |||
| /* | |||
| * 故障码定义(写入 0x2006,须经 PlsrCommandSetFault,勿直接只写寄存器) | |||
| * | |||
| * 码 | 宏名 | 含义 | 等级 | 处理策略 | |||
| * ----|-------------------------|--------------------------------|------|------------------ | |||
| * 0 | PLSR_ERR_NONE | 无故障 | — | 启动/CLR 时写入 | |||
| * 1 | PLSR_ERR_PULSE_CNT | 实际脉冲数与段目标不一致 | 告警 | 继续跑,不中断 | |||
| * 2 | PLSR_ERR_FREQ_UNREACH | 规划峰值达不到设定目标(三角) | 告警 | 按压后规划继续 | |||
| * 3 | PLSR_ERR_CURVE_MISMATCH | 加减速相内脉冲与规划预算不符 | 告警 | 继续跑 | |||
| * 4 | PLSR_ERR_FREQ_ILLEGAL | 频率 ∉ [1, 100000] Hz | 门禁 | 禁止启动/改频 | |||
| */ | |||
| #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 | |||
| /** | |||
| * @brief 指令模块初始化 | |||
| * @note 调用时机:PlsrInit 末、PlsrTask 首次延迟后再次调用 | |||
| * @note 副作用:清 0x3000、清故障、刷新全部监控寄存器 | |||
| */ | |||
| void PlsrCommandInit(void); | |||
| /** | |||
| * @brief 周期轮询控制位并刷新监控 | |||
| * @note 调用时机:PlsrTask 每 ~1ms | |||
| * @note 副作用:可能触发 Start/Stop/Clear/ChangeFreq,写 0x2000~0x2006 | |||
| */ | |||
| 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); | |||
| /** | |||
| * @brief 清故障为 0 | |||
| * @note 调用时机:START 前、CLR 控制位、Init | |||
| */ | |||
| void PlsrCommandClearFault(void); | |||
| /** @return 当前故障码镜像(与 0x2006 一致) */ | |||
| uint16_t PlsrCommandGetFault(void); | |||
| /** | |||
| * FC10 写段频率双字后调用:仅当地址对应「当前运行段」频率时, | |||
| * 更新段表并置标志,由 Poll 调 ChangeFreq。 | |||
| * @brief FC10 写段频率双字后的钩子 | |||
| * @param start_addr 写入起始保持寄存器地址 | |||
| * @param quantity 写入字数,须为 2 | |||
| * @note 调用时机:Modbus 从站 FC10 应答路径;仅运行中且地址为「当前段 FREQ_L」有效 | |||
| * @note 副作用:更新段表 freq_hz,置 s_live_freq_req 由 Poll 调 ChangeFreq | |||
| */ | |||
| void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity); | |||
| @@ -1,6 +1,21 @@ | |||
| /** | |||
| * @file plsr_param.c | |||
| * @brief 参数块管理模块实现(运行映像 + 分帧导入/导出) | |||
| * @brief 参数块管理实现:运行映像 + 分帧导入/导出 + ParamBlockTask | |||
| * | |||
| * @details 模块职责 | |||
| * 维护静态映像 s_cfg / s_seg[],实现寄存器 ↔ 结构体双向转换,以及 | |||
| * 「1+N 分帧」收齐后的异步 Apply(信号量唤醒本任务)。 | |||
| * | |||
| * 静态状态说明 | |||
| * s_cfg / s_seg[] — 运行唯一参数源 | |||
| * s_param_sem — 收齐分帧后 Post,Task Pend 后 Apply | |||
| * s_expect_frames — 期望总帧 = 1 + 公共帧中的段数 | |||
| * s_recv_frames — 已收帧计数(公共算第 1 帧) | |||
| * s_xfer_active — 正在接收一批分帧传输 | |||
| * | |||
| * 与其它模块关系 | |||
| * Modbus FC10 应答路径调用 OnHoldWrite;Command START 也会同步 ApplyFromHold; | |||
| * Persist 仅保存公共参数;段表每次由上位机全量下发。 | |||
| */ | |||
| #include "plsr_param.h" | |||
| #include "plsr_persist.h" | |||
| @@ -20,6 +35,10 @@ static volatile uint8_t s_xfer_active; | |||
| /* 运行映像 */ | |||
| /*============================================================================*/ | |||
| /** | |||
| * @brief 出厂默认参数(见 plsr_param.h) | |||
| * @note 与上位机 initCommonDefaults 约定一致,便于未写 PLC 时行为可预期 | |||
| */ | |||
| void PlsrParamInitDefault(void) | |||
| { | |||
| uint16_t i; | |||
| @@ -27,7 +46,7 @@ void PlsrParamInitDefault(void) | |||
| memset(&s_cfg, 0, sizeof(s_cfg)); | |||
| memset(s_seg, 0, sizeof(s_seg)); | |||
| /* 与上位机默认约定一致;真正运行参数由 ApplyFromHold 覆盖 */ | |||
| /* 与上位机默认约定一致;真正运行参数由 ApplyFromHold / PersistLoad 覆盖 */ | |||
| s_cfg.pulse_y = 0U; | |||
| s_cfg.dir_y = 3U; | |||
| s_cfg.wait_x_sel = 0U; | |||
| @@ -56,11 +75,16 @@ void PlsrParamInitDefault(void) | |||
| } | |||
| } | |||
| /** @return 公共参数映像指针(见 plsr_param.h) */ | |||
| PlsrCfg_t *PlsrParamGetCfg(void) | |||
| { | |||
| return &s_cfg; | |||
| } | |||
| /** | |||
| * @brief 取段参数指针(见 plsr_param.h) | |||
| * @note 越界钳到段 0,避免空指针 | |||
| */ | |||
| PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based) | |||
| { | |||
| if (seg_0based >= PLSR_SEG_MAX) | |||
| @@ -70,6 +94,7 @@ PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based) | |||
| return &s_seg[seg_0based]; | |||
| } | |||
| /** @return 有效段数,钳制到 [0, PLSR_SEG_MAX](见 plsr_param.h) */ | |||
| uint16_t PlsrParamGetSegCount(void) | |||
| { | |||
| if (s_cfg.seg_count < 1U) | |||
| @@ -83,6 +108,10 @@ uint16_t PlsrParamGetSegCount(void) | |||
| return s_cfg.seg_count; | |||
| } | |||
| /** | |||
| * @brief 启动段 1-based(见 plsr_param.h) | |||
| * @note 非法起始段当前仅裁剪,TODO 可报故障码 | |||
| */ | |||
| uint16_t PlsrParamGetStartSeg(void) | |||
| { | |||
| uint16_t n = PlsrParamGetSegCount(); | |||
| @@ -104,10 +133,10 @@ uint16_t PlsrParamGetStartSeg(void) | |||
| } | |||
| /*============================================================================*/ | |||
| /* 分帧导入 / 导出 */ | |||
| /* 寄存器双字读写辅助 */ | |||
| /*============================================================================*/ | |||
| /** 读相邻两寄存器拼成无符号 32 位(低字在前) */ | |||
| /** 读相邻两寄存器拼成无符号 32 位(低地址=低 16 位) */ | |||
| static uint32_t PlsrParamReadDWord(uint16_t addr) | |||
| { | |||
| uint16_t low = ReadHoldReg(addr); | |||
| @@ -115,17 +144,23 @@ static uint32_t PlsrParamReadDWord(uint16_t addr) | |||
| return ((uint32_t)high << 16) | (uint32_t)low; | |||
| } | |||
| /** 写无符号 32 位到相邻两寄存器 */ | |||
| 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)); | |||
| } | |||
| /** 按有符号 32 位解释双字(频率/脉冲补码) */ | |||
| static int32_t PlsrParamReadSignedDWord(uint16_t addr) | |||
| { | |||
| return (int32_t)PlsrParamReadDWord(addr); | |||
| } | |||
| /** | |||
| * @brief 判断本次 FC10 是否为「整段 8 字写到某段基址」 | |||
| * @return 1=是段帧;0=否 | |||
| */ | |||
| static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity) | |||
| { | |||
| uint16_t i; | |||
| @@ -146,6 +181,11 @@ static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity) | |||
| return 0U; | |||
| } | |||
| /** | |||
| * @brief 更新分帧状态三寄存器,供上位机诊断 | |||
| * @param st PLSR_PARAM_XFER_* | |||
| * @param fail_frame 失败帧序号(成功时写 0) | |||
| */ | |||
| static void PlsrParamSetXferStatus(uint16_t st, uint16_t fail_frame) | |||
| { | |||
| WriteHoldReg(PLSR_PARAM_XFER_STATUS_REG, st); | |||
| @@ -153,6 +193,18 @@ static void PlsrParamSetXferStatus(uint16_t st, uint16_t fail_frame) | |||
| WriteHoldReg(PLSR_PARAM_XFER_RECV_REG, s_recv_frames); | |||
| } | |||
| /*============================================================================*/ | |||
| /* 分帧导入 / 导出 */ | |||
| /*============================================================================*/ | |||
| /** | |||
| * @brief 保持寄存器 → 运行映像(见 plsr_param.h) | |||
| * | |||
| * 字段裁剪策略(当前不做故障码,仅钳制到合法枚举/范围): | |||
| * pulse_y>2 → 0;dir 强制 3;accel_mode>2 → LINEAR;seg_count 上限 10; | |||
| * start_seg 钳到 [1,n];wait_type>4 → TIME。 | |||
| * 频率合法性由 run_control 启动/开段时查 0x04,此处不拒绝写入。 | |||
| */ | |||
| uint8_t PlsrParamBlockApplyFromHold(void) | |||
| { | |||
| uint16_t i; | |||
| @@ -235,6 +287,10 @@ uint8_t PlsrParamBlockApplyFromHold(void) | |||
| return 1U; | |||
| } | |||
| /** | |||
| * @brief 运行映像 → 全部相关保持寄存器(含未使用段槽位) | |||
| * @note 0x100F 保留字写 0;段表写满 PLSR_SEG_MAX 槽,避免读回脏数据 | |||
| */ | |||
| void PlsrParamBlockExportToHold(void) | |||
| { | |||
| uint16_t i; | |||
| @@ -272,6 +328,15 @@ void PlsrParamBlockExportToHold(void) | |||
| } | |||
| } | |||
| /** | |||
| * @brief FC10 写后分帧状态机(见 plsr_param.h) | |||
| * | |||
| * 状态转移: | |||
| * 写满公共 20 字 → BUSY,expect=1+N,recv=1 | |||
| * 传输中写段基址 8 字 → recv++ | |||
| * recv>=expect → 清 active,Post 信号量(或同步 Apply) | |||
| * 其它写地址忽略(例如运行中只写频率双字不走本路径的分帧逻辑) | |||
| */ | |||
| void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity) | |||
| { | |||
| if (quantity == 0U) | |||
| @@ -317,6 +382,7 @@ void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity) | |||
| } | |||
| else if (PlsrParamBlockApplyFromHold() != 0U) | |||
| { | |||
| /* 信号量尚未创建时的兜底:同步 Apply */ | |||
| PlsrParamSetXferStatus(PLSR_PARAM_XFER_OK, 0U); | |||
| } | |||
| else | |||
| @@ -326,6 +392,7 @@ void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity) | |||
| } | |||
| } | |||
| /** @brief 创建信号量并置传输空闲(见 plsr_param.h) */ | |||
| void PlsrParamBlockInit(void) | |||
| { | |||
| s_param_sem = OSSemCreate(0U); | |||
| @@ -335,6 +402,14 @@ void PlsrParamBlockInit(void) | |||
| PlsrParamSetXferStatus(PLSR_PARAM_XFER_IDLE, 0U); | |||
| } | |||
| /** | |||
| * @brief 参数块任务(见 plsr_param.h) | |||
| * | |||
| * for(;;) 为 uC/OS 任务标准死循环: | |||
| * OSSemPend 永久等待 → ApplyFromHold → 写 XFER_OK 并尽快 PersistPoll | |||
| * (不等 PlsrTask,缩短参数落盘延迟)。 | |||
| * Apply 失败则写 XFER_FAIL 与当前 recv 帧号。 | |||
| */ | |||
| void PlsrParamBlockTask(void *pArg) | |||
| { | |||
| INT8U err; | |||
| @@ -1,140 +1,221 @@ | |||
| /** | |||
| * @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. 维护运行侧唯一映像 s_cfg / s_seg[](运动规划只读此映像,不直接啃寄存器); | |||
| * 3. 支持上位机「1 公共帧 + N 段帧」分帧 FC10 写入:收齐后 ApplyFromHold; | |||
| * 4. 支持 ExportToHold:上电默认或读回时把映像摊到保持寄存器。 | |||
| * | |||
| * 与其它模块关系 | |||
| * - modbus_rtu / modbus_data :ReadHoldReg / WriteHoldReg | |||
| * - plsr_command :启动前 ApplyFromHold;运行中写频改 s_seg[].freq_hz | |||
| * - plsr_run_control :GetCfg / GetSeg / GetSegCount / GetStartSeg | |||
| * - plsr_persist :Apply 成功后 NotifyCommonChanged,落盘仅公共参数 | |||
| * - PlsrParamBlockTask :独立 uC/OS 任务,等信号量后 Apply + PersistPoll | |||
| * | |||
| * 关键数据流 | |||
| * 上位机写公共帧@0x1000 → OnHoldWrite 开传输 expect=1+N | |||
| * 上位机写段帧@0x1100+ → recv++;收齐 → OSSemPost | |||
| * ParamBlockTask → ApplyFromHold → 结构体更新 → PersistNotify → 状态 OK | |||
| * 上电:InitDefault → PersistLoad 覆盖公共 → ExportToHold | |||
| * | |||
| * 协议概要 | |||
| * - 公共帧:0x1000~0x1013(20 字),0x1009=段数 N,预期总帧=1+N | |||
| * - 段帧:0x1100+(i)*0x10,每段 8 字 | |||
| * - 传输状态:0x0FFC 已收帧 / 0x0FFD 失败帧 / 0x0FFE 状态 | |||
| */ | |||
| #ifndef PLSR_PARAM_H | |||
| #define PLSR_PARAM_H | |||
| #include <stdint.h> | |||
| /** 段表最大段数;与上位机 PLSR_SEG_MAX、寄存器布局一致 */ | |||
| #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_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 帧数) */ | |||
| #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_START_SPD_L 0x100DU /* 起速 DW */ | |||
| #define PLSR_REG_START_SPD_L 0x100DU /* 起速 Hz */ | |||
| #define PLSR_REG_START_SPD_H 0x100EU | |||
| /* 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_ACCEL_MS 0x1012U | |||
| #define PLSR_REG_DECEL_MS 0x1013U | |||
| #define PLSR_REG_ACCEL_MS 0x1012U /* 默认速度加速时间 ms(定斜率) */ | |||
| #define PLSR_REG_DECEL_MS 0x1013U /* 默认速度减速时间 ms */ | |||
| /* ---------- 段参数:第 i 段基址 = 0x1100 + i*0x10 ---------- */ | |||
| /* ---------- 段参数:第 i 段基址 = 0x1100 + i*0x10,每段 8 字 ---------- */ | |||
| #define PLSR_REG_SEG1_BASE 0x1100U | |||
| #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_PULSE_L 2U /* 脉冲数 SDW,负=反转 */ | |||
| #define PLSR_SEG_OFF_PULSE_L 2U /* 脉冲数/绝对坐标 SDW,负=反转 */ | |||
| #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_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=跳转目标段号 */ | |||
| /* 分帧传输状态(上位机可轮询) */ | |||
| #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_RECV_REG 0x0FFCU /* 已接收帧计数 */ | |||
| #define PLSR_PARAM_XFER_FAIL_FRAME_REG 0x0FFDU /* 失败时的帧序号 */ | |||
| #define PLSR_PARAM_XFER_STATUS_REG 0x0FFEU /* IDLE/BUSY/OK/FAIL */ | |||
| #define PLSR_PARAM_XFER_IDLE 0U | |||
| #define PLSR_PARAM_XFER_BUSY 1U | |||
| #define PLSR_PARAM_XFER_OK 2U | |||
| #define PLSR_PARAM_XFER_FAIL 3U | |||
| /** 方向逻辑:正转时方向脚电平 */ | |||
| typedef enum { | |||
| PLSR_DIR_LOGIC_POS = 0, /* 正转→方向高,反转→低 */ | |||
| PLSR_DIR_LOGIC_NEG = 1 /* 相反 */ | |||
| } PlsrDirLogic_e; | |||
| /** | |||
| * 发送方式(影响本段终止频率 f_end 与是否允许同向不停表衔接) | |||
| * COMPLETE:段末规划到公共止速;FOLLOW:无门禁时可衔接下一段目标频 | |||
| */ | |||
| typedef enum { | |||
| PLSR_SEND_COMPLETE = 0, /* 完成:上一段发完后,本段开头再加减速到本段频率 */ | |||
| PLSR_SEND_FOLLOW = 1 /* 后续:上一段发完时频率已到本段目标;同向可不停表 */ | |||
| PLSR_SEND_COMPLETE = 0, | |||
| PLSR_SEND_FOLLOW = 1 | |||
| } PlsrSendMode_e; | |||
| /** 加减速曲线形状(脉冲域规划) */ | |||
| 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 曲线按脉冲进度 */ | |||
| PLSR_ACCEL_SINE = 2 /* raised-cosine */ | |||
| } PlsrAccelMode_e; | |||
| /** | |||
| * 定位模式 | |||
| * RELATIVE:pulse_cnt = 本段相对位移(可负) | |||
| * ABSOLUTE:pulse_cnt = 相对「本次启动原点」的目标坐标 | |||
| */ | |||
| typedef enum { | |||
| PLSR_POS_RELATIVE = 0, /* 脉冲数=相对位移 */ | |||
| PLSR_POS_ABSOLUTE = 1 /* 脉冲数=相对本次启动原点的绝对坐标 */ | |||
| PLSR_POS_RELATIVE = 0, | |||
| PLSR_POS_ABSOLUTE = 1 | |||
| } PlsrPosMode_e; | |||
| /** | |||
| * 段末/段内等待类型(由 run_control + signal_io 执行) | |||
| * TIME/SIGNAL/ACT/EXT 均会阻止 FOLLOW 不停表衔接(BlocksFollowKeep) | |||
| */ | |||
| 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; | |||
| /** 公共运行参数(运动侧只读此映像,不直接啃寄存器) */ | |||
| /** | |||
| * 公共运行参数映像(运动侧只读) | |||
| * 斜率:a ≈ default_speed * 1000 / accel_ms(Hz/s) | |||
| */ | |||
| 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; | |||
| PlsrSendMode_e send_mode; | |||
| uint16_t dir_delay_ms; | |||
| PlsrDirLogic_e dir_logic; | |||
| PlsrAccelMode_e accel_mode; | |||
| PlsrPosMode_e run_mode; | |||
| uint16_t seg_count; | |||
| uint16_t seg_count; /* 有效段数 0~PLSR_SEG_MAX */ | |||
| 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; | |||
| /** 单段运行参数 */ | |||
| typedef struct { | |||
| int32_t freq_hz; /* 0=用默认速度;须解析后在 1~100k,否则故障 */ | |||
| int32_t freq_hz; /* 0=用默认速度;解析后须在 1~100k,否则 0x04 */ | |||
| int32_t pulse_cnt; /* 可负=反向;0=本段不发 */ | |||
| PlsrWaitType_e wait_type; | |||
| uint16_t wait_ms; | |||
| uint16_t act_ms; | |||
| uint16_t jump_seg; /* 0=末段结束 / 非末段则顺序下一段;1~N=跳转目标段号 */ | |||
| uint16_t jump_seg; /* 0=顺序/末段结束;1~N=跳转 */ | |||
| } PlsrSeg_t; | |||
| /* ---------- 运行映像访问 ---------- */ | |||
| /** | |||
| * @brief 填出厂默认 s_cfg / s_seg(随后可被 PersistLoad / Apply 覆盖) | |||
| * @note 调用时机:PlsrInit 最先调用 | |||
| */ | |||
| void PlsrParamInitDefault(void); | |||
| /** @return 公共参数映像指针(非空) */ | |||
| PlsrCfg_t *PlsrParamGetCfg(void); | |||
| /** | |||
| * @param seg_0based 段索引 0-based;越界钳到 0 | |||
| * @return 段参数指针 | |||
| */ | |||
| PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based); | |||
| /** @return 有效段数,钳制到 [0, PLSR_SEG_MAX] */ | |||
| uint16_t PlsrParamGetSegCount(void); | |||
| /** | |||
| * @return 启动段 1-based,钳制到 [1, seg_count] | |||
| * @note 非法起始段当前仅裁剪,TODO 可报故障码 | |||
| */ | |||
| uint16_t PlsrParamGetStartSeg(void); | |||
| /* ---------- 分帧导入 / 导出 / 管理任务 ---------- */ | |||
| /** OSInit 之后调用,创建分帧应用信号量 */ | |||
| /** | |||
| * @brief 创建分帧应用信号量,清传输状态 | |||
| * @note 调用时机:OSInit 之后、任务创建前 | |||
| */ | |||
| void PlsrParamBlockInit(void); | |||
| /** 等信号量后 hold→结构体 */ | |||
| /** | |||
| * @brief 参数块任务:阻塞等信号量 → ApplyFromHold → 状态 OK/FAIL → PersistPoll | |||
| * @param pArg 未使用 | |||
| * @note for(;;) 死循环为 RTOS 任务常态;空循环体是错觉(花括号内有 Pend/Apply) | |||
| */ | |||
| void PlsrParamBlockTask(void *pArg); | |||
| /** FC10 应答后由 Modbus 调用:分帧计数,收齐则 post 信号量 */ | |||
| /** | |||
| * @brief FC10 写保持寄存器后由 Modbus 回调 | |||
| * @param start_addr 起始地址 | |||
| * @param quantity 字数 | |||
| * @note 公共帧开传输;段帧累加;收齐 post 信号量(或无信号量时同步 Apply) | |||
| */ | |||
| void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity); | |||
| /** 寄存器→运行结构体;成功返回 1(完整校验/故障码待实现) */ | |||
| /** | |||
| * @brief 保持寄存器 → s_cfg / s_seg | |||
| * @return 1=成功(当前恒为 1;完整校验/故障码待扩展) | |||
| * @note 副作用:NotifyCommonChanged 标记 Flash 待存 | |||
| */ | |||
| uint8_t PlsrParamBlockApplyFromHold(void); | |||
| /** 运行结构体→寄存器(上电默认、上位机读回) */ | |||
| /** | |||
| * @brief s_cfg / s_seg → 保持寄存器(供 0x03 读回) | |||
| * @note 调用时机:上电 Export、PlsrTask 首次延迟后再次 Export | |||
| */ | |||
| void PlsrParamBlockExportToHold(void); | |||
| #endif | |||
| @@ -1,10 +1,22 @@ | |||
| /** | |||
| * @file plsr_path_plan.c | |||
| * @brief 多段路径规划实现(只做决策,不碰 TIM/GPIO) | |||
| * | |||
| * @details 模块职责 | |||
| * NextSeg / ResolveAfterSeg:按 jump_seg 决定下一段或结束; | |||
| * IsForward:相对模式看 pulse_cnt 符号,绝对模式看「目标−相对原点位置」符号; | |||
| * GetWaitMs:仅 WAIT_TIME,0 映射为 1ms。 | |||
| * | |||
| * 关键数据流 | |||
| * AfterSegDone / CutSeg / ActExpire → ResolveAfterSeg → BeginSeg 或 FinishAll | |||
| * WillFollowKeep / CutSeg keep 判定 → IsForward(下一段, AbsLocalPos) | |||
| */ | |||
| #include "plsr_path_plan.h" | |||
| #include "plsr_param.h" | |||
| /** | |||
| * @brief 解析下一段(见 plsr_path_plan.h) | |||
| */ | |||
| int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based) | |||
| { | |||
| PlsrSeg_t *seg = PlsrParamGetSeg(cur_seg_0based); | |||
| @@ -33,6 +45,9 @@ int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based) | |||
| return -1; | |||
| } | |||
| /** | |||
| * @brief 判断段方向(见 plsr_path_plan.h) | |||
| */ | |||
| uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse) | |||
| { | |||
| PlsrSeg_t *seg = PlsrParamGetSeg(seg_0based); | |||
| @@ -51,6 +66,9 @@ uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse) | |||
| return (move >= 0) ? 1U : 0U; | |||
| } | |||
| /** | |||
| * @brief 段后 WAIT 时间(见 plsr_path_plan.h) | |||
| */ | |||
| uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based) | |||
| { | |||
| PlsrSeg_t *seg = PlsrParamGetSeg(seg_0based); | |||
| @@ -64,6 +82,9 @@ uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based) | |||
| return 0U; | |||
| } | |||
| /** | |||
| * @brief 段后解析下一段(见 plsr_path_plan.h) | |||
| */ | |||
| int16_t PlsrPathPlanResolveAfterSeg(uint16_t cur_seg_0based) | |||
| { | |||
| return PlsrPathPlanNextSeg(cur_seg_0based); | |||
| @@ -1,9 +1,14 @@ | |||
| /** | |||
| * @file plsr_path_plan.h | |||
| * @brief 多段路径规划:段序、跳转、段间等待、相对/绝对方向判定 | |||
| * @brief 多段路径规划:段序、跳转、相对/绝对方向判定 | |||
| * | |||
| * 不直接驱动硬件;由运行控制查询后执行。 | |||
| * 等待类型由 run_control + signal_io 执行(时间用 TIM3,信号用 EXTI)。 | |||
| * @details 模块职责 | |||
| * 只读段表与公共定位模式,回答「下一段是谁」「这一段正还是反」「段后 WAIT 多久」。 | |||
| * 不驱动 TIM/GPIO;等待类型的具体执行由 run_control + signal_io 完成。 | |||
| * | |||
| * 与其它模块关系 | |||
| * - plsr_param :GetSeg / GetSegCount / GetCfg | |||
| * - plsr_run_control :GotoNext、CutSeg、WillFollowKeep、BeginSeg 方向 | |||
| */ | |||
| #ifndef PLSR_PATH_PLAN_H | |||
| #define PLSR_PATH_PLAN_H | |||
| @@ -16,22 +21,29 @@ | |||
| * @return 下一段 0-based;无下一段或非法跳转返回 -1 | |||
| * @note jump=0:末段→结束;非末段→顺序下一段 | |||
| * jump=1~N:跳到对应段(可循环) | |||
| * 其它:结束 | |||
| */ | |||
| int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based); | |||
| /** | |||
| * @brief 判断该段运动方向 | |||
| * @param acc_pulse 相对本次启动原点的位置(绝对模式算位移用) | |||
| * @param seg_0based 段索引 | |||
| * @param acc_pulse 相对本次启动原点的位置(绝对模式算位移用) | |||
| * @return 1=正向,0=反向 | |||
| * @note 相对:看 pulse_cnt 符号;绝对:看 (目标−相对原点位置) 符号 | |||
| */ | |||
| uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse); | |||
| /** | |||
| * @brief 段结束后 WAIT 时间(ms);仅 WAIT_TIME 有效,0 按 1ms | |||
| * @brief 段结束后 WAIT 时间(ms) | |||
| * @note 仅 WAIT_TIME 有效;0 按 1ms;其它等待类型返回 0(由 run_control 处理) | |||
| */ | |||
| uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based); | |||
| /** 段结束(或等待结束)后解析下一段,语义同 NextSeg */ | |||
| /** | |||
| * @brief 段结束(或等待结束)后解析下一段 | |||
| * @note 当前语义同 NextSeg;保留独立符号便于日后插入后处理 | |||
| */ | |||
| int16_t PlsrPathPlanResolveAfterSeg(uint16_t cur_seg_0based); | |||
| #endif | |||
| @@ -1,6 +1,18 @@ | |||
| /** | |||
| * @file plsr.c | |||
| * @brief PLSR 门面实现:转发到运行控制,并提供周期任务 | |||
| * @brief PLSR 门面实现:初始化编排与周期任务 | |||
| * | |||
| * @details 模块职责 | |||
| * 按固定顺序调用各子模块 Init,并在 PlsrTask 中串联指令轮询、持久化与 | |||
| * 运行控制节拍。对外 API 均为薄转发,不含业务逻辑。 | |||
| * | |||
| * 与其它模块关系 | |||
| * 本文件仅依赖 plsr.h 及各子模块头文件,不被其它 plsr 模块反向引用。 | |||
| * | |||
| * 关键数据流 | |||
| * PlsrInit 顺序:Param 默认 → Persist 加载 → PulseDriver → SignalIo → | |||
| * RunControl → ExportToHold → CommandInit | |||
| * PlsrTask 循环:CommandPoll → PersistPoll → RunControlTickMs → OSTimeDly(1) | |||
| */ | |||
| #include "plsr.h" | |||
| #include "plsr_pulse_driver.h" | |||
| @@ -11,17 +23,23 @@ | |||
| #include "plsr_signal_io.h" | |||
| #include "ucos_ii.h" | |||
| /** | |||
| * @brief 子系统初始化(见 plsr.h) | |||
| */ | |||
| void PlsrInit(void) | |||
| { | |||
| PlsrParamInitDefault(); | |||
| PlsrPersistLoad(); /* 有 Flash 记录则覆盖公共参数;段表仍用默认 */ | |||
| PlsrPersistLoad(); /* 有 Flash 记录则覆盖公共参数;段表仍用默认/上位机下发 */ | |||
| PlsrPulseDriverInit(); | |||
| PlsrSignalIoInit(); | |||
| PlsrRunControlInit(); | |||
| PlsrParamBlockExportToHold(); /* 上电把运行映像摊到保持寄存器 */ | |||
| PlsrParamBlockExportToHold(); /* 上电把运行映像摊到保持寄存器,供 Modbus 0x03 读 */ | |||
| PlsrCommandInit(); | |||
| } | |||
| /** | |||
| * @brief 启动多段脉冲(见 plsr.h) | |||
| */ | |||
| uint8_t PlsrStart(uint16_t start_seg) | |||
| { | |||
| if (start_seg == 0U) | |||
| @@ -31,36 +49,51 @@ uint8_t PlsrStart(uint16_t start_seg) | |||
| return PlsrRunControlStart(start_seg); | |||
| } | |||
| /** @brief 急停(见 plsr.h) */ | |||
| void PlsrStop(void) | |||
| { | |||
| PlsrRunControlStop(); | |||
| } | |||
| /** @brief 运行中改频(见 plsr.h) */ | |||
| uint8_t PlsrChangeFreq(uint32_t new_tgt_hz) | |||
| { | |||
| return PlsrRunControlChangeFreq(new_tgt_hz); | |||
| } | |||
| /** @brief 忙闲查询(见 plsr.h) */ | |||
| uint8_t PlsrIsBusy(void) | |||
| { | |||
| return PlsrRunControlIsBusy(); | |||
| } | |||
| /** @brief 累计脉冲(见 plsr.h) */ | |||
| int32_t PlsrGetAccPulse(void) | |||
| { | |||
| return PlsrRunControlGetAccPulse(); | |||
| } | |||
| /** @brief 清零累计(见 plsr.h) */ | |||
| void PlsrClearAccPulse(void) | |||
| { | |||
| PlsrRunControlClearAccPulse(); | |||
| } | |||
| /** @brief 脉冲 ISR 入口(见 plsr.h) */ | |||
| 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) | |||
| { | |||
| static uint8_t s_booted = 0U; | |||
| @@ -73,7 +106,10 @@ void PlsrTask(void *pArg) | |||
| for (;;) | |||
| { | |||
| /* 首次进入:等 Modbus 清寄存器后再导出运行映像,避免被冲掉 */ | |||
| /* | |||
| * 首次进入:Modbus 从站上电常会清零保持寄存器。 | |||
| * 延迟 ~300ms 后再 Export + CommandInit,避免默认映像被冲掉。 | |||
| */ | |||
| if (s_booted == 0U) | |||
| { | |||
| OSTimeDly(30U); /* ~300ms @ 100Hz tick */ | |||
| @@ -86,12 +122,15 @@ void PlsrTask(void *pArg) | |||
| #endif | |||
| } | |||
| PlsrCommandPoll(); /* 收 START/STOP 等,回写忙闲状态 */ | |||
| PlsrCommandPoll(); /* 收 START/STOP/CLR,回写 0x2000~0x2006 监控 */ | |||
| PlsrPersistPoll(); /* 公共参数有改且空闲则追加写入 Flash */ | |||
| PlsrRunControlTickMs(); /* ACT/WAIT/EXT;改频在脉冲 ISR */ | |||
| PlsrRunControlTickMs(); /* ACT/WAIT/EXT、换向延时、ApplyPending */ | |||
| #if (PLSR_LOOP_TEST_EN != 0) | |||
| /* 调试:空闲满 1s 自动从第 1 段再启 */ | |||
| /* | |||
| * 调试循环:检测到 busy→idle 边沿后,空闲满 1s 自动从第 1 段再启。 | |||
| * 正式产品 PLSR_LOOP_TEST_EN=0 时本段不参与编译。 | |||
| */ | |||
| if (s_booted != 0U) | |||
| { | |||
| uint8_t busy = PlsrIsBusy(); | |||
| @@ -122,6 +161,6 @@ void PlsrTask(void *pArg) | |||
| } | |||
| #endif | |||
| OSTimeDly(1U); | |||
| OSTimeDly(1U); /* 约 1ms 任务节拍,与 TIM3 独立 */ | |||
| } | |||
| } | |||
| @@ -1,8 +1,27 @@ | |||
| /** | |||
| * @file plsr.h | |||
| * @brief PLSR 对外门面:初始化、周期任务、启停与状态查询 | |||
| * @brief PLSR 多段脉冲输出子系统对外门面 | |||
| * | |||
| * 内部模块:参数块 / 公共参数持久化 / 指令接口 / 路径规划 / 加减速曲线 / 运行控制 / 脉冲驱动 | |||
| * @details 模块职责 | |||
| * 本文件定义上位机/应用层唯一可见的 PLSR 入口,封装初始化、周期任务、 | |||
| * 启停控制、运行状态查询与脉冲中断回调。内部实现全部委托给子模块, | |||
| * 本层不做运动规划与硬件寄存器操作。 | |||
| * | |||
| * 与其它模块关系 | |||
| * - plsr_param :运行参数映像(公共 + 段表),Modbus 保持寄存器 ↔ 结构体 | |||
| * - plsr_persist :公共参数 Flash 掉电保存(不含段表) | |||
| * - plsr_command :Modbus 监控区 0x2000~0x2006、控制区 0x3000 指令解析 | |||
| * - plsr_run_control:段状态机、等待策略、加减速执行、故障诊断 | |||
| * - plsr_pulse_driver:TIM PWM 脉冲 + Y3 方向 GPIO | |||
| * - plsr_signal_io :WAIT/EXT 外部输入、TIM3 1ms 时基 | |||
| * | |||
| * 关键数据流 | |||
| * 上电:PlsrInit → 默认参数 → Flash 覆盖公共参数 → 驱动/IO/运行控制初始化 | |||
| * → 参数导出到保持寄存器 → 指令模块初始化 | |||
| * 周期:PlsrTask → CommandPoll(START/STOP/CLR、监控回写) | |||
| * → PersistPoll(空闲时 Flash 落盘)→ RunControlTickMs(等待/切段) | |||
| * 脉冲:TIM UPDATE 中断 → PlsrOnPulseIsr → RunControl 计数 + 逐拍改频 | |||
| * 启动:PlsrStart → RunControlStart → 路径规划 + 曲线规划 + PWM 输出 | |||
| */ | |||
| #ifndef PLSR_H | |||
| #define PLSR_H | |||
| @@ -10,38 +29,79 @@ | |||
| #include <stdint.h> | |||
| #include "plsr_param.h" | |||
| /* 1=第1段跑完后停约1s再循环(仅调试用,正式产品保持 0) */ | |||
| /* | |||
| * PLSR_LOOP_TEST_EN:调试宏。 | |||
| * 1 = 第 1 段跑完后空闲约 1s 自动再启(仅联调);正式产品保持 0。 | |||
| */ | |||
| #ifndef PLSR_LOOP_TEST_EN | |||
| #define PLSR_LOOP_TEST_EN 0 | |||
| #endif | |||
| /** 初始化参数映像、脉冲驱动、运行控制、指令寄存器 */ | |||
| /** | |||
| * @brief 子系统一次性初始化 | |||
| * @note 调用时机:OS 启动后、Modbus 从站就绪前,由 main 或启动任务调用一次 | |||
| * @note 副作用:加载 Flash 公共参数、初始化 TIM/GPIO、导出默认映像到保持寄存器 | |||
| */ | |||
| void PlsrInit(void); | |||
| /** uC/OS 任务:周期处理指令 + 运行控制节拍 */ | |||
| /** | |||
| * @brief uC/OS-II 周期任务入口 | |||
| * @param pArg 任务参数(未使用,传 NULL) | |||
| * @note 调用时机:创建后永久循环,建议 1ms 节拍(OSTimeDly(1)) | |||
| * @note 副作用:轮询 Modbus 控制位、刷新监控寄存器、推进运行控制状态机、 | |||
| * 空闲时触发 Flash 持久化;首次进入延迟 ~300ms 再导出参数(避免 Modbus 清零冲掉) | |||
| */ | |||
| 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); | |||
| /** 急停:关 PWM、清方向、退出运行 */ | |||
| /** | |||
| * @brief 急停 | |||
| * @note 调用时机:CommandPoll 收到 STOP 位,或应用层主动停止 | |||
| * @note 副作用:关 PWM、解锁 PSC、清方向、复位运行状态机与 ACT/WAIT 标志 | |||
| */ | |||
| 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 PlsrIsBusy(void); /**< 1=运动中 */ | |||
| int32_t PlsrGetAccPulse(void); /**< 累计脉冲(绝对坐标用) */ | |||
| /** | |||
| * @brief 查询是否运动中 | |||
| * @return 1=busy(段执行或等待中);0=空闲 | |||
| */ | |||
| uint8_t PlsrIsBusy(void); | |||
| /** | |||
| * @brief 读取累计脉冲计数(有符号,正=正向累计) | |||
| * @return 自系统运行以来的净脉冲数,供绝对坐标与监控 0x2000 使用 | |||
| */ | |||
| int32_t PlsrGetAccPulse(void); | |||
| /** | |||
| * @brief 清零累计脉冲与绝对坐标原点 | |||
| * @note 调用时机:CommandPoll 收到 CLR 控制位 | |||
| * @note 副作用:s_acc_pulse=0、s_abs_origin=0,同步清故障码 | |||
| */ | |||
| void PlsrClearAccPulse(void); | |||
| /** TIM UPDATE 中断入口(每发出一个脉冲调用一次) */ | |||
| /** | |||
| * @brief 脉冲 TIM UPDATE 中断入口 | |||
| * @note 调用时机:每发出一个脉冲边沿,由 stm32f4xx_it 中对应 TIM 回调转发 | |||
| * @note 副作用:s_done++/累计坐标更新、逐拍加减速改频、段末/ACT 边界切段 | |||
| */ | |||
| void PlsrOnPulseIsr(void); | |||
| #endif | |||
| @@ -1,7 +1,16 @@ | |||
| /** | |||
| * @file plsr_pulse_driver.c | |||
| * @brief 脉冲输出:Y0/PF6/TIM10、Y1/PF8/TIM13、Y2/PF7/TIM11;方向固定 Y3 | |||
| * @note TIM10/11=168MHz,TIM13=84MHz;段内锁 PSC 只改 ARR;改频在周期边界生效 | |||
| * @brief 脉冲输出驱动实现:TIM PWM + Y3 方向 | |||
| * | |||
| * @details 模块职责 | |||
| * 实现 plsr_pulse_driver.h 全部 API。核心约束: | |||
| * - 空闲不关 CC1E:Forced active 主动拉高,避免 AF 高阻耦合假脉冲; | |||
| * - ARR/CCR 预装载:改频写影子寄存器,下一 UPDATE 生效,减少毛刺/多计; | |||
| * - ISR 内禁止停表换 PSC:挂起 s_req_*,任务 ApplyPending。 | |||
| * | |||
| * 关键数据流 | |||
| * PlanSeg → LockPscRange → DIR_WAIT → Start → ISR SetFreqIsr(只改 ARR) | |||
| * 段末 ArmOnePulseStop → AfterSegDone Stop / FOLLOW ClearOnePulseStop | |||
| */ | |||
| #include "plsr_pulse_driver.h" | |||
| #include "plsr_param.h" | |||
| @@ -23,6 +32,13 @@ TIM_HandleTypeDef htim13; | |||
| #define PLSR_Y3_PORT GPIOF | |||
| #define PLSR_Y3_PIN GPIO_PIN_9 | |||
| /* | |||
| * ---------- 模块静态状态 ---------- | |||
| * s_last_freq/psc/arr — 上次量化结果;匀速热路径比较后可跳过写寄存器 | |||
| * s_active_y / htim — 当前选中脉冲路 | |||
| * s_psc_locked / locked_psc — 段内 PSC 锁;跨度过大时 locked=0 | |||
| * s_req_freq / pending — ISR 无法换 PSC 时的挂起改频 | |||
| */ | |||
| static uint32_t s_last_freq; | |||
| static uint32_t s_last_psc = 0xFFFFFFFFUL; | |||
| static uint32_t s_last_arr = 0xFFFFFFFFUL; | |||
| @@ -35,9 +51,16 @@ static uint32_t s_locked_psc; | |||
| static volatile uint32_t s_req_freq; | |||
| static volatile uint8_t s_req_pending; | |||
| /*============================================================================*/ | |||
| /* 路选择 / 时钟 / GPIO */ | |||
| /*============================================================================*/ | |||
| /** | |||
| * @brief 按 pulse_y 返回对应 HAL 句柄 | |||
| * @note Y1→TIM13、Y2→TIM11,与端子编号交叉 | |||
| */ | |||
| static TIM_HandleTypeDef *PlsrPulseDriverHtimByY(uint16_t pulse_y) | |||
| { | |||
| /* Y1=PF8→TIM13,Y2=PF7→TIM11,与端子编号交叉 */ | |||
| if (pulse_y == 1U) | |||
| { | |||
| return &htim13; | |||
| @@ -49,6 +72,7 @@ static TIM_HandleTypeDef *PlsrPulseDriverHtimByY(uint16_t pulse_y) | |||
| return &htim10; | |||
| } | |||
| /** @brief 从公共参数取脉冲端子,越界钳到 Y0 */ | |||
| static uint8_t PlsrPulseDriverSelectY(void) | |||
| { | |||
| PlsrCfg_t *cfg = PlsrParamGetCfg(); | |||
| @@ -61,7 +85,7 @@ static uint8_t PlsrPulseDriverSelectY(void) | |||
| return (uint8_t)y; | |||
| } | |||
| /** TIM10/11:APB2 定时器 168MHz;TIM13:APB1 定时器 84MHz */ | |||
| /** @brief TIM10/11=168MHz;TIM13=84MHz */ | |||
| static uint32_t PlsrPulseDriverTimClkHz(const TIM_HandleTypeDef *htim) | |||
| { | |||
| if ((htim != (const TIM_HandleTypeDef *)0) && | |||
| @@ -72,6 +96,7 @@ static uint32_t PlsrPulseDriverTimClkHz(const TIM_HandleTypeDef *htim) | |||
| return PLSR_TIM_CLK_APB2_HZ; | |||
| } | |||
| /** @brief 初始化 Y3 方向脚为推挽输出低 */ | |||
| static void PlsrPulseDriverDirGpioInit(void) | |||
| { | |||
| GPIO_InitTypeDef gpio = {0}; | |||
| @@ -114,6 +139,7 @@ static void PlsrPulseDriverOcHoldHigh(TIM_HandleTypeDef *htim) | |||
| TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_READY); | |||
| } | |||
| /** @brief 比较通道切回 PWM1 模式(开跑前) */ | |||
| static void PlsrPulseDriverOcPwm1(TIM_HandleTypeDef *htim) | |||
| { | |||
| uint32_t ccmr; | |||
| @@ -124,6 +150,10 @@ static void PlsrPulseDriverOcPwm1(TIM_HandleTypeDef *htim) | |||
| htim->Instance->CCMR1 = ccmr; | |||
| } | |||
| /** | |||
| * @brief HAL MSP:为 TIM10/11/13 配 AF GPIO 与 NVIC | |||
| * @note 由 HAL_TIM_PWM_Init 回调;优先级 5,低于 EXTI/TIM3 之外的系统中断惯例 | |||
| */ | |||
| void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) | |||
| { | |||
| GPIO_InitTypeDef gpio = {0}; | |||
| @@ -165,6 +195,11 @@ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim) | |||
| } | |||
| } | |||
| /** | |||
| * @brief 初始化单路脉冲 TIM:PWM1 + ARR/CCR 预装载 + 空闲 Forced 拉高 | |||
| * @param htim HAL 句柄 | |||
| * @param inst TIM10 / TIM11 / TIM13 | |||
| */ | |||
| static void PlsrPulseDriverTimInitOne(TIM_HandleTypeDef *htim, TIM_TypeDef *inst) | |||
| { | |||
| TIM_OC_InitTypeDef oc = {0}; | |||
| @@ -198,6 +233,7 @@ static void PlsrPulseDriverTimInitOne(TIM_HandleTypeDef *htim, TIM_TypeDef *inst | |||
| PlsrPulseDriverOcHoldHigh(htim); | |||
| } | |||
| /** @brief 驱动初始化(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverInit(void) | |||
| { | |||
| s_last_freq = 0U; | |||
| @@ -216,6 +252,7 @@ void PlsrPulseDriverInit(void) | |||
| PlsrPulseDriverTimInitOne(&htim13, TIM13); | |||
| } | |||
| /** @brief 写方向脚(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverSetDir(uint8_t forward) | |||
| { | |||
| PlsrCfg_t *cfg = PlsrParamGetCfg(); | |||
| @@ -233,16 +270,26 @@ void PlsrPulseDriverSetDir(uint8_t forward) | |||
| level ? GPIO_PIN_SET : GPIO_PIN_RESET); | |||
| } | |||
| /** @brief 方向脚拉低(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverClearDir(void) | |||
| { | |||
| HAL_GPIO_WritePin(PLSR_Y3_PORT, PLSR_Y3_PIN, GPIO_PIN_RESET); | |||
| } | |||
| /** @brief 是否脉冲 TIM(见 plsr_pulse_driver.h) */ | |||
| uint8_t PlsrPulseDriverIsPulseTim(TIM_TypeDef *instance) | |||
| { | |||
| return ((instance == TIM10) || (instance == TIM11) || (instance == TIM13)) ? 1U : 0U; | |||
| } | |||
| /*============================================================================*/ | |||
| /* PSC / ARR 量化 */ | |||
| /*============================================================================*/ | |||
| /** | |||
| * @brief 在给定 PSC 下计算 ARR/CCR(约 50% 占空比) | |||
| * @note freq 钳到 [1,100000];ARR 落在 16 位合法范围 | |||
| */ | |||
| static void PlsrPulseDriverCalcArrWithPsc(uint32_t clk_hz, uint32_t freq_hz, uint32_t psc, | |||
| uint32_t *arr, uint32_t *ccr) | |||
| { | |||
| @@ -290,6 +337,9 @@ static void PlsrPulseDriverCalcArrWithPsc(uint32_t clk_hz, uint32_t freq_hz, uin | |||
| *ccr = c; | |||
| } | |||
| /** | |||
| * @brief 自适应选择 PSC,再算 ARR/CCR(未锁 PSC 时用) | |||
| */ | |||
| static void PlsrPulseDriverCalcPscArr(uint32_t clk_hz, uint32_t freq_hz, | |||
| uint32_t *psc, uint32_t *arr, uint32_t *ccr) | |||
| { | |||
| @@ -330,6 +380,7 @@ static void PlsrPulseDriverCalcPscArr(uint32_t clk_hz, uint32_t freq_hz, | |||
| PlsrPulseDriverCalcArrWithPsc(clk_hz, freq_hz, p, arr, ccr); | |||
| } | |||
| /** @brief 按频率跨度锁 PSC(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverLockPscRange(uint32_t f_min_hz, uint32_t f_max_hz) | |||
| { | |||
| uint32_t ticks_lo; | |||
| @@ -394,17 +445,28 @@ void PlsrPulseDriverLockPscRange(uint32_t f_min_hz, uint32_t f_max_hz) | |||
| s_psc_locked = 1U; | |||
| } | |||
| /** @brief 解除 PSC 锁(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverUnlockPsc(void) | |||
| { | |||
| s_psc_locked = 0U; | |||
| s_locked_psc = 0U; | |||
| } | |||
| /** @brief 停单路:Forced active 拉高,关 CEN/UPDATE */ | |||
| static void PlsrPulseDriverStopHtim(TIM_HandleTypeDef *htim) | |||
| { | |||
| PlsrPulseDriverOcHoldHigh(htim); | |||
| } | |||
| /*============================================================================*/ | |||
| /* 启停 / 改频 */ | |||
| /*============================================================================*/ | |||
| /** | |||
| * @brief Start / StartFreeRun 公共路径 | |||
| * @param freq_hz 0=停表;>0 开跑 | |||
| * @param enable_update_it 1=开 UPDATE 中断(计数脉冲);0=自由跑 | |||
| */ | |||
| static void PlsrPulseDriverStartCommon(uint32_t freq_hz, uint8_t enable_update_it) | |||
| { | |||
| uint32_t psc; | |||
| @@ -480,6 +542,7 @@ static void PlsrPulseDriverStartCommon(uint32_t freq_hz, uint8_t enable_update_i | |||
| } | |||
| } | |||
| /** @brief 任务上下文改频(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverSetFreq(uint32_t freq_hz) | |||
| { | |||
| uint32_t psc; | |||
| @@ -550,6 +613,7 @@ void PlsrPulseDriverSetFreq(uint32_t freq_hz) | |||
| s_last_arr = arr; | |||
| } | |||
| /** @brief ISR 改频(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverSetFreqIsr(uint32_t freq_hz) | |||
| { | |||
| uint32_t psc; | |||
| @@ -612,17 +676,20 @@ void PlsrPulseDriverSetFreqIsr(uint32_t freq_hz) | |||
| s_last_arr = arr; | |||
| } | |||
| /** @brief 挂起改频请求(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverRequestFreq(uint32_t freq_hz) | |||
| { | |||
| s_req_freq = freq_hz; | |||
| s_req_pending = 1U; | |||
| } | |||
| /** @brief 丢弃挂起(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverClearPending(void) | |||
| { | |||
| s_req_pending = 0U; | |||
| } | |||
| /** @brief 消费挂起改频(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverApplyPending(void) | |||
| { | |||
| uint32_t f; | |||
| @@ -641,16 +708,19 @@ void PlsrPulseDriverApplyPending(void) | |||
| PlsrPulseDriverSetFreq(f); | |||
| } | |||
| /** @brief 开 PWM + UPDATE 中断(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverStart(uint32_t freq_hz) | |||
| { | |||
| PlsrPulseDriverStartCommon(freq_hz, 1U); | |||
| } | |||
| /** @brief 开 PWM 不使能 UPDATE(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverStartFreeRun(uint32_t freq_hz) | |||
| { | |||
| PlsrPulseDriverStartCommon(freq_hz, 0U); | |||
| } | |||
| /** @brief 停三路(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverStop(void) | |||
| { | |||
| s_req_pending = 0U; | |||
| @@ -662,6 +732,7 @@ void PlsrPulseDriverStop(void) | |||
| s_last_arr = 0xFFFFFFFFUL; | |||
| } | |||
| /** @brief 武装 OPM 段末停表(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverArmOnePulseStop(void) | |||
| { | |||
| TIM_HandleTypeDef *htim = s_active_htim; | |||
| @@ -674,6 +745,7 @@ void PlsrPulseDriverArmOnePulseStop(void) | |||
| htim->Instance->CR1 |= TIM_CR1_OPM; | |||
| } | |||
| /** @brief 取消 OPM(见 plsr_pulse_driver.h) */ | |||
| void PlsrPulseDriverClearOnePulseStop(void) | |||
| { | |||
| TIM_HandleTypeDef *htim = s_active_htim; | |||
| @@ -1,15 +1,26 @@ | |||
| /** | |||
| * @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) | |||
| * Y1 → PF8 / TIM13_CH1 AF9 (APB1 Timer 84MHz) | |||
| * Y2 → PF7 / TIM11_CH1 AF3 (APB2 Timer 168MHz) | |||
| * 方向固定 Y3 → PF9 GPIO(不可选) | |||
| * Y3 → PF9 GPIO 方向(固定,不可选) | |||
| * | |||
| * 频率 0:停止该路 PWM;>0 时按 Hz 输出。 | |||
| * 段内锁定 PSC、只改 ARR;改频请求在脉冲周期边界生效,减少毛刺。 | |||
| * 频率约定 | |||
| * 0 :停止该路 PWM(空闲 Forced active 拉高) | |||
| * 1~100k:按 Hz 输出;超出由 Calc 路径钳到 100k | |||
| */ | |||
| #ifndef PLSR_PULSE_DRIVER_H | |||
| #define PLSR_PULSE_DRIVER_H | |||
| @@ -17,40 +28,98 @@ | |||
| #include <stdint.h> | |||
| #include "stm32f4xx_hal.h" | |||
| /** HAL 句柄:由本模块 Init 填充,中断向量里转发到 HAL_TIM_IRQHandler */ | |||
| extern TIM_HandleTypeDef htim10; | |||
| extern TIM_HandleTypeDef htim11; | |||
| extern TIM_HandleTypeDef htim13; | |||
| /** | |||
| * @brief 初始化三路脉冲 TIM + 方向 GPIO | |||
| * @note 调用时机:PlsrInit,在 RunControl 之前 | |||
| * @note 副作用:Y0/Y1/Y2 空闲拉高、计数器关;Y3 输出低 | |||
| */ | |||
| void PlsrPulseDriverInit(void); | |||
| /** | |||
| * @brief 按正/反向与 dir_logic 写 Y3 方向脚 | |||
| * @param forward 1=正转语义,0=反转 | |||
| */ | |||
| void PlsrPulseDriverSetDir(uint8_t forward); | |||
| /** @brief 方向脚拉低(收尾/急停) */ | |||
| 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); | |||
| /** @brief 解除 PSC 锁定(FinishAll / Stop) */ | |||
| void PlsrPulseDriverUnlockPsc(void); | |||
| /** | |||
| * @brief 以指定频率启动选中路 PWM,并打开 UPDATE 中断 | |||
| * @param freq_hz 0=停表;>0 开跑 | |||
| */ | |||
| void PlsrPulseDriverStart(uint32_t freq_hz); | |||
| /** | |||
| * @brief 同 Start,但不使能 UPDATE 中断(自由跑,不计入脉冲 ISR) | |||
| */ | |||
| void PlsrPulseDriverStartFreeRun(uint32_t freq_hz); | |||
| /** | |||
| * @brief 任务上下文改频:PSC 不变则预装载 ARR;须换 PSC 则停表重装 | |||
| * @note 频率或量化结果未变则立即返回 | |||
| */ | |||
| void PlsrPulseDriverSetFreq(uint32_t freq_hz); | |||
| /** | |||
| * 脉冲 ISR 改频:仅写 ARR/CCR(预装载,下一拍生效)。 | |||
| * PSC 不变时直接改;若必须换 PSC 则挂起,由任务 ApplyPending。 | |||
| * 频率未变或 ARR 未变则立即返回(匀速热路径)。 | |||
| * @brief 脉冲 ISR 改频:仅写 ARR/CCR(预装载,下一拍生效) | |||
| * @note PSC 不变时直接改;必须换 PSC 则挂起,由任务 ApplyPending | |||
| * @note 频率未变或 ARR 未变则立即返回(匀速热路径) | |||
| */ | |||
| void PlsrPulseDriverSetFreqIsr(uint32_t freq_hz); | |||
| /** 请求改频(ISR 周期边界再落到硬件) */ | |||
| /** | |||
| * @brief 请求改频(挂起),在周期边界或任务里落到硬件 | |||
| * @param freq_hz 目标 Hz;0 表示挂起停表 | |||
| */ | |||
| void PlsrPulseDriverRequestFreq(uint32_t freq_hz); | |||
| /** 在更新中断里调用:应用挂起频率 */ | |||
| /** | |||
| * @brief 消费挂起改频(TickMs / 更新路径) | |||
| * @note s_req_freq==0 时 Stop;否则 SetFreq | |||
| */ | |||
| void PlsrPulseDriverApplyPending(void); | |||
| /** 丢弃未生效的改频请求(进匀速锁定时用) */ | |||
| /** @brief 丢弃未生效的改频请求(进匀速锁定时用) */ | |||
| void PlsrPulseDriverClearPending(void); | |||
| /** | |||
| * @brief 停止三路脉冲 TIM,空闲 Forced active 拉高 | |||
| * @note 不清方向脚;不清 PSC 锁(由上层 Unlock) | |||
| */ | |||
| void PlsrPulseDriverStop(void); | |||
| /** 当前拍结束后停止计数,避免多出一个上升沿 */ | |||
| /** | |||
| * @brief 当前拍结束后停止计数(CR1.OPM),避免段末多出一个上升沿 | |||
| * @note 调用时机:倒数第二脉冲 ISR,且不会 FOLLOW 不停表衔接时 | |||
| */ | |||
| void PlsrPulseDriverArmOnePulseStop(void); | |||
| /** 取消单脉冲停表(后续模式同向衔接时用) */ | |||
| /** | |||
| * @brief 取消单脉冲停表(后续模式同向衔接 / CutSeg keep 时) | |||
| */ | |||
| void PlsrPulseDriverClearOnePulseStop(void); | |||
| /** | |||
| * @brief 判断 instance 是否为本模块脉冲 TIM | |||
| * @return 1=TIM10/11/13;0=否 | |||
| * @note 供 stm32f4xx_it 过滤 UPDATE 回调 | |||
| */ | |||
| uint8_t PlsrPulseDriverIsPulseTim(TIM_TypeDef *instance); | |||
| #endif | |||
| @@ -17,6 +17,12 @@ | |||
| * 匀速复用 ARR(不改频);remain<=dec_n 进减速。 | |||
| * 任务 TickMs:仅 ACT/WAIT/EXT/换向延时 + ApplyPending(极少换 PSC)。 | |||
| * TIM3 1ms:仅 ACT/WAIT 时基。 | |||
| * | |||
| * 【故障诊断 0x2006】(PlsrCommandSetFault,告警码 1~3 不停机;4 禁止启动) | |||
| * 0x01 段结束 AfterSegDone:s_done != s_target | |||
| * 0x02 PlanSeg 后:规划峰值达不到设定 f_tgt(典型三角) | |||
| * 0x03 加速离开 APPROACH / 减速段末:相内脉冲 ≠ 规划预算 | |||
| * 0x04 目标频率 ∉ [1,100000]:Start / BeginSeg / ChangeFreq / 运行中写频 | |||
| */ | |||
| #include "plsr_run_control.h" | |||
| #include "plsr_path_plan.h" | |||
| @@ -28,6 +34,13 @@ | |||
| #include "ucos_ii.h" | |||
| #include <stdint.h> | |||
| /* | |||
| * ---------- 运行主状态机 RcState_e ---------- | |||
| * RC_IDLE — 无段在执行;可 Start;脉冲 ISR 不推进 | |||
| * RC_DIR_WAIT — 方向 GPIO 已设,等 dir_delay_ms(OS tick)后开 PWM | |||
| * RC_RUN — 脉冲 ISR 推进 s_done、逐拍改频;TickMs 处理 EXT | |||
| * RC_WAIT_COND — 段后等待:时间(TIM3)或 WAIT/EXT 下降沿 | |||
| */ | |||
| typedef enum { | |||
| RC_IDLE = 0, | |||
| RC_DIR_WAIT, | |||
| @@ -35,12 +48,30 @@ typedef enum { | |||
| RC_WAIT_COND | |||
| } RcState_e; | |||
| /* | |||
| * ---------- 段内运行相 RunPhase_e(脉冲 ISR 热路径) ---------- | |||
| * PH_APPROACH — 起速 → 本段峰值 f_tgt | |||
| * PH_CONST — 匀速,复用 ARR 不写寄存器 | |||
| * PH_DECEL — 段末过渡 → f_end(可升可降) | |||
| */ | |||
| typedef enum { | |||
| PH_APPROACH = 0, /* 起速 → 本段目标/峰值 */ | |||
| PH_CONST, /* 匀速 */ | |||
| PH_DECEL /* 段末过渡 → f_end(可升可降:完成=止速;后续=下一段目标) */ | |||
| PH_APPROACH = 0, | |||
| PH_CONST, | |||
| PH_DECEL | |||
| } RunPhase_e; | |||
| /* | |||
| * ---------- 模块静态状态(按功能分组) ---------- | |||
| * | |||
| * 段进度:s_state, s_busy, s_cur_seg, s_done, s_target, s_forward | |||
| * 频率链:s_cur_freq, s_chain_freq, s_chain_valid, s_follow_cont, s_pwm_on | |||
| * 坐标 :s_acc_pulse(全局累计), s_abs_origin(本次 Start 快照,绝对模式用) | |||
| * 曲线 :s_accel_plan, s_pulse_rt, s_phase, s_plan_acc_n, s_plan_dec_n, s_decel_budget | |||
| * ACT :s_act_armed, s_act_time_ms, s_act_arm_ms, s_act_cut_pending, s_act_handoff_keep, s_act_expire_req | |||
| * WAIT :s_wait_is_signal, s_wait_deadline_ms, s_wait_expire_req | |||
| * 换向 :s_dir_deadline(OS tick) | |||
| * 段参数:s_run_accel_ms, s_run_decel_ms(PlanSeg 锁定,ChangeFreq 复用) | |||
| */ | |||
| static volatile RcState_e s_state; | |||
| static volatile uint8_t s_busy; | |||
| static volatile uint8_t s_forward; | |||
| @@ -77,12 +108,18 @@ static uint8_t s_pwm_on; | |||
| static uint16_t s_run_accel_ms; | |||
| static uint16_t s_run_decel_ms; | |||
| /* 规划锁定的加/减速脉冲预算(EnterDecel 会改写 plan.dec_n,故另存) */ | |||
| /* 规划锁定的加/减速脉冲预算(供 0x03 诊断;EnterDecel 会改写 plan.dec_n,故另存) */ | |||
| static uint32_t s_plan_acc_n; | |||
| static uint32_t s_plan_dec_n; | |||
| static uint32_t s_decel_budget; /* 进入减速时的 remain,用于 0x03 */ | |||
| /** 策略层交给段处理的接口参数 */ | |||
| static uint32_t s_decel_budget; | |||
| /* | |||
| * PlsrSegCallIo_t — 策略层解析后交给 PlanSeg 的段端点参数 | |||
| * f_from 本段起始频率(真实 Hz,>=1) | |||
| * f_end 本段终止频率(完成=止速;后续=下一段目标) | |||
| * accel_ms 本段加速时间(来自公共参数,段内不读 wait_type) | |||
| * decel_ms 本段减速时间 | |||
| */ | |||
| typedef struct { | |||
| uint32_t f_from; | |||
| uint32_t f_end; | |||
| @@ -117,6 +154,11 @@ static uint8_t PlsrRunControlGetSegTargetFreq(int32_t freq_hz, | |||
| uint32_t default_spd, | |||
| uint32_t *out_hz); | |||
| /** | |||
| * @brief 开跑瞬间武装 ACT/EXT | |||
| * @note EXT:清旧沿防误切;WAIT 信号不在此清(段中沿留给※2) | |||
| * @note ACT:写时长与 arm_ms 后再武装,计时跟自由运行 s_ms | |||
| */ | |||
| static void PlsrRunControlArmActExtOnPulseStart(void) | |||
| { | |||
| PlsrSeg_t *seg = PlsrParamGetSeg(s_cur_seg); | |||
| @@ -145,7 +187,8 @@ static void PlsrRunControlArmActExtOnPulseStart(void) | |||
| } | |||
| /** | |||
| * EXT 触发切段(脉冲可能还在跑)。 | |||
| * @brief EXT 触发切段(脉冲可能还在跑) | |||
| * | |||
| * 同向且 PWM 在跑:不停表,下一段 f_from = 当前频率(衔接加速)。 | |||
| * 反向或已停表:停表,下一段从起速爬。 | |||
| */ | |||
| @@ -191,9 +234,11 @@ static void PlsrRunControlCutSegToNext(void) | |||
| } | |||
| /** | |||
| * ACT 时间到(脉冲可能还没发完):丢掉本段剩余脉冲/减速,从当前频率切下一段。 | |||
| * @brief ACT 时间到(脉冲可能还没发完):丢掉本段剩余,从当前频率切下一段 | |||
| * | |||
| * 同向:下一段 f_from=当前频率(逐层跳,不进本段减速)。 | |||
| * 反向或无频率:停表,下一段从起速爬。 | |||
| * act_handoff_keep:对齐脉冲边界先停表后仍保持衔接。 | |||
| */ | |||
| static void PlsrRunControlActExpire(void) | |||
| { | |||
| @@ -238,6 +283,12 @@ static void PlsrRunControlActExpire(void) | |||
| PlsrRunControlGotoNextOrFinish(s_cur_seg); | |||
| } | |||
| /** | |||
| * @brief 段发完后的等待策略入口 | |||
| * | |||
| * WAIT时间/信号/纯EXT → 停表清链后进 RC_WAIT_COND 或立即 GotoNext; | |||
| * EXT_OR_DONE 脉冲先完 → 立刻下一段;其它 → GotoNextOrFinish。 | |||
| */ | |||
| static void PlsrRunControlEnterPostWaitOrNext(void) | |||
| { | |||
| PlsrSeg_t *seg = PlsrParamGetSeg(s_cur_seg); | |||
| @@ -315,6 +366,9 @@ static void PlsrRunControlEnterPostWaitOrNext(void) | |||
| PlsrRunControlGotoNextOrFinish(s_cur_seg); | |||
| } | |||
| /** | |||
| * @brief 解析下一段:无则 FinishAll,有则 BeginSeg | |||
| */ | |||
| static void PlsrRunControlGotoNextOrFinish(uint16_t cur_seg) | |||
| { | |||
| int16_t next0 = PlsrPathPlanResolveAfterSeg(cur_seg); | |||
| @@ -329,7 +383,14 @@ static void PlsrRunControlGotoNextOrFinish(uint16_t cur_seg) | |||
| } | |||
| } | |||
| /** 0x01:段正常结束时实际脉冲 ≠ 本段目标(告警,不停) */ | |||
| /* ---------- 故障诊断(写 0x2006,见文件头说明) ---------- */ | |||
| /** | |||
| * 0x01 脉冲个数诊断 | |||
| * 时机:段正常结束进入 AfterSegDone 时。 | |||
| * 条件:本段目标 s_target>0 且实际发出 s_done 与目标不等。 | |||
| * 处理:告警,不中断后续切段/收尾逻辑。 | |||
| */ | |||
| static void PlsrRunControlCheckPulseCntFault(void) | |||
| { | |||
| if ((s_target > 0) && (s_done != s_target)) | |||
| @@ -339,8 +400,11 @@ static void PlsrRunControlCheckPulseCntFault(void) | |||
| } | |||
| /** | |||
| * 0x03:加速段结束时,相内脉冲与规划 acc_n 不一致。 | |||
| * by_pulse_budget:因 n 走到预算而收尾,须精确相等;因频率提前到位则仅超额时报。 | |||
| * 0x03 加速曲线诊断(离开 APPROACH → 匀速/减速前调用) | |||
| * @param by_pulse_budget | |||
| * 1:因相内脉冲走到规划 acc_n 而收尾 → 要求 s_pulse_rt.n == s_plan_acc_n | |||
| * 0:因频率已提前到达目标而收尾 → 仅当 n 超过规划预算时报故障 | |||
| * 对照量用 PlanSeg 时锁定的 s_plan_acc_n(运行中 plan.acc_n 可能被改写)。 | |||
| */ | |||
| static void PlsrRunControlCheckApproachCurveFault(uint8_t by_pulse_budget) | |||
| { | |||
| @@ -361,7 +425,11 @@ static void PlsrRunControlCheckApproachCurveFault(uint8_t by_pulse_budget) | |||
| } | |||
| } | |||
| /** 0x03:减速段在段末核对相内脉冲与进入减速时的预算 */ | |||
| /** | |||
| * 0x03 减速曲线诊断 | |||
| * 时机:段结束 AfterSegDone;仅当仍处 DECEL 相。 | |||
| * 条件:进入减速时记下的 remain(s_decel_budget)与相内实发脉冲不等。 | |||
| */ | |||
| static void PlsrRunControlCheckDecelCurveFault(void) | |||
| { | |||
| if ((s_phase == PH_DECEL) && (s_decel_budget > 0U) && | |||
| @@ -371,12 +439,16 @@ static void PlsrRunControlCheckDecelCurveFault(void) | |||
| } | |||
| } | |||
| /** | |||
| * @brief 段正常结束:诊断 → 链式频率 → FOLLOW keep 或停表 → EnterPostWaitOrNext | |||
| */ | |||
| static void PlsrRunControlAfterSegDone(void) | |||
| { | |||
| PlsrCfg_t *cfg = PlsrParamGetCfg(); | |||
| PlsrSeg_t *seg = PlsrParamGetSeg(s_cur_seg); | |||
| uint8_t keep_pwm; | |||
| /* 段末诊断:0x01 脉冲个数;若在减速相再核 0x03 */ | |||
| PlsrRunControlCheckPulseCntFault(); | |||
| PlsrRunControlCheckDecelCurveFault(); | |||
| @@ -450,6 +522,10 @@ static void PlsrRunControlAfterSegDone(void) | |||
| PlsrRunControlEnterPostWaitOrNext(); | |||
| } | |||
| /** | |||
| * @brief 哪些等待类型禁止 FOLLOW 不停表衔接 | |||
| * @return 1=必须停表等条件;0=允许(含 EXT_OR_DONE 脉冲先完) | |||
| */ | |||
| static uint8_t PlsrRunControlBlocksFollowKeep(uint16_t cur_seg) | |||
| { | |||
| PlsrSeg_t *seg = PlsrParamGetSeg(cur_seg); | |||
| @@ -590,6 +666,10 @@ static uint16_t PlsrRunControlActTimeMs(const PlsrSeg_t *seg) | |||
| return ms; | |||
| } | |||
| /** | |||
| * @brief 段末是否可不停表衔接下一段 | |||
| * @note 须 FOLLOW + 有同向下一段 + 无 BlocksFollowKeep | |||
| */ | |||
| static uint8_t PlsrRunControlWillFollowKeep(uint16_t cur_seg) | |||
| { | |||
| PlsrCfg_t *cfg = PlsrParamGetCfg(); | |||
| @@ -615,6 +695,7 @@ static uint8_t PlsrRunControlWillFollowKeep(uint16_t cur_seg) | |||
| return 1U; | |||
| } | |||
| /** @brief ms → OS tick(向上取整,至少 1) */ | |||
| static INT32U PlsrRunControlMsToTicks(uint32_t ms) | |||
| { | |||
| INT32U t; | |||
| @@ -631,11 +712,16 @@ static INT32U PlsrRunControlMsToTicks(uint32_t ms) | |||
| return t; | |||
| } | |||
| /** @brief OS 时间是否已到 deadline(有符号环绕安全) */ | |||
| static uint8_t PlsrRunControlOsTimeReached(INT32U deadline) | |||
| { | |||
| return ((INT32S)(OSTimeGet() - deadline) >= 0) ? 1U : 0U; | |||
| } | |||
| /** | |||
| * 解析段目标频率。 | |||
| * freq_hz==0 用公共默认速度;结果须落在 [1, 100000],否则返回 0(调用方写 0x04)。 | |||
| */ | |||
| static uint8_t PlsrRunControlGetSegTargetFreq(int32_t freq_hz, | |||
| uint32_t default_spd, | |||
| uint32_t *out_hz) | |||
| @@ -654,6 +740,7 @@ static uint8_t PlsrRunControlGetSegTargetFreq(int32_t freq_hz, | |||
| { | |||
| f = (uint32_t)freq_hz; | |||
| } | |||
| /* 合法范围:1Hz ~ 100kHz(含) */ | |||
| if ((f < 1U) || (f > 100000U)) | |||
| { | |||
| return 0U; | |||
| @@ -743,6 +830,9 @@ static int32_t PlsrRunControlAbsLocalPos(void) | |||
| return s_acc_pulse - s_abs_origin; | |||
| } | |||
| /** | |||
| * @brief 进入减速相:用 remain 覆盖 plan.dec_n,BeginDec,锁定 s_decel_budget(0x03 用) | |||
| */ | |||
| static void PlsrRunControlEnterDecel(uint32_t from_hz, uint32_t remain_pulses) | |||
| { | |||
| PlsrRunControlEnterPhase(PH_DECEL, from_hz); | |||
| @@ -774,7 +864,7 @@ static void PlsrRunControlEnterDecel(uint32_t from_hz, uint32_t remain_pulses) | |||
| } | |||
| } | |||
| /** 离开加速相时做 0x03 诊断 */ | |||
| /** 离开加速相时触发 0x03 加速诊断(仅当前相为 APPROACH 时有效) */ | |||
| static void PlsrRunControlOnApproachDone(void) | |||
| { | |||
| uint8_t by_budget; | |||
| @@ -783,6 +873,7 @@ static void PlsrRunControlOnApproachDone(void) | |||
| { | |||
| return; | |||
| } | |||
| /* n 已走到预算 → 按「预算收尾」精确比对;否则按「提前到频」只查超额 */ | |||
| by_budget = ((s_plan_acc_n > 0U) && (s_pulse_rt.n >= s_plan_acc_n)) ? 1U : 0U; | |||
| PlsrRunControlCheckApproachCurveFault(by_budget); | |||
| } | |||
| @@ -884,6 +975,10 @@ static void PlsrRunControlRefreshProfile(uint8_t do_start) | |||
| } | |||
| } | |||
| /** | |||
| * @brief 规划本段波形并锁 PSC / 进初始相(见文件头故障 0x02) | |||
| * @note 与 ChangeFreq 共用;不读 wait_type | |||
| */ | |||
| static void PlsrRunControlPlanSeg(uint32_t total, uint32_t f_from, | |||
| uint32_t f_tgt, uint32_t f_end, | |||
| uint16_t accel_ms, uint16_t decel_ms) | |||
| @@ -914,8 +1009,9 @@ static void PlsrRunControlPlanSeg(uint32_t total, uint32_t f_from, | |||
| cfg->accel_mode); | |||
| /* | |||
| * 0x02:规划后峰值低于设定目标 → 脉冲不够爬到目标频(三角等) | |||
| * 告警,仍按当前规划继续跑。 | |||
| * 0x02 频率可达性诊断 | |||
| * AccelCurvePlan 后:若脉冲不够爬升/下降到设定目标,规划会压低/抬高实际峰值 | |||
| * (典型为三角波)。此时写故障码 2,仍按压后的规划继续运行。 | |||
| */ | |||
| if ((f_tgt > f_from) && (s_accel_plan.f_tgt < f_tgt)) | |||
| { | |||
| @@ -926,7 +1022,11 @@ static void PlsrRunControlPlanSeg(uint32_t total, uint32_t f_from, | |||
| PlsrCommandSetFault(PLSR_ERR_FREQ_UNREACH); | |||
| } | |||
| /* 0x03 对照用:规划预算(运行中 s_accel_plan.acc_n 可能被改写) */ | |||
| /* | |||
| * 锁定本段规划预算,供 0x03 对照。 | |||
| * EnterDecel 会改写 s_accel_plan.dec_n,故加速预算另存 s_plan_acc_n; | |||
| * 减速对照用进入减速瞬间的 remain(s_decel_budget)。 | |||
| */ | |||
| s_plan_acc_n = s_accel_plan.acc_n; | |||
| s_plan_dec_n = s_accel_plan.dec_n; | |||
| s_decel_budget = 0U; | |||
| @@ -1024,6 +1124,9 @@ static void PlsrRunControlPlanSeg(uint32_t total, uint32_t f_from, | |||
| } | |||
| } | |||
| /** | |||
| * @brief 全部段结束:停 PWM、解锁 PSC、清方向与全部运行标志 | |||
| */ | |||
| static void PlsrRunControlFinishAll(void) | |||
| { | |||
| PlsrPulseDriverStop(); | |||
| @@ -1045,6 +1148,10 @@ static void PlsrRunControlFinishAll(void) | |||
| s_act_arm_ms = 0U; | |||
| } | |||
| /** | |||
| * @brief 开一段:算位移/方向 → 解析端点 → PlanSeg → DIR_WAIT 或衔接 RUN | |||
| * @param seg_0 0-based 段号;越界则 FinishAll | |||
| */ | |||
| static void PlsrRunControlBeginSeg(uint16_t seg_0) | |||
| { | |||
| PlsrSeg_t *seg; | |||
| @@ -1097,7 +1204,10 @@ static void PlsrRunControlBeginSeg(uint16_t seg_0) | |||
| if (PlsrRunControlGetSegTargetFreq(seg->freq_hz, cfg->default_speed, &f_tgt) == 0U) | |||
| { | |||
| /* 0x04:频率非法,禁止启动/切段 */ | |||
| /* | |||
| * 0x04:本段(或默认速度)频率非法。 | |||
| * 禁止启动该段:清忙并结束整次运行,不 AfterSegDone 以免误切下一段。 | |||
| */ | |||
| PlsrCommandSetFault(PLSR_ERR_FREQ_ILLEGAL); | |||
| PlsrRunControlFinishAll(); | |||
| return; | |||
| @@ -1134,6 +1244,7 @@ static void PlsrRunControlBeginSeg(uint16_t seg_0) | |||
| } | |||
| } | |||
| /** @brief 运行控制初始化(见 plsr_run_control.h) */ | |||
| void PlsrRunControlInit(void) | |||
| { | |||
| s_state = RC_IDLE; | |||
| @@ -1165,6 +1276,7 @@ void PlsrRunControlInit(void) | |||
| s_wait_deadline_ms = 0U; | |||
| } | |||
| /** @brief 启动多段序列(见 plsr_run_control.h) */ | |||
| uint8_t PlsrRunControlStart(uint16_t start_seg_1based) | |||
| { | |||
| uint16_t n; | |||
| @@ -1190,7 +1302,11 @@ uint8_t PlsrRunControlStart(uint16_t start_seg_1based) | |||
| PlsrSeg_t *seg = PlsrParamGetSeg((uint16_t)(start_seg_1based - 1U)); | |||
| uint32_t f_chk; | |||
| /* 启动前校验首段频率;非法则写 0x04 且不进入运行 */ | |||
| /* | |||
| * 启动前门禁(0x04):首段目标频率须在 1~100000。 | |||
| * 非法则写故障码并返回 0,不置忙、不开段。 | |||
| * (启动前 CommandPoll 已 ClearFault,此处再写 4 覆盖为异常。) | |||
| */ | |||
| if (PlsrRunControlGetSegTargetFreq(seg->freq_hz, | |||
| cfg->default_speed, | |||
| &f_chk) == 0U) | |||
| @@ -1210,6 +1326,7 @@ uint8_t PlsrRunControlStart(uint16_t start_seg_1based) | |||
| return 1U; | |||
| } | |||
| /** @brief 急停(见 plsr_run_control.h) */ | |||
| void PlsrRunControlStop(void) | |||
| { | |||
| PlsrPulseDriverStop(); | |||
| @@ -1231,6 +1348,7 @@ void PlsrRunControlStop(void) | |||
| s_act_arm_ms = 0U; | |||
| } | |||
| /** @brief 运行中改频(见 plsr_run_control.h) */ | |||
| uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz) | |||
| { | |||
| uint32_t remain; | |||
| @@ -1240,6 +1358,7 @@ uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz) | |||
| { | |||
| return 0U; | |||
| } | |||
| /* 0x04:动态改频目标非法则拒收,保持原规划 */ | |||
| if ((new_tgt_hz < 1U) || (new_tgt_hz > 100000U)) | |||
| { | |||
| PlsrCommandSetFault(PLSR_ERR_FREQ_ILLEGAL); | |||
| @@ -1260,6 +1379,7 @@ uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz) | |||
| return 1U; | |||
| } | |||
| /** @brief 毫秒任务节拍(见 plsr_run_control.h) */ | |||
| void PlsrRunControlTickMs(void) | |||
| { | |||
| PlsrSeg_t *seg; | |||
| @@ -1338,10 +1458,7 @@ void PlsrRunControlTickMs(void) | |||
| } | |||
| } | |||
| /** | |||
| * TIM3 1ms:仅 ACT/WAIT 时基(不再改频)。 | |||
| * 切段在 TickMs(避免 ISR 里 PlanSeg 丢拍)。 | |||
| */ | |||
| /** @brief TIM3 1ms 回调(见 plsr_run_control.h) */ | |||
| void PlsrRunControlOn1ms(void) | |||
| { | |||
| if (s_state == RC_WAIT_COND) | |||
| @@ -1372,7 +1489,7 @@ void PlsrRunControlOn1ms(void) | |||
| } | |||
| } | |||
| /** 脉冲 UPDATE:计脉冲 + 逐拍改频(匀速复用 ARR) */ | |||
| /** @brief 脉冲 UPDATE ISR(见 plsr_run_control.h) */ | |||
| void PlsrRunControlOnPulseIsr(void) | |||
| { | |||
| uint32_t remain; | |||
| @@ -1473,22 +1590,28 @@ void PlsrRunControlOnPulseIsr(void) | |||
| } | |||
| } | |||
| /** @return 1=运动中(见 plsr_run_control.h) */ | |||
| uint8_t PlsrRunControlIsBusy(void) | |||
| { | |||
| return s_busy; | |||
| } | |||
| /** @return 累计脉冲(见 plsr_run_control.h) */ | |||
| int32_t PlsrRunControlGetAccPulse(void) | |||
| { | |||
| return s_acc_pulse; | |||
| } | |||
| /** @brief 清累计与绝对原点(见 plsr_run_control.h) */ | |||
| void PlsrRunControlClearAccPulse(void) | |||
| { | |||
| s_acc_pulse = 0; | |||
| s_abs_origin = 0; | |||
| } | |||
| /** | |||
| * @return 当前段号 1-based;空闲为 0(见 plsr_run_control.h) | |||
| */ | |||
| uint16_t PlsrRunControlGetCurSeg(void) | |||
| { | |||
| if (s_busy == 0U) | |||
| @@ -1498,6 +1621,7 @@ uint16_t PlsrRunControlGetCurSeg(void) | |||
| return (uint16_t)(s_cur_seg + 1U); | |||
| } | |||
| /** @return 当前输出频率 Hz(见 plsr_run_control.h) */ | |||
| uint32_t PlsrRunControlGetCurFreq(void) | |||
| { | |||
| return s_cur_freq; | |||
| @@ -1,23 +1,103 @@ | |||
| /** | |||
| * @file plsr_run_control.h | |||
| * @brief 运行控制:完成方式 / 后续方式(加减速形状见 accel_mode) | |||
| * @brief 运行控制模块:多段脉冲执行状态机 | |||
| * | |||
| * @details 模块职责 | |||
| * 协调路径规划、加减速曲线、脉冲驱动与外部信号,完成「开段 → 发脉冲 → | |||
| * 段后等待/跳转 → 收尾」全流程。负责解析发送模式(完成/后续)、等待类型 | |||
| * (时间/信号/ACT/EXT)及段间频率衔接策略。 | |||
| * | |||
| * 与其它模块关系 | |||
| * - plsr_path_plan :下一段索引、正反向判定(只读决策,不碰硬件) | |||
| * - plsr_accel_curve :段内梯形/三角/S/正弦脉冲域规划与 ISR 逐拍取频 | |||
| * - plsr_param :段表与公共参数只读访问 | |||
| * - plsr_pulse_driver:PWM 启停、方向、ARR/PSC 改频 | |||
| * - plsr_command :故障码 0x01~0x04 上报(PlsrCommandSetFault) | |||
| * - plsr_signal_io :TIM3 1ms、WAIT/EXT 下降沿、ACT/WAIT 超时 | |||
| * | |||
| * 关键数据流 | |||
| * Start → BeginSeg → PlanSeg → DIR_WAIT → RUN(脉冲 ISR 驱动) | |||
| * 段末 → AfterSegDone → EnterPostWaitOrNext / GotoNextOrFinish / FinishAll | |||
| * 运行中 EXT/ACT → CutSegToNext / ActExpire(可能不停表衔接下一段) | |||
| * | |||
| * 状态机概要(RcState_e) | |||
| * RC_IDLE :无段执行,可 Start | |||
| * RC_DIR_WAIT :方向已设,等 dir_delay_ms 后开 PWM | |||
| * RC_RUN :脉冲 ISR 推进 s_done,逐拍改频 | |||
| * RC_WAIT_COND :段后等待(时间到期或 WAIT/EXT 沿) | |||
| */ | |||
| #ifndef PLSR_RUN_CONTROL_H | |||
| #define PLSR_RUN_CONTROL_H | |||
| #include <stdint.h> | |||
| /** | |||
| * @brief 运行控制模块初始化 | |||
| * @note 调用时机:PlsrInit 中,PulseDriver 初始化之后 | |||
| * @note 副作用:全部静态状态复位为 RC_IDLE / 非 busy | |||
| */ | |||
| void PlsrRunControlInit(void); | |||
| /** | |||
| * @brief 从指定段启动多段序列 | |||
| * @param start_seg_1based 起始段号 1-based,越界则回退到参数 start_seg | |||
| * @return 1=已开段;0=忙、无段或首段频率非法 | |||
| * @note 调用时机:PlsrStart 转发;启动前做 0x04 频率门禁 | |||
| * @note 副作用:记录绝对坐标原点、清外部沿、进入 BeginSeg | |||
| */ | |||
| uint8_t PlsrRunControlStart(uint16_t start_seg_1based); | |||
| /** | |||
| * @brief 急停 | |||
| * @note 副作用:关 PWM、清方向/链式频率/ACT/WAIT 全部标志 | |||
| */ | |||
| void PlsrRunControlStop(void); | |||
| /** | |||
| * @brief 运行中重规划剩余脉冲的目标频率 | |||
| * @param new_tgt_hz 新目标 Hz,[1, 100000] | |||
| * @return 1=成功;0=非 RUN、非法频率或本段已完成 | |||
| * @note 副作用:remain=s_target-s_done 作为新总长,f_from=s_cur_freq 重 PlanSeg | |||
| */ | |||
| uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz); | |||
| void PlsrRunControlOn1ms(void); /* TIM3 1ms:仅 ACT/WAIT 时基 */ | |||
| void PlsrRunControlOnPulseIsr(void); /* 脉冲 UPDATE:计数 + 逐拍改频 */ | |||
| void PlsrRunControlTickMs(void); /* 任务:ACT/WAIT/EXT + ApplyPending */ | |||
| /** | |||
| * @brief TIM3 1ms 回调(由 signal_io 转发) | |||
| * @note 调用时机:TIM3 UPDATE 中断,仅处理 ACT 到期置位、WAIT 时间到期置位 | |||
| * @note 副作用:置 s_act_cut_pending / s_wait_expire_req,实际切段在 TickMs | |||
| */ | |||
| void PlsrRunControlOn1ms(void); | |||
| /** | |||
| * @brief 脉冲 UPDATE 中断回调 | |||
| * @note 调用时机:每发一个脉冲;仅在 RC_RUN 有效 | |||
| * @note 副作用:s_done++、累计坐标、逐拍 Step 改频、段末 AfterSegDone | |||
| */ | |||
| void PlsrRunControlOnPulseIsr(void); | |||
| /** | |||
| * @brief 毫秒级任务节拍(PlsrTask 中调用) | |||
| * @note 处理:ACT/WAIT 到期切段、DIR_WAIT 超时开跑、EXT 沿 CutSeg、ApplyPending | |||
| */ | |||
| void PlsrRunControlTickMs(void); | |||
| /** @return 1=运动中(s_busy) */ | |||
| uint8_t PlsrRunControlIsBusy(void); | |||
| /** @return 累计脉冲有符号计数 */ | |||
| int32_t PlsrRunControlGetAccPulse(void); | |||
| /** 清零累计脉冲与绝对原点 */ | |||
| void PlsrRunControlClearAccPulse(void); | |||
| /** | |||
| * @return 当前段号 1-based;空闲时返回 0 | |||
| */ | |||
| uint16_t PlsrRunControlGetCurSeg(void); | |||
| /** | |||
| * @return 当前输出/profile 频率 Hz(监控 0x2002 数据源) | |||
| */ | |||
| uint32_t PlsrRunControlGetCurFreq(void); | |||
| #endif | |||
| @@ -1,10 +1,19 @@ | |||
| /** | |||
| * @file plsr_signal_io.c | |||
| * @brief WAIT/EXT:EXTI 下降沿触发(X4=PB5、X5=PG12) | |||
| * @brief WAIT/EXT 输入与 TIM3 1ms 时基实现 | |||
| * | |||
| * 输入上拉,空闲为高;飞线接地产生下降沿。 | |||
| * 触发只认 EXTI,不靠平时扫口。沿到来后等 20ms 再读一次: | |||
| * 仍为低才锁存(消抖,并滤掉 Y0 脉冲耦合出的尖峰)。 | |||
| * @details 模块职责 | |||
| * EXTI 下降沿 → pending → TIM3 每 ms 累加 wait_ms,满 20ms 且口线仍低 → fell。 | |||
| * 触发只认「确认后的 fell」,不靠平时扫口,避免脉冲耦合尖峰误切段。 | |||
| * | |||
| * 硬件 | |||
| * X4=PB5 / EXTI9_5;X5=PG12 / EXTI15_10;上拉,空闲高。 | |||
| * TIM3:PSC=83、ARR=999 → 约 1ms @ 84MHz APB1 定时器时钟惯例。 | |||
| * | |||
| * 静态状态 | |||
| * s_ms — 自由运行毫秒 | |||
| * s_db_x4/x5 — 每口 pending / wait_ms / fell | |||
| * s_htim3 — 时基句柄 | |||
| */ | |||
| #include "plsr_signal_io.h" | |||
| #include "plsr_param.h" | |||
| @@ -21,10 +30,16 @@ | |||
| #define PLSR_SIGNAL_DEBOUNCE_MS 20U | |||
| /** | |||
| * 单路输入消抖状态 | |||
| * pending — EXTI 已见下降沿,等待 20ms 确认 | |||
| * wait_ms — 消抖累计 ms(TIM3 递增) | |||
| * fell — 确认有效,供 Take* 读清 | |||
| */ | |||
| typedef struct { | |||
| volatile uint8_t pending; /* EXTI 下降沿,待 20ms 确认 */ | |||
| volatile uint8_t pending; | |||
| volatile uint8_t wait_ms; | |||
| volatile uint8_t fell; /* 确认后的下降沿,读清 */ | |||
| volatile uint8_t fell; | |||
| GPIO_TypeDef *port; | |||
| uint16_t pin; | |||
| } PlsrEdge_t; | |||
| @@ -35,17 +50,27 @@ static PlsrEdge_t s_db_x5; | |||
| static TIM_HandleTypeDef s_htim3; | |||
| /*============================================================================*/ | |||
| /* 消抖辅助 */ | |||
| /*============================================================================*/ | |||
| /** @return 1=口线当前为低(接地) */ | |||
| static uint8_t PlsrSignalIoPinIsLow(const PlsrEdge_t *db) | |||
| { | |||
| return (HAL_GPIO_ReadPin(db->port, db->pin) == GPIO_PIN_RESET) ? 1U : 0U; | |||
| } | |||
| /** @brief EXTI 入口:置 pending,清消抖计时,等待 TIM3 确认 */ | |||
| static void PlsrSignalIoOnFalling(PlsrEdge_t *db) | |||
| { | |||
| db->pending = 1U; | |||
| db->wait_ms = 0U; | |||
| } | |||
| /** | |||
| * @brief TIM3 每 ms 调用:pending 满 20ms 后读电平 | |||
| * @note 仍为低才置 fell;已回高则丢弃(滤耦合尖峰) | |||
| */ | |||
| static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *db) | |||
| { | |||
| if (db->pending == 0U) | |||
| @@ -70,6 +95,11 @@ static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *db) | |||
| } | |||
| } | |||
| /*============================================================================*/ | |||
| /* 硬件初始化 */ | |||
| /*============================================================================*/ | |||
| /** @brief X4/X5 下降沿 EXTI + NVIC(优先级 8) */ | |||
| static void PlsrSignalIoGpioExtiInit(void) | |||
| { | |||
| GPIO_InitTypeDef gpio = {0}; | |||
| @@ -102,6 +132,7 @@ static void PlsrSignalIoGpioExtiInit(void) | |||
| HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); | |||
| } | |||
| /** @brief TIM3 约 1ms 基时 + UPDATE 中断(优先级 9) */ | |||
| static void PlsrSignalIoTim3Init(void) | |||
| { | |||
| __HAL_RCC_TIM3_CLK_ENABLE(); | |||
| @@ -119,6 +150,7 @@ static void PlsrSignalIoTim3Init(void) | |||
| (void)HAL_TIM_Base_Start_IT(&s_htim3); | |||
| } | |||
| /** @brief 模块初始化(见 plsr_signal_io.h) */ | |||
| void PlsrSignalIoInit(void) | |||
| { | |||
| s_ms = 0U; | |||
| @@ -147,6 +179,10 @@ void PlsrSignalIoInit(void) | |||
| s_db_x5.pending = 0U; | |||
| } | |||
| /*============================================================================*/ | |||
| /* 时基 / 沿 API */ | |||
| /*============================================================================*/ | |||
| uint32_t PlsrSignalIoGetMs(void) | |||
| { | |||
| return s_ms; | |||
| @@ -180,6 +216,7 @@ void PlsrSignalIoClearPending(void) | |||
| s_db_x5.pending = 0U; | |||
| } | |||
| /** @brief sel 0=X4,非 0=X5 */ | |||
| static PlsrEdge_t *PlsrSignalIoDbBySel(uint16_t sel_01) | |||
| { | |||
| return (sel_01 != 0U) ? &s_db_x5 : &s_db_x4; | |||
| @@ -217,6 +254,11 @@ uint8_t PlsrSignalIoTakeExtFalling(void) | |||
| return 0U; | |||
| } | |||
| /*============================================================================*/ | |||
| /* 中断入口(由 stm32f4xx_it 转发) */ | |||
| /*============================================================================*/ | |||
| /** @brief TIM3:清 UIF → s_ms++ → 消抖 → RunControlOn1ms */ | |||
| void PlsrSignalIoOnTim3Irq(void) | |||
| { | |||
| if ((TIM3->SR & TIM_SR_UIF) != 0U) | |||
| @@ -1,48 +1,93 @@ | |||
| /** | |||
| * @file plsr_signal_io.h | |||
| * @brief 等待/EXT 输入(X4=PB5、X5=PG12) | |||
| * @brief WAIT/EXT 外部输入与 TIM3 1ms 时基 | |||
| * | |||
| * 公共参数:wait_x_sel / ext_x_sel:0=X4,1=X5。 | |||
| * 上拉空闲为高;飞线接地产生下降沿,由 EXTI 触发(不轮询)。 | |||
| * @details 模块职责 | |||
| * 1. 配置 X4=PB5、X5=PG12 为下降沿 EXTI(上拉空闲高,飞线接地出沿); | |||
| * 2. 20ms 消抖确认后锁存 fell,供 run_control 消费; | |||
| * 3. TIM3 自由运行毫秒计数,驱动 ACT/WAIT 到期与消抖确认,并转发 On1ms。 | |||
| * | |||
| * 与其它模块关系 | |||
| * - plsr_param :wait_x_sel / ext_x_sel 选择 X4 或 X5 | |||
| * - plsr_run_control :TakeWait/ExtFalling、GetMs/Deadline/MsReached、On1ms | |||
| * - stm32f4xx_it :TIM3 / EXTI9_5 / EXTI15_10 转发到本模块 On* 入口 | |||
| * | |||
| * 关键约定 | |||
| * ClearEdges — 清 pending+fell(开跑 EXT、Start) | |||
| * ClearPending— 只清消抖中假沿,保留已确认 fell(纯 EXT 发完后过滤耦合) | |||
| * Take* — 读清 fell,一次按压只切一次段 | |||
| */ | |||
| #ifndef PLSR_SIGNAL_IO_H | |||
| #define PLSR_SIGNAL_IO_H | |||
| #include <stdint.h> | |||
| /** | |||
| * @brief 初始化 GPIO EXTI + TIM3 1ms | |||
| * @note 调用时机:PlsrInit;配置后清假沿 | |||
| */ | |||
| void PlsrSignalIoInit(void); | |||
| /** TIM3 毫秒计数(自由运行,约 1ms/格) */ | |||
| /** | |||
| * @brief 读取 TIM3 毫秒计数(自由运行,约 1ms/格) | |||
| * @return 自 Init 起累加的 ms;可能无符号环绕 | |||
| */ | |||
| uint32_t PlsrSignalIoGetMs(void); | |||
| /** deadline = GetMs()+ms;ms=0 则立即到期 */ | |||
| /** | |||
| * @brief 从当前 ms 起算 deadline | |||
| * @param ms 延时;0 表示立即到期(deadline=当前) | |||
| */ | |||
| uint32_t PlsrSignalIoDeadlineFromNow(uint32_t ms); | |||
| /** 1=已到或过期(含无符号环绕) */ | |||
| /** | |||
| * @brief 判断是否已到或过期 | |||
| * @return 1=已到(含无符号环绕比较);0=未到 | |||
| */ | |||
| uint8_t PlsrSignalIoMsReached(uint32_t deadline_ms); | |||
| /** 清 WAIT/EXT 下降沿锁存(包含已确认的 fell) */ | |||
| /** | |||
| * @brief 清 WAIT/EXT 下降沿锁存(含已确认 fell 与 pending) | |||
| * @note Start / EXT 开跑时调用,避免旧沿立刻切段 | |||
| */ | |||
| void PlsrSignalIoClearEdges(void); | |||
| /** 只清消抖 pending,保留已确认的 fell(发完后过滤脉冲耦合假沿用) */ | |||
| /** | |||
| * @brief 只清消抖 pending,保留已确认 fell | |||
| * @note 纯 EXT 脉冲发完后过滤 Y 口耦合假沿,同时保留真沿供 TakeExt | |||
| */ | |||
| void PlsrSignalIoClearPending(void); | |||
| /** WAIT 口当前是否为低(已接地)。仅段末判断※2 时读一次,不作周期轮询触发 */ | |||
| /** | |||
| * @brief WAIT 口当前是否为低(已接地) | |||
| * @note 仅段末判断等场景读一次;不作周期轮询触发(触发只认 EXTI+消抖) | |||
| */ | |||
| uint8_t PlsrSignalIoWaitIsOn(void); | |||
| /** | |||
| * WAIT:消耗一次已确认的 EXTI 下降沿。 | |||
| * ※2 段中飞线接地并保持 → 发完立刻下一段;※3 未触发则一直等。 | |||
| * @brief 消耗一次已确认的 WAIT 口下降沿 | |||
| * @return 1=有沿并已清;0=无 | |||
| * @note ※2 段中接地保持 → 发完立刻下一段;※3 未触发则 RC_WAIT_COND 一直等 | |||
| */ | |||
| uint8_t PlsrSignalIoTakeWaitFalling(void); | |||
| /** | |||
| * EXT:消耗一次 EXTI 下降沿锁存。一次按压只切一次段。 | |||
| * @brief 消耗一次已确认的 EXT 口下降沿 | |||
| * @return 1=有沿并已清;0=无 | |||
| * @note 运行中 EXT / EXT_OR_DONE 切段;一次按压只切一次 | |||
| */ | |||
| uint8_t PlsrSignalIoTakeExtFalling(void); | |||
| /** | |||
| * @brief TIM3 UPDATE 中断入口 | |||
| * @note s_ms++、消抖确认、转发 PlsrRunControlOn1ms | |||
| */ | |||
| void PlsrSignalIoOnTim3Irq(void); | |||
| /** @brief EXTI9_5:处理 X4=PB5 下降沿 → 置 pending */ | |||
| void PlsrSignalIoOnExti9_5(void); | |||
| /** @brief EXTI15_10:处理 X5=PG12 下降沿 → 置 pending */ | |||
| void PlsrSignalIoOnExti15_10(void); | |||
| #endif | |||