24 KiB
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 и выходные направления этих линий.
Цепочка работы:
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) выполняет:
- Опускает CS и вызывает
DSP28x_usDelay(1L). - Повторяет 16 раз: SCLK=1 → DIN=старший бит → сдвиг слова влево → задержка → SCLK=0 → задержка.
- Поднимает CS.
Передача начинается со старшего бита: D15 … D0. После кадра SCLK остаётся в 0,
DIN — в состоянии последнего бита. Между кадрами CS остаётся в 1.
Перед первым кадром dSEND отдельно не устанавливает исходное состояние SCLK.
Последовательность одного бита:
SCLK ↑ → выставить DIN → выдержать время → SCLK ↓ → выдержать время
Новый бит устанавливается после восходящего фронта, но до нисходящего. Следовательно, код рассчитан на чтение данных по нисходящему фронту; это вывод из последовательности операций, а не подтверждение режима конкретной микросхемы. При SCLK idle=0 такое поведение соответствует замыслу SPI mode 1. Если приёмник читает по восходящему фронту, нужного setup-time здесь нет. Нельзя менять фронты по привычному примеру SPI mode 0 без проверки схемы и паспорта ЦАП.
Из исходников установлены такие слова:
| Вызов | Передаваемое слово |
|---|---|
Init_DAC() |
0x9002 |
| Обычное обновление | `0x8000 |
| Ручная калибровка | `0x8000 |
Локальная переменная 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.
Расчёт кода:
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 есть строка:
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.
В данном проекте 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. Практический порядок работ и проверка
- Установить модель ЦАП, назначение GPIO9, схемные инверсии и требования timing.
- Исправить порядок готовности ЦАП при запуске и защитить деление на ноль.
- Согласовать диапазоны уставок, исправить ограничение и обрезание кода.
- Снять текущие CS/SCLK/DIN при запуске, рабочем разгоне и нагрузке CAN/RS.
- Нормировать программные задержки; аппаратный 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. Основания анализа
Факты о текущем поведении взяты из следующих файлов; номера строк относятся к рабочей копии на дату анализа:
- Формирование кадра, инициализация, расчёт выхода и разгон.
- Запуск таймера и порядок инициализации.
- Вызов Load_runner из ISR.
- Частоты и время разгона.
- Назначение линий платы.
- Mux и направления GPIO.
- Аппаратный SPI EEPROM.
- Счётчик ассемблерной задержки.
- Настройки PLL и CPU_RATE.
- Приоритеты прерываний.
Модель ЦАП и электрическая схема не установлены. Режим выборки, назначение командных битов и допустимые длительности нельзя считать подтверждёнными до сверки с документацией конкретной микросхемы. Сборка/прошивка и стендовые измерения в рамках подготовки этого документа не выполнялись.