Добавить протокол Altera Logic и общие клиенты прошивки

This commit is contained in:
2026-09-19 07:12:09 +03:00
parent def3eb08f3
commit 80ba17d77d
38 changed files with 3271 additions and 10 deletions

View File

@@ -1,11 +1,144 @@
# PORTING
# Портирование firmware-info
Аппаратно-зависимый контракт ограничен `firmware_info_config.h`:
`FIRMWARE_VERSION_MAJOR`, `FIRMWARE_VERSION_MINOR`, `FIRMWARE_VERSION_PATCH` и
необязательный `FIRMWARE_BUILD_ID`. Выберите порт по семейству МК или создайте
его копию. Публикация — ответственность транспорта: 12 `uint16_t` для Modbus
либо 24 little-endian байта для SETGUI.
## Назначение и границы
При переносе проверьте: поддержку `__DATE__`/`__TIME__`, наличие generated в
include path, запуск генератора до компиляции и декодирование build ID как двух
ASCII-байтов в каждом логическом слове.
Это C-библиотека описания работающего образа: SemVer, дата/время компиляции,
8 символов build ID и сериализация контракта v1. Она не обращается к сети,
не читает версию из сервера и не записывает Flash. HAL, RTOS, UART и CAN
ядру не нужны. Отправку результата выполняет приложение.
Для размещения `.hex/.bin` в каталоге SETGUI уже существуют
[`setprotocol.firmware_publish`](../../python/setprotocol/firmware_publish.py)
и [`tools/firmware-publish`](../../tools/firmware-publish/README.md).
Связь компонентов и перенос публикации описаны в
[`tools/firmware-publish/PORTING.md`](../../tools/firmware-publish/PORTING.md).
## 1. Файлы и конфигурация
Пример ниже предполагает `lib/templates` внутри нового проекта:
```text
project/
inc/firmware_info_config.h
generated/firmware_build_id.h # создаётся до сборки
lib/templates/c/firmware-info/
src/
```
Добавьте в сборку оба файла:
```text
lib/templates/c/firmware-info/src/firmware_info.c
lib/templates/c/firmware-info/src/firmware_info_port.c
```
В include path добавьте `inc`, `generated` и
`lib/templates/c/firmware-info/include`. Встроенный `CMakeLists.txt` собирает
только ядро и host-тест; `firmware_info_port.c` и каталоги конфигурации нужно
добавлять к целевому firmware target самостоятельно.
Скопируйте `ports/<mcu>/firmware_info_config.template.h` как
`inc/firmware_info_config.h`. Есть варианты STM32F1/F4/G4 и К1921ВК028;
они не содержат регистров МК. Для другого МК с обычными 8-битными байтами
достаточно такого же config:
```c
#ifndef FIRMWARE_INFO_CONFIG_H
#define FIRMWARE_INFO_CONFIG_H
#define FIRMWARE_VERSION_MAJOR 1U
#define FIRMWARE_VERSION_MINOR 2U
#define FIRMWARE_VERSION_PATCH 3U
#include "firmware_build_id.h"
#endif
```
Обязательный include в этом примере позволяет обнаружить пропущенный pre-build.
В готовых шаблонах include опциональный через `__has_include`; если заголовок
не подключён, порт использует `LOCALDEV`. Для компилятора без `__has_include`
используйте явный include. Можно связать три версии с существующими макросами
проекта, как это сделано в `KONOR_ds18b20/inc/firmware_info_config.h`.
## 2. Build ID
Из корня нового проекта перед компиляцией запустите:
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File "lib/templates/c/firmware-info/tools/make_build_id.ps1" -Repository "." -Output "generated/firmware_build_id.h"
```
Указывайте `-Repository` и `-Output` явно: расположение сабмодуля и рабочий
каталог IDE различаются между проектами. `-Repository` должен указывать на
исходники прошивки, а не на репозиторий `templates`. Для Keil из каталога `mdk`
соответственно используйте `..\lib\templates\...`, `-Repository ".."` и
`-Output ".\Generated\firmware_build_id.h"`.
При доступном Git чистый checkout получает 8 знаков commit, изменения tracked
файлов — 7 знаков и `+`. Если commit определить нельзя, используется `NOGIT000`.
Новые untracked-файлы текущий генератор при определении dirty не учитывает.
Дата/время берутся из `__DATE__`/`__TIME__` при компиляции `firmware_info_port.c`;
для выпуска выполняйте полный Rebuild, чтобы не оставить старый объектный файл.
## 3. Обработчик запроса версии
Пример функции подготовки полезной нагрузки, вызываемой вашим обработчиком:
```c
#include "firmware_info_port.h"
firmware_info_status_t app_make_firmware_info(uint8_t *payload, size_t capacity)
{
firmware_info_t info;
firmware_info_status_t status = firmware_info_port_describe(&info);
if (status != FIRMWARE_INFO_OK) return status;
return firmware_info_to_le_bytes(&info, payload, capacity);
}
```
При успехе передайте ровно `FIRMWARE_INFO_PAYLOAD_SIZE` (24) байта в собственный
транспорт. При ошибке верните ошибку протокола, а не содержимое буфера.
Не отправляйте `sizeof(firmware_info_t)`: структура не является wire format.
В KONOR обработчик `app_send_firmware_info()` отвечает на
`PROTO_MSG_FIRMWARE_INFO = 0x03`, сохраняя sequence запроса. Старый 32-байтовый
`DEVICE_INFO` остаётся отдельным сообщением. Для нового протокола сначала
согласуйте команду с клиентом: подключение библиотеки само по себе не добавит
декодер и отображение версии в GUI.
Для Modbus вызовите `firmware_info_to_registers()` с массивом из 12 `uint16_t`
и разместите его в выбранной карте регистров. Адреса и Modbus-порядок байтов
обеспечивает ваш Modbus-стек; LE-буфер в Modbus напрямую не копируйте.
## 4. Контракт v1
| Индекс слова | Содержимое |
|---|---|
| 0 | Версия контракта: 1 |
| 1, 2, 3 | major, minor, patch |
| 4 | Год |
| 5 | `(month << 8) \| day` |
| 6 | `(hour << 8) \| minute` |
| 7 | Секунды |
| 811 | По два ASCII-символа build ID: первый в старшем байте слова |
В LE-представлении младший байт каждого слова идёт первым. Например, build ID
`ab` в начале строки даёт слово `0x6162`, но байты `62 61`; декодируйте сначала
слово, затем символы из старшего/младшего байта. NUL-терминатор не передаётся.
Текущая проверка допускает major/minor до 255 и patch до 999. Если в каталоге
используется `(major << 16) | (minor << 8) | patch`, ограничьте **все три** части
до 255, иначе значения пересекутся. Версия каталога автоматически из C-config
не извлекается.
Для C2000 с 16-битным `char` нельзя считать готовым байтовый порт: отдельно
проверьте наличие `uint8_t` и представление октетов в транспорте. Публикация
TMS SCI8-файла с ПК поддерживается независимо от переноса этой C-библиотеки.
## 5. Проверка переноса
1. Соберите host-тест из `tests/test_firmware_info.c` вместе с ядром либо
используйте CMake/CTest. Он проверяет дату, регистры и порядок байтов build ID.
2. Соберите целевую прошивку с обоими `.c` и сгенерированным заголовком.
3. Запросите версию у устройства: сравните SemVer, build ID, время и 24-байтовый
ответ с конкретной сборкой. Проверьте, что прежний `DEVICE_INFO` не изменился.
4. Подключите выпуск по инструкции публикатора; сравните версию в config и
`firmware-release.cmd` перед Rebuild и `--preflight`.

View File

@@ -25,3 +25,7 @@ config, а не дублирования ядра.
KONOR публикует эти 24 байта ответом `FIRMWARE_INFO (0x03)`, сохраняя старый
32-байтовый `DEVICE_INFO` без изменений.
Подробная инструкция с кодом обработчика, форматом ответа и проверкой переноса:
[`PORTING.md`](PORTING.md). Для размещения файла прошивки в каталоге SETGUI
используется отдельный [общий публикатор](../../tools/firmware-publish/PORTING.md).

View File

@@ -0,0 +1,93 @@
# Altera Logic: общее ядро и порты
Потоковый SETCAN/GAS-профиль и общие порты описаны отдельно:
[Altera Logic ONLINE](../../doc/setcan/ALTERA_LOGIC_STREAM.md).
Ниже — совместимый прежний буферный протокол UART `A5 CMD`.
Клиент UART-протокола FPGA-анализатора Cyclone IV: 16 каналов, 4096 выборок.
Работает с существующим RTL `altera_loganalisator`, версия протокола 1.
Ядро C99 не зависит от Qt, ОС, системных часов и динамической памяти.
Слои: SETGUI → Python ctypes → C99 → Python Qt-порт → COM / USBUART.
GUI передаёт числовые настройки и получает готовый массив; формат пакетов,
XOR, endian, последовательность команд, парсер и состояния тайм-аутов находятся в C.
| Файл | Назначение и зависимости |
|---|---|
| `include/altera_logic.h` | Публичный ABI, макрос экспорта из `pcan_abi.h` |
| `src/altera_logic.c` | Кодек и автомат обмена; только стандартная библиотека C |
| `tests/test_altera_logic.c` | Эталонные пакеты RTL, полный захват, ошибки |
| `../../python/altera_logic/native.py` | ctypes-модели, загрузка DLL, экспорт CSV; stdlib |
| `../../python/altera_logic/qt_port.py` | Асинхронный COM-порт; PySide6 QtSerialPort |
## Сборка
Из корня `templates`:
```powershell
python c/set-protocol/tools/build_host.py --output python/altera_logic/native/setprotocol.dll
```
На Linux выходной файл — `python/altera_logic/native/libsetprotocol.so`.
Также поддерживается основная CMake-сборка `c/set-protocol`; путь к результату
задаётся через `ALTERA_LOGIC_LIBRARY`. Библиотека без символов `la_*`
не поддерживается: тихого Python-fallback нет.
## Контракт порта
Выделить выровненную память размером `la_context_size()`; все обращения к
одному контексту выполняются в одном потоке.
- `la_init(ctx)` — начать с INFO.
- `la_next(ctx, out, capacity)` — получить следующий запрос, либо 0, если отправлять нечего.
- `la_feed(ctx, bytes, size)` — передать принятые байты, включая частичные ответы.
- `la_tick(ctx, elapsed_ms)` — сообщить прошедшее время; тайм-аут ответа 1000 мс.
- `la_start(ctx, divider, mask, value, edge_mask, edge_value)` — настройка и новый захват.
- `la_get(ctx, field)` — состояние и прогресс; `la_samples` — завершённая запись.
- `la_fail(ctx, LA_IO_ERROR)` — сообщить ошибку транспорта.
COM: 921600 бод, 8N1, без управления потоком. Одновременно ожидается только
один ответ. После ARM STATUS опрашивается каждые 25 мс; ожидание самого
триггера не ограничено. После done читаются 64 блока по 64 выборки.
Ошибки закрывают сеанс без автоматического повтора ARM. Неверный размер,
код команды или XOR прекращает сеанс. При рассинхронизации запроса на FPGA
может потребоваться аппаратный RESET_N; переподключение не гарантирует её сброс.
Клиент намеренно принимает только профиль 16 × 4096 версии 1.
Поддержка внешней SDRAM и расширенных адресов сюда пока не входит.
Прерывание в GUI закрывает порт ПК: STOP в протоколе FPGA отсутствует.
## Быстрый старт без платы
Добавить `templates/python` в PYTHONPATH:
```python
from altera_logic import NativeAnalyzer
client = NativeAnalyzer()
client.reset(demo=True)
client.start(divider=49)
capture = client.capture()
print(len(capture.samples)) # 4096
print(capture.sample_rate) # 1_000_000
print(capture.trigger_index) # 2048
capture.save_csv("demo.csv")
```
Демо создаёт синтетическую запись в C; не моделирует заданные условия триггера.
CSV содержит явный признак demo, время относительно триггера и состояния D0…D15.
## Потребители и проверка
Используется вкладкой SETGUI «Altera Logic». Для Android/JNI новый потребитель
может использовать тот же ABI; интерфейс Android в этой задаче не изменяется.
Существующий ABI SETProtocol не изменён, добавлены только символы `la_*`.
```powershell
$env:PYTHONPATH = "$PWD/python"
python -m unittest discover -s python/tests -p test_altera_logic.py
```
CMake включает `test_altera_logic` и CTest `altera_logic`.
На плате требуется отдельно проверить разводку пинов, RAM-модуль и прошивку;
программные проверки не заменяют аппаратную проверку.

View File

@@ -6,6 +6,8 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
# SET protocol v2: управление, телеметрия, CAN segmentation и firmware flow.
set(SETPROTOCOL_V2_SOURCES
src/altera_logic.c
src/altera_stream.c
src/set_protocol.c
src/set_can.c
src/set_firmware.c
@@ -79,6 +81,12 @@ endif()
option(SETP_BUILD_TESTS "Build host tests" ON)
if(SETP_BUILD_TESTS)
enable_testing()
add_executable(test_altera_logic tests/test_altera_logic.c)
target_link_libraries(test_altera_logic PRIVATE setprotocol_static)
add_test(NAME altera_logic COMMAND test_altera_logic)
add_executable(test_altera_stream tests/test_altera_stream.c)
target_link_libraries(test_altera_stream PRIVATE setprotocol_static)
add_test(NAME altera_stream COMMAND test_altera_stream)
add_executable(test_plot tests/test_plot.c)
target_link_libraries(test_plot PRIVATE setprotocol_static)

View File

@@ -0,0 +1,35 @@
/** Altera logic analyzer UART client. C99, caller-owned memory, no OS/heap.
* One request at a time; no automatic retries (ARM is not idempotent).
* Initialize aligned storage of la_context_size() bytes, then call la_next,
* la_feed and la_tick from one thread. tick receives elapsed milliseconds.
*/
#ifndef ALTERA_LOGIC_H
#define ALTERA_LOGIC_H
#include "pcan_abi.h"
#ifdef __cplusplus
extern "C" {
#endif
enum { LA_CONNECTING, LA_READY, LA_CAPTURING, LA_DONE, LA_ERROR };
enum { LA_OK, LA_BAD_FRAME, LA_CHECKSUM, LA_DEVICE_ERROR, LA_TIMEOUT,
LA_UNSUPPORTED, LA_ARGUMENT, LA_IO_ERROR };
enum { LA_STATE, LA_ERROR_CODE, LA_CHANNELS, LA_DEPTH, LA_CLOCK_HZ,
LA_FLAGS, LA_TRIGGER_INDEX, LA_READ_COUNT, LA_DIVIDER };
PCAN_ABI_API size_t la_context_size(void);
PCAN_ABI_API void la_init(void *ctx);
/** Return 0 on success. Configuration is immutable throughout this capture. */
PCAN_ABI_API int la_start(void *ctx, uint32_t divider, uint32_t mask,
uint32_t value, uint32_t edge_mask, uint32_t edge_value);
/** Returns 6 when a request is ready, otherwise 0. Capacity must be >=6. */
PCAN_ABI_API size_t la_next(void *ctx, uint8_t *out, size_t capacity);
PCAN_ABI_API void la_feed(void *ctx, const uint8_t *data, size_t size);
PCAN_ABI_API void la_tick(void *ctx, uint32_t elapsed_ms);
PCAN_ABI_API void la_fail(void *ctx, uint32_t code);
PCAN_ABI_API uint32_t la_get(const void *ctx, uint32_t field);
PCAN_ABI_API size_t la_samples(const void *ctx, uint16_t *out, size_t capacity);
/** Offline fixture, never communicates with hardware or claims a real trigger. */
PCAN_ABI_API void la_demo_init(void *ctx);
PCAN_ABI_API int la_demo_capture(void *ctx, uint32_t divider);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,46 @@
/** SETCAN/GAS Altera Logic online stream, shared by CAN and UART.
* Caller-owned context; one serialized caller. No heap, OS or Qt dependencies.
*/
#ifndef ALTERA_STREAM_H
#define ALTERA_STREAM_H
#include "pcan_abi.h"
#include "pcan_frame.h"
#ifdef __cplusplus
extern "C" {
#endif
#define LAS_DEVICE_TYPE 6U
#define LAS_DEVICE_ID 14U
#define LAS_GAS_META 0xFE00U
#define LAS_GAS_DATA 0xFF00U
#define LAS_CAPACITY 8192U
#define LAS_TAG 0x4C01U
enum { LAS_COUNT, LAS_PERIOD_NS, LAS_SESSION, LAS_RECEIVED, LAS_MISSING,
LAS_DUPLICATES, LAS_INVALID, LAS_IGNORED, LAS_CRC_ERRORS, LAS_HAS_META };
PCAN_ABI_API size_t las_context_size(void);
PCAN_ABI_API int las_init(void *ctx, uint32_t device_id);
PCAN_ABI_API void las_can(void *ctx, uint32_t id, const uint8_t *data, size_t size,
uint32_t extended, uint32_t remote);
PCAN_ABI_API void las_uart(void *ctx, const uint8_t *data, size_t size);
PCAN_ABI_API uint32_t las_get(const void *ctx, uint32_t field);
/** Chronological bounded history. breaks[i]=1 marks a discontinuity before i. */
PCAN_ABI_API size_t las_snapshot(const void *ctx, uint64_t *indices, uint16_t *samples,
uint8_t *breaks, size_t capacity);
/** Sparse step trace for rendering; capacity >= 2*LAS_COUNT, move=pen-up. */
PCAN_ABI_API size_t las_trace(const void *ctx, uint32_t channel, uint64_t *indices,
uint8_t *levels, uint8_t *moves, size_t capacity);
PCAN_ABI_API uint32_t las_device_type(void);
PCAN_ABI_API uint32_t las_device_id(void);
PCAN_ABI_API const char *las_device_name(void);
PCAN_ABI_API void las_demo_step(void *ctx, uint32_t count);
/** Device-side packet builders; return zero for invalid arguments.
* uart=0: 8-byte CAN data + *id; uart=1: complete AA55 transport + *id.
* Same builders are used by firmware ports, tests and the demo producer.
*/
PCAN_ABI_API size_t las_metadata(uint32_t device, uint32_t session, uint32_t period_ns,
uint32_t uart, uint32_t *id, uint8_t *out, size_t capacity);
PCAN_ABI_API size_t las_data(uint32_t device, uint32_t session, uint32_t index,
uint32_t sample, uint32_t uart, uint32_t *id, uint8_t *out, size_t capacity);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -0,0 +1,142 @@
#include "altera_logic.h"
#include <string.h>
typedef struct {
uint32_t state, error, clock_hz, elapsed, delay;
uint16_t config[5], channels, depth, trigger, count, offset;
uint16_t samples[4096];
uint8_t command, pending, flags, requested, rx[133];
size_t used;
} la_context;
static uint8_t checksum(const uint8_t *p, size_t n) {
uint8_t x = 0; size_t i;
for (i = 0; i < n; ++i) x ^= p[i];
return x;
}
static uint16_t u16(const uint8_t *p) {
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
size_t la_context_size(void) { return sizeof(la_context); }
void la_init(void *ctx) {
la_context *s = (la_context *)ctx;
memset(s, 0, sizeof(*s)); s->command = 1; s->state = LA_CONNECTING;
}
void la_fail(void *ctx, uint32_t code) {
la_context *s = (la_context *)ctx;
s->state = LA_ERROR; s->error = code; s->pending = 0; s->command = 0;
}
int la_start(void *ctx, uint32_t div, uint32_t mask, uint32_t val,
uint32_t emask, uint32_t eval) {
la_context *s = (la_context *)ctx;
if ((s->state != LA_READY && s->state != LA_DONE) ||
div > 65535 || mask > 65535 || val > 65535 ||
emask > 65535 || eval > 65535 || ((val ^ eval) & mask & emask))
return LA_ARGUMENT;
s->config[0]=(uint16_t)div; s->config[1]=(uint16_t)mask;
s->config[2]=(uint16_t)val; s->config[3]=(uint16_t)emask;
s->config[4]=(uint16_t)eval;
s->state=LA_CAPTURING; s->error=0; s->flags=0; s->count=0;
s->offset=0; s->command=2; s->delay=0;
return LA_OK;
}
size_t la_next(void *ctx, uint8_t *out, size_t capacity) {
la_context *s = (la_context *)ctx; uint16_t arg = 0;
if (!out || capacity < 6 || !s->command || s->pending || s->delay) return 0;
if (s->command >= 2 && s->command <= 6) arg=s->config[s->command-2];
if (s->command == 9) {
arg=s->offset;
s->requested=(uint8_t)((s->depth-s->offset > 64) ? 64 : s->depth-s->offset);
}
out[0]=0xa5; out[1]=s->command; out[2]=(uint8_t)arg;
out[3]=(uint8_t)(arg >> 8); out[4]=(s->command == 9) ? s->requested : 0;
out[5]=checksum(out,5);
s->pending=1; s->elapsed=0; s->used=0;
return 6;
}
static void complete(la_context *s) {
size_t i;
if (checksum(s->rx,s->used)) { la_fail(s,LA_CHECKSUM); return; }
if (s->rx[2]) { la_fail(s,LA_DEVICE_ERROR); return; }
s->pending=0;
if (s->command == 1) {
s->channels=s->rx[3]; s->depth=u16(s->rx+4);
s->clock_hz=(uint32_t)s->rx[6]*1000000U;
if (s->channels != 16 || s->depth != 4096 || !s->clock_hz || s->rx[7]!=1) {
la_fail(s,LA_UNSUPPORTED); return;
}
s->state=LA_READY; s->command=0;
} else if (s->command < 7) ++s->command;
else if (s->command == 7) { s->command=8; s->delay=25; }
else if (s->command == 8) {
s->flags=s->rx[3]; s->trigger=u16(s->rx+4);
if (s->flags & 4) {
if (s->trigger >= s->depth || (s->flags & 1)) { la_fail(s,LA_BAD_FRAME); return; }
s->command=9;
} else s->delay=25;
} else {
for(i=0; i<s->requested; ++i) s->samples[s->offset+i]=u16(s->rx+4+i*2);
s->offset=(uint16_t)(s->offset+s->requested); s->count=s->offset;
if(s->offset == s->depth) { s->state=LA_DONE; s->command=0; }
}
}
void la_feed(void *ctx, const uint8_t *data, size_t size) {
la_context *s = (la_context *)ctx; size_t i, expected;
if (!data) return;
for(i=0; i<size; ++i) {
if(!s->pending || s->used >= sizeof(s->rx)) { la_fail(s,LA_BAD_FRAME); return; }
s->rx[s->used++]=data[i];
if(s->rx[0]!=0x5a || (s->used>=2 && s->rx[1]!=(s->command|0x80))) {
la_fail(s,LA_BAD_FRAME); return;
}
if(s->used<3) continue;
expected=4;
if(!s->rx[2]) {
if(s->command==1) expected=9;
else if(s->command==8) expected=7;
else if(s->command==9) {
if(s->used<4) continue;
if(s->rx[3]!=s->requested) { la_fail(s,LA_BAD_FRAME); return; }
expected=5U+2U*s->requested;
}
}
if(s->used==expected) complete(s);
}
}
void la_tick(void *ctx, uint32_t ms) {
la_context *s=(la_context *)ctx;
if(s->pending) {
if(ms >= 1000U-s->elapsed) la_fail(s,LA_TIMEOUT);
else s->elapsed+=ms;
}
s->delay=(ms >= s->delay) ? 0 : s->delay-ms;
}
uint32_t la_get(const void *ctx, uint32_t field) {
const la_context *s=(const la_context *)ctx;
switch(field) {
case LA_STATE:return s->state; case LA_ERROR_CODE:return s->error;
case LA_CHANNELS:return s->channels; case LA_DEPTH:return s->depth;
case LA_CLOCK_HZ:return s->clock_hz; case LA_FLAGS:return s->flags;
case LA_TRIGGER_INDEX:return s->trigger; case LA_READ_COUNT:return s->count;
case LA_DIVIDER:return s->config[0]; default:return 0;
}
}
size_t la_samples(const void *ctx, uint16_t *out, size_t capacity) {
const la_context *s=(const la_context *)ctx;
if(s->state!=LA_DONE || !out || capacity<s->count) return 0;
memcpy(out,s->samples,s->count*sizeof(uint16_t)); return s->count;
}
void la_demo_init(void *ctx) {
la_context *s=(la_context *)ctx; la_init(s);
s->channels=16; s->depth=4096; s->clock_hz=50000000;
s->state=LA_READY; s->command=0;
}
int la_demo_capture(void *ctx, uint32_t divider) {
la_context *s=(la_context *)ctx; size_t i, bit;
int rc=la_start(s,divider,0,0,0,0); if(rc) return rc;
for(i=0;i<4096;++i) {
uint16_t v=0;
for(bit=0;bit<16;++bit) if(((i/(4U+bit*7U))&1U)!=0) v|=(uint16_t)(1U<<bit);
s->samples[i]=v;
}
s->trigger=2048; s->flags=6; s->count=4096; s->command=0; s->state=LA_DONE;
return LA_OK;
}

View File

@@ -0,0 +1,158 @@
#include "altera_stream.h"
#include "pcan_id.h"
#include <string.h>
typedef struct {
pcan_parser_t parser;
uint64_t indices[LAS_CAPACITY], next_index;
uint16_t samples[LAS_CAPACITY];
uint8_t breaks[LAS_CAPACITY];
uint32_t head, count, period, session, received, missing, duplicates, invalid, ignored;
uint8_t device, metadata, started;
} las_context;
static uint16_t r16(const uint8_t *p) { return (uint16_t)(p[0]|((uint16_t)p[1]<<8)); }
static uint32_t r32(const uint8_t *p) { return (uint32_t)r16(p)|((uint32_t)r16(p+2)<<16); }
static void w16(uint8_t *p,uint16_t v) { p[0]=(uint8_t)v;p[1]=(uint8_t)(v>>8); }
static void w32(uint8_t *p,uint32_t v) { w16(p,(uint16_t)v);w16(p+2,(uint16_t)(v>>16)); }
uint32_t las_device_type(void) { return LAS_DEVICE_TYPE; }
uint32_t las_device_id(void) { return LAS_DEVICE_ID; }
const char *las_device_name(void) { return "Altera Logic"; }
size_t las_context_size(void) { return sizeof(las_context); }
int las_init(void *ctx,uint32_t device) {
las_context *s=(las_context *)ctx;
if(!ctx || device>15) return 0;
memset(s,0,sizeof(*s));s->device=(uint8_t)device;pcan_parser_init(&s->parser);return 1;
}
static void append(las_context *s,uint64_t index,uint16_t value,uint8_t gap) {
s->indices[s->head]=index;s->samples[s->head]=value;s->breaks[s->head]=gap;
s->head=(s->head+1U)%LAS_CAPACITY;
if(s->count<LAS_CAPACITY) ++s->count;
}
void las_can(void *ctx,uint32_t raw,const uint8_t *p,size_t n,uint32_t extended,uint32_t remote) {
las_context *s=(las_context *)ctx;pcan_id_t id;uint32_t index,delta,period,session;
if(!extended || remote || raw>PCAN_ID_MASK) { ++s->ignored;return; }
pcan_id_unpack(raw,&id);
if(id.device_type!=LAS_DEVICE_TYPE || id.device_id!=s->device ||
id.route!=PCAN_ROUTE_FROM_DEVICE || id.msg_type!=PCAN_MSG_GAS ||
(id.msg_body!=LAS_GAS_META && id.msg_body!=LAS_GAS_DATA)) { ++s->ignored;return; }
if(n!=8 || !p) { ++s->invalid;return; }
if(id.msg_body==LAS_GAS_META) {
period=r32(p+4);session=r16(p+2);
if(r16(p)!=LAS_TAG || period==0) { ++s->invalid;return; }
if(s->metadata && session==s->session) {
if(period!=s->period) ++s->invalid;
return;
}
if(s->metadata && (uint16_t)(session-s->session)>=0x8000U) {
++s->duplicates;return;
}
s->metadata=1;s->session=session;s->period=period;
s->count=0;s->head=0;s->started=0;s->next_index=0;
return;
}
if(!s->metadata) { ++s->ignored;return; }
if(r16(p)!=s->session) { ++s->ignored;return; }
index=r32(p+4);
delta=0;
if(s->started) {
delta=index-(uint32_t)s->next_index;
if(delta>=0x80000000U) { ++s->duplicates;return; }
s->missing+=delta;
} else s->next_index=index;
s->next_index+=delta;
append(s,s->next_index,r16(p+2),(uint8_t)(!s->started || delta!=0));
s->next_index+=1;s->started=1;++s->received;
}
static void frame_callback(const pcan_frame_t *frame,void *user) {
if(frame->flags & (PCAN_FLAG_DIR|PCAN_FLAG_ERR)) {
++((las_context *)user)->ignored;return;
}
las_can(user,frame->id,frame->data,frame->dlc,
frame->flags&PCAN_FLAG_IDE,frame->flags&PCAN_FLAG_RTR);
}
void las_uart(void *ctx,const uint8_t *data,size_t size) {
las_context *s=(las_context *)ctx;
if(data) pcan_parser_feed(&s->parser,data,size,frame_callback,s);
}
uint32_t las_get(const void *ctx,uint32_t field) {
const las_context *s=(const las_context *)ctx;
switch(field) {
case LAS_COUNT:return s->count;case LAS_PERIOD_NS:return s->period;
case LAS_SESSION:return s->session;case LAS_RECEIVED:return s->received;
case LAS_MISSING:return s->missing;case LAS_DUPLICATES:return s->duplicates;
case LAS_INVALID:return s->invalid;case LAS_IGNORED:return s->ignored;
case LAS_CRC_ERRORS:return s->parser.stats.crc_errors;
case LAS_HAS_META:return s->metadata;default:return 0;
}
}
size_t las_snapshot(const void *ctx,uint64_t *indices,uint16_t *samples,uint8_t *breaks,size_t cap) {
const las_context *s=(const las_context *)ctx;uint32_t i,pos;
if(!indices || !samples || !breaks || cap<s->count) return 0;
pos=(s->head+LAS_CAPACITY-s->count)%LAS_CAPACITY;
for(i=0;i<s->count;++i) {
indices[i]=s->indices[pos];samples[i]=s->samples[pos];breaks[i]=s->breaks[pos];
pos=(pos+1U)%LAS_CAPACITY;
}
return s->count;
}
size_t las_trace(const void *ctx,uint32_t channel,uint64_t *indices,uint8_t *levels,
uint8_t *moves,size_t cap) {
const las_context *s=(const las_context *)ctx;uint32_t i,pos;
uint64_t previous_x=0;uint8_t previous_y=0;size_t n=0;
if(channel>=16 || !indices || !levels || !moves || cap<2U*s->count) return 0;
pos=(s->head+LAS_CAPACITY-s->count)%LAS_CAPACITY;
for(i=0;i<s->count;++i) {
uint64_t x=s->indices[pos];uint8_t y=(uint8_t)((s->samples[pos]>>channel)&1U);
if(!i) { indices[n]=x;levels[n]=y;moves[n++]=1; }
else if(s->breaks[pos]) {
indices[n]=previous_x;levels[n]=previous_y;moves[n++]=0;
indices[n]=x;levels[n]=y;moves[n++]=1;
} else if(y!=previous_y) {
indices[n]=x;levels[n]=previous_y;moves[n++]=0;
indices[n]=x;levels[n]=y;moves[n++]=0;
}
previous_x=x;previous_y=y;pos=(pos+1U)%LAS_CAPACITY;
}
if(s->count>1) { indices[n]=previous_x;levels[n]=previous_y;moves[n++]=0; }
return n;
}
static size_t packet(uint32_t device,uint16_t body,const uint8_t *data,uint32_t uart,
uint32_t *raw,uint8_t *out,size_t cap) {
pcan_id_t id;pcan_frame_t frame;
if(device>15 || !raw || !out || cap<(uart?PCAN_FRAME_MAX:8U)) return 0;
memset(&id,0,sizeof(id));id.device_type=LAS_DEVICE_TYPE;id.device_id=(uint8_t)device;
id.priority=PCAN_PRIORITY_STANDARD;id.route=PCAN_ROUTE_FROM_DEVICE;
id.msg_type=PCAN_MSG_GAS;id.msg_body=body;
memset(&frame,0,sizeof(frame));frame.id=pcan_id_pack(&id);
frame.flags=PCAN_FLAG_IDE;frame.dlc=8;memcpy(frame.data,data,8);*raw=frame.id;
if(uart) return pcan_frame_encode(&frame,out,cap);
memcpy(out,data,8);return 8;
}
size_t las_metadata(uint32_t device,uint32_t session,uint32_t period,uint32_t uart,
uint32_t *id,uint8_t *out,size_t cap) {
uint8_t data[8];if(session>65535 || !period) return 0;
w16(data,LAS_TAG);w16(data+2,(uint16_t)session);w32(data+4,period);
return packet(device,LAS_GAS_META,data,uart,id,out,cap);
}
size_t las_data(uint32_t device,uint32_t session,uint32_t index,uint32_t sample,uint32_t uart,
uint32_t *id,uint8_t *out,size_t cap) {
uint8_t data[8];if(session>65535 || sample>65535) return 0;
w16(data,(uint16_t)session);w16(data+2,(uint16_t)sample);w32(data+4,index);
return packet(device,LAS_GAS_DATA,data,uart,id,out,cap);
}
void las_demo_step(void *ctx,uint32_t count) {
las_context *s=(las_context *)ctx;uint32_t i,bit,idx,raw;uint8_t packet_data[PCAN_FRAME_MAX];size_t n;
if(!s->metadata) {
n=las_metadata(s->device,1,1000000,1,&raw,packet_data,sizeof(packet_data));
las_uart(s,packet_data,n);
}
if(count>4096) count=4096;
for(i=0;i<count;++i) {
uint16_t a=0;idx=(uint32_t)s->next_index;
for(bit=0;bit<16;++bit) {
if((idx/(5U+bit*7U))&1U) a|=(uint16_t)(1U<<bit);
}
n=las_data(s->device,s->session,idx,a,1,&raw,packet_data,sizeof(packet_data));
las_uart(s,packet_data,n);
}
}

View File

@@ -0,0 +1,72 @@
#include "altera_logic.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
static void response(void *s, uint8_t *data, size_t n) {
size_t i; uint8_t sum=0;
for(i=0;i<n-1;++i) sum^=data[i]; data[n-1]=sum;
/* Serial reads can split at every byte. */
for(i=0;i<n;++i) la_feed(s,data+i,1);
}
static void connect_ok(void *s) {
uint8_t p[6], info[]={0x5a,0x81,0,16,0,16,50,1,0xe8};
const uint8_t request[]={0xa5,1,0,0,0,0xa4};
la_init(s); assert(la_next(s,p,5)==0);
assert(la_next(s,p,6)==6 && !memcmp(p,request,6));
assert(la_next(s,p,6)==0);
response(s,info,sizeof(info)); assert(la_get(s,LA_STATE)==LA_READY);
}
int main(void) {
void *s=calloc(1,la_context_size()); uint8_t p[6], ack[4], status[7], block[133];
uint16_t samples[4096]; unsigned int cmd, offset, i;
assert(s); connect_ok(s);
assert(la_start(s,65536,0,0,0,0)==LA_ARGUMENT);
assert(la_start(s,49,1,0,1,1)==LA_ARGUMENT);
assert(la_start(s,49,0,0,1,1)==0);
for(cmd=2;cmd<=7;++cmd) {
assert(la_next(s,p,6)==6 && p[1]==cmd);
if(cmd==2) { const uint8_t v[]={0xa5,2,49,0,0,0x96}; assert(!memcmp(p,v,6)); }
ack[0]=0x5a; ack[1]=(uint8_t)(cmd|0x80); ack[2]=0; response(s,ack,4);
}
assert(la_next(s,p,6)==0); la_tick(s,25);
assert(la_next(s,p,6)==6 && p[1]==8);
{ uint8_t waiting[]={0x5a,0x88,0,1,0,8,0}; response(s,waiting,7); }
la_tick(s,60000); /* Waiting for a physical trigger is not a UART timeout. */
assert(la_get(s,LA_STATE)==LA_CAPTURING);
assert(la_next(s,p,6)==6 && p[1]==8);
status[0]=0x5a; status[1]=0x88; status[2]=0; status[3]=6;
status[4]=0;status[5]=8;response(s,status,7);
for(offset=0;offset<4096;offset+=64) {
assert(la_next(s,p,6)==6 && p[1]==9 && p[4]==64);
assert((unsigned int)(p[2]|(p[3]<<8))==offset);
block[0]=0x5a;block[1]=0x89;block[2]=0;block[3]=64;
for(i=0;i<64;++i) { block[4+2*i]=(uint8_t)(offset+i);block[5+2*i]=(uint8_t)((offset+i)>>8); }
response(s,block,133);
}
assert(la_get(s,LA_STATE)==LA_DONE);
assert(la_samples(s,samples,4095)==0);
assert(la_samples(s,samples,4096)==4096);
for(i=0;i<4096;++i) assert(samples[i]==i);
assert(la_get(s,LA_TRIGGER_INDEX)==2048);
/* Corrupt XOR, wrong command, device error, timeout and extra data. */
la_init(s);la_next(s,p,6);
{ uint8_t bad[]={0x5a,0x81,0,16,0,16,50,1,0};la_feed(s,bad,9); }
assert(la_get(s,LA_ERROR_CODE)==LA_CHECKSUM);
la_init(s);la_next(s,p,6);
{ uint8_t bad[]={0x5a,0x88};la_feed(s,bad,2); }
assert(la_get(s,LA_ERROR_CODE)==LA_BAD_FRAME);
la_init(s);la_next(s,p,6);
{ uint8_t bad[]={0x5a,0x81,1,0};response(s,bad,4); }
assert(la_get(s,LA_ERROR_CODE)==LA_DEVICE_ERROR);
la_init(s);la_next(s,p,6);la_tick(s,999);
assert(la_get(s,LA_STATE)==LA_CONNECTING);la_tick(s,1);
assert(la_get(s,LA_ERROR_CODE)==LA_TIMEOUT && la_next(s,p,6)==0);
connect_ok(s);la_feed(s,p,1);assert(la_get(s,LA_ERROR_CODE)==LA_BAD_FRAME);
la_init(s);la_next(s,p,6);
{ uint8_t bad[]={0x5a,0x81,0,8,0,16,50,1,0};response(s,bad,9); }
assert(la_get(s,LA_ERROR_CODE)==LA_UNSUPPORTED);
la_demo_init(s);assert(la_demo_capture(s,49)==0);
assert(la_get(s,LA_STATE)==LA_DONE && la_samples(s,samples,4096)==4096);
free(s);puts("Altera Logic C tests passed");return 0;
}

View File

@@ -0,0 +1,62 @@
#include "altera_stream.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
static void meta(void *s,uint32_t session,int uart) {
uint8_t data[32];uint32_t id;size_t n,i;
n=las_metadata(LAS_DEVICE_ID,session,1000000,(uint32_t)uart,&id,data,sizeof(data));assert(n);
assert(id==0x1EE3FE00U);
if(uart) for(i=0;i<n;++i) las_uart(s,data+i,1);
else las_can(s,id,data,n,1,0);
}
static void sample(void *s,uint32_t session,uint32_t index,int uart) {
uint8_t data[32];uint32_t id;size_t n;
n=las_data(LAS_DEVICE_ID,session,index,index&65535U,(uint32_t)uart,&id,data,sizeof(data));assert(n);
assert(id==0x1EE3FF00U);
if(uart) las_uart(s,data,n);else las_can(s,id,data,n,1,0);
}
int main(void) {
void *can=malloc(las_context_size()),*uart=malloc(las_context_size());
uint64_t *indices=malloc(LAS_CAPACITY*sizeof(uint64_t));
uint16_t *samples=malloc(LAS_CAPACITY*sizeof(uint16_t));
uint8_t *breaks=malloc(LAS_CAPACITY);uint8_t packet[32];uint32_t id;size_t n,i;
assert(can&&uart&&indices&&samples&&breaks);
assert(las_init(can,14)&&las_init(uart,14));assert(!las_init(can,16));
sample(can,1,0,0);assert(las_get(can,LAS_COUNT)==0);
meta(can,1,0);meta(uart,1,1);
/* Independent golden SETCAN bytes. */
n=las_data(14,1,0x12345678U,0x8001,0,&id,packet,sizeof(packet));
{ const uint8_t v[]={1,0,1,0x80,0x78,0x56,0x34,0x12};assert(n==8&&!memcmp(v,packet,8)); }
for(i=0;i<9000;++i) { sample(can,1,(uint32_t)i,0);sample(uart,1,(uint32_t)i,1); }
assert(las_get(can,LAS_COUNT)==8192&&las_get(uart,LAS_COUNT)==8192);
assert(las_get(can,LAS_RECEIVED)==9000&&las_get(uart,LAS_RECEIVED)==9000);
assert(las_snapshot(can,indices,samples,breaks,LAS_CAPACITY)==8192);
assert(indices[0]==808&&indices[8191]==8999&&samples[8191]==8999);
assert(las_snapshot(uart,indices,samples,breaks,LAS_CAPACITY)==8192);
assert(indices[0]==808&&indices[8191]==8999&&samples[8191]==8999);
sample(can,1,8999,1);assert(las_get(can,LAS_DUPLICATES)==1); /* second transport duplicate */
sample(can,1,9003,0);assert(las_get(can,LAS_MISSING)==3);
las_snapshot(can,indices,samples,breaks,LAS_CAPACITY);assert(breaks[8191]);
/* Repeated metadata must not erase rolling history. */
meta(can,1,1);assert(las_get(can,LAS_COUNT)==8192);
meta(can,2,0);assert(las_get(can,LAS_COUNT)==0);
sample(can,1,9004,1);assert(las_get(can,LAS_COUNT)==0);
meta(can,1,1);assert(las_get(can,LAS_SESSION)==2);
sample(can,2,0xffffffffU,0);sample(can,2,0,1);
las_snapshot(can,indices,samples,breaks,LAS_CAPACITY);
assert(indices[0]==0xffffffffULL&&indices[1]==0x100000000ULL&&!breaks[1]);
/* Reject wrong address, standard ID, RTR and short payload. */
n=las_data(14,2,1,0,0,&id,packet,sizeof(packet));assert(n==8);
las_can(can,id,packet,8,0,0);las_can(can,id,packet,8,1,1);
las_can(can,id^(1U<<20),packet,8,1,0);las_can(can,id,packet,7,1,0);
assert(las_get(can,LAS_COUNT)==2&&las_get(can,LAS_INVALID)==1);
n=las_data(14,2,1,1,1,&id,packet,sizeof(packet));packet[n-1]^=0x80;
las_uart(can,packet,n);assert(las_get(can,LAS_CRC_ERRORS)==1);
sample(can,2,1,1);assert(las_get(can,LAS_COUNT)==3); /* recovery after CRC */
assert(!las_metadata(14,65536,1000,0,&id,packet,sizeof(packet)));
assert(!las_metadata(14,1,0,0,&id,packet,sizeof(packet)));
assert(!las_data(14,1,0,65536,0,&id,packet,sizeof(packet)));
free(can);free(uart);free(indices);free(samples);free(breaks);
puts("Altera SETCAN/CAN/UART stream tests passed");return 0;
}

View File

@@ -20,6 +20,8 @@ INCLUDE = ROOT / "include"
JNI_INCLUDES: list[Path] = []
SOURCES = [
ROOT / "src" / name for name in (
"altera_logic.c",
"altera_stream.c",
"set_protocol.c", "set_can.c", "set_firmware.c", "set_telemetry.c", "set_plot.c", "set_trends.c", "set_spectrum.c",
"balsam_can.c", "set_crc.c", "periph28335.c", "tms2812.c", "gui_catalog.c", "gui_frame.c", "pcan_abi.c", "pcan_crc.c",
"pcan_frame.c", "pcan_id.c", "pcan_link.c", "pcan_ring.c",