Line data Source code
1 : /** 2 : * @file stm32f4_uart1.c 3 : * @brief Implements serial communication over UART1. 4 : * 5 : * Copyright (c) 2025 Cory McKiel. 6 : * Licensed under the MIT License. See LICENSE file in the project root. 7 : */ 8 : #ifdef DESKTOP_BUILD 9 : #include "registers.h" 10 : #include "nvic.h" 11 : #else 12 : #include "stm32f4xx.h" 13 : #endif 14 : 15 : #include "stm32f4_hal.h" 16 : #include "stm32f4_uart_util.h" 17 : #include "stm32f4_uart1.h" 18 : #include "circular_buffer.h" 19 : 20 : #define UART_BAUDRATE 115200 21 : 22 : #define UART_BUFFER_RX_SIZE CIRCULAR_BUFFER_MAX_SIZE 23 : #define UART_BUFFER_TX_SIZE CIRCULAR_BUFFER_MAX_SIZE 24 : 25 : static bool uart1_initialized = false; 26 : 27 : static circular_buffer_ctx rx_ctx; 28 : static circular_buffer_ctx tx_ctx; 29 : 30 : static void configure_gpio_pins(); 31 : static void configure_uart(); 32 : static void configure_interrupt(); 33 : 34 4123 : void USART1_IRQHandler(void) 35 : { 36 4123 : if (USART1->SR & USART_SR_RXNE) 37 : { 38 : // A received byte is waiting in data register. 39 2265 : uint8_t byte = USART1->DR & 0xFF; 40 2265 : circular_buffer_push_with_overwrite(&rx_ctx, byte); 41 : } 42 : 43 4123 : if (USART1->SR & USART_SR_TXE) 44 : { 45 : // Transmit register is empty. Ready for a new byte. 46 1858 : uint8_t byte = 0; 47 1858 : if (circular_buffer_pop(&tx_ctx, &byte)) 48 : { 49 1853 : USART1->DR = byte; 50 : } 51 : else 52 : { 53 : // Buffer empty — stop TXE interrupt to prevent ISR from firing again 54 5 : USART1->CR1 &= ~USART_CR1_TXEIE; 55 : } 56 : } 57 4123 : } 58 : 59 34 : hal_status_t stm32f4_uart1_init() 60 : { 61 : // Prevent multiple initialization 62 34 : if (uart1_initialized) 63 : { 64 1 : return HAL_STATUS_ERROR; 65 : } 66 : 67 33 : if (!circular_buffer_init(&rx_ctx, UART_BUFFER_RX_SIZE) || 68 33 : !circular_buffer_init(&tx_ctx, UART_BUFFER_TX_SIZE)) 69 : { 70 0 : return HAL_STATUS_ERROR; 71 : } 72 : 73 33 : configure_gpio_pins(); 74 33 : configure_uart(); 75 33 : configure_interrupt(); 76 : 77 33 : uart1_initialized = true; // Mark as initialized only after success. 78 33 : return HAL_STATUS_OK; 79 : } 80 : 81 51 : hal_status_t stm32f4_uart1_deinit() 82 : { 83 51 : if (!uart1_initialized) 84 : { 85 19 : return HAL_STATUS_ERROR; 86 : } 87 : 88 : // Disable interrupts 89 32 : USART1->CR1 &= ~(USART_CR1_RXNEIE | USART_CR1_TXEIE); 90 32 : NVIC_DisableIRQ(USART1_IRQn); 91 : 92 : // Disable UART 93 32 : USART1->CR1 &= ~USART_CR1_UE; 94 : 95 : // Disable clocks 96 32 : RCC->APB2ENR &= ~RCC_APB2ENR_USART1EN; 97 : 98 32 : uart1_initialized = false; 99 32 : return HAL_STATUS_OK; 100 : } 101 : 102 16 : hal_status_t stm32f4_uart1_read(uint8_t *data, size_t len, size_t *bytes_read) 103 : { 104 16 : hal_status_t res = HAL_STATUS_ERROR; 105 : 106 16 : if (data && bytes_read && uart1_initialized) 107 : { 108 12 : *bytes_read = 0; 109 12 : res = HAL_STATUS_OK; 110 : 111 12 : uint8_t byte = 0; 112 : 113 : // While there are still bytes in the buffer and we still have space to store them. 114 2187 : while (!circular_buffer_is_empty(&rx_ctx) && *bytes_read < len) 115 : { 116 : // *************************************************** 117 : // CRITICAL_SECTION_ENTER 118 2175 : NVIC_DisableIRQ(USART1_IRQn); 119 2175 : bool popped = circular_buffer_pop(&rx_ctx, &byte); 120 : // CRITICAL_SECTION_EXIT 121 2175 : NVIC_EnableIRQ(USART1_IRQn); 122 : // *************************************************** 123 : 124 2175 : if (popped) 125 : { 126 2175 : data[*bytes_read] = byte; 127 2175 : *bytes_read = *bytes_read + 1; 128 : } 129 : else 130 : { 131 : // Error 132 0 : res = HAL_STATUS_ERROR; 133 0 : break; 134 : } 135 : } 136 : } 137 : 138 16 : return res; 139 : } 140 : 141 21 : hal_status_t stm32f4_uart1_write(const uint8_t *data, size_t len, size_t *bytes_written) 142 : { 143 21 : hal_status_t res = HAL_STATUS_ERROR; 144 21 : bool push_success = true; 145 : 146 21 : if (uart1_initialized && bytes_written && data && len > 0) 147 : { 148 15 : *bytes_written = 0; 149 2941 : for (size_t i = 0; i < len; i++) 150 : { 151 : // *************************************************** 152 : // CRITICAL SECTION ENTER 153 2927 : NVIC_DisableIRQ(USART1_IRQn); 154 2927 : push_success = circular_buffer_push_no_overwrite(&tx_ctx, data[i]); 155 2927 : NVIC_EnableIRQ(USART1_IRQn); 156 : // CRITICAL SECTION EXIT 157 : // *************************************************** 158 : 159 2927 : if (push_success) 160 : { 161 2926 : *bytes_written += 1; 162 : } 163 : else 164 : { 165 1 : break; 166 : } 167 : } 168 : 169 : // If bytes were written successfully to buffer, then enable the transmit 170 : // interrupt because those bytes need to be sent out. 171 15 : if (*bytes_written > 0) 172 : { 173 15 : USART1->CR1 |= USART_CR1_TXEIE; // Enable TXE interrupt 174 : } 175 : 176 : // If we successfully wrote all bytes to buffer, then the function was an 177 : // overall success. 178 15 : res = (*bytes_written == len) ? HAL_STATUS_OK : HAL_STATUS_ERROR; 179 : } 180 : 181 21 : return res; 182 : } 183 : 184 33 : static void configure_gpio_pins() 185 : { 186 : // Enable Bus. 187 33 : RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; 188 : 189 : // Set PA9 (usart1 tx pin) mode to alternate function. 190 33 : GPIOA->MODER &= ~BIT_18; 191 33 : GPIOA->MODER |= BIT_19; 192 : 193 : // Set PA10 (usart1 rx pin) mode to alternate function. 194 33 : GPIOA->MODER &= ~BIT_20; 195 33 : GPIOA->MODER |= BIT_21; 196 : 197 : // Set PA9 alternate function type to UART_TX (AF07) 198 : // Pin number is misleading here, because AFR is divided into high and low regs. 199 33 : GPIOA->AFR[1] &= ~(0xF << (PIN_1 * AF_SHIFT_WIDTH)); 200 33 : GPIOA->AFR[1] |= (AF7_MASK << (PIN_1 * AF_SHIFT_WIDTH)); 201 : 202 : // Set PA10 alternate function type to UART_RX (AF07) 203 : // Pin number is misleading here, because AFR is divided into high and low regs. 204 33 : GPIOA->AFR[1] &= ~(0xF << (PIN_2 * AF_SHIFT_WIDTH)); // clear bits 15-12 205 33 : GPIOA->AFR[1] |= (AF7_MASK << (PIN_2 * AF_SHIFT_WIDTH)); // set bits 15-12 as 0111 aka AF07 for PA3. 206 33 : } 207 : 208 33 : static void configure_uart() 209 : { 210 : // Enable the bus. 211 33 : RCC->APB2ENR |= RCC_APB2ENR_USART1EN; 212 : 213 : // Program the M bit in USART_CR1 to define the word length. 214 33 : USART1->CR1 &= ~(USART_CR1_M); 215 : 216 : // Select the desired baud rate using the USART_BRR register. 217 33 : USART1->BRR = stm32f4_hal_compute_uart_bd(APB2_CLK, UART_BAUDRATE); 218 : 219 : // Set the TE bit in USART_CR1 to send an idle frame as first transmission. 220 33 : USART1->CR1 = USART_CR1_TE; // No OR, sets the UART to a default state. 221 33 : USART1->CR1 |= USART_CR1_RE; // Enable receiver bit. 222 : 223 : // Set the CR2 register to a default state. 224 33 : USART1->CR2 = 0; 225 : 226 : // Enable the USART by writing the UE bit in USART_CR1 register to 1. 227 33 : USART1->CR1 |= USART_CR1_UE; 228 33 : } 229 : 230 33 : static void configure_interrupt() 231 : { 232 : // Enable RXNE Interrupt. 233 33 : USART1->CR1 |= USART_CR1_RXNEIE; 234 : 235 : // Enable NVIC Interrupt. 236 33 : NVIC_EnableIRQ(USART1_IRQn); 237 33 : }