Line data Source code
1 : /** 2 : * @file stm32f4_uart2.c 3 : * @brief Implements serial communication over UART2. 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_uart2.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 uart2_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 4824 : void USART2_IRQHandler(void) 35 : { 36 4824 : if (USART2->SR & USART_SR_RXNE) 37 : { 38 : // A received byte is waiting in data register. 39 3012 : uint8_t byte = USART2->DR & 0xFF; 40 3012 : circular_buffer_push_with_overwrite(&rx_ctx, byte); 41 : } 42 : 43 4824 : if (USART2->SR & USART_SR_TXE) 44 : { 45 : // Transmit register is empty. Ready for a new byte. 46 1812 : uint8_t byte = 0; 47 1812 : if (circular_buffer_pop(&tx_ctx, &byte)) 48 : { 49 1809 : USART2->DR = byte; 50 : } 51 : else 52 : { 53 : // Buffer empty — stop TXE interrupt to prevent ISR from firing again 54 3 : USART2->CR1 &= ~USART_CR1_TXEIE; 55 : } 56 : } 57 4824 : } 58 : 59 : /** 60 : * @brief Initialize the UART2. (Connected to the USB) 61 : */ 62 26 : hal_status_t stm32f4_uart2_init() 63 : { 64 : // Prevent multiple initialization 65 26 : if (uart2_initialized) 66 : { 67 1 : return HAL_STATUS_ERROR; 68 : } 69 : 70 25 : if (!circular_buffer_init(&rx_ctx, UART_BUFFER_RX_SIZE) || 71 25 : !circular_buffer_init(&tx_ctx, UART_BUFFER_TX_SIZE)) 72 : { 73 0 : return HAL_STATUS_ERROR; 74 : } 75 : 76 25 : configure_gpio_pins(); 77 25 : configure_uart(); 78 25 : configure_interrupt(); 79 : 80 25 : uart2_initialized = true; // Mark as initialized only after success. 81 25 : return HAL_STATUS_OK; 82 : } 83 : 84 46 : hal_status_t stm32f4_uart2_deinit() 85 : { 86 46 : if (!uart2_initialized) 87 : { 88 22 : return HAL_STATUS_ERROR; 89 : } 90 : 91 : // Disable interrupts 92 24 : USART2->CR1 &= ~(USART_CR1_RXNEIE | USART_CR1_TXEIE); 93 24 : NVIC_DisableIRQ(USART2_IRQn); 94 : 95 : // Disable UART 96 24 : USART2->CR1 &= ~USART_CR1_UE; 97 : 98 : // Disable clock 99 24 : RCC->APB1ENR &= ~RCC_APB1ENR_USART2EN; 100 : 101 24 : uart2_initialized = false; 102 24 : return HAL_STATUS_OK; 103 : } 104 : 105 : /** 106 : * @brief Reads data from UART2 register. 107 : */ 108 13 : hal_status_t stm32f4_uart2_read(uint8_t *data, size_t len, size_t *bytes_read) 109 : { 110 13 : hal_status_t res = HAL_STATUS_ERROR; 111 : 112 13 : if (data && bytes_read && uart2_initialized) 113 : { 114 10 : *bytes_read = 0; 115 10 : res = HAL_STATUS_OK; 116 : 117 10 : uint8_t byte = 0; 118 : 119 : // While there are still bytes in the buffer and we still have space to store them. 120 2943 : while (!circular_buffer_is_empty(&rx_ctx) && *bytes_read < len) 121 : { 122 : // *************************************************** 123 : // CRITICAL_SECTION_ENTER 124 2933 : NVIC_DisableIRQ(USART2_IRQn); 125 2933 : bool popped = circular_buffer_pop(&rx_ctx, &byte); 126 2933 : NVIC_EnableIRQ(USART2_IRQn); 127 : // CRITICAL_SECTION_EXIT 128 : // *************************************************** 129 : 130 2933 : if (popped) 131 : { 132 2933 : data[*bytes_read] = byte; 133 2933 : *bytes_read = *bytes_read + 1; 134 : } 135 : else 136 : { 137 : // Error 138 0 : res = HAL_STATUS_ERROR; 139 0 : break; 140 : } 141 : } 142 : } 143 : 144 13 : return res; 145 : } 146 : 147 : /** 148 : * @brief Writes data to UART2 register. 149 : */ 150 14 : hal_status_t stm32f4_uart2_write(const uint8_t *data, size_t len, size_t *bytes_written) 151 : { 152 14 : hal_status_t res = HAL_STATUS_ERROR; 153 14 : bool push_success = true; 154 : 155 14 : if (uart2_initialized && bytes_written && data && len > 0) 156 : { 157 10 : *bytes_written = 0; 158 2855 : for (size_t i = 0; i < len; i++) 159 : { 160 : // *************************************************** 161 : // CRITICAL_SECTION_ENTER 162 2846 : NVIC_DisableIRQ(USART2_IRQn); 163 2846 : push_success = circular_buffer_push_no_overwrite(&tx_ctx, data[i]); 164 2846 : NVIC_EnableIRQ(USART2_IRQn); 165 : // CRITICAL_SECTION_EXIT 166 : // *************************************************** 167 : 168 2846 : if (push_success) 169 : { 170 2845 : *bytes_written += 1; 171 : } 172 : else 173 : { 174 1 : break; 175 : } 176 : } 177 : 178 : // If bytes were written successfully to buffer, then enable the transmit 179 : // interrupt because those bytes need to be sent out. 180 10 : if (*bytes_written > 0) 181 : { 182 10 : USART2->CR1 |= USART_CR1_TXEIE; // Enable TXE interrupt 183 : } 184 : 185 : // If we successfully wrote all bytes to buffer, then the function was an 186 : // overall success. 187 10 : res = (*bytes_written == len) ? HAL_STATUS_OK : HAL_STATUS_ERROR; 188 : } 189 : 190 14 : return res; 191 : } 192 : 193 25 : static void configure_gpio_pins() 194 : { 195 : // Enable Bus. 196 25 : RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; 197 : 198 : // Set PA2 (usart2 tx pin) mode to alternate function. 199 25 : GPIOA->MODER &= ~BIT_4; 200 25 : GPIOA->MODER |= BIT_5; 201 : 202 : // Set PA3 (usart2 rx pin) mode to alternate function. 203 25 : GPIOA->MODER &= ~BIT_6; 204 25 : GPIOA->MODER |= BIT_7; 205 : 206 : // Set PA2 alternate function type to UART_TX (AF07) 207 25 : GPIOA->AFR[0] &= ~(0xF << (PIN_2 * AF_SHIFT_WIDTH)); // clear bits 11-8 208 25 : GPIOA->AFR[0] |= (AF7_MASK << (PIN_2 * AF_SHIFT_WIDTH)); // set bits 11-8 as 0111 aka AF07 for PA2. 209 : 210 : // Set PA3 alternate function type to UART_RX (AF07) 211 25 : GPIOA->AFR[0] &= ~(0xF << (PIN_3 * AF_SHIFT_WIDTH)); // clear bits 15-12 212 25 : GPIOA->AFR[0] |= (AF7_MASK << (PIN_3 * AF_SHIFT_WIDTH)); // set bits 15-12 as 0111 aka AF07 for PA3. 213 25 : } 214 : 215 25 : static void configure_uart() 216 : { 217 : // Enable the bus. 218 25 : RCC->APB1ENR |= RCC_APB1ENR_USART2EN; 219 : 220 : // Program the M bit in USART_CR1 to define the word length. 221 25 : USART2->CR1 &= ~(USART_CR1_M); 222 : 223 : // Select the desired baud rate using the USART_BRR register. 224 25 : USART2->BRR = stm32f4_hal_compute_uart_bd(APB1_CLK, UART_BAUDRATE); 225 : 226 : // Set the TE bit in USART_CR1 to send an idle frame as first transmission. 227 25 : USART2->CR1 = USART_CR1_TE; // No OR, sets the UART to a default state. 228 25 : USART2->CR1 |= USART_CR1_RE; // Enable receiver bit. 229 : 230 : // Set the CR2 register to a default state. 231 25 : USART2->CR2 = 0; 232 : 233 : // Enable the USART by writing the UE bit in USART_CR1 register to 1. 234 25 : USART2->CR1 |= USART_CR1_UE; 235 25 : } 236 : 237 25 : static void configure_interrupt() 238 : { 239 : // Enable RXNE Interrupt. 240 25 : USART2->CR1 |= USART_CR1_RXNEIE; 241 : 242 : // Enable NVIC Interrupt. 243 25 : NVIC_EnableIRQ(USART2_IRQn); 244 25 : }