Repository navigation
Bare metal C Ch10 UART
한 줄 요약: NUCLEO-F439ZI의 ST-LINK VCP에 연결된 USART3를 115200 bps로 설정하고, 1초마다 문자열을 PC 터미널로 출력한다.
UART는 별도의 clock line 없이 TX/RX 선으로 데이터를 주고받는 비동기 직렬 통신이다. 양쪽 장치가 같은 baud rate를 사용해야 하므로 baud-rate 설정이 중요하다.
항목 | 원서 NUCLEO-F411 | NUCLEO-F439ZI 실습 -- | -- | -- ST-LINK VCP | USART2 | USART3 TX | PA2 | PD8 RX | PA3 | PD9 Clock enable | USART2EN | USART3EN, APB1ENR bit18 Alternate Function | AF7 | AF7 AFR | PA2/3 → AFR[0] | PD8/9 → AFR[1]Notion과 실제 코드에서는:
(periph_clk + baudrate / 2U) / baudrate
형태로 정수 나눗셈의 반올림을 구현한다.
APB1 clock = 16 MHz
baud = 115200
BRR ≈ 139
while (!(USART3->SR & SR_TXE)) { }
USART3->DR = data;
CPU가 TXE가 set될 때까지 기다린 뒤 다음 byte를 DR에 쓴다.
GPIOD clock enable
│
▼
PD8 / PD9 → Alternate Function
│
▼
AF7 → USART3
│
▼
USART3 clock enable
│
▼
BRR = 115200 bps 설정
│
▼
TE + UE enable
│
▼
TXE polling
│
▼
DR write
│
▼
ST-LINK VCP → PC terminal
Ch10_uart/
├── Inc/gpio.h
├── Src/gpio.c
├── Inc/systick.h
├── Src/systick.c
├── Inc/uart.h
├── Src/uart.c
├── Src/main.c
syscalls.c,sysmem.c, startup file, linker script, CubeIDE metadata/build output은 Ch04에서 다룬 공통 기반이므로 이 페이지에서는 반복하지 않는다. 아래에는 이 chapter 기능 구현에 직접 사용한 source/header를 전부 기록한다.
#ifndef GPIO_H
#define GPIO_H
#include <stdbool.h>
/* ---- LED (PB0, LD1 녹색) ---- */
void led_init(void);
void led_on(void);
void led_off(void);
/* ---- Push Button (PC13, B1) ---- */
void button_init(void);
bool get_btn_state(void);
void led_toggle(void); /* Ch08에서 추가 */
#endif /* GPIO_H */
#include "stm32f4xx.h"
#include "gpio.h"
#define LED_PIN 0U /* PB0 = LD1 (녹색) /
#define BTN_PIN 13U / PC13 = B1 */
void led_init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;
GPIOB->MODER &= ~(0x3UL << (LED_PIN * 2));
GPIOB->MODER |= (0x1UL << (LED_PIN * 2));
}
void led_on(void)
{
GPIOB->BSRR = (1UL << LED_PIN); /* Set */
}
void led_off(void)
{
GPIOB->BSRR = (1UL << (LED_PIN + 16)); /* Reset */
}
void button_init(void)
{
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN;
GPIOC->MODER &= ~(0x3UL << (BTN_PIN * 2)); /* Input */
GPIOC->PUPDR &= ~(0x3UL << (BTN_PIN * 2)); /* No pull (보드가 자체 처리) */
}
bool get_btn_state(void)
{
return (GPIOC->IDR & (1UL << BTN_PIN)) != 0U; /* active-HIGH */
}
void led_toggle(void)
{
GPIOB->ODR ^= (1UL << 0); /* PB0 = LD1 */
}
#ifndef SYSTICK_H_
#define SYSTICK_H_
#include <stdint.h>
#include "stm32f4xx.h"
void systick_msec_delay(uint32_t delay);
#endif /* SYSTICK_H_ */
#include "systick.h"
#define CTRL_ENABLE (1UL << 0)
#define CTRL_CLKSRC (1UL << 2)
#define CTRL_COUNTFLAG (1UL << 16)
/* 코어클럭 16MHz(기본 HSI) 기준 1ms당 클럭 사이클 수 */
#define ONE_MSEC_LOAD 16000UL
void systick_msec_delay(uint32_t delay)
{
SysTick->LOAD = ONE_MSEC_LOAD - 1UL;
SysTick->VAL = 0UL;
SysTick->CTRL = CTRL_CLKSRC;
SysTick->CTRL |= CTRL_ENABLE;
for (uint32_t i = 0; i < delay; i++)
{
while ((SysTick->CTRL & CTRL_COUNTFLAG) == 0UL) { }
}
SysTick->CTRL = 0UL;
}
#ifndef UART_H
#define UART_H
#include "stm32f4xx.h"
void uart_init(void);
void uart_write_string(const char *s);
int __io_putchar(int ch);
#endif
#include <stdint.h>
#include "uart.h"
#define GPIODEN (1U << 3)
#define USART3EN (1U << 18)
#define DBG_UART_BAUDRATE 115200U
#define SYS_FREQ 16000000U
#define APB1_CLK SYS_FREQ
#define CR1_TE (1U << 3)
#define CR1_UE (1U << 13)
#define SR_TXE (1U << 7)
static void uart_write(int ch);
static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate);
static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate);
int __io_putchar(int ch)
{
uart_write(ch);
return ch;
}
void uart_init(void)
{
RCC->AHB1ENR |= GPIODEN;
GPIOD->MODER &= ~(0x3UL << (8 * 2));
GPIOD->MODER |= (0x2UL << (8 * 2));
GPIOD->MODER &= ~(0x3UL << (9 * 2));
GPIOD->MODER |= (0x2UL << (9 * 2));
GPIOD->AFR[1] &= ~(0xFUL << 0);
GPIOD->AFR[1] |= (0x7UL << 0); /* PD8 -> AF7 (USART3_TX) */
GPIOD->AFR[1] &= ~(0xFUL << 4);
GPIOD->AFR[1] |= (0x7UL << 4); /* PD9 -> AF7 (USART3_RX) */
RCC->APB1ENR |= USART3EN;
uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);
USART3->CR1 = CR1_TE;
USART3->CR1 |= CR1_UE;
}
static void uart_write(int ch)
{
while (!(USART3->SR & SR_TXE)) { }
USART3->DR = (ch & 0xFF);
}
void uart_write_string(const char *s)
{
while (*s) uart_write((int)*s++);
}
static uint16_t compute_uart_bd(uint32_t periph_clk, uint32_t baudrate)
{
return (uint16_t)((periph_clk + (baudrate / 2U)) / baudrate);
}
static void uart_set_baudrate(uint32_t periph_clk, uint32_t baudrate)
{
USART3->BRR = compute_uart_bd(periph_clk, baudrate);
}
#include "gpio.h"
#include "systick.h"
#include "uart.h"
int main(void)
{
led_init();
uart_init();
while (1)
{
uart_write_string("Hello from NUCLEO-F439ZI! (USART3, 115200bps)\r\n");
led_toggle();
systick_msec_delay(1000);
}
}
Notion 기록 기준 실제 보드 검증 완료.
STM32CubeIDE build/download/run 완료
VCP를
115200-8-N-1로 열어 문자열 출력 확인1초마다 문자열 출력과 LD1 toggle 확인
text = 1168 bytes
data = 0 bytes
bss = 2560 bytes
warning 없음
F439ZI VCP는 USART3 PD8/PD9를 사용한다.
GPIO Alternate Function 설정 후 USART clock/BRR/TE/UE를 설정한다.
TXE polling 후 DR에 byte를 기록한다.