diff --git a/Doc/DAC_SPI_REVIEW.md b/Doc/DAC_SPI_REVIEW.md new file mode 100644 index 0000000..733a20f --- /dev/null +++ b/Doc/DAC_SPI_REVIEW.md @@ -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). + +Модель ЦАП и электрическая схема не установлены. Режим выборки, назначение +командных битов и допустимые длительности нельзя считать подтверждёнными +до сверки с документацией конкретной микросхемы. Сборка/прошивка и стендовые +измерения в рамках подготовки этого документа не выполнялись. diff --git a/Doc/PERIPH28335_CAN.md b/Doc/PERIPH28335_CAN.md index e0e1c9e..a5995c2 100644 --- a/Doc/PERIPH28335_CAN.md +++ b/Doc/PERIPH28335_CAN.md @@ -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`. diff --git a/Source/Internal/Include/ump_can_protocol.h b/Source/Internal/Include/ump_can_protocol.h index e374045..01e80e5 100644 --- a/Source/Internal/Include/ump_can_protocol.h +++ b/Source/Internal/Include/ump_can_protocol.h @@ -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 diff --git a/Source/Internal/ecan.c b/Source/Internal/ecan.c index cf586c7..e3ac7ac 100644 --- a/Source/Internal/ecan.c +++ b/Source/Internal/ecan.c @@ -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; diff --git a/Source/Internal/message.c b/Source/Internal/message.c index ac71829..7921912 100644 --- a/Source/Internal/message.c +++ b/Source/Internal/message.c @@ -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= 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) diff --git a/Source/Internal/shared_ump.c b/Source/Internal/shared_ump.c new file mode 100644 index 0000000..743f05b --- /dev/null +++ b/Source/Internal/shared_ump.c @@ -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" diff --git a/UKSSTMS320F28335.pjt b/UKSSTMS320F28335.pjt index 4f03892..4cef5e5 100644 --- a/UKSSTMS320F28335.pjt +++ b/UKSSTMS320F28335.pjt @@ -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" diff --git a/tests/ump_logger/recorder_test.c b/tests/ump_logger/recorder_test.c index 550feaf..c2f4a25 100644 --- a/tests/ump_logger/recorder_test.c +++ b/tests/ump_logger/recorder_test.c @@ -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)); diff --git a/tests/ump_logger/run_test.py b/tests/ump_logger/run_test.py index fa34440..ffcebe2 100644 --- a/tests/ump_logger/run_test.py +++ b/tests/ump_logger/run_test.py @@ -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([