diff --git a/drv/dma.c b/drv/dma.c index f13f9f1..7d68b09 100644 --- a/drv/dma.c +++ b/drv/dma.c @@ -19,9 +19,9 @@ //--local definitions----------------------------------------------------------- static void configure_dma(volatile struct DMA* dma, enum DmaChannel channel, - enum DmaConfig config_mask, volatile void* periph, volatile void* mem, - uint16_t size); - + enum DmaConfig config_mask, volatile void* periph); +static void start_dma(volatile struct DMA* dma, enum DmaChannel channel, + volatile void* mem, uint16_t size); //--local variables------------------------------------------------------------- @@ -36,8 +36,8 @@ static volatile void* dma2_cb_params[5]; //--public functions------------------------------------------------------------ void dma_configure(enum DmaPeriph dma, enum DmaChannel channel, - enum DmaConfig config_mask, volatile void* periph, volatile void* mem, - uint16_t size, DmaCallback callback, volatile void* cb_param) + enum DmaConfig config_mask, volatile void* periph, + DmaCallback callback, volatile void* cb_param) { //reset peripheral first, to ensure proper configuration dma_reset(dma, channel); @@ -45,7 +45,7 @@ void dma_configure(enum DmaPeriph dma, enum DmaChannel channel, switch (dma) { case DMA_PERIPH_1: rcc_enable(RCC_AHB_DMA1, RCC_APB1_NONE, RCC_APB2_NONE); - configure_dma(dma1, channel, config_mask, periph, mem, size); + configure_dma(dma1, channel, config_mask, periph); if (callback) { dma1_callbacks[channel] = callback; dma1_cb_params[channel] = cb_param; @@ -54,7 +54,7 @@ void dma_configure(enum DmaPeriph dma, enum DmaChannel channel, break; case DMA_PERIPH_2: rcc_enable(RCC_AHB_DMA2, RCC_APB1_NONE, RCC_APB2_NONE); - configure_dma(dma2, channel, config_mask, periph, mem, size); + configure_dma(dma2, channel, config_mask, periph); if (callback) { dma2_callbacks[channel] = callback; dma2_cb_params[channel] = cb_param; @@ -95,14 +95,21 @@ void dma_reset(enum DmaPeriph dma, enum DmaChannel channel) regs->CPAR = 0; } -void dma_exit_critical(enum DmaPeriph dma, enum DmaChannel channel) +void dma_start(enum DmaPeriph dma, enum DmaChannel channel, + volatile void* mem, uint16_t size) { switch (dma) { case DMA_PERIPH_1: - nvic_enable(NVIC_IRQ_DMA1_CHANNEL1 + channel); + if (dma1_callbacks[channel]) { + nvic_enable(NVIC_IRQ_DMA1_CHANNEL1 + channel); + } + start_dma(dma1, channel, mem, size); break; case DMA_PERIPH_2: - nvic_enable(NVIC_IRQ_DMA2_CHANNEL1 + channel); + if (dma2_callbacks[channel]) { + nvic_enable(NVIC_IRQ_DMA2_CHANNEL1 + channel); + } + start_dma(dma2, channel, mem, size); break; default: return; @@ -110,19 +117,19 @@ void dma_exit_critical(enum DmaPeriph dma, enum DmaChannel channel) } } -void dma_enable(enum DmaPeriph dma, enum DmaChannel channel) +void dma_stop(enum DmaPeriph dma, enum DmaChannel channel) { switch (dma) { case DMA_PERIPH_1: - reg_set(dma1->CHANNELS[channel].CCR, DMA_CCR_EN); + reg_reset(dma1->CHANNELS[channel].CCR, DMA_CCR_EN); if (dma1_callbacks[channel]) { - nvic_enable(NVIC_IRQ_DMA1_CHANNEL1 + channel); + nvic_disable(NVIC_IRQ_DMA1_CHANNEL1 + channel); } break; case DMA_PERIPH_2: - reg_set(dma2->CHANNELS[channel].CCR, DMA_CCR_EN); + reg_reset(dma2->CHANNELS[channel].CCR, DMA_CCR_EN); if (dma2_callbacks[channel]) { - nvic_enable(NVIC_IRQ_DMA2_CHANNEL1 + channel); + nvic_disable(NVIC_IRQ_DMA2_CHANNEL1 + channel); } break; default: @@ -146,20 +153,14 @@ void dma_enter_critical(enum DmaPeriph dma, enum DmaChannel channel) } } -void dma_disable(enum DmaPeriph dma, enum DmaChannel channel) +void dma_exit_critical(enum DmaPeriph dma, enum DmaChannel channel) { switch (dma) { case DMA_PERIPH_1: - reg_reset(dma1->CHANNELS[channel].CCR, DMA_CCR_EN); - if (dma1_callbacks[channel]) { - nvic_disable(NVIC_IRQ_DMA1_CHANNEL1 + channel); - } + nvic_enable(NVIC_IRQ_DMA1_CHANNEL1 + channel); break; case DMA_PERIPH_2: - reg_reset(dma2->CHANNELS[channel].CCR, DMA_CCR_EN); - if (dma2_callbacks[channel]) { - nvic_disable(NVIC_IRQ_DMA2_CHANNEL1 + channel); - } + nvic_enable(NVIC_IRQ_DMA2_CHANNEL1 + channel); break; default: return; @@ -184,22 +185,34 @@ uint16_t dma_get_remaining(enum DmaPeriph dma, enum DmaChannel channel) //--local functions------------------------------------------------------------- +/** + * Applies the given configuration mask to the given DMA channel + */ static void configure_dma(volatile struct DMA* dma, enum DmaChannel channel, - enum DmaConfig config_mask, volatile void* periph, volatile void* mem, - uint16_t size) + enum DmaConfig config_mask, volatile void* periph) { volatile struct DMA_CHANNEL* regs = &dma->CHANNELS[channel]; //registers should already be at reset value, apply new config regs->CCR.word = config_mask; - reg_write(regs->CNDTR, DMA_CNDTR_NDT, size); regs->CPAR = (uint32_t)periph; - regs->CMAR = (uint32_t)mem; - - //only enable channel when everything is configured - reg_set(regs->CCR, DMA_CCR_EN); } +/** + * Starts the given DMA channel using the given parameters + */ +static void start_dma(volatile struct DMA* dma, enum DmaChannel channel, + volatile void* mem, uint16_t size) +{ + volatile struct DMA_CHANNEL* regs = &dma->CHANNELS[channel]; + + //registers should already be configured, apply transfer config + reg_write(regs->CNDTR, DMA_CNDTR_NDT, size); + regs->CMAR = (uint32_t)mem; + + //only start transfer when everything is configured + reg_set(regs->CCR, DMA_CCR_EN); +} //--ISRs------------------------------------------------------------------------ diff --git a/drv/dma.h b/drv/dma.h index 309f797..5367530 100644 --- a/drv/dma.h +++ b/drv/dma.h @@ -15,11 +15,19 @@ //--type definitions------------------------------------------------------------ +/** + * Available DMA peripherals. Note that some of these peripherals may not be + * available on all chips + */ enum DmaPeriph { DMA_PERIPH_1, DMA_PERIPH_2, }; +/** + * Available DMA channels. Note that some of these channels may not be + * available on all chips and all DMA peripheral + */ enum DmaChannel { DMA_CHANNEL_1 = 0, DMA_CHANNEL_2, @@ -30,6 +38,9 @@ enum DmaChannel { DMA_CHANNEL_7, //not available for DMA 2 }; +/** + * Configuration options for memory-to-peripheral transfers + */ enum DmaConfig { DMA_CONFIG_IRQ_COMPLETE = (0x1 << 1), DMA_CONFIG_IRQ_HALF = (0x1 << 2), @@ -51,6 +62,9 @@ enum DmaConfig { DMA_CONFIG_PRIO_VHIGH = (0x3 << 12), }; +/** + * Configuration options for memory-to-memory transfers + */ enum DmaConfigM2M { DMA_CONFIG_M2M_IRQ_COMPLETE = (0x1 << 1), DMA_CONFIG_M2M_IRQ_HALF = (0x1 << 2), @@ -69,35 +83,96 @@ enum DmaConfigM2M { DMA_CONFIG_M2M_PRIO_VHIGH = (0x3 << 12), }; +/** + * Available sources for a DMA IRQ. These sources can be enabled independently + * in the DMA configuration. + */ enum DmaIRQSource { DMA_IRQ_SOURCE_COMPLETE = (0x1 << 1), DMA_IRQ_SOURCE_HALF = (0x1 << 2), DMA_IQR_SOURCE_ERROR = (0x2 << 3), }; +/** + * Prototype of the IRQ callbacks that the applicative code can provide + */ typedef void (*DmaCallback)(enum DmaIRQSource, volatile void* param); +/** + * Generic struct used to share DAM configs between peripheral drivers and + * services providing DMA interfaces + */ +struct DmaParam { + void* periph; + enum DmaConfig config; //DMA config, must correspond to peripheral + enum DmaPeriph dma; //DMA peripheral, must correspond to peripheral + enum DmaChannel channel; //DMA channel, must correspond to peripheral +}; + //--functions------------------------------------------------------------------- +/** + * Configures the given DMA channel with the provided configuration, to perform + * a transfer to or from the given peripheral register. The specified callback, + * if any, will be called on the enabled IRQ sources, with the specified + * parameter, if any. + * + * This function doesn't initiate transfers, use dma_start() for that + */ void dma_configure(enum DmaPeriph dma, enum DmaChannel channel, - enum DmaConfig config_mask, volatile void* periph, volatile void* mem, - uint16_t size, DmaCallback callback, volatile void* cb_param); + enum DmaConfig config_mask, volatile void* periph, + DmaCallback callback, volatile void* cb_param); +/** + * Unimplemented + */ void dma_configure_mem2mem(enum DmaPeriph dma, enum DmaChannel channel, enum DmaConfigM2M config_mask, const void* src, void* dest, uint16_t size, DmaCallback callback, void* cb_param); +/** + * Resets the given DMA channel, restoring the default configuration and + * disabling it + */ void dma_reset(enum DmaPeriph dma, enum DmaChannel channel); -void dma_exit_critical(enum DmaPeriph dma, enum DmaChannel channel); +/** + * Initiate a transfer on the given DMA channel, to or from the given memory + * address, and of the specified size. + * + * Should only be used after the channel has been configured through + * dma_configure() + * All transfers started must be eventually stopped, or the channel reset, for + * proper IRQ behavior. + */ +void dma_start(enum DmaPeriph dma, enum DmaChannel channel, + volatile void* mem, uint16_t size); -void dma_enable(enum DmaPeriph dma, enum DmaChannel channel); +/** + * Stops a transfer on the given DMA channel. If the transfer has already + * ended, properly shutdown the channel. Configuration is not lost after calling + * this function and dma_start() may be called again + */ +void dma_stop(enum DmaPeriph dma, enum DmaChannel channel); +/** + * Enters a DMA critical section, disabling the given channel's IRQs until + * dma_exit_critical() is called + */ void dma_enter_critical(enum DmaPeriph dma, enum DmaChannel channel); -void dma_disable(enum DmaPeriph dma, enum DmaChannel channel); +/** + * Exists a DMA critical section previously entered through + * dma_enter_critical(). Reenables the given channel's IRQs + */ +void dma_exit_critical(enum DmaPeriph dma, enum DmaChannel channel); +/** + * Returns the remaining number of bytes to be transmitted while a transfer is + * running. When no transfer is running, returns the number of bytes to transfer + * next + */ uint16_t dma_get_remaining(enum DmaPeriph dma, enum DmaChannel channe); diff --git a/drv/dma_mbuf.c b/drv/dma_mbuf.c deleted file mode 100644 index 8d10b27..0000000 --- a/drv/dma_mbuf.c +++ /dev/null @@ -1,143 +0,0 @@ -/** @file dma_mbuf.h - * Module handling Direct Memory Access controller's multibuffer system - * - * The module provides a convenient tool to send data to a peripheral in a - * buffered, low-altency, low-cpu usage, non-blocking way - */ - -//--includes-------------------------------------------------------------------- - -#include "dma_mbuf.h" - - -//--local definitions----------------------------------------------------------- - -#define DMA_CONFIG (DMA_CONFIG_IRQ_COMPLETE | DMA_CONFIG_FROM_MEM \ - | DMA_CONFIG_INC_MEM | DMA_CONFIG_PSIZE_8BITS \ - | DMA_CONFIG_MSIZE_8BITS) - -static void mbuf_callback(enum DmaIRQSource src, volatile void* param); - -//take last 2 bytes of a buffer and assemble them store the current byte index -#define byte_index (((uint16_t**)buffer->buffers) \ - [buffer->buffer_index][buffer->buffer_size/2]) -#define dma_byte_index (((uint16_t**)buffer->buffers) \ - [buffer->dma_buffer_index][buffer->buffer_size/2]) - - -//--local variables------------------------------------------------------------- - -//--public functions------------------------------------------------------------ - -void dma_mbuf_configure(volatile struct DmaMultiBuffer* buffer, void** buffers, - volatile void* dest, uint16_t buffer_size, uint8_t buffer_nb, - enum DmaPeriph dma, enum DmaChannel channel, enum DmaConfig priority) -{ - #warning "check for null ptr" - - buffer->buffers = buffers; - buffer->dest = dest; - - buffer->buffer_size = buffer_size - 2; - - buffer->buffer_nb = buffer_nb; - buffer->free_buffer_nb = buffer_nb - 1; - buffer->buffer_index = 0; - buffer->dma_buffer_index = 0; - - buffer->dma = dma; - buffer->channel = channel; - buffer->config = DMA_CONFIG | priority; - - for (uint8_t i=0; ibuffer_nb; ++i) { - for (uint16_t j=0; jbuffer_size + 2; ++j) { - ((uint8_t**)buffer->buffers)[i][j] = 0; - } - } -} - -uint32_t dma_mbuf_write_byte(volatile struct DmaMultiBuffer* buffer, - uint8_t byte) -{ - dma_enter_critical(buffer->dma, buffer->channel); - - //if the current buffer is full, we need to switch it with an empty one - volatile uint16_t index = byte_index; - if (byte_index >= buffer->buffer_size) { - - //if all buffer full, give up - if (buffer->free_buffer_nb == 0) { - dma_exit_critical(buffer->dma, buffer->channel); - return 1; - } - - ++buffer->buffer_index; - if (buffer->buffer_index >= buffer->buffer_nb) { - buffer->buffer_index = 0; - } - --buffer->free_buffer_nb; - - byte_index = 0; - } - - //write the byte - uint8_t** buffers = (uint8_t**)buffer->buffers; - buffers[buffer->buffer_index][byte_index] = byte; - ++byte_index; - ++index; - - dma_exit_critical(buffer->dma, buffer->channel); - return 0; -} - -void dma_mbuf_refresh(volatile struct DmaMultiBuffer* buffer) -{ - //no more data to send, stop here - if (buffer->dma_buffer_index == buffer->buffer_index - && byte_index == 0) { - return; - } - - //else start a new transfer - dma_configure(buffer->dma, buffer->channel, buffer->config, buffer->dest, - buffer->buffers[buffer->dma_buffer_index], - dma_byte_index, mbuf_callback, buffer); - - //if the newly transfering buffer was being written to, switch the current - //buffer. Since we just ended a transfer, the next buffer should be empty - if (buffer->dma_buffer_index == buffer->buffer_index) - { - ++buffer->buffer_index; - if (buffer->buffer_index >= buffer->buffer_nb) { - buffer->buffer_index = 0; - } - --buffer->free_buffer_nb; - - byte_index = 0; - } - - return; -} - - -//--local functions------------------------------------------------------------- - -/** - * Callback called on DMA TX tranfert's completion. Checks for any remaining - * data to send. If any, starts a new transfer, else stop the DMA - */ -static void mbuf_callback(enum DmaIRQSource src, volatile void* param) -{ - (void)src; //only transfer complete expected - volatile struct DmaMultiBuffer* buffer = param; - - //increment DMA's buffer since the last once has already been sent - ++buffer->dma_buffer_index; - if (buffer->dma_buffer_index >= buffer->buffer_nb) { - buffer->dma_buffer_index = 0; - } - ++buffer->free_buffer_nb; - - dma_mbuf_refresh(param); -} - diff --git a/drv/dma_mbuf.h b/drv/dma_mbuf.h deleted file mode 100644 index 4ab6952..0000000 --- a/drv/dma_mbuf.h +++ /dev/null @@ -1,78 +0,0 @@ -/** @file dma_mbuf.h - * Module handling Direct Memory Access controller's multibuffer system - * - * The module provides a convenient tool to send data to a peripheral in a - * buffered, low-altency, low-cpu usage, non-blocking way - */ - -#ifndef _DMA_MBUF_H_ -#define _DMA_MBUF_H_ - -//--includes-------------------------------------------------------------------- - -#include "dma.h" - - -//--type definitions------------------------------------------------------------ - -/** - * Struct used by the multibuffer system. This system allows bufferized writes - * to a peripheral with no latency, minimal cpu usage and customisable buffer - * sizes - */ -struct DmaMultiBuffer { - void** buffers; //list of buffers to write to - volatile void* dest; //destination peripheral register - - uint16_t buffer_size; //size of a single buffer - - uint8_t buffer_nb; //total number of buffers - uint8_t free_buffer_nb; //number of buffers not currently used - uint8_t buffer_index; //index of the current buffer - uint8_t dma_buffer_index; //index of the DMA's current buffer - - enum DmaPeriph dma; //DMA peripheral, must correspond to peripheral - enum DmaChannel channel; //DMA channel, must correspond to peripheral - enum DmaConfig config; //DMA config, must correspond to peripheral -}; - - -//--functions------------------------------------------------------------------- - -/** - * Configure a DMA multibuffer for a single DMA channel. A list of buffers is - * used to allow concurent write and DMA tranfers to the specified destination - * wich must be a peripheral. The DMA's priority is also given as parameters. - * The peripheral's specific configuration must be handled separately. 2 bytes - * are reserved in each buffer for index storage. - * - * This system needs to be started manually: dma_mbuf_refresh() should be called - * whenever a DMA transfer can be started. This can be done manually after - * filling up the buffer. Transfers will then automatically be started as long - * as there are bytes in the buffer. See the usart module for an usage exemple - */ -void dma_mbuf_configure(volatile struct DmaMultiBuffer* buffer, void** buffers, - volatile void* dest, uint16_t buffer_size, uint8_t buffer_nb, - enum DmaPeriph dma, enum DmaChannel channel, enum DmaConfig priority); - -/** - * Write the given byte to the given buffer. Returns 0 if the write operation - * was successfull, 1 otherwise. The function is non-blocking. - * - * Note: calling this function will not trigger a DMA transfer, see - * dma_mbuf_refresh() to do that - */ -uint32_t dma_mbuf_write_byte(volatile struct DmaMultiBuffer* buffer, - uint8_t byte); - -/** - * Refresh the buffer state, checking for bytes to send and triggering a DMA - * transfer if necessary. Should be called for the first transfer, any remaining - * transfer will be handled automatically until there are no bytes left in the - * buffer - */ -void dma_mbuf_refresh(volatile struct DmaMultiBuffer* buffer); - - -#endif //_DMA_MBUF_H_ - diff --git a/drv/usart.c b/drv/usart.c index ae53d66..25167a2 100644 --- a/drv/usart.c +++ b/drv/usart.c @@ -8,14 +8,11 @@ //--includes-------------------------------------------------------------------- #include "usart.h" -#include "nvic.h" #include "usart_regs.h" #include "reg.h" #include "rcc.h" #include "dma.h" -#include "dma_mbuf.h" -#include "dma_cbuf.h" #include "stddef.h" @@ -27,19 +24,58 @@ static void configure_usart(volatile struct USART* regs, static void configure_baudrate(volatile struct USART* regs, uint32_t clock, uint32_t baudrate); +#define DMA_CONFIG (DMA_CONFIG_PSIZE_8BITS) + //--local variables------------------------------------------------------------- -static volatile struct USART* const usart1 = (struct USART*)USART1_BASE_ADDRESS; -static volatile struct USART* const usart2 = (struct USART*)USART2_BASE_ADDRESS; -static volatile struct USART* const usart3 = (struct USART*)USART3_BASE_ADDRESS; +static volatile struct USART* const usarts[] = { + (struct USART*)USART1_BASE_ADDRESS, + (struct USART*)USART2_BASE_ADDRESS, + (struct USART*)USART3_BASE_ADDRESS, +}; -static volatile struct DmaCircBuffer usart1_rx_buffer; -static volatile struct DmaMultiBuffer usart1_tx_buffer; -static volatile struct DmaCircBuffer usart2_rx_buffer; -static volatile struct DmaMultiBuffer usart2_tx_buffer; -static volatile struct DmaCircBuffer usart3_rx_buffer; -static volatile struct DmaMultiBuffer usart3_tx_buffer; +static const struct DmaParam usarts_rx_param[] = { + { + (void*)&usarts[USART_PERIPH_1]->DR, + DMA_CONFIG, + DMA_PERIPH_1, + DMA_CHANNEL_5, + }, + { + (void*)&usarts[USART_PERIPH_2]->DR, + DMA_CONFIG, + DMA_PERIPH_1, + DMA_CHANNEL_6, + }, + { + (void*)&usarts[USART_PERIPH_3]->DR, + DMA_CONFIG, + DMA_PERIPH_1, + DMA_CHANNEL_3, + }, +}; + +static const struct DmaParam usarts_tx_param[] = { + { + (void*)&usarts[USART_PERIPH_1]->DR, + DMA_CONFIG, + DMA_PERIPH_1, + DMA_CHANNEL_4, + }, + { + (void*)&usarts[USART_PERIPH_2]->DR, + DMA_CONFIG, + DMA_PERIPH_1, + DMA_CHANNEL_7, + }, + { + (void*)&usarts[USART_PERIPH_3]->DR, + DMA_CONFIG, + DMA_PERIPH_1, + DMA_CHANNEL_2, + }, +}; //--public functions------------------------------------------------------------ @@ -53,161 +89,77 @@ void usart_configure(enum UsartPeriph periph, enum UsartConfig config, switch (periph) { case USART_PERIPH_1: rcc_enable(RCC_AHB_NONE, RCC_APB1_NONE, RCC_APB2_USART); - configure_baudrate(usart1, clocks.apb2_freq, baudrate); - configure_usart(usart1, config); - usart1_tx_buffer.buffers = NULL; - usart1_rx_buffer.buffer = NULL; + configure_baudrate(usarts[USART_PERIPH_1], clocks.apb2_freq, + baudrate); + configure_usart(usarts[USART_PERIPH_1], config); break; case USART_PERIPH_2: rcc_enable(RCC_AHB_NONE, RCC_APB1_USART2, RCC_APB2_NONE); - configure_baudrate(usart2, clocks.apb1_freq, baudrate); - configure_usart(usart2, config); - usart2_tx_buffer.buffers = NULL; - usart2_rx_buffer.buffer = NULL; + configure_baudrate(usarts[USART_PERIPH_2], clocks.apb1_freq, + baudrate); + configure_usart(usarts[USART_PERIPH_2], config); break; case USART_PERIPH_3: rcc_enable(RCC_AHB_NONE, RCC_APB1_USART3, RCC_APB2_NONE); - configure_baudrate(usart3, clocks.apb1_freq, baudrate); - configure_usart(usart3, config); - usart3_tx_buffer.buffers = NULL; - usart3_rx_buffer.buffer = NULL; + configure_baudrate(usarts[USART_PERIPH_3], clocks.apb1_freq, + baudrate); + configure_usart(usarts[USART_PERIPH_3], config); break; default: break; } } -uint32_t usart_write_byte(enum UsartPeriph periph, uint8_t byte) -{ - volatile struct USART* regs; - volatile struct DmaMultiBuffer* buffer; - enum NvicIrq irq; - - switch (periph) { - case USART_PERIPH_1: - regs = usart1; - buffer = &usart1_tx_buffer; - irq = NVIC_IRQ_USART1; - break; - case USART_PERIPH_2: - regs = usart2; - buffer = &usart2_tx_buffer; - irq = NVIC_IRQ_USART2; - break; - case USART_PERIPH_3: - regs = usart3; - buffer = &usart3_tx_buffer; - irq = NVIC_IRQ_USART3; - break; - default: - return 1; - break; - } - - if (buffer->buffers) { - //if the tx register is empty, there is no need to go through the dma - if (regs->SR.TC) { - reg_write(regs->DR, USART_DR_DR, byte); - //enable IRQ, disable DMA - reg_reset(regs->CR3, USART_CR3_DMAT); - reg_set(regs->CR1, USART_CR1_TXEIE); - nvic_enable(irq); - return 0; - } else { - return dma_mbuf_write_byte(buffer, byte); - } - } else { - //only write data if the tx register it empty, give up otherwise - if (regs->SR.TXE) { - reg_write(regs->DR, USART_DR_DR, byte); - return 0; - } else { - return 1; - } - } -} - uint32_t usart_read_byte(enum UsartPeriph periph, uint8_t* byte) { - volatile struct USART* regs; - volatile struct DmaCircBuffer* buffer; - - switch (periph) { - case USART_PERIPH_1: - regs = usart1; - buffer = &usart1_rx_buffer; - break; - case USART_PERIPH_2: - regs = usart2; - buffer = &usart2_rx_buffer; - break; - case USART_PERIPH_3: - regs = usart3; - buffer = &usart3_rx_buffer; - break; - default: - return 1; - break; + if (periph > USART_PERIPH_3) { + return 1; } - if (buffer->buffer) { - return dma_cbuf_read_byte(buffer, byte); + if (usarts[periph]->SR.RXNE) { + *byte = usarts[periph]->DR.DR; + return 0; } else { - if (regs->SR.RXNE) { - *byte = regs->DR.DR; - return 0; - } else { - return 1; - } + return 1; } } -void usart_set_tx_buffer(enum UsartPeriph periph, uint8_t** buffers, - uint16_t buffer_size, uint8_t buffer_nb, enum DmaConfig priority) +uint32_t usart_write_byte(enum UsartPeriph periph, uint8_t byte) { - switch (periph) { - case USART_PERIPH_1: - dma_mbuf_configure(&usart1_tx_buffer, (void**)buffers, &usart1->DR, - buffer_size, buffer_nb, DMA_PERIPH_1, DMA_CHANNEL_4, - priority); - break; - case USART_PERIPH_2: - dma_mbuf_configure(&usart2_tx_buffer, (void**)buffers, &usart2->DR, - buffer_size, buffer_nb, DMA_PERIPH_1, DMA_CHANNEL_7, - priority); - break; - case USART_PERIPH_3: - dma_mbuf_configure(&usart3_tx_buffer, (void**)buffers, &usart3->DR, - buffer_size, buffer_nb, DMA_PERIPH_1, DMA_CHANNEL_2, - priority); - break; + if (periph > USART_PERIPH_3) { + return 1; + } + + //only write data if the tx register it empty, give up otherwise + if (usarts[periph]->SR.TXE) { + reg_write(usarts[periph]->DR, USART_DR_DR, byte); + return 0; + } else { + return 1; } } -void usart_set_rx_buffer(enum UsartPeriph periph, uint8_t* buffer, - uint16_t size, enum DmaConfig priority) +const struct DmaParam* usart_configure_rx_dma(enum UsartPeriph periph) { - switch (periph) { - case USART_PERIPH_1: - dma_cbuf_configure(&usart1_rx_buffer, buffer, (void*)&usart1->DR, - size, DMA_PERIPH_1, DMA_CHANNEL_5, priority); - reg_set(usart1->CR3, USART_CR3_DMAR); - break; - case USART_PERIPH_2: - dma_cbuf_configure(&usart2_rx_buffer, buffer, (void*)&usart2->DR, - size, DMA_PERIPH_1, DMA_CHANNEL_6, priority); - reg_set(usart2->CR3, USART_CR3_DMAR); - break; - - case USART_PERIPH_3: - dma_cbuf_configure(&usart3_rx_buffer, buffer, (void*)&usart3->DR, - size, DMA_PERIPH_1, DMA_CHANNEL_3, priority); - reg_set(usart3->CR3, USART_CR3_DMAR); - - break; + if (periph > USART_PERIPH_3) { + return nullptr; } + + reg_set(usarts[periph]->CR3, USART_CR3_DMAR); + return &usarts_rx_param[periph]; } +const struct DmaParam* usart_configure_tx_dma(enum UsartPeriph periph) +{ + if (periph > USART_PERIPH_3) { + return nullptr; + } + + reg_set(usarts[periph]->CR3, USART_CR3_DMAT); + return &usarts_tx_param[periph]; +} + + //--local functions------------------------------------------------------------- /** @@ -293,7 +245,7 @@ static void configure_usart(volatile struct USART* regs, /** * Configure the given registers with the given baudrate. Baudrate is dependant - * on the peripheric's clock and may not be exact due to precision errors (see + * on the peripheral's clock and may not be exact due to precision errors (see * table 192 in documentation) */ static void configure_baudrate(volatile struct USART* regs, uint32_t clock, @@ -309,39 +261,3 @@ static void configure_baudrate(volatile struct USART* regs, uint32_t clock, reg_write(regs->BRR, USART_BRR_DIV_Fraction, divider & 0xF); } - -//--ISRs------------------------------------------------------------------------ - -void hdr_usart1(void) -{ - //disable the interrupt. It will be reenabled on a write if needed - nvic_clear_pending(NVIC_IRQ_USART1); - nvic_disable(NVIC_IRQ_USART1); - reg_reset(usart1->CR1, USART_CR1_TXEIE); - reg_set(usart1->CR3, USART_CR3_DMAT); - - dma_mbuf_refresh(&usart1_tx_buffer); -} - -void hdr_usart2(void) -{ - //disable the interrupt. It will be reenabled on a write if needed - nvic_clear_pending(NVIC_IRQ_USART2); - nvic_disable(NVIC_IRQ_USART2); - reg_reset(usart2->CR1, USART_CR1_TXEIE); - reg_set(usart2->CR3, USART_CR3_DMAT); - - dma_mbuf_refresh(&usart2_tx_buffer); -} - -void hdr_usart3(void) -{ - //disable the interrupt. It will be reenabled on a write if needed - nvic_clear_pending(NVIC_IRQ_USART3); - nvic_disable(NVIC_IRQ_USART3); - reg_reset(usart3->CR1, USART_CR1_TXEIE); - reg_set(usart3->CR3, USART_CR3_DMAT); - - dma_mbuf_refresh(&usart3_tx_buffer); -} - diff --git a/drv/usart.h b/drv/usart.h index 8739e63..960bf47 100644 --- a/drv/usart.h +++ b/drv/usart.h @@ -17,12 +17,19 @@ //--type definitions------------------------------------------------------------ +/** + * Available USART peripherals. Note that some of these peripherals may not be + * available on all chips + */ enum UsartPeriph { USART_PERIPH_1, USART_PERIPH_2, USART_PERIPH_3, }; +/** + * Available configuration options + */ enum UsartConfig { USART_CONFIG_7E1, USART_CONFIG_7E2, @@ -39,20 +46,65 @@ enum UsartConfig { //--functions------------------------------------------------------------------- +/** + * Configures the given USART peripheral using the provided condiguration + * options and baudrate. The baudrate may be any value supported by the + * peripheral, though some may not be exact due to precision errors (see + * table 192 in documentation). The baudrate is dependant on the peripheral's + * clock and changes to the later after this function has been called will cause + * the effective baudrate to change + * + * This function doesn't configure the required ports. This should be done using + * the gpio driver: + * - Tx port should be using GPIO_CONFIG_OUT_ALT_PUSH_PULL with + * the appropriate output speed based on the baurate (see GpioMode) + * - Rx port should be using GPIO_CONFIG_IN_FLOATING in input mode + * Both ports do not need to be configured if not used (e.g. if only using Tx, + * the Rx port can be left unconfigured) + */ void usart_configure(enum UsartPeriph periph, enum UsartConfig config, uint32_t baudrate); +/** + * Resets the given USART peripheral, applying the default configuration and + * disabling it + */ void usart_reset(enum UsartPeriph periph); -uint32_t usart_write_byte(enum UsartPeriph periph, uint8_t byte); - +/** + * Reads a single byte to the given USART peripheral, returning 0 if + * successfull, 1 otherwise. + * + * The Rx port must be configured for this function to ever return successfully + */ uint32_t usart_read_byte(enum UsartPeriph periph, uint8_t* byte); -void usart_set_tx_buffer(enum UsartPeriph periph, uint8_t** buffers, - uint16_t buffer_size, uint8_t buffer_nb, enum DmaConfig priority); +/** + * Writes a single byte to the given USART peripheral, returning 0 if + * successfull, 1 otherwise. + * + * The Tx port must be configured for this function to do anything, though the + * function would still return 0 + */ +uint32_t usart_write_byte(enum UsartPeriph periph, uint8_t byte); -void usart_set_rx_buffer(enum UsartPeriph periph, uint8_t* buffer, - uint16_t size, enum DmaConfig priority); +/** + * Configures the given USART peripheral for DMA Rx operations, returning the + * corresponding DMA parameters to be used. + * + * The DMA must be configured separately using the DMA driver or an existing + * service + */ +const struct DmaParam* usart_configure_rx_dma(enum UsartPeriph periph); + +/** + * Configures the given USART peripheral for DMA Rx operations, returning the + * corresponding DMA parameters to be used. + * + * The DMA must be configured separately using the DMA driver or an existing + * service + */ +const struct DmaParam* usart_configure_tx_dma(enum UsartPeriph periph); #endif //_USART_H_ diff --git a/drv/dma_cbuf.c b/srv/dma_cbuf.c similarity index 80% rename from drv/dma_cbuf.c rename to srv/dma_cbuf.c index f324d20..22f1fe4 100644 --- a/drv/dma_cbuf.c +++ b/srv/dma_cbuf.c @@ -12,9 +12,13 @@ //--local definitions----------------------------------------------------------- +/** + * DMA configuration to be used for the buffer. Additionnal configuration may be + * added by the peripherals used + */ #define DMA_CONFIG (DMA_CONFIG_IRQ_COMPLETE | DMA_CONFIG_FROM_PERIPH \ | DMA_CONFIG_CIRCULAR | DMA_CONFIG_INC_MEM \ - | DMA_CONFIG_PSIZE_8BITS | DMA_CONFIG_MSIZE_8BITS) + | DMA_CONFIG_MSIZE_8BITS) static void cbuf_callback(enum DmaIRQSource src, volatile void* param); @@ -24,25 +28,25 @@ static void cbuf_callback(enum DmaIRQSource src, volatile void* param); //--public functions------------------------------------------------------------ void dma_cbuf_configure(volatile struct DmaCircBuffer* buffer, - volatile void* raw_buffer, volatile void* src, uint16_t buffer_size, - enum DmaPeriph dma, enum DmaChannel channel, enum DmaConfig priority) + const struct DmaParam* param, enum DmaConfig priority, + volatile void* raw_buffer, uint16_t buffer_size) { #warning "check for null ptr" buffer->buffer = raw_buffer; - buffer->src = src; buffer->size = buffer_size; buffer->begin = 0; - buffer->dma = dma; - buffer->channel = channel; - buffer->config = DMA_CONFIG | priority; + buffer->dma = param->dma; + buffer->channel = param->channel; buffer->dma_looped = false; - dma_configure(buffer->dma, buffer->channel, buffer->config, buffer->src, - buffer->buffer, buffer->size, cbuf_callback, buffer); + dma_configure(buffer->dma, buffer->channel, + DMA_CONFIG | param->config | priority, + param->periph, cbuf_callback, buffer); + dma_start(buffer->dma, buffer->channel, buffer->buffer, buffer->size); } uint32_t dma_cbuf_read_byte(volatile struct DmaCircBuffer* buffer, @@ -86,4 +90,3 @@ static void cbuf_callback(enum DmaIRQSource src, volatile void* param) buffer->dma_looped = true; } - diff --git a/drv/dma_cbuf.h b/srv/dma_cbuf.h similarity index 68% rename from drv/dma_cbuf.h rename to srv/dma_cbuf.h index 4221f14..cd21f49 100644 --- a/drv/dma_cbuf.h +++ b/srv/dma_cbuf.h @@ -10,7 +10,7 @@ //--includes-------------------------------------------------------------------- -#include "dma.h" +#include "../drv/dma.h" //--type definitions------------------------------------------------------------ @@ -22,14 +22,12 @@ */ struct DmaCircBuffer { volatile void* buffer; //the buffer to use as a circular buffer - volatile void* src; //source peripheral register uint16_t size; //the size of the buffer uint16_t begin; //pointer to the current begin of the buffer - enum DmaPeriph dma; //DMA peripheral, must correspond to peripheral - enum DmaChannel channel; //DMA channel, must correspond to peripheral - enum DmaConfig config; //DMA config, must correspond to peripheral + enum DmaPeriph dma; //DMA peripheral used for transfers + enum DmaChannel channel; //DMA channel used for transfers bool dma_looped; //whether the DMA looped or not (buffer overflow) }; @@ -40,16 +38,24 @@ struct DmaCircBuffer { /** * Configure a DMA circular buffer for a single DMA channel. A standard buffer * is used a base to construct a circular buffer, supplied in data by DMA - * tranfers from a peripheral. The DMA's priority is given as parameters. + * tranfers from a peripheral. * The peripheral's specific configuration must be handled separately. + * Each peripheral's driver is responsible for providing the DMA configuration + * for said peripheral, though it can be manually given too. Please note that + * multiple peripherals may share the same DMA channel, which will cause issue + * when used with this buffer. * * Once this function is called, the system will start running right away and * will stay running until manually stopped */ void dma_cbuf_configure(volatile struct DmaCircBuffer* buffer, - volatile void* raw_buffer, volatile void* src, uint16_t buffer_size, - enum DmaPeriph dma, enum DmaChannel channel, enum DmaConfig priority); + const struct DmaParam* param, enum DmaConfig priority, + volatile void* raw_buffer, uint16_t buffer_size); +/** + * Reads a bytes from the buffer, returning if the read operation was + * successfull, 1 otherwise. This function is non-blocking. + */ uint32_t dma_cbuf_read_byte(volatile struct DmaCircBuffer* buffer, uint8_t* byte); diff --git a/srv/dma_mbuf.c b/srv/dma_mbuf.c new file mode 100644 index 0000000..4f2255c --- /dev/null +++ b/srv/dma_mbuf.c @@ -0,0 +1,107 @@ +/** @file dma_mbuf.h + * Module handling Direct Memory Access controller's multibuffer system + * + * The module provides a convenient tool to send data to a peripheral in a + * buffered, low-altency, low-cpu usage, non-blocking way + */ + +//--includes-------------------------------------------------------------------- + +#include "dma_mbuf.h" + + +//--local definitions----------------------------------------------------------- + +/** + * DMA configuration to be used for the buffer. Additionnal configuration may be + * added by the peripherals used + */ +#define DMA_CONFIG (DMA_CONFIG_IRQ_COMPLETE | DMA_CONFIG_FROM_MEM \ + | DMA_CONFIG_INC_MEM | DMA_CONFIG_MSIZE_8BITS) + +static void mbuf_callback(enum DmaIRQSource src, volatile void* param); + + +//--local variables------------------------------------------------------------- + +//--public functions------------------------------------------------------------ + +void dma_mbuf_configure(volatile struct DmaMultiBuffer* buffer, + const struct DmaParam* param, enum DmaConfig priority, + void* raw_buffer, uint16_t buffer_size) +{ + #warning "check for null ptr" + + buffer->raw_buffer = raw_buffer; + + buffer->buffer_size = buffer_size / 2; + + buffer->byte_index = 0; + buffer->buffer_index = 0; + buffer->dma = param->dma; + buffer->channel = param->channel; + + buffer->dma_running = false; + + dma_configure(buffer->dma, buffer->channel, + DMA_CONFIG | param->config | priority, + param->periph, mbuf_callback, buffer); +} + +uint32_t dma_mbuf_write_byte(volatile struct DmaMultiBuffer* buffer, + uint8_t byte) +{ + //if the current buffer is full, give up + if (buffer->byte_index >= buffer->buffer_size) { + return 1; + } + + //write the byte + buffer->raw_buffer[buffer->buffer_index * buffer->buffer_size + + buffer->byte_index] = byte; + ++buffer->byte_index; + return 0; +} + +uint32_t dma_mbuf_switch(volatile struct DmaMultiBuffer* buffer) +{ + //no data to send, stop here + if (buffer->byte_index == 0) { + return 0; + } + + //dma already running, give up + if (buffer->dma_running) { + return 1; + } + + //start a new transfer + buffer->dma_running = true; + dma_start(buffer->dma, buffer->channel, + &buffer->raw_buffer[buffer->buffer_index * buffer->buffer_size], + buffer->byte_index); + + buffer->byte_index = 0; + ++buffer->buffer_index; + if (buffer->buffer_index > 1) { + buffer->buffer_index = 0; + } + + return 0; +} + + +//--local functions------------------------------------------------------------- + +/** + * Callback called on DMA TX tranfert's completion. Stops the DMA and notifies + * the buffer that the transfer is done. + */ +static void mbuf_callback(enum DmaIRQSource src, volatile void* param) +{ + (void)src; //only transfer complete expected + volatile struct DmaMultiBuffer* buffer = param; + dma_stop(buffer->dma, buffer->channel); + buffer->dma_running = false; +} + diff --git a/srv/dma_mbuf.h b/srv/dma_mbuf.h new file mode 100644 index 0000000..b5f930c --- /dev/null +++ b/srv/dma_mbuf.h @@ -0,0 +1,76 @@ +/** @file dma_mbuf.h + * Module handling Direct Memory Access controller's multibuffer system + * + * The module provides a convenient tool to send data to a peripheral in a + * buffered, low-latency, low-cpu usage, non-blocking way + */ + +#ifndef _DMA_MBUF_H_ +#define _DMA_MBUF_H_ + +//--includes-------------------------------------------------------------------- + +#include "../drv/dma.h" + + +//--type definitions------------------------------------------------------------ + +/** + * Struct used by the multibuffer system. This system allows bufferized writes + * to a peripheral with controlled latency, minimal cpu usage and customisable + * buffer size + */ +struct DmaMultiBuffer { + uint8_t* raw_buffer; //the buffer to use as a multi-buffer + + uint16_t buffer_size; //the size of the buffer + + uint8_t byte_index; //index of the next byte to be written + uint8_t buffer_index; //index of the buffer being written to + enum DmaPeriph dma; //DMA peripheral used for transfers + enum DmaChannel channel; //DMA channel used for transfers + + bool dma_running; //whether the dam is currently running or not +}; + + +//--functions------------------------------------------------------------------- + +/** + * Configure a DMA multibuffer for a single DMA channel. The given buffer is + * divided into 2 to allow one part to be written to while the other is being + * transfered by the DMA to a peripheral. + * The peripheral's specific configuration must be handled separately. + * Each peripheral's driver is responsible for providing the DMA configuration + * for said peripheral, though it can be manually given too. Please note that + * multiple peripherals may share the same DMA channel, which will cause issue + * when used with this buffer. + * + * This buffer can be written to at any given time using dma_mbuf_write_byte(), + * but its content are only written to the corresponding peripheral when + * dma_mbuf_switch() is called. + */ +void dma_mbuf_configure(volatile struct DmaMultiBuffer* buffer, + const struct DmaParam* param, enum DmaConfig priority, + void* raw_buffer, uint16_t buffer_size); + +/** + * Write the given byte to the given buffer. Returns 0 if the write operation + * was successfull, 1 otherwise. The function is non-blocking. + * + * Note: calling this function will not trigger a DMA transfer, see + * dma_mbuf_switch() to do that + */ +uint32_t dma_mbuf_write_byte(volatile struct DmaMultiBuffer* buffer, + uint8_t byte); + +/** + * Switch the buffers, triggering a DMA transfer while allowing writes to + * continue. This function may fail if a DMA transfer is already running, + * returning 1 + */ +uint32_t dma_mbuf_switch(volatile struct DmaMultiBuffer* buffer); + + +#endif //_DMA_MBUF_H_ + diff --git a/startup.s b/startup.s index 8f48739..cbc3e23 100644 --- a/startup.s +++ b/startup.s @@ -4,7 +4,7 @@ * This startup file provides a reset handler to setup the basics for c to run * (stack pointer, static data, ...) as well as the vector table and the * differents interrupt handlers that go with it. By default, all handlers use - * the default handler wich is simply and infinite loop + * the default handler wich is simply an infinite loop */ .syntax unified @@ -25,7 +25,7 @@ */ .section .text.hdr_reset .weak hdr_reset - .type hdr_reset, %function + .type hdr_reset, %function hdr_reset: ldr r0, = _estack mov sp, r0