From c7b851659ea26f9af8563e146bf5332c3c8d6dfd Mon Sep 17 00:00:00 2001 From: ywh <2227158009@qq.com> Date: Wed, 12 Aug 2026 16:24:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=202026=20PLSR=20=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=E5=8F=8A=E6=9C=80=E5=B0=8F=20Modbus=20RTU?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/Inc/stm32f4xx_it.h | 5 +- Core/Src/main.c | 67 +- EWARM/Modbus.ewp | 48 + EWARM/stm32f407xx_flash.icf | 4 +- HostComputer/README.md | 32 +- HostComputer/plsr_control_panel.py | 935 +++++ HostComputer/plsr_modbus_product_test.py | 1162 ++++++ .../Third_Party/Micrium/Config/app_cfg.h | 3 + Modbus/Inc/modbus_rtu_slave.h | 130 +- Modbus/Src/modbus_rtu_slave.c | 1033 ++--- PLSR/Inc/plsr.h | 94 + PLSR/Src/plsr.c | 3599 +++++++++++++++++ PLSR/Src/plsr_internal.h | 49 + PLSR/Src/plsr_platform.h | 34 + PLSR/Src/plsr_platform_f407.c | 1202 ++++++ tests/plsr_host/run_tests.ps1 | 42 + tests/plsr_host/test_plsr_host.c | 1964 +++++++++ 17 files changed, 9438 insertions(+), 965 deletions(-) create mode 100644 HostComputer/plsr_control_panel.py create mode 100644 HostComputer/plsr_modbus_product_test.py create mode 100644 PLSR/Inc/plsr.h create mode 100644 PLSR/Src/plsr.c create mode 100644 PLSR/Src/plsr_internal.h create mode 100644 PLSR/Src/plsr_platform.h create mode 100644 PLSR/Src/plsr_platform_f407.c create mode 100644 tests/plsr_host/run_tests.ps1 create mode 100644 tests/plsr_host/test_plsr_host.c diff --git a/Core/Inc/stm32f4xx_it.h b/Core/Inc/stm32f4xx_it.h index a25a65a..ae330a9 100644 --- a/Core/Inc/stm32f4xx_it.h +++ b/Core/Inc/stm32f4xx_it.h @@ -60,7 +60,10 @@ void DMA2_Stream2_IRQHandler(void); void OTG_FS_IRQHandler(void); void DMA2_Stream7_IRQHandler(void); /* USER CODE BEGIN EFP */ - +void TIM1_UP_TIM10_IRQHandler(void); +void TIM8_UP_TIM13_IRQHandler(void); +void TIM1_TRG_COM_TIM11_IRQHandler(void); +void TIM8_TRG_COM_TIM14_IRQHandler(void); /* USER CODE END EFP */ #ifdef __cplusplus diff --git a/Core/Src/main.c b/Core/Src/main.c index 5fb8f1f..f84d298 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -24,7 +24,7 @@ /* USER CODE BEGIN Includes */ #include "ucos_ii.h" #include "modbus_rtu_slave.h" -#include "stdio.h" +#include "plsr.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -46,7 +46,6 @@ UART_HandleTypeDef huart1; DMA_HandleTypeDef hdma_usart1_rx; DMA_HandleTypeDef hdma_usart1_tx; -static volatile uint8_t ConnectFlag; /* USER CODE BEGIN PV */ static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; @@ -54,7 +53,6 @@ static OS_STK AppTaskStartStk[APP_TASK_START_STK_SIZE]; /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); -static HAL_StatusTypeDef BackupSramInit(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_USART1_UART_Init(void); @@ -68,55 +66,17 @@ static void AppTaskStart(void *pArg); static void AppTaskStart(void *pArg) { - (void)pArg; - /* - * F4作为Modbus RTU从站,触摸屏作为主站 - * 初始化函数会立即启动USART1的DMA空闲接收 - */ - (void)ModbusSlaveInit(&huart1, MODBUS_SLAVE_DEFAULT_ADDRESS); - //ModbusRetainedRegistersLoad(); + if (ModbusSlaveInit(&huart1, MODBUS_SLAVE_DEFAULT_ADDRESS) != HAL_OK) + { + Error_Handler(); + } + while (1) { - /* - * 维护几个供触摸屏首次联调读取的保持寄存器: - */ - - // (void)ModbusSlaveSetHoldingRegister( // OS时钟 - // HMI_REG_UPTIME_SECONDS, (uint16_t)(OSTimeGet() / OS_TICKS_PER_SEC)); - // (void)ModbusSlaveSetHoldingRegister( // 有效帧计数 - // HMI_REG_RX_FRAME_COUNT, - // (uint16_t)ModbusSlaveStatistics.validFrameCount); - - // (void)ModbusSlaveSetHoldingRegister( // 从站地址不匹配计数 - // D3, (uint16_t)ModbusSlaveStatistics.ignoredAddressCount); - // (void)ModbusSlaveSetHoldingRegister( // 非法功能码计数 - // D4, (uint16_t)ModbusSlaveStatistics.illegalFunctionCount); - - // (void)ModbusSlaveSetHoldingRegister( // 非法地址计数 - // D5, (uint16_t)ModbusSlaveStatistics.illegalAddressCount); - - // (void)ModbusSlaveSetHoldingRegister( // 非法数据值计数 - // D6, (uint16_t)ModbusSlaveStatistics.illegalValueCount); - - /* - * 每1ms轮询一次, - */ + PlsrPoll1ms(); ModbusSlavePoll(); - // ModbusRetainedRegistersPoll(); - - if (ModbusSlaveIsConnected(MODBUS_CONNECTION_TIMEOUT_MS) != 0U) - { - /* 主站在线 */ - ConnectFlag = 1; - } - else - { - /* 主站超时或断开 */ - ConnectFlag = 0; - } - OSTimeDly(1U); } } @@ -156,7 +116,10 @@ int main(void) MX_DMA_Init(); MX_USB_DEVICE_Init(); MX_USART1_UART_Init(); - (void)BackupSramInit(); + if (PlsrInit() == 0U) + { + Error_Handler(); + } /* USER CODE BEGIN 2 */ INT8U osError; @@ -188,14 +151,6 @@ int main(void) /* USER CODE END 3 */ } -static HAL_StatusTypeDef BackupSramInit(void) -{ - HAL_PWR_EnableBkUpAccess(); - __HAL_RCC_BKPSRAM_CLK_ENABLE(); - - return HAL_PWREx_EnableBkUpReg(); -} - /** * @brief System Clock Configuration * @retval None diff --git a/EWARM/Modbus.ewp b/EWARM/Modbus.ewp index 5c4ccb6..6817707 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,53 @@ + + PLSR + + $PROJ_DIR$\..\PLSR\Inc\plsr.h + + + $PROJ_DIR$\..\PLSR\Src\plsr_internal.h + + + $PROJ_DIR$\..\PLSR\Src\plsr_platform.h + + + $PROJ_DIR$\..\PLSR\Src\plsr.c + + Modbus + + ICCARM + + 35 + 0 + 1 + + + + + + + + + + $PROJ_DIR$\..\PLSR\Src\plsr_platform_f407.c + + Modbus diff --git a/EWARM/stm32f407xx_flash.icf b/EWARM/stm32f407xx_flash.icf index 472464c..68f508e 100644 --- a/EWARM/stm32f407xx_flash.icf +++ b/EWARM/stm32f407xx_flash.icf @@ -5,7 +5,7 @@ define symbol __ICFEDIT_intvec_start__ = 0x08000000; /*-Memory Regions-*/ define symbol __ICFEDIT_region_ROM_start__ = 0x08000000; -define symbol __ICFEDIT_region_ROM_end__ = 0x080FFFFF; +define symbol __ICFEDIT_region_ROM_end__ = 0x080BFFFF; define symbol __ICFEDIT_region_RAM_start__ = 0x20000000; define symbol __ICFEDIT_region_RAM_end__ = 0x2001FFFF; define symbol __ICFEDIT_region_CCMRAM_start__ = 0x10000000; @@ -35,4 +35,4 @@ place in RAM_region { readwrite, place in CCMRAM_region { section .ccmram -}; \ No newline at end of file +}; diff --git a/HostComputer/README.md b/HostComputer/README.md index 5fefb5c..b6d282d 100644 --- a/HostComputer/README.md +++ b/HostComputer/README.md @@ -1,4 +1,34 @@ -# Modbus RTU T1.5/T3.5测试上位机 +# 上位机工具 + +## PLSR 控制面板 + +`plsr_control_panel.py`用于配置和控制2026版PLSR指令。串口参数固定为 +9600、8位数据、偶校验、1位停止位(8E1),从站地址可选1~247。 + +在仓库根目录运行: + +```powershell +py -B HostComputer\plsr_control_panel.py +``` + +界面可读写公共参数和全部10段参数,轮询显示累计位置、当前频率、运行 +状态、当前段和错误码,并可发送启动、停止、清零命令。Y0~Y3是同一条 +PLSR逻辑的可选脉冲输出,不是四条可同时运行的独立PLSR指令;方向输出为 +Y12~Y15,WAIT/EXT输入为X4或X5。 + +离线自测不会创建窗口或打开串口: + +```powershell +py -B HostComputer\plsr_control_panel.py --self-test +``` + +依赖安装: + +```powershell +py -m pip install -r HostComputer\requirements.txt +``` + +## Modbus RTU T1.5/T3.5测试上位机 ## 直接运行EXE diff --git a/HostComputer/plsr_control_panel.py b/HostComputer/plsr_control_panel.py new file mode 100644 index 0000000..551ad3e --- /dev/null +++ b/HostComputer/plsr_control_panel.py @@ -0,0 +1,935 @@ +"""Tkinter control panel for the 2026 PLSR Modbus register map.""" + +from __future__ import annotations + +import argparse +import queue +import threading +import time +import tkinter as tk +import tkinter.font as tkfont +from tkinter import messagebox, ttk + +import serial +from serial.tools import list_ports + +# Keep one RTU implementation for the board tests and this operator panel. +from plsr_modbus_product_test import ( + COMMAND_CLEAR, + COMMAND_START, + COMMAND_STOP, + COMMON_WORDS, + CONFIG_BASE, + CONTROL, + ModbusException, + RtuClient, + SEGMENT_1_BASE, + SEGMENT_WORDS, + TestFailure, + read_status, + split_i32, + split_u32, +) + + +BAUD_RATE = 9600 +SERIAL_TIMEOUT_SECONDS = 0.8 +STATUS_POLL_SECONDS = 0.25 +SEGMENT_COUNT = 10 +SEGMENT_STRIDE = 0x10 +MAX_FREQUENCY_HZ = 100_000 + +PULSE_OPTIONS = ( + ("Y0 (0)", 0), + ("Y1 (1)", 1), + ("Y2 (2)", 2), + ("Y3 (3)", 3), +) +DIRECTION_OPTIONS = ( + ("Y12 (0)", 0), + ("Y13 (1)", 1), + ("Y14 (2)", 2), + ("Y15 (3)", 3), +) +INPUT_OPTIONS = (("X4 (0)", 0), ("X5 (1)", 1)) +SEND_OPTIONS = (("脉冲发送完毕 (0)", 0), ("后续段 (1)", 1)) +LOGIC_OPTIONS = (("正逻辑 (0)", 0), ("负逻辑 (1)", 1)) +CURVE_OPTIONS = (("直线 (0)", 0), ("S 曲线 (1)", 1), ("正弦曲线 (2)", 2)) +POSITION_OPTIONS = (("相对位置 (0)", 0), ("绝对位置 (1)", 1)) +WAIT_OPTIONS = ( + ("WAIT 时间 (0)", 0), + ("WAIT 信号 (1)", 1), + ("ACT 时间 (2)", 2), + ("EXT 信号 (3)", 3), + ("EXT 或完成 (4)", 4), +) + +STATUS_NAMES = { + 0: "未初始化", + 1: "空闲", + 2: "加速", + 3: "运行", + 4: "减速", + 5: "等待", + 6: "暂停", + 7: "完成", + 8: "停止", + 9: "错误", +} +ERROR_NAMES = { + 0: "无错误", + 1: "状态转换非法", + 2: "资源冲突", + 3: "资源非法", + 4: "定时器错误", + 5: "计数错误", + 6: "正限位", + 7: "负限位", + 8: "急停", + 9: "内部错误", +} + + +def option_labels(options): + return tuple(label for label, _value in options) + + +def option_label(options, value): + for label, candidate in options: + if candidate == value: + return label + return str(value) + + +def option_value(options, label, field_name): + for candidate_label, value in options: + if candidate_label == label: + return value + raise ValueError("%s 不是有效选项" % field_name) + + +def parse_integer(text, field_name, minimum, maximum): + raw = text.strip() + if not raw: + raise ValueError("%s 不能为空" % field_name) + try: + value = int(raw, 0) + except ValueError: + try: + value = int(raw, 10) + except ValueError as error: + raise ValueError("%s 必须是整数" % field_name) from error + if value < minimum or value > maximum: + raise ValueError( + "%s 必须在 %d 到 %d 之间" % (field_name, minimum, maximum) + ) + return value + + +class SerialWorker(threading.Thread): + """Own the serial port and report all results through a queue.""" + + def __init__(self): + super().__init__(name="plsr-serial", daemon=True) + self.commands = queue.Queue() + self.results = queue.Queue() + self.stop_event = threading.Event() + self.client = None + self.next_status_poll = 0.0 + + def submit(self, command, **payload): + self.commands.put((command, payload)) + + def close(self): + self.stop_event.set() + self.commands.put(("shutdown", {})) + + def _emit(self, event, **payload): + self.results.put((event, payload)) + + def _disconnect(self, notify=True, reason=""): + client = self.client + self.client = None + if client is not None: + try: + client.__exit__(None, None, None) + except (OSError, serial.SerialException): + pass + if notify: + self._emit("connection", connected=False, reason=reason) + + def _require_client(self): + if self.client is None: + raise RuntimeError("串口尚未连接") + return self.client + + def _read_configuration(self): + client = self._require_client() + common = client.read_holding(CONFIG_BASE, COMMON_WORDS) + segments = [] + for index in range(SEGMENT_COUNT): + address = SEGMENT_1_BASE + index * SEGMENT_STRIDE + segments.append(client.read_holding(address, SEGMENT_WORDS)) + self._emit("configuration", common=common, segments=segments) + + def _handle_connection_error(self, operation, error): + self._disconnect(notify=True, reason=str(error)) + self._emit("error", operation=operation, message=str(error), modal=True) + + def _handle_command(self, command, payload): + if command == "connect": + self._disconnect(notify=False) + try: + client = RtuClient( + payload["port"], + BAUD_RATE, + payload["slave"], + SERIAL_TIMEOUT_SECONDS, + ) + client.__enter__() + self.client = client + self.next_status_poll = 0.0 + self._emit( + "connection", + connected=True, + port=payload["port"], + slave=payload["slave"], + ) + self._read_configuration() + except (OSError, serial.SerialException) as error: + self._handle_connection_error("连接", error) + except (TestFailure, ModbusException, RuntimeError) as error: + self._disconnect(notify=True, reason=str(error)) + self._emit( + "error", operation="读取参数", message=str(error), modal=True + ) + return + + if command == "disconnect": + self._disconnect(notify=True) + return + + try: + client = self._require_client() + if command == "read_configuration": + self._read_configuration() + self._emit("operation", message="参数读取完成") + elif command == "write_common": + client.write_multiple(CONFIG_BASE, payload["words"]) + common = client.read_holding(CONFIG_BASE, COMMON_WORDS) + self._emit("common", words=common) + self._emit("operation", message="公共参数写入并回读完成") + elif command == "write_segments": + for index, words in enumerate(payload["segments"]): + address = SEGMENT_1_BASE + index * SEGMENT_STRIDE + client.write_multiple(address, words) + segments = [] + for index in range(SEGMENT_COUNT): + address = SEGMENT_1_BASE + index * SEGMENT_STRIDE + segments.append(client.read_holding(address, SEGMENT_WORDS)) + self._emit("segments", words=segments) + self._emit("operation", message="10 段参数写入并回读完成") + elif command == "control": + client.write_single(CONTROL, payload["value"]) + self._emit("operation", message=payload["message"]) + else: + raise RuntimeError("未知后台命令: %s" % command) + except (OSError, serial.SerialException) as error: + self._handle_connection_error(payload.get("operation", "通信"), error) + except (TestFailure, ModbusException, RuntimeError) as error: + self._emit( + "error", + operation=payload.get("operation", "通信"), + message=str(error), + modal=True, + ) + + def _poll_status(self): + try: + status = read_status(self._require_client()) + self._emit("status", status=status) + self.next_status_poll = time.monotonic() + STATUS_POLL_SECONDS + except (OSError, serial.SerialException) as error: + self._handle_connection_error("状态轮询", error) + except (TestFailure, ModbusException, RuntimeError) as error: + self._emit( + "error", operation="状态轮询", message=str(error), modal=False + ) + self.next_status_poll = time.monotonic() + 1.0 + + def run(self): + try: + while not self.stop_event.is_set(): + try: + command, payload = self.commands.get(timeout=0.05) + except queue.Empty: + command = None + payload = None + + if command == "shutdown": + break + if command is not None: + self._handle_command(command, payload) + + if ( + self.client is not None + and time.monotonic() >= self.next_status_poll + ): + self._poll_status() + finally: + self._disconnect(notify=False) + + +class PlsrControlPanel: + def __init__(self, root): + self.root = root + self.root.title("PLSR 控制面板") + self.root.geometry("1180x720") + self.root.minsize(980, 650) + self.root.protocol("WM_DELETE_WINDOW", self._on_close) + + self.connected = False + self.operation_pending = False + self.worker = SerialWorker() + self.worker.start() + + self.port_var = tk.StringVar() + self.slave_var = tk.StringVar(value="1") + self.connection_var = tk.StringVar(value="未连接") + self.footer_var = tk.StringVar(value="请选择串口并连接") + self.status_vars = { + "position": tk.StringVar(value="--"), + "frequency": tk.StringVar(value="--"), + "state": tk.StringVar(value="--"), + "segment": tk.StringVar(value="--"), + "error": tk.StringVar(value="--"), + } + self.common_vars = {} + self.segment_vars = [] + self.connection_widgets = [] + self.action_buttons = [] + + self._configure_style() + self._build_ui() + self.refresh_ports() + self._set_connected(False) + self.root.after(50, self._drain_results) + + def _configure_style(self): + default_font = tkfont.nametofont("TkDefaultFont") + default_font.configure(family="Microsoft YaHei UI", size=10) + text_font = tkfont.nametofont("TkTextFont") + text_font.configure(family="Microsoft YaHei UI", size=10) + style = ttk.Style(self.root) + style.configure("Connected.TLabel", foreground="#18794e") + style.configure("Disconnected.TLabel", foreground="#5f6368") + style.configure("Error.TLabel", foreground="#b42318") + style.configure("StatusValue.TLabel", font=("Microsoft YaHei UI", 11, "bold")) + + def _build_ui(self): + self.root.columnconfigure(0, weight=1) + self.root.rowconfigure(2, weight=1) + + connection = ttk.LabelFrame(self.root, text="连接") + connection.grid(row=0, column=0, padx=10, pady=(10, 6), sticky="ew") + connection.columnconfigure(7, weight=1) + + ttk.Label(connection, text="串口").grid(row=0, column=0, padx=(8, 4), pady=8) + self.port_box = ttk.Combobox( + connection, textvariable=self.port_var, width=13, state="readonly" + ) + self.port_box.grid(row=0, column=1, padx=4, pady=8) + refresh_button = ttk.Button(connection, text="刷新", command=self.refresh_ports) + refresh_button.grid(row=0, column=2, padx=(0, 12), pady=8) + ttk.Label(connection, text="串口参数").grid(row=0, column=3, padx=4, pady=8) + ttk.Label(connection, text="9600, 8E1").grid(row=0, column=4, padx=(4, 12), pady=8) + ttk.Label(connection, text="从站地址").grid(row=0, column=5, padx=4, pady=8) + slave_entry = ttk.Entry(connection, textvariable=self.slave_var, width=7) + slave_entry.grid(row=0, column=6, padx=(4, 12), pady=8) + self.connect_button = ttk.Button( + connection, text="连接", command=self._toggle_connection + ) + self.connect_button.grid(row=0, column=8, padx=8, pady=8) + self.connection_label = ttk.Label( + connection, + textvariable=self.connection_var, + style="Disconnected.TLabel", + width=28, + anchor="e", + ) + self.connection_label.grid(row=0, column=7, padx=8, pady=8, sticky="e") + self.connection_widgets = [self.port_box, refresh_button, slave_entry] + + self._build_runtime_status() + + notebook = ttk.Notebook(self.root) + notebook.grid(row=2, column=0, padx=10, pady=6, sticky="nsew") + common_tab = ttk.Frame(notebook, padding=12) + segment_tab = ttk.Frame(notebook, padding=12) + notebook.add(common_tab, text="公共参数 0x1000-0x1013") + notebook.add(segment_tab, text="10 段参数 0x1100-0x1197") + self._build_common_tab(common_tab) + self._build_segment_tab(segment_tab) + + footer = ttk.Frame(self.root) + footer.grid(row=3, column=0, padx=10, pady=(4, 10), sticky="ew") + footer.columnconfigure(4, weight=1) + read_button = ttk.Button(footer, text="读取全部参数", command=self._read_all) + read_button.grid(row=0, column=0, padx=(0, 6)) + common_button = ttk.Button( + footer, text="写入公共参数", command=self._write_common + ) + common_button.grid(row=0, column=1, padx=6) + segment_button = ttk.Button( + footer, text="写入 10 段参数", command=self._write_segments + ) + segment_button.grid(row=0, column=2, padx=6) + self.action_buttons.extend([read_button, common_button, segment_button]) + self.footer_label = ttk.Label( + footer, textvariable=self.footer_var, anchor="e" + ) + self.footer_label.grid(row=0, column=4, padx=(12, 0), sticky="ew") + + def _build_runtime_status(self): + frame = ttk.LabelFrame(self.root, text="实时状态 0x2000-0x2006") + frame.grid(row=1, column=0, padx=10, pady=6, sticky="ew") + for column in range(11): + frame.columnconfigure(column, weight=1 if column % 2 else 0) + + fields = ( + ("累计位置", "position"), + ("当前频率", "frequency"), + ("运行状态", "state"), + ("当前段", "segment"), + ("错误码", "error"), + ) + for index, (label, key) in enumerate(fields): + ttk.Label(frame, text=label).grid( + row=0, column=index * 2, padx=(8, 4), pady=9, sticky="e" + ) + ttk.Label( + frame, textvariable=self.status_vars[key], style="StatusValue.TLabel" + ).grid(row=0, column=index * 2 + 1, padx=(4, 12), pady=9, sticky="w") + + commands = ttk.Frame(frame) + commands.grid(row=0, column=10, padx=8, pady=6, sticky="e") + start_button = ttk.Button( + commands, text="启动", command=lambda: self._send_control(COMMAND_START) + ) + stop_button = ttk.Button( + commands, text="停止", command=lambda: self._send_control(COMMAND_STOP) + ) + clear_button = ttk.Button(commands, text="清零", command=self._clear_position) + start_button.grid(row=0, column=0, padx=3) + stop_button.grid(row=0, column=1, padx=3) + clear_button.grid(row=0, column=2, padx=3) + self.action_buttons.extend([start_button, stop_button, clear_button]) + + def _add_entry(self, parent, row, group, key, label, default="0", width=13): + column = group * 2 + ttk.Label(parent, text=label).grid( + row=row, column=column, padx=(8, 4), pady=7, sticky="e" + ) + variable = tk.StringVar(value=default) + ttk.Entry(parent, textvariable=variable, width=width).grid( + row=row, column=column + 1, padx=(4, 18), pady=7, sticky="w" + ) + self.common_vars[key] = variable + + def _add_combo(self, parent, row, group, key, label, options): + column = group * 2 + ttk.Label(parent, text=label).grid( + row=row, column=column, padx=(8, 4), pady=7, sticky="e" + ) + variable = tk.StringVar(value=options[0][0]) + ttk.Combobox( + parent, + textvariable=variable, + values=option_labels(options), + state="readonly", + width=18, + ).grid(row=row, column=column + 1, padx=(4, 18), pady=7, sticky="w") + self.common_vars[key] = variable + + def _build_common_tab(self, parent): + for column in (1, 3, 5): + parent.columnconfigure(column, weight=1) + + self._add_combo( + parent, 0, 0, "pulse_output", "脉冲输出(单逻辑 PLSR)", PULSE_OPTIONS + ) + self._add_combo( + parent, 0, 1, "direction_output", "方向输出", DIRECTION_OPTIONS + ) + self._add_entry(parent, 0, 2, "direction_delay", "方向延时 ms") + + self._add_combo(parent, 1, 0, "wait_input", "WAIT 输入", INPUT_OPTIONS) + self._add_combo(parent, 1, 1, "ext_input", "EXT 输入", INPUT_OPTIONS) + self._add_combo(parent, 1, 2, "send_mode", "发送模式", SEND_OPTIONS) + + self._add_combo(parent, 2, 0, "negative_logic", "方向逻辑", LOGIC_OPTIONS) + self._add_combo(parent, 2, 1, "curve_mode", "曲线模式", CURVE_OPTIONS) + self._add_combo(parent, 2, 2, "position_mode", "位置模式", POSITION_OPTIONS) + + self._add_entry(parent, 3, 0, "segment_count", "段数", default="1") + self._add_entry(parent, 3, 1, "start_segment", "起始段", default="1") + self._add_entry(parent, 3, 2, "default_speed", "基准速度 Hz", default="1000") + + self._add_entry(parent, 4, 0, "start_speed", "启动速度 Hz", default="100") + self._add_entry(parent, 4, 1, "stop_speed", "停止速度 Hz", default="100") + self._add_entry(parent, 4, 2, "acceleration", "加速时间 ms") + + self._add_entry(parent, 5, 0, "deceleration", "减速时间 ms") + + def _build_segment_tab(self, parent): + headers = ( + ("段 / 基址", 15), + ("频率 Hz", 13), + ("脉冲数", 15), + ("等待类型", 19), + ("WAIT ms", 11), + ("ACT ms", 11), + ("跳转段", 9), + ) + for column, (label, _width) in enumerate(headers): + parent.columnconfigure(column, weight=1 if column in (1, 2, 3) else 0) + ttk.Label(parent, text=label, anchor="center").grid( + row=0, column=column, padx=4, pady=(0, 6), sticky="ew" + ) + + for index in range(SEGMENT_COUNT): + address = SEGMENT_1_BASE + index * SEGMENT_STRIDE + ttk.Label(parent, text="%d / 0x%04X" % (index + 1, address)).grid( + row=index + 1, column=0, padx=(2, 8), pady=4, sticky="e" + ) + variables = { + "frequency": tk.StringVar(value="1000"), + "pulses": tk.StringVar(value="0"), + "wait_type": tk.StringVar(value=WAIT_OPTIONS[0][0]), + "wait_ms": tk.StringVar(value="0"), + "act_ms": tk.StringVar(value="0"), + "jump": tk.StringVar(value="0"), + } + ttk.Entry(parent, textvariable=variables["frequency"], width=13).grid( + row=index + 1, column=1, padx=4, pady=4, sticky="ew" + ) + ttk.Entry(parent, textvariable=variables["pulses"], width=15).grid( + row=index + 1, column=2, padx=4, pady=4, sticky="ew" + ) + ttk.Combobox( + parent, + textvariable=variables["wait_type"], + values=option_labels(WAIT_OPTIONS), + state="readonly", + width=19, + ).grid(row=index + 1, column=3, padx=4, pady=4, sticky="ew") + ttk.Entry(parent, textvariable=variables["wait_ms"], width=11).grid( + row=index + 1, column=4, padx=4, pady=4 + ) + ttk.Entry(parent, textvariable=variables["act_ms"], width=11).grid( + row=index + 1, column=5, padx=4, pady=4 + ) + ttk.Entry(parent, textvariable=variables["jump"], width=9).grid( + row=index + 1, column=6, padx=4, pady=4 + ) + self.segment_vars.append(variables) + + def refresh_ports(self): + ports = [item.device for item in list_ports.comports()] + self.port_box["values"] = ports + if ports and self.port_var.get() not in ports: + self.port_var.set(ports[0]) + elif not ports: + self.port_var.set("") + + def _set_connected(self, connected, description=""): + self.connected = connected + self.connect_button.configure( + text="断开" if connected else "连接", state="normal" + ) + connection_state = "disabled" if connected else "normal" + for widget in self.connection_widgets: + if widget is self.port_box: + widget.configure(state="disabled" if connected else "readonly") + else: + widget.configure(state=connection_state) + if connected: + self.connection_var.set(description or "已连接") + self.connection_label.configure(style="Connected.TLabel") + else: + self.connection_var.set(description or "未连接") + self.connection_label.configure( + style="Error.TLabel" if description else "Disconnected.TLabel" + ) + for variable in self.status_vars.values(): + variable.set("--") + self._update_action_state() + + def _update_action_state(self): + state = "normal" if self.connected and not self.operation_pending else "disabled" + for button in self.action_buttons: + button.configure(state=state) + + def _start_operation(self, message): + if not self.connected: + messagebox.showerror("未连接", "请先连接 PLSR 从站") + return False + if self.operation_pending: + return False + self.operation_pending = True + self.footer_var.set(message) + self._update_action_state() + return True + + def _finish_operation(self, message): + self.operation_pending = False + self.footer_var.set(message) + self.footer_label.configure(style="TLabel") + self._update_action_state() + + def _toggle_connection(self): + if self.connected: + self.operation_pending = False + self.footer_var.set("正在断开") + self.worker.submit("disconnect") + return + port = self.port_var.get().strip() + if not port: + messagebox.showerror("连接参数", "请选择可用串口") + return + try: + slave = parse_integer(self.slave_var.get(), "从站地址", 1, 247) + except ValueError as error: + messagebox.showerror("连接参数", str(error)) + return + self.connect_button.configure(state="disabled") + self.operation_pending = True + self._update_action_state() + self.connection_var.set("正在连接 %s" % port) + self.footer_var.set("正在打开串口并读取参数") + self.worker.submit("connect", port=port, slave=slave) + + def _common_words(self): + values = self.common_vars + segment_count = parse_integer(values["segment_count"].get(), "段数", 1, 10) + start_segment = parse_integer( + values["start_segment"].get(), "起始段", 1, 10 + ) + if start_segment > segment_count: + raise ValueError("起始段不能大于段数") + + words = [0] * COMMON_WORDS + words[0] = option_value( + PULSE_OPTIONS, values["pulse_output"].get(), "脉冲输出" + ) + words[1] = option_value( + DIRECTION_OPTIONS, values["direction_output"].get(), "方向输出" + ) + words[2] = option_value(INPUT_OPTIONS, values["wait_input"].get(), "WAIT 输入") + words[3] = option_value(INPUT_OPTIONS, values["ext_input"].get(), "EXT 输入") + words[4] = option_value(SEND_OPTIONS, values["send_mode"].get(), "发送模式") + words[5] = parse_integer( + values["direction_delay"].get(), "方向延时", 0, 0xFFFF + ) + words[6] = option_value( + LOGIC_OPTIONS, values["negative_logic"].get(), "方向逻辑" + ) + words[7] = option_value(CURVE_OPTIONS, values["curve_mode"].get(), "曲线模式") + words[8] = option_value( + POSITION_OPTIONS, values["position_mode"].get(), "位置模式" + ) + words[9] = segment_count + words[10] = start_segment + words[11:13] = split_u32( + parse_integer( + values["default_speed"].get(), "基准速度", 1, MAX_FREQUENCY_HZ + ) + ) + words[13:15] = split_u32( + parse_integer( + values["start_speed"].get(), "启动速度", 0, MAX_FREQUENCY_HZ + ) + ) + words[15] = 0 + words[16:18] = split_u32( + parse_integer( + values["stop_speed"].get(), "停止速度", 0, MAX_FREQUENCY_HZ + ) + ) + words[18] = parse_integer(values["acceleration"].get(), "加速时间", 0, 0xFFFF) + words[19] = parse_integer(values["deceleration"].get(), "减速时间", 0, 0xFFFF) + return words + + def _segment_words(self): + segment_count = parse_integer( + self.common_vars["segment_count"].get(), "段数", 1, SEGMENT_COUNT + ) + encoded = [] + for index, variables in enumerate(self.segment_vars): + name = "第 %d 段" % (index + 1) + frequency = parse_integer( + variables["frequency"].get(), name + "频率", 1, MAX_FREQUENCY_HZ + ) + pulses = parse_integer( + variables["pulses"].get(), name + "脉冲数", -(1 << 31), (1 << 31) - 1 + ) + wait_type = option_value( + WAIT_OPTIONS, variables["wait_type"].get(), name + "等待类型" + ) + wait_ms = parse_integer( + variables["wait_ms"].get(), name + " WAIT 时间", 0, 0xFFFF + ) + act_ms = parse_integer( + variables["act_ms"].get(), name + " ACT 时间", 0, 0xFFFF + ) + jump = parse_integer(variables["jump"].get(), name + "跳转段", 0, 10) + if index < segment_count and jump > segment_count: + raise ValueError( + "%s跳转段必须为 0 或不大于脉冲总段数 %d" + % (name, segment_count) + ) + words = split_u32(frequency) + split_i32(pulses) + words.extend((wait_type, wait_ms, act_ms, jump)) + encoded.append(words) + return encoded + + def _set_common_words(self, words): + if len(words) != COMMON_WORDS: + raise ValueError("公共参数回读长度错误") + values = self.common_vars + values["pulse_output"].set(option_label(PULSE_OPTIONS, words[0])) + values["direction_output"].set(option_label(DIRECTION_OPTIONS, words[1])) + values["wait_input"].set(option_label(INPUT_OPTIONS, words[2])) + values["ext_input"].set(option_label(INPUT_OPTIONS, words[3])) + values["send_mode"].set(option_label(SEND_OPTIONS, words[4])) + values["direction_delay"].set(str(words[5])) + values["negative_logic"].set(option_label(LOGIC_OPTIONS, words[6])) + values["curve_mode"].set(option_label(CURVE_OPTIONS, words[7])) + values["position_mode"].set(option_label(POSITION_OPTIONS, words[8])) + values["segment_count"].set(str(words[9])) + values["start_segment"].set(str(words[10])) + values["default_speed"].set(str(words[11] | (words[12] << 16))) + values["start_speed"].set(str(words[13] | (words[14] << 16))) + values["stop_speed"].set(str(words[16] | (words[17] << 16))) + values["acceleration"].set(str(words[18])) + values["deceleration"].set(str(words[19])) + + def _set_segment_words(self, segments): + if len(segments) != SEGMENT_COUNT: + raise ValueError("段参数回读数量错误") + for index, words in enumerate(segments): + if len(words) != SEGMENT_WORDS: + raise ValueError("第 %d 段回读长度错误" % (index + 1)) + variables = self.segment_vars[index] + frequency = words[0] | (words[1] << 16) + pulse_bits = words[2] | (words[3] << 16) + pulses = pulse_bits - (1 << 32) if pulse_bits & 0x80000000 else pulse_bits + variables["frequency"].set(str(frequency)) + variables["pulses"].set(str(pulses)) + variables["wait_type"].set(option_label(WAIT_OPTIONS, words[4])) + variables["wait_ms"].set(str(words[5])) + variables["act_ms"].set(str(words[6])) + variables["jump"].set(str(words[7])) + + def _read_all(self): + if self._start_operation("正在读取公共参数和 10 段参数"): + self.worker.submit("read_configuration", operation="读取参数") + + def _write_common(self): + try: + words = self._common_words() + except ValueError as error: + messagebox.showerror("参数错误", str(error)) + return + if self._start_operation("正在写入公共参数"): + self.worker.submit( + "write_common", words=words, operation="写入公共参数" + ) + + def _write_segments(self): + try: + segments = self._segment_words() + except ValueError as error: + messagebox.showerror("参数错误", str(error)) + return + if self._start_operation("正在写入 10 段参数"): + self.worker.submit( + "write_segments", segments=segments, operation="写入段参数" + ) + + def _send_control(self, command): + messages = { + COMMAND_START: "启动命令已发送", + COMMAND_STOP: "停止命令已发送", + COMMAND_CLEAR: "清零命令已发送", + } + if self._start_operation("正在发送控制命令"): + self.worker.submit( + "control", + value=command, + message=messages[command], + operation="控制命令", + ) + + def _clear_position(self): + if messagebox.askyesno("确认清零", "确认将累计位置清零?"): + self._send_control(COMMAND_CLEAR) + + def _show_status(self, status): + self.status_vars["position"].set(str(status.position)) + self.status_vars["frequency"].set("%d Hz" % status.frequency_hz) + self.status_vars["state"].set( + "%s (%d)" % (STATUS_NAMES.get(status.state, "未知"), status.state) + ) + self.status_vars["segment"].set(str(status.segment)) + self.status_vars["error"].set( + "%s (%d)" % (ERROR_NAMES.get(status.error, "未知"), status.error) + ) + + def _handle_result(self, event, payload): + if event == "connection": + if payload["connected"]: + description = "%s / 从站 %d" % (payload["port"], payload["slave"]) + self._set_connected(True, description) + self.footer_var.set("已连接,正在读取参数") + else: + reason = payload.get("reason", "") + self.operation_pending = False + self.connect_button.configure(state="normal") + self._set_connected(False, reason) + self.footer_var.set("连接已断开" if not reason else "通信错误") + elif event == "configuration": + self._set_common_words(payload["common"]) + self._set_segment_words(payload["segments"]) + self._finish_operation("全部参数已读取") + elif event == "common": + self._set_common_words(payload["words"]) + elif event == "segments": + self._set_segment_words(payload["words"]) + elif event == "status": + self._show_status(payload["status"]) + elif event == "operation": + self._finish_operation(payload["message"]) + elif event == "error": + message = "%s失败:%s" % (payload["operation"], payload["message"]) + self._finish_operation(message) + self.footer_label.configure(style="Error.TLabel") + if payload.get("modal", False): + messagebox.showerror("PLSR 通信错误", message) + + def _drain_results(self): + try: + while True: + event, payload = self.worker.results.get_nowait() + try: + self._handle_result(event, payload) + except (KeyError, ValueError) as error: + self._finish_operation("界面数据错误:%s" % error) + except queue.Empty: + pass + self.root.after(50, self._drain_results) + + def _on_close(self): + self.worker.close() + self.root.destroy() + + +class _SelfTestVariable: + def __init__(self, value): + self.value = str(value) + + def get(self): + return self.value + + def set(self, value): + self.value = str(value) + + +def self_test(): + assert BAUD_RATE == 9600 + assert INPUT_OPTIONS == (("X4 (0)", 0), ("X5 (1)", 1)) + assert [SEGMENT_1_BASE + index * SEGMENT_STRIDE + for index in range(SEGMENT_COUNT)] == [ + 0x1100, 0x1110, 0x1120, 0x1130, 0x1140, + 0x1150, 0x1160, 0x1170, 0x1180, 0x1190, + ] + assert parse_integer("0x10", "测试值", 0, 16) == 16 + try: + parse_integer("17", "测试值", 0, 16) + except ValueError: + pass + else: + raise AssertionError("range validation did not reject 17") + + panel = PlsrControlPanel.__new__(PlsrControlPanel) + panel.common_vars = { + "pulse_output": _SelfTestVariable(PULSE_OPTIONS[3][0]), + "direction_output": _SelfTestVariable(DIRECTION_OPTIONS[3][0]), + "wait_input": _SelfTestVariable(INPUT_OPTIONS[1][0]), + "ext_input": _SelfTestVariable(INPUT_OPTIONS[0][0]), + "send_mode": _SelfTestVariable(SEND_OPTIONS[1][0]), + "direction_delay": _SelfTestVariable(25), + "negative_logic": _SelfTestVariable(LOGIC_OPTIONS[1][0]), + "curve_mode": _SelfTestVariable(CURVE_OPTIONS[2][0]), + "position_mode": _SelfTestVariable(POSITION_OPTIONS[1][0]), + "segment_count": _SelfTestVariable(10), + "start_segment": _SelfTestVariable(2), + "default_speed": _SelfTestVariable(100000), + "start_speed": _SelfTestVariable(0), + "stop_speed": _SelfTestVariable(65536), + "acceleration": _SelfTestVariable(1000), + "deceleration": _SelfTestVariable(2000), + } + common = panel._common_words() + assert common[:11] == [3, 3, 1, 0, 1, 25, 1, 2, 1, 10, 2] + assert common[11:20] == [34464, 1, 0, 0, 0, 0, 1, 1000, 2000] + + panel.segment_vars = [] + for index in range(SEGMENT_COUNT): + panel.segment_vars.append({ + "frequency": _SelfTestVariable(1000 + index), + "pulses": _SelfTestVariable(-1 if index == 0 else index), + "wait_type": _SelfTestVariable(WAIT_OPTIONS[4][0]), + "wait_ms": _SelfTestVariable(index), + "act_ms": _SelfTestVariable(index + 1), + "jump": _SelfTestVariable(0), + }) + segments = panel._segment_words() + assert len(segments) == SEGMENT_COUNT + assert all(len(words) == SEGMENT_WORDS for words in segments) + assert segments[0] == [1000, 0, 0xFFFF, 0xFFFF, 4, 0, 1, 0] + + panel.common_vars["segment_count"].set(1) + panel.segment_vars[0]["jump"].set(2) + try: + panel._segment_words() + except ValueError: + pass + else: + raise AssertionError("active segment jump exceeded segment count") + print("PLSR control panel self-test passed; no GUI or serial port was opened") + + +def parse_arguments(arguments=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--self-test", + action="store_true", + help="validate register encoding without opening the GUI or serial port", + ) + return parser.parse_args(arguments) + + +def main(arguments=None): + args = parse_arguments(arguments) + if args.self_test: + self_test() + return 0 + root = tk.Tk() + PlsrControlPanel(root) + root.mainloop() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/HostComputer/plsr_modbus_product_test.py b/HostComputer/plsr_modbus_product_test.py new file mode 100644 index 0000000..a1661a1 --- /dev/null +++ b/HostComputer/plsr_modbus_product_test.py @@ -0,0 +1,1162 @@ +"""Minimal board test for the 2026 PLSR Modbus product register map. + +The script is inert unless --run is supplied. Motion phases additionally +require --allow-motion because they drive the configured pulse output. +""" + +import argparse +import dataclasses +import re +import subprocess +import sys +import time + +import serial + + +CONFIG_BASE = 0x1000 +COMMON_WORDS = 0x14 +SEGMENT_1_BASE = 0x1100 +SEGMENT_2_BASE = 0x1110 +SEGMENT_WORDS = 8 +STATUS_BASE = 0x2000 +STATUS_WORDS = 7 +CONTROL = 0x3000 + +COMMAND_START = 0x0001 +COMMAND_STOP = 0x0002 +COMMAND_CLEAR = 0x0004 + +STATUS_IDLE = 1 +STATUS_ACCELERATING = 2 +STATUS_RUNNING = 3 +STATUS_DECELERATING = 4 +STATUS_WAITING = 5 +STATUS_COMPLETED = 7 +STATUS_STOPPED = 8 +STATUS_ERROR = 9 +ERROR_NONE = 0 + +WAIT_TIME = 0 +WAIT_SIGNAL = 1 +ACT_TIME = 2 +EXT_SIGNAL = 3 +EXT_OR_COMPLETE = 4 + +SEND_COMPLETE = 0 +SEND_SUBSEQUENT = 1 + +DEFAULT_STLINK_CLI = r"F:\ST-LINK Utility\ST-LINK_CLI.exe" + +GPIOE_CLOCK_BIT = 0x42470610 +GPIOI_CLOCK_BIT = 0x42470620 +GPIOE_PIN6_OUTPUT_BIT = 0x42420298 +GPIOI_PIN8_OUTPUT_BIT = 0x424402A0 +GPIOE_PIN6_MODE_LOW_BIT = 0x42420030 +GPIOE_PIN6_MODE_HIGH_BIT = 0x42420034 +GPIOI_PIN8_MODE_LOW_BIT = 0x42440040 +GPIOI_PIN8_MODE_HIGH_BIT = 0x42440044 +GPIOE_PIN6_PULL_LOW_BIT = 0x424201B0 +GPIOE_PIN6_PULL_HIGH_BIT = 0x424201B4 +GPIOI_PIN8_PULL_LOW_BIT = 0x424401C0 +GPIOI_PIN8_PULL_HIGH_BIT = 0x424401C4 +X4_INPUT_BIT = 0x42408214 +X5_INPUT_BIT = 0x42430230 + +OUTPUT_OFF = 1 +OUTPUT_ON = 0 + +EX_ILLEGAL_FUNCTION = 0x01 +EX_ILLEGAL_ADDRESS = 0x02 +EX_ILLEGAL_VALUE = 0x03 +EX_DEVICE_FAILURE = 0x04 +EX_DEVICE_BUSY = 0x06 + +ACTIVE_STATES = { + STATUS_ACCELERATING, + STATUS_RUNNING, + STATUS_DECELERATING, + STATUS_WAITING, +} +TERMINAL_STATES = {STATUS_COMPLETED, STATUS_STOPPED, STATUS_ERROR} + + +class TestFailure(RuntimeError): + pass + + +class ModbusException(RuntimeError): + def __init__(self, function, code): + super().__init__( + "Modbus exception: function=0x%02X code=0x%02X" + % (function, code) + ) + self.function = function + self.code = code + + +def parse_stlink_word(output, address): + pattern = r"(?im)^\s*0x%08x\s*:\s*([0-9a-f]{8})\s*$" % address + match = re.search(pattern, output) + require(match is not None, + "ST-LINK output did not contain address 0x%08X" % address) + return int(match.group(1), 16) + + +class X45Fixture: + """Drive Y4/Y5 through SWD without adding product test registers.""" + + def __init__(self, executable, probe_id, timeout): + self.executable = executable + self.probe_id = probe_id + self.timeout = timeout + + def _run(self, *arguments): + command_line = [ + self.executable, + "-c", + "ID=%d" % self.probe_id, + "SWD", + "HOTPLUG", + ] + list(arguments) + ["-Q", "-NoPrompt"] + try: + result = subprocess.run( + command_line, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + errors="replace", + timeout=self.timeout, + check=False, + ) + except (OSError, subprocess.SubprocessError) as error: + raise TestFailure("ST-LINK command failed: %s" % error) from error + require( + result.returncode == 0, + "ST-LINK command returned %d:\n%s" + % (result.returncode, result.stdout.strip()), + ) + return result.stdout + + def prepare(self): + self._run( + "-w32", hex(GPIOE_CLOCK_BIT), "1", + "-w32", hex(GPIOI_CLOCK_BIT), "1", + "-w32", hex(GPIOI_PIN8_OUTPUT_BIT), str(OUTPUT_OFF), + "-w32", hex(GPIOE_PIN6_OUTPUT_BIT), str(OUTPUT_OFF), + "-w32", hex(GPIOI_PIN8_MODE_HIGH_BIT), "0", + "-w32", hex(GPIOI_PIN8_MODE_LOW_BIT), "1", + "-w32", hex(GPIOE_PIN6_MODE_HIGH_BIT), "0", + "-w32", hex(GPIOE_PIN6_MODE_LOW_BIT), "1", + ) + time.sleep(0.050) + + def drive(self, input_selection, active): + require(input_selection in (0, 1), "input selection must be X4 or X5") + if input_selection == 0: + address = GPIOI_PIN8_OUTPUT_BIT + else: + address = GPIOE_PIN6_OUTPUT_BIT + value = OUTPUT_ON if active else OUTPUT_OFF + self._run("-w32", hex(address), str(value)) + time.sleep(0.050) + + def read(self, input_selection): + require(input_selection in (0, 1), "input selection must be X4 or X5") + address = X4_INPUT_BIT if input_selection == 0 else X5_INPUT_BIT + output = self._run("-r32", hex(address), "1") + return parse_stlink_word(output, address) & 1 + + def all_off(self): + self._run( + "-w32", hex(GPIOI_PIN8_OUTPUT_BIT), str(OUTPUT_OFF), + "-w32", hex(GPIOE_PIN6_OUTPUT_BIT), str(OUTPUT_OFF), + ) + time.sleep(0.050) + + def release(self): + self._run( + "-w32", hex(GPIOI_PIN8_OUTPUT_BIT), str(OUTPUT_OFF), + "-w32", hex(GPIOE_PIN6_OUTPUT_BIT), str(OUTPUT_OFF), + "-w32", hex(GPIOI_PIN8_MODE_LOW_BIT), "0", + "-w32", hex(GPIOI_PIN8_MODE_HIGH_BIT), "0", + "-w32", hex(GPIOI_PIN8_PULL_LOW_BIT), "0", + "-w32", hex(GPIOI_PIN8_PULL_HIGH_BIT), "0", + "-w32", hex(GPIOE_PIN6_MODE_LOW_BIT), "0", + "-w32", hex(GPIOE_PIN6_MODE_HIGH_BIT), "0", + "-w32", hex(GPIOE_PIN6_PULL_LOW_BIT), "0", + "-w32", hex(GPIOE_PIN6_PULL_HIGH_BIT), "0", + ) + + +def require(condition, message): + if not condition: + raise TestFailure(message) + + +def crc16(data): + value = 0xFFFF + for byte in data: + value ^= byte + for _ in range(8): + if value & 1: + value = (value >> 1) ^ 0xA001 + else: + value >>= 1 + return value + + +def append_crc(payload): + checksum = crc16(payload) + return bytes(payload) + bytes((checksum & 0xFF, checksum >> 8)) + + +def split_u32(value): + value &= 0xFFFFFFFF + return [value & 0xFFFF, value >> 16] + + +def split_i32(value): + require(-(1 << 31) <= value < (1 << 31), "signed value is not int32") + return split_u32(value) + + +def join_u32(low_word, high_word): + return low_word | (high_word << 16) + + +def join_i32(low_word, high_word): + value = join_u32(low_word, high_word) + return value - (1 << 32) if value & 0x80000000 else value + + +def frequency_matches(actual_hz, requested_hz): + tolerance_hz = max(1, requested_hz // 1000) + return abs(actual_hz - requested_hz) <= tolerance_hz + + +@dataclasses.dataclass +class Status: + position: int + frequency_hz: int + state: int + segment: int + error: int + + +class RtuClient: + def __init__(self, port, baud, slave, timeout): + self.port_name = port + self.baud = baud + self.slave = slave + self.timeout = timeout + self.character_seconds = 11.0 / baud + if baud > 19200: + self.frame_gap_seconds = 0.00175 + else: + self.frame_gap_seconds = 3.5 * self.character_seconds + self.serial_port = None + self.last_request_finished = 0.0 + + def __enter__(self): + self.serial_port = serial.Serial( + port=self.port_name, + baudrate=self.baud, + bytesize=serial.EIGHTBITS, + parity=serial.PARITY_EVEN, + stopbits=serial.STOPBITS_ONE, + timeout=0, + write_timeout=1, + ) + self.serial_port.reset_input_buffer() + self.serial_port.reset_output_buffer() + return self + + def __exit__(self, exc_type, exc_value, traceback): + if self.serial_port is not None: + self.serial_port.close() + self.serial_port = None + + def _exchange(self, pdu): + request = append_crc(bytes((self.slave,)) + bytes(pdu)) + now = time.monotonic() + delay = self.frame_gap_seconds - (now - self.last_request_finished) + if delay > 0: + time.sleep(delay) + + self.serial_port.reset_input_buffer() + self.serial_port.write(request) + self.serial_port.flush() + + response = bytearray() + deadline = time.monotonic() + self.timeout + expected_length = None + while time.monotonic() < deadline: + waiting = self.serial_port.in_waiting + if waiting: + response.extend(self.serial_port.read(waiting)) + if len(response) >= 2 and response[1] == (pdu[0] | 0x80): + expected_length = 5 + elif pdu[0] == 0x03 and len(response) >= 3: + expected_length = response[2] + 5 + elif pdu[0] in (0x06, 0x10): + expected_length = 8 + if expected_length is not None and len(response) >= expected_length: + break + if not waiting: + time.sleep(0.0005) + + self.last_request_finished = time.monotonic() + require(response, "no Modbus response to %s" % request.hex(" ")) + require(expected_length is not None, "response header is incomplete") + require(len(response) == expected_length, + "incomplete or oversized Modbus response: %s" % response.hex(" ")) + require(len(response) >= 5, "short Modbus response: %s" % response.hex(" ")) + received_crc = response[-2] | (response[-1] << 8) + require( + received_crc == crc16(response[:-2]), + "bad response CRC: %s" % response.hex(" "), + ) + require(response[0] == self.slave, "response slave address mismatch") + + requested_function = pdu[0] + if response[1] == (requested_function | 0x80): + require(len(response) == 5, "invalid exception response length") + raise ModbusException(requested_function, response[2]) + require(response[1] == requested_function, "response function mismatch") + return bytes(response) + + def read_holding(self, address, quantity): + require(1 <= quantity <= 125, "FC03 quantity must be 1..125") + pdu = bytes( + ( + 0x03, + address >> 8, + address & 0xFF, + quantity >> 8, + quantity & 0xFF, + ) + ) + response = self._exchange(pdu) + byte_count = response[2] + require(byte_count == quantity * 2, "FC03 byte count mismatch") + require(len(response) == byte_count + 5, "FC03 response length mismatch") + return [ + (response[3 + index * 2] << 8) | response[4 + index * 2] + for index in range(quantity) + ] + + def write_single(self, address, value): + value &= 0xFFFF + pdu = bytes( + ( + 0x06, + address >> 8, + address & 0xFF, + value >> 8, + value & 0xFF, + ) + ) + response = self._exchange(pdu) + require(response[:6] == bytes((self.slave,)) + pdu, "FC06 echo mismatch") + + def write_multiple(self, address, values): + require(1 <= len(values) <= 123, "FC10 quantity must be 1..123") + data = bytearray() + for value in values: + value &= 0xFFFF + data.extend((value >> 8, value & 0xFF)) + quantity = len(values) + pdu = bytes( + ( + 0x10, + address >> 8, + address & 0xFF, + quantity >> 8, + quantity & 0xFF, + len(data), + ) + ) + bytes(data) + response = self._exchange(pdu) + expected = bytes( + ( + self.slave, + 0x10, + address >> 8, + address & 0xFF, + quantity >> 8, + quantity & 0xFF, + ) + ) + require(response[:6] == expected, "FC10 acknowledgement mismatch") + + +def read_status(client): + words = client.read_holding(STATUS_BASE, STATUS_WORDS) + return Status( + position=join_i32(words[0], words[1]), + frequency_hz=join_u32(words[2], words[3]), + state=words[4], + segment=words[5], + error=words[6], + ) + + +def wait_for_status(client, predicate, timeout, description): + deadline = time.monotonic() + timeout + last_status = None + while time.monotonic() < deadline: + last_status = read_status(client) + if last_status.state == STATUS_ERROR: + raise TestFailure( + "%s entered ERROR (code=%d)" % (description, last_status.error) + ) + if predicate(last_status): + return last_status + time.sleep(0.005) + raise TestFailure("timeout waiting for %s; last=%r" % (description, last_status)) + + +def wait_terminal(client, timeout): + status = wait_for_status( + client, + lambda item: item.state in TERMINAL_STATES, + timeout, + "terminal state", + ) + require(status.state != STATUS_ERROR, "motion ended in ERROR %d" % status.error) + return status + + +def expect_exception(operation, expected_code, label): + try: + operation() + except ModbusException as error: + require( + error.code == expected_code, + "%s expected exception 0x%02X, got 0x%02X" + % (label, expected_code, error.code), + ) + return + raise TestFailure("%s did not return a Modbus exception" % label) + + +def make_common( + position_mode=0, + segment_count=1, + start_segment=1, + send_mode=SEND_COMPLETE, + curve_mode=0, + default_hz=1000, + start_hz=500, + stop_hz=100, + acceleration_ms=0, + deceleration_ms=0, +): + words = [0] * COMMON_WORDS + words[0x00] = 0 + words[0x01] = 0 + words[0x02] = 0 + words[0x03] = 0 + words[0x04] = send_mode + words[0x05] = 0 + words[0x06] = 0 + words[0x07] = curve_mode + words[0x08] = position_mode + words[0x09] = segment_count + words[0x0A] = start_segment + words[0x0B:0x0D] = split_u32(default_hz) + words[0x0D:0x0F] = split_u32(start_hz) + words[0x0F] = 0 + words[0x10:0x12] = split_u32(stop_hz) + words[0x12] = acceleration_ms + words[0x13] = deceleration_ms + return words + + +def make_segment( + frequency_hz, + pulses, + wait_type=EXT_OR_COMPLETE, + wait_time_ms=0, + act_time_ms=0, + jump_segment=0, +): + return ( + split_u32(frequency_hz) + + split_i32(pulses) + + [wait_type, wait_time_ms, act_time_ms, jump_segment] + ) + + +def configure(client, common, segment_1, segment_2=None): + client.write_multiple(CONFIG_BASE, common) + client.write_multiple(SEGMENT_1_BASE, segment_1) + if segment_2 is not None: + client.write_multiple(SEGMENT_2_BASE, segment_2) + + +def restore_default_configuration(client): + common = make_common( + default_hz=1000, + start_hz=100, + stop_hz=100, + acceleration_ms=100, + deceleration_ms=100, + ) + common[0x05] = 10 + client.write_multiple(CONFIG_BASE, common) + for index in range(10): + base = SEGMENT_1_BASE + index * 0x10 + pulses = 1000 if index == 0 else 0 + client.write_multiple(base, make_segment(1000, pulses)) + + +def command(client, value): + client.write_single(CONTROL, value) + + +def clear_position(client): + command(client, COMMAND_CLEAR) + status = read_status(client) + require(status.state == STATUS_IDLE, "CLEAR did not enter IDLE") + require(status.position == 0, "CLEAR did not zero logical position") + + +def run_case(name, function): + started = time.monotonic() + print("RUN %s" % name) + function() + print("PASS %s (%.3fs)" % (name, time.monotonic() - started)) + + +def test_fixed_map_and_exceptions(client): + common = client.read_holding(CONFIG_BASE, COMMON_WORDS) + segment = client.read_holding(SEGMENT_1_BASE, SEGMENT_WORDS) + status = read_status(client) + control = client.read_holding(CONTROL, 1) + require(len(common) == COMMON_WORDS, "common map read failed") + require(len(segment) == SEGMENT_WORDS, "segment map read failed") + require(0 <= status.state <= STATUS_ERROR, "status enum is out of range") + require(control == [0], "control register must read as zero") + require(client.read_holding(0x100F, 1) == [0], "reserved word is not zero") + require(client.read_holding(0x1108, 1) == [0], "segment padding is not zero") + + expect_exception( + lambda: client.read_holding(0x0000, 1), + EX_ILLEGAL_ADDRESS, + "FC03 address 0", + ) + expect_exception( + lambda: client._exchange(bytes((0x01, 0x00, 0x00, 0x00, 0x01))), + EX_ILLEGAL_FUNCTION, + "FC01 unsupported function", + ) + expect_exception( + lambda: client.read_holding(0x1197, 2), + EX_ILLEGAL_ADDRESS, + "range crossing 0x1197", + ) + expect_exception( + lambda: client.write_single(0x100F, 1), + EX_ILLEGAL_VALUE, + "nonzero reserved write", + ) + expect_exception( + lambda: client.write_single(0x100B, 1000), + EX_ILLEGAL_ADDRESS, + "single half of a DWORD", + ) + expect_exception( + lambda: client.write_single(STATUS_BASE, 0), + EX_ILLEGAL_ADDRESS, + "status write", + ) + expect_exception( + lambda: client.write_single(CONTROL, 3), + EX_ILLEGAL_VALUE, + "combined control command", + ) + + +def test_positive_negative_and_absolute_zero(client): + clear_position(client) + common = make_common(position_mode=0, start_hz=500) + configure(client, common, make_segment(500, 25)) + command(client, COMMAND_START) + status = wait_terminal(client, 3.0) + require(status.state == STATUS_COMPLETED, "positive move did not complete") + require(status.position == 25, "positive accumulation expected 25") + + configure(client, common, make_segment(500, -10)) + command(client, COMMAND_START) + status = wait_terminal(client, 3.0) + require(status.state == STATUS_COMPLETED, "negative move did not complete") + require(status.position == 15, "negative accumulation expected 15") + + absolute = make_common(position_mode=1, start_hz=500) + configure(client, absolute, make_segment(500, 15)) + before = read_status(client) + command(client, COMMAND_START) + status = wait_terminal(client, 1.0) + require(status.state == STATUS_COMPLETED, "absolute zero move did not complete") + require(status.position == before.position, "absolute zero changed position") + + +def test_all_outputs_at_100khz(client): + for output in range(4): + clear_position(client) + common = make_common( + default_hz=100000, + start_hz=100000, + stop_hz=100000, + ) + common[0x00] = output + common[0x01] = output + configure(client, common, make_segment(100000, 1000)) + command(client, COMMAND_START) + status = wait_terminal(client, 2.0) + require(status.state == STATUS_COMPLETED, + "output %d did not complete" % output) + require(status.position == 1000, + "output %d count expected 1000, got %d" + % (output, status.position)) + + +def test_short_final_profiles(client): + for curve_mode in range(3): + clear_position(client) + common = make_common( + curve_mode=curve_mode, + default_hz=100000, + start_hz=100000, + stop_hz=100, + deceleration_ms=1000, + ) + configure(client, common, make_segment(100000, 10)) + command(client, COMMAND_START) + status = wait_terminal(client, 2.0) + require(status.state == STATUS_COMPLETED, + "short curve %d did not complete" % curve_mode) + require(status.error == ERROR_NONE, + "short curve %d reported error %d" + % (curve_mode, status.error)) + require(status.position == 10, + "short curve %d count expected 10, got %d" + % (curve_mode, status.position)) + + +def test_wait_time(client): + clear_position(client) + configure( + client, + make_common(start_hz=500), + make_segment(500, 10, wait_type=WAIT_TIME, wait_time_ms=120), + ) + command(client, COMMAND_START) + waiting = wait_for_status( + client, + lambda item: item.state == STATUS_WAITING, + 2.0, + "WAIT_TIME state", + ) + waiting_at = time.monotonic() + require(waiting.position == 10, "WAIT_TIME position expected 10") + status = wait_terminal(client, 2.0) + observed_wait = time.monotonic() - waiting_at + require(status.state == STATUS_COMPLETED, "WAIT_TIME did not complete") + require(observed_wait >= 0.050, "WAIT_TIME completed implausibly early") + + +def test_act_time(client): + clear_position(client) + configure( + client, + make_common(start_hz=1000), + make_segment(1000, 2000, wait_type=ACT_TIME, act_time_ms=80), + ) + command(client, COMMAND_START) + status = wait_terminal(client, 3.0) + require(status.state == STATUS_COMPLETED, "ACT_TIME did not complete") + require(0 < status.position < 2000, "ACT_TIME did not cut the segment") + + +def test_dynamic_frequency_and_repeated_stop(client): + clear_position(client) + configure( + client, + make_common(start_hz=500, deceleration_ms=150), + make_segment(500, 3000), + ) + command(client, COMMAND_START) + wait_for_status( + client, + lambda item: item.segment == 1 and item.frequency_hz == 500, + 2.0, + "initial frequency", + ) + client.write_multiple(SEGMENT_1_BASE, split_u32(1200)) + dynamic = wait_for_status( + client, + lambda item: item.segment == 1 + and frequency_matches(item.frequency_hz, 1200), + 2.0, + "dynamic frequency", + ) + print("INFO dynamic frequency requested=1200 actual=%d" % + dynamic.frequency_hz) + expect_exception( + lambda: client.write_single(CONFIG_BASE, 1), + EX_DEVICE_BUSY, + "pulse output write while busy", + ) + command(client, COMMAND_STOP) + command(client, COMMAND_STOP) + status = wait_terminal(client, 3.0) + require(status.state == STATUS_STOPPED, "repeated STOP did not stop") + require(status.position < 3000, "STOP did not cut the active move") + + +def test_future_segment_frequency_applies_on_arrival(client): + clear_position(client) + common = make_common(segment_count=2, start_hz=400) + segment_1 = make_segment(400, 200) + segment_2 = make_segment(700, 200) + configure(client, common, segment_1, segment_2) + command(client, COMMAND_START) + wait_for_status( + client, + lambda item: item.segment == 1 and item.frequency_hz == 400, + 2.0, + "segment 1", + ) + client.write_multiple(SEGMENT_2_BASE, split_u32(900)) + status = read_status(client) + require(status.segment == 1, "future write changed the current segment") + require(status.frequency_hz == 400, "future write changed current frequency") + + wait_for_status( + client, + lambda item: item.segment == 2 + and frequency_matches(item.frequency_hz, 900), + 3.0, + "updated segment 2 frequency", + ) + status = wait_terminal(client, 3.0) + require(status.state == STATUS_COMPLETED, "two-segment move did not complete") + + +def test_subsequent_future_frequency_rebuilds_handoff(client): + clear_position(client) + common = make_common( + segment_count=2, + send_mode=SEND_SUBSEQUENT, + start_hz=400, + ) + segment_1 = make_segment(400, 200) + segment_2 = make_segment(700, 200) + configure(client, common, segment_1, segment_2) + command(client, COMMAND_START) + wait_for_status( + client, + lambda item: item.segment == 1 + and frequency_matches(item.frequency_hz, 400), + 2.0, + "subsequent segment 1", + ) + client.write_multiple(SEGMENT_2_BASE, split_u32(900)) + status = read_status(client) + require(status.segment == 1, "future write changed the current segment") + require( + frequency_matches(status.frequency_hz, 400), + "future write changed current frequency", + ) + + wait_for_status( + client, + lambda item: item.segment == 2 + and frequency_matches(item.frequency_hz, 900), + 3.0, + "rebuilt subsequent handoff frequency", + ) + status = wait_terminal(client, 3.0) + require(status.state == STATUS_COMPLETED, + "subsequent two-segment move did not complete") + require(status.position == 400, + "subsequent two-segment position expected 400") + + +def require_fixture_level(fixture, input_selection, expected, label): + actual = fixture.read(input_selection) + require( + actual == expected, + "%s expected X%d=%d, got %d" + % (label, input_selection + 4, expected, actual), + ) + + +def test_x45_electrical_scan(fixture): + fixture.all_off() + require_fixture_level(fixture, 0, 0, "both outputs off") + require_fixture_level(fixture, 1, 0, "both outputs off") + for input_selection in range(2): + other = 1 - input_selection + fixture.drive(input_selection, True) + require_fixture_level(fixture, input_selection, 1, "output on") + require_fixture_level(fixture, other, 0, "cross-channel isolation") + fixture.drive(input_selection, False) + require_fixture_level(fixture, input_selection, 0, "output off") + + +def test_wait_signal_input(client, fixture, input_selection): + fixture.drive(input_selection, False) + clear_position(client) + common = make_common( + default_hz=1000, + start_hz=1000, + stop_hz=1000, + ) + common[0x02] = input_selection + configure(client, common, make_segment(1000, 50, wait_type=WAIT_SIGNAL)) + command(client, COMMAND_START) + waiting = wait_for_status( + client, + lambda item: item.state == STATUS_WAITING, + 2.0, + "X%d WAIT_SIGNAL" % (input_selection + 4), + ) + require(waiting.position == 50, "WAIT_SIGNAL pulse count expected 50") + time.sleep(0.050) + require(read_status(client).state == STATUS_WAITING, + "WAIT_SIGNAL advanced while input was off") + fixture.drive(input_selection, True) + status = wait_terminal(client, 2.0) + require(status.state == STATUS_COMPLETED, "WAIT_SIGNAL did not complete") + require(status.position == 50, "WAIT_SIGNAL changed completed position") + fixture.drive(input_selection, False) + + +def test_ext_signal_input(client, fixture, input_selection, wait_type): + fixture.drive(input_selection, False) + clear_position(client) + common = make_common( + default_hz=1000, + start_hz=1000, + stop_hz=1000, + ) + common[0x03] = input_selection + configure(client, common, make_segment(1000, 5000, wait_type=wait_type)) + command(client, COMMAND_START) + wait_for_status( + client, + lambda item: item.state in ACTIVE_STATES and item.position >= 10, + 2.0, + "X%d external-signal active move" % (input_selection + 4), + ) + fixture.drive(input_selection, True) + status = wait_terminal(client, 2.0) + require(status.state == STATUS_COMPLETED, + "external-signal move did not complete") + require(0 < status.position < 5000, + "external signal did not cut the active segment") + fixture.drive(input_selection, False) + + +def test_ext_or_complete_natural(client, fixture, input_selection): + fixture.drive(input_selection, False) + clear_position(client) + common = make_common( + default_hz=1000, + start_hz=1000, + stop_hz=1000, + ) + common[0x03] = input_selection + configure( + client, + common, + make_segment(1000, 50, wait_type=EXT_OR_COMPLETE), + ) + command(client, COMMAND_START) + status = wait_terminal(client, 2.0) + require(status.state == STATUS_COMPLETED, + "EXT_OR_COMPLETE natural branch did not complete") + require(status.position == 50, + "EXT_OR_COMPLETE natural branch expected 50 pulses") + + +def run_x45(client, fixture, keep_config): + snapshot = ( + client.read_holding(CONFIG_BASE, COMMON_WORDS), + client.read_holding(SEGMENT_1_BASE, SEGMENT_WORDS), + client.read_holding(SEGMENT_2_BASE, SEGMENT_WORDS), + ) + try: + fixture.prepare() + run_case("x45_electrical_scan", + lambda: test_x45_electrical_scan(fixture)) + for input_selection in range(2): + name = "X%d" % (input_selection + 4) + run_case( + "%s_wait_signal" % name, + lambda selection=input_selection: + test_wait_signal_input(client, fixture, selection), + ) + run_case( + "%s_ext_signal" % name, + lambda selection=input_selection: + test_ext_signal_input( + client, fixture, selection, EXT_SIGNAL), + ) + run_case( + "%s_ext_or_complete_natural" % name, + lambda selection=input_selection: + test_ext_or_complete_natural(client, fixture, selection), + ) + run_case( + "%s_ext_or_complete_trigger" % name, + lambda selection=input_selection: + test_ext_signal_input( + client, fixture, selection, EXT_OR_COMPLETE), + ) + finally: + try: + fixture.all_off() + finally: + try: + safe_stop(client) + if not keep_config: + restore_configuration(client, snapshot) + finally: + fixture.release() + + +def safe_stop(client): + try: + status = read_status(client) + if status.state in ACTIVE_STATES: + command(client, COMMAND_STOP) + wait_terminal(client, 3.0) + except (ModbusException, TestFailure, serial.SerialException): + pass + + +def restore_configuration(client, snapshot): + safe_stop(client) + command(client, COMMAND_CLEAR) + client.write_multiple(CONFIG_BASE, snapshot[0]) + client.write_multiple(SEGMENT_1_BASE, snapshot[1]) + client.write_multiple(SEGMENT_2_BASE, snapshot[2]) + + +def run_smoke(client): + run_case("fixed_map_and_exceptions", lambda: test_fixed_map_and_exceptions(client)) + + +def run_all(client, keep_config): + snapshot = ( + client.read_holding(CONFIG_BASE, COMMON_WORDS), + client.read_holding(SEGMENT_1_BASE, SEGMENT_WORDS), + client.read_holding(SEGMENT_2_BASE, SEGMENT_WORDS), + ) + try: + run_smoke(client) + run_case( + "positive_negative_absolute_zero", + lambda: test_positive_negative_and_absolute_zero(client), + ) + run_case("all_outputs_100khz", + lambda: test_all_outputs_at_100khz(client)) + run_case("short_final_profiles", + lambda: test_short_final_profiles(client)) + run_case("wait_time", lambda: test_wait_time(client)) + run_case("act_time", lambda: test_act_time(client)) + run_case( + "dynamic_frequency_repeated_stop", + lambda: test_dynamic_frequency_and_repeated_stop(client), + ) + run_case( + "future_segment_frequency_on_arrival", + lambda: test_future_segment_frequency_applies_on_arrival(client), + ) + run_case( + "subsequent_future_frequency_handoff", + lambda: test_subsequent_future_frequency_rebuilds_handoff(client), + ) + finally: + if keep_config: + safe_stop(client) + else: + restore_configuration(client, snapshot) + + +def persistence_prepare(client): + clear_position(client) + configure( + client, + make_common(start_hz=500), + make_segment(500, 7), + ) + command(client, COMMAND_START) + status = wait_terminal(client, 3.0) + require(status.state == STATUS_COMPLETED, "persistence move did not complete") + require(status.position == 7, "persistence position expected 7") + time.sleep(1.2) + require(read_status(client).position == 7, "position changed before reset") + print("PREPARED: release COM, reset/power-cycle the MCU, then run verify phase") + + +def persistence_verify(client, cleanup): + status = read_status(client) + require(status.state == STATUS_IDLE, "post-reset state must be IDLE") + require(status.position == 7, "post-reset position expected 7") + segment = client.read_holding(SEGMENT_1_BASE, SEGMENT_WORDS) + require(join_u32(segment[0], segment[1]) == 500, "frequency was not restored") + require(join_i32(segment[2], segment[3]) == 7, "pulse target was not restored") + if cleanup: + command(client, COMMAND_CLEAR) + require(read_status(client).position == 0, "cleanup CLEAR failed") + restore_default_configuration(client) + time.sleep(1.2) + common = client.read_holding(CONFIG_BASE, COMMON_WORDS) + segment = client.read_holding(SEGMENT_1_BASE, SEGMENT_WORDS) + require(join_u32(common[0x0B], common[0x0C]) == 1000, + "cleanup default speed was not restored") + require(join_u32(segment[0], segment[1]) == 1000, + "cleanup segment frequency was not restored") + require(join_i32(segment[2], segment[3]) == 1000, + "cleanup segment pulses were not restored") + + +def defaults_verify(client): + status = read_status(client) + require(status.state == STATUS_IDLE, "default state must be IDLE") + require(status.position == 0, "default position must be zero") + common = client.read_holding(CONFIG_BASE, COMMON_WORDS) + require(common[0x05] == 10, "default direction delay expected 10 ms") + require(join_u32(common[0x0B], common[0x0C]) == 1000, + "default speed expected 1000 Hz") + require(join_u32(common[0x0D], common[0x0E]) == 100, + "start speed expected 100 Hz") + require(join_u32(common[0x10], common[0x11]) == 100, + "stop speed expected 100 Hz") + for index in range(10): + base = SEGMENT_1_BASE + index * 0x10 + segment = client.read_holding(base, SEGMENT_WORDS) + expected_pulses = 1000 if index == 0 else 0 + require(join_u32(segment[0], segment[1]) == 1000, + "default segment %d frequency mismatch" % (index + 1)) + require(join_i32(segment[2], segment[3]) == expected_pulses, + "default segment %d pulse count mismatch" % (index + 1)) + + +def self_test(): + payload = bytes.fromhex("01 01 00 00 00 01") + require(crc16(payload) == 0xCAFD, "CRC reference vector failed") + require(append_crc(payload) == bytes.fromhex("01 01 00 00 00 01 FD CA"), + "wire CRC order failed") + for value in (0, 1, 0x7FFFFFFF, -1, -123456, -0x80000000): + words = split_i32(value) + require(join_i32(words[0], words[1]) == value, "int32 word round trip failed") + require(frequency_matches(1199, 1200), "frequency tolerance rejected 1 Hz") + require(not frequency_matches(1198, 1200), + "frequency tolerance accepted excessive error") + sample = "\n0x42408214 : 00000001 \n" + require(parse_stlink_word(sample, X4_INPUT_BIT) == 1, + "ST-LINK read parser failed") + print("Self-test passed; no serial port was opened") + + +def print_preflight(args): + print("No serial port opened. Board-test preconditions:") + print(" - Firmware containing the PLSR 0x1000/0x2000/0x3000 map is flashed.") + print(" - RS-485 is %d baud, 8E1, slave %d on %s." % + (args.baud, args.slave, args.port)) + print(" - Y0 pulse and Y12 direction outputs are safe to toggle.") + print(" - No other program has the COM port open.") + print("Run smoke only:") + print(" py -B HostComputer\\plsr_modbus_product_test.py --run --phase smoke") + print("Run motion matrix:") + print(" py -B HostComputer\\plsr_modbus_product_test.py --run --allow-motion") + print("Run X4/X5 loopback tests:") + print(" py -B HostComputer\\plsr_modbus_product_test.py --run --phase x45 --allow-motion") + print("Run offline checks:") + print(" py -B HostComputer\\plsr_modbus_product_test.py --self-test") + + +def parse_arguments(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--port", default="COM5") + parser.add_argument("--baud", type=int, default=9600) + parser.add_argument("--slave", type=int, default=1) + parser.add_argument("--timeout", type=float, default=1.5) + parser.add_argument("--stlink-cli", default=DEFAULT_STLINK_CLI) + parser.add_argument("--stlink-id", type=int, default=0) + parser.add_argument("--stlink-timeout", type=float, default=20.0) + parser.add_argument( + "--phase", + choices=("smoke", "all", "x45", "persistence-prepare", + "persistence-verify", "defaults-verify"), + default="all", + ) + parser.add_argument("--run", action="store_true", + help="open the serial port and execute the selected phase") + parser.add_argument("--allow-motion", action="store_true", + help="confirm that pulse and direction outputs may toggle") + parser.add_argument("--keep-config", action="store_true", + help="do not restore the three modified config blocks") + parser.add_argument("--cleanup", action="store_true", + help="CLEAR position after persistence verification") + parser.add_argument("--self-test", action="store_true", + help="run offline CRC/word tests without opening a port") + return parser.parse_args() + + +def main(): + args = parse_arguments() + require(1 <= args.slave <= 247, "slave must be 1..247") + require(args.baud > 0, "baud must be positive") + require(args.timeout > 0, "timeout must be positive") + require(args.stlink_id >= 0, "ST-LINK ID must not be negative") + require(args.stlink_timeout > 0, "ST-LINK timeout must be positive") + + if args.self_test: + self_test() + return 0 + if not args.run: + print_preflight(args) + return 0 + + motion_phase = args.phase in ("all", "x45", "persistence-prepare") + if motion_phase and not args.allow_motion: + raise TestFailure("motion phase requires --allow-motion") + + print("Opening %s at %d 8E1, slave %d" % + (args.port, args.baud, args.slave)) + with RtuClient(args.port, args.baud, args.slave, args.timeout) as client: + if args.phase == "smoke": + run_smoke(client) + elif args.phase == "all": + run_all(client, args.keep_config) + elif args.phase == "x45": + fixture = X45Fixture( + args.stlink_cli, + args.stlink_id, + args.stlink_timeout, + ) + run_x45(client, fixture, args.keep_config) + elif args.phase == "persistence-prepare": + persistence_prepare(client) + elif args.phase == "persistence-verify": + persistence_verify(client, args.cleanup) + else: + defaults_verify(client) + print("PASS phase=%s" % args.phase) + return 0 + + +if __name__ == "__main__": + try: + sys.exit(main()) + except (TestFailure, ModbusException, serial.SerialException) as error: + print("FAIL: %s" % error, file=sys.stderr) + sys.exit(1) diff --git a/Middlewares/Third_Party/Micrium/Config/app_cfg.h b/Middlewares/Third_Party/Micrium/Config/app_cfg.h index bd83e5b..7059bcf 100644 --- a/Middlewares/Third_Party/Micrium/Config/app_cfg.h +++ b/Middlewares/Third_Party/Micrium/Config/app_cfg.h @@ -15,4 +15,7 @@ #define APP_TASK_START_STK_SIZE 256u #define APP_TASK_TEST_STK_SIZE 256u +/* The short-profile pulse IRQ needs more than the port's 128-word default. */ +#define OS_CPU_EXCEPT_STK_SIZE 256u + #endif diff --git a/Modbus/Inc/modbus_rtu_slave.h b/Modbus/Inc/modbus_rtu_slave.h index 3e139df..7c38bad 100644 --- a/Modbus/Inc/modbus_rtu_slave.h +++ b/Modbus/Inc/modbus_rtu_slave.h @@ -3,145 +3,19 @@ #include "stm32f4xx_hal.h" #include + #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif -/** - * @brief Modbus 数据模型的最大地址 - * - * 任务要求规定线圈和保持寄存器地址范围均为 0~0x270F, - * 因此每种数据共有 10000 项 - */ -#define MODBUS_MAP_LAST_ADDRESS (0x270FU) // 保持寄存器或线圈地址 -#define MODBUS_MAP_ITEM_COUNT (0x2710U) // 保持寄存器或线圈数量 - #define MODBUS_SLAVE_DEFAULT_ADDRESS (1U) -#define MODBUS_CONNECTION_TIMEOUT_MS (1000U) - -/** - * @brief 与 TouchWin 触摸屏联调使用的演示地址 - * - * 可以直接访问以下地址 - */ -#define HMI_REG_DEVICE_ID (0U) -#define HMI_REG_UPTIME_SECONDS (1U) -#define HMI_REG_RX_FRAME_COUNT (2U) -#define D3 (3U) -#define D4 (4U) -#define D5 (5U) -#define D6 (6U) -#define D7 (7U) -#define HMI_REG_WRITE_TEST (10U) -#define HMI_COIL_WRITE_TEST (0U) - -#define MODBUS_RETAINED_D_START (100U) -#define MODBUS_RETAINED_D_END (120U) -#define MODBUS_RETAINED_D_COUNT (21U) - -#define MODBUS_BACKUP_MAGIC (0x44313030UL) -/** - * @brief 掉电保持寄存器在Backup SRAM中的存储格式 - */ -typedef struct -{ - uint32_t magic; ///< 掉电保持数据有效标志 - uint16_t retainedD[MODBUS_RETAINED_D_COUNT]; ///< D100~D120保持值 -} MODBUS_BACKUP_DATA; -/** - * @brief Modbus 从站通信统计信息 - */ -typedef struct -{ - uint32_t rxEventCount; ///< 串口接收事件总数 - uint32_t validFrameCount; ///< CRC 和从站地址均有效的帧数 - uint32_t txFrameCount; ///< 成功启动 DMA 发送的帧数 - uint32_t crcErrorCount; ///< CRC 校验错误帧数 - uint32_t ignoredAddressCount; ///< 因从站地址不匹配而忽略的帧数 - uint32_t illegalFunctionCount; ///< 非法功能码计数 - uint32_t illegalAddressCount; ///< 非法数据地址计数 - uint32_t illegalValueCount; ///< 非法数据值计数 - uint32_t droppedFrameCount; ///< 接收槽占用或长度错误导致的丢帧数 - uint32_t uartErrorCount; ///< HAL 串口错误计数 -} MODBUS_SLAVE_STATS; -/** @brief Modbus 从站通信统计数据 */ -extern volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; -/** - * @brief 初始化 Modbus 从站并启动 USART DMA 空闲接收 - * @param[in] huart 已由 HAL 初始化完成的串口句柄 - * @param[in] slaveAddress 从站地址,有效范围为 1~247 - * @retval HAL_OK 初始化成功且 DMA 接收已启动 - * @retval HAL_ERROR 输入参数无效 - * @retval HAL_BUSY 串口或 DMA 当前忙 - */ HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart, uint8_t slaveAddress); -/** - * @brief 校验并处理一帧待处理请求 - * - * 本函数应在 uC/OS-II 任务中周期调用中断只负责接收数据, - * CRC 校验、协议解析和响应发送均在任务上下文中执行 - */ void ModbusSlavePoll(void); -/** - * @brief 处理 HAL 串口 DMA 接收事件 - * @param[in] huart 产生接收事件的串口句柄 - * @param[in] size 本次接收到的字节数 - */ void ModbusSlaveOnRxEvent(UART_HandleTypeDef *huart, uint16_t size); -/** - * @brief 处理 HAL 串口 DMA 发送完成事件 - * @param[in] huart 完成发送的串口句柄 - */ void ModbusSlaveOnTxComplete(UART_HandleTypeDef *huart); -/** - * @brief 处理 HAL 串口错误事件并恢复接收 - * @param[in] huart 发生错误的串口句柄 - */ void ModbusSlaveOnUartError(UART_HandleTypeDef *huart); -/** - * @brief 写入一个保持寄存器 - * @param[in] address 保持寄存器地址 - * @param[in] value 待写入的 16 位数据 - * @retval 1 写入成功 - * @retval 0 保持寄存器地址无效 - */ -uint8_t ModbusSlaveSetHoldingRegister(uint16_t address, uint16_t value); -/** - * @brief 读取一个保持寄存器 - * @param[in] address 保持寄存器地址 - * @param[out] value 用于保存读取结果的指针 - * @retval 1 读取成功 - * @retval 0 保持寄存器地址无效或输出指针为空 - */ -uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *value); -/** - * @brief 设置一个线圈的状态 - * @param[in] address 线圈地址 - * @param[in] state 0 表示复位,非 0 表示置位 - * @retval 1 设置成功 - * @retval 0 线圈地址无效 - */ -uint8_t ModbusSlaveSetCoil(uint16_t address, uint8_t state); -/** - * @brief 读取一个线圈的状态 - * @param[in] address 线圈地址 - * @param[out] state 用于保存线圈状态的指针,结果为 0 或 1 - * @retval 1 读取成功 - * @retval 0 线圈地址无效或输出指针为空 - */ -uint8_t ModbusSlaveGetCoil(uint16_t address, uint8_t *state); -/** - * @brief 判断指定时间内是否收到过有效请求 - * @param[in] timeoutMs 连接超时时间,单位为毫秒 - * @retval 1 从站在指定时间内收到过有效请求 - * @retval 0 尚未收到有效请求或连接已经超时 - */ -uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs); -void ModbusRetainedRegistersLoad(void); -void ModbusRetainedRegistersPoll(void); #ifdef __cplusplus } diff --git a/Modbus/Src/modbus_rtu_slave.c b/Modbus/Src/modbus_rtu_slave.c index bf00866..e08adb0 100644 --- a/Modbus/Src/modbus_rtu_slave.c +++ b/Modbus/Src/modbus_rtu_slave.c @@ -1,77 +1,66 @@ #include "modbus_rtu_slave.h" +#include "plsr.h" #include -#define MODBUS_RTU_ADU_SIZE_MAX (256U) // Modbus RTU 最大 ADU 长度,单位为字节 +#define MODBUS_RTU_ADU_SIZE_MAX (256U) +#define MODBUS_RTU_BITS_PER_CHAR (11UL) +#define MODBUS_RTU_HIGH_BAUD_LIMIT (19200UL) +#define MODBUS_RTU_T15_US (750UL) +#define MODBUS_RTU_T35_US (1750UL) -#define MODBUS_RTU_T15_US (750UL) // 高波特率下固定T1.5时间,单位us -#define MODBUS_RTU_T35_US (1750UL) // 高波特率下固定T3.5时间,单位us -#define MODBUS_RTU_BITS_PER_CHAR (11UL) // 8E1包含11个传输位 -#define MODBUS_RTU_HIGH_BAUD_LIMIT (19200UL) // 高低波特率计算方式的分界值 +#define MODBUS_BROADCAST_ADDRESS (0U) +#define MODBUS_SLAVE_ADDRESS_MAX (247U) -#define MODBUS_BROADCAST_ADDRESS (0U) // 广播地址 +#define MODBUS_FC_READ_HOLDING (0x03U) +#define MODBUS_FC_WRITE_SINGLE (0x06U) +#define MODBUS_FC_WRITE_MULTIPLE (0x10U) -#define MODBUS_EX_ILLEGAL_FUNCTION (0x01U) // 非法功能码的异常码 -#define MODBUS_EX_ILLEGAL_ADDRESS (0x02U) // 非法地址的异常码 -#define MODBUS_EX_ILLEGAL_VALUE (0x03U) // 非法数据的异常码 +#define MODBUS_EX_ILLEGAL_FUNCTION (0x01U) +#define MODBUS_EX_ILLEGAL_ADDRESS (0x02U) +#define MODBUS_EX_ILLEGAL_VALUE (0x03U) +#define MODBUS_EX_SERVER_FAILURE (0x04U) +#define MODBUS_EX_SERVER_BUSY (0x06U) -#define MODBUS_READ_COILS_MAX (2000U) // 一次ADU最大线圈读取数量 -#define MODBUS_READ_REGS_MAX (125U) // 一次ADU最大寄存器读取数量 -#define MODBUS_WRITE_COILS_MAX (1968U) // 一次ADU最大线圈写入数量 -#define MODBUS_WRITE_REGS_MAX (123U) // 一次ADU最大寄存器写入数量 - -#define MODBUS_COIL_VALUE_ON (0xFF00U) -#define MODBUS_COIL_VALUE_OFF (0x0000U) +#define MODBUS_READ_REGISTERS_MAX (125U) +#define MODBUS_WRITE_REGISTERS_MAX (123U) static UART_HandleTypeDef *ModbusUart; static uint8_t ModbusSlaveAddress; -static HAL_StatusTypeDef ModbusStartReceive(void); -/*最近一次字节尾到字节头的间隔周期数*/ -static volatile uint32_t ModbusLastInterFrameGapCycles; -/** - * DMA 缓冲区只由 DMA 写入;帧缓冲区在接收回调中完成一次快照, - * 随后由任务解析,避免 DMA 重启后覆盖尚未处理的数据 - */ + static uint8_t ModbusRxDmaBuffer[MODBUS_RTU_ADU_SIZE_MAX]; -/* 保存被UART IDLE事件分开的DMA片段,达到T3.5后再提交解析 */ static uint8_t ModbusRxAssemblyBuffer[MODBUS_RTU_ADU_SIZE_MAX]; static uint8_t ModbusRxFrame[MODBUS_RTU_ADU_SIZE_MAX]; static uint8_t ModbusTxFrame[MODBUS_RTU_ADU_SIZE_MAX]; -/* D100~D120 上一次已保存的值,用于检测数据是否变化 */ -static uint16_t ModbusRetainedSnapshot[MODBUS_RETAINED_D_COUNT]; +static uint16_t ModbusRegisterScratch[MODBUS_READ_REGISTERS_MAX]; + +static volatile uint16_t ModbusRxAssemblyLength; +static volatile uint8_t ModbusRxAssemblyInvalid; +static volatile uint32_t ModbusRxLastByteCycle; static volatile uint16_t ModbusRxFrameLength; static volatile uint8_t ModbusRxFrameReady; static volatile uint8_t ModbusTxBusy; -static volatile uint16_t ModbusRxAssemblyLength; // 当前拼帧长度 -static volatile uint8_t ModbusRxAssemblyInvalid; // 帧内间隔超过T1.5时置1 -static volatile uint32_t ModbusRxLastByteCycle; // 上一片段末字节结束时刻 -static uint32_t ModbusRtuT15Cycles; // T1.5对应的CPU周期数 -static uint32_t ModbusRtuT35Cycles; // T3.5对应的CPU周期数 -static uint32_t ModbusRtuCharCycles; // 一个UART字符对应的CPU周期数 -static volatile uint32_t ModbusLastValidFrameTick; -static volatile uint8_t ModbusHasReceivedValidFrame; -static volatile MODBUS_BACKUP_DATA *ModbusBackupData = - (volatile MODBUS_BACKUP_DATA *)BKPSRAM_BASE; - -/** - * 10000 个保持寄存器占用 20000 字节;10000 个线圈按位存储, - * 占用 1250 字节 - */ -static uint16_t ModbusHoldingRegisters[40000]; - -#pragma location = ".ccmram" -#pragma data_alignment = 4 -__root static uint16_t ModbusRegistersCcm[29999]; - -static uint8_t ModbusCoils[(MODBUS_MAP_ITEM_COUNT + 7U) / 8U]; - -volatile MODBUS_SLAVE_STATS ModbusSlaveStatistics; - -/** - * @brief 计算 Modbus RTU CRC16 校验值 - * @param[in] data 待校验数据 - * @param[in] length 待校验数据长度 - * @return CRC16 校验值 - */ + +static uint32_t ModbusRtuCharCycles; +static uint32_t ModbusRtuT15Cycles; +static uint32_t ModbusRtuT35Cycles; + +static HAL_StatusTypeDef ModbusStartReceive(void); + +static uint32_t ModbusEnterCritical(void) +{ + uint32_t primask = __get_PRIMASK(); + + __disable_irq(); + __DMB(); + return primask; +} + +static void ModbusExitCritical(uint32_t primask) +{ + __DMB(); + __set_PRIMASK(primask); +} + static uint16_t ModbusCrc16(const uint8_t *data, uint16_t length) { uint16_t crc = 0xFFFFU; @@ -81,10 +70,9 @@ static uint16_t ModbusCrc16(const uint8_t *data, uint16_t length) for (index = 0U; index < length; index++) { crc ^= data[index]; - for (bit = 0U; bit < 8U; bit++) { - if ((crc & 0x0001U) != 0U) + if ((crc & 1U) != 0U) { crc = (uint16_t)((crc >> 1U) ^ 0xA001U); } @@ -94,107 +82,39 @@ static uint16_t ModbusCrc16(const uint8_t *data, uint16_t length) } } } - return crc; } -/** - * @brief 提取一个 16 位无符号整数 - * @param[in] data 两个字节的数据地址 - * @return 转换后的 16 位无符号整数 - */ static uint16_t ModbusGetU16Be(const uint8_t *data) { return (uint16_t)(((uint16_t)data[0] << 8U) | data[1]); } -/** - * @brief 提取一个 24 位无符号整数 - * @param[in] data 3个字节的数据地址 - * @return 转换后的 24 位无符号整数 - */ -static uint32_t ModbusGetU24Be(const uint8_t *data) -{ - return (uint32_t)(((uint32_t)data[0] << 16U) | ((uint32_t)data[1] << 8U) - | (uint32_t)data[2]); -} - -/** - * @brief 检查连续数据地址范围是否合法 - * @param[in] start 起始地址 - * @param[in] quantity 数据项数量 - */ -static uint8_t ModbusAddressRangeIsValid(uint16_t start, uint16_t quantity) -{ - if (start >= MODBUS_MAP_ITEM_COUNT) - { - return 0U; - } - - // 先做减法再比较,避免溢出 - return (quantity <= (MODBUS_MAP_ITEM_COUNT - start)) ? 1U : 0U; -} - -/** - * @brief 读取已经确认地址合法的线圈 - * @param[in] address 线圈地址 - * @return 线圈状态,取值为 0 或 1 - */ -static uint8_t ModbusCoilGetUnchecked(uint16_t address) -{ - uint8_t mask = (uint8_t)(1U << (address & 0x0007U)); - - return ((ModbusCoils[address >> 3U] & mask) != 0U) ? 1U : 0U; -} - -/** - * @brief 设置已经确认地址合法的线圈 - * @param[in] address 线圈地址 - * @param[in] state 0 表示复位,非 0 表示置位 - */ -static void ModbusCoilSetUnchecked(uint16_t address, uint8_t state) -{ - uint8_t mask = (uint8_t)(1U << (address & 0x0007U)); - - if (state != 0U) - { - ModbusCoils[address >> 3U] |= mask; - } - else - { - ModbusCoils[address >> 3U] &= (uint8_t)(~mask); - } -} - -/** - * @brief 初始化用于RTU帧间隔测量的DWT周期计数器 - * @param[in] baudRate 当前串口波特率 - */ static void ModbusRtuTimingInit(uint32_t baudRate) { - uint64_t coreClock; - /* 开启DWT周期计数器,CYCCNT每经过一个CPU时钟周期自动加1 */ + uint64_t coreClock = SystemCoreClock; + uint64_t charCycleNumerator = coreClock * MODBUS_RTU_BITS_PER_CHAR; + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0U; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; - coreClock = SystemCoreClock; - /* 一个字符周期数=CPU频率*每字符位数/波特率 */ ModbusRtuCharCycles = - (uint32_t)((coreClock * MODBUS_RTU_BITS_PER_CHAR) / baudRate); + (uint32_t)((charCycleNumerator + baudRate - 1UL) / baudRate); + if (ModbusRtuCharCycles == 0UL) + { + ModbusRtuCharCycles = 1UL; + } if (baudRate > MODBUS_RTU_HIGH_BAUD_LIMIT) { - /* 高波特率使用固定750us和1750us,999999用于向上取整 */ - ModbusRtuT15Cycles = - (uint32_t)((coreClock * MODBUS_RTU_T15_US + 999999UL) / 1000000UL); - - ModbusRtuT35Cycles = - (uint32_t)((coreClock * MODBUS_RTU_T35_US + 999999UL) / 1000000UL); + ModbusRtuT15Cycles = (uint32_t)( + (coreClock * MODBUS_RTU_T15_US + 999999UL) / 1000000UL); + ModbusRtuT35Cycles = (uint32_t)( + (coreClock * MODBUS_RTU_T35_US + 999999UL) / 1000000UL); } else { - /* 低波特率按照1.5个字符时间和3.5个字符时间计算 */ ModbusRtuT15Cycles = (uint32_t)(((uint64_t)ModbusRtuCharCycles * 3UL + 1UL) / 2UL); ModbusRtuT35Cycles = @@ -202,50 +122,57 @@ static void ModbusRtuTimingInit(uint32_t baudRate) } } -/** - * @brief 结束当前RTU接收组帧 - * @note 调用本函数时必须保证不会与串口接收中断并发执行 - */ static void ModbusRxAssemblyFinalize(void) { - if (ModbusRxAssemblyLength == 0U) + uint16_t length = ModbusRxAssemblyLength; + + if (length == 0U) { return; } - /*拼帧区是否有数据*/ + if ((ModbusRxAssemblyInvalid == 0U) && (ModbusRxFrameReady == 0U)) { - /* 当前帧未违反T1.5且解析缓冲区空闲时提交完整帧 */ - (void)memcpy(ModbusRxFrame, ModbusRxAssemblyBuffer, - ModbusRxAssemblyLength); - ModbusRxFrameLength = ModbusRxAssemblyLength; + (void)memcpy(ModbusRxFrame, ModbusRxAssemblyBuffer, length); + ModbusRxFrameLength = length; ModbusRxFrameReady = 1U; } - else - { - /* T1.5无效帧或上一帧尚未处理完成时丢弃 */ - ModbusSlaveStatistics.droppedFrameCount++; - } ModbusRxAssemblyLength = 0U; ModbusRxAssemblyInvalid = 0U; } -/** - * @brief 静默时间达到T3.5后,将接收数据交给协议解析任务 - */ +static HAL_StatusTypeDef ModbusStartReceive(void) +{ + HAL_StatusTypeDef status; + + if ((ModbusUart == NULL) || (ModbusTxBusy != 0U)) + { + return HAL_BUSY; + } + + status = HAL_UARTEx_ReceiveToIdle_DMA(ModbusUart, + ModbusRxDmaBuffer, + sizeof(ModbusRxDmaBuffer)); + if (status == HAL_OK) + { + __HAL_DMA_DISABLE_IT(ModbusUart->hdmarx, DMA_IT_HT); + } + return status; +} + static void ModbusTryFinalizeReceive(void) { uint32_t now; + uint32_t primask; + uint8_t restartReceive; - if (ModbusRxAssemblyLength == 0U) + if ((ModbusUart == NULL) || (ModbusRxAssemblyLength == 0U)) { return; } - /* DMA缓冲区出现新数据时,说明串口仍在接收当前片段 */ - if ((ModbusUart->hdmarx != NULL) && /*串口DMA使能*/ - ((ModbusUart->Instance->CR3 & USART_CR3_DMAR) != 0U) + if (((ModbusUart->Instance->CR3 & USART_CR3_DMAR) != 0U) && (__HAL_DMA_GET_COUNTER(ModbusUart->hdmarx) < MODBUS_RTU_ADU_SIZE_MAX)) { @@ -253,427 +180,193 @@ static void ModbusTryFinalizeReceive(void) } now = DWT->CYCCNT; - /* 从末字节结束时刻开始计算静默时间,未达到T3.5时继续等待 */ if ((uint32_t)(now - ModbusRxLastByteCycle) < ModbusRtuT35Cycles) { return; } - /* 静默达到T3.5,当前RTU帧结束,发送响应前停止接收DMA */ - (void)HAL_UART_AbortReceive(ModbusUart); - - __disable_irq(); - ModbusRxAssemblyFinalize(); - __enable_irq(); -/* 无效帧被丢弃后,重新启动DMA接收。 */ - if (ModbusRxFrameReady == 0U) + if (HAL_UART_AbortReceive(ModbusUart) != HAL_OK) { + primask = ModbusEnterCritical(); + ModbusRxAssemblyLength = 0U; + ModbusRxAssemblyInvalid = 0U; + ModbusExitCritical(primask); + (void)HAL_UART_Abort(ModbusUart); (void)ModbusStartReceive(); + return; } + primask = ModbusEnterCritical(); + ModbusRxAssemblyFinalize(); + restartReceive = (ModbusRxFrameReady == 0U) ? 1U : 0U; + ModbusExitCritical(primask); -} - -/** - * @brief 启动 USART DMA 空闲接收 - * @retval HAL_OK DMA 接收启动成功 - * @retval HAL_BUSY 串口未配置或发送尚未结束 - * @return 其他 HAL 状态表示 DMA 接收启动失败 - */ -static HAL_StatusTypeDef ModbusStartReceive(void) -{ - HAL_StatusTypeDef status; - - if (ModbusTxBusy != 0U) - { - return HAL_BUSY; - } - - status = HAL_UARTEx_ReceiveToIdle_DMA(ModbusUart, ModbusRxDmaBuffer, - sizeof(ModbusRxDmaBuffer)); - - if ((status == HAL_OK) && (ModbusUart->hdmarx != NULL)) + if (restartReceive != 0U) { - /* - * 普通 Modbus 帧只应在 IDLE 或缓冲区满时交给应用 - * 关闭 DMA 半传输中断,避免长帧在一半位置被误认为完整帧 - */ - __HAL_DMA_DISABLE_IT(ModbusUart->hdmarx, DMA_IT_HT); + (void)ModbusStartReceive(); } - - return status; } -/** - * @brief 在 RTU 帧末尾追加 CRC 低字节和高字节 - * @param[in,out] frame 待追加 CRC 的帧缓冲区 - * @param[in] payloadLength 不包含 CRC 的有效载荷长度 - */ static void ModbusAppendCrc(uint8_t *frame, uint16_t payloadLength) { uint16_t crc = ModbusCrc16(frame, payloadLength); - /* Modbus RTU 在线路上传输 CRC 低字节在前、高字节在后 */ frame[payloadLength] = (uint8_t)(crc & 0x00FFU); frame[payloadLength + 1U] = (uint8_t)(crc >> 8U); } -/** - * @brief 构造 Modbus 异常响应 - * @param[in] function 请求功能码 - * @param[in] exception 异常码 - * @return 异常响应 ADU 长度 - */ static uint16_t ModbusBuildException(uint8_t function, uint8_t exception) { ModbusTxFrame[0] = ModbusSlaveAddress; ModbusTxFrame[1] = (uint8_t)(function | 0x80U); ModbusTxFrame[2] = exception; ModbusAppendCrc(ModbusTxFrame, 3U); - return 5U; } -/** - * @brief 处理读线圈功能码 0x01 - * @param[in] request RTU 请求帧 - * @param[in] requestLength 请求帧长度 - * @return 待发送响应长度,异常请求返回异常响应长度 - */ -static uint16_t ModbusProcessReadCoils(const uint8_t *request, - uint16_t requestLength) +static uint16_t ModbusBuildPlsrException(uint8_t function, + PLSR_MB_RESULT result, + uint8_t isBroadcast) { + uint8_t exception; - uint16_t start; - uint16_t quantity; - uint16_t index; - uint8_t byteCount; - - if (requestLength != 8U) - { - ModbusSlaveStatistics.illegalValueCount++; - return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - } - - start = ModbusGetU16Be(&request[2]); - quantity = ModbusGetU16Be(&request[4]); - - if ((quantity == 0U) - || (quantity > MODBUS_READ_COILS_MAX)) // 数量0或者数量大于最大值 + if (isBroadcast != 0U) { - ModbusSlaveStatistics.illegalValueCount++; - return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); + return 0U; } - if (ModbusAddressRangeIsValid(start, quantity) == 0U) // 地址检查 + switch (result) { - ModbusSlaveStatistics.illegalAddressCount++; - return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); + case PLSR_MB_NOT_HANDLED: + case PLSR_MB_ILLEGAL_ADDRESS: + exception = MODBUS_EX_ILLEGAL_ADDRESS; + break; + case PLSR_MB_ILLEGAL_VALUE: + exception = MODBUS_EX_ILLEGAL_VALUE; + break; + case PLSR_MB_DEVICE_BUSY: + exception = MODBUS_EX_SERVER_BUSY; + break; + case PLSR_MB_SERVER_FAILURE: + default: + exception = MODBUS_EX_SERVER_FAILURE; + break; } - - byteCount = (uint8_t)((quantity + 7U) / 8U); - ModbusTxFrame[0] = ModbusSlaveAddress; - ModbusTxFrame[1] = 0X01; - ModbusTxFrame[2] = byteCount; - (void)memset(&ModbusTxFrame[3], 0, byteCount); - - for (index = 0U; index < quantity; index++) - { - if (ModbusCoilGetUnchecked((uint16_t)(start + index)) != 0U) - { - ModbusTxFrame[3U + (index >> 3U)] |= - (uint8_t)(1U << (index & 0x0007U)); - } - } - - ModbusAppendCrc(ModbusTxFrame, (uint16_t)(3U + byteCount)); - return (uint16_t)(5U + byteCount); + return ModbusBuildException(function, exception); } -/** - * @brief 处理读保持寄存器功能码 0x03 - * @param[in] request RTU 请求帧 - * @param[in] requestLength 请求帧长度 - * @return 待发送响应长度,异常请求返回异常响应长度 - */ static uint16_t ModbusProcessReadHolding(const uint8_t *request, uint16_t requestLength) - { uint16_t start; uint16_t quantity; uint16_t index; - uint16_t value; + PLSR_MB_RESULT result; if (requestLength != 8U) { - ModbusSlaveStatistics.illegalValueCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } start = ModbusGetU16Be(&request[2]); quantity = ModbusGetU16Be(&request[4]); - - if ((quantity == 0U) - || (quantity > MODBUS_READ_REGS_MAX)) // 数量0或者数量大于最大值 + if ((quantity == 0U) || (quantity > MODBUS_READ_REGISTERS_MAX)) { - ModbusSlaveStatistics.illegalValueCount++; return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } - if (ModbusAddressRangeIsValid(start, quantity) == 0U) // 地址检查 + result = PlsrModbusReadHolding(start, quantity, ModbusRegisterScratch); + if (result != PLSR_MB_OK) { - if ((start >= 20000U) && (start < 25000U) && (quantity > 0U) - && (quantity <= (25000U - start))) - goto tx; - ModbusSlaveStatistics.illegalAddressCount++; - return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); + return ModbusBuildPlsrException(request[1], result, 0U); } -tx: + ModbusTxFrame[0] = ModbusSlaveAddress; - ModbusTxFrame[1] = 0X03; + ModbusTxFrame[1] = MODBUS_FC_READ_HOLDING; ModbusTxFrame[2] = (uint8_t)(quantity * 2U); - for (index = 0U; index < quantity; index++) { - uint16_t address = start + index; - /* D20000~D24999映射到D1、D3、D5……D9999 */ - if (address >= 20000U) - { - address = (address - 20000U) * 2U + 1U; - } - value = ModbusHoldingRegisters[address]; - ModbusTxFrame[3U + index * 2U] = (uint8_t)(value >> 8U); - ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU); + ModbusTxFrame[3U + index * 2U] = + (uint8_t)(ModbusRegisterScratch[index] >> 8U); + ModbusTxFrame[4U + index * 2U] = + (uint8_t)(ModbusRegisterScratch[index] & 0x00FFU); } - ModbusAppendCrc(ModbusTxFrame, (uint16_t)(3U + quantity * 2U)); return (uint16_t)(5U + quantity * 2U); } -/** - * @brief 处理写单个保持寄存器功能码 0x06 - * @param[in] request RTU 请求帧 - * @param[in] requestLength 请求帧长度 - * @param[in] isBroadcast 非 0 表示当前请求为广播 - * @return 单播响应长度;广播或无法响应时返回 0 - */ -static uint16_t ModbusProcessWriteSingleRegister(const uint8_t *request, - uint16_t requestLength, - uint8_t isBroadcast) +static uint16_t ModbusProcessWriteSingle(const uint8_t *request, + uint16_t requestLength, + uint8_t isBroadcast) { uint16_t address; uint16_t value; + PLSR_MB_RESULT result; if (requestLength != 8U) { - ModbusSlaveStatistics.illegalValueCount++; - return (isBroadcast != 0U) ? 0U: ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - - + return (isBroadcast != 0U) + ? 0U + : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } address = ModbusGetU16Be(&request[2]); value = ModbusGetU16Be(&request[4]); - - if (address >= MODBUS_MAP_ITEM_COUNT) + result = PlsrModbusWriteHolding(address, 1U, &value); + if (result != PLSR_MB_OK) { - ModbusSlaveStatistics.illegalAddressCount++; - return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); - - + return ModbusBuildPlsrException(request[1], result, isBroadcast); } - ModbusHoldingRegisters[address] = value; - if (isBroadcast != 0U) { return 0U; } - /* 0x06 的正常响应必须原样回显请求的前 6 个字节 */ (void)memcpy(ModbusTxFrame, request, 6U); ModbusAppendCrc(ModbusTxFrame, 6U); return 8U; } -/** - * @brief 处理写单个线圈功能码 0x05 - * @param[in] request RTU 请求帧 - * @param[in] requestLength 请求帧长度 - * @param[in] isBroadcast 非 0 表示当前请求为广播 - * @return 单播响应长度;广播或无法响应时返回 0 - */ -static uint16_t ModbusProcessWriteSingleCoil(const uint8_t *request, - uint16_t requestLength, - uint8_t isBroadcast) -{ - uint16_t address; - uint16_t value; - - if (requestLength != 8U) - { - ModbusSlaveStatistics.illegalValueCount++; - return (isBroadcast != 0U)? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - - - } - - address = ModbusGetU16Be(&request[2]); - value = ModbusGetU16Be(&request[4]); - - /* - * 0x05 只接受 0xFF00(线圈置位)和 0x0000(线圈复位); - * 其他数值属于非法数据值 - */ - if ((value != MODBUS_COIL_VALUE_ON) && (value != MODBUS_COIL_VALUE_OFF)) - { - ModbusSlaveStatistics.illegalValueCount++; - return (isBroadcast != 0U)? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - - - } - - if (address >= MODBUS_MAP_ITEM_COUNT) - { - ModbusSlaveStatistics.illegalAddressCount++; - return (isBroadcast != 0U) ? 0U: ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); - - - } - - ModbusCoilSetUnchecked(address, (value == MODBUS_COIL_VALUE_ON) ? 1U : 0U); - - if (isBroadcast != 0U) - { - return 0U; - } - - /* 0x05 正常应答原样回显请求的前 6 个字节,再追加 CRC */ - (void)memcpy(ModbusTxFrame, request, 6U); - ModbusAppendCrc(ModbusTxFrame, 6U); - return 8U; -} - -/** - * @brief 处理写多个线圈功能码 0x0F - * @param[in] request RTU 请求帧 - * @param[in] requestLength 请求帧长度 - * @param[in] isBroadcast 非 0 表示当前请求为广播 - * @return 单播响应长度;广播或无法响应时返回 0 - */ -static uint16_t ModbusProcessWriteMultipleCoils(const uint8_t *request, - uint16_t requestLength, - uint8_t isBroadcast) +static uint16_t ModbusProcessWriteMultiple(const uint8_t *request, + uint16_t requestLength, + uint8_t isBroadcast) { uint16_t start; uint16_t quantity; + uint16_t byteCount; uint16_t index; - uint8_t byteCount; - uint8_t expectedByteCount; + PLSR_MB_RESULT result; if (requestLength < 9U) { - ModbusSlaveStatistics.illegalValueCount++; - return (isBroadcast != 0U) ? 0U: ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - - + return (isBroadcast != 0U) + ? 0U + : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } start = ModbusGetU16Be(&request[2]); quantity = ModbusGetU16Be(&request[4]); byteCount = request[6]; - expectedByteCount = (uint8_t)((quantity + 7U) / 8U); - - if ((quantity == 0U) || (quantity > MODBUS_WRITE_COILS_MAX) - || (byteCount != expectedByteCount) + if ((quantity == 0U) + || (quantity > MODBUS_WRITE_REGISTERS_MAX) + || (byteCount != (uint16_t)(quantity * 2U)) || (requestLength != (uint16_t)(9U + byteCount))) { - ModbusSlaveStatistics.illegalValueCount++; - return (isBroadcast != 0U)? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - - - } - - if (ModbusAddressRangeIsValid(start, quantity) == 0U) - { - ModbusSlaveStatistics.illegalAddressCount++; - return (isBroadcast != 0U) ? 0U: ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); - - + return (isBroadcast != 0U) + ? 0U + : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); } for (index = 0U; index < quantity; index++) { - uint8_t state; - - state = (uint8_t)((request[7U + (index >> 3U)] >> (index & 0x0007U)) & 0x01U); - - ModbusCoilSetUnchecked((uint16_t)(start + index), state); - } - - if (isBroadcast != 0U) - { - return 0U; - } - - ModbusTxFrame[0] = ModbusSlaveAddress; - ModbusTxFrame[1] = 0X0F; - (void)memcpy(&ModbusTxFrame[2], &request[2], 4U); - ModbusAppendCrc(ModbusTxFrame, 6U); - return 8U; -} - -/** - * @brief 处理写多个保持寄存器功能码 0x10 - * @param[in] request RTU 请求帧 - * @param[in] requestLength 请求帧长度 - * @param[in] isBroadcast 非 0 表示当前请求为广播 - * @return 单播响应长度;广播或无法响应时返回 0 - */ -static uint16_t ModbusProcessWriteMultipleRegisters(const uint8_t *request, - uint16_t requestLength, - uint8_t isBroadcast) -{ - uint16_t start; - uint16_t quantity; - uint16_t index; - uint8_t byteCount; - - if (requestLength < 9U) - { - ModbusSlaveStatistics.illegalValueCount++; - return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - - - } - - start = ModbusGetU16Be(&request[2]); - quantity = ModbusGetU16Be(&request[4]); - byteCount = request[6]; - - if ((quantity == 0U) || (quantity > MODBUS_WRITE_REGS_MAX) - || (byteCount != (uint8_t)(quantity * 2U)) - || (requestLength != (uint16_t)(9U + byteCount))) - { - ModbusSlaveStatistics.illegalValueCount++; - return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - - - } - - if (ModbusAddressRangeIsValid(start, quantity) == 0U) - { - ModbusSlaveStatistics.illegalAddressCount++; - return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); - - + ModbusRegisterScratch[index] = + ModbusGetU16Be(&request[7U + index * 2U]); } - - for (index = 0U; index < quantity; index++) + result = PlsrModbusWriteHolding(start, quantity, ModbusRegisterScratch); + if (result != PLSR_MB_OK) { - ModbusHoldingRegisters[start + index] = - ModbusGetU16Be(&request[7U + index * 2U]); + return ModbusBuildPlsrException(request[1], result, isBroadcast); } if (isBroadcast != 0U) @@ -682,101 +375,15 @@ static uint16_t ModbusProcessWriteMultipleRegisters(const uint8_t *request, } ModbusTxFrame[0] = ModbusSlaveAddress; - ModbusTxFrame[1] = 0X10; + ModbusTxFrame[1] = MODBUS_FC_WRITE_MULTIPLE; (void)memcpy(&ModbusTxFrame[2], &request[2], 4U); ModbusAppendCrc(ModbusTxFrame, 6U); return 8U; } -/** - * @brief 处理读取扩展地址保持寄存器功能码0x48 - * @param[in] request RTU请求帧 - * @param[in] requestLength 请求帧长度 - * @return 待发送响应长度,异常请求返回异常响应长度 - */ -static uint16_t ModbusProcessReadBigHolding(const uint8_t *request, - uint16_t requestLength) -{ - uint32_t start; - uint32_t currentAddress; - uint16_t quantity; - uint16_t index; - uint16_t value; - - /* 请求帧:站号1 + 功能码1 + 地址3 + 数量2 + CRC2 */ - if (requestLength != 9U) - { - ModbusSlaveStatistics.illegalValueCount++; - - return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - } - - /* 提取24位起始地址和16位寄存器数量 */ - start = ModbusGetU24Be(&request[2]); - quantity = ModbusGetU16Be(&request[5]); - - /* 一次最多读取125个寄存器 */ - if ((quantity == 0U) || (quantity > MODBUS_READ_REGS_MAX)) - { - ModbusSlaveStatistics.illegalValueCount++; - - return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_VALUE); - } - - /* - * 普通SRAM有40000个寄存器,CCMRAM有29999个寄存器, - * 总地址范围为0~69998 - */ - if ((start >= 69999UL) || ((uint32_t)quantity > (69999UL - start))) - - { - ModbusSlaveStatistics.illegalAddressCount++; - - return ModbusBuildException(request[1], MODBUS_EX_ILLEGAL_ADDRESS); - } - - /* 组成正常响应帧头 */ - ModbusTxFrame[0] = ModbusSlaveAddress; - ModbusTxFrame[1] = 0x48U; - ModbusTxFrame[2] = (uint8_t)(quantity * 2U); - - for (index = 0U; index < quantity; index++) - { - currentAddress = start + (uint32_t)index; - - /* - * 地址0~39999位于普通SRAM; - * 地址40000~69998位于CCMRAM - */ - if (currentAddress < 40000UL) - { - value = ModbusHoldingRegisters[currentAddress]; - } - else - { - value = ModbusRegistersCcm[currentAddress - 40000UL]; - } - - /* 每个寄存器按照高字节、低字节装入响应帧 */ - ModbusTxFrame[3U + index * 2U] = (uint8_t)(value >> 8U); - ModbusTxFrame[4U + index * 2U] = (uint8_t)(value & 0x00FFU); - } - /* 添加CRC */ - ModbusAppendCrc(ModbusTxFrame, (uint16_t)(3U + quantity * 2U)); - - return (uint16_t)(5U + quantity * 2U); -} - -/** - * @brief 校验并分发一帧 Modbus RTU 请求 - * @param[in] request RTU 请求帧 - * @param[in] requestLength 请求帧长度 - * @return 待发送响应长度;无需响应时返回 0 - */ static uint16_t ModbusProcessRequest(const uint8_t *request, uint16_t requestLength) { - uint16_t calculatedCrc; uint16_t receivedCrc; uint8_t isBroadcast; @@ -785,64 +392,35 @@ static uint16_t ModbusProcessRequest(const uint8_t *request, return 0U; } - calculatedCrc = ModbusCrc16(request, (uint16_t)(requestLength - 2U)); receivedCrc = (uint16_t)(request[requestLength - 2U] - | ((uint16_t)request[requestLength - 1U] << 8U)); - - if (calculatedCrc != receivedCrc) // CRC校验 + | ((uint16_t)request[requestLength - 1U] << 8U)); + if (ModbusCrc16(request, (uint16_t)(requestLength - 2U)) != receivedCrc) { - ModbusSlaveStatistics.crcErrorCount++; return 0U; } if ((request[0] != ModbusSlaveAddress) - && (request[0] != MODBUS_BROADCAST_ADDRESS)) // 非法从站地址 + && (request[0] != MODBUS_BROADCAST_ADDRESS)) { - ModbusSlaveStatistics.ignoredAddressCount++; return 0U; } - isBroadcast = - (request[0] == MODBUS_BROADCAST_ADDRESS) ? 1U : 0U; // 是否广播请求 - ModbusSlaveStatistics.validFrameCount++; - ModbusLastValidFrameTick = HAL_GetTick(); - ModbusHasReceivedValidFrame = 1U; - + isBroadcast = (request[0] == MODBUS_BROADCAST_ADDRESS) ? 1U : 0U; switch (request[1]) { - case 0X01U: // 读线圈 - // 广播请求不允许读取,从站不作响应 - return (isBroadcast != 0U) - ? 0U - : ModbusProcessReadCoils(request, requestLength); - - case 0X03U: // 读保持寄存器 + case MODBUS_FC_READ_HOLDING: return (isBroadcast != 0U) ? 0U : ModbusProcessReadHolding(request, requestLength); - case 0x05U: // 写单个线圈 - return ModbusProcessWriteSingleCoil(request, requestLength, - isBroadcast); - - case 0x06U: // 写单个保持寄存器 - return ModbusProcessWriteSingleRegister(request, requestLength, - isBroadcast); - - case 0x0FU: // 写多个线圈 - return ModbusProcessWriteMultipleCoils(request, requestLength, - isBroadcast); - - case 0x10U: // 写多个保持寄存器 - return ModbusProcessWriteMultipleRegisters(request, requestLength, - isBroadcast); - - case 0x48U: // 读大地址保持寄存器 - return (isBroadcast != 0U) - ? 0U - : ModbusProcessReadBigHolding(request, requestLength); - - default: // 未知功能码 - ModbusSlaveStatistics.illegalFunctionCount++; + case MODBUS_FC_WRITE_SINGLE: + return ModbusProcessWriteSingle(request, + requestLength, + isBroadcast); + case MODBUS_FC_WRITE_MULTIPLE: + return ModbusProcessWriteMultiple(request, + requestLength, + isBroadcast); + default: return (isBroadcast != 0U) ? 0U : ModbusBuildException(request[1], @@ -852,60 +430,70 @@ static uint16_t ModbusProcessRequest(const uint8_t *request, HAL_StatusTypeDef ModbusSlaveInit(UART_HandleTypeDef *huart, uint8_t slaveAddress) - { + if ((huart == NULL) + || (huart->Instance == NULL) + || (huart->hdmarx == NULL) + || (huart->hdmatx == NULL) + || (huart->Init.BaudRate == 0UL) + || (slaveAddress == MODBUS_BROADCAST_ADDRESS) + || (slaveAddress > MODBUS_SLAVE_ADDRESS_MAX)) + { + return HAL_ERROR; + } ModbusUart = huart; ModbusSlaveAddress = slaveAddress; - /* 清空拼帧状态并根据当前波特率初始化T1.5和T3.5 */ ModbusRxAssemblyLength = 0U; ModbusRxAssemblyInvalid = 0U; + ModbusRxLastByteCycle = 0UL; + ModbusRxFrameLength = 0U; ModbusRxFrameReady = 0U; ModbusTxBusy = 0U; ModbusRtuTimingInit(huart->Init.BaudRate); - //ModbusHoldingRegisters[HMI_REG_DEVICE_ID] = 0xF407U; - return ModbusStartReceive(); } void ModbusSlavePoll(void) { uint16_t responseLength; + uint32_t primask; + HAL_StatusTypeDef txStatus = HAL_OK; + + if (ModbusUart == NULL) + { + return; + } - /* 检查末字节后的静默时间是否已经达到T3.5 */ ModbusTryFinalizeReceive(); if ((ModbusRxFrameReady == 0U) || (ModbusTxBusy != 0U)) { return; } - responseLength = ModbusProcessRequest(ModbusRxFrame, ModbusRxFrameLength); + responseLength = ModbusProcessRequest(ModbusRxFrame, + ModbusRxFrameLength); + + primask = ModbusEnterCritical(); + ModbusRxFrameReady = 0U; if (responseLength > 0U) { ModbusTxBusy = 1U; - - if (HAL_UART_Transmit_DMA(ModbusUart, ModbusTxFrame, responseLength) - == HAL_OK) - { - ModbusSlaveStatistics.txFrameCount++; - } - else + txStatus = HAL_UART_Transmit_DMA(ModbusUart, + ModbusTxFrame, + responseLength); + if (txStatus != HAL_OK) { ModbusTxBusy = 0U; - ModbusSlaveStatistics.uartErrorCount++; - (void)ModbusStartReceive(); // 重启DMA接收 } } - else + ModbusExitCritical(primask); + + if ((responseLength == 0U) || (txStatus != HAL_OK)) { (void)ModbusStartReceive(); } - - /* - * 当前帧;处理完成后再释放帧槽 - */ - ModbusRxFrameReady = 0U; } void ModbusSlaveOnRxEvent(UART_HandleTypeDef *huart, uint16_t size) @@ -915,199 +503,90 @@ void ModbusSlaveOnRxEvent(UART_HandleTypeDef *huart, uint16_t size) uint32_t lastByteCycle; uint32_t firstByteCycle; uint32_t chunkCycles; - uint32_t interFrameGap; + uint32_t interChunkGap; - ModbusSlaveStatistics.rxEventCount++; // 串口接收事件计数 - - if ((huart != ModbusUart) || (ModbusUart == NULL)) + if ((ModbusUart == NULL) || (huart != ModbusUart)) { return; } - if ((size > 0U) && (size <= MODBUS_RTU_ADU_SIZE_MAX)) + eventType = HAL_UARTEx_GetRxEventType(huart); + if (eventType == HAL_UART_RXEVENT_HT) { - now = DWT->CYCCNT; - eventType = HAL_UARTEx_GetRxEventType(huart); - - /* IDLE事件比末字节结束晚约一个字符时间,减去字符时间得到末字节时刻 */ - lastByteCycle = now; - if (eventType == HAL_UART_RXEVENT_IDLE) - { - lastByteCycle -= ModbusRtuCharCycles; - } + return; + } - /* 根据本次接收字节数反推DMA片段首字节的开始时刻 */ - chunkCycles = (uint32_t)((uint64_t)size * ModbusRtuCharCycles); - firstByteCycle = lastByteCycle - chunkCycles; + if ((size == 0U) || (size > MODBUS_RTU_ADU_SIZE_MAX)) + { + ModbusRxAssemblyLength = 0U; + ModbusRxAssemblyInvalid = 0U; + (void)ModbusStartReceive(); + return; + } - if (ModbusRxAssemblyLength > 0U) - { - interFrameGap = (uint32_t)(firstByteCycle - ModbusRxLastByteCycle); - ModbusLastInterFrameGapCycles = interFrameGap; - if (interFrameGap >= ModbusRtuT35Cycles) - { - /* 间隔达到T3.5,结束上一帧,本片段作为新帧开始 */ - ModbusRxAssemblyFinalize(); - } - else if (interFrameGap > ModbusRtuT15Cycles) - { - /* 帧内静默超过T1.5但不足T3.5,标记整帧无效 */ - ModbusRxAssemblyInvalid = 1U; - } - else - { - /* 间隔不超过T1.5,当前片段继续拼入同一帧 */ - } - } + now = DWT->CYCCNT; + lastByteCycle = now; + if (eventType == HAL_UART_RXEVENT_IDLE) + { + lastByteCycle -= ModbusRtuCharCycles; + } + chunkCycles = (uint32_t)((uint64_t)size * ModbusRtuCharCycles); + firstByteCycle = lastByteCycle - chunkCycles; - if (size - <= (uint16_t)(MODBUS_RTU_ADU_SIZE_MAX - ModbusRxAssemblyLength)) + if (ModbusRxAssemblyLength > 0U) + { + interChunkGap = (uint32_t)(firstByteCycle + - ModbusRxLastByteCycle); + if (interChunkGap >= ModbusRtuT35Cycles) { - (void)memcpy(&ModbusRxAssemblyBuffer[ModbusRxAssemblyLength], - ModbusRxDmaBuffer, size); - ModbusRxAssemblyLength += size; + ModbusRxAssemblyFinalize(); } - else + else if (interChunkGap > ModbusRtuT15Cycles) { ModbusRxAssemblyInvalid = 1U; } + } - /* 保存末字节时刻并立即重启DMA,继续等待可能的后续片段 */ - ModbusRxLastByteCycle = lastByteCycle; - (void)ModbusStartReceive(); + if (size <= (uint16_t)(MODBUS_RTU_ADU_SIZE_MAX + - ModbusRxAssemblyLength)) + { + (void)memcpy(&ModbusRxAssemblyBuffer[ModbusRxAssemblyLength], + ModbusRxDmaBuffer, + size); + ModbusRxAssemblyLength += size; } else { - // 长度异常帧 - ModbusSlaveStatistics.droppedFrameCount++; - ModbusRxAssemblyLength = 0U; - ModbusRxAssemblyInvalid = 0U; - (void)ModbusStartReceive(); + ModbusRxAssemblyInvalid = 1U; } + + ModbusRxLastByteCycle = lastByteCycle; + (void)ModbusStartReceive(); } void ModbusSlaveOnTxComplete(UART_HandleTypeDef *huart) { - if ((huart != ModbusUart) || (ModbusUart == NULL)) + if ((ModbusUart == NULL) || (huart != ModbusUart)) { return; } ModbusTxBusy = 0U; - (void)ModbusStartReceive(); // 重启DMA接收 + (void)ModbusStartReceive(); } void ModbusSlaveOnUartError(UART_HandleTypeDef *huart) { - if ((huart != ModbusUart) || (ModbusUart == NULL)) + if ((ModbusUart == NULL) || (huart != ModbusUart)) { return; } - ModbusSlaveStatistics.uartErrorCount++; ModbusTxBusy = 0U; ModbusRxAssemblyLength = 0U; ModbusRxAssemblyInvalid = 0U; - (void)HAL_UART_Abort(huart); // 立即终止这个串口当前正在进行的发送和接收操作 - (void)ModbusStartReceive(); // 重启DMA接收 -} - -uint8_t ModbusSlaveSetHoldingRegister(uint16_t address, uint16_t value) -{ - if (address >= MODBUS_MAP_ITEM_COUNT) - { - return 0U; - } - - ModbusHoldingRegisters[address] = value; - return 1U; -} - -uint8_t ModbusSlaveGetHoldingRegister(uint16_t address, uint16_t *value) -{ - if ((address >= MODBUS_MAP_ITEM_COUNT) || (value == NULL)) - { - return 0U; - } - - *value = ModbusHoldingRegisters[address]; - return 1U; -} - -uint8_t ModbusSlaveSetCoil(uint16_t address, uint8_t state) -{ - if (address >= MODBUS_MAP_ITEM_COUNT) - { - return 0U; - } - - ModbusCoilSetUnchecked(address, state); - return 1U; -} - -uint8_t ModbusSlaveGetCoil(uint16_t address, uint8_t *state) -{ - if ((address >= MODBUS_MAP_ITEM_COUNT) || (state == NULL)) - { - return 0U; - } - - *state = ModbusCoilGetUnchecked(address); - return 1U; -} - -uint8_t ModbusSlaveIsConnected(uint32_t timeoutMs) -{ - if (ModbusHasReceivedValidFrame == 0U) - { - return 0U; - } - - return ((HAL_GetTick() - ModbusLastValidFrameTick) <= timeoutMs) ? 1U : 0U; -} - -// 上电恢复函数 -void ModbusRetainedRegistersLoad(void) -{ - uint16_t index; - - if (ModbusBackupData->magic == MODBUS_BACKUP_MAGIC) - { - for (index = 0U; index < MODBUS_RETAINED_D_COUNT; index++) - { - ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index] = - ModbusBackupData->retainedD[index]; - } - } - else - { - for (index = 0U; index < MODBUS_RETAINED_D_COUNT; index++) - { - ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index] = 0U; - - ModbusBackupData->retainedD[index] = 0U; - } - - /* - * 数据初始化完成后最后写magic,避免初始化中途断电 - * 却把不完整数据标记成有效 - */ - ModbusBackupData->magic = MODBUS_BACKUP_MAGIC; - } -} -void ModbusRetainedRegistersPoll(void) -{ - uint16_t index; - uint16_t value; - - for (index = 0; index < MODBUS_RETAINED_D_COUNT; index++) - { - value = ModbusHoldingRegisters[MODBUS_RETAINED_D_START + index]; - if (value != ModbusRetainedSnapshot[index]) - { - ModbusBackupData->retainedD[index] = value; - } - - ModbusRetainedSnapshot[index] = value; - } + ModbusRxFrameLength = 0U; + ModbusRxFrameReady = 0U; + (void)HAL_UART_Abort(huart); + (void)ModbusStartReceive(); } diff --git a/PLSR/Inc/plsr.h b/PLSR/Inc/plsr.h new file mode 100644 index 0000000..12f5cf2 --- /dev/null +++ b/PLSR/Inc/plsr.h @@ -0,0 +1,94 @@ +#ifndef PLSR_H +#define PLSR_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PLSR_CONFIG_FIRST_ADDRESS (0x1000U) +#define PLSR_CONFIG_LAST_ADDRESS (0x1197U) +#define PLSR_STATUS_FIRST_ADDRESS (0x2000U) +#define PLSR_STATUS_LAST_ADDRESS (0x2006U) +#define PLSR_CONTROL_ADDRESS (0x3000U) + +typedef enum +{ + PLSR_MB_NOT_HANDLED = 0, + PLSR_MB_OK, + PLSR_MB_ILLEGAL_ADDRESS, + PLSR_MB_ILLEGAL_VALUE, + PLSR_MB_DEVICE_BUSY, + PLSR_MB_SERVER_FAILURE +} PLSR_MB_RESULT; + +typedef enum +{ + PLSR_STATUS_UNINITIALIZED = 0, + PLSR_STATUS_IDLE = 1, + PLSR_STATUS_ACCELERATING = 2, + PLSR_STATUS_RUNNING = 3, + PLSR_STATUS_DECELERATING = 4, + PLSR_STATUS_WAITING = 5, + PLSR_STATUS_PAUSED = 6, + PLSR_STATUS_COMPLETED = 7, + PLSR_STATUS_STOPPED = 8, + PLSR_STATUS_ERROR = 9 +} PLSR_STATUS; + +typedef enum +{ + PLSR_ERROR_NONE = 0, + PLSR_ERROR_INVALID_TRANSITION = 1, + PLSR_ERROR_RESOURCE_CONFLICT = 2, + PLSR_ERROR_INVALID_RESOURCE = 3, + PLSR_ERROR_TIMER = 4, + PLSR_ERROR_COUNT = 5, + PLSR_ERROR_POSITIVE_LIMIT = 6, + PLSR_ERROR_NEGATIVE_LIMIT = 7, + PLSR_ERROR_EMERGENCY_STOP = 8, + PLSR_ERROR_INTERNAL = 9 +} PLSR_ERROR; + +uint8_t PlsrInit(void); +void PlsrPoll1ms(void); + +PLSR_MB_RESULT PlsrModbusReadHolding(uint16_t startAddress, + uint16_t quantity, + uint16_t *values); +PLSR_MB_RESULT PlsrModbusWriteHolding(uint16_t startAddress, + uint16_t quantity, + const uint16_t *values); + +/* Called by the selected output timer update interrupt once per pulse. */ +void PlsrPulseTimerIrq(uint8_t pulseOutput); + +#ifdef PLSR_HOST_TEST +uint64_t PlsrTestDivideU64ByU32(uint64_t dividend, + uint32_t divisor, + uint32_t *remainder); +void PlsrTestSetInput(uint8_t inputSelection, uint8_t level); +void PlsrTestEmitPulses(uint32_t pulseCount); +void PlsrTestEmitPulseOnCriticalEntry(void); +void PlsrTestEmitPulseAfterCriticalEntries(uint8_t entriesToSkip); +void PlsrTestEmitPulseOnCriticalExit(void); +void PlsrTestLatchPulseOnCriticalEntry(void); +void PlsrTestServicePendingPulse(void); +void PlsrTestFailNextStart(void); +void PlsrTestFailNextFrequencyAtUpdate(void); +uint8_t PlsrTestPulseIsActive(void); +uint32_t PlsrTestOutputFrequency(void); +uint32_t PlsrTestQueuedFrequency(void); +uint8_t PlsrTestDirectionLevel(void); +void PlsrTestSetPosition(int32_t position, uint8_t positionValid); +void PlsrTestClearPersistentStorage(void); +void PlsrTestResetSaveCount(void); +uint32_t PlsrTestSaveCount(void); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* PLSR_H */ diff --git a/PLSR/Src/plsr.c b/PLSR/Src/plsr.c new file mode 100644 index 0000000..9445eb3 --- /dev/null +++ b/PLSR/Src/plsr.c @@ -0,0 +1,3599 @@ +#include "plsr.h" +#include "plsr_internal.h" +#include "plsr_platform.h" +#include +#include + +#if defined(__ICCARM__) +#include +#endif + +#define PLSR_COMMON_FIRST_ADDRESS (0x1000U) +#define PLSR_COMMON_LAST_ADDRESS (0x10FFU) +#define PLSR_SEGMENT_FIRST_ADDRESS (0x1100U) +#define PLSR_SEGMENT_STRIDE (0x0010U) +#define PLSR_SEGMENT_DEFINED_WORDS (8U) + +#define PLSR_WAIT_TIME (0U) +#define PLSR_WAIT_SIGNAL (1U) +#define PLSR_ACT_TIME (2U) +#define PLSR_EXT_SIGNAL (3U) +#define PLSR_EXT_OR_COMPLETE (4U) + +#define PLSR_SEND_COMPLETE (0U) +#define PLSR_SEND_SUBSEQUENT (1U) +#define PLSR_POSITION_RELATIVE (0U) +#define PLSR_POSITION_ABSOLUTE (1U) + +#define PLSR_COMMAND_START (0x0001U) +#define PLSR_COMMAND_STOP (0x0002U) +#define PLSR_COMMAND_CLEAR (0x0004U) + +#define PLSR_CONFIG_SAVE_DELAY_MS (1000U) +#define PLSR_POSITION_CHECKPOINT_MS (10U) +#define PLSR_SHORT_PROFILE_MAX_PULSES (65535UL) +#define PLSR_Q32_ONE (4294967296ULL) + +typedef struct +{ + uint32_t fromHz; + uint32_t toHz; + uint32_t durationMs; + uint32_t elapsedMs; + uint8_t active; +} PLSR_RAMP; + +typedef struct +{ + uint32_t startHz; + uint32_t peakHz; + uint32_t endHz; + uint16_t pulseCount; + uint16_t entryPulses; + uint16_t steadyPulses; + uint16_t exitPulses; + volatile uint16_t nextPeriod; + uint64_t rampBoundaryQ32; + uint64_t rampTotalAreaQ32; + uint64_t rampTargetAreaQ32; + uint64_t rampAreaStepQ32; + uint32_t rampAreaRemainder; + uint32_t rampRemainderAccumulator; + uint64_t lastRampPhaseStepQ32; + uint64_t entryFirstBoundaryQ32; + uint64_t entrySecondBoundaryQ32; + uint64_t exitFirstBoundaryQ32; + uint64_t exitSecondBoundaryQ32; + uint32_t lastRampFrequencyHz; + volatile uint8_t active; +} PLSR_SHORT_PROFILE; + +typedef struct +{ + uint64_t magnitude; + uint32_t firstFrequencyHz; + PLSR_SHORT_PROFILE profile; + uint8_t nextSegment; + uint8_t positive; + volatile uint8_t valid; +} PLSR_HANDOFF_PLAN; + +typedef enum +{ + PLSR_WORD_OK = 0, + PLSR_WORD_ILLEGAL_ADDRESS, + PLSR_WORD_ILLEGAL_VALUE +} PLSR_WORD_RESULT; + +typedef enum +{ + PLSR_COMMAND_MAILBOX_EMPTY = 0, + PLSR_COMMAND_MAILBOX_PENDING, + PLSR_COMMAND_MAILBOX_EXECUTING +} PLSR_COMMAND_MAILBOX_STATE; + +typedef struct +{ + PLSR_CONFIG startConfig; + uint16_t command; + volatile uint8_t state; +} PLSR_COMMAND_MAILBOX; + +static const uint16_t PlsrSineProgressQ16[65] = +{ + 0U, 39U, 158U, 355U, 630U, 982U, 1411U, 1915U, + 2494U, 3146U, 3869U, 4662U, 5522U, 6448U, 7438U, 8488U, + 9597U, 10762U, 11980U, 13248U, 14563U, 15922U, 17321U, + 18758U, 20228U, 21728U, 23256U, 24806U, 26375U, 27960U, + 29556U, 31160U, 32767U, 34375U, 35979U, 37575U, 39160U, + 40729U, 42279U, 43807U, 45307U, 46777U, 48214U, 49613U, + 50972U, 52287U, 53555U, 54773U, 55938U, 57047U, 58097U, + 59087U, 60013U, 60873U, 61666U, 62389U, 63041U, 63620U, + 64124U, 64553U, 64905U, 65180U, 65377U, 65496U, 65535U +}; + +static const uint32_t PlsrSmoothIntegralQ24[65] = +{ + 0UL, 64UL, 504UL, 1688UL, 3968UL, 7688UL, 13176UL, 20752UL, + 30720UL, 43376UL, 59000UL, 77864UL, 100224UL, 126328UL, 156408UL, + 190688UL, 229376UL, 272672UL, 320760UL, 373816UL, 432000UL, + 495464UL, 564344UL, 638768UL, 718848UL, 804688UL, 896376UL, + 993992UL, 1097600UL, 1207256UL, 1323000UL, 1444864UL, 1572864UL, + 1707008UL, 1847288UL, 1993688UL, 2146176UL, 2304712UL, 2469240UL, + 2639696UL, 2816000UL, 2998064UL, 3185784UL, 3379048UL, 3577728UL, + 3781688UL, 3990776UL, 4204832UL, 4423680UL, 4647136UL, 4875000UL, + 5107064UL, 5343104UL, 5582888UL, 5826168UL, 6072688UL, 6322176UL, + 6574352UL, 6828920UL, 7085576UL, 7344000UL, 7603864UL, 7864824UL, + 8126528UL, 8388608UL +}; + +static const uint32_t PlsrSineIntegralQ24[65] = +{ + 0UL, 53UL, 421UL, 1420UL, 3362UL, 6560UL, 11321UL, 17949UL, + 26744UL, 38000UL, 52007UL, 69047UL, 89393UL, 113314UL, 141066UL, + 172899UL, 209052UL, 249753UL, 295221UL, 345662UL, 401269UL, + 462225UL, 528698UL, 600845UL, 678806UL, 762711UL, 852672UL, + 948789UL, 1051146UL, 1159812UL, 1274841UL, 1396271UL, 1524127UL, + 1658415UL, 1799129UL, 1946244UL, 2099722UL, 2259509UL, 2425536UL, + 2597719UL, 2775958UL, 2960141UL, 3150138UL, 3345809UL, 3546997UL, + 3753534UL, 3965237UL, 4181913UL, 4403356UL, 4629347UL, 4859658UL, + 5094050UL, 5332273UL, 5574071UL, 5819175UL, 6067312UL, 6318200UL, + 6571549UL, 6827065UL, 7084448UL, 7343394UL, 7603596UL, 7864741UL, + 8126517UL, 8388608UL +}; + +static PLSR_CONFIG PlsrShadowConfig; +static PLSR_CONFIG PlsrActiveConfig; +static PLSR_CONFIG PlsrCandidateConfig; +static volatile int32_t PlsrPosition; +static volatile uint64_t PlsrRemainingPulses; +static volatile uint8_t PlsrPulseActive; +static volatile uint8_t PlsrCutRequested; +static volatile uint8_t PlsrBoundaryPending; +static volatile uint8_t PlsrBoundaryWasCut; +static volatile uint8_t PlsrCountPositive; +static volatile uint8_t PlsrCountOverflowPending; +static volatile uint8_t PlsrPositionValid; +static volatile uint8_t PlsrPositionCheckpointDirty; +static volatile uint8_t PlsrFrequencyUpdatePending; +static volatile uint8_t PlsrFrequencyUpdateSegment; +static volatile uint8_t PlsrSeamlessHandoffPending; +static volatile uint8_t PlsrTimerErrorPending; +static volatile uint8_t PlsrDeferredFrequencyPending; +static volatile uint32_t PlsrCurrentFrequencyHz; +static volatile uint32_t PlsrQueuedFrequencyHz; +static volatile uint32_t PlsrBoundaryFrequencyHz; +static volatile uint32_t PlsrFrequencyUpdateTargetHz; +static volatile uint32_t PlsrDeferredFrequencyHz; +static volatile uint32_t PlsrSegmentEpoch; + +static volatile PLSR_STATUS PlsrRunStatus = PLSR_STATUS_UNINITIALIZED; +static PLSR_ERROR PlsrError = PLSR_ERROR_NONE; +static PLSR_RAMP PlsrRamp; +static PLSR_SHORT_PROFILE PlsrShortProfile; +static PLSR_HANDOFF_PLAN PlsrHandoffPlan; +static PLSR_HANDOFF_PLAN + PlsrPreparedHandoffPlans[2][PLSR_SEGMENT_COUNT_MAX]; +static volatile uint8_t PlsrPreparedHandoffBank; +static uint8_t PlsrInitialized; +static volatile uint8_t PlsrCurrentSegment; +static uint8_t PlsrDirectionDelayActive; +static uint16_t PlsrDirectionDelayRemainingMs; +static volatile uint8_t PlsrSegmentClockStarted; +static volatile uint32_t PlsrSegmentElapsedMs; +static volatile uint32_t PlsrWaitElapsedMs; +static volatile uint8_t PlsrExtPreviousLevel; +static volatile uint8_t PlsrExtEdgePending; +static volatile uint8_t PlsrStopRequested; +static volatile uint8_t PlsrStopPulsesRemaining; +static volatile uint8_t PlsrBoundaryRampStarted; +static uint8_t PlsrLastDirectionValid; +static uint8_t PlsrLastDirectionOutput; +static uint8_t PlsrLastDirectionLevel; +static uint8_t PlsrPersistenceDirty; +static uint16_t PlsrPersistenceDelayMs; +static uint8_t PlsrPositionCheckpointElapsedMs; +static PLSR_COMMAND_MAILBOX PlsrCommandMailbox; + +static uint8_t PlsrIsBusy(void); +static void PlsrSetDefaults(PLSR_CONFIG *config); +static uint8_t PlsrConfigIsValid(const PLSR_CONFIG *config, + uint8_t validateActivePath); +static uint16_t PlsrReadConfigWord(const PLSR_CONFIG *config, + uint16_t address); +static PLSR_WORD_RESULT PlsrWriteConfigWord(PLSR_CONFIG *config, + uint16_t address, + uint16_t value); +static uint8_t PlsrAddressIsDwordHalf(uint16_t address, + uint16_t *pairedAddress); +static PLSR_MB_RESULT PlsrQueueCommand(uint16_t command); +static uint8_t PlsrPollCommandMailbox(void); +static void PlsrExecuteStart(void); +static uint8_t PlsrExecuteStop(void); +static void PlsrExecuteClear(void); +static uint8_t PlsrStartSegment(uint8_t segmentNumber, + uint8_t allowCarry, + uint32_t carryFrequencyHz); +static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz); +static void PlsrHandleBoundary(uint8_t extEdge); +static void PlsrTransitionToNext(uint8_t allowCarry); +static void PlsrFinishCompleted(void); +static void PlsrFinishStopped(void); +static void PlsrEnterError(PLSR_ERROR error); +static void PlsrMarkPersistenceDirty(uint16_t delayMs); +static void PlsrCheckpointPosition(uint8_t wasBusy); +static void PlsrPollPositionCheckpoint(void); +static uint8_t PlsrTrySubsequentHandoff(void); +static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, + uint8_t segmentNumber, + uint32_t startFrequencyHz, + uint32_t targetFrequencyHz, + uint64_t pulseCount); +static uint32_t PlsrShortProfileTakeFrequency(PLSR_SHORT_PROFILE *profile); +static uint8_t PlsrAdvanceShortProfile(uint8_t pulseOutput); +static void PlsrCopyShortProfile(PLSR_SHORT_PROFILE *destination, + const PLSR_SHORT_PROFILE *source); +static void PlsrInvalidateHandoffPlans(void); +static uint8_t PlsrBuildHandoffPlanBank( + const PLSR_CONFIG *frequencyConfig); +static uint8_t PlsrSelectPreparedHandoffPlan(PLSR_HANDOFF_PLAN *plan); +static uint8_t PlsrPrimeHandoff(void); +static uint8_t PlsrBuildHandoffPlan(uint8_t sourceSegment, + const PLSR_CONFIG *frequencyConfig, + PLSR_HANDOFF_PLAN *plan); + +static uint8_t PlsrIsBusy(void) +{ + return ((PlsrRunStatus == PLSR_STATUS_ACCELERATING) + || (PlsrRunStatus == PLSR_STATUS_RUNNING) + || (PlsrRunStatus == PLSR_STATUS_DECELERATING) + || (PlsrRunStatus == PLSR_STATUS_WAITING) + || (PlsrRunStatus == PLSR_STATUS_PAUSED)) ? 1U : 0U; +} + +static uint32_t PlsrJoinU32(uint16_t lowWord, uint16_t highWord) +{ + return (uint32_t)lowWord | ((uint32_t)highWord << 16U); +} + +static uint16_t PlsrLowWord(uint32_t value) +{ + return (uint16_t)(value & 0xFFFFUL); +} + +static uint16_t PlsrHighWord(uint32_t value) +{ + return (uint16_t)(value >> 16U); +} + +static void PlsrSetDefaults(PLSR_CONFIG *config) +{ + uint8_t index; + + (void)memset(config, 0, sizeof(*config)); + config->pulseOutput = 0U; + config->directionOutput = 0U; + config->waitInput = 0U; + config->extInput = 0U; + config->sendMode = PLSR_SEND_COMPLETE; + config->directionDelayMs = 10U; + config->directionNegativeLogic = 0U; + config->curveMode = 0U; + config->positionMode = PLSR_POSITION_RELATIVE; + config->segmentCount = 1U; + config->startSegment = 1U; + config->defaultSpeedHz = 1000UL; + config->startSpeedHz = 100UL; + config->stopSpeedHz = 100UL; + config->accelerationTimeMs = 100U; + config->decelerationTimeMs = 100U; + + for (index = 0U; index < PLSR_SEGMENT_COUNT_MAX; index++) + { + config->segments[index].frequencyHz = 1000UL; + config->segments[index].pulses = (index == 0U) ? 1000L : 0L; + config->segments[index].waitType = PLSR_EXT_OR_COMPLETE; + config->segments[index].waitTimeMs = 0U; + config->segments[index].actTimeMs = 0U; + config->segments[index].jumpSegment = 0U; + } +} + +static uint8_t PlsrConfigIsValid(const PLSR_CONFIG *config, + uint8_t validateActivePath) +{ + uint8_t index; + + if ((config->pulseOutput > 3U) || (config->directionOutput > 3U) + || (config->waitInput > 1U) || (config->extInput > 1U) + || (config->sendMode > PLSR_SEND_SUBSEQUENT) + || (config->directionNegativeLogic > 1U) + || (config->curveMode > 2U) + || (config->positionMode > PLSR_POSITION_ABSOLUTE) + || (config->segmentCount == 0U) + || (config->segmentCount > PLSR_SEGMENT_COUNT_MAX) + || (config->startSegment == 0U) + || (config->startSegment > PLSR_SEGMENT_COUNT_MAX) + || (config->defaultSpeedHz == 0UL) + || (config->defaultSpeedHz > PLSR_FREQUENCY_MAX_HZ) + || (config->startSpeedHz > PLSR_FREQUENCY_MAX_HZ) + || (config->stopSpeedHz > PLSR_FREQUENCY_MAX_HZ)) + { + return 0U; + } + + if ((validateActivePath != 0U) + && (config->startSegment > config->segmentCount)) + { + return 0U; + } + + for (index = 0U; index < PLSR_SEGMENT_COUNT_MAX; index++) + { + const PLSR_SEGMENT_CONFIG *segment = &config->segments[index]; + + if ((segment->frequencyHz == 0UL) + || (segment->frequencyHz > PLSR_FREQUENCY_MAX_HZ) + || (segment->waitType > PLSR_EXT_OR_COMPLETE) + || (segment->jumpSegment > PLSR_SEGMENT_COUNT_MAX)) + { + return 0U; + } + + if ((validateActivePath != 0U) + && (index < config->segmentCount) + && (segment->jumpSegment > config->segmentCount)) + { + return 0U; + } + } + + return 1U; +} + +static uint16_t PlsrReadConfigWord(const PLSR_CONFIG *config, + uint16_t address) +{ + uint16_t offset; + uint8_t segmentIndex; + const PLSR_SEGMENT_CONFIG *segment; + + switch (address) + { + case 0x1000U: return config->pulseOutput; + case 0x1001U: return config->directionOutput; + case 0x1002U: return config->waitInput; + case 0x1003U: return config->extInput; + case 0x1004U: return config->sendMode; + case 0x1005U: return config->directionDelayMs; + case 0x1006U: return config->directionNegativeLogic; + case 0x1007U: return config->curveMode; + case 0x1008U: return config->positionMode; + case 0x1009U: return config->segmentCount; + case 0x100AU: return config->startSegment; + case 0x100BU: return PlsrLowWord(config->defaultSpeedHz); + case 0x100CU: return PlsrHighWord(config->defaultSpeedHz); + case 0x100DU: return PlsrLowWord(config->startSpeedHz); + case 0x100EU: return PlsrHighWord(config->startSpeedHz); + case 0x1010U: return PlsrLowWord(config->stopSpeedHz); + case 0x1011U: return PlsrHighWord(config->stopSpeedHz); + case 0x1012U: return config->accelerationTimeMs; + case 0x1013U: return config->decelerationTimeMs; + default: break; + } + + if ((address >= PLSR_SEGMENT_FIRST_ADDRESS) + && (address <= PLSR_CONFIG_LAST_ADDRESS)) + { + offset = (uint16_t)(address - PLSR_SEGMENT_FIRST_ADDRESS); + segmentIndex = (uint8_t)(offset / PLSR_SEGMENT_STRIDE); + offset = (uint16_t)(offset % PLSR_SEGMENT_STRIDE); + segment = &config->segments[segmentIndex]; + + switch (offset) + { + case 0U: return PlsrLowWord(segment->frequencyHz); + case 1U: return PlsrHighWord(segment->frequencyHz); + case 2U: return PlsrLowWord((uint32_t)segment->pulses); + case 3U: return PlsrHighWord((uint32_t)segment->pulses); + case 4U: return segment->waitType; + case 5U: return segment->waitTimeMs; + case 6U: return segment->actTimeMs; + case 7U: return segment->jumpSegment; + default: return 0U; + } + } + + /* 0x100F, 0x1014..0x10FF, and segment padding read as zero. */ + return 0U; +} + +static PLSR_WORD_RESULT PlsrWriteConfigWord(PLSR_CONFIG *config, + uint16_t address, + uint16_t value) +{ + uint16_t offset; + uint8_t segmentIndex; + PLSR_SEGMENT_CONFIG *segment; + + switch (address) + { + case 0x1000U: config->pulseOutput = value; return PLSR_WORD_OK; + case 0x1001U: config->directionOutput = value; return PLSR_WORD_OK; + case 0x1002U: config->waitInput = value; return PLSR_WORD_OK; + case 0x1003U: config->extInput = value; return PLSR_WORD_OK; + case 0x1004U: config->sendMode = value; return PLSR_WORD_OK; + case 0x1005U: config->directionDelayMs = value; return PLSR_WORD_OK; + case 0x1006U: + config->directionNegativeLogic = value; + return PLSR_WORD_OK; + case 0x1007U: config->curveMode = value; return PLSR_WORD_OK; + case 0x1008U: config->positionMode = value; return PLSR_WORD_OK; + case 0x1009U: config->segmentCount = value; return PLSR_WORD_OK; + case 0x100AU: config->startSegment = value; return PLSR_WORD_OK; + case 0x100BU: + config->defaultSpeedHz = + PlsrJoinU32(value, PlsrHighWord(config->defaultSpeedHz)); + return PLSR_WORD_OK; + case 0x100CU: + config->defaultSpeedHz = + PlsrJoinU32(PlsrLowWord(config->defaultSpeedHz), value); + return PLSR_WORD_OK; + case 0x100DU: + config->startSpeedHz = + PlsrJoinU32(value, PlsrHighWord(config->startSpeedHz)); + return PLSR_WORD_OK; + case 0x100EU: + config->startSpeedHz = + PlsrJoinU32(PlsrLowWord(config->startSpeedHz), value); + return PLSR_WORD_OK; + case 0x100FU: + return (value == 0U) ? PLSR_WORD_OK : PLSR_WORD_ILLEGAL_VALUE; + case 0x1010U: + config->stopSpeedHz = + PlsrJoinU32(value, PlsrHighWord(config->stopSpeedHz)); + return PLSR_WORD_OK; + case 0x1011U: + config->stopSpeedHz = + PlsrJoinU32(PlsrLowWord(config->stopSpeedHz), value); + return PLSR_WORD_OK; + case 0x1012U: + config->accelerationTimeMs = value; + return PLSR_WORD_OK; + case 0x1013U: + config->decelerationTimeMs = value; + return PLSR_WORD_OK; + default: break; + } + + if ((address >= 0x1014U) && (address <= PLSR_COMMON_LAST_ADDRESS)) + { + return (value == 0U) ? PLSR_WORD_OK : PLSR_WORD_ILLEGAL_VALUE; + } + + if ((address < PLSR_SEGMENT_FIRST_ADDRESS) + || (address > PLSR_CONFIG_LAST_ADDRESS)) + { + return PLSR_WORD_ILLEGAL_ADDRESS; + } + + offset = (uint16_t)(address - PLSR_SEGMENT_FIRST_ADDRESS); + segmentIndex = (uint8_t)(offset / PLSR_SEGMENT_STRIDE); + offset = (uint16_t)(offset % PLSR_SEGMENT_STRIDE); + segment = &config->segments[segmentIndex]; + + switch (offset) + { + case 0U: + segment->frequencyHz = + PlsrJoinU32(value, PlsrHighWord(segment->frequencyHz)); + return PLSR_WORD_OK; + case 1U: + segment->frequencyHz = + PlsrJoinU32(PlsrLowWord(segment->frequencyHz), value); + return PLSR_WORD_OK; + case 2U: + segment->pulses = (int32_t)PlsrJoinU32( + value, PlsrHighWord((uint32_t)segment->pulses)); + return PLSR_WORD_OK; + case 3U: + segment->pulses = (int32_t)PlsrJoinU32( + PlsrLowWord((uint32_t)segment->pulses), value); + return PLSR_WORD_OK; + case 4U: segment->waitType = value; return PLSR_WORD_OK; + case 5U: segment->waitTimeMs = value; return PLSR_WORD_OK; + case 6U: segment->actTimeMs = value; return PLSR_WORD_OK; + case 7U: segment->jumpSegment = value; return PLSR_WORD_OK; + default: + return (value == 0U) ? PLSR_WORD_OK : PLSR_WORD_ILLEGAL_VALUE; + } +} + +static uint8_t PlsrAddressIsDwordHalf(uint16_t address, + uint16_t *pairedAddress) +{ + uint16_t offset; + + switch (address) + { + case 0x100BU: case 0x100DU: case 0x1010U: + *pairedAddress = (uint16_t)(address + 1U); + return 1U; + case 0x100CU: case 0x100EU: case 0x1011U: + *pairedAddress = (uint16_t)(address - 1U); + return 1U; + default: break; + } + + if ((address >= PLSR_SEGMENT_FIRST_ADDRESS) + && (address <= PLSR_CONFIG_LAST_ADDRESS)) + { + offset = (uint16_t)((address - PLSR_SEGMENT_FIRST_ADDRESS) + % PLSR_SEGMENT_STRIDE); + if ((offset == 0U) || (offset == 2U)) + { + *pairedAddress = (uint16_t)(address + 1U); + return 1U; + } + if ((offset == 1U) || (offset == 3U)) + { + *pairedAddress = (uint16_t)(address - 1U); + return 1U; + } + } + + return 0U; +} + +static uint8_t PlsrAddressIsProduct(uint16_t address) +{ + return (((address >= PLSR_CONFIG_FIRST_ADDRESS) + && (address <= PLSR_CONFIG_LAST_ADDRESS)) + || ((address >= PLSR_STATUS_FIRST_ADDRESS) + && (address <= PLSR_STATUS_LAST_ADDRESS)) + || (address == PLSR_CONTROL_ADDRESS)) ? 1U : 0U; +} + +static PLSR_MB_RESULT PlsrClassifyRange(uint16_t startAddress, + uint16_t quantity) +{ + uint32_t address; + uint32_t endAddress; + uint8_t foundProduct = 0U; + uint8_t foundOther = 0U; + + if (quantity == 0U) + { + return PLSR_MB_ILLEGAL_VALUE; + } + + endAddress = (uint32_t)startAddress + (uint32_t)quantity - 1UL; + if (endAddress > 0xFFFFUL) + { + return PLSR_MB_ILLEGAL_ADDRESS; + } + + for (address = startAddress; address <= endAddress; address++) + { + if (PlsrAddressIsProduct((uint16_t)address) != 0U) + { + foundProduct = 1U; + } + else + { + foundOther = 1U; + } + } + + if (foundProduct == 0U) + { + return PLSR_MB_NOT_HANDLED; + } + return (foundOther != 0U) ? PLSR_MB_ILLEGAL_ADDRESS : PLSR_MB_OK; +} + +static uint32_t PlsrCurveProgressQ16(uint32_t elapsed, + uint32_t duration, + uint16_t curveMode) +{ + uint32_t linear; + + if ((duration == 0UL) || (elapsed >= duration)) + { + return 65535UL; + } + + linear = (uint32_t)(((uint64_t)elapsed * 65535UL) / duration); + if (curveMode == 1U) + { + uint64_t x = linear; + uint64_t x2 = (x * x) / 65535UL; + return (uint32_t)((x2 * (196605UL - 2UL * x)) / 65535UL); + } + if (curveMode == 2U) + { + uint32_t scaled = linear * 64UL; + uint32_t index = scaled / 65535UL; + uint32_t fraction = scaled % 65535UL; + uint32_t first; + uint32_t second; + + if (index >= 64UL) + { + return 65535UL; + } + first = PlsrSineProgressQ16[index]; + second = PlsrSineProgressQ16[index + 1UL]; + return first + (uint32_t)(((uint64_t)(second - first) * fraction) + / 65535UL); + } + return linear; +} + +static uint32_t PlsrRampDurationMs(uint32_t fromHz, uint32_t toHz) +{ + uint32_t gap; + uint32_t baseTimeMs; + uint64_t duration; + + if (fromHz == toHz) + { + return 0UL; + } + gap = (fromHz > toHz) ? (fromHz - toHz) : (toHz - fromHz); + baseTimeMs = (toHz > fromHz) ? PlsrActiveConfig.accelerationTimeMs + : PlsrActiveConfig.decelerationTimeMs; + if (baseTimeMs == 0UL) + { + return 0UL; + } + + duration = ((uint64_t)gap * baseTimeMs + + PlsrActiveConfig.defaultSpeedHz - 1UL) + / PlsrActiveConfig.defaultSpeedHz; + if (duration > 0xFFFFFFFFUL) + { + return 0xFFFFFFFFUL; + } + return (uint32_t)duration; +} + +static void PlsrRampStart(uint32_t fromHz, uint32_t toHz) +{ + PlsrRamp.fromHz = fromHz; + PlsrRamp.toHz = toHz; + PlsrRamp.durationMs = PlsrRampDurationMs(fromHz, toHz); + PlsrRamp.elapsedMs = 0UL; + PlsrRamp.active = (PlsrRamp.durationMs != 0UL) ? 1U : 0U; + + if (toHz > fromHz) + { + PlsrRunStatus = PLSR_STATUS_ACCELERATING; + } + else if (toHz < fromHz) + { + PlsrRunStatus = PLSR_STATUS_DECELERATING; + } + else + { + PlsrRunStatus = PLSR_STATUS_RUNNING; + } +} + +static uint8_t PlsrApplyFrequencyPair(uint32_t requestedFirstFrequencyHz, + uint32_t requestedQueuedFrequencyHz, + uint32_t expectedEpoch) +{ + uint32_t criticalState; + uint32_t actualFirstFrequencyHz; + uint32_t actualQueuedFrequencyHz; + uint32_t hardwareFirstFrequencyHz = requestedFirstFrequencyHz; + uint32_t hardwareQueuedFrequencyHz = requestedQueuedFrequencyHz; + PLSR_HANDOFF_PLAN candidatePlan; + uint8_t haveCandidatePlan = 0U; + + criticalState = PlsrPlatformEnterCritical(); + if ((PlsrSegmentEpoch != expectedEpoch) + || (PlsrBoundaryPending != 0U) || (PlsrRemainingPulses == 0UL)) + { + PlsrPlatformExitCritical(criticalState); + return 1U; + } + + if (hardwareFirstFrequencyHz == 0UL) + { + if (PlsrPulseActive == 0U) + { + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrPlatformExitCritical(criticalState); + return 1U; + } + hardwareFirstFrequencyHz = 1UL; + } + if (hardwareQueuedFrequencyHz == 0UL) + { + hardwareQueuedFrequencyHz = 1UL; + } + + if (PlsrPulseActive != 0U) + { + if (PlsrHandoffPlan.valid != 0U) + { + PlsrPlatformExitCritical(criticalState); + return 1U; + } + PlsrDeferredFrequencyHz = hardwareQueuedFrequencyHz; + PlsrDeferredFrequencyPending = 1U; + PlsrPlatformExitCritical(criticalState); + return 1U; + } + else + { + if (PlsrSelectPreparedHandoffPlan(&candidatePlan) != 0U) + { + hardwareQueuedFrequencyHz = candidatePlan.firstFrequencyHz; + haveCandidatePlan = 1U; + } + if (PlsrPlatformStartPulse((uint8_t)PlsrActiveConfig.pulseOutput, + hardwareFirstFrequencyHz, + hardwareQueuedFrequencyHz, + &actualFirstFrequencyHz, + &actualQueuedFrequencyHz) == 0U) + { + PlsrPlatformExitCritical(criticalState); + return 0U; + } + PlsrPulseActive = 1U; + PlsrCurrentFrequencyHz = actualFirstFrequencyHz; + PlsrHandoffPlan.valid = 0U; + if (haveCandidatePlan != 0U) + { + PlsrHandoffPlan.magnitude = candidatePlan.magnitude; + PlsrHandoffPlan.firstFrequencyHz = actualQueuedFrequencyHz; + PlsrCopyShortProfile(&PlsrHandoffPlan.profile, + &candidatePlan.profile); + PlsrHandoffPlan.nextSegment = candidatePlan.nextSegment; + PlsrHandoffPlan.positive = candidatePlan.positive; + PlsrHandoffPlan.valid = 1U; + } + } + + PlsrQueuedFrequencyHz = actualQueuedFrequencyHz; + PlsrPlatformExitCritical(criticalState); + return 1U; +} + +static uint8_t PlsrApplyFrequency(uint32_t requestedFrequencyHz, + uint32_t expectedEpoch) +{ + return PlsrApplyFrequencyPair(requestedFrequencyHz, + requestedFrequencyHz, + expectedEpoch); +} + +static uint8_t PlsrStopDrainPulseCount(void) +{ + uint8_t deferredPending = PlsrDeferredFrequencyPending; + uint32_t deferredFrequencyHz = PlsrDeferredFrequencyHz; + uint32_t queuedFrequencyHz = PlsrQueuedFrequencyHz; + uint32_t currentFrequencyHz = PlsrCurrentFrequencyHz; + + if ((deferredPending != 0U) + && (deferredFrequencyHz != queuedFrequencyHz)) + { + return 3U; + } + PlsrDeferredFrequencyPending = 0U; + return (currentFrequencyHz == queuedFrequencyHz) ? 1U : 2U; +} + +static uint8_t PlsrCommitDeferredFrequency(uint8_t pulseOutput) +{ + uint32_t requestedFrequencyHz; + uint32_t actualFrequencyHz; + + if (PlsrDeferredFrequencyPending == 0U) + { + return 1U; + } + requestedFrequencyHz = PlsrDeferredFrequencyHz; + PlsrDeferredFrequencyPending = 0U; + if (PlsrPlatformQueueFrequency(pulseOutput, requestedFrequencyHz, + &actualFrequencyHz) == 0U) + { + return 0U; + } + PlsrQueuedFrequencyHz = actualFrequencyHz; + return 1U; +} + +static uint8_t PlsrRampAdvance(uint32_t expectedEpoch) +{ + uint32_t progress; + uint32_t frequency; + uint32_t gap; + uint32_t criticalState; + uint32_t fromHz; + uint32_t toHz; + uint32_t durationMs; + uint32_t elapsedMs; + + criticalState = PlsrPlatformEnterCritical(); + if ((PlsrSegmentEpoch != expectedEpoch) || (PlsrRamp.active == 0U)) + { + PlsrPlatformExitCritical(criticalState); + return 1U; + } + PlsrRamp.elapsedMs++; + fromHz = PlsrRamp.fromHz; + toHz = PlsrRamp.toHz; + durationMs = PlsrRamp.durationMs; + elapsedMs = PlsrRamp.elapsedMs; + PlsrPlatformExitCritical(criticalState); + + progress = PlsrCurveProgressQ16(elapsedMs, + durationMs, + PlsrActiveConfig.curveMode); + if (toHz >= fromHz) + { + gap = toHz - fromHz; + frequency = fromHz + + (uint32_t)(((uint64_t)gap * progress) / 65535UL); + } + else + { + gap = fromHz - toHz; + frequency = fromHz + - (uint32_t)(((uint64_t)gap * progress) / 65535UL); + } + + if (PlsrApplyFrequency(frequency, expectedEpoch) == 0U) + { + return 0U; + } + + criticalState = PlsrPlatformEnterCritical(); + if (PlsrSegmentEpoch != expectedEpoch) + { + PlsrPlatformExitCritical(criticalState); + return 1U; + } + if (elapsedMs >= durationMs) + { + PlsrRamp.active = 0U; + if (PlsrStopRequested == 0U) + { + PlsrRunStatus = PLSR_STATUS_RUNNING; + } + else + { + PlsrStopPulsesRemaining = PlsrStopDrainPulseCount(); + } + } + PlsrPlatformExitCritical(criticalState); + return 1U; +} + +static uint8_t PlsrGetNextSegment(uint8_t *nextSegment) +{ + const PLSR_SEGMENT_CONFIG *segment = + &PlsrActiveConfig.segments[PlsrCurrentSegment - 1U]; + + if (segment->jumpSegment != 0U) + { + *nextSegment = (uint8_t)segment->jumpSegment; + return 1U; + } + if (PlsrCurrentSegment < PlsrActiveConfig.segmentCount) + { + *nextSegment = (uint8_t)(PlsrCurrentSegment + 1U); + return 1U; + } + return 0U; +} + +static int64_t PlsrSegmentDisplacement(uint8_t segmentNumber, + int32_t referencePosition) +{ + int32_t configured = + PlsrActiveConfig.segments[segmentNumber - 1U].pulses; + + if (PlsrActiveConfig.positionMode == PLSR_POSITION_ABSOLUTE) + { + return (int64_t)configured - (int64_t)referencePosition; + } + return configured; +} + +static uint8_t PlsrPredictNextDirection(uint8_t nextSegment, + uint8_t *positive) +{ + uint32_t criticalState; + uint64_t remaining; + int32_t position; + uint32_t predictedBits; + int32_t predictedPosition; + int64_t displacement; + + criticalState = PlsrPlatformEnterCritical(); + remaining = PlsrRemainingPulses; + position = PlsrPosition; + PlsrPlatformExitCritical(criticalState); + + predictedBits = (uint32_t)position; + if (PlsrCountPositive != 0U) + { + predictedBits += (uint32_t)remaining; + } + else + { + predictedBits -= (uint32_t)remaining; + } + predictedPosition = (int32_t)predictedBits; + displacement = PlsrSegmentDisplacement(nextSegment, predictedPosition); + if (displacement == 0) + { + return 0U; + } + *positive = (displacement > 0) ? 1U : 0U; + return 1U; +} + +static uint64_t PlsrRemainingSnapshot(void) +{ + uint32_t criticalState = PlsrPlatformEnterCritical(); + uint64_t remaining = PlsrRemainingPulses; + PlsrPlatformExitCritical(criticalState); + return remaining; +} + +static uint64_t PlsrRampPulseEstimate(uint32_t fromHz, uint32_t toHz) +{ + uint32_t duration = PlsrRampDurationMs(fromHz, toHz); + uint64_t sum = (uint64_t)fromHz + toHz; + + return (sum * duration + 1999UL) / 2000UL + 2UL; +} + +static uint16_t PlsrShortProfileRampTime(uint32_t fromHz, uint32_t toHz) +{ + if (toHz > fromHz) + { + return PlsrActiveConfig.accelerationTimeMs; + } + if (toHz < fromHz) + { + return PlsrActiveConfig.decelerationTimeMs; + } + return 0U; +} + +static uint64_t PlsrShortProfileRampWeight(uint32_t fromHz, + uint32_t toHz, + uint16_t timeMs) +{ + uint64_t fromSquared = (uint64_t)fromHz * fromHz; + uint64_t toSquared = (uint64_t)toHz * toHz; + uint64_t difference = (fromSquared > toSquared) + ? (fromSquared - toSquared) + : (toSquared - fromSquared); + + return difference * timeMs; +} + +static uint32_t PlsrIntegerSquareRoot(uint64_t value) +{ + uint64_t bit = (uint64_t)1U << 62U; + uint64_t root = 0UL; + + while (bit > value) + { + bit >>= 2U; + } + while (bit != 0UL) + { + if (value >= (root + bit)) + { + value -= root + bit; + root = (root >> 1U) + bit; + } + else + { + root >>= 1U; + } + bit >>= 2U; + } + return (uint32_t)root; +} + +static uint32_t PlsrShortProfilePeak(uint32_t startHz, + uint32_t targetHz, + uint32_t endHz, + uint16_t pulseCount) +{ + uint32_t upperEndpoint = (startHz > endHz) ? startHz : endHz; + uint32_t lowerEndpoint = (startHz < endHz) ? startHz : endHz; + uint16_t entryTime; + uint16_t exitTime; + uint32_t timeSum; + uint64_t weightedEndpoints; + uint64_t availableArea; + uint64_t peakSquared; + uint32_t peakHz; + + if (targetHz > upperEndpoint) + { + entryTime = PlsrShortProfileRampTime(startHz, targetHz); + exitTime = PlsrShortProfileRampTime(targetHz, endHz); + timeSum = (uint32_t)entryTime + exitTime; + if (timeSum == 0UL) + { + return targetHz; + } + weightedEndpoints = ((uint64_t)startHz * startHz * entryTime) + + ((uint64_t)endHz * endHz * exitTime); + availableArea = (uint64_t)2U * pulseCount + * PlsrActiveConfig.defaultSpeedHz * 1000UL; + peakSquared = (availableArea + weightedEndpoints) / timeSum; + peakHz = PlsrIntegerSquareRoot(peakSquared); + if (peakHz < upperEndpoint) + { + peakHz = upperEndpoint; + } + if (peakHz > targetHz) + { + peakHz = targetHz; + } + return peakHz; + } + + if (targetHz < lowerEndpoint) + { + entryTime = PlsrShortProfileRampTime(startHz, targetHz); + exitTime = PlsrShortProfileRampTime(targetHz, endHz); + timeSum = (uint32_t)entryTime + exitTime; + if (timeSum == 0UL) + { + return targetHz; + } + weightedEndpoints = ((uint64_t)startHz * startHz * entryTime) + + ((uint64_t)endHz * endHz * exitTime); + availableArea = (uint64_t)2U * pulseCount + * PlsrActiveConfig.defaultSpeedHz * 1000UL; + if (availableArea >= weightedEndpoints) + { + return targetHz; + } + peakSquared = (weightedEndpoints - availableArea) / timeSum; + peakHz = PlsrIntegerSquareRoot(peakSquared); + if (peakHz < targetHz) + { + peakHz = targetHz; + } + if (peakHz > lowerEndpoint) + { + peakHz = lowerEndpoint; + } + return peakHz; + } + + return targetHz; +} + +static uint64_t PlsrShortProfileRequiredSteps(uint64_t rampWeight) +{ + uint64_t denominator = (uint64_t)2U + * PlsrActiveConfig.defaultSpeedHz * 1000UL; + + if (rampWeight == 0UL) + { + return 0UL; + } + return (rampWeight + denominator - 1UL) / denominator; +} + +static uint32_t PlsrShortProfileReachableFrequency(uint32_t fromHz, + uint32_t towardHz, + uint16_t pulseCount) +{ + uint16_t baseTime = PlsrShortProfileRampTime(fromHz, towardHz); + uint64_t frequencySquared = (uint64_t)fromHz * fromHz; + uint64_t changeSquared; + uint32_t reachableHz; + + if ((baseTime == 0U) || (fromHz == towardHz)) + { + return towardHz; + } + changeSquared = (uint64_t)2U * pulseCount + * PlsrActiveConfig.defaultSpeedHz * 1000UL / baseTime; + if (towardHz > fromHz) + { + reachableHz = PlsrIntegerSquareRoot(frequencySquared + changeSquared); + return (reachableHz > towardHz) ? towardHz : reachableHz; + } + + frequencySquared = (changeSquared >= frequencySquared) + ? 0UL : (frequencySquared - changeSquared); + reachableHz = PlsrIntegerSquareRoot(frequencySquared); + if (((uint64_t)reachableHz * reachableHz) < frequencySquared) + { + reachableHz++; + } + return (reachableHz < towardHz) ? towardHz : reachableHz; +} + +static uint64_t PlsrCurveIntegralQ32(uint64_t progressQ32, + uint16_t curveMode) +{ + uint64_t scaled; + uint32_t index; + uint32_t fraction; + uint64_t first; + uint64_t second; + const uint32_t *table; + + if (progressQ32 >= PLSR_Q32_ONE) + { + return PLSR_Q32_ONE / 2ULL; + } + if (curveMode == 0U) + { + return (progressQ32 * progressQ32) >> 33U; + } + + table = (curveMode == 1U) ? PlsrSmoothIntegralQ24 + : PlsrSineIntegralQ24; + scaled = progressQ32 * 64ULL; + index = (uint32_t)(scaled >> 32U); + fraction = (uint32_t)scaled; + first = (uint64_t)table[index] << 8U; + second = (uint64_t)table[index + 1UL] << 8U; + return first + (((second - first) * fraction) >> 32U); +} + +static uint64_t PlsrRampAreaQ32(uint32_t fromHz, + uint32_t toHz, + uint64_t progressQ32) +{ + int64_t delta = (int64_t)toHz - (int64_t)fromHz; + int64_t area = (int64_t)((uint64_t)fromHz * progressQ32) + + delta * (int64_t)PlsrCurveIntegralQ32( + progressQ32, PlsrActiveConfig.curveMode); + return (uint64_t)area; +} + +static uint32_t PlsrRampInstantFrequency(uint32_t fromHz, + uint32_t toHz, + uint64_t progressQ32) +{ + uint64_t curveProgressQ32; + uint64_t scaled; + uint32_t index; + uint32_t tableSlopeQ32; + uint32_t gap; + const uint32_t *integralTable; + + if (progressQ32 >= PLSR_Q32_ONE) + { + return toHz; + } + if (PlsrActiveConfig.curveMode != 0U) + { + scaled = progressQ32 * 64ULL; + index = (uint32_t)(scaled >> 32U); + integralTable = (PlsrActiveConfig.curveMode == 1U) + ? PlsrSmoothIntegralQ24 + : PlsrSineIntegralQ24; + tableSlopeQ32 = + (integralTable[index + 1UL] - integralTable[index]) << 14U; + curveProgressQ32 = tableSlopeQ32; + } + else + { + curveProgressQ32 = progressQ32; + } + + if (toHz >= fromHz) + { + gap = toHz - fromHz; + return fromHz + + (uint32_t)(((uint64_t)gap * curveProgressQ32) >> 32U); + } + gap = fromHz - toHz; + return fromHz + - (uint32_t)(((uint64_t)gap * curveProgressQ32) >> 32U); +} + +static uint64_t PlsrExactRampBoundaryQ32(uint32_t fromHz, + uint32_t toHz, + uint64_t previousBoundaryQ32, + uint64_t targetAreaQ32) +{ + uint64_t lowerQ32 = previousBoundaryQ32; + uint64_t upperQ32 = PLSR_Q32_ONE; + uint64_t middleQ32; + uint32_t iteration; + + /* The Q32 domain is 2^32 units wide, so 32 fixed bisections are exact. */ + for (iteration = 0UL; iteration < 32UL; iteration++) + { + middleQ32 = lowerQ32 + ((upperQ32 - lowerQ32) >> 1U); + if (PlsrRampAreaQ32(fromHz, toHz, middleQ32) < targetAreaQ32) + { + lowerQ32 = middleQ32; + } + else + { + upperQ32 = middleQ32; + } + } + return upperQ32; +} + +static uint32_t PlsrCountLeadingZeros32(uint32_t value) +{ +#if defined(__ICCARM__) + return __CLZ(value); +#else + uint32_t count = 0U; + + if ((value & 0xFFFF0000UL) == 0UL) + { + count += 16U; + value <<= 16U; + } + if ((value & 0xFF000000UL) == 0UL) + { + count += 8U; + value <<= 8U; + } + if ((value & 0xF0000000UL) == 0UL) + { + count += 4U; + value <<= 4U; + } + if ((value & 0xC0000000UL) == 0UL) + { + count += 2U; + value <<= 2U; + } + if ((value & 0x80000000UL) == 0UL) + { + count++; + } + return count; +#endif +} + +static uint32_t PlsrDivideU64Low(uint32_t highWord, + uint32_t lowWord, + uint32_t divisor, + uint32_t *remainder) +{ + const uint32_t halfBase = 0x10000UL; + uint32_t shift = PlsrCountLeadingZeros32(divisor); + uint32_t normalizedDivisor = divisor << shift; + uint32_t divisorHigh = normalizedDivisor >> 16U; + uint32_t divisorLow = normalizedDivisor & 0xFFFFUL; + uint32_t normalizedHigh; + uint32_t normalizedLow = lowWord << shift; + uint32_t lowHigh = normalizedLow >> 16U; + uint32_t lowLow = normalizedLow & 0xFFFFUL; + uint32_t quotientHigh; + uint32_t quotientLow; + uint32_t partialRemainder; + uint32_t middle; + uint32_t normalizedRemainder; + + if (shift == 0U) + { + normalizedHigh = highWord; + } + else + { + normalizedHigh = (highWord << shift) + | (lowWord >> (32U - shift)); + } + + quotientHigh = normalizedHigh / divisorHigh; + partialRemainder = normalizedHigh - quotientHigh * divisorHigh; + while ((quotientHigh >= halfBase) + || (quotientHigh * divisorLow + > (partialRemainder << 16U) + lowHigh)) + { + quotientHigh--; + partialRemainder += divisorHigh; + if (partialRemainder >= halfBase) + { + break; + } + } + + middle = normalizedHigh * halfBase + lowHigh + - quotientHigh * normalizedDivisor; + quotientLow = middle / divisorHigh; + partialRemainder = middle - quotientLow * divisorHigh; + while ((quotientLow >= halfBase) + || (quotientLow * divisorLow + > (partialRemainder << 16U) + lowLow)) + { + quotientLow--; + partialRemainder += divisorHigh; + if (partialRemainder >= halfBase) + { + break; + } + } + + normalizedRemainder = middle * halfBase + lowLow + - quotientLow * normalizedDivisor; + if (remainder != NULL) + { + *remainder = normalizedRemainder >> shift; + } + return quotientHigh * halfBase + quotientLow; +} + +static uint64_t PlsrDivideU64ByU32(uint64_t dividend, + uint32_t divisor, + uint32_t *remainder) +{ + uint32_t highWord = (uint32_t)(dividend >> 32U); + uint32_t lowWord = (uint32_t)dividend; + uint32_t quotientHigh = highWord / divisor; + uint32_t highRemainder = highWord - quotientHigh * divisor; + uint32_t quotientLow = PlsrDivideU64Low(highRemainder, lowWord, + divisor, remainder); + + return ((uint64_t)quotientHigh << 32U) | quotientLow; +} + +static void PlsrPrepareShortProfileBoundaries(PLSR_SHORT_PROFILE *profile) +{ + uint64_t totalAreaQ32; + uint64_t areaStepQ32; + uint64_t targetAreaQ32; + uint32_t areaRemainder; + + if (profile->entryPulses != 0U) + { + totalAreaQ32 = PlsrRampAreaQ32( + profile->startHz, profile->peakHz, PLSR_Q32_ONE); + areaStepQ32 = totalAreaQ32 / profile->entryPulses; + areaRemainder = (uint32_t)(totalAreaQ32 % profile->entryPulses); + profile->entryFirstBoundaryQ32 = PlsrExactRampBoundaryQ32( + profile->startHz, profile->peakHz, 0ULL, areaStepQ32); + if (profile->entryPulses > 1U) + { + targetAreaQ32 = areaStepQ32 * 2ULL + + (((uint64_t)areaRemainder * 2ULL) + / profile->entryPulses); + profile->entrySecondBoundaryQ32 = PlsrExactRampBoundaryQ32( + profile->startHz, profile->peakHz, + profile->entryFirstBoundaryQ32, targetAreaQ32); + } + } + if (profile->exitPulses != 0U) + { + totalAreaQ32 = PlsrRampAreaQ32( + profile->peakHz, profile->endHz, PLSR_Q32_ONE); + areaStepQ32 = totalAreaQ32 / profile->exitPulses; + areaRemainder = (uint32_t)(totalAreaQ32 % profile->exitPulses); + profile->exitFirstBoundaryQ32 = PlsrExactRampBoundaryQ32( + profile->peakHz, profile->endHz, 0ULL, areaStepQ32); + if (profile->exitPulses > 1U) + { + targetAreaQ32 = areaStepQ32 * 2ULL + + (((uint64_t)areaRemainder * 2ULL) + / profile->exitPulses); + profile->exitSecondBoundaryQ32 = PlsrExactRampBoundaryQ32( + profile->peakHz, profile->endHz, + profile->exitFirstBoundaryQ32, targetAreaQ32); + } + } +} + +static uint64_t PlsrRampBoundaryQ32(uint32_t fromHz, + uint32_t toHz, + uint64_t previousBoundaryQ32, + uint64_t targetAreaQ32, + uint64_t predictedStepQ32) +{ + uint64_t currentAreaQ32; + uint64_t candidateQ32; + uint64_t candidateAreaQ32; + uint64_t differenceQ32; + uint64_t correctionQ32; + uint32_t derivativeHz; + + currentAreaQ32 = (previousBoundaryQ32 == 0ULL) + ? 0ULL + : PlsrRampAreaQ32(fromHz, toHz, + previousBoundaryQ32); + if (targetAreaQ32 <= currentAreaQ32) + { + return previousBoundaryQ32 + 1ULL; + } + + if (predictedStepQ32 != 0ULL) + { + if (predictedStepQ32 >= PLSR_Q32_ONE - previousBoundaryQ32) + { + candidateQ32 = PLSR_Q32_ONE; + } + else + { + candidateQ32 = previousBoundaryQ32 + predictedStepQ32; + } + } + else + { + derivativeHz = PlsrRampInstantFrequency(fromHz, toHz, + previousBoundaryQ32); + if (derivativeHz == 0UL) + { + derivativeHz = 1UL; + } + differenceQ32 = targetAreaQ32 - currentAreaQ32; + correctionQ32 = + PlsrDivideU64ByU32(differenceQ32 + derivativeHz - 1UL, + derivativeHz, NULL); + if (correctionQ32 >= PLSR_Q32_ONE - previousBoundaryQ32) + { + candidateQ32 = PLSR_Q32_ONE; + } + else + { + candidateQ32 = previousBoundaryQ32 + correctionQ32; + } + } + + candidateAreaQ32 = PlsrRampAreaQ32(fromHz, toHz, candidateQ32); + derivativeHz = PlsrRampInstantFrequency(fromHz, toHz, candidateQ32); + if (derivativeHz == 0UL) + { + derivativeHz = 1UL; + } + if (candidateAreaQ32 < targetAreaQ32) + { + differenceQ32 = targetAreaQ32 - candidateAreaQ32; + correctionQ32 = + PlsrDivideU64ByU32(differenceQ32 + derivativeHz - 1UL, + derivativeHz, NULL); + if (correctionQ32 >= PLSR_Q32_ONE - candidateQ32) + { + candidateQ32 = PLSR_Q32_ONE; + } + else + { + candidateQ32 += correctionQ32; + } + } + else if (candidateAreaQ32 > targetAreaQ32) + { + differenceQ32 = candidateAreaQ32 - targetAreaQ32; + correctionQ32 = PlsrDivideU64ByU32(differenceQ32, derivativeHz, + NULL); + if (correctionQ32 == 0ULL) + { + correctionQ32 = 1ULL; + } + if (correctionQ32 >= candidateQ32 - previousBoundaryQ32) + { + candidateQ32 = previousBoundaryQ32 + 1ULL; + } + else + { + candidateQ32 -= correctionQ32; + } + } + + /* The corrected phase step seeds the next pulse and keeps ISR work fixed. */ + return candidateQ32; +} + +static uint32_t PlsrRampAverageFrequency(uint64_t areaIncrementQ32, + uint64_t firstBoundaryQ32, + uint64_t secondBoundaryQ32) +{ + uint64_t denominator; + uint64_t frequencyHz; + + if (secondBoundaryQ32 <= firstBoundaryQ32) + { + return 1UL; + } + denominator = secondBoundaryQ32 - firstBoundaryQ32; + if (denominator == PLSR_Q32_ONE) + { + frequencyHz = (areaIncrementQ32 + denominator / 2ULL) >> 32U; + } + else + { + frequencyHz = PlsrDivideU64ByU32( + areaIncrementQ32 + denominator / 2ULL, + (uint32_t)denominator, NULL); + } + if (frequencyHz == 0UL) + { + return 1UL; + } + if (frequencyHz > PLSR_FREQUENCY_MAX_HZ) + { + return PLSR_FREQUENCY_MAX_HZ; + } + return (uint32_t)frequencyHz; +} + +static uint8_t PlsrPrepareShortProfile(PLSR_SHORT_PROFILE *profile, + uint8_t segmentNumber, + uint32_t startFrequencyHz, + uint32_t targetFrequencyHz, + uint64_t pulseCount) +{ + const PLSR_SEGMENT_CONFIG *segment; + uint32_t endFrequencyHz; + uint32_t peakFrequencyHz; + uint16_t entryTime; + uint16_t exitTime; + uint16_t totalPulses; + uint64_t entryWeight; + uint64_t exitWeight; + uint64_t entryRequired; + uint64_t exitRequired; + uint64_t totalWeight; + uint64_t scaledEntry; + + uint64_t durationWeight; + uint64_t singleFrequencyHz; + uint16_t directTime; + uint64_t directWeight; + uint64_t directRequired; + + (void)memset(profile, 0, sizeof(*profile)); + if ((pulseCount == 0UL) + || (pulseCount > PLSR_SHORT_PROFILE_MAX_PULSES) + || (segmentNumber != PlsrActiveConfig.segmentCount)) + { + return 0U; + } + + segment = &PlsrActiveConfig.segments[segmentNumber - 1U]; + if (segment->jumpSegment != 0U) + { + return 0U; + } + + endFrequencyHz = PlsrActiveConfig.stopSpeedHz; + totalPulses = (uint16_t)pulseCount; + directTime = PlsrShortProfileRampTime(startFrequencyHz, endFrequencyHz); + directWeight = PlsrShortProfileRampWeight(startFrequencyHz, + endFrequencyHz, + directTime); + directRequired = PlsrShortProfileRequiredSteps(directWeight); + if (directRequired > totalPulses) + { + profile->startHz = startFrequencyHz; + profile->peakHz = PlsrShortProfileReachableFrequency( + startFrequencyHz, endFrequencyHz, totalPulses); + profile->endHz = profile->peakHz; + profile->pulseCount = totalPulses; + profile->entryPulses = totalPulses; + profile->active = 1U; + PlsrPrepareShortProfileBoundaries(profile); + return 1U; + } + + peakFrequencyHz = PlsrShortProfilePeak(startFrequencyHz, + targetFrequencyHz, + endFrequencyHz, + (uint16_t)pulseCount); + entryTime = PlsrShortProfileRampTime(startFrequencyHz, peakFrequencyHz); + exitTime = PlsrShortProfileRampTime(peakFrequencyHz, endFrequencyHz); + entryWeight = PlsrShortProfileRampWeight(startFrequencyHz, + peakFrequencyHz, + entryTime); + exitWeight = PlsrShortProfileRampWeight(peakFrequencyHz, + endFrequencyHz, + exitTime); + entryRequired = PlsrShortProfileRequiredSteps(entryWeight); + exitRequired = PlsrShortProfileRequiredSteps(exitWeight); + if ((entryRequired == 0UL) && (exitRequired == 0UL)) + { + return 0U; + } + + profile->startHz = startFrequencyHz; + profile->peakHz = peakFrequencyHz; + profile->endHz = endFrequencyHz; + profile->pulseCount = totalPulses; + + if (totalPulses == 1U) + { + durationWeight = + (uint64_t)((peakFrequencyHz > startFrequencyHz) + ? (peakFrequencyHz - startFrequencyHz) + : (startFrequencyHz - peakFrequencyHz)) * entryTime + + (uint64_t)((peakFrequencyHz > endFrequencyHz) + ? (peakFrequencyHz - endFrequencyHz) + : (endFrequencyHz - peakFrequencyHz)) * exitTime; + if (durationWeight == 0UL) + { + return 0U; + } + singleFrequencyHz = + ((uint64_t)PlsrActiveConfig.defaultSpeedHz * 1000UL + + durationWeight / 2UL) / durationWeight; + if (singleFrequencyHz == 0UL) + { + singleFrequencyHz = 1UL; + } + if (singleFrequencyHz > PLSR_FREQUENCY_MAX_HZ) + { + singleFrequencyHz = PLSR_FREQUENCY_MAX_HZ; + } + profile->peakHz = (uint32_t)singleFrequencyHz; + profile->steadyPulses = 1U; + profile->active = 1U; + return 1U; + } + + if ((exitRequired == 0UL) && (peakFrequencyHz != endFrequencyHz)) + { + exitRequired = 1UL; + exitWeight = (uint64_t)2U + * PlsrActiveConfig.defaultSpeedHz * 1000UL; + } + + if ((entryRequired + exitRequired) <= totalPulses) + { + profile->entryPulses = (uint16_t)entryRequired; + profile->exitPulses = (uint16_t)exitRequired; + profile->steadyPulses = + (uint16_t)(totalPulses - profile->entryPulses + - profile->exitPulses); + } + else if (entryRequired == 0UL) + { + profile->exitPulses = totalPulses; + } + else if (exitRequired == 0UL) + { + profile->entryPulses = totalPulses; + } + else + { + totalWeight = entryWeight + exitWeight; + /* Peak planning bounds both weights by this move's pulse budget. */ + scaledEntry = ((uint64_t)totalPulses * entryWeight + + totalWeight / 2UL) / totalWeight; + if (scaledEntry == 0UL) + { + scaledEntry = 1UL; + } + if (scaledEntry >= totalPulses) + { + scaledEntry = totalPulses - 1U; + } + profile->entryPulses = (uint16_t)scaledEntry; + profile->exitPulses = + (uint16_t)(totalPulses - profile->entryPulses); + } + + profile->active = 1U; + PlsrPrepareShortProfileBoundaries(profile); + return 1U; +} + +static uint32_t PlsrShortProfileTakeFrequency(PLSR_SHORT_PROFILE *profile) +{ + uint16_t period = profile->nextPeriod; + uint16_t relativePeriod; + uint16_t rampPulseCount; + uint32_t fromHz; + uint32_t toHz; + uint64_t firstBoundaryQ32; + uint64_t secondBoundaryQ32; + uint64_t previousTargetAreaQ32; + uint64_t areaIncrementQ32; + uint64_t firstCachedBoundaryQ32; + uint64_t secondCachedBoundaryQ32; + uint32_t frequencyHz; + + if ((profile->active == 0U) || (period >= profile->pulseCount)) + { + return (profile->endHz == 0UL) ? 1UL : profile->endHz; + } + profile->nextPeriod = (uint16_t)(period + 1U); + + if (period < profile->entryPulses) + { + relativePeriod = period; + rampPulseCount = profile->entryPulses; + fromHz = profile->startHz; + toHz = profile->peakHz; + firstCachedBoundaryQ32 = profile->entryFirstBoundaryQ32; + secondCachedBoundaryQ32 = profile->entrySecondBoundaryQ32; + firstBoundaryQ32 = (relativePeriod == 0U) + ? 0ULL : profile->rampBoundaryQ32; + } + else if (period < (uint16_t)(profile->entryPulses + + profile->steadyPulses)) + { + profile->lastRampFrequencyHz = 0UL; + return (profile->peakHz == 0UL) ? 1UL : profile->peakHz; + } + else + { + relativePeriod = + (uint16_t)(period - profile->entryPulses + - profile->steadyPulses); + rampPulseCount = profile->exitPulses; + fromHz = profile->peakHz; + toHz = profile->endHz; + firstCachedBoundaryQ32 = profile->exitFirstBoundaryQ32; + secondCachedBoundaryQ32 = profile->exitSecondBoundaryQ32; + firstBoundaryQ32 = (relativePeriod == 0U) + ? 0ULL : profile->rampBoundaryQ32; + } + + if (relativePeriod == 0U) + { + profile->rampTotalAreaQ32 = + PlsrRampAreaQ32(fromHz, toHz, PLSR_Q32_ONE); + profile->rampAreaStepQ32 = + PlsrDivideU64ByU32(profile->rampTotalAreaQ32, + rampPulseCount, + &profile->rampAreaRemainder); + profile->rampRemainderAccumulator = 0UL; + profile->rampTargetAreaQ32 = 0ULL; + profile->rampBoundaryQ32 = 0ULL; + profile->lastRampPhaseStepQ32 = 0ULL; + profile->lastRampFrequencyHz = 0UL; + } + previousTargetAreaQ32 = profile->rampTargetAreaQ32; + profile->rampTargetAreaQ32 += profile->rampAreaStepQ32; + profile->rampRemainderAccumulator += profile->rampAreaRemainder; + if (profile->rampRemainderAccumulator >= rampPulseCount) + { + profile->rampTargetAreaQ32++; + profile->rampRemainderAccumulator -= rampPulseCount; + } + if ((uint16_t)(relativePeriod + 1U) >= rampPulseCount) + { + profile->rampTargetAreaQ32 = profile->rampTotalAreaQ32; + secondBoundaryQ32 = PLSR_Q32_ONE; + } + else if (relativePeriod == 0U) + { + secondBoundaryQ32 = firstCachedBoundaryQ32; + } + else if (relativePeriod == 1U) + { + secondBoundaryQ32 = secondCachedBoundaryQ32; + } + else + { + secondBoundaryQ32 = PlsrRampBoundaryQ32( + fromHz, toHz, firstBoundaryQ32, + profile->rampTargetAreaQ32, + (relativePeriod < 2U) ? 0ULL + : profile->lastRampPhaseStepQ32); + } + areaIncrementQ32 = + profile->rampTargetAreaQ32 - previousTargetAreaQ32; + profile->rampBoundaryQ32 = secondBoundaryQ32; + profile->lastRampPhaseStepQ32 = + secondBoundaryQ32 - firstBoundaryQ32; + frequencyHz = PlsrRampAverageFrequency(areaIncrementQ32, firstBoundaryQ32, + secondBoundaryQ32); + if ((profile->lastRampFrequencyHz != 0UL) + && (((toHz > fromHz) + && (frequencyHz < profile->lastRampFrequencyHz)) + || ((toHz < fromHz) + && (frequencyHz > profile->lastRampFrequencyHz)))) + { + frequencyHz = profile->lastRampFrequencyHz; + } + profile->lastRampFrequencyHz = frequencyHz; + return frequencyHz; +} + +static void PlsrCopyShortProfile(PLSR_SHORT_PROFILE *destination, + const PLSR_SHORT_PROFILE *source) +{ + destination->active = 0U; + destination->startHz = source->startHz; + destination->peakHz = source->peakHz; + destination->endHz = source->endHz; + destination->pulseCount = source->pulseCount; + destination->entryPulses = source->entryPulses; + destination->steadyPulses = source->steadyPulses; + destination->exitPulses = source->exitPulses; + destination->nextPeriod = source->nextPeriod; + destination->rampBoundaryQ32 = source->rampBoundaryQ32; + destination->rampTotalAreaQ32 = source->rampTotalAreaQ32; + destination->rampTargetAreaQ32 = source->rampTargetAreaQ32; + destination->rampAreaStepQ32 = source->rampAreaStepQ32; + destination->rampAreaRemainder = source->rampAreaRemainder; + destination->rampRemainderAccumulator = + source->rampRemainderAccumulator; + destination->lastRampPhaseStepQ32 = source->lastRampPhaseStepQ32; + destination->entryFirstBoundaryQ32 = source->entryFirstBoundaryQ32; + destination->entrySecondBoundaryQ32 = source->entrySecondBoundaryQ32; + destination->exitFirstBoundaryQ32 = source->exitFirstBoundaryQ32; + destination->exitSecondBoundaryQ32 = source->exitSecondBoundaryQ32; + destination->lastRampFrequencyHz = source->lastRampFrequencyHz; + destination->active = source->active; +} + +static void PlsrInvalidateHandoffPlans(void) +{ + uint8_t bank; + uint8_t index; + uint32_t criticalState = PlsrPlatformEnterCritical(); + + PlsrHandoffPlan.valid = 0U; + for (bank = 0U; bank < 2U; bank++) + { + for (index = 0U; index < PLSR_SEGMENT_COUNT_MAX; index++) + { + PlsrPreparedHandoffPlans[bank][index].valid = 0U; + } + } + PlsrPlatformExitCritical(criticalState); +} + +static uint8_t PlsrAdvanceShortProfile(uint8_t pulseOutput) +{ + uint32_t requestedFrequencyHz; + uint32_t actualFrequencyHz; + + if ((PlsrShortProfile.active == 0U) + || (PlsrShortProfile.nextPeriod >= PlsrShortProfile.pulseCount)) + { + return 1U; + } + + requestedFrequencyHz = + PlsrShortProfileTakeFrequency(&PlsrShortProfile); + PlsrDeferredFrequencyPending = 0U; + if (PlsrPlatformQueueFrequency(pulseOutput, + requestedFrequencyHz, + &actualFrequencyHz) == 0U) + { + return 0U; + } + PlsrQueuedFrequencyHz = actualFrequencyHz; + if (actualFrequencyHz > PlsrCurrentFrequencyHz) + { + PlsrRunStatus = PLSR_STATUS_ACCELERATING; + } + else if (actualFrequencyHz < PlsrCurrentFrequencyHz) + { + PlsrRunStatus = PLSR_STATUS_DECELERATING; + } + else + { + PlsrRunStatus = PLSR_STATUS_RUNNING; + } + return 1U; +} + +static void PlsrMaybePlanBoundaryRamp(uint32_t expectedEpoch) +{ + const PLSR_SEGMENT_CONFIG *segment; + uint8_t nextSegment; + uint8_t nextPositive; + uint8_t hasNext; + uint32_t targetHz; + uint32_t criticalState; + uint64_t estimate; + uint64_t remaining; + + if ((PlsrPulseActive == 0U) || (PlsrBoundaryRampStarted != 0U) + || (PlsrShortProfile.active != 0U) + || (PlsrHandoffPlan.valid != 0U) + || (PlsrStopRequested != 0U) || (PlsrCurrentSegment == 0U)) + { + return; + } + + segment = &PlsrActiveConfig.segments[PlsrCurrentSegment - 1U]; + hasNext = PlsrGetNextSegment(&nextSegment); + targetHz = PlsrActiveConfig.stopSpeedHz; + + if ((PlsrActiveConfig.sendMode == PLSR_SEND_SUBSEQUENT) + && (hasNext != 0U) + && (segment->waitType == PLSR_EXT_OR_COMPLETE)) + { + if ((PlsrPredictNextDirection(nextSegment, &nextPositive) != 0U) + && (nextPositive == PlsrCountPositive)) + { + targetHz = PlsrActiveConfig.segments[nextSegment - 1U].frequencyHz; + } + } + + estimate = PlsrRampPulseEstimate(PlsrCurrentFrequencyHz, targetHz); + criticalState = PlsrPlatformEnterCritical(); + remaining = PlsrRemainingPulses; + if ((PlsrSegmentEpoch != expectedEpoch) + || (PlsrBoundaryPending != 0U) + || (PlsrPulseActive == 0U) + || (PlsrBoundaryRampStarted != 0U) + || (PlsrShortProfile.active != 0U) + || (PlsrHandoffPlan.valid != 0U) + || (PlsrStopRequested != 0U)) + { + PlsrPlatformExitCritical(criticalState); + return; + } + if (remaining > estimate) + { + PlsrPlatformExitCritical(criticalState); + return; + } + + PlsrBoundaryRampStarted = 1U; + PlsrRampStart(PlsrCurrentFrequencyHz, targetHz); + PlsrPlatformExitCritical(criticalState); + if (PlsrRamp.active == 0U) + { + (void)PlsrApplyFrequency(targetHz, expectedEpoch); + } +} + +static uint8_t PlsrBeginSegmentOutput(uint32_t startFrequencyHz) +{ + uint32_t targetFrequencyHz = + PlsrActiveConfig.segments[PlsrCurrentSegment - 1U].frequencyHz; + uint32_t firstFrequencyHz; + uint32_t secondFrequencyHz; + + PlsrSegmentClockStarted = 1U; + PlsrSegmentElapsedMs = 0UL; + if (PlsrPrepareShortProfile(&PlsrShortProfile, + PlsrCurrentSegment, startFrequencyHz, + targetFrequencyHz, + PlsrRemainingSnapshot()) != 0U) + { + PlsrRamp.active = 0U; + firstFrequencyHz = + PlsrShortProfileTakeFrequency(&PlsrShortProfile); + secondFrequencyHz = + (PlsrShortProfile.nextPeriod < PlsrShortProfile.pulseCount) + ? PlsrShortProfileTakeFrequency(&PlsrShortProfile) + : firstFrequencyHz; + if (secondFrequencyHz > firstFrequencyHz) + { + PlsrRunStatus = PLSR_STATUS_ACCELERATING; + } + else if (secondFrequencyHz < firstFrequencyHz) + { + PlsrRunStatus = PLSR_STATUS_DECELERATING; + } + else + { + PlsrRunStatus = PLSR_STATUS_RUNNING; + } + if (PlsrApplyFrequencyPair(firstFrequencyHz, + secondFrequencyHz, + PlsrSegmentEpoch) == 0U) + { + PlsrShortProfile.active = 0U; + return 0U; + } + return 1U; + } + + PlsrRampStart(startFrequencyHz, targetFrequencyHz); + + if (PlsrRamp.active == 0U) + { + if (PlsrApplyFrequency(targetFrequencyHz, PlsrSegmentEpoch) == 0U) + { + return 0U; + } + PlsrRunStatus = PLSR_STATUS_RUNNING; + } + else if ((startFrequencyHz != 0UL) + && (PlsrApplyFrequency(startFrequencyHz, + PlsrSegmentEpoch) == 0U)) + { + return 0U; + } + return 1U; +} + +static uint8_t PlsrStartSegment(uint8_t segmentNumber, + uint8_t allowCarry, + uint32_t carryFrequencyHz) +{ + uint32_t criticalState; + int32_t position; + int64_t displacement; + uint64_t magnitude; + uint8_t positive; + uint8_t directionLevel; + uint8_t directionChanged; + uint32_t startFrequencyHz; + + if ((segmentNumber == 0U) + || (segmentNumber > PlsrActiveConfig.segmentCount)) + { + return 0U; + } + + criticalState = PlsrPlatformEnterCritical(); + position = PlsrPosition; + PlsrPlatformExitCritical(criticalState); + displacement = PlsrSegmentDisplacement(segmentNumber, position); + positive = (displacement >= 0) ? 1U : 0U; + magnitude = (displacement < 0) ? (uint64_t)(-displacement) + : (uint64_t)displacement; + + PlsrSegmentEpoch++; + PlsrCurrentSegment = segmentNumber; + PlsrSegmentClockStarted = 0U; + PlsrSegmentElapsedMs = 0UL; + PlsrWaitElapsedMs = 0UL; + PlsrBoundaryRampStarted = 0U; + PlsrBoundaryPending = 0U; + PlsrBoundaryWasCut = 0U; + PlsrCutRequested = 0U; + PlsrStopPulsesRemaining = 0U; + PlsrFrequencyUpdatePending = 0U; + PlsrDeferredFrequencyPending = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrHandoffPlan.valid = 0U; + PlsrShortProfile.active = 0U; + PlsrShortProfile.nextPeriod = 0U; + PlsrDirectionDelayActive = 0U; + PlsrDirectionDelayRemainingMs = 0U; + PlsrExtEdgePending = 0U; + PlsrExtPreviousLevel = + PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.extInput); + + criticalState = PlsrPlatformEnterCritical(); + PlsrRemainingPulses = magnitude; + PlsrCountPositive = positive; + PlsrPlatformExitCritical(criticalState); + + if ((PlsrActiveConfig.segments[segmentNumber - 1U].waitType + == PLSR_ACT_TIME) + && (PlsrActiveConfig.segments[segmentNumber - 1U].actTimeMs == 0U)) + { + PlsrSegmentClockStarted = 1U; + PlsrRunStatus = PLSR_STATUS_RUNNING; + PlsrBoundaryFrequencyHz = (allowCarry != 0U) ? carryFrequencyHz : 0UL; + PlsrBoundaryWasCut = 1U; + PlsrBoundaryPending = 1U; + return 1U; + } + + if (magnitude == 0UL) + { + PlsrSegmentClockStarted = 1U; + PlsrRunStatus = PLSR_STATUS_RUNNING; + PlsrBoundaryFrequencyHz = 0UL; + PlsrBoundaryPending = 1U; + return 1U; + } + + directionLevel = positive; + if (PlsrActiveConfig.directionNegativeLogic != 0U) + { + directionLevel ^= 1U; + } + directionChanged = ((PlsrLastDirectionValid == 0U) + || (PlsrLastDirectionOutput + != (uint8_t)PlsrActiveConfig.directionOutput) + || (PlsrLastDirectionLevel != directionLevel)) ? 1U : 0U; + + if (PlsrPlatformPrepare((uint8_t)PlsrActiveConfig.pulseOutput, + (uint8_t)PlsrActiveConfig.directionOutput, + directionLevel) == 0U) + { + return 0U; + } + PlsrLastDirectionValid = 1U; + PlsrLastDirectionOutput = (uint8_t)PlsrActiveConfig.directionOutput; + PlsrLastDirectionLevel = directionLevel; + + if ((allowCarry != 0U) && (directionChanged == 0U) + && (carryFrequencyHz != 0UL)) + { + startFrequencyHz = carryFrequencyHz; + } + else + { + startFrequencyHz = PlsrActiveConfig.startSpeedHz; + } + + if ((directionChanged != 0U) + && (PlsrActiveConfig.directionDelayMs != 0U)) + { + PlsrDirectionDelayActive = 1U; + PlsrDirectionDelayRemainingMs = PlsrActiveConfig.directionDelayMs; + PlsrRunStatus = PLSR_STATUS_ACCELERATING; + return 1U; + } + + PlsrDirectionDelayActive = 0U; + return PlsrBeginSegmentOutput(startFrequencyHz); +} + +static void PlsrMarkPersistenceDirty(uint16_t delayMs) +{ + PlsrPersistenceDirty = 1U; + PlsrPersistenceDelayMs = delayMs; +} + +static void PlsrCheckpointPosition(uint8_t wasBusy) +{ + uint32_t criticalState; + int32_t position; + uint8_t positionValid; + + criticalState = PlsrPlatformEnterCritical(); + position = PlsrPosition; + positionValid = PlsrPositionValid; + PlsrPositionCheckpointDirty = 0U; + PlsrPlatformExitCritical(criticalState); + PlsrPlatformCheckpointPosition(position, positionValid, wasBusy); + PlsrPositionCheckpointElapsedMs = 0U; +} + +static void PlsrPollPositionCheckpoint(void) +{ + if (PlsrPositionCheckpointDirty == 0U) + { + PlsrPositionCheckpointElapsedMs = 0U; + return; + } + if (PlsrPositionCheckpointElapsedMs < PLSR_POSITION_CHECKPOINT_MS) + { + PlsrPositionCheckpointElapsedMs++; + } + if (PlsrPositionCheckpointElapsedMs >= PLSR_POSITION_CHECKPOINT_MS) + { + PlsrCheckpointPosition(1U); + } +} + +static void PlsrFinishCompleted(void) +{ + PlsrPlatformStopPulse((uint8_t)PlsrActiveConfig.pulseOutput); + PlsrRemainingPulses = 0UL; + PlsrPulseActive = 0U; + PlsrCutRequested = 0U; + PlsrBoundaryPending = 0U; + PlsrBoundaryWasCut = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrCurrentSegment = 0U; + PlsrSegmentClockStarted = 0U; + PlsrDirectionDelayActive = 0U; + PlsrDirectionDelayRemainingMs = 0U; + PlsrExtEdgePending = 0U; + PlsrStopRequested = 0U; + PlsrStopPulsesRemaining = 0U; + PlsrSeamlessHandoffPending = 0U; + PlsrDeferredFrequencyPending = 0U; + PlsrShortProfile.active = 0U; + PlsrHandoffPlan.valid = 0U; + PlsrTimerErrorPending = 0U; + PlsrRamp.active = 0U; + PlsrInvalidateHandoffPlans(); + PlsrRunStatus = PLSR_STATUS_COMPLETED; + PlsrError = PLSR_ERROR_NONE; + PlsrCheckpointPosition(0U); + PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); +} + +static void PlsrFinishStopped(void) +{ + PlsrPlatformStopPulse((uint8_t)PlsrActiveConfig.pulseOutput); + PlsrRemainingPulses = 0UL; + PlsrPulseActive = 0U; + PlsrCutRequested = 0U; + PlsrBoundaryPending = 0U; + PlsrBoundaryWasCut = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrCurrentSegment = 0U; + PlsrSegmentClockStarted = 0U; + PlsrDirectionDelayActive = 0U; + PlsrDirectionDelayRemainingMs = 0U; + PlsrExtEdgePending = 0U; + PlsrStopRequested = 0U; + PlsrStopPulsesRemaining = 0U; + PlsrSeamlessHandoffPending = 0U; + PlsrDeferredFrequencyPending = 0U; + PlsrShortProfile.active = 0U; + PlsrHandoffPlan.valid = 0U; + PlsrTimerErrorPending = 0U; + PlsrRamp.active = 0U; + PlsrInvalidateHandoffPlans(); + PlsrRunStatus = PLSR_STATUS_STOPPED; + PlsrError = PLSR_ERROR_NONE; + PlsrCheckpointPosition(0U); + PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); +} + +static void PlsrEnterError(PLSR_ERROR error) +{ + PlsrPlatformStopPulse((uint8_t)PlsrActiveConfig.pulseOutput); + PlsrRemainingPulses = 0UL; + PlsrPulseActive = 0U; + PlsrCutRequested = 0U; + PlsrBoundaryPending = 0U; + PlsrBoundaryWasCut = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrCurrentSegment = 0U; + PlsrSegmentClockStarted = 0U; + PlsrDirectionDelayActive = 0U; + PlsrDirectionDelayRemainingMs = 0U; + PlsrExtEdgePending = 0U; + PlsrStopRequested = 0U; + PlsrStopPulsesRemaining = 0U; + PlsrSeamlessHandoffPending = 0U; + PlsrDeferredFrequencyPending = 0U; + PlsrShortProfile.active = 0U; + PlsrHandoffPlan.valid = 0U; + PlsrTimerErrorPending = 0U; + PlsrRamp.active = 0U; + PlsrInvalidateHandoffPlans(); + PlsrRunStatus = PLSR_STATUS_ERROR; + PlsrError = error; + PlsrCheckpointPosition(0U); + PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); +} + +static void PlsrTransitionToNext(uint8_t allowCarry) +{ + uint8_t nextSegment; + uint32_t carryFrequencyHz = PlsrBoundaryFrequencyHz; + + if (PlsrGetNextSegment(&nextSegment) == 0U) + { + PlsrFinishCompleted(); + return; + } + + if (PlsrStartSegment(nextSegment, allowCarry, carryFrequencyHz) == 0U) + { + PlsrEnterError(PLSR_ERROR_INVALID_RESOURCE); + } +} + +static uint8_t PlsrBuildHandoffPlan(uint8_t sourceSegment, + const PLSR_CONFIG *frequencyConfig, + PLSR_HANDOFF_PLAN *plan) +{ + const PLSR_SEGMENT_CONFIG *segment; + uint8_t nextSegment; + int64_t displacement; + uint8_t positive; + + plan->valid = 0U; + if ((PlsrActiveConfig.sendMode != PLSR_SEND_SUBSEQUENT) + || (sourceSegment == 0U) + || (sourceSegment > PlsrActiveConfig.segmentCount)) + { + return 0U; + } + + segment = &PlsrActiveConfig.segments[sourceSegment - 1U]; + if (segment->waitType != PLSR_EXT_OR_COMPLETE) + { + return 0U; + } + if (segment->jumpSegment != 0U) + { + nextSegment = (uint8_t)segment->jumpSegment; + } + else if (sourceSegment < PlsrActiveConfig.segmentCount) + { + nextSegment = (uint8_t)(sourceSegment + 1U); + } + else + { + return 0U; + } + + if (PlsrActiveConfig.positionMode == PLSR_POSITION_ABSOLUTE) + { + displacement = (int64_t)PlsrActiveConfig.segments[nextSegment - 1U].pulses + - (int64_t)segment->pulses; + } + else + { + displacement = PlsrActiveConfig.segments[nextSegment - 1U].pulses; + } + if (displacement == 0) + { + return 0U; + } + positive = (displacement > 0) ? 1U : 0U; + + plan->magnitude = (displacement < 0) ? (uint64_t)(-displacement) + : (uint64_t)displacement; + plan->firstFrequencyHz = + frequencyConfig->segments[nextSegment - 1U].frequencyHz; + if (PlsrPrepareShortProfile(&plan->profile, nextSegment, + plan->firstFrequencyHz, + plan->firstFrequencyHz, + plan->magnitude) != 0U) + { + plan->firstFrequencyHz = + PlsrShortProfileTakeFrequency(&plan->profile); + } + plan->nextSegment = nextSegment; + plan->positive = positive; + plan->valid = 1U; + return 1U; +} + +static uint8_t PlsrBuildHandoffPlanBank( + const PLSR_CONFIG *frequencyConfig) +{ + uint8_t buildBank = (uint8_t)(PlsrPreparedHandoffBank ^ 1U); + uint8_t sourceSegment; + PLSR_HANDOFF_PLAN *destination; + + for (sourceSegment = 0U; + sourceSegment < PLSR_SEGMENT_COUNT_MAX; + sourceSegment++) + { + PlsrPreparedHandoffPlans[buildBank][sourceSegment].valid = 0U; + } + for (sourceSegment = 1U; + sourceSegment <= PlsrActiveConfig.segmentCount; + sourceSegment++) + { + destination = + &PlsrPreparedHandoffPlans[buildBank][sourceSegment - 1U]; + (void)PlsrBuildHandoffPlan(sourceSegment, frequencyConfig, + destination); + } + return buildBank; +} + +static uint8_t PlsrSelectPreparedHandoffPlan(PLSR_HANDOFF_PLAN *plan) +{ + const PLSR_HANDOFF_PLAN *prepared; + uint8_t preparedBank; + uint8_t currentSegment; + + plan->valid = 0U; + currentSegment = PlsrCurrentSegment; + if ((PlsrRemainingPulses != 1UL) + || (PlsrActiveConfig.sendMode != PLSR_SEND_SUBSEQUENT) + || (PlsrStopRequested != 0U) + || (PlsrCountOverflowPending != 0U) + || (PlsrCutRequested != 0U) + || (currentSegment == 0U) + || (currentSegment > PlsrActiveConfig.segmentCount)) + { + return 0U; + } + preparedBank = PlsrPreparedHandoffBank; + prepared = &PlsrPreparedHandoffPlans[preparedBank][currentSegment - 1U]; + if ((prepared->valid == 0U) + || (prepared->positive != PlsrCountPositive)) + { + return 0U; + } + + plan->magnitude = prepared->magnitude; + plan->firstFrequencyHz = prepared->firstFrequencyHz; + PlsrCopyShortProfile(&plan->profile, &prepared->profile); + plan->nextSegment = prepared->nextSegment; + plan->positive = prepared->positive; + plan->valid = 1U; + return 1U; +} + +static uint8_t PlsrPrimeHandoff(void) +{ + PLSR_HANDOFF_PLAN candidatePlan; + uint32_t actualFrequencyHz; + + PlsrHandoffPlan.valid = 0U; + if (PlsrSelectPreparedHandoffPlan(&candidatePlan) == 0U) + { + return 0U; + } + PlsrDeferredFrequencyPending = 0U; + if (PlsrPlatformQueueFrequency( + (uint8_t)PlsrActiveConfig.pulseOutput, + candidatePlan.firstFrequencyHz, &actualFrequencyHz) == 0U) + { + PlsrTimerErrorPending = 1U; + return 0U; + } + + PlsrQueuedFrequencyHz = actualFrequencyHz; + PlsrHandoffPlan.magnitude = candidatePlan.magnitude; + PlsrHandoffPlan.firstFrequencyHz = actualFrequencyHz; + PlsrCopyShortProfile(&PlsrHandoffPlan.profile, + &candidatePlan.profile); + PlsrHandoffPlan.nextSegment = candidatePlan.nextSegment; + PlsrHandoffPlan.positive = candidatePlan.positive; + PlsrHandoffPlan.valid = 1U; + return 1U; +} + +static uint8_t PlsrTrySubsequentHandoff(void) +{ + uint32_t requestedQueuedFrequencyHz; + uint32_t actualQueuedFrequencyHz; + uint32_t currentFrequencyHz; + uint32_t queuedFrequencyHz; + uint8_t nextSegment = PlsrHandoffPlan.nextSegment; + uint64_t magnitude = PlsrHandoffPlan.magnitude; + uint8_t positive = PlsrHandoffPlan.positive; + + if ((PlsrHandoffPlan.valid == 0U) + || (PlsrActiveConfig.sendMode != PLSR_SEND_SUBSEQUENT) + || (PlsrStopRequested != 0U) + || (PlsrCountOverflowPending != 0U) + || (PlsrCutRequested != 0U) + || (PlsrCurrentFrequencyHz != PlsrHandoffPlan.firstFrequencyHz) + || (nextSegment == 0U) + || (nextSegment > PlsrActiveConfig.segmentCount)) + { + return 0U; + } + + PlsrCopyShortProfile(&PlsrShortProfile, + &PlsrHandoffPlan.profile); + requestedQueuedFrequencyHz = PlsrCurrentFrequencyHz; + if ((PlsrShortProfile.active != 0U) + && (PlsrShortProfile.nextPeriod < PlsrShortProfile.pulseCount)) + { + requestedQueuedFrequencyHz = + PlsrShortProfileTakeFrequency(&PlsrShortProfile); + } + PlsrDeferredFrequencyPending = 0U; + if (PlsrPlatformQueueFrequency( + (uint8_t)PlsrActiveConfig.pulseOutput, + requestedQueuedFrequencyHz, &actualQueuedFrequencyHz) == 0U) + { + PlsrShortProfile.active = 0U; + PlsrHandoffPlan.valid = 0U; + PlsrTimerErrorPending = 1U; + return 0U; + } + + PlsrQueuedFrequencyHz = actualQueuedFrequencyHz; + PlsrSegmentEpoch++; + PlsrCurrentSegment = nextSegment; + PlsrRemainingPulses = magnitude; + PlsrCountPositive = positive; + PlsrBoundaryFrequencyHz = PlsrCurrentFrequencyHz; + PlsrSegmentClockStarted = 1U; + PlsrSegmentElapsedMs = 0UL; + PlsrWaitElapsedMs = 0UL; + PlsrBoundaryRampStarted = 0U; + PlsrBoundaryPending = 0U; + PlsrBoundaryWasCut = 0U; + PlsrCutRequested = 0U; + PlsrFrequencyUpdatePending = 0U; + PlsrDeferredFrequencyPending = 0U; + PlsrExtEdgePending = 0U; + PlsrExtPreviousLevel = + PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.extInput); + PlsrSeamlessHandoffPending = 1U; + PlsrHandoffPlan.valid = 0U; + if (PlsrRemainingPulses == 1UL) + { + (void)PlsrPrimeHandoff(); + } + queuedFrequencyHz = PlsrQueuedFrequencyHz; + currentFrequencyHz = PlsrCurrentFrequencyHz; + if (PlsrShortProfile.active != 0U) + { + if (queuedFrequencyHz > currentFrequencyHz) + { + PlsrRunStatus = PLSR_STATUS_ACCELERATING; + } + else if (queuedFrequencyHz < currentFrequencyHz) + { + PlsrRunStatus = PLSR_STATUS_DECELERATING; + } + else + { + PlsrRunStatus = PLSR_STATUS_RUNNING; + } + } + else + { + PlsrRunStatus = PLSR_STATUS_RUNNING; + } + return 1U; +} + +static void PlsrHandleBoundary(uint8_t extEdge) +{ + const PLSR_SEGMENT_CONFIG *segment; + uint8_t wasCut = PlsrBoundaryWasCut; + + PlsrBoundaryPending = 0U; + PlsrBoundaryWasCut = 0U; + PlsrPulseActive = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrShortProfile.active = 0U; + PlsrHandoffPlan.valid = 0U; + PlsrDeferredFrequencyPending = 0U; + PlsrCheckpointPosition(1U); + + if (PlsrTimerErrorPending != 0U) + { + PlsrTimerErrorPending = 0U; + PlsrEnterError(PLSR_ERROR_TIMER); + return; + } + if (PlsrCountOverflowPending != 0U) + { + PlsrCountOverflowPending = 0U; + PlsrEnterError(PLSR_ERROR_COUNT); + return; + } + + if (PlsrStopRequested != 0U) + { + PlsrFinishStopped(); + return; + } + if ((PlsrCurrentSegment == 0U) + || (PlsrCurrentSegment > PlsrActiveConfig.segmentCount)) + { + PlsrEnterError(PLSR_ERROR_INTERNAL); + return; + } + + segment = &PlsrActiveConfig.segments[PlsrCurrentSegment - 1U]; + if (wasCut != 0U) + { + PlsrTransitionToNext( + (PlsrActiveConfig.sendMode == PLSR_SEND_SUBSEQUENT) ? 1U : 0U); + return; + } + + switch (segment->waitType) + { + case PLSR_WAIT_TIME: + PlsrWaitElapsedMs = 0UL; + PlsrRunStatus = PLSR_STATUS_WAITING; + break; + + case PLSR_WAIT_SIGNAL: + if (PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.waitInput) != 0U) + { + PlsrTransitionToNext(0U); + } + else + { + PlsrRunStatus = PLSR_STATUS_WAITING; + } + break; + + case PLSR_ACT_TIME: + if (PlsrSegmentElapsedMs >= segment->actTimeMs) + { + PlsrTransitionToNext(0U); + } + else + { + PlsrRunStatus = PLSR_STATUS_WAITING; + } + break; + + case PLSR_EXT_SIGNAL: + if (extEdge != 0U) + { + PlsrTransitionToNext(0U); + } + else + { + PlsrRunStatus = PLSR_STATUS_WAITING; + } + break; + + case PLSR_EXT_OR_COMPLETE: + PlsrTransitionToNext( + (PlsrActiveConfig.sendMode == PLSR_SEND_SUBSEQUENT) ? 1U : 0U); + break; + + default: + PlsrEnterError(PLSR_ERROR_INTERNAL); + break; + } +} + +static void PlsrRequestCut(uint32_t expectedEpoch) +{ + uint32_t criticalState = PlsrPlatformEnterCritical(); + + if (PlsrSegmentEpoch != expectedEpoch) + { + PlsrPlatformExitCritical(criticalState); + return; + } + if (PlsrBoundaryPending != 0U) + { + PlsrBoundaryWasCut = 1U; + } + else if (PlsrPulseActive != 0U) + { + PlsrCutRequested = 1U; + } + else + { + PlsrBoundaryFrequencyHz = PlsrCurrentFrequencyHz; + PlsrBoundaryWasCut = 1U; + PlsrBoundaryPending = 1U; + } + PlsrPlatformExitCritical(criticalState); +} + +static void PlsrPollWaiting(uint8_t extEdge) +{ + const PLSR_SEGMENT_CONFIG *segment = + &PlsrActiveConfig.segments[PlsrCurrentSegment - 1U]; + + switch (segment->waitType) + { + case PLSR_WAIT_TIME: + PlsrWaitElapsedMs++; + if (PlsrWaitElapsedMs >= segment->waitTimeMs) + { + PlsrTransitionToNext(0U); + } + break; + case PLSR_WAIT_SIGNAL: + if (PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.waitInput) != 0U) + { + PlsrTransitionToNext(0U); + } + break; + case PLSR_ACT_TIME: + if (PlsrSegmentElapsedMs >= segment->actTimeMs) + { + PlsrTransitionToNext(0U); + } + break; + case PLSR_EXT_SIGNAL: + if (extEdge != 0U) + { + PlsrTransitionToNext(0U); + } + break; + default: + PlsrEnterError(PLSR_ERROR_INTERNAL); + break; + } +} + +static void PlsrPollPersistence(void) +{ + PLSR_PERSIST_PAYLOAD payload; + uint32_t criticalState; + + if ((PlsrPersistenceDirty == 0U) || (PlsrIsBusy() != 0U)) + { + return; + } + if (PlsrPersistenceDelayMs != 0U) + { + PlsrPersistenceDelayMs--; + return; + } + + payload.config = PlsrShadowConfig; + criticalState = PlsrPlatformEnterCritical(); + payload.position = PlsrPosition; + payload.positionValid = PlsrPositionValid; + PlsrPlatformExitCritical(criticalState); + payload.wasBusy = 0U; + payload.reserved = 0U; + if (PlsrPlatformSave(&payload) != 0U) + { + PlsrPersistenceDirty = 0U; + } + else + { + PlsrRunStatus = PLSR_STATUS_ERROR; + PlsrError = PLSR_ERROR_INTERNAL; + } +} + +uint8_t PlsrInit(void) +{ + PLSR_PERSIST_PAYLOAD payload; + + PlsrInitialized = 0U; + PlsrRunStatus = PLSR_STATUS_UNINITIALIZED; + if (PlsrPlatformInit() == 0U) + { + return 0U; + } + + if ((PlsrPlatformLoad(&payload) == 0U) + || (PlsrConfigIsValid(&payload.config, 0U) == 0U) + || (payload.positionValid > 1U) || (payload.wasBusy > 1U)) + { + PlsrSetDefaults(&PlsrShadowConfig); + PlsrPosition = 0L; + PlsrPositionValid = 1U; + PlsrPlatformCheckpointConfig(&PlsrShadowConfig); + PlsrPlatformCheckpointPosition(0L, 1U, 0U); + PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); + } + else + { + PlsrShadowConfig = payload.config; + PlsrPosition = payload.position; + PlsrPositionValid = ((payload.positionValid != 0U) + && (payload.wasBusy == 0U)) ? 1U : 0U; + PlsrPersistenceDirty = 0U; + PlsrPersistenceDelayMs = 0U; + } + + (void)memset(&PlsrActiveConfig, 0, sizeof(PlsrActiveConfig)); + (void)memset(&PlsrRamp, 0, sizeof(PlsrRamp)); + (void)memset(&PlsrShortProfile, 0, sizeof(PlsrShortProfile)); + (void)memset(&PlsrHandoffPlan, 0, sizeof(PlsrHandoffPlan)); + (void)memset(PlsrPreparedHandoffPlans, 0, + sizeof(PlsrPreparedHandoffPlans)); + PlsrPreparedHandoffBank = 0U; + PlsrRemainingPulses = 0UL; + PlsrPulseActive = 0U; + PlsrCutRequested = 0U; + PlsrBoundaryPending = 0U; + PlsrBoundaryWasCut = 0U; + PlsrCountOverflowPending = 0U; + PlsrPositionCheckpointDirty = 0U; + PlsrFrequencyUpdatePending = 0U; + PlsrDeferredFrequencyPending = 0U; + PlsrFrequencyUpdateSegment = 0U; + PlsrSeamlessHandoffPending = 0U; + PlsrTimerErrorPending = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrBoundaryFrequencyHz = 0UL; + PlsrFrequencyUpdateTargetHz = 0UL; + PlsrDeferredFrequencyHz = 0UL; + PlsrSegmentEpoch = 0UL; + PlsrCurrentSegment = 0U; + PlsrDirectionDelayActive = 0U; + PlsrDirectionDelayRemainingMs = 0U; + PlsrSegmentClockStarted = 0U; + PlsrExtPreviousLevel = 0U; + PlsrExtEdgePending = 0U; + PlsrStopRequested = 0U; + PlsrStopPulsesRemaining = 0U; + PlsrLastDirectionValid = 0U; + PlsrPositionCheckpointElapsedMs = 0U; + PlsrCommandMailbox.command = 0U; + PlsrCommandMailbox.state = PLSR_COMMAND_MAILBOX_EMPTY; + PlsrError = PLSR_ERROR_NONE; + PlsrRunStatus = PLSR_STATUS_IDLE; + PlsrInitialized = 1U; + return 1U; +} + +static PLSR_MB_RESULT PlsrQueueCommand(uint16_t command) +{ + uint32_t criticalState; + PLSR_MB_RESULT result = PLSR_MB_OK; + + if (PlsrInitialized == 0U) + { + return PLSR_MB_SERVER_FAILURE; + } + + criticalState = PlsrPlatformEnterCritical(); + /* Capacity one: repeats acknowledge the first command; conflicts wait. */ + if (PlsrCommandMailbox.state != PLSR_COMMAND_MAILBOX_EMPTY) + { + result = (PlsrCommandMailbox.command == command) + ? PLSR_MB_OK : PLSR_MB_DEVICE_BUSY; + PlsrPlatformExitCritical(criticalState); + return result; + } + + if (command == PLSR_COMMAND_START) + { + if ((PlsrIsBusy() != 0U) + || (PlsrRunStatus == PLSR_STATUS_ERROR)) + { + result = PLSR_MB_DEVICE_BUSY; + } + else if ((PlsrRunStatus != PLSR_STATUS_IDLE) + && (PlsrRunStatus != PLSR_STATUS_COMPLETED) + && (PlsrRunStatus != PLSR_STATUS_STOPPED)) + { + result = PLSR_MB_ILLEGAL_VALUE; + } + else if ((PlsrConfigIsValid(&PlsrShadowConfig, 1U) == 0U) + || ((PlsrShadowConfig.positionMode + == PLSR_POSITION_ABSOLUTE) + && (PlsrPositionValid == 0U))) + { + result = PLSR_MB_ILLEGAL_VALUE; + } + else + { + PlsrCommandMailbox.startConfig = PlsrShadowConfig; + } + } + else if ((command == PLSR_COMMAND_CLEAR) && (PlsrIsBusy() != 0U)) + { + result = PLSR_MB_DEVICE_BUSY; + } + + if (result == PLSR_MB_OK) + { + PlsrCommandMailbox.command = command; + PlsrCommandMailbox.state = PLSR_COMMAND_MAILBOX_PENDING; + } + PlsrPlatformExitCritical(criticalState); + return result; +} + +static void PlsrExecuteStart(void) +{ + uint32_t criticalState; + uint8_t handoffBank; + + PlsrActiveConfig = PlsrCommandMailbox.startConfig; + handoffBank = PlsrBuildHandoffPlanBank(&PlsrActiveConfig); + criticalState = PlsrPlatformEnterCritical(); + PlsrPreparedHandoffBank = handoffBank; + PlsrHandoffPlan.valid = 0U; + PlsrPlatformExitCritical(criticalState); + PlsrStopRequested = 0U; + PlsrTimerErrorPending = 0U; + PlsrShortProfile.active = 0U; + PlsrError = PLSR_ERROR_NONE; + PlsrLastDirectionValid = 0U; + PlsrCheckpointPosition(1U); + if (PlsrStartSegment((uint8_t)PlsrActiveConfig.startSegment, 0U, 0UL) + == 0U) + { + PlsrEnterError(PLSR_ERROR_INVALID_RESOURCE); + } +} + +static uint8_t PlsrExecuteStop(void) +{ + uint32_t criticalState; + uint32_t stopTargetHz; + + if (PlsrIsBusy() == 0U) + { + return 0U; + } + criticalState = PlsrPlatformEnterCritical(); + if (PlsrStopRequested != 0U) + { + PlsrPlatformExitCritical(criticalState); + return 0U; + } + + PlsrStopRequested = 1U; + PlsrStopPulsesRemaining = 0U; + PlsrShortProfile.active = 0U; + PlsrHandoffPlan.valid = 0U; + PlsrInvalidateHandoffPlans(); + if (PlsrBoundaryPending != 0U) + { + PlsrPlatformExitCritical(criticalState); + return 1U; + } + if (PlsrPulseActive == 0U) + { + PlsrPlatformExitCritical(criticalState); + PlsrFinishStopped(); + return 1U; + } + + stopTargetHz = PlsrActiveConfig.stopSpeedHz; + if (stopTargetHz > PlsrCurrentFrequencyHz) + { + stopTargetHz = PlsrCurrentFrequencyHz; + } + PlsrRampStart(PlsrCurrentFrequencyHz, stopTargetHz); + PlsrRunStatus = PLSR_STATUS_DECELERATING; + if (PlsrRamp.active == 0U) + { + if (PlsrApplyFrequency(stopTargetHz, PlsrSegmentEpoch) == 0U) + { + PlsrPlatformExitCritical(criticalState); + PlsrEnterError(PLSR_ERROR_TIMER); + return 1U; + } + PlsrStopPulsesRemaining = PlsrStopDrainPulseCount(); + } + PlsrPlatformExitCritical(criticalState); + return 1U; +} + +static void PlsrExecuteClear(void) +{ + uint32_t criticalState; + + criticalState = PlsrPlatformEnterCritical(); + PlsrPosition = 0L; + PlsrPositionValid = 1U; + PlsrRemainingPulses = 0UL; + PlsrPlatformExitCritical(criticalState); + PlsrPlatformCheckpointPosition(0L, 1U, 0U); + PlsrCountOverflowPending = 0U; + PlsrPositionCheckpointDirty = 0U; + PlsrPositionCheckpointElapsedMs = 0U; + PlsrCurrentSegment = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrCutRequested = 0U; + PlsrBoundaryPending = 0U; + PlsrBoundaryWasCut = 0U; + PlsrDirectionDelayActive = 0U; + PlsrDirectionDelayRemainingMs = 0U; + PlsrExtEdgePending = 0U; + PlsrShortProfile.active = 0U; + PlsrHandoffPlan.valid = 0U; + PlsrInvalidateHandoffPlans(); + PlsrDeferredFrequencyPending = 0U; + PlsrTimerErrorPending = 0U; + PlsrRamp.active = 0U; + PlsrError = PLSR_ERROR_NONE; + PlsrRunStatus = PLSR_STATUS_IDLE; + PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); +} + +static uint8_t PlsrPollCommandMailbox(void) +{ + uint16_t command; + uint8_t endPoll = 1U; + uint32_t criticalState; + + criticalState = PlsrPlatformEnterCritical(); + if (PlsrCommandMailbox.state != PLSR_COMMAND_MAILBOX_PENDING) + { + PlsrPlatformExitCritical(criticalState); + return 0U; + } + PlsrCommandMailbox.state = PLSR_COMMAND_MAILBOX_EXECUTING; + command = PlsrCommandMailbox.command; + PlsrPlatformExitCritical(criticalState); + + switch (command) + { + case PLSR_COMMAND_START: PlsrExecuteStart(); break; + case PLSR_COMMAND_STOP: endPoll = PlsrExecuteStop(); break; + case PLSR_COMMAND_CLEAR: PlsrExecuteClear(); break; + default: PlsrEnterError(PLSR_ERROR_INTERNAL); break; + } + + criticalState = PlsrPlatformEnterCritical(); + PlsrCommandMailbox.command = 0U; + PlsrCommandMailbox.state = PLSR_COMMAND_MAILBOX_EMPTY; + PlsrPlatformExitCritical(criticalState); + return endPoll; +} + +static void PlsrEnterErrorIfEpoch(PLSR_ERROR error, uint32_t expectedEpoch) +{ + uint32_t criticalState = PlsrPlatformEnterCritical(); + + if (PlsrSegmentEpoch == expectedEpoch) + { + PlsrEnterError(error); + } + PlsrPlatformExitCritical(criticalState); +} + +void PlsrPoll1ms(void) +{ + uint8_t extLevel; + uint8_t extEdge; + uint8_t activeSegmentNumber; + uint8_t applyDynamicFrequency = 0U; + PLSR_SEGMENT_CONFIG *activeSegment; + uint32_t criticalState; + uint32_t newTargetHz; + uint32_t pollEpoch; + + if (PlsrInitialized == 0U) + { + return; + } + + if (PlsrPollCommandMailbox() != 0U) + { + return; + } + + PlsrPollPositionCheckpoint(); + + criticalState = PlsrPlatformEnterCritical(); + pollEpoch = PlsrSegmentEpoch; + extLevel = PlsrPlatformReadInput((uint8_t)PlsrActiveConfig.extInput); + extEdge = ((extLevel != 0U) && (PlsrExtPreviousLevel == 0U)) ? 1U : 0U; + PlsrExtPreviousLevel = extLevel; + if (PlsrExtEdgePending != 0U) + { + extEdge = 1U; + } + PlsrPlatformExitCritical(criticalState); + + if (PlsrBoundaryPending != 0U) + { + PlsrExtEdgePending = 0U; + PlsrHandleBoundary(extEdge); + PlsrPollPersistence(); + return; + } + + if (PlsrIsBusy() == 0U) + { + PlsrExtEdgePending = 0U; + PlsrPollPersistence(); + return; + } + + if (PlsrSegmentEpoch != pollEpoch) + { + return; + } + + if (PlsrDirectionDelayActive != 0U) + { + if (extEdge != 0U) + { + PlsrExtEdgePending = 1U; + } + if (PlsrDirectionDelayRemainingMs != 0U) + { + PlsrDirectionDelayRemainingMs--; + } + if (PlsrDirectionDelayRemainingMs == 0U) + { + PlsrDirectionDelayActive = 0U; + if (PlsrBeginSegmentOutput(PlsrActiveConfig.startSpeedHz) == 0U) + { + PlsrEnterError(PLSR_ERROR_TIMER); + } + } + return; + } + + criticalState = PlsrPlatformEnterCritical(); + if (PlsrSegmentEpoch != pollEpoch) + { + PlsrPlatformExitCritical(criticalState); + return; + } + PlsrExtEdgePending = 0U; + if (PlsrSegmentClockStarted != 0U) + { + PlsrSegmentElapsedMs++; + } + if (PlsrRunStatus == PLSR_STATUS_WAITING) + { + PlsrPlatformExitCritical(criticalState); + PlsrPollWaiting(extEdge); + return; + } + + activeSegmentNumber = PlsrCurrentSegment; + if ((activeSegmentNumber == 0U) + || (activeSegmentNumber > PlsrActiveConfig.segmentCount)) + { + PlsrPlatformExitCritical(criticalState); + PlsrEnterError(PLSR_ERROR_INTERNAL); + return; + } + activeSegment = &PlsrActiveConfig.segments[activeSegmentNumber - 1U]; + if (PlsrSeamlessHandoffPending != 0U) + { + PlsrSeamlessHandoffPending = 0U; + if ((PlsrStopRequested == 0U) + && (PlsrShortProfile.active == 0U) + && (PlsrFrequencyUpdatePending == 0U)) + { + PlsrRamp.active = 0U; + PlsrRunStatus = PLSR_STATUS_RUNNING; + } + } + newTargetHz = PlsrFrequencyUpdateTargetHz; + if ((PlsrStopRequested == 0U) + && (PlsrFrequencyUpdatePending != 0U) + && (PlsrFrequencyUpdateSegment == activeSegmentNumber)) + { + PlsrFrequencyUpdatePending = 0U; + activeSegment->frequencyHz = newTargetHz; + PlsrShortProfile.active = 0U; + PlsrBoundaryRampStarted = 0U; + PlsrRampStart(PlsrCurrentFrequencyHz, newTargetHz); + if (PlsrRamp.active == 0U) + { + applyDynamicFrequency = 1U; + } + } + PlsrPlatformExitCritical(criticalState); + + if (applyDynamicFrequency != 0U) + { + if (PlsrApplyFrequency(newTargetHz, pollEpoch) == 0U) + { + PlsrEnterErrorIfEpoch(PLSR_ERROR_TIMER, pollEpoch); + return; + } + criticalState = PlsrPlatformEnterCritical(); + if (PlsrSegmentEpoch == pollEpoch) + { + PlsrRunStatus = PLSR_STATUS_RUNNING; + } + PlsrPlatformExitCritical(criticalState); + } + + if ((PlsrShortProfile.active == 0U) + && (PlsrRamp.active != 0U) + && (PlsrRampAdvance(pollEpoch) == 0U)) + { + PlsrEnterErrorIfEpoch(PLSR_ERROR_TIMER, pollEpoch); + return; + } + + criticalState = PlsrPlatformEnterCritical(); + if (PlsrSegmentEpoch != pollEpoch) + { + PlsrPlatformExitCritical(criticalState); + return; + } + if ((PlsrStopRequested != 0U) && (PlsrRamp.active == 0U)) + { + if (PlsrStopPulsesRemaining == 0U) + { + PlsrStopPulsesRemaining = PlsrStopDrainPulseCount(); + } + PlsrPlatformExitCritical(criticalState); + return; + } + PlsrPlatformExitCritical(criticalState); + + if ((activeSegment->waitType == PLSR_ACT_TIME) + && (PlsrSegmentElapsedMs >= activeSegment->actTimeMs)) + { + PlsrRequestCut(pollEpoch); + return; + } + if (((activeSegment->waitType == PLSR_EXT_SIGNAL) + || (activeSegment->waitType == PLSR_EXT_OR_COMPLETE)) + && (extEdge != 0U)) + { + PlsrRequestCut(pollEpoch); + return; + } + + PlsrMaybePlanBoundaryRamp(pollEpoch); +} + +void PlsrPulseTimerIrq(uint8_t pulseOutput) +{ + uint32_t positionBits; + uint32_t completedFrequencyHz; + uint32_t activeFrequencyHz; + + if ((PlsrPulseActive == 0U) + || (pulseOutput != (uint8_t)PlsrActiveConfig.pulseOutput)) + { + return; + } + + completedFrequencyHz = PlsrCurrentFrequencyHz; + activeFrequencyHz = PlsrPlatformActiveFrequency(pulseOutput); + if (activeFrequencyHz == 0UL) + { + PlsrTimerErrorPending = 1U; + return; + } + PlsrCurrentFrequencyHz = activeFrequencyHz; + if ((PlsrStopRequested != 0U) && (PlsrRamp.active == 0U) + && (PlsrStopPulsesRemaining != 0U)) + { + PlsrStopPulsesRemaining--; + if (PlsrStopPulsesRemaining == 0U) + { + PlsrCutRequested = 1U; + } + } + positionBits = (uint32_t)PlsrPosition; + if (PlsrCountPositive != 0U) + { + if (PlsrPosition == INT32_MAX) + { + PlsrPositionValid = 0U; + PlsrCountOverflowPending = 1U; + } + positionBits++; + } + else + { + if (PlsrPosition == INT32_MIN) + { + PlsrPositionValid = 0U; + PlsrCountOverflowPending = 1U; + } + positionBits--; + } + PlsrPosition = (int32_t)positionBits; + PlsrPositionCheckpointDirty = 1U; + + if (PlsrRemainingPulses != 0UL) + { + PlsrRemainingPulses--; + } + + if ((PlsrRemainingPulses == 0UL) || (PlsrCutRequested != 0U) + || (PlsrCountOverflowPending != 0U)) + { + if ((PlsrRemainingPulses == 0UL) + && (PlsrTrySubsequentHandoff() != 0U)) + { + return; + } + PlsrShortProfile.active = 0U; + PlsrHandoffPlan.valid = 0U; + PlsrBoundaryFrequencyHz = completedFrequencyHz; + PlsrBoundaryWasCut = (PlsrCutRequested != 0U) ? 1U : 0U; + PlsrCutRequested = 0U; + PlsrPlatformStopPulse(pulseOutput); + PlsrPulseActive = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrBoundaryPending = 1U; + return; + } + + if ((PlsrShortProfile.active != 0U) + && (PlsrAdvanceShortProfile(pulseOutput) == 0U)) + { + PlsrTimerErrorPending = 1U; + PlsrShortProfile.active = 0U; + PlsrHandoffPlan.valid = 0U; + PlsrBoundaryFrequencyHz = completedFrequencyHz; + PlsrBoundaryWasCut = 0U; + PlsrCutRequested = 0U; + PlsrPlatformStopPulse(pulseOutput); + PlsrPulseActive = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrBoundaryPending = 1U; + return; + } + + if ((PlsrShortProfile.active == 0U) + && (PlsrRemainingPulses == 1UL)) + { + (void)PlsrPrimeHandoff(); + } + if ((PlsrShortProfile.active == 0U) + && (PlsrHandoffPlan.valid == 0U) + && (PlsrCommitDeferredFrequency(pulseOutput) == 0U)) + { + PlsrTimerErrorPending = 1U; + PlsrBoundaryFrequencyHz = completedFrequencyHz; + PlsrBoundaryWasCut = 0U; + PlsrCutRequested = 0U; + PlsrPlatformStopPulse(pulseOutput); + PlsrPulseActive = 0U; + PlsrCurrentFrequencyHz = 0UL; + PlsrQueuedFrequencyHz = 0UL; + PlsrBoundaryPending = 1U; + } +} + +PLSR_MB_RESULT PlsrModbusReadHolding(uint16_t startAddress, + uint16_t quantity, + uint16_t *values) +{ + PLSR_MB_RESULT classification; + uint16_t index; + uint32_t criticalState; + int32_t position; + uint32_t frequency; + uint16_t statusWords[7]; + + if (values == NULL) + { + return PLSR_MB_ILLEGAL_VALUE; + } + classification = PlsrClassifyRange(startAddress, quantity); + if (classification != PLSR_MB_OK) + { + return classification; + } + + criticalState = PlsrPlatformEnterCritical(); + position = PlsrPosition; + frequency = PlsrCurrentFrequencyHz; + statusWords[0] = PlsrLowWord((uint32_t)position); + statusWords[1] = PlsrHighWord((uint32_t)position); + statusWords[2] = PlsrLowWord(frequency); + statusWords[3] = PlsrHighWord(frequency); + statusWords[4] = (uint16_t)PlsrRunStatus; + statusWords[5] = PlsrCurrentSegment; + statusWords[6] = (uint16_t)PlsrError; + PlsrPlatformExitCritical(criticalState); + + for (index = 0U; index < quantity; index++) + { + uint16_t address = (uint16_t)(startAddress + index); + + if ((address >= PLSR_CONFIG_FIRST_ADDRESS) + && (address <= PLSR_CONFIG_LAST_ADDRESS)) + { + values[index] = PlsrReadConfigWord(&PlsrShadowConfig, address); + } + else if ((address >= PLSR_STATUS_FIRST_ADDRESS) + && (address <= PLSR_STATUS_LAST_ADDRESS)) + { + values[index] = statusWords[address - PLSR_STATUS_FIRST_ADDRESS]; + } + else if (address == PLSR_CONTROL_ADDRESS) + { + values[index] = 0U; + } + else + { + return PLSR_MB_ILLEGAL_ADDRESS; + } + } + return PLSR_MB_OK; +} + +PLSR_MB_RESULT PlsrModbusWriteHolding(uint16_t startAddress, + uint16_t quantity, + const uint16_t *values) +{ + PLSR_MB_RESULT classification; + PLSR_WORD_RESULT wordResult; + uint16_t index; + uint16_t pairedAddress; + uint32_t requestEnd; + uint32_t criticalState; + uint8_t handoffBank = 0U; + uint8_t updateActiveFrequencies = 0U; + uint8_t drainedToDifferentSegment = 0U; + uint8_t segmentBeforeDrain; + uint32_t drainedSegmentTargetHz = 0UL; + + if (values == NULL) + { + return PLSR_MB_ILLEGAL_VALUE; + } + classification = PlsrClassifyRange(startAddress, quantity); + if (classification != PLSR_MB_OK) + { + return classification; + } + + if ((startAddress >= PLSR_STATUS_FIRST_ADDRESS) + && (startAddress <= PLSR_STATUS_LAST_ADDRESS)) + { + return PLSR_MB_ILLEGAL_ADDRESS; + } + + if (startAddress == PLSR_CONTROL_ADDRESS) + { + if (quantity != 1U) + { + return PLSR_MB_ILLEGAL_ADDRESS; + } + switch (values[0]) + { + case 0U: return PLSR_MB_OK; + case PLSR_COMMAND_START: + case PLSR_COMMAND_STOP: + case PLSR_COMMAND_CLEAR: + return PlsrQueueCommand(values[0]); + default: return PLSR_MB_ILLEGAL_VALUE; + } + } + + if ((startAddress < PLSR_CONFIG_FIRST_ADDRESS) + || ((uint32_t)startAddress + quantity - 1UL + > PLSR_CONFIG_LAST_ADDRESS)) + { + return PLSR_MB_ILLEGAL_ADDRESS; + } + if ((PlsrIsBusy() != 0U) + && (startAddress <= 0x1001U) + && ((uint32_t)startAddress + quantity - 1UL >= 0x1000UL)) + { + return PLSR_MB_DEVICE_BUSY; + } + + requestEnd = (uint32_t)startAddress + quantity; + for (index = 0U; index < quantity; index++) + { + uint16_t address = (uint16_t)(startAddress + index); + if (PlsrAddressIsDwordHalf(address, &pairedAddress) != 0U) + { + if (((uint32_t)pairedAddress < startAddress) + || ((uint32_t)pairedAddress >= requestEnd)) + { + return PLSR_MB_ILLEGAL_ADDRESS; + } + } + } + + PlsrCandidateConfig = PlsrShadowConfig; + for (index = 0U; index < quantity; index++) + { + wordResult = PlsrWriteConfigWord(&PlsrCandidateConfig, + (uint16_t)(startAddress + index), + values[index]); + if (wordResult == PLSR_WORD_ILLEGAL_ADDRESS) + { + return PLSR_MB_ILLEGAL_ADDRESS; + } + if (wordResult == PLSR_WORD_ILLEGAL_VALUE) + { + return PLSR_MB_ILLEGAL_VALUE; + } + } + + if (PlsrConfigIsValid(&PlsrCandidateConfig, 0U) == 0U) + { + return PLSR_MB_ILLEGAL_VALUE; + } + + if (PlsrIsBusy() != 0U) + { + for (index = 0U; index < PlsrActiveConfig.segmentCount; index++) + { + if (PlsrCandidateConfig.segments[index].frequencyHz + != PlsrShadowConfig.segments[index].frequencyHz) + { + updateActiveFrequencies = 1U; + } + } + } + if (updateActiveFrequencies != 0U) + { + handoffBank = PlsrBuildHandoffPlanBank(&PlsrCandidateConfig); + } + + criticalState = PlsrPlatformEnterCritical(); + segmentBeforeDrain = PlsrCurrentSegment; + if ((updateActiveFrequencies != 0U) && (PlsrPulseActive != 0U)) + { + PlsrPlatformDrainPendingPulse( + (uint8_t)PlsrActiveConfig.pulseOutput); + drainedToDifferentSegment = + (PlsrCurrentSegment != segmentBeforeDrain) ? 1U : 0U; + if ((drainedToDifferentSegment != 0U) + && (PlsrCurrentSegment != 0U) + && (PlsrCurrentSegment <= PlsrCandidateConfig.segmentCount)) + { + drainedSegmentTargetHz = + PlsrCandidateConfig.segments[PlsrCurrentSegment - 1U].frequencyHz; + } + } + PlsrShadowConfig = PlsrCandidateConfig; + if (updateActiveFrequencies != 0U) + { + for (index = 0U; index < PlsrActiveConfig.segmentCount; index++) + { + uint8_t frequencyChanged = + (PlsrActiveConfig.segments[index].frequencyHz + != PlsrCandidateConfig.segments[index].frequencyHz) + ? 1U : 0U; + + PlsrActiveConfig.segments[index].frequencyHz = + PlsrCandidateConfig.segments[index].frequencyHz; + if ((frequencyChanged != 0U) + && (index + 1U == PlsrCurrentSegment) + && (PlsrStopRequested == 0U) + && (PlsrRunStatus != PLSR_STATUS_WAITING)) + { + PlsrFrequencyUpdateTargetHz = + PlsrCandidateConfig.segments[index].frequencyHz; + PlsrFrequencyUpdateSegment = (uint8_t)(index + 1U); + PlsrFrequencyUpdatePending = 1U; + } + } + PlsrPreparedHandoffBank = handoffBank; + PlsrHandoffPlan.valid = 0U; + if ((drainedToDifferentSegment != 0U) + && (PlsrPulseActive != 0U) + && (PlsrCurrentSegment != 0U) + && (PlsrCurrentSegment <= PlsrActiveConfig.segmentCount) + && (PlsrStopRequested == 0U) + && (PlsrRunStatus != PLSR_STATUS_WAITING) + && (PlsrCurrentFrequencyHz != drainedSegmentTargetHz)) + { + PlsrFrequencyUpdateTargetHz = + drainedSegmentTargetHz; + PlsrFrequencyUpdateSegment = PlsrCurrentSegment; + PlsrFrequencyUpdatePending = 1U; + } + if ((PlsrPulseActive != 0U) + && (PlsrShortProfile.active == 0U) + && (PlsrRemainingPulses == 1UL)) + { + (void)PlsrPrimeHandoff(); + } + } + PlsrPlatformExitCritical(criticalState); + + if ((drainedToDifferentSegment != 0U) + && (PlsrFrequencyUpdatePending != 0U)) + { + uint32_t drainEpoch = PlsrSegmentEpoch; + uint32_t actualDrainFrequencyHz; + + if (PlsrPlatformQueueFrequency( + (uint8_t)PlsrActiveConfig.pulseOutput, + drainedSegmentTargetHz, &actualDrainFrequencyHz) == 0U) + { + PlsrTimerErrorPending = 1U; + } + else + { + uint32_t currentEpoch; + uint8_t updateSegment; + uint8_t currentSegment; + + criticalState = PlsrPlatformEnterCritical(); + currentEpoch = PlsrSegmentEpoch; + updateSegment = PlsrFrequencyUpdateSegment; + currentSegment = PlsrCurrentSegment; + if ((currentEpoch == drainEpoch) + && (updateSegment == currentSegment)) + { + PlsrQueuedFrequencyHz = actualDrainFrequencyHz; + PlsrFrequencyUpdatePending = 0U; + PlsrRamp.active = 0U; + PlsrRunStatus = PLSR_STATUS_RUNNING; + } + PlsrPlatformExitCritical(criticalState); + } + } + PlsrPlatformCheckpointConfig(&PlsrShadowConfig); + PlsrMarkPersistenceDirty(PLSR_CONFIG_SAVE_DELAY_MS); + return PLSR_MB_OK; +} + +#ifdef PLSR_HOST_TEST +uint64_t PlsrTestDivideU64ByU32(uint64_t dividend, + uint32_t divisor, + uint32_t *remainder) +{ + return PlsrDivideU64ByU32(dividend, divisor, remainder); +} + +void PlsrTestSetPosition(int32_t position, uint8_t positionValid) +{ + uint32_t criticalState = PlsrPlatformEnterCritical(); + + PlsrPosition = position; + PlsrPositionValid = (positionValid != 0U) ? 1U : 0U; + PlsrRemainingPulses = 0UL; + PlsrPositionCheckpointDirty = 0U; + PlsrPlatformExitCritical(criticalState); + PlsrPlatformCheckpointPosition(position, PlsrPositionValid, 0U); +} +#endif diff --git a/PLSR/Src/plsr_internal.h b/PLSR/Src/plsr_internal.h new file mode 100644 index 0000000..54d56c2 --- /dev/null +++ b/PLSR/Src/plsr_internal.h @@ -0,0 +1,49 @@ +#ifndef PLSR_INTERNAL_H +#define PLSR_INTERNAL_H + +#include + +#define PLSR_SEGMENT_COUNT_MAX (10U) +#define PLSR_FREQUENCY_MAX_HZ (100000UL) + +typedef struct +{ + uint32_t frequencyHz; + int32_t pulses; + uint16_t waitType; + uint16_t waitTimeMs; + uint16_t actTimeMs; + uint16_t jumpSegment; +} PLSR_SEGMENT_CONFIG; + +typedef struct +{ + uint16_t pulseOutput; + uint16_t directionOutput; + uint16_t waitInput; + uint16_t extInput; + uint16_t sendMode; + uint16_t directionDelayMs; + uint16_t directionNegativeLogic; + uint16_t curveMode; + uint16_t positionMode; + uint16_t segmentCount; + uint16_t startSegment; + uint32_t defaultSpeedHz; + uint32_t startSpeedHz; + uint32_t stopSpeedHz; + uint16_t accelerationTimeMs; + uint16_t decelerationTimeMs; + PLSR_SEGMENT_CONFIG segments[PLSR_SEGMENT_COUNT_MAX]; +} PLSR_CONFIG; + +typedef struct +{ + PLSR_CONFIG config; + int32_t position; + uint8_t positionValid; + uint8_t wasBusy; + uint16_t reserved; +} PLSR_PERSIST_PAYLOAD; + +#endif /* PLSR_INTERNAL_H */ diff --git a/PLSR/Src/plsr_platform.h b/PLSR/Src/plsr_platform.h new file mode 100644 index 0000000..e7b45cd --- /dev/null +++ b/PLSR/Src/plsr_platform.h @@ -0,0 +1,34 @@ +#ifndef PLSR_PLATFORM_H +#define PLSR_PLATFORM_H + +#include "plsr_internal.h" +#include + +uint8_t PlsrPlatformInit(void); +uint8_t PlsrPlatformPrepare(uint8_t pulseOutput, + uint8_t directionOutput, + uint8_t directionLevel); +uint8_t PlsrPlatformStartPulse(uint8_t pulseOutput, + uint32_t firstFrequencyHz, + uint32_t queuedFrequencyHz, + uint32_t *actualFirstFrequencyHz, + uint32_t *actualQueuedFrequencyHz); +uint8_t PlsrPlatformQueueFrequency(uint8_t pulseOutput, + uint32_t frequencyHz, + uint32_t *actualFrequencyHz); +void PlsrPlatformDrainPendingPulse(uint8_t pulseOutput); +uint32_t PlsrPlatformActiveFrequency(uint8_t pulseOutput); +void PlsrPlatformStopPulse(uint8_t pulseOutput); +uint8_t PlsrPlatformReadInput(uint8_t inputSelection); + +uint8_t PlsrPlatformLoad(PLSR_PERSIST_PAYLOAD *payload); +uint8_t PlsrPlatformSave(const PLSR_PERSIST_PAYLOAD *payload); +void PlsrPlatformCheckpointConfig(const PLSR_CONFIG *config); +void PlsrPlatformCheckpointPosition(int32_t position, + uint8_t positionValid, + uint8_t wasBusy); + +uint32_t PlsrPlatformEnterCritical(void); +void PlsrPlatformExitCritical(uint32_t state); + +#endif /* PLSR_PLATFORM_H */ diff --git a/PLSR/Src/plsr_platform_f407.c b/PLSR/Src/plsr_platform_f407.c new file mode 100644 index 0000000..ee899dd --- /dev/null +++ b/PLSR/Src/plsr_platform_f407.c @@ -0,0 +1,1202 @@ +#include "plsr_platform.h" +#include "plsr.h" + +#ifdef PLSR_HOST_TEST + +#include + +static uint8_t PlsrHostPulseActive[4]; +static uint32_t PlsrHostFrequency[4]; +static uint32_t PlsrHostQueuedFrequency[4]; +static uint8_t PlsrHostUpdatePending[4]; +static uint8_t PlsrHostInputs[2]; +static uint8_t PlsrHostSelectedPulse; +static uint8_t PlsrHostDirectionLevel; +static uint8_t PlsrHostEmitPulseOnCriticalEntry; +static uint8_t PlsrHostEmitPulseOnCriticalExit; +static uint8_t PlsrHostLatchPulseOnCriticalEntry; +static uint8_t PlsrHostCriticalEntriesToSkip; +static uint8_t PlsrHostFailNextStart; +static uint8_t PlsrHostFailNextFrequencyAtUpdate; +static PLSR_PERSIST_PAYLOAD PlsrHostPersistentPayload; +static uint8_t PlsrHostPersistentValid; +static uint32_t PlsrHostSaveCount; + +static void PlsrHostLatchPulse(uint8_t pulseOutput) +{ + if ((pulseOutput <= 3U) + && (PlsrHostPulseActive[pulseOutput] != 0U)) + { + PlsrHostFrequency[pulseOutput] = + PlsrHostQueuedFrequency[pulseOutput]; + PlsrHostUpdatePending[pulseOutput] = 1U; + } +} + +static void PlsrHostServicePendingPulse(uint8_t pulseOutput) +{ + if ((pulseOutput <= 3U) + && (PlsrHostUpdatePending[pulseOutput] != 0U)) + { + PlsrHostUpdatePending[pulseOutput] = 0U; + PlsrPulseTimerIrq(pulseOutput); + } +} + +uint8_t PlsrPlatformInit(void) +{ + (void)memset(PlsrHostPulseActive, 0, sizeof(PlsrHostPulseActive)); + (void)memset(PlsrHostFrequency, 0, sizeof(PlsrHostFrequency)); + (void)memset(PlsrHostQueuedFrequency, 0, + sizeof(PlsrHostQueuedFrequency)); + (void)memset(PlsrHostUpdatePending, 0, + sizeof(PlsrHostUpdatePending)); + PlsrHostSelectedPulse = 0U; + PlsrHostDirectionLevel = 0U; + PlsrHostEmitPulseOnCriticalEntry = 0U; + PlsrHostEmitPulseOnCriticalExit = 0U; + PlsrHostLatchPulseOnCriticalEntry = 0U; + PlsrHostCriticalEntriesToSkip = 0U; + PlsrHostFailNextStart = 0U; + PlsrHostFailNextFrequencyAtUpdate = 0U; + return 1U; +} + +uint8_t PlsrPlatformPrepare(uint8_t pulseOutput, + uint8_t directionOutput, + uint8_t directionLevel) +{ + uint8_t index; + (void)directionOutput; + + if (PlsrHostFailNextStart != 0U) + { + PlsrHostFailNextStart = 0U; + return 0U; + } + if ((pulseOutput > 3U) || (directionOutput > 3U)) + { + return 0U; + } + for (index = 0U; index < 4U; index++) + { + PlsrHostPulseActive[index] = 0U; + PlsrHostFrequency[index] = 0UL; + PlsrHostQueuedFrequency[index] = 0UL; + PlsrHostUpdatePending[index] = 0U; + } + PlsrHostSelectedPulse = pulseOutput; + PlsrHostDirectionLevel = (directionLevel != 0U) ? 1U : 0U; + return 1U; +} + +uint8_t PlsrPlatformStartPulse(uint8_t pulseOutput, + uint32_t firstFrequencyHz, + uint32_t queuedFrequencyHz, + uint32_t *actualFirstFrequencyHz, + uint32_t *actualQueuedFrequencyHz) +{ + if ((pulseOutput > 3U) || (firstFrequencyHz == 0UL) + || (firstFrequencyHz > PLSR_FREQUENCY_MAX_HZ) + || (queuedFrequencyHz == 0UL) + || (queuedFrequencyHz > PLSR_FREQUENCY_MAX_HZ) + || (actualFirstFrequencyHz == NULL) + || (actualQueuedFrequencyHz == NULL)) + { + return 0U; + } + PlsrHostPulseActive[pulseOutput] = 1U; + PlsrHostFrequency[pulseOutput] = firstFrequencyHz; + PlsrHostQueuedFrequency[pulseOutput] = queuedFrequencyHz; + PlsrHostUpdatePending[pulseOutput] = 0U; + PlsrHostSelectedPulse = pulseOutput; + *actualFirstFrequencyHz = firstFrequencyHz; + *actualQueuedFrequencyHz = queuedFrequencyHz; + return 1U; +} + +uint8_t PlsrPlatformQueueFrequency(uint8_t pulseOutput, + uint32_t frequencyHz, + uint32_t *actualFrequencyHz) +{ + if (PlsrHostFailNextFrequencyAtUpdate != 0U) + { + PlsrHostFailNextFrequencyAtUpdate = 0U; + return 0U; + } + if ((pulseOutput > 3U) || (frequencyHz == 0UL) + || (frequencyHz > PLSR_FREQUENCY_MAX_HZ) + || (actualFrequencyHz == NULL) + || (PlsrHostPulseActive[pulseOutput] == 0U)) + { + return 0U; + } + PlsrHostQueuedFrequency[pulseOutput] = frequencyHz; + *actualFrequencyHz = frequencyHz; + return 1U; +} + +void PlsrPlatformDrainPendingPulse(uint8_t pulseOutput) +{ + PlsrHostServicePendingPulse(pulseOutput); +} + +uint32_t PlsrPlatformActiveFrequency(uint8_t pulseOutput) +{ + return (pulseOutput <= 3U) ? PlsrHostFrequency[pulseOutput] : 0UL; +} + +void PlsrPlatformStopPulse(uint8_t pulseOutput) +{ + if (pulseOutput <= 3U) + { + PlsrHostPulseActive[pulseOutput] = 0U; + PlsrHostFrequency[pulseOutput] = 0UL; + PlsrHostQueuedFrequency[pulseOutput] = 0UL; + PlsrHostUpdatePending[pulseOutput] = 0U; + } +} + +uint8_t PlsrPlatformReadInput(uint8_t inputSelection) +{ + return (inputSelection <= 1U) ? PlsrHostInputs[inputSelection] : 0U; +} + +uint8_t PlsrPlatformLoad(PLSR_PERSIST_PAYLOAD *payload) +{ + if ((payload == NULL) || (PlsrHostPersistentValid == 0U)) + { + return 0U; + } + *payload = PlsrHostPersistentPayload; + return 1U; +} + +uint8_t PlsrPlatformSave(const PLSR_PERSIST_PAYLOAD *payload) +{ + if (payload == NULL) + { + return 0U; + } + PlsrHostPersistentPayload = *payload; + PlsrHostPersistentValid = 1U; + PlsrHostSaveCount++; + return 1U; +} + +void PlsrPlatformCheckpointConfig(const PLSR_CONFIG *config) +{ + if (config != NULL) + { + PlsrHostPersistentPayload.config = *config; + PlsrHostPersistentValid = 1U; + } +} + +void PlsrPlatformCheckpointPosition(int32_t position, + uint8_t positionValid, + uint8_t wasBusy) +{ + PlsrHostPersistentPayload.position = position; + PlsrHostPersistentPayload.positionValid = positionValid; + PlsrHostPersistentPayload.wasBusy = wasBusy; + PlsrHostPersistentPayload.reserved = 0U; +} + +uint32_t PlsrPlatformEnterCritical(void) +{ + if (PlsrHostEmitPulseOnCriticalEntry != 0U) + { + if (PlsrHostCriticalEntriesToSkip != 0U) + { + PlsrHostCriticalEntriesToSkip--; + } + else + { + PlsrHostEmitPulseOnCriticalEntry = 0U; + PlsrHostLatchPulse(PlsrHostSelectedPulse); + PlsrHostServicePendingPulse(PlsrHostSelectedPulse); + } + } + if (PlsrHostLatchPulseOnCriticalEntry != 0U) + { + PlsrHostLatchPulseOnCriticalEntry = 0U; + PlsrHostLatchPulse(PlsrHostSelectedPulse); + } + return 0UL; +} + +void PlsrPlatformExitCritical(uint32_t state) +{ + (void)state; + if (PlsrHostEmitPulseOnCriticalExit != 0U) + { + PlsrHostEmitPulseOnCriticalExit = 0U; + PlsrHostLatchPulse(PlsrHostSelectedPulse); + } + PlsrHostServicePendingPulse(PlsrHostSelectedPulse); +} + +void PlsrTestSetInput(uint8_t inputSelection, uint8_t level) +{ + if (inputSelection <= 1U) + { + PlsrHostInputs[inputSelection] = (level != 0U) ? 1U : 0U; + } +} + +void PlsrTestEmitPulses(uint32_t pulseCount) +{ + while ((pulseCount != 0UL) + && (PlsrHostPulseActive[PlsrHostSelectedPulse] != 0U)) + { + PlsrHostLatchPulse(PlsrHostSelectedPulse); + PlsrHostServicePendingPulse(PlsrHostSelectedPulse); + pulseCount--; + } +} + +void PlsrTestEmitPulseOnCriticalEntry(void) +{ + PlsrHostCriticalEntriesToSkip = 0U; + PlsrHostEmitPulseOnCriticalEntry = 1U; +} + +void PlsrTestEmitPulseAfterCriticalEntries(uint8_t entriesToSkip) +{ + PlsrHostCriticalEntriesToSkip = entriesToSkip; + PlsrHostEmitPulseOnCriticalEntry = 1U; +} + +void PlsrTestEmitPulseOnCriticalExit(void) +{ + PlsrHostEmitPulseOnCriticalExit = 1U; +} + +void PlsrTestLatchPulseOnCriticalEntry(void) +{ + PlsrHostLatchPulseOnCriticalEntry = 1U; +} + +void PlsrTestServicePendingPulse(void) +{ + PlsrHostServicePendingPulse(PlsrHostSelectedPulse); +} + +void PlsrTestFailNextStart(void) +{ + PlsrHostFailNextStart = 1U; +} + +void PlsrTestFailNextFrequencyAtUpdate(void) +{ + PlsrHostFailNextFrequencyAtUpdate = 1U; +} + +uint8_t PlsrTestPulseIsActive(void) +{ + return PlsrHostPulseActive[PlsrHostSelectedPulse]; +} + +uint32_t PlsrTestOutputFrequency(void) +{ + return PlsrHostFrequency[PlsrHostSelectedPulse]; +} + +uint32_t PlsrTestQueuedFrequency(void) +{ + return PlsrHostQueuedFrequency[PlsrHostSelectedPulse]; +} + +uint8_t PlsrTestDirectionLevel(void) +{ + return PlsrHostDirectionLevel; +} + +void PlsrTestClearPersistentStorage(void) +{ + (void)memset(&PlsrHostPersistentPayload, 0, + sizeof(PlsrHostPersistentPayload)); + (void)memset(PlsrHostInputs, 0, sizeof(PlsrHostInputs)); + PlsrHostPersistentValid = 0U; + PlsrHostSaveCount = 0UL; +} + +void PlsrTestResetSaveCount(void) +{ + PlsrHostSaveCount = 0UL; +} + +uint32_t PlsrTestSaveCount(void) +{ + return PlsrHostSaveCount; +} + +#else + +#include "stm32f4xx_hal.h" +#include +#include + +#define PLSR_ENABLE_IRQ_CYCLE_DIAG (0U) + +#define PLSR_FLASH_SLOT_A_ADDRESS (0x080C0000UL) +#define PLSR_FLASH_SLOT_B_ADDRESS (0x080E0000UL) +#define PLSR_FLASH_MAGIC (0x50534C52UL) +#define PLSR_FLASH_VERSION (2U) +#define PLSR_BACKUP_CONFIG_ADDRESS (BKPSRAM_BASE + 0x0100UL) +#define PLSR_BACKUP_POSITION_ADDRESS (BKPSRAM_BASE + 0x0200UL) +#define PLSR_BACKUP_CONFIG_MAGIC (0x50434647UL) +#define PLSR_BACKUP_POSITION_MAGIC (0x50504F53UL) + +typedef struct +{ + TIM_TypeDef *timer; + GPIO_TypeDef *port; + uint16_t pin; + uint8_t pinIndex; + uint8_t alternate; + IRQn_Type irq; + uint32_t timerClockHz; +} PLSR_TIMER_MAP; + +typedef struct +{ + GPIO_TypeDef *port; + uint16_t pin; +} PLSR_GPIO_MAP; + +typedef struct +{ + uint32_t prescaler; + uint32_t period; + uint32_t compare; + uint32_t actualFrequencyHz; +} PLSR_TIMER_SETTING; + +typedef struct +{ + uint32_t magic; + uint16_t version; + uint16_t payloadSize; + uint32_t generation; + PLSR_PERSIST_PAYLOAD payload; + uint32_t crc32; +} PLSR_FLASH_RECORD; + +typedef struct +{ + uint32_t magic; + PLSR_CONFIG config; + uint32_t crc32; +} PLSR_BACKUP_CONFIG_RECORD; + +typedef struct +{ + uint32_t magic; + uint32_t generation; + int32_t position; + uint8_t positionValid; + uint8_t wasBusy; + uint16_t reserved; + uint32_t crc32; +} PLSR_BACKUP_POSITION_RECORD; + +static const PLSR_TIMER_MAP PlsrTimerMap[4] = +{ + {TIM10, GPIOF, GPIO_PIN_6, 6U, GPIO_AF3_TIM10, + TIM1_UP_TIM10_IRQn, 168000000UL}, + {TIM13, GPIOF, GPIO_PIN_8, 8U, GPIO_AF9_TIM13, + TIM8_UP_TIM13_IRQn, 84000000UL}, + {TIM11, GPIOF, GPIO_PIN_7, 7U, GPIO_AF3_TIM11, + TIM1_TRG_COM_TIM11_IRQn, 168000000UL}, + {TIM14, GPIOF, GPIO_PIN_9, 9U, GPIO_AF9_TIM14, + TIM8_TRG_COM_TIM14_IRQn, 84000000UL} +}; + +static const PLSR_GPIO_MAP PlsrDirectionMap[4] = +{ + {GPIOH, GPIO_PIN_9}, + {GPIOH, GPIO_PIN_8}, + {GPIOH, GPIO_PIN_7}, + {GPIOH, GPIO_PIN_6} +}; + +static PLSR_FLASH_RECORD PlsrFlashRecordBuffer; +static uint32_t PlsrBackupPositionGeneration; +static uint32_t PlsrTimerActiveFrequencyHz[4]; +static uint32_t PlsrTimerQueuedFrequencyHz[4]; +static uint32_t PlsrTimerQueueGeneration[4]; + +static void PlsrHandleTimerIrq(uint8_t pulseOutput); + +#if PLSR_ENABLE_IRQ_CYCLE_DIAG +volatile uint32_t PlsrIrqCount[4]; +volatile uint32_t PlsrIrqLastCycles[4]; +volatile uint32_t PlsrIrqMaxCycles[4]; +#endif + +static uint32_t PlsrCrc32(const void *data, uint32_t length) +{ + const uint8_t *bytes = (const uint8_t *)data; + uint32_t crc = 0xFFFFFFFFUL; + uint32_t index; + uint8_t bit; + + for (index = 0UL; index < length; index++) + { + crc ^= bytes[index]; + for (bit = 0U; bit < 8U; bit++) + { + crc = ((crc & 1UL) != 0UL) ? ((crc >> 1U) ^ 0xEDB88320UL) + : (crc >> 1U); + } + } + return ~crc; +} + +static uint8_t PlsrGenerationIsNewer(uint32_t first, uint32_t second) +{ + return ((int32_t)(first - second) > 0) ? 1U : 0U; +} + +static uint32_t PlsrFlashRecordCrc(const PLSR_FLASH_RECORD *record) +{ + const uint8_t *start = (const uint8_t *)&record->version; + uint32_t length = (uint32_t)(offsetof(PLSR_FLASH_RECORD, crc32) + - offsetof(PLSR_FLASH_RECORD, version)); + return PlsrCrc32(start, length); +} + +static uint8_t PlsrFlashRecordIsValid(const PLSR_FLASH_RECORD *record) +{ + return ((record->magic == PLSR_FLASH_MAGIC) + && (record->version == PLSR_FLASH_VERSION) + && (record->payloadSize == sizeof(PLSR_PERSIST_PAYLOAD)) + && (record->crc32 == PlsrFlashRecordCrc(record))) ? 1U : 0U; +} + +static uint8_t PlsrBackupConfigIsValid( + const PLSR_BACKUP_CONFIG_RECORD *record) +{ + return ((record->magic == PLSR_BACKUP_CONFIG_MAGIC) + && (record->crc32 + == PlsrCrc32(&record->config, sizeof(record->config)))) ? 1U : 0U; +} + +static uint8_t PlsrBackupPositionIsValid( + const PLSR_BACKUP_POSITION_RECORD *record) +{ + uint32_t crc = PlsrCrc32(&record->generation, + sizeof(record->generation) + + sizeof(record->position) + + sizeof(record->positionValid) + + sizeof(record->wasBusy) + + sizeof(record->reserved)); + return ((record->magic == PLSR_BACKUP_POSITION_MAGIC) + && (record->crc32 == crc)) ? 1U : 0U; +} + +static const PLSR_BACKUP_POSITION_RECORD *PlsrNewestBackupPosition(void) +{ + const PLSR_BACKUP_POSITION_RECORD *slots = + (const PLSR_BACKUP_POSITION_RECORD *)PLSR_BACKUP_POSITION_ADDRESS; + uint8_t validA = PlsrBackupPositionIsValid(&slots[0]); + uint8_t validB = PlsrBackupPositionIsValid(&slots[1]); + + if ((validA == 0U) && (validB == 0U)) + { + return NULL; + } + if (validA == 0U) + { + return &slots[1]; + } + if (validB == 0U) + { + return &slots[0]; + } + return (PlsrGenerationIsNewer(slots[1].generation, + slots[0].generation) != 0U) + ? &slots[1] : &slots[0]; +} + +static void PlsrTimerStop(TIM_TypeDef *timer) +{ + timer->DIER &= ~TIM_DIER_UIE; + timer->CR1 &= ~TIM_CR1_CEN; + timer->CCER &= ~TIM_CCER_CC1E; + timer->SR = ~TIM_SR_UIF; +} + +static void PlsrTimerInitialize(TIM_TypeDef *timer) +{ + timer->CR1 = TIM_CR1_ARPE | TIM_CR1_URS; + timer->CR2 = 0UL; + timer->SMCR = 0UL; + timer->DIER = 0UL; + timer->CCMR1 = TIM_CCMR1_OC1PE | (6UL << TIM_CCMR1_OC1M_Pos); + timer->CCER = 0UL; + timer->PSC = 0UL; + timer->ARR = 999UL; + timer->CCR1 = 500UL; + timer->CNT = 0UL; + timer->EGR = TIM_EGR_UG; + timer->SR = 0UL; +} + +static void PlsrPulsePinHoldIdle(uint8_t pulseOutput) +{ + const PLSR_TIMER_MAP *map = &PlsrTimerMap[pulseOutput]; + GPIO_InitTypeDef gpio; + + HAL_GPIO_WritePin(map->port, map->pin, GPIO_PIN_SET); + gpio.Pin = map->pin; + gpio.Mode = GPIO_MODE_OUTPUT_PP; + gpio.Pull = GPIO_NOPULL; + gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + gpio.Alternate = 0U; + HAL_GPIO_Init(map->port, &gpio); +} + +static void PlsrPulsePinCaptureIdle(uint8_t pulseOutput) +{ + const PLSR_TIMER_MAP *map = &PlsrTimerMap[pulseOutput]; + uint32_t shift = (uint32_t)map->pinIndex * 2UL; + uint32_t mode = map->port->MODER; + + /* The update IRQ occurs while PWM is high; switch to GPIO high first. */ + map->port->BSRR = map->pin; + mode &= ~(3UL << shift); + mode |= 1UL << shift; + map->port->MODER = mode; + __DSB(); +} + +static void PlsrPulsePinRelease(uint8_t pulseOutput) +{ + const PLSR_TIMER_MAP *map = &PlsrTimerMap[pulseOutput]; + GPIO_InitTypeDef gpio; + + gpio.Pin = map->pin; + gpio.Mode = GPIO_MODE_AF_PP; + gpio.Pull = GPIO_NOPULL; + gpio.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + gpio.Alternate = map->alternate; + HAL_GPIO_Init(map->port, &gpio); + __DSB(); +} + +static uint8_t PlsrTimerCalculate(uint8_t pulseOutput, + uint32_t frequencyHz, + PLSR_TIMER_SETTING *setting) +{ + const PLSR_TIMER_MAP *map; + uint32_t prescalerDivider; + uint32_t denominator; + uint32_t periodCounts; + + if ((pulseOutput > 3U) || (frequencyHz == 0UL) + || (frequencyHz > PLSR_FREQUENCY_MAX_HZ) + || (setting == NULL)) + { + return 0U; + } + + map = &PlsrTimerMap[pulseOutput]; + prescalerDivider = (((map->timerClockHz - 1UL) / frequencyHz) >> 16U) + + 1UL; + if (prescalerDivider > 65536UL) + { + return 0U; + } + denominator = prescalerDivider * frequencyHz; + periodCounts = (map->timerClockHz + denominator / 2UL) / denominator; + if (periodCounts < 2UL) + { + periodCounts = 2UL; + } + if (periodCounts > 65536UL) + { + periodCounts = 65536UL; + } + + setting->prescaler = prescalerDivider - 1UL; + setting->period = periodCounts - 1UL; + setting->compare = periodCounts / 2UL; + setting->actualFrequencyHz = + map->timerClockHz / (prescalerDivider * periodCounts); + return 1U; +} + +static void PlsrTimerWriteSetting(TIM_TypeDef *timer, + const PLSR_TIMER_SETTING *setting) +{ + timer->PSC = setting->prescaler; + timer->ARR = setting->period; + timer->CCR1 = setting->compare; +} + +uint8_t PlsrPlatformInit(void) +{ + GPIO_InitTypeDef gpio; + uint8_t index; + const PLSR_BACKUP_POSITION_RECORD *positionRecord; + + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOF_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + __HAL_RCC_GPIOH_CLK_ENABLE(); + __HAL_RCC_TIM10_CLK_ENABLE(); + __HAL_RCC_TIM11_CLK_ENABLE(); + __HAL_RCC_TIM13_CLK_ENABLE(); + __HAL_RCC_TIM14_CLK_ENABLE(); + __HAL_RCC_PWR_CLK_ENABLE(); + HAL_PWR_EnableBkUpAccess(); + __HAL_RCC_BKPSRAM_CLK_ENABLE(); + if (HAL_PWREx_EnableBkUpReg() != HAL_OK) + { + return 0U; + } + +#if PLSR_ENABLE_IRQ_CYCLE_DIAG + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CYCCNT = 0UL; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + (void)memset((void *)PlsrIrqCount, 0, sizeof(PlsrIrqCount)); + (void)memset((void *)PlsrIrqLastCycles, 0, sizeof(PlsrIrqLastCycles)); + (void)memset((void *)PlsrIrqMaxCycles, 0, sizeof(PlsrIrqMaxCycles)); +#endif + + HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 + | GPIO_PIN_9, GPIO_PIN_SET); + gpio.Pin = GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9; + gpio.Mode = GPIO_MODE_OUTPUT_PP; + gpio.Pull = GPIO_NOPULL; + gpio.Speed = GPIO_SPEED_FREQ_HIGH; + gpio.Alternate = 0U; + HAL_GPIO_Init(GPIOH, &gpio); + + gpio.Mode = GPIO_MODE_INPUT; + gpio.Pull = GPIO_NOPULL; + gpio.Speed = GPIO_SPEED_FREQ_LOW; + gpio.Alternate = 0U; + gpio.Pin = GPIO_PIN_5; + HAL_GPIO_Init(GPIOB, &gpio); + gpio.Pin = GPIO_PIN_12; + HAL_GPIO_Init(GPIOG, &gpio); + + for (index = 0U; index < 4U; index++) + { + PlsrTimerActiveFrequencyHz[index] = 0UL; + PlsrTimerQueuedFrequencyHz[index] = 0UL; + PlsrTimerQueueGeneration[index] = 0UL; + PlsrTimerInitialize(PlsrTimerMap[index].timer); + PlsrPulsePinHoldIdle(index); + HAL_NVIC_SetPriority(PlsrTimerMap[index].irq, 1U, 0U); + HAL_NVIC_EnableIRQ(PlsrTimerMap[index].irq); + } + + positionRecord = PlsrNewestBackupPosition(); + PlsrBackupPositionGeneration = + (positionRecord == NULL) ? 0UL : positionRecord->generation; + return 1U; +} + +uint8_t PlsrPlatformPrepare(uint8_t pulseOutput, + uint8_t directionOutput, + uint8_t directionLevel) +{ + uint8_t index; + + if ((pulseOutput > 3U) || (directionOutput > 3U)) + { + return 0U; + } + for (index = 0U; index < 4U; index++) + { + PlsrPulsePinHoldIdle(index); + PlsrTimerStop(PlsrTimerMap[index].timer); + HAL_GPIO_WritePin(PlsrDirectionMap[index].port, + PlsrDirectionMap[index].pin, + GPIO_PIN_SET); + } + if (directionLevel != 0U) + { + HAL_GPIO_WritePin(PlsrDirectionMap[directionOutput].port, + PlsrDirectionMap[directionOutput].pin, + GPIO_PIN_RESET); + } + return 1U; +} + +uint8_t PlsrPlatformStartPulse(uint8_t pulseOutput, + uint32_t firstFrequencyHz, + uint32_t queuedFrequencyHz, + uint32_t *actualFirstFrequencyHz, + uint32_t *actualQueuedFrequencyHz) +{ + TIM_TypeDef *timer; + PLSR_TIMER_SETTING firstSetting; + PLSR_TIMER_SETTING queuedSetting; + + if ((actualFirstFrequencyHz == NULL) + || (actualQueuedFrequencyHz == NULL) + || (PlsrTimerCalculate(pulseOutput, firstFrequencyHz, + &firstSetting) == 0U) + || (PlsrTimerCalculate(pulseOutput, queuedFrequencyHz, + &queuedSetting) == 0U)) + { + return 0U; + } + timer = PlsrTimerMap[pulseOutput].timer; + timer->DIER &= ~TIM_DIER_UIE; + timer->CR1 &= ~TIM_CR1_CEN; + timer->CCER &= ~TIM_CCER_CC1E; + timer->CNT = 0UL; + PlsrTimerWriteSetting(timer, &firstSetting); + timer->EGR = TIM_EGR_UG; + PlsrTimerWriteSetting(timer, &queuedSetting); + timer->SR = 0UL; + timer->CCER |= TIM_CCER_CC1E; + __DSB(); + timer->DIER |= TIM_DIER_UIE; + PlsrPulsePinRelease(pulseOutput); + timer->CR1 |= TIM_CR1_CEN; + PlsrTimerActiveFrequencyHz[pulseOutput] = + firstSetting.actualFrequencyHz; + PlsrTimerQueuedFrequencyHz[pulseOutput] = + queuedSetting.actualFrequencyHz; + PlsrTimerQueueGeneration[pulseOutput]++; + *actualFirstFrequencyHz = firstSetting.actualFrequencyHz; + *actualQueuedFrequencyHz = queuedSetting.actualFrequencyHz; + return 1U; +} + +uint8_t PlsrPlatformQueueFrequency(uint8_t pulseOutput, + uint32_t frequencyHz, + uint32_t *actualFrequencyHz) +{ + TIM_TypeDef *timer; + PLSR_TIMER_SETTING setting; + uint32_t counterBefore; + uint32_t counterAfter; + uint32_t counterFinal; + uint32_t criticalState; + uint32_t generationBefore; + uint32_t ownGeneration; + uint8_t wrappedWhileUpdatesDisabled; + + if ((pulseOutput > 3U) || (actualFrequencyHz == NULL) + || (PlsrTimerCalculate(pulseOutput, frequencyHz, &setting) == 0U)) + { + return 0U; + } + timer = PlsrTimerMap[pulseOutput].timer; + if ((timer->CR1 & TIM_CR1_CEN) == 0UL) + { + return 0U; + } + + criticalState = PlsrPlatformEnterCritical(); + generationBefore = PlsrTimerQueueGeneration[pulseOutput]; + if ((timer->SR & TIM_SR_UIF) != 0UL) + { + PlsrHandleTimerIrq(pulseOutput); + if (PlsrTimerQueueGeneration[pulseOutput] != generationBefore) + { + *actualFrequencyHz = PlsrTimerQueuedFrequencyHz[pulseOutput]; + PlsrPlatformExitCritical(criticalState); + return 1U; + } + if ((timer->CR1 & TIM_CR1_CEN) == 0UL) + { + PlsrPlatformExitCritical(criticalState); + return 0U; + } + } + + /* UDIS blocks shadow transfers while the three preload registers are + replaced. The counter and PWM output continue without interruption. */ + counterBefore = timer->CNT; + timer->CR1 |= TIM_CR1_UDIS; + __DMB(); + if ((timer->SR & TIM_SR_UIF) != 0UL) + { + timer->CR1 &= ~TIM_CR1_UDIS; + PlsrHandleTimerIrq(pulseOutput); + if ((PlsrTimerQueueGeneration[pulseOutput] != generationBefore) + || ((timer->CR1 & TIM_CR1_CEN) == 0UL)) + { + uint8_t stillRunning = + ((timer->CR1 & TIM_CR1_CEN) != 0UL) ? 1U : 0U; + + *actualFrequencyHz = PlsrTimerQueuedFrequencyHz[pulseOutput]; + PlsrPlatformExitCritical(criticalState); + return stillRunning; + } + counterBefore = timer->CNT; + timer->CR1 |= TIM_CR1_UDIS; + __DMB(); + } + PlsrTimerWriteSetting(timer, &setting); + __DMB(); + PlsrTimerQueuedFrequencyHz[pulseOutput] = setting.actualFrequencyHz; + PlsrTimerQueueGeneration[pulseOutput]++; + ownGeneration = PlsrTimerQueueGeneration[pulseOutput]; + counterAfter = timer->CNT; + timer->CR1 &= ~TIM_CR1_UDIS; + __DMB(); + counterFinal = timer->CNT; + + /* With UDIS set an overflow does not set UIF. A wrapped counter proves + that its real output edge occurred, so account for that edge once. */ + wrappedWhileUpdatesDisabled = + ((counterAfter < counterBefore) + || ((counterFinal < counterAfter) + && ((timer->SR & TIM_SR_UIF) == 0UL))) ? 1U : 0U; + if (wrappedWhileUpdatesDisabled != 0U) + { + PlsrPulseTimerIrq(pulseOutput); + } + else if ((timer->SR & TIM_SR_UIF) != 0UL) + { + PlsrHandleTimerIrq(pulseOutput); + } + *actualFrequencyHz = + (PlsrTimerQueueGeneration[pulseOutput] == ownGeneration) + ? setting.actualFrequencyHz + : PlsrTimerQueuedFrequencyHz[pulseOutput]; + PlsrPlatformExitCritical(criticalState); + return 1U; +} + +void PlsrPlatformDrainPendingPulse(uint8_t pulseOutput) +{ + if (pulseOutput <= 3U) + { + PlsrHandleTimerIrq(pulseOutput); + } +} + +uint32_t PlsrPlatformActiveFrequency(uint8_t pulseOutput) +{ + return (pulseOutput <= 3U) + ? PlsrTimerActiveFrequencyHz[pulseOutput] : 0UL; +} + +void PlsrPlatformStopPulse(uint8_t pulseOutput) +{ + if (pulseOutput <= 3U) + { + PlsrPulsePinCaptureIdle(pulseOutput); + PlsrTimerStop(PlsrTimerMap[pulseOutput].timer); + PlsrTimerActiveFrequencyHz[pulseOutput] = 0UL; + PlsrTimerQueuedFrequencyHz[pulseOutput] = 0UL; + PlsrTimerQueueGeneration[pulseOutput]++; + } +} + +uint8_t PlsrPlatformReadInput(uint8_t inputSelection) +{ + if (inputSelection == 0U) + { + return (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_5) == GPIO_PIN_SET) ? 1U : 0U; + } + if (inputSelection == 1U) + { + return (HAL_GPIO_ReadPin(GPIOG, GPIO_PIN_12) == GPIO_PIN_SET) ? 1U : 0U; + } + return 0U; +} + +uint8_t PlsrPlatformLoad(PLSR_PERSIST_PAYLOAD *payload) +{ + const PLSR_FLASH_RECORD *slotA = + (const PLSR_FLASH_RECORD *)PLSR_FLASH_SLOT_A_ADDRESS; + const PLSR_FLASH_RECORD *slotB = + (const PLSR_FLASH_RECORD *)PLSR_FLASH_SLOT_B_ADDRESS; + const PLSR_FLASH_RECORD *selected = NULL; + const PLSR_BACKUP_CONFIG_RECORD *backupConfig = + (const PLSR_BACKUP_CONFIG_RECORD *)PLSR_BACKUP_CONFIG_ADDRESS; + const PLSR_BACKUP_POSITION_RECORD *backupPosition; + uint8_t validA; + uint8_t validB; + uint8_t haveConfig = 0U; + + if (payload == NULL) + { + return 0U; + } + + validA = PlsrFlashRecordIsValid(slotA); + validB = PlsrFlashRecordIsValid(slotB); + if ((validA != 0U) && (validB != 0U)) + { + selected = (PlsrGenerationIsNewer(slotB->generation, + slotA->generation) != 0U) + ? slotB : slotA; + } + else if (validA != 0U) + { + selected = slotA; + } + else if (validB != 0U) + { + selected = slotB; + } + + if (selected != NULL) + { + *payload = selected->payload; + haveConfig = 1U; + } + else + { + (void)memset(payload, 0, sizeof(*payload)); + } + + if (PlsrBackupConfigIsValid(backupConfig) != 0U) + { + payload->config = backupConfig->config; + haveConfig = 1U; + } + backupPosition = PlsrNewestBackupPosition(); + if (backupPosition != NULL) + { + payload->position = backupPosition->position; + payload->positionValid = backupPosition->positionValid; + payload->wasBusy = backupPosition->wasBusy; + } + return haveConfig; +} + +uint8_t PlsrPlatformSave(const PLSR_PERSIST_PAYLOAD *payload) +{ + const PLSR_FLASH_RECORD *slotA = + (const PLSR_FLASH_RECORD *)PLSR_FLASH_SLOT_A_ADDRESS; + const PLSR_FLASH_RECORD *slotB = + (const PLSR_FLASH_RECORD *)PLSR_FLASH_SLOT_B_ADDRESS; + uint8_t validA; + uint8_t validB; + uint32_t newestGeneration = 0UL; + uint32_t targetAddress; + uint32_t targetSector; + uint32_t sectorError; + uint32_t index; + uint32_t wordCount; + const uint32_t *words; + FLASH_EraseInitTypeDef erase; + HAL_StatusTypeDef status = HAL_OK; + + if (payload == NULL) + { + return 0U; + } + + validA = PlsrFlashRecordIsValid(slotA); + validB = PlsrFlashRecordIsValid(slotB); + if ((validA != 0U) && (validB != 0U)) + { + if (PlsrGenerationIsNewer(slotB->generation, slotA->generation) != 0U) + { + newestGeneration = slotB->generation; + targetAddress = PLSR_FLASH_SLOT_A_ADDRESS; + targetSector = FLASH_SECTOR_10; + } + else + { + newestGeneration = slotA->generation; + targetAddress = PLSR_FLASH_SLOT_B_ADDRESS; + targetSector = FLASH_SECTOR_11; + } + } + else if (validA != 0U) + { + newestGeneration = slotA->generation; + targetAddress = PLSR_FLASH_SLOT_B_ADDRESS; + targetSector = FLASH_SECTOR_11; + } + else if (validB != 0U) + { + newestGeneration = slotB->generation; + targetAddress = PLSR_FLASH_SLOT_A_ADDRESS; + targetSector = FLASH_SECTOR_10; + } + else + { + targetAddress = PLSR_FLASH_SLOT_A_ADDRESS; + targetSector = FLASH_SECTOR_10; + } + + (void)memset(&PlsrFlashRecordBuffer, 0, sizeof(PlsrFlashRecordBuffer)); + PlsrFlashRecordBuffer.magic = PLSR_FLASH_MAGIC; + PlsrFlashRecordBuffer.version = PLSR_FLASH_VERSION; + PlsrFlashRecordBuffer.payloadSize = sizeof(PLSR_PERSIST_PAYLOAD); + PlsrFlashRecordBuffer.generation = newestGeneration + 1UL; + PlsrFlashRecordBuffer.payload = *payload; + PlsrFlashRecordBuffer.crc32 = PlsrFlashRecordCrc(&PlsrFlashRecordBuffer); + + if (HAL_FLASH_Unlock() != HAL_OK) + { + return 0U; + } + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR + | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR + | FLASH_FLAG_PGSERR); + erase.TypeErase = FLASH_TYPEERASE_SECTORS; + erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; + erase.Sector = targetSector; + erase.NbSectors = 1U; + if (HAL_FLASHEx_Erase(&erase, §orError) != HAL_OK) + { + status = HAL_ERROR; + } + + words = (const uint32_t *)&PlsrFlashRecordBuffer; + wordCount = sizeof(PlsrFlashRecordBuffer) / sizeof(uint32_t); + if (status == HAL_OK) + { + for (index = 1UL; index < wordCount; index++) + { + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, + targetAddress + index * 4UL, + words[index]) != HAL_OK) + { + status = HAL_ERROR; + break; + } + } + } + if ((status == HAL_OK) + && (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, targetAddress, + PLSR_FLASH_MAGIC) != HAL_OK)) + { + status = HAL_ERROR; + } + if (HAL_FLASH_Lock() != HAL_OK) + { + status = HAL_ERROR; + } + + if ((status == HAL_OK) + && (PlsrFlashRecordIsValid( + (const PLSR_FLASH_RECORD *)targetAddress) != 0U)) + { + return 1U; + } + return 0U; +} + +void PlsrPlatformCheckpointConfig(const PLSR_CONFIG *config) +{ + PLSR_BACKUP_CONFIG_RECORD *record = + (PLSR_BACKUP_CONFIG_RECORD *)PLSR_BACKUP_CONFIG_ADDRESS; + + if (config == NULL) + { + return; + } + record->magic = 0UL; + record->config = *config; + record->crc32 = PlsrCrc32(&record->config, sizeof(record->config)); + __DMB(); + record->magic = PLSR_BACKUP_CONFIG_MAGIC; + __DMB(); +} + +void PlsrPlatformCheckpointPosition(int32_t position, + uint8_t positionValid, + uint8_t wasBusy) +{ + PLSR_BACKUP_POSITION_RECORD *slots = + (PLSR_BACKUP_POSITION_RECORD *)PLSR_BACKUP_POSITION_ADDRESS; + PLSR_BACKUP_POSITION_RECORD *record; + + PlsrBackupPositionGeneration++; + record = &slots[PlsrBackupPositionGeneration & 1UL]; + record->magic = 0UL; + record->generation = PlsrBackupPositionGeneration; + record->position = position; + record->positionValid = (positionValid != 0U) ? 1U : 0U; + record->wasBusy = (wasBusy != 0U) ? 1U : 0U; + record->reserved = 0U; + record->crc32 = PlsrCrc32(&record->generation, + sizeof(record->generation) + + sizeof(record->position) + + sizeof(record->positionValid) + + sizeof(record->wasBusy) + + sizeof(record->reserved)); + __DMB(); + record->magic = PLSR_BACKUP_POSITION_MAGIC; + __DMB(); +} + +uint32_t PlsrPlatformEnterCritical(void) +{ + uint32_t state = __get_PRIMASK(); + __disable_irq(); + __DMB(); + return state; +} + +void PlsrPlatformExitCritical(uint32_t state) +{ + __DMB(); + if (state == 0UL) + { + __enable_irq(); + } +} + +static void PlsrHandleTimerIrq(uint8_t pulseOutput) +{ + TIM_TypeDef *timer = PlsrTimerMap[pulseOutput].timer; +#if PLSR_ENABLE_IRQ_CYCLE_DIAG + uint32_t startedAt; + uint32_t elapsedCycles; +#endif + + if (((timer->SR & TIM_SR_UIF) != 0UL) + && ((timer->DIER & TIM_DIER_UIE) != 0UL)) + { +#if PLSR_ENABLE_IRQ_CYCLE_DIAG + startedAt = DWT->CYCCNT; +#endif + timer->SR = ~TIM_SR_UIF; + PlsrTimerActiveFrequencyHz[pulseOutput] = + PlsrTimerQueuedFrequencyHz[pulseOutput]; + PlsrPulseTimerIrq(pulseOutput); +#if PLSR_ENABLE_IRQ_CYCLE_DIAG + elapsedCycles = DWT->CYCCNT - startedAt; + PlsrIrqCount[pulseOutput]++; + PlsrIrqLastCycles[pulseOutput] = elapsedCycles; + if (elapsedCycles > PlsrIrqMaxCycles[pulseOutput]) + { + PlsrIrqMaxCycles[pulseOutput] = elapsedCycles; + } +#endif + } +} + +void TIM1_UP_TIM10_IRQHandler(void) +{ + PlsrHandleTimerIrq(0U); +} + +void TIM8_UP_TIM13_IRQHandler(void) +{ + PlsrHandleTimerIrq(1U); +} + +void TIM1_TRG_COM_TIM11_IRQHandler(void) +{ + PlsrHandleTimerIrq(2U); +} + +void TIM8_TRG_COM_TIM14_IRQHandler(void) +{ + PlsrHandleTimerIrq(3U); +} + +#endif /* PLSR_HOST_TEST */ diff --git a/tests/plsr_host/run_tests.ps1 b/tests/plsr_host/run_tests.ps1 new file mode 100644 index 0000000..9c2cd76 --- /dev/null +++ b/tests/plsr_host/run_tests.ps1 @@ -0,0 +1,42 @@ +$ErrorActionPreference = "Stop" + +$gcc = "D:\Dev-Cpp\MinGW64\bin\gcc.exe" +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path +$temporaryExe = Join-Path ([System.IO.Path]::GetTempPath()) ` + ("plsr_host_tests_{0}.exe" -f [System.Guid]::NewGuid().ToString("N")) +$exitCode = 1 + +if (-not (Test-Path -LiteralPath $gcc)) { + throw "Required compiler not found: $gcc" +} + +$compileArguments = @( + "-std=c99" + "-Wall" + "-Wextra" + "-Werror" + "-DPLSR_HOST_TEST" + "-I$repoRoot\PLSR\Inc" + "-I$repoRoot\PLSR\Src" + "$repoRoot\PLSR\Src\plsr.c" + "$repoRoot\PLSR\Src\plsr_platform_f407.c" + "$PSScriptRoot\test_plsr_host.c" + "-o" + $temporaryExe +) + +try { + & $gcc @compileArguments + if ($LASTEXITCODE -ne 0) { + $exitCode = $LASTEXITCODE + } + else { + & $temporaryExe + $exitCode = $LASTEXITCODE + } +} +finally { + Remove-Item -LiteralPath $temporaryExe -Force -ErrorAction SilentlyContinue +} + +exit $exitCode diff --git a/tests/plsr_host/test_plsr_host.c b/tests/plsr_host/test_plsr_host.c new file mode 100644 index 0000000..033b6bd --- /dev/null +++ b/tests/plsr_host/test_plsr_host.c @@ -0,0 +1,1964 @@ +#include "plsr.h" + +#include +#include + +#define PLSR_WAIT_TIME (0U) +#define PLSR_WAIT_SIGNAL (1U) +#define PLSR_ACT_TIME (2U) +#define PLSR_EXT_SIGNAL (3U) +#define PLSR_EXT_OR_COMPLETE (4U) + +#define PLSR_SEND_COMPLETE (0U) +#define PLSR_SEND_SUBSEQUENT (1U) +#define PLSR_POSITION_RELATIVE (0U) +#define PLSR_POSITION_ABSOLUTE (1U) + +#define PLSR_COMMAND_START (0x0001U) +#define PLSR_COMMAND_STOP (0x0002U) +#define PLSR_COMMAND_CLEAR (0x0004U) +#define PLSR_ERROR_COUNT (5U) + +static const char *CurrentTest; +static unsigned int AssertionCount; +static unsigned int FailureCount; + +static void ExpectUnsigned(unsigned long expected, + unsigned long actual, + const char *expression, + int line) +{ + AssertionCount++; + if (expected != actual) + { + FailureCount++; + (void)printf("FAIL %s:%d: %s expected %lu, got %lu\n", + CurrentTest, line, expression, expected, actual); + } +} + +static void ExpectSigned(long expected, + long actual, + const char *expression, + int line) +{ + AssertionCount++; + if (expected != actual) + { + FailureCount++; + (void)printf("FAIL %s:%d: %s expected %ld, got %ld\n", + CurrentTest, line, expression, expected, actual); + } +} + +static void ExpectTrue(int condition, const char *expression, int line) +{ + AssertionCount++; + if (!condition) + { + FailureCount++; + (void)printf("FAIL %s:%d: expected true: %s\n", + CurrentTest, line, expression); + } +} + +#define EXPECT_U(expected, actual) \ + ExpectUnsigned((unsigned long)(expected), \ + (unsigned long)(actual), #actual, __LINE__) +#define EXPECT_I(expected, actual) \ + ExpectSigned((long)(expected), (long)(actual), #actual, __LINE__) +#define EXPECT_TRUE(expression) ExpectTrue((expression), #expression, __LINE__) + +static uint16_t LowWord(uint32_t value) +{ + return (uint16_t)(value & 0xFFFFUL); +} + +static uint16_t HighWord(uint32_t value) +{ + return (uint16_t)(value >> 16U); +} + +static void TestU64ByU32Division(void) +{ + static const uint64_t dividends[] = + { + 0ULL, 1ULL, 0xFFFFFFFFULL, 0x100000000ULL, + 0x100000001ULL, 0xFFFFFFFFFFFFFFFFULL, + 0x8000000000000000ULL, 0x7FFFFFFFFFFFFFFFULL + }; + static const uint32_t divisors[] = + { + 1UL, 2UL, 3UL, 0xFFFFUL, 0x10000UL, 100000UL, + 0x7FFFFFFFUL, 0x80000000UL, 0xFFFFFFFFUL + }; + uint32_t state = 0xA5C39E17UL; + uint64_t dividend; + uint64_t quotient; + uint32_t divisor; + uint32_t remainder; + uint32_t dividendIndex; + uint32_t divisorIndex; + uint32_t iteration; + + for (dividendIndex = 0U; + dividendIndex < (uint32_t)(sizeof(dividends) / sizeof(dividends[0])); + dividendIndex++) + { + for (divisorIndex = 0U; + divisorIndex < (uint32_t)(sizeof(divisors) / sizeof(divisors[0])); + divisorIndex++) + { + dividend = dividends[dividendIndex]; + divisor = divisors[divisorIndex]; + quotient = PlsrTestDivideU64ByU32(dividend, divisor, &remainder); + EXPECT_TRUE(quotient == dividend / divisor); + EXPECT_U((uint32_t)(dividend % divisor), remainder); + } + } + + for (iteration = 0U; iteration < 250000UL; iteration++) + { + state = state * 1664525UL + 1013904223UL; + dividend = (uint64_t)state << 32U; + state = state * 1664525UL + 1013904223UL; + dividend |= state; + state = state * 1664525UL + 1013904223UL; + divisor = state | 1UL; + + quotient = PlsrTestDivideU64ByU32(dividend, divisor, &remainder); + EXPECT_TRUE(quotient == dividend / divisor); + EXPECT_U((uint32_t)(dividend % divisor), remainder); + } +} + +static uint64_t PulsePeriodNs(uint32_t frequencyHz) +{ + return (1000000000ULL + frequencyHz / 2UL) / frequencyHz; +} + +static uint64_t UnsignedDifference64(uint64_t first, uint64_t second) +{ + return (first > second) ? (first - second) : (second - first); +} + +static uint32_t IntegerSquareRoot64(uint64_t value) +{ + uint64_t bit = (uint64_t)1U << 62U; + uint64_t root = 0ULL; + + while (bit > value) + { + bit >>= 2U; + } + while (bit != 0ULL) + { + if (value >= root + bit) + { + value -= root + bit; + root = (root >> 1U) + bit; + } + else + { + root >>= 1U; + } + bit >>= 2U; + } + return (uint32_t)root; +} + +static PLSR_MB_RESULT WriteWord(uint16_t address, uint16_t value) +{ + return PlsrModbusWriteHolding(address, 1U, &value); +} + +static PLSR_MB_RESULT WriteU32(uint16_t address, uint32_t value) +{ + uint16_t words[2]; + + words[0] = LowWord(value); + words[1] = HighWord(value); + return PlsrModbusWriteHolding(address, 2U, words); +} + +static PLSR_MB_RESULT WriteI32(uint16_t address, int32_t value) +{ + return WriteU32(address, (uint32_t)value); +} + +static uint16_t ReadWord(uint16_t address) +{ + uint16_t value = 0xDEADU; + PLSR_MB_RESULT result = PlsrModbusReadHolding(address, 1U, &value); + + EXPECT_U(PLSR_MB_OK, result); + return value; +} + +static uint32_t ReadU32(uint16_t address) +{ + uint16_t words[2] = {0xDEADU, 0xBEEFU}; + PLSR_MB_RESULT result = PlsrModbusReadHolding(address, 2U, words); + + EXPECT_U(PLSR_MB_OK, result); + return (uint32_t)words[0] | ((uint32_t)words[1] << 16U); +} + +static int32_t ReadPosition(void) +{ + return (int32_t)ReadU32(PLSR_STATUS_FIRST_ADDRESS); +} + +static uint32_t ReadFrequency(void) +{ + return ReadU32((uint16_t)(PLSR_STATUS_FIRST_ADDRESS + 2U)); +} + +static uint16_t ReadStatus(void) +{ + return ReadWord((uint16_t)(PLSR_STATUS_FIRST_ADDRESS + 4U)); +} + +static uint16_t ReadCurrentSegment(void) +{ + return ReadWord((uint16_t)(PLSR_STATUS_FIRST_ADDRESS + 5U)); +} + +static uint16_t ReadError(void) +{ + return ReadWord((uint16_t)(PLSR_STATUS_FIRST_ADDRESS + 6U)); +} + +static PLSR_MB_RESULT QueueCommand(uint16_t command) +{ + return PlsrModbusWriteHolding(PLSR_CONTROL_ADDRESS, 1U, &command); +} + +static PLSR_MB_RESULT SendCommand(uint16_t command) +{ + PLSR_MB_RESULT result = QueueCommand(command); + + if (result == PLSR_MB_OK) + { + PlsrPoll1ms(); + } + return result; +} + +static void ResetCore(void) +{ + PlsrTestClearPersistentStorage(); + EXPECT_U(1U, PlsrInit()); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); +} + +static void ConfigureCommon(uint16_t curveMode, + uint16_t positionMode, + uint16_t segmentCount, + uint16_t sendMode, + uint32_t defaultSpeedHz, + uint32_t startSpeedHz, + uint32_t stopSpeedHz, + uint16_t accelerationTimeMs, + uint16_t decelerationTimeMs) +{ + uint16_t words[0x14U]; + PLSR_MB_RESULT result; + + result = PlsrModbusReadHolding(0x1000U, 0x14U, words); + EXPECT_U(PLSR_MB_OK, result); + words[0x04U] = sendMode; + words[0x05U] = 0U; + words[0x07U] = curveMode; + words[0x08U] = positionMode; + words[0x09U] = segmentCount; + words[0x0AU] = 1U; + words[0x0BU] = LowWord(defaultSpeedHz); + words[0x0CU] = HighWord(defaultSpeedHz); + words[0x0DU] = LowWord(startSpeedHz); + words[0x0EU] = HighWord(startSpeedHz); + words[0x0FU] = 0U; + words[0x10U] = LowWord(stopSpeedHz); + words[0x11U] = HighWord(stopSpeedHz); + words[0x12U] = accelerationTimeMs; + words[0x13U] = decelerationTimeMs; + result = PlsrModbusWriteHolding(0x1000U, 0x14U, words); + EXPECT_U(PLSR_MB_OK, result); +} + +static PLSR_MB_RESULT SetSegment(uint8_t segmentNumber, + uint32_t frequencyHz, + int32_t pulses, + uint16_t waitType, + uint16_t waitTimeMs, + uint16_t actTimeMs, + uint16_t jumpSegment) +{ + uint16_t words[8]; + uint16_t address = (uint16_t)(0x1100U + + ((uint16_t)segmentNumber - 1U) * 0x10U); + + words[0] = LowWord(frequencyHz); + words[1] = HighWord(frequencyHz); + words[2] = LowWord((uint32_t)pulses); + words[3] = HighWord((uint32_t)pulses); + words[4] = waitType; + words[5] = waitTimeMs; + words[6] = actTimeMs; + words[7] = jumpSegment; + return PlsrModbusWriteHolding(address, 8U, words); +} + +static void StopAndSettle(void) +{ + unsigned int pulse; + + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_STOP)); + for (pulse = 0U; (pulse < 4U) && (PlsrTestPulseIsActive() != 0U); + pulse++) + { + PlsrTestEmitPulses(1UL); + } + PlsrPoll1ms(); + EXPECT_U(0U, PlsrTestPulseIsActive()); + EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); +} + +static void TestProtocolBoundaries(void) +{ + uint16_t value = 0U; + uint16_t values[2]; + uint16_t before; + + ResetCore(); + + EXPECT_U(PLSR_MB_NOT_HANDLED, + PlsrModbusReadHolding(0x0FFEU, 1U, &value)); + EXPECT_U(PLSR_MB_NOT_HANDLED, + PlsrModbusReadHolding(0x1198U, 1U, &value)); + EXPECT_U(PLSR_MB_ILLEGAL_ADDRESS, + PlsrModbusReadHolding(0x0FFFU, 2U, values)); + EXPECT_U(PLSR_MB_ILLEGAL_ADDRESS, + PlsrModbusReadHolding(0x1197U, 2U, values)); + EXPECT_U(PLSR_MB_ILLEGAL_ADDRESS, + PlsrModbusReadHolding(0x2006U, 2U, values)); + EXPECT_U(PLSR_MB_ILLEGAL_ADDRESS, + PlsrModbusReadHolding(0xFFFFU, 2U, values)); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, + PlsrModbusReadHolding(0x1000U, 0U, &value)); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, + PlsrModbusReadHolding(0x1000U, 1U, NULL)); + + EXPECT_U(0U, ReadWord(0x100FU)); + EXPECT_U(PLSR_MB_OK, WriteWord(0x100FU, 0U)); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, WriteWord(0x100FU, 1U)); + EXPECT_U(0U, ReadWord(0x1108U)); + EXPECT_U(PLSR_MB_OK, WriteWord(0x1108U, 0U)); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, WriteWord(0x1108U, 1U)); + + EXPECT_U(PLSR_MB_ILLEGAL_ADDRESS, WriteWord(0x100BU, 2000U)); + EXPECT_U(PLSR_MB_OK, WriteU32(0x100BU, 2000UL)); + EXPECT_U(2000UL, ReadU32(0x100BU)); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, WriteU32(0x100BU, 100001UL)); + EXPECT_U(2000UL, ReadU32(0x100BU)); + + before = ReadWord(0x1013U); + values[0] = 77U; + values[1] = 1U; + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, + PlsrModbusWriteHolding(0x1013U, 2U, values)); + EXPECT_U(before, ReadWord(0x1013U)); + + EXPECT_U(PLSR_MB_ILLEGAL_ADDRESS, + PlsrModbusWriteHolding(0x2000U, 1U, &value)); + EXPECT_U(PLSR_MB_ILLEGAL_ADDRESS, + PlsrModbusWriteHolding(0x3000U, 2U, values)); + value = 3U; + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, + PlsrModbusWriteHolding(0x3000U, 1U, &value)); + value = 0U; + EXPECT_U(PLSR_MB_OK, + PlsrModbusWriteHolding(0x3000U, 1U, &value)); + EXPECT_U(0U, ReadWord(0x3000U)); + + EXPECT_U(PLSR_MB_OK, WriteWord(0x1197U, 0U)); + EXPECT_U(0U, ReadWord(0x1197U)); + EXPECT_U(PLSR_MB_NOT_HANDLED, WriteWord(0x1198U, 0U)); +} + +static void TestWaitZeroUsesOneMillisecond(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_WAIT_TIME, 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(1UL); + + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_WAITING, ReadStatus()); + EXPECT_U(1U, ReadCurrentSegment()); + EXPECT_U(0U, PlsrTestPulseIsActive()); + + PlsrPoll1ms(); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrTestEmitPulses(1UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(2L, ReadPosition()); +} + +static void TestActZeroSkipsWithoutPulse(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 100L, PLSR_ACT_TIME, 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(0U, PlsrTestPulseIsActive()); + EXPECT_U(1U, ReadCurrentSegment()); + EXPECT_I(0L, ReadPosition()); + + PlsrPoll1ms(); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_I(0L, ReadPosition()); + PlsrTestEmitPulses(1UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(1L, ReadPosition()); +} + +static void TestRelativeAndAbsoluteZeroDisplacement(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 0L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_STATUS_RUNNING, ReadStatus()); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(0L, ReadPosition()); + + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 5L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(5UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(5L, ReadPosition()); + + EXPECT_U(PLSR_MB_OK, WriteWord(0x1008U, PLSR_POSITION_ABSOLUTE)); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 5L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(5L, ReadPosition()); +} + +static void TestSelfJumpCanStop(void) +{ + unsigned int loop; + + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 2L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 1U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + + for (loop = 0U; loop < 3U; loop++) + { + PlsrTestEmitPulses(2UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_RUNNING, ReadStatus()); + EXPECT_U(1U, ReadCurrentSegment()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + } + + StopAndSettle(); +} + +static void TestStopDeceleratesBeforeCut(void) +{ + unsigned int tick; + uint32_t middleFrequency; + + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 1000U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 10000L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_STOP)); + EXPECT_U(PLSR_STATUS_DECELERATING, ReadStatus()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + + for (tick = 0U; tick < 450U; tick++) + { + PlsrPoll1ms(); + PlsrTestEmitPulses(1UL); + } + middleFrequency = PlsrTestOutputFrequency(); + EXPECT_TRUE((middleFrequency > 100UL) && (middleFrequency < 1000UL)); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_STOP)); + + for (tick = 450U; tick < 900U; tick++) + { + PlsrPoll1ms(); + PlsrTestEmitPulses(1UL); + } + EXPECT_TRUE(PlsrTestOutputFrequency() > 100UL); + EXPECT_TRUE(PlsrTestOutputFrequency() <= 103UL); + EXPECT_U(PLSR_STATUS_DECELERATING, ReadStatus()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrTestEmitPulses(1UL); + EXPECT_U(100UL, PlsrTestOutputFrequency()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus()); + EXPECT_I(902L, ReadPosition()); +} + +static void TestStopAtPendingBoundary(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_STOP)); + EXPECT_U(PLSR_STATUS_RUNNING, ReadStatus()); + + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus()); + EXPECT_U(0U, ReadCurrentSegment()); + EXPECT_I(1L, ReadPosition()); + + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus()); + EXPECT_U(0U, PlsrTestPulseIsActive()); +} + +static void TestOneMillisecondDirectionDelay(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, WriteWord(0x1005U, 1U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 10L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(0U, PlsrTestPulseIsActive()); + + PlsrPoll1ms(); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + StopAndSettle(); +} + +static void TestExtEdgeAtNaturalBoundary(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + PlsrTestSetInput(0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_SIGNAL, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + + PlsrTestEmitPulseOnCriticalEntry(); + PlsrTestSetInput(0U, 1U); + PlsrPoll1ms(); + EXPECT_U(0U, PlsrTestPulseIsActive()); + + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(0U, ReadCurrentSegment()); + EXPECT_I(1L, ReadPosition()); +} + +static void ConfigureExtBoundaryCarryRace(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, + PLSR_SEND_SUBSEQUENT, 2000UL, 500UL, 1000UL, + 0U, 1000U); + PlsrTestSetInput(0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 2000UL, 1L, PLSR_EXT_SIGNAL, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 1000UL, 70000L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(2000UL, PlsrTestOutputFrequency()); + PlsrTestSetInput(0U, 1U); +} + +static void ExpectExtBoundaryCarry(void) +{ + EXPECT_U(0U, PlsrTestPulseIsActive()); + EXPECT_U(1U, ReadCurrentSegment()); + PlsrPoll1ms(); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(2000UL, PlsrTestOutputFrequency()); + EXPECT_I(1L, ReadPosition()); +} + +static void TestExtCutAndNaturalBoundaryInterleavings(void) +{ + ConfigureExtBoundaryCarryRace(); + PlsrTestEmitPulseAfterCriticalEntries(3U); + PlsrPoll1ms(); + ExpectExtBoundaryCarry(); + + ConfigureExtBoundaryCarryRace(); + PlsrPoll1ms(); + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrTestEmitPulses(1UL); + ExpectExtBoundaryCarry(); +} + +static void TestExtEdgeDuringDirectionDelay(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, WriteWord(0x1005U, 2U)); + PlsrTestSetInput(0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_SIGNAL, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + + PlsrTestSetInput(0U, 1U); + PlsrPoll1ms(); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrTestEmitPulses(1UL); + + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(1L, ReadPosition()); +} + +static void TestStoppedTaskClearsPendingExtEdge(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, WriteWord(0x1005U, 2U)); + PlsrTestSetInput(0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 100L, PLSR_EXT_SIGNAL, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(0U, PlsrTestPulseIsActive()); + + PlsrTestSetInput(0U, 1U); + PlsrPoll1ms(); + EXPECT_U(0U, PlsrTestPulseIsActive()); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_STOP)); + EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus()); + + PlsrTestSetInput(0U, 0U); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_I(0L, ReadPosition()); + StopAndSettle(); +} + +static void TestExtEdgeDoesNotCutSeamlessNextSegment(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, + PLSR_SEND_SUBSEQUENT, 1000UL, 1000UL, 100UL, 0U, 0U); + PlsrTestSetInput(0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 2000UL, 4L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + + PlsrTestSetInput(0U, 1U); + PlsrTestEmitPulseAfterCriticalEntries(3U); + PlsrPoll1ms(); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(2000UL, PlsrTestOutputFrequency()); + EXPECT_I(1L, ReadPosition()); + + PlsrTestEmitPulses(4UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(5L, ReadPosition()); +} + +static void TestOldRampDoesNotRetargetSeamlessNextSegment(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, + PLSR_SEND_SUBSEQUENT, 2000UL, 1000UL, 100UL, 100U, 0U); + PlsrTestSetInput(0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 2000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 3000UL, 4L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + + PlsrTestEmitPulseAfterCriticalEntries(3U); + PlsrPoll1ms(); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(3000UL, PlsrTestOutputFrequency()); + EXPECT_I(1L, ReadPosition()); + + PlsrTestEmitPulses(4UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(5L, ReadPosition()); +} + +static void TestShortProfileBoundaryMatrix(void) +{ + static const uint16_t pulseCounts[] = {1U, 2U, 10U, 99U, 100U, 101U}; + uint32_t samples[3][101]; + uint64_t previousDurationNs[3] = {0ULL, 0ULL, 0ULL}; + uint64_t durationNs[3]; + unsigned int countIndex; + uint16_t curveMode; + unsigned int pulse; + unsigned int firstPeak; + unsigned int lastPeak; + uint32_t peakFrequencyHz; + uint32_t expectedPeakHz; + uint64_t expectedDurationNs; + uint64_t toleranceNs; + uint8_t linearDiffersFromSmooth; + uint8_t smoothDiffersFromSine; + uint8_t anyLinearDiffersFromSmooth = 0U; + uint8_t anySmoothDiffersFromSine = 0U; + + for (countIndex = 0U; + countIndex < sizeof(pulseCounts) / sizeof(pulseCounts[0]); + countIndex++) + { + for (curveMode = 0U; curveMode <= 2U; curveMode++) + { + ResetCore(); + ConfigureCommon(curveMode, PLSR_POSITION_RELATIVE, 1U, + PLSR_SEND_COMPLETE, 1000UL, 100UL, 100UL, + 1000U, 1000U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, pulseCounts[countIndex], + PLSR_EXT_OR_COMPLETE, 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(1U, PlsrTestPulseIsActive()); + durationNs[curveMode] = 0ULL; + + for (pulse = 0U; pulse < pulseCounts[countIndex]; pulse++) + { + samples[curveMode][pulse] = PlsrTestOutputFrequency(); + EXPECT_TRUE(samples[curveMode][pulse] >= 100UL); + EXPECT_TRUE(samples[curveMode][pulse] <= 1000UL); + durationNs[curveMode] += + PulsePeriodNs(samples[curveMode][pulse]); + PlsrTestEmitPulses(1UL); + if ((pulse + 1U) < pulseCounts[countIndex]) + { + EXPECT_U(1U, PlsrTestPulseIsActive()); + } + } + + peakFrequencyHz = samples[curveMode][0]; + firstPeak = 0U; + lastPeak = 0U; + for (pulse = 1U; pulse < pulseCounts[countIndex]; pulse++) + { + if (samples[curveMode][pulse] > peakFrequencyHz) + { + peakFrequencyHz = samples[curveMode][pulse]; + firstPeak = pulse; + lastPeak = pulse; + } + else if (samples[curveMode][pulse] == peakFrequencyHz) + { + lastPeak = pulse; + } + } + for (pulse = 1U; pulse <= firstPeak; pulse++) + { + EXPECT_TRUE(samples[curveMode][pulse] + >= samples[curveMode][pulse - 1U]); + } + for (pulse = firstPeak + 1U; pulse <= lastPeak; pulse++) + { + EXPECT_U(peakFrequencyHz, samples[curveMode][pulse]); + } + for (pulse = lastPeak + 1U; + pulse < pulseCounts[countIndex]; pulse++) + { + EXPECT_TRUE(samples[curveMode][pulse] + <= samples[curveMode][pulse - 1U]); + } + EXPECT_TRUE((samples[curveMode][0] + > samples[curveMode][pulseCounts[countIndex] - 1U] + ? samples[curveMode][0] + - samples[curveMode][pulseCounts[countIndex] - 1U] + : samples[curveMode][pulseCounts[countIndex] - 1U] + - samples[curveMode][0]) <= 2UL); + + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I((long)pulseCounts[countIndex], ReadPosition()); + EXPECT_TRUE(durationNs[curveMode] + > previousDurationNs[curveMode]); + previousDurationNs[curveMode] = durationNs[curveMode]; + + expectedPeakHz = IntegerSquareRoot64( + 10000ULL + (uint64_t)pulseCounts[countIndex] * 1000ULL); + expectedDurationNs = + (uint64_t)2U * (expectedPeakHz - 100UL) * 1000000ULL; + toleranceNs = expectedDurationNs / 8ULL + 2000000ULL; + EXPECT_TRUE(UnsignedDifference64(durationNs[curveMode], + expectedDurationNs) + <= toleranceNs); + } + + toleranceNs = durationNs[0] / 50ULL + 1000000ULL; + EXPECT_TRUE(UnsignedDifference64(durationNs[0], durationNs[1]) + <= toleranceNs); + EXPECT_TRUE(UnsignedDifference64(durationNs[1], durationNs[2]) + <= toleranceNs); + + if (pulseCounts[countIndex] >= 10U) + { + linearDiffersFromSmooth = 0U; + smoothDiffersFromSine = 0U; + for (pulse = 0U; pulse < pulseCounts[countIndex]; pulse++) + { + if (samples[0][pulse] != samples[1][pulse]) + { + linearDiffersFromSmooth = 1U; + } + if (samples[1][pulse] != samples[2][pulse]) + { + smoothDiffersFromSine = 1U; + } + } + EXPECT_U(1U, linearDiffersFromSmooth); + if (linearDiffersFromSmooth != 0U) + { + anyLinearDiffersFromSmooth = 1U; + } + if (smoothDiffersFromSine != 0U) + { + anySmoothDiffersFromSine = 1U; + } + } + } + EXPECT_U(1U, anyLinearDiffersFromSmooth); + EXPECT_U(1U, anySmoothDiffersFromSine); +} + +static void TestShortFinalDeceleratesWithoutPoll(void) +{ + uint16_t curveMode; + unsigned int pulse; + uint32_t previousFrequencyHz; + uint32_t currentFrequencyHz; + uint64_t durationNs; + + for (curveMode = 0U; curveMode <= 2U; curveMode++) + { + ResetCore(); + ConfigureCommon(curveMode, PLSR_POSITION_RELATIVE, 1U, + PLSR_SEND_COMPLETE, 100000UL, 100000UL, 100UL, + 0U, 1000U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 100000UL, 10L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + previousFrequencyHz = PlsrTestOutputFrequency(); + EXPECT_TRUE(previousFrequencyHz >= 99990UL); + EXPECT_TRUE(previousFrequencyHz <= 100000UL); + durationNs = PulsePeriodNs(previousFrequencyHz); + + for (pulse = 1U; pulse < 10U; pulse++) + { + PlsrTestEmitPulses(1UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + currentFrequencyHz = PlsrTestOutputFrequency(); + EXPECT_TRUE(currentFrequencyHz <= previousFrequencyHz); + EXPECT_TRUE(currentFrequencyHz >= 99990UL); + durationNs += PulsePeriodNs(currentFrequencyHz); + previousFrequencyHz = currentFrequencyHz; + } + EXPECT_TRUE(durationNs >= 99990ULL); + EXPECT_TRUE(durationNs <= 100010ULL); + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I(10L, ReadPosition()); + } +} + +static void TestZeroToMaximumRampKeepsConfiguredDuration(void) +{ + uint16_t curveMode; + uint32_t firstFrequencyHz[3]; + uint32_t previousFrequencyHz; + uint32_t currentFrequencyHz; + uint32_t frequencyStepHz; + uint32_t maximumStepHz; + uint32_t pulse; + uint64_t durationNs; + const uint64_t expectedDurationNs = 20000000ULL; + const uint64_t toleranceNs = 100000ULL; + + for (curveMode = 0U; curveMode <= 2U; curveMode++) + { + ResetCore(); + ConfigureCommon(curveMode, PLSR_POSITION_RELATIVE, 1U, + PLSR_SEND_COMPLETE, 100000UL, 0UL, 100000UL, + 20U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 100000UL, 1000L, + PLSR_EXT_OR_COMPLETE, 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(1U, PlsrTestPulseIsActive()); + + previousFrequencyHz = PlsrTestOutputFrequency(); + firstFrequencyHz[curveMode] = previousFrequencyHz; + EXPECT_TRUE(previousFrequencyHz >= 400UL); + EXPECT_TRUE(previousFrequencyHz <= 2500UL); + maximumStepHz = 0UL; + durationNs = PulsePeriodNs(previousFrequencyHz); + + for (pulse = 1UL; pulse < 1000UL; pulse++) + { + PlsrTestEmitPulses(1UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + currentFrequencyHz = PlsrTestOutputFrequency(); + EXPECT_TRUE(currentFrequencyHz >= previousFrequencyHz); + EXPECT_TRUE(currentFrequencyHz <= 100000UL); + frequencyStepHz = currentFrequencyHz - previousFrequencyHz; + if (frequencyStepHz > maximumStepHz) + { + maximumStepHz = frequencyStepHz; + } + durationNs += PulsePeriodNs(currentFrequencyHz); + previousFrequencyHz = currentFrequencyHz; + } + + EXPECT_TRUE(maximumStepHz <= 10000UL); + EXPECT_TRUE(previousFrequencyHz >= 99000UL); + EXPECT_TRUE(UnsignedDifference64(durationNs, expectedDurationNs) + <= toleranceNs); + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I(1000L, ReadPosition()); + } + EXPECT_TRUE(firstFrequencyHz[0] > firstFrequencyHz[1]); + EXPECT_TRUE(firstFrequencyHz[1] > firstFrequencyHz[2]); +} + +static void TestMaximumPulseRampKeepsReachableDuration(void) +{ + uint16_t curveMode; + uint32_t previousFrequencyHz; + uint32_t currentFrequencyHz; + uint32_t frequencyStepHz; + uint32_t maximumStepHz; + uint32_t pulse; + uint32_t reachablePeakHz = IntegerSquareRoot64( + (uint64_t)2U * 65535UL * 100000UL * 1000UL / 1311UL); + uint64_t durationNs; + uint64_t expectedDurationNs = + (uint64_t)2U * 65535UL * 1000000000ULL / reachablePeakHz; + uint64_t toleranceNs = expectedDurationNs / 500ULL + 1000000ULL; + + for (curveMode = 0U; curveMode <= 2U; curveMode++) + { + ResetCore(); + ConfigureCommon(curveMode, PLSR_POSITION_RELATIVE, 1U, + PLSR_SEND_COMPLETE, 100000UL, 0UL, 100000UL, + 1311U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 100000UL, 65535L, + PLSR_EXT_OR_COMPLETE, 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(1U, PlsrTestPulseIsActive()); + + previousFrequencyHz = PlsrTestOutputFrequency(); + EXPECT_TRUE(previousFrequencyHz > 1UL); + EXPECT_TRUE(previousFrequencyHz < reachablePeakHz); + maximumStepHz = 0UL; + durationNs = PulsePeriodNs(previousFrequencyHz); + + for (pulse = 1UL; pulse < 65535UL; pulse++) + { + PlsrTestEmitPulses(1UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + currentFrequencyHz = PlsrTestOutputFrequency(); + EXPECT_TRUE(currentFrequencyHz >= previousFrequencyHz); + EXPECT_TRUE(currentFrequencyHz <= reachablePeakHz + 5UL); + frequencyStepHz = currentFrequencyHz - previousFrequencyHz; + if (frequencyStepHz > maximumStepHz) + { + maximumStepHz = frequencyStepHz; + } + durationNs += PulsePeriodNs(currentFrequencyHz); + previousFrequencyHz = currentFrequencyHz; + } + + EXPECT_TRUE(maximumStepHz <= 5000UL); + EXPECT_TRUE(previousFrequencyHz * 100UL + >= reachablePeakHz * 99UL); + EXPECT_TRUE(UnsignedDifference64(durationNs, expectedDurationNs) + <= toleranceNs); + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I(65535L, ReadPosition()); + } +} + +static void TestMaximumSingleRampWithoutPoll(void) +{ + uint16_t curveMode; + uint32_t previousFrequencyHz; + uint32_t currentFrequencyHz; + uint32_t minimumFrequencyHz; + uint32_t maximumFrequencyHz; + uint32_t pulse; + + for (curveMode = 0U; curveMode <= 2U; curveMode++) + { + ResetCore(); + ConfigureCommon(curveMode, PLSR_POSITION_RELATIVE, 1U, + PLSR_SEND_COMPLETE, 1000UL, 100000UL, 0UL, + 1000U, 1000U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 100000UL, 65535L, + PLSR_EXT_OR_COMPLETE, 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + previousFrequencyHz = PlsrTestOutputFrequency(); + minimumFrequencyHz = previousFrequencyHz; + maximumFrequencyHz = previousFrequencyHz; + EXPECT_TRUE(previousFrequencyHz >= 99000UL); + EXPECT_TRUE(previousFrequencyHz <= 100000UL); + + for (pulse = 1UL; pulse < 65535UL; pulse++) + { + PlsrTestEmitPulses(1UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + currentFrequencyHz = PlsrTestOutputFrequency(); + EXPECT_TRUE(currentFrequencyHz <= previousFrequencyHz); + EXPECT_TRUE(currentFrequencyHz >= 99000UL); + if (currentFrequencyHz < minimumFrequencyHz) + { + minimumFrequencyHz = currentFrequencyHz; + } + if (currentFrequencyHz > maximumFrequencyHz) + { + maximumFrequencyHz = currentFrequencyHz; + } + previousFrequencyHz = currentFrequencyHz; + } + EXPECT_TRUE(minimumFrequencyHz > 1UL); + EXPECT_TRUE(maximumFrequencyHz <= 100000UL); + EXPECT_TRUE(minimumFrequencyHz < maximumFrequencyHz); + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I(65535L, ReadPosition()); + } +} + +static void TestShortProfileTimerFailure(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 100UL, 100UL, 1000U, 1000U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 10L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestFailNextFrequencyAtUpdate(); + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_ERROR, ReadStatus()); + EXPECT_U(PLSR_ERROR_TIMER, ReadError()); + EXPECT_I(1L, ReadPosition()); +} + +static void TestStopImmediatelyAfterSubsequentHandoff(void) +{ + unsigned int tick; + + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, + PLSR_SEND_SUBSEQUENT, 1000UL, 1000UL, 100UL, + 0U, 100U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 3L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 2000UL, 20L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(3UL); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_TRUE(PlsrTestOutputFrequency() <= 2000UL); + EXPECT_TRUE(PlsrTestOutputFrequency() > 100UL); + + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_STOP)); + PlsrPoll1ms(); + PlsrTestEmitPulses(1UL); + EXPECT_U(PLSR_STATUS_DECELERATING, ReadStatus()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_TRUE(PlsrTestOutputFrequency() < 2000UL); + EXPECT_TRUE(PlsrTestOutputFrequency() > 100UL); + + for (tick = 1U; tick < 190U; tick++) + { + PlsrPoll1ms(); + } + while (PlsrTestPulseIsActive() != 0U) + { + PlsrTestEmitPulses(1UL); + } + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I(7L, ReadPosition()); +} + +static void TestShortProfileZeroSpeedEdges(void) +{ + unsigned int tick; + + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 0UL, 0UL, 1000U, 1000U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 2L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_TRUE(PlsrTestOutputFrequency() > 0UL); + PlsrTestEmitPulses(1UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_TRUE(PlsrTestOutputFrequency() > 0UL); + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(2L, ReadPosition()); + + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 0UL, 0UL, 1000U, 1000U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + for (tick = 0U; (tick < 10U) && (PlsrTestPulseIsActive() == 0U); + tick++) + { + PlsrPoll1ms(); + } + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrTestEmitPulses(1UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(1L, ReadPosition()); +} + +static void TestShortProfileDynamicRetarget(void) +{ + unsigned int tick; + uint32_t frequencyAfterRetarget; + int32_t positionBeforeStop; + int32_t positionAfterStop; + + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 100UL, 100UL, 1000U, 1000U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 50L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(3UL); + EXPECT_U(PLSR_MB_OK, WriteU32(0x1100U, 2000UL)); + PlsrPoll1ms(); + PlsrTestEmitPulses(1UL); + frequencyAfterRetarget = PlsrTestOutputFrequency(); + PlsrTestEmitPulses(1UL); + EXPECT_TRUE(PlsrTestOutputFrequency() > 0UL); + for (tick = 0U; tick < 10U; tick++) + { + PlsrPoll1ms(); + PlsrTestEmitPulses(1UL); + } + EXPECT_TRUE(PlsrTestOutputFrequency() > frequencyAfterRetarget); + + positionBeforeStop = ReadPosition(); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_STOP)); + for (tick = 0U; tick < 2000U; tick++) + { + PlsrPoll1ms(); + } + while (PlsrTestPulseIsActive() != 0U) + { + PlsrTestEmitPulses(1UL); + } + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + positionAfterStop = ReadPosition(); + EXPECT_TRUE(positionAfterStop > positionBeforeStop); + EXPECT_TRUE(positionAfterStop <= positionBeforeStop + 3L); +} + +static void TestShortProfileExtCut(void) +{ + uint32_t frequencyBeforeCut; + + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 100UL, 100UL, 1000U, 1000U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 50L, PLSR_EXT_SIGNAL, + 0U, 0U, 0U)); + PlsrTestSetInput(0U, 0U); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(3UL); + frequencyBeforeCut = PlsrTestOutputFrequency(); + PlsrTestSetInput(0U, 1U); + PlsrPoll1ms(); + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + EXPECT_U(0UL, PlsrTestOutputFrequency()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I(4L, ReadPosition()); + EXPECT_TRUE(frequencyBeforeCut > 100UL); +} + +static void TestCurrentFrequencyIsDynamic(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 100L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + EXPECT_U(PLSR_MB_OK, WriteU32(0x1100U, 2000UL)); + PlsrPoll1ms(); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + PlsrTestEmitPulses(1UL); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + EXPECT_U(1000UL, ReadFrequency()); + PlsrTestEmitPulses(1UL); + EXPECT_U(2000UL, PlsrTestOutputFrequency()); + EXPECT_U(2000UL, ReadFrequency()); + + EXPECT_U(PLSR_MB_OK, WriteI32(0x1102U, 1L)); + PlsrTestEmitPulses(2UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + StopAndSettle(); +} + +static void TestPollMailboxDefersPlatformFailure(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 100L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestFailNextFrequencyAtUpdate(); + EXPECT_U(PLSR_MB_OK, WriteU32(0x1100U, 2000UL)); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_RUNNING, ReadStatus()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_ERROR, ReadStatus()); + EXPECT_U(PLSR_ERROR_TIMER, ReadError()); + EXPECT_I(1L, ReadPosition()); +} + +static void TestFutureSegmentFrequencyAppliesOnArrival(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 3000UL, 10L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_MB_OK, WriteU32(0x1110U, 5000UL)); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + + PlsrTestEmitPulses(1UL); + PlsrPoll1ms(); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(5000UL, PlsrTestOutputFrequency()); + EXPECT_U(5000UL, ReadFrequency()); + StopAndSettle(); +} + +static void TestSubsequentFutureFrequencyRebuildsHandoff(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, + PLSR_SEND_SUBSEQUENT, 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 3L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 2000UL, 4L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_MB_OK, WriteU32(0x1110U, 5000UL)); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + + PlsrTestEmitPulses(3UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(5000UL, PlsrTestOutputFrequency()); + EXPECT_U(5000UL, ReadFrequency()); + + PlsrTestEmitPulses(4UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(7L, ReadPosition()); +} + +static void TestFutureFrequencyRaceAtLastPulse(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, + PLSR_SEND_SUBSEQUENT, 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 2000UL, 4L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + + PlsrTestEmitPulseOnCriticalExit(); + EXPECT_U(PLSR_MB_OK, WriteU32(0x1110U, 5000UL)); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(5000UL, PlsrTestOutputFrequency()); + EXPECT_I(1L, ReadPosition()); + + PlsrTestEmitPulses(4UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I(5L, ReadPosition()); +} + +static void TestLatchedUpdateDrainsBeforeFutureFrequencyCommit(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, + PLSR_SEND_SUBSEQUENT, 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 2000UL, 4L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + EXPECT_U(2000UL, PlsrTestQueuedFrequency()); + + /* The physical edge latches 2000 Hz while interrupts are masked by the + configuration commit. The ISR must consume that old handoff before + the new 5000 Hz preload replaces it. */ + PlsrTestLatchPulseOnCriticalEntry(); + EXPECT_U(PLSR_MB_OK, WriteU32(0x1110U, 5000UL)); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_I(1L, ReadPosition()); + EXPECT_U(2000UL, ReadFrequency()); + EXPECT_U(2000UL, PlsrTestOutputFrequency()); + + PlsrPoll1ms(); + EXPECT_U(5000UL, PlsrTestQueuedFrequency()); + + PlsrTestEmitPulses(1UL); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_I(2L, ReadPosition()); + EXPECT_U(5000UL, ReadFrequency()); + EXPECT_U(5000UL, PlsrTestOutputFrequency()); + + PlsrTestEmitPulses(3UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I(5L, ReadPosition()); +} + +static void TestSubsequentHandoffHasNoPollGap(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, + PLSR_SEND_SUBSEQUENT, 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 3L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 2000UL, 4L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + + PlsrTestEmitPulses(3UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(2000UL, PlsrTestOutputFrequency()); + EXPECT_I(3L, ReadPosition()); + + PlsrTestEmitPulses(4UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(7L, ReadPosition()); +} + +static void TestThreeSinglePulseSubsequentWithoutPoll(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 3U, + PLSR_SEND_SUBSEQUENT, 3000UL, 1000UL, 3000UL, + 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 2000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(3U, 3000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(1000UL, PlsrTestOutputFrequency()); + + PlsrTestEmitPulses(1UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(2U, ReadCurrentSegment()); + EXPECT_U(2000UL, PlsrTestOutputFrequency()); + EXPECT_I(1L, ReadPosition()); + + PlsrTestEmitPulses(1UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_U(3U, ReadCurrentSegment()); + EXPECT_U(3000UL, PlsrTestOutputFrequency()); + EXPECT_I(2L, ReadPosition()); + + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + EXPECT_I(3L, ReadPosition()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); +} + +static void ConfigureHandoffFailureCase(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 2U, + PLSR_SEND_SUBSEQUENT, 2000UL, 1000UL, 2000UL, + 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 3L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(2U, 2000UL, 2L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); +} + +static void ExpectHandoffTimerError(void) +{ + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_ERROR, ReadStatus()); + EXPECT_U(PLSR_ERROR_TIMER, ReadError()); + EXPECT_U(0U, ReadCurrentSegment()); + EXPECT_I(3L, ReadPosition()); +} + +static void TestHandoffQueueFailuresBecomeTimerError(void) +{ + ConfigureHandoffFailureCase(); + PlsrTestEmitPulses(1UL); + PlsrTestFailNextFrequencyAtUpdate(); + PlsrTestEmitPulses(1UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrTestEmitPulses(1UL); + ExpectHandoffTimerError(); + + ConfigureHandoffFailureCase(); + PlsrTestEmitPulses(2UL); + PlsrTestFailNextFrequencyAtUpdate(); + PlsrTestEmitPulses(1UL); + ExpectHandoffTimerError(); +} + +static void TestCommandStateRestrictions(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 100L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_STOP)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_CLEAR)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_MB_DEVICE_BUSY, SendCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_MB_DEVICE_BUSY, SendCommand(PLSR_COMMAND_CLEAR)); + EXPECT_U(PLSR_MB_DEVICE_BUSY, WriteWord(0x1000U, 1U)); + EXPECT_U(PLSR_MB_DEVICE_BUSY, WriteWord(0x1001U, 1U)); + EXPECT_U(PLSR_MB_OK, WriteWord(0x1002U, 1U)); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, SendCommand(3U)); + StopAndSettle(); +} + +static void TestCommandMailboxStartSnapshotAndConflicts(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 1000UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 20L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_U(0U, ReadCurrentSegment()); + EXPECT_U(0UL, ReadFrequency()); + EXPECT_U(0U, PlsrTestPulseIsActive()); + EXPECT_I(0L, ReadPosition()); + + EXPECT_U(PLSR_MB_OK, WriteI32(0x1102U, 1L)); + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_MB_DEVICE_BUSY, QueueCommand(PLSR_COMMAND_STOP)); + EXPECT_U(PLSR_MB_DEVICE_BUSY, QueueCommand(PLSR_COMMAND_CLEAR)); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, QueueCommand(3U)); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_U(0U, PlsrTestPulseIsActive()); + + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_RUNNING, ReadStatus()); + EXPECT_U(1U, ReadCurrentSegment()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrTestEmitPulses(1UL); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_I(1L, ReadPosition()); + StopAndSettle(); +} + +static void TestCommandMailboxStopAndClearAreDeferred(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 1000UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 100L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(2UL); + + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_STOP)); + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_STOP)); + EXPECT_U(PLSR_MB_DEVICE_BUSY, QueueCommand(PLSR_COMMAND_CLEAR)); + EXPECT_U(PLSR_MB_DEVICE_BUSY, QueueCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_STATUS_RUNNING, ReadStatus()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + EXPECT_I(2L, ReadPosition()); + + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_DECELERATING, ReadStatus()); + EXPECT_U(1U, PlsrTestPulseIsActive()); + PlsrTestEmitPulses(1UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus()); + EXPECT_U(0U, PlsrTestPulseIsActive()); + + PlsrTestSetPosition(17L, 1U); + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_CLEAR)); + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_CLEAR)); + EXPECT_U(PLSR_MB_DEVICE_BUSY, QueueCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_STATUS_STOPPED, ReadStatus()); + EXPECT_I(17L, ReadPosition()); + + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_I(0L, ReadPosition()); +} + +static void TestCommandMailboxValidatesBeforeQueue(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 1000UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 10L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + + EXPECT_U(PLSR_MB_OK, WriteWord(0x100AU, 2U)); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, QueueCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_U(0U, PlsrTestPulseIsActive()); + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_STOP)); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + + EXPECT_U(PLSR_MB_OK, WriteWord(0x100AU, 1U)); + EXPECT_U(PLSR_MB_OK, WriteWord(0x1008U, PLSR_POSITION_ABSOLUTE)); + PlsrTestSetPosition(3L, 0U); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, QueueCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_U(0U, PlsrTestPulseIsActive()); +} + +static void TestCommandMailboxStartFailureBecomesError(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 1000UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 10L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + PlsrTestFailNextStart(); + + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_ERROR, ReadStatus()); + EXPECT_U(PLSR_ERROR_INVALID_RESOURCE, ReadError()); + EXPECT_U(0U, ReadCurrentSegment()); + EXPECT_U(0U, PlsrTestPulseIsActive()); + + EXPECT_U(PLSR_MB_DEVICE_BUSY, QueueCommand(PLSR_COMMAND_START)); + EXPECT_U(PLSR_MB_OK, QueueCommand(PLSR_COMMAND_CLEAR)); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_U(PLSR_ERROR_NONE, ReadError()); +} + +static void TestWaitSignalPositivePath(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 2L, PLSR_WAIT_SIGNAL, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(2UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_WAITING, ReadStatus()); + PlsrTestSetInput(0U, 1U); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); +} + +static void TestPersistenceAcrossReinit(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 2345UL, 2345UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, WriteWord(0x1000U, 2U)); + EXPECT_U(PLSR_MB_OK, WriteWord(0x1001U, 3U)); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 2345UL, 4L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(4UL); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); + EXPECT_I(4L, ReadPosition()); + + EXPECT_U(1U, PlsrInit()); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_U(2U, ReadWord(0x1000U)); + EXPECT_U(3U, ReadWord(0x1001U)); + EXPECT_U(2345UL, ReadU32(0x1100U)); + EXPECT_I(4L, ReadPosition()); + + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_CLEAR)); + PlsrPoll1ms(); + EXPECT_U(1U, PlsrInit()); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_I(0L, ReadPosition()); +} + +static void TestPositionSchedulesFlashSave(void) +{ + unsigned int tick; + + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 1L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + for (tick = 0U; tick < 1001U; tick++) + { + PlsrPoll1ms(); + } + PlsrTestResetSaveCount(); + + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(1UL); + PlsrPoll1ms(); + EXPECT_U(0UL, PlsrTestSaveCount()); + for (tick = 0U; tick < 1001U; tick++) + { + PlsrPoll1ms(); + } + EXPECT_U(1UL, PlsrTestSaveCount()); + + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_CLEAR)); + for (tick = 0U; tick < 1001U; tick++) + { + PlsrPoll1ms(); + } + EXPECT_U(2UL, PlsrTestSaveCount()); +} + +static void TestBusyResetInvalidatesAbsolutePosition(void) +{ + unsigned int tick; + + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 100L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(5UL); + for (tick = 0U; tick < 10U; tick++) + { + PlsrPoll1ms(); + } + EXPECT_I(5L, ReadPosition()); + + EXPECT_U(1U, PlsrInit()); + EXPECT_U(PLSR_STATUS_IDLE, ReadStatus()); + EXPECT_I(5L, ReadPosition()); + EXPECT_U(PLSR_MB_OK, WriteWord(0x1008U, PLSR_POSITION_ABSOLUTE)); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 5L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_ILLEGAL_VALUE, SendCommand(PLSR_COMMAND_START)); + + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_CLEAR)); + EXPECT_I(0L, ReadPosition()); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 0L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_COMPLETED, ReadStatus()); +} + +static void TestPositionOverflowStopsWithError(void) +{ + ResetCore(); + ConfigureCommon(0U, PLSR_POSITION_RELATIVE, 1U, PLSR_SEND_COMPLETE, + 1000UL, 1000UL, 100UL, 0U, 0U); + EXPECT_U(PLSR_MB_OK, + SetSegment(1U, 1000UL, 2L, PLSR_EXT_OR_COMPLETE, + 0U, 0U, 0U)); + PlsrTestSetPosition(INT32_MAX, 1U); + EXPECT_I(INT32_MAX, ReadPosition()); + EXPECT_U(PLSR_MB_OK, SendCommand(PLSR_COMMAND_START)); + PlsrTestEmitPulses(1UL); + EXPECT_U(0U, PlsrTestPulseIsActive()); + PlsrPoll1ms(); + EXPECT_U(PLSR_STATUS_ERROR, ReadStatus()); + EXPECT_U(PLSR_ERROR_COUNT, ReadError()); + EXPECT_I(INT32_MIN, ReadPosition()); +} + +typedef void (*TEST_FUNCTION)(void); + +typedef struct +{ + const char *name; + TEST_FUNCTION function; +} TEST_CASE; + +static const TEST_CASE TestCases[] = +{ + {"u64_by_u32_division", TestU64ByU32Division}, + {"protocol_boundaries", TestProtocolBoundaries}, + {"wait_zero_one_ms", TestWaitZeroUsesOneMillisecond}, + {"act_zero_no_pulse", TestActZeroSkipsWithoutPulse}, + {"relative_absolute_zero", TestRelativeAndAbsoluteZeroDisplacement}, + {"self_jump_stop", TestSelfJumpCanStop}, + {"stop_deceleration", TestStopDeceleratesBeforeCut}, + {"stop_pending_boundary", TestStopAtPendingBoundary}, + {"direction_delay_one_ms", TestOneMillisecondDirectionDelay}, + {"ext_edge_at_natural_boundary", TestExtEdgeAtNaturalBoundary}, + {"ext_cut_natural_boundary_interleavings", + TestExtCutAndNaturalBoundaryInterleavings}, + {"ext_edge_during_direction_delay", TestExtEdgeDuringDirectionDelay}, + {"stopped_clears_pending_ext", TestStoppedTaskClearsPendingExtEdge}, + {"ext_edge_seamless_epoch", TestExtEdgeDoesNotCutSeamlessNextSegment}, + {"ramp_seamless_epoch", TestOldRampDoesNotRetargetSeamlessNextSegment}, + {"short_profile_boundary_matrix", TestShortProfileBoundaryMatrix}, + {"short_final_no_poll", TestShortFinalDeceleratesWithoutPoll}, + {"zero_to_maximum_ramp_duration", + TestZeroToMaximumRampKeepsConfiguredDuration}, + {"maximum_pulse_ramp_duration", + TestMaximumPulseRampKeepsReachableDuration}, + {"maximum_single_ramp_no_poll", TestMaximumSingleRampWithoutPoll}, + {"short_timer_failure", TestShortProfileTimerFailure}, + {"stop_after_subsequent_handoff", + TestStopImmediatelyAfterSubsequentHandoff}, + {"short_zero_speed_edges", TestShortProfileZeroSpeedEdges}, + {"short_dynamic_retarget", TestShortProfileDynamicRetarget}, + {"short_ext_cut", TestShortProfileExtCut}, + {"current_frequency_dynamic", TestCurrentFrequencyIsDynamic}, + {"poll_mailbox_failure", TestPollMailboxDefersPlatformFailure}, + {"future_frequency_on_arrival", + TestFutureSegmentFrequencyAppliesOnArrival}, + {"subsequent_future_frequency_handoff", + TestSubsequentFutureFrequencyRebuildsHandoff}, + {"future_frequency_last_pulse_race", + TestFutureFrequencyRaceAtLastPulse}, + {"latched_update_before_future_frequency_commit", + TestLatchedUpdateDrainsBeforeFutureFrequencyCommit}, + {"subsequent_no_poll_gap", TestSubsequentHandoffHasNoPollGap}, + {"three_single_pulse_no_poll", TestThreeSinglePulseSubsequentWithoutPoll}, + {"handoff_queue_failures", TestHandoffQueueFailuresBecomeTimerError}, + {"command_state_restrictions", TestCommandStateRestrictions}, + {"command_mailbox_start_snapshot", + TestCommandMailboxStartSnapshotAndConflicts}, + {"command_mailbox_stop_clear_deferred", + TestCommandMailboxStopAndClearAreDeferred}, + {"command_mailbox_validation", TestCommandMailboxValidatesBeforeQueue}, + {"command_mailbox_start_failure", + TestCommandMailboxStartFailureBecomesError}, + {"wait_signal_positive", TestWaitSignalPositivePath}, + {"persistence_reinit", TestPersistenceAcrossReinit}, + {"position_flash_save", TestPositionSchedulesFlashSave}, + {"busy_reset_absolute_gate", TestBusyResetInvalidatesAbsolutePosition}, + {"position_overflow", TestPositionOverflowStopsWithError} +}; + +int main(void) +{ + unsigned int index; + + for (index = 0U; + index < (unsigned int)(sizeof(TestCases) / sizeof(TestCases[0])); + index++) + { + unsigned int failuresBefore = FailureCount; + + CurrentTest = TestCases[index].name; + (void)printf("RUN %s\n", CurrentTest); + TestCases[index].function(); + if (FailureCount == failuresBefore) + { + (void)printf("PASS %s\n", CurrentTest); + } + else + { + (void)printf("FAIL %s (%u new failure(s))\n", + CurrentTest, FailureCount - failuresBefore); + } + } + + (void)printf("SUMMARY tests=%u assertions=%u failures=%u\n", + (unsigned int)(sizeof(TestCases) / sizeof(TestCases[0])), + AssertionCount, FailureCount); + return (FailureCount == 0U) ? 0 : 1; +}