# SPI и управление ЦАП в Бальзам 167 Дата анализа: 23.09.2026. Рассмотрена текущая рабочая копия проекта `167`. Документ описывает существующий код и предложения; прошивка в рамках этого анализа не изменялась. ## 1. Как устроено сейчас ЦАП обслуживает **программный SPI через GPIO**, реализованный в `DAC.c`. Аппаратный SPI-A используется отдельно для EEPROM в `spise2p.c`. Изменение `SpiaRegs.SPIBRR` не меняет скорость передачи в ЦАП. | Сигнал ЦАП | Вывод DSP | Имя в таблице платы | Действие | |---|---|---|---| | CS | GPIO59 | csdac, 1:13B | 0 на время кадра, затем 1 | | DIN / MOSI | GPIO60 | didac, 1:13A | данные от DSP к ЦАП | | SCLK | GPIO63 | clkdac, 1:14A | 16 тактов на слово | Обозначения контактов взяты из комментариев `GPIO_table.h`, а не из проверенной схемы. MISO в драйвере отсутствует: код отправляет команды, но не читает подтверждение или статус ЦАП. Для платы нагрузки `get_Mode()` выбирает обычные GPIO и выходные направления этих линий. Цепочка работы: ```text CPU Timer1: 1000 Гц, период 1 мс → cpu_timer1_isr_SENS(), только Desk == dsk_LOAD → Load_runner() → каждые 5 вызовов: расчёт задания / инициализация → Anal_output(kod, time_dac) → dSEND(16-битное слово) → GPIO59, GPIO60, GPIO63 → ump_log_tick() ``` `READY_FREQ=1000`, `DAC_FREQ=200`, поэтому `period_dac=5`. 200 Гц — частота обновления задания, **не частота SCLK**. `LOAD_TIME=30`, `time_dac=6000` — количество шагов, используемое в расчёте разгона. Вызов `dSEND` синхронный: обработчик таймера занят, пока не выставлены все биты. ## 2. Что происходит внутри одного SPI-кадра Функция `dSEND(word)` выполняет: 1. Опускает CS и вызывает `DSP28x_usDelay(1L)`. 2. Повторяет 16 раз: SCLK=1 → DIN=старший бит → сдвиг слова влево → задержка → SCLK=0 → задержка. 3. Поднимает CS. Передача начинается со старшего бита: D15 … D0. После кадра SCLK остаётся в 0, DIN — в состоянии последнего бита. Между кадрами CS остаётся в 1. Перед первым кадром `dSEND` отдельно не устанавливает исходное состояние SCLK. Последовательность одного бита: ```text SCLK ↑ → выставить DIN → выдержать время → SCLK ↓ → выдержать время ``` Новый бит устанавливается **после восходящего фронта**, но до нисходящего. Следовательно, код рассчитан на чтение данных по нисходящему фронту; это вывод из последовательности операций, а не подтверждение режима конкретной микросхемы. При SCLK idle=0 такое поведение соответствует замыслу SPI mode 1. Если приёмник читает по восходящему фронту, нужного setup-time здесь нет. Нельзя менять фронты по привычному примеру SPI mode 0 без проверки схемы и паспорта ЦАП. Из исходников установлены такие слова: | Вызов | Передаваемое слово | |---|---| | `Init_DAC()` | `0x9002` | | Обычное обновление | `0x8000 | (out & 0x0FFF)` | | Ручная калибровка | `0x8000 | (DAC_cal() & 0x0FFF)` | Локальная переменная `x` в `Anal_output()` каждый раз получает 0. Ветка отправки без `0x8000` сейчас не выполняется. Точный смысл командных битов, номера канала, режима питания и `0x9002` нельзя достоверно назвать: маркировка ЦАП в исследованных исходниках не указана. Нужны схема или название установленной микросхемы. После отправки `0x9002` функция `Init_DAC()` также переключает `RES_OUT_1` — для платы нагрузки это GPIO9. Это отдельная линия с неизвестным здесь электрическим назначением; считать её аппаратным RESET или LDAC без схемы нельзя. ## 3. Задержки и влияние прерываний `DSP28x_usDelay(1L)` **не задаёт 1 мкс**. Ассемблерная функция принимает счётчик циклов своего внутреннего цикла. Для микросекунд библиотека предоставляет макрос `DELAY_US`, который предварительно пересчитывает время. В комментарии самой ассемблерной функции приведена модель `9 + 5 × LoopCount` тактов процессора. Она применима с оговорками о памяти без wait-state; вызовы, GPIO-записи, цикл C и прерывания дополнительно влияют на реальную длительность. Поэтому по числу `1L` нельзя вычислять частоту SCLK как 500 кГц или 1 МГц. В проекте `XCLKIN=30 МГц`, `CLKMULT=2`, PLL множитель 4, делитель 2: расчётная частота SYSCLKOUT — **60 МГц**, если входной генератор соответствует константе. При этом `CPU_RATE` для макроса `DELAY_US` остался `6.667 нс` (150 МГц). Это несоответствие не участвует в прямом вызове `DSP28x_usDelay(1L)`, но помешает простой замене на `DELAY_US(1)`: сначала надо согласовать CPU_RATE с фактической частотой. При 60 МГц период такта примерно 16.667 нс. В таймерном ISR выполняется `EINT` после настройки `IER/MINT13`. В текущей таблице приоритетов Group9 имеет уровень 1, Timer1 — 4; разрешённые прерывания CAN/UART могут удлинять части SPI-кадра. Сам `dSEND` не запрещает прерывания и не имеет защиты от повторного входа. В найденных вызовах передача ЦАП идёт из `Load_runner`; добавлять второго вызывающего без организации единого владельца передачи нельзя. Это не доказывает потерю данных: некоторые ЦАП допускают произвольное растяжение SCLK. Следует проверить паспортные ограничения и измерить минимальные и максимальные времена под нагрузкой CAN/RS. Полный запрет прерываний на кадр может уменьшить разброс, но увеличит задержку обработки связи. ## 4. Инициализация и расчёт аналогового выхода `Setup_DAC_time()` задаёт `WAKEpowse=601`. Счётчик уменьшается с частотой 200 Гц. Во время примерно трёхсекундной стартовой фазы `Init_DAC()` вызывается шесть раз, при остатках 600, 500, 400, 300, 200, 100. После этого начинается обычный вывод. Команда `cInitDac` вызывает повторную инициализацию; на этом шаге слово задания не отправляется. При остановленном УМП счётчик `x` в `Load_runner` также вызывает периодическую инициализацию примерно раз в 5.12 с. Это другая переменная `x`, не связанная с постоянным нулём в `Anal_output`. Расчёт кода: ```text out = DAC_min + vrot × DAC_max / maxx − vrot × DAC_min / maxx при cCalibrDac: out = DAC_cal затем out &= 0x0FFF ``` Два деления здесь целочисленные. Их нельзя без проверки заменить одним: `DAC_min + vrot × (DAC_max − DAC_min) / maxx` может дать другое округление. Сначала нужно определить требуемое округление и допустимость изменения результата. В `modbus[0x7B]` сохраняется отправленный 12-битный код, а в `modbus[0x1C]` — расчётное задание в десятых долях мА. Это **не измерение выходного тока**. В режиме `cCalibrDac` эти значения могут описывать разные вещи: в ЦАП уходит ручной код, а `0x1C` остаётся результатом формулы разгона. ## 5. Что стоит исправить в первую очередь ### 5.1. Порядок запуска таймера — высокий приоритет В `main()` выполняются `timer_Init(); EnableInterrupts();`, а затем загрузка параметров и только после неё `Setup_DAC_time()`. В `cpu_timer1_isr_SENS` перед `Load_runner()` нет проверки `READY`. До настройки глобальные `period_dac`, `time_dac`, `WAKEpowse` нулевые. Если в этом промежутке сработает Timer1 на плате нагрузки, условие счётчика проходит при нулевом `period_dac`, стартовая задержка отсутствует, и путь может дойти до `Anal_output(..., 0)` — деления на ноль. Это найденный путь выполнения, не результат воспроизведения на железе. Предложение: до допуска обслуживания ЦАП настроить GPIO в известное состояние, загрузить/проверить параметры и выполнить `Setup_DAC_time`. Для этого можно отдельно разрешать ветку ЦАП флагом готовности или запускать Timer1 позднее. Нельзя просто отключить все прерывания на время загрузки: EEPROM использует Timer2. Дополнительно проверять `maxx > 0` и допустимость `period_dac` перед расчётом. ### 5.2. Ошибка ограничения уставки В `Load_runner` есть строка: ```c if (dac_stop > 16) dac_go = 16; ``` При превышении конечной уставки меняется начальная. Вероятное намерение — ограничивать `dac_stop`, но это надо подтвердить требованиями к разгонной характеристике. Ранее `dac_go` ограничивается диапазоном 0..5, а эта строка может сразу заменить его на 16. Следующая проверка способна поднять `dac_stop` до 17. Это влияет на ток независимо от исправности SPI. Предложение: описать допустимые пары начальной/конечной уставок в мА, проверять их совместно и протестировать границы. Внутри алгоритма из обеих уставок вычитается 4 мА; ограничения должны учитывать это смещение. ### 5.3. Маскирование вместо насыщения `out & 0xFFF` обрезает старшие биты, но не ограничивает числовое значение: 4096 превращается в 0, −1 — в 4095. При ошибочной калибровке или выходе за диапазон это создаёт скачок кода. Предложение: сначала проверить входные данные и ограничить результат диапазоном 0..4095, затем сформировать командное слово. Для ошибочных параметров явно определить поведение: отказ, удержание предыдущего кода или заданное проектом аварийное значение. Не выбирать «0» автоматически: код и ток связаны калибровкой и внешней схемой. ### 5.4. Реальная длительность разгона После половины диапазона `count_load` увеличивается дважды за шаг: один раз в `if(count_load > time_dac/2)`, второй — в `++count_load`. Поэтому достижение конца при `LOAD_TIME=30` занимает примерно **22.5 с**, а не 30 с, без учёта начальной фазы и задержек исполнения. Это может быть намеренной двухступенчатой характеристикой. Предложение: явно описать её либо перейти к расчёту по прошедшему времени, если требование действительно состоит в линейном разгоне за 30 с. ## 6. Улучшения непосредственно SPI | Изменение | Польза | Что проверить | |---|---|---| | Явный `dac_bus_init`: CS=1, SCLK в idle, DIN определён; затем включение выходов | Предсказуемый первый кадр | Схема, подтяжки, активные уровни | | Именованные задержки setup/hold/high/low/CS-high | Понятные временные ограничения | Паспорт ЦАП и фактическая частота DSP | | Имена констант вместо `0x9002`, `0x8000`, `0xFFF` | Ясный смысл команд | Сначала установить модель ЦАП | | Один владелец передачи и флаг занятости | Исключает наложение двух кадров | Все вызывающие и разрешённые ISR | | Отдельные функции расчёта кода и отправки слова | Проверка алгоритма без железа | Сохранение округления и масштаба | | Диагностика отправок/максимальной длительности | Показывает нагрузку и задержки | Счётчик отправок не считать ACK от ЦАП | | Удаление неиспользуемого `wast()` и недостижимой ветки `x` | Упрощает чтение | Не затронуть другой `x` в Load_runner | Не следует повышать DAC_FREQ ради ускорения SPI: меняется алгоритм обновления, число шагов и стартовые интервалы. Для других частот проверить целочисленность отношения READY_FREQ/DAC_FREQ и диапазон 16-битного `time_dac` на C28x. Текущие 6000 шагов помещаются; будущие LOAD_TIME/DAC_FREQ могут вызвать переполнение. Вариант без переделки платы: сохранить GPIO-передачу, нормировать её тайминги, измерить длительность кадра и только затем решать, нужна ли короткая критическая секция. Не переносить отправку в основной цикл без анализа задержек: в проекте есть блокирующие операции EEPROM. Если переносить — использовать явную очередь/последнее задание с оговорённым временем обслуживания и отдельными командами инициализации. ## 7. Можно ли перейти на аппаратный SPI У F28335 один SPI-A. Его выводы могут быть GPIO16/17/18/19 или GPIO54/55/56/57. GPIO60 и GPIO63 не имеют функции SPISIMOA/SPICLKA, поэтому текущие DIN/SCLK не переводятся на SPI-A одной настройкой mux. Нужны изменения соединений. Это подтверждается [таблицами выводов TI, SPRS439Q](https://www.ti.com/lit/ds/symlink/tms320f28335.pdf). В данном проекте SPI-A уже обслуживает EEPROM на GPIO16/17/18 и CS=GPIO19. Вариант общей шины требует подключения ЦАП к тем же MOSI/SCLK и отдельного CS, проверки электрической совместимости и последовательного доступа к SPI-A. Если меняются длина слова/фаза/частота, переключать настройки можно только после завершения предыдущей операции. Текущий автомат EEPROM работает через Timer2 и сам меняет формат слова: потребуется общий владелец шины. Сначала достаточно рассмотреть один 16-битный аппаратный кадр с ограниченным ожиданием либо прерыванием завершения. FIFO/DMA усложнят короткую передачу; их необходимость следует обосновывать измерениями. CS надо отпускать после фактического окончания сдвига последнего бита, а не сразу после записи TXBUF. Возможность DMA именно для выбранного периферийного блока отдельно проверять по TRM, не переносить механически решение STM32. Для будущего порта STM32 можно сохранить общий расчёт и формат слова, а отправку реализовать аппаратным SPI на выводах конкретной платы. Нынешний эмулятор лишь генерирует поля ЦАП в журнале; он не подтверждает физические SPI-фронты и выходной ток устройства. ## 8. Практический порядок работ и проверка 1. Установить модель ЦАП, назначение GPIO9, схемные инверсии и требования timing. 2. Исправить порядок готовности ЦАП при запуске и защитить деление на ноль. 3. Согласовать диапазоны уставок, исправить ограничение и обрезание кода. 4. Снять текущие CS/SCLK/DIN при запуске, рабочем разгоне и нагрузке CAN/RS. 5. Нормировать программные задержки; аппаратный SPI выбирать по результатам измерений. Логический анализатор подключить к CS, SCLK, DIN и общей земле; при необходимости добавить GPIO9. Записать 16 бит каждого кадра, порядок битов, `0x9002`, минимум/середину/максимум кода, setup/hold DIN, CS setup/hold, SCLK high/low, длину кадра и интервал обновления. Отдельно проверить первый кадр после сброса. Частоту дискретизации анализатора выбрать по измеренному SCLK с запасом. Осциллографом проверить форму фронтов и выбросы, внешним измерителем — ток. После правок проверить граничные и ошибочные уставки, `maxx=0`, калибровку, разгон/останов, cInitDac, сохранение параметров EEPROM одновременно с CAN/RS. Тесты расчёта должны фиксировать требуемое округление и поведение вне диапазона; программная трасса GPIO — количество фронтов и порядок данных. Проверка GPIO на компьютере не заменяет измерение времён на собранной плате. ## 9. Основания анализа Факты о текущем поведении взяты из следующих файлов; номера строк относятся к рабочей копии на дату анализа: - [Формирование кадра, инициализация, расчёт выхода и разгон](C:/setcorp/git/tms_periph_28335/167/Source/Internal/DAC.c:45). - [Запуск таймера и порядок инициализации](C:/setcorp/git/tms_periph_28335/167/Source/Internal/main.c:26). - [Вызов Load_runner из ISR](C:/setcorp/git/tms_periph_28335/167/Source/Internal/measure.c:119). - [Частоты и время разгона](C:/setcorp/git/tms_periph_28335/167/Source/Internal/Include/measure.h:73). - [Назначение линий платы](C:/setcorp/git/tms_periph_28335/167/Source/Internal/Include/GPIO_table.h:294). - [Mux и направления GPIO](C:/setcorp/git/tms_periph_28335/167/Source/Internal/peripher.c:44). - [Аппаратный SPI EEPROM](C:/setcorp/git/tms_periph_28335/167/Source/Internal/spise2p.c:98). - [Счётчик ассемблерной задержки](C:/setcorp/git/tms_periph_28335/167/Source/External/v120/DSP2833x_common/source/DSP2833x_usDelay.asm:62). - [Настройки PLL и CPU_RATE](C:/setcorp/git/tms_periph_28335/167/Source/External/v120/DSP2833x_common/include/DSP2833x_Examples.h:66). - [Приоритеты прерываний](C:/setcorp/git/tms_periph_28335/167/Source/External/v120/DSP2833x_common/include/DSP2833x_SWPrioritizedIsrLevels.h:61). Модель ЦАП и электрическая схема не установлены. Режим выборки, назначение командных битов и допустимые длительности нельзя считать подтверждёнными до сверки с документацией конкретной микросхемы. Сборка/прошивка и стендовые измерения в рамках подготовки этого документа не выполнялись.