/* * 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 void usart_schedule_dma(); void usart_dma_init() { usart_tx_buf.xfr_start = -1, usart_tx_buf.xfr_end = 0, usart_tx_buf.wr_pos = 0, /* Configure DMA 1 Channel 2 to handle uart transmission */ DMA1_Channel2->CPAR = (unsigned int)&(USART1->TDR); DMA1_Channel2->CCR = (0<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 1MBd @48MHz system clock. */ USART1->BRR = 48; 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, 1); /* And... go! */ USART1->CR1 |= USART_CR1_UE; } 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; size_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_dma_fifo_push(volatile struct dma_tx_buf *buf, char 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; } void usart_putc(char 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 */ } } 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(); } void usart_send_packet(const uint8_t *data, size_t len) { /* ignore return value as putf is blocking and always succeeds */ (void)cobs_encode_usart((char *)data, len); /* If the DMA stream is idle right now, schedule a transfer */ if (!(DMA1_Channel2->CCR & DMA_CCR_EN)) usart_schedule_dma(); }