#include "plc_device.h" #include "modbus_data_store.h" #include "plsr_core.h" #include "plsr_hal_f407.h" #include "plsr_job.h" #include "plsr_modbus_control.h" #include "plsr_persistence.h" #include "plsr_resource.h" #include "plsr_self_test.h" #include #include #define TEST_WORD_CAPACITY (3000U) #define TEST_BIT_CAPACITY (128U) #define TEST_S0_BASE (100U) #define TEST_S1_BASE (200U) typedef struct { uint16_t words[3][TEST_WORD_CAPACITY]; uint8_t bits[3][TEST_BIT_CAPACITY]; } TEST_MEMORY; static int TestFailures; static int TestChecks; #define CHECK(condition) \ do \ { \ TestChecks++; \ if (!(condition)) \ { \ TestFailures++; \ (void)printf("FAIL line %d: %s\n", __LINE__, #condition); \ } \ } while (0) static uint8_t TestValidateWords(void *context, PLSR_DEVICE_TYPE device, uint32_t firstAddress, uint32_t wordCount) { (void)context; (void)device; return (((uint64_t)firstAddress + wordCount) <= TEST_WORD_CAPACITY) ? 1U : 0U; } static uint8_t TestReadWord(void *context, PLSR_DEVICE_TYPE device, uint32_t address, uint16_t *value) { TEST_MEMORY *memory = (TEST_MEMORY *)context; if ((memory == NULL) || (value == NULL) || (device > PLSR_DEVICE_FD) || (address >= TEST_WORD_CAPACITY)) { return 0U; } *value = memory->words[device][address]; return 1U; } static uint8_t TestReadBit(void *context, PLSR_DEVICE_TYPE device, uint32_t address, uint8_t *value) { TEST_MEMORY *memory = (TEST_MEMORY *)context; uint8_t index; if ((memory == NULL) || (value == NULL) || (device < PLSR_DEVICE_X) || (device > PLSR_DEVICE_HM) || (address >= TEST_BIT_CAPACITY)) { return 0U; } index = (uint8_t)(device - PLSR_DEVICE_X); *value = memory->bits[index][address]; return 1U; } static void TestWriteDword(TEST_MEMORY *memory, PLSR_DEVICE_TYPE device, uint32_t address, int32_t value) { uint32_t raw = (uint32_t)value; memory->words[device][address] = (uint16_t)(raw & 0xFFFFUL); memory->words[device][address + 1UL] = (uint16_t)(raw >> 16U); } static void TestWriteSfdDword(uint16_t address, uint32_t value) { CHECK(PlcDeviceWriteSfd(address, (uint16_t)(value & 0xFFFFUL)) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd((uint16_t)(address + 1U), (uint16_t)(value >> 16U)) == PLC_DEVICE_OK); } static void TestSetSegment(TEST_MEMORY *memory, uint16_t number, uint32_t frequency, int32_t pulses) { uint32_t base = TEST_S0_BASE + (uint32_t)number * 10UL; TestWriteDword(memory, PLSR_DEVICE_D, base, (int32_t)frequency); TestWriteDword(memory, PLSR_DEVICE_D, base + 2UL, pulses); memory->words[PLSR_DEVICE_D][base + 4UL] = 0U; TestWriteDword(memory, PLSR_DEVICE_D, base + 5UL, 0); memory->words[PLSR_DEVICE_D][base + 7UL] = 0U; TestWriteDword(memory, PLSR_DEVICE_D, base + 8UL, 0); } static void TestResetEnvironment(void) { PlsrPersistenceTestResetStorage(); CHECK(PlcDeviceInit() == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(906U, 4) == PLC_DEVICE_OK); CHECK(PlsrInit() == PLSR_RESULT_OK); } static PLSR_CALL TestMakeCall(TEST_MEMORY *memory) { PLSR_CALL call; (void)memset(&call, 0, sizeof(call)); call.sequence = 10UL; call.source.context = memory; call.source.validateWords = TestValidateWords; call.source.readWord = TestReadWord; call.source.readBit = TestReadBit; call.s0.device = PLSR_DEVICE_D; call.s0.address = TEST_S0_BASE; call.s1.device = PLSR_DEVICE_D; call.s1.address = TEST_S1_BASE; call.s2.type = PLSR_OPERAND_CONSTANT; call.s2.constant = 1; call.dAxis = 0U; call.outputModeOverride = PLSR_OUTPUT_MODE_FROM_SFD; return call; } static PLSR_STATUS TestGetStatus(void) { PLSR_STATUS status; (void)memset(&status, 0, sizeof(status)); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); return status; } static int32_t TestReadSdDword(uint16_t lowAddress) { int32_t lowWord = 0; int32_t highWord = 0; uint32_t rawValue; CHECK(PlcDeviceReadSd(lowAddress, &lowWord) == PLC_DEVICE_OK); CHECK(PlcDeviceReadSd((uint16_t)(lowAddress + 1U), &highWord) == PLC_DEVICE_OK); rawValue = ((uint32_t)lowWord & 0xFFFFUL) | (((uint32_t)highWord & 0xFFFFUL) << 16U); return (int32_t)rawValue; } /* ---- HAL 单测 ---- */ static void TestMapping(void) { (void)PlsrHwInit(); CHECK(PlsrHwGetTimerClockHz(0U) == 168000000UL); CHECK(PlsrHwGetTimerClockHz(1U) == 84000000UL); CHECK(PlsrHwGetTimerClockHz(2U) == 168000000UL); CHECK(PlsrHwGetTimerClockHz(3U) == 84000000UL); CHECK(PlsrHwGetTimerClockHz(4U) == 0UL); CHECK(PlsrHwResolveDirectionPoint(4U) != 0U); CHECK(PlsrHwResolveDirectionPoint(8U) == 0U); CHECK(PlsrHwResolveDirectionPoint(20U) != 0U); CHECK(PlsrHwResolveDirectionPoint(21U) == 0U); } static void TestDirDelaySequence(void) { (void)PlsrHwInit(); PLSR_HW_START_PARAMS params; uint16_t psc; uint16_t arr; int ticks; (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 1000UL; params.targetPulses = 100; params.directionPoint = 4U; params.directionPositive = 1U; params.directionDelayMs = 10U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING); CHECK(PlsrHwTestGetDirLevel(0U) == 1U); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwIsPulseActive(0U) == 0U); for (ticks = 0; ticks < 9; ticks++) { PlsrHwTick(0U); } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING); PlsrHwTick(0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING); /* 首个非零频率启动 PWM,ARR/CCR 与分频计算一致。 */ CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U); CHECK(PlsrHwIsPulseActive(0U) == 1U); CHECK(PlsrCalculateTimerDivider(168000000UL, 1000UL, &psc, &arr) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetArr(0U) == arr); CHECK(PlsrHwTestGetPsc(0U) == psc); CHECK(PlsrHwTestGetCcr(0U) == arr / 2UL); /* PWM 模式 1(OC1M=110):复位后 CCMR1=0 冻结,无此配置输出恒定电平。 */ CHECK((PlsrHwTestGetCcmr1(0U) & 0x70UL) == 0x60UL); /* ARR/CCR 预装载(ARPE=CR1 bit7,OC1PE=CCMR1 bit3): * 运行中调频不产生提前回绕,否则加速段多出 ~ln(f1/f0) 个假脉冲。 */ CHECK((PlsrHwTestGetCr1(0U) & 0x80UL) == 0x80UL); CHECK((PlsrHwTestGetCcmr1(0U) & 0x08UL) == 0x08UL); /* 段间同向衔接:方向不变时跳过方向延时,直接进入 PWM 待启动。 */ CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); params.targetPulses = 50; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING); CHECK(PlsrHwTestGetDirLevel(0U) == 1U); /* 反向时方向延时仍生效。 */ params.directionPositive = 0U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING); CHECK(PlsrHwTestGetDirLevel(0U) == 0U); /* Bit1 negative logic reverses only the electrical DIR terminal. */ params.directionNegativeLogic = 1U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING); CHECK(PlsrHwTestGetDirLevel(0U) == 1U); params.directionPositive = 1U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetDirLevel(0U) == 0U); } static void TestDirectionBatch(void) { PLSR_HW_START_PARAMS params; (void)PlsrHwInit(); (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 0UL; params.targetPulses = 10; params.outputMode = PLSR_OUTPUT_PULSE_DIR; params.directionPoint = 4U; params.directionPositive = 1U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); params.directionPoint = 3U; params.directionNegativeLogic = 1U; CHECK(PlsrHwStartPulse(1U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetDirLevel(0U) == 1U); CHECK(PlsrHwTestGetDirLevel(1U) == 0U); PlsrHwBeginDirectionBatch(); params.directionPoint = 4U; params.directionPositive = 0U; params.directionNegativeLogic = 0U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); params.directionPoint = 3U; params.directionNegativeLogic = 1U; CHECK(PlsrHwStartPulse(1U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetDirLevel(0U) == 1U); CHECK(PlsrHwTestGetDirLevel(1U) == 0U); PlsrHwEndDirectionBatch(); CHECK(PlsrHwTestGetDirLevel(0U) == 0U); CHECK(PlsrHwTestGetDirLevel(1U) == 1U); } static void TestHardwareCounterLeases(void) { PLSR_HW_START_PARAMS params; (void)PlsrHwInit(); (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 100000UL; params.targetPulses = 10; params.outputMode = PLSR_OUTPUT_PULSE_DIR; params.directionPoint = 4U; params.directionPositive = 1U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwUsesHardwareCounter(0U) == 1U); /* Replacing a prepared segment must release and reacquire the same lease * instead of orphaning TIM9 under the old preparation. */ CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwUsesHardwareCounter(0U) == 1U); CHECK(PlsrHwSetFrequency(0U, 100000UL) == PLSR_RESULT_OK); /* A hardware-counted PWM starts in the inactive half-cycle. TIM9/TIM12 * must see a low ITR level when external-clock mode is armed, otherwise * the startup OCREF level is counted before a terminal pulse exists. */ CHECK(PlsrHwTestGetCnt(0U) == PlsrHwTestGetCcr(0U)); params.directionPoint = 5U; CHECK(PlsrHwStartPulse(2U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwUsesHardwareCounter(2U) == 0U); CHECK(PlsrHwSetFrequency(2U, 100000UL) == PLSR_RESULT_OK); /* PWM compare and update flags coexist at the period boundary. A * software fallback axis must consume both in one ISR and count once. */ PlsrHwTestTriggerUpdateAndCompare(2U); CHECK(PlsrHwGetEmittedPulses(2U) == 1); PlsrHwTestTriggerCompare(2U); CHECK(PlsrHwGetEmittedPulses(2U) == 1); CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); CHECK(PlsrHwStopPulse(2U) == PLSR_RESULT_OK); CHECK(PlsrHwUsesHardwareCounter(0U) == 0U); params.directionPoint = 5U; CHECK(PlsrHwStartPulse(2U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwUsesHardwareCounter(2U) == 1U); CHECK(PlsrHwSetFrequency(2U, 100000UL) == PLSR_RESULT_OK); while (PlsrHwGetState(2U) != PLSR_HW_STATE_DONE) { PlsrHwTestTriggerUpdate(2U); } CHECK(PlsrHwGetEmittedPulses(2U) == 10); CHECK(PlsrHwUsesHardwareCounter(2U) == 0U); } static void TestCwCcwSequence(void) { PLSR_HW_START_PARAMS params; uint16_t psc; uint16_t arr; (void)PlsrHwInit(); (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 2000UL; params.targetPulses = 3; params.outputMode = PLSR_OUTPUT_CW_CCW; params.directionPoint = PLSR_HW_DIR_POINT_NONE; params.directionPositive = 1U; params.directionDelayMs = 10U; CHECK(PlsrHwStartPulse(1U, ¶ms) == PLSR_RESULT_INVALID_AXIS); CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING); CHECK(PlsrHwSetFrequency(0U, 2000UL) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); PlsrHwTestTriggerCompare(1U); CHECK(PlsrHwGetEmittedPulses(0U) == 0); PlsrHwTestTriggerCompare(0U); PlsrHwTestTriggerCompare(0U); PlsrHwTestTriggerCompare(0U); CHECK(PlsrHwGetEmittedPulses(0U) == 3); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U); CHECK(PlsrHwTestGetCc1PolarityInverted(0U) == 0U); PlsrHwTestTriggerUpdate(0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); params.frequencyHz = 1000UL; params.targetPulses = 2; params.directionPositive = 0U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 1U); CHECK(PlsrCalculateTimerDivider(84000000UL, 1000UL, &psc, &arr) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetPsc(1U) == psc); CHECK(PlsrHwTestGetArr(1U) == arr); PlsrHwTestTriggerCompare(1U); PlsrHwTestTriggerCompare(1U); CHECK(PlsrHwGetEmittedPulses(0U) == 2); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwTestGetPwmEnabled(1U) == 1U); PlsrHwTestTriggerUpdate(1U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); } static void TestFastRefreshControlTick(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_STATUS status; int tick; TestResetEnvironment(); (void)memset(&memory, 0, sizeof(memory)); CHECK(PlcDeviceWriteSfd(900U, 0U) == PLC_DEVICE_OK); TestWriteSfdDword(902U, 1UL); TestWriteSfdDword(904U, 1UL); CHECK(PlcDeviceWriteSfd(906U, 4U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); TestWriteSfdDword(950U, 1000UL); CHECK(PlcDeviceWriteSfd(952U, 100U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(953U, 100U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(954U, 0U) == PLC_DEVICE_OK); /* Linear curve: 10Hz/ms becomes exactly 1Hz per 0.1ms tick. */ CHECK(PlcDeviceWriteSfd(955U, 0U) == PLC_DEVICE_OK); TestWriteSfdDword(956U, 100000UL); TestWriteSfdDword(958U, 0UL); TestWriteSfdDword(960U, 0UL); CHECK(PlcDeviceWriteSfd(962U, 50U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(963U, 0U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(964U, 2U) == PLC_DEVICE_OK); TestWriteSfdDword(966U, 2000UL); TestWriteSfdDword(968U, 200UL); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 10000); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S1_BASE, 0); call = TestMakeCall(&memory); call.sequence = 0xB000UL; call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.state == PLSR_STATE_ACCEL); CHECK(status.jobValid != 0U); CHECK(PlsrTestGetJobRefreshCode(0U) == 2U); CHECK(PlsrTestGetProfileRefreshHz(0U) == 10000UL); CHECK(PlsrTestGetProfileActive(0U) != 0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING); CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 0UL); /* A normal 1ms process pass must not advance a 0.1ms profile. */ PlsrProcess(); CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 0UL); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING); CHECK(PlsrTestGetJobRefreshCode(0U) == 2U); CHECK(PlsrTestGetProfileActive(0U) != 0U); PlsrControlTick100us(); CHECK(PlsrTestGetProfileFrequencyHz(0U) == 1UL); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwGetCurrentFrequencyHz(0U) > 0UL); for (tick = 1; tick < 10; tick++) { PlsrControlTick100us(); } CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 10UL); PlsrProcess(); CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 10UL); for (tick = 0; tick < 10; tick++) { PlsrControlTick100us(); } CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 20UL); } static void TestDynamicFrequencyRetarget(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_STATUS status; TestResetEnvironment(); (void)memset(&memory, 0, sizeof(memory)); CHECK(PlcDeviceWriteSfd(900U, 0U) == PLC_DEVICE_OK); TestWriteSfdDword(902U, 1UL); TestWriteSfdDword(904U, 1UL); CHECK(PlcDeviceWriteSfd(906U, 4U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); TestWriteSfdDword(950U, 1000UL); CHECK(PlcDeviceWriteSfd(952U, 100U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(953U, 100U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(954U, 0U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(955U, 0U) == PLC_DEVICE_OK); TestWriteSfdDword(956U, 5000UL); TestWriteSfdDword(958U, 1000UL); TestWriteSfdDword(960U, 0UL); CHECK(PlcDeviceWriteSfd(962U, 50U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(963U, 0U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(964U, 2U) == PLC_DEVICE_OK); TestWriteSfdDword(966U, 2000UL); TestWriteSfdDword(968U, 200UL); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 100000); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S1_BASE, 0); call = TestMakeCall(&memory); call.sequence = 0xB100UL; call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.currentFrequencyHz == 1000UL); CHECK(status.targetFrequencyHz == 1000UL); /* Only the 100us control tick may observe/apply a refreshCode=2 edit. */ TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 4000); PlsrProcess(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.targetFrequencyHz == 1000UL); CHECK(status.currentFrequencyHz == 1000UL); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.targetFrequencyHz == 4000UL); CHECK(status.currentFrequencyHz == 1001UL); PlsrControlTick100us(); CHECK(PlsrHwGetCurrentFrequencyHz(0U) == 1002UL); /* Down-retarget follows the configured slope instead of jumping. */ TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 500); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.targetFrequencyHz == 500UL); CHECK(status.currentFrequencyHz == 1001UL); /* Raw zero means the immutable S2 default speed. */ TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 0); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.targetFrequencyHz == 1000UL); CHECK(status.currentFrequencyHz == 1000UL); /* Above-maximum values clamp; invalid negatives retain the last safe * target and produce one sticky rejection for that observed value. */ TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 8000); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.targetFrequencyHz == 5000UL); CHECK(status.currentFrequencyHz == 1001UL); CHECK(status.speedClamped != 0U); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, -1); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.targetFrequencyHz == 5000UL); CHECK(status.currentFrequencyHz == 1002UL); CHECK(status.lastLiveFrequencyResult == PLSR_RESULT_INVALID_FREQUENCY); CHECK(status.liveFrequencyRejectCount == 1UL); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.liveFrequencyRejectCount == 1UL); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE + 10U, 2000); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.targetFrequencyHz == 2000UL); CHECK(status.currentFrequencyHz == 1004UL); CHECK(status.lastLiveFrequencyResult == PLSR_RESULT_OK); } static void TestZeroFrequencyWaits(void) { (void)PlsrHwInit(); PLSR_HW_START_PARAMS params; (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 0UL; params.targetPulses = 50; params.directionPoint = PLSR_HW_DIR_POINT_NONE; params.directionPositive = 1U; params.directionDelayMs = 0U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING); PlsrHwTick(0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); /* 起始速度为 0:profile 升频后首个非零频率才启动 PWM。 */ CHECK(PlsrHwSetFrequency(0U, 10UL) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U); } static void TestPulseCounting(void) { (void)PlsrHwInit(); PLSR_HW_START_PARAMS params; int pulse; (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 1000UL; params.targetPulses = 5; params.directionPoint = PLSR_HW_DIR_POINT_NONE; params.directionPositive = 1U; params.directionDelayMs = 0U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwGetEmittedPulses(0U) == 0); /* EGR.UG 只加载预装载寄存器,不能被当作物理脉冲。 * 共享 IRQ 入口在对应定时器没有 UIF 时也必须无动作。 */ PlsrHwOnTimerUpdate(0U); CHECK(PlsrHwGetEmittedPulses(0U) == 0); for (pulse = 0; pulse < 4; pulse++) { PlsrHwTestTriggerUpdate(0U); CHECK(PlsrHwGetEmittedPulses(0U) == pulse + 1); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); } /* 第 5 个脉冲:到目标,停止 + 段完成事件。 */ PlsrHwTestTriggerUpdate(0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwIsPulseActive(0U) == 0U); /* 停止后再触发更新中断无动作。 */ PlsrHwTestTriggerUpdate(0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); } static void TestAbPhaseAndCounting(void) { PLSR_HW_START_PARAMS params; static const uint8_t positiveA[4] = {1U, 1U, 0U, 0U}; static const uint8_t positiveB[4] = {0U, 1U, 1U, 0U}; static const uint8_t negativeA[4] = {0U, 1U, 1U, 0U}; static const uint8_t negativeB[4] = {1U, 1U, 0U, 0U}; uint32_t oldArr; uint32_t oldBasePsc; uint32_t oldPairPsc; uint32_t newPeriod; int quarter; (void)PlsrHwInit(); (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 1000UL; params.targetPulses = 4; params.outputMode = PLSR_OUTPUT_AB; params.directionPoint = 4U; /* AB 模式必须忽略独立 DIR 点。 */ params.directionPositive = 1U; params.directionDelayMs = 10U; /* AB 模式不得执行方向延时。 */ CHECK(PlsrHwStartPulse(1U, ¶ms) == PLSR_RESULT_INVALID_AXIS); CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_PWM_PENDING); CHECK(PlsrHwTestGetAbPhaseA(0U) == 0U); CHECK(PlsrHwTestGetAbPhaseB(0U) == 0U); CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); CHECK((PlsrHwTestGetPsc(0U) + 1UL) == 2UL * (PlsrHwTestGetPsc(1U) + 1UL)); CHECK(PlsrHwTestGetArr(0U) == PlsrHwTestGetArr(1U)); CHECK(PlsrHwTestGetCcr(0U) == PlsrHwTestGetCcr(1U)); CHECK(PlsrHwTestGetCcr(0U) == (PlsrHwTestGetArr(0U) + 1UL) / 2UL); /* 两相从精确 00 边界起步;CC1IF 会在开中断前再次清除。 */ CHECK(PlsrHwTestGetCnt(0U) == ((PlsrHwTestGetArr(0U) + 1UL) * 3UL) / 4UL + 1UL); CHECK(PlsrHwTestGetCnt(1U) == PlsrHwTestGetCcr(1U) + 1UL); CHECK(PlsrHwIsAbStartupPriming(0U) == 0U); /* 任一物理 timer update 不能直接计作完整 AB 周期。 */ PlsrHwTestTriggerUpdate(0U); CHECK(PlsrHwGetEmittedPulses(0U) == 0); /* 正向:00→10→11→01→00;四次相位跳变只计一个脉冲。 */ for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); CHECK(PlsrHwTestGetAbPhaseA(0U) == positiveA[quarter]); CHECK(PlsrHwTestGetAbPhaseB(0U) == positiveB[quarter]); } CHECK(PlsrHwGetEmittedPulses(0U) == 1); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); /* 运行中调频先排队,不能让相差 1/4 周期的两路各自加载 ARR。 */ oldArr = PlsrHwTestGetArr(0U); oldBasePsc = PlsrHwTestGetPsc(0U); oldPairPsc = PlsrHwTestGetPsc(1U); CHECK(PlsrHwSetFrequency(0U, 2000UL) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); } CHECK(PlsrHwGetEmittedPulses(0U) == 2); CHECK(PlsrHwTestGetArr(0U) == oldArr); CHECK(PlsrHwTestGetPsc(0U) == oldBasePsc); CHECK(PlsrHwTestGetPsc(1U) == oldPairPsc); CHECK(PlsrHwSetFrequency(0U, 2000UL) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetAbQuarter(0U) == 0U); CHECK(PlsrHwTestGetArr(0U) == oldArr); CHECK(PlsrHwTestGetArr(1U) == oldArr); CHECK(PlsrHwTestGetPsc(0U) == oldBasePsc); CHECK(PlsrHwTestGetPsc(1U) == oldPairPsc); for (quarter = 0; quarter < 3; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); CHECK(PlsrHwTestGetArr(0U) == oldArr); CHECK(PlsrHwTestGetArr(1U) == oldArr); CHECK(PlsrHwTestGetPsc(0U) == oldBasePsc); CHECK(PlsrHwTestGetPsc(1U) == oldPairPsc); } /* 回到 00 后,两路同时装载新频率并从精确 90° 位置重启。 */ PlsrHwTestAdvanceAbQuarter(0U); CHECK(PlsrHwGetEmittedPulses(0U) == 3); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK((PlsrHwTestGetArr(0U) != oldArr) || (PlsrHwTestGetPsc(0U) != oldBasePsc)); CHECK((PlsrHwTestGetPsc(0U) + 1UL) == 2UL * (PlsrHwTestGetPsc(1U) + 1UL)); CHECK(PlsrHwTestGetArr(0U) == PlsrHwTestGetArr(1U)); newPeriod = PlsrHwTestGetArr(0U) + 1UL; CHECK(PlsrHwTestGetCnt(0U) == (newPeriod * 3UL) / 4UL + 1UL); CHECK(PlsrHwTestGetCnt(1U) == newPeriod / 2UL + 1UL); for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); } CHECK(PlsrHwGetEmittedPulses(0U) == 4); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); PlsrHwTick(0U); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); CHECK(PlsrHwTestGetAbPhaseA(0U) == 0U); CHECK(PlsrHwTestGetAbPhaseB(0U) == 0U); /* 反向:00→01→11→10→00。 */ params.targetPulses = 1; params.directionPositive = 0U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); CHECK(PlsrHwTestGetAbPhaseA(0U) == negativeA[quarter]); CHECK(PlsrHwTestGetAbPhaseB(0U) == negativeB[quarter]); } CHECK(PlsrHwGetEmittedPulses(0U) == 1); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); PlsrHwTick(0U); /* 紧急停止即使发生在周期中间,也必须回到安全 00。 */ params.targetPulses = 10; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); PlsrHwTestAdvanceAbQuarter(0U); CHECK(PlsrHwTestGetAbQuarter(0U) == 1U); CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetAbQuarter(0U) == 0U); CHECK(PlsrHwTestGetAbPhaseA(0U) == 0U); CHECK(PlsrHwTestGetAbPhaseB(0U) == 0U); } static void TestTwoAbAxesIndependent(void) { PLSR_HW_START_PARAMS params; int quarter; (void)PlsrHwInit(); (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 1000UL; params.targetPulses = 1; params.outputMode = PLSR_OUTPUT_AB; params.directionPoint = PLSR_HW_DIR_POINT_NONE; params.directionPositive = 1U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwStartPulse(2U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(2U, 2000UL) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(2U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(3U) != 0U); for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); PlsrHwTick(0U); CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwGetEmittedPulses(2U) == 0); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(2U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(3U) != 0U); for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(2U); } CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwGetEmittedPulses(2U) == 1); PlsrHwTick(2U); } static void TestDualAbHardwareCountersAndResume(void) { PLSR_HW_START_PARAMS params; int quarter; int cycle; (void)PlsrHwInit(); (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 100000UL; params.targetPulses = 6; params.outputMode = PLSR_OUTPUT_AB; params.directionPoint = PLSR_HW_DIR_POINT_NONE; params.directionPositive = 1U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwStartPulse(2U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwUsesHardwareCounter(0U) == 1U); CHECK(PlsrHwUsesHardwareCounter(2U) == 1U); CHECK(PlsrHwSetFrequency(0U, 100000UL) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(2U, 100000UL) == PLSR_RESULT_OK); for (cycle = 0; cycle < 2; cycle++) { for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); } } for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(2U); } CHECK(PlsrHwGetEmittedPulses(0U) == 2); CHECK(PlsrHwGetEmittedPulses(2U) == 1); /* The source timer can cross its edge just before the ITR slave observes * it. The phase-corrected public count must not regress from 2 to 1 in * that synchronization window, otherwise core PAUSE raises COUNTER_FAULT. */ PlsrHwTestSetAbQuarterWithoutCounter(0U, 1U); CHECK(PlsrHwGetEmittedPulses(0U) == 2); PlsrHwTestSetAbQuarterWithoutCounter(0U, 0U); /* Request PAUSE after the source edge but before the following 00. The * pair must finish this already-started cycle instead of forcing GPIO 00 * and re-emitting its source edge after RESUME. */ PlsrHwTestAdvanceAbQuarter(0U); CHECK(PlsrHwGetEmittedPulses(0U) == 2); CHECK(PlsrHwSetFrequency(0U, 0UL) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetAbQuarter(0U) == 1U); CHECK((PlsrHwTestGetCr1(0U) & 1UL) != 0UL); CHECK((PlsrHwTestGetCr1(1U) & 1UL) != 0UL); for (quarter = 1; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); } CHECK(PlsrHwGetEmittedPulses(0U) == 3); CHECK((PlsrHwTestGetCr1(0U) & 1UL) == 0UL); CHECK((PlsrHwTestGetCr1(1U) & 1UL) == 0UL); CHECK(PlsrHwResumePulse(0U) == PLSR_RESULT_INVALID_STATE); PlsrHwTick(0U); CHECK(PlsrHwGetEmittedPulses(0U) == 3); CHECK(PlsrHwResumePulse(0U) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 100000UL) == PLSR_RESULT_OK); for (cycle = 0; cycle < 3; cycle++) { for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); } } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwGetEmittedPulses(0U) == 6); PlsrHwTick(0U); CHECK(PlsrHwUsesHardwareCounter(0U) == 0U); CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_RUNNING); for (cycle = 1; cycle < 6; cycle++) { for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(2U); } } CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwGetEmittedPulses(2U) == 6); PlsrHwTick(2U); CHECK(PlsrHwUsesHardwareCounter(2U) == 0U); } static void TestDualAbSimultaneousFastGate(void) { PLSR_HW_START_PARAMS params; (void)PlsrHwInit(); (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 100000UL; params.targetPulses = 200000; params.outputMode = PLSR_OUTPUT_AB; params.directionPoint = PLSR_HW_DIR_POINT_NONE; params.directionPositive = 1U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); params.directionPositive = 0U; CHECK(PlsrHwStartPulse(2U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 100000UL) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(2U, 100000UL) == PLSR_RESULT_OK); CHECK(PlsrHwUsesHardwareCounter(0U) == 1U); CHECK(PlsrHwUsesHardwareCounter(2U) == 1U); /* Both lag flags become pending before the first equal-priority handler. * Its entry scan must freeze all four timers before publishing axis 0. */ PlsrHwTestSignalDualAbFinalBoundary(0U); CHECK(PlsrHwTestGetAbFullGateCount() == 1UL); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwGetEmittedPulses(0U) == 200000); CHECK((PlsrHwTestGetCr1(2U) & 1UL) == 0UL); CHECK((PlsrHwTestGetCr1(3U) & 1UL) == 0UL); CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_RUNNING); /* The lag axis for negative Q2/Q3 is Q2. */ PlsrHwTestTriggerCompare(2U); CHECK(PlsrHwTestGetAbFullGateCount() == 2UL); CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwGetEmittedPulses(2U) == 200000); PlsrHwTick(0U); PlsrHwTick(2U); CHECK(PlsrHwUsesHardwareCounter(0U) == 0U); CHECK(PlsrHwUsesHardwareCounter(2U) == 0U); /* Repeat with pair 2's flag injected after the first entry scan. The * second scan must gate it before pair 0 performs deferred GPIO/counter * cleanup, and both pairs must still be frozen at a real 00 boundary. */ (void)PlsrHwInit(); params.directionPositive = 1U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); params.directionPositive = 0U; CHECK(PlsrHwStartPulse(2U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 100000UL) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(2U, 100000UL) == PLSR_RESULT_OK); PlsrHwTestSignalDualAbStaggeredFinalBoundary(0U); CHECK(PlsrHwTestGetAbFullGateCount() == 1UL); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwGetEmittedPulses(0U) == 200000); CHECK((PlsrHwTestGetCr1(2U) & 1UL) == 0UL); CHECK((PlsrHwTestGetCr1(3U) & 1UL) == 0UL); CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_RUNNING); PlsrHwTestTriggerCompare(2U); CHECK(PlsrHwTestGetAbFullGateCount() == 2UL); CHECK(PlsrHwGetState(2U) == PLSR_HW_STATE_DONE); CHECK(PlsrHwGetEmittedPulses(2U) == 200000); PlsrHwTick(0U); PlsrHwTick(2U); CHECK(PlsrHwUsesHardwareCounter(0U) == 0U); CHECK(PlsrHwUsesHardwareCounter(2U) == 0U); } static void TestAbFrequencyLimits(void) { PLSR_HW_START_PARAMS params; uint32_t oldArr; int quarter; (void)PlsrHwInit(); (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 1UL; params.targetPulses = 100; params.outputMode = PLSR_OUTPUT_AB; params.directionPoint = PLSR_HW_DIR_POINT_NONE; params.directionPositive = 1U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 1UL) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetArr(0U) == PlsrHwTestGetArr(1U)); CHECK((PlsrHwTestGetPsc(0U) + 1UL) == 2UL * (PlsrHwTestGetPsc(1U) + 1UL)); CHECK(PlsrHwTestGetArr(0U) <= 65535UL); oldArr = PlsrHwTestGetArr(0U); CHECK(PlsrHwSetFrequency(0U, 100000UL) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetArr(0U) == oldArr); for (quarter = 0; quarter < 4; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); } /* This request is 99 cycles away from target-1: it must be applied by * the frequency one-shot CCIE, not accidentally by the final guard. */ CHECK(PlsrHwTestGetArr(0U) != oldArr); CHECK(PlsrHwTestGetArr(0U) == 839UL); CHECK(PlsrHwTestGetArr(0U) == PlsrHwTestGetArr(1U)); CHECK((PlsrHwTestGetPsc(0U) + 1UL) == 2UL * (PlsrHwTestGetPsc(1U) + 1UL)); CHECK(PlsrHwTestGetArr(0U) >= 3UL); CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); } static void TestStopAndInvalidArgs(void) { (void)PlsrHwInit(); PLSR_HW_START_PARAMS params; (void)memset(¶ms, 0, sizeof(params)); params.frequencyHz = 1000UL; params.targetPulses = 100; params.directionPoint = PLSR_HW_DIR_POINT_NONE; params.directionPositive = 1U; params.directionDelayMs = 0U; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_OK); CHECK(PlsrHwSetFrequency(0U, 1000UL) == PLSR_RESULT_OK); CHECK(PlsrHwIsPulseActive(0U) == 1U); CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); CHECK(PlsrHwIsPulseActive(0U) == 0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwStartPulse(4U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT); CHECK(PlsrHwStartPulse(0U, NULL) == PLSR_RESULT_INVALID_ARGUMENT); params.targetPulses = 0; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT); params.targetPulses = 1; params.outputMode = PLSR_OUTPUT_CW_CCW; CHECK(PlsrHwStartPulse(1U, ¶ms) == PLSR_RESULT_INVALID_AXIS); params.outputMode = (PLSR_OUTPUT_MODE)99; CHECK(PlsrHwStartPulse(0U, ¶ms) == PLSR_RESULT_INVALID_ARGUMENT); CHECK(PlsrHwSetFrequency(4U, 1000UL) == PLSR_RESULT_INVALID_ARGUMENT); CHECK(PlsrHwStopPulse(4U) == PLSR_RESULT_INVALID_ARGUMENT); } /* ---- 端到端集成:START → 硬件 → 计数 → 事件 → 段间 → 完成 ---- */ static void TestEndToEndTwoSegments(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_STATUS status; uint16_t initialPsc; uint16_t initialArr; int ticks; int pulse; TestResetEnvironment(); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 2); TestSetSegment(&memory, 1U, 1000U, 100); TestSetSegment(&memory, 2U, 2000U, 200); call = TestMakeCall(&memory); CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_ACCEL); CHECK(status.currentSegment == 1U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DIR_SETTLING); /* DIR 延时 10ms → PWM 启动(段1 起始速度 0,profile 升频后启动)。 */ for (ticks = 0; ticks < 9; ticks++) { PlsrProcess(); } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U); CHECK(PlsrCalculateTimerDivider(168000000UL, 75UL, &initialPsc, &initialArr) == PLSR_RESULT_OK); CHECK(PlsrHwTestGetPsc(0U) == initialPsc); CHECK(PlsrHwTestGetArr(0U) == initialArr); /* 加速完成 → 状态机进入 RUN。 */ for (ticks = 0; ticks < 500; ticks++) { PlsrProcess(); if (TestGetStatus().state == PLSR_STATE_RUN) { break; } } status = TestGetStatus(); CHECK(status.state == PLSR_STATE_RUN); CHECK(status.currentSegment == 1U); /* 段1 脉冲完成:100 次更新中断 → SEGMENT_COMPLETE → 段2 启动。 */ for (pulse = 0; pulse < 100; pulse++) { PlsrHwTestTriggerUpdate(0U); } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_ACCEL); CHECK(status.currentSegment == 2U); CHECK(status.logicalPosition == 100); CHECK(status.taskPulses == 100); CHECK(status.totalPulses == 100); CHECK(TestReadSdDword(1000U) == 2); CHECK(TestReadSdDword(1002U) == 0); /* 段2:DIR 延时 → PWM → 加速 → RUN。 */ for (ticks = 0; ticks < 600; ticks++) { PlsrProcess(); if (TestGetStatus().state == PLSR_STATE_RUN) { break; } } status = TestGetStatus(); CHECK(status.state == PLSR_STATE_RUN); CHECK(status.currentSegment == 2U); /* 段2 脉冲完成 → 任务结束。 */ for (pulse = 0; pulse < 200; pulse++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.done != 0U); CHECK(status.logicalPosition == 300); CHECK(status.taskPulses == 300); CHECK(status.totalPulses == 300); CHECK(TestReadSdDword(1000U) == 2); CHECK(TestReadSdDword(1002U) == 200); CHECK(TestReadSdDword(1004U) == 200); CHECK(TestReadSdDword(1006U) == 0); { int32_t hsdPulses; int32_t hsdEquivalent; CHECK(PlcDeviceReadHsdDword(0U, &hsdPulses) == PLC_DEVICE_OK); CHECK(hsdPulses == 300); CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); CHECK(hsdEquivalent == 300); } /* 终态转换后 HAL 回 IDLE(允许重新启动),脉冲已停止。 */ CHECK(PlsrHwIsPulseActive(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); } static void TestEndToEndAbSegment(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_STATUS status; int quarter; TestResetEnvironment(); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, -2); call = TestMakeCall(&memory); call.sequence = 15UL; call.outputModeOverride = PLSR_OUTPUT_AB; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_ACCEL); CHECK(status.outputMode == PLSR_OUTPUT_AB); CHECK(status.directionPoint == PLSR_DIRECTION_POINT_NONE); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); CHECK(PlsrHwTestGetCc1PolarityInverted(0U) != 0U); CHECK(PlsrHwTestGetCc1PolarityInverted(1U) != 0U); /* 负脉冲选择反向相序,完整两个周期后由同一事件链结束任务。 */ PlsrHwTestAdvanceAbQuarter(0U); CHECK(PlsrHwTestGetAbPhaseA(0U) == 0U); CHECK(PlsrHwTestGetAbPhaseB(0U) == 1U); for (quarter = 1; quarter < 8; quarter++) { PlsrHwTestAdvanceAbQuarter(0U); } CHECK(PlsrHwGetEmittedPulses(0U) == 2); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_DONE); PlsrProcess(); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.done != 0U); CHECK(status.directionPositive == 0U); CHECK(status.logicalPosition == -2); CHECK(status.taskPulses == -2); CHECK(status.totalPulses == 2); CHECK(TestReadSdDword(1002U) == -2); CHECK(TestReadSdDword(1004U) == -2); CHECK(status.highResourceMask == 0U); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); } static void TestPositionOnImmediateStop(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_COMMAND command; PLSR_STATUS status; int pulse; int tick; TestResetEnvironment(); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 100); call = TestMakeCall(&memory); call.sequence = 30UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); for (tick = 0; tick < 10; tick++) { PlsrProcess(); } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); for (pulse = 0; pulse < 37; pulse++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.logicalPosition == 37); CHECK(status.taskPulses == 37); CHECK(status.totalPulses == 37); command.sequence = 31UL; command.axis = 0U; command.opcode = PLSR_CMD_STOP_IMMEDIATE; command.argument = 0; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); PlsrProcess(); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE); CHECK(PlsrPostEvent(0U, PLSR_EVENT_STOP_IMMEDIATE_DONE) == PLSR_RESULT_OK); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_STOPPED); CHECK(status.logicalPosition == 37); CHECK(status.taskPulses == 37); CHECK(status.totalPulses == 37); CHECK(TestReadSdDword(1002U) == 37); command.sequence = 32UL; command.opcode = PLSR_CMD_CLEAR_TOTAL; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(); CHECK(status.logicalPosition == 37); CHECK(status.totalPulses == 0); } static void TestAbsolutePositionAccounting(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_COMMAND command; PLSR_STATUS status; int32_t hsdPosition; int pulse; int tick; TestResetEnvironment(); command.sequence = 40UL; command.axis = 0U; command.opcode = PLSR_CMD_SET_POSITION; command.argument = 100; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); PlsrProcess(); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 130); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S1_BASE, 1); call = TestMakeCall(&memory); call.sequence = 41UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); for (tick = 0; tick < 10; tick++) { PlsrProcess(); } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); for (pulse = 0; pulse < 30; pulse++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.logicalPosition == 130); CHECK(status.taskPulses == 30); CHECK(status.totalPulses == 30); CHECK(status.positionValid != 0U); CHECK(PlcDeviceReadHsdDword(0U, &hsdPosition) == PLC_DEVICE_OK); CHECK(hsdPosition == 130); } static void TestEquivalentRemainderAccounting(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_STATUS status; int32_t hsdPulses; int32_t hsdEquivalent; TestResetEnvironment(); CHECK(PlcDeviceWriteSfd(900U, (1U << 8U)) == PLC_DEVICE_OK); TestWriteSfdDword(902U, 3UL); TestWriteSfdDword(904U, 2UL); TestWriteSfdDword(956U, 60000UL); CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 1); call = TestMakeCall(&memory); call.sequence = 30UL; call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; /* 3脉冲/2单位:第一次1单位只输出1脉冲并保存1/2余数。 */ CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); PlsrHwTestTriggerUpdate(0U); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.logicalPosition == 1); CHECK(status.taskPulses == 1); CHECK(status.totalPulses == 1); CHECK(PlcDeviceReadHsdDword(0U, &hsdPulses) == PLC_DEVICE_OK); CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); CHECK(hsdPulses == 1); CHECK(hsdEquivalent == 0); /* 第二次1单位合并余数后输出2脉冲;两次合计精确为3脉冲/2单位。 */ call.sequence = 31UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); PlsrHwTestTriggerUpdate(0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); PlsrHwTestTriggerUpdate(0U); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.logicalPosition == 3); CHECK(status.taskPulses == 2); CHECK(status.totalPulses == 3); CHECK(PlcDeviceReadHsdDword(0U, &hsdPulses) == PLC_DEVICE_OK); CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); CHECK(hsdPulses == 3); CHECK(hsdEquivalent == 2); CHECK(TestReadSdDword(1002U) == 2); CHECK(TestReadSdDword(1004U) == 1); } static void TestEquivalentCompatibleError(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_STATUS status; int32_t errorCode = -1; int32_t errorBlock = -1; TestResetEnvironment(); CHECK(PlcDeviceWriteSfd(900U, (1U << 8U)) == PLC_DEVICE_OK); TestWriteSfdDword(902U, 0UL); TestWriteSfdDword(904U, 2UL); TestWriteSfdDword(956U, 60000UL); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 1); call = TestMakeCall(&memory); call.sequence = 32UL; call.outputModeOverride = PLSR_OUTPUT_PULSE_DIR; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(); CHECK(status.lastCommandResult == PLSR_RESULT_INVALID_S2); CHECK(status.state == PLSR_STATE_IDLE); CHECK(PlcDeviceReadSd(1010U, &errorCode) == PLC_DEVICE_OK); CHECK(PlcDeviceReadSd(1011U, &errorBlock) == PLC_DEVICE_OK); CHECK(errorCode == 2); CHECK(errorBlock == 0); /* A valid retry clears the compatible parameter error. */ TestWriteSfdDword(902U, 3UL); call.sequence = 33UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); CHECK(PlcDeviceReadSd(1010U, &errorCode) == PLC_DEVICE_OK); CHECK(errorCode == 0); CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); } static void TestSoftLimitAndSegmentEvent(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_COMMAND command; PLSR_STATUS status; PLC_DEVICE_EVENT_RECORD eventRecord; int32_t errorCode; int pulse; int tick; uint32_t pulsePhase = 0UL; TestResetEnvironment(); CHECK(PlcDeviceWriteSfd(900U, (1U << 2U)) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(912U, 0U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(915U, 0xFFFFU) == PLC_DEVICE_OK); TestWriteSfdDword(930U, 100UL); TestWriteSfdDword(932U, (uint32_t)(int32_t)-100); command.sequence = 40UL; command.axis = 0U; command.opcode = PLSR_CMD_SET_POSITION; command.argument = 100; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); PlsrProcess(); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 10); call = TestMakeCall(&memory); call.sequence = 41UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(); CHECK(status.lastCommandResult == PLSR_RESULT_LIMIT_POSITIVE); CHECK(status.state == PLSR_STATE_IDLE); CHECK(status.positiveLimitActive != 0U); CHECK(status.error == PLSR_ERROR_LIMIT_POSITIVE); CHECK(PlcDeviceReadSd(1010U, &errorCode) == PLC_DEVICE_OK); CHECK(errorCode == 5); /* 正限位上只禁止正向,反向离开仍可正常完成。 */ TestSetSegment(&memory, 1U, 1000U, -10); call.sequence = 42UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); for (pulse = 0; pulse < 10; pulse++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.logicalPosition == 90); CHECK(status.error == PLSR_ERROR_NONE); CHECK(PlcDeviceReadEvent(6000U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); CHECK(eventRecord.lastReason == PLSR_STOP_REASON_NORMAL_COMPLETE); CHECK(eventRecord.pending != 0U); /* 运行中按预计制动距离触发软限位,PWM保持运行并按曲线缓停。 */ TestResetEnvironment(); CHECK(PlcDeviceWriteSfd(900U, (1U << 2U)) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); TestWriteSfdDword(930U, 50UL); TestWriteSfdDword(932U, (uint32_t)(int32_t)-50); command.sequence = 43UL; command.opcode = PLSR_CMD_SET_POSITION; command.argument = 0; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); PlsrProcess(); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 10000); call = TestMakeCall(&memory); call.sequence = 44UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); for (pulse = 0; pulse < 49; pulse++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_DECEL); CHECK(status.stopReason == PLSR_STOP_REASON_LIMIT_POSITIVE); CHECK(status.positiveLimitActive != 0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlcDeviceReadSd(1010U, &errorCode) == PLC_DEVICE_OK); CHECK(errorCode == 5); for (tick = 0; tick < 20; tick++) { PlsrProcess(); } status = TestGetStatus(); CHECK(status.state == PLSR_STATE_STOPPED); CHECK(status.logicalPosition == 49); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE); CHECK(PlcDeviceReadEvent(6000U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); CHECK(eventRecord.lastReason == PLSR_STOP_REASON_LIMIT_POSITIVE); /* Board-equivalent time simulation for the +500 precision case. Each * loop represents 1 ms and emits complete hardware periods according to * the frequency that was active during that interval. */ TestResetEnvironment(); CHECK(PlcDeviceWriteSfd(900U, (1U << 2U)) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); TestWriteSfdDword(930U, 500UL); TestWriteSfdDword(932U, (uint32_t)(int32_t)-500); TestWriteSfdDword(950U, 1000UL); CHECK(PlcDeviceWriteSfd(952U, 100U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(953U, 100U) == PLC_DEVICE_OK); TestWriteSfdDword(958U, 1000UL); TestWriteSfdDword(960U, 0UL); command.sequence = 45UL; command.opcode = PLSR_CMD_SET_POSITION; command.argument = 0; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); PlsrProcess(); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 10000); call = TestMakeCall(&memory); call.sequence = 46UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); for (tick = 0; tick < 1000; tick++) { pulsePhase += PlsrHwGetCurrentFrequencyHz(0U); while ((pulsePhase >= 1000UL) && (PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING)) { pulsePhase -= 1000UL; PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); if (status.state == PLSR_STATE_STOPPED) { break; } } CHECK(status.state == PLSR_STATE_STOPPED); CHECK(status.stopReason == PLSR_STOP_REASON_LIMIT_POSITIVE); CHECK(status.logicalPosition >= 499); CHECK(status.logicalPosition <= 501); /* Negative/high-speed companion case for the P15 board matrix. */ TestResetEnvironment(); pulsePhase = 0UL; CHECK(PlcDeviceWriteSfd(900U, (1U << 2U)) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); TestWriteSfdDword(930U, 1000UL); TestWriteSfdDword(932U, (uint32_t)(int32_t)-1000); TestWriteSfdDword(950U, 2000UL); CHECK(PlcDeviceWriteSfd(952U, 100U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(953U, 100U) == PLC_DEVICE_OK); TestWriteSfdDword(958U, 2000UL); TestWriteSfdDword(960U, 0UL); command.sequence = 47UL; command.opcode = PLSR_CMD_SET_POSITION; command.argument = 0; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); PlsrProcess(); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 2000U, -10000); call = TestMakeCall(&memory); call.sequence = 48UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); for (tick = 0; tick < 1000; tick++) { pulsePhase += PlsrHwGetCurrentFrequencyHz(0U); while ((pulsePhase >= 1000UL) && (PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING)) { pulsePhase -= 1000UL; PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); if (status.state == PLSR_STATE_STOPPED) { break; } } CHECK(status.state == PLSR_STATE_STOPPED); CHECK(status.stopReason == PLSR_STOP_REASON_LIMIT_NEGATIVE); CHECK(status.logicalPosition >= -1001); CHECK(status.logicalPosition <= -999); } static void TestHardLimitAndEmergencyLatch(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_COMMAND command; PLSR_STATUS status; PLC_DEVICE_EVENT_RECORD eventRecord; TestResetEnvironment(); CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(912U, 0U) == PLC_DEVICE_OK); CHECK(PlcDeviceWriteSfd(915U, 0xFF03U) == PLC_DEVICE_OK); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 10000); call = TestMakeCall(&memory); call.sequence = 50UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); memory.bits[0][3U] = 1U; PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_DECEL); CHECK(status.positiveLimitActive != 0U); CHECK(status.stopReason == PLSR_STOP_REASON_LIMIT_POSITIVE); CHECK(PlcDeviceReadEvent(6000U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.lastReason == PLSR_STOP_REASON_LIMIT_POSITIVE); TestResetEnvironment(); CHECK(PlcDeviceWriteSfd(907U, 0U) == PLC_DEVICE_OK); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 10000); call = TestMakeCall(&memory); call.sequence = 51UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); command.sequence = 52UL; command.axis = 0U; command.opcode = PLSR_CMD_RESET_ERROR; command.argument = 0; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); CHECK(PlsrPostEvent(0U, PLSR_EVENT_LIMIT_POSITIVE | PLSR_EVENT_SOFTWARE_EMERGENCY) == PLSR_RESULT_OK); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_STOPPED); CHECK(status.stopReason == PLSR_STOP_REASON_SOFTWARE_EMERGENCY); CHECK(status.error == PLSR_ERROR_EMERGENCY); CHECK(status.emergencyLatched != 0U); CHECK(status.lastCommandSequence == 52UL); CHECK(status.lastCommandResult == PLSR_RESULT_BUSY); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE); CHECK(PlcDeviceReadEvent(6000U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); CHECK(eventRecord.lastReason == PLSR_STOP_REASON_SOFTWARE_EMERGENCY); call.sequence = 53UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(); CHECK(status.lastCommandResult == PLSR_RESULT_EMERGENCY_LATCHED); CHECK(status.state == PLSR_STATE_STOPPED); command.sequence = 54UL; command.axis = 0U; command.opcode = PLSR_CMD_RESET_ERROR; command.argument = 0; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_IDLE); CHECK(status.error == PLSR_ERROR_NONE); CHECK(status.emergencyLatched == 0U); } static void TestProductionSelfTestStartsAb(void) { PLSR_STATUS status; TestResetEnvironment(); CHECK(PlsrSelfTestQueue() == PLSR_RESULT_QUEUED); PlsrProcess(); status = TestGetStatus(); CHECK(status.lastCommandResult == PLSR_RESULT_OK); CHECK(status.outputMode == PLSR_OUTPUT_AB); CHECK(status.currentSegment == 1U); CHECK(status.state == PLSR_STATE_ACCEL); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); CHECK(PlsrHwTestGetPwmEnabled(0U) != 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) != 0U); CHECK(PlsrHwStopPulse(0U) == PLSR_RESULT_OK); } static void TestEquivalentSelfTest(void) { PLSR_STATUS status; int32_t hsdPulses; int32_t hsdEquivalent; int ticks; TestResetEnvironment(); CHECK(PlsrEquivalentSelfTestQueue() == PLSR_RESULT_QUEUED); PlsrProcess(); /* SFD907=10ms. Advance the simulated hardware delay before segment 1. */ for (ticks = 0; ticks < 10; ticks++) { PlsrProcess(); } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); /* 3 pulses / 2 units: 1001 units emit 1501 pulses and keep 1/2 remainder. */ for (ticks = 0; ticks < 1501; ticks++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); /* Segment 2 consumes the remainder and therefore emits 1502 pulses. */ for (ticks = 0; ticks < 1502; ticks++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.lastCommandResult == PLSR_RESULT_OK); CHECK(status.outputMode == PLSR_OUTPUT_PULSE_DIR); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.currentSegment == 2U); CHECK(status.logicalPosition == 3003); CHECK(status.taskPulses == 3003); CHECK(status.totalPulses == 3003); CHECK(PlcDeviceReadHsdDword(0U, &hsdPulses) == PLC_DEVICE_OK); CHECK(PlcDeviceReadHsdDword(2U, &hsdEquivalent) == PLC_DEVICE_OK); CHECK(hsdPulses == 3003); CHECK(hsdEquivalent == 2002); CHECK(TestReadSdDword(1002U) == 1502); CHECK(TestReadSdDword(1004U) == 1001); } static void TestProtectionSelfTest(void) { PLC_DEVICE_EVENT_RECORD eventRecord; PLSR_STATUS status; uint32_t pulseAccumulator = 0UL; int32_t value; int ticks; TestResetEnvironment(); CHECK(PlsrProtectionSelfTestQueue() == PLSR_RESULT_QUEUED); PlsrProcess(); /* Complete the 10ms direction-settle interval. */ for (ticks = 0; ticks < 10; ticks++) { PlsrProcess(); } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); /* Convert the current frequency into simulated hardware updates over * each 1ms process tick. This also models the falling pulse density * during the controlled stop. */ status = TestGetStatus(); for (ticks = 0; ticks < 600; ticks++) { pulseAccumulator += PlsrHwGetCurrentFrequencyHz(0U); while ((pulseAccumulator >= 1000UL) && (PlsrHwIsPulseActive(0U) != 0U)) { PlsrHwTestTriggerUpdate(0U); pulseAccumulator -= 1000UL; } PlsrProcess(); status = TestGetStatus(); if (status.state == PLSR_STATE_STOPPED) { break; } } status = TestGetStatus(); CHECK(status.state == PLSR_STATE_STOPPED); CHECK(status.stopReason == PLSR_STOP_REASON_LIMIT_POSITIVE); CHECK(status.error == PLSR_ERROR_LIMIT_POSITIVE); CHECK(status.emergencyLatched == 0U); CHECK(status.logicalPosition >= 499); CHECK(status.logicalPosition <= 501); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE); CHECK(PlcDeviceReadSd(1010U, &value) == PLC_DEVICE_OK); CHECK(value == 5U); CHECK(PlcDeviceReadEvent(6000U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); CHECK(eventRecord.pending != 0U); 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]); CHECK(status.hardwareCounter == ((axis < 2U) ? 1U : 0U)); } /* 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 TestBacklashSelfTest(void) { PLC_DEVICE_EVENT_RECORD eventRecord; PLSR_STATUS status; int32_t hsdPosition; int ticks; TestResetEnvironment(); CHECK(PlsrBacklashSelfTestQueue() == PLSR_RESULT_QUEUED); PlsrProcess(); for (ticks = 0; ticks < 10; ticks++) { PlsrProcess(); } /* Segment 1: +200 user pulses, with no compensation on first motion. */ CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); for (ticks = 0; ticks < 200; ticks++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.currentSegment == 2U); CHECK(status.backlashActive != 0U); CHECK(status.directionPositive == 0U); CHECK(status.logicalPosition == 200); CHECK(status.taskPulses == 200); CHECK(status.totalPulses == 200); CHECK(status.physicalPulses == 200UL); CHECK(PlcDeviceReadEvent(6000U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); CHECK(PlcDeviceReadEvent(6001U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 0UL); /* Direction change to negative: 20 physical compensation pulses first. */ for (ticks = 0; ticks < 10; ticks++) { PlsrProcess(); } CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); for (ticks = 0; ticks < 20; ticks++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.backlashActive == 0U); CHECK(status.currentSegment == 2U); CHECK(status.logicalPosition == 200); CHECK(status.taskPulses == 200); CHECK(status.totalPulses == 200); CHECK(status.physicalPulses == 220UL); CHECK(PlcDeviceReadEvent(6001U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 0UL); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_RUNNING); /* The real segment 2 contributes -200 to user position/counting. */ for (ticks = 0; ticks < 200; ticks++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.currentSegment == 3U); CHECK(status.backlashActive != 0U); CHECK(status.directionPositive != 0U); CHECK(status.logicalPosition == 0); CHECK(status.taskPulses == 0); CHECK(status.totalPulses == 400); CHECK(status.physicalPulses == 420UL); CHECK(PlcDeviceReadEvent(6001U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); CHECK(PlcDeviceReadEvent(6002U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 0UL); /* Direction change back to positive: 10 compensation, then +100 user. */ for (ticks = 0; ticks < 10; ticks++) { PlsrProcess(); } for (ticks = 0; ticks < 10; ticks++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.backlashActive == 0U); CHECK(status.logicalPosition == 0); CHECK(status.totalPulses == 400); CHECK(status.physicalPulses == 430UL); CHECK(PlcDeviceReadEvent(6002U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 0UL); for (ticks = 0; ticks < 100; ticks++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.done != 0U); CHECK(status.logicalPosition == 100); CHECK(status.taskPulses == 100); CHECK(status.totalPulses == 500); CHECK(status.physicalPulses == 530UL); CHECK(PlcDeviceReadHsdDword(0U, &hsdPosition) == PLC_DEVICE_OK); CHECK(hsdPosition == 100); CHECK(PlcDeviceReadEvent(6002U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); CHECK(eventRecord.lastReason == PLSR_STOP_REASON_NORMAL_COMPLETE); } static void TestDirectionLogicSelfTest(void) { static const uint8_t expectedDirectionPoint[2] = {4U, 3U}; PLC_DEVICE_EVENT_RECORD eventRecord; PLSR_STATUS status; uint8_t smDirection; uint8_t axis; int ticks; TestResetEnvironment(); CHECK(PlsrDirectionLogicSelfTestQueue() == PLSR_RESULT_QUEUED); PlsrProcess(); for (ticks = 0; ticks < 10; ticks++) { PlsrProcess(); } /* Both axes move logically positive. Electrical DIR terminals are * complementary because axis 1 has SFD1030 Bit1 set. */ CHECK(PlsrHwTestGetDirLevel(0U) == 1U); CHECK(PlsrHwTestGetDirLevel(1U) == 0U); for (axis = 0U; axis < 2U; axis++) { CHECK(PlsrGetStatus(axis, &status) == PLSR_RESULT_OK); CHECK(status.state == PLSR_STATE_RUN); CHECK(status.directionPoint == expectedDirectionPoint[axis]); CHECK(status.directionPositive != 0U); CHECK(PlsrHwGetState(axis) == PLSR_HW_STATE_RUNNING); } for (ticks = 0; ticks < 200; ticks++) { PlsrHwTestTriggerUpdate(0U); PlsrHwTestTriggerUpdate(1U); } PlsrProcess(); /* Both reverse logically; only the physical terminal mapping differs. */ CHECK(PlsrHwTestGetDirLevel(0U) == 0U); CHECK(PlsrHwTestGetDirLevel(1U) == 1U); for (axis = 0U; axis < 2U; axis++) { CHECK(PlsrGetStatus(axis, &status) == PLSR_RESULT_OK); CHECK(status.currentSegment == 2U); CHECK(status.directionPositive == 0U); CHECK(status.logicalPosition == 200); CHECK(status.totalPulses == 200); CHECK(PlsrHwGetState(axis) == PLSR_HW_STATE_DIR_SETTLING); } for (ticks = 0; ticks < 10; ticks++) { PlsrProcess(); } for (ticks = 0; ticks < 200; ticks++) { PlsrHwTestTriggerUpdate(0U); PlsrHwTestTriggerUpdate(1U); } PlsrProcess(); for (axis = 0U; axis < 2U; axis++) { CHECK(PlsrGetStatus(axis, &status) == PLSR_RESULT_OK); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.done != 0U); CHECK(status.directionPositive == 0U); CHECK(status.logicalPosition == 0); CHECK(status.taskPulses == 0); CHECK(status.totalPulses == 400); CHECK(status.physicalPulses == 400UL); CHECK(PlcDeviceReadSm((uint16_t)(1001U + (uint16_t)axis * 20U), &smDirection) == PLC_DEVICE_OK); CHECK(smDirection == 0U); CHECK(PlcDeviceReadEvent((uint16_t)(6000U + (uint16_t)axis * 100U), &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); CHECK(PlcDeviceReadEvent((uint16_t)(6001U + (uint16_t)axis * 100U), &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); } CHECK(PlsrHwTestGetDirLevel(0U) == 0U); CHECK(PlsrHwTestGetDirLevel(1U) == 1U); } static void TestCwCcwSelfTest(void) { PLC_DEVICE_EVENT_RECORD eventRecord; PLSR_STATUS status; int pulse; TestResetEnvironment(); CHECK(PlsrCwCcwSelfTestQueue() == PLSR_RESULT_QUEUED); PlsrProcess(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.state == PLSR_STATE_RUN); CHECK(status.outputMode == PLSR_OUTPUT_CW_CCW); CHECK(status.directionPositive != 0U); CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); for (pulse = 0; pulse < 300; pulse++) { PlsrHwTestTriggerCompare(0U); } PlsrHwTestTriggerUpdate(0U); PlsrProcess(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.currentSegment == 2U); CHECK(status.directionPositive == 0U); CHECK(status.logicalPosition == 300); CHECK(status.totalPulses == 300); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 1U); for (pulse = 0; pulse < 200; pulse++) { PlsrHwTestTriggerCompare(1U); } PlsrHwTestTriggerUpdate(1U); PlsrProcess(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.state == PLSR_STATE_COMPLETED); CHECK(status.done != 0U); CHECK(status.logicalPosition == 100); CHECK(status.taskPulses == 100); CHECK(status.totalPulses == 500); CHECK(status.physicalPulses == 500UL); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); CHECK(PlsrHwTestGetPwmEnabled(1U) == 0U); CHECK(PlcDeviceReadEvent(6000U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); CHECK(PlcDeviceReadEvent(6001U, &eventRecord) == PLC_DEVICE_OK); CHECK(eventRecord.count == 1UL); } static void TestFastRefreshSelfTest(void) { PLSR_STATUS status; uint32_t beforeFastHz; uint16_t refreshCode; int tick; TestResetEnvironment(); CHECK(PlsrFastRefreshSelfTestQueue() == PLSR_RESULT_QUEUED); PlsrProcess(); for (tick = 0; tick < 10; tick++) { PlsrProcess(); } CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.state == PLSR_STATE_ACCEL); CHECK(status.directionPoint == 4U); CHECK(PlsrHwGetCurrentFrequencyHz(0U) > 0UL); CHECK(PlsrGetStatus(1U, &status) == PLSR_RESULT_OK); CHECK(status.state == PLSR_STATE_ACCEL); CHECK(status.directionPoint == 3U); CHECK(PlsrTestGetJobRefreshCode(1U) == 2U); CHECK(PlsrTestGetProfileRefreshHz(1U) == 10000UL); beforeFastHz = PlsrTestGetProfileFrequencyHz(1U); PlsrControlTick100us(); CHECK(PlsrTestGetProfileFrequencyHz(1U) > beforeFastHz); CHECK(PlcDeviceReadSfd(964U, &refreshCode) == PLC_DEVICE_OK); CHECK(refreshCode == 0U); CHECK(PlcDeviceReadSfd(1094U, &refreshCode) == PLC_DEVICE_OK); CHECK(refreshCode == 2U); } static void TestDynamicFrequencySelfTest(void) { PLSR_STATUS status; int tick; TestResetEnvironment(); CHECK(PlsrDynamicFrequencySelfTestQueue() == PLSR_RESULT_QUEUED); PlsrProcess(); for (tick = 0; tick < 10; tick++) { PlsrProcess(); } PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.currentFrequencyHz == 1000UL); CHECK(status.targetFrequencyHz == 1000UL); PlsrSelfTestLiveFrequencyHz = 4000; PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.currentFrequencyHz == 1001UL); CHECK(status.targetFrequencyHz == 4000UL); PlsrSelfTestLiveFrequencyHz = 500; PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.currentFrequencyHz == 1000UL); CHECK(status.targetFrequencyHz == 500UL); CHECK(status.liveFrequencyRejectCount == 0UL); } static void TestDynamicFrequencySchedule(void) { int tick; TestResetEnvironment(); CHECK(PlsrDynamicFrequencySelfTestQueue() == PLSR_RESULT_QUEUED); CHECK(PlsrSelfTestLiveFrequencyHz == 1000); CHECK(PlsrSelfTestDynamicPhase == 0U); for (tick = 0; tick < 9999; tick++) { PlsrSelfTestControlTick100us(); } CHECK(PlsrSelfTestLiveFrequencyHz == 1000); PlsrSelfTestControlTick100us(); CHECK(PlsrSelfTestLiveFrequencyHz == 4000); CHECK(PlsrSelfTestDynamicPhase == 1U); for (tick = 0; tick < 5000; tick++) { PlsrSelfTestControlTick100us(); } CHECK(PlsrSelfTestLiveFrequencyHz == 500); CHECK(PlsrSelfTestDynamicPhase == 2U); for (tick = 0; tick < 5000; tick++) { PlsrSelfTestControlTick100us(); } CHECK(PlsrSelfTestLiveFrequencyHz == 0); CHECK(PlsrSelfTestDynamicPhase == 3U); for (tick = 0; tick < 2000; tick++) { PlsrSelfTestControlTick100us(); } CHECK(PlsrSelfTestLiveFrequencyHz == 8000); CHECK(PlsrSelfTestDynamicPhase == 4U); for (tick = 0; tick < 5000; tick++) { PlsrSelfTestControlTick100us(); } CHECK(PlsrSelfTestLiveFrequencyHz == -1); CHECK(PlsrSelfTestDynamicPhase == 5U); for (tick = 0; tick < 2000; tick++) { PlsrSelfTestControlTick100us(); } CHECK(PlsrSelfTestLiveFrequencyHz == 2000); CHECK(PlsrSelfTestDynamicPhase == 6U); CHECK(PlsrSelfTestDynamicTick100us == 29000UL); PlsrSelfTestControlTick100us(); CHECK(PlsrSelfTestDynamicTick100us == 29000UL); } static void TestModbusDataSelfTest(void) { PLSR_STATUS status; uint16_t frequencyWords[2]; uint16_t word; int tick; TestResetEnvironment(); CHECK(PlsrModbusDataSelfTestQueue() == PLSR_RESULT_QUEUED); PlsrProcess(); for (tick = 0; tick < 10; tick++) { PlsrProcess(); } CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, 1000UL, &word) == 1U); CHECK(word == 1U); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK((status.state == PLSR_STATE_ACCEL) || (status.state == PLSR_STATE_RUN)); CHECK(PlsrHwIsPulseActive(0U) == 1U); CHECK(status.currentFrequencyHz == 1000UL); CHECK(status.targetFrequencyHz == 1000UL); frequencyWords[0] = 4000U; frequencyWords[1] = 0U; CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, 1010UL, frequencyWords, 2UL) == 1U); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.currentFrequencyHz == 1001UL); CHECK(status.targetFrequencyHz == 4000UL); CHECK(status.liveFrequencyRejectCount == 0UL); frequencyWords[0] = 0xFFFFU; frequencyWords[1] = 0xFFFFU; CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, 1010UL, frequencyWords, 2UL) == 1U); PlsrControlTick100us(); CHECK(PlsrGetStatus(0U, &status) == PLSR_RESULT_OK); CHECK(status.targetFrequencyHz == 4000UL); CHECK(status.liveFrequencyRejectCount == 1UL); CHECK(status.lastLiveFrequencyResult == PLSR_RESULT_INVALID_FREQUENCY); } static void TestModbusControlProtocol(void) { const uint32_t controlBase = 1200UL; const uint32_t s0Base = 1600UL; const uint32_t s1Base = 1700UL; uint16_t s0Words[20] = {0U}; uint16_t s1Words[4] = {0U}; uint16_t callRequest[16] = {0U}; uint16_t callResponse[12]; uint16_t commandRequest[8] = {0U}; uint16_t commandResponse[8]; uint16_t controlHeader[8]; uint16_t performanceWords[8]; uint16_t stagePerformanceWords[PLSR_MODBUS_STAGE_PERFORMANCE_WORDS]; uint16_t usbDiagnosticsWords[PLSR_MODBUS_USB_DIAGNOSTICS_WORDS]; uint16_t axisStatus[48]; uint16_t pulseWords[2]; uint32_t generationBegin; uint32_t generationEnd; PLSR_STATUS coreStatus; int64_t pausedPulses; int tick; TestResetEnvironment(); CHECK(PlsrModbusControlSelfTestPrepare() == PLSR_RESULT_OK); CHECK(PlsrModbusControlInit((uint16_t)controlBase) == PLSR_RESULT_OK); CHECK(PlsrModbusControlIsEnabled() == 1U); CHECK(PlsrModbusControlGetBaseAddress() == controlBase); for (tick = 0; tick < 8; tick++) { CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, controlBase + (uint32_t)tick, &controlHeader[tick]) == 1U); CHECK(ModbusDataReadWord( MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_PERFORMANCE_OFFSET + (uint32_t)tick, &performanceWords[tick]) == 1U); } CHECK(controlHeader[5] == (uint16_t)(168000000UL & 0xFFFFUL)); CHECK(controlHeader[6] == (uint16_t)(168000000UL >> 16U)); CHECK(controlHeader[7] == PLSR_MODBUS_PERFORMANCE_VERSION); CHECK(controlHeader[3] == (uint16_t)PLSR_MODBUS_WINDOW_WORDS); for (tick = 0; tick < 8; tick++) { CHECK(performanceWords[tick] == 0U); } for (tick = 0; tick < (int)PLSR_MODBUS_STAGE_PERFORMANCE_WORDS; tick++) { CHECK(ModbusDataReadWord( MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_STAGE_PERFORMANCE_OFFSET + (uint32_t)tick, &stagePerformanceWords[tick]) == 1U); CHECK(stagePerformanceWords[tick] == 0U); } for (tick = 0; tick < (int)PLSR_MODBUS_USB_DIAGNOSTICS_WORDS; tick++) { CHECK(ModbusDataReadWord( MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_USB_DIAGNOSTICS_OFFSET + (uint32_t)tick, &usbDiagnosticsWords[tick]) == 1U); } generationBegin = (uint32_t)usbDiagnosticsWords[0] | ((uint32_t)usbDiagnosticsWords[1] << 16U); generationEnd = (uint32_t)usbDiagnosticsWords[20] | ((uint32_t)usbDiagnosticsWords[21] << 16U); CHECK(generationBegin == generationEnd); CHECK((generationBegin & 1UL) == 0UL); CHECK(usbDiagnosticsWords[2] == PLSR_MODBUS_USB_DIAGNOSTICS_VERSION); for (tick = 3; tick < 20; tick++) { CHECK(usbDiagnosticsWords[tick] == 0U); } s0Words[0] = 1U; s0Words[10] = 2000U; s0Words[12] = (uint16_t)(50000UL & 0xFFFFUL); s0Words[13] = (uint16_t)(50000UL >> 16U); CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, s0Base, s0Words, 20UL) == 1U); CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, s1Base, s1Words, 4UL) == 1U); callRequest[0] = 1U; callRequest[2] = PLSR_DEVICE_D; callRequest[3] = (uint16_t)s0Base; callRequest[5] = PLSR_DEVICE_D; callRequest[6] = (uint16_t)s1Base; callRequest[8] = PLSR_OPERAND_CONSTANT; callRequest[10] = 1U; callRequest[12] = 0U; callRequest[13] = PLSR_OUTPUT_PULSE_DIR; callRequest[14] = PLSR_MODBUS_CALL_COMMIT; CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_CALL_REQUEST_OFFSET, callRequest, 16UL) == 1U); PlsrModbusControlPoll(); CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET, &callResponse[0]) == 1U); for (tick = 1; tick < 12; tick++) { CHECK(ModbusDataReadWord( MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET + (uint32_t)tick, &callResponse[tick]) == 1U); } CHECK(callResponse[0] == 1U); CHECK(callResponse[2] == PLSR_MODBUS_CALL_COMMIT); CHECK(callResponse[3] == PLSR_RESULT_OK); CHECK(callResponse[11] == 1U); /* Any S0 edit after COMMIT invalidates START until a new COMMIT. */ pulseWords[0] = (uint16_t)(50001UL & 0xFFFFUL); pulseWords[1] = (uint16_t)(50001UL >> 16U); CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, s0Base + 12UL, pulseWords, 2UL) == 1U); callRequest[0] = 2U; callRequest[14] = PLSR_MODBUS_CALL_START; CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_CALL_REQUEST_OFFSET, callRequest, 16UL) == 1U); PlsrModbusControlPoll(); CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET + 3UL, &callResponse[3]) == 1U); CHECK(callResponse[3] == PLSR_RESULT_BUSY); pulseWords[0] = (uint16_t)(50000UL & 0xFFFFUL); pulseWords[1] = (uint16_t)(50000UL >> 16U); CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, s0Base + 12UL, pulseWords, 2UL) == 1U); callRequest[0] = 3U; callRequest[14] = PLSR_MODBUS_CALL_COMMIT; CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_CALL_REQUEST_OFFSET, callRequest, 16UL) == 1U); PlsrModbusControlPoll(); CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET + 3UL, &callResponse[3]) == 1U); CHECK(callResponse[3] == PLSR_RESULT_OK); callRequest[0] = 4U; callRequest[14] = PLSR_MODBUS_CALL_START; CHECK(ModbusDataWriteWords(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_CALL_REQUEST_OFFSET, callRequest, 16UL) == 1U); PlsrModbusControlPoll(); CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_CALL_RESPONSE_OFFSET + 3UL, &callResponse[3]) == 1U); CHECK(callResponse[3] == PLSR_RESULT_QUEUED); for (tick = 0; tick < 10; tick++) { PlsrProcess(); } PlsrModbusControlPoll(); for (tick = 0; tick < 48; tick++) { CHECK(ModbusDataReadWord( MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_AXIS_STATUS_OFFSET + (uint32_t)tick, &axisStatus[tick]) == 1U); } generationBegin = (uint32_t)axisStatus[0] | ((uint32_t)axisStatus[1] << 16U); generationEnd = (uint32_t)axisStatus[46] | ((uint32_t)axisStatus[47] << 16U); CHECK(generationBegin == generationEnd); CHECK((generationBegin & 1UL) == 0UL); CHECK((axisStatus[2] == PLSR_STATE_ACCEL) || (axisStatus[2] == PLSR_STATE_RUN)); CHECK(axisStatus[10] == 4U); CHECK(axisStatus[11] == 0U); CHECK(axisStatus[8] == PLSR_RESULT_OK); CHECK(axisStatus[37] == 1U); commandRequest[0] = 10U; commandRequest[2] = PLSR_CMD_PAUSE; commandRequest[3] = 0U; CHECK(ModbusDataWriteWords( MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_COMMAND_REQUEST_OFFSET, commandRequest, 8UL) == 1U); PlsrModbusControlPoll(); CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_COMMAND_RESPONSE_OFFSET + 4UL, &commandResponse[4]) == 1U); CHECK(commandResponse[4] == PLSR_RESULT_QUEUED); for (tick = 0; tick < 400; tick++) { PlsrProcess(); CHECK(PlsrGetStatus(0U, &coreStatus) == PLSR_RESULT_OK); if (coreStatus.state == PLSR_STATE_PAUSED) { break; } } CHECK(coreStatus.state == PLSR_STATE_PAUSED); CHECK(coreStatus.currentFrequencyHz == 0UL); CHECK(PlsrHwTestGetPwmEnabled(0U) == 0U); pausedPulses = coreStatus.taskPulses; PlsrModbusControlPoll(); CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_AXIS_STATUS_OFFSET + 2UL, &axisStatus[2]) == 1U); CHECK(axisStatus[2] == PLSR_STATE_PAUSED); /* Polling an unchanged request sequence must not execute PAUSE twice. */ PlsrModbusControlPoll(); commandRequest[0] = 11U; commandRequest[2] = PLSR_CMD_RESUME; CHECK(ModbusDataWriteWords( MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_COMMAND_REQUEST_OFFSET, commandRequest, 8UL) == 1U); PlsrModbusControlPoll(); PlsrProcess(); CHECK(PlsrGetStatus(0U, &coreStatus) == PLSR_RESULT_OK); CHECK(coreStatus.currentFrequencyHz > 0UL); CHECK(PlsrHwTestGetPwmEnabled(0U) == 1U); for (tick = 0; tick < 10; tick++) { PlsrHwTestTriggerUpdate(0U); } PlsrProcess(); CHECK(PlsrGetStatus(0U, &coreStatus) == PLSR_RESULT_OK); CHECK(coreStatus.taskPulses == pausedPulses + 10); PlsrModbusControlPoll(); CHECK(ModbusDataReadWord(MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_AXIS_STATUS_OFFSET + 2UL, &axisStatus[2]) == 1U); CHECK((axisStatus[2] == PLSR_STATE_ACCEL) || (axisStatus[2] == PLSR_STATE_RUN)); commandRequest[0] = 12U; commandRequest[2] = PLSR_CMD_STOP_DECEL; CHECK(ModbusDataWriteWords( MODBUS_DATA_DEVICE_D, controlBase + PLSR_MODBUS_COMMAND_REQUEST_OFFSET, commandRequest, 8UL) == 1U); PlsrModbusControlPoll(); for (tick = 0; tick < 400; tick++) { PlsrProcess(); CHECK(PlsrGetStatus(0U, &coreStatus) == PLSR_RESULT_OK); if (coreStatus.state == PLSR_STATE_STOPPED) { break; } } CHECK(coreStatus.state == PLSR_STATE_STOPPED); CHECK(coreStatus.currentFrequencyHz == 0UL); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE); } static void TestStopStopsHardware(void) { TEST_MEMORY memory; PLSR_CALL call; PLSR_COMMAND command; PLSR_STATUS status; int ticks; TestResetEnvironment(); (void)memset(&memory, 0, sizeof(memory)); TestWriteDword(&memory, PLSR_DEVICE_D, TEST_S0_BASE, 1); TestSetSegment(&memory, 1U, 1000U, 10000); call = TestMakeCall(&memory); call.sequence = 20UL; CHECK(PlsrPostCall(&call) == PLSR_RESULT_QUEUED); PlsrProcess(); for (ticks = 0; ticks < 10; ticks++) { PlsrProcess(); } CHECK(PlsrHwIsPulseActive(0U) == 1U); /* STOP_IMMEDIATE:硬件立即停止。 */ command.sequence = 21UL; command.axis = 0U; command.opcode = PLSR_CMD_STOP_IMMEDIATE; command.argument = 0; CHECK(PlsrPostCommand(&command) == PLSR_RESULT_QUEUED); PlsrProcess(); CHECK(PlsrHwIsPulseActive(0U) == 0U); CHECK(PlsrHwGetState(0U) == PLSR_HW_STATE_IDLE); CHECK(PlsrPostEvent(0U, PLSR_EVENT_STOP_IMMEDIATE_DONE) == PLSR_RESULT_OK); PlsrProcess(); status = TestGetStatus(); CHECK(status.state == PLSR_STATE_STOPPED); } int main(void) { TestMapping(); TestDirDelaySequence(); TestDirectionBatch(); TestHardwareCounterLeases(); TestCwCcwSequence(); TestFastRefreshControlTick(); TestDynamicFrequencyRetarget(); TestZeroFrequencyWaits(); TestPulseCounting(); TestAbPhaseAndCounting(); TestTwoAbAxesIndependent(); TestDualAbHardwareCountersAndResume(); TestDualAbSimultaneousFastGate(); TestAbFrequencyLimits(); TestStopAndInvalidArgs(); TestEndToEndTwoSegments(); TestEndToEndAbSegment(); TestPositionOnImmediateStop(); TestAbsolutePositionAccounting(); TestEquivalentRemainderAccounting(); TestEquivalentCompatibleError(); TestSoftLimitAndSegmentEvent(); TestHardLimitAndEmergencyLatch(); TestProductionSelfTestStartsAb(); TestEquivalentSelfTest(); TestProtectionSelfTest(); TestFourAxisSelfTest(); TestBacklashSelfTest(); TestDirectionLogicSelfTest(); TestCwCcwSelfTest(); TestFastRefreshSelfTest(); TestDynamicFrequencySelfTest(); TestDynamicFrequencySchedule(); TestModbusDataSelfTest(); TestModbusControlProtocol(); TestStopStopsHardware(); if (TestFailures != 0) { (void)printf("FAIL: %d of %d PLSR HAL checks failed\n", TestFailures, TestChecks); return 1; } (void)printf("PASS: %d PLSR HAL checks\n", TestChecks); return 0; }