refactor: merge protocol cores as SETProtocol

This commit is contained in:
2026-09-01 09:58:09 +03:00
parent 5504104cc5
commit 19becd7b8c
56 changed files with 1256 additions and 359 deletions

View File

@@ -0,0 +1,23 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := setprotocol
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../include
LOCAL_SRC_FILES := \
../../src/set_protocol.c \
../../src/set_can.c \
../../src/set_firmware.c \
../../src/set_telemetry.c \
../../src/gui_catalog.c \
../../src/gui_frame.c \
../../src/pcan_abi.c \
../../src/pcan_crc.c \
../../src/pcan_frame.c \
../../src/pcan_id.c \
../../src/pcan_link.c \
../../src/pcan_ring.c \
../../src/pcan_gas.c \
setprotocol_jni.c
LOCAL_CFLAGS := -std=c99 -Wall -Wextra -Wpedantic -fvisibility=hidden
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)

View File

@@ -0,0 +1,10 @@
# Android port
`Android.mk` builds `libsetprotocol.so` from the same C99 sources used by MCU
and desktop builds. `setprotocol_jni.c` contains only JNI marshalling; protocol rules
remain in the core. Add the Kotlin directory as an Android source directory
and include this `Android.mk` from the application NDK build.
The JNI streaming parser returns fixed 15-byte records
`SEQ | FLAGS | CAN_ID_LE | DLC | DATA[8]`. Dynamic allocation is confined to
the Android adapter; the portable core remains allocation free.

View File

@@ -0,0 +1,42 @@
package ru.setcorp.setprotocol
/** Thin Kotlin facade over the shared C99 SETProtocol core. */
object NativeSetProtocol {
val available: Boolean by lazy {
runCatching {
System.loadLibrary("setprotocol")
nativeAbiVersion() == 1
}.getOrDefault(false)
}
external fun nativeAbiVersion(): Int
external fun nativePackId(
priority: Int,
route: Int,
deviceType: Int,
deviceId: Int,
messageType: Int,
body: Int,
): Long
external fun nativeUnpackId(raw: Long): IntArray?
external fun nativeCrc16(input: ByteArray): Int
external fun nativeEncodeFrame(
sequence: Int,
flags: Int,
canId: Long,
input: ByteArray,
): ByteArray?
external fun nativeCreateParser(): Long
external fun nativeDestroyParser(handle: Long)
external fun nativeFeedParser(handle: Long, input: ByteArray): ByteArray?
external fun nativeParserStats(handle: Long): IntArray?
external fun nativeGuiEncode(
messageType: Int,
sequence: Int,
input: ByteArray,
): ByteArray?
external fun nativeCreateGuiParser(): Long
external fun nativeDestroyGuiParser(handle: Long)
external fun nativeFeedGuiParser(handle: Long, input: ByteArray): ByteArray?
external fun nativeGuiParserStats(handle: Long): IntArray?
}

View File

@@ -0,0 +1,317 @@
#include <jni.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "setprotocol_abi.h"
typedef struct {
uint8_t *storage;
size_t storage_size;
int last_sequence;
uint32_t sequence_lost;
} android_parser_t;
typedef struct {
uint8_t *storage;
size_t storage_size;
} android_gui_parser_t;
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeAbiVersion(JNIEnv *env, jobject self)
{
(void)env;
(void)self;
return (jint)pcan_abi_version();
}
JNIEXPORT jlong JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePackId(
JNIEnv *env, jobject self, jint priority, jint route, jint device_type,
jint device_id, jint message_type, jint body)
{
(void)env;
(void)self;
return (jlong)pcan_abi_id_pack((uint8_t)priority, (uint8_t)route,
(uint8_t)device_type, (uint8_t)device_id,
(uint8_t)message_type, (uint16_t)body);
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeUnpackId(
JNIEnv *env, jobject self, jlong raw)
{
(void)self;
uint8_t priority, route, device_type, device_id, message_type;
uint16_t body;
pcan_abi_id_unpack((uint32_t)raw, &priority, &route, &device_type,
&device_id, &message_type, &body);
jint values[6] = { (jint)priority, (jint)route, (jint)device_type,
(jint)device_id, (jint)message_type, (jint)body };
jintArray result = (*env)->NewIntArray(env, 6);
if (result != NULL) {
(*env)->SetIntArrayRegion(env, result, 0, 6, values);
}
return result;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeCrc16(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if (data == NULL) {
return 0;
}
uint16_t crc = pcan_abi_crc16((const uint8_t *)data, (size_t)size);
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return (jint)crc;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeEncodeFrame(
JNIEnv *env, jobject self, jint sequence, jint flags, jlong can_id,
jbyteArray input)
{
(void)self;
jsize size = (*env)->GetArrayLength(env, input);
if (size > 8) {
return NULL;
}
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) {
return NULL;
}
uint8_t output[19];
size_t written = pcan_abi_frame_encode(
(uint8_t)sequence, (uint8_t)flags, (uint32_t)can_id,
(const uint8_t *)data, (uint8_t)size, output, sizeof output);
if (data != NULL) {
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
}
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 jlong JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeCreateParser(JNIEnv *env, jobject self)
{
(void)env;
(void)self;
android_parser_t *parser = (android_parser_t *)calloc(1U, sizeof *parser);
if (parser == NULL) {
return 0;
}
parser->storage_size = pcan_abi_parser_size();
parser->storage = (uint8_t *)malloc(parser->storage_size);
parser->last_sequence = -1;
if ((parser->storage == NULL) ||
!pcan_abi_parser_init(parser->storage, parser->storage_size)) {
free(parser->storage);
free(parser);
return 0;
}
return (jlong)(intptr_t)parser;
}
JNIEXPORT void JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeDestroyParser(
JNIEnv *env, jobject self, jlong handle)
{
(void)env;
(void)self;
android_parser_t *parser = (android_parser_t *)(intptr_t)handle;
if (parser != NULL) {
free(parser->storage);
free(parser);
}
}
/* Each returned record is 15 bytes: seq, flags, id LE, dlc, data[8]. */
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeFeedParser(
JNIEnv *env, jobject self, jlong handle, jbyteArray input)
{
(void)self;
android_parser_t *parser = (android_parser_t *)(intptr_t)handle;
if (parser == NULL) {
return NULL;
}
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) {
return NULL;
}
size_t capacity = ((size_t)size / 11U + 2U) * 15U;
uint8_t *records = (uint8_t *)malloc(capacity);
if (records == NULL) {
if (data != NULL) {
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
}
return NULL;
}
size_t used = 0U;
pcan_abi_frame_t frame;
for (jsize i = 0; i < size; ++i) {
if (pcan_abi_parser_push(parser->storage, (uint8_t)data[i], &frame) > 0) {
uint8_t *record = &records[used];
record[0] = frame.sequence;
record[1] = frame.flags;
record[2] = (uint8_t)frame.can_id;
record[3] = (uint8_t)(frame.can_id >> 8);
record[4] = (uint8_t)(frame.can_id >> 16);
record[5] = (uint8_t)(frame.can_id >> 24);
record[6] = frame.dlc;
memset(&record[7], 0, 8U);
memcpy(&record[7], frame.data, frame.dlc);
used += 15U;
if (parser->last_sequence >= 0) {
parser->sequence_lost += (uint8_t)(
frame.sequence - (uint8_t)parser->last_sequence - 1U);
}
parser->last_sequence = frame.sequence;
}
}
if (data != NULL) {
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
}
jbyteArray result = (*env)->NewByteArray(env, (jsize)used);
if (result != NULL && used != 0U) {
(*env)->SetByteArrayRegion(env, result, 0, (jsize)used,
(const jbyte *)records);
}
free(records);
return result;
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeParserStats(
JNIEnv *env, jobject self, jlong handle)
{
(void)self;
android_parser_t *parser = (android_parser_t *)(intptr_t)handle;
if (parser == NULL) {
return NULL;
}
uint32_t frames = 0U, crc = 0U, bad_len = 0U, stray = 0U;
pcan_abi_parser_stats(parser->storage, &frames, &crc, &bad_len, &stray);
jint values[5] = { (jint)frames, (jint)crc, (jint)bad_len,
(jint)stray, (jint)parser->sequence_lost };
jintArray result = (*env)->NewIntArray(env, 5);
if (result != NULL) {
(*env)->SetIntArrayRegion(env, result, 0, 5, values);
}
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeGuiEncode(
JNIEnv *env, jobject self, jint message_type, jint sequence,
jbyteArray input)
{
(void)self;
jsize size = (*env)->GetArrayLength(env, input);
if (size > (jsize)PCAN_ABI_GUI_PAYLOAD_MAX) return NULL;
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
uint8_t output[PCAN_ABI_GUI_PAYLOAD_MAX + 12U];
size_t written = pcan_abi_gui_frame_encode(
(uint8_t)message_type, (uint16_t)sequence, (const uint8_t *)data,
(uint16_t)size, output, sizeof output);
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
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 jlong JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeCreateGuiParser(JNIEnv *env, jobject self)
{
(void)env; (void)self;
android_gui_parser_t *parser = (android_gui_parser_t *)calloc(1U, sizeof *parser);
if (parser == NULL) return 0;
parser->storage_size = pcan_abi_gui_parser_size();
parser->storage = (uint8_t *)malloc(parser->storage_size);
if ((parser->storage == NULL) ||
!pcan_abi_gui_parser_init(parser->storage, parser->storage_size)) {
free(parser->storage); free(parser); return 0;
}
return (jlong)(intptr_t)parser;
}
JNIEXPORT void JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeDestroyGuiParser(
JNIEnv *env, jobject self, jlong handle)
{
(void)env; (void)self;
android_gui_parser_t *parser = (android_gui_parser_t *)(intptr_t)handle;
if (parser != NULL) { free(parser->storage); free(parser); }
}
/* Records: type[1], sequence LE[2], size LE[2], payload[size]. */
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeFeedGuiParser(
JNIEnv *env, jobject self, jlong handle, jbyteArray input)
{
(void)self;
android_gui_parser_t *parser = (android_gui_parser_t *)(intptr_t)handle;
if (parser == NULL) return NULL;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
size_t capacity = (size_t)size + ((size_t)size / 12U + 2U) * 5U + 512U;
uint8_t *records = (uint8_t *)malloc(capacity);
if (records == NULL) {
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return NULL;
}
size_t used = 0U;
pcan_abi_gui_frame_t frame;
for (jsize i = 0; i < size; ++i) {
if (pcan_abi_gui_parser_push(parser->storage, (uint8_t)data[i], &frame) > 0) {
records[used++] = frame.message_type;
records[used++] = (uint8_t)frame.sequence;
records[used++] = (uint8_t)(frame.sequence >> 8);
records[used++] = (uint8_t)frame.size;
records[used++] = (uint8_t)(frame.size >> 8);
memcpy(&records[used], frame.payload, frame.size);
used += frame.size;
}
}
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
jbyteArray result = (*env)->NewByteArray(env, (jsize)used);
if ((result != NULL) && (used != 0U)) {
(*env)->SetByteArrayRegion(env, result, 0, (jsize)used,
(const jbyte *)records);
}
free(records);
return result;
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeGuiParserStats(
JNIEnv *env, jobject self, jlong handle)
{
(void)self;
android_gui_parser_t *parser = (android_gui_parser_t *)(intptr_t)handle;
if (parser == NULL) return NULL;
uint32_t frames = 0U, crc = 0U, version = 0U, length = 0U, stray = 0U;
pcan_abi_gui_parser_stats(parser->storage, &frames, &crc, &version,
&length, &stray);
jint values[5] = { (jint)frames, (jint)crc, (jint)version,
(jint)length, (jint)stray };
jintArray result = (*env)->NewIntArray(env, 5);
if (result != NULL) (*env)->SetIntArrayRegion(env, result, 0, 5, values);
return result;
}

View File

@@ -0,0 +1,287 @@
#include <stdint.h>
#include "pcan_uart_stm32f4.h"
/* --- Работа с флагами DMA -------------------------------------------------
* У DMA на F4 флаги четырёх потоков лежат в LISR/LIFCR, ещё четырёх -
* в HISR/HIFCR, причём внутри регистра смещения неравномерны.
*/
static const uint8_t s_flag_shift[4] = { 0U, 6U, 16U, 22U };
#define DMA_FLAG_FEIF 0x01U
#define DMA_FLAG_DMEIF 0x04U
#define DMA_FLAG_TEIF 0x08U
#define DMA_FLAG_HTIF 0x10U
#define DMA_FLAG_TCIF 0x20U
#define DMA_FLAG_ALL (DMA_FLAG_FEIF | DMA_FLAG_DMEIF | DMA_FLAG_TEIF | \
DMA_FLAG_HTIF | DMA_FLAG_TCIF)
static inline uint32_t dma_isr(const DMA_TypeDef *dma, uint8_t stream)
{
uint32_t reg = (stream < 4U) ? dma->LISR : dma->HISR;
return (reg >> s_flag_shift[stream & 3U]) & DMA_FLAG_ALL;
}
static inline void dma_clear(DMA_TypeDef *dma, uint8_t stream, uint32_t flags)
{
uint32_t value = (flags & DMA_FLAG_ALL) << s_flag_shift[stream & 3U];
if (stream < 4U) {
dma->LIFCR = value;
} else {
dma->HIFCR = value;
}
}
static inline void dma_disable(DMA_Stream_TypeDef *stream)
{
stream->CR &= ~DMA_SxCR_EN;
/* Аппаратура снимает EN не мгновенно; трогать регистры потока
до этого момента нельзя. */
while ((stream->CR & DMA_SxCR_EN) != 0U) {
}
}
/* --- Управление драйвером RS485 ------------------------------------------- */
static inline void de_assert(const pcan_uart_t *u)
{
if (u->cfg.de_port == NULL) {
return;
}
uint32_t pin = 1UL << u->cfg.de_pin;
u->cfg.de_port->BSRR = u->cfg.de_active_low ? (pin << 16) : pin;
}
static inline void de_release(const pcan_uart_t *u)
{
if (u->cfg.de_port == NULL) {
return;
}
uint32_t pin = 1UL << u->cfg.de_pin;
u->cfg.de_port->BSRR = u->cfg.de_active_low ? pin : (pin << 16);
}
/* --- Передача -------------------------------------------------------------
* Кольцо заворачивается, а DMA нужен непрерывный участок, поэтому за раз
* отдаём кусок до конца буфера. Остаток уйдёт следующей посылкой.
*/
static void tx_start(pcan_uart_t *u)
{
if (u->tx_busy) {
return;
}
const uint8_t *ptr = NULL;
uint16_t len = pcan_ring_linear(&u->tx, &ptr);
if (len == 0U) {
return;
}
u->tx_busy = 1U;
u->tx_chunk = len;
de_assert(u);
/* Флаг TC мог остаться от прошлой посылки - иначе прерывание придёт
сразу и отпустит DE посреди новой. */
(void)u->cfg.uart->SR;
u->cfg.uart->SR = ~USART_SR_TC;
dma_clear(u->cfg.dma, u->cfg.tx_stream_idx, DMA_FLAG_ALL);
u->cfg.tx_stream->M0AR = (uint32_t)(uintptr_t)ptr;
u->cfg.tx_stream->NDTR = len;
u->cfg.tx_stream->CR |= DMA_SxCR_EN;
}
void pcan_uart_dma_tx_irq(pcan_uart_t *u)
{
uint32_t flags = dma_isr(u->cfg.dma, u->cfg.tx_stream_idx);
if ((flags & (DMA_FLAG_TEIF | DMA_FLAG_DMEIF | DMA_FLAG_FEIF)) != 0U) {
dma_clear(u->cfg.dma, u->cfg.tx_stream_idx, DMA_FLAG_ALL);
dma_disable(u->cfg.tx_stream);
/* Ошибка передачи: кусок считаем отданным, иначе встанет очередь. */
pcan_ring_consume(&u->tx, u->tx_chunk);
u->tx_busy = 0U;
u->uart_errors++;
tx_start(u);
return;
}
if ((flags & DMA_FLAG_TCIF) == 0U) {
return;
}
dma_clear(u->cfg.dma, u->cfg.tx_stream_idx, DMA_FLAG_ALL);
dma_disable(u->cfg.tx_stream);
pcan_ring_consume(&u->tx, u->tx_chunk);
u->tx_chunk = 0U;
u->tx_busy = 0U;
if (!pcan_ring_empty(&u->tx)) {
tx_start(u);
return;
}
/* Данных больше нет. DMA лишь дописал последний байт в DR - в линии он
ещё не весь. Отпускаем DE только по TC. */
if (u->cfg.de_port != NULL) {
u->cfg.uart->CR1 |= USART_CR1_TCIE;
}
}
void pcan_uart_irq(pcan_uart_t *u)
{
uint32_t sr = u->cfg.uart->SR;
if ((sr & (USART_SR_ORE | USART_SR_FE | USART_SR_NE | USART_SR_PE)) != 0U) {
(void)u->cfg.uart->DR; /* чтение SR + DR снимает флаги */
u->uart_errors++;
}
if (((sr & USART_SR_TC) != 0U) &&
((u->cfg.uart->CR1 & USART_CR1_TCIE) != 0U)) {
u->cfg.uart->SR = ~USART_SR_TC;
u->cfg.uart->CR1 &= ~USART_CR1_TCIE;
/* Если пока ждали TC успел встать новый кадр - DE не опускаем. */
if (!u->tx_busy && pcan_ring_empty(&u->tx)) {
de_release(u);
}
}
}
static size_t io_write(void *ctx, const uint8_t *data, size_t len)
{
pcan_uart_t *u = (pcan_uart_t *)ctx;
if (len > 0xFFFFU) {
return 0U;
}
if (!pcan_ring_write(&u->tx, data, (uint16_t)len)) {
return 0U;
}
/* Прерывание DMA может добраться до очереди одновременно с нами,
но tx_busy защищает от двойного запуска потока. */
tx_start(u);
return len;
}
static size_t io_space(void *ctx)
{
pcan_uart_t *u = (pcan_uart_t *)ctx;
return pcan_ring_free(&u->tx);
}
void pcan_uart_io(pcan_uart_t *u, pcan_io_t *io)
{
io->write = io_write;
io->tx_space = io_space;
io->ctx = u;
}
/* --- Приём ---------------------------------------------------------------- */
size_t pcan_uart_read(pcan_uart_t *u, uint8_t *out, size_t max)
{
uint16_t head = (uint16_t)(u->rx_size - (uint16_t)u->cfg.rx_stream->NDTR);
uint16_t tail = u->rx_tail;
if ((head == tail) || (max == 0U)) {
return 0U;
}
uint16_t pending = (uint16_t)((head - tail) & (uint16_t)(u->rx_size - 1U));
if (pending > (uint16_t)(u->rx_size - (u->rx_size / 4U))) {
/* Кольцо почти догнало нас: часть байт, вероятно, уже затёрта.
Точнее аппаратура не скажет - в circular-режиме DMA не
сигнализирует переполнение. */
u->rx_overruns++;
}
size_t n = (pending > max) ? max : pending;
for (size_t i = 0U; i < n; i++) {
out[i] = u->rx_buf[tail];
tail = (uint16_t)((tail + 1U) & (uint16_t)(u->rx_size - 1U));
}
u->rx_tail = tail;
return n;
}
size_t pcan_uart_poll(pcan_uart_t *u, pcan_link_t *link)
{
uint8_t chunk[64];
size_t frames = 0U;
size_t n;
while ((n = pcan_uart_read(u, chunk, sizeof chunk)) != 0U) {
frames += pcan_link_feed(link, chunk, n);
}
return frames;
}
/* --- Инициализация -------------------------------------------------------- */
bool pcan_uart_init(pcan_uart_t *u, const pcan_uart_cfg_t *cfg,
uint8_t *tx_buf, uint16_t tx_size,
uint8_t *rx_buf, uint16_t rx_size)
{
if ((u == NULL) || (cfg == NULL) || (cfg->uart == NULL) ||
(cfg->dma == NULL) || (cfg->tx_stream == NULL) ||
(cfg->rx_stream == NULL) || (cfg->baud == 0U)) {
return false;
}
if ((rx_buf == NULL) || (rx_size < 2U) ||
((rx_size & (uint16_t)(rx_size - 1U)) != 0U)) {
return false;
}
u->cfg = *cfg;
if (!pcan_ring_init(&u->tx, tx_buf, tx_size)) {
return false;
}
u->rx_buf = rx_buf;
u->rx_size = rx_size;
u->rx_tail = 0U;
u->tx_chunk = 0U;
u->tx_busy = 0U;
u->rx_overruns = 0U;
u->uart_errors = 0U;
/* -- USART: 8N1, OVER8 = 0 -- */
cfg->uart->CR1 = 0U;
cfg->uart->CR2 = 0U;
cfg->uart->CR3 = 0U;
cfg->uart->BRR = (uint16_t)((cfg->pclk_hz + (cfg->baud / 2U)) / cfg->baud);
/* -- DMA передачи: память -> периферия, обычный режим -- */
dma_disable(cfg->tx_stream);
dma_clear(cfg->dma, cfg->tx_stream_idx, DMA_FLAG_ALL);
cfg->tx_stream->PAR = (uint32_t)(uintptr_t)&cfg->uart->DR;
cfg->tx_stream->FCR = 0U; /* прямой режим, без FIFO */
cfg->tx_stream->CR =
((uint32_t)cfg->tx_channel << DMA_SxCR_CHSEL_Pos)
| DMA_SxCR_DIR_0 /* мем -> периферия */
| DMA_SxCR_MINC
| (2UL << DMA_SxCR_PL_Pos) /* высокий приоритет */
| DMA_SxCR_TCIE | DMA_SxCR_TEIE;
/* -- DMA приёма: периферия -> память, кольцевой, работает всегда -- */
dma_disable(cfg->rx_stream);
dma_clear(cfg->dma, cfg->rx_stream_idx, DMA_FLAG_ALL);
cfg->rx_stream->PAR = (uint32_t)(uintptr_t)&cfg->uart->DR;
cfg->rx_stream->M0AR = (uint32_t)(uintptr_t)rx_buf;
cfg->rx_stream->NDTR = rx_size;
cfg->rx_stream->FCR = 0U;
cfg->rx_stream->CR =
((uint32_t)cfg->rx_channel << DMA_SxCR_CHSEL_Pos)
| DMA_SxCR_MINC
| DMA_SxCR_CIRC
| (2UL << DMA_SxCR_PL_Pos);
cfg->rx_stream->CR |= DMA_SxCR_EN;
de_release(u);
cfg->uart->CR3 = USART_CR3_DMAT | USART_CR3_DMAR;
cfg->uart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
return true;
}

View File

@@ -0,0 +1,110 @@
/**
* @file pcan_uart_stm32f4.h
* @brief Порт транспорта на USART + DMA для STM32F4.
*
* Передача - пакетная: DMA отдаёт непрерывный кусок очереди целиком,
* одно прерывание на пакет вместо одного на байт.
* Приём - кольцевой DMA, позиция вычисляется по NDTR, поэтому длина
* посылки заранее не нужна и байты не теряются между вызовами.
*
* Чего порт НЕ делает намеренно (это забота платы):
* - не включает такты RCC для USART, DMA и GPIO;
* - не настраивает выводы и альтернативные функции;
* - не разрешает прерывания в NVIC.
* Так один и тот же порт живёт на любой плате без правок.
*
* ВАЖНО про буферы: на STM32F407 DMA не имеет доступа к CCM RAM
* (0x10000000). Буферы обязаны лежать в основном SRAM (0x20000000),
* иначе передача молча не пойдёт.
*
* Прерывания, которые нужно прокинуть из вектора:
* DMAx_StreamN_IRQHandler -> pcan_uart_dma_tx_irq()
* USARTx_IRQHandler -> pcan_uart_irq()
*/
#ifndef PCAN_UART_STM32F4_H
#define PCAN_UART_STM32F4_H
#include "stm32f4xx.h"
#include "pcan_link.h"
#include "pcan_ring.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
USART_TypeDef *uart;
uint32_t pclk_hz; /**< частота шины этого USART */
uint32_t baud;
DMA_TypeDef *dma;
DMA_Stream_TypeDef *tx_stream;
uint8_t tx_stream_idx; /**< 0..7, нужен для флагов LISR/HISR */
uint8_t tx_channel; /**< 0..7 */
DMA_Stream_TypeDef *rx_stream;
uint8_t rx_stream_idx;
uint8_t rx_channel;
/** Управление драйвером RS485. NULL - обычный полнодуплексный UART. */
GPIO_TypeDef *de_port;
uint8_t de_pin;
uint8_t de_active_low; /**< 1, если DE активен низким уровнем */
} pcan_uart_cfg_t;
typedef struct {
pcan_uart_cfg_t cfg;
pcan_ring_t tx; /**< очередь передачи */
uint8_t *rx_buf; /**< кольцо приёма DMA */
uint16_t rx_size;
uint16_t rx_tail;
volatile uint16_t tx_chunk; /**< длина текущей DMA-посылки */
volatile uint8_t tx_busy;
uint32_t rx_overruns; /**< подозрение на потерю в кольце */
uint32_t uart_errors; /**< ORE / FE / NE / PE */
} pcan_uart_t;
/**
* @brief Настраивает USART и оба потока DMA.
* @param tx_buf,tx_size очередь передачи, размер - степень двойки;
* @param rx_buf,rx_size кольцо приёма, размер - степень двойки.
* @return false при неверных аргументах.
*/
bool pcan_uart_init(pcan_uart_t *u, const pcan_uart_cfg_t *cfg,
uint8_t *tx_buf, uint16_t tx_size,
uint8_t *rx_buf, uint16_t rx_size);
/** Заполняет интерфейс для pcan_link_init(). */
void pcan_uart_io(pcan_uart_t *u, pcan_io_t *io);
/**
* @brief Забирает принятые DMA байты в буфер вызывающего.
*
* Порт не знает, какой протокол поверх него живёт, поэтому отдаёт сырые
* байты: на одном и том же UART так работают и кадр полевой шины,
* и GUI-протокол.
*
* @return число скопированных байт; 0 - новых данных нет.
*/
size_t pcan_uart_read(pcan_uart_t *u, uint8_t *out, size_t max);
/**
* @brief Отдаёт принятые DMA байты в канал pcan_link. Из главного цикла.
* @return число разобранных кадров.
*/
size_t pcan_uart_poll(pcan_uart_t *u, pcan_link_t *link);
/** Обработчик прерывания потока DMA передачи. */
void pcan_uart_dma_tx_irq(pcan_uart_t *u);
/** Обработчик прерывания USART: снимает DE по флагу TC, считает ошибки. */
void pcan_uart_irq(pcan_uart_t *u);
#ifdef __cplusplus
}
#endif
#endif /* PCAN_UART_STM32F4_H */