# Слабые места обработки START/STOP Дата: 22.09.2026. Проверен код проекта 166 после переноса исправления из коммита `799e6ba` проекта 167. Документ описывает результаты просмотра кода; сценарии на устройстве не воспроизводились. Предлагаемые ниже изменения не внесены. ## Что исправлено переносом В `Source/Internal/main.c:166` команды копируются в локальные `umpstart` и `umpstop`. Проверка фронта и сохранение предыдущего состояния используют одну копию каждой команды за проход основного цикла. Раньше команда читалась дважды. При обновлении из CAN-прерывания между проверкой и сохранением состояния переход `0 → 1` мог быть пропущен: проверка видела 0, а предыдущее состояние уже сохранялось как 1. Перенос устраняет эту ошибку на уровне алгоритма, но не обеспечивает накопление событий и не решает остальные вопросы доступа из прерываний. ## 1. Команда может целиком пройти между опросами **Код:** `Source/Internal/main.c:166`, `Source/Internal/ecan.c:211`. CAN-приём записывает текущее значение в `modbus`. Основной цикл проверяет только последнее состояние. Если до следующего опроса придут включение и выключение команды (`0 → 1 → 0`), цикл увидит 0 и не выдаст импульс. Локальная копия этот случай не закрывает. **Когда существенно:** если отправитель не гарантирует достаточное время удержания команды или основной цикл задерживается. **Возможное исправление:** фиксировать фронт при приёме команды и хранить событие до обработки. Передача и сброс такого события должны быть согласованы с прерыванием. Если важен каждый повторный фронт, одного флага недостаточно: нужен счётчик либо очередь. Альтернатива — протокол с подтверждением обработки. ## 2. START и STOP могут быть активны одновременно **Код:** `Source/Internal/main.c:169–183`. Для START и STOP используются два независимых `if`. Каждый включает свой выход, не снимая противоположный. Оба выхода сбрасываются только после обнуления общего `IMPowse`. Возможны два сценария: - Одновременно обнаружены оба фронта: включаются оба выхода. Значение `RES_OUT_2` в итоге соответствует STOP, поскольку его обработка идёт второй. - STOP приходит во время активного START: включается STOP, START остаётся включённым, общий таймер запускается заново. Импульс START удлиняется. Аналогичное перекрытие возможно при обратном порядке команд. **Возможное исправление:** определить по требованиям СИФУ приоритет команд, допустимость перекрытия и поведение повторной команды. Затем реализовать переключение состояний либо отдельные таймеры. Выбирать приоритет STOP без подтверждённых требований к устройству нельзя. ## 3. Длительность импульса зависит от основного цикла **Код:** `Source/Internal/DAC.c:107`, `Source/Internal/main.c:183`. Счётчик `IMPowse` уменьшается в `Load_runner()`, вызываемом из обработчика таймера (`Source/Internal/measure.c:137`). Однако физические выходы сбрасываются в основном цикле, когда он обнаруживает нулевой счётчик. Если цикл задержался, счётчик уже может быть равен нулю, а выход остаётся активным. Перед проверкой команд выполняются, в частности, сохранение и загрузка параметров, расчёт коэффициентов. Время их выполнения в этой проверке не измерялось. Сброс также расположен внутри условия `Desk == dsk_LOAD`; если допускается смена режима во время импульса, нужен явный сброс при выходе из режима. **Возможное исправление:** завершать импульс в обработчике таймера либо аппаратно. При этом запуск и завершение должны быть синхронизированы, чтобы обработчик не снял только что запущенный импульс. ## 4. Данные, общие с прерываниями, не имеют volatile **Код:** - `Source/Internal/message.c:21` — `int modbus[ANSWER_LEN+1]`. - `Source/Internal/Include/message.h:31` — объявление массива. - `Source/Internal/Include/package.h:58` — `Modbus` приводится к `WORDE *`. - `Source/Internal/DAC.c:21` и `Source/Internal/Include/DAC.h:6` — `IMPowse`. Команды изменяются в CAN-прерывании и читаются основным циклом. `IMPowse` записывается основным циклом и уменьшается обработчиком таймера. Обычные обращения к памяти не описывают компилятору асинхронное изменение этих объектов. Конкретное проявление зависит от оптимизации и машинного кода; сбой из-за этого в ходе проверки не подтверждён. **Возможное исправление:** согласовать `volatile` в определениях, объявлениях и указателях доступа. Изменение только массива недостаточно, если макрос `Modbus` продолжит обращаться через `WORDE *` без `volatile`. Потребуется проверить и функции, которым передаётся массив. `volatile` не делает последовательность операций атомарной и не заменяет синхронизацию. Для общего счётчика отдельно нужно проверить машинные инструкции доступа и допустимые точки прерывания. ## 5. START и STOP не образуют согласованный снимок **Код:** `Source/Internal/main.c:166–167`, `Source/Internal/Include/package.h:134`. Обе команды находятся в одном слове `Commands`, но читаются двумя выражениями. Если CAN обновит слово между ними, `umpstart` может относиться к старому состоянию, а `umpstop` — к новому. Например, при смене START на STOP локальные копии могут содержать две единицы, хотя ни одно полученное слово не содержало их одновременно. Фактическое число чтений памяти зависит от сгенерированного кода. **Возможное исправление:** один раз прочитать всё слово команд через корректный `volatile`-доступ, затем выделить биты START и STOP из локальной копии. Это согласует снимок, но не решает потерю коротких событий. ## Дополнительное замечание только к проекту 167 В `../167/Source/Internal/DAC.c:139` обнаружена вероятная опечатка: ```c if(dac_stop > 16) dac_go = 16; ``` По соседним ограничениям ожидается присваивание `dac_stop = 16`. Сейчас превышение конечного значения меняет начальное, а следующая проверка может дополнительно изменить конечное значение. Этот участок отсутствует в текущем коде 166 и не перенесён вместе с `799e6ba`. Он не относится к изменениям рассматриваемого коммита. ## Порядок дальнейших действий 1. Согласовать доступ к общим данным и чтение одного слова команд. 2. Определить требования к удержанию команд, повторным фронтам и одновременным START/STOP. 3. По этим требованиям выбрать фиксацию событий и управление выходами. 4. Проверить на стенде короткую команду, смену START на STOP, оба фронта одновременно, повторную команду до завершения импульса и задержку основного цикла. Измерить длительность и перекрытие выходных импульсов. Успешная сборка подтверждает собираемость перенесённого исправления, но не проверяет перечисленные временные сценарии.