/* * This file is part of the libusbhost library * hosted at http://github.com/libusbhost/libusbhost * * Copyright (C) 2015 Amir Hammad * * * 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 . * */ #include "global.h" #include "serial.h" #include "cobs.h" #include #include #include 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); void usart_dma_init() { usart_tx_buf.xfr_start = -1; usart_tx_buf.xfr_end = 0; usart_tx_buf.wr_pos = 0; usart_tx_buf.wr_idx = 0; usart_tx_buf.cur_packet = -1; usart_tx_buf.retransmit_rq = 0; usart_tx_buf.wraparound = 0; for (size_t i=0; iCPAR = (uint32_t)&(USART1->TDR); DMA1_Channel2->CCR = (0<CMAR = (uint32_t)&(CRC->DR); DMA1_Channel3->CCR = (1<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; iorig_id)) rx_protocol_errors++; break; case CTRL_PKT_RETRANSMIT: usart_tx_buf.retransmit_rq = 1; if (!(DMA1_Channel2->CCR & DMA_CCR_EN)) usart_schedule_dma(); 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 next_start, next_idx; if (buf->wraparound) { buf->wraparound = 0; next_idx = buf->cur_packet; next_start = 0; } else if (buf->retransmit_rq) { buf->retransmit_rq = 0; next_idx = buf->cur_packet; next_start = buf->xfr_start; } else { next_idx = (buf->cur_packet + 1) % ARRAY_LEN(usart_tx_buf.packet_end); next_start = buf->xfr_end; } ssize_t next_end = buf->packet_end[next_idx]; /* Nothing to trasnmit */ if (next_end == -1) return; ssize_t xfr_len; if (next_end > next_start) /* no wraparound */ xfr_len = next_end - next_start; else /* wraparound */ xfr_len = sizeof(buf->data) - next_start; /* schedule transfer until end of buffer */ buf->xfr_start = next_start; buf->xfr_end = (next_start + xfr_len) % sizeof(buf->data); /* handle wraparound */ buf->cur_packet = next_idx; /* initiate transmission of new buffer */ DMA1_Channel2->CMAR = (uint32_t)(buf->data + next_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_end)) return -EINVAL; if (idx != usart_tx_buf.cur_packet) return -EINVAL; usart_tx_buf.packet_end[idx] = -1; /* If the DMA stream is idle right now, schedule the next transfer */ if (!(DMA1_Channel2->CCR & DMA_CCR_EN)) usart_schedule_dma(); 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 (buf->wr_pos == buf->xfr_start) { NVIC_EnableIRQ(DMA1_Channel2_3_IRQn); return -EBUSY; } 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.retransmit_rq || usart_tx_buf.wraparound) usart_schedule_dma(); } /* len is the packet length including headers */ int usart_send_packet_nonblocking(struct ll_pkt *pkt, size_t pkt_len) { if (usart_tx_buf.packet_end[usart_tx_buf.wr_idx] != -1) { /* Find a free slot for this packet */ tx_overruns++; return -EBUSY; } pkt->pid = usart_tx_buf.wr_idx; pkt->_pad = 0; /* make the value this wonky-ass CRC implementation produces match zlib etc. */ CRC->CR = CRC_CR_REV_OUT | (1<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; usart_tx_buf.packet_end[usart_tx_buf.wr_idx] = usart_tx_buf.wr_pos; usart_tx_buf.wr_idx = (usart_tx_buf.wr_idx + 1) % ARRAY_LEN(usart_tx_buf.packet_end); if (!(DMA1_Channel2->CCR & DMA_CCR_EN)) usart_schedule_dma(); return 0; }