本批完成内容: 四轴并发自测(P6): - 新增 PlsrFourAxisSelfTestQueue:Q0~Q3 四路独立 PULSE/DIR 任务(1000Hz/1000、2000Hz/2000、3000Hz/3000、4000Hz/4000 脉冲),每轴独立 S0/S1 基址与 SFD 配置(Q4~Q7 方向点)。 - main.c 上电自测入口切换为四轴并发测试(验证后可删除)。 状态机修复: - start==target 或零加速时直接进入 CRUISE 的段,补发 ACCEL_COMPLETE 事件,避免轴状态卡在 ACCEL 整段。 host 测试扩展: - 四轴并发启动/计数相关断言(HAL 测试 +90 项)。 上板验证(逻辑分析仪实测): - Q0/Q1/Q2/Q3 = 1000/2000/3000/4000Hz,上升沿 1000/2000/3000/4000 全部精确,占空比 50%,时长约 1s, 四路独立无干扰。 - 同时验证 84MHz 定时器时钟修正有效:Q1(TIM13)、Q3 (TIM14)频率精确无减半。master
| @@ -202,9 +202,8 @@ int main(void) | |||||
| /* 上电自测:延时 1s 后由 Q0/Q1 输出三段 AB 正交周期(验证后关闭)。 */ | /* 上电自测:延时 1s 后由 Q0/Q1 输出三段 AB 正交周期(验证后关闭)。 */ | ||||
| HAL_Delay(1000U); | HAL_Delay(1000U); | ||||
| /* P5 board test: Q0=PULSE, Q4=DIR, controlled stop at +500. */ | |||||
| //(void)PlsrProtectionSelfTestQueue(); | |||||
| (void)PlsrProtectionSelfTestQueue(); | |||||
| /* P6 board test: Q0..Q3 run four independent PULSE/DIR jobs. */ | |||||
| (void)PlsrFourAxisSelfTestQueue(); | |||||
| OSStart(); | OSStart(); | ||||
| /* USER CODE END 2 */ | /* USER CODE END 2 */ | ||||
| @@ -19,6 +19,9 @@ PLSR_RESULT PlsrEquivalentSelfTestQueue(void); | |||||
| * distance is deliberately longer than the +500-pulse soft limit. */ | * distance is deliberately longer than the +500-pulse soft limit. */ | ||||
| PLSR_RESULT PlsrProtectionSelfTestQueue(void); | PLSR_RESULT PlsrProtectionSelfTestQueue(void); | ||||
| /* P6 board test: start four independent PULSE/DIR axes together. */ | |||||
| PLSR_RESULT PlsrFourAxisSelfTestQueue(void); | |||||
| #ifdef __cplusplus | #ifdef __cplusplus | ||||
| } | } | ||||
| #endif | #endif | ||||
| @@ -1782,6 +1782,12 @@ static PLSR_RESULT PlsrStartSegmentHardware(uint8_t axis, | |||||
| axisObject->profileWasAccel = | axisObject->profileWasAccel = | ||||
| (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; | (axisObject->profile.phase == PLSR_PROFILE_PHASE_ACCEL) ? 1U : 0U; | ||||
| PlsrPublishAxis(axis); | PlsrPublishAxis(axis); | ||||
| if (axisObject->profileWasAccel == 0U) | |||||
| { | |||||
| /* start==target or zero acceleration enters CRUISE directly. The | |||||
| * axis state must not remain stuck in ACCEL for the whole segment. */ | |||||
| (void)PlsrPostEvent(axis, PLSR_EVENT_ACCEL_COMPLETE); | |||||
| } | |||||
| return PLSR_RESULT_OK; | return PLSR_RESULT_OK; | ||||
| } | } | ||||
| @@ -12,10 +12,12 @@ | |||||
| * 段3:1000Hz / -500 周期(B 超前 A,验证反向) | * 段3:1000Hz / -500 周期(B 超前 A,验证反向) | ||||
| * 数据源为静态数组,仅自测使用(正式 D 设备适配器见 Modbus 阶段)。 */ | * 数据源为静态数组,仅自测使用(正式 D 设备适配器见 Modbus 阶段)。 */ | ||||
| #define SELF_TEST_WORD_CAPACITY (64U) | |||||
| #define SELF_TEST_WORD_CAPACITY (192U) | |||||
| #define SELF_TEST_S0_BASE (10U) | #define SELF_TEST_S0_BASE (10U) | ||||
| #define SELF_TEST_S1_BASE (60U) | #define SELF_TEST_S1_BASE (60U) | ||||
| #define SELF_TEST_DIR_POINT (4U) | #define SELF_TEST_DIR_POINT (4U) | ||||
| #define SELF_TEST_SFD_AXIS_STRIDE (130U) | |||||
| #define SELF_TEST_SFD_SET_OFFSET (50U) | |||||
| static uint16_t SelfTestWords[3][SELF_TEST_WORD_CAPACITY]; | static uint16_t SelfTestWords[3][SELF_TEST_WORD_CAPACITY]; | ||||
| @@ -234,3 +236,108 @@ PLSR_RESULT PlsrProtectionSelfTestQueue(void) | |||||
| call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; | call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; | ||||
| return PlsrPostCall(&call); | return PlsrPostCall(&call); | ||||
| } | } | ||||
| PLSR_RESULT PlsrFourAxisSelfTestQueue(void) | |||||
| { | |||||
| static const uint16_t s0Base[PLSR_AXIS_COUNT] = | |||||
| { | |||||
| 10U, 40U, 70U, 100U | |||||
| }; | |||||
| static const uint16_t s1Base[PLSR_AXIS_COUNT] = | |||||
| { | |||||
| 160U, 164U, 168U, 172U | |||||
| }; | |||||
| static const uint32_t frequencyHz[PLSR_AXIS_COUNT] = | |||||
| { | |||||
| 1000UL, 2000UL, 3000UL, 4000UL | |||||
| }; | |||||
| static const int32_t pulseCount[PLSR_AXIS_COUNT] = | |||||
| { | |||||
| 1000, 2000, 3000, 4000 | |||||
| }; | |||||
| PLSR_CALL call; | |||||
| PLSR_COMMAND command; | |||||
| PLSR_RESULT result; | |||||
| uint16_t commonBase; | |||||
| uint16_t setBase; | |||||
| uint8_t axis; | |||||
| (void)memset(SelfTestWords, 0, sizeof(SelfTestWords)); | |||||
| for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) | |||||
| { | |||||
| commonBase = (uint16_t)(900U | |||||
| + (uint16_t)axis | |||||
| * SELF_TEST_SFD_AXIS_STRIDE); | |||||
| setBase = (uint16_t)(commonBase + SELF_TEST_SFD_SET_OFFSET); | |||||
| /* Pulse unit, PULSE/DIR, no limit input, Q4..Q7 as DIR. */ | |||||
| (void)PlcDeviceWriteSfd(commonBase, 0U); | |||||
| SelfTestWriteSfdDword((uint16_t)(commonBase + 2U), 1UL); | |||||
| SelfTestWriteSfdDword((uint16_t)(commonBase + 4U), 1UL); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(commonBase + 6U), | |||||
| (uint16_t)(SELF_TEST_DIR_POINT + axis)); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(commonBase + 7U), 10U); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(commonBase + 12U), 0U); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(commonBase + 15U), 0xFFFFU); | |||||
| /* K1 has no ramp so all four channels keep an exact fixed rate. */ | |||||
| SelfTestWriteSfdDword(setBase, frequencyHz[axis]); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(setBase + 2U), 0U); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(setBase + 3U), 0U); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(setBase + 4U), 0U); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(setBase + 5U), 0U); | |||||
| SelfTestWriteSfdDword((uint16_t)(setBase + 6U), 100000UL); | |||||
| SelfTestWriteSfdDword((uint16_t)(setBase + 8U), | |||||
| frequencyHz[axis]); | |||||
| SelfTestWriteSfdDword((uint16_t)(setBase + 10U), 0UL); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(setBase + 12U), 50U); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(setBase + 13U), 0U); | |||||
| (void)PlcDeviceWriteSfd((uint16_t)(setBase + 14U), 0U); | |||||
| SelfTestWriteSfdDword((uint16_t)(setBase + 16U), 2000UL); | |||||
| SelfTestWriteSfdDword((uint16_t)(setBase + 18U), 200UL); | |||||
| SelfTestWriteDword(PLSR_DEVICE_D, s0Base[axis], 1U); | |||||
| SelfTestWriteDword(PLSR_DEVICE_D, | |||||
| (uint32_t)s0Base[axis] + 10UL, | |||||
| frequencyHz[axis]); | |||||
| SelfTestWriteDword(PLSR_DEVICE_D, | |||||
| (uint32_t)s0Base[axis] + 12UL, | |||||
| (uint32_t)pulseCount[axis]); | |||||
| SelfTestWriteDword(PLSR_DEVICE_D, s1Base[axis], 0U); | |||||
| /* Make the board-test result independent of a previously restored | |||||
| * Backup SRAM position. */ | |||||
| (void)memset(&command, 0, sizeof(command)); | |||||
| command.sequence = 0xA500UL + axis; | |||||
| command.axis = axis; | |||||
| command.opcode = PLSR_CMD_SET_POSITION; | |||||
| command.argument = 0; | |||||
| result = PlsrPostCommand(&command); | |||||
| if (result != PLSR_RESULT_QUEUED) | |||||
| { | |||||
| return result; | |||||
| } | |||||
| (void)memset(&call, 0, sizeof(call)); | |||||
| call.sequence = 0xA600UL + axis; | |||||
| call.source.context = NULL; | |||||
| call.source.validateWords = SelfTestValidateWords; | |||||
| call.source.readWord = SelfTestReadWord; | |||||
| call.source.readBit = SelfTestReadBit; | |||||
| call.s0.device = PLSR_DEVICE_D; | |||||
| call.s0.address = s0Base[axis]; | |||||
| call.s1.device = PLSR_DEVICE_D; | |||||
| call.s1.address = s1Base[axis]; | |||||
| call.s2.type = PLSR_OPERAND_CONSTANT; | |||||
| call.s2.constant = 1; | |||||
| call.dAxis = axis; | |||||
| call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; | |||||
| result = PlsrPostCall(&call); | |||||
| if (result != PLSR_RESULT_QUEUED) | |||||
| { | |||||
| return result; | |||||
| } | |||||
| } | |||||
| return PLSR_RESULT_QUEUED; | |||||
| } | |||||
| @@ -3,6 +3,7 @@ | |||||
| #include "plsr_hal_f407.h" | #include "plsr_hal_f407.h" | ||||
| #include "plsr_job.h" | #include "plsr_job.h" | ||||
| #include "plsr_persistence.h" | #include "plsr_persistence.h" | ||||
| #include "plsr_resource.h" | |||||
| #include "plsr_self_test.h" | #include "plsr_self_test.h" | ||||
| #include <stdio.h> | #include <stdio.h> | ||||
| #include <string.h> | #include <string.h> | ||||
| @@ -1243,6 +1244,94 @@ static void TestProtectionSelfTest(void) | |||||
| CHECK(eventRecord.lastReason == PLSR_STOP_REASON_LIMIT_POSITIVE); | CHECK(eventRecord.lastReason == PLSR_STOP_REASON_LIMIT_POSITIVE); | ||||
| } | } | ||||
| static void TestFourAxisSelfTest(void) | |||||
| { | |||||
| static const uint32_t expectedFrequency[PLSR_AXIS_COUNT] = | |||||
| { | |||||
| 1000UL, 2000UL, 3000UL, 4000UL | |||||
| }; | |||||
| static const int32_t expectedPulses[PLSR_AXIS_COUNT] = | |||||
| { | |||||
| 1000, 2000, 3000, 4000 | |||||
| }; | |||||
| PLC_DEVICE_EVENT_RECORD eventRecord; | |||||
| PLSR_RESOURCE_STATUS resources; | |||||
| PLSR_STATUS status; | |||||
| int32_t hsdPulses; | |||||
| int subTick; | |||||
| int ticks; | |||||
| uint8_t axis; | |||||
| TestResetEnvironment(); | |||||
| CHECK(PlsrFourAxisSelfTestQueue() == PLSR_RESULT_QUEUED); | |||||
| PlsrProcess(); | |||||
| for (ticks = 0; ticks < 10; ticks++) | |||||
| { | |||||
| PlsrProcess(); | |||||
| } | |||||
| for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) | |||||
| { | |||||
| CHECK(PlsrGetStatus(axis, &status) == PLSR_RESULT_OK); | |||||
| CHECK(status.state == PLSR_STATE_RUN); | |||||
| CHECK(status.outputMode == PLSR_OUTPUT_PULSE_DIR); | |||||
| CHECK(status.directionPoint == (uint8_t)(4U + axis)); | |||||
| CHECK(status.directionPositive != 0U); | |||||
| CHECK(PlsrHwGetState(axis) == PLSR_HW_STATE_RUNNING); | |||||
| CHECK(PlsrHwGetCurrentFrequencyHz(axis) | |||||
| == expectedFrequency[axis]); | |||||
| } | |||||
| /* A 0.25ms base slot produces 1/2/3/4kHz update ratios while all four | |||||
| * hardware channels are active concurrently for one simulated second. */ | |||||
| for (subTick = 0; subTick < 4000; subTick++) | |||||
| { | |||||
| if ((subTick & 3) == 0) | |||||
| { | |||||
| PlsrHwTestTriggerUpdate(0U); | |||||
| } | |||||
| if ((subTick & 1) == 0) | |||||
| { | |||||
| PlsrHwTestTriggerUpdate(1U); | |||||
| } | |||||
| if ((subTick & 3) != 3) | |||||
| { | |||||
| PlsrHwTestTriggerUpdate(2U); | |||||
| } | |||||
| PlsrHwTestTriggerUpdate(3U); | |||||
| if ((subTick & 3) == 3) | |||||
| { | |||||
| PlsrProcess(); | |||||
| } | |||||
| } | |||||
| for (axis = 0U; axis < PLSR_AXIS_COUNT; axis++) | |||||
| { | |||||
| CHECK(PlsrGetStatus(axis, &status) == PLSR_RESULT_OK); | |||||
| CHECK(status.state == PLSR_STATE_COMPLETED); | |||||
| CHECK(status.stopReason == PLSR_STOP_REASON_NORMAL_COMPLETE); | |||||
| CHECK(status.done != 0U); | |||||
| CHECK(status.logicalPosition == expectedPulses[axis]); | |||||
| CHECK(status.taskPulses == expectedPulses[axis]); | |||||
| CHECK(status.totalPulses == expectedPulses[axis]); | |||||
| CHECK(PlsrHwGetState(axis) == PLSR_HW_STATE_IDLE); | |||||
| CHECK(PlcDeviceReadHsdDword((uint16_t)(axis * 4U), | |||||
| &hsdPulses) == PLC_DEVICE_OK); | |||||
| CHECK(hsdPulses == expectedPulses[axis]); | |||||
| CHECK(PlcDeviceReadEvent((uint16_t)(6000U | |||||
| + (uint16_t)axis * 100U), | |||||
| &eventRecord) == PLC_DEVICE_OK); | |||||
| CHECK(eventRecord.count == 1UL); | |||||
| CHECK(eventRecord.pending != 0U); | |||||
| CHECK(eventRecord.lastReason == PLSR_STOP_REASON_NORMAL_COMPLETE); | |||||
| } | |||||
| PlsrResourceGetStatus(&resources); | |||||
| CHECK(resources.outputMask == 0UL); | |||||
| CHECK(resources.highMask == 0U); | |||||
| CHECK(PlsrResourceCheckInvariant() != 0U); | |||||
| } | |||||
| static void TestStopStopsHardware(void) | static void TestStopStopsHardware(void) | ||||
| { | { | ||||
| TEST_MEMORY memory; | TEST_MEMORY memory; | ||||
| @@ -1304,6 +1393,7 @@ int main(void) | |||||
| TestProductionSelfTestStartsAb(); | TestProductionSelfTestStartsAb(); | ||||
| TestEquivalentSelfTest(); | TestEquivalentSelfTest(); | ||||
| TestProtectionSelfTest(); | TestProtectionSelfTest(); | ||||
| TestFourAxisSelfTest(); | |||||
| TestStopStopsHardware(); | TestStopStopsHardware(); | ||||
| if (TestFailures != 0) | if (TestFailures != 0) | ||||