6 Commits

76 changed files with 2287 additions and 2470 deletions

View File

@@ -13,7 +13,6 @@
templates/
c/ библиотеки на C99: заголовок, реализация, README, где есть — порт и тесты
python/ модули на чистом Python 3.9+, только stdlib
tools/ общие инструменты сборки и выпуска прошивок
```
Пошаговая раскладка нового проекта и выбор портов для STM32F103, STM32G431
@@ -34,7 +33,9 @@ templates/
| [`c/eeprom-ft24c256`](c/eeprom-ft24c256) | EEPROM 24Cxx по I²C с нарезкой записи по страницам | `stdint.h` | две I²C-транзакции, задержка |
| [`c/can-sensor`](c/can-sensor) | однокадровые SETCAN SETTINGS для 64-битных ROM | ядро: `stdint.h`; порт F1: CMSIS | callbacks либо готовый bxCAN STM32F1 |
| [`c/ds18b20`](c/ds18b20) | термометры DS18B20 поверх программной 1-Wire | `stdint.h` | Init, DelayUs, Reset, WriteBit, ReadBit — **порты STM32F103, STM32G431 и STM32G474 в комплекте** |
| [`c/ds18b20-ds2480`](c/ds18b20-ds2480) | DS18B20 через DS2480B: поиск ROM, температура, EEPROM, паразитное питание | C99 | UART 9600 8N1, сброс моста, задержка |
| [`c/set-protocol`](c/set-protocol) | единое ядро SETProtocol: SET v2, совместимые ProtoCAN/GUI v1, GAS, телеметрия, firmware flow и стабильный host ABI | C99 | COM/SLCAN/SocketCAN/USB/Ethernet или callbacks — **Windows, Android и STM32F4-порты в комплекте** |
| [`c/set-protocol/ports/stm32f407-devboard-v1`](c/set-protocol/ports/stm32f407-devboard-v1) | доступ SETGUI к Modbus-регистрам F407 через CAN485 DevBoard_V1 | `pcan_modbus_server`, STM32 HAL CAN | bxCAN FIFO0 и callbacks карты регистров |
| [`c/set-protocol/ports/stm32-bxcan`](c/set-protocol/ports/stm32-bxcan) | порт прикладного ProtoCAN для STM32, бывший SETCAN; сохранён API `PROTOCAN_*` | STM32 HAL CAN/RTC/TIM + общее ядро `pcan_id` | classic bxCAN; настройки платы предоставляет прошивка |
| [`c/protocan-boot`](c/protocan-boot) | адресная прошивка по ProtoCAN: A/B-слоты, сессия, CRC32, verify и rollback-контракт | C99 | CAN TX, erase/write Flash, boot metadata, проверка образа и reboot |
| [`c/rs485-boot`](c/rs485-boot) | прошивка по RS-485 в формате SETGUI v1: потоковый parser, CRC32 и resume | C99 | UART TX/RX, DE, Flash — **порты STM32F103 и STM32G474VET в комплекте** |
@@ -49,12 +50,6 @@ templates/
| [`python/protocan`](python/protocan) | разбор ProtoCAN, транспортный кадр моста, кадр SETGUI, кодеки каталога | stdlib, Python 3.9+ |
| [`python/protocan/trends.py`](python/protocan/trends.py) | общие настройки графиков, ограниченная история, ctypes-декодер GAS/raw CAN | stdlib, опционально SETProtocol DLL/SO |
### Инструменты
| Инструмент | Что делает |
|---|---|
| [`tools/firmware-publish`](tools/firmware-publish) | единый BAT и конфигурации для проверки и публикации `.hex` Keil / `.bin` CCS 12 в каталоге SETGUI |
Общие тренды для Android GUI и SETGUI: [формат JSON, C99-ядро и адаптеры](c/set-protocol/docs/GUI_TRENDS.md).
Общие масштабирование и маркеры: [C99, JNI и Python/Qt](c/set-protocol/docs/GUI_PLOT.md).

View File

@@ -1,80 +0,0 @@
# Правила общего кроссплатформенного кода
Этот репозиторий — единственный источник общих алгоритмов для `GUI_Android`,
`SETGUI`, прошивок и будущих GUI. Копирование одной реализации между Kotlin,
Python, C# или другим языком запрещено.
## 1. Граница C-ядра и порта
В `c/set-protocol` на C99 обязательно размещаются:
- форматы кадров и идентификаторов, CRC/checksum, endian-преобразования;
- построение команд, разбор и проверка ответов;
- автоматы обмена, сегментация, повтор, таймаутные состояния без системных часов;
- общие вычисления, таблицы и каталоги, влияющие на поведение протокола;
- проверка образов прошивки и других бинарных форматов.
Порт на языке GUI содержит только:
- вызовы C через стабильный ABI (`ctypes`, JNI, P/Invoke, Swift FFI и т. п.);
- преобразование C-структур в модели языка без повторения алгоритма;
- работу с USB, COM, Bluetooth, SocketCAN и API операционной системы;
- жизненный цикл, потоки, разрешения, хранение настроек и UI;
- локализованный текст и чисто визуальные преобразования.
Порт не вычисляет CRC, не собирает wire-пакет и не разбирает его поля заново.
Если для функции C-ядро недоступно, приложение сообщает об ошибке сборки или
загрузки. Алгоритмический fallback на языке GUI запрещён: он снова создаёт две
версии протокола.
## 2. Разделение контроллеров
Профили контроллеров нельзя сливать по совпадению названия транспорта:
- **ПМ67 / TMS320F2812** — основной контроллер, собственные RS и CAN;
- **ПМ35 / TMS320F28335 periph** — отдельный контроллер и отдельный CAN для
настроечного терминала, а также собственный прямой RS232/485-протокол.
Выбор профиля выполняется в GUI, но выбранный профиль вызывает свой отдельный
модуль C-ядра. Наличие одной CAN-линии не даёт права удалить или подменить
другую.
## 3. Порядок изменения протокола
1. Добавить или изменить публичный заголовок и реализацию в
`c/set-protocol/include` и `c/set-protocol/src`.
2. Зафиксировать эталонные байты и ошибочные случаи в C-тесте.
3. При необходимости расширить `pcan_abi.h`, сохраняя бинарную совместимость.
4. Добавить тонкие порты в `ports/<platform>` и `python/`; в них не должно быть
второго кодека.
5. Одними и теми же векторами проверить C, Python и Android/JVM.
6. Собрать SETGUI и Android с одним commit submodule `templates`.
Изменение только в одном GUI считается незавершённым. Сначала меняется
`templates`, затем оба потребителя обновляют ссылку submodule на проверенный
commit.
## 4. Требования к C-ядру
- C99, без зависимости от GUI и конкретной ОС.
- Буферы и их размеры передаются явно; владение памятью остаётся у вызывающего.
- Для MCU основная логика не требует heap, исключений или файловой системы.
- Endian и размеры целых задаются через `stdint.h`, структуры wire-формата не
передаются через ABI без явного стабильного представления.
- Экспорт shared library идёт через `PCAN_ABI_API`; существующие символы не
меняют смысл и сигнатуру.
- Ошибки возвращаются детерминированным кодом и тестируются наряду с успехом.
## 5. Проверка на ревью
Изменение нельзя принимать, если ответ «да» хотя бы на один вопрос:
- появился одинаковый CRC/parser/builder в двух языках;
- UI знает byte offset, endian или служебный байт wire-протокола;
- Python и Kotlin содержат одинаковую таблицу команд, влияющую на обмен;
- добавлен тихий fallback, поведение которого отличается от C;
- обновлён один GUI без обновления и теста `templates`;
- ПМ67 и ПМ35 сведены к одному соединению или одному состоянию контроллера.
Текущее состояние и очередь переноса перечислены в
[`doc/CROSS_PLATFORM_AUDIT.md`](doc/CROSS_PLATFORM_AUDIT.md).

View File

@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.13)
project(ds18b20_ds2480 C)
add_library(ds18b20_ds2480 STATIC ds2480.c ds18b20_ds2480.c)
target_include_directories(ds18b20_ds2480 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_features(ds18b20_ds2480 PUBLIC c_std_99)
if(MSVC)
target_compile_options(ds18b20_ds2480 PRIVATE /W4)
else()
target_compile_options(ds18b20_ds2480 PRIVATE -Wall -Wextra -Wpedantic)
endif()
option(DS18B20_DS2480_BUILD_TESTS "Build host tests" ON)
if(DS18B20_DS2480_BUILD_TESTS)
enable_testing()
add_executable(test_ds18b20_ds2480 tests/test_ds18b20_ds2480.c)
target_link_libraries(test_ds18b20_ds2480 PRIVATE ds18b20_ds2480)
add_test(NAME ds18b20_ds2480 COMMAND test_ds18b20_ds2480)
endif()

145
c/ds18b20-ds2480/README.md Normal file
View File

@@ -0,0 +1,145 @@
# DS18B20 через DS2480B
Переносимая библиотека C99: поиск DS18B20 на общей шине, температура,
разрешение 912 бит, TH/TL и сохранение в EEPROM через UART-мост DS2480B.
Поддерживаются внешнее и паразитное питание: преобразование и запись EEPROM
используют strong pullup, включаемый мостом сразу после последнего бита команды.
Ядро не зависит от HAL, CMSIS, ОС или существующей GPIO-библиотеки `ds18b20`.
Динамической памяти и глобального состояния нет. Каждый UART-мост имеет свой
`ds2480`, каждый обход — свой `ds2480_search`.
```text
Приложение → ds18b20_ds2480 → ds2480 → callbacks UART/задержки → платформа
```
## Файлы
| Файл | Назначение | Зависимости |
|---|---|---|
| `ds2480.h`, `ds2480.c` | Калибровка, reset, обмен битами/байтами, поиск ROM, CRC, strong pullup | `stdint.h`, callbacks |
| `ds18b20_ds2480.h`, `ds18b20_ds2480.c` | Команды термометра, проверка scratchpad, знаковая температура | `ds2480`, `string.h` |
| `examples/read_first.c` | Полный цикл для первого найденного DS18B20 | ядро, порт приложения |
| `tests/test_ds18b20_ds2480.c` | Модель UART-моста и нескольких устройств 1-Wire | ядро, стандартная библиотека C |
## Контракт порта
```c
int prepare(void *user);
int write(void *user, const uint8_t *data, uint32_t size, uint32_t timeout_ms);
int read(void *user, uint8_t *data, uint32_t size, uint32_t timeout_ms);
void delay_ms(void *user, uint32_t ms);
```
Первые три callbacks возвращают `0` при успехе, иначе ошибку.
`prepare` аппаратно сбрасывает DS2480B или формирует UART BREAK не короче 2 мс,
настраивает **9600 бод, 8N1**, выдерживает минимум 2 мс после сброса и очищает RX
и ошибки UART. Эта функция должна иметь собственный конечный таймаут.
Нельзя просто посылать `C1` работающему мосту вместо сброса.
`write` передаёт ровно `size` байтов и ждёт окончания передачи; `read` получает
ровно `size` байтов. Обе операции ограничены `timeout_ms`; при частичном обмене
возвращается ошибка. Приём должен работать уже во время передачи: ответ может
появиться до вызова `read`. `delay_ms` не должна возвращаться раньше заданного
времени (учтите округление системного тика).
Управлять одним UART извне одновременно с библиотекой нельзя. В RTOS блокировка
нужна на всю операцию верхнего уровня, включая поиск и ожидание преобразования.
Тайминги 1-Wire формирует мост, запрещать прерывания на время слотов не требуется.
## Быстрый старт
Функции `prepare`, `write`, `read`, `delay_ms` и `uart_context` предоставляет плата.
Следующий код располагается внутри функции приложения:
```c
ds2480 bus;
ds2480_search search = {{0}, 0, 0};
ds2480_port port = {uart_context, prepare, write, read, delay_ms};
int16_t raw;
ds2480_status status = ds2480_init(&bus, &port, 20);
if (status != DS2480_OK) return;
status = ds18b20_ds2480_next(&bus, &search);
if (status != DS2480_OK) return;
status = ds18b20_ds2480_convert(&bus, search.rom);
if (status != DS2480_OK) return;
status = ds18b20_ds2480_temperature(&bus, search.rom, &raw);
if (status != DS2480_OK) return;
float celsius = raw / 16.0f;
(void)celsius;
```
Вызывайте `next` повторно с тем же курсором до `DS2480_DONE`, сохраняя каждый
найденный ROM в памяти приложения. Лимита количества датчиков в ядре нет.
Пустая шина возвращает `DS2480_NO_PRESENCE`; обрыв поиска и ошибки CRC не
маскируются под успешное завершение. Для нового обхода обнулите курсор.
`convert(bus, NULL)` одновременно запускает все устройства — допустимо только
на шине исключительно с DS18B20. Затем читайте каждое по ROM. Для паразитного
питания суммарный ток должен укладываться в возможности моста; при необходимости
преобразуйте по одному датчику. Преобразование блокирует вызов на 750 мс плюс
UART-обмен. EEPROM использует выдержку 12 мс, её содержимое после перезапуска
этой функцией не проверяется. Чтение температуры само преобразование не запускает.
Значение 85 °C после включения нельзя отличить от настоящих 85 °C без завершённого
преобразования; не считайте его заведомой ошибкой.
## Ошибки и ограничения
- `IO` — таймаут/ошибка порта, `PROTOCOL` — неожиданный ответ DS2480B.
После них экземпляр не готов к работе: повторите `ds2480_init`.
- `SHORT` — короткое замыкание, `NO_PRESENCE` — нет presence.
- `CRC` — неверная контрольная сумма, `DATA` — недопустимые данные или
неподтверждённая запись. Выход температуры/scratchpad при ошибке не меняется.
- При ошибке во время strong pullup ядро вызывает `prepare`, чтобы прекратить
импульс, и остаётся неготовым. При отказе самого порта снятие питания гарантировать
невозможно; восстановление UART/моста остаётся задачей приложения.
Используется стандартная скорость 1-Wire и только Command Mode: байт передаётся
восьмью командами Single Bit. Поэтому любые значения, включая `E3`, передаются
без экранирования. Это простая реализация для опроса температуры; повышенные
скорости UART, Overdrive и Search Accelerator пока не реализованы. Параметры
таймингов длинной линии остаются заводскими. Аппаратная проверка обязательна
для выбранной топологии/нагрузки. Протокол проверен по документации **DS2480B**;
старые ревизии DS2480 без суффикса B отдельно не проверялись.
## Подключение и тесты
Добавьте `ds2480.c`, `ds18b20_ds2480.c` и путь к заголовкам в сборку прошивки.
Номер UART, GPIO и библиотеку платформы выбирает приложение. Готовый порт
для конкретной платы пока не входит в библиотеку.
```cmake
set(DS18B20_DS2480_BUILD_TESTS OFF CACHE BOOL "" FORCE)
add_subdirectory(third_party/templates/c/ds18b20-ds2480)
target_link_libraries(firmware PRIVATE ds18b20_ds2480)
```
Отдельная сборка хостовых тестов:
```sh
cmake -S . -B build
cmake --build build --config Debug
ctest --test-dir build -C Debug --output-on-failure
```
Без CMake, из каталога библиотеки:
```sh
clang -std=c99 -Wall -Wextra -Wpedantic -Werror -I . ds2480.c ds18b20_ds2480.c tests/test_ds18b20_ds2480.c -o build/test.exe
./build/test.exe
```
Тесты моделируют калибровку без ответа, несколько ROM с развилками, другие
семейства, адресацию, все разрешения, отрицательную температуру, CRC, пустую
и замкнутую шину, ошибки транспорта, strong pullup и восстановление после ошибок.
Это программная модель; испытания на физическом DS2480B пока не проводились.
## Использование
Добавлена в сабмодуль `templates` проекта `john103C6T6NewVer`, ветка `ds2480`.
В рабочую прошивку ещё не включена: для этого требуется порт выбранного UART.
## Источники
- [DS2480B datasheet, таблицы команд и ответов](https://www.analog.com/media/en/technical-documentation/data-sheets/ds2480b.pdf)
- [DS18B20 datasheet, команды, питание и scratchpad](https://www.analog.com/media/en/technical-documentation/data-sheets/ds18b20.pdf)

View File

@@ -0,0 +1,108 @@
#include "ds18b20_ds2480.h"
#include <string.h>
static ds2480_status write_byte(ds2480 *bus, uint8_t value)
{
uint8_t echoed;
ds2480_status status = ds2480_byte(bus, value, &echoed);
if (status != DS2480_OK) return status;
return echoed == value ? DS2480_OK : DS2480_DATA;
}
static ds2480_status select_rom(ds2480 *bus, const uint8_t *rom)
{
uint8_t i;
ds2480_status status;
if (rom && (rom[0] != 0x28 || ds2480_crc8(rom, 8))) return DS2480_ARGUMENT;
status = ds2480_reset(bus);
if (status != DS2480_OK) return status;
status = write_byte(bus, rom ? 0x55 : 0xCC);
if (status != DS2480_OK || !rom) return status;
for (i = 0; i < 8; ++i) {
status = write_byte(bus, rom[i]);
if (status != DS2480_OK) return status;
}
return DS2480_OK;
}
ds2480_status ds18b20_ds2480_next(ds2480 *bus, ds2480_search *search)
{
ds2480_status status;
do {
status = ds2480_search_next(bus, search);
if (status != DS2480_OK) return status;
} while (search->rom[0] != 0x28);
return DS2480_OK;
}
ds2480_status ds18b20_ds2480_convert(ds2480 *bus, const uint8_t rom[8])
{
ds2480_status status = select_rom(bus, rom);
if (status != DS2480_OK) return status;
return ds2480_power_byte(bus, 0x44, 750);
}
ds2480_status ds18b20_ds2480_read(ds2480 *bus, const uint8_t rom[8], uint8_t scratchpad[9])
{
uint8_t data[9], i;
ds2480_status status;
if (!rom || !scratchpad) return DS2480_ARGUMENT;
status = select_rom(bus, rom);
if (status != DS2480_OK) return status;
status = write_byte(bus, 0xBE);
if (status != DS2480_OK) return status;
for (i = 0; i < 9; ++i) {
status = ds2480_byte(bus, 0xFF, &data[i]);
if (status != DS2480_OK) return status;
}
if (ds2480_crc8(data, 9)) return DS2480_CRC;
/* All-zero data has valid CRC but cannot be a DS18B20 scratchpad. */
if ((data[4] & 0x9F) != 0x1F) return DS2480_DATA;
memcpy(scratchpad, data, sizeof data);
return DS2480_OK;
}
ds2480_status ds18b20_ds2480_temperature(ds2480 *bus, const uint8_t rom[8], int16_t *raw)
{
uint8_t data[9], undefined;
uint16_t value;
int32_t signed_value;
ds2480_status status;
if (!raw) return DS2480_ARGUMENT;
status = ds18b20_ds2480_read(bus, rom, data);
if (status != DS2480_OK) return status;
undefined = (uint8_t)(3 - ((data[4] >> 5) & 3));
value = (uint16_t)((uint16_t)data[0] | ((uint16_t)data[1] << 8));
value &= (uint16_t)~((1U << undefined) - 1U);
signed_value = value & 0x8000 ? (int32_t)value - 65536 : (int32_t)value;
if (signed_value < -55 * 16 || signed_value > 125 * 16) return DS2480_DATA;
*raw = (int16_t)signed_value;
return DS2480_OK;
}
ds2480_status ds18b20_ds2480_configure(ds2480 *bus, const uint8_t rom[8],
int8_t th, int8_t tl, uint8_t resolution, uint8_t save)
{
uint8_t data[9], config;
ds2480_status status;
if (!rom || resolution < 9 || resolution > 12) return DS2480_ARGUMENT;
config = (uint8_t)(0x1F | ((resolution - 9) << 5));
status = select_rom(bus, rom);
if (status != DS2480_OK) return status;
status = write_byte(bus, 0x4E);
if (status != DS2480_OK) return status;
status = write_byte(bus, (uint8_t)th);
if (status != DS2480_OK) return status;
status = write_byte(bus, (uint8_t)tl);
if (status != DS2480_OK) return status;
status = write_byte(bus, config);
if (status != DS2480_OK) return status;
status = ds18b20_ds2480_read(bus, rom, data);
if (status != DS2480_OK) return status;
if (data[2] != (uint8_t)th || data[3] != (uint8_t)tl || data[4] != config)
return DS2480_DATA;
if (!save) return DS2480_OK;
status = select_rom(bus, rom);
if (status != DS2480_OK) return status;
return ds2480_power_byte(bus, 0x48, 12);
}

View File

@@ -0,0 +1,32 @@
#ifndef DS18B20_DS2480_H
#define DS18B20_DS2480_H
#include "ds2480.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Find next DS18B20, skipping other families; same cursor rules as bridge. */
ds2480_status ds18b20_ds2480_next(ds2480 *bus, ds2480_search *search);
/** Start conversion and hold strong pullup for 750 ms (all resolutions).
* rom=NULL broadcasts: use only on a bus containing exclusively DS18B20.
* Supports external and parasite power within the bridge's current budget.
*/
ds2480_status ds18b20_ds2480_convert(ds2480 *bus, const uint8_t rom[8]);
/** Read and validate all 9 scratchpad bytes. Output is unchanged on error.
* rom is mandatory, must have family 0x28 and valid CRC.
*/
ds2480_status ds18b20_ds2480_read(ds2480 *bus, const uint8_t rom[8], uint8_t scratchpad[9]);
/** Read last conversion in signed 1/16 Celsius units; masks undefined bits
* for 9/10/11-bit resolution. Call convert first: power-on 85 C is ambiguous.
* Output is unchanged on error. Does not start a conversion itself.
*/
ds2480_status ds18b20_ds2480_temperature(ds2480 *bus, const uint8_t rom[8], int16_t *raw);
/** Set TH/TL and resolution (9..12); verify by reading back.
* save!=0 copies to EEPROM with 12 ms strong pullup; avoid frequent writes.
*/
ds2480_status ds18b20_ds2480_configure(ds2480 *bus, const uint8_t rom[8],
int8_t th, int8_t tl, uint8_t resolution, uint8_t save);
#ifdef __cplusplus
}
#endif
#endif

164
c/ds18b20-ds2480/ds2480.c Normal file
View File

@@ -0,0 +1,164 @@
#include "ds2480.h"
static ds2480_status fault(ds2480 *bus, ds2480_status status)
{
bus->ready = 0;
return status;
}
static ds2480_status exchange(ds2480 *bus, uint8_t command, uint8_t *reply)
{
if (!bus || !reply) return DS2480_ARGUMENT;
if (!bus->ready) return DS2480_NOT_READY;
if (bus->port.write(bus->port.user, &command, 1, bus->timeout_ms) ||
bus->port.read(bus->port.user, reply, 1, bus->timeout_ms))
return fault(bus, DS2480_IO);
return DS2480_OK;
}
ds2480_status ds2480_init(ds2480 *bus, const ds2480_port *port, uint32_t timeout_ms)
{
uint8_t calibration = 0xC1, reply;
ds2480_status status;
ds2480_port copy;
if (!bus) return DS2480_ARGUMENT;
if (!port || !port->prepare || !port->write || !port->read ||
!port->delay_ms || !timeout_ms) {
bus->ready = 0;
return DS2480_ARGUMENT;
}
copy = *port; /* Also permit reinitialization with &bus->port. */
bus->ready = 0;
bus->port = copy;
bus->timeout_ms = timeout_ms;
if (copy.prepare(copy.user) ||
copy.write(copy.user, &calibration, 1, timeout_ms)) return DS2480_IO;
/* First C1 calibrates only: there is NO response byte. */
copy.delay_ms(copy.user, 2);
bus->ready = 1;
/* Strong pullup duration = infinite, command-mode operations only. */
status = exchange(bus, 0x3F, &reply);
if (status != DS2480_OK) return status;
if (reply != 0x3E) return fault(bus, DS2480_PROTOCOL);
return DS2480_OK;
}
ds2480_status ds2480_reset(ds2480 *bus)
{
uint8_t reply;
ds2480_status status = exchange(bus, 0xC1, &reply);
if (status != DS2480_OK) return status;
/* DS2480B revision pattern from table 2; bit 5 is unspecified. */
if ((reply & 0xDC) != 0xCC) return fault(bus, DS2480_PROTOCOL);
switch (reply & 3) {
case 0: return DS2480_SHORT;
case 3: return DS2480_NO_PRESENCE;
default: return DS2480_OK; /* Ordinary or alarming presence. */
}
}
static ds2480_status slot(ds2480 *bus, uint8_t bit, uint8_t power, uint8_t *received)
{
uint8_t reply, command = (uint8_t)(0x81 | (bit ? 0x10 : 0) | (power ? 2 : 0));
ds2480_status status = exchange(bus, command, &reply);
if (status != DS2480_OK) return status;
if ((reply & 0xFC) != (command & 0xFC) ||
((reply & 3) != 0 && (reply & 3) != 3))
return fault(bus, DS2480_PROTOCOL);
*received = reply & 1;
return DS2480_OK;
}
ds2480_status ds2480_bit(ds2480 *bus, uint8_t bit, uint8_t *received)
{
if (!received) return DS2480_ARGUMENT;
return slot(bus, bit, 0, received);
}
ds2480_status ds2480_byte(ds2480 *bus, uint8_t value, uint8_t *received)
{
uint8_t i, bit, result = 0;
ds2480_status status;
if (!received) return DS2480_ARGUMENT;
for (i = 0; i < 8; ++i) {
status = ds2480_bit(bus, (uint8_t)((value >> i) & 1), &bit);
if (status != DS2480_OK) return status;
result |= (uint8_t)(bit << i);
}
*received = result;
return DS2480_OK;
}
ds2480_status ds2480_power_byte(ds2480 *bus, uint8_t value, uint32_t hold_ms)
{
uint8_t i, bit, reply, echoed = 0;
ds2480_status status;
if (!bus || !hold_ms || hold_ms > 1000) return DS2480_ARGUMENT;
for (i = 0; i < 8; ++i) {
status = slot(bus, (uint8_t)((value >> i) & 1), (uint8_t)(i == 7), &bit);
if (status != DS2480_OK) {
/* A lost reply may leave an infinite pulse active: reset hardware. */
if (i == 7 && !bus->ready) (void)bus->port.prepare(bus->port.user);
return status;
}
echoed |= (uint8_t)(bit << i);
}
bus->port.delay_ms(bus->port.user, hold_ms);
status = exchange(bus, 0xF1, &reply);
if (status == DS2480_OK && reply != 0xEC && reply != 0xEF)
status = fault(bus, DS2480_PROTOCOL);
if (status != DS2480_OK) (void)bus->port.prepare(bus->port.user);
if (status == DS2480_OK && echoed != value) return DS2480_DATA;
return status;
}
uint8_t ds2480_crc8(const uint8_t *data, uint32_t size)
{
uint8_t crc = 0, bit;
uint32_t i;
for (i = 0; i < size; ++i) {
crc ^= data[i];
for (bit = 0; bit < 8; ++bit)
crc = (uint8_t)((crc >> 1) ^ ((crc & 1) ? 0x8C : 0));
}
return crc;
}
ds2480_status ds2480_search_next(ds2480 *bus, ds2480_search *search)
{
ds2480_search next;
ds2480_status status;
uint8_t pos, a, b, direction, ignored, last_zero = 0;
if (!bus || !search || search->discrepancy > 64) return DS2480_ARGUMENT;
if (search->done) return DS2480_DONE;
next = *search;
status = ds2480_reset(bus);
if (status != DS2480_OK) return status;
status = ds2480_byte(bus, 0xF0, &ignored);
if (status != DS2480_OK) return status;
for (pos = 1; pos <= 64; ++pos) {
uint8_t index = (uint8_t)((pos - 1) / 8);
uint8_t mask = (uint8_t)(1U << ((pos - 1) % 8));
status = ds2480_bit(bus, 1, &a);
if (status != DS2480_OK) return status;
status = ds2480_bit(bus, 1, &b);
if (status != DS2480_OK) return status;
if (a && b) return DS2480_DATA; /* Devices disappeared mid-search. */
if (a != b) direction = a;
else {
direction = pos < search->discrepancy ? (uint8_t)!!(search->rom[index] & mask)
: (uint8_t)(pos == search->discrepancy);
if (!direction) last_zero = pos;
}
if (direction) next.rom[index] |= mask;
else next.rom[index] &= (uint8_t)~mask;
status = ds2480_bit(bus, direction, &ignored);
if (status != DS2480_OK) return status;
}
if (!next.rom[0]) return DS2480_DATA;
if (ds2480_crc8(next.rom, 8)) return DS2480_CRC;
next.discrepancy = last_zero;
next.done = (uint8_t)(last_zero == 0);
*search = next;
return DS2480_OK;
}

69
c/ds18b20-ds2480/ds2480.h Normal file
View File

@@ -0,0 +1,69 @@
#ifndef DS2480_H
#define DS2480_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
DS2480_OK = 0, DS2480_DONE, DS2480_NO_PRESENCE, DS2480_SHORT,
DS2480_IO, DS2480_PROTOCOL, DS2480_CRC, DS2480_ARGUMENT,
DS2480_NOT_READY, DS2480_DATA
} ds2480_status;
/** Blocking UART callbacks: 0 = success, nonzero = error/timeout.
* prepare resets the bridge (BREAK >= 2 ms or hardware reset), sets UART
* to 9600 8N1, waits >= 2 ms after reset, clears RX/errors before returning.
* write waits for physical TX completion; read receives exactly size bytes.
* Both enforce timeout_ms. RX must remain enabled while transmitting.
* delay_ms waits AT LEAST the requested duration. No callback may reenter.
*/
typedef struct {
void *user;
int (*prepare)(void *user);
int (*write)(void *user, const uint8_t *data, uint32_t size, uint32_t timeout_ms);
int (*read)(void *user, uint8_t *data, uint32_t size, uint32_t timeout_ms);
void (*delay_ms)(void *user, uint32_t ms);
} ds2480_port;
/** One instance per UART/bridge; serialize access for the whole operation. */
typedef struct {
ds2480_port port;
uint32_t timeout_ms;
uint8_t ready;
} ds2480;
/** Independent ROM search cursor. Zero-initialize before each enumeration. */
typedef struct {
uint8_t rom[8];
uint8_t discrepancy;
uint8_t done;
} ds2480_search;
/** Reset/calibrate DS2480B and verify configuration. Empty bus is allowed.
* On IO/protocol errors call init again before further transactions.
*/
ds2480_status ds2480_init(ds2480 *bus, const ds2480_port *port, uint32_t timeout_ms);
/** Standard-speed reset; distinguish short, empty bus and transport failure. */
ds2480_status ds2480_reset(ds2480 *bus);
/** Exchange one slot (write 1 to read); received must be non-NULL. */
ds2480_status ds2480_bit(ds2480 *bus, uint8_t bit, uint8_t *received);
/** Exchange a byte, LSB first, in command mode (eight UART transactions). */
ds2480_status ds2480_byte(ds2480 *bus, uint8_t value, uint8_t *received);
/** Write a byte and start strong pullup immediately after its last slot.
* Blocks for hold_ms (1..1000), then terminates the pulse and consumes reply.
*/
ds2480_status ds2480_power_byte(ds2480 *bus, uint8_t value, uint32_t hold_ms);
/** CRC8 Dallas/Maxim. data must address size bytes (NULL allowed for size=0). */
uint8_t ds2480_crc8(const uint8_t *data, uint32_t size);
/** Search ALL families. OK yields ROM with valid CRC; DONE ends enumeration.
* Cursor changes only on success. After an error retry or zero it to restart.
*/
ds2480_status ds2480_search_next(ds2480 *bus, ds2480_search *search);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,19 @@
#include "ds18b20_ds2480.h"
/* board_port supplies the four callbacks documented in ds2480.h.
* Blocking example: includes bridge reset, scan and 750 ms conversion.
*/
ds2480_status ds18b20_example_read_first(const ds2480_port *board_port, int16_t *raw)
{
ds2480 bus;
ds2480_search search = {{0}, 0, 0};
ds2480_status status;
if (!raw) return DS2480_ARGUMENT;
status = ds2480_init(&bus, board_port, 20);
if (status != DS2480_OK) return status;
status = ds18b20_ds2480_next(&bus, &search);
if (status != DS2480_OK) return status;
status = ds18b20_ds2480_convert(&bus, search.rom);
if (status != DS2480_OK) return status;
return ds18b20_ds2480_temperature(&bus, search.rom, raw);
}

View File

@@ -0,0 +1,228 @@
#include "ds18b20_ds2480.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "line %d: %s\n", __LINE__, #x); exit(1); } } while (0)
enum { ROM_COMMAND, MATCH, FUNCTION, SEARCH, READ, CONFIG };
typedef struct {
uint8_t rom[3][8], scratch[9];
unsigned count, active, state, phase, position, bits, value, match_index;
unsigned prepared, calibrated, pending, reply, pulse, hold, convert, copy;
unsigned fail_read, fail_write, fail_prepare, corrupt_reply, reset_reply;
unsigned fail_power, fail_stop, reject_config;
} fake;
static void crc_scratch(fake *f) { f->scratch[8] = ds2480_crc8(f->scratch, 8); }
static void setup(fake *f)
{
unsigned i;
static const uint8_t scratch[9] = {0x91, 0x01, 0x4B, 0x46, 0x7F, 0xFF, 0x0C, 0x10, 0x70};
memset(f, 0, sizeof *f);
f->count = 3;
f->reset_reply = 0xCD;
memcpy(f->scratch, scratch, 9);
for (i = 0; i < 3; ++i) {
f->rom[i][0] = i == 2 ? 0x10 : 0x28;
f->rom[i][1] = (uint8_t)(i + 1);
f->rom[i][7] = ds2480_crc8(f->rom[i], 7);
}
}
static void byte_in(fake *f, uint8_t byte)
{
unsigned i;
if (f->state == ROM_COMMAND) {
if (byte == 0xF0) { f->state = SEARCH; f->position = f->phase = 0; }
else if (byte == 0x55) { f->state = MATCH; f->match_index = 0; }
else { CHECK(byte == 0xCC); f->state = FUNCTION; }
} else if (f->state == MATCH) {
for (i = 0; i < f->count; ++i)
if (f->rom[i][f->match_index] != byte) f->active &= ~(1U << i);
if (++f->match_index == 8) f->state = FUNCTION;
} else if (f->state == FUNCTION) {
CHECK(f->active != 0);
switch (byte) {
case 0xBE: f->state = READ; f->position = 0; break;
case 0x4E: f->state = CONFIG; f->position = 2; break;
case 0x44: ++f->convert; break;
case 0x48: ++f->copy; break;
default: CHECK(0);
}
} else {
CHECK(f->state == CONFIG);
if (!f->reject_config) f->scratch[f->position] = byte;
if (++f->position == 5) { crc_scratch(f); f->state = FUNCTION; }
}
}
static uint8_t wire_bit(fake *f, uint8_t bit)
{
unsigned i, result = 1;
if (f->state == SEARCH) {
for (i = 0; i < f->count; ++i) {
unsigned v = (f->rom[i][f->position / 8] >> (f->position % 8)) & 1;
if (!(f->active & (1U << i))) continue;
if (f->phase < 2) result &= f->phase ? !v : v;
else if (v != bit) f->active &= ~(1U << i);
}
if (++f->phase == 3) { f->phase = 0; ++f->position; }
return (uint8_t)result;
}
if (f->state == READ) {
CHECK(f->position < 72 && bit == 1);
result = (f->scratch[f->position / 8] >> (f->position % 8)) & 1;
++f->position;
return (uint8_t)result;
}
f->value |= (unsigned)bit << f->bits;
if (++f->bits == 8) {
uint8_t byte = (uint8_t)f->value;
f->bits = f->value = 0;
byte_in(f, byte);
}
return bit;
}
static int prepare(void *user)
{
fake *f = user;
++f->prepared;
f->pending = f->pulse = f->calibrated = 0;
return (int)f->fail_prepare;
}
static int transmit(void *user, const uint8_t *data, uint32_t size, uint32_t timeout)
{
fake *f = user;
uint8_t command = *data, bit;
CHECK(size == 1 && timeout == 20 && !f->pending);
if (f->fail_write) return -1;
if (!f->calibrated) { CHECK(command == 0xC1); f->calibrated = 1; return 0; }
CHECK(!f->pulse || command == 0xF1);
if (command == 0x3F) f->reply = 0x3E;
else if (command == 0xC1) {
f->reply = f->count ? f->reset_reply : 0xCF;
f->state = ROM_COMMAND; f->bits = f->value = 0;
f->active = (1U << f->count) - 1;
} else if (command == 0xF1) {
CHECK(f->pulse && (f->hold == 750 || f->hold == 12));
f->pulse = 0; f->reply = 0xEC;
if (f->fail_stop) f->fail_read = 1;
} else {
CHECK(command == 0x81 || command == 0x91 || command == 0x83 || command == 0x93);
bit = wire_bit(f, (uint8_t)((command >> 4) & 1));
f->reply = (command & 0xFC) | (bit ? 3U : 0U);
if (command & 2) {
CHECK(f->bits == 0 && (f->convert || f->copy));
f->pulse = 1; f->hold = 0;
if (f->fail_power) f->fail_read = 1;
}
}
f->pending = 1;
return 0;
}
static int receive(void *user, uint8_t *data, uint32_t size, uint32_t timeout)
{
fake *f = user;
CHECK(size == 1 && timeout == 20 && f->pending);
if (f->fail_read) return -1;
*data = (uint8_t)(f->corrupt_reply ? 0 : f->reply);
f->pending = 0;
return 0;
}
static void delay(void *user, uint32_t ms)
{
fake *f = user;
if (f->pulse) f->hold += ms;
else CHECK(ms == 2);
}
static void init(fake *f, ds2480 *bus)
{
ds2480_port port = { f, prepare, transmit, receive, delay };
CHECK(ds2480_init(bus, &port, 20) == DS2480_OK);
CHECK(!f->pending);
}
int main(void)
{
fake f, other;
ds2480 bus, second;
ds2480_search search = {{0}, 0, 0}, saved;
uint8_t bit, scratch[9], rom[8];
int16_t raw = 999;
unsigned found = 0, i;
setup(&f); init(&f, &bus);
CHECK(ds2480_crc8(f.scratch, 9) == 0); /* Datasheet vector, not generated. */
while (ds18b20_ds2480_next(&bus, &search) == DS2480_OK) {
if (!memcmp(search.rom, f.rom[0], 8)) found |= 1;
else if (!memcmp(search.rom, f.rom[1], 8)) found |= 2;
else CHECK(0);
}
CHECK(found == 3 && search.done);
CHECK(ds18b20_ds2480_next(&bus, &search) == DS2480_DONE);
CHECK(ds18b20_ds2480_convert(&bus, NULL) == DS2480_OK);
CHECK(f.convert == 1 && f.hold == 750 && !f.pulse && !f.pending);
CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_OK && raw == 401);
CHECK(ds18b20_ds2480_convert(&bus, f.rom[1]) == DS2480_OK && f.active == 2);
for (i = 9; i <= 12; ++i) {
CHECK(ds18b20_ds2480_configure(&bus, f.rom[0], -5, -20, (uint8_t)i, 1) == DS2480_OK);
CHECK(f.hold == 12 && !f.pulse && !f.pending);
f.scratch[0] = 0x5F; f.scratch[1] = 0xFF; crc_scratch(&f);
CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_OK);
CHECK(raw == (i == 9 ? -168 : i == 10 ? -164 : i == 11 ? -162 : -161));
}
CHECK(f.copy == 4);
f.reject_config = 1;
CHECK(ds18b20_ds2480_configure(&bus, f.rom[0], 1, 2, 9, 1) == DS2480_DATA && f.copy == 4);
f.scratch[8] ^= 1; raw = 999;
CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_CRC && raw == 999);
memset(f.scratch, 0, 9);
CHECK(ds18b20_ds2480_read(&bus, f.rom[0], scratch) == DS2480_DATA);
memset(f.scratch, 0xFF, 9);
CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_CRC);
memcpy(rom, f.rom[0], 8); rom[7] ^= 1;
CHECK(ds18b20_ds2480_convert(&bus, rom) == DS2480_ARGUMENT);
CHECK(ds18b20_ds2480_read(&bus, NULL, scratch) == DS2480_ARGUMENT);
CHECK(ds18b20_ds2480_configure(&bus, f.rom[0], 0, 0, 8, 0) == DS2480_ARGUMENT);
CHECK(ds2480_bit(&bus, 1, NULL) == DS2480_ARGUMENT);
f.reset_reply = 0xEC; CHECK(ds2480_reset(&bus) == DS2480_SHORT);
f.reset_reply = 0xEE; CHECK(ds2480_reset(&bus) == DS2480_OK);
f.count = 0; CHECK(ds2480_reset(&bus) == DS2480_NO_PRESENCE);
memset(&search, 0, sizeof search);
CHECK(ds2480_search_next(&bus, &search) == DS2480_NO_PRESENCE);
setup(&f); init(&f, &bus); f.count = 1; f.rom[0][7] ^= 1;
saved = search;
CHECK(ds2480_search_next(&bus, &search) == DS2480_CRC);
CHECK(!memcmp(&search, &saved, sizeof search));
f.fail_read = 1; CHECK(ds2480_reset(&bus) == DS2480_IO);
CHECK(ds2480_reset(&bus) == DS2480_NOT_READY);
setup(&f); init(&f, &bus); f.corrupt_reply = 1;
CHECK(ds2480_reset(&bus) == DS2480_PROTOCOL && !bus.ready);
setup(&f); init(&f, &bus); CHECK(ds2480_reset(&bus) == DS2480_OK); f.corrupt_reply = 1;
CHECK(ds2480_bit(&bus, 1, &bit) == DS2480_PROTOCOL);
setup(&f); init(&f, &bus); f.fail_write = 1;
CHECK(ds2480_reset(&bus) == DS2480_IO && !bus.ready);
setup(&f); init(&f, &bus); f.fail_power = 1;
CHECK(ds18b20_ds2480_convert(&bus, NULL) == DS2480_IO);
CHECK(!f.pulse && !bus.ready && f.prepared == 2);
setup(&f); init(&f, &bus); f.fail_stop = 1;
CHECK(ds18b20_ds2480_convert(&bus, NULL) == DS2480_IO);
CHECK(!f.pulse && !bus.ready && f.prepared == 2);
setup(&f); init(&f, &bus);
setup(&other); init(&other, &second);
CHECK(ds18b20_ds2480_convert(&second, other.rom[1]) == DS2480_OK);
CHECK(f.convert == 0 && other.convert == 1);
CHECK(ds18b20_ds2480_temperature(&bus, f.rom[0], &raw) == DS2480_OK && raw == 401);
CHECK(ds2480_init(&bus, &bus.port, 20) == DS2480_OK);
f.fail_prepare = 1;
CHECK(ds2480_init(&bus, &bus.port, 20) == DS2480_IO && !bus.ready);
CHECK(ds2480_init(&bus, NULL, 20) == DS2480_ARGUMENT);
puts("DS18B20/DS2480: all host tests passed");
return 0;
}

View File

@@ -18,9 +18,6 @@ set(SETPROTOCOL_V2_SOURCES
# Совместимые ProtoCAN/SETGUI v1 форматы переходного периода.
set(SETPROTOCOL_LEGACY_SOURCES
src/balsam_can.c
src/set_crc.c
src/periph28335.c
src/tms2812.c
src/gui_catalog.c
src/gui_frame.c
src/pcan_abi.c
@@ -29,6 +26,7 @@ set(SETPROTOCOL_LEGACY_SOURCES
src/pcan_gas.c
src/pcan_id.c
src/pcan_link.c
src/pcan_modbus_server.c
src/pcan_ring.c
)
@@ -91,6 +89,10 @@ if(SETP_BUILD_TESTS)
target_link_libraries(test_transport PRIVATE setprotocol_static)
add_test(NAME legacy_transport COMMAND test_transport)
add_executable(test_modbus_server tests/test_modbus_server.c)
target_link_libraries(test_modbus_server PRIVATE setprotocol_static)
add_test(NAME protocan_modbus_server COMMAND test_modbus_server)
add_executable(test_gui tests/test_gui.c)
target_link_libraries(test_gui PRIVATE setprotocol_static)
add_test(NAME legacy_gui COMMAND test_gui)
@@ -101,12 +103,6 @@ if(SETP_BUILD_TESTS)
add_executable(test_balsam_can tests/test_balsam_can.c)
target_link_libraries(test_balsam_can PRIVATE setprotocol_static)
add_test(NAME legacy_balsam_can COMMAND test_balsam_can)
add_executable(test_periph28335 tests/test_periph28335.c)
target_link_libraries(test_periph28335 PRIVATE setprotocol_static)
add_test(NAME shared_periph28335 COMMAND test_periph28335)
add_executable(test_tms2812 tests/test_tms2812.c)
target_link_libraries(test_tms2812 PRIVATE setprotocol_static)
add_test(NAME shared_tms2812 COMMAND test_tms2812)
add_executable(test_trends tests/test_trends.c)
target_link_libraries(test_trends PRIVATE setprotocol_static)
add_test(NAME shared_trends COMMAND test_trends)

View File

@@ -21,12 +21,6 @@ TCP, UART/DMA и аппаратный CAN подключаются портам
Масштабирование, координаты и измерительные маркеры Android/SETGUI используют
общий `set_plot.c`: [границы модулей, ABI и проверки](docs/GUI_PLOT.md).
Прямой терминал ПМ35/TMS320F28335 использует единый wire contract
`Set_Terminal_28335`: функции MODBUS RTU `03/06`, 128 регистров и CRC16.
Эталонные адаптеры находятся в
[`python/protocan/periph28335.py`](../../python/protocan/periph28335.py) и
[`ports/android/kotlin/.../periph28335`](ports/android/kotlin/ru/setcorp/setprotocol/periph28335).
## Структура
| Каталог | Назначение |

View File

@@ -3,8 +3,7 @@
Численная логика находится в `include/set_plot.h` и `src/set_plot.c`.
Это модуль C99 без Qt, Android, транспорта, динамической памяти и глобального
состояния. Он собирается в существующую библиотеку SETProtocol; отдельная DLL
для графиков не требуется. Версия ABI графиков — `set_plot_abi_version() == 1`;
новые операции добавляются без изменения существующих значений и сигнатур.
для графиков не требуется. Версия ABI графиков — `set_plot_abi_version() == 1`.
| Общее в templates | Адаптер приложения |
|---|---|
@@ -14,7 +13,6 @@
| Перевод значения в долю экрана и обратно, инверсия Y | Canvas/QPainter и оформление шкал |
| Перемещение маркера от начальной координаты, ограничение видимой областью | Захват линии пальцем/мышью, редактор положения |
| Разность BA, DC и множитель единиц, шаги шкалы 1/2/5 | Подписи, цвета, миллисекунды/герцы/единицы сигнала |
| Проверка абсолютных границ X/Y для фиксации осей | Диалог ввода и хранение отдельных границ времени/FFT |
| Модели маркеров и их размещение в Kotlin/Python-портах | Жизненный цикл экрана, очистка и выбор источника |
Модель маркеров: A/B — координаты X и вертикальные линии во всю высоту поля;
@@ -61,9 +59,8 @@ SETGUI: `ui/plot_interaction.py` адаптирует общий модуль к
используются исходные метки приёма. Дискретные дорожки имеют общую X-шкалу,
а маркеры уровня Y относятся к аналоговому полю.
Расчёт FFT не является частью этого модуля. Android и SETGUI вычисляют спектр через
`set_spectrum.c`; там же находится общий поиск доминирующего узкополосного пика,
а трёхсекундный таймер его отображения остаётся состоянием GUI. Вкладка спектра SETGUI получает готовые уровни в дБмВ от
Расчёт FFT не является частью этого модуля. Android вычисляет спектр через
`set_spectrum.c`; вкладка спектра SETGUI получает готовые уровни в дБмВ от
прибора. Общая интерактивная часть не меняет эти данные или единицы.
## Проверка и сборка

View File

@@ -12,7 +12,7 @@
| `include/set_trends.h`, `src/set_trends.c` | C99: фильтрация GAS/raw CAN, signed/unsigned word, payload подписки SET GUI; использует `pcan_id` и ABI export macro |
| `ports/android/kotlin/ru/setcorp/setprotocol/trends/` | Модель, валидация JSON, ограниченная история, GAS_WATCH; JVM + org.json, без Android/Compose |
| `ports/android/setprotocol_jni.c` | Только преобразование JNI-аргументов |
| `python/protocan/trends.py` | Модель, JSON, история и `NativeTrends` (word/CAN/GAS request/ack/data); Python 3.9+, stdlib, без Qt |
| `python/protocan/trends.py` | Модель, JSON, история и `NativeTrends`; Python 3.9+, stdlib, без Qt |
| `tests/fixtures/trends-v1.json` | Один образец для тестов обоих GUI и обмена настройками |
## Формат файла
@@ -44,8 +44,6 @@ UTF-8 JSON: `format = "setflash-trends"`, `version = 1`, `profiles` — слов
CAN — пассивный приём, без записи GAS и автоматической отправки запросов.
GAS принимает только входящие FROM_DEVICE; TX/RTR/ошибки исключены.
Полный `GAS_WATCH_DATA`, включая 32-битную метку прибора, разбирается функцией
`set_trend_watch_decode`; UI не знает смещений полей и endian.
SET GUI оформляет подписку в порядке адресов. ACK должен подтвердить весь
список: при частичном принятии нельзя определить пропущенные адреса, поэтому
строить график по смещённым индексам запрещено. При паузе порт приложения

View File

@@ -73,49 +73,6 @@ PCAN_ABI_API const char *pcan_abi_balsam_device_name(uint8_t device);
PCAN_ABI_API size_t pcan_abi_balsam_register_name(
uint8_t device, uint16_t address, char *output, size_t output_size);
/* PM35/TMS320F28335 direct RS232/485 register terminal. */
PCAN_ABI_API uint16_t pcan_abi_periph28335_crc16(
const uint8_t *data, size_t size);
PCAN_ABI_API size_t pcan_abi_periph28335_append_crc(
const uint8_t *payload, size_t payload_size,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_periph28335_build_read(
uint8_t controller, uint16_t start, uint16_t count,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_periph28335_build_write(
uint8_t controller, uint16_t address, uint16_t value,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_periph28335_build_command(
uint8_t controller, uint8_t command_index,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_periph28335_expected_read_size(uint16_t count);
PCAN_ABI_API int pcan_abi_periph28335_decode_read(
const uint8_t *data, size_t size, uint8_t controller, uint16_t count,
uint16_t *output, size_t output_count);
PCAN_ABI_API int pcan_abi_periph28335_validate_write(
const uint8_t *response, size_t response_size,
const uint8_t *request, size_t request_size);
PCAN_ABI_API size_t pcan_abi_periph28335_project_count(void);
PCAN_ABI_API const char *pcan_abi_periph28335_project_name(
size_t project_index);
PCAN_ABI_API const char *pcan_abi_periph28335_command_name(
size_t project_index, size_t command_index);
/* PM67/TMS320F2812 legacy memory upload. */
PCAN_ABI_API uint16_t pcan_abi_tms2812_crc16(
const uint8_t *data, size_t size);
PCAN_ABI_API size_t pcan_abi_tms2812_build_upload(
uint8_t controller, uint32_t word_address, uint32_t byte_count,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_tms2812_expected_upload_size(
uint32_t byte_count);
PCAN_ABI_API int pcan_abi_tms2812_validate_upload(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count);
PCAN_ABI_API int pcan_abi_tms2812_decode_upload(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count, uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_frame_encode(uint8_t sequence, uint8_t flags,
uint32_t can_id,
const uint8_t *data, uint8_t dlc,

View File

@@ -0,0 +1,60 @@
/**
* @file pcan_modbus_server.h
* @brief Modbus register windows transported in one classic ProtoCAN frame.
*/
#ifndef PCAN_MODBUS_SERVER_H
#define PCAN_MODBUS_SERVER_H
#include <stdbool.h>
#include <stdint.h>
#include "pcan_frame.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
PCAN_MODBUS_COIL = 0x4U,
PCAN_MODBUS_DISCRETE = 0x5U,
PCAN_MODBUS_HOLDING = 0x6U,
PCAN_MODBUS_INPUT = 0x7U
} pcan_modbus_bank_t;
typedef bool (*pcan_modbus_read_fn)(void *user, pcan_modbus_bank_t bank,
uint16_t address, uint16_t *value);
typedef bool (*pcan_modbus_write_fn)(void *user, pcan_modbus_bank_t bank,
uint16_t address, uint16_t value);
typedef struct {
uint8_t device_type;
uint8_t device_id;
pcan_modbus_read_fn read;
pcan_modbus_write_fn write;
void *user;
uint32_t requests;
uint32_t responses;
uint32_t rejected;
} pcan_modbus_server_t;
typedef enum {
PCAN_MODBUS_NOT_FOR_US = 0,
PCAN_MODBUS_HANDLED_NO_RESPONSE,
PCAN_MODBUS_RESPONSE
} pcan_modbus_result_t;
bool pcan_modbus_server_init(pcan_modbus_server_t *server,
uint8_t device_type, uint8_t device_id,
pcan_modbus_read_fn read,
pcan_modbus_write_fn write, void *user);
/** Zero DLC reads; data writes COIL/HOLDING. Register words are little-endian. */
pcan_modbus_result_t pcan_modbus_server_handle(pcan_modbus_server_t *server,
const pcan_frame_t *request,
pcan_frame_t *response);
#ifdef __cplusplus
}
#endif
#endif /* PCAN_MODBUS_SERVER_H */

View File

@@ -1,68 +0,0 @@
/**
* @file periph28335.h
* @brief Shared PM35/TMS320F28335 register protocol.
*
* The protocol is independent from PM67/TMS320F2812 CAN and RS channels.
* It implements the direct RS232/485 register terminal used by PM35.
*/
#ifndef PERIPH28335_H
#define PERIPH28335_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define PERIPH28335_REGISTER_COUNT 128U
#define PERIPH28335_REQUEST_SIZE 8U
#define PERIPH28335_COMMAND_COUNT 17U
typedef enum {
PERIPH28335_OK = 0,
PERIPH28335_ERROR_ARGUMENT = -1,
PERIPH28335_ERROR_RANGE = -2,
PERIPH28335_ERROR_LENGTH = -3,
PERIPH28335_ERROR_CRC = -4,
PERIPH28335_ERROR_HEADER = -5,
PERIPH28335_ERROR_CAPACITY = -6
} periph28335_status_t;
uint16_t periph28335_crc16_modbus(const uint8_t *data, size_t size);
size_t periph28335_append_crc(const uint8_t *payload, size_t payload_size,
uint8_t *output, size_t output_size);
size_t periph28335_build_read_registers(
uint8_t controller, uint16_t start, uint16_t count,
uint8_t *output, size_t output_size);
size_t periph28335_build_write_register(
uint8_t controller, uint16_t address, uint16_t value,
uint8_t *output, size_t output_size);
size_t periph28335_build_command(
uint8_t controller, uint8_t command_index,
uint8_t *output, size_t output_size);
size_t periph28335_expected_read_response_size(uint16_t count);
int periph28335_decode_read_response(
const uint8_t *data, size_t size, uint8_t controller, uint16_t count,
uint16_t *output, size_t output_count);
int periph28335_validate_write_response(
const uint8_t *response, size_t response_size,
const uint8_t *request, size_t request_size);
size_t periph28335_project_count(void);
const char *periph28335_project_name(size_t project_index);
const char *periph28335_command_name(size_t project_index,
size_t command_index);
#ifdef __cplusplus
}
#endif
#endif /* PERIPH28335_H */

View File

@@ -23,6 +23,7 @@
#include "pcan_gas.h"
#include "pcan_id.h"
#include "pcan_link.h"
#include "pcan_modbus_server.h"
#include "pcan_ring.h"
#endif /* PROTOCAN_TRANSPORT_H */

View File

@@ -1,19 +0,0 @@
/** @file set_crc.h @brief Shared checksums used by legacy SET controllers. */
#ifndef SET_CRC_H
#define SET_CRC_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/** CRC-16/Modbus: reflected polynomial 0xA001, initial value 0xFFFF. */
uint16_t set_crc16_modbus(const uint8_t *data, size_t size);
#ifdef __cplusplus
}
#endif
#endif /* SET_CRC_H */

View File

@@ -17,11 +17,10 @@ enum set_plot_operation {
SET_PLOT_DRAG = 4, /* initial,deltaPixels,length,low,high,inverted -> clamped value */
SET_PLOT_TICK_STEP = 5, /* range,lengthPixels -> nice step */
SET_PLOT_DELTA = 6, /* A,B,multiplier -> (B-A)*multiplier */
SET_PLOT_DB_DELTA = 7, /* A,B -> 20*log10(abs(B/A)); zero is invalid */
SET_PLOT_LIMITS = 8 /* xMin,xMax,yMin,yMax -> validated unchanged limits */
SET_PLOT_DB_DELTA = 7 /* A,B -> 20*log10(abs(B/A)); zero is invalid */
};
/** Version of this additive plot ABI, independently of the transport ABI. */
/** Version of this plot ABI, independently of the transport ABI. */
PCAN_ABI_API uint32_t set_plot_abi_version(void);
/** Evaluates one operation. Returns output count, or 0 for invalid arguments.
* TRANSFORM rejects malformed viewports; invalid gesture values return the

View File

@@ -28,15 +28,6 @@ PCAN_ABI_API int set_spectrum_analyze(const double *times, const double *values,
size_t max_size, int window, int filter, double low_hz, double high_hz, int remove_mean,
double *amplitudes, size_t capacity, double *meta);
/** Find the strongest non-DC local maximum above both the absolute floor and
* relative_threshold * median(non-DC amplitudes). The caller supplies scratch
* storage of at least count-1 doubles. peak = {frequency_hz, amplitude}.
* Returns 1 when found, 0 when no narrow-band peak exists, -1 on invalid input.
*/
PCAN_ABI_API int set_spectrum_dominant_peak(const double *amplitudes, size_t count,
double bin_hz, double relative_threshold, double absolute_floor,
double *scratch, size_t scratch_capacity, double *peak, size_t peak_capacity);
#ifdef __cplusplus
}
#endif

View File

@@ -48,13 +48,6 @@ PCAN_ABI_API int set_trend_watch_ack(
PCAN_ABI_API int set_trend_watch_values(
const uint8_t *payload, size_t size, uint16_t *words, size_t capacity);
/** Decode the complete GAS_WATCH_DATA payload including its device timestamp.
* This is the preferred ABI for GUI ports; the older values-only symbol remains
* available for binary compatibility.
*/
PCAN_ABI_API int set_trend_watch_decode(const uint8_t *payload, size_t size,
uint32_t *timestamp_ms, uint16_t *words, size_t capacity);
#ifdef __cplusplus
}
#endif

View File

@@ -6,8 +6,6 @@
#define SETPROTOCOL_H
#include "balsam_can.h"
#include "periph28335.h"
#include "tms2812.h"
/* Основной SET protocol v2. */
#include "set_protocol.h"

View File

@@ -1,49 +0,0 @@
/**
* @file tms2812.h
* @brief Shared PM67/TMS320F2812 legacy upload protocol.
*/
#ifndef TMS2812_H
#define TMS2812_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define TMS2812_CMD_UPLOAD 52U
#define TMS2812_UPLOAD_REQUEST_SIZE 12U
#define TMS2812_UPLOAD_RESPONSE_OVERHEAD 8U
typedef enum {
TMS2812_OK = 0,
TMS2812_ERROR_ARGUMENT = -1,
TMS2812_ERROR_RANGE = -2,
TMS2812_ERROR_LENGTH = -3,
TMS2812_ERROR_CRC = -4,
TMS2812_ERROR_HEADER = -5,
TMS2812_ERROR_CAPACITY = -6
} tms2812_status_t;
uint16_t tms2812_crc16(const uint8_t *data, size_t size);
size_t tms2812_build_upload_request(
uint8_t controller, uint32_t word_address, uint32_t byte_count,
uint8_t *output, size_t output_size);
size_t tms2812_expected_upload_response_size(uint32_t byte_count);
int tms2812_validate_upload_response(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count);
int tms2812_decode_upload_response(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count, uint8_t *output, size_t output_size);
#ifdef __cplusplus
}
#endif
#endif /* TMS2812_H */

View File

@@ -13,9 +13,6 @@ LOCAL_SRC_FILES := \
../../src/set_trends.c \
../../src/set_spectrum.c \
../../src/balsam_can.c \
../../src/set_crc.c \
../../src/periph28335.c \
../../src/tms2812.c \
../../src/gui_catalog.c \
../../src/gui_frame.c \
../../src/pcan_abi.c \

View File

@@ -175,9 +175,7 @@ object CanBridgeProtocol {
stats = Stats(
frames = values[0],
crcErrors = values[1],
// The former Kotlin parser counted one resync step
// for a rejected CRC frame in addition to stray bytes.
resyncBytes = values[1] + values[2] + values[3],
resyncBytes = values[2] + values[3],
sequenceLost = values[4],
)
}

View File

@@ -4,10 +4,7 @@ package ru.setcorp.setprotocol
object NativeSetProtocol {
val available: Boolean by lazy {
runCatching {
val hostLibrary = System.getProperty("setprotocol.library")
?: System.getProperty("setplot.library")
if (hostLibrary != null) System.load(hostLibrary)
else System.loadLibrary("setprotocol")
System.loadLibrary("setprotocol")
nativeAbiVersion() == 1
}.getOrDefault(false)
}
@@ -16,18 +13,12 @@ object NativeSetProtocol {
/** {status, N, Fs, jitter, amplitudes...}; status != 0 has no amplitudes. */
external fun nativeSpectrum(times: DoubleArray, values: DoubleArray, maxSize: Int,
window: Int, filter: Int, lowHz: Double, highHz: Double, removeMean: Boolean): DoubleArray?
/** {frequencyHz, amplitude}, empty when no narrow-band peak is present. */
external fun nativeSpectrumPeak(amplitudes: DoubleArray, binHz: Double,
relativeThreshold: Double, absoluteFloor: Double): DoubleArray?
external fun nativeTrendCanValue(
source: Int, address: Long, deviceType: Int, device: Int, byteOffset: Int,
extended: Boolean, signed: Boolean, canId: Long, flags: Int, input: ByteArray,
): Int
external fun nativeTrendWatchRequest(period: Int, addresses: IntArray): ByteArray?
external fun nativeTrendWatchAck(input: ByteArray, period: Int, count: Int): Boolean
external fun nativeTrendWatchValues(input: ByteArray): IntArray?
/** {unsigned timestampMs, word0, ...}. */
external fun nativeTrendWatchDecode(input: ByteArray): LongArray?
external fun nativePackId(
priority: Int,
route: Int,
@@ -41,22 +32,6 @@ object NativeSetProtocol {
external fun nativeBalsamDecode(canId: Long, input: ByteArray): IntArray?
external fun nativeBalsamDeviceName(device: Int): String
external fun nativeBalsamRegisterName(device: Int, address: Int): String
external fun nativePeriph28335Crc16(input: ByteArray): Int
external fun nativePeriph28335AppendCrc(input: ByteArray): ByteArray?
external fun nativePeriph28335BuildRead(controller: Int, start: Int, count: Int): ByteArray?
external fun nativePeriph28335BuildWrite(controller: Int, address: Int, value: Int): ByteArray?
external fun nativePeriph28335BuildCommand(controller: Int, commandIndex: Int): ByteArray?
external fun nativePeriph28335ExpectedReadSize(count: Int): Int
external fun nativePeriph28335DecodeRead(input: ByteArray, controller: Int, count: Int): IntArray?
external fun nativePeriph28335ValidateWrite(response: ByteArray, request: ByteArray): Int
external fun nativePeriph28335ProjectCount(): Int
external fun nativePeriph28335ProjectName(projectIndex: Int): String?
external fun nativePeriph28335CommandName(projectIndex: Int, commandIndex: Int): String?
external fun nativeTms2812Crc16(input: ByteArray): Int
external fun nativeTms2812BuildUpload(controller: Int, wordAddress: Long, byteCount: Long): ByteArray?
external fun nativeTms2812ExpectedUploadSize(byteCount: Long): Int
external fun nativeTms2812ValidateUpload(input: ByteArray, controller: Int, byteCount: Int): Boolean
external fun nativeTms2812DecodeUpload(input: ByteArray, controller: Int, byteCount: Int): ByteArray?
external fun nativeEncodeFrame(
sequence: Int,
flags: Int,

View File

@@ -1,117 +0,0 @@
package ru.setcorp.setprotocol.periph28335
import ru.setcorp.setprotocol.NativeSetProtocol
/** Kotlin UI adapter; all PM35 wire logic and the command catalog live in C99. */
object Periph28335Protocol {
const val REGISTER_COUNT = 128
const val DEFAULT_CONTROLLER = 16
const val DEFAULT_BAUD_RATE = 115_200
val projectCommands: Map<String, List<String>> by lazy {
requireNative()
buildMap {
repeat(NativeSetProtocol.nativePeriph28335ProjectCount()) { project ->
val name = requireNotNull(
NativeSetProtocol.nativePeriph28335ProjectName(project),
) { "Повреждён каталог проектов ПМ35" }
put(name, List(17) { command ->
requireNotNull(
NativeSetProtocol.nativePeriph28335CommandName(project, command),
) { "Повреждён каталог команд ПМ35" }
})
}
}
}
fun crc16Modbus(data: ByteArray, initial: Int = 0xFFFF): Int {
require(initial == 0xFFFF) {
"Произвольное начальное значение CRC не входит в протокол ПМ35"
}
requireNative()
return NativeSetProtocol.nativePeriph28335Crc16(data)
}
fun withCrc(payload: ByteArray): ByteArray {
requireNative()
return requireNotNull(NativeSetProtocol.nativePeriph28335AppendCrc(payload)) {
"SETProtocol отклонил данные ПМ35"
}
}
fun buildReadRegisters(controller: Int, start: Int, count: Int): ByteArray {
requireRange("Адрес контроллера", controller, 0xFF)
requireRange("Начальный регистр", start, 0xFFFF)
require(count in 1..REGISTER_COUNT && start + count <= REGISTER_COUNT) {
"Диапазон регистров должен находиться в 0..127"
}
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335BuildRead(controller, start, count),
) { "SETProtocol отклонил запрос чтения ПМ35" }
}
fun buildWriteRegister(controller: Int, address: Int, value: Int): ByteArray {
requireRange("Адрес контроллера", controller, 0xFF)
requireRange("Адрес регистра", address, REGISTER_COUNT - 1)
requireRange("Значение", value, 0xFFFF)
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335BuildWrite(controller, address, value),
) { "SETProtocol отклонил запрос записи ПМ35" }
}
fun buildCommand(controller: Int, commandIndex: Int): ByteArray {
require(commandIndex in 0..16) { "Номер команды должен быть в диапазоне 0..16" }
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335BuildCommand(controller, commandIndex),
) { "SETProtocol отклонил команду ПМ35" }
}
fun expectedReadResponseSize(count: Int): Int {
require(count in 1..REGISTER_COUNT)
requireNative()
return NativeSetProtocol.nativePeriph28335ExpectedReadSize(count)
}
fun decodeReadResponse(data: ByteArray, controller: Int, count: Int): List<Int> {
val expected = expectedReadResponseSize(count)
require(data.size == expected) { "Ожидалось $expected байт, получено ${data.size}" }
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335DecodeRead(data, controller, count),
) { "Повреждён ответ ПМ35: заголовок, длина или CRC" }.toList()
}
fun validateWriteResponse(data: ByteArray, request: ByteArray): Boolean {
requireNative()
return NativeSetProtocol.nativePeriph28335ValidateWrite(data, request) == 0
}
// Presentation-only conversions stay in the GUI port; they do not define wire bytes.
fun bitsLsbFirst(value: Int): List<Boolean> {
requireRange("Значение", value, 0xFFFF)
return (0 until 16).map { bit -> value and (1 shl bit) != 0 }
}
fun wordFromBits(bits: List<Boolean>): Int {
require(bits.size == 16) { "Должно быть ровно 16 бит" }
return bits.foldIndexed(0) { bit, value, checked ->
if (checked) value or (1 shl bit) else value
}
}
fun signedWord(value: Int): Int {
requireRange("Значение", value, 0xFFFF)
return if (value < 0x8000) value else value - 0x10000
}
private fun requireNative() {
check(NativeSetProtocol.available) { "Нативное ядро SETProtocol недоступно" }
}
private fun requireRange(name: String, value: Int, maximum: Int) {
require(value in 0..maximum) { "$name вне диапазона 0..$maximum" }
}
}

View File

@@ -10,28 +10,37 @@ object GuiGasWatch {
fun request(periodMs: Int, addresses: List<Int>): ByteArray {
require(periodMs in 0..65535 && addresses.size <= 64 && addresses.all { it in 0..65535 })
check(NativeSetProtocol.available) { "Общая библиотека SETProtocol недоступна" }
return requireNotNull(NativeSetProtocol.nativeTrendWatchRequest(periodMs, addresses.toIntArray()))
if (NativeSetProtocol.available) return requireNotNull(NativeSetProtocol.nativeTrendWatchRequest(periodMs, addresses.toIntArray()))
return ByteArray(4 + addresses.size * 2).also { output ->
put16(output, 0, periodMs)
put16(output, 2, addresses.size)
addresses.forEachIndexed { index, address -> put16(output, 4 + index * 2, address) }
}
}
fun validateAck(payload: ByteArray, periodMs: Int, count: Int) {
require(NativeSetProtocol.available && NativeSetProtocol.nativeTrendWatchAck(payload, periodMs, count)) {
require(payload.size == 4 && read16(payload, 0) == periodMs && read16(payload, 2) == count) {
"Прибор принял не все адреса GAS. Проверьте карту регистров; отображение по неполной подписке невозможно"
}
}
fun values(payload: ByteArray, expectedCount: Int): List<Int> {
check(NativeSetProtocol.available) { "Общая библиотека SETProtocol недоступна" }
val values = requireNotNull(NativeSetProtocol.nativeTrendWatchValues(payload)) { "Повреждён GAS_WATCH_DATA" }.toList()
val values = if (NativeSetProtocol.available) {
requireNotNull(NativeSetProtocol.nativeTrendWatchValues(payload)) { "Повреждён GAS_WATCH_DATA" }.toList()
} else {
require(payload.size >= 6) { "GAS_WATCH_DATA короче заголовка" }
val count = read16(payload, 4)
require(count <= 64 && payload.size == 6 + count * 2) { "Неверная длина GAS_WATCH_DATA" }
List(count) { read16(payload, 6 + it * 2) }
}
require(values.size == expectedCount) { "Число значений GAS не соответствует подписке" }
return values
}
data class Sample(val timestampMs: Long, val values: List<Int>)
fun decode(payload: ByteArray, expectedCount: Int): Sample {
check(NativeSetProtocol.available) { "Общая библиотека SETProtocol недоступна" }
val decoded = requireNotNull(NativeSetProtocol.nativeTrendWatchDecode(payload)) { "Повреждён GAS_WATCH_DATA" }
require(decoded.size == expectedCount + 1) { "Число значений GAS не соответствует подписке" }
return Sample(decoded[0], decoded.drop(1).map(Long::toInt))
private fun read16(data: ByteArray, offset: Int): Int =
(data[offset].toInt() and 0xFF) or ((data[offset + 1].toInt() and 0xFF) shl 8)
private fun put16(data: ByteArray, offset: Int, value: Int) {
data[offset] = value.toByte()
data[offset + 1] = (value ushr 8).toByte()
}
}

View File

@@ -32,10 +32,3 @@ fun plotTickStep(range: Double, pixels: Double): Double = NativePlot.call(5, ran
fun plotDelta(a: Double, b: Double, multiplier: Double = 1.0): Double = NativePlot.call(6, a, b, multiplier)[0]
fun plotDbDelta(a: Double, b: Double): Double? =
runCatching { NativePlot.call(7, a, b)[0] }.getOrNull()
data class PlotLimits(val xMin: Double, val xMax: Double, val yMin: Double, val yMax: Double) {
fun validated(): PlotLimits {
val values = NativePlot.call(8, xMin, xMax, yMin, yMax)
return PlotLimits(values[0], values[1], values[2], values[3])
}
}

View File

@@ -6,7 +6,7 @@ enum class TrendSection { SIGNALS, CHART }
/** File-format IDs; host applications map their connection profiles to these IDs. */
enum class TrendProfile(val title: String) {
SET_V1("SET GUI v1"), TMS2812("TMS320F2812 / BALZAM"), TMS28335("TMS320F28335 / ПМ35"), CAN_BRIDGE("CAN ↔ RS485"),
SET_V1("SET GUI v1"), TMS2812("TMS320F2812 / BALZAM"), CAN_BRIDGE("CAN ↔ RS485"),
GS_USB_CAN("CANgaroo / gs_usb"), SLCAN("SKLab SLCAN"), CANGAROO_SLCAN("CANgaroo / SLCAN"),
BALZAM_CAN("Старый CAN BALZAM"),
}
@@ -29,7 +29,7 @@ enum class TrendValueType(val title: String) {
}
fun TrendProfile.trendSources(): List<TrendSource> = when (this) {
TrendProfile.TMS2812, TrendProfile.TMS28335 -> listOf(TrendSource.TMS_MEMORY)
TrendProfile.TMS2812 -> listOf(TrendSource.TMS_MEMORY)
TrendProfile.SET_V1 -> listOf(TrendSource.SET_GAS, TrendSource.SET_SENSOR)
TrendProfile.BALZAM_CAN -> listOf(TrendSource.CAN_RAW)
else -> listOf(TrendSource.CAN_GAS, TrendSource.CAN_RAW)

View File

@@ -34,8 +34,6 @@ data class TrendSpectrum(
val binHz: Double get() = if (size > 0) sampleRate / size else 0.0
}
data class SpectrumPeak(val frequencyHz: Double, val amplitude: Double)
/** Math is implemented once in C and used unchanged by JNI and ctypes. */
object SpectrumAnalyzer {
fun analyze(points: List<TrendPoint>, options: SpectrumOptions): TrendSpectrum {
@@ -66,12 +64,4 @@ object SpectrumAnalyzer {
return TrendSpectrum(output[1].toInt(), output[2], output[3],
if (error == null) output.drop(4) else emptyList(), error)
}
fun dominantPeak(spectrum: TrendSpectrum, relativeThreshold: Double = 3.0,
absoluteFloor: Double = 1e-6): SpectrumPeak? {
if (spectrum.error != null || spectrum.size <= 0 || spectrum.amplitudes.size < 3) return null
val result = NativeSetProtocol.nativeSpectrumPeak(spectrum.amplitudes.toDoubleArray(),
spectrum.binHz, relativeThreshold, absoluteFloor) ?: return null
return result.takeIf { it.size == 2 }?.let { SpectrumPeak(it[0], it[1]) }
}
}

View File

@@ -7,273 +7,6 @@
#include "set_trends.h"
#include "set_spectrum.h"
#include "balsam_can.h"
#include "periph28335.h"
#include "tms2812.h"
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812Crc16(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
if (input == NULL) return 0;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) return 0;
uint16_t crc = tms2812_crc16((const uint8_t *)data, (size_t)size);
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return (jint)crc;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812BuildUpload(
JNIEnv *env, jobject self, jint controller, jlong word_address,
jlong byte_count)
{
(void)self;
uint8_t output[TMS2812_UPLOAD_REQUEST_SIZE];
if (controller < 0 || controller > 255 || word_address < 0 ||
(uint64_t)word_address > UINT32_MAX || byte_count < 1 ||
(uint64_t)byte_count > UINT32_MAX) return NULL;
size_t written = tms2812_build_upload_request(
(uint8_t)controller, (uint32_t)word_address, (uint32_t)byte_count,
output, sizeof output);
if (written == 0U) return NULL;
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
return result;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812ExpectedUploadSize(
JNIEnv *env, jobject self, jlong byte_count)
{
(void)env; (void)self;
if (byte_count < 1 || (uint64_t)byte_count > UINT32_MAX) return 0;
size_t size = tms2812_expected_upload_response_size((uint32_t)byte_count);
return size <= INT32_MAX ? (jint)size : 0;
}
JNIEXPORT jboolean JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812ValidateUpload(
JNIEnv *env, jobject self, jbyteArray input, jint controller,
jint byte_count)
{
(void)self;
if (input == NULL || controller < 0 || controller > 255 || byte_count < 1)
return JNI_FALSE;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if (data == NULL) return JNI_FALSE;
int status = tms2812_validate_upload_response(
(const uint8_t *)data, (size_t)size, (uint8_t)controller,
(uint32_t)byte_count);
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return status == TMS2812_OK ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812DecodeUpload(
JNIEnv *env, jobject self, jbyteArray input, jint controller,
jint byte_count)
{
(void)self;
if (input == NULL || controller < 0 || controller > 255 || byte_count < 1)
return NULL;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if (data == NULL) return NULL;
uint8_t *output = (uint8_t *)malloc((size_t)byte_count);
if (output == NULL) {
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return NULL;
}
int status = tms2812_decode_upload_response(
(const uint8_t *)data, (size_t)size, (uint8_t)controller,
(uint32_t)byte_count, output, (size_t)byte_count);
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
if (status != TMS2812_OK) { free(output); return NULL; }
jbyteArray result = (*env)->NewByteArray(env, byte_count);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, byte_count, (const jbyte *)output);
free(output);
return result;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335Crc16(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
if (input == NULL) return 0;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) return 0;
uint16_t crc = periph28335_crc16_modbus((const uint8_t *)data, (size_t)size);
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return (jint)crc;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335AppendCrc(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
if (input == NULL) return NULL;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) return NULL;
uint8_t *output = (uint8_t *)malloc((size_t)size + 2U);
if (output == NULL) {
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return NULL;
}
size_t written = periph28335_append_crc(
(const uint8_t *)data, (size_t)size, output, (size_t)size + 2U);
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
if (written == 0U) { free(output); return NULL; }
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
free(output);
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335BuildRead(
JNIEnv *env, jobject self, jint controller, jint start, jint count)
{
(void)self;
uint8_t output[PERIPH28335_REQUEST_SIZE];
if (controller < 0 || controller > 255 || start < 0 || start > 65535 ||
count < 0 || count > 65535) return NULL;
size_t written = periph28335_build_read_registers(
(uint8_t)controller, (uint16_t)start, (uint16_t)count,
output, sizeof output);
if (written == 0U) return NULL;
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335BuildWrite(
JNIEnv *env, jobject self, jint controller, jint address, jint value)
{
(void)self;
uint8_t output[PERIPH28335_REQUEST_SIZE];
if (controller < 0 || controller > 255 || address < 0 || address > 65535 ||
value < 0 || value > 65535) return NULL;
size_t written = periph28335_build_write_register(
(uint8_t)controller, (uint16_t)address, (uint16_t)value,
output, sizeof output);
if (written == 0U) return NULL;
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335BuildCommand(
JNIEnv *env, jobject self, jint controller, jint command_index)
{
(void)self;
uint8_t output[PERIPH28335_REQUEST_SIZE];
if (controller < 0 || controller > 255 || command_index < 0 || command_index > 255) return NULL;
size_t written = periph28335_build_command(
(uint8_t)controller, (uint8_t)command_index, output, sizeof output);
if (written == 0U) return NULL;
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
return result;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ExpectedReadSize(
JNIEnv *env, jobject self, jint count)
{
(void)env; (void)self;
if (count < 0 || count > 65535) return 0;
return (jint)periph28335_expected_read_response_size((uint16_t)count);
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335DecodeRead(
JNIEnv *env, jobject self, jbyteArray input, jint controller, jint count)
{
(void)self;
if (input == NULL || controller < 0 || controller > 255 ||
count < 1 || count > (jint)PERIPH28335_REGISTER_COUNT) return NULL;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) return NULL;
uint16_t words[PERIPH28335_REGISTER_COUNT];
int status = periph28335_decode_read_response(
(const uint8_t *)data, (size_t)size, (uint8_t)controller,
(uint16_t)count, words, PERIPH28335_REGISTER_COUNT);
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
if (status != PERIPH28335_OK) return NULL;
jint values[PERIPH28335_REGISTER_COUNT];
for (jint index = 0; index < count; ++index) values[index] = words[index];
jintArray result = (*env)->NewIntArray(env, count);
if (result != NULL) (*env)->SetIntArrayRegion(env, result, 0, count, values);
return result;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ValidateWrite(
JNIEnv *env, jobject self, jbyteArray response, jbyteArray request)
{
(void)self;
if (response == NULL || request == NULL) return PERIPH28335_ERROR_ARGUMENT;
jsize response_size = (*env)->GetArrayLength(env, response);
jsize request_size = (*env)->GetArrayLength(env, request);
jbyte *response_data = (*env)->GetByteArrayElements(env, response, NULL);
jbyte *request_data = (*env)->GetByteArrayElements(env, request, NULL);
if (response_data == NULL || request_data == NULL) {
if (response_data != NULL) (*env)->ReleaseByteArrayElements(env, response, response_data, JNI_ABORT);
if (request_data != NULL) (*env)->ReleaseByteArrayElements(env, request, request_data, JNI_ABORT);
return PERIPH28335_ERROR_ARGUMENT;
}
int status = periph28335_validate_write_response(
(const uint8_t *)response_data, (size_t)response_size,
(const uint8_t *)request_data, (size_t)request_size);
(*env)->ReleaseByteArrayElements(env, response, response_data, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, request, request_data, JNI_ABORT);
return status;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ProjectCount(
JNIEnv *env, jobject self)
{
(void)env; (void)self;
return (jint)periph28335_project_count();
}
JNIEXPORT jstring JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ProjectName(
JNIEnv *env, jobject self, jint project_index)
{
(void)self;
const char *name = project_index >= 0
? periph28335_project_name((size_t)project_index) : NULL;
return name != NULL ? (*env)->NewStringUTF(env, name) : NULL;
}
JNIEXPORT jstring JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335CommandName(
JNIEnv *env, jobject self, jint project_index, jint command_index)
{
(void)self;
const char *name = project_index >= 0 && command_index >= 0
? periph28335_command_name((size_t)project_index, (size_t)command_index)
: NULL;
return name != NULL ? (*env)->NewStringUTF(env, name) : NULL;
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeBalsamDecode(
@@ -367,30 +100,6 @@ Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendCanValue(
(uint8_t)flags, (const uint8_t *)data, (size_t)size);
}
JNIEXPORT jdoubleArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeSpectrumPeak(
JNIEnv *env, jobject self, jdoubleArray input, jdouble bin_hz,
jdouble relative_threshold, jdouble absolute_floor)
{
(void)self;
jsize count = input == NULL ? 0 : (*env)->GetArrayLength(env, input);
if (count < 3 || count > (jsize)(SET_SPECTRUM_MAX / 2U + 1U)) return NULL;
double *buffer = (double *)malloc(sizeof(double) * (size_t)(count * 2 - 1));
if (buffer == NULL) return NULL;
double *amplitudes = buffer, *scratch = buffer + count, peak[2];
(*env)->GetDoubleArrayRegion(env, input, 0, count, amplitudes);
int status = (*env)->ExceptionCheck(env) ? -1 : set_spectrum_dominant_peak(
amplitudes, (size_t)count, bin_hz, relative_threshold, absolute_floor,
scratch, (size_t)count - 1U, peak, 2U);
jdoubleArray result = NULL;
if (status >= 0) {
result = (*env)->NewDoubleArray(env, status == 1 ? 2 : 0);
if (result != NULL && status == 1) (*env)->SetDoubleArrayRegion(env, result, 0, 2, peak);
}
free(buffer);
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchRequest(
JNIEnv *env, jobject self, jint period, jintArray input)
@@ -413,20 +122,6 @@ Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchRequest(
return result;
}
JNIEXPORT jboolean JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchAck(
JNIEnv *env, jobject self, jbyteArray input, jint period, jint count)
{
(void)self;
if (input == NULL || period < 0 || period > 65535 || count < 0) return JNI_FALSE;
jsize size = (*env)->GetArrayLength(env, input);
if (size != 4) return JNI_FALSE;
jbyte payload[4];
(*env)->GetByteArrayRegion(env, input, 0, size, payload);
return set_trend_watch_ack((const uint8_t *)payload, (size_t)size,
(uint16_t)period, (size_t)count) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchValues(
JNIEnv *env, jobject self, jbyteArray input)
@@ -446,28 +141,6 @@ Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchValues(
return result;
}
JNIEXPORT jlongArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchDecode(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
jsize size = input == NULL ? 0 : (*env)->GetArrayLength(env, input);
if (size < 6 || size > (jsize)(6U + 2U * SET_TREND_WATCH_MAX)) return NULL;
jbyte payload[6U + 2U * SET_TREND_WATCH_MAX];
uint16_t words[SET_TREND_WATCH_MAX];
uint32_t timestamp = 0U;
jlong values[1U + SET_TREND_WATCH_MAX];
(*env)->GetByteArrayRegion(env, input, 0, size, payload);
int count = set_trend_watch_decode((const uint8_t *)payload, (size_t)size,
&timestamp, words, SET_TREND_WATCH_MAX);
if (count < 0) return NULL;
values[0] = (jlong)timestamp;
for (int i = 0; i < count; ++i) values[i + 1] = (jlong)words[i];
jlongArray result = (*env)->NewLongArray(env, count + 1);
if (result != NULL) (*env)->SetLongArrayRegion(env, result, 0, count + 1, values);
return result;
}
typedef struct {
uint8_t *storage;
size_t storage_size;

View File

@@ -1,35 +0,0 @@
package ru.setcorp.setprotocol.periph28335
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class Periph28335ProtocolTest {
@Test fun readRequestMatchesDelphiByteOrder() {
val request = Periph28335Protocol.buildReadRegisters(16, 24, 64)
assertTrue(request.copyOf(6).contentEquals(byteArrayOf(0x10, 0x03, 0x00, 0x18, 0x00, 0x40)))
assertEquals(
Periph28335Protocol.crc16Modbus(request.copyOf(6)),
(request[6].toInt() and 0xFF) or ((request[7].toInt() and 0xFF) shl 8),
)
}
@Test fun writeAndCommandUseRegister127() {
assertTrue(
Periph28335Protocol.buildWriteRegister(16, 7, 0x1234).copyOf(6)
.contentEquals(byteArrayOf(0x10, 0x06, 0x00, 0x07, 0x12, 0x34)),
)
assertTrue(
Periph28335Protocol.buildCommand(16, 15).copyOf(6)
.contentEquals(byteArrayOf(0x10, 0x06, 0x00, 0x7F, 0x80.toByte(), 0x00)),
)
}
@Test fun responseAndBitsRoundTrip() {
val response = Periph28335Protocol.withCrc(byteArrayOf(0x10, 0x03, 0x04, 0x80.toByte(), 0x05, 0x12, 0x34))
assertEquals(listOf(0x8005, 0x1234), Periph28335Protocol.decodeReadResponse(response, 16, 2))
val bits = Periph28335Protocol.bitsLsbFirst(0x8005)
assertTrue(bits[0] && bits[2] && bits[15])
assertEquals(0x8005, Periph28335Protocol.wordFromBits(bits))
}
}

View File

@@ -36,12 +36,4 @@ class PlotViewportTest {
}
SpectrumOptions().validate()
}
@Test fun absoluteLimitsAndDominantPeakUseNativeCore() {
assertEquals(PlotLimits(0.0, 500.0, -1.0, 1.0),
PlotLimits(0.0, 500.0, -1.0, 1.0).validated())
assertTrue(runCatching { PlotLimits(1.0, 1.0, -1.0, 1.0).validated() }.isFailure)
val spectrum = TrendSpectrum(12, 60.0, 0.0,
listOf(10.0, .01, .02, .8, .03, .4, .02))
assertEquals(15.0, SpectrumAnalyzer.dominantPeak(spectrum)!!.frequencyHz, 0.0)
}
}

View File

@@ -0,0 +1,22 @@
# STM32F407 + CAN485 DevBoard_V1
Порт даёт SETGUI доступ к существующим Modbus-регистрам STM32F407 через
классический CAN. ПК подключён к USB-UART платы CAN485 DevBoard_V1, команда
`T,E` платы отправляет ProtoCAN-кадр, а этот порт отвечает тем же типом кадра.
При маршруте `Q3` плата одновременно ретранслирует поток по RS-485.
Порт не настраивает GPIO, bitrate, фильтры и не запускает CAN: это остаётся за
CubeMX-проектом. Необработанные кадры передаются callback-у приложения, поэтому
загрузчик ProtoCAN и регистровый сервис могут делить FIFO0.
Слой приложения предоставляет callback-и чтения и записи. Нулевой DLC означает
чтение; данные в кадре `HOLDING`/`COIL` — запись. За кадр передаются до четырёх
16-битных регистров или до пятнадцати coils (предел 4-битного `RegCount`).
```c
static set_devboard_v1_stm32f407_t gui_can;
set_devboard_v1_stm32f407_init(&gui_can, &hcan, 7, 3,
app_read_register, app_write_register, NULL,
app_handle_boot_frame, NULL);
for (;;) set_devboard_v1_stm32f407_poll(&gui_can);
```

View File

@@ -0,0 +1,83 @@
#include "set_devboard_v1_stm32f407.h"
static bool transmit(set_devboard_v1_stm32f407_t *port,
const pcan_frame_t *frame)
{
CAN_TxHeaderTypeDef header = {0};
uint32_t mailbox = 0U;
uint8_t data[8] = {0U};
header.ExtId = frame->id & 0x1FFFFFFFUL;
header.IDE = CAN_ID_EXT;
header.RTR = ((frame->flags & PCAN_FLAG_RTR) != 0U) ?
CAN_RTR_REMOTE : CAN_RTR_DATA;
header.DLC = frame->dlc;
header.TransmitGlobalTime = DISABLE;
for (uint8_t index = 0U; index < frame->dlc; ++index) {
data[index] = frame->data[index];
}
if (HAL_CAN_AddTxMessage(port->can, &header, data, &mailbox) != HAL_OK) {
port->tx_errors++;
return false;
}
port->tx_frames++;
return true;
}
bool set_devboard_v1_stm32f407_init(
set_devboard_v1_stm32f407_t *port, CAN_HandleTypeDef *can,
uint8_t device_type, uint8_t device_id,
pcan_modbus_read_fn read, pcan_modbus_write_fn write, void *register_user,
set_devboard_v1_unhandled_fn unhandled, void *unhandled_user)
{
if ((port == NULL) || (can == NULL)) {
return false;
}
port->can = can;
port->unhandled = unhandled;
port->unhandled_user = unhandled_user;
port->rx_frames = 0U;
port->tx_frames = 0U;
port->tx_errors = 0U;
return pcan_modbus_server_init(&port->server, device_type, device_id,
read, write, register_user);
}
size_t set_devboard_v1_stm32f407_poll(set_devboard_v1_stm32f407_t *port)
{
size_t handled = 0U;
if ((port == NULL) || (port->can == NULL)) {
return 0U;
}
while (HAL_CAN_GetRxFifoFillLevel(port->can, CAN_RX_FIFO0) != 0U) {
CAN_RxHeaderTypeDef header;
uint8_t data[8] = {0U};
pcan_frame_t request = {0};
pcan_frame_t response;
pcan_modbus_result_t result;
if (HAL_CAN_GetRxMessage(port->can, CAN_RX_FIFO0, &header, data) != HAL_OK) {
break;
}
port->rx_frames++;
request.id = (header.IDE == CAN_ID_EXT) ? header.ExtId : header.StdId;
request.flags = (header.IDE == CAN_ID_EXT) ? PCAN_FLAG_IDE : 0U;
if (header.RTR == CAN_RTR_REMOTE) {
request.flags |= PCAN_FLAG_RTR;
}
request.dlc = (header.DLC > 8U) ? 8U : (uint8_t)header.DLC;
for (uint8_t index = 0U; index < request.dlc; ++index) {
request.data[index] = data[index];
}
result = pcan_modbus_server_handle(&port->server, &request, &response);
if (result == PCAN_MODBUS_RESPONSE) {
(void)transmit(port, &response);
handled++;
} else if ((result == PCAN_MODBUS_NOT_FOR_US) &&
(port->unhandled != NULL)) {
port->unhandled(port->unhandled_user, &header, data);
}
}
return handled;
}

View File

@@ -0,0 +1,38 @@
/** STM32F407 bxCAN port for SETGUI through CAN485 DevBoard_V1. */
#ifndef SET_DEVBOARD_V1_STM32F407_H
#define SET_DEVBOARD_V1_STM32F407_H
#include "stm32f4xx_hal.h"
#include "pcan_modbus_server.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef void (*set_devboard_v1_unhandled_fn)(void *user,
const CAN_RxHeaderTypeDef *header,
const uint8_t data[8]);
typedef struct {
CAN_HandleTypeDef *can;
pcan_modbus_server_t server;
set_devboard_v1_unhandled_fn unhandled;
void *unhandled_user;
uint32_t rx_frames;
uint32_t tx_frames;
uint32_t tx_errors;
} set_devboard_v1_stm32f407_t;
bool set_devboard_v1_stm32f407_init(
set_devboard_v1_stm32f407_t *port, CAN_HandleTypeDef *can,
uint8_t device_type, uint8_t device_id,
pcan_modbus_read_fn read, pcan_modbus_write_fn write, void *register_user,
set_devboard_v1_unhandled_fn unhandled, void *unhandled_user);
size_t set_devboard_v1_stm32f407_poll(set_devboard_v1_stm32f407_t *port);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -4,8 +4,6 @@
#include "pcan_crc.h"
#include "balsam_can.h"
#include "periph28335.h"
#include "tms2812.h"
#include "pcan_frame.h"
#include "pcan_id.h"
#include "gui_frame.h"
@@ -90,113 +88,6 @@ size_t pcan_abi_balsam_register_name(uint8_t device, uint16_t address,
return balsam_can_register_name(device, address, output, output_size);
}
uint16_t pcan_abi_periph28335_crc16(const uint8_t *data, size_t size)
{
return periph28335_crc16_modbus(data, size);
}
size_t pcan_abi_periph28335_append_crc(
const uint8_t *payload, size_t payload_size,
uint8_t *output, size_t output_size)
{
return periph28335_append_crc(payload, payload_size, output, output_size);
}
size_t pcan_abi_periph28335_build_read(
uint8_t controller, uint16_t start, uint16_t count,
uint8_t *output, size_t output_size)
{
return periph28335_build_read_registers(
controller, start, count, output, output_size);
}
size_t pcan_abi_periph28335_build_write(
uint8_t controller, uint16_t address, uint16_t value,
uint8_t *output, size_t output_size)
{
return periph28335_build_write_register(
controller, address, value, output, output_size);
}
size_t pcan_abi_periph28335_build_command(
uint8_t controller, uint8_t command_index,
uint8_t *output, size_t output_size)
{
return periph28335_build_command(
controller, command_index, output, output_size);
}
size_t pcan_abi_periph28335_expected_read_size(uint16_t count)
{
return periph28335_expected_read_response_size(count);
}
int pcan_abi_periph28335_decode_read(
const uint8_t *data, size_t size, uint8_t controller, uint16_t count,
uint16_t *output, size_t output_count)
{
return periph28335_decode_read_response(
data, size, controller, count, output, output_count);
}
int pcan_abi_periph28335_validate_write(
const uint8_t *response, size_t response_size,
const uint8_t *request, size_t request_size)
{
return periph28335_validate_write_response(
response, response_size, request, request_size);
}
size_t pcan_abi_periph28335_project_count(void)
{
return periph28335_project_count();
}
const char *pcan_abi_periph28335_project_name(size_t project_index)
{
return periph28335_project_name(project_index);
}
const char *pcan_abi_periph28335_command_name(
size_t project_index, size_t command_index)
{
return periph28335_command_name(project_index, command_index);
}
uint16_t pcan_abi_tms2812_crc16(const uint8_t *data, size_t size)
{
return tms2812_crc16(data, size);
}
size_t pcan_abi_tms2812_build_upload(
uint8_t controller, uint32_t word_address, uint32_t byte_count,
uint8_t *output, size_t output_size)
{
return tms2812_build_upload_request(
controller, word_address, byte_count, output, output_size);
}
size_t pcan_abi_tms2812_expected_upload_size(uint32_t byte_count)
{
return tms2812_expected_upload_response_size(byte_count);
}
int pcan_abi_tms2812_validate_upload(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count)
{
return tms2812_validate_upload_response(
data, size, controller, byte_count);
}
int pcan_abi_tms2812_decode_upload(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count, uint8_t *output, size_t output_size)
{
return tms2812_decode_upload_response(
data, size, controller, byte_count, output, output_size);
}
size_t pcan_abi_frame_encode(uint8_t sequence, uint8_t flags,
uint32_t can_id, const uint8_t *data, uint8_t dlc,
uint8_t *output, size_t output_size)

View File

@@ -0,0 +1,144 @@
#include "pcan_modbus_server.h"
#include <stddef.h>
#include "pcan_id.h"
static bool is_bank(uint8_t value)
{
return (value >= (uint8_t)PCAN_MODBUS_COIL) &&
(value <= (uint8_t)PCAN_MODBUS_INPUT);
}
bool pcan_modbus_server_init(pcan_modbus_server_t *server,
uint8_t device_type, uint8_t device_id,
pcan_modbus_read_fn read,
pcan_modbus_write_fn write, void *user)
{
if ((server == NULL) || (read == NULL) || (device_type > 7U) ||
(device_id > 15U)) {
return false;
}
server->device_type = device_type;
server->device_id = device_id;
server->read = read;
server->write = write;
server->user = user;
server->requests = 0U;
server->responses = 0U;
server->rejected = 0U;
return true;
}
static pcan_modbus_result_t reject(pcan_modbus_server_t *server)
{
server->rejected++;
return PCAN_MODBUS_HANDLED_NO_RESPONSE;
}
pcan_modbus_result_t pcan_modbus_server_handle(pcan_modbus_server_t *server,
const pcan_frame_t *request,
pcan_frame_t *response)
{
pcan_id_t id;
uint16_t address;
uint8_t count;
pcan_modbus_bank_t bank;
if ((server == NULL) || (request == NULL) || (response == NULL) ||
((request->flags & PCAN_FLAG_IDE) == 0U)) {
return PCAN_MODBUS_NOT_FOR_US;
}
pcan_id_unpack(request->id, &id);
if ((id.route != PCAN_ROUTE_FROM_PM) ||
(id.device_type != server->device_type) ||
(id.device_id != server->device_id) || !is_bank(id.msg_type)) {
return PCAN_MODBUS_NOT_FOR_US;
}
server->requests++;
address = (uint16_t)(id.msg_body >> 4U);
count = (uint8_t)(id.msg_body & 0x0FU);
bank = (pcan_modbus_bank_t)id.msg_type;
if (count == 0U) {
return reject(server);
}
*response = *request;
id.route = PCAN_ROUTE_FROM_DEVICE;
response->id = pcan_id_pack(&id);
response->flags = PCAN_FLAG_IDE;
response->seq = 0U;
if (request->dlc == 0U) {
uint16_t packed = 0U;
if ((bank == PCAN_MODBUS_COIL) || (bank == PCAN_MODBUS_DISCRETE)) {
if (count > 16U) {
return reject(server);
}
for (uint8_t index = 0U; index < count; ++index) {
uint16_t value = 0U;
if (!server->read(server->user, bank,
(uint16_t)(address + index), &value)) {
return reject(server);
}
if (value != 0U) {
packed |= (uint16_t)(1U << index);
}
}
response->dlc = 2U;
response->data[0] = (uint8_t)packed;
response->data[1] = (uint8_t)(packed >> 8U);
} else {
if (count > 4U) {
return reject(server);
}
response->dlc = (uint8_t)(count * 2U);
for (uint8_t index = 0U; index < count; ++index) {
uint16_t value = 0U;
if (!server->read(server->user, bank,
(uint16_t)(address + index), &value)) {
return reject(server);
}
response->data[index * 2U] = (uint8_t)value;
response->data[(index * 2U) + 1U] = (uint8_t)(value >> 8U);
}
}
} else {
if ((bank == PCAN_MODBUS_INPUT) || (bank == PCAN_MODBUS_DISCRETE) ||
(server->write == NULL)) {
return reject(server);
}
if (bank == PCAN_MODBUS_COIL) {
uint16_t bits;
if ((count > 16U) || (request->dlc != 2U)) {
return reject(server);
}
bits = (uint16_t)((uint16_t)request->data[0] |
((uint16_t)request->data[1] << 8U));
for (uint8_t index = 0U; index < count; ++index) {
if (!server->write(server->user, bank,
(uint16_t)(address + index),
(uint16_t)((bits >> index) & 1U))) {
return reject(server);
}
}
} else {
if ((count > 4U) || (request->dlc != (uint8_t)(count * 2U))) {
return reject(server);
}
for (uint8_t index = 0U; index < count; ++index) {
uint16_t value = (uint16_t)(request->data[index * 2U] |
((uint16_t)request->data[(index * 2U) + 1U] << 8U));
if (!server->write(server->user, bank,
(uint16_t)(address + index), value)) {
return reject(server);
}
}
}
response->dlc = request->dlc;
}
server->responses++;
return PCAN_MODBUS_RESPONSE;
}

View File

@@ -1,161 +0,0 @@
#include "periph28335.h"
#include <string.h>
#include "set_crc.h"
typedef struct {
const char *project;
const char *commands[PERIPH28335_COMMAND_COUNT];
} periph28335_project_t;
static const periph28335_project_t projects[] = {
{ "По умолчанию", { "Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Light", "Raw", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "23470", { "Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "-", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "23550", { "Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "Send", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "23550.2", { "Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Send", "Raw", "Beep", "", "", "", "", "Log", "Reset", "Nothing at all" } },
{ "ICE 22220.1-3", { "Test", "Zero", "Save", "Def", "Calibr", "Read", "ExtLamp", "ExtLite", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "ICE 22220.4-5", { "Test", "Def", "Save", "Load", "Raw", "Read", "ExtLamp", "ExtLite", "No log", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "Бальзам 161", { "Test", "Zero", "Save", "Def", "Calibr", "Clbr 400", "Stop", "Start", "Init", "Secret", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "Бальзам 162", { "Test", "Def", "Save", "Load", "Calibr", "Secret", "Stop", "Start", "Init", "Tune", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "Бальзам 163", { "Test", "Def", "Save", "Load", "Calibr", "Calcul", "Stop", "Start", "Init", "Tune", "Secret", "-", "-", "-", "-", "Reset", "Nothing at all" } }
};
static void put_be16(uint8_t *output, uint16_t value)
{
output[0] = (uint8_t)(value >> 8);
output[1] = (uint8_t)value;
}
static uint16_t get_be16(const uint8_t *input)
{
return (uint16_t)(((uint16_t)input[0] << 8) | input[1]);
}
uint16_t periph28335_crc16_modbus(const uint8_t *data, size_t size)
{
return set_crc16_modbus(data, size);
}
size_t periph28335_append_crc(const uint8_t *payload, size_t payload_size,
uint8_t *output, size_t output_size)
{
uint16_t crc;
if ((output == NULL) || ((payload == NULL) && (payload_size != 0U)) ||
(payload_size > output_size) || (output_size - payload_size < 2U)) {
return 0U;
}
if (payload_size != 0U) memcpy(output, payload, payload_size);
crc = periph28335_crc16_modbus(payload, payload_size);
output[payload_size] = (uint8_t)crc;
output[payload_size + 1U] = (uint8_t)(crc >> 8);
return payload_size + 2U;
}
size_t periph28335_build_read_registers(
uint8_t controller, uint16_t start, uint16_t count,
uint8_t *output, size_t output_size)
{
uint8_t payload[6];
if ((count == 0U) || (count > PERIPH28335_REGISTER_COUNT) ||
(start >= PERIPH28335_REGISTER_COUNT) ||
((uint32_t)start + count > PERIPH28335_REGISTER_COUNT)) return 0U;
payload[0] = controller;
payload[1] = 3U;
put_be16(&payload[2], start);
put_be16(&payload[4], count);
return periph28335_append_crc(payload, sizeof payload, output, output_size);
}
size_t periph28335_build_write_register(
uint8_t controller, uint16_t address, uint16_t value,
uint8_t *output, size_t output_size)
{
uint8_t payload[6];
if (address >= PERIPH28335_REGISTER_COUNT) return 0U;
payload[0] = controller;
payload[1] = 6U;
put_be16(&payload[2], address);
put_be16(&payload[4], value);
return periph28335_append_crc(payload, sizeof payload, output, output_size);
}
size_t periph28335_build_command(
uint8_t controller, uint8_t command_index,
uint8_t *output, size_t output_size)
{
uint16_t value;
if (command_index >= PERIPH28335_COMMAND_COUNT) return 0U;
value = command_index < 16U ? (uint16_t)(1UL << command_index) : 0U;
return periph28335_build_write_register(
controller, PERIPH28335_REGISTER_COUNT - 1U, value,
output, output_size);
}
size_t periph28335_expected_read_response_size(uint16_t count)
{
return (count >= 1U && count <= PERIPH28335_REGISTER_COUNT)
? (size_t)count * 2U + 5U : 0U;
}
int periph28335_decode_read_response(
const uint8_t *data, size_t size, uint8_t controller, uint16_t count,
uint16_t *output, size_t output_count)
{
size_t expected = periph28335_expected_read_response_size(count);
uint16_t crc;
size_t index;
if ((data == NULL) || (output == NULL)) return PERIPH28335_ERROR_ARGUMENT;
if (expected == 0U) return PERIPH28335_ERROR_RANGE;
if (size != expected) return PERIPH28335_ERROR_LENGTH;
if (output_count < count) return PERIPH28335_ERROR_CAPACITY;
crc = periph28335_crc16_modbus(data, size - 2U);
if ((data[size - 2U] != (uint8_t)crc) ||
(data[size - 1U] != (uint8_t)(crc >> 8))) {
return PERIPH28335_ERROR_CRC;
}
if ((data[0] != controller) || (data[1] != 3U) ||
(data[2] != (uint8_t)(count * 2U))) {
return PERIPH28335_ERROR_HEADER;
}
for (index = 0U; index < count; ++index) {
output[index] = get_be16(&data[3U + index * 2U]);
}
return PERIPH28335_OK;
}
int periph28335_validate_write_response(
const uint8_t *response, size_t response_size,
const uint8_t *request, size_t request_size)
{
uint16_t crc;
if ((response == NULL) || (request == NULL)) return PERIPH28335_ERROR_ARGUMENT;
if ((response_size != PERIPH28335_REQUEST_SIZE) ||
(request_size != PERIPH28335_REQUEST_SIZE)) return PERIPH28335_ERROR_LENGTH;
crc = periph28335_crc16_modbus(response, response_size - 2U);
if ((response[response_size - 2U] != (uint8_t)crc) ||
(response[response_size - 1U] != (uint8_t)(crc >> 8))) {
return PERIPH28335_ERROR_CRC;
}
return memcmp(response, request, PERIPH28335_REQUEST_SIZE) == 0
? PERIPH28335_OK : PERIPH28335_ERROR_HEADER;
}
size_t periph28335_project_count(void)
{
return sizeof projects / sizeof projects[0];
}
const char *periph28335_project_name(size_t project_index)
{
return project_index < periph28335_project_count()
? projects[project_index].project : NULL;
}
const char *periph28335_command_name(size_t project_index,
size_t command_index)
{
return project_index < periph28335_project_count() &&
command_index < PERIPH28335_COMMAND_COUNT
? projects[project_index].commands[command_index] : NULL;
}

View File

@@ -1,18 +0,0 @@
#include "set_crc.h"
uint16_t set_crc16_modbus(const uint8_t *data, size_t size)
{
uint16_t crc = 0xFFFFU;
size_t index;
uint8_t bit;
if ((data == NULL) && (size != 0U)) return 0U;
for (index = 0U; index < size; ++index) {
crc ^= data[index];
for (bit = 0U; bit < 8U; ++bit) {
crc = (crc & 1U) != 0U
? (uint16_t)((crc >> 1) ^ 0xA001U)
: (uint16_t)(crc >> 1);
}
}
return crc;
}

View File

@@ -11,10 +11,10 @@ static int finite_values(const double *v, size_t n) {
uint32_t set_plot_abi_version(void) { return 1U; }
size_t set_plot_eval(uint32_t op, const double *v, size_t n, double *out, size_t cap) {
static const size_t sizes[] = {10, 3, 4, 4, 6, 2, 3, 2, 4};
static const size_t sizes[] = {10, 3, 4, 4, 6, 2, 3, 2};
double span, fraction;
if (op > SET_PLOT_LIMITS || !v || !out || n != sizes[op] ||
cap < (op == SET_PLOT_TRANSFORM || op == SET_PLOT_LIMITS ? 4U : 1U)) return 0;
if (op > SET_PLOT_DB_DELTA || !v || !out || n != sizes[op] ||
cap < (op == SET_PLOT_TRANSFORM ? 4U : 1U)) return 0;
if (op == SET_PLOT_TRANSFORM) {
double w, h, fx, fy;
if (!finite_values(v, 4) || v[2] < 1.0/128 || v[2] > 1 ||
@@ -69,10 +69,6 @@ size_t set_plot_eval(uint32_t op, const double *v, size_t n, double *out, size_t
if (v[0] == 0 || v[1] == 0) return 0;
out[0] = 20 * log10(fabs(v[1] / v[0]));
break;
case SET_PLOT_LIMITS:
if (v[0] >= v[1] || v[2] >= v[3]) return 0;
out[0] = v[0]; out[1] = v[1]; out[2] = v[2]; out[3] = v[3];
return 4;
default: return 0;
}
return isfinite(out[0]) ? 1 : 0;

View File

@@ -4,13 +4,6 @@
#define PI 3.14159265358979323846
static int compare_double(const void *left, const void *right)
{
const double a = *(const double *)left;
const double b = *(const double *)right;
return (a > b) - (a < b);
}
static double window_value(int window, size_t index, size_t n)
{
double phase = 2.0 * PI * (double)index / (double)n;
@@ -131,35 +124,3 @@ int set_spectrum_analyze(const double *times, const double *values, size_t count
free(scratch);
return result;
}
int set_spectrum_dominant_peak(const double *amplitudes, size_t count,
double bin_hz, double relative_threshold, double absolute_floor,
double *scratch, size_t scratch_capacity, double *peak, size_t peak_capacity)
{
size_t i, usable = 0U, peak_bin = 0U;
double peak_amplitude = -1.0, median, threshold;
if (amplitudes == NULL || scratch == NULL || peak == NULL || count < 3U ||
scratch_capacity < count - 1U || peak_capacity < 2U || !isfinite(bin_hz) ||
bin_hz <= 0.0 || !isfinite(relative_threshold) || relative_threshold <= 0.0 ||
!isfinite(absolute_floor) || absolute_floor < 0.0) return -1;
for (i = 1U; i < count; ++i) {
if (isfinite(amplitudes[i]) && amplitudes[i] >= 0.0)
scratch[usable++] = amplitudes[i];
}
if (usable < 2U) return 0;
qsort(scratch, usable, sizeof(double), compare_double);
median = scratch[usable / 2U];
for (i = 1U; i + 1U < count; ++i) {
const double value = amplitudes[i];
if (isfinite(value) && isfinite(amplitudes[i - 1U]) && isfinite(amplitudes[i + 1U]) &&
value >= amplitudes[i - 1U] && value > amplitudes[i + 1U] && value > peak_amplitude) {
peak_bin = i;
peak_amplitude = value;
}
}
threshold = fmax(absolute_floor, median * relative_threshold);
if (peak_bin == 0U || peak_amplitude < threshold) return 0;
peak[0] = (double)peak_bin * bin_hz;
peak[1] = peak_amplitude;
return 1;
}

View File

@@ -6,12 +6,6 @@ static uint16_t get16(const uint8_t *p)
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static uint32_t get32(const uint8_t *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static void put16(uint8_t *p, uint16_t value)
{
p[0] = (uint8_t)value;
@@ -69,12 +63,6 @@ int set_trend_watch_ack(const uint8_t *payload, size_t size, uint16_t period_ms,
}
int set_trend_watch_values(const uint8_t *payload, size_t size, uint16_t *words, size_t capacity)
{
return set_trend_watch_decode(payload, size, NULL, words, capacity);
}
int set_trend_watch_decode(const uint8_t *payload, size_t size, uint32_t *timestamp_ms,
uint16_t *words, size_t capacity)
{
size_t i, count;
if (payload == NULL || size < 6U) return -1;
@@ -82,6 +70,5 @@ int set_trend_watch_decode(const uint8_t *payload, size_t size, uint32_t *timest
if (count > SET_TREND_WATCH_MAX || size != 6U + count * 2U || count > capacity ||
(count > 0U && words == NULL)) return -1;
for (i = 0U; i < count; ++i) words[i] = get16(payload + 6U + i * 2U);
if (timestamp_ms != NULL) *timestamp_ms = get32(payload);
return (int)count;
}

View File

@@ -1,78 +0,0 @@
#include "tms2812.h"
#include <stdint.h>
#include <string.h>
#include "set_crc.h"
static void put_le32(uint8_t *output, uint32_t value)
{
output[0] = (uint8_t)value;
output[1] = (uint8_t)(value >> 8);
output[2] = (uint8_t)(value >> 16);
output[3] = (uint8_t)(value >> 24);
}
uint16_t tms2812_crc16(const uint8_t *data, size_t size)
{
return set_crc16_modbus(data, size);
}
size_t tms2812_build_upload_request(
uint8_t controller, uint32_t word_address, uint32_t byte_count,
uint8_t *output, size_t output_size)
{
uint16_t crc;
if ((output == NULL) || (output_size < TMS2812_UPLOAD_REQUEST_SIZE) ||
(byte_count == 0U)) return 0U;
output[0] = controller;
output[1] = TMS2812_CMD_UPLOAD;
put_le32(&output[2], word_address);
put_le32(&output[6], byte_count);
crc = tms2812_crc16(output, 10U);
output[10] = (uint8_t)crc;
output[11] = (uint8_t)(crc >> 8);
return TMS2812_UPLOAD_REQUEST_SIZE;
}
size_t tms2812_expected_upload_response_size(uint32_t byte_count)
{
if (byte_count == 0U ||
(uint64_t)byte_count + TMS2812_UPLOAD_RESPONSE_OVERHEAD > SIZE_MAX) {
return 0U;
}
return (size_t)byte_count + TMS2812_UPLOAD_RESPONSE_OVERHEAD;
}
int tms2812_validate_upload_response(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count)
{
size_t expected = tms2812_expected_upload_response_size(byte_count);
size_t crc_offset;
uint16_t expected_crc;
uint16_t actual_crc;
if (data == NULL) return TMS2812_ERROR_ARGUMENT;
if (expected == 0U) return TMS2812_ERROR_RANGE;
if (size != expected) return TMS2812_ERROR_LENGTH;
if ((data[0] != controller) || (data[1] != TMS2812_CMD_UPLOAD))
return TMS2812_ERROR_HEADER;
crc_offset = (size_t)byte_count + 2U;
expected_crc = tms2812_crc16(data, crc_offset);
actual_crc = (uint16_t)(data[crc_offset] |
((uint16_t)data[crc_offset + 1U] << 8));
return actual_crc == expected_crc ? TMS2812_OK : TMS2812_ERROR_CRC;
}
int tms2812_decode_upload_response(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count, uint8_t *output, size_t output_size)
{
int status = tms2812_validate_upload_response(
data, size, controller, byte_count);
if (status != TMS2812_OK) return status;
if ((output == NULL) || (output_size < byte_count))
return TMS2812_ERROR_CAPACITY;
memcpy(output, &data[2], byte_count);
return TMS2812_OK;
}

View File

@@ -23,8 +23,6 @@
{"name":"db_gain","op":7,"input":[1,10],"output":[20]},
{"name":"db_attenuation","op":7,"input":[10,1],"output":[-20]},
{"name":"db_zero_reference","op":7,"input":[0,1],"output":null},
{"name":"absolute_limits","op":8,"input":[0,500,-2,2],"output":[0,500,-2,2]},
{"name":"reversed_x_limits","op":8,"input":[1,1,-2,2],"output":null},
{"name":"zero_range","op":2,"input":[1,1,1,0],"output":null},
{"name":"zero_pixels","op":4,"input":[0,1,0,0,1,0],"output":null},
{"name":"bad_viewport","op":0,"input":[0,0,0,1,2,1,0,0,0.5,0.5],"output":null}

View File

@@ -0,0 +1,90 @@
#include <stdio.h>
#include <string.h>
#include "pcan_id.h"
#include "pcan_modbus_server.h"
static uint16_t holding[32];
static uint16_t input[32];
static uint16_t coils;
static bool read_word(void *user, pcan_modbus_bank_t bank,
uint16_t address, uint16_t *value)
{
(void)user;
if (address >= 32U) return false;
if (bank == PCAN_MODBUS_HOLDING) *value = holding[address];
else if (bank == PCAN_MODBUS_INPUT) *value = input[address];
else if (bank == PCAN_MODBUS_COIL) *value = (uint16_t)((coils >> address) & 1U);
else return false;
return true;
}
static bool write_word(void *user, pcan_modbus_bank_t bank,
uint16_t address, uint16_t value)
{
(void)user;
if (address >= 32U) return false;
if (bank == PCAN_MODBUS_HOLDING) holding[address] = value;
else if (bank == PCAN_MODBUS_COIL && address < 16U) {
if (value) coils |= (uint16_t)(1U << address);
else coils &= (uint16_t)~(1U << address);
} else return false;
return true;
}
static pcan_frame_t request(uint8_t type, uint16_t address, uint8_t count)
{
pcan_id_t id = {0};
pcan_frame_t frame = {0};
id.priority = PCAN_PRIORITY_STANDARD;
id.route = PCAN_ROUTE_FROM_PM;
id.device_type = 7U;
id.device_id = 3U;
id.msg_type = type;
id.msg_body = pcan_body_modbus(address, count);
frame.id = pcan_id_pack(&id);
frame.flags = PCAN_FLAG_IDE;
return frame;
}
#define CHECK(x) do { if (!(x)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #x); return 1; \
} } while (0)
int main(void)
{
pcan_modbus_server_t server;
pcan_frame_t req;
pcan_frame_t rsp;
pcan_id_t id;
memset(holding, 0, sizeof(holding));
memset(input, 0, sizeof(input));
input[10] = 0x1234U;
input[11] = 0xABCDU;
CHECK(pcan_modbus_server_init(&server, 7U, 3U, read_word, write_word, NULL));
req = request(PCAN_MODBUS_INPUT, 10U, 2U);
CHECK(pcan_modbus_server_handle(&server, &req, &rsp) == PCAN_MODBUS_RESPONSE);
CHECK(rsp.dlc == 4U && rsp.data[0] == 0x34U && rsp.data[1] == 0x12U);
CHECK(rsp.data[2] == 0xCDU && rsp.data[3] == 0xABU);
pcan_id_unpack(rsp.id, &id);
CHECK(id.route == PCAN_ROUTE_FROM_DEVICE);
req = request(PCAN_MODBUS_HOLDING, 3U, 2U);
req.dlc = 4U;
req.data[0] = 0x22U; req.data[1] = 0x11U;
req.data[2] = 0x44U; req.data[3] = 0x33U;
CHECK(pcan_modbus_server_handle(&server, &req, &rsp) == PCAN_MODBUS_RESPONSE);
CHECK(holding[3] == 0x1122U && holding[4] == 0x3344U);
req = request(PCAN_MODBUS_COIL, 2U, 3U);
req.dlc = 2U; req.data[0] = 0x05U; req.data[1] = 0U;
CHECK(pcan_modbus_server_handle(&server, &req, &rsp) == PCAN_MODBUS_RESPONSE);
CHECK(coils == 0x0014U);
req = request(PCAN_MODBUS_INPUT, 0U, 5U);
CHECK(pcan_modbus_server_handle(&server, &req, &rsp) == PCAN_MODBUS_HANDLED_NO_RESPONSE);
CHECK(server.requests == 4U && server.responses == 3U && server.rejected == 1U);
return 0;
}

View File

@@ -1,32 +0,0 @@
#include <stdio.h>
#include <string.h>
#include "periph28335.h"
int main(void)
{
uint8_t request[PERIPH28335_REQUEST_SIZE];
static const uint8_t read_prefix[] = { 0x10U, 0x03U, 0x00U, 0x18U, 0x00U, 0x40U };
static const uint8_t response_payload[] = { 0x10U, 0x03U, 0x04U, 0x80U, 0x05U, 0x12U, 0x34U };
uint8_t response[sizeof response_payload + 2U];
uint16_t words[2];
if (periph28335_build_read_registers(16U, 24U, 64U, request, sizeof request)
!= sizeof request || memcmp(request, read_prefix, sizeof read_prefix) != 0) return 1;
if (periph28335_build_write_register(16U, 7U, 0x1234U, request, sizeof request)
!= sizeof request || memcmp(request, "\x10\x06\x00\x07\x12\x34", 6U) != 0) return 2;
if (periph28335_build_command(16U, 15U, request, sizeof request)
!= sizeof request || memcmp(request, "\x10\x06\x00\x7F\x80\x00", 6U) != 0) return 3;
if (periph28335_append_crc(response_payload, sizeof response_payload,
response, sizeof response) != sizeof response) return 4;
if (periph28335_decode_read_response(response, sizeof response, 16U, 2U,
words, 2U) != PERIPH28335_OK || words[0] != 0x8005U || words[1] != 0x1234U) return 5;
response[0] = 17U;
if (periph28335_decode_read_response(response, sizeof response, 16U, 2U,
words, 2U) != PERIPH28335_ERROR_CRC) return 6;
if (periph28335_project_count() != 9U ||
strcmp(periph28335_project_name(2U), "23550") != 0 ||
strcmp(periph28335_command_name(2U, 7U), "Send") != 0) return 7;
puts("PM35/TMS320F28335 protocol tests passed");
return 0;
}

View File

@@ -26,13 +26,6 @@ int main(void) {
assert(output[0] == y[0]);
assert(set_plot_eval(SET_PLOT_DRAG, drag, 6, output, 4) == 1);
assert(output[0] == -4);
{
const double limits[] = {0, 500, -2, 2};
const double reversed[] = {1, 1, -2, 2};
assert(set_plot_eval(SET_PLOT_LIMITS, limits, 4, output, 4) == 4);
assert(output[0] == 0 && output[1] == 500 && output[2] == -2 && output[3] == 2);
assert(set_plot_eval(SET_PLOT_LIMITS, reversed, 4, output, 4) == 0);
}
puts("shared plot: OK");
return 0;
}

Some files were not shown because too many files have changed in this diff Show More