289 lines
24 KiB
Markdown
289 lines
24 KiB
Markdown
# 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).
|
||
|
||
Модель ЦАП и электрическая схема не установлены. Режим выборки, назначение
|
||
командных битов и допустимые длительности нельзя считать подтверждёнными
|
||
до сверки с документацией конкретной микросхемы. Сборка/прошивка и стендовые
|
||
измерения в рамках подготовки этого документа не выполнялись.
|