diff --git a/Core/Src/main.c b/Core/Src/main.c
index 5fb8f1f..a3b15d7 100644
--- a/Core/Src/main.c
+++ b/Core/Src/main.c
@@ -24,6 +24,7 @@
/* USER CODE BEGIN Includes */
#include "ucos_ii.h"
#include "modbus_rtu_slave.h"
+#include "plc_device.h"
#include "stdio.h"
/* USER CODE END Includes */
@@ -156,7 +157,14 @@ int main(void)
MX_DMA_Init();
MX_USB_DEVICE_Init();
MX_USART1_UART_Init();
- (void)BackupSramInit();
+ if (BackupSramInit() != HAL_OK)
+ {
+ Error_Handler();
+ }
+ if (PlcDeviceInit() != PLC_DEVICE_OK)
+ {
+ Error_Handler();
+ }
/* USER CODE BEGIN 2 */
INT8U osError;
diff --git a/EWARM/Modbus.ewp b/EWARM/Modbus.ewp
index 5c4ccb6..33fbc95 100644
--- a/EWARM/Modbus.ewp
+++ b/EWARM/Modbus.ewp
@@ -360,6 +360,7 @@
$PROJ_DIR$/../Drivers/CMSIS/Device/ST/STM32F4xx/Include
$PROJ_DIR$/../Drivers/CMSIS/Include
$PROJ_DIR$\..\Modbus\Inc
+ $PROJ_DIR$\..\PLSR\Inc
$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\Config
$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uCOS-II\Source
$PROJ_DIR$\..\Middlewares\Third_Party\Micrium\uCOS-II\Ports\ARM-Cortex-M4\IAR
@@ -1260,6 +1261,24 @@
+
+ PLSR
+
+ $PROJ_DIR$\..\PLSR\Inc\plsr_address_map.h
+
+
+ $PROJ_DIR$\..\PLSR\Inc\plsr_persistence.h
+
+
+ $PROJ_DIR$\..\PLSR\Inc\plc_device.h
+
+
+ $PROJ_DIR$\..\PLSR\Src\plsr_persistence.c
+
+
+ $PROJ_DIR$\..\PLSR\Src\plc_device.c
+
+
Modbus
diff --git a/Modbus/Src/modbus_rtu_slave.c b/Modbus/Src/modbus_rtu_slave.c
index bf00866..b4756f1 100644
--- a/Modbus/Src/modbus_rtu_slave.c
+++ b/Modbus/Src/modbus_rtu_slave.c
@@ -56,7 +56,7 @@ static volatile MODBUS_BACKUP_DATA *ModbusBackupData =
* 10000 个保持寄存器占用 20000 字节;10000 个线圈按位存储,
* 占用 1250 字节
*/
-static uint16_t ModbusHoldingRegisters[40000];
+static uint16_t ModbusHoldingRegisters[20000];
#pragma location = ".ccmram"
#pragma data_alignment = 4
diff --git a/PLSR/Inc/plc_device.h b/PLSR/Inc/plc_device.h
new file mode 100644
index 0000000..b3561c8
--- /dev/null
+++ b/PLSR/Inc/plc_device.h
@@ -0,0 +1,62 @@
+#ifndef PLC_DEVICE_H
+#define PLC_DEVICE_H
+
+#include "plsr_address_map.h"
+#include "plsr_persistence.h"
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum
+{
+ PLC_DEVICE_OK = 0,
+ PLC_DEVICE_INVALID_ADDRESS,
+ PLC_DEVICE_INVALID_ARGUMENT,
+ PLC_DEVICE_NULL_POINTER,
+ PLC_DEVICE_READ_ONLY,
+ PLC_DEVICE_PERSISTENCE_ERROR,
+ PLC_DEVICE_NOT_IMPLEMENTED
+} PLC_DEVICE_RESULT;
+
+PLC_DEVICE_RESULT PlcDeviceInit(void);
+
+PLC_DEVICE_RESULT PlcDeviceReadHsd(uint16_t address, int32_t *value);
+PLC_DEVICE_RESULT PlcDeviceWriteHsdConfig(uint16_t address, int32_t value);
+PLC_DEVICE_RESULT PlcDevicePublishHsdRuntime(uint16_t address, int32_t value);
+PLC_DEVICE_RESULT PlcDeviceReadHsdPair(uint16_t lowAddress, int64_t *value);
+PLC_DEVICE_RESULT PlcDevicePublishHsdPair(uint16_t lowAddress, int64_t value);
+PLC_DEVICE_RESULT PlcDeviceCheckpointHsd(void);
+
+PLC_DEVICE_RESULT PlcDeviceReadSfd(uint16_t address, int32_t *value);
+PLC_DEVICE_RESULT PlcDeviceWriteSfd(uint16_t address, int32_t value);
+PLC_DEVICE_RESULT PlcDeviceLoadSfd(void);
+PLC_DEVICE_RESULT PlcDeviceSaveSfd(void);
+
+PLC_DEVICE_RESULT PlcDeviceReadSm(uint16_t address, uint8_t *state);
+PLC_DEVICE_RESULT PlcDeviceWriteSm(uint16_t address, uint8_t state);
+PLC_DEVICE_RESULT PlcDevicePublishSm(uint8_t axis,
+ uint8_t pulseActive,
+ uint8_t direction);
+
+PLC_DEVICE_RESULT PlcDeviceReadSd(uint16_t address, int32_t *value);
+PLC_DEVICE_RESULT PlcDeviceWriteSd(uint16_t address, int32_t value);
+PLC_DEVICE_RESULT PlcDevicePublishSd(uint8_t axis,
+ uint8_t item,
+ int32_t value);
+
+PLC_DEVICE_RESULT PlcDeviceGetEventAddress(uint8_t axis,
+ uint16_t segmentNumber,
+ uint16_t *eventAddress);
+
+uint8_t PlcDeviceIsHsdDirty(void);
+uint8_t PlcDeviceIsSfdDirty(void);
+PLSR_PERSISTENCE_RESULT PlcDeviceGetLastHsdLoadResult(void);
+PLSR_PERSISTENCE_RESULT PlcDeviceGetLastSfdLoadResult(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PLC_DEVICE_H */
diff --git a/PLSR/Inc/plsr_address_map.h b/PLSR/Inc/plsr_address_map.h
new file mode 100644
index 0000000..5580be3
--- /dev/null
+++ b/PLSR/Inc/plsr_address_map.h
@@ -0,0 +1,40 @@
+#ifndef PLSR_ADDRESS_MAP_H
+#define PLSR_ADDRESS_MAP_H
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PLSR_AXIS_COUNT (4U)
+
+#define PLSR_HSD_RUNTIME_START (0U)
+#define PLSR_HSD_RUNTIME_COUNT (16U)
+#define PLSR_HSD_RUNTIME_AXIS_COUNT (4U)
+
+#define PLSR_HSD_CONFIG_START (460U)
+#define PLSR_HSD_CONFIG_COUNT (80U)
+
+#define PLSR_SFD_CONFIG_START (900U)
+#define PLSR_SFD_CONFIG_COUNT (520U)
+
+#define PLSR_SD_AXIS_ITEM_COUNT (12U)
+#define PLSR_EVENT_AXIS_ITEM_COUNT (100U)
+
+typedef struct
+{
+ uint16_t hsdRuntimeBase;
+ uint16_t smPulseActiveAddress;
+ uint16_t smDirectionAddress;
+ uint16_t sdRuntimeBase;
+ uint16_t eventBase;
+} PLSR_AXIS_ADDRESS_MAP;
+
+extern const PLSR_AXIS_ADDRESS_MAP PlsrAxisAddressMap[PLSR_AXIS_COUNT];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PLSR_ADDRESS_MAP_H */
diff --git a/PLSR/Inc/plsr_persistence.h b/PLSR/Inc/plsr_persistence.h
new file mode 100644
index 0000000..e2f253a
--- /dev/null
+++ b/PLSR/Inc/plsr_persistence.h
@@ -0,0 +1,47 @@
+#ifndef PLSR_PERSISTENCE_H
+#define PLSR_PERSISTENCE_H
+
+#include "plsr_address_map.h"
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct
+{
+ int32_t runtime[PLSR_HSD_RUNTIME_COUNT];
+ int32_t config[PLSR_HSD_CONFIG_COUNT];
+} PLSR_HSD_DATA;
+
+typedef struct
+{
+ int32_t config[PLSR_SFD_CONFIG_COUNT];
+} PLSR_SFD_DATA;
+
+typedef enum
+{
+ PLSR_PERSISTENCE_OK = 0,
+ PLSR_PERSISTENCE_DEFAULTED,
+ PLSR_PERSISTENCE_INVALID_ARGUMENT,
+ PLSR_PERSISTENCE_VERIFY_FAILED,
+ PLSR_PERSISTENCE_NOT_IMPLEMENTED
+} PLSR_PERSISTENCE_RESULT;
+
+PLSR_PERSISTENCE_RESULT PlsrPersistenceLoadHsd(PLSR_HSD_DATA *data);
+PLSR_PERSISTENCE_RESULT PlsrPersistenceSaveHsd(const PLSR_HSD_DATA *data);
+void PlsrPersistenceResetHsd(void);
+
+PLSR_PERSISTENCE_RESULT PlsrPersistenceLoadSfd(PLSR_SFD_DATA *data);
+PLSR_PERSISTENCE_RESULT PlsrPersistenceSaveSfd(const PLSR_SFD_DATA *data);
+
+#ifdef PLSR_HOST_TEST
+void PlsrPersistenceTestResetStorage(void);
+void PlsrPersistenceTestCorruptNewestHsd(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PLSR_PERSISTENCE_H */
diff --git a/PLSR/Src/plc_device.c b/PLSR/Src/plc_device.c
new file mode 100644
index 0000000..4a756ca
--- /dev/null
+++ b/PLSR/Src/plc_device.c
@@ -0,0 +1,478 @@
+#include "plc_device.h"
+#include
+
+#ifndef PLSR_HOST_TEST
+#include "stm32f4xx.h"
+#endif
+
+#define PLC_SM_PULSE_ACTIVE_MASK (0x01U)
+#define PLC_SM_DIRECTION_MASK (0x02U)
+
+const PLSR_AXIS_ADDRESS_MAP PlsrAxisAddressMap[PLSR_AXIS_COUNT] =
+{
+ {0U, 1000U, 1001U, 1000U, 6000U},
+ {4U, 1020U, 1021U, 1020U, 6100U},
+ {8U, 1040U, 1041U, 1040U, 6200U},
+ {12U, 1060U, 1061U, 1060U, 6300U}
+};
+
+static PLSR_HSD_DATA PlcHsdData;
+static PLSR_SFD_DATA PlcSfdData;
+static int32_t PlcSdRuntime[PLSR_AXIS_COUNT][PLSR_SD_AXIS_ITEM_COUNT];
+static uint8_t PlcSmFlags[PLSR_AXIS_COUNT];
+static uint8_t PlcHsdDirty;
+static uint8_t PlcSfdDirty;
+static uint32_t PlcHsdChangeCounter;
+static PLSR_PERSISTENCE_RESULT PlcLastHsdLoadResult;
+static PLSR_PERSISTENCE_RESULT PlcLastSfdLoadResult;
+
+static uint32_t PlcDeviceEnterCritical(void)
+{
+#ifdef PLSR_HOST_TEST
+ return 0UL;
+#else
+ uint32_t interruptState = __get_PRIMASK();
+ __disable_irq();
+ __DMB();
+ return interruptState;
+#endif
+}
+
+static void PlcDeviceExitCritical(uint32_t interruptState)
+{
+#ifdef PLSR_HOST_TEST
+ (void)interruptState;
+#else
+ __DMB();
+ if (interruptState == 0UL)
+ {
+ __enable_irq();
+ }
+#endif
+}
+
+static int32_t *PlcDeviceResolveHsd(uint16_t address)
+{
+ if (address < PLSR_HSD_RUNTIME_START + PLSR_HSD_RUNTIME_COUNT)
+ {
+ return &PlcHsdData.runtime[address - PLSR_HSD_RUNTIME_START];
+ }
+ if ((address >= PLSR_HSD_CONFIG_START)
+ && (address < PLSR_HSD_CONFIG_START + PLSR_HSD_CONFIG_COUNT))
+ {
+ return &PlcHsdData.config[address - PLSR_HSD_CONFIG_START];
+ }
+
+ return NULL;
+}
+
+static int32_t *PlcDeviceResolveSd(uint16_t address,
+ uint8_t *axis,
+ uint8_t *item)
+{
+ uint8_t axisIndex;
+ uint16_t base;
+
+ for (axisIndex = 0U; axisIndex < PLSR_AXIS_COUNT; axisIndex++)
+ {
+ base = PlsrAxisAddressMap[axisIndex].sdRuntimeBase;
+ if ((address >= base)
+ && (address < base + PLSR_SD_AXIS_ITEM_COUNT))
+ {
+ if (axis != NULL)
+ {
+ *axis = axisIndex;
+ }
+ if (item != NULL)
+ {
+ *item = (uint8_t)(address - base);
+ }
+ return &PlcSdRuntime[axisIndex][address - base];
+ }
+ }
+
+ return NULL;
+}
+
+static PLC_DEVICE_RESULT PlcDeviceResolveSm(uint16_t address,
+ uint8_t *axis,
+ uint8_t *mask)
+{
+ uint8_t axisIndex;
+
+ if ((axis == NULL) || (mask == NULL))
+ {
+ return PLC_DEVICE_NULL_POINTER;
+ }
+
+ for (axisIndex = 0U; axisIndex < PLSR_AXIS_COUNT; axisIndex++)
+ {
+ if (address == PlsrAxisAddressMap[axisIndex].smPulseActiveAddress)
+ {
+ *axis = axisIndex;
+ *mask = PLC_SM_PULSE_ACTIVE_MASK;
+ return PLC_DEVICE_OK;
+ }
+ if (address == PlsrAxisAddressMap[axisIndex].smDirectionAddress)
+ {
+ *axis = axisIndex;
+ *mask = PLC_SM_DIRECTION_MASK;
+ return PLC_DEVICE_OK;
+ }
+ }
+
+ return PLC_DEVICE_INVALID_ADDRESS;
+}
+
+PLC_DEVICE_RESULT PlcDeviceInit(void)
+{
+ (void)memset(&PlcHsdData, 0, sizeof(PlcHsdData));
+ (void)memset(&PlcSfdData, 0, sizeof(PlcSfdData));
+ (void)memset(PlcSdRuntime, 0, sizeof(PlcSdRuntime));
+ (void)memset(PlcSmFlags, 0, sizeof(PlcSmFlags));
+ PlcHsdChangeCounter = 0UL;
+
+ PlcLastHsdLoadResult = PlsrPersistenceLoadHsd(&PlcHsdData);
+ PlcHsdDirty = (PlcLastHsdLoadResult == PLSR_PERSISTENCE_DEFAULTED)
+ ? 1U
+ : 0U;
+ if ((PlcLastHsdLoadResult != PLSR_PERSISTENCE_OK)
+ && (PlcLastHsdLoadResult != PLSR_PERSISTENCE_DEFAULTED))
+ {
+ return PLC_DEVICE_PERSISTENCE_ERROR;
+ }
+
+ PlcLastSfdLoadResult = PlsrPersistenceLoadSfd(&PlcSfdData);
+ PlcSfdDirty = (PlcLastSfdLoadResult == PLSR_PERSISTENCE_OK) ? 0U : 1U;
+ if ((PlcLastSfdLoadResult != PLSR_PERSISTENCE_OK)
+ && (PlcLastSfdLoadResult != PLSR_PERSISTENCE_NOT_IMPLEMENTED))
+ {
+ return PLC_DEVICE_PERSISTENCE_ERROR;
+ }
+
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceReadHsd(uint16_t address, int32_t *value)
+{
+ int32_t *source;
+
+ if (value == NULL)
+ {
+ return PLC_DEVICE_NULL_POINTER;
+ }
+ source = PlcDeviceResolveHsd(address);
+ if (source == NULL)
+ {
+ return PLC_DEVICE_INVALID_ADDRESS;
+ }
+
+ *value = *source;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceWriteHsdConfig(uint16_t address, int32_t value)
+{
+ if ((address < PLSR_HSD_CONFIG_START)
+ || (address >= PLSR_HSD_CONFIG_START + PLSR_HSD_CONFIG_COUNT))
+ {
+ return (address < PLSR_HSD_RUNTIME_START
+ + PLSR_HSD_RUNTIME_COUNT)
+ ? PLC_DEVICE_READ_ONLY
+ : PLC_DEVICE_INVALID_ADDRESS;
+ }
+
+ PlcHsdData.config[address - PLSR_HSD_CONFIG_START] = value;
+ PlcHsdChangeCounter++;
+ PlcHsdDirty = 1U;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDevicePublishHsdRuntime(uint16_t address, int32_t value)
+{
+ if (address >= PLSR_HSD_RUNTIME_START + PLSR_HSD_RUNTIME_COUNT)
+ {
+ return PLC_DEVICE_INVALID_ADDRESS;
+ }
+
+ PlcHsdData.runtime[address - PLSR_HSD_RUNTIME_START] = value;
+ PlcHsdChangeCounter++;
+ PlcHsdDirty = 1U;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceReadHsdPair(uint16_t lowAddress, int64_t *value)
+{
+ uint64_t combined;
+ uint32_t interruptState;
+ uint16_t offset;
+
+ if (value == NULL)
+ {
+ return PLC_DEVICE_NULL_POINTER;
+ }
+ if ((lowAddress >= PLSR_HSD_RUNTIME_COUNT)
+ || ((lowAddress & 1U) != 0U)
+ || ((uint16_t)(lowAddress + 1U) >= PLSR_HSD_RUNTIME_COUNT))
+ {
+ return PLC_DEVICE_INVALID_ADDRESS;
+ }
+
+ offset = (uint16_t)(lowAddress - PLSR_HSD_RUNTIME_START);
+ interruptState = PlcDeviceEnterCritical();
+ combined = (uint64_t)(uint32_t)PlcHsdData.runtime[offset];
+ combined |= ((uint64_t)(uint32_t)PlcHsdData.runtime[offset + 1U]) << 32U;
+ PlcDeviceExitCritical(interruptState);
+ *value = (int64_t)combined;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDevicePublishHsdPair(uint16_t lowAddress, int64_t value)
+{
+ uint64_t rawValue;
+ uint32_t interruptState;
+ uint16_t offset;
+
+ if ((lowAddress >= PLSR_HSD_RUNTIME_COUNT)
+ || ((lowAddress & 1U) != 0U)
+ || ((uint16_t)(lowAddress + 1U) >= PLSR_HSD_RUNTIME_COUNT))
+ {
+ return PLC_DEVICE_INVALID_ADDRESS;
+ }
+
+ rawValue = (uint64_t)value;
+ offset = (uint16_t)(lowAddress - PLSR_HSD_RUNTIME_START);
+ interruptState = PlcDeviceEnterCritical();
+ PlcHsdData.runtime[offset] = (int32_t)(uint32_t)rawValue;
+ PlcHsdData.runtime[offset + 1U] = (int32_t)(uint32_t)(rawValue >> 32U);
+ PlcHsdChangeCounter++;
+ PlcHsdDirty = 1U;
+ PlcDeviceExitCritical(interruptState);
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceCheckpointHsd(void)
+{
+ PLSR_HSD_DATA snapshot;
+ PLSR_PERSISTENCE_RESULT result;
+ uint32_t snapshotCounter;
+ uint32_t interruptState;
+
+ if (PlcHsdDirty == 0U)
+ {
+ return PLC_DEVICE_OK;
+ }
+
+ interruptState = PlcDeviceEnterCritical();
+ snapshot = PlcHsdData;
+ snapshotCounter = PlcHsdChangeCounter;
+ PlcDeviceExitCritical(interruptState);
+
+ result = PlsrPersistenceSaveHsd(&snapshot);
+ if (result != PLSR_PERSISTENCE_OK)
+ {
+ return PLC_DEVICE_PERSISTENCE_ERROR;
+ }
+
+ interruptState = PlcDeviceEnterCritical();
+ if (PlcHsdChangeCounter == snapshotCounter)
+ {
+ PlcHsdDirty = 0U;
+ }
+ PlcDeviceExitCritical(interruptState);
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceReadSfd(uint16_t address, int32_t *value)
+{
+ if (value == NULL)
+ {
+ return PLC_DEVICE_NULL_POINTER;
+ }
+ if ((address < PLSR_SFD_CONFIG_START)
+ || (address >= PLSR_SFD_CONFIG_START + PLSR_SFD_CONFIG_COUNT))
+ {
+ return PLC_DEVICE_INVALID_ADDRESS;
+ }
+
+ *value = PlcSfdData.config[address - PLSR_SFD_CONFIG_START];
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceWriteSfd(uint16_t address, int32_t value)
+{
+ if ((address < PLSR_SFD_CONFIG_START)
+ || (address >= PLSR_SFD_CONFIG_START + PLSR_SFD_CONFIG_COUNT))
+ {
+ return PLC_DEVICE_INVALID_ADDRESS;
+ }
+
+ PlcSfdData.config[address - PLSR_SFD_CONFIG_START] = value;
+ PlcSfdDirty = 1U;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceLoadSfd(void)
+{
+ PlcLastSfdLoadResult = PlsrPersistenceLoadSfd(&PlcSfdData);
+ if (PlcLastSfdLoadResult == PLSR_PERSISTENCE_NOT_IMPLEMENTED)
+ {
+ return PLC_DEVICE_NOT_IMPLEMENTED;
+ }
+ if (PlcLastSfdLoadResult != PLSR_PERSISTENCE_OK)
+ {
+ return PLC_DEVICE_PERSISTENCE_ERROR;
+ }
+
+ PlcSfdDirty = 0U;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceSaveSfd(void)
+{
+ PLSR_PERSISTENCE_RESULT result = PlsrPersistenceSaveSfd(&PlcSfdData);
+
+ if (result == PLSR_PERSISTENCE_NOT_IMPLEMENTED)
+ {
+ return PLC_DEVICE_NOT_IMPLEMENTED;
+ }
+ if (result != PLSR_PERSISTENCE_OK)
+ {
+ return PLC_DEVICE_PERSISTENCE_ERROR;
+ }
+
+ PlcSfdDirty = 0U;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceReadSm(uint16_t address, uint8_t *state)
+{
+ uint8_t axis;
+ uint8_t mask;
+ PLC_DEVICE_RESULT result;
+
+ if (state == NULL)
+ {
+ return PLC_DEVICE_NULL_POINTER;
+ }
+ result = PlcDeviceResolveSm(address, &axis, &mask);
+ if (result != PLC_DEVICE_OK)
+ {
+ return result;
+ }
+
+ *state = ((PlcSmFlags[axis] & mask) != 0U) ? 1U : 0U;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceWriteSm(uint16_t address, uint8_t state)
+{
+ uint8_t axis;
+ uint8_t mask;
+ PLC_DEVICE_RESULT result;
+
+ (void)state;
+ result = PlcDeviceResolveSm(address, &axis, &mask);
+ return (result == PLC_DEVICE_OK) ? PLC_DEVICE_READ_ONLY : result;
+}
+
+PLC_DEVICE_RESULT PlcDevicePublishSm(uint8_t axis,
+ uint8_t pulseActive,
+ uint8_t direction)
+{
+ uint8_t flags = 0U;
+
+ if (axis >= PLSR_AXIS_COUNT)
+ {
+ return PLC_DEVICE_INVALID_ARGUMENT;
+ }
+ if (pulseActive != 0U)
+ {
+ flags |= PLC_SM_PULSE_ACTIVE_MASK;
+ }
+ if (direction != 0U)
+ {
+ flags |= PLC_SM_DIRECTION_MASK;
+ }
+
+ PlcSmFlags[axis] = flags;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceReadSd(uint16_t address, int32_t *value)
+{
+ int32_t *source;
+
+ if (value == NULL)
+ {
+ return PLC_DEVICE_NULL_POINTER;
+ }
+ source = PlcDeviceResolveSd(address, NULL, NULL);
+ if (source == NULL)
+ {
+ return PLC_DEVICE_INVALID_ADDRESS;
+ }
+
+ *value = *source;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceWriteSd(uint16_t address, int32_t value)
+{
+ (void)value;
+ return (PlcDeviceResolveSd(address, NULL, NULL) != NULL)
+ ? PLC_DEVICE_READ_ONLY
+ : PLC_DEVICE_INVALID_ADDRESS;
+}
+
+PLC_DEVICE_RESULT PlcDevicePublishSd(uint8_t axis,
+ uint8_t item,
+ int32_t value)
+{
+ if ((axis >= PLSR_AXIS_COUNT) || (item >= PLSR_SD_AXIS_ITEM_COUNT))
+ {
+ return PLC_DEVICE_INVALID_ARGUMENT;
+ }
+
+ PlcSdRuntime[axis][item] = value;
+ return PLC_DEVICE_OK;
+}
+
+PLC_DEVICE_RESULT PlcDeviceGetEventAddress(uint8_t axis,
+ uint16_t segmentNumber,
+ uint16_t *eventAddress)
+{
+ if (eventAddress == NULL)
+ {
+ return PLC_DEVICE_NULL_POINTER;
+ }
+ if ((axis >= PLSR_AXIS_COUNT) || (segmentNumber == 0U)
+ || (segmentNumber > PLSR_EVENT_AXIS_ITEM_COUNT))
+ {
+ return PLC_DEVICE_INVALID_ARGUMENT;
+ }
+
+ *eventAddress = (uint16_t)(PlsrAxisAddressMap[axis].eventBase
+ + segmentNumber - 1U);
+ return PLC_DEVICE_OK;
+}
+
+uint8_t PlcDeviceIsHsdDirty(void)
+{
+ return PlcHsdDirty;
+}
+
+uint8_t PlcDeviceIsSfdDirty(void)
+{
+ return PlcSfdDirty;
+}
+
+PLSR_PERSISTENCE_RESULT PlcDeviceGetLastHsdLoadResult(void)
+{
+ return PlcLastHsdLoadResult;
+}
+
+PLSR_PERSISTENCE_RESULT PlcDeviceGetLastSfdLoadResult(void)
+{
+ return PlcLastSfdLoadResult;
+}
diff --git a/PLSR/Src/plsr_persistence.c b/PLSR/Src/plsr_persistence.c
new file mode 100644
index 0000000..9375cb5
--- /dev/null
+++ b/PLSR/Src/plsr_persistence.c
@@ -0,0 +1,316 @@
+#include "plsr_persistence.h"
+#include
+#include
+
+#define PLSR_HSD_BACKUP_MAGIC (0x504C4853UL)
+#define PLSR_HSD_BACKUP_VERSION (1U)
+#define PLSR_BACKUP_SLOT_A_OFFSET (0x0100UL)
+#define PLSR_BACKUP_SLOT_STRIDE (0x0200UL)
+
+typedef struct
+{
+ uint32_t magic;
+ uint16_t version;
+ uint16_t payloadLength;
+ uint32_t generation;
+ PLSR_HSD_DATA data;
+ uint32_t crc32;
+} PLSR_HSD_BACKUP_RECORD;
+
+#ifdef PLSR_HOST_TEST
+static PLSR_HSD_BACKUP_RECORD PlsrHostBackupSlots[2];
+#else
+#include "stm32f4xx.h"
+#endif
+
+static uint32_t PlsrPersistenceCrc32(const volatile uint8_t *data,
+ uint32_t length)
+{
+ uint32_t crc = 0xFFFFFFFFUL;
+ uint32_t index;
+ uint8_t bit;
+
+ for (index = 0U; index < length; index++)
+ {
+ crc ^= data[index];
+ for (bit = 0U; bit < 8U; bit++)
+ {
+ if ((crc & 1UL) != 0UL)
+ {
+ crc = (crc >> 1U) ^ 0xEDB88320UL;
+ }
+ else
+ {
+ crc >>= 1U;
+ }
+ }
+ }
+
+ return ~crc;
+}
+
+static volatile PLSR_HSD_BACKUP_RECORD *PlsrPersistenceGetHsdSlot(
+ uint8_t slot)
+{
+#ifdef PLSR_HOST_TEST
+ return &PlsrHostBackupSlots[slot];
+#else
+ uint32_t offset = PLSR_BACKUP_SLOT_A_OFFSET
+ + (uint32_t)slot * PLSR_BACKUP_SLOT_STRIDE;
+ return (volatile PLSR_HSD_BACKUP_RECORD *)(BKPSRAM_BASE + offset);
+#endif
+}
+
+static uint8_t PlsrPersistenceHsdRecordIsValid(
+ const volatile PLSR_HSD_BACKUP_RECORD *record)
+{
+ uint32_t expectedCrc;
+
+ if ((record->magic != PLSR_HSD_BACKUP_MAGIC)
+ || (record->version != PLSR_HSD_BACKUP_VERSION)
+ || (record->payloadLength != sizeof(PLSR_HSD_DATA)))
+ {
+ return 0U;
+ }
+
+ expectedCrc = PlsrPersistenceCrc32(
+ (const volatile uint8_t *)record,
+ (uint32_t)offsetof(PLSR_HSD_BACKUP_RECORD, crc32));
+
+ return (expectedCrc == record->crc32) ? 1U : 0U;
+}
+
+static uint8_t PlsrPersistenceGenerationIsNewer(uint32_t left,
+ uint32_t right)
+{
+ return (((int32_t)(left - right)) > 0) ? 1U : 0U;
+}
+
+static void PlsrPersistenceCopyHsdFromVolatile(
+ PLSR_HSD_DATA *destination,
+ const volatile PLSR_HSD_DATA *source)
+{
+ uint16_t index;
+
+ for (index = 0U; index < PLSR_HSD_RUNTIME_COUNT; index++)
+ {
+ destination->runtime[index] = source->runtime[index];
+ }
+ for (index = 0U; index < PLSR_HSD_CONFIG_COUNT; index++)
+ {
+ destination->config[index] = source->config[index];
+ }
+}
+
+static void PlsrPersistenceWriteRecord(
+ volatile PLSR_HSD_BACKUP_RECORD *destination,
+ const PLSR_HSD_BACKUP_RECORD *source)
+{
+ const uint32_t *sourceWords = (const uint32_t *)source;
+ volatile uint32_t *destinationWords = (volatile uint32_t *)destination;
+ uint32_t wordCount = (uint32_t)(sizeof(PLSR_HSD_BACKUP_RECORD)
+ / sizeof(uint32_t));
+ uint32_t index;
+
+ destination->magic = 0UL;
+#ifndef PLSR_HOST_TEST
+ __DMB();
+#endif
+
+ for (index = 1U; index < wordCount; index++)
+ {
+ destinationWords[index] = sourceWords[index];
+ }
+
+#ifndef PLSR_HOST_TEST
+ __DMB();
+#endif
+ destination->magic = PLSR_HSD_BACKUP_MAGIC;
+#ifndef PLSR_HOST_TEST
+ __DMB();
+#endif
+}
+
+PLSR_PERSISTENCE_RESULT PlsrPersistenceLoadHsd(PLSR_HSD_DATA *data)
+{
+ volatile PLSR_HSD_BACKUP_RECORD *slotA;
+ volatile PLSR_HSD_BACKUP_RECORD *slotB;
+ const volatile PLSR_HSD_BACKUP_RECORD *selected;
+ uint32_t generationA;
+ uint32_t generationB;
+ uint8_t validA;
+ uint8_t validB;
+
+ if (data == NULL)
+ {
+ return PLSR_PERSISTENCE_INVALID_ARGUMENT;
+ }
+
+ slotA = PlsrPersistenceGetHsdSlot(0U);
+ slotB = PlsrPersistenceGetHsdSlot(1U);
+ validA = PlsrPersistenceHsdRecordIsValid(slotA);
+ validB = PlsrPersistenceHsdRecordIsValid(slotB);
+
+ if ((validA == 0U) && (validB == 0U))
+ {
+ (void)memset(data, 0, sizeof(*data));
+ return PLSR_PERSISTENCE_DEFAULTED;
+ }
+
+ if ((validA != 0U) && (validB != 0U))
+ {
+ generationA = slotA->generation;
+ generationB = slotB->generation;
+ selected = (PlsrPersistenceGenerationIsNewer(generationB,
+ generationA)
+ != 0U)
+ ? slotB
+ : slotA;
+ }
+ else
+ {
+ selected = (validA != 0U) ? slotA : slotB;
+ }
+
+ PlsrPersistenceCopyHsdFromVolatile(data, &selected->data);
+ return PLSR_PERSISTENCE_OK;
+}
+
+PLSR_PERSISTENCE_RESULT PlsrPersistenceSaveHsd(const PLSR_HSD_DATA *data)
+{
+ volatile PLSR_HSD_BACKUP_RECORD *slotA;
+ volatile PLSR_HSD_BACKUP_RECORD *slotB;
+ volatile PLSR_HSD_BACKUP_RECORD *target;
+ PLSR_HSD_BACKUP_RECORD record;
+ uint32_t newestGeneration = 0UL;
+ uint32_t generationA;
+ uint32_t generationB;
+ uint8_t validA;
+ uint8_t validB;
+
+ if (data == NULL)
+ {
+ return PLSR_PERSISTENCE_INVALID_ARGUMENT;
+ }
+ if (sizeof(PLSR_HSD_BACKUP_RECORD) > PLSR_BACKUP_SLOT_STRIDE)
+ {
+ return PLSR_PERSISTENCE_VERIFY_FAILED;
+ }
+
+ slotA = PlsrPersistenceGetHsdSlot(0U);
+ slotB = PlsrPersistenceGetHsdSlot(1U);
+ validA = PlsrPersistenceHsdRecordIsValid(slotA);
+ validB = PlsrPersistenceHsdRecordIsValid(slotB);
+
+ if ((validA != 0U) && (validB != 0U))
+ {
+ generationA = slotA->generation;
+ generationB = slotB->generation;
+ if (PlsrPersistenceGenerationIsNewer(generationB,
+ generationA)
+ != 0U)
+ {
+ newestGeneration = generationB;
+ target = slotA;
+ }
+ else
+ {
+ newestGeneration = generationA;
+ target = slotB;
+ }
+ }
+ else if (validA != 0U)
+ {
+ newestGeneration = slotA->generation;
+ target = slotB;
+ }
+ else if (validB != 0U)
+ {
+ newestGeneration = slotB->generation;
+ target = slotA;
+ }
+ else
+ {
+ target = slotA;
+ }
+
+ (void)memset(&record, 0, sizeof(record));
+ record.magic = PLSR_HSD_BACKUP_MAGIC;
+ record.version = PLSR_HSD_BACKUP_VERSION;
+ record.payloadLength = (uint16_t)sizeof(PLSR_HSD_DATA);
+ record.generation = newestGeneration + 1UL;
+ record.data = *data;
+ record.crc32 = PlsrPersistenceCrc32(
+ (const volatile uint8_t *)&record,
+ (uint32_t)offsetof(PLSR_HSD_BACKUP_RECORD, crc32));
+
+ PlsrPersistenceWriteRecord(target, &record);
+ return (PlsrPersistenceHsdRecordIsValid(target) != 0U)
+ ? PLSR_PERSISTENCE_OK
+ : PLSR_PERSISTENCE_VERIFY_FAILED;
+}
+
+void PlsrPersistenceResetHsd(void)
+{
+ PlsrPersistenceGetHsdSlot(0U)->magic = 0UL;
+ PlsrPersistenceGetHsdSlot(1U)->magic = 0UL;
+#ifndef PLSR_HOST_TEST
+ __DMB();
+#endif
+}
+
+PLSR_PERSISTENCE_RESULT PlsrPersistenceLoadSfd(PLSR_SFD_DATA *data)
+{
+ if (data == NULL)
+ {
+ return PLSR_PERSISTENCE_INVALID_ARGUMENT;
+ }
+
+ (void)memset(data, 0, sizeof(*data));
+ return PLSR_PERSISTENCE_NOT_IMPLEMENTED;
+}
+
+PLSR_PERSISTENCE_RESULT PlsrPersistenceSaveSfd(const PLSR_SFD_DATA *data)
+{
+ if (data == NULL)
+ {
+ return PLSR_PERSISTENCE_INVALID_ARGUMENT;
+ }
+
+ return PLSR_PERSISTENCE_NOT_IMPLEMENTED;
+}
+
+#ifdef PLSR_HOST_TEST
+void PlsrPersistenceTestResetStorage(void)
+{
+ (void)memset(PlsrHostBackupSlots, 0, sizeof(PlsrHostBackupSlots));
+}
+
+void PlsrPersistenceTestCorruptNewestHsd(void)
+{
+ volatile PLSR_HSD_BACKUP_RECORD *slotA = PlsrPersistenceGetHsdSlot(0U);
+ volatile PLSR_HSD_BACKUP_RECORD *slotB = PlsrPersistenceGetHsdSlot(1U);
+ uint8_t validA = PlsrPersistenceHsdRecordIsValid(slotA);
+ uint8_t validB = PlsrPersistenceHsdRecordIsValid(slotB);
+ volatile PLSR_HSD_BACKUP_RECORD *newest;
+
+ if ((validA == 0U) && (validB == 0U))
+ {
+ return;
+ }
+ if ((validA != 0U) && (validB != 0U))
+ {
+ newest = (PlsrPersistenceGenerationIsNewer(slotB->generation,
+ slotA->generation)
+ != 0U)
+ ? slotB
+ : slotA;
+ }
+ else
+ {
+ newest = (validA != 0U) ? slotA : slotB;
+ }
+
+ newest->crc32 ^= 1UL;
+}
+#endif
diff --git a/PLSR/Test/run_host_tests.ps1 b/PLSR/Test/run_host_tests.ps1
new file mode 100644
index 0000000..9a11e9e
--- /dev/null
+++ b/PLSR/Test/run_host_tests.ps1
@@ -0,0 +1,51 @@
+$ErrorActionPreference = 'Stop'
+
+$workspacePath = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
+$compiler = Get-Command gcc.exe -ErrorAction SilentlyContinue
+if ($null -eq $compiler)
+{
+ throw 'gcc.exe was not found; PLSR host tests cannot run.'
+}
+
+$outputPath = Join-Path $workspacePath 'tmp\test_plc_device.exe'
+$outputDirectory = Split-Path -Parent $outputPath
+if (-not (Test-Path -LiteralPath $outputDirectory))
+{
+ New-Item -ItemType Directory -Path $outputDirectory | Out-Null
+}
+
+$arguments = @(
+ '-std=c11',
+ '-Wall',
+ '-Wextra',
+ '-Werror',
+ '-DPLSR_HOST_TEST',
+ "-I$workspacePath\PLSR\Inc",
+ "$workspacePath\PLSR\Src\plc_device.c",
+ "$workspacePath\PLSR\Src\plsr_persistence.c",
+ "$workspacePath\PLSR\Test\test_plc_device.c",
+ '-o',
+ $outputPath
+)
+
+try
+{
+ & $compiler.Source @arguments
+ if ($LASTEXITCODE -ne 0)
+ {
+ throw "PLSR host-test compilation failed with exit code $LASTEXITCODE."
+ }
+
+ & $outputPath
+ if ($LASTEXITCODE -ne 0)
+ {
+ throw "PLSR host tests failed with exit code $LASTEXITCODE."
+ }
+}
+finally
+{
+ if (Test-Path -LiteralPath $outputPath)
+ {
+ Remove-Item -LiteralPath $outputPath -Force
+ }
+}
diff --git a/PLSR/Test/test_plc_device.c b/PLSR/Test/test_plc_device.c
new file mode 100644
index 0000000..17ad754
--- /dev/null
+++ b/PLSR/Test/test_plc_device.c
@@ -0,0 +1,135 @@
+#include "plc_device.h"
+#include "plsr_persistence.h"
+#include
+#include
+#include
+
+static unsigned int TestCount;
+
+#define TEST_CHECK(condition) \
+ do \
+ { \
+ TestCount++; \
+ if (!(condition)) \
+ { \
+ (void)fprintf(stderr, "FAIL line %d: %s\n", __LINE__, #condition); \
+ exit(EXIT_FAILURE); \
+ } \
+ } while (0)
+
+static void TestAddressMap(void)
+{
+ uint16_t eventAddress;
+
+ TEST_CHECK(PlsrAxisAddressMap[0].hsdRuntimeBase == 0U);
+ TEST_CHECK(PlsrAxisAddressMap[3].hsdRuntimeBase == 12U);
+ TEST_CHECK(PlsrAxisAddressMap[0].smPulseActiveAddress == 1000U);
+ TEST_CHECK(PlsrAxisAddressMap[3].smDirectionAddress == 1061U);
+ TEST_CHECK(PlsrAxisAddressMap[2].sdRuntimeBase == 1040U);
+ TEST_CHECK(PlsrAxisAddressMap[3].eventBase == 6300U);
+
+ TEST_CHECK(PlcDeviceGetEventAddress(0U, 1U, &eventAddress) == PLC_DEVICE_OK);
+ TEST_CHECK(eventAddress == 6000U);
+ TEST_CHECK(PlcDeviceGetEventAddress(3U, 100U, &eventAddress) == PLC_DEVICE_OK);
+ TEST_CHECK(eventAddress == 6399U);
+ TEST_CHECK(PlcDeviceGetEventAddress(4U, 1U, &eventAddress)
+ == PLC_DEVICE_INVALID_ARGUMENT);
+ TEST_CHECK(PlcDeviceGetEventAddress(0U, 0U, &eventAddress)
+ == PLC_DEVICE_INVALID_ARGUMENT);
+}
+
+static void TestHsdAndPersistence(void)
+{
+ int32_t value32;
+ int64_t value64;
+ const int64_t firstPair = INT64_C(0x1234567887654321);
+ const int64_t secondPair = -INT64_C(0x102030405060708);
+
+ PlsrPersistenceTestResetStorage();
+ TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceGetLastHsdLoadResult() == PLSR_PERSISTENCE_DEFAULTED);
+ TEST_CHECK(PlcDeviceIsHsdDirty() != 0U);
+
+ TEST_CHECK(PlcDeviceWriteHsdConfig(460U, 11) == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceWriteHsdConfig(539U, -22) == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceWriteHsdConfig(0U, 1) == PLC_DEVICE_READ_ONLY);
+ TEST_CHECK(PlcDeviceWriteHsdConfig(459U, 1) == PLC_DEVICE_INVALID_ADDRESS);
+ TEST_CHECK(PlcDeviceReadHsd(460U, &value32) == PLC_DEVICE_OK);
+ TEST_CHECK(value32 == 11);
+ TEST_CHECK(PlcDeviceReadHsd(539U, &value32) == PLC_DEVICE_OK);
+ TEST_CHECK(value32 == -22);
+ TEST_CHECK(PlcDeviceReadHsd(540U, &value32) == PLC_DEVICE_INVALID_ADDRESS);
+
+ TEST_CHECK(PlcDevicePublishHsdPair(0U, firstPair) == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceReadHsdPair(0U, &value64) == PLC_DEVICE_OK);
+ TEST_CHECK(value64 == firstPair);
+ TEST_CHECK(PlcDeviceReadHsdPair(1U, &value64) == PLC_DEVICE_INVALID_ADDRESS);
+ TEST_CHECK(PlcDeviceCheckpointHsd() == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceIsHsdDirty() == 0U);
+
+ TEST_CHECK(PlcDeviceWriteHsdConfig(460U, 22) == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDevicePublishHsdPair(0U, secondPair) == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceCheckpointHsd() == PLC_DEVICE_OK);
+
+ PlsrPersistenceTestCorruptNewestHsd();
+ TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceGetLastHsdLoadResult() == PLSR_PERSISTENCE_OK);
+ TEST_CHECK(PlcDeviceReadHsd(460U, &value32) == PLC_DEVICE_OK);
+ TEST_CHECK(value32 == 11);
+ TEST_CHECK(PlcDeviceReadHsdPair(0U, &value64) == PLC_DEVICE_OK);
+ TEST_CHECK(value64 == firstPair);
+}
+
+static void TestSfd(void)
+{
+ int32_t value;
+
+ TEST_CHECK(PlcDeviceWriteSfd(900U, 123) == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceWriteSfd(1419U, -456) == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceReadSfd(900U, &value) == PLC_DEVICE_OK);
+ TEST_CHECK(value == 123);
+ TEST_CHECK(PlcDeviceReadSfd(1419U, &value) == PLC_DEVICE_OK);
+ TEST_CHECK(value == -456);
+ TEST_CHECK(PlcDeviceReadSfd(899U, &value) == PLC_DEVICE_INVALID_ADDRESS);
+ TEST_CHECK(PlcDeviceReadSfd(1420U, &value) == PLC_DEVICE_INVALID_ADDRESS);
+ TEST_CHECK(PlcDeviceIsSfdDirty() != 0U);
+ TEST_CHECK(PlcDeviceSaveSfd() == PLC_DEVICE_NOT_IMPLEMENTED);
+}
+
+static void TestSmAndSd(void)
+{
+ uint8_t state;
+ int32_t value;
+
+ TEST_CHECK(PlcDevicePublishSm(2U, 1U, 0U) == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceReadSm(1040U, &state) == PLC_DEVICE_OK);
+ TEST_CHECK(state == 1U);
+ TEST_CHECK(PlcDeviceReadSm(1041U, &state) == PLC_DEVICE_OK);
+ TEST_CHECK(state == 0U);
+ TEST_CHECK(PlcDeviceWriteSm(1040U, 0U) == PLC_DEVICE_READ_ONLY);
+ TEST_CHECK(PlcDeviceReadSm(1042U, &state) == PLC_DEVICE_INVALID_ADDRESS);
+
+ TEST_CHECK(PlcDevicePublishSd(3U, 11U, -12345) == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceReadSd(1071U, &value) == PLC_DEVICE_OK);
+ TEST_CHECK(value == -12345);
+ TEST_CHECK(PlcDeviceWriteSd(1071U, 0) == PLC_DEVICE_READ_ONLY);
+ TEST_CHECK(PlcDeviceReadSd(1012U, &value) == PLC_DEVICE_INVALID_ADDRESS);
+ TEST_CHECK(PlcDevicePublishSd(4U, 0U, 0) == PLC_DEVICE_INVALID_ARGUMENT);
+
+ TEST_CHECK(PlcDeviceInit() == PLC_DEVICE_OK);
+ TEST_CHECK(PlcDeviceReadSm(1040U, &state) == PLC_DEVICE_OK);
+ TEST_CHECK(state == 0U);
+ TEST_CHECK(PlcDeviceReadSd(1071U, &value) == PLC_DEVICE_OK);
+ TEST_CHECK(value == 0);
+}
+
+int main(void)
+{
+ TestAddressMap();
+ TestHsdAndPersistence();
+ TestSfd();
+ TestSmAndSd();
+
+ (void)printf("PASS: %u checks\n", TestCount);
+ return EXIT_SUCCESS;
+}