ihsm-strain-gage-controller-fw/src/serial.c
2023-06-05 19:00:29 +02:00

213 lines
6.3 KiB
C

#include <global.h>
#include <serial.h>
#include <cobs.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
volatile struct uart_dma_state uart_st;
static void uart_schedule_dma(bool retransmit);
static int uart_putc_nonblocking(uint8_t c);
static void uart_dma_reset(void);
void uart_dma_reset() {
memset((void *)&uart_st, 0, sizeof(uart_st));
uart_st.tx_buf.xfr_start = -1;
for (size_t i=0; i<COUNT_OF(uart_st.tx_buf.packet_end); i++)
uart_st.tx_buf.packet_end[i] = -1;
cobs_decode_incremental_initialize((struct cobs_decode_state *)&uart_st.cobs_state);
}
void uart_dma_init(USART_TypeDef *usart, DMA_Channel_TypeDef *tx_dma_ch, int addr) {
uart_dma_reset();
uart_st.tx_dma_ch = tx_dma_ch;
uart_st.usart = usart;
uart_st.addr = addr;
/* Configure DMA 1 Channel 2 to handle uart transmission */
uart_st.tx_dma_ch->CPAR = (uint32_t)&(usart->TDR);
uart_st.tx_dma_ch->CCR = (0<<DMA_CCR_PL_Pos)
| DMA_CCR_DIR
| (0<<DMA_CCR_MSIZE_Pos) /* 8 bit */
| (0<<DMA_CCR_PSIZE_Pos) /* 8 bit */
| DMA_CCR_MINC
| DMA_CCR_TCIE; /* Enable transfer complete interrupt. */
usart->CR1 = /* 8-bit -> M1, M0 clear */
/* OVER8 clear. Use default 16x oversampling */
/* CMIF clear */
/* MME clear */
/* WAKE clear */
/* PCE, PS clear */
USART_CR1_RXNEIE /* Enable receive interrupt */
/* other interrupts clear */
| USART_CR1_TE
| USART_CR1_RE;
/* Set baudrate */
usart->BRR = 192; /* 250kBd */
usart->CR3 |= USART_CR3_DMAT /* TX DMA enable */
| USART_CR3_DEM; /* Output DE signal on RTS pin */
/* And... go! */
usart->CR1 |= USART_CR1_UE;
}
void uart_interrupt(uint32_t isr) {
if (isr & USART_ISR_ORE) {
uart_st.error = ERR_RX_OVERRUN;
uart_st.last_error = sys_time_us;
return;
}
if (isr & USART_ISR_RXNE) {
uint8_t c = uart_st.usart->RDR;
int rc = cobs_decode_incremental((struct cobs_decode_state *)&uart_st.cobs_state, (char *)&uart_st.rx_buf, sizeof(uart_st.rx_buf), c);
if (rc == 0) /* packet still incomplete */
return;
if (rc < 0) {
uart_st.error = ERR_FRAMING;
uart_st.last_error = sys_time_us;
return;
}
/* A complete frame received */
if (rc < (int)sizeof(struct ll_pkt)) {
uart_st.error = ERR_PROTOCOL;
uart_st.last_error = sys_time_us;
return;
}
volatile struct ll_pkt *pkt = (volatile struct ll_pkt *)uart_st.rx_buf;
if (pkt->dst != uart_st.addr && pkt->dst != SER_ADDR_BROADCAST) {
/* we are not addressed */
return;
}
uart_st.last_packet = sys_time_us;
uart_handle_user_packet((struct ll_pkt *)pkt, rc);
}
}
void uart_schedule_dma(bool retransmit) {
volatile struct dma_tx_buf *buf = &uart_st.tx_buf;
ssize_t xfr_start, xfr_end, xfr_len;
if (buf->wraparound) {
buf->wraparound = false;
xfr_start = 0;
xfr_len = buf->xfr_end;
xfr_end = buf->xfr_end;
} else if (!retransmit) {
if (buf->packet_end[buf->xfr_next] == -1)
return; /* Nothing to transmit */
xfr_start = buf->xfr_end;
xfr_end = buf->packet_end[buf->xfr_next];
buf->packet_end[buf->xfr_next] = -1;
buf->xfr_next = (buf->xfr_next + 1) % COUNT_OF(buf->packet_end);
if (xfr_end > xfr_start) { /* no wraparound */
xfr_len = xfr_end - xfr_start;
} else { /* wraparound */
if (xfr_end != 0)
buf->wraparound = true;
xfr_len = sizeof(buf->data) - xfr_start;
}
} else {
/* retransmit */
/* First, send a zero to delimit any garbage from the following good packet */
uart_st.usart->TDR = 0x00;
xfr_start = buf->xfr_start;
xfr_end = buf->xfr_end;
if (xfr_end > xfr_start) { /* no wraparound */
xfr_len = xfr_end - xfr_start;
} else { /* wraparound */
if (xfr_end != 0)
buf->wraparound = true;
xfr_len = sizeof(buf->data) - xfr_start;
}
uart_st.retransmissions++;
}
buf->xfr_start = xfr_start;
buf->xfr_end = xfr_end;
/* Wait for channel to turn off */
while (uart_st.tx_dma_ch->CCR & DMA_CCR_EN) {
/* do nothing */
}
/* initiate transmission of new buffer */
uart_st.tx_dma_ch->CMAR = (uint32_t)(buf->data + xfr_start);
uart_st.tx_dma_ch->CNDTR = xfr_len;
uart_st.tx_dma_ch->CCR |= DMA_CCR_EN;
}
int uart_putc_nonblocking(uint8_t c) {
volatile struct dma_tx_buf *buf = &uart_st.tx_buf;
if (buf->wr_pos == buf->xfr_start)
return ERR_BUSY;
buf->data[buf->wr_pos] = c;
buf->wr_pos = (buf->wr_pos + 1) % sizeof(buf->data);
return 0;
}
void uart_dma_interrupt(DMA_Channel_TypeDef *channel, uint32_t flags) {
uart_st.tx_dma_ch->CCR &= ~DMA_CCR_EN;
if (flags & DMA_ISR_TEIF1) {
uart_st.error = ERR_DMA;
uart_st.last_error = sys_time_us;
} else if (flags & DMA_ISR_TCIF1 && channel == uart_st.tx_dma_ch) {
uart_schedule_dma(false);
}
}
/* len is the packet length including headers */
int uart_send_packet_nonblocking(struct ll_pkt *pkt, size_t pkt_len) {
if (uart_st.tx_buf.packet_end[uart_st.tx_buf.wr_idx] != -1) {
return ERR_TX_OVERRUN;
}
uart_st.last_transmission = sys_time_us;
/* make the value this wonky-ass CRC implementation produces match zlib etc. */
CRC->CR = CRC_CR_REV_OUT | (1<<CRC_CR_REV_IN_Pos) | CRC_CR_RESET;
for (size_t i=offsetof(struct ll_pkt, pid); i<pkt_len; i++)
CRC->DR = ((uint8_t *)pkt)[i];
pkt->crc32 = ~CRC->DR;
int rc = cobs_encode_usart((int (*)(char))uart_putc_nonblocking, (char *)pkt, pkt_len);
if (rc)
return rc;
uart_st.tx_buf.packet_end[uart_st.tx_buf.wr_idx] = uart_st.tx_buf.wr_pos;
uart_st.tx_buf.wr_idx = (uart_st.tx_buf.wr_idx + 1) % COUNT_OF(uart_st.tx_buf.packet_end);
uart_st.packet_count++;
if (!(uart_st.tx_dma_ch->CCR & DMA_CCR_EN))
uart_schedule_dma(false);
return 0;
}