Исправлена обработка фронтов START/STOP в 166
Добавлены локальные копии команд и обновлены файлы прошивки. Сохранены анализ слабых мест START/STOP и тесты воспроизведения ошибки ограничения ЦАП проекта 167.
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
# Проверка ошибки ограничения DAC_stop в 167
|
||||
|
||||
Дата: 22.09.2026.
|
||||
|
||||
## Результат
|
||||
|
||||
Выполнены 7 тестов, все прошли. Это тесты воспроизведения существующей
|
||||
ошибки: успешный результат означает, что описанные сценарии подтверждены
|
||||
расчётной моделью. Код прошивки не изменялся, проверки на устройстве не было.
|
||||
|
||||
Ошибка возникает при **DAC_stop > 20** в момент захвата параметров на
|
||||
фронте START. В коде сначала вычисляется `dac_stop = DAC_stop - 4`,
|
||||
поэтому условие `dac_stop > 16` впервые выполняется при `DAC_stop = 21`.
|
||||
Оно ошибочно присваивает `dac_go = 16`, отменяя ранее применённое
|
||||
ограничение начального значения до 5. Конечное значение не ограничивается.
|
||||
|
||||
Для неотрицательных значений DAC_stop проверен весь диапазон 0..32767
|
||||
и 10 вариантов DAC_go: -100, 0, 4, 6, 8, 9, 10, 20, 100, 32767.
|
||||
Всего перебрано **327 680 пар**. Отличие от варианта с исправленной
|
||||
строкой обнаруживается ровно при DAC_stop > 20.
|
||||
|
||||
## Граничные примеры
|
||||
|
||||
`DAC_go` и `DAC_stop` — входные параметры, `dac_go` и `dac_stop` —
|
||||
внутренние значения после вычитания 4 и проверок. Таблица показывает
|
||||
именно внутренние значения, а не измеренный ток и не 12-битный код ЦАП.
|
||||
|
||||
| DAC_go | DAC_stop | Сейчас: dac_go → dac_stop | При исправлении опечатки | Проявление |
|
||||
|---:|---:|---:|---:|---|
|
||||
| 6 | 16 | 2 → 12 | 2 → 12 | Штатные значения из Default_params(), ошибки нет |
|
||||
| 6 | 19 | 2 → 15 | 2 → 15 | Ошибки нет |
|
||||
| 6 | 20 | 2 → 16 | 2 → 16 | Верхняя граница без ошибки |
|
||||
| 6 | 21 | 16 → 17 | 2 → 16 | Первый входной уровень, вызывающий ошибку |
|
||||
| 6 | 22 | 16 → 18 | 2 → 16 | Начало поднято до 16, конец не ограничен |
|
||||
| 6 | 30 | 16 → 26 | 2 → 16 | То же, с большим превышением |
|
||||
| 4 | 21 | 16 → 17 | 0 → 16 | Начало 0 заменено на 16 |
|
||||
| 9 | 21 | 16 → 17 | 5 → 16 | Разрешённое начало 5 заменено на 16 |
|
||||
| 20 | 21 | 16 → 17 | 5 → 16 | Предварительное ограничение начала до 5 отменено |
|
||||
|
||||
Под «исправлением» здесь понимается только замена
|
||||
`if(dac_stop>16) dac_go=16;` на `if(dac_stop>16) dac_stop=16;`.
|
||||
Другие правила расчёта не менялись.
|
||||
|
||||
## Что получает Anal_output
|
||||
|
||||
Для DAC_go=6, DAC_stop=21, LOAD_TIME=30 и DAC_FREQ=200:
|
||||
`time_dac = 6000`. При запуске из состояния с обнулённым count_load
|
||||
первый расчёт выполняется при count_load=1.
|
||||
|
||||
| Точка | Текущий kod | kod при исправлении | maxx |
|
||||
|---|---:|---:|---:|
|
||||
| Первый расчёт | 6000 | 750 | 6000 |
|
||||
| Конец нарастания | 6375 | 6000 | 6000 |
|
||||
|
||||
Таким образом, вместо начала с 12,5% шкалы интерполяции код сразу
|
||||
задаёт 100%, а в конце — 106,25%. Это аргументы функции Anal_output,
|
||||
не непосредственные значения, отправляемые в ЦАП.
|
||||
|
||||
Физический ток определяется калибровкой DAC_min()/DAC_max() и аппаратной
|
||||
схемой. Формула в Anal_output интерполирует между этими значениями;
|
||||
направление изменения численного кода ЦАП зависит от калибровки.
|
||||
В режиме cCalibrDac результат заменяется DAC_cal(), поэтому рассчитанное
|
||||
нарастание не определяет выход. Последующее `out & 0xFFF` маскирует
|
||||
число, а не насыщает его на границе диапазона; при выходе за 12 бит
|
||||
возможен переход через границу. Для утверждений о фактическом токе
|
||||
нужны реальные калибровочные параметры и измерения.
|
||||
|
||||
## Как сценарий может возникнуть в работе
|
||||
|
||||
1. В регистр modbus[0x51] (DAC_stop) записано 21 или больше;
|
||||
modbus[0x50] (DAC_go), например, равен 6.
|
||||
2. После завершения начальной паузы обработчик Load_runner наблюдает
|
||||
неактивный START, а затем активный: preStart=0 и cUMPstart=1.
|
||||
3. Параметры захватываются, выполняется ошибочное ограничение и начинается
|
||||
расчёт с завышенным начальным значением.
|
||||
|
||||
В ReceiveCommandModbus6 есть прямая запись значения регистра, а в приёме
|
||||
CAN — проверка адреса без ограничения значения DAC_stop до 20.
|
||||
Поэтому по этим путям код 167 сам не исключает такую настройку.
|
||||
Ограничения внешнего пульта/ПО и текущие параметры устройства не проверены.
|
||||
|
||||
Если изменить DAC_stop во время непрерывно активного START, локальные
|
||||
параметры не перечитываются на каждом вызове. Ошибка проявится при
|
||||
следующем захвате параметров. Стандартные значения DAC_go=6 и DAC_stop=16
|
||||
эту ветку не вызывают.
|
||||
|
||||
## Файлы и запуск
|
||||
|
||||
Тест: `tests/test_dac167_limit.py` в проекте 166. Он читает соседний
|
||||
`167/Source/Internal/DAC.c` и проверяет совпадение моделируемого блока
|
||||
с исходником, чтобы изменение кода не осталось незамеченным.
|
||||
|
||||
Запуск из корня 166:
|
||||
|
||||
```powershell
|
||||
py -3 -B -m unittest discover -s tests -p test_dac167_limit.py -v
|
||||
```
|
||||
|
||||
Модель проверяет ограничения параметров и выбранные точки формулы.
|
||||
Это не запуск скомпилированной прошивки и не эмуляция C2000. Перебор
|
||||
больших значений проверяет только блок ограничения: дальнейшее
|
||||
преобразование float в 16-битный int может выйти за диапазон и модель
|
||||
не делает прогнозов для такого выхода. Значения -32768..-32765 исключены
|
||||
из модели вычитания 4 из signed int из-за переполнения; это проверяется
|
||||
отдельным тестом. Конкурентные обновления CAN и временные характеристики
|
||||
прерываний этими тестами не моделируются.
|
||||
|
||||
В прошивке 166 рассматриваемого блока нет. Тест и отчёт сохранены здесь
|
||||
для анализа 167; изменения DAC.c из 167 в 166 не переносились.
|
||||
@@ -0,0 +1,144 @@
|
||||
# Слабые места обработки 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, оба фронта
|
||||
одновременно, повторную команду до завершения импульса и задержку
|
||||
основного цикла. Измерить длительность и перекрытие выходных импульсов.
|
||||
|
||||
Успешная сборка подтверждает собираемость перенесённого исправления,
|
||||
но не проверяет перечисленные временные сценарии.
|
||||
Reference in New Issue
Block a user