Compare commits

...

29 Commits
master ... dev2

Author SHA1 Message Date
  hanyongwei dd37a5345c 清理旧查表残留 2 weeks ago
  hanyongwei 62b49681a3 修改为定点数Q24,采用查形状表+线性插值 2 weeks ago
  hanyongwei 92742fffc1 增加了注释 2 weeks ago
  hanyongwei 5bd7cb9c53 删掉二分降峰后重复判断逻辑 2 weeks ago
  hanyongwei 1d799e89d5 补充了部分注释 2 weeks ago
  hanyongwei 27140c2ddb 修改部分过长的变量命名 2 weeks ago
  hanyongwei b86e343c92 清理冗余设计3 2 weeks ago
  hanyongwei ad2a6c7d76 清理部分冗余设计2 2 weeks ago
  hanyongwei 5deb2ab693 清理部分冗余设计 2 weeks ago
  hanyongwei 183bd22770 修复起始脉冲重复输出 3 weeks ago
  hanyongwei 2073601b84 简化无用的代码 3 weeks ago
  hanyongwei 1941ee6b05 修复ACT时间切段异常 3 weeks ago
  hanyongwei e825e45a36 修复脉冲个数异常无法触发异常码05的问题 3 weeks ago
  hanyongwei 8ebd1b7382 修复输出起始频率不对 3 weeks ago
  hanyongwei 87ade7a5fd 修正起始频率 3 weeks ago
  hanyongwei c93c9b4e77 修复S曲线规划和正弦曲线规划 3 weeks ago
  hanyongwei e00bd1bcaa 修复起始频率大于目标频率时从起始频率减速 3 weeks ago
  hanyongwei 4cc13d4a75 增加EXT或者脉冲发送完成中间判断,跳过无效段并换向要减速到0 3 weeks ago
  hanyongwei 96fe4e6461 增加故障码判断 3 weeks ago
  hanyongwei f2e8d00daf 修复bkp存储失败问题 3 weeks ago
  hanyongwei 2f3ae275d9 最后一个脉冲下降沿强制拉低电平,避免出现极窄的脉冲 3 weeks ago
  hanyongwei 854f0e9cb9 修复bsk存储,改为稀疏映射,使用数组存储数据 3 weeks ago
  hanyongwei fabb0f833f 改用bkp存储,存储公共参数、段参数、记录信息 3 weeks ago
  hanyongwei 4b93dd0f63 删掉modbus的01,0F功能码 4 weeks ago
  hanyongwei cda1eac2af 增加故障诊断 1 month ago
  hanyongwei dacd97c62a 增加动态修改频率功能 1 month ago
  hanyongwei 584280033e 移除对os节拍的依赖,纯脉冲计数改频 1 month ago
  hanyongwei 5386a08534 用运动学公式计算下一拍频率 1 month ago
  hanyongwei 6ff53fdf14 改为按脉冲累加发送,不再按照1ms改频 1 month ago
31 changed files with 6632 additions and 5340 deletions
Unified View
  1. +1
    -4
      .gitignore
  2. +63
    -29
      app/main.c
  3. +3
    -3
      drivers/Source/stm32f4xx_it.c
  4. +1366
    -1568
      iar/plsr.dep
  5. +0
    -31
      iar/plsr.ewp
  6. +70
    -19
      iar/plsr.ewt
  7. +0
    -30
      modbus/modbus_common.h
  8. +0
    -194
      modbus/modbus_data.c
  9. +0
    -59
      modbus/modbus_data.h
  10. +0
    -424
      modbus/modbus_fc.c
  11. +0
    -25
      modbus/modbus_fc.h
  12. +0
    -188
      modbus/modbus_port.c
  13. +0
    -17
      modbus/modbus_port.h
  14. +617
    -7
      modbus/modbus_rtu.c
  15. +18
    -69
      modbus/modbus_rtu.h
  16. +1258
    -724
      plsr/accel_curve/plsr_accel_curve.c
  17. +74
    -107
      plsr/accel_curve/plsr_accel_curve.h
  18. +170
    -18
      plsr/command/plsr_command.c
  19. +89
    -15
      plsr/command/plsr_command.h
  20. +219
    -211
      plsr/param/plsr_param.c
  21. +143
    -73
      plsr/param/plsr_param.h
  22. +0
    -69
      plsr/path_plan/plsr_path_plan.c
  23. +0
    -37
      plsr/path_plan/plsr_path_plan.h
  24. +55
    -90
      plsr/plsr.c
  25. +54
    -18
      plsr/plsr.h
  26. +631
    -220
      plsr/pulse_driver/plsr_pulse_driver.c
  27. +109
    -13
      plsr/pulse_driver/plsr_pulse_driver.h
  28. +1371
    -941
      plsr/run_control/plsr_run_control.c
  29. +22
    -11
      plsr/run_control/plsr_run_control.h
  30. +237
    -106
      plsr/signal_io/plsr_signal_io.c
  31. +62
    -20
      plsr/signal_io/plsr_signal_io.h

+ 1
- 4
.gitignore View File

@@ -15,11 +15,8 @@ iar/modbus/Exe/
iar/modbus/List/ iar/modbus/List/
iar/modbus/Obj/ iar/modbus/Obj/
iar/Backup of modbus.ewp iar/Backup of modbus.ewp
host/
iar/settings/modbus.reggroups iar/settings/modbus.reggroups
build-plsr_host-Desktop_Qt_5_6_3_MinGW_32bit-Debug/ build-plsr_host-Desktop_Qt_5_6_3_MinGW_32bit-Debug/
plsr/test/ plsr/test/
tools/ tools/
.cursor/
*.pdf
3. 定位控制手册.pdf
.cursor/

+ 63
- 29
app/main.c View File

@@ -1,11 +1,26 @@
/** /**
****************************************************************************** ******************************************************************************
* @file main.c * @file main.c
* @brief 初始化系统时钟配置、初始化GPIO、初始化串口、启动任务
* @brief 应用入口:时钟/GPIO/串口/定时器初始化,创建 uCOS-II 任务并启动调度
* *
* @note Modbus / PLSR / 参数块管理 / 指令接口
* @note PLSR: Y0(PF6)=TIM10 / Y1(PF8)=TIM13 / Y2(PF7)=TIM11 脉冲,Y3(PF9)=方向
* @details 本文件是下位机固件的启动枢纽,不实现业务算法,只完成:
* 1) HAL / 系统时钟 / SysTick 优先级
* 2) 外设:GPIO、USART1(Modbus RTU)、TIM2(PLSR 相关)
* 3) PlsrInit:脉冲驱动、运行控制、指令监控等模块初始化
* 4) OSInit 后创建三个业务任务,再 OSStart 进入多任务
* *
* 任务分工(优先级数字越小越高优先级,与 uCOS-II 一致)
* PlsrTask prio=3 — 运行控制节拍、CommandPoll、监控刷新
* ModbusSlaveTask prio=4 — RS485 从站收发、保持寄存器访问
* PlsrParamBlockTask prio=5 — 分帧参数收齐后 Apply + 持久化落盘
*
* 引脚约定(PLSR 脉冲口)
* Y0(PF6)=TIM10 / Y1(PF8)=TIM13 / Y2(PF7)=TIM11 脉冲输出
* Y3(PF9)=方向电平
*
* 注意
* PlsrParamBlockInit 内部创建信号量,必须在 OSInit() 之后调用。
* OSStart() 之后理论上不会返回;末尾 while(1) 仅作防护。
****************************************************************************** ******************************************************************************
*/ */


@@ -18,14 +33,15 @@
#include "plsr.h" #include "plsr.h"
#include "plsr_param.h" #include "plsr_param.h"


/* ---------- 任务栈与优先级(数字越小优先级越高) ---------- */
#define MODBUS_TASK_STACK_SIZE 512 #define MODBUS_TASK_STACK_SIZE 512
#define MODBUS_TASK_PRIO 3
#define MODBUS_TASK_PRIO 4 /* Modbus 从站:低于 PLSR,避免抢占脉冲路径过久 */


#define PLSR_TASK_STACK_SIZE 512 #define PLSR_TASK_STACK_SIZE 512
#define PLSR_TASK_PRIO 4
#define PLSR_TASK_PRIO 3 /* 运行控制最高业务优先级 */


#define PARAM_BLOCK_STACK_SIZE 256 #define PARAM_BLOCK_STACK_SIZE 256
#define PARAM_BLOCK_TASK_PRIO 5
#define PARAM_BLOCK_TASK_PRIO 5 /* 参数 Apply/落盘可稍慢,不挡运动 */


OS_STK ModbusTaskStk[MODBUS_TASK_STACK_SIZE]; OS_STK ModbusTaskStk[MODBUS_TASK_STACK_SIZE];
OS_STK PlsrTaskStk[PLSR_TASK_STACK_SIZE]; OS_STK PlsrTaskStk[PLSR_TASK_STACK_SIZE];
@@ -33,34 +49,44 @@ OS_STK ParamBlockTaskStk[PARAM_BLOCK_STACK_SIZE];


void SystemClock_Config(void); void SystemClock_Config(void);


/**
* @brief 复位后入口:硬件就绪 → 模块 Init → 建任务 → 启动 OS
* @note 不返回;调度器接管 CPU
*/
int main(void) int main(void)
{ {
HAL_Init(); HAL_Init();
/* SysTick 优先级须低于 PendSV,供 uCOS 节拍与上下文切换 */
HAL_NVIC_SetPriority(SysTick_IRQn, 14, 0); HAL_NVIC_SetPriority(SysTick_IRQn, 14, 0);
HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0); HAL_NVIC_SetPriority(PendSV_IRQn, 15, 0);
SystemClock_Config(); SystemClock_Config();


/* 配置 Cortex-M SysTick 为 OS 节拍源(与 OS_TICKS_PER_SEC 匹配) */
OS_CPU_SysTickInit(168000000 / OS_TICKS_PER_SEC); OS_CPU_SysTickInit(168000000 / OS_TICKS_PER_SEC);


MX_GPIO_Init(); MX_GPIO_Init();
MX_USART1_UART_Init();
MX_USART1_UART_Init(); /* Modbus RTU 物理层 */


MX_TIM2_Init(); MX_TIM2_Init();
PlsrInit();
PlsrInit(); /* 脉冲驱动 / 运行控制 / 指令接口等 */


OSInit(); OSInit();
PlsrParamBlockInit(); /* 依赖 OS,须在 OSInit 之后 */

OSTaskCreateExt(ModbusSlaveTask,
NULL,
&ModbusTaskStk[MODBUS_TASK_STACK_SIZE - 1],
MODBUS_TASK_PRIO,
MODBUS_TASK_PRIO,
&ModbusTaskStk[0],
MODBUS_TASK_STACK_SIZE,
NULL,
/* 参数块信号量依赖 OS,必须放在 OSInit 之后 */
PlsrParamBlockInit();

/* Modbus 从站任务:解析 FC03/06/10,维护保持寄存器映像 */
OSTaskCreateExt(ModbusSlaveTask,//任务函数入口,任务实体函数
NULL,//传递给任务函数的入口参数
&ModbusTaskStk[MODBUS_TASK_STACK_SIZE - 1],//栈向下增长,栈从高地址往低地址生长
MODBUS_TASK_PRIO,//任务优先级
MODBUS_TASK_PRIO,//任务id,一般直接等于优先级;可以不等于优先级,但不能冲突
&ModbusTaskStk[0],//栈数组的起始地址,数组下标 0 的地址
MODBUS_TASK_STACK_SIZE,//栈的元素数量,不是字节数
NULL,//TCB 扩展指针
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP);
//开启栈用量统计;任务创建的时候,把整个栈内存全部清零;保存 FPU 浮点寄存器


/* PLSR 主任务:TickMs / CommandPoll / 监控发布 */
OSTaskCreateExt(PlsrTask, OSTaskCreateExt(PlsrTask,
NULL, NULL,
&PlsrTaskStk[PLSR_TASK_STACK_SIZE - 1], &PlsrTaskStk[PLSR_TASK_STACK_SIZE - 1],
@@ -70,7 +96,11 @@ int main(void)
PLSR_TASK_STACK_SIZE, PLSR_TASK_STACK_SIZE,
NULL, NULL,
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP);

//开启栈用量统计;任务创建的时候,把整个栈内存全部清零;保存 FPU 浮点寄存器
/*
* 参数块任务:Pend → ApplyFromHold(校验)→ 通过则 SaveParamsFromHold。
* 分帧写完后由 OnHoldWrite Post;Save 落 BKP,Export 仅供读回钳位结果。
*/
OSTaskCreateExt(PlsrParamBlockTask, OSTaskCreateExt(PlsrParamBlockTask,
NULL, NULL,
&ParamBlockTaskStk[PARAM_BLOCK_STACK_SIZE - 1], &ParamBlockTaskStk[PARAM_BLOCK_STACK_SIZE - 1],
@@ -81,13 +111,18 @@ int main(void)
NULL, NULL,
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP); OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP);


OSStart();
OSStart(); /* 启动多任务;正常情况下不再返回 */


while (1) while (1)
{ {
} }
} }


/**
* @brief 系统时钟:HSE(12MHz) → PLL → 168MHz SYSCLK
* @note 须与 OS_CPU_SysTickInit、pulse_driver 使用的主频常量一致
* @note PLLM 须等于晶振 MHz:12MHz 用 12;误用 8 会使 SYSCLK=252MHz,脉冲频率偏 1.5 倍
*/
void SystemClock_Config(void) void SystemClock_Config(void)
{ {
RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_OscInitTypeDef RCC_OscInitStruct = {0};
@@ -100,28 +135,31 @@ void SystemClock_Config(void)
RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 12;
RCC_OscInitStruct.PLL.PLLM = 12; /* HSE=12MHz → VCO in = 1MHz */
RCC_OscInitStruct.PLL.PLLN = 336; RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 4;
RCC_OscInitStruct.PLL.PLLQ = 4;/*不准改成7*/
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{ {
Error_Handler(); Error_Handler();
} }


RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
{ {
Error_Handler(); Error_Handler();
} }
} }


/**
* @brief 致命错误停机(时钟配置失败等)
* @note 生产可用 LED 闪烁;此处死循环便于调试挂起
*/
void Error_Handler(void) void Error_Handler(void)
{ {
__disable_irq(); __disable_irq();
@@ -129,7 +167,3 @@ void Error_Handler(void)
{ {
} }
} }

#ifdef USE_FULL_ASSERT

#endif

+ 3
- 3
drivers/Source/stm32f4xx_it.c View File

@@ -225,12 +225,12 @@ void TIM2_IRQHandler(void)
} }


/** /**
* @brief TIM3:PLSR WAIT/ACT 1ms 时基
* @brief TIM5:PLSR 单次延时到期(换向 / WAIT / ACT)
*/ */
void TIM3_IRQHandler(void)
void TIM5_IRQHandler(void)
{ {
OSIntEnter(); OSIntEnter();
PlsrSignalIoOnTim3Irq();
PlsrSignalIoOnTim5Irq();
OSIntExit(); OSIntExit();
} }




+ 1366
- 1568
iar/plsr.dep
File diff suppressed because it is too large
View File


+ 0
- 31
iar/plsr.ewp View File

@@ -359,7 +359,6 @@
<state>$PROJ_DIR$\..\plsr\param</state> <state>$PROJ_DIR$\..\plsr\param</state>
<state>$PROJ_DIR$\..\plsr\persist</state> <state>$PROJ_DIR$\..\plsr\persist</state>
<state>$PROJ_DIR$\..\plsr\pulse_driver</state> <state>$PROJ_DIR$\..\plsr\pulse_driver</state>
<state>$PROJ_DIR$\..\plsr\path_plan</state>
<state>$PROJ_DIR$\..\plsr\accel_curve</state> <state>$PROJ_DIR$\..\plsr\accel_curve</state>
<state>$PROJ_DIR$\..\plsr\run_control</state> <state>$PROJ_DIR$\..\plsr\run_control</state>
<state>$PROJ_DIR$\..\plsr\command</state> <state>$PROJ_DIR$\..\plsr\command</state>
@@ -2841,27 +2840,6 @@
</group> </group>
<group> <group>
<name>modbus</name> <name>modbus</name>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_common.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_data.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_data.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_fc.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_fc.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_port.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_port.h</name>
</file>
<file> <file>
<name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name> <name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name>
</file> </file>
@@ -2904,15 +2882,6 @@
<name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name> <name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name>
</file> </file>
</group> </group>
<group>
<name>path_plan</name>
<file>
<name>$PROJ_DIR$\..\plsr\path_plan\plsr_path_plan.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\path_plan\plsr_path_plan.h</name>
</file>
</group>
<group> <group>
<name>accel_curve</name> <name>accel_curve</name>
<file> <file>


+ 70
- 19
iar/plsr.ewt View File

@@ -3074,31 +3074,82 @@
<group> <group>
<name>modbus</name> <name>modbus</name>
<file> <file>
<name>$PROJ_DIR$\..\modbus\modbus_common.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_data.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_data.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_fc.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_fc.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_port.c</name>
<name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name>
</file> </file>
<file> <file>
<name>$PROJ_DIR$\..\modbus\modbus_port.h</name>
<name>$PROJ_DIR$\..\modbus\modbus_rtu.h</name>
</file> </file>
</group>
<group>
<name>plsr</name>
<group>
<name>accel_curve</name>
<file>
<name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.h</name>
</file>
</group>
<group>
<name>command</name>
<file>
<name>$PROJ_DIR$\..\plsr\command\plsr_command.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\command\plsr_command.h</name>
</file>
</group>
<group>
<name>param</name>
<file>
<name>$PROJ_DIR$\..\plsr\param\plsr_param.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\param\plsr_param.h</name>
</file>
</group>
<group>
<name>persist</name>
<file>
<name>$PROJ_DIR$\..\plsr\persist\plsr_persist.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\persist\plsr_persist.h</name>
</file>
</group>
<group>
<name>pulse_driver</name>
<file>
<name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name>
</file>
</group>
<group>
<name>run_control</name>
<file>
<name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.h</name>
</file>
</group>
<group>
<name>signal_io</name>
<file>
<name>$PROJ_DIR$\..\plsr\signal_io\plsr_signal_io.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\signal_io\plsr_signal_io.h</name>
</file>
</group>
<file> <file>
<name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name>
<name>$PROJ_DIR$\..\plsr\plsr.c</name>
</file> </file>
<file> <file>
<name>$PROJ_DIR$\..\modbus\modbus_rtu.h</name>
<name>$PROJ_DIR$\..\plsr\plsr.h</name>
</file> </file>
</group> </group>
<group> <group>


+ 0
- 30
modbus/modbus_common.h View File

@@ -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

+ 0
- 194
modbus/modbus_data.c View File

@@ -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;
}

+ 0
- 59
modbus/modbus_data.h View File

@@ -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

+ 0
- 424
modbus/modbus_fc.c View File

@@ -1,424 +0,0 @@
/**
* @file modbus_fc.c
* @brief Modbus 功能码 01/03/06/0F/10 与异常应答实现
*/

#include "modbus_fc.h"
#include "modbus_common.h"
#include "modbus_data.h"
#include "modbus_port.h"
#include "plsr_param.h"
#include <string.h>

/*应答帧组帧缓冲 */
static uint8_t g_modbus_tx_buf[MODBUS_TX_BUF_LEN];

/* 0 = 广播禁止回帧;1 = 正常回帧 */
static uint8_t g_modbus_reply_enable = 1U;

/* 当前请求从站地址(异常帧使用) */
static uint8_t g_modbus_current_addr = MODBUS_SLAVE_ADDR;

static void AppendCrcAndSend(uint8_t *pdu, uint16_t pduLen);
static void SendException(uint8_t funcCode, uint8_t exCode);
static void ReadCoils(const uint8_t *frame, uint16_t len);
static void ReadHoldingRegs(const uint8_t *frame, uint16_t len);
static void WriteSingleReg(const uint8_t *frame, uint16_t len);
static void WriteMultiCoils(const uint8_t *frame, uint16_t len);
static void WriteMultiRegs(const uint8_t *frame, uint16_t len);

/**
* @brief 拼装响应帧
* @param[in] slaveAddr 请求中的从站地址
* @param[in] replyEnable 0 = 广播不回帧;1 = 正常回帧
*/
void ReplyContext(uint8_t slaveAddr, uint8_t replyEnable)
{
g_modbus_current_addr = slaveAddr;
g_modbus_reply_enable = replyEnable;
}

/**
* @brief 按功能码分发到对应处理函数
* @param[in] frame 完整 ADU(含 CRC)
* @param[in] len ADU 长度
*/
void ProcessRequest(const uint8_t *frame, uint16_t len)
{
uint8_t funcCode;

if ((frame == NULL) || (len < 4U))
{
return;
}

funcCode = frame[1];

switch (funcCode)
{
case READ_COILS:
ReadCoils(frame, len);
break;

case READ_HOLDING_REGS:
ReadHoldingRegs(frame, len);
break;

case WRITE_SINGLE_REG:
WriteSingleReg(frame, len);
break;

case WRITE_MULTI_COILS:
WriteMultiCoils(frame, len);
break;

case WRITE_MULTI_REGS:
WriteMultiRegs(frame, len);
break;

default:
SendException(funcCode, ILLEGAL_FUNCTION);
break;
}
}

/*============================================================================*/
/* 功能码 01:读线圈 */
/*============================================================================*/

/**
* @brief 处理读线圈请求(0x01)
* @param[in] frame 完整请求 ADU
* @param[in] len 合法请求固定为 8
*/
static void ReadCoils(const uint8_t *frame, uint16_t len)
{
uint16_t startAddr;
uint16_t quantity;
uint16_t byteCount;
uint16_t i;
uint8_t bitMask;

if (len != 8U)
{
SendException(READ_COILS, ILLEGAL_DATA_VALUE);
return;
}

startAddr = ReadU16Be(&frame[2]);
quantity = ReadU16Be(&frame[4]);

if ((quantity < 1U) || (quantity > MAX_READ_COILS))
{
SendException(READ_COILS, ILLEGAL_DATA_VALUE);
return;
}

if (ModbusIsCoilRangeValid(startAddr, quantity) == 0U)
{
SendException(READ_COILS, ILLEGAL_DATA_ADDRESS);
return;
}

byteCount = (uint16_t)((quantity + 7U) / 8U);

g_modbus_tx_buf[0] = frame[0];
g_modbus_tx_buf[1] = READ_COILS;
g_modbus_tx_buf[2] = (uint8_t)byteCount;
memset(&g_modbus_tx_buf[3], 0, byteCount);

for (i = 0U; i < quantity; i++)
{
if (ReadCoil((uint16_t)(startAddr + i)) != 0U)
{
bitMask = (uint8_t)(1U << (i % 8U));
g_modbus_tx_buf[3U + (i / 8U)] |= bitMask;
}
}

AppendCrcAndSend(g_modbus_tx_buf, (uint16_t)(3U + byteCount));
}

/*============================================================================*/
/* 功能码 03:读保持寄存器 */
/*============================================================================*/

/**
* @brief 处理读保持寄存器请求(0x03)
* @param[in] frame 完整请求 ADU
* @param[in] len 合法请求固定为 8
*/
static void ReadHoldingRegs(const uint8_t *frame, uint16_t len)
{
uint16_t startAddr;
uint16_t quantity;
uint16_t byteCount;
uint16_t i;
uint16_t regVal;

if (len != 8U)
{
SendException(READ_HOLDING_REGS, ILLEGAL_DATA_VALUE);
return;
}

startAddr = ReadU16Be(&frame[2]);
quantity = ReadU16Be(&frame[4]);

if ((quantity < 1U) || (quantity > MAX_READ_REGS))
{
SendException(READ_HOLDING_REGS, ILLEGAL_DATA_VALUE);
return;
}

if (ModbusIsRegRangeValid(startAddr, quantity) == 0U)
{
SendException(READ_HOLDING_REGS, ILLEGAL_DATA_ADDRESS);
return;
}

byteCount = (uint16_t)(quantity * 2U);

g_modbus_tx_buf[0] = frame[0];
g_modbus_tx_buf[1] = READ_HOLDING_REGS;
g_modbus_tx_buf[2] = (uint8_t)byteCount;

for (i = 0U; i < quantity; i++)
{
regVal = ReadHoldReg((uint16_t)(startAddr + i));
g_modbus_tx_buf[3U + (i * 2U)] = (uint8_t)(regVal >> 8);
g_modbus_tx_buf[4U + (i * 2U)] = (uint8_t)(regVal & 0xFFU);
}

AppendCrcAndSend(g_modbus_tx_buf, (uint16_t)(3U + byteCount));
}

/*============================================================================*/
/* 功能码 06:写单个保持寄存器 */
/*============================================================================*/

/**
* @brief 处理写单个保持寄存器请求(0x06)
* @param[in] frame 完整请求 ADU
* @param[in] len 合法请求固定为 8
*
* 读写都经 WriteHoldReg / ReadHoldReg,落在 g_hold_reg[]。
* 应答回显地址与写入值。
*/
static void WriteSingleReg(const uint8_t *frame, uint16_t len)
{
uint16_t addr;
uint16_t value;

if (len != 8U)
{
SendException(WRITE_SINGLE_REG, ILLEGAL_DATA_VALUE);
return;
}

addr = ReadU16Be(&frame[2]);
value = ReadU16Be(&frame[4]);

if (ModbusIsRegRangeValid(addr, 1U) == 0U)
{
SendException(WRITE_SINGLE_REG, ILLEGAL_DATA_ADDRESS);
return;
}

WriteHoldReg(addr, value);

g_modbus_tx_buf[0] = frame[0];
g_modbus_tx_buf[1] = WRITE_SINGLE_REG;
g_modbus_tx_buf[2] = frame[2];
g_modbus_tx_buf[3] = frame[3];
g_modbus_tx_buf[4] = frame[4];
g_modbus_tx_buf[5] = frame[5];
AppendCrcAndSend(g_modbus_tx_buf, 6U);

PlsrParamBlockOnHoldWrite(addr, 1U);
}

/*============================================================================*/
/* 功能码 0F:写多个线圈 */
/*============================================================================*/

/**
* @brief 处理写多个线圈请求(0x0F)
* @param[in] frame 完整请求 ADU
* @param[in] len 等于 7 + ByteCount + 2
* @retval None
*/
static void WriteMultiCoils(const uint8_t *frame, uint16_t len)
{
uint16_t startAddr;
uint16_t quantity;
uint8_t byteCount;
uint16_t expectedLen;
uint16_t i;
uint8_t bitMask;
uint8_t coilVal;

if (len < 9U)
{
SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_VALUE);
return;
}

startAddr = ReadU16Be(&frame[2]);
quantity = ReadU16Be(&frame[4]);
byteCount = frame[6];

if ((quantity < 1U) || (quantity > MAX_WRITE_COILS))
{
SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_VALUE);
return;
}

if (byteCount != (uint8_t)((quantity + 7U) / 8U))
{
SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_VALUE);
return;
}

expectedLen = (uint16_t)(7U + byteCount + 2U);
if (len != expectedLen)
{
SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_VALUE);
return;
}

if (ModbusIsCoilRangeValid(startAddr, quantity) == 0U)
{
SendException(WRITE_MULTI_COILS, ILLEGAL_DATA_ADDRESS);
return;
}

for (i = 0U; i < quantity; i++)
{
bitMask = (uint8_t)(1U << (i % 8U));
coilVal = (frame[7U + (i / 8U)] & bitMask) ? 1U : 0U;
WriteCoil((uint16_t)(startAddr + i), coilVal);
}

g_modbus_tx_buf[0] = frame[0];
g_modbus_tx_buf[1] = WRITE_MULTI_COILS;
g_modbus_tx_buf[2] = frame[2];
g_modbus_tx_buf[3] = frame[3];
g_modbus_tx_buf[4] = frame[4];
g_modbus_tx_buf[5] = frame[5];

AppendCrcAndSend(g_modbus_tx_buf, 6U);
}

/*============================================================================*/
/* 功能码 10:写多个保持寄存器 */
/*============================================================================*/

/**
* @brief 处理写多个保持寄存器请求(0x10)
* @param[in] frame 完整请求 ADU
* @param[in] len 等于 7 + ByteCount + 2
* @retval None
*/
static void WriteMultiRegs(const uint8_t *frame, uint16_t len)
{
uint16_t startAddr;
uint16_t quantity;
uint8_t byteCount;
uint16_t expectedLen;
uint16_t i;

if (len < 9U)
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE);
return;
}

startAddr = ReadU16Be(&frame[2]);
quantity = ReadU16Be(&frame[4]);
byteCount = frame[6];

if ((quantity < 1U) || (quantity > MAX_WRITE_REGS))
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE);
return;
}

if (byteCount != (uint8_t)(quantity * 2U))
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE);
return;
}

expectedLen = (uint16_t)(7U + byteCount + 2U);
if (len != expectedLen)
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE);
return;
}

if (ModbusIsRegRangeValid(startAddr, quantity) == 0U)
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_ADDRESS);
return;
}

for (i = 0U; i < quantity; i++)
{
WriteHoldReg((uint16_t)(startAddr + i),
ReadU16Be(&frame[7U + (i * 2U)]));
}

/* 先回 FC10 应答,再做分帧计数(避免应答被后续逻辑拖住) */
g_modbus_tx_buf[0] = frame[0];
g_modbus_tx_buf[1] = WRITE_MULTI_REGS;
g_modbus_tx_buf[2] = frame[2];
g_modbus_tx_buf[3] = frame[3];
g_modbus_tx_buf[4] = frame[4];
g_modbus_tx_buf[5] = frame[5];
AppendCrcAndSend(g_modbus_tx_buf, 6U);

PlsrParamBlockOnHoldWrite(startAddr, quantity);
}

/*============================================================================*/
/* 异常应答与发送 */
/*============================================================================*/

/**
* @brief 组并发送异常应答
* @param[in] funcCode 原功能码(未置位 0x80)
* @param[in] exCode 异常码 01/02/03
*/
static void SendException(uint8_t funcCode, uint8_t exCode)
{
g_modbus_tx_buf[0] = g_modbus_current_addr;
g_modbus_tx_buf[1] = (uint8_t)(funcCode | 0x80U);
g_modbus_tx_buf[2] = exCode;

AppendCrcAndSend(g_modbus_tx_buf, 3U);
LedErrorBlink();
}

/* 添加CRC */
static void AppendCrcAndSend(uint8_t *pdu, uint16_t pduLen)
{
uint16_t crc;

if ((pdu == NULL) || (pduLen == 0U) ||
((uint16_t)(pduLen + 2U) > MODBUS_TX_BUF_LEN))
{
return;
}

if (g_modbus_reply_enable == 0U)
{
return;
}

crc = ModbusCrc16(pdu, pduLen);
pdu[pduLen] = (uint8_t)(crc & 0xFFU);
pdu[pduLen + 1U] = (uint8_t)(crc >> 8);

SendData(pdu, (uint16_t)(pduLen + 2U));
}

+ 0
- 25
modbus/modbus_fc.h View File

@@ -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

+ 0
- 188
modbus/modbus_port.c View File

@@ -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();
}

+ 0
- 17
modbus/modbus_port.h View File

@@ -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

+ 617
- 7
modbus/modbus_rtu.c View File

@@ -8,13 +8,52 @@
*/ */


#include "modbus_rtu.h" #include "modbus_rtu.h"
#include "modbus_data.h"
#include "modbus_port.h"
#include "modbus_fc.h"
#include <string.h> #include <string.h>
#include "plsr.h"
#include "plsr_pulse_driver.h"
#include "gpio.h"
#include "plsr_param.h"
#include "plsr_command.h"
#include "plsr_persist.h"


static uint8_t g_modbus_proc_buf[MODBUS_RX_BUF_LEN]; static uint8_t g_modbus_proc_buf[MODBUS_RX_BUF_LEN];


/* 保持寄存器数据映像 */
uint16_t g_hold_reg[HOLD_REG_BUFFER_SIZE] = {0};

/*应答帧组帧缓冲 */
static uint8_t g_modbus_tx_buf[MODBUS_TX_BUF_LEN];

/* 0 = 广播禁止回帧;1 = 正常回帧 */
static uint8_t g_modbus_reply_enable = 1U;

/* 当前请求从站地址(异常帧使用) */
static uint8_t g_modbus_current_addr = MODBUS_SLAVE_ADDR;
uint8_t g_modbus_rx_buf[MODBUS_RX_BUF_LEN] = {0};
volatile uint16_t g_modbus_rx_len = 0U;
OS_EVENT *g_modbus_rx_sem = (OS_EVENT *)0;

static uint8_t g_rxByte;
static volatile uint8_t g_modbus_frame_pending = 0U;
static volatile uint8_t g_modbus_tim_armed = 0U;

static void AppendCrcAndSend(uint8_t *pdu, uint16_t pduLen);
static void SendException(uint8_t funcCode, uint8_t exCode);
static void ReadHoldingRegs(const uint8_t *frame, uint16_t len);
static void WriteSingleReg(const uint8_t *frame, uint16_t len);
static void WriteMultiRegs(const uint8_t *frame, uint16_t len);
static void StopFrameTimer(void);
static void FlushUartRx(void);
static void RestartReceive(void);
static uint16_t ModbusCrc16(uint8_t *data, uint16_t len);
static void SendData(uint8_t *buf, uint16_t len);
//static void ModbusDataInit(void);
static uint16_t ModbusMapRegAddr(uint16_t addr);
static uint8_t ModbusIsRegRangeValid(uint16_t addr, uint16_t qty);
static uint16_t ReadU16Be(const uint8_t *data);
static void ReplyContext(uint8_t slaveAddr, uint8_t replyEnable);
static void ProcessRequest(const uint8_t *frame, uint16_t len);

/** /**
* @brief 从站任务:阻塞等待完整帧,校验后分发处理 * @brief 从站任务:阻塞等待完整帧,校验后分发处理
*/ */
@@ -29,7 +68,12 @@ void ModbusSlaveTask(void *pArg)


(void)pArg; (void)pArg;


ModbusDataInit();
/*
* ModbusDataInit 会清零 g_hold_reg;上电后仅从 BKP 恢复参数/监控到 hold,
* 不有点多此一举,直接不初始化g_hold_reg。
*/
// ModbusDataInit();
// PlsrPersistLoad();
ModbusPortInit(); ModbusPortInit();


while (1) while (1)
@@ -48,12 +92,12 @@ void ModbusSlaveTask(void *pArg)
} }
memcpy(g_modbus_proc_buf, g_modbus_rx_buf, frameLen); memcpy(g_modbus_proc_buf, g_modbus_rx_buf, frameLen);
g_modbus_rx_len = 0U; g_modbus_rx_len = 0U;
PortClearFramePending();
/*清除帧挂起*/
g_modbus_frame_pending = 0U;
OS_EXIT_CRITICAL(); OS_EXIT_CRITICAL();


if (frameLen < 4U) if (frameLen < 4U)
{ {
LedErrorBlink();
continue; continue;
} }


@@ -62,7 +106,6 @@ void ModbusSlaveTask(void *pArg)
crcCalc = ModbusCrc16(g_modbus_proc_buf, (uint16_t)(frameLen - 2U)); crcCalc = ModbusCrc16(g_modbus_proc_buf, (uint16_t)(frameLen - 2U));
if (crcRecv != crcCalc) if (crcRecv != crcCalc)
{ {
LedErrorBlink();
continue; continue;
} }


@@ -76,3 +119,570 @@ void ModbusSlaveTask(void *pArg)
ProcessRequest(g_modbus_proc_buf, frameLen); ProcessRequest(g_modbus_proc_buf, frameLen);
} }
} }


/*============================================================================*/
/* 初始化 */
/*============================================================================*/

/**
* @brief 初始化保持寄存器映像
* @retval None
*/
//void ModbusDataInit(void)
//{
// memset(g_hold_reg, 0, sizeof(g_hold_reg));
//}

/*============================================================================*/
/* 地址映射与区间校验 */
/*============================================================================*/

/**
* @brief 将保持寄存器地址映射为本地映像下标
* @param[in] addr 寄存器地址
* @retval 合法时返回下标;非法返回 MODBUS_ADDR_INVALID
*/
uint16_t ModbusMapRegAddr(uint16_t addr)
{
return (addr <= MODBUS_MB_MAXIMUM) ? addr : MODBUS_ADDR_INVALID;
}

/**
* @brief 校验保持寄存器连续区间是否全部合法
* @param[in] addr 起始地址
* @param[in] qty 数量(须 ≥ 1)
* @retval 1 合法;0 非法
*/
uint8_t ModbusIsRegRangeValid(uint16_t addr, uint16_t qty)
{
uint32_t endAddr;

if ((qty == 0U) || (ModbusMapRegAddr(addr) == MODBUS_ADDR_INVALID))
{
return 0U;
}

endAddr = (uint32_t)addr + (uint32_t)qty - 1U;
if (endAddr > (uint32_t)MODBUS_MB_MAXIMUM)
{
return 0U;
}

return 1U;
}

/**
* @brief 从缓冲读取大端序 uint16
* @param[in] data 至少 2字节,高字节在前
* @retval 解析得到的 16位无符号值
*/
uint16_t ReadU16Be(const uint8_t *data)
{
return (uint16_t)(((uint16_t)data[0] << 8) | data[1]);
}

/*============================================================================*/
/* 公共映像访问 */
/*============================================================================*/

/**
* @brief 读保持寄存器映像
* @param[in] addr 寄存器地址
* @retval 寄存器值;地址非法时返回 0
*/
uint16_t ReadHoldReg(uint16_t addr)
{
uint16_t offset;

offset = ModbusMapRegAddr(addr);
if (offset == MODBUS_ADDR_INVALID)
{
return 0U;
}

return g_hold_reg[offset];
}

/**
* @brief 写保持寄存器映像
* @param[in] addr 寄存器地址
* @param[in] val 写入值
* @retval None
*/
void WriteHoldReg(uint16_t addr, uint16_t val)
{
uint16_t offset;

offset = ModbusMapRegAddr(addr);
if (offset == MODBUS_ADDR_INVALID)
{
return;
}

g_hold_reg[offset] = val;
}

/*============================================================================*/
/* 请求处理 */
/*============================================================================*/

/**
* @brief 拼装响应帧
* @param[in] slaveAddr 请求中的从站地址
* @param[in] replyEnable 0 = 广播不回帧;1 = 正常回帧
*/
void ReplyContext(uint8_t slaveAddr, uint8_t replyEnable)
{
g_modbus_current_addr = slaveAddr;
g_modbus_reply_enable = replyEnable;
}

/**
* @brief 按功能码分发到对应处理函数
* @param[in] frame 完整 ADU(含 CRC)
* @param[in] len ADU 长度
*/
void ProcessRequest(const uint8_t *frame, uint16_t len)
{
uint8_t funcCode;

if ((frame == NULL) || (len < 4U))
{
return;
}

funcCode = frame[1];

switch (funcCode)
{
case READ_HOLDING_REGS:
ReadHoldingRegs(frame, len);
break;

case WRITE_SINGLE_REG:
WriteSingleReg(frame, len);
break;

case WRITE_MULTI_REGS:
WriteMultiRegs(frame, len);
break;

default:
SendException(funcCode, ILLEGAL_FUNCTION);
break;
}
}

/*============================================================================*/
/* 功能码 03:读保持寄存器 */
/*============================================================================*/

/**
* @brief 处理读保持寄存器请求(0x03)
* @param[in] frame 完整请求 ADU
* @param[in] len 合法请求固定为 8
*/
static void ReadHoldingRegs(const uint8_t *frame, uint16_t len)
{
uint16_t startAddr;
uint16_t quantity;
uint16_t byteCount;
uint16_t i;
uint16_t regVal;

if (len != 8U)
{
SendException(READ_HOLDING_REGS, ILLEGAL_DATA_VALUE);
return;
}

startAddr = ReadU16Be(&frame[2]);
quantity = ReadU16Be(&frame[4]);

if ((quantity < 1U) || (quantity > MAX_READ_REGS))
{
SendException(READ_HOLDING_REGS, ILLEGAL_DATA_VALUE);
return;
}

if (ModbusIsRegRangeValid(startAddr, quantity) == 0U)
{
SendException(READ_HOLDING_REGS, ILLEGAL_DATA_ADDRESS);
return;
}

byteCount = (uint16_t)(quantity * 2U);

g_modbus_tx_buf[0] = frame[0];
g_modbus_tx_buf[1] = READ_HOLDING_REGS;
g_modbus_tx_buf[2] = (uint8_t)byteCount;

for (i = 0U; i < quantity; i++)
{
regVal = ReadHoldReg((uint16_t)(startAddr + i));
g_modbus_tx_buf[3U + (i * 2U)] = (uint8_t)(regVal >> 8);
g_modbus_tx_buf[4U + (i * 2U)] = (uint8_t)(regVal & 0xFFU);
}

AppendCrcAndSend(g_modbus_tx_buf, (uint16_t)(3U + byteCount));
}

/*============================================================================*/
/* 功能码 06:写单个保持寄存器 */
/*============================================================================*/

/**
* @brief 处理写单个保持寄存器请求(0x06)
* @param[in] frame 完整请求 ADU
* @param[in] len 合法请求固定为 8
*
* 读写都经 WriteHoldReg / ReadHoldReg,落在 g_hold_reg[]。
* 应答回显地址与写入值。
*/
static void WriteSingleReg(const uint8_t *frame, uint16_t len)
{
uint16_t addr;
uint16_t value;

if (len != 8U)
{
SendException(WRITE_SINGLE_REG, ILLEGAL_DATA_VALUE);
return;
}

addr = ReadU16Be(&frame[2]);
value = ReadU16Be(&frame[4]);

if (ModbusIsRegRangeValid(addr, 1U) == 0U)
{
SendException(WRITE_SINGLE_REG, ILLEGAL_DATA_ADDRESS);
return;
}

WriteHoldReg(addr, value);

g_modbus_tx_buf[0] = frame[0];
g_modbus_tx_buf[1] = WRITE_SINGLE_REG;
g_modbus_tx_buf[2] = frame[2];
g_modbus_tx_buf[3] = frame[3];
g_modbus_tx_buf[4] = frame[4];
g_modbus_tx_buf[5] = frame[5];
AppendCrcAndSend(g_modbus_tx_buf, 6U);

PlsrParamBlockOnHoldWrite(addr, 1U);
}

/*============================================================================*/
/* 功能码 10:写多个保持寄存器 */
/*============================================================================*/

/**
* @brief 处理写多个保持寄存器请求(0x10)
* @param[in] frame 完整请求 ADU
* @param[in] len 等于 7 + ByteCount + 2
* @retval None
*/
static void WriteMultiRegs(const uint8_t *frame, uint16_t len)
{
uint16_t startAddr;
uint16_t quantity;
uint8_t byteCount;
uint16_t expectedLen;
uint16_t i;

if (len < 9U)
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE);
return;
}

startAddr = ReadU16Be(&frame[2]);
quantity = ReadU16Be(&frame[4]);
byteCount = frame[6];

if ((quantity < 1U) || (quantity > MAX_WRITE_REGS))
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE);
return;
}

if (byteCount != (uint8_t)(quantity * 2U))
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE);
return;
}

expectedLen = (uint16_t)(7U + byteCount + 2U);
if (len != expectedLen)
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_VALUE);
return;
}

if (ModbusIsRegRangeValid(startAddr, quantity) == 0U)
{
SendException(WRITE_MULTI_REGS, ILLEGAL_DATA_ADDRESS);
return;
}

for (i = 0U; i < quantity; i++)
{
WriteHoldReg((uint16_t)(startAddr + i),
ReadU16Be(&frame[7U + (i * 2U)]));
}

/* 先回 FC10 应答,再做分帧计数(避免应答被后续逻辑拖住) */
g_modbus_tx_buf[0] = frame[0];
g_modbus_tx_buf[1] = WRITE_MULTI_REGS;
g_modbus_tx_buf[2] = frame[2];
g_modbus_tx_buf[3] = frame[3];
g_modbus_tx_buf[4] = frame[4];
g_modbus_tx_buf[5] = frame[5];
AppendCrcAndSend(g_modbus_tx_buf, 6U);

/*统计接受帧数,收满释放信号量*/
PlsrParamBlockOnHoldWrite(startAddr, quantity);
/* 动态改频率 */
PlsrCommandOnSegFreqHoldWrite(startAddr, quantity);
}

/*============================================================================*/
/* 异常应答与发送 */
/*============================================================================*/

/**
* @brief 组并发送异常应答
* @param[in] funcCode 原功能码(未置位 0x80)
* @param[in] exCode 异常码 01/02/03
*/
static void SendException(uint8_t funcCode, uint8_t exCode)
{
g_modbus_tx_buf[0] = g_modbus_current_addr;
g_modbus_tx_buf[1] = (uint8_t)(funcCode | 0x80U);
g_modbus_tx_buf[2] = exCode;

AppendCrcAndSend(g_modbus_tx_buf, 3U);
}

/**
* @brief 添加CRC
* @param[in] frame PDU
* @param[in] len 根据协议规定传入对应字节数
* @retval None
*/
static void AppendCrcAndSend(uint8_t *pdu, uint16_t pduLen)
{
uint16_t crc;

if ((pdu == NULL) || (pduLen == 0U) ||
((uint16_t)(pduLen + 2U) > MODBUS_TX_BUF_LEN))
{
return;
}

if (g_modbus_reply_enable == 0U)
{
return;
}

crc = ModbusCrc16(pdu, pduLen);
pdu[pduLen] = (uint8_t)(crc & 0xFFU);
pdu[pduLen + 1U] = (uint8_t)(crc >> 8);

SendData(pdu, (uint16_t)(pduLen + 2U));
}

/**
* @brief 停止帧定时器
* @retval None
*/
static void StopFrameTimer(void)
{
(void)HAL_TIM_Base_Stop_IT(&htim2);
g_modbus_tim_armed = 0U;
__HAL_TIM_SET_COUNTER(&htim2, 0U);
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
}

/**
* @brief 清掉串口接收标志
* @retval None
*/
static void FlushUartRx(void)
{
__HAL_UART_CLEAR_OREFLAG(&huart1);
__HAL_UART_CLEAR_NEFLAG(&huart1);
__HAL_UART_CLEAR_FEFLAG(&huart1);
while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET)
{
(void)huart1.Instance->DR;
}
}

/**
* @brief 重启串口接收
* @retval None
*/
static void RestartReceive(void)
{
FlushUartRx();
huart1.RxState = HAL_UART_STATE_READY;
(void)HAL_UART_Receive_IT(&huart1, &g_rxByte, 1U);
}

/**
* @brief modbus串口初始化
* @retval None
*/
void ModbusPortInit(void)
{
g_modbus_rx_len = 0U;
g_modbus_frame_pending = 0U;
g_modbus_tim_armed = 0U;
g_modbus_rx_sem = OSSemCreate(0U);
RestartReceive();
}

/**
* @brief 串口接收中断回调
* @param[in] huart 串口句柄
* @retval None
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
uint32_t gapTicks;

if (g_modbus_frame_pending == 0U)
{
/* 1.5T 字符间隙:判定上一帧已结束,丢弃半包后重新组帧 */
if (g_modbus_tim_armed != 0U)
{
gapTicks = __HAL_TIM_GET_COUNTER(&htim2);
if (gapTicks >= (uint32_t)MODBUS_T15_TICKS)
{
g_modbus_rx_len = 0U;
}
}
if (g_modbus_rx_len < MODBUS_RX_BUF_LEN)
{
g_modbus_rx_buf[g_modbus_rx_len] = g_rxByte;
g_modbus_rx_len++;
}
}

__HAL_TIM_SET_COUNTER(&htim2, 0U);
if (g_modbus_tim_armed == 0U)
{
g_modbus_tim_armed = 1U;
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
(void)HAL_TIM_Base_Start_IT(&htim2);
}

(void)HAL_UART_Receive_IT(&huart1, &g_rxByte, 1U);
}
}

/**
* @brief 串口异常回调
* @param[in] huart 串口句柄
* @retval None
*/
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1)
{
RestartReceive();
}
}

/**
* @brief 定时器更新(溢出)中断回调函数
* @param[in] htim 定时器句柄指针
* @retval None
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
(void)HAL_TIM_Base_Stop_IT(&htim2);
g_modbus_tim_armed = 0U;

if ((g_modbus_rx_sem != (OS_EVENT *)0) &&
(g_modbus_rx_len > 0U) &&
(g_modbus_frame_pending == 0U))
{
g_modbus_frame_pending = 1U;
(void)OSSemPost(g_modbus_rx_sem);
}
}
else if (PlsrPulseDriverIsPulseTim(htim->Instance) != 0U)
{
/* 每个 PWM 周期溢出 = 1 个脉冲(TIM10/11/13) */
PlsrOnPulseIsr();
}
}

/**
* @brief CRC16校验
* @param[in] data 数据
* @param[in] len 字节长度
* @retval crc
*/
uint16_t ModbusCrc16(uint8_t *data, uint16_t len)
{
uint16_t crc = 0xFFFFU;
uint16_t i;
uint16_t j;

for (i = 0U; i < len; i++)
{
crc ^= data[i];
for (j = 0U; j < 8U; j++)
{
if ((crc & 0x0001U) != 0U)
{
crc = (uint16_t)((crc >> 1) ^ 0xA001U);
}
else
{
crc >>= 1;
}
}
}
return crc;
}

/**
* @brief 发送数据
* @param[in] buf 待发送数据
* @param[in] len 待发送数据字节长度
* @retval None
*/
void SendData(uint8_t *buf, uint16_t len)
{
OS_CPU_SR cpu_sr;
volatile uint32_t turnaround;

(void)HAL_UART_AbortReceive_IT(&huart1);
StopFrameTimer();

OS_ENTER_CRITICAL();
g_modbus_rx_len = 0U;
OS_EXIT_CRITICAL();

(void)HAL_UART_Transmit(&huart1, buf, len, 200U);

while (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) == RESET)
{
}

for (turnaround = 0U; turnaround < 8000U; turnaround++)
{
}

FlushUartRx();
RestartReceive();
}

+ 18
- 69
modbus/modbus_rtu.h View File

@@ -36,23 +36,15 @@
/* 地址范围(M0/D0 起,地址直接当下标) */ /* 地址范围(M0/D0 起,地址直接当下标) */
/*============================================================================*/ /*============================================================================*/


/* 线圈/保持寄存器最大协议地址 */
/* 保持寄存器最大协议地址 */
#define MODBUS_MB_MAXIMUM 0x3000U #define MODBUS_MB_MAXIMUM 0x3000U


/* 实际能存储的最大数据个数 */ /* 实际能存储的最大数据个数 */
#define MODBUS_POINT_COUNT (MODBUS_MB_MAXIMUM + 1U) #define MODBUS_POINT_COUNT (MODBUS_MB_MAXIMUM + 1U)


/* 线圈最大协议地址 */
#define MODBUS_MAX_COIL_ADDR MODBUS_MB_MAXIMUM

/*保持寄存器最大协议地址 */ /*保持寄存器最大协议地址 */
#define MODBUS_MAX_REG_ADDR MODBUS_MB_MAXIMUM #define MODBUS_MAX_REG_ADDR MODBUS_MB_MAXIMUM


/* 线圈起始地址、数量、末位地址 */
#define MODBUS_M_COIL_BASE 0x0000U
#define MODBUS_M_COIL_COUNT MODBUS_POINT_COUNT
#define MODBUS_M_COIL_LAST MODBUS_MB_MAXIMUM

/* 保持寄存器起始地址、数量、末位地址*/ /* 保持寄存器起始地址、数量、末位地址*/
#define MODBUS_D_REG_BASE 0x0000U #define MODBUS_D_REG_BASE 0x0000U
#define MODBUS_D_REG_COUNT MODBUS_POINT_COUNT #define MODBUS_D_REG_COUNT MODBUS_POINT_COUNT
@@ -62,42 +54,34 @@
/* Modbus 应用层数量上限 */ /* Modbus 应用层数量上限 */
/*============================================================================*/ /*============================================================================*/


/* 单次可读线圈数量上限 */
#define MAX_READ_COILS 2000U

/* 单次可读保持寄存器数量上限 */ /* 单次可读保持寄存器数量上限 */
#define MAX_READ_REGS 125U #define MAX_READ_REGS 125U


/* 单次可写线圈数量上限 */
#define MAX_WRITE_COILS 1968U

/* 单次可写保持寄存器数量上限 */ /* 单次可写保持寄存器数量上限 */
#define MAX_WRITE_REGS 123U #define MAX_WRITE_REGS 123U


/* 线圈映像数组长度 */
#define COIL_BUFFER_SIZE MODBUS_POINT_COUNT

/* 保持寄存器映像数组长度 */ /* 保持寄存器映像数组长度 */
#define HOLD_REG_BUFFER_SIZE MODBUS_POINT_COUNT #define HOLD_REG_BUFFER_SIZE MODBUS_POINT_COUNT


extern UART_HandleTypeDef huart1; extern UART_HandleTypeDef huart1;
extern TIM_HandleTypeDef htim2; extern TIM_HandleTypeDef htim2;


/* 接收原始请求帧的缓冲区、任务侧拷贝到处理缓冲后清空长度
*/
extern uint8_t g_modbus_rx_buf[MODBUS_RX_BUF_LEN];
/* 功能码 */
#define READ_HOLDING_REGS 0x03U
#define WRITE_SINGLE_REG 0x06U
#define WRITE_MULTI_REGS 0x10U


/* 前接收缓冲中已收到的字节数,注意临界区 */
extern volatile uint16_t g_modbus_rx_len;
/* 地址映射失败时的哨兵值 */
#define MODBUS_ADDR_INVALID 0xFFFFU


/* TIM2 空闲超时后 OSSemPost;ModbusSlaveTask 中 OSSemPend */
extern OS_EVENT *g_modbus_rx_sem;
/* 异常码 01:非法功能 */
#define ILLEGAL_FUNCTION 0x01U


/* 线圈数据映像 */
extern uint8_t g_coil_buf[COIL_BUFFER_SIZE];
/* 异常码 02:非法数据地址 */
#define ILLEGAL_DATA_ADDRESS 0x02U


/* 保持寄存器数据映像 */
extern uint16_t g_hold_reg[HOLD_REG_BUFFER_SIZE];
/* 异常码 03:非法数据值 */
#define ILLEGAL_DATA_VALUE 0x03U


/*============================================================================*/ /*============================================================================*/
/* 公共函数 */ /* 公共函数 */
@@ -112,46 +96,6 @@ extern uint16_t g_hold_reg[HOLD_REG_BUFFER_SIZE];
*/ */
void ModbusSlaveTask(void *pArg); void ModbusSlaveTask(void *pArg);


/**
* @brief 计算 Modbus RTU CRC16
* @param[in] data 待校验数据指针(不含CRC字段)
* @param[in] len 数据长度(字节)
* @retval CRC16
* @note 低字节在前、高字节在后
*/
uint16_t ModbusCrc16(uint8_t *data, uint16_t len);

/**
* @brief 阻塞发送一帧完整 ADU
* @details 发送前中止接收并清空接收长度,发送完成后等待 TC,短延时换向,
* 再恢复中断接收,避免自发自收污染缓冲。
* @param[in] buf 待发送缓冲区(含 CRC)
* @param[in] len 发送长度(字节)
* @retval None
*/
void SendData(uint8_t *buf, uint16_t len);

/**
* @brief 读线圈映像
* @param[in] addr 协议线圈地址
* @retval 1 线圈为 ON
* @retval 0 线圈为 OFF,或地址非法
*/
uint8_t ReadCoil(uint16_t addr);

/**
* @brief 写线圈映像
* @param[in] addr 协议线圈地址
* @param[in] val 非0写ON,0写OFF;地址非法则忽略
* @retval None
*/
void WriteCoil(uint16_t addr, uint8_t val);

/**
* @brief 读保持寄存器映像
* @param[in] addr 协议寄存器地址
* @retval 寄存器值;地址非法时返回 0
*/
uint16_t ReadHoldReg(uint16_t addr); uint16_t ReadHoldReg(uint16_t addr);


/** /**
@@ -161,4 +105,9 @@ uint16_t ReadHoldReg(uint16_t addr);
*/ */
void WriteHoldReg(uint16_t addr, uint16_t val); void WriteHoldReg(uint16_t addr, uint16_t val);


/**
* @brief 端口初始化
*/
void ModbusPortInit(void);

#endif #endif

+ 1258
- 724
plsr/accel_curve/plsr_accel_curve.c
File diff suppressed because it is too large
View File


+ 74
- 107
plsr/accel_curve/plsr_accel_curve.h View File

@@ -1,12 +1,9 @@
/** /**
* @file plsr_accel_curve.h * @file plsr_accel_curve.h
* @brief 预估加/匀/减脉冲;相内按时间走直线/S/正弦
* @brief 脉冲域加/匀/减规划与取频(运动学:v^2 - v0^2 = 2ax)
* *
* 1ms 改频:脉冲数按 t=0..T-1 实际取频累加;到不了目标频则降峰值走三角(无匀速)。
* 匀速仅当加速+减速脉冲之和 < 总脉冲(真正到达目标频)时出现。
* 斜率按公共参数:K_acc=默认速度/加速时间,K_dec=默认速度/减速时间(减 0,不用起/止速)。
* 小频差时 T 可能 <1ms,无可见斜坡属正常。
* 开段端点由运行层 ResolveStart/EndHz 解析后再传入 Plan(Plan 不解释策略 0)。
* @details 按起/峰/止频率、斜率时间与总脉冲规划三相脉冲预算,并在脉冲序号上取频。
* 不读 wait_type,不碰 TIM/GPIO。
*/ */
#ifndef PLSR_ACCEL_CURVE_H #ifndef PLSR_ACCEL_CURVE_H
#define PLSR_ACCEL_CURVE_H #define PLSR_ACCEL_CURVE_H
@@ -14,120 +11,90 @@
#include <stdint.h> #include <stdint.h>
#include "plsr_param.h" #include "plsr_param.h"


typedef struct {
uint32_t acc_n;
uint32_t const_n;
uint32_t dec_n;
uint32_t f_cur;
uint32_t f_tgt;
uint32_t f_end;
uint32_t t_acc_ms;
uint32_t t_dec_ms;
uint32_t t_decel_start_ms; /* 反向规划:段内时间轴上进入减速的时刻 */
PlsrAccelMode_e mode;
} PlsrAccelPlan_t;

typedef enum {
PLSR_CURVE_PHASE_ACC = 0,
PLSR_CURVE_PHASE_CONST,
PLSR_CURVE_PHASE_DEC
} PlsrCurvePhase_e;

uint32_t PlsrAccelCurveClampFreq(uint32_t freq_hz);

/** /**
* 起跳频率:f_jump = sqrt(加速度_Hz/s)。
* 起速/止速为 0 时,第一个脉冲(或最后一个脉冲)应以此频率输出,
* 使脉冲周期恰好等于加速度产生该频率所需的时间。
* 单段脉冲域规划结果(PlsrAccelCurvePlan 输出)
* run_control 进入减速时使用局部规划副本,不会改写这里的整段规划预算。
*/ */
uint32_t PlsrAccelCurveJumpFreq(uint32_t f_from, uint32_t f_to,
uint32_t t_ms);
typedef struct {
uint32_t accel_pulses; /* 入口到目标频率需要的脉冲数 */
uint32_t const_pulses; /* 保持目标频率的脉冲数 */
uint32_t decel_pulses; /* 目标到出口频率需要的脉冲数 */
uint32_t start_freq_hz; /* 本段实际入口频率 */
uint32_t target_freq_hz; /* 实际目标频率,短段可能被降低 */
uint32_t end_freq_hz; /* 本段出口频率 */
uint32_t accel_rate_hz_per_s; /* 升高频率时使用的变化率 */
uint32_t decel_rate_hz_per_s; /* 降低频率时使用的变化率 */
PlsrAccelMode_e curve_mode; /* 直线 / S / 正弦 */
} PlsrAccelPlan_t;


/**
* 配置起速 → 规划用真实起点频率。
* f_cfg>=1:原样(钳位);f_cfg==0:按斜率算起跳频率(从静止爬升)。
* 由运行层在开段前调用;Plan 只吃已经解析好的端点。
*/
uint32_t PlsrAccelCurveResolveStartHz(uint32_t f_cfg,
uint32_t f_tgt,
uint32_t default_spd,
uint32_t start_spd_ref,
uint32_t end_spd_ref,
uint32_t accel_ms,
uint32_t decel_ms);
/** 配置起速 → 规划入口:起速>目标用起速;否则目标与起跳比较取其一 */
uint32_t PlsrAccelCurveResolveStartHz(uint32_t configured_freq_hz,
uint32_t target_freq_hz,
uint32_t default_speed_hz,
uint32_t accel_time_ms,
uint32_t decel_time_ms);


/**
* 配置止速 → 规划用真实终点频率。
* f_cfg>=1:原样(钳位);f_cfg==0:按斜率算落地频率(避免 1Hz 拖尾)。
* 段末是否停表由运行层决定,与本函数无关。
*/
uint32_t PlsrAccelCurveResolveEndHz(uint32_t f_cfg,
uint32_t f_tgt,
uint32_t default_spd,
uint32_t start_spd_ref,
uint32_t end_spd_ref,
uint32_t accel_ms,
uint32_t decel_ms);
/** 配置止速 → 规划出口:止速>目标用止速;否则目标与起跳比较取其一(不默认到 0) */
uint32_t PlsrAccelCurveResolveEndHz(uint32_t configured_freq_hz,
uint32_t target_freq_hz,
uint32_t default_speed_hz,
uint32_t accel_time_ms,
uint32_t decel_time_ms);


/** /**
* 斜坡上按 mode 取频(µs 时间轴)。ISR 可用:O(1)。
* f = f0+(f1-f0)*shape(t/T)
* 规划一整段脉冲域三相预算与实际峰值
* 脉冲不够到达目标频率时取消匀速段,再降低实际目标频率。
*/ */
uint32_t PlsrAccelCurveFreqOnRampUs(uint32_t f0,
uint32_t f1,
uint32_t t_us,
uint32_t T_us,
PlsrAccelMode_e mode);

/**
* 与 TIM3 1ms 改频一致的斜坡取频(规划/运行共用)。
*/
uint32_t PlsrAccelCurveFreqOnRampMs(uint32_t f0,
uint32_t f1,
uint32_t t_ms,
uint32_t T_ms,
PlsrAccelMode_e mode);
void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan,
uint32_t total_pulses,
uint32_t start_freq_hz,
uint32_t target_freq_hz,
uint32_t end_freq_hz,
uint32_t default_speed_hz,
uint32_t accel_time_ms,
uint32_t decel_time_ms,
PlsrAccelMode_e curve_mode);


/**
* 斜坡第一拍频率(起速为 0 时用自洽点,避免按 1Hz 整秒吃光加速相)。
* ISR 可用:O(1)。
*/
uint32_t PlsrAccelCurveFirstPulseFreq(uint32_t f0,
uint32_t f1,
uint32_t T_us,
PlsrAccelMode_e mode);
/** 按已完成脉冲数取频(任务/预览;ISR 热路径用 PlsrAccelNextFreq) */
uint32_t PlsrAccelCurveFreqAtPulse(const PlsrAccelPlan_t *plan,
uint32_t completed_seg_pulses);


/** /**
* 减速落地频率:f_end>=1 即止速;f_end==0 返回 0(停表,不用 1Hz 冒充)。
* 单相逐脉冲运行态(ISR 热路径,每来一个 UPDATE 调 Step 一次)
* 直线:每次按目标平方值逐步逼近下一频率
* S/正弦:时间累计 + 257 点 Q24 形状表线性插值
*/ */
uint32_t PlsrAccelCurveLandFreq(uint32_t f_end,
uint32_t f_peak,
uint32_t T_ms,
PlsrAccelMode_e mode);
typedef struct {
uint32_t cur_freq_hz; /* 当前输出频率 */
uint32_t start_freq_hz; /* 当前相起点频率 */
uint32_t end_freq_hz; /* 当前相终点频率 */
uint32_t accel_hz_per_s; /* 当前相使用的变化率 */
uint32_t completed_pulses; /* 当前相已经步进的脉冲数 */
uint32_t total_pulses; /* 当前相计划步进的脉冲数 */
uint32_t elapsed_time_us; /* 时间曲线已经经过的时间 */
uint32_t jerk_time_us; /* S 曲线单个 jerk 阶段时间 */
uint32_t const_accel_time_us; /* S 曲线恒加速阶段时间 */
uint32_t ramp_time_us; /* 整个斜坡时间 */
uint8_t freq_rising; /* 1=频率升高,0=降低 */
uint8_t is_active; /* 1=当前相仍在运行 */
PlsrAccelMode_e curve_mode; /* 直线 / S / 正弦 */
} PlsrAccelRuntime_t;


/** 拟合斜坡时间(ms),使离散模型脉冲数尽量等于 max_pulses(任务上下文) */
uint32_t PlsrAccelCurveFitRampTimeMs(uint32_t f_from,
uint32_t f_to,
uint32_t max_pulses,
uint32_t t_prefer_ms,
PlsrAccelMode_e mode);
/* 当前段唯一的曲线规划和逐拍状态,run_control 直接使用,不再另存副本。 */
extern PlsrAccelPlan_t g_plsr_accel_plan; /* 本段三相规划结果 */
extern PlsrAccelRuntime_t g_plsr_accel_runtime; /* 当前相 ISR 逐拍状态 */


void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan,
uint32_t total_pulses,
uint32_t f_cur,
uint32_t f_tgt,
uint32_t f_end,
uint32_t default_spd,
uint32_t start_spd_ref,
uint32_t end_spd_ref,
uint32_t accel_ms,
uint32_t decel_ms,
PlsrAccelMode_e mode);
/** 进入加速相:起→峰;S/正弦在 ISR 内形状表取频 */
void PlsrAccelBeginAccel(PlsrAccelRuntime_t *runtime,
const PlsrAccelPlan_t *plan);
/** 进入减速相:峰→止;S/正弦在 ISR 内形状表取频 */
void PlsrAccelBeginDecel(PlsrAccelRuntime_t *runtime,
const PlsrAccelPlan_t *plan);
/** 进入匀速相:钉峰值,ISR 不再改频 */
void PlsrAccelBeginConstSpeed(PlsrAccelRuntime_t *runtime,
const PlsrAccelPlan_t *plan);


/**
* 1ms 改频:按段内绝对时间取频(相界由 t_decel_start_ms 决定,不用脉冲域)。
*/
uint32_t PlsrAccelCurveFreqAtSegTime(const PlsrAccelPlan_t *plan,
uint32_t seg_elapsed_ms);
/** 本相前进 1 脉冲,返回下一拍命令频率 */
uint32_t PlsrAccelNextFreq(PlsrAccelRuntime_t *runtime);


#endif #endif

+ 170
- 18
plsr/command/plsr_command.c View File

@@ -1,84 +1,236 @@
/** /**
* @file plsr_command.c * @file plsr_command.c
* @brief 指令接口:0x3000 控制位 → 运行控制;监控写回 0x2000~0x2006
* @brief Modbus 指令接口实现
*
* @details 模块职责
* 将 Modbus 控制/监控寄存器与 PLSR 运行层对接:解析 0x3000 控制位、
* 周期刷新 0x2000~0x2006、维护故障码镜像、处理运行中动态写频。
*
* 故障码数据路径
* 1. run_control 等模块调用 PlsrCommandSetFault(n) → 更新 s_fault + 写 0x2006
* 2. Poll 末尾 PublishMonitor 重发 s_fault,保证寄存器与镜像一致
* 3. 仅改寄存器不改 s_fault 会被下一周期 Poll 覆盖
* 4. START / CLR 控制位处理时 ClearFault(),表示用户确认恢复正常
*
* 动态改频路径
* FC10 写当前段 FREQ → OnSegFreqHoldWrite → s_live_freq_req=1
* → Poll 调 PlsrChangeFreq → run_control 重规划剩余脉冲
*/ */
#include "plsr_command.h" #include "plsr_command.h"
#include "plsr_param.h" #include "plsr_param.h"
#include "plsr_run_control.h" #include "plsr_run_control.h"
#include "plsr_pulse_driver.h"
#include "plsr.h" #include "plsr.h"
#include "plsr_persist.h"
#include "modbus_rtu.h" #include "modbus_rtu.h"


static uint16_t s_fault = PLSR_ERR_NONE;
/*
* 模块静态状态
* s_fault — 故障码镜像,与 0x2006 同步;告警码持久直到 START/CLR
* s_live_freq_req — 运行中写频请求标志,Poll 消费后清 0
* s_live_freq_hz — 待应用的 new_tgt_hz
*/
static uint16_t s_fault = PLSR_ERR_NONE; /* 故障码镜像,与 0x2006 同步 */
static volatile uint8_t s_live_freq_req; /* 1=运行中改频待 Poll 消费 */
static volatile uint32_t s_live_freq_hz; /* 待应用的新目标频率 Hz */


/**
* @brief 写 32 位值到相邻两个modbus保持寄存器
* @param addr 低字寄存器地址
* @param value 32 位值
*/
static void PlsrCommandWriteDWord(uint16_t addr, uint32_t value) static void PlsrCommandWriteDWord(uint16_t addr, uint32_t value)
{ {
WriteHoldReg(addr, (uint16_t)(value & 0xFFFFU)); WriteHoldReg(addr, (uint16_t)(value & 0xFFFFU));
WriteHoldReg((uint16_t)(addr + 1U), (uint16_t)((value >> 16) & 0xFFFFU)); WriteHoldReg((uint16_t)(addr + 1U), (uint16_t)((value >> 16) & 0xFFFFU));
} }


/**
* @brief 刷新全部监控寄存器 0x2000~0x2006到modbus保持寄存器
* @note 调用时机:Init 末、Poll 每周期末
* @note 数据源:RunControl / Plsr 任务 + s_fault 镜像
*/
static void PlsrCommandPublishMonitor(void) static void PlsrCommandPublishMonitor(void)
{ {
PlsrCommandWriteDWord(PLSR_MON_ACC_PULSE_L, (uint32_t)PlsrGetAccPulse());
PlsrCommandWriteDWord(PLSR_MON_FREQ_L, PlsrRunControlGetCurFreq());
WriteHoldReg(PLSR_MON_RUN_STATUS, PlsrIsBusy() ? 1U : 0U);
WriteHoldReg(PLSR_MON_CUR_SEG, PlsrRunControlGetCurSeg());
PlsrCommandWriteDWord(PLSR_MON_ACC_PULSE_L, (uint32_t)g_plsr_accumulated_pulses);
WriteHoldReg(PLSR_MON_ERR, s_fault); WriteHoldReg(PLSR_MON_ERR, s_fault);

if (g_plsr_busy != 0U)
{
PlsrCommandWriteDWord(PLSR_MON_FREQ_L, g_plsr_output_freq_hz); /* 0x2002/03 当前频 */
WriteHoldReg(PLSR_MON_RUN_STATUS, 1U); /* 0x2004 运行中 */
WriteHoldReg(PLSR_MON_CUR_SEG, (uint16_t)(g_plsr_cur_seg_idx + 1U)); /* 0x2005 1-based */
}
else
{
/* 空闲/停止:运行、段、频率一律清零;累计与故障码单独维护 */
PlsrCommandWriteDWord(PLSR_MON_FREQ_L, 0U);
WriteHoldReg(PLSR_MON_RUN_STATUS, 0U);
WriteHoldReg(PLSR_MON_CUR_SEG, 0U);
}
} }


/** @brief 置故障码 */
void PlsrCommandSetFault(uint16_t code) void PlsrCommandSetFault(uint16_t code)
{ {
s_fault = code; s_fault = code;
WriteHoldReg(PLSR_MON_ERR, code); WriteHoldReg(PLSR_MON_ERR, code);
} }


/** @brief 清故障码 */
void PlsrCommandClearFault(void) void PlsrCommandClearFault(void)
{ {
s_fault = PLSR_ERR_NONE; s_fault = PLSR_ERR_NONE;
WriteHoldReg(PLSR_MON_ERR, PLSR_ERR_NONE); WriteHoldReg(PLSR_MON_ERR, PLSR_ERR_NONE);
} }


/** @brief persist模块读故障码 */
uint16_t PlsrCommandGetFault(void) uint16_t PlsrCommandGetFault(void)
{ {
return s_fault; return s_fault;
} }


/** @brief 参数/重复启动类故障是否禁止 START */
uint8_t PlsrCommandIsStartBlocked(void)
{
if ((s_fault >= PLSR_ERR_PARAM_BLOCK_MIN) &&
(s_fault <= PLSR_ERR_PARAM_BLOCK_MAX))
{
return 1U;
}
return 0U;
}

/** @brief 指令模块初始化 */
void PlsrCommandInit(void) void PlsrCommandInit(void)
{ {
WriteHoldReg(PLSR_CTRL_REG, 0U); WriteHoldReg(PLSR_CTRL_REG, 0U);
PlsrCommandClearFault();
s_live_freq_req = 0U;
s_live_freq_hz = 0U;
/* 不清故障:上电由 PersistRestoreMonitor 恢复;无镜像时 s_fault 默认为 0 */
PlsrCommandPublishMonitor(); PlsrCommandPublishMonitor();
} }


/**
* @brief 运行中 FC10 写段频率
* @note 限制:quantity==2、Busy、地址对齐 FREQ_L、当前改频段,当前运行段
* @note 频率非法写 0x04 且不更新段表、不请求 ChangeFreq
*/
void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity)
{
uint16_t cur_seg; /* 当前运行段(1-based) */
uint16_t seg; /* 本次写入对应的段下标(0-based) */
uint16_t offset;
uint32_t freqency;

if (quantity != 2U)
{
return; /* 须为频率双字 */
}
if (g_plsr_busy == 0U)
{
return; /* 空闲改频走参数块,不走本路径 */
}
if (start_addr < PLSR_REG_SEG1_BASE)
{
return;
}

offset = (uint16_t)(start_addr - PLSR_REG_SEG1_BASE);
if ((offset % PLSR_SEG_STRIDE) != PLSR_SEG_OFF_FREQ_L)
{
return; /* 地址须对齐该段 FREQ_L */
}
seg = (uint16_t)(offset / PLSR_SEG_STRIDE);
if (seg >= PLSR_SEG_MAX)
{
return;
}

cur_seg = (uint16_t)(g_plsr_cur_seg_idx + 1U);
if ((cur_seg < 1U) || ((uint16_t)(cur_seg - 1U) != seg))
{
return; /* 仅允许改当前运行段 */
}

freqency = (uint32_t)ReadHoldReg(start_addr) |
((uint32_t)ReadHoldReg((uint16_t)(start_addr + 1U)) << 16);
if ((freqency < 1U) || (freqency > 100000U))
{
PlsrCommandSetFault(PLSR_ERR_FREQ_ILLEGAL); /* 非法:不改段表、不请求 */
return;
}

g_plsr_segs[seg].freq_hz = (int32_t)freqency; /* 更新运行段表 */
PlsrPersistSaveSegFreqToBkp(seg, freqency); /* 仅该段频率落 BKP */

s_live_freq_hz = freqency;
s_live_freq_req = 1U; /* 交 Poll → ChangeFreq */
}

/**
* @brief 周期轮询
*
* 控制位处理顺序(同周期多 bit 时):
* 1. 读 ctrl 后立即写 0(避免重复触发)
* 2. STOP — 急停
* 3. CLR — 清累计 + ClearFault
* 4. START — 忙则 0x0C;否则 Apply 校验 → 0x04~0x0C 故障码则拒启 → Stop+Start
* 动态改频在控制位之后、PublishMonitor 之前执行。
*/
void PlsrCommandPoll(void) void PlsrCommandPoll(void)
{ {
uint16_t ctrl = ReadHoldReg(PLSR_CTRL_REG); uint16_t ctrl = ReadHoldReg(PLSR_CTRL_REG);
uint8_t skip_monitor = 0U; /* 精确等待时跳过本拍监控刷新 */


if (ctrl != 0U) if (ctrl != 0U)
{ {
/* 上位机置位,从站处理后立刻清零 */
WriteHoldReg(PLSR_CTRL_REG, 0U);
WriteHoldReg(PLSR_CTRL_REG, 0U); /* 立即清,避免重复触发 */


if ((ctrl & PLSR_CTRL_BIT_STOP) != 0U) if ((ctrl & PLSR_CTRL_BIT_STOP) != 0U)
{ {
PlsrStop();
PlsrStop(); /* 急停 */
} }


if ((ctrl & PLSR_CTRL_BIT_CLR) != 0U) if ((ctrl & PLSR_CTRL_BIT_CLR) != 0U)
{ {
PlsrClearAccPulse();
PlsrCommandClearFault();
PlsrClearAccPulse(); /* 清累计 + 绝对原点 */
PlsrCommandClearFault(); /* 同步清故障码 */
} }


/* 同时有停止位时不启动 */
/* START:同拍有 STOP 则不启 */
if (((ctrl & PLSR_CTRL_BIT_START) != 0U) && if (((ctrl & PLSR_CTRL_BIT_START) != 0U) &&
((ctrl & PLSR_CTRL_BIT_STOP) == 0U)) ((ctrl & PLSR_CTRL_BIT_STOP) == 0U))
{ {
(void)PlsrParamBlockApplyFromHold();
PlsrCommandClearFault();
PlsrStop();
(void)PlsrStart(PlsrParamGetStartSeg());
if (g_plsr_busy != 0U)
{
PlsrCommandSetFault(PLSR_ERR_DUP_START); /* 0x0C 重复启动 */
}
else if (PlsrCommandIsStartBlocked() != 0U)
{
PlsrStop(); /* 0x04~0x0B:拒启,仅停 */
}
else
{
PlsrStop(); /* 先清残留再启 */
(void)PlsrStart(PlsrParamGetStartSeg());
if (PlsrRunControlIsPreciseWait() != 0U)
{
skip_monitor = 1U; /* 换向/WAIT 精确等 TIM5 */
}
}
} }
} }


PlsrCommandPublishMonitor();
if (s_live_freq_req != 0U)
{
uint32_t freq = s_live_freq_hz;
s_live_freq_req = 0U;
(void)PlsrChangeFreq(freq); /* 重规划剩余脉冲 */
}

if (skip_monitor == 0U)
{
PlsrCommandPublishMonitor(); /* 回写 0x2000~0x2006 */
}
} }

+ 89
- 15
plsr/command/plsr_command.h View File

@@ -1,37 +1,111 @@
/** /**
* @file plsr_command.h * @file plsr_command.h
* @brief 指令接口模块
* @brief Modbus 指令接口:监控回写与控制位解析
* *
* 监控区 0x2000~0x2006:下位机刷新,上位机 0x03 读。
* 控制寄存器 0x3000:上位机置位,下位机处理后清零。
* @details 模块职责
* 维护故障码镜像 s_fault,周期性将运行状态写入监控保持寄存器;
* 解析控制寄存器 0x3000 的 START/STOP/CLR 位并驱动 PlsrStart/Stop/Clear;
* 处理运行中 FC10 写当前段频率的双字,触发 ChangeFreq。
*
* 与其它模块关系
* - modbus_rtu :ReadHoldReg / WriteHoldReg 底层访问
* - plsr_run_control :GetCurSeg / GetCurFreq / IsBusy 数据源
* - plsr_param :ApplyFromHold、段表 FREQ 偏移常量
* - plsr.h :PlsrStart / Stop / ChangeFreq / ClearAccPulse
*
* 关键数据流
* 上位机写 0x3000 → Poll 读 ctrl → 清 0 → 执行 STOP/CLR/START → PublishMonitor
* run_control 诊断 → SetFault(n) → 写 0x2006;Poll 每周期重发 s_fault
* FC10 写段频 → OnSegFreqHoldWrite → s_live_freq_req → Poll → ChangeFreq
*
* 寄存器映射(保持寄存器,Modbus 0x03/0x06/0x10)
* 监控区 0x2000~0x2006:下位机刷新,上位机只读
* 控制区 0x3000 :上位机置位,从站处理后清零
*/ */
#ifndef PLSR_COMMAND_H #ifndef PLSR_COMMAND_H
#define PLSR_COMMAND_H #define PLSR_COMMAND_H


#include <stdint.h> #include <stdint.h>


/* ---------- 监控保持寄存器(双字低地址存低字) ---------- */
#define PLSR_MON_ACC_PULSE_L 0x2000U /* 脉冲累计,有符号补码 */
/* ---------- 监控保持寄存器(双字低地址存低 16 位,高地址存高 16 位) ---------- */
#define PLSR_MON_ACC_PULSE_L 0x2000U /* 脉冲累计 SDW,有符号补码 */
#define PLSR_MON_ACC_PULSE_H 0x2001U #define PLSR_MON_ACC_PULSE_H 0x2001U
#define PLSR_MON_FREQ_L 0x2002U /* 当前发送频率 Hz */
#define PLSR_MON_FREQ_L 0x2002U /* 当前发送频率 Hz,UDW */
#define PLSR_MON_FREQ_H 0x2003U #define PLSR_MON_FREQ_H 0x2003U
#define PLSR_MON_RUN_STATUS 0x2004U /* 0空闲 1运行 */
#define PLSR_MON_CUR_SEG 0x2005U /* 当前执行段 1-based */
#define PLSR_MON_ERR 0x2006U /* 错误码 */
#define PLSR_MON_RUN_STATUS 0x2004U /* 0=空闲,1=运行中 */
#define PLSR_MON_CUR_SEG 0x2005U /* 当前执行段号,1-based;空闲为 0 */
#define PLSR_MON_ERR 0x2006U /* 故障码,见 PLSR_ERR_* */


/* ---------- 控制寄存器(读/写,处理后由从站清 0) ---------- */
/* ---------- 控制寄存器(读/写;从站处理后写回 0) ---------- */
#define PLSR_CTRL_REG 0x3000U #define PLSR_CTRL_REG 0x3000U
#define PLSR_CTRL_BIT_START (1U << 0) /* 启动脉冲 */
#define PLSR_CTRL_BIT_STOP (1U << 1) /* 停止脉冲 */
#define PLSR_CTRL_BIT_CLR (1U << 2) /* 清除状态信息 */
#define PLSR_CTRL_BIT_START (1U << 0) /* 置 1:Apply 参数后启动 */
#define PLSR_CTRL_BIT_STOP (1U << 1) /* 置 1:急停 */
#define PLSR_CTRL_BIT_CLR (1U << 2) /* 置 1:清零累计脉冲与故障码 */


#define PLSR_ERR_NONE 0U
#define PLSR_ERR_SEG_FREQ 1U /* 段频率非法(非 0 且越界,或解析后不在 1~100k) */
/*
* 故障码定义(写入 0x2006,须经 PlsrCommandSetFault,勿直接只写寄存器)
*/
#define PLSR_ERR_NONE 0x00U //无故障
#define PLSR_ERR_PULSE_CNT 0x01U //实际脉冲数与段目标不一致
#define PLSR_ERR_FREQ_UNREACH 0x02U //规划峰值达不到设定目标(三角)
#define PLSR_ERR_CURVE_MISMATCH 0x03U //加减速相内脉冲与规划预算不符
#define PLSR_ERR_FREQ_ILLEGAL 0x04U //段频率 ∉ [1, 100000] Hz(非 0)
#define PLSR_ERR_PULSE_RANGE 0x05U //脉冲个数 ∉ int32 合法范围
#define PLSR_ERR_SEG_COUNT 0x06U //总段数 ∉ [1, 10]
#define PLSR_ERR_START_SEG 0x07U //起始段 ∉ [1, 10]
#define PLSR_ERR_START_END_SPD 0x08U //起/止速非0且 ∉ [1, 100000] Hz
#define PLSR_ERR_PULSE_Y 0x09U //脉冲端子 ∉ {Y0,Y1,Y2}
#define PLSR_ERR_DIR_Y 0x0AU //方向端子 ≠ Y3
#define PLSR_ERR_DEFAULT_SPD 0x0BU //默认速度 ∉ [1, 100000] Hz
#define PLSR_ERR_DUP_START 0x0CU //运行中重复 START

/** 参数类门禁故障码下限 */
#define PLSR_ERR_PARAM_BLOCK_MIN PLSR_ERR_FREQ_ILLEGAL
/** 参数类门禁故障码上限 */
#define PLSR_ERR_PARAM_BLOCK_MAX PLSR_ERR_DUP_START


/**
* @brief 指令模块初始化
* @note 调用时机:PlsrInit 末、PlsrTask 首次延迟后再次调用
* @note 副作用:清 0x3000、清故障、刷新全部监控寄存器
*/
void PlsrCommandInit(void); void PlsrCommandInit(void);

/**
* @brief 周期轮询控制位并刷新监控
* @note 调用时机:PlsrTask 每 ~1ms
* @note 副作用:可能触发 Start/Stop/Clear/ChangeFreq,写 0x2000~0x2006
*/
void PlsrCommandPoll(void); void PlsrCommandPoll(void);

/**
* @brief 置故障码并立即写 0x2006
* @param code PLSR_ERR_* 之一
* @note 调用时机:run_control 诊断点;PublishMonitor 也会重发 s_fault
* @note 注意:更新 s_fault 镜像,告警码不自动清除(须 START 或 CLR)
*/
void PlsrCommandSetFault(uint16_t code); void PlsrCommandSetFault(uint16_t code);

/**
* @brief 清故障为 0
* @note 调用时机:START 前、CLR 控制位(Init 不再清,以便上电恢复)
*/
void PlsrCommandClearFault(void); void PlsrCommandClearFault(void);

/** @return 当前故障码镜像(与 0x2006 一致) */
uint16_t PlsrCommandGetFault(void); uint16_t PlsrCommandGetFault(void);


/**
* @brief 是否因参数/重复启动类故障禁止 START
* @return 1=故障码为 0x04~0x0C;0=可尝试启动
*/
uint8_t PlsrCommandIsStartBlocked(void);

/**
* @brief FC10 写段频率双字后的钩子
* @param start_addr 写入起始保持寄存器地址
* @param quantity 写入字数,须为 2
*/
void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity);

#endif #endif

+ 219
- 211
plsr/param/plsr_param.c View File

@@ -1,113 +1,67 @@
/** /**
* @file plsr_param.c * @file plsr_param.c
* @brief 参数块管理模块实现(运行映像 + 分帧导入/导出)
* @brief 参数块管理实现:运行映像 + 分帧导入/导出 + ParamBlockTask
*
* @details 模块职责
* 维护公共参数 g_plsr_config 和段参数 g_plsr_segs[],实现寄存器与
* 运行参数之间的双向转换,以及「5+N 分帧」收齐后的异步导入
*
* 与其它模块关系
* Modbus FC10 应答路径调用 OnHoldWrite;START 也会同步 ApplyFromHold;
* Persist:校验全部通过才 SaveParamsFromHold;失败只写故障码,不存BKP;
* 非上电不再 ExportToHold(hold 保留上位机写入值,由故障码提示修正)。
*/ */
#include "plsr_param.h" #include "plsr_param.h"
#include "plsr_persist.h" #include "plsr_persist.h"
#include "plsr_command.h"
#include "modbus_rtu.h" #include "modbus_rtu.h"
#include "ucos_ii.h" #include "ucos_ii.h"
#include <string.h>


static PlsrCfg_t s_cfg;
static PlsrSeg_t s_seg[PLSR_SEG_MAX];
PlsrCfg_t g_plsr_config; /* 公共参数运行映像 */
PlsrSeg_t g_plsr_segs[PLSR_SEG_MAX]; /* 段表运行映像(最多 PLSR_SEG_MAX) */


static OS_EVENT *s_param_sem;
static volatile uint16_t s_expect_frames;
static volatile uint16_t s_recv_frames;
static volatile uint8_t s_xfer_active;

/*============================================================================*/
/* 运行映像 */
/*============================================================================*/

void PlsrParamInitDefault(void)
{
uint16_t i;

memset(&s_cfg, 0, sizeof(s_cfg));
memset(s_seg, 0, sizeof(s_seg));

/* 与上位机默认约定一致;真正运行参数由 ApplyFromHold 覆盖 */
s_cfg.pulse_y = 0U;
s_cfg.dir_y = 3U;
s_cfg.wait_x_sel = 0U;
s_cfg.ext_x_sel = 0U;
s_cfg.send_mode = PLSR_SEND_COMPLETE;
s_cfg.dir_delay_ms = 10U;
s_cfg.dir_logic = PLSR_DIR_LOGIC_POS;
s_cfg.accel_mode = PLSR_ACCEL_LINEAR;
s_cfg.run_mode = PLSR_POS_RELATIVE;
s_cfg.seg_count = 0U;
s_cfg.start_seg = 1U;
s_cfg.default_speed = 1000UL;
s_cfg.start_speed = 0UL;
s_cfg.end_speed = 0UL;
s_cfg.accel_ms = 100U;
s_cfg.decel_ms = 100U;

for (i = 0U; i < PLSR_SEG_MAX; i++)
{
s_seg[i].freq_hz = 0;
s_seg[i].pulse_cnt = 0;
s_seg[i].wait_type = PLSR_WAIT_TIME;
s_seg[i].wait_ms = 0U;
s_seg[i].act_ms = 0U;
s_seg[i].jump_seg = 0U;
}
}

PlsrCfg_t *PlsrParamGetCfg(void)
{
return &s_cfg;
}

PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based)
{
if (seg_0based >= PLSR_SEG_MAX)
{
seg_0based = 0U;
}
return &s_seg[seg_0based];
}
static OS_EVENT *s_param_sem; /* 分帧收齐后唤醒 ParamBlockTask */
static volatile uint16_t s_expect_frames; /* 本批期望总帧数 = 5 + 段数 */
static volatile uint16_t s_recv_frames; /* 已收帧计数 */
static volatile uint8_t s_xfer_active; /* 1=正在接收一批分帧 */


/** @return 有效段数,软钳到 [0, PLSR_SEG_MAX] */
uint16_t PlsrParamGetSegCount(void) uint16_t PlsrParamGetSegCount(void)
{ {
if (s_cfg.seg_count < 1U)
if (g_plsr_config.seg_count < 1U)
{ {
return 0U;
return 0U; /* 无有效段 */
} }
if (s_cfg.seg_count > PLSR_SEG_MAX)
if (g_plsr_config.seg_count > PLSR_SEG_MAX)
{ {
return (uint16_t)PLSR_SEG_MAX;
return (uint16_t)PLSR_SEG_MAX; /* 超上限钳住 */
} }
return s_cfg.seg_count;
return g_plsr_config.seg_count;
} }


/**
* @brief 启动段 1-based,软钳到 [1, seg_count]
*/
uint16_t PlsrParamGetStartSeg(void) uint16_t PlsrParamGetStartSeg(void)
{ {
uint16_t n = PlsrParamGetSegCount(); uint16_t n = PlsrParamGetSegCount();


if (n < 1U) if (n < 1U)
{ {
return 1U;
return 1U; /* 无段时仍返回 1 */
} }
if (s_cfg.start_seg < 1U)
if (g_plsr_config.start_seg < 1U)
{ {
return 1U; return 1U;
} }
/* TODO:起始段非法时输出故障码 */
if (s_cfg.start_seg > n)
if (g_plsr_config.start_seg > n)
{ {
return n;
return n; /* 超出则钳到末段 */
} }
return s_cfg.start_seg;
return g_plsr_config.start_seg;
} }


/*============================================================================*/
/* 分帧导入 / 导出 */
/*============================================================================*/

/** 读相邻两寄存器拼成无符号 32 位(低字在前) */
/** 读相邻两寄存器拼成无符号 32 位(低地址=低 16 位) */
static uint32_t PlsrParamReadDWord(uint16_t addr) static uint32_t PlsrParamReadDWord(uint16_t addr)
{ {
uint16_t low = ReadHoldReg(addr); uint16_t low = ReadHoldReg(addr);
@@ -115,17 +69,30 @@ static uint32_t PlsrParamReadDWord(uint16_t addr)
return ((uint32_t)high << 16) | (uint32_t)low; return ((uint32_t)high << 16) | (uint32_t)low;
} }


static void PlsrParamWriteDWord(uint16_t addr, uint32_t value)
{
WriteHoldReg(addr, (uint16_t)(value & 0xFFFFU));
WriteHoldReg((uint16_t)(addr + 1U), (uint16_t)((value >> 16) & 0xFFFFU));
}

static int32_t PlsrParamReadSignedDWord(uint16_t addr)
/**
* @brief 判断本次 FC10 是否为公共帧 2~5
* @return 1=是;0=否
*/
static uint8_t PlsrParamIsCommonTailFrameWrite(uint16_t start_addr, uint16_t quantity)
{ {
return (int32_t)PlsrParamReadDWord(addr);
if (quantity < PLSR_PARAM_COMMON_DWORD_WORDS)
{
return 0U;
}
if ((start_addr == PLSR_REG_COMMON_F2_BASE) ||
(start_addr == PLSR_REG_COMMON_F3_BASE) ||
(start_addr == PLSR_REG_COMMON_F4_BASE) ||
(start_addr == PLSR_REG_COMMON_F5_BASE))
{
return 1U;
}
return 0U;
} }


/**
* @brief 判断本次 FC10 是否为段参数
* @return 1=是段帧;0=否
*/
static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity) static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity)
{ {
uint16_t i; uint16_t i;
@@ -146,132 +113,185 @@ static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity)
return 0U; return 0U;
} }


static void PlsrParamSetXferStatus(uint16_t st, uint16_t fail_frame)
/*============================================================================*/
/* 参数校验辅助 */
/*============================================================================*/

#define PLSR_PARAM_SPD_MIN_HZ 1U
#define PLSR_PARAM_SPD_MAX_HZ 100000U

/** 速度 Hz 是否落在 [1, 100k](默认速度/段频非 0) */
static uint8_t PlsrParamIsSpeedHzValid(uint32_t hz)
{
return ((hz >= PLSR_PARAM_SPD_MIN_HZ) && (hz <= PLSR_PARAM_SPD_MAX_HZ)) ? 1U : 0U;
}

/**
* 起速/止速:0 合法;
* 非 0 须在 1~100kHz。
*/
static uint8_t PlsrParamIsStartEndSpeedValid(uint32_t hz)
{ {
WriteHoldReg(PLSR_PARAM_XFER_STATUS_REG, st);
WriteHoldReg(PLSR_PARAM_XFER_FAIL_FRAME_REG, fail_frame);
WriteHoldReg(PLSR_PARAM_XFER_RECV_REG, s_recv_frames);
if (hz == 0U)
{
return 1U;
}
return PlsrParamIsSpeedHzValid(hz);
} }


/** 段频率:0=用默认速度;非 0 须在 1~100kHz */
static uint8_t PlsrParamIsSegFreqValid(int32_t freq_hz)
{
if (freq_hz == 0)
{
return 1U;
}
if (freq_hz < 0)
{
return 0U;
}
return PlsrParamIsSpeedHzValid((uint32_t)freq_hz);
}

/** 脉冲个数 SDW:须在 int32 可安全取绝对值的范围内(排除 INT_MIN) */
static uint8_t PlsrParamIsPulseCntValid(int32_t pulse_cnt)
{
/* INT_MIN 无法安全取反成正的目标脉冲数,报 0x05 */
if (pulse_cnt == (int32_t)(-2147483647L - 1L))
{
return 0U;
}
return 1U;
}

/*============================================================================*/
/* 分帧导入 / 导出 */
/*============================================================================*/

/**
* @brief 保持寄存器 → 运行映像,边拷贝边校验
*
* @details 校验失败时仍完成拷贝,故障码写入 0x2006;
* 多项非法时仅保留校验过程中最后一个故障码。
* @return 1=全部通过;0=存在门禁故障(不落存BKP)
*/
uint8_t PlsrParamBlockApplyFromHold(void) uint8_t PlsrParamBlockApplyFromHold(void)
{ {
uint16_t i; uint16_t i;
uint16_t n; uint16_t n;
uint16_t base; uint16_t base;
uint16_t m;
PlsrSeg_t *seg;

/*
* 从保持寄存器组装运行映像。
* TODO:段数/频率/脉冲/端子冲突/跳转非法 → 故障码(当前仅裁剪)
*/
s_cfg.pulse_y = ReadHoldReg(PLSR_REG_PULSE_Y);
if (s_cfg.pulse_y > 2U)
uint16_t fault = PLSR_ERR_NONE;

g_plsr_config.pulse_y = ReadHoldReg(PLSR_REG_PULSE_Y); /* 脉冲端子 Y0~Y2 */
if (g_plsr_config.pulse_y > 2U)
{ {
s_cfg.pulse_y = 0U;
fault = PLSR_ERR_PULSE_Y; /* 0x07:端子非法 */
} }
s_cfg.dir_y = 3U; /* 方向固定 Y3,忽略上位机其它值 */
WriteHoldReg(PLSR_REG_DIR_Y, 3U);

s_cfg.wait_x_sel = ReadHoldReg(PLSR_REG_WAIT_X) ? 1U : 0U;
s_cfg.ext_x_sel = ReadHoldReg(PLSR_REG_EXT_X) ? 1U : 0U;
s_cfg.send_mode = (PlsrSendMode_e)(ReadHoldReg(PLSR_REG_SEND_MODE) ? 1U : 0U);
s_cfg.dir_delay_ms = ReadHoldReg(PLSR_REG_DIR_DELAY);
s_cfg.dir_logic = (ReadHoldReg(PLSR_REG_DIR_LOGIC) != 0U) ?
PLSR_DIR_LOGIC_NEG : PLSR_DIR_LOGIC_POS;

m = ReadHoldReg(PLSR_REG_ACCEL_MODE);
if (m > (uint16_t)PLSR_ACCEL_SINE)

g_plsr_config.dir_y = ReadHoldReg(PLSR_REG_DIR_Y); /* 方向端子须为 Y3 */
if (g_plsr_config.dir_y != 3U)
{ {
m = (uint16_t)PLSR_ACCEL_LINEAR;
fault = PLSR_ERR_DIR_Y; /* 0x08 */
} }
s_cfg.accel_mode = (PlsrAccelMode_e)m;
s_cfg.run_mode = (ReadHoldReg(PLSR_REG_RUN_MODE) != 0U) ?
PLSR_POS_ABSOLUTE : PLSR_POS_RELATIVE;

g_plsr_config.wait_x_sel = ReadHoldReg(PLSR_REG_WAIT_X) ? 1U : 0U; /* 0=X4,1=X5 */
g_plsr_config.ext_x_sel = ReadHoldReg(PLSR_REG_EXT_X) ? 1U : 0U; /* 0=X4,1=X5 */
g_plsr_config.send_mode = (PlsrSendMode_e)(ReadHoldReg(PLSR_REG_SEND_MODE) ? 1U : 0U);
g_plsr_config.dir_delay_ms = ReadHoldReg(PLSR_REG_DIR_DELAY); /* 换向延时 ms */
g_plsr_config.dir_logic = (ReadHoldReg(PLSR_REG_DIR_LOGIC) != 0U) ?
PLSR_DIR_LOGIC_NEG : PLSR_DIR_LOGIC_POS; /* 方向电平逻辑 */
g_plsr_config.accel_mode = (PlsrAccelMode_e)ReadHoldReg(PLSR_REG_ACCEL_MODE);
g_plsr_config.run_mode = (ReadHoldReg(PLSR_REG_RUN_MODE) != 0U) ?
PLSR_POS_ABSOLUTE : PLSR_POS_RELATIVE; /* 绝对/相对 */


n = ReadHoldReg(PLSR_REG_SEG_COUNT); n = ReadHoldReg(PLSR_REG_SEG_COUNT);
if (n > PLSR_SEG_MAX)
g_plsr_config.seg_count = n;
if ((n < 1U) || (n > PLSR_SEG_MAX))
{ {
n = (uint16_t)PLSR_SEG_MAX;
fault = PLSR_ERR_SEG_COUNT; /* 0x09:段数越界 */
} }
s_cfg.seg_count = n;


s_cfg.start_seg = ReadHoldReg(PLSR_REG_START_SEG);
if (s_cfg.start_seg < 1U)
g_plsr_config.start_seg = ReadHoldReg(PLSR_REG_START_SEG); /* 1-based 启动段 */
if ((n >= 1U) && (n <= PLSR_SEG_MAX))
{
if ((g_plsr_config.start_seg < 1U) || (g_plsr_config.start_seg > n))
{
fault = PLSR_ERR_START_SEG; /* 0x0A:启动段越界 */
}
}
else if (g_plsr_config.start_seg < 1U)
{ {
s_cfg.start_seg = 1U;
fault = PLSR_ERR_START_SEG;
} }
if ((n >= 1U) && (s_cfg.start_seg > n))

g_plsr_config.default_speed = PlsrParamReadDWord(PLSR_REG_DEFAULT_SPD_L);
if (PlsrParamIsSpeedHzValid(g_plsr_config.default_speed) == 0U)
{ {
s_cfg.start_seg = n;
fault = PLSR_ERR_DEFAULT_SPD; /* 0x0B:默认速度非法 */
} }


s_cfg.default_speed = PlsrParamReadDWord(PLSR_REG_DEFAULT_SPD_L);
s_cfg.start_speed = PlsrParamReadDWord(PLSR_REG_START_SPD_L);
s_cfg.end_speed = PlsrParamReadDWord(PLSR_REG_END_SPD_L);
s_cfg.accel_ms = ReadHoldReg(PLSR_REG_ACCEL_MS);
s_cfg.decel_ms = ReadHoldReg(PLSR_REG_DECEL_MS);
g_plsr_config.start_speed = PlsrParamReadDWord(PLSR_REG_START_SPD_L); /* 起跳频,0 合法 */
if (PlsrParamIsStartEndSpeedValid(g_plsr_config.start_speed) == 0U)
{
fault = PLSR_ERR_START_END_SPD;
}


for (i = 0U; i < n; i++)
g_plsr_config.end_speed = PlsrParamReadDWord(PLSR_REG_END_SPD_L); /* 止速,0 合法 */
if (PlsrParamIsStartEndSpeedValid(g_plsr_config.end_speed) == 0U)
{ {
seg = &s_seg[i];
base = (uint16_t)(PLSR_REG_SEG1_BASE + i * PLSR_SEG_STRIDE);
seg->freq_hz = PlsrParamReadSignedDWord((uint16_t)(base + PLSR_SEG_OFF_FREQ_L));
seg->pulse_cnt = PlsrParamReadSignedDWord((uint16_t)(base + PLSR_SEG_OFF_PULSE_L));
fault = PLSR_ERR_START_END_SPD;
}

g_plsr_config.accel_ms = ReadHoldReg(PLSR_REG_ACCEL_MS); /* 加速参考时间 */
g_plsr_config.decel_ms = ReadHoldReg(PLSR_REG_DECEL_MS); /* 减速参考时间 */

if ((n >= 1U) && (n <= PLSR_SEG_MAX))
{
for (i = 0U; i < n; i++)
{ {
uint16_t w = ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT));
if (w > 4U)
base = (uint16_t)(PLSR_REG_SEG1_BASE + i * PLSR_SEG_STRIDE);
g_plsr_segs[i].freq_hz =
(int32_t)PlsrParamReadDWord((uint16_t)(base + PLSR_SEG_OFF_FREQ_L));
if (PlsrParamIsSegFreqValid(g_plsr_segs[i].freq_hz) == 0U)
{
fault = PLSR_ERR_FREQ_ILLEGAL; /* 0x04:段频率非法 */
}

g_plsr_segs[i].pulse_cnt =
(int32_t)PlsrParamReadDWord((uint16_t)(base + PLSR_SEG_OFF_PULSE_L));
if (PlsrParamIsPulseCntValid(g_plsr_segs[i].pulse_cnt) == 0U)
{ {
w = 0U;
fault = PLSR_ERR_PULSE_RANGE; /* 0x05:脉冲数 INT_MIN */
} }
seg->wait_type = (PlsrWaitType_e)w;

g_plsr_segs[i].wait_type =
(PlsrWaitType_e)ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT));
g_plsr_segs[i].wait_ms =
ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT_MS)); /* 段后等待 ms */
g_plsr_segs[i].act_ms =
ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_ACT_MS)); /* ACT 定时 ms */
g_plsr_segs[i].jump_seg =
ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_JUMP)); /* 跳转目标段 */
} }
seg->wait_ms = ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT_MS));
seg->act_ms = ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_ACT_MS));
seg->jump_seg = ReadHoldReg((uint16_t)(base + PLSR_SEG_OFF_JUMP));
} }


PlsrPersistNotifyCommonChanged();
return 1U;
}

void PlsrParamBlockExportToHold(void)
{
uint16_t i;
uint16_t base;
PlsrSeg_t *seg;

WriteHoldReg(PLSR_REG_PULSE_Y, s_cfg.pulse_y);
WriteHoldReg(PLSR_REG_DIR_Y, s_cfg.dir_y);
WriteHoldReg(PLSR_REG_WAIT_X, s_cfg.wait_x_sel);
WriteHoldReg(PLSR_REG_EXT_X, s_cfg.ext_x_sel);
WriteHoldReg(PLSR_REG_SEND_MODE, (uint16_t)s_cfg.send_mode);
WriteHoldReg(PLSR_REG_DIR_DELAY, s_cfg.dir_delay_ms);
WriteHoldReg(PLSR_REG_DIR_LOGIC, (uint16_t)s_cfg.dir_logic);
WriteHoldReg(PLSR_REG_ACCEL_MODE, (uint16_t)s_cfg.accel_mode);
WriteHoldReg(PLSR_REG_RUN_MODE, (uint16_t)s_cfg.run_mode);
WriteHoldReg(PLSR_REG_SEG_COUNT, s_cfg.seg_count);
WriteHoldReg(PLSR_REG_START_SEG, s_cfg.start_seg);
PlsrParamWriteDWord(PLSR_REG_DEFAULT_SPD_L, s_cfg.default_speed);
PlsrParamWriteDWord(PLSR_REG_START_SPD_L, s_cfg.start_speed);
WriteHoldReg(0x100FU, 0U);
PlsrParamWriteDWord(PLSR_REG_END_SPD_L, s_cfg.end_speed);
WriteHoldReg(PLSR_REG_ACCEL_MS, s_cfg.accel_ms);
WriteHoldReg(PLSR_REG_DECEL_MS, s_cfg.decel_ms);
PlsrCommandSetFault(fault); /* 写 0x2006 镜像 */


for (i = 0U; i < PLSR_SEG_MAX; i++)
if (fault == PLSR_ERR_NONE)
{ {
seg = &s_seg[i];
base = (uint16_t)(PLSR_REG_SEG1_BASE + i * PLSR_SEG_STRIDE);
PlsrParamWriteDWord((uint16_t)(base + PLSR_SEG_OFF_FREQ_L), (uint32_t)seg->freq_hz);
PlsrParamWriteDWord((uint16_t)(base + PLSR_SEG_OFF_PULSE_L), (uint32_t)seg->pulse_cnt);
WriteHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT), (uint16_t)seg->wait_type);
WriteHoldReg((uint16_t)(base + PLSR_SEG_OFF_WAIT_MS), seg->wait_ms);
WriteHoldReg((uint16_t)(base + PLSR_SEG_OFF_ACT_MS), seg->act_ms);
WriteHoldReg((uint16_t)(base + PLSR_SEG_OFF_JUMP), seg->jump_seg);
return 1U; /* 可落存 BKP */
} }
return 0U;
} }


/**
* @brief 统计接受帧数,收满释放信号量
*
* 公共帧1@0x1000~0x100A,expect=5+N,recv=1
* 传输中公共帧2~5 / 段帧 → recv++
* recv>=expect → 清 active,Post 信号量
* 运行中只写频率双字不走本路径
*/
void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity) void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity)
{ {
if (quantity == 0U) if (quantity == 0U)
@@ -279,69 +299,62 @@ void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity)
return; return;
} }


/*
* 公共帧:起始 0x1000 且覆盖到 0x1013 → 开启传输,期望帧=1+段数。
* 段帧:传输中且写到某段基址 → 帧计数+1。
* 收齐后 post 信号量,由 ParamBlockTask 执行 Apply。
*/
if ((start_addr == PLSR_REG_PULSE_Y) &&
(quantity >= (uint16_t)(PLSR_REG_DECEL_MS - PLSR_REG_PULSE_Y + 1U)))
if ((start_addr == PLSR_REG_COMMON_F1_BASE) &&
(quantity >= PLSR_PARAM_COMMON_FRAME1_WORDS))
{ {
/* 公共帧 1:开新一批,期望 5+段数 */
uint16_t seg_n = ReadHoldReg(PLSR_REG_SEG_COUNT); uint16_t seg_n = ReadHoldReg(PLSR_REG_SEG_COUNT);
if (seg_n > PLSR_SEG_MAX) if (seg_n > PLSR_SEG_MAX)
{ {
seg_n = (uint16_t)PLSR_SEG_MAX;
seg_n = (uint16_t)PLSR_SEG_MAX; /* 计数软钳,避免 expect 溢出 */
} }
s_expect_frames = (uint16_t)(1U + seg_n);
s_recv_frames = 1U;
s_xfer_active = 1U;
PlsrParamSetXferStatus(PLSR_PARAM_XFER_BUSY, 0U);
s_expect_frames = (uint16_t)(PLSR_PARAM_COMMON_FRAMES + seg_n);
s_recv_frames = 1U; /* 帧 1 已计入 */
s_xfer_active = 1U; /* 开始收批 */
} }
else if ((s_xfer_active != 0U) && else if ((s_xfer_active != 0U) &&
(PlsrParamIsSegFrameWrite(start_addr, quantity) != 0U))
((PlsrParamIsCommonTailFrameWrite(start_addr, quantity) != 0U) ||
(PlsrParamIsSegFrameWrite(start_addr, quantity) != 0U)))
{ {
s_recv_frames++;
WriteHoldReg(PLSR_PARAM_XFER_RECV_REG, s_recv_frames);
s_recv_frames++; /* 公共帧 2~5 或段帧 +1 */
} }
else else
{ {
return;
return; /* 非本批帧 / 未开批 → 忽略 */
} }


if ((s_xfer_active != 0U) && (s_recv_frames >= s_expect_frames)) if ((s_xfer_active != 0U) && (s_recv_frames >= s_expect_frames))
{ {
s_xfer_active = 0U;
s_xfer_active = 0U; /* 收齐 */
if (s_param_sem != (OS_EVENT *)0) if (s_param_sem != (OS_EVENT *)0)
{ {
(void)OSSemPost(s_param_sem);
}
else if (PlsrParamBlockApplyFromHold() != 0U)
{
PlsrParamSetXferStatus(PLSR_PARAM_XFER_OK, 0U);
}
else
{
PlsrParamSetXferStatus(PLSR_PARAM_XFER_FAIL, s_recv_frames);
(void)OSSemPost(s_param_sem); /* 唤醒 ParamBlockTask */
} }
} }
} }


/** @brief 创建信号量并置清空接收计数 */
void PlsrParamBlockInit(void) void PlsrParamBlockInit(void)
{ {
s_param_sem = OSSemCreate(0U); s_param_sem = OSSemCreate(0U);
s_expect_frames = 0U; s_expect_frames = 0U;
s_recv_frames = 0U; s_recv_frames = 0U;
s_xfer_active = 0U; s_xfer_active = 0U;
PlsrParamSetXferStatus(PLSR_PARAM_XFER_IDLE, 0U);
} }


/**
* @brief 参数块任务
*
* OSSemPend → ApplyFromHold(校验+写 0x2006)→ 通过则 SaveParamsFromHold。
* 校验失败:不存 BKP,hold 保留上位机原值,由故障码提示修正。
*/
void PlsrParamBlockTask(void *pArg) void PlsrParamBlockTask(void *pArg)
{ {
INT8U err; INT8U err;


(void)pArg; (void)pArg;


for (;;)
while(1)
{ {
(void)OSSemPend(s_param_sem, 0U, &err); (void)OSSemPend(s_param_sem, 0U, &err);
if (err != OS_ERR_NONE) if (err != OS_ERR_NONE)
@@ -351,12 +364,7 @@ void PlsrParamBlockTask(void *pArg)


if (PlsrParamBlockApplyFromHold() != 0U) if (PlsrParamBlockApplyFromHold() != 0U)
{ {
PlsrParamSetXferStatus(PLSR_PARAM_XFER_OK, 0U);
PlsrPersistPoll(); /* 分帧收齐后尽快落盘,不等 PlsrTask */
}
else
{
PlsrParamSetXferStatus(PLSR_PARAM_XFER_FAIL, s_recv_frames);
PlsrPersistSaveParamsFromHold(); /* 仅全部校验通过才存 BKP */
} }
} }
} }

+ 143
- 73
plsr/param/plsr_param.h View File

@@ -1,140 +1,210 @@
/** /**
* @file plsr_param.h * @file plsr_param.h
* @brief 参数块管理模块:寄存器地址、运行结构体、分帧导入/导出
* @brief 参数块管理:保持寄存器映射、运行映像结构体、分帧导入/导出
* *
* 协议概要:
* - 公共帧:0x1000~0x1013(20 字),0x1009=段数 N,预期总帧=1+N
* - 段帧:0x1100+(i)*0x10,每段 8 字
* - 传输状态:0x0FFC/0x0FFD/0x0FFE
* @details 模块职责
* 1. 定义公共参数区 0x1000~0x1013、段表区 0x1100+i*0x10 的地址与字段语义;
* 2. 维护 g_plsr_config / g_plsr_segs[];
* 3. 支持上位机「5 公共帧 + N 段帧」分帧 FC10 写入:收齐后 ApplyFromHold;
* 4. 上电由 PersistLoad 填 hold;ExportToHold 仅保留作调试接口。
*
* 与其它模块关系
* - modbus_rtu / modbus_data :ReadHoldReg / WriteHoldReg
* - plsr_command :启动前 ApplyFromHold;运行中修改当前段 freq_hz
* - plsr_run_control :GetCfg / GetSeg / GetSegCount / GetStartSeg
* - plsr_persist :拷贝成功后存入BKP;动态改频存频率进BKP
* - PlsrParamBlockTask :独立 uC/OS 任务,等信号量后 Apply + 整包存 BKP
*
* 关键数据流
* 上位机写公共帧1@0x1000 → OnHoldWrite 开传输 expect=5+N,recv=1
* 再写公共帧2~5 → recv++;写段帧@0x1100+ → recv++;收齐 → OSSemPost
* ParamBlockTask → ApplyFromHold(校验)→ 通过则 SaveParamsFromHold → 状态 OK/FAIL
* 上电:PersistLoad(BKP→hold) → ApplyFromHold(hold→结构体)
*
* 协议概要
* - 公共 5 帧:
* 1: 0x1000~0x100A(11 字)
* 2: 0x100B~0x100C(默认速度)
* 3: 0x100D~0x100E(起速)
* 4: 0x1010~0x1011(止速;跳过 0x100F)
* 5: 0x1012~0x1013(加减速时间)
* - 段帧:0x1100+(i)*0x10,每段 8 字
* - 预期总帧 = PLSR_PARAM_COMMON_FRAMES + N
* - 传输状态:0x0FFC 已收帧 / 0x0FFD 失败帧 / 0x0FFE 状态
*/ */
#ifndef PLSR_PARAM_H #ifndef PLSR_PARAM_H
#define PLSR_PARAM_H #define PLSR_PARAM_H


#include <stdint.h> #include <stdint.h>


/** 段表最大段数;与上位机 PLSR_SEG_MAX、寄存器布局一致 */
#define PLSR_SEG_MAX 10U #define PLSR_SEG_MAX 10U


/* ---------- 公共参数保持寄存器 ---------- */
#define PLSR_REG_PULSE_Y 0x1000U /* 0=Y0 1=Y1 2=Y2 */
#define PLSR_REG_DIR_Y 0x1001U /* 固定写 3=Y3 */
#define PLSR_REG_WAIT_X 0x1002U /* WAIT 输入选择 */
#define PLSR_REG_EXT_X 0x1003U /* EXT 输入选择 */
#define PLSR_REG_SEND_MODE 0x1004U /* 0完成 1后续 */
/* ---------- 公共参数保持寄存器(0x1000 起,共 20 字到 0x1013) ---------- */
#define PLSR_REG_PULSE_Y 0x1000U /* 脉冲端子:0=Y0 1=Y1 2=Y2 */
#define PLSR_REG_DIR_Y 0x1001U /* 方向端子:固件强制 3=Y3 */
#define PLSR_REG_WAIT_X 0x1002U /* WAIT 输入选择:0=X4 1=X5 */
#define PLSR_REG_EXT_X 0x1003U /* EXT 输入选择:0=X4 1=X5 */
#define PLSR_REG_SEND_MODE 0x1004U /* 发送方式:0完成 1后续 */
#define PLSR_REG_DIR_DELAY 0x1005U /* 方向建立延时 ms */ #define PLSR_REG_DIR_DELAY 0x1005U /* 方向建立延时 ms */
#define PLSR_REG_DIR_LOGIC 0x1006U /* 0正逻辑 1负逻辑 */
#define PLSR_REG_ACCEL_MODE 0x1007U /* 0直线 1-S 2正弦 */
#define PLSR_REG_RUN_MODE 0x1008U /* 0相对 1绝对 */
#define PLSR_REG_SEG_COUNT 0x1009U /* 脉冲总段数 N */
#define PLSR_REG_START_SEG 0x100AU /* 起始执行段 1-based */
#define PLSR_REG_DEFAULT_SPD_L 0x100BU /* 默认速度 DW 低 */
#define PLSR_REG_DIR_LOGIC 0x1006U /* 方向逻辑:0正 1负 */
#define PLSR_REG_ACCEL_MODE 0x1007U /* 曲线:0直线 1=S 2正弦 */
#define PLSR_REG_RUN_MODE 0x1008U /* 定位:0相对 1绝对 */
#define PLSR_REG_SEG_COUNT 0x1009U /* 脉冲总段数 N(决定 expect=5+N) */
#define PLSR_REG_START_SEG 0x100AU /* 启动段号 1-based */
#define PLSR_REG_DEFAULT_SPD_L 0x100BU /* 默认速度 Hz,双字低 */
#define PLSR_REG_DEFAULT_SPD_H 0x100CU #define PLSR_REG_DEFAULT_SPD_H 0x100CU
#define PLSR_REG_START_SPD_L 0x100DU /* 起速 DW */
#define PLSR_REG_START_SPD_L 0x100DU /* 起速 Hz */
#define PLSR_REG_START_SPD_H 0x100EU #define PLSR_REG_START_SPD_H 0x100EU
/* 0x100F 保留,写帧填 0 */ /* 0x100F 保留,写帧填 0 */
#define PLSR_REG_END_SPD_L 0x1010U /* 止速 DW */
#define PLSR_REG_END_SPD_L 0x1010U /* 止速 Hz */
#define PLSR_REG_END_SPD_H 0x1011U #define PLSR_REG_END_SPD_H 0x1011U
#define PLSR_REG_ACCEL_MS 0x1012U
#define PLSR_REG_DECEL_MS 0x1013U

/* ---------- 段参数:第 i 段基址 = 0x1100 + i*0x10 ---------- */
#define PLSR_REG_ACCEL_MS 0x1012U /* 默认速度加速时间 ms */
#define PLSR_REG_DECEL_MS 0x1013U /* 默认速度减速时间 ms */

/* ---------- 公共参数分帧(5 帧 + N 段帧) ---------- */
#define PLSR_PARAM_COMMON_FRAMES 5U
#define PLSR_PARAM_COMMON_FRAME1_WORDS 11U /* 0x1000~0x100A */
#define PLSR_PARAM_COMMON_DWORD_WORDS 2U /* 双字帧字数 */

#define PLSR_REG_COMMON_F1_BASE PLSR_REG_PULSE_Y /* 0x1000 */
#define PLSR_REG_COMMON_F1_END PLSR_REG_START_SEG /* 0x100A */
#define PLSR_REG_COMMON_F2_BASE PLSR_REG_DEFAULT_SPD_L /* 0x100B */
#define PLSR_REG_COMMON_F3_BASE PLSR_REG_START_SPD_L /* 0x100D */
#define PLSR_REG_COMMON_F4_BASE PLSR_REG_END_SPD_L /* 0x1010 */
#define PLSR_REG_COMMON_F5_BASE PLSR_REG_ACCEL_MS /* 0x1012 */

/* ---------- 段参数:第 i 段基址 = 0x1100 + i*0x10,每段 8 字 ---------- */
#define PLSR_REG_SEG1_BASE 0x1100U #define PLSR_REG_SEG1_BASE 0x1100U
#define PLSR_SEG_STRIDE 0x0010U #define PLSR_SEG_STRIDE 0x0010U


#define PLSR_SEG_OFF_FREQ_L 0U /* 频率 SDW */
#define PLSR_SEG_OFF_FREQ_L 0U /* 目标频率 SDW;0=用 default_speed */
#define PLSR_SEG_OFF_FREQ_H 1U #define PLSR_SEG_OFF_FREQ_H 1U
#define PLSR_SEG_OFF_PULSE_L 2U /* 脉冲数 SDW,负=反转 */
#define PLSR_SEG_OFF_PULSE_L 2U /* 脉冲数/绝对坐标 SDW,负=反转 */
#define PLSR_SEG_OFF_PULSE_H 3U #define PLSR_SEG_OFF_PULSE_H 3U
#define PLSR_SEG_OFF_WAIT 4U /* 等待类型 */
#define PLSR_SEG_OFF_WAIT_MS 5U
#define PLSR_SEG_OFF_ACT_MS 6U
#define PLSR_SEG_OFF_JUMP 7U /* 0=末段停 / 非末段接下一段;1~N=跳转目标段号 */

/* 分帧传输状态(上位机可轮询) */
#define PLSR_PARAM_XFER_RECV_REG 0x0FFCU
#define PLSR_PARAM_XFER_FAIL_FRAME_REG 0x0FFDU
#define PLSR_PARAM_XFER_STATUS_REG 0x0FFEU

#define PLSR_PARAM_XFER_IDLE 0U
#define PLSR_PARAM_XFER_BUSY 1U
#define PLSR_PARAM_XFER_OK 2U
#define PLSR_PARAM_XFER_FAIL 3U
#define PLSR_SEG_OFF_WAIT 4U /* PlsrWaitType_e */
#define PLSR_SEG_OFF_WAIT_MS 5U /* WAIT 时间 ms */
#define PLSR_SEG_OFF_ACT_MS 6U /* ACT 时间 ms */
#define PLSR_SEG_OFF_JUMP 7U /* 0=顺序/末段停;1~N=跳转目标段号 */


/** 方向逻辑:正转时方向脚电平 */
typedef enum { typedef enum {
PLSR_DIR_LOGIC_POS = 0, /* 正转→方向高,反转→低 */ PLSR_DIR_LOGIC_POS = 0, /* 正转→方向高,反转→低 */
PLSR_DIR_LOGIC_NEG = 1 /* 相反 */ PLSR_DIR_LOGIC_NEG = 1 /* 相反 */
} PlsrDirLogic_e; } PlsrDirLogic_e;


/**
* 发送方式(与等待类型一起决定本段 f_end,并影响是否允许同向不停表衔接)
* COMPLETE:EXT_OR_DONE 同向时匀速跑完;反向或其它等待类型规划到止速
* FOLLOW:仅 EXT_OR_DONE 同向可规划到下一段目标并允许不停表衔接;反向同其它等待
*/
typedef enum { typedef enum {
PLSR_SEND_COMPLETE = 0, /* 完成:上一段发完后,本段开头再加减速到本段频率 */
PLSR_SEND_FOLLOW = 1 /* 后续:上一段发完时频率已到本段目标;同向可不停表 */
PLSR_SEND_COMPLETE = 0,
PLSR_SEND_FOLLOW = 1
} PlsrSendMode_e; } PlsrSendMode_e;


/** 加减速曲线形状 */
typedef enum { typedef enum {
PLSR_ACCEL_LINEAR = 0,
PLSR_ACCEL_S = 1,
PLSR_ACCEL_SINE = 2
PLSR_ACCEL_LINEAR = 0, /* f^2 = f0^2 ± 2an */
PLSR_ACCEL_S = 1, /* 时间域七段 S:加速度梯形 1:2:1 */
PLSR_ACCEL_SINE = 2 /* 时间域 raised-cosine */
} PlsrAccelMode_e; } PlsrAccelMode_e;


/**
* 定位模式
* RELATIVE:pulse_cnt = 本段相对位移(可负)
* ABSOLUTE:pulse_cnt = 相对「本次启动原点」的目标坐标
*/
typedef enum { typedef enum {
PLSR_POS_RELATIVE = 0, /* 脉冲数=相对位移 */
PLSR_POS_ABSOLUTE = 1 /* 脉冲数=绝对坐标 */
PLSR_POS_RELATIVE = 0,
PLSR_POS_ABSOLUTE = 1
} PlsrPosMode_e; } PlsrPosMode_e;


/**
* 段末/段内等待类型
* TIME/SIGNAL/ACT/EXT:完成/后续均规划到止速;并阻止 FOLLOW 不停表衔接
* EXT_OR_DONE:与下一段同向时后续可接目标/完成匀速跑完并可 FOLLOW 衔接;
* 与下一段反向时规划同其它等待(减速到止速,下一段从起速再爬)
*/
typedef enum { typedef enum {
PLSR_WAIT_TIME = 0, /* 发完后等 wait_ms,再跳转 */
PLSR_WAIT_SIGNAL = 1, /* 发完后等 WAIT 口下降沿(EXTI),再跳转 */
PLSR_WAIT_ACT = 2, /* ACT:满 act_ms 从当前频率切下一段;脉冲先完则等到点 */
PLSR_WAIT_EXT = 3, /* 运行中 EXT 下降沿中途切段;发完后仍等 EXT */
PLSR_WAIT_EXT_OR_DONE = 4 /* EXT 沿或脉冲发完,先到先切段 */
PLSR_WAIT_TIME = 0, /* 发完后等 wait_ms(0 按 1ms),再跳转 */
PLSR_WAIT_SIGNAL = 1, /* 发完后等 WAIT 口下降沿 */
PLSR_WAIT_ACT = 2, /* 开跑后满 act_ms 从当前频切下一段;脉冲先完则等到点 */
PLSR_WAIT_EXT = 3, /* 运行中 EXT 沿中途切段;发完后仍等 EXT */
PLSR_WAIT_EXT_OR_DONE = 4 /* EXT 沿或脉冲发完,先到先切 */
} PlsrWaitType_e; } PlsrWaitType_e;


/** 公共运行参数(运动侧只读此映像,不直接啃寄存器) */
/**
* 公共运行参数映像
*/
typedef struct { typedef struct {
uint16_t pulse_y; /* 0=Y0/PF6/TIM10 1=Y1/PF8/TIM13 2=Y2/PF7/TIM11 */
uint16_t dir_y; /* 固定 3=Y3,不可选 */
uint16_t wait_x_sel;
uint16_t pulse_y; /* 0=Y0/TIM10 1=Y1/TIM13 2=Y2/TIM11 */
uint16_t dir_y; /* 固定 3=Y3 */
uint16_t wait_x_sel; /* 0=X4 1=X5 */
uint16_t ext_x_sel; uint16_t ext_x_sel;
PlsrSendMode_e send_mode; PlsrSendMode_e send_mode;
uint16_t dir_delay_ms; uint16_t dir_delay_ms;
PlsrDirLogic_e dir_logic; PlsrDirLogic_e dir_logic;
PlsrAccelMode_e accel_mode; PlsrAccelMode_e accel_mode;
PlsrPosMode_e run_mode; PlsrPosMode_e run_mode;
uint16_t seg_count;
uint16_t seg_count; /* 有效段数 0~PLSR_SEG_MAX */
uint16_t start_seg; /* 1-based */ uint16_t start_seg; /* 1-based */
uint32_t default_speed; /* 脉冲默认速度 Hz:斜率 K=默认速度/加减速时间;段频为 0 时作段目标 */
uint32_t start_speed; /* 起速Hz;0=从静止起跳(运行层 ResolveStartHz) */
uint32_t end_speed; /* 止速Hz;0=落地频率后停表(运行层 ResolveEndHz) */
uint16_t accel_ms; /* 默认速度加速时间 ms(定加速斜率) */
uint16_t decel_ms; /* 默认速度减速时间 ms(定减速斜率) */
uint32_t default_speed; /* Hz;段频为 0 时作段目标;并定斜率 */
uint32_t start_speed; /* 起速;0=运行层 ResolveStartHz 起跳 */
uint32_t end_speed; /* 止速;0=ResolveEndHz 落地后停表 */
uint16_t accel_ms;
uint16_t decel_ms;
} PlsrCfg_t; } PlsrCfg_t;


/** 单段运行参数 */ /** 单段运行参数 */
typedef struct { typedef struct {
int32_t freq_hz; /* 0=用默认速度;须解析后在 1~100k,否则故障 */
int32_t freq_hz; /* 0=用默认速度;解析后须在 1~100k,否则 0x04 */
int32_t pulse_cnt; /* 可负=反向;0=本段不发 */ int32_t pulse_cnt; /* 可负=反向;0=本段不发 */
PlsrWaitType_e wait_type; PlsrWaitType_e wait_type;
uint16_t wait_ms; uint16_t wait_ms;
uint16_t act_ms; uint16_t act_ms;
uint16_t jump_seg; /* 0=末段结束 / 非末段则顺序下一段;1~N=跳转目标段号 */
uint16_t jump_seg; /* 0=顺序/末段结束;1~N=跳转 */
} PlsrSeg_t; } PlsrSeg_t;


/* ---------- 运行映像访问 ---------- */
void PlsrParamInitDefault(void);
PlsrCfg_t *PlsrParamGetCfg(void);
PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based);
/* ---------- 运行参数 ---------- */
extern PlsrCfg_t g_plsr_config;
extern PlsrSeg_t g_plsr_segs[PLSR_SEG_MAX];

/** @return 有效段数,钳制到 [0, PLSR_SEG_MAX] */
uint16_t PlsrParamGetSegCount(void); uint16_t PlsrParamGetSegCount(void);

/**
* @return 启动段 1-based,钳制到 [1, seg_count]
* @note 非法起始段当前仅裁剪,TODO 可报故障码
*/
uint16_t PlsrParamGetStartSeg(void); uint16_t PlsrParamGetStartSeg(void);


/* ---------- 分帧导入 / 导出 / 管理任务 ---------- */ /* ---------- 分帧导入 / 导出 / 管理任务 ---------- */
/** OSInit 之后调用,创建分帧应用信号量 */

/**
* @brief 创建分帧应用信号量,清传输状态
* @note 调用时机:OSInit 之后、任务创建前
*/
void PlsrParamBlockInit(void); void PlsrParamBlockInit(void);
/** 等信号量后 hold→结构体 */

/**
* @brief 参数块任务:阻塞等信号量 → ApplyFromHold(校验)→ 通过则 SaveParamsFromHold
* @param pArg 未使用
*/
void PlsrParamBlockTask(void *pArg); void PlsrParamBlockTask(void *pArg);
/** FC10 应答后由 Modbus 调用:分帧计数,收齐则 post 信号量 */

/**
* @brief FC10 写保持寄存器后由 Modbus 回调
* @param start_addr 起始地址
* @param quantity 字数
* @note 公共帧开传输;段帧累加;收齐 post 信号量
*/
void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity); void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity);
/** 寄存器→运行结构体;成功返回 1(完整校验/故障码待实现) */

/**
* @brief 保持寄存器 → g_plsr_config / g_plsr_segs,边拷贝边校验
* @return 1=全部通过;0=存在门禁故障
*/
uint8_t PlsrParamBlockApplyFromHold(void); uint8_t PlsrParamBlockApplyFromHold(void);
/** 运行结构体→寄存器(上电默认、上位机读回) */
void PlsrParamBlockExportToHold(void);


#endif #endif

+ 0
- 69
plsr/path_plan/plsr_path_plan.c View File

@@ -1,69 +0,0 @@
/**
* @file plsr_path_plan.c
* @brief 多段路径规划实现(只做决策,不碰 TIM/GPIO)
*/
#include "plsr_path_plan.h"
#include "plsr_param.h"

int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based)
{
PlsrSeg_t *seg = PlsrParamGetSeg(cur_seg_0based);
uint16_t n = PlsrParamGetSegCount();
uint16_t jump = seg->jump_seg;

/*
* 跳转目标(1-based):
* 0 → 末段:结束;非末段:顺序下一段(cur+1)
* 1..N → 跳到对应段(可顺序、回跳循环、任意段)
* 其它 → 结束
*/
if (jump == 0U)
{
/* 非末段写 0:视为顺序下一档,不是停机 */
if ((n >= 1U) && (cur_seg_0based + 1U < n))
{
return (int16_t)(cur_seg_0based + 1U);
}
return -1;
}
if ((jump >= 1U) && (jump <= n))
{
return (int16_t)(jump - 1U);
}
return -1;
}

uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse)
{
PlsrSeg_t *seg = PlsrParamGetSeg(seg_0based);
PlsrCfg_t *cfg = PlsrParamGetCfg();
int32_t move;

if (cfg->run_mode == PLSR_POS_ABSOLUTE)
{
move = seg->pulse_cnt - acc_pulse;
}
else
{
move = seg->pulse_cnt;
}
return (move >= 0) ? 1U : 0U;
}

uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based)
{
PlsrSeg_t *seg = PlsrParamGetSeg(seg_0based);

/* 仅段后 WAIT 时间;0 按 1ms(手册);信号/ACT/EXT 由 run_control 处理 */
if (seg->wait_type == PLSR_WAIT_TIME)
{
return (seg->wait_ms == 0U) ? 1U : seg->wait_ms;
}
(void)seg_0based;
return 0U;
}

int16_t PlsrPathPlanResolveAfterSeg(uint16_t cur_seg_0based)
{
return PlsrPathPlanNextSeg(cur_seg_0based);
}

+ 0
- 37
plsr/path_plan/plsr_path_plan.h View File

@@ -1,37 +0,0 @@
/**
* @file plsr_path_plan.h
* @brief 多段路径规划:段序、跳转、段间等待、相对/绝对方向判定
*
* 不直接驱动硬件;由运行控制查询后执行。
* 等待类型由 run_control + signal_io 执行(时间用 TIM3,信号用 EXTI)。
*/
#ifndef PLSR_PATH_PLAN_H
#define PLSR_PATH_PLAN_H

#include <stdint.h>

/**
* @brief 解析下一段索引
* @param cur_seg_0based 当前段 0-based
* @return 下一段 0-based;无下一段或非法跳转返回 -1
* @note jump=0:末段→结束;非末段→顺序下一段
* jump=1~N:跳到对应段(可循环)
*/
int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based);

/**
* @brief 判断该段运动方向
* @param acc_pulse 当前累计脉冲(绝对模式算位移用)
* @return 1=正向,0=反向
*/
uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse);

/**
* @brief 段结束后 WAIT 时间(ms);仅 WAIT_TIME 有效,0 按 1ms
*/
uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based);

/** 段结束(或等待结束)后解析下一段,语义同 NextSeg */
int16_t PlsrPathPlanResolveAfterSeg(uint16_t cur_seg_0based);

#endif

+ 55
- 90
plsr/plsr.c View File

@@ -1,6 +1,13 @@
/** /**
* @file plsr.c * @file plsr.c
* @brief PLSR 门面实现:转发到运行控制,并提供周期任务
* @brief PLSR 顶层初始化和周期任务
*
* @details 按顺序初始化各模块,以及运行 PlsrTask 周期任务。
*
* 关键数据流
* PlsrInit 顺序:PersistLoad(BKP→hold) → ApplyFromHold(hold→结构体) → …
* ModbusDataInit 清 hold 后:再次 PersistLoad(BKP→hold),不再 ExportToHold
* PlsrTask 循环:TickMs → DebouncePoll → CommandPoll → TickMonitor → WakePend
*/ */
#include "plsr.h" #include "plsr.h"
#include "plsr_pulse_driver.h" #include "plsr_pulse_driver.h"
@@ -11,117 +18,75 @@
#include "plsr_signal_io.h" #include "plsr_signal_io.h"
#include "ucos_ii.h" #include "ucos_ii.h"


/**
* @brief 子系统初始化
*
* 顺序要点
* PersistLoad → Apply:BKP → hold → 结构体(BKP 内参数已合法);
* 非上电不再把结构体 Export 回 hold;Modbus 清零后由 BKP 再填 hold。
*/
void PlsrInit(void) void PlsrInit(void)
{ {
PlsrParamInitDefault();
PlsrPersistLoad(); /* 有 Flash 记录则覆盖公共参数;段表仍用默认 */
PlsrPersistInit(); /* 使能 BKPSRAM 写访问,否则存BKP无效 */
PlsrPersistLoad(); /* BKPSRAM → g_hold_reg */
(void)PlsrParamBlockApplyFromHold(); /* hold → 运行参数 + 校验 */
PlsrPulseDriverInit(); PlsrPulseDriverInit();
PlsrSignalIoInit(); PlsrSignalIoInit();
PlsrRunControlInit(); PlsrRunControlInit();
PlsrParamBlockExportToHold(); /* 上电把运行映像摊到保持寄存器 */
PlsrPersistRestoreRuntime(); /* hold 0x2000 累计 → run_control */
PlsrCommandInit(); PlsrCommandInit();
PlsrPersistRestoreMonitor(); /* 恢复故障码;0x3000/运行态强制空闲 */
PlsrPersistTickMonitor(); /* 监控区 hold+BKP 与运行态对齐 */
} }


uint8_t PlsrStart(uint16_t start_seg)
{
if (start_seg == 0U)
{
start_seg = PlsrParamGetStartSeg();
}
return PlsrRunControlStart(start_seg);
}

void PlsrStop(void)
{
PlsrRunControlStop();
}

uint8_t PlsrChangeFreq(uint32_t new_tgt_hz)
{
return PlsrRunControlChangeFreq(new_tgt_hz);
}

uint8_t PlsrIsBusy(void)
{
return PlsrRunControlIsBusy();
}

int32_t PlsrGetAccPulse(void)
{
return PlsrRunControlGetAccPulse();
}

void PlsrClearAccPulse(void)
{
PlsrRunControlClearAccPulse();
}

void PlsrOnPulseIsr(void)
{
PlsrRunControlOnPulseIsr();
}

/**
* @brief PLSR 主周期任务
* @param pArg 未使用
*
* 任务内局部状态:
* s_booted — 首次延迟导出完成标志
* s_was_busy — 仅 LOOP_TEST:上一拍 busy,用于检测「刚结束」边沿
* s_idle_ticks — 仅 LOOP_TEST:空闲累计 tick,满 OS_TICKS_PER_SEC 自动再启
*/
void PlsrTask(void *pArg) void PlsrTask(void *pArg)
{ {
static uint8_t s_booted = 0U;
#if (PLSR_LOOP_TEST_EN != 0)
static uint8_t s_was_busy = 0U;
static uint16_t s_idle_ticks = 0U;
#endif
static uint8_t s_booted = 0U; /* 1=已完成首次延迟对齐(等 Modbus 填 hold) */


(void)pArg; (void)pArg;


for (;;)
PlsrRunControlWakeInit(); /* OS 已启动,创建 WAIT/ACT 到期唤醒信号量 */

while(1)
{ {
/* 首次进入:等 Modbus 清寄存器后再导出运行映像,避免被冲掉 */
/*
* 首次进入:ModbusSlaveTask 已 DataInit 清 hold 并 PersistLoad 填回 BKP 参数。
* 此处仅恢复监控/指令侧,不再 Export 结构体到 hold。
*/
if (s_booted == 0U) if (s_booted == 0U)
{ {
OSTimeDly(30U); /* ~300ms @ 100Hz tick */
PlsrParamBlockExportToHold();
OSTimeDly(3U); /* ~30ms @ 100Hz tick;让出 CPU 给 Modbus 完成初始化 */
PlsrCommandInit(); PlsrCommandInit();
PlsrPersistRestoreMonitor();
PlsrPersistTickMonitor();
s_booted = 1U; s_booted = 1U;
#if (PLSR_LOOP_TEST_EN != 0)
s_was_busy = 0U;
s_idle_ticks = 0U;
#endif
} }


PlsrCommandPoll(); /* 收 START/STOP 等,回写忙闲状态 */
PlsrPersistPoll(); /* 公共参数有改且空闲则追加写入 Flash */
PlsrRunControlTickMs(); /* 方向延时、段间等待到期处理 */
PlsrRunControlTickMs(); /* 处理延时/外部信号输入,优先:WAIT/换向到期立刻开表 */
PlsrSignalIoDebouncePoll(); /* OS 节拍消抖确认 */
PlsrCommandPoll(); /* 收 START/STOP/CLR,回写 0x2000~0x2006 监控 ,决定是否开pwm输出*/
PlsrPersistTickMonitor(); /* 监控区:运行态→hold→BKP(每 OS 节拍) */


#if (PLSR_LOOP_TEST_EN != 0)
/* 调试:空闲满 1s 自动从第 1 段再启 */
if (s_booted != 0U)
/*
* 换向 / WAIT 时间:WakePend(0) 只等 TIM5 Post(墙钟≈设定 ms)。
* 其它:超时 1 OS tick,兼顾 EXT 轮询。
*/
if (PlsrRunControlIsPreciseWait() != 0U)
{ {
uint8_t busy = PlsrIsBusy();

if ((s_was_busy != 0U) && (busy == 0U))
{
s_idle_ticks = 0U;
}

if (busy == 0U)
{
s_idle_ticks++;
if (s_idle_ticks >= (uint16_t)OS_TICKS_PER_SEC)
{
s_idle_ticks = 0U;
if (PlsrParamGetSegCount() >= 1U)
{
(void)PlsrStart(1U);
}
}
}
else
{
s_idle_ticks = 0U;
}

s_was_busy = busy;
PlsrRunControlWakePend(0U);
}
else
{
PlsrRunControlWakePend(1U);
} }
#endif

OSTimeDly(1U);
} }
} }

+ 54
- 18
plsr/plsr.h View File

@@ -1,47 +1,83 @@
/** /**
* @file plsr.h * @file plsr.h
* @brief PLSR 对外门面:初始化、周期任务、启停与状态查询
* @brief PLSR 多段脉冲输出子系统公共接口
* *
* 内部模块:参数块 / 公共参数持久化 / 指令接口 / 路径规划 / 加减速曲线 / 运行控制 / 脉冲驱动
* @details 模块职责
* 本文件只声明公共入口与共享运行状态。初始化和周期任务实现在 plsr.c;
* 启停和脉冲中断直接实现在 run_control.c,不再经过 plsr.c 转发。
*
* 与其它模块关系
* - plsr_param :运行参数映像(公共 + 段表),Modbus 保持寄存器 ↔ 结构体
* - plsr_persist :BKPSRAM 掉电保存(公共+段表、动态频率、监控区)
* - plsr_command :Modbus 监控区 0x2000~0x2006、控制区 0x3000 指令解析
* - plsr_run_control:段状态机、等待策略、加减速执行、故障诊断
* - plsr_pulse_driver:TIM PWM 脉冲 + Y3 方向 GPIO
* - plsr_signal_io :WAIT/EXT 外部输入、TIM5 延时
*
* 关键数据流
* 上电:PlsrInit → PersistLoad(BKP→hold) → ApplyFromHold → …
* →(ModbusDataInit 后再次 PersistLoad 填 hold)
* 写参:分帧收齐 → ParamBlockTask Apply(校验)→ 通过则 SaveParamsFromHold
*/ */
#ifndef PLSR_H #ifndef PLSR_H
#define PLSR_H #define PLSR_H


#include <stdint.h> #include <stdint.h>
#include "plsr_param.h"


/* 1=第1段跑完后停约1s再循环(仅调试用,正式产品保持 0) */
#ifndef PLSR_LOOP_TEST_EN
#define PLSR_LOOP_TEST_EN 0
#endif
/* run_control 拥有的共享运行状态;段下标从 0 开始。 */
extern volatile uint8_t g_plsr_busy; /* 运行忙标志(上位机监控) */
extern volatile uint16_t g_plsr_cur_seg_idx; /* 当前段下标(0-based) */
extern volatile int32_t g_plsr_accumulated_pulses; /* 全局累计脉冲(有符号) */
#include "plsr_param.h"


/** 初始化参数映像、脉冲驱动、运行控制、指令寄存器 */
/**
* @brief plsr任务初始化
*/
void PlsrInit(void); void PlsrInit(void);


/** uC/OS 任务:周期处理指令 + 运行控制节拍 */
/**
* @brief uC/OS-II 周期任务入口
* @param pArg 任务参数
*/
void PlsrTask(void *pArg); void PlsrTask(void *pArg);


/** /**
* @brief 启动多段脉冲
* @param start_seg 起始段号 1-based;传 0 则用参数块中的起始段
* @return 1=已启动,0=忙或无有效段
* @brief 启动多段脉冲序列
* @param start_seg 起始段号,1-based;传 0 则使用参数块中的 start_seg
* @return 1=已成功置忙并开段;0=当前忙、无有效段或首段频率非法(写故障码 0x04)
* @note 调用时机:应用层或 CommandPoll 收到 START 位后
* @note 副作用:清外部沿锁存、设置绝对坐标原点、进入 RC_DIR_WAIT 或 RC_RUN
*/ */
uint8_t PlsrStart(uint16_t start_seg); uint8_t PlsrStart(uint16_t start_seg);


/** 急停:关 PWM、清方向、退出运行 */
/**
* @brief 急停
* @note 调用时机:CommandPoll 收到 STOP 位,或应用层主动停止
* @note 副作用:关 PWM、解锁 PSC、清方向、复位运行状态机与 ACT/WAIT 标志
*/
void PlsrStop(void); void PlsrStop(void);


/** /**
* @brief 运行中动态改目标频率(当前输出频率 → new_tgt,按 accel_mode 过渡)
* @return 1=已重规划,0=非运行或频率非法
* @brief 运行中动态修改当前段目标频率
* @param new_tgt_hz 新目标频率 Hz,须在 [1, 100000]
* @return 1=已按剩余脉冲重规划;0=非 RC_RUN、频率非法或本段已发完
* @note 调用时机:CommandPoll 处理运行中 FC10 写频,或直接调用
* @note 副作用:以当前输出频率为起点、剩余脉冲为总长重新 PlanSeg;非法频率写 0x04
*/ */
uint8_t PlsrChangeFreq(uint32_t new_tgt_hz); uint8_t PlsrChangeFreq(uint32_t new_tgt_hz);


uint8_t PlsrIsBusy(void); /**< 1=运动中 */
int32_t PlsrGetAccPulse(void); /**< 累计脉冲(绝对坐标用) */
/**
* @brief 清零累计脉冲与绝对坐标原点
* @note 调用时机:CommandPoll 收到 CLR 控制位
* @note 同时清除累计脉冲和绝对模式原点,下次 Start 重新锁定原点
*/
void PlsrClearAccPulse(void); void PlsrClearAccPulse(void);


/** TIM UPDATE 中断入口(每发出一个脉冲调用一次) */
/**
* @brief 脉冲 TIM UPDATE 中断入口
* @note 调用时机:每发出一个脉冲边沿,由 stm32f4xx_it 中对应 TIM 回调转发
* @note 副作用:累计本段脉冲和坐标、逐拍改频、处理段末/ACT 边界
*/
void PlsrOnPulseIsr(void); void PlsrOnPulseIsr(void);


#endif #endif

+ 631
- 220
plsr/pulse_driver/plsr_pulse_driver.c
File diff suppressed because it is too large
View File


+ 109
- 13
plsr/pulse_driver/plsr_pulse_driver.h View File

@@ -1,15 +1,27 @@
/** /**
* @file plsr_pulse_driver.h * @file plsr_pulse_driver.h
* @brief 脉冲输出驱动(方案书 5.1):TIM 脉冲 + 方向 GPIO
* @brief 脉冲输出驱动(方案书 5.1):TIM PWM 脉冲 + 方向 GPIO
* *
* 脉冲端子(由参数 pulse_y 选择;端子编号与 TIM 编号交叉):
* @details 模块职责
* 按公共参数 pulse_y 选择 Y0/Y1/Y2 之一输出方波脉冲,方向固定走 Y3。
* 段内尽量锁定 PSC、只改 ARR,使加减速改频在周期边界生效,减少毛刺与多计边沿。
* ISR 热路径只写预装载 ARR/CCR;必须换 PSC 时挂起,由任务 ApplyPending 处理。
*
* 与其它模块关系
* - plsr_param :读 pulse_y / dir_logic
* - plsr_run_control :Start / SetFreq / SetFreqIsr / Stop / 方向 / PSC 锁
* - stm32f4xx_it :TIM UPDATE → PlsrOnPulseIsr;IsPulseTim 识别脉冲 TIM
*
* 端子与 TIM 映射(端子编号与 TIM 编号交叉,勿按序号假设)
* Y0 → PF6 / TIM10_CH1 AF3 (APB2 Timer 168MHz) * Y0 → PF6 / TIM10_CH1 AF3 (APB2 Timer 168MHz)
* Y1 → PF8 / TIM13_CH1 AF9 (APB1 Timer 84MHz) * Y1 → PF8 / TIM13_CH1 AF9 (APB1 Timer 84MHz)
* Y2 → PF7 / TIM11_CH1 AF3 (APB2 Timer 168MHz) * Y2 → PF7 / TIM11_CH1 AF3 (APB2 Timer 168MHz)
* 方向固定 Y3 → PF9 GPIO(不可选)
* Y3 → PF9 GPIO 方向(固定,不可选)
* *
* 频率 0:停止该路 PWM;>0 时按 Hz 输出。
* 段内锁定 PSC、只改 ARR;改频请求在脉冲周期边界生效,减少毛刺。
* 频率约定
* 0 :停止该路 PWM(空闲 Forced inactive 拉低)
* 1~100k:按 Hz 输出;超出由 Calc 路径钳到 100k
* 波形:空闲/开跑起点低;运行中 PWM1 每周期先高后低
*/ */
#ifndef PLSR_PULSE_DRIVER_H #ifndef PLSR_PULSE_DRIVER_H
#define PLSR_PULSE_DRIVER_H #define PLSR_PULSE_DRIVER_H
@@ -17,32 +29,116 @@
#include <stdint.h> #include <stdint.h>
#include "stm32f4xx_hal.h" #include "stm32f4xx_hal.h"


/** HAL 句柄:由本模块 Init 填充,中断向量里转发到 HAL_TIM_IRQHandler */
extern TIM_HandleTypeDef htim10; extern TIM_HandleTypeDef htim10;
extern TIM_HandleTypeDef htim11; extern TIM_HandleTypeDef htim11;
extern TIM_HandleTypeDef htim13; extern TIM_HandleTypeDef htim13;


/* 驱动拥有的真实输出状态,run_control 直接读取,不再保存第二份。 */
extern volatile uint32_t g_plsr_output_freq_hz; /* 当前输出频率 Hz */
extern volatile uint8_t g_plsr_pwm_running; /* 1=PWM 正在跑 */
extern uint8_t g_plsr_direction_forward; /* 1=正转方向语义 */
extern uint8_t g_plsr_direction_valid; /* 1=方向脚已有效写出 */

/**
* @brief 初始化三路脉冲 TIM + 方向 GPIO
* @note 调用时机:PlsrInit,在 RunControl 之前
* @note 副作用:Y0/Y1/Y2 空闲拉低、计数器关;Y3 输出低
*/
void PlsrPulseDriverInit(void); void PlsrPulseDriverInit(void);


/**
* @brief 按正/反向与 dir_logic 写 Y3 方向脚
* @param forward 1=正转语义,0=反转
*/
void PlsrPulseDriverSetDir(uint8_t forward); void PlsrPulseDriverSetDir(uint8_t forward);

/** @brief 方向脚拉低(收尾/急停) */
void PlsrPulseDriverClearDir(void); void PlsrPulseDriverClearDir(void);


/** 按本段 [f_min,f_max] 锁定 PSC,整段只改 ARR */
/**
* @brief 按本段频率跨度 [f_min,f_max] 尝试锁定单一 PSC
* @note 若单一 PSC 无法同时覆盖两端(如 1Hz~很高频),自动不锁,改由 SetFreq 自适应
* @note 后续同向不停表开段时禁止重锁(由 run_control 在 pwm_on 时跳过调用)
*/
void PlsrPulseDriverLockPscRange(uint32_t f_min_hz, uint32_t f_max_hz); void PlsrPulseDriverLockPscRange(uint32_t f_min_hz, uint32_t f_max_hz);

/** @brief 解除 PSC 锁定(FinishAll / Stop) */
void PlsrPulseDriverUnlockPsc(void); void PlsrPulseDriverUnlockPsc(void);


/**
* @brief 开沿顺序:ARR/CCR → CNT=0 → UG → CCxE → CEN(CEN 后立刻有效电平)
* @note 换向延时到期应在 TIM5 延时中断内调用;勿经任务调度再开表
*/
void PlsrPulseDriverStart(uint32_t freq_hz); void PlsrPulseDriverStart(uint32_t freq_hz);
void PlsrPulseDriverStartFreeRun(uint32_t freq_hz);

/**
* @brief 换向延时期间装载 ARR/CCR,Forced 低且 CEN 关
* @note 到期在 ISR 里 Start:CNT=0+UG+CEN,首沿对齐延时结束
*/
void PlsrPulseDriverPrepare(uint32_t freq_hz);

/**
* @brief 任务上下文改频:PSC 不变则预装载 ARR;须换 PSC 则停表重装
* @note 频率或量化结果未变则立即返回
*/
void PlsrPulseDriverSetFreq(uint32_t freq_hz); void PlsrPulseDriverSetFreq(uint32_t freq_hz);
/** 请求改频(ISR 周期边界再落到硬件) */
void PlsrPulseDriverRequestFreq(uint32_t freq_hz);
/** 在更新中断里调用:应用挂起频率 */

/**
* @brief 脉冲 ISR 改频:仅写 ARR/CCR(预装载,下一拍生效)
* @note PSC 不变时直接改;必须换 PSC 则挂起,由任务 ApplyPending
* @note 频率未变或 ARR 未变则立即返回(匀速热路径)
*/
void PlsrPulseDriverSetFreqIsr(uint32_t freq_hz);

/**
* @brief 消费挂起改频(TickMs / 更新路径)
* @note s_req_freq==0 时 Stop;否则 SetFreq
*/
void PlsrPulseDriverApplyPending(void); void PlsrPulseDriverApplyPending(void);

/** @brief 丢弃未生效的改频请求(进匀速锁定时用) */
void PlsrPulseDriverClearPending(void);

/**
* @brief 取消冷启动首拍 ARR 保护(进入匀速、无首拍改频时调用)
*/
void PlsrPulseDriverClearStartPeriodProtect(void);

/**
* @brief 停止三路脉冲 TIM,空闲 Forced inactive 拉低
* @note 不清方向脚;不清 PSC 锁(由上层 Unlock)
*/
void PlsrPulseDriverStop(void); void PlsrPulseDriverStop(void);
/** 当前拍结束后停止计数,避免多出一个上升沿 */
void PlsrPulseDriverArmOnePulseStop(void);
/** 取消单脉冲停表(后续模式同向衔接时用) */

/**
* @brief ACT 到期后收完当前脉冲、锁低输出,并在本周期末停止计数
* @note 可在周期任意位置调用:高电平等下降沿,低电平立即 Forced inactive
*/
void PlsrPulseDriverArmActStop(void);

/**
* @brief 段末停表:OPM 等周期结束 + CC1 下降沿立刻 Forced 拉低
* @note 在已计入末拍上升沿的 UPDATE ISR 中调用
*/
void PlsrPulseDriverArmSegEndStop(void);

/**
* @brief 取消单脉冲停表(后续模式同向衔接 / CutSeg keep 时)
*/
void PlsrPulseDriverClearOnePulseStop(void); void PlsrPulseDriverClearOnePulseStop(void);


/**
* @brief 立即将当前活动路输出强制拉低(Forced inactive),不关其它路
* @note ISR 安全;OPM 段末 UPDATE 后 CNT 回零时 PWM1 可能短暂为高,须先于收尾调用
*/
void PlsrPulseDriverHoldOutputLow(void);

/**
* @brief 判断 instance 是否为本模块脉冲 TIM
* @return 1=TIM10/11/13;0=否
* @note 供 stm32f4xx_it 过滤 UPDATE 回调
*/
uint8_t PlsrPulseDriverIsPulseTim(TIM_TypeDef *instance); uint8_t PlsrPulseDriverIsPulseTim(TIM_TypeDef *instance);


#endif #endif

+ 1371
- 941
plsr/run_control/plsr_run_control.c
File diff suppressed because it is too large
View File


+ 22
- 11
plsr/run_control/plsr_run_control.h View File

@@ -1,6 +1,6 @@
/** /**
* @file plsr_run_control.h * @file plsr_run_control.h
* @brief 运行控制:完成方式 / 后续方式(加减速形状见 accel_mode
* @brief 运行控制:多段脉冲状态机(开段 → 发脉冲 → 等待/跳转 → 收尾
*/ */
#ifndef PLSR_RUN_CONTROL_H #ifndef PLSR_RUN_CONTROL_H
#define PLSR_RUN_CONTROL_H #define PLSR_RUN_CONTROL_H
@@ -8,16 +8,27 @@
#include <stdint.h> #include <stdint.h>


void PlsrRunControlInit(void); void PlsrRunControlInit(void);
uint8_t PlsrRunControlStart(uint16_t start_seg_1based);
void PlsrRunControlStop(void);
uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz);
/** @brief 上电从 BKPSRAM 恢复累计脉冲(不清绝对原点锁以外的运行态) */
void PlsrRunControlRestoreAccPulse(int32_t acc_pulse);
void PlsrRunControlTickMs(void); void PlsrRunControlTickMs(void);
void PlsrRunControlOn1ms(void); /* TIM3 1ms:改频 + WAIT/ACT 时基 */
void PlsrRunControlOnPulseIsr(void); /* 脉冲 UPDATE:只计脉冲 */
uint8_t PlsrRunControlIsBusy(void);
int32_t PlsrRunControlGetAccPulse(void);
void PlsrRunControlClearAccPulse(void);
uint16_t PlsrRunControlGetCurSeg(void);
uint32_t PlsrRunControlGetCurFreq(void);
/** @brief TIM5 单次延时到期(换向 / WAIT / ACT 剩余) */
void PlsrRunControlOnDelayTimer(void);

/**
* @brief 创建任务唤醒信号量(须在 OSInit 之后、首次 Pend/Post 之前)
*/
void PlsrRunControlWakeInit(void);

/**
* @brief PlsrTask 休眠:被 ISR Post 立刻醒,或最多等 timeout_ticks 个 OS 节拍
* @param timeout_ticks 与 OSSemPend 相同;0=一直等到 Post;常用 1
*/
void PlsrRunControlWakePend(uint16_t timeout_ticks);

/**
* @brief 是否在 TIM5 单次延时等待(换向 / WAIT 时间)
* @note 此时 WakePend(0) 只等到期 Post
*/
uint8_t PlsrRunControlIsPreciseWait(void);


#endif #endif

+ 237
- 106
plsr/signal_io/plsr_signal_io.c View File

@@ -1,75 +1,133 @@
/** /**
* @file plsr_signal_io.c * @file plsr_signal_io.c
* @brief WAIT/EXT:EXTI 下降沿触发(X4=PB5、X5=PG12)
* @brief WAIT/EXT 输入 + TIM5 单次延时 + OS 节拍消抖
* *
* 输入上拉,空闲为高;飞线接地产生下降沿。
* 触发只认 EXTI,不靠平时扫口。沿到来后等 20ms 再读一次:
* 仍为低才锁存(消抖,并滤掉 Y0 脉冲耦合出的尖峰)。
* @details 实现要点
* 【EXTI 输入】
* X4=PB5 → EXTI9_5;X5=PG12 → EXTI15_10。
* 下降沿触发 OnFalling:仅 pending=1,不在 ISR 里判有效(避免抖动/毛刺)。
*
* 【消抖】
* DebouncePoll(任务上下文,约 10ms 一次):
* pending 且首次 → 记 arm_tick;
* 满 PLSR_SIGNAL_DEBOUNCE_MS(20ms)且 PinIsLow → fell=1。
* 上升沿恢复高电平则 Confirm 末尾不置 fell(pending 被清掉)。
*
* 【TIM5 单次延时】
* 32 位 TIM5,APB1 84MHz;仅在有延时时开表(URS+OPM)。
* PSC=8399 → 10kHz(1 计数 = 100µs);ARR = delay_ms×10 − 1。
* 一次 UPDATE 溢出 = delay_ms;中断内 Cancel 并 PlsrRunControlOnDelayTimer()。
*
* 【与 run_control 配合】
* ScheduleDelayMs :方向延时 / WAIT 时间 / ACT 定时
* TakeExtFalling :RUN 中 EXT 切段、WAIT_COND 等
* TakeWaitFalling :WAIT 信号段后等待
* ClearEdges :EXT 段开跑前丢旧沿
* ClearPending :纯 EXT 段末保留 fell、清 pending
* DelayRemainMs :ACT 脉冲先完后续装剩余时间
*/ */
#include "plsr_signal_io.h" #include "plsr_signal_io.h"
#include "plsr_param.h" #include "plsr_param.h"
#include "plsr_run_control.h" #include "plsr_run_control.h"
#include "main.h" #include "main.h"
#include "ucos_ii.h"


/* ---------- 硬件引脚 ---------- */
#define PLSR_X4_PORT GPIOB #define PLSR_X4_PORT GPIOB
#define PLSR_X4_PIN GPIO_PIN_5
#define PLSR_X4_PIN GPIO_PIN_5 /* X4:WAIT/EXT 可选 */
#define PLSR_X5_PORT GPIOG #define PLSR_X5_PORT GPIOG
#define PLSR_X5_PIN GPIO_PIN_12
#define PLSR_X5_PIN GPIO_PIN_12 /* X5:WAIT/EXT 可选 */


#define PLSR_TIM3_PSC 83U
#define PLSR_TIM3_ARR 999U
/*
* TIM5 时基:84MHz / (8399+1) = 10kHz → 100µs/格。
* PSC=8399 落在 16 位内
*/
#define PLSR_TIM5_PSC_100US 8399U
#define PLSR_TIM5_TICKS_PER_MS 10U /* 10×100µs = 1ms */
#define PLSR_DELAY_MS_MAX 65535U


/** OS 节拍消抖窗口(ms),与 PlsrTask tick 配合 */
#define PLSR_SIGNAL_DEBOUNCE_MS 20U #define PLSR_SIGNAL_DEBOUNCE_MS 20U


/** 单路 X 口消抖状态(pending / fell 由 ISR 与任务共享,volatile) */
typedef struct { typedef struct {
volatile uint8_t pending; /* EXTI 下降沿,待 20ms 确认 */
volatile uint8_t wait_ms;
volatile uint8_t fell; /* 确认后的下降沿,读清 */
volatile uint8_t pending; /* 1=EXTI 已见下降沿,待 Confirm */
volatile uint8_t stamp_ok; /* 1=已记 arm_tick,正在计时 */
volatile INT32U arm_tick; /* 消抖起点 OS tick */
volatile uint8_t fell; /* 1=消抖确认的有效下降沿,Take* 消费 */
GPIO_TypeDef *port; GPIO_TypeDef *port;
uint16_t pin; uint16_t pin;
} PlsrEdge_t; } PlsrEdge_t;


static volatile uint32_t s_ms;
static PlsrEdge_t s_db_x4;
static PlsrEdge_t s_db_x5;
static PlsrEdge_t s_debounce_x4; /* X4 消抖状态 */
static PlsrEdge_t s_debounce_x5; /* X5 消抖状态 */
static volatile uint8_t s_delay_armed; /* 1=TIM5 单次延时正在计时 */

/** 毫秒 → OS tick 数(向上取整,至少 1 tick) */
static INT32U PlsrSignalIoMsToTicks(uint32_t ms)
{
INT32U t;


static TIM_HandleTypeDef s_htim3;
if (ms == 0U)
{
return 1U;
}
t = ((INT32U)ms * (INT32U)OS_TICKS_PER_SEC + 999U) / 1000U;
if (t < 1U)
{
t = 1U;
}
return t;
}


static uint8_t PlsrSignalIoPinIsLow(const PlsrEdge_t *db)
/** @return 1=脚当前为低(有效按下/接地) */
static uint8_t PlsrSignalIoPinIsLow(const PlsrEdge_t *debounce)
{ {
return (HAL_GPIO_ReadPin(db->port, db->pin) == GPIO_PIN_RESET) ? 1U : 0U;
return (HAL_GPIO_ReadPin(debounce->port, debounce->pin) == GPIO_PIN_RESET) ? 1U : 0U;
} }


static void PlsrSignalIoOnFalling(PlsrEdge_t *db)
/** EXTI 下降沿:置 pending,重置计时标志消抖在 DebouncePoll 完成 */
static void PlsrSignalIoOnFalling(PlsrEdge_t *debounce)
{ {
db->pending = 1U;
db->wait_ms = 0U;
debounce->pending = 1U;
debounce->stamp_ok = 0U;
} }


static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *db)
/**
* @brief 单路消抖确认(DebouncePoll 每 tick 调用)
* @note 满 debounce_ms 仍低 → fell;否则丢弃 pending
*/
static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *debounce)
{ {
if (db->pending == 0U)
INT32U need;

if (debounce->pending == 0U)
{ {
return; return;
} }


if (db->wait_ms < 0xFFU)
if (debounce->stamp_ok == 0U)
{ {
db->wait_ms++;
debounce->arm_tick = OSTimeGet();
debounce->stamp_ok = 1U;
return;
} }
if (db->wait_ms < PLSR_SIGNAL_DEBOUNCE_MS)

need = PlsrSignalIoMsToTicks(PLSR_SIGNAL_DEBOUNCE_MS);
if ((OSTimeGet() - debounce->arm_tick) < need)
{ {
return; return;
} }


db->pending = 0U;
/* 尖峰/耦合在 20ms 后已回到高,不算触发;飞线接地保持为低才算 */
if (PlsrSignalIoPinIsLow(db) != 0U)
debounce->pending = 0U;
debounce->stamp_ok = 0U;
if (PlsrSignalIoPinIsLow(debounce) != 0U)
{ {
db->fell = 1U;
debounce->fell = 1U;
} }
} }


/** X4/X5:下降沿 EXTI + 上拉;先关 NVIC 再配 GPIO,避免配置期间误触发 */
static void PlsrSignalIoGpioExtiInit(void) static void PlsrSignalIoGpioExtiInit(void)
{ {
GPIO_InitTypeDef gpio = {0}; GPIO_InitTypeDef gpio = {0};
@@ -87,7 +145,6 @@ static void PlsrSignalIoGpioExtiInit(void)


gpio.Pin = PLSR_X4_PIN; gpio.Pin = PLSR_X4_PIN;
HAL_GPIO_Init(PLSR_X4_PORT, &gpio); HAL_GPIO_Init(PLSR_X4_PORT, &gpio);

gpio.Pin = PLSR_X5_PIN; gpio.Pin = PLSR_X5_PIN;
HAL_GPIO_Init(PLSR_X5_PORT, &gpio); HAL_GPIO_Init(PLSR_X5_PORT, &gpio);


@@ -102,147 +159,221 @@ static void PlsrSignalIoGpioExtiInit(void)
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn); HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
} }


static void PlsrSignalIoTim3Init(void)
/** TIM5:预配 PSC/最大 ARR,默认停表;ScheduleDelayMs 时再装 ARR 并 OPM 启动 */
static void PlsrSignalIoTim5Init(void)
{ {
__HAL_RCC_TIM3_CLK_ENABLE();
s_htim3.Instance = TIM3;
s_htim3.Init.Prescaler = PLSR_TIM3_PSC;
s_htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
s_htim3.Init.Period = PLSR_TIM3_ARR;
s_htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
s_htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
(void)HAL_TIM_Base_Init(&s_htim3);
HAL_NVIC_SetPriority(TIM3_IRQn, 9, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
(void)HAL_TIM_Base_Start_IT(&s_htim3);
__HAL_RCC_TIM5_CLK_ENABLE();
TIM5->CR1 = 0U; /* 停表:向上计、关 OPM */
TIM5->DIER = 0U; /* 关 UPDATE 中断 */
TIM5->SR = 0U; /* 清状态标志 */
TIM5->PSC = PLSR_TIM5_PSC_100US; /* PSC=8399 → 10kHz(100µs/格),非 1MHz */
TIM5->ARR = 0xFFFFFFFFUL; /* 预装最大 ARR,真正延时在 Schedule 重写 */
TIM5->CNT = 0U;
TIM5->EGR = TIM_EGR_UG; /* UG:PSC 立刻生效(会置 UIF) */
TIM5->SR = 0U; /* 清 UG 带来的 UIF */
HAL_NVIC_SetPriority(TIM5_IRQn, 7, 0);
HAL_NVIC_EnableIRQ(TIM5_IRQn);
} }


/** @brief 信号 IO 初始化:消抖状态 + EXTI + TIM5 */
void PlsrSignalIoInit(void) void PlsrSignalIoInit(void)
{ {
s_ms = 0U;

s_db_x4.port = PLSR_X4_PORT;
s_db_x4.pin = PLSR_X4_PIN;
s_db_x4.pending = 0U;
s_db_x4.wait_ms = 0U;
s_db_x4.fell = 0U;

s_db_x5.port = PLSR_X5_PORT;
s_db_x5.pin = PLSR_X5_PIN;
s_db_x5.pending = 0U;
s_db_x5.wait_ms = 0U;
s_db_x5.fell = 0U;
s_delay_armed = 0U;

s_debounce_x4.port = PLSR_X4_PORT;
s_debounce_x4.pin = PLSR_X4_PIN;
s_debounce_x4.pending = 0U;
s_debounce_x4.stamp_ok = 0U;
s_debounce_x4.arm_tick = 0U;
s_debounce_x4.fell = 0U;

s_debounce_x5.port = PLSR_X5_PORT;
s_debounce_x5.pin = PLSR_X5_PIN;
s_debounce_x5.pending = 0U;
s_debounce_x5.stamp_ok = 0U;
s_debounce_x5.arm_tick = 0U;
s_debounce_x5.fell = 0U;


PlsrSignalIoGpioExtiInit(); PlsrSignalIoGpioExtiInit();
PlsrSignalIoTim3Init();

/* 配置 EXTI 时可能打出假沿,丢掉 */
PlsrSignalIoTim5Init();
/* 配完 NVIC 后再清一次,避免配置期误沿 */
__HAL_GPIO_EXTI_CLEAR_IT(PLSR_X4_PIN); __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X4_PIN);
__HAL_GPIO_EXTI_CLEAR_IT(PLSR_X5_PIN); __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X5_PIN);
s_db_x4.fell = 0U;
s_db_x4.pending = 0U;
s_db_x5.fell = 0U;
s_db_x5.pending = 0U;
s_debounce_x4.fell = 0U;
s_debounce_x4.pending = 0U;
s_debounce_x5.fell = 0U;
s_debounce_x5.pending = 0U;
} }


uint32_t PlsrSignalIoGetMs(void)
/** @brief 装 TIM5 单次延时(ms),OPM 到期进 OnTim5Irq */
void PlsrSignalIoScheduleDelayMs(uint32_t delay_ms)
{ {
return s_ms;
}
uint32_t ticks;


uint32_t PlsrSignalIoDeadlineFromNow(uint32_t ms)
{
if (ms == 0U)
if (delay_ms < 1U)
{ {
return s_ms;
delay_ms = 1U;
} }
return s_ms + ms;
if (delay_ms > PLSR_DELAY_MS_MAX)
{
delay_ms = PLSR_DELAY_MS_MAX;
}

/* N ms → N×10 个 100µs 格;OPM 计满一次 UPDATE 即到期 */
ticks = delay_ms * PLSR_TIM5_TICKS_PER_MS;

TIM5->CR1 = 0U;
TIM5->DIER = 0U;
TIM5->SR = 0U;
TIM5->PSC = PLSR_TIM5_PSC_100US;
TIM5->ARR = ticks - 1U;
TIM5->CR1 = TIM_CR1_URS; /* 仅溢出产生 UPDATE */
TIM5->EGR = TIM_EGR_UG; /* PSC/ARR 立刻生效 */
TIM5->SR = 0U; /* 清 UG 带来的 UIF */
TIM5->CNT = 0U;
TIM5->DIER = TIM_DIER_UIE;
s_delay_armed = 1U;
TIM5->CR1 = (uint32_t)(TIM_CR1_CEN | TIM_CR1_OPM | TIM_CR1_URS); /* 开表单次 */
} }


uint8_t PlsrSignalIoMsReached(uint32_t deadline_ms)
/** @brief 取消 TIM5 延时并清武装 */
void PlsrSignalIoCancelDelay(void)
{ {
return ((int32_t)(s_ms - deadline_ms) >= 0) ? 1U : 0U;
TIM5->CR1 = 0U;
TIM5->DIER = 0U;
TIM5->SR = 0U;
s_delay_armed = 0U;
} }


void PlsrSignalIoClearEdges(void)
/** @brief 查询 TIM5 剩余延时 ms(未武装或已停返回 0) */
uint32_t PlsrSignalIoDelayRemainMs(void)
{ {
s_db_x4.fell = 0U;
s_db_x4.pending = 0U;
s_db_x5.fell = 0U;
s_db_x5.pending = 0U;
uint32_t arr;
uint32_t cnt;
uint32_t left_ticks;

if (s_delay_armed == 0U)
{
return 0U;
}
if ((TIM5->CR1 & TIM_CR1_CEN) == 0U)
{
return 0U;
}

arr = TIM5->ARR;
cnt = TIM5->CNT;
if (cnt >= (arr + 1U))
{
return 0U;
}
left_ticks = (arr + 1U) - cnt;
/*会有个小bug。假定计数剩余个数655个,则要计数65.5ms,
该函数会向下取整,导致返回65ms。再次进来计数时就会误差0.5ms。理论最大误差1ms。*/
return left_ticks / PLSR_TIM5_TICKS_PER_MS;
} }


void PlsrSignalIoClearPending(void)
/** @brief OS 节拍消抖:确认 X4/X5 有效下降沿 */
void PlsrSignalIoDebouncePoll(void)
{ {
s_db_x4.pending = 0U;
s_db_x5.pending = 0U;
PlsrSignalIoDebounceConfirm(&s_debounce_x4);
PlsrSignalIoDebounceConfirm(&s_debounce_x5);
} }


static PlsrEdge_t *PlsrSignalIoDbBySel(uint16_t sel_01)
/** @brief 丢弃两侧沿与 pending(EXT 段开跑前) */
void PlsrSignalIoClearEdges(void)
{ {
return (sel_01 != 0U) ? &s_db_x5 : &s_db_x4;
s_debounce_x4.fell = 0U;
s_debounce_x4.pending = 0U;
s_debounce_x4.stamp_ok = 0U;
s_debounce_x5.fell = 0U;
s_debounce_x5.pending = 0U;
s_debounce_x5.stamp_ok = 0U;
} }


uint8_t PlsrSignalIoWaitIsOn(void)
/** @brief 仅清 pending,保留已确认 fell(纯 EXT 段末) */
void PlsrSignalIoClearPending(void)
{ {
PlsrCfg_t *cfg = PlsrParamGetCfg();
return PlsrSignalIoPinIsLow(PlsrSignalIoDbBySel(cfg->wait_x_sel));
s_debounce_x4.pending = 0U;
s_debounce_x4.stamp_ok = 0U;
s_debounce_x5.pending = 0U;
s_debounce_x5.stamp_ok = 0U;
} }


/** @brief 取 WAIT 口有效下降沿(读后清) */
uint8_t PlsrSignalIoTakeWaitFalling(void) uint8_t PlsrSignalIoTakeWaitFalling(void)
{ {
PlsrCfg_t *cfg = PlsrParamGetCfg();
PlsrEdge_t *db = PlsrSignalIoDbBySel(cfg->wait_x_sel);

if (db->fell != 0U)
/* wait_x_sel:0=X4,1=X5 */
if (g_plsr_config.wait_x_sel == 0U)
{
if (s_debounce_x4.fell != 0U)
{
s_debounce_x4.fell = 0U;
return 1U;
}
}
else if (s_debounce_x5.fell != 0U)
{ {
db->fell = 0U;
s_debounce_x5.fell = 0U;
return 1U; return 1U;
} }
return 0U; return 0U;
} }


/** @brief 取 EXT 口有效下降沿(读后清) */
uint8_t PlsrSignalIoTakeExtFalling(void) uint8_t PlsrSignalIoTakeExtFalling(void)
{ {
PlsrCfg_t *cfg = PlsrParamGetCfg();
PlsrEdge_t *db = PlsrSignalIoDbBySel(cfg->ext_x_sel);

if (db->fell != 0U)
/* ext_x_sel:0=X4,1=X5 */
if (g_plsr_config.ext_x_sel == 0U)
{
if (s_debounce_x4.fell != 0U)
{
s_debounce_x4.fell = 0U;
return 1U;
}
}
else if (s_debounce_x5.fell != 0U)
{ {
db->fell = 0U;
s_debounce_x5.fell = 0U;
return 1U; return 1U;
} }
return 0U; return 0U;
} }


void PlsrSignalIoOnTim3Irq(void)
/** @brief TIM5 UPDATE:到期回调 run_control */
void PlsrSignalIoOnTim5Irq(void)
{ {
if ((TIM3->SR & TIM_SR_UIF) != 0U)
if ((TIM5->SR & TIM_SR_UIF) != 0U)
{ {
TIM3->SR = (uint32_t)~TIM_SR_UIF;
s_ms++;
PlsrSignalIoDebounceConfirm(&s_db_x4);
PlsrSignalIoDebounceConfirm(&s_db_x5);
PlsrRunControlOn1ms();
TIM5->SR = (uint32_t)~TIM_SR_UIF;
TIM5->DIER = 0U;
TIM5->CR1 = 0U;
if (s_delay_armed != 0U)
{
s_delay_armed = 0U;
PlsrRunControlOnDelayTimer(); /* 方向/WAIT/ACT 到期 */
}
} }
} }


/** @brief EXTI9_5:X4 下降沿 → pending */
void PlsrSignalIoOnExti9_5(void) void PlsrSignalIoOnExti9_5(void)
{ {
if (__HAL_GPIO_EXTI_GET_IT(PLSR_X4_PIN) != 0U) if (__HAL_GPIO_EXTI_GET_IT(PLSR_X4_PIN) != 0U)
{ {
__HAL_GPIO_EXTI_CLEAR_IT(PLSR_X4_PIN); __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X4_PIN);
PlsrSignalIoOnFalling(&s_db_x4);
PlsrSignalIoOnFalling(&s_debounce_x4);
} }
} }


/** @brief EXTI15_10:X5 下降沿 → pending */
void PlsrSignalIoOnExti15_10(void) void PlsrSignalIoOnExti15_10(void)
{ {
if (__HAL_GPIO_EXTI_GET_IT(PLSR_X5_PIN) != 0U) if (__HAL_GPIO_EXTI_GET_IT(PLSR_X5_PIN) != 0U)
{ {
__HAL_GPIO_EXTI_CLEAR_IT(PLSR_X5_PIN); __HAL_GPIO_EXTI_CLEAR_IT(PLSR_X5_PIN);
PlsrSignalIoOnFalling(&s_db_x5);
PlsrSignalIoOnFalling(&s_debounce_x5);
} }
} }

+ 62
- 20
plsr/signal_io/plsr_signal_io.h View File

@@ -1,48 +1,90 @@
/** /**
* @file plsr_signal_io.h * @file plsr_signal_io.h
* @brief 等待/EXT 输入(X4=PB5、X5=PG12)
* @brief WAIT/EXT 外部输入与 TIM5 单次延时
* *
* 公共参数:wait_x_sel / ext_x_sel:0=X4,1=X5。
* 上拉空闲为高;飞线接地产生下降沿,由 EXTI 触发(不轮询)。
* @details 模块职责
* 1. 配置 X4=PB5、X5=PG12 为下降沿 EXTI(内部上拉,空闲高;飞线/触点接地出有效沿);
* 2. 消抖:EXTI 仅置 pending,任务侧 DebouncePoll 按 OS 节拍确认约 20ms 仍低 → fell;
* 3. TIM5(32 位)按需单次计时:100µs 时基,ARR=ms×10−1,一次 UPDATE = 设定 ms
* (最长 65535ms);到期回调 run_control::OnDelayTimer。
*
* 硬件与参数映射
* X4 (PB5, EXTI9_5) / X5 (PG12, EXTI15_10)
* wait_x_sel (0x1002) → 0=X4,1=X5,供 WAIT 信号等待(PLSR_WAIT_SIGNAL)
* ext_x_sel (0x1003) → 0=X4,1=X5,供 EXT 切段(PLSR_WAIT_EXT / EXT_OR_DONE)
* WAIT 与 EXT 可独立选口,共用同一套消抖状态机。
*
* TIM5 单次延时用途
* - 方向切换延时 dir_delay_ms(RC_DIR_WAIT → Start)
* - WAIT 时间 PLSR_WAIT_TIME 段后等待
* - ACT 时间 PLSR_WAIT_ACT 段内定时(到期 OPM 收当前拍再切段)
* - ACT 脉冲先完但定时未到:DelayRemainMs 续装剩余 ms
*/ */
#ifndef PLSR_SIGNAL_IO_H #ifndef PLSR_SIGNAL_IO_H
#define PLSR_SIGNAL_IO_H #define PLSR_SIGNAL_IO_H


#include <stdint.h> #include <stdint.h>


/**
* @brief 初始化 X4/X5 EXTI 消抖状态与 TIM5(不启动延时)
*/
void PlsrSignalIoInit(void); void PlsrSignalIoInit(void);


/** TIM3 毫秒计数(自由运行,约 1ms/格) */
uint32_t PlsrSignalIoGetMs(void);
/**
* @brief TIM5 单次延时(ms),到期 OnTim5Irq → PlsrRunControlOnDelayTimer
* @param delay_ms 0 按 1ms;上限 65535ms
* @note 重入会先 Cancel 再重装;OPM 计满自动停表
*/
void PlsrSignalIoScheduleDelayMs(uint32_t delay_ms);

/** @brief 停止 TIM5 单次延时,s_delay_armed=0 */
void PlsrSignalIoCancelDelay(void);


/** deadline = GetMs()+ms;ms=0 则立即到期 */
uint32_t PlsrSignalIoDeadlineFromNow(uint32_t ms);
/**
* @brief 读 TIM5 单次延时剩余 ms
* @note ACT 脉冲先完、定时仍走时,用于续装剩余 ACT 时间
*/
uint32_t PlsrSignalIoDelayRemainMs(void);


/** 1=已到或过期(含无符号环绕) */
uint8_t PlsrSignalIoMsReached(uint32_t deadline_ms);
/**
* @brief OS 节拍消抖轮询(X4/X5 各跑一遍 Confirm)
* @note 由 PlsrTask 每 tick 调用;满 PLSR_SIGNAL_DEBOUNCE_MS 且仍低才置 fell
*/
void PlsrSignalIoDebouncePoll(void);


/** 清 WAIT/EXT 下降沿锁存(包含已确认的 fell) */
/**
* @brief 清 X4/X5 的 pending 与 fell(开跑 EXT 段、FinishAll 时用)
* @note 丢弃尚未确认的沿与已锁存沿,防误切段
*/
void PlsrSignalIoClearEdges(void); void PlsrSignalIoClearEdges(void);


/** 只清消抖 pending,保留已确认的 fell(发完后过滤脉冲耦合假沿用) */
/**
* @brief 只清 pending/stamp,保留已确认的 fell
* @note 纯 EXT 段末:清消抖中的假沿,已确认沿留给 TakeExtFalling
*/
void PlsrSignalIoClearPending(void); void PlsrSignalIoClearPending(void);


/** WAIT 口当前是否为低(已接地)。仅段末判断※2 时读一次,不作周期轮询触发 */
uint8_t PlsrSignalIoWaitIsOn(void);

/** /**
* WAIT:消耗一次已确认的 EXTI 下降沿。
* ※2 段中飞线接地并保持 → 发完立刻下一段;※3 未触发则一直等。
* @brief 消费 WAIT 口(wait_x_sel)上已确认的下降沿
* @return 1=本次取到有效沿并已清零 fell;0=无沿
* @note 仅认消抖后的 fell,不读当前 GPIO 电平(防虚低误跳)
*/ */
uint8_t PlsrSignalIoTakeWaitFalling(void); uint8_t PlsrSignalIoTakeWaitFalling(void);


/** /**
* EXT:消耗一次 EXTI 下降沿锁存。一次按压只切一次段。
* @brief 消费 EXT 口(ext_x_sel)上已确认的下降沿
* @return 1=本次取到有效沿并已清零 fell;0=无沿
* @note RUN 中 EXT/EXT_OR_DONE 切段、WAIT_COND 等路径共用
*/ */
uint8_t PlsrSignalIoTakeExtFalling(void); uint8_t PlsrSignalIoTakeExtFalling(void);


void PlsrSignalIoOnTim3Irq(void);
void PlsrSignalIoOnExti9_5(void);
void PlsrSignalIoOnExti15_10(void);
/** @brief TIM5 UPDATE 中断入口:单次延时到期 → run_control */
void PlsrSignalIoOnTim5Irq(void);

/** @brief EXTI9_5 中断入口(X4 / PB5) */
void PlsrSignalIoOnExti9_5(void);

/** @brief EXTI15_10 中断入口(X5 / PG12) */
void PlsrSignalIoOnExti15_10(void);


#endif #endif

Loading…
Cancel
Save