This repository has been archived on 2020-06-14. You can view files and clone it, but cannot push or open issues or pull requests.
Lennart Eriksson d8a9adff6d USART implementation
This is an implementation of the USART but the DMA transfer is not
supported, all other functionalities are working including i.e. polling,
regular processor transmit and dma double buffer
2016-09-30 12:20:10 +02:00

400 lines
16 KiB
C

/**************************************************************************
* 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;
}
}