Files
Balsam_167/Doc/DAC_SPI_REVIEW.md

24 KiB
Raw Blame History

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) выполняет:

  1. Опускает CS и вызывает DSP28x_usDelay(1L).
  2. Повторяет 16 раз: SCLK=1 → DIN=старший бит → сдвиг слова влево → задержка → SCLK=0 → задержка.
  3. Поднимает 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. Практический порядок работ и проверка

  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. Основания анализа

Факты о текущем поведении взяты из следующих файлов; номера строк относятся к рабочей копии на дату анализа:

Модель ЦАП и электрическая схема не установлены. Режим выборки, назначение командных битов и допустимые длительности нельзя считать подтверждёнными до сверки с документацией конкретной микросхемы. Сборка/прошивка и стендовые измерения в рамках подготовки этого документа не выполнялись.