Skip to content

Bare metal C Ch10 UART

Hongjayeong-02 edited this page Sep 21, 2026 · 2 revisions

Ch10. UART (Universal Asynchronous Receiver/Transmitter)

한 줄 요약: NUCLEO-F439ZI의 ST-LINK VCP에 연결된 USART3를 115200 bps로 설정하고, 1초마다 문자열을 PC 터미널로 출력한다.

1. 핵심 이론

UART는 별도의 clock line 없이 TX/RX 선으로 데이터를 주고받는 비동기 직렬 통신이다. 양쪽 장치가 같은 baud rate를 사용해야 하므로 baud-rate 설정이 중요하다.

원서 보드와 F439ZI의 차이

항목 | 원서 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]

Baud rate

Notion과 실제 코드에서는:

(periph_clk + baudrate / 2U) / baudrate

형태로 정수 나눗셈의 반올림을 구현한다.

APB1 clock = 16 MHz
baud       = 115200
BRR        ≈ 139

Polling 송신

while (!(USART3->SR & SR_TXE)) { }
USART3->DR = data;

CPU가 TXE가 set될 때까지 기다린 뒤 다음 byte를 DR에 쓴다.

2. 동작 흐름

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

3. 실제 프로젝트 구조


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를 전부 기록한다.

4. 전체 코드

Inc/gpio.h

#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 */

Src/gpio.c

#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-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (LED_PIN * 2));
GPIOB-&gt;MODER |=  (0x1UL &lt;&lt; (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-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (BTN_PIN * 2));  /* Input */
GPIOC-&gt;PUPDR &amp;= ~(0x3UL &lt;&lt; (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 */ }

Inc/systick.h

#ifndef SYSTICK_H_
#define SYSTICK_H_

#include <stdint.h> #include "stm32f4xx.h"

void systick_msec_delay(uint32_t delay);

#endif /* SYSTICK_H_ */

Src/systick.c

#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 &lt; delay; i++)
{
    while ((SysTick-&gt;CTRL &amp; CTRL_COUNTFLAG) == 0UL) { }
}

SysTick-&gt;CTRL = 0UL;

}

Inc/uart.h

#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

Src/uart.c

#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-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (8 * 2));
GPIOD-&gt;MODER &amp;= ~(0x3UL &lt;&lt; (9 * 2));
GPIOD-&gt;MODER |=  (0x2UL &lt;&lt; (9 * 2));

GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 0);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 0);   /* PD8 -&gt; AF7 (USART3_TX) */
GPIOD-&gt;AFR[1] &amp;= ~(0xFUL &lt;&lt; 4);
GPIOD-&gt;AFR[1] |=  (0x7UL &lt;&lt; 4);   /* PD9 -&gt; AF7 (USART3_RX) */

RCC-&gt;APB1ENR |= USART3EN;

uart_set_baudrate(APB1_CLK, DBG_UART_BAUDRATE);

USART3-&gt;CR1 = CR1_TE;
USART3-&gt;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); }

Src/main.c

#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);
}

}

5. 실습 결과

Notion 기록 기준 실제 보드 검증 완료.

  • STM32CubeIDE build/download/run 완료

  • VCP를 115200-8-N-1로 열어 문자열 출력 확인

  • 1초마다 문자열 출력과 LD1 toggle 확인

text = 1168 bytes
data = 0 bytes
bss  = 2560 bytes
warning 없음

6. 핵심 정리

  • F439ZI VCP는 USART3 PD8/PD9를 사용한다.

  • GPIO Alternate Function 설정 후 USART clock/BRR/TE/UE를 설정한다.

  • TXE polling 후 DR에 byte를 기록한다.

Clone this wiki locally