Добавить протокол 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

@@ -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",