feat(protocan-transport): транспортный уровень ProtoCAN и каталог GUI
Перенесён из репозитория protocan-transport, который подключался сабмодулем в CAN_to_RS485. Кадрирование AA 55 с CRC16 поверх любого байтового потока (RS485, RS232, USB CDC), разбор 29-битного идентификатора, общее адресное пространство регистров и каталог с подпиской на поток значений для SETGUI. Состояние живёт в структурах вызывающего, поэтому в одной прошивке поднимается сколько угодно независимых каналов. Порт STM32F4 (USART + DMA) в комплекте. Хостовые тесты test_transport и test_gui проходят.
This commit is contained in:
287
c/protocan-transport/ports/stm32f4/pcan_uart_stm32f4.c
Normal file
287
c/protocan-transport/ports/stm32f4/pcan_uart_stm32f4.c
Normal 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;
|
||||
}
|
||||
110
c/protocan-transport/ports/stm32f4/pcan_uart_stm32f4.h
Normal file
110
c/protocan-transport/ports/stm32f4/pcan_uart_stm32f4.h
Normal 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 */
|
||||
Reference in New Issue
Block a user