/** Write out data over the USART using DMA. Note that this function is not * reentrant and does not guard against DMA IRQs running at the same time which * will also cause spurious behaviours. Ensure that the calling function * appropriately prevents these things from happeing. * * \param data A pointer to the data to write out. * \param len The number of bytes to write. * * \return The number of bytes that will be written, may be less than len. */ u32 usart_write_dma(u8 data[], u32 len) { /* Check if the write would cause a buffer overflow, if so only write up to * the end of the buffer. */ u32 n_free = usart_tx_n_free(); if (len > n_free) len = n_free; /* If there is no data to write, just return. */ if (len == 0) return 0; u32 old_wr = wr; wr = (wr + len) % USART_TX_BUFFER_LEN; if (old_wr + len <= USART_TX_BUFFER_LEN) { memcpy(&buff[old_wr], data, len); } else { /* Deal with case where write wraps the buffer. */ memcpy(&buff[old_wr], &data[0], USART_TX_BUFFER_LEN - old_wr); memcpy(&buff[0], &data[USART_TX_BUFFER_LEN-old_wr], len - (USART_TX_BUFFER_LEN - old_wr)); } /* Check if there is a DMA transfer either in progress or waiting for its * interrupt to be serviced. Its very important to also check the interrupt * flag as EN will be cleared when the transfer finishes but we really need * to make sure the ISR has been run to finish up the bookkeeping for the * transfer. Also, make sure that this is done atomically without a DMA * interrupt squeezing in there. */ if (!((DMA2_S7CR & DMA_SxCR_EN) || (DMA2_HISR & DMA_HISR_TCIF7))) dma_schedule(); return len; }
/** USART TX DMA interrupt service routine. * Should be called from the relevant DMA stream ISR. * \param s The USART DMA state structure. */ void usart_tx_dma_isr(usart_tx_dma_state* s) { if (dma_get_interrupt_flag(s->dma, s->stream, DMA_TEIF | DMA_DMEIF)) /* TODO: Handle error interrupts! */ screaming_death("DMA TX error interrupt"); if (dma_get_interrupt_flag(s->dma, s->stream, DMA_TCIF)) { /* Interrupt is Transmit Complete. */ /* Clear the DMA transmit complete and half complete interrupt flags. */ dma_clear_interrupt_flags(s->dma, s->stream, DMA_HTIF | DMA_TCIF); /* Now that the transfer has finished we can increment the read index. */ s->rd = (s->rd + s->xfer_len) % USART_TX_BUFFER_LEN; if (s->wr != s->rd) /* Buffer not empty. */ dma_schedule(s); } if (dma_get_interrupt_flag(s->dma, s->stream, DMA_FEIF)) /* Clear FIFO error flag */ dma_clear_interrupt_flags(s->dma, s->stream, DMA_HTIF | DMA_FEIF); }
static void usart_tx_dma_isr(struct usart_tx_dma_state* s, u32 flags) { if ((flags & (STM32_DMA_ISR_TEIF | STM32_DMA_ISR_DMEIF)) != 0) screaming_death("USART TX DMA error interrupt"); osalSysLockFromISR(); /* Now that the transfer has finished we can increment the read index. */ s->rd = (s->rd + s->xfer_len) % USART_TX_BUFFER_LEN; if (s->wr != s->rd) /* Buffer not empty. */ dma_schedule(s); else s->busy = false; osalSysUnlockFromISR(); }
/** USART1 TX DMA interrupt handler. */ void dma2_stream7_isr() { if (DMA2_HISR & DMA_HISR_TCIF7) { /* Interrupt is Transmit Complete. */ /* Clear the DMA transmit half and complete interrupt flags. */ DMA2_HIFCR = DMA_HIFCR_CTCIF7; DMA2_HIFCR = DMA_HIFCR_CHTIF7; /* Now that the transfer has finished we can increment the read index. */ rd = (rd + xfer_len) % USART_TX_BUFFER_LEN; if (wr != rd) /* FIFO not empty. */ dma_schedule(); } else { /* TODO: Handle error interrupts! */ speaking_death("DMA TX error interrupt"); } }
u32 usart_support_write(void *sd, const u8 data[], u32 len) { struct usart_tx_dma_state *s = &((struct usart_support_s *)sd)->tx; /* If there is no data to write, just return. */ if (len == 0) return 0; chSysLock(); /* Check if the write would cause a buffer overflow, if so only write up to * the end of the buffer. */ u32 n_free = usart_tx_n_free(sd); if (len > n_free) { chSysUnlock(); return 0; } u32 old_wr = s->wr; s->wr = (s->wr + len) % USART_TX_BUFFER_LEN; if (old_wr + len <= USART_TX_BUFFER_LEN) memcpy(&(s->buff[old_wr]), data, len); else { /* Deal with case where write wraps the buffer. */ memcpy(&(s->buff[old_wr]), &data[0], USART_TX_BUFFER_LEN - old_wr); memcpy(&(s->buff[0]), &data[USART_TX_BUFFER_LEN - old_wr], len - (USART_TX_BUFFER_LEN - old_wr)); } /* Check if there is a DMA transfer either in progress or waiting for its * interrupt to be serviced. Its very important to also check the interrupt * flag as EN will be cleared when the transfer finishes but we really need * to make sure the ISR has been run to finish up the bookkeeping for the * transfer. Also, make sure that this is done atomically without a DMA * interrupt squeezing in there. */ if (!s->busy) dma_schedule(s); chSysUnlock(); return len; }