/* Управление импульсами СИФУ. Чистый автомат без доступа к GPIO. * Один и тот же код используется прошивкой и host-тестом. * ВАЖНО: вызывающая сторона сериализует init/accept/tick запретом IRQ. * volatile обеспечивает обращения к общему состоянию, но не заменяет * критическую секцию: обновление нескольких полей не является атомарным. */ #ifndef UMP_PULSE_H #define UMP_PULSE_H #define UMP_START_MASK 0x0080U #define UMP_STOP_MASK 0x0040U #define UMP_PULSE_NONE 0U #define UMP_PULSE_START 1U #define UMP_PULSE_STOP 2U typedef struct { unsigned int previous; unsigned int remaining; unsigned int output; unsigned int relay; } UMP_PULSE_STATE; static void ump_pulse_init(volatile UMP_PULSE_STATE *s, unsigned int commands) { /* Уже активная команда при окончании загрузки не является новым пуском. * Снимаем физические выходы отдельно в аппаратной обёртке. * Если начальное слово нулевое, первая свежая единица будет принята: * больше не нужен случайный проход main, успевший прочитать ноль. */ s->previous = commands; s->remaining = 0; s->output = UMP_PULSE_NONE; s->relay = 0; } static void ump_pulse_accept(volatile UMP_PULSE_STATE *s, unsigned int commands, unsigned int ticks) { unsigned int rising = commands & ~s->previous; /* История относится к каждому принятому слову CAN/RS, а не к редким * снимкам main. Поэтому даже 1->0->1 между проходами main замечается. * Повторные пакеты с удерживаемой единицей не продлевают импульс. */ s->previous = commands; if (commands & UMP_STOP_MASK) { /* STOP имеет приоритет даже при одновременном фронте START. * Его удержание запрещает новый START. Запрещённый пуск не ставим * в очередь: снятие STOP не должно неожиданно запускать СИФУ. */ if (rising & UMP_STOP_MASK) { s->output = UMP_PULSE_STOP; s->remaining = ticks; s->relay = 0; } } else if (rising & UMP_START_MASK) { /* Новый START после снятия команды STOP переключает выходы. * Новый фронт во время START продлевает существующий импульс, * как раньше; отдельный электрический фронт здесь не обещается. */ s->output = UMP_PULSE_START; s->remaining = ticks; s->relay = 1; } } static void ump_pulse_tick(volatile UMP_PULSE_STATE *s) { /* Единственный владелец отсчёта — таймер 1 кГц. main не участвует * в завершении импульса, поэтому EEPROM не растягивает его. * RES_OUT_2 сохраняет последнее состояние после конца импульса. */ if (s->remaining && --s->remaining == 0) s->output = UMP_PULSE_NONE; } #endif