Use shared UMP protocol core and document DAC SPI review
This commit is contained in:
288
Doc/DAC_SPI_REVIEW.md
Normal file
288
Doc/DAC_SPI_REVIEW.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# 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).
|
||||
|
||||
Модель ЦАП и электрическая схема не установлены. Режим выборки, назначение
|
||||
командных битов и допустимые длительности нельзя считать подтверждёнными
|
||||
до сверки с документацией конкретной микросхемы. Сборка/прошивка и стендовые
|
||||
измерения в рамках подготовки этого документа не выполнялись.
|
||||
@@ -111,3 +111,9 @@ GUI принимает только ответ выбранной платы с
|
||||
Особенно проверить тайминги IRQ и приоритет штатной телеметрии на нагруженной
|
||||
шине. Файлы прошивки Bin/*.out/hex не заменены. Архив остаётся в ОЗУ:
|
||||
команда отключения фиксирует его, пропадание питания стирает.
|
||||
|
||||
## Общее ядро templates
|
||||
|
||||
Сервер CAN/RS логгера подключён через `Source/Internal/shared_ump.c`.
|
||||
Для сборки требуется соседний `../../templates`. Контракт и порты:
|
||||
`templates/c/set-protocol/docs/UMP_PROTOCOL.md`.
|
||||
|
||||
@@ -1,39 +1,9 @@
|
||||
/* Служебный CAN-протокол логгера УМП. Не использует адреса 0..127
|
||||
* технологического обмена. Формат полезных данных совместим с порядком
|
||||
* слов старого CAN: MDL (слова 1,2), затем MDH (маркер, слово 0).
|
||||
* Запрос: маркер 0x1F00, слово 0 = token:8 | function:8,
|
||||
* слова 1,2 = адрес расширения и число слов/записываемое значение.
|
||||
* Ответы: маркер 0x1F00 + номер фрагмента, слово 0 = token:8 | error:8,
|
||||
* слова 1,2 = два слова единого зафиксированного снимка.
|
||||
* Биты маски записи в заголовке запроса равны нулю: старая плата
|
||||
* игнорирует запрос, не изменяя технологические регистры. */
|
||||
/* Shared logger protocol; application owns IRQ protection and CAN scheduling. */
|
||||
#ifndef UMP_CAN_PROTOCOL_H
|
||||
#define UMP_CAN_PROTOCOL_H
|
||||
#include "log_to_mem.h"
|
||||
#define UMP_CAN_MARKER 0x1F00U
|
||||
#define UMP_CAN_MAX_WORDS 124U
|
||||
|
||||
/* Вызывающий код защищает весь снимок от IRQ. Функция не ждёт CAN и
|
||||
* не пишет за пределами reply[124]. Ошибки возвращаются отдельным кадром. */
|
||||
static unsigned int ump_can_prepare(unsigned int function, unsigned int address,
|
||||
unsigned int value, unsigned int *reply, unsigned int *error)
|
||||
{
|
||||
unsigned int i;
|
||||
*error = 0;
|
||||
if (function == 3) {
|
||||
if (!((address == UMP_LOG_BASE && value == 16) ||
|
||||
(address == UMP_LOG_DATA && (value == 40 || value == 80 || value == 120)) ||
|
||||
(address == UMP_LOG_LIVE && value == 124))) *error = 2;
|
||||
else {
|
||||
for (i = 0; i < value; i++) reply[i] = ump_log_read(address+i);
|
||||
return value;
|
||||
}
|
||||
} else if (function == 6) {
|
||||
if (address < UMP_LOG_BASE || address > UMP_LOG_BASE+3) *error = 2;
|
||||
else if (!ump_log_write(address, value)) *error = 3;
|
||||
} else *error = 1;
|
||||
/* Для записи возвращаем эхо адреса и значения, для ошибки — её контекст. */
|
||||
reply[0] = address; reply[1] = value;
|
||||
return 2;
|
||||
}
|
||||
#include "../../../../../templates/c/set-protocol/ports/tms320f28335/ump_tms28335.h"
|
||||
#define UMP_CAN_MARKER UMP_MARKER
|
||||
#define UMP_CAN_MAX_WORDS UMP_MAX_WORDS
|
||||
#define ump_can_prepare ump_tms28335_prepare
|
||||
#endif
|
||||
|
||||
@@ -214,7 +214,7 @@ void Handlai(volatile struct ECAN_MBOXES * ECanMboxes)
|
||||
* çàïîëíÿòü æóðíàë CAN-ñîáûòèÿìè ïðè îíëàéí-÷òåíèè. Êîðîòêèå êàäðû
|
||||
* îòáðàñûâàþòñÿ: íåçàïîëíåííûå áàéòû mailbox ìîãëè îñòàòüñÿ îò ïðîøëîãî. */
|
||||
if ((unsigned int)(hiword >> 16) == UMP_CAN_MARKER) {
|
||||
if (ECanMboxes->MBOX1.MSGCTRL.bit.DLC == 8) {
|
||||
if (ECanMboxes->MBOX1.MSGCTRL.bit.DLC == 8 && (hiword & 0xFF00UL)) {
|
||||
ump_can_tag = (unsigned int)hiword;
|
||||
ump_can_address = (unsigned int)(loword >> 16);
|
||||
ump_can_value = (unsigned int)loword;
|
||||
@@ -294,7 +294,7 @@ interrupt void CANa_reset_err(void)
|
||||
void UMP_CAN_service(void)
|
||||
{
|
||||
unsigned int saved, tag, address, value, error;
|
||||
unsigned long hiword, loword;
|
||||
uint32_t hiword, loword;
|
||||
if (ump_can_waiting) {
|
||||
if (ECanaRegs.CANTA.all & 4UL) {
|
||||
ECanaRegs.CANTA.all = 4UL;
|
||||
@@ -324,8 +324,11 @@ void UMP_CAN_service(void)
|
||||
if (ump_can_index >= ump_can_length) return;
|
||||
/* Ïåðåä ñëåäóþùèì ôðàãìåíòîì óñòóïàåì øòàòíîé òåëåìåòðèè MBOX0. */
|
||||
if (ECanaRegs.CANTRS.all & 1UL) return;
|
||||
hiword = ((unsigned long)(UMP_CAN_MARKER + ump_can_index/2) << 16) | ump_can_reply_tag;
|
||||
loword = ((unsigned long)ump_can_reply[ump_can_index] << 16) | ump_can_reply[ump_can_index+1];
|
||||
if (!ump_tms28335_fragment(ump_can_reply, ump_can_length, ump_can_index/2,
|
||||
ump_can_reply_tag >> 8, ump_can_reply_tag & 255U, &loword, &hiword)) {
|
||||
ump_can_length = 0;
|
||||
return;
|
||||
}
|
||||
ECanaRegs.CANTA.all = 4UL; ECanaRegs.CANAA.all = 4UL;
|
||||
ECanaMboxes.MBOX2.MDH.all = hiword;
|
||||
ECanaMboxes.MBOX2.MDL.all = loword;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include "caliber.h"
|
||||
#include "log_to_mem.h"
|
||||
#include "../../../../templates/c/set-protocol/ports/tms320f28335/ump_tms28335.h"
|
||||
|
||||
int modbus[ANSWER_LEN+1];
|
||||
unsigned int param[ANSWER_LEN+1];
|
||||
@@ -195,9 +196,30 @@ static void modbus_exception(RS_DATA *rs_arr, unsigned int function, unsigned in
|
||||
RS_Send(rs_arr, rs_arr->buffer, 5);
|
||||
}
|
||||
|
||||
/* UART parser has already validated CRC and accepted the device address. */
|
||||
static int receive_ump(RS_DATA *rs_arr)
|
||||
{
|
||||
unsigned int address, value, saved, length, count, error;
|
||||
uint16_t words[UMP_MAX_WORDS];
|
||||
address = (rs_arr->RS_Header[2] << 8) | rs_arr->RS_Header[3];
|
||||
if (address < UMP_BASE) return 0;
|
||||
value = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5];
|
||||
saved = __disable_interrupts();
|
||||
count = ump_tms28335_capture(rs_arr->RS_Header[1], address, value, words, &error);
|
||||
__restore_interrupts(saved);
|
||||
length = ump_rtu_encode(CNTRL_ADDR, rs_arr->RS_Header[1], error,
|
||||
words, count, rs_arr->buffer, MAX_SEND_LENGTH);
|
||||
if (length) {
|
||||
rs_arr->flag_TIMEOUT_to_Send = true;
|
||||
RS_Send(rs_arr, rs_arr->buffer, length);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void ReceiveCommandModbus3(RS_DATA *rs_arr)
|
||||
{
|
||||
unsigned int crc, Address_MB, Length_MB, i, value, saved = 0;
|
||||
unsigned int crc, Address_MB, Length_MB, i, value;
|
||||
if (receive_ump(rs_arr)) return;
|
||||
|
||||
// ïîëó÷èëè íà÷àëüíûé àäðåñ ÷òåíèà
|
||||
Address_MB = (rs_arr->RS_Header[2] << 8) | rs_arr->RS_Header[3];
|
||||
@@ -205,12 +227,7 @@ void ReceiveCommandModbus3(RS_DATA *rs_arr)
|
||||
// ïîëó÷èëè êîëè÷åñòâî ñëîâ äàííûõ
|
||||
Length_MB = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5];
|
||||
if (!Length_MB || Length_MB > 125 ||
|
||||
!((Address_MB < ANSWER_LEN && Length_MB <= ANSWER_LEN-Address_MB) ||
|
||||
(Address_MB >= UMP_LOG_BASE && Address_MB <= UMP_LOG_BASE+15 &&
|
||||
Length_MB <= UMP_LOG_BASE+16-Address_MB) ||
|
||||
(Address_MB >= UMP_LOG_DATA && Address_MB < UMP_LOG_DATA+120 &&
|
||||
Length_MB <= UMP_LOG_DATA+120-Address_MB) ||
|
||||
(Address_MB == UMP_LOG_LIVE && Length_MB == 124))) {
|
||||
!(Address_MB < ANSWER_LEN && Length_MB <= ANSWER_LEN-Address_MB)) {
|
||||
modbus_exception(rs_arr, CMD_MODBUS_3, 2); return;
|
||||
}
|
||||
|
||||
@@ -224,15 +241,13 @@ void ReceiveCommandModbus3(RS_DATA *rs_arr)
|
||||
|
||||
/* Çàùèùàåì öåëîñòíîñòü îäíîãî ñíèìêà/áëîêà; ïåðåäà÷à UART èä¸ò óæå
|
||||
* ñ ðàçðåø¸ííûìè ïðåðûâàíèÿìè è íå çàäåðæèâàåò èçìåðåíèå/óïðàâëåíèå. */
|
||||
if (Address_MB >= UMP_LOG_BASE) saved = __disable_interrupts();
|
||||
for (i=0;i<Length_MB;i++)
|
||||
{
|
||||
value = Address_MB >= UMP_LOG_BASE ? ump_log_read(Address_MB+i) : modbus[Address_MB+i];
|
||||
value = modbus[Address_MB+i];
|
||||
rs_arr->buffer[3+i*2] = (value >> 8) & 0xFF;
|
||||
rs_arr->buffer[3+i*2+1] = value & 0xFF;
|
||||
}
|
||||
|
||||
if (Address_MB >= UMP_LOG_BASE) __restore_interrupts(saved);
|
||||
crc = 0xffff;
|
||||
crc = get_crc_16(crc, rs_arr->buffer, Length_MB*2+3);
|
||||
|
||||
@@ -248,7 +263,7 @@ void ReceiveCommandModbus3(RS_DATA *rs_arr)
|
||||
/* Ðàñøèðåíèå ëîããåðà îòâå÷àåò òî÷íûì êàäðîì Modbus áåç õâîñòîâûõ
|
||||
* íóëåé: îíè èíà÷å ïîïàëè áû â íà÷àëî îòâåòà ñëåäóþùåãî áëîêà GUI.
|
||||
* Èñòîðè÷åñêèé ôîðìàò òåõíîëîãè÷åñêèõ ðåãèñòðîâ îñòàâëåí ïðåæíèì. */
|
||||
RS_Send(rs_arr, rs_arr->buffer, Length_MB*2+(Address_MB >= UMP_LOG_BASE ? 5 : 8));
|
||||
RS_Send(rs_arr, rs_arr->buffer, Length_MB*2+8);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -256,6 +271,7 @@ void ReceiveCommandModbus3(RS_DATA *rs_arr)
|
||||
void ReceiveCommandModbus6(RS_DATA *rs_arr)
|
||||
{
|
||||
unsigned int Address_MB, Data_MB, i;
|
||||
if (receive_ump(rs_arr)) return;
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// Îòñûëêà
|
||||
@@ -271,17 +287,13 @@ void ReceiveCommandModbus6(RS_DATA *rs_arr)
|
||||
// ïîëó÷èëè ñëîâî äàííûõ
|
||||
Data_MB = (rs_arr->RS_Header[4] << 8) | rs_arr->RS_Header[5];
|
||||
|
||||
if (Address_MB >= UMP_LOG_BASE) {
|
||||
if (!ump_log_write(Address_MB, Data_MB)) {
|
||||
modbus_exception(rs_arr, CMD_MODBUS_6, 3); return;
|
||||
}
|
||||
} else if (Address_MB < ANSWER_LEN) {
|
||||
if (Address_MB < ANSWER_LEN) {
|
||||
Modbus[Address_MB].all = Data_MB;
|
||||
if (Address_MB == 127) ump_log_rs();
|
||||
} else { modbus_exception(rs_arr, CMD_MODBUS_6, 2); return; }
|
||||
|
||||
rs_arr->flag_TIMEOUT_to_Send=true;
|
||||
RS_Send(rs_arr, rs_arr->buffer, Address_MB >= UMP_LOG_BASE ? 8 : 10);
|
||||
RS_Send(rs_arr, rs_arr->buffer, 10);
|
||||
}
|
||||
|
||||
void SendCommandModbus4(RS_DATA *rs_arr)
|
||||
|
||||
3
Source/Internal/shared_ump.c
Normal file
3
Source/Internal/shared_ump.c
Normal file
@@ -0,0 +1,3 @@
|
||||
/* Build the canonical sources directly; do not copy their implementations. */
|
||||
#include "../../../../templates/c/set-protocol/src/ump_protocol.c"
|
||||
#include "../../../../templates/c/set-protocol/ports/tms320f28335/ump_tms28335.c"
|
||||
@@ -40,6 +40,7 @@ Source="Source\Internal\pulto.c"
|
||||
Source="Source\Internal\RS485.c"
|
||||
Source="Source\Internal\spise2p.c"
|
||||
Source="Source\Internal\tools.c"
|
||||
Source="Source\Internal\shared_ump.c"
|
||||
Source="F28335.cmd"
|
||||
Source="Source\External\v120\DSP2833x_headers\cmd\DSP2833x_Headers_nonBIOS.cmd"
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ unsigned int ADC_table[4];
|
||||
/* Возвращаем номер строки при ошибке, ноль при успехе; CRT не требуется. */
|
||||
__declspec(dllexport) int run_tests(void)
|
||||
{
|
||||
unsigned int i, generation, archived, reply[UMP_CAN_MAX_WORDS], error, length;
|
||||
unsigned int i, generation, archived, error, length;
|
||||
uint16_t reply[UMP_CAN_MAX_WORDS];
|
||||
unsigned long sequence;
|
||||
CHECK(!ump_log_write(UMP_LOG_BASE+1, 0));
|
||||
CHECK(!ump_log_write(UMP_LOG_BASE+1, 1001));
|
||||
|
||||
@@ -16,7 +16,7 @@ with tempfile.TemporaryDirectory(prefix='ump-recorder-') as directory:
|
||||
'clang', '-shared', '-nostdlib', '-fuse-ld=lld',
|
||||
'-Wl,/noentry,/nodefaultlib', '-Wno-unknown-pragmas', '-Wno-int-to-pointer-cast',
|
||||
'-Itests/ump_logger/stubs', '-ISource/Internal/Include',
|
||||
'tests/ump_logger/recorder_test.c', '-o', str(library),
|
||||
'tests/ump_logger/recorder_test.c', 'Source/Internal/shared_ump.c', '-o', str(library),
|
||||
], cwd=root, check=True)
|
||||
# Отдельный процесс освобождает DLL до удаления временной папки Windows.
|
||||
subprocess.run([
|
||||
|
||||
Reference in New Issue
Block a user