stm32f1xx_HBL/drv/usart.c
Steins7 5e4d87474a Fix major reg bitfield issue
A while back, macros had to be put in place to avoid letting the
compiler directly use the bitfields. This was necessary because the
compiler used strb instruction which only write bytes. On the AHB bus,
byte writes are transformed into word writes by repeating the byte,
which caused mayhem in the registers. After a lot of research, turns out
the packed attribute stops the compiler from does optimal (word) writes
and isn't needed anyway. Removing them fixes the issue
2024-07-10 23:16:49 +02:00

260 lines
5.4 KiB
C

/** @file usart.c
* Module handling Universal Synchronous/Asynchronous Receiver/Transmitter
*
* The module provides functions to configure the usarts and read/write from/to
* it
*/
//--includes--------------------------------------------------------------------
#include "usart.h"
#include "usart_regs.h"
#include "rcc.h"
#include "dma.h"
#include "stddef.h"
//--local definitions-----------------------------------------------------------
static void configure_usart(volatile struct USART* regs,
enum UsartConfig config);
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 usarts[] = {
(struct USART*)USART1_BASE_ADDRESS,
(struct USART*)USART2_BASE_ADDRESS,
(struct USART*)USART3_BASE_ADDRESS,
};
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------------------------------------------------------------
void usart_configure(enum UsartPeriph periph, enum UsartConfig config,
uint32_t baudrate)
{
struct RccClocks clocks;
rcc_get_clocks(&clocks);
switch (periph) {
case USART_PERIPH_1:
rcc_enable(RCC_AHB_NONE, RCC_APB1_NONE, RCC_APB2_USART);
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(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(usarts[USART_PERIPH_3], clocks.apb1_freq,
baudrate);
configure_usart(usarts[USART_PERIPH_3], config);
break;
default:
break;
}
}
uint32_t usart_read_byte(enum UsartPeriph periph, uint8_t* byte)
{
if (periph > USART_PERIPH_3) {
return 1;
}
if (usarts[periph]->SR.RXNE) {
*byte = usarts[periph]->DR.DR;
return 0;
} else {
return 1;
}
}
uint32_t usart_write_byte(enum UsartPeriph periph, uint8_t byte)
{
if (periph > USART_PERIPH_3) {
return 1;
}
//only write data if the tx register it empty, give up otherwise
if (usarts[periph]->SR.TXE) {
usarts[periph]->DR.DR = byte;
return 0;
} else {
return 1;
}
}
const struct DmaParam* usart_configure_rx_dma(enum UsartPeriph periph)
{
if (periph > USART_PERIPH_3) {
return nullptr;
}
usarts[periph]->CR3.DMAR = 1;
return &usarts_rx_param[periph];
}
const struct DmaParam* usart_configure_tx_dma(enum UsartPeriph periph)
{
if (periph > USART_PERIPH_3) {
return nullptr;
}
usarts[periph]->CR3.DMAT = 1;
return &usarts_tx_param[periph];
}
//--local functions-------------------------------------------------------------
/**
* Apply the given configuration to the given registers. Generic version of
* usart_configure()
*/
static void configure_usart(volatile struct USART* regs,
enum UsartConfig config)
{
//configure parity
switch (config)
{
case USART_CONFIG_7E1:
case USART_CONFIG_8E1:
case USART_CONFIG_7E2:
case USART_CONFIG_8E2:
regs->CR1.PCE = 1;
regs->CR1.PS = 0;
break;
case USART_CONFIG_7O1:
case USART_CONFIG_7O2:
case USART_CONFIG_8O1:
case USART_CONFIG_8O2:
regs->CR1.PCE = 1;
regs->CR1.PS = 1;
break;
case USART_CONFIG_8N1:
case USART_CONFIG_8N2:
regs->CR1.PCE = 0;
break;
default:
break;
}
//configure bit number
switch (config)
{
case USART_CONFIG_7E1:
case USART_CONFIG_7E2:
case USART_CONFIG_7O1:
case USART_CONFIG_7O2:
case USART_CONFIG_8N1:
case USART_CONFIG_8N2:
regs->CR1.M = 0;
break;
case USART_CONFIG_8E2:
case USART_CONFIG_8E1:
case USART_CONFIG_8O1:
case USART_CONFIG_8O2:
regs->CR1.M = 1;
break;
default:
break;
}
//configure stop bits
switch (config)
{
case USART_CONFIG_7E1:
case USART_CONFIG_7O1:
case USART_CONFIG_8N1:
case USART_CONFIG_8E1:
case USART_CONFIG_8O1:
regs->CR2.STOP = 0;
break;
case USART_CONFIG_7E2:
case USART_CONFIG_7O2:
case USART_CONFIG_8N2:
case USART_CONFIG_8E2:
case USART_CONFIG_8O2:
regs->CR2.STOP = 2;
break;
default:
break;
}
//enable Rx/Tx
regs->CR1.TE = 1;
regs->CR1.RE = 1;
regs->CR1.UE = 1;
}
/**
* Configure the given registers with the given baudrate. Baudrate is dependant
* 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,
uint32_t baudrate)
{
uint32_t mantissa = clock / (baudrate * 16);
uint32_t factor = clock / baudrate;
volatile uint32_t divider = factor - (mantissa * 16);
regs->BRR.DIV_Mantissa = mantissa & 0xFFF;
regs->BRR.DIV_Fraction = divider & 0xF;
}