/************************************************************************** * NAME: usart.c * AUTHOR: Lennart Eriksson * PURPOSE: USART implementation for sending data * INFORMATION: * This file includes 2 types of USARTS, regular polling or DMA based * the polling version of the USART uses the processor in order to get * messages while the DMA has Direct Memory Access and does not need the * processor to receive the messages and can copy the entire message once. * The DMA is implemented using a double buffer with fixed sizes of the * buffers in order to work with fixed data sizes of the messages. The up * side of this is that the system can read a complete message and not * where the DMA is not writing on the same place. Though this means that * the message sizes needs to be know on before hand * * GLOBAL VARIABLES: * Variable Type Description * -------- ---- ----------- **************************************************************************/ #include "drivers/usart.h" #include "stm32f4xx_revo.h" #include "drivers/system_clock.h" //Define registers for the USART since the HAL library does not work with sending //data at the moment // CR1 #define UE 0x2000 // Usart Enabled #define M 0x0000 // word length 8 #define RE 0x0004 // Receive enabled #define TE 0x0008 // Transmit enabled #define PARITY_OFFSET 9 //Offset to parity bits //CR2 #define STOP_OFFSET 12 // offset to stop bits in CR2 //CR3 #define DMAR 0x0040 // Enable DMA rx #define DMAT 0x0080 // Enable DMA tx //BRR #define USART_BRR(_PCLK_, _BAUD_) ((_PCLK_ /(_BAUD_ * 16)) * 16) // Calculate BRR from the desired baud rate /*********************************************************************** * BRIEF: Initialize the USART with DMA reception of messages * INFORMATION: * Initialize the specified USART and enable the DMA for it so that the * messages can be received without utilizing any processor load. This * function returns false if any error occurred during the initialization * and true of everything went well ***********************************************************************/ bool usart_init_dma(USART_TypeDef* usart_inst, // The USART instance to be used, i.e. USART1, USART3 or USART6 for the REVO card usart_dma_profile* profile_out, // The USART profile that will be used when sending or receiving data uint32_t baud_rate, // The baud rate that the USART will communicate with stop_bits stopbits, // The number of stop bits that the USART should have parity parity_mode, // The parity that the USART should have uint32_t dma_rx_buffersize, // The buffer size for the DMA rx buffers uint32_t dma_tx_buffersize) // The buffer size for the DMA tx buffers { // Local variables used when extracting the different USARTs DMA_Stream_TypeDef *dma_rx_instance, *dma_tx_instance; uint32_t channel; // Check if the instance is either USART1, USART3 of USART6 since // those are the only ones available on the REVO card if(usart_inst == USART1) { dma_rx_instance = DMA2_Stream2; dma_tx_instance = DMA2_Stream5; channel = DMA_CHANNEL_4; } else if(usart_inst == USART3) { dma_rx_instance = DMA1_Stream1; dma_tx_instance = DMA1_Stream3; channel = DMA_CHANNEL_4; } else if(usart_inst == USART6) { dma_rx_instance = DMA2_Stream2; dma_tx_instance = DMA2_Stream6; channel = DMA_CHANNEL_5; } else return false; // If any other USART is sent in return false // Enable the correct clock if it has not been enabled already if(__HAL_RCC_DMA2_IS_CLK_DISABLED() && (usart_inst == USART6 || usart_inst == USART1)) __HAL_RCC_DMA2_CLK_ENABLE(); if(__HAL_RCC_DMA1_IS_CLK_DISABLED()&& usart_inst == USART3) __HAL_RCC_DMA1_CLK_ENABLE(); // Initialize the regular usart before adding on the DMA if(!usart_init(usart_inst, &profile_out->usart_pro, baud_rate, stopbits, parity_mode)) return false; // If the initialization did not complete return false // set the USART profile buffers and initialize them to 0x00 for every element profile_out->dma_rx_buffer1 = malloc(sizeof(uint8_t) * dma_rx_buffersize); profile_out->dma_rx_buffer2 = malloc(sizeof(uint8_t) * dma_rx_buffersize); profile_out->dma_tx_buffer = malloc(sizeof(uint8_t) * dma_tx_buffersize); memset(profile_out->dma_rx_buffer1, 0x00, dma_rx_buffersize); memset(profile_out->dma_rx_buffer2, 0x00, dma_rx_buffersize); memset(profile_out->dma_tx_buffer, 0x00, dma_tx_buffersize); // Set the DMA instances in the USART profile profile_out->dma_usart_rx_instance = dma_rx_instance; profile_out->dma_usart_tx_instance = dma_tx_instance; // Enable the DMA on the USARTon register level profile_out->usart_pro.usart_instance->CR3 |= DMAR | DMAT; // This is only a dummy that is used by the DMA linking later on USART_HandleTypeDef usart; usart.Instance = usart_inst; // Set up the DMA handle for the USART rx DMA_HandleTypeDef g_DmaHandle_rx; g_DmaHandle_rx.Instance = dma_rx_instance; // the rx instance g_DmaHandle_rx.Init.Channel = channel; // the rx channel g_DmaHandle_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // set data direction to peripheral to memory g_DmaHandle_rx.Init.PeriphInc = DMA_PINC_DISABLE; // peripheral increment data pointer disabled g_DmaHandle_rx.Init.MemInc = DMA_MINC_ENABLE; // Memory increment data pointer enabled g_DmaHandle_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; // Align data words on the peripheral g_DmaHandle_rx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; // Align data words on the memory g_DmaHandle_rx.Init.Mode = DMA_SxCR_DBM; // Enable Double buffer mode g_DmaHandle_rx.Init.Priority = DMA_PRIORITY_HIGH; // Set the receive priority to high g_DmaHandle_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // Disable fifo mode g_DmaHandle_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL; // Set fifo threshold to half full although this is probably not used g_DmaHandle_rx.Init.MemBurst = DMA_MBURST_SINGLE; // In double buffer mode the burst must always be single g_DmaHandle_rx.Init.PeriphBurst = DMA_PBURST_SINGLE; // In double buffer mode the burst must always be single // Initialize the DMA handle HAL_DMA_Init(&g_DmaHandle_rx); // Link the DMA to the USART instance __HAL_LINKDMA(&usart, hdmarx ,g_DmaHandle_rx); //Set up the DMA handle for the USART tx DMA_HandleTypeDef g_DmaHandle_tx; g_DmaHandle_tx.Instance = dma_tx_instance; g_DmaHandle_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; g_DmaHandle_tx.Init.Channel = channel; HAL_DMA_Init(&g_DmaHandle_tx); __HAL_LINKDMA(&usart, hdmatx ,g_DmaHandle_tx); // g_DmaHandle.Instance = dma_tx_instance; // g_DmaHandle.Init.Direction = DMA_MEMORY_TO_PERIPH; // g_DmaHandle.Init.Mode = 0x00; // g_DmaHandle.Init.FIFOMode = DMA_FIFOMODE_ENABLE; // g_DmaHandle.Init.Priority = DMA_PRIORITY_MEDIUM; // // HAL_DMA_Init(&g_DmaHandle); // // __HAL_LINKDMA(&usart, hdmatx ,g_DmaHandle); //Setup DMA buffers // Disable the DMA, must be done before writing to the addresses below dma_rx_instance->CR &= ~(DMA_SxCR_EN); dma_rx_instance->NDTR = dma_rx_buffersize; // Set the buffer size dma_rx_instance->PAR = (uint32_t)&profile_out->usart_pro.usart_instance->DR; // Set the address to the USART data register dma_rx_instance->M0AR = (uint32_t)profile_out->dma_rx_buffer1; // Set the address to the first DMA buffer dma_rx_instance->M1AR = (uint32_t)profile_out->dma_rx_buffer2; // Set the address to the second DMA buffer dma_rx_instance->CR &= ~(0xF << 11); // Set the data size to 8 bit values //Enable the DMA again to start receiving data from the USART dma_rx_instance->CR |= DMA_SxCR_EN; dma_tx_instance->CR &= ~(DMA_SxCR_EN); dma_tx_instance->NDTR = dma_tx_buffersize; dma_tx_instance->PAR = (uint32_t)&profile_out->usart_pro.usart_instance->DR; dma_tx_instance->M0AR = (uint32_t)profile_out->dma_tx_buffer; dma_tx_instance->CR &= ~(0xF << 11); dma_tx_instance->CR |= DMA_SxCR_EN; return true; // everything went as planned and the USART should be ready to use } /*********************************************************************** * BRIEF: Initialize a regular USART that can be used for polling * INFORMATION: * Initialize the specified USART in order to get polling and regular * transmit of messages to work. If the initialization fails this function * returns false and otherwise it returns true ***********************************************************************/ bool usart_init(USART_TypeDef* usart_inst, // The USART instance to be used, i.e. USART1, USART3 or USART6 for the REVO board usart_profile* profile_out, // The USART profile that will be used when sending or receiving data uint32_t baud_rate, // The baud rate that the USART will communicate with stop_bits stopbits, // The number of stop bits that the USART should have parity parity_mode) // The parity that the USART should have { // set the USART profiles USART instance profile_out->usart_instance = usart_inst; // Local variables used when extracting the different USARTs uint32_t rx_pin, tx_pin, af_func; GPIO_TypeDef* gpioport; // Check if the instance is either USART1, USART3 of USART6 since // those are the only ones available on the REVO card if(usart_inst == USART1) { rx_pin = USART1_RX_PIN; tx_pin = USART1_TX_PIN; af_func = GPIO_AF7_USART1; gpioport = USART1_RX_PORT; //Enable clock if not already enabled if(__HAL_RCC_USART1_IS_CLK_DISABLED()) __HAL_RCC_USART1_CLK_ENABLE(); } else if(usart_inst == USART3) { rx_pin = USART3_RX_PIN; tx_pin = USART3_TX_PIN; af_func = GPIO_AF7_USART3; gpioport = USART3_RX_PORT; //Enable clock if not already enabled if(__HAL_RCC_USART3_IS_CLK_DISABLED()) __HAL_RCC_USART3_CLK_ENABLE(); } else if(usart_inst == USART6) { rx_pin = USART6_RX_PIN; tx_pin = USART6_TX_PIN; af_func = GPIO_AF8_USART6; gpioport = USART6_RX_PORT; //Enable clock if not already enabled if(__HAL_RCC_USART6_IS_CLK_DISABLED()) __HAL_RCC_USART6_CLK_ENABLE(); } else return false;// If any other USART is sent in return false // PIN Initialization for the USART GPIO_InitTypeDef gpio; gpio.Pin = rx_pin | tx_pin; gpio.Pull = GPIO_NOPULL; gpio.Mode = GPIO_MODE_AF_PP; gpio.Speed = GPIO_SPEED_HIGH; gpio.Alternate = af_func; HAL_GPIO_Init(gpioport, &gpio); // USART initialization // This is on register level since the HAL library did not work as expected usart_inst->CR1 = UE | M | RE | TE | (parity_mode << PARITY_OFFSET); usart_inst->CR2 = stopbits << STOP_OFFSET; usart_inst->BRR = USART_BRR(HAL_RCC_GetPCLK2Freq(), baud_rate); return true; // Everything went as planned and the USART is enabled } /*********************************************************************** * BRIEF: Send message over USART * INFORMATION: * Try to send a message over USART, if the time exceeds the specified * timeout the transmit will be stopped. This function returns the number * of bytes that was successfully sent down to the USART bus even if * the timeout was reached before it was done. ***********************************************************************/ uint32_t usart_transmit(usart_profile *profile, // The USART profile to send data from uint8_t* buffer, // The buffer to send uint32_t size, // The size of the buffer to send uint32_t timeout_us) // If time exceeds this microsecond value the function will return { // Calculate at what time the function should stop sending and return if not done uint32_t time_to_leave = clock_get_us() + timeout_us; int i; // Send all messages in the buffer for(i = 0; i < size; i++) { profile->usart_instance->DR = buffer[i]; // Wait for the buffer to be emptied and sent over the usart, if the timeout value is reached leave this function while (!(profile->usart_instance->SR & 0x40) && time_to_leave > clock_get_us()); // If the overrun error is active clear it before continue if(profile->usart_instance->SR & 0x08) { profile->usart_instance->SR; profile->usart_instance->DR; } // if the timeout is reached return the number of bytes that was successfully sent over USART if(time_to_leave <= clock_get_us()) return i; } //return the number of Bytes sent on the USART, this should be the same size as the provided buffer size return i; } /*********************************************************************** * BRIEF: NOT IMPLEMENTED YET * INFORMATION: * Use the usart_transmit function instead with the * usart_dma_profile.usart_pro as input argument instead ***********************************************************************/ void usart_transmit_dma(usart_dma_profile *profile, uint8_t* buffer, uint32_t size) { // this is only a try to get the system working so there is no definite proof that this // is the correct way. This is only kept if it were to be implemented to see what have been // done. /* for(int i = 0; i < size; i++) { profile->dma_tx_buffer[i] = buffer[i]; } profile->dma_usart_tx_instance->CR &= ~(DMA_SxCR_EN); profile->dma_usart_tx_instance->NDTR = size; profile->dma_usart_tx_instance->CR |= DMA_SxCR_EN; */ } /*********************************************************************** * BRIEF: return if the USART has any data to be received in the buffer * INFORMATION: ***********************************************************************/ bool usart_poll_data_ready(usart_profile* profile) { // check if the Read Data Register Not Empty (RXNE) is set if(profile->usart_instance->SR & 0x20) return true; return false; } /*********************************************************************** * BRIEF: Poll messages from the USART * INFORMATION: * Try to get a message from the USART, if the time spent receiving * exceeds the specified timeout the function will return the buffer * that has been received to that point. The function returns the number * of bytes received even if the timeout was exceeded. ***********************************************************************/ uint32_t usart_poll(usart_profile *profile, // The USART profile to receive data from uint8_t* buffer, // The buffer to put the data in uint32_t size, // The expected size of the data uint32_t timeout_us) // If time exceeds this microsecond value the function will return { // Calculate at what time the function should stop sending and return if not done uint32_t time_to_leave = clock_get_us() + timeout_us; int i = 0; // While the timeout is not exceeded and we have data to read run this loop while(time_to_leave > clock_get_us() && i < size) { // Check if data is ready to be received if(usart_poll_data_ready(profile)) { // Copy the data from the data register to the buffer buffer[i++] = profile->usart_instance->DR & 0xFF; // Wait until the status register gets ready again while (profile->usart_instance->SR & 0x20); } } //return the number of bytes received return i; } /*********************************************************************** * BRIEF: Get the DMA buffer that was most recently completed * INFORMATION: * This function will return the buffer that the DMA most recently * completed so that the DMA can continue writing to the second buffer * without interfering with the rest of the system ***********************************************************************/ uint8_t* usart_get_dma_buffer(usart_dma_profile *profile) { // Check which buffer the DMA is writing to at the moment and return the other buffer if(profile->dma_usart_rx_instance->CR & DMA_SxCR_CT) { return profile->dma_rx_buffer1; } else { return profile->dma_rx_buffer2; } }