Procházet zdrojové kódy

删掉modbus的01,0F功能码

dev1
hanyongwei před 4 týdny
rodič
revize
4b93dd0f63
29 změnil soubory, kde provedl 2950 přidání a 3229 odebrání
  1. +49
    -17
      app/main.c
  2. +1048
    -1929
      iar/plsr.dep
  3. +24
    -45
      iar/plsr.ewp
  4. +79
    -19
      iar/plsr.ewt
  5. +0
    -30
      modbus/modbus_common.h
  6. +0
    -194
      modbus/modbus_data.c
  7. +0
    -59
      modbus/modbus_data.h
  8. +0
    -426
      modbus/modbus_fc.c
  9. +0
    -25
      modbus/modbus_fc.h
  10. +0
    -188
      modbus/modbus_port.c
  11. +0
    -17
      modbus/modbus_port.h
  12. +608
    -6
      modbus/modbus_rtu.c
  13. +18
    -69
      modbus/modbus_rtu.h
  14. +44
    -3
      plsr/accel_curve/plsr_accel_curve.c
  15. +73
    -19
      plsr/accel_curve/plsr_accel_curve.h
  16. +53
    -6
      plsr/command/plsr_command.c
  17. +77
    -20
      plsr/command/plsr_command.h
  18. +79
    -4
      plsr/param/plsr_param.c
  19. +142
    -61
      plsr/param/plsr_param.h
  20. +21
    -0
      plsr/path_plan/plsr_path_plan.c
  21. +18
    -6
      plsr/path_plan/plsr_path_plan.h
  22. +47
    -8
      plsr/plsr.c
  23. +74
    -14
      plsr/plsr.h
  24. +76
    -4
      plsr/pulse_driver/plsr_pulse_driver.c
  25. +83
    -14
      plsr/pulse_driver/plsr_pulse_driver.h
  26. +148
    -24
      plsr/run_control/plsr_run_control.c
  27. +84
    -4
      plsr/run_control/plsr_run_control.h
  28. +48
    -6
      plsr/signal_io/plsr_signal_io.c
  29. +57
    -12
      plsr/signal_io/plsr_signal_io.h

+ 49
- 17
app/main.c Zobrazit soubor

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

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

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

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

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

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

void SystemClock_Config(void);

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

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

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

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

OSInit();
PlsrParamBlockInit(); /* 依赖 OS,须在 OSInit 之后 */
/* 参数块信号量依赖 OS,必须放在 OSInit 之后 */
PlsrParamBlockInit();

/* Modbus 从站任务:解析 FC03/06/10,维护保持寄存器映像 */
OSTaskCreateExt(ModbusSlaveTask,
NULL,
&ModbusTaskStk[MODBUS_TASK_STACK_SIZE - 1],
@@ -61,6 +85,7 @@ int main(void)
NULL,
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP);

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

/*
* 参数块任务:Pend 信号量 → ApplyFromHold → PersistPoll。
* 分帧写完后由 OnHoldWrite Post,尽快落盘,不堵在 PlsrTask 里。
*/
OSTaskCreateExt(PlsrParamBlockTask,
NULL,
&ParamBlockTaskStk[PARAM_BLOCK_STACK_SIZE - 1],
@@ -81,13 +110,17 @@ int main(void)
NULL,
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR | OS_TASK_OPT_SAVE_FP);

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

while (1)
{
}
}

/**
* @brief 系统时钟:HSE → PLL → 168MHz SYSCLK(APM32F407 / STM32F4 类)
* @note 须与 OS_CPU_SysTickInit 使用的主频常量一致
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
@@ -109,19 +142,22 @@ void SystemClock_Config(void)
Error_Handler();
}

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

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

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

#ifdef USE_FULL_ASSERT

#endif

+ 1048
- 1929
iar/plsr.dep
Diff nebyl zobrazen, protože je příliš veliký
Zobrazit soubor


+ 24
- 45
iar/plsr.ewp Zobrazit soubor

@@ -2841,27 +2841,6 @@
</group>
<group>
<name>modbus</name>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_common.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_data.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_data.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_fc.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_fc.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_port.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_port.h</name>
</file>
<file>
<name>$PROJ_DIR$\..\modbus\modbus_rtu.c</name>
</file>
@@ -2871,37 +2850,31 @@
</group>
<group>
<name>plsr</name>
<file>
<name>$PROJ_DIR$\..\plsr\plsr.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\plsr.h</name>
</file>
<group>
<name>param</name>
<name>accel_curve</name>
<file>
<name>$PROJ_DIR$\..\plsr\param\plsr_param.c</name>
<name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\param\plsr_param.h</name>
<name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.h</name>
</file>
</group>
<group>
<name>persist</name>
<name>command</name>
<file>
<name>$PROJ_DIR$\..\plsr\persist\plsr_persist.c</name>
<name>$PROJ_DIR$\..\plsr\command\plsr_command.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\persist\plsr_persist.h</name>
<name>$PROJ_DIR$\..\plsr\command\plsr_command.h</name>
</file>
</group>
<group>
<name>pulse_driver</name>
<name>param</name>
<file>
<name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.c</name>
<name>$PROJ_DIR$\..\plsr\param\plsr_param.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name>
<name>$PROJ_DIR$\..\plsr\param\plsr_param.h</name>
</file>
</group>
<group>
@@ -2914,30 +2887,30 @@
</file>
</group>
<group>
<name>accel_curve</name>
<name>persist</name>
<file>
<name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.c</name>
<name>$PROJ_DIR$\..\plsr\persist\plsr_persist.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\accel_curve\plsr_accel_curve.h</name>
<name>$PROJ_DIR$\..\plsr\persist\plsr_persist.h</name>
</file>
</group>
<group>
<name>run_control</name>
<name>pulse_driver</name>
<file>
<name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.c</name>
<name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.h</name>
<name>$PROJ_DIR$\..\plsr\pulse_driver\plsr_pulse_driver.h</name>
</file>
</group>
<group>
<name>command</name>
<name>run_control</name>
<file>
<name>$PROJ_DIR$\..\plsr\command\plsr_command.c</name>
<name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\command\plsr_command.h</name>
<name>$PROJ_DIR$\..\plsr\run_control\plsr_run_control.h</name>
</file>
</group>
<group>
@@ -2949,6 +2922,12 @@
<name>$PROJ_DIR$\..\plsr\signal_io\plsr_signal_io.h</name>
</file>
</group>
<file>
<name>$PROJ_DIR$\..\plsr\plsr.c</name>
</file>
<file>
<name>$PROJ_DIR$\..\plsr\plsr.h</name>
</file>
</group>
<group>
<name>ucos</name>


+ 79
- 19
iar/plsr.ewt Zobrazit soubor

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


+ 0
- 30
modbus/modbus_common.h Zobrazit soubor

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

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

@@ -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
- 426
modbus/modbus_fc.c Zobrazit soubor

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

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

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

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

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

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

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

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

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

funcCode = frame[1];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

WriteHoldReg(addr, value);

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

PlsrParamBlockOnHoldWrite(addr, 1U);
}

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

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

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

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

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

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

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

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

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

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

AppendCrcAndSend(g_modbus_tx_buf, 6U);
}

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

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

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

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

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

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

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

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

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

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

PlsrParamBlockOnHoldWrite(startAddr, quantity);
PlsrCommandOnSegFreqHoldWrite(startAddr, quantity);
}

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

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

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

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

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

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

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

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

+ 0
- 25
modbus/modbus_fc.h Zobrazit soubor

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

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

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

+ 608
- 6
modbus/modbus_rtu.c Zobrazit soubor

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

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

static uint8_t g_modbus_proc_buf[MODBUS_RX_BUF_LEN];

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

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

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

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

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

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

/**
* @brief 从站任务:阻塞等待完整帧,校验后分发处理
*/
@@ -48,12 +86,12 @@ void ModbusSlaveTask(void *pArg)
}
memcpy(g_modbus_proc_buf, g_modbus_rx_buf, frameLen);
g_modbus_rx_len = 0U;
PortClearFramePending();
/*清除帧挂起*/
g_modbus_frame_pending = 0U;
OS_EXIT_CRITICAL();

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

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

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


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

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

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

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

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

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

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

return 1U;
}

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

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

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

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

return g_hold_reg[offset];
}

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

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

g_hold_reg[offset] = val;
}

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

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

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

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

funcCode = frame[1];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

WriteHoldReg(addr, value);

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

PlsrParamBlockOnHoldWrite(addr, 1U);
}

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

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

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

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

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

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

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

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

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

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

PlsrParamBlockOnHoldWrite(startAddr, quantity);
PlsrCommandOnSegFreqHoldWrite(startAddr, quantity);
}

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

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

AppendCrcAndSend(g_modbus_tx_buf, 3U);
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

FlushUartRx();
RestartReceive();
}

+ 18
- 69
modbus/modbus_rtu.h Zobrazit soubor

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

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

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

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

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

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

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

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

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

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

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

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

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

extern UART_HandleTypeDef huart1;
extern TIM_HandleTypeDef htim2;

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

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

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

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

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

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

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

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

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

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

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

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

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

#endif

+ 44
- 3
plsr/accel_curve/plsr_accel_curve.c Zobrazit soubor

@@ -1,13 +1,20 @@
/**
* @file plsr_accel_curve.c
* @brief 脉冲域规划/取频:f^2 = f0^2 + 2*a*n(直线);S/正弦按脉冲进度 shape
* @brief 脉冲域规划/取频实现:直线 f^2=f0^2±2an;S/正弦按脉冲进度 shape
*
* 脉冲预算不够到目标频时:降低峰值(三角,无匀速)。
* @details 模块职责
* 实现 plsr_accel_curve.h 全部 API。核心路径:
* Plan → 估 acc_n/dec_n → 不够则 FitPeak 降峰;
* PulseRtBeginAcc/Dec/Const + Step 供 ISR 每脉冲改频。
*
* 关键约束
* 频率钳制上限 100000Hz;运行门禁非法频率由 command/run_control 报 0x04。
* 脉冲预算不够到目标频时:降低峰值(三角,无匀速),调用方写故障 0x02。
*/
#include "plsr_accel_curve.h"
#include <stdint.h>

/*频率大于100k时暂时固定到100k,后面增加报错*/
/* 频率大于 100k 时暂时钳到 100k;启动门禁另报 0x04 */
uint32_t PlsrAccelCurveClampFreq(uint32_t freq_hz)
{
if (freq_hz > 100000U)
@@ -17,6 +24,11 @@ uint32_t PlsrAccelCurveClampFreq(uint32_t freq_hz)
return freq_hz;
}

/*============================================================================*/
/* 整数开方 / 斜率时间 */
/*============================================================================*/

/** @brief |a-b| */
static uint32_t PlsrAccelCurveAbsDiff(uint32_t a, uint32_t b)
{
return (a >= b) ? (a - b) : (b - a);
@@ -856,6 +868,15 @@ static uint32_t PlsrAccelCurveFitPeak(uint32_t total_pulses,
return f_cur - best;
}

/**
* @brief 规划一整段脉冲域(见 plsr_accel_curve.h)
*
* 算法步骤:
* 1. 钳频、解析 f_cur/f_end 若仍 <1;
* 2. 由 default_spd 与 accel/decel_ms 算 a_acc/a_dec(无默认速则频差回退);
* 3. PulsesForRamp 估 acc_n/dec_n;若和 > total → FitPeak 降峰重估;
* 4. 写出 plan 三相预算与 t_* 预览字段。
*/
void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan,
uint32_t total_pulses,
uint32_t f_cur,
@@ -1030,6 +1051,10 @@ void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan,
plan->t_decel_start_ms = t_acc + PlsrAccelCurveConstTimeMs(plan->const_n, f_peak);
}

/**
* @brief 按已完成脉冲数取频(见 plsr_accel_curve.h)
* @note 加速:Kinematic 或 Shape+Lerp;匀速:f_tgt;减速:同理
*/
uint32_t PlsrAccelCurveFreqAtPulse(const PlsrAccelPlan_t *plan,
uint32_t pulse_done)
{
@@ -1120,6 +1145,13 @@ uint32_t PlsrAccelCurveFreqAtSegTime(const PlsrAccelPlan_t *plan,
return PlsrAccelCurveFreqAtPulse(plan, approx_n);
}

/*============================================================================*/
/* 逐拍运行态(ISR) */
/*============================================================================*/

/**
* @brief 为 S/正弦准备 Q24 步进:(1<<24)/N 商余,避免每拍除法
*/
static void PlsrAccelPulseRtSetupShape(PlsrAccelPulseRt_t *rt, uint32_t n_total)
{
if (n_total < 1U)
@@ -1135,6 +1167,7 @@ static void PlsrAccelPulseRtSetupShape(PlsrAccelPulseRt_t *rt, uint32_t n_total)
rt->rem_acc = 0U;
}

/** @brief 进入加速相(见 plsr_accel_curve.h) */
void PlsrAccelPulseRtBeginAcc(PlsrAccelPulseRt_t *rt,
const PlsrAccelPlan_t *plan)
{
@@ -1154,6 +1187,7 @@ void PlsrAccelPulseRtBeginAcc(PlsrAccelPulseRt_t *rt,
PlsrAccelPulseRtSetupShape(rt, rt->n_total);
}

/** @brief 进入减速相(见 plsr_accel_curve.h) */
void PlsrAccelPulseRtBeginDec(PlsrAccelPulseRt_t *rt,
const PlsrAccelPlan_t *plan)
{
@@ -1173,6 +1207,7 @@ void PlsrAccelPulseRtBeginDec(PlsrAccelPulseRt_t *rt,
PlsrAccelPulseRtSetupShape(rt, rt->n_total);
}

/** @brief 进入匀速相(见 plsr_accel_curve.h) */
void PlsrAccelPulseRtBeginConst(PlsrAccelPulseRt_t *rt,
const PlsrAccelPlan_t *plan)
{
@@ -1279,6 +1314,9 @@ static uint32_t PlsrAccelPulseRtSquareStep(PlsrAccelPulseRt_t *rt)
return PlsrAccelCurveClampFreq(f);
}

/**
* @brief S/正弦:累加 Q24 进度 → shape 千分比 → Lerp(f0,f1)
*/
static uint32_t PlsrAccelPulseRtShapeStep(PlsrAccelPulseRt_t *rt)
{
uint32_t ratio;
@@ -1320,6 +1358,9 @@ static uint32_t PlsrAccelPulseRtShapeStep(PlsrAccelPulseRt_t *rt)
return PlsrAccelCurveClampFreq(f);
}

/**
* @brief 本相前进一脉冲(见 plsr_accel_curve.h)
*/
uint32_t PlsrAccelPulseRtStep(PlsrAccelPulseRt_t *rt)
{
if (rt == (PlsrAccelPulseRt_t *)0)


+ 73
- 19
plsr/accel_curve/plsr_accel_curve.h Zobrazit soubor

@@ -1,11 +1,21 @@
/**
* @file plsr_accel_curve.h
* @brief 脉冲域加/匀/减规划与取频(v^2 - v0^2 = 2ax)
* @brief 脉冲域加/匀/减规划与取频(运动学:v^2 - v0^2 = 2ax)
*
* 加速度 a = 默认速度*1000/加减速时间(Hz/s)。
* 脉冲序号 n 上:f(n) = sqrt(f0^2 + 2*a*n);初速为 0 即 f0=0。
* 匀速仅当加速+减速脉冲之和 < 总脉冲(真正到达目标频)时出现。
* 开段端点由运行层 ResolveStart/EndHz 解析后再传入 Plan。
* @details 模块职责
* 纯算法层:根据起/峰/止频率、斜率时间与总脉冲,规划 acc_n/const_n/dec_n,
* 并在脉冲序号上取频。不读 wait_type,不碰 TIM/GPIO。
*
* 核心约定
* - 加速度 a ≈ default_speed * 1000 / accel_ms(Hz/s);起/止速只定端点
* - 直线:f(n) = sqrt(f0^2 ± 2*a*n);S/正弦:按脉冲进度 shape 插值
* - 匀速仅当 acc_n+dec_n < total(真正到达目标峰)时出现
* - 脉冲不够到设定峰:FitPeak 降峰(三角),调用方可写故障 0x02
* - 开段端点由运行层 ResolveStart/EndHz 解析为真实 Hz(>=1)后再 Plan
*
* 与其它模块关系
* - plsr_run_control :PlanSeg → Plan;ISR → PulseRtStep;Refresh → FreqAtPulse
* - plsr_param :accel_mode / default_speed / accel_ms / decel_ms
*/
#ifndef PLSR_ACCEL_CURVE_H
#define PLSR_ACCEL_CURVE_H
@@ -13,6 +23,13 @@
#include <stdint.h>
#include "plsr_param.h"

/**
* 一段脉冲域规划结果(Plan 填出,运行层只读/EnterDecel 可改写 dec_n)
* acc_n/const_n/dec_n — 三相脉冲预算
* f_cur / f_tgt / f_end — 实际起速、峰值、止速(可能已 FitPeak)
* a_acc / a_dec — Hz/s
* t_*_ms — 斜率时间估算(预览/兼容;运行热路径不依赖)
*/
typedef struct {
uint32_t acc_n;
uint32_t const_n;
@@ -20,31 +37,37 @@ typedef struct {
uint32_t f_cur;
uint32_t f_tgt;
uint32_t f_end;
uint32_t a_acc; /* 加速 a(Hz/s) */
uint32_t a_dec; /* 减速 a(Hz/s) */
uint32_t t_acc_ms; /* 斜率时间估算(预览/兼容,运行不依赖) */
uint32_t a_acc;
uint32_t a_dec;
uint32_t t_acc_ms;
uint32_t t_dec_ms;
uint32_t t_decel_start_ms; /* 估算:进减速时刻(预览用) */
uint32_t t_decel_start_ms;
PlsrAccelMode_e mode;
} PlsrAccelPlan_t;

/** 曲线相枚举(预留/文档;运行层用自有 RunPhase_e) */
typedef enum {
PLSR_CURVE_PHASE_ACC = 0,
PLSR_CURVE_PHASE_CONST,
PLSR_CURVE_PHASE_DEC
} PlsrCurvePhase_e;

/**
* @brief 频率钳到上限 100000Hz(不抬下限)
* @note 非法低频门禁由 command/run_control 写 0x04,本函数不做
*/
uint32_t PlsrAccelCurveClampFreq(uint32_t freq_hz);

/**
* 起跳/步进频率:f = sqrt(f_from^2 + 2a),a = |f_to-f_from|*1000/t_ms。
* f_from=0 时退化为 sqrt(2a);超过 f_to 则钳到 f_to。
* @brief 起跳/步进频率:f = sqrt(f_from^2 + 2a),a = |f_to-f_from|*1000/t_ms
* @note f_from=0 退化为 sqrt(2a);超过 f_to 钳到 f_to;t_ms=0 直接返回 f_to
*/
uint32_t PlsrAccelCurveJumpFreq(uint32_t f_from, uint32_t f_to,
uint32_t t_ms);

/**
* 配置起速 → 规划用真实起点:一律 JumpFreq(f_cfg, f_tgt, t_acc)。
* @brief 配置起速 → 规划用真实起点:JumpFreq(f_cfg, f_tgt, t_acc)
* @note 起速 0 与非 0 统一走 Jump,避免配置 0 时从 0Hz 开跑
*/
uint32_t PlsrAccelCurveResolveStartHz(uint32_t f_cfg,
uint32_t f_tgt,
@@ -55,8 +78,8 @@ uint32_t PlsrAccelCurveResolveStartHz(uint32_t f_cfg,
uint32_t decel_ms);

/**
* 配置止速 → 规划用真实终点
* f_cfg<=1:按落地频率估计(避免 1Hz 拖尾);否则 JumpFreq
* @brief 配置止速 → 规划用真实终点
* @note f_cfg<=1:按落地频率估计(避免 1Hz 拖尾);否则 JumpFreq
*/
uint32_t PlsrAccelCurveResolveEndHz(uint32_t f_cfg,
uint32_t f_tgt,
@@ -66,34 +89,54 @@ uint32_t PlsrAccelCurveResolveEndHz(uint32_t f_cfg,
uint32_t accel_ms,
uint32_t decel_ms);

/**
* @brief 斜坡取频(µs 时间轴,O(1))
* @note 直线:代数插值;S/正弦:shape(t/T) 再 Lerp。主要用于估计/兼容,非 ISR 主路径
*/
uint32_t PlsrAccelCurveFreqOnRampUs(uint32_t f0,
uint32_t f1,
uint32_t t_us,
uint32_t T_us,
PlsrAccelMode_e mode);

/** @brief ms 接口,内部转 µs 调 FreqOnRampUs */
uint32_t PlsrAccelCurveFreqOnRampMs(uint32_t f0,
uint32_t f1,
uint32_t t_ms,
uint32_t T_ms,
PlsrAccelMode_e mode);

/**
* @brief 斜坡第一拍自洽频率(起速=0 时避免整周期 1Hz 吃光加速时间)
*/
uint32_t PlsrAccelCurveFirstPulseFreq(uint32_t f0,
uint32_t f1,
uint32_t T_us,
PlsrAccelMode_e mode);

/**
* @brief 减速落地频率:f_end>=1 返回止速;f_end==0 返回 0(停表)
*/
uint32_t PlsrAccelCurveLandFreq(uint32_t f_end,
uint32_t f_peak,
uint32_t T_ms,
PlsrAccelMode_e mode);

/**
* @brief 拟合斜坡时间 T,使离散脉冲估计尽量等于 max_pulses(任务上下文)
*/
uint32_t PlsrAccelCurveFitRampTimeMs(uint32_t f_from,
uint32_t f_to,
uint32_t max_pulses,
uint32_t t_prefer_ms,
PlsrAccelMode_e mode);

/**
* @brief 规划一整段脉冲域三相预算与实际峰值
* @param total_pulses 本段总脉冲
* @param f_cur/f_tgt/f_end 起/目标峰/止(真实 Hz,由策略层解析)
* @note 不够爬升则 FitPeak;结果写入 *plan
*/
void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan,
uint32_t total_pulses,
uint32_t f_cur,
@@ -107,18 +150,20 @@ void PlsrAccelCurvePlan(PlsrAccelPlan_t *plan,
PlsrAccelMode_e mode);

/**
* 按已完成脉冲数取频(任务/预览可用
* 运行热路径请用 PlsrAccelPulseRt(逐拍 ±1 / Q24,可进 ISR)
* @brief 按已完成脉冲数取频(任务/预览)
* @note 运行热路径请用 PlsrAccelPulseRt(逐拍 ±1 / Q24,可进 ISR)
*/
uint32_t PlsrAccelCurveFreqAtPulse(const PlsrAccelPlan_t *plan,
uint32_t pulse_done);

/** @deprecated 时间轴取频;保留给旧预览,运行请用脉冲域 */
/**
* @deprecated 时间轴取频;保留给旧预览,运行请用脉冲域
*/
uint32_t PlsrAccelCurveFreqAtSegTime(const PlsrAccelPlan_t *plan,
uint32_t seg_elapsed_ms);

/**
* 逐拍运行态(ISR 热路径)
* 逐拍运行态(ISR 热路径)
* - 直线:上一拍频率 ±1 逼近 sqrt(f0^2±2an),不全量 Isqrt64
* - S/正弦:Begin 时算 Q24 步长,逐拍商余累加,不做 index/total 除法
*/
@@ -138,13 +183,22 @@ typedef struct {
PlsrAccelMode_e mode;
} PlsrAccelPulseRt_t;

/** @brief 进入加速相运行态(n=0,active 视 acc_n) */
void PlsrAccelPulseRtBeginAcc(PlsrAccelPulseRt_t *rt,
const PlsrAccelPlan_t *plan);

/** @brief 进入减速相运行态 */
void PlsrAccelPulseRtBeginDec(PlsrAccelPulseRt_t *rt,
const PlsrAccelPlan_t *plan);

/** @brief 进入匀速:active=0,复用 f_tgt */
void PlsrAccelPulseRtBeginConst(PlsrAccelPulseRt_t *rt,
const PlsrAccelPlan_t *plan);
/** 本相前进 1 脉冲,返回下一拍命令频率(匀速直接返回缓存 f) */

/**
* @brief 本相前进 1 脉冲,返回下一拍命令频率
* @note 匀速直接返回缓存 f;走完 n_total 后钳到 f1 并清 active
*/
uint32_t PlsrAccelPulseRtStep(PlsrAccelPulseRt_t *rt);

#endif

+ 53
- 6
plsr/command/plsr_command.c Zobrazit soubor

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

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

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

/**
* @brief 刷新全部监控寄存器 0x2000~0x2006
* @note 调用时机:Init 末、Poll 每周期末
* @note 数据源:RunControl / Plsr 门面 API + s_fault 镜像
*/
static void PlsrCommandPublishMonitor(void)
{
PlsrCommandWriteDWord(PLSR_MON_ACC_PULSE_L, (uint32_t)PlsrGetAccPulse());
@@ -27,23 +58,27 @@ static void PlsrCommandPublishMonitor(void)
WriteHoldReg(PLSR_MON_ERR, s_fault);
}

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

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

/** @brief 读故障镜像(见 plsr_command.h) */
uint16_t PlsrCommandGetFault(void)
{
return s_fault;
}

/** @brief 指令模块初始化(见 plsr_command.h) */
void PlsrCommandInit(void)
{
WriteHoldReg(PLSR_CTRL_REG, 0U);
@@ -54,8 +89,9 @@ void PlsrCommandInit(void)
}

/**
* 上位机只改当前段频率:地址必须是当前段 FREQ_L,且一次写 2 字。
* 更新 s_seg[].freq_hz,置位由 Poll 调 ChangeFreq。
* @brief 运行中 FC10 写段频率钩子(见 plsr_command.h)
* @note 门禁:quantity==2、Busy、地址对齐 FREQ_L、seg0==当前段
* @note 频率非法写 0x04 且不更新段表、不请求 ChangeFreq
*/
void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity)
{
@@ -97,6 +133,7 @@ void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity)

f = (uint32_t)ReadHoldReg(start_addr) |
((uint32_t)ReadHoldReg((uint16_t)(start_addr + 1U)) << 16);
/* 运行中写频也做 0x04 门禁:非法不改段表、不请求 ChangeFreq */
if ((f < 1U) || (f > 100000U))
{
PlsrCommandSetFault(PLSR_ERR_FREQ_ILLEGAL);
@@ -113,14 +150,23 @@ void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity)
s_live_freq_req = 1U;
}

/**
* @brief 周期轮询(见 plsr_command.h)
*
* 控制位处理顺序(同周期多 bit 时):
* 1. 读 ctrl 后立即写 0(避免重复触发)
* 2. STOP — 急停
* 3. CLR — 清累计 + ClearFault
* 4. START — 仅当无 STOP 位:Apply → ClearFault → Stop → Start
* 动态改频在控制位之后、PublishMonitor 之前执行。
*/
void PlsrCommandPoll(void)
{
uint16_t ctrl = ReadHoldReg(PLSR_CTRL_REG);

if (ctrl != 0U)
{
/* 上位机置位,从站处理后立刻清零 */
WriteHoldReg(PLSR_CTRL_REG, 0U);
WriteHoldReg(PLSR_CTRL_REG, 0U); /* 从站「读-改-清」协议:处理后清零 */

if ((ctrl & PLSR_CTRL_BIT_STOP) != 0U)
{
@@ -130,7 +176,7 @@ void PlsrCommandPoll(void)
if ((ctrl & PLSR_CTRL_BIT_CLR) != 0U)
{
PlsrClearAccPulse();
PlsrCommandClearFault();
PlsrCommandClearFault(); /* 清零累计时同步清 0x2006 */
}

/* 同时有停止位时不启动 */
@@ -138,6 +184,7 @@ void PlsrCommandPoll(void)
((ctrl & PLSR_CTRL_BIT_STOP) == 0U))
{
(void)PlsrParamBlockApplyFromHold();
/* 启动视为正常入口:先写故障码 0,再 Stop+Start;非法频率由 Start 改写为 4 */
PlsrCommandClearFault();
PlsrStop();
(void)PlsrStart(PlsrParamGetStartSeg());


+ 77
- 20
plsr/command/plsr_command.h Zobrazit soubor

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

#include <stdint.h>

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

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

#define PLSR_ERR_NONE 0x00U /* 无故障 */
#define PLSR_ERR_PULSE_CNT 0x01U /* 脉冲输出个数与设置不一致(告警,继续) */
#define PLSR_ERR_FREQ_UNREACH 0x02U /* 输出频率与设置不一致/三角达不到(告警,继续) */
#define PLSR_ERR_CURVE_MISMATCH 0x03U /* 加减速曲线与设置不一致(告警,继续) */
#define PLSR_ERR_FREQ_ILLEGAL 0x04U /* 脉冲频率非法(1~100k)(告警,禁止启动) */
/*
* 故障码定义(写入 0x2006,须经 PlsrCommandSetFault,勿直接只写寄存器)
*
* 码 | 宏名 | 含义 | 等级 | 处理策略
* ----|-------------------------|--------------------------------|------|------------------
* 0 | PLSR_ERR_NONE | 无故障 | — | 启动/CLR 时写入
* 1 | PLSR_ERR_PULSE_CNT | 实际脉冲数与段目标不一致 | 告警 | 继续跑,不中断
* 2 | PLSR_ERR_FREQ_UNREACH | 规划峰值达不到设定目标(三角) | 告警 | 按压后规划继续
* 3 | PLSR_ERR_CURVE_MISMATCH | 加减速相内脉冲与规划预算不符 | 告警 | 继续跑
* 4 | PLSR_ERR_FREQ_ILLEGAL | 频率 ∉ [1, 100000] Hz | 门禁 | 禁止启动/改频
*/
#define PLSR_ERR_NONE 0x00U
#define PLSR_ERR_PULSE_CNT 0x01U
#define PLSR_ERR_FREQ_UNREACH 0x02U
#define PLSR_ERR_CURVE_MISMATCH 0x03U
#define PLSR_ERR_FREQ_ILLEGAL 0x04U

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

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

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

/**
* @brief 清故障为 0
* @note 调用时机:START 前、CLR 控制位、Init
*/
void PlsrCommandClearFault(void);

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

/**
* FC10 写段频率双字后调用:仅当地址对应「当前运行段」频率时,
* 更新段表并置标志,由 Poll 调 ChangeFreq。
* @brief FC10 写段频率双字后的钩子
* @param start_addr 写入起始保持寄存器地址
* @param quantity 写入字数,须为 2
* @note 调用时机:Modbus 从站 FC10 应答路径;仅运行中且地址为「当前段 FREQ_L」有效
* @note 副作用:更新段表 freq_hz,置 s_live_freq_req 由 Poll 调 ChangeFreq
*/
void PlsrCommandOnSegFreqHoldWrite(uint16_t start_addr, uint16_t quantity);



+ 79
- 4
plsr/param/plsr_param.c Zobrazit soubor

@@ -1,6 +1,21 @@
/**
* @file plsr_param.c
* @brief 参数块管理模块实现(运行映像 + 分帧导入/导出)
* @brief 参数块管理实现:运行映像 + 分帧导入/导出 + ParamBlockTask
*
* @details 模块职责
* 维护静态映像 s_cfg / s_seg[],实现寄存器 ↔ 结构体双向转换,以及
* 「1+N 分帧」收齐后的异步 Apply(信号量唤醒本任务)。
*
* 静态状态说明
* s_cfg / s_seg[] — 运行唯一参数源
* s_param_sem — 收齐分帧后 Post,Task Pend 后 Apply
* s_expect_frames — 期望总帧 = 1 + 公共帧中的段数
* s_recv_frames — 已收帧计数(公共算第 1 帧)
* s_xfer_active — 正在接收一批分帧传输
*
* 与其它模块关系
* Modbus FC10 应答路径调用 OnHoldWrite;Command START 也会同步 ApplyFromHold;
* Persist 仅保存公共参数;段表每次由上位机全量下发。
*/
#include "plsr_param.h"
#include "plsr_persist.h"
@@ -20,6 +35,10 @@ static volatile uint8_t s_xfer_active;
/* 运行映像 */
/*============================================================================*/

/**
* @brief 出厂默认参数(见 plsr_param.h)
* @note 与上位机 initCommonDefaults 约定一致,便于未写 PLC 时行为可预期
*/
void PlsrParamInitDefault(void)
{
uint16_t i;
@@ -27,7 +46,7 @@ void PlsrParamInitDefault(void)
memset(&s_cfg, 0, sizeof(s_cfg));
memset(s_seg, 0, sizeof(s_seg));

/* 与上位机默认约定一致;真正运行参数由 ApplyFromHold 覆盖 */
/* 与上位机默认约定一致;真正运行参数由 ApplyFromHold / PersistLoad 覆盖 */
s_cfg.pulse_y = 0U;
s_cfg.dir_y = 3U;
s_cfg.wait_x_sel = 0U;
@@ -56,11 +75,16 @@ void PlsrParamInitDefault(void)
}
}

/** @return 公共参数映像指针(见 plsr_param.h) */
PlsrCfg_t *PlsrParamGetCfg(void)
{
return &s_cfg;
}

/**
* @brief 取段参数指针(见 plsr_param.h)
* @note 越界钳到段 0,避免空指针
*/
PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based)
{
if (seg_0based >= PLSR_SEG_MAX)
@@ -70,6 +94,7 @@ PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based)
return &s_seg[seg_0based];
}

/** @return 有效段数,钳制到 [0, PLSR_SEG_MAX](见 plsr_param.h) */
uint16_t PlsrParamGetSegCount(void)
{
if (s_cfg.seg_count < 1U)
@@ -83,6 +108,10 @@ uint16_t PlsrParamGetSegCount(void)
return s_cfg.seg_count;
}

/**
* @brief 启动段 1-based(见 plsr_param.h)
* @note 非法起始段当前仅裁剪,TODO 可报故障码
*/
uint16_t PlsrParamGetStartSeg(void)
{
uint16_t n = PlsrParamGetSegCount();
@@ -104,10 +133,10 @@ uint16_t PlsrParamGetStartSeg(void)
}

/*============================================================================*/
/* 分帧导入 / 导出 */
/* 寄存器双字读写辅助 */
/*============================================================================*/

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

/** 写无符号 32 位到相邻两寄存器 */
static void PlsrParamWriteDWord(uint16_t addr, uint32_t value)
{
WriteHoldReg(addr, (uint16_t)(value & 0xFFFFU));
WriteHoldReg((uint16_t)(addr + 1U), (uint16_t)((value >> 16) & 0xFFFFU));
}

/** 按有符号 32 位解释双字(频率/脉冲补码) */
static int32_t PlsrParamReadSignedDWord(uint16_t addr)
{
return (int32_t)PlsrParamReadDWord(addr);
}

/**
* @brief 判断本次 FC10 是否为「整段 8 字写到某段基址」
* @return 1=是段帧;0=否
*/
static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity)
{
uint16_t i;
@@ -146,6 +181,11 @@ static uint8_t PlsrParamIsSegFrameWrite(uint16_t start_addr, uint16_t quantity)
return 0U;
}

/**
* @brief 更新分帧状态三寄存器,供上位机诊断
* @param st PLSR_PARAM_XFER_*
* @param fail_frame 失败帧序号(成功时写 0)
*/
static void PlsrParamSetXferStatus(uint16_t st, uint16_t fail_frame)
{
WriteHoldReg(PLSR_PARAM_XFER_STATUS_REG, st);
@@ -153,6 +193,18 @@ static void PlsrParamSetXferStatus(uint16_t st, uint16_t fail_frame)
WriteHoldReg(PLSR_PARAM_XFER_RECV_REG, s_recv_frames);
}

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

/**
* @brief 保持寄存器 → 运行映像(见 plsr_param.h)
*
* 字段裁剪策略(当前不做故障码,仅钳制到合法枚举/范围):
* pulse_y>2 → 0;dir 强制 3;accel_mode>2 → LINEAR;seg_count 上限 10;
* start_seg 钳到 [1,n];wait_type>4 → TIME。
* 频率合法性由 run_control 启动/开段时查 0x04,此处不拒绝写入。
*/
uint8_t PlsrParamBlockApplyFromHold(void)
{
uint16_t i;
@@ -235,6 +287,10 @@ uint8_t PlsrParamBlockApplyFromHold(void)
return 1U;
}

/**
* @brief 运行映像 → 全部相关保持寄存器(含未使用段槽位)
* @note 0x100F 保留字写 0;段表写满 PLSR_SEG_MAX 槽,避免读回脏数据
*/
void PlsrParamBlockExportToHold(void)
{
uint16_t i;
@@ -272,6 +328,15 @@ void PlsrParamBlockExportToHold(void)
}
}

/**
* @brief FC10 写后分帧状态机(见 plsr_param.h)
*
* 状态转移:
* 写满公共 20 字 → BUSY,expect=1+N,recv=1
* 传输中写段基址 8 字 → recv++
* recv>=expect → 清 active,Post 信号量(或同步 Apply)
* 其它写地址忽略(例如运行中只写频率双字不走本路径的分帧逻辑)
*/
void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity)
{
if (quantity == 0U)
@@ -317,6 +382,7 @@ void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity)
}
else if (PlsrParamBlockApplyFromHold() != 0U)
{
/* 信号量尚未创建时的兜底:同步 Apply */
PlsrParamSetXferStatus(PLSR_PARAM_XFER_OK, 0U);
}
else
@@ -326,6 +392,7 @@ void PlsrParamBlockOnHoldWrite(uint16_t start_addr, uint16_t quantity)
}
}

/** @brief 创建信号量并置传输空闲(见 plsr_param.h) */
void PlsrParamBlockInit(void)
{
s_param_sem = OSSemCreate(0U);
@@ -335,6 +402,14 @@ void PlsrParamBlockInit(void)
PlsrParamSetXferStatus(PLSR_PARAM_XFER_IDLE, 0U);
}

/**
* @brief 参数块任务(见 plsr_param.h)
*
* for(;;) 为 uC/OS 任务标准死循环:
* OSSemPend 永久等待 → ApplyFromHold → 写 XFER_OK 并尽快 PersistPoll
* (不等 PlsrTask,缩短参数落盘延迟)。
* Apply 失败则写 XFER_FAIL 与当前 recv 帧号。
*/
void PlsrParamBlockTask(void *pArg)
{
INT8U err;


+ 142
- 61
plsr/param/plsr_param.h Zobrazit soubor

@@ -1,140 +1,221 @@
/**
* @file plsr_param.h
* @brief 参数块管理模块:寄存器地址、运行结构体、分帧导入/导出
* @brief 参数块管理:保持寄存器映射、运行映像结构体、分帧导入/导出
*
* 协议概要:
* - 公共帧:0x1000~0x1013(20 字),0x1009=段数 N,预期总帧=1+N
* - 段帧:0x1100+(i)*0x10,每段 8 字
* - 传输状态:0x0FFC/0x0FFD/0x0FFE
* @details 模块职责
* 1. 定义公共参数区 0x1000~0x1013、段表区 0x1100+i*0x10 的地址与字段语义;
* 2. 维护运行侧唯一映像 s_cfg / s_seg[](运动规划只读此映像,不直接啃寄存器);
* 3. 支持上位机「1 公共帧 + N 段帧」分帧 FC10 写入:收齐后 ApplyFromHold;
* 4. 支持 ExportToHold:上电默认或读回时把映像摊到保持寄存器。
*
* 与其它模块关系
* - modbus_rtu / modbus_data :ReadHoldReg / WriteHoldReg
* - plsr_command :启动前 ApplyFromHold;运行中写频改 s_seg[].freq_hz
* - plsr_run_control :GetCfg / GetSeg / GetSegCount / GetStartSeg
* - plsr_persist :Apply 成功后 NotifyCommonChanged,落盘仅公共参数
* - PlsrParamBlockTask :独立 uC/OS 任务,等信号量后 Apply + PersistPoll
*
* 关键数据流
* 上位机写公共帧@0x1000 → OnHoldWrite 开传输 expect=1+N
* 上位机写段帧@0x1100+ → recv++;收齐 → OSSemPost
* ParamBlockTask → ApplyFromHold → 结构体更新 → PersistNotify → 状态 OK
* 上电:InitDefault → PersistLoad 覆盖公共 → ExportToHold
*
* 协议概要
* - 公共帧:0x1000~0x1013(20 字),0x1009=段数 N,预期总帧=1+N
* - 段帧:0x1100+(i)*0x10,每段 8 字
* - 传输状态:0x0FFC 已收帧 / 0x0FFD 失败帧 / 0x0FFE 状态
*/
#ifndef PLSR_PARAM_H
#define PLSR_PARAM_H

#include <stdint.h>

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

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

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

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

/* 分帧传输状态(上位机可轮询) */
#define PLSR_PARAM_XFER_RECV_REG 0x0FFCU
#define PLSR_PARAM_XFER_FAIL_FRAME_REG 0x0FFDU
#define PLSR_PARAM_XFER_STATUS_REG 0x0FFEU
/* ---------- 分帧传输状态(上位机可选读) ---------- */
#define PLSR_PARAM_XFER_RECV_REG 0x0FFCU /* 已接收帧计数 */
#define PLSR_PARAM_XFER_FAIL_FRAME_REG 0x0FFDU /* 失败时的帧序号 */
#define PLSR_PARAM_XFER_STATUS_REG 0x0FFEU /* IDLE/BUSY/OK/FAIL */

#define PLSR_PARAM_XFER_IDLE 0U
#define PLSR_PARAM_XFER_BUSY 1U
#define PLSR_PARAM_XFER_OK 2U
#define PLSR_PARAM_XFER_FAIL 3U

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

/**
* 发送方式(影响本段终止频率 f_end 与是否允许同向不停表衔接)
* COMPLETE:段末规划到公共止速;FOLLOW:无门禁时可衔接下一段目标频
*/
typedef enum {
PLSR_SEND_COMPLETE = 0, /* 完成:上一段发完后,本段开头再加减速到本段频率 */
PLSR_SEND_FOLLOW = 1 /* 后续:上一段发完时频率已到本段目标;同向可不停表 */
PLSR_SEND_COMPLETE = 0,
PLSR_SEND_FOLLOW = 1
} PlsrSendMode_e;

/** 加减速曲线形状(脉冲域规划) */
typedef enum {
PLSR_ACCEL_LINEAR = 0,
PLSR_ACCEL_S = 1,
PLSR_ACCEL_SINE = 2
PLSR_ACCEL_LINEAR = 0, /* f^2 = f0^2 ± 2an */
PLSR_ACCEL_S = 1, /* 七段 S 曲线按脉冲进度 */
PLSR_ACCEL_SINE = 2 /* raised-cosine */
} PlsrAccelMode_e;

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

/**
* 段末/段内等待类型(由 run_control + signal_io 执行)
* TIME/SIGNAL/ACT/EXT 均会阻止 FOLLOW 不停表衔接(BlocksFollowKeep)
*/
typedef enum {
PLSR_WAIT_TIME = 0, /* 发完后等 wait_ms,再跳转 */
PLSR_WAIT_SIGNAL = 1, /* 发完后等 WAIT 口下降沿(EXTI),再跳转 */
PLSR_WAIT_ACT = 2, /* ACT:满 act_ms 从当前频率切下一段;脉冲先完则等到点 */
PLSR_WAIT_EXT = 3, /* 运行中 EXT 下降沿中途切段;发完后仍等 EXT */
PLSR_WAIT_EXT_OR_DONE = 4 /* EXT 沿或脉冲发完,先到先切 */
PLSR_WAIT_TIME = 0, /* 发完后等 wait_ms(0 按 1ms),再跳转 */
PLSR_WAIT_SIGNAL = 1, /* 发完后等 WAIT 口下降沿 */
PLSR_WAIT_ACT = 2, /* 开跑后满 act_ms 从当前频切下一段;脉冲先完则等到点 */
PLSR_WAIT_EXT = 3, /* 运行中 EXT 沿中途切段;发完后仍等 EXT */
PLSR_WAIT_EXT_OR_DONE = 4 /* EXT 沿或脉冲发完,先到先切 */
} PlsrWaitType_e;

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

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

/* ---------- 运行映像访问 ---------- */

/**
* @brief 填出厂默认 s_cfg / s_seg(随后可被 PersistLoad / Apply 覆盖)
* @note 调用时机:PlsrInit 最先调用
*/
void PlsrParamInitDefault(void);

/** @return 公共参数映像指针(非空) */
PlsrCfg_t *PlsrParamGetCfg(void);

/**
* @param seg_0based 段索引 0-based;越界钳到 0
* @return 段参数指针
*/
PlsrSeg_t *PlsrParamGetSeg(uint16_t seg_0based);

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

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

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

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

/**
* @brief 参数块任务:阻塞等信号量 → ApplyFromHold → 状态 OK/FAIL → PersistPoll
* @param pArg 未使用
* @note for(;;) 死循环为 RTOS 任务常态;空循环体是错觉(花括号内有 Pend/Apply)
*/
void PlsrParamBlockTask(void *pArg);
/** FC10 应答后由 Modbus 调用:分帧计数,收齐则 post 信号量 */

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

/**
* @brief 保持寄存器 → s_cfg / s_seg
* @return 1=成功(当前恒为 1;完整校验/故障码待扩展)
* @note 副作用:NotifyCommonChanged 标记 Flash 待存
*/
uint8_t PlsrParamBlockApplyFromHold(void);
/** 运行结构体→寄存器(上电默认、上位机读回) */

/**
* @brief s_cfg / s_seg → 保持寄存器(供 0x03 读回)
* @note 调用时机:上电 Export、PlsrTask 首次延迟后再次 Export
*/
void PlsrParamBlockExportToHold(void);

#endif

+ 21
- 0
plsr/path_plan/plsr_path_plan.c Zobrazit soubor

@@ -1,10 +1,22 @@
/**
* @file plsr_path_plan.c
* @brief 多段路径规划实现(只做决策,不碰 TIM/GPIO)
*
* @details 模块职责
* NextSeg / ResolveAfterSeg:按 jump_seg 决定下一段或结束;
* IsForward:相对模式看 pulse_cnt 符号,绝对模式看「目标−相对原点位置」符号;
* GetWaitMs:仅 WAIT_TIME,0 映射为 1ms。
*
* 关键数据流
* AfterSegDone / CutSeg / ActExpire → ResolveAfterSeg → BeginSeg 或 FinishAll
* WillFollowKeep / CutSeg keep 判定 → IsForward(下一段, AbsLocalPos)
*/
#include "plsr_path_plan.h"
#include "plsr_param.h"

/**
* @brief 解析下一段(见 plsr_path_plan.h)
*/
int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based)
{
PlsrSeg_t *seg = PlsrParamGetSeg(cur_seg_0based);
@@ -33,6 +45,9 @@ int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based)
return -1;
}

/**
* @brief 判断段方向(见 plsr_path_plan.h)
*/
uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse)
{
PlsrSeg_t *seg = PlsrParamGetSeg(seg_0based);
@@ -51,6 +66,9 @@ uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse)
return (move >= 0) ? 1U : 0U;
}

/**
* @brief 段后 WAIT 时间(见 plsr_path_plan.h)
*/
uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based)
{
PlsrSeg_t *seg = PlsrParamGetSeg(seg_0based);
@@ -64,6 +82,9 @@ uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based)
return 0U;
}

/**
* @brief 段后解析下一段(见 plsr_path_plan.h)
*/
int16_t PlsrPathPlanResolveAfterSeg(uint16_t cur_seg_0based)
{
return PlsrPathPlanNextSeg(cur_seg_0based);


+ 18
- 6
plsr/path_plan/plsr_path_plan.h Zobrazit soubor

@@ -1,9 +1,14 @@
/**
* @file plsr_path_plan.h
* @brief 多段路径规划:段序、跳转、段间等待、相对/绝对方向判定
* @brief 多段路径规划:段序、跳转、相对/绝对方向判定
*
* 不直接驱动硬件;由运行控制查询后执行。
* 等待类型由 run_control + signal_io 执行(时间用 TIM3,信号用 EXTI)。
* @details 模块职责
* 只读段表与公共定位模式,回答「下一段是谁」「这一段正还是反」「段后 WAIT 多久」。
* 不驱动 TIM/GPIO;等待类型的具体执行由 run_control + signal_io 完成。
*
* 与其它模块关系
* - plsr_param :GetSeg / GetSegCount / GetCfg
* - plsr_run_control :GotoNext、CutSeg、WillFollowKeep、BeginSeg 方向
*/
#ifndef PLSR_PATH_PLAN_H
#define PLSR_PATH_PLAN_H
@@ -16,22 +21,29 @@
* @return 下一段 0-based;无下一段或非法跳转返回 -1
* @note jump=0:末段→结束;非末段→顺序下一段
* jump=1~N:跳到对应段(可循环)
* 其它:结束
*/
int16_t PlsrPathPlanNextSeg(uint16_t cur_seg_0based);

/**
* @brief 判断该段运动方向
* @param acc_pulse 相对本次启动原点的位置(绝对模式算位移用)
* @param seg_0based 段索引
* @param acc_pulse 相对本次启动原点的位置(绝对模式算位移用)
* @return 1=正向,0=反向
* @note 相对:看 pulse_cnt 符号;绝对:看 (目标−相对原点位置) 符号
*/
uint8_t PlsrPathPlanIsForward(uint16_t seg_0based, int32_t acc_pulse);

/**
* @brief 段结束后 WAIT 时间(ms);仅 WAIT_TIME 有效,0 按 1ms
* @brief 段结束后 WAIT 时间(ms)
* @note 仅 WAIT_TIME 有效;0 按 1ms;其它等待类型返回 0(由 run_control 处理)
*/
uint16_t PlsrPathPlanGetWaitMs(uint16_t seg_0based);

/** 段结束(或等待结束)后解析下一段,语义同 NextSeg */
/**
* @brief 段结束(或等待结束)后解析下一段
* @note 当前语义同 NextSeg;保留独立符号便于日后插入后处理
*/
int16_t PlsrPathPlanResolveAfterSeg(uint16_t cur_seg_0based);

#endif

+ 47
- 8
plsr/plsr.c Zobrazit soubor

@@ -1,6 +1,18 @@
/**
* @file plsr.c
* @brief PLSR 门面实现:转发到运行控制,并提供周期任务
* @brief PLSR 门面实现:初始化编排与周期任务
*
* @details 模块职责
* 按固定顺序调用各子模块 Init,并在 PlsrTask 中串联指令轮询、持久化与
* 运行控制节拍。对外 API 均为薄转发,不含业务逻辑。
*
* 与其它模块关系
* 本文件仅依赖 plsr.h 及各子模块头文件,不被其它 plsr 模块反向引用。
*
* 关键数据流
* PlsrInit 顺序:Param 默认 → Persist 加载 → PulseDriver → SignalIo →
* RunControl → ExportToHold → CommandInit
* PlsrTask 循环:CommandPoll → PersistPoll → RunControlTickMs → OSTimeDly(1)
*/
#include "plsr.h"
#include "plsr_pulse_driver.h"
@@ -11,17 +23,23 @@
#include "plsr_signal_io.h"
#include "ucos_ii.h"

/**
* @brief 子系统初始化(见 plsr.h)
*/
void PlsrInit(void)
{
PlsrParamInitDefault();
PlsrPersistLoad(); /* 有 Flash 记录则覆盖公共参数;段表仍用默认 */
PlsrPersistLoad(); /* 有 Flash 记录则覆盖公共参数;段表仍用默认/上位机下发 */
PlsrPulseDriverInit();
PlsrSignalIoInit();
PlsrRunControlInit();
PlsrParamBlockExportToHold(); /* 上电把运行映像摊到保持寄存器 */
PlsrParamBlockExportToHold(); /* 上电把运行映像摊到保持寄存器,供 Modbus 0x03 读 */
PlsrCommandInit();
}

/**
* @brief 启动多段脉冲(见 plsr.h)
*/
uint8_t PlsrStart(uint16_t start_seg)
{
if (start_seg == 0U)
@@ -31,36 +49,51 @@ uint8_t PlsrStart(uint16_t start_seg)
return PlsrRunControlStart(start_seg);
}

/** @brief 急停(见 plsr.h) */
void PlsrStop(void)
{
PlsrRunControlStop();
}

/** @brief 运行中改频(见 plsr.h) */
uint8_t PlsrChangeFreq(uint32_t new_tgt_hz)
{
return PlsrRunControlChangeFreq(new_tgt_hz);
}

/** @brief 忙闲查询(见 plsr.h) */
uint8_t PlsrIsBusy(void)
{
return PlsrRunControlIsBusy();
}

/** @brief 累计脉冲(见 plsr.h) */
int32_t PlsrGetAccPulse(void)
{
return PlsrRunControlGetAccPulse();
}

/** @brief 清零累计(见 plsr.h) */
void PlsrClearAccPulse(void)
{
PlsrRunControlClearAccPulse();
}

/** @brief 脉冲 ISR 入口(见 plsr.h) */
void PlsrOnPulseIsr(void)
{
PlsrRunControlOnPulseIsr();
}

/**
* @brief PLSR 主周期任务
* @param pArg 未使用
*
* 任务内局部状态:
* s_booted — 首次延迟导出完成标志
* s_was_busy — 仅 LOOP_TEST:上一拍 busy,用于检测「刚结束」边沿
* s_idle_ticks — 仅 LOOP_TEST:空闲累计 tick,满 OS_TICKS_PER_SEC 自动再启
*/
void PlsrTask(void *pArg)
{
static uint8_t s_booted = 0U;
@@ -73,7 +106,10 @@ void PlsrTask(void *pArg)

for (;;)
{
/* 首次进入:等 Modbus 清寄存器后再导出运行映像,避免被冲掉 */
/*
* 首次进入:Modbus 从站上电常会清零保持寄存器。
* 延迟 ~300ms 后再 Export + CommandInit,避免默认映像被冲掉。
*/
if (s_booted == 0U)
{
OSTimeDly(30U); /* ~300ms @ 100Hz tick */
@@ -86,12 +122,15 @@ void PlsrTask(void *pArg)
#endif
}

PlsrCommandPoll(); /* 收 START/STOP 等,回写忙闲状态 */
PlsrCommandPoll(); /* 收 START/STOP/CLR,回写 0x2000~0x2006 监控 */
PlsrPersistPoll(); /* 公共参数有改且空闲则追加写入 Flash */
PlsrRunControlTickMs(); /* ACT/WAIT/EXT;改频在脉冲 ISR */
PlsrRunControlTickMs(); /* ACT/WAIT/EXT、换向延时、ApplyPending */

#if (PLSR_LOOP_TEST_EN != 0)
/* 调试:空闲满 1s 自动从第 1 段再启 */
/*
* 调试循环:检测到 busy→idle 边沿后,空闲满 1s 自动从第 1 段再启。
* 正式产品 PLSR_LOOP_TEST_EN=0 时本段不参与编译。
*/
if (s_booted != 0U)
{
uint8_t busy = PlsrIsBusy();
@@ -122,6 +161,6 @@ void PlsrTask(void *pArg)
}
#endif

OSTimeDly(1U);
OSTimeDly(1U); /* 约 1ms 任务节拍,与 TIM3 独立 */
}
}

+ 74
- 14
plsr/plsr.h Zobrazit soubor

@@ -1,8 +1,27 @@
/**
* @file plsr.h
* @brief PLSR 对外门面:初始化、周期任务、启停与状态查询
* @brief PLSR 多段脉冲输出子系统对外门面
*
* 内部模块:参数块 / 公共参数持久化 / 指令接口 / 路径规划 / 加减速曲线 / 运行控制 / 脉冲驱动
* @details 模块职责
* 本文件定义上位机/应用层唯一可见的 PLSR 入口,封装初始化、周期任务、
* 启停控制、运行状态查询与脉冲中断回调。内部实现全部委托给子模块,
* 本层不做运动规划与硬件寄存器操作。
*
* 与其它模块关系
* - plsr_param :运行参数映像(公共 + 段表),Modbus 保持寄存器 ↔ 结构体
* - plsr_persist :公共参数 Flash 掉电保存(不含段表)
* - plsr_command :Modbus 监控区 0x2000~0x2006、控制区 0x3000 指令解析
* - plsr_run_control:段状态机、等待策略、加减速执行、故障诊断
* - plsr_pulse_driver:TIM PWM 脉冲 + Y3 方向 GPIO
* - plsr_signal_io :WAIT/EXT 外部输入、TIM3 1ms 时基
*
* 关键数据流
* 上电:PlsrInit → 默认参数 → Flash 覆盖公共参数 → 驱动/IO/运行控制初始化
* → 参数导出到保持寄存器 → 指令模块初始化
* 周期:PlsrTask → CommandPoll(START/STOP/CLR、监控回写)
* → PersistPoll(空闲时 Flash 落盘)→ RunControlTickMs(等待/切段)
* 脉冲:TIM UPDATE 中断 → PlsrOnPulseIsr → RunControl 计数 + 逐拍改频
* 启动:PlsrStart → RunControlStart → 路径规划 + 曲线规划 + PWM 输出
*/
#ifndef PLSR_H
#define PLSR_H
@@ -10,38 +29,79 @@
#include <stdint.h>
#include "plsr_param.h"

/* 1=第1段跑完后停约1s再循环(仅调试用,正式产品保持 0) */
/*
* PLSR_LOOP_TEST_EN:调试宏。
* 1 = 第 1 段跑完后空闲约 1s 自动再启(仅联调);正式产品保持 0。
*/
#ifndef PLSR_LOOP_TEST_EN
#define PLSR_LOOP_TEST_EN 0
#endif

/** 初始化参数映像、脉冲驱动、运行控制、指令寄存器 */
/**
* @brief 子系统一次性初始化
* @note 调用时机:OS 启动后、Modbus 从站就绪前,由 main 或启动任务调用一次
* @note 副作用:加载 Flash 公共参数、初始化 TIM/GPIO、导出默认映像到保持寄存器
*/
void PlsrInit(void);

/** uC/OS 任务:周期处理指令 + 运行控制节拍 */
/**
* @brief uC/OS-II 周期任务入口
* @param pArg 任务参数(未使用,传 NULL)
* @note 调用时机:创建后永久循环,建议 1ms 节拍(OSTimeDly(1))
* @note 副作用:轮询 Modbus 控制位、刷新监控寄存器、推进运行控制状态机、
* 空闲时触发 Flash 持久化;首次进入延迟 ~300ms 再导出参数(避免 Modbus 清零冲掉)
*/
void PlsrTask(void *pArg);

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

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

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

uint8_t PlsrIsBusy(void); /**< 1=运动中 */
int32_t PlsrGetAccPulse(void); /**< 累计脉冲(绝对坐标用) */
/**
* @brief 查询是否运动中
* @return 1=busy(段执行或等待中);0=空闲
*/
uint8_t PlsrIsBusy(void);

/**
* @brief 读取累计脉冲计数(有符号,正=正向累计)
* @return 自系统运行以来的净脉冲数,供绝对坐标与监控 0x2000 使用
*/
int32_t PlsrGetAccPulse(void);

/**
* @brief 清零累计脉冲与绝对坐标原点
* @note 调用时机:CommandPoll 收到 CLR 控制位
* @note 副作用:s_acc_pulse=0、s_abs_origin=0,同步清故障码
*/
void PlsrClearAccPulse(void);

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

#endif

+ 76
- 4
plsr/pulse_driver/plsr_pulse_driver.c Zobrazit soubor

@@ -1,7 +1,16 @@
/**
* @file plsr_pulse_driver.c
* @brief 脉冲输出:Y0/PF6/TIM10、Y1/PF8/TIM13、Y2/PF7/TIM11;方向固定 Y3
* @note TIM10/11=168MHz,TIM13=84MHz;段内锁 PSC 只改 ARR;改频在周期边界生效
* @brief 脉冲输出驱动实现:TIM PWM + Y3 方向
*
* @details 模块职责
* 实现 plsr_pulse_driver.h 全部 API。核心约束:
* - 空闲不关 CC1E:Forced active 主动拉高,避免 AF 高阻耦合假脉冲;
* - ARR/CCR 预装载:改频写影子寄存器,下一 UPDATE 生效,减少毛刺/多计;
* - ISR 内禁止停表换 PSC:挂起 s_req_*,任务 ApplyPending。
*
* 关键数据流
* PlanSeg → LockPscRange → DIR_WAIT → Start → ISR SetFreqIsr(只改 ARR)
* 段末 ArmOnePulseStop → AfterSegDone Stop / FOLLOW ClearOnePulseStop
*/
#include "plsr_pulse_driver.h"
#include "plsr_param.h"
@@ -23,6 +32,13 @@ TIM_HandleTypeDef htim13;
#define PLSR_Y3_PORT GPIOF
#define PLSR_Y3_PIN GPIO_PIN_9

/*
* ---------- 模块静态状态 ----------
* s_last_freq/psc/arr — 上次量化结果;匀速热路径比较后可跳过写寄存器
* s_active_y / htim — 当前选中脉冲路
* s_psc_locked / locked_psc — 段内 PSC 锁;跨度过大时 locked=0
* s_req_freq / pending — ISR 无法换 PSC 时的挂起改频
*/
static uint32_t s_last_freq;
static uint32_t s_last_psc = 0xFFFFFFFFUL;
static uint32_t s_last_arr = 0xFFFFFFFFUL;
@@ -35,9 +51,16 @@ static uint32_t s_locked_psc;
static volatile uint32_t s_req_freq;
static volatile uint8_t s_req_pending;

/*============================================================================*/
/* 路选择 / 时钟 / GPIO */
/*============================================================================*/

/**
* @brief 按 pulse_y 返回对应 HAL 句柄
* @note Y1→TIM13、Y2→TIM11,与端子编号交叉
*/
static TIM_HandleTypeDef *PlsrPulseDriverHtimByY(uint16_t pulse_y)
{
/* Y1=PF8→TIM13,Y2=PF7→TIM11,与端子编号交叉 */
if (pulse_y == 1U)
{
return &htim13;
@@ -49,6 +72,7 @@ static TIM_HandleTypeDef *PlsrPulseDriverHtimByY(uint16_t pulse_y)
return &htim10;
}

/** @brief 从公共参数取脉冲端子,越界钳到 Y0 */
static uint8_t PlsrPulseDriverSelectY(void)
{
PlsrCfg_t *cfg = PlsrParamGetCfg();
@@ -61,7 +85,7 @@ static uint8_t PlsrPulseDriverSelectY(void)
return (uint8_t)y;
}

/** TIM10/11:APB2 定时器 168MHz;TIM13:APB1 定时器 84MHz */
/** @brief TIM10/11=168MHz;TIM13=84MHz */
static uint32_t PlsrPulseDriverTimClkHz(const TIM_HandleTypeDef *htim)
{
if ((htim != (const TIM_HandleTypeDef *)0) &&
@@ -72,6 +96,7 @@ static uint32_t PlsrPulseDriverTimClkHz(const TIM_HandleTypeDef *htim)
return PLSR_TIM_CLK_APB2_HZ;
}

/** @brief 初始化 Y3 方向脚为推挽输出低 */
static void PlsrPulseDriverDirGpioInit(void)
{
GPIO_InitTypeDef gpio = {0};
@@ -114,6 +139,7 @@ static void PlsrPulseDriverOcHoldHigh(TIM_HandleTypeDef *htim)
TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_READY);
}

/** @brief 比较通道切回 PWM1 模式(开跑前) */
static void PlsrPulseDriverOcPwm1(TIM_HandleTypeDef *htim)
{
uint32_t ccmr;
@@ -124,6 +150,10 @@ static void PlsrPulseDriverOcPwm1(TIM_HandleTypeDef *htim)
htim->Instance->CCMR1 = ccmr;
}

/**
* @brief HAL MSP:为 TIM10/11/13 配 AF GPIO 与 NVIC
* @note 由 HAL_TIM_PWM_Init 回调;优先级 5,低于 EXTI/TIM3 之外的系统中断惯例
*/
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef gpio = {0};
@@ -165,6 +195,11 @@ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
}
}

/**
* @brief 初始化单路脉冲 TIM:PWM1 + ARR/CCR 预装载 + 空闲 Forced 拉高
* @param htim HAL 句柄
* @param inst TIM10 / TIM11 / TIM13
*/
static void PlsrPulseDriverTimInitOne(TIM_HandleTypeDef *htim, TIM_TypeDef *inst)
{
TIM_OC_InitTypeDef oc = {0};
@@ -198,6 +233,7 @@ static void PlsrPulseDriverTimInitOne(TIM_HandleTypeDef *htim, TIM_TypeDef *inst
PlsrPulseDriverOcHoldHigh(htim);
}

/** @brief 驱动初始化(见 plsr_pulse_driver.h) */
void PlsrPulseDriverInit(void)
{
s_last_freq = 0U;
@@ -216,6 +252,7 @@ void PlsrPulseDriverInit(void)
PlsrPulseDriverTimInitOne(&htim13, TIM13);
}

/** @brief 写方向脚(见 plsr_pulse_driver.h) */
void PlsrPulseDriverSetDir(uint8_t forward)
{
PlsrCfg_t *cfg = PlsrParamGetCfg();
@@ -233,16 +270,26 @@ void PlsrPulseDriverSetDir(uint8_t forward)
level ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

/** @brief 方向脚拉低(见 plsr_pulse_driver.h) */
void PlsrPulseDriverClearDir(void)
{
HAL_GPIO_WritePin(PLSR_Y3_PORT, PLSR_Y3_PIN, GPIO_PIN_RESET);
}

/** @brief 是否脉冲 TIM(见 plsr_pulse_driver.h) */
uint8_t PlsrPulseDriverIsPulseTim(TIM_TypeDef *instance)
{
return ((instance == TIM10) || (instance == TIM11) || (instance == TIM13)) ? 1U : 0U;
}

/*============================================================================*/
/* PSC / ARR 量化 */
/*============================================================================*/

/**
* @brief 在给定 PSC 下计算 ARR/CCR(约 50% 占空比)
* @note freq 钳到 [1,100000];ARR 落在 16 位合法范围
*/
static void PlsrPulseDriverCalcArrWithPsc(uint32_t clk_hz, uint32_t freq_hz, uint32_t psc,
uint32_t *arr, uint32_t *ccr)
{
@@ -290,6 +337,9 @@ static void PlsrPulseDriverCalcArrWithPsc(uint32_t clk_hz, uint32_t freq_hz, uin
*ccr = c;
}

/**
* @brief 自适应选择 PSC,再算 ARR/CCR(未锁 PSC 时用)
*/
static void PlsrPulseDriverCalcPscArr(uint32_t clk_hz, uint32_t freq_hz,
uint32_t *psc, uint32_t *arr, uint32_t *ccr)
{
@@ -330,6 +380,7 @@ static void PlsrPulseDriverCalcPscArr(uint32_t clk_hz, uint32_t freq_hz,
PlsrPulseDriverCalcArrWithPsc(clk_hz, freq_hz, p, arr, ccr);
}

/** @brief 按频率跨度锁 PSC(见 plsr_pulse_driver.h) */
void PlsrPulseDriverLockPscRange(uint32_t f_min_hz, uint32_t f_max_hz)
{
uint32_t ticks_lo;
@@ -394,17 +445,28 @@ void PlsrPulseDriverLockPscRange(uint32_t f_min_hz, uint32_t f_max_hz)
s_psc_locked = 1U;
}

/** @brief 解除 PSC 锁(见 plsr_pulse_driver.h) */
void PlsrPulseDriverUnlockPsc(void)
{
s_psc_locked = 0U;
s_locked_psc = 0U;
}

/** @brief 停单路:Forced active 拉高,关 CEN/UPDATE */
static void PlsrPulseDriverStopHtim(TIM_HandleTypeDef *htim)
{
PlsrPulseDriverOcHoldHigh(htim);
}

/*============================================================================*/
/* 启停 / 改频 */
/*============================================================================*/

/**
* @brief Start / StartFreeRun 公共路径
* @param freq_hz 0=停表;>0 开跑
* @param enable_update_it 1=开 UPDATE 中断(计数脉冲);0=自由跑
*/
static void PlsrPulseDriverStartCommon(uint32_t freq_hz, uint8_t enable_update_it)
{
uint32_t psc;
@@ -480,6 +542,7 @@ static void PlsrPulseDriverStartCommon(uint32_t freq_hz, uint8_t enable_update_i
}
}

/** @brief 任务上下文改频(见 plsr_pulse_driver.h) */
void PlsrPulseDriverSetFreq(uint32_t freq_hz)
{
uint32_t psc;
@@ -550,6 +613,7 @@ void PlsrPulseDriverSetFreq(uint32_t freq_hz)
s_last_arr = arr;
}

/** @brief ISR 改频(见 plsr_pulse_driver.h) */
void PlsrPulseDriverSetFreqIsr(uint32_t freq_hz)
{
uint32_t psc;
@@ -612,17 +676,20 @@ void PlsrPulseDriverSetFreqIsr(uint32_t freq_hz)
s_last_arr = arr;
}

/** @brief 挂起改频请求(见 plsr_pulse_driver.h) */
void PlsrPulseDriverRequestFreq(uint32_t freq_hz)
{
s_req_freq = freq_hz;
s_req_pending = 1U;
}

/** @brief 丢弃挂起(见 plsr_pulse_driver.h) */
void PlsrPulseDriverClearPending(void)
{
s_req_pending = 0U;
}

/** @brief 消费挂起改频(见 plsr_pulse_driver.h) */
void PlsrPulseDriverApplyPending(void)
{
uint32_t f;
@@ -641,16 +708,19 @@ void PlsrPulseDriverApplyPending(void)
PlsrPulseDriverSetFreq(f);
}

/** @brief 开 PWM + UPDATE 中断(见 plsr_pulse_driver.h) */
void PlsrPulseDriverStart(uint32_t freq_hz)
{
PlsrPulseDriverStartCommon(freq_hz, 1U);
}

/** @brief 开 PWM 不使能 UPDATE(见 plsr_pulse_driver.h) */
void PlsrPulseDriverStartFreeRun(uint32_t freq_hz)
{
PlsrPulseDriverStartCommon(freq_hz, 0U);
}

/** @brief 停三路(见 plsr_pulse_driver.h) */
void PlsrPulseDriverStop(void)
{
s_req_pending = 0U;
@@ -662,6 +732,7 @@ void PlsrPulseDriverStop(void)
s_last_arr = 0xFFFFFFFFUL;
}

/** @brief 武装 OPM 段末停表(见 plsr_pulse_driver.h) */
void PlsrPulseDriverArmOnePulseStop(void)
{
TIM_HandleTypeDef *htim = s_active_htim;
@@ -674,6 +745,7 @@ void PlsrPulseDriverArmOnePulseStop(void)
htim->Instance->CR1 |= TIM_CR1_OPM;
}

/** @brief 取消 OPM(见 plsr_pulse_driver.h) */
void PlsrPulseDriverClearOnePulseStop(void)
{
TIM_HandleTypeDef *htim = s_active_htim;


+ 83
- 14
plsr/pulse_driver/plsr_pulse_driver.h Zobrazit soubor

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

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

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

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

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

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

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

/**
* @brief 以指定频率启动选中路 PWM,并打开 UPDATE 中断
* @param freq_hz 0=停表;>0 开跑
*/
void PlsrPulseDriverStart(uint32_t freq_hz);

/**
* @brief 同 Start,但不使能 UPDATE 中断(自由跑,不计入脉冲 ISR)
*/
void PlsrPulseDriverStartFreeRun(uint32_t freq_hz);

/**
* @brief 任务上下文改频:PSC 不变则预装载 ARR;须换 PSC 则停表重装
* @note 频率或量化结果未变则立即返回
*/
void PlsrPulseDriverSetFreq(uint32_t freq_hz);

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

/**
* @brief 请求改频(挂起),在周期边界或任务里落到硬件
* @param freq_hz 目标 Hz;0 表示挂起停表
*/
void PlsrPulseDriverRequestFreq(uint32_t freq_hz);
/** 在更新中断里调用:应用挂起频率 */

/**
* @brief 消费挂起改频(TickMs / 更新路径)
* @note s_req_freq==0 时 Stop;否则 SetFreq
*/
void PlsrPulseDriverApplyPending(void);
/** 丢弃未生效的改频请求(进匀速锁定时用) */

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

/**
* @brief 停止三路脉冲 TIM,空闲 Forced active 拉高
* @note 不清方向脚;不清 PSC 锁(由上层 Unlock)
*/
void PlsrPulseDriverStop(void);
/** 当前拍结束后停止计数,避免多出一个上升沿 */

/**
* @brief 当前拍结束后停止计数(CR1.OPM),避免段末多出一个上升沿
* @note 调用时机:倒数第二脉冲 ISR,且不会 FOLLOW 不停表衔接时
*/
void PlsrPulseDriverArmOnePulseStop(void);
/** 取消单脉冲停表(后续模式同向衔接时用) */

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

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

#endif

+ 148
- 24
plsr/run_control/plsr_run_control.c Zobrazit soubor

@@ -17,6 +17,12 @@
* 匀速复用 ARR(不改频);remain<=dec_n 进减速。
* 任务 TickMs:仅 ACT/WAIT/EXT/换向延时 + ApplyPending(极少换 PSC)。
* TIM3 1ms:仅 ACT/WAIT 时基。
*
* 【故障诊断 0x2006】(PlsrCommandSetFault,告警码 1~3 不停机;4 禁止启动)
* 0x01 段结束 AfterSegDone:s_done != s_target
* 0x02 PlanSeg 后:规划峰值达不到设定 f_tgt(典型三角)
* 0x03 加速离开 APPROACH / 减速段末:相内脉冲 ≠ 规划预算
* 0x04 目标频率 ∉ [1,100000]:Start / BeginSeg / ChangeFreq / 运行中写频
*/
#include "plsr_run_control.h"
#include "plsr_path_plan.h"
@@ -28,6 +34,13 @@
#include "ucos_ii.h"
#include <stdint.h>

/*
* ---------- 运行主状态机 RcState_e ----------
* RC_IDLE — 无段在执行;可 Start;脉冲 ISR 不推进
* RC_DIR_WAIT — 方向 GPIO 已设,等 dir_delay_ms(OS tick)后开 PWM
* RC_RUN — 脉冲 ISR 推进 s_done、逐拍改频;TickMs 处理 EXT
* RC_WAIT_COND — 段后等待:时间(TIM3)或 WAIT/EXT 下降沿
*/
typedef enum {
RC_IDLE = 0,
RC_DIR_WAIT,
@@ -35,12 +48,30 @@ typedef enum {
RC_WAIT_COND
} RcState_e;

/*
* ---------- 段内运行相 RunPhase_e(脉冲 ISR 热路径) ----------
* PH_APPROACH — 起速 → 本段峰值 f_tgt
* PH_CONST — 匀速,复用 ARR 不写寄存器
* PH_DECEL — 段末过渡 → f_end(可升可降)
*/
typedef enum {
PH_APPROACH = 0, /* 起速 → 本段目标/峰值 */
PH_CONST, /* 匀速 */
PH_DECEL /* 段末过渡 → f_end(可升可降:完成=止速;后续=下一段目标) */
PH_APPROACH = 0,
PH_CONST,
PH_DECEL
} RunPhase_e;

/*
* ---------- 模块静态状态(按功能分组) ----------
*
* 段进度:s_state, s_busy, s_cur_seg, s_done, s_target, s_forward
* 频率链:s_cur_freq, s_chain_freq, s_chain_valid, s_follow_cont, s_pwm_on
* 坐标 :s_acc_pulse(全局累计), s_abs_origin(本次 Start 快照,绝对模式用)
* 曲线 :s_accel_plan, s_pulse_rt, s_phase, s_plan_acc_n, s_plan_dec_n, s_decel_budget
* ACT :s_act_armed, s_act_time_ms, s_act_arm_ms, s_act_cut_pending, s_act_handoff_keep, s_act_expire_req
* WAIT :s_wait_is_signal, s_wait_deadline_ms, s_wait_expire_req
* 换向 :s_dir_deadline(OS tick)
* 段参数:s_run_accel_ms, s_run_decel_ms(PlanSeg 锁定,ChangeFreq 复用)
*/
static volatile RcState_e s_state;
static volatile uint8_t s_busy;
static volatile uint8_t s_forward;
@@ -77,12 +108,18 @@ static uint8_t s_pwm_on;
static uint16_t s_run_accel_ms;
static uint16_t s_run_decel_ms;

/* 规划锁定的加/减速脉冲预算(EnterDecel 会改写 plan.dec_n,故另存) */
/* 规划锁定的加/减速脉冲预算(供 0x03 诊断;EnterDecel 会改写 plan.dec_n,故另存) */
static uint32_t s_plan_acc_n;
static uint32_t s_plan_dec_n;
static uint32_t s_decel_budget; /* 进入减速时的 remain,用于 0x03 */

/** 策略层交给段处理的接口参数 */
static uint32_t s_decel_budget;

/*
* PlsrSegCallIo_t — 策略层解析后交给 PlanSeg 的段端点参数
* f_from 本段起始频率(真实 Hz,>=1)
* f_end 本段终止频率(完成=止速;后续=下一段目标)
* accel_ms 本段加速时间(来自公共参数,段内不读 wait_type)
* decel_ms 本段减速时间
*/
typedef struct {
uint32_t f_from;
uint32_t f_end;
@@ -117,6 +154,11 @@ static uint8_t PlsrRunControlGetSegTargetFreq(int32_t freq_hz,
uint32_t default_spd,
uint32_t *out_hz);

/**
* @brief 开跑瞬间武装 ACT/EXT
* @note EXT:清旧沿防误切;WAIT 信号不在此清(段中沿留给※2)
* @note ACT:写时长与 arm_ms 后再武装,计时跟自由运行 s_ms
*/
static void PlsrRunControlArmActExtOnPulseStart(void)
{
PlsrSeg_t *seg = PlsrParamGetSeg(s_cur_seg);
@@ -145,7 +187,8 @@ static void PlsrRunControlArmActExtOnPulseStart(void)
}

/**
* EXT 触发切段(脉冲可能还在跑)。
* @brief EXT 触发切段(脉冲可能还在跑)
*
* 同向且 PWM 在跑:不停表,下一段 f_from = 当前频率(衔接加速)。
* 反向或已停表:停表,下一段从起速爬。
*/
@@ -191,9 +234,11 @@ static void PlsrRunControlCutSegToNext(void)
}

/**
* ACT 时间到(脉冲可能还没发完):丢掉本段剩余脉冲/减速,从当前频率切下一段。
* @brief ACT 时间到(脉冲可能还没发完):丢掉本段剩余,从当前频率切下一段
*
* 同向:下一段 f_from=当前频率(逐层跳,不进本段减速)。
* 反向或无频率:停表,下一段从起速爬。
* act_handoff_keep:对齐脉冲边界先停表后仍保持衔接。
*/
static void PlsrRunControlActExpire(void)
{
@@ -238,6 +283,12 @@ static void PlsrRunControlActExpire(void)
PlsrRunControlGotoNextOrFinish(s_cur_seg);
}

/**
* @brief 段发完后的等待策略入口
*
* WAIT时间/信号/纯EXT → 停表清链后进 RC_WAIT_COND 或立即 GotoNext;
* EXT_OR_DONE 脉冲先完 → 立刻下一段;其它 → GotoNextOrFinish。
*/
static void PlsrRunControlEnterPostWaitOrNext(void)
{
PlsrSeg_t *seg = PlsrParamGetSeg(s_cur_seg);
@@ -315,6 +366,9 @@ static void PlsrRunControlEnterPostWaitOrNext(void)
PlsrRunControlGotoNextOrFinish(s_cur_seg);
}

/**
* @brief 解析下一段:无则 FinishAll,有则 BeginSeg
*/
static void PlsrRunControlGotoNextOrFinish(uint16_t cur_seg)
{
int16_t next0 = PlsrPathPlanResolveAfterSeg(cur_seg);
@@ -329,7 +383,14 @@ static void PlsrRunControlGotoNextOrFinish(uint16_t cur_seg)
}
}

/** 0x01:段正常结束时实际脉冲 ≠ 本段目标(告警,不停) */
/* ---------- 故障诊断(写 0x2006,见文件头说明) ---------- */

/**
* 0x01 脉冲个数诊断
* 时机:段正常结束进入 AfterSegDone 时。
* 条件:本段目标 s_target>0 且实际发出 s_done 与目标不等。
* 处理:告警,不中断后续切段/收尾逻辑。
*/
static void PlsrRunControlCheckPulseCntFault(void)
{
if ((s_target > 0) && (s_done != s_target))
@@ -339,8 +400,11 @@ static void PlsrRunControlCheckPulseCntFault(void)
}

/**
* 0x03:加速段结束时,相内脉冲与规划 acc_n 不一致。
* by_pulse_budget:因 n 走到预算而收尾,须精确相等;因频率提前到位则仅超额时报。
* 0x03 加速曲线诊断(离开 APPROACH → 匀速/减速前调用)
* @param by_pulse_budget
* 1:因相内脉冲走到规划 acc_n 而收尾 → 要求 s_pulse_rt.n == s_plan_acc_n
* 0:因频率已提前到达目标而收尾 → 仅当 n 超过规划预算时报故障
* 对照量用 PlanSeg 时锁定的 s_plan_acc_n(运行中 plan.acc_n 可能被改写)。
*/
static void PlsrRunControlCheckApproachCurveFault(uint8_t by_pulse_budget)
{
@@ -361,7 +425,11 @@ static void PlsrRunControlCheckApproachCurveFault(uint8_t by_pulse_budget)
}
}

/** 0x03:减速段在段末核对相内脉冲与进入减速时的预算 */
/**
* 0x03 减速曲线诊断
* 时机:段结束 AfterSegDone;仅当仍处 DECEL 相。
* 条件:进入减速时记下的 remain(s_decel_budget)与相内实发脉冲不等。
*/
static void PlsrRunControlCheckDecelCurveFault(void)
{
if ((s_phase == PH_DECEL) && (s_decel_budget > 0U) &&
@@ -371,12 +439,16 @@ static void PlsrRunControlCheckDecelCurveFault(void)
}
}

/**
* @brief 段正常结束:诊断 → 链式频率 → FOLLOW keep 或停表 → EnterPostWaitOrNext
*/
static void PlsrRunControlAfterSegDone(void)
{
PlsrCfg_t *cfg = PlsrParamGetCfg();
PlsrSeg_t *seg = PlsrParamGetSeg(s_cur_seg);
uint8_t keep_pwm;

/* 段末诊断:0x01 脉冲个数;若在减速相再核 0x03 */
PlsrRunControlCheckPulseCntFault();
PlsrRunControlCheckDecelCurveFault();

@@ -450,6 +522,10 @@ static void PlsrRunControlAfterSegDone(void)
PlsrRunControlEnterPostWaitOrNext();
}

/**
* @brief 哪些等待类型禁止 FOLLOW 不停表衔接
* @return 1=必须停表等条件;0=允许(含 EXT_OR_DONE 脉冲先完)
*/
static uint8_t PlsrRunControlBlocksFollowKeep(uint16_t cur_seg)
{
PlsrSeg_t *seg = PlsrParamGetSeg(cur_seg);
@@ -590,6 +666,10 @@ static uint16_t PlsrRunControlActTimeMs(const PlsrSeg_t *seg)
return ms;
}

/**
* @brief 段末是否可不停表衔接下一段
* @note 须 FOLLOW + 有同向下一段 + 无 BlocksFollowKeep
*/
static uint8_t PlsrRunControlWillFollowKeep(uint16_t cur_seg)
{
PlsrCfg_t *cfg = PlsrParamGetCfg();
@@ -615,6 +695,7 @@ static uint8_t PlsrRunControlWillFollowKeep(uint16_t cur_seg)
return 1U;
}

/** @brief ms → OS tick(向上取整,至少 1) */
static INT32U PlsrRunControlMsToTicks(uint32_t ms)
{
INT32U t;
@@ -631,11 +712,16 @@ static INT32U PlsrRunControlMsToTicks(uint32_t ms)
return t;
}

/** @brief OS 时间是否已到 deadline(有符号环绕安全) */
static uint8_t PlsrRunControlOsTimeReached(INT32U deadline)
{
return ((INT32S)(OSTimeGet() - deadline) >= 0) ? 1U : 0U;
}

/**
* 解析段目标频率。
* freq_hz==0 用公共默认速度;结果须落在 [1, 100000],否则返回 0(调用方写 0x04)。
*/
static uint8_t PlsrRunControlGetSegTargetFreq(int32_t freq_hz,
uint32_t default_spd,
uint32_t *out_hz)
@@ -654,6 +740,7 @@ static uint8_t PlsrRunControlGetSegTargetFreq(int32_t freq_hz,
{
f = (uint32_t)freq_hz;
}
/* 合法范围:1Hz ~ 100kHz(含) */
if ((f < 1U) || (f > 100000U))
{
return 0U;
@@ -743,6 +830,9 @@ static int32_t PlsrRunControlAbsLocalPos(void)
return s_acc_pulse - s_abs_origin;
}

/**
* @brief 进入减速相:用 remain 覆盖 plan.dec_n,BeginDec,锁定 s_decel_budget(0x03 用)
*/
static void PlsrRunControlEnterDecel(uint32_t from_hz, uint32_t remain_pulses)
{
PlsrRunControlEnterPhase(PH_DECEL, from_hz);
@@ -774,7 +864,7 @@ static void PlsrRunControlEnterDecel(uint32_t from_hz, uint32_t remain_pulses)
}
}

/** 离开加速相时做 0x03 诊断 */
/** 离开加速相时触发 0x03 加速诊断(仅当前相为 APPROACH 时有效) */
static void PlsrRunControlOnApproachDone(void)
{
uint8_t by_budget;
@@ -783,6 +873,7 @@ static void PlsrRunControlOnApproachDone(void)
{
return;
}
/* n 已走到预算 → 按「预算收尾」精确比对;否则按「提前到频」只查超额 */
by_budget = ((s_plan_acc_n > 0U) && (s_pulse_rt.n >= s_plan_acc_n)) ? 1U : 0U;
PlsrRunControlCheckApproachCurveFault(by_budget);
}
@@ -884,6 +975,10 @@ static void PlsrRunControlRefreshProfile(uint8_t do_start)
}
}

/**
* @brief 规划本段波形并锁 PSC / 进初始相(见文件头故障 0x02)
* @note 与 ChangeFreq 共用;不读 wait_type
*/
static void PlsrRunControlPlanSeg(uint32_t total, uint32_t f_from,
uint32_t f_tgt, uint32_t f_end,
uint16_t accel_ms, uint16_t decel_ms)
@@ -914,8 +1009,9 @@ static void PlsrRunControlPlanSeg(uint32_t total, uint32_t f_from,
cfg->accel_mode);

/*
* 0x02:规划后峰值低于设定目标 → 脉冲不够爬到目标频(三角等)
* 告警,仍按当前规划继续跑。
* 0x02 频率可达性诊断
* AccelCurvePlan 后:若脉冲不够爬升/下降到设定目标,规划会压低/抬高实际峰值
* (典型为三角波)。此时写故障码 2,仍按压后的规划继续运行。
*/
if ((f_tgt > f_from) && (s_accel_plan.f_tgt < f_tgt))
{
@@ -926,7 +1022,11 @@ static void PlsrRunControlPlanSeg(uint32_t total, uint32_t f_from,
PlsrCommandSetFault(PLSR_ERR_FREQ_UNREACH);
}

/* 0x03 对照用:规划预算(运行中 s_accel_plan.acc_n 可能被改写) */
/*
* 锁定本段规划预算,供 0x03 对照。
* EnterDecel 会改写 s_accel_plan.dec_n,故加速预算另存 s_plan_acc_n;
* 减速对照用进入减速瞬间的 remain(s_decel_budget)。
*/
s_plan_acc_n = s_accel_plan.acc_n;
s_plan_dec_n = s_accel_plan.dec_n;
s_decel_budget = 0U;
@@ -1024,6 +1124,9 @@ static void PlsrRunControlPlanSeg(uint32_t total, uint32_t f_from,
}
}

/**
* @brief 全部段结束:停 PWM、解锁 PSC、清方向与全部运行标志
*/
static void PlsrRunControlFinishAll(void)
{
PlsrPulseDriverStop();
@@ -1045,6 +1148,10 @@ static void PlsrRunControlFinishAll(void)
s_act_arm_ms = 0U;
}

/**
* @brief 开一段:算位移/方向 → 解析端点 → PlanSeg → DIR_WAIT 或衔接 RUN
* @param seg_0 0-based 段号;越界则 FinishAll
*/
static void PlsrRunControlBeginSeg(uint16_t seg_0)
{
PlsrSeg_t *seg;
@@ -1097,7 +1204,10 @@ static void PlsrRunControlBeginSeg(uint16_t seg_0)

if (PlsrRunControlGetSegTargetFreq(seg->freq_hz, cfg->default_speed, &f_tgt) == 0U)
{
/* 0x04:频率非法,禁止启动/切段 */
/*
* 0x04:本段(或默认速度)频率非法。
* 禁止启动该段:清忙并结束整次运行,不 AfterSegDone 以免误切下一段。
*/
PlsrCommandSetFault(PLSR_ERR_FREQ_ILLEGAL);
PlsrRunControlFinishAll();
return;
@@ -1134,6 +1244,7 @@ static void PlsrRunControlBeginSeg(uint16_t seg_0)
}
}

/** @brief 运行控制初始化(见 plsr_run_control.h) */
void PlsrRunControlInit(void)
{
s_state = RC_IDLE;
@@ -1165,6 +1276,7 @@ void PlsrRunControlInit(void)
s_wait_deadline_ms = 0U;
}

/** @brief 启动多段序列(见 plsr_run_control.h) */
uint8_t PlsrRunControlStart(uint16_t start_seg_1based)
{
uint16_t n;
@@ -1190,7 +1302,11 @@ uint8_t PlsrRunControlStart(uint16_t start_seg_1based)
PlsrSeg_t *seg = PlsrParamGetSeg((uint16_t)(start_seg_1based - 1U));
uint32_t f_chk;

/* 启动前校验首段频率;非法则写 0x04 且不进入运行 */
/*
* 启动前门禁(0x04):首段目标频率须在 1~100000。
* 非法则写故障码并返回 0,不置忙、不开段。
* (启动前 CommandPoll 已 ClearFault,此处再写 4 覆盖为异常。)
*/
if (PlsrRunControlGetSegTargetFreq(seg->freq_hz,
cfg->default_speed,
&f_chk) == 0U)
@@ -1210,6 +1326,7 @@ uint8_t PlsrRunControlStart(uint16_t start_seg_1based)
return 1U;
}

/** @brief 急停(见 plsr_run_control.h) */
void PlsrRunControlStop(void)
{
PlsrPulseDriverStop();
@@ -1231,6 +1348,7 @@ void PlsrRunControlStop(void)
s_act_arm_ms = 0U;
}

/** @brief 运行中改频(见 plsr_run_control.h) */
uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz)
{
uint32_t remain;
@@ -1240,6 +1358,7 @@ uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz)
{
return 0U;
}
/* 0x04:动态改频目标非法则拒收,保持原规划 */
if ((new_tgt_hz < 1U) || (new_tgt_hz > 100000U))
{
PlsrCommandSetFault(PLSR_ERR_FREQ_ILLEGAL);
@@ -1260,6 +1379,7 @@ uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz)
return 1U;
}

/** @brief 毫秒任务节拍(见 plsr_run_control.h) */
void PlsrRunControlTickMs(void)
{
PlsrSeg_t *seg;
@@ -1338,10 +1458,7 @@ void PlsrRunControlTickMs(void)
}
}

/**
* TIM3 1ms:仅 ACT/WAIT 时基(不再改频)。
* 切段在 TickMs(避免 ISR 里 PlanSeg 丢拍)。
*/
/** @brief TIM3 1ms 回调(见 plsr_run_control.h) */
void PlsrRunControlOn1ms(void)
{
if (s_state == RC_WAIT_COND)
@@ -1372,7 +1489,7 @@ void PlsrRunControlOn1ms(void)
}
}

/** 脉冲 UPDATE:计脉冲 + 逐拍改频(匀速复用 ARR) */
/** @brief 脉冲 UPDATE ISR(见 plsr_run_control.h) */
void PlsrRunControlOnPulseIsr(void)
{
uint32_t remain;
@@ -1473,22 +1590,28 @@ void PlsrRunControlOnPulseIsr(void)
}
}

/** @return 1=运动中(见 plsr_run_control.h) */
uint8_t PlsrRunControlIsBusy(void)
{
return s_busy;
}

/** @return 累计脉冲(见 plsr_run_control.h) */
int32_t PlsrRunControlGetAccPulse(void)
{
return s_acc_pulse;
}

/** @brief 清累计与绝对原点(见 plsr_run_control.h) */
void PlsrRunControlClearAccPulse(void)
{
s_acc_pulse = 0;
s_abs_origin = 0;
}

/**
* @return 当前段号 1-based;空闲为 0(见 plsr_run_control.h)
*/
uint16_t PlsrRunControlGetCurSeg(void)
{
if (s_busy == 0U)
@@ -1498,6 +1621,7 @@ uint16_t PlsrRunControlGetCurSeg(void)
return (uint16_t)(s_cur_seg + 1U);
}

/** @return 当前输出频率 Hz(见 plsr_run_control.h) */
uint32_t PlsrRunControlGetCurFreq(void)
{
return s_cur_freq;


+ 84
- 4
plsr/run_control/plsr_run_control.h Zobrazit soubor

@@ -1,23 +1,103 @@
/**
* @file plsr_run_control.h
* @brief 运行控制:完成方式 / 后续方式(加减速形状见 accel_mode)
* @brief 运行控制模块:多段脉冲执行状态机
*
* @details 模块职责
* 协调路径规划、加减速曲线、脉冲驱动与外部信号,完成「开段 → 发脉冲 →
* 段后等待/跳转 → 收尾」全流程。负责解析发送模式(完成/后续)、等待类型
* (时间/信号/ACT/EXT)及段间频率衔接策略。
*
* 与其它模块关系
* - plsr_path_plan :下一段索引、正反向判定(只读决策,不碰硬件)
* - plsr_accel_curve :段内梯形/三角/S/正弦脉冲域规划与 ISR 逐拍取频
* - plsr_param :段表与公共参数只读访问
* - plsr_pulse_driver:PWM 启停、方向、ARR/PSC 改频
* - plsr_command :故障码 0x01~0x04 上报(PlsrCommandSetFault)
* - plsr_signal_io :TIM3 1ms、WAIT/EXT 下降沿、ACT/WAIT 超时
*
* 关键数据流
* Start → BeginSeg → PlanSeg → DIR_WAIT → RUN(脉冲 ISR 驱动)
* 段末 → AfterSegDone → EnterPostWaitOrNext / GotoNextOrFinish / FinishAll
* 运行中 EXT/ACT → CutSegToNext / ActExpire(可能不停表衔接下一段)
*
* 状态机概要(RcState_e)
* RC_IDLE :无段执行,可 Start
* RC_DIR_WAIT :方向已设,等 dir_delay_ms 后开 PWM
* RC_RUN :脉冲 ISR 推进 s_done,逐拍改频
* RC_WAIT_COND :段后等待(时间到期或 WAIT/EXT 沿)
*/
#ifndef PLSR_RUN_CONTROL_H
#define PLSR_RUN_CONTROL_H

#include <stdint.h>

/**
* @brief 运行控制模块初始化
* @note 调用时机:PlsrInit 中,PulseDriver 初始化之后
* @note 副作用:全部静态状态复位为 RC_IDLE / 非 busy
*/
void PlsrRunControlInit(void);

/**
* @brief 从指定段启动多段序列
* @param start_seg_1based 起始段号 1-based,越界则回退到参数 start_seg
* @return 1=已开段;0=忙、无段或首段频率非法
* @note 调用时机:PlsrStart 转发;启动前做 0x04 频率门禁
* @note 副作用:记录绝对坐标原点、清外部沿、进入 BeginSeg
*/
uint8_t PlsrRunControlStart(uint16_t start_seg_1based);

/**
* @brief 急停
* @note 副作用:关 PWM、清方向/链式频率/ACT/WAIT 全部标志
*/
void PlsrRunControlStop(void);

/**
* @brief 运行中重规划剩余脉冲的目标频率
* @param new_tgt_hz 新目标 Hz,[1, 100000]
* @return 1=成功;0=非 RUN、非法频率或本段已完成
* @note 副作用:remain=s_target-s_done 作为新总长,f_from=s_cur_freq 重 PlanSeg
*/
uint8_t PlsrRunControlChangeFreq(uint32_t new_tgt_hz);
void PlsrRunControlOn1ms(void); /* TIM3 1ms:仅 ACT/WAIT 时基 */
void PlsrRunControlOnPulseIsr(void); /* 脉冲 UPDATE:计数 + 逐拍改频 */
void PlsrRunControlTickMs(void); /* 任务:ACT/WAIT/EXT + ApplyPending */

/**
* @brief TIM3 1ms 回调(由 signal_io 转发)
* @note 调用时机:TIM3 UPDATE 中断,仅处理 ACT 到期置位、WAIT 时间到期置位
* @note 副作用:置 s_act_cut_pending / s_wait_expire_req,实际切段在 TickMs
*/
void PlsrRunControlOn1ms(void);

/**
* @brief 脉冲 UPDATE 中断回调
* @note 调用时机:每发一个脉冲;仅在 RC_RUN 有效
* @note 副作用:s_done++、累计坐标、逐拍 Step 改频、段末 AfterSegDone
*/
void PlsrRunControlOnPulseIsr(void);

/**
* @brief 毫秒级任务节拍(PlsrTask 中调用)
* @note 处理:ACT/WAIT 到期切段、DIR_WAIT 超时开跑、EXT 沿 CutSeg、ApplyPending
*/
void PlsrRunControlTickMs(void);

/** @return 1=运动中(s_busy) */
uint8_t PlsrRunControlIsBusy(void);

/** @return 累计脉冲有符号计数 */
int32_t PlsrRunControlGetAccPulse(void);

/** 清零累计脉冲与绝对原点 */
void PlsrRunControlClearAccPulse(void);

/**
* @return 当前段号 1-based;空闲时返回 0
*/
uint16_t PlsrRunControlGetCurSeg(void);

/**
* @return 当前输出/profile 频率 Hz(监控 0x2002 数据源)
*/
uint32_t PlsrRunControlGetCurFreq(void);

#endif

+ 48
- 6
plsr/signal_io/plsr_signal_io.c Zobrazit soubor

@@ -1,10 +1,19 @@
/**
* @file plsr_signal_io.c
* @brief WAIT/EXT:EXTI 下降沿触发(X4=PB5、X5=PG12)
* @brief WAIT/EXT 输入与 TIM3 1ms 时基实现
*
* 输入上拉,空闲为高;飞线接地产生下降沿。
* 触发只认 EXTI,不靠平时扫口。沿到来后等 20ms 再读一次:
* 仍为低才锁存(消抖,并滤掉 Y0 脉冲耦合出的尖峰)。
* @details 模块职责
* EXTI 下降沿 → pending → TIM3 每 ms 累加 wait_ms,满 20ms 且口线仍低 → fell。
* 触发只认「确认后的 fell」,不靠平时扫口,避免脉冲耦合尖峰误切段。
*
* 硬件
* X4=PB5 / EXTI9_5;X5=PG12 / EXTI15_10;上拉,空闲高。
* TIM3:PSC=83、ARR=999 → 约 1ms @ 84MHz APB1 定时器时钟惯例。
*
* 静态状态
* s_ms — 自由运行毫秒
* s_db_x4/x5 — 每口 pending / wait_ms / fell
* s_htim3 — 时基句柄
*/
#include "plsr_signal_io.h"
#include "plsr_param.h"
@@ -21,10 +30,16 @@

#define PLSR_SIGNAL_DEBOUNCE_MS 20U

/**
* 单路输入消抖状态
* pending — EXTI 已见下降沿,等待 20ms 确认
* wait_ms — 消抖累计 ms(TIM3 递增)
* fell — 确认有效,供 Take* 读清
*/
typedef struct {
volatile uint8_t pending; /* EXTI 下降沿,待 20ms 确认 */
volatile uint8_t pending;
volatile uint8_t wait_ms;
volatile uint8_t fell; /* 确认后的下降沿,读清 */
volatile uint8_t fell;
GPIO_TypeDef *port;
uint16_t pin;
} PlsrEdge_t;
@@ -35,17 +50,27 @@ static PlsrEdge_t s_db_x5;

static TIM_HandleTypeDef s_htim3;

/*============================================================================*/
/* 消抖辅助 */
/*============================================================================*/

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

/** @brief EXTI 入口:置 pending,清消抖计时,等待 TIM3 确认 */
static void PlsrSignalIoOnFalling(PlsrEdge_t *db)
{
db->pending = 1U;
db->wait_ms = 0U;
}

/**
* @brief TIM3 每 ms 调用:pending 满 20ms 后读电平
* @note 仍为低才置 fell;已回高则丢弃(滤耦合尖峰)
*/
static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *db)
{
if (db->pending == 0U)
@@ -70,6 +95,11 @@ static void PlsrSignalIoDebounceConfirm(PlsrEdge_t *db)
}
}

/*============================================================================*/
/* 硬件初始化 */
/*============================================================================*/

/** @brief X4/X5 下降沿 EXTI + NVIC(优先级 8) */
static void PlsrSignalIoGpioExtiInit(void)
{
GPIO_InitTypeDef gpio = {0};
@@ -102,6 +132,7 @@ static void PlsrSignalIoGpioExtiInit(void)
HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
}

/** @brief TIM3 约 1ms 基时 + UPDATE 中断(优先级 9) */
static void PlsrSignalIoTim3Init(void)
{
__HAL_RCC_TIM3_CLK_ENABLE();
@@ -119,6 +150,7 @@ static void PlsrSignalIoTim3Init(void)
(void)HAL_TIM_Base_Start_IT(&s_htim3);
}

/** @brief 模块初始化(见 plsr_signal_io.h) */
void PlsrSignalIoInit(void)
{
s_ms = 0U;
@@ -147,6 +179,10 @@ void PlsrSignalIoInit(void)
s_db_x5.pending = 0U;
}

/*============================================================================*/
/* 时基 / 沿 API */
/*============================================================================*/

uint32_t PlsrSignalIoGetMs(void)
{
return s_ms;
@@ -180,6 +216,7 @@ void PlsrSignalIoClearPending(void)
s_db_x5.pending = 0U;
}

/** @brief sel 0=X4,非 0=X5 */
static PlsrEdge_t *PlsrSignalIoDbBySel(uint16_t sel_01)
{
return (sel_01 != 0U) ? &s_db_x5 : &s_db_x4;
@@ -217,6 +254,11 @@ uint8_t PlsrSignalIoTakeExtFalling(void)
return 0U;
}

/*============================================================================*/
/* 中断入口(由 stm32f4xx_it 转发) */
/*============================================================================*/

/** @brief TIM3:清 UIF → s_ms++ → 消抖 → RunControlOn1ms */
void PlsrSignalIoOnTim3Irq(void)
{
if ((TIM3->SR & TIM_SR_UIF) != 0U)


+ 57
- 12
plsr/signal_io/plsr_signal_io.h Zobrazit soubor

@@ -1,48 +1,93 @@
/**
* @file plsr_signal_io.h
* @brief 等待/EXT 输入(X4=PB5、X5=PG12)
* @brief WAIT/EXT 外部输入与 TIM3 1ms 时基
*
* 公共参数:wait_x_sel / ext_x_sel:0=X4,1=X5。
* 上拉空闲为高;飞线接地产生下降沿,由 EXTI 触发(不轮询)。
* @details 模块职责
* 1. 配置 X4=PB5、X5=PG12 为下降沿 EXTI(上拉空闲高,飞线接地出沿);
* 2. 20ms 消抖确认后锁存 fell,供 run_control 消费;
* 3. TIM3 自由运行毫秒计数,驱动 ACT/WAIT 到期与消抖确认,并转发 On1ms。
*
* 与其它模块关系
* - plsr_param :wait_x_sel / ext_x_sel 选择 X4 或 X5
* - plsr_run_control :TakeWait/ExtFalling、GetMs/Deadline/MsReached、On1ms
* - stm32f4xx_it :TIM3 / EXTI9_5 / EXTI15_10 转发到本模块 On* 入口
*
* 关键约定
* ClearEdges — 清 pending+fell(开跑 EXT、Start)
* ClearPending— 只清消抖中假沿,保留已确认 fell(纯 EXT 发完后过滤耦合)
* Take* — 读清 fell,一次按压只切一次段
*/
#ifndef PLSR_SIGNAL_IO_H
#define PLSR_SIGNAL_IO_H

#include <stdint.h>

/**
* @brief 初始化 GPIO EXTI + TIM3 1ms
* @note 调用时机:PlsrInit;配置后清假沿
*/
void PlsrSignalIoInit(void);

/** TIM3 毫秒计数(自由运行,约 1ms/格) */
/**
* @brief 读取 TIM3 毫秒计数(自由运行,约 1ms/格)
* @return 自 Init 起累加的 ms;可能无符号环绕
*/
uint32_t PlsrSignalIoGetMs(void);

/** deadline = GetMs()+ms;ms=0 则立即到期 */
/**
* @brief 从当前 ms 起算 deadline
* @param ms 延时;0 表示立即到期(deadline=当前)
*/
uint32_t PlsrSignalIoDeadlineFromNow(uint32_t ms);

/** 1=已到或过期(含无符号环绕) */
/**
* @brief 判断是否已到或过期
* @return 1=已到(含无符号环绕比较);0=未到
*/
uint8_t PlsrSignalIoMsReached(uint32_t deadline_ms);

/** 清 WAIT/EXT 下降沿锁存(包含已确认的 fell) */
/**
* @brief 清 WAIT/EXT 下降沿锁存(含已确认 fell 与 pending)
* @note Start / EXT 开跑时调用,避免旧沿立刻切段
*/
void PlsrSignalIoClearEdges(void);

/** 只清消抖 pending,保留已确认的 fell(发完后过滤脉冲耦合假沿用) */
/**
* @brief 只清消抖 pending,保留已确认 fell
* @note 纯 EXT 脉冲发完后过滤 Y 口耦合假沿,同时保留真沿供 TakeExt
*/
void PlsrSignalIoClearPending(void);

/** WAIT 口当前是否为低(已接地)。仅段末判断※2 时读一次,不作周期轮询触发 */
/**
* @brief WAIT 口当前是否为低(已接地)
* @note 仅段末判断等场景读一次;不作周期轮询触发(触发只认 EXTI+消抖)
*/
uint8_t PlsrSignalIoWaitIsOn(void);

/**
* WAIT:消耗一次已确认的 EXTI 下降沿。
* ※2 段中飞线接地并保持 → 发完立刻下一段;※3 未触发则一直等。
* @brief 消耗一次已确认的 WAIT 口下降沿
* @return 1=有沿并已清;0=无
* @note ※2 段中接地保持 → 发完立刻下一段;※3 未触发则 RC_WAIT_COND 一直等
*/
uint8_t PlsrSignalIoTakeWaitFalling(void);

/**
* EXT:消耗一次 EXTI 下降沿锁存。一次按压只切一次段。
* @brief 消耗一次已确认的 EXT 口下降沿
* @return 1=有沿并已清;0=无
* @note 运行中 EXT / EXT_OR_DONE 切段;一次按压只切一次
*/
uint8_t PlsrSignalIoTakeExtFalling(void);

/**
* @brief TIM3 UPDATE 中断入口
* @note s_ms++、消抖确认、转发 PlsrRunControlOn1ms
*/
void PlsrSignalIoOnTim3Irq(void);

/** @brief EXTI9_5:处理 X4=PB5 下降沿 → 置 pending */
void PlsrSignalIoOnExti9_5(void);

/** @brief EXTI15_10:处理 X5=PG12 下降沿 → 置 pending */
void PlsrSignalIoOnExti15_10(void);

#endif

Načítá se…
Zrušit
Uložit