Files
templates/c/protocan-transport/ports/stm32f4/pcan_uart_stm32f4.c
Andrey Kruchinkin 3dc636e012 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 проходят.
2026-08-23 01:15:35 +03:00

288 lines
9.3 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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;
}