master-thesis/gm_platform/fw/serial.c
2020-01-27 21:58:22 +01:00

300 lines
9 KiB
C

/*
* This file is part of the libusbhost library
* hosted at http://github.com/libusbhost/libusbhost
*
* Copyright (C) 2015 Amir Hammad <amir.hammad@hotmail.com>
*
*
* libusbhost is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "global.h"
#include "serial.h"
#include "cobs.h"
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
volatile struct dma_tx_buf usart_tx_buf;
static uint32_t tx_overruns=0, rx_overruns=0;
static uint32_t rx_framing_errors=0, rx_protocol_errors=0;
static struct cobs_decode_state cobs_state;
static volatile uint8_t rx_buf[32];
static void usart_schedule_dma(void);
static int usart_putc_nonblocking(uint8_t c);
static int usart_retransmit_packet(uint8_t idx);
void usart_dma_init() {
usart_tx_buf.xfr_start = -1;
usart_tx_buf.xfr_end = 0;
usart_tx_buf.wr_pos = 0;
for (size_t i=0; i<ARRAY_LEN(usart_tx_buf.packet_start); i++)
usart_tx_buf.packet_start[i] = -1;
cobs_decode_incremental_initialize(&cobs_state);
/* Configure DMA 1 Channel 2 to handle uart transmission */
DMA1_Channel2->CPAR = (uint32_t)&(USART1->TDR);
DMA1_Channel2->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. */
DMA1_Channel3->CMAR = (uint32_t)&(CRC->DR);
DMA1_Channel3->CCR = (1<<DMA_CCR_PL_Pos)
| (0<<DMA_CCR_MSIZE_Pos) /* 8 bit */
| (0<<DMA_CCR_PSIZE_Pos) /* 8 bit */
| DMA_CCR_PINC
| DMA_CCR_TCIE; /* Enable transfer complete interrupt. */
/* triggered on transfer completion. We use this to process the ADC data */
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
NVIC_SetPriority(DMA1_Channel2_3_IRQn, 1<<5);
USART1->CR1 = /* 8-bit -> M1, M0 clear */
/* OVER8 clear. Use default 16x oversampling */
/* CMIF clear */
USART_CR1_MME
/* WAKE clear */
/* PCE, PS clear */
| USART_CR1_RXNEIE /* Enable receive interrupt */
/* other interrupts clear */
| USART_CR1_TE
| USART_CR1_RE;
/* Set divider for 115.2kBd @48MHz system clock. */
//USART1->BRR = 417;
//USART1->BRR = 48; /* 1MBd */
//USART1->BRR = 96; /* 500kBd */
USART1->BRR = 192; /* 250kBd */
//USART1->BRR = 208; /* 230400 */
USART1->CR2 = USART_CR2_TXINV | USART_CR2_RXINV;
USART1->CR3 |= USART_CR3_DMAT; /* TX DMA enable */
/* Enable receive interrupt */
NVIC_EnableIRQ(USART1_IRQn);
NVIC_SetPriority(USART1_IRQn, 3<<5);
/* And... go! */
USART1->CR1 |= USART_CR1_UE;
}
void USART1_IRQHandler() {
uint32_t isr = USART1->ISR;
if (isr & USART_ISR_ORE) {
USART1->ICR = USART_ICR_ORECF;
rx_overruns++;
return;
}
if (isr & USART_ISR_RXNE) {
uint8_t c = USART1->RDR;
int rc = cobs_decode_incremental(&cobs_state, (char *)rx_buf, sizeof(rx_buf), c);
if (rc == 0) /* packet still incomplete */
return;
if (rc < 0) {
rx_framing_errors++;
return;
}
/* A complete frame received */
if (rc != 2) {
rx_protocol_errors++;
return;
}
volatile struct ctrl_pkt *pkt = (volatile struct ctrl_pkt *)rx_buf;
switch (pkt->type) {
case CTRL_PKT_RESET:
for (size_t i=0; i<ARRAY_LEN(usart_tx_buf.packet_start); i++)
usart_tx_buf.packet_start[i] = -1;
break;
case CTRL_PKT_ACK:
if (usart_ack_packet(pkt->orig_id))
rx_protocol_errors++;
break;
case CTRL_PKT_RETRANSMIT:
if (usart_retransmit_packet(pkt->orig_id))
rx_protocol_errors++;
break;
default:
rx_protocol_errors++;
}
return;
}
}
void usart_schedule_dma() {
/* This function is only called when the DMA channel is disabled. This means we don't have to guard it in IRQ
* disables. */
volatile struct dma_tx_buf *buf = &usart_tx_buf;
ssize_t xfr_len, xfr_start = buf->xfr_end;
if (buf->wr_pos > xfr_start) /* no wraparound */
xfr_len = buf->wr_pos - xfr_start;
else /* wraparound */
xfr_len = sizeof(buf->data) - xfr_start; /* schedule transfer until end of buffer */
buf->xfr_start = xfr_start;
buf->xfr_end = (xfr_start + xfr_len) % sizeof(buf->data); /* handle wraparound */
/* initiate transmission of new buffer */
DMA1_Channel2->CMAR = (uint32_t)(buf->data + xfr_start);
DMA1_Channel2->CNDTR = xfr_len;
DMA1_Channel2->CCR |= DMA_CCR_EN;
}
int usart_ack_packet(uint8_t idx) {
if (idx > ARRAY_LEN(usart_tx_buf.packet_start))
return -EINVAL;
usart_tx_buf.packet_start[idx] = -1;
return 0;
}
int usart_dma_fifo_push(volatile struct dma_tx_buf *buf, uint8_t c) {
/* This function must be guarded by IRQ disable since the IRQ may schedule a new transfer and charge pos/start. */
NVIC_DisableIRQ(DMA1_Channel2_3_IRQn);
/* If the write pointer hit any unacknowledged packet start position we can't advance it.
* Packet start positions are unordered and we have to scan here. */
for (size_t i=0; i<ARRAY_LEN(buf->packet_start); i++) {
if (buf->wr_pos == buf->packet_start[i]) {
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
return -EBUSY;
}
}
/* write byte, then increment to avoid racing the DMA ISR reading wr_pos */
buf->data[buf->wr_pos] = c;
buf->wr_pos = (buf->wr_pos + 1) % sizeof(buf->data);
NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
return 0;
}
int usart_putc(uint8_t c) {
/* push char to fifo, busy-loop if stalled to wait for USART to empty fifo via DMA */
while (usart_dma_fifo_push(&usart_tx_buf, c) == -EBUSY) {
/* idle */
}
return 0;
}
int usart_putc_nonblocking(uint8_t c) {
return usart_dma_fifo_push(&usart_tx_buf, c);
}
void DMA1_Channel2_3_IRQHandler(void) {
/* Transfer complete */
DMA1->IFCR |= DMA_IFCR_CTCIF2;
DMA1_Channel2->CCR &= ~DMA_CCR_EN;
if (usart_tx_buf.wr_pos != usart_tx_buf.xfr_end) /* buffer not empty */
usart_schedule_dma();
}
int usart_retransmit_packet(uint8_t idx) {
/* Disable ADC DMA IRQ to prevent write races */
NVIC_DisableIRQ(DMA1_Channel1_IRQn);
ssize_t i = usart_tx_buf.packet_start[idx];
ssize_t start = i;
/* Copy packet */
uint8_t c;
while ((c = usart_tx_buf.data[i++])) {
if (usart_putc_nonblocking(c)) {
tx_overruns++;
return -EBUSY;
}
}
/* Terminating null byte */
if (usart_putc_nonblocking(0)) {
tx_overruns++;
return -EBUSY;
}
/* Update start index */
usart_tx_buf.packet_start[idx] = start;
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
return 0;
}
/* len is the packet length including headers */
int usart_send_packet_nonblocking(struct ll_pkt *pkt, size_t pkt_len) {
ssize_t start = usart_tx_buf.wr_pos;
/* Find a free slot for this packet */
size_t packet_idx = 0;
do {
if (usart_tx_buf.packet_start[packet_idx] == -1)
goto success;
} while (++packet_idx <ARRAY_LEN(usart_tx_buf.packet_start));
tx_overruns++;
return -EBUSY;
success:
pkt->pid = packet_idx;
pkt->_pad = 0;
/* 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))usart_putc_nonblocking, (char *)pkt, pkt_len);
if (rc)
return rc;
/* Checkpoint packet start index to prevent overwriting before ack */
usart_tx_buf.packet_start[packet_idx] = start;
/* FIXME debug code
static uint8_t x = 0;
for (size_t i=0; i<351; i++)
usart_putc_nonblocking(x++);
*/
/* If the DMA stream is idle right now, schedule a transfer */
if (!(DMA1_Channel2->CCR & DMA_CCR_EN))
usart_schedule_dma();
return 0;
}