UART/USART

moonsyu·2026년 8월 25일

stm32

목록 보기
4/9
post-thumbnail

UART란?

  1. Universal Asynchronous Receiver/Transmitter의 약자
  2. CPU가 다루는 병렬 데이터 한 바이트를 TX 핀에서 한 비트씩 내보내고, RX 핀으로 들어오는 비트열을 다시 바이트로 조립하는 하드웨어 주변장치
  3. 별도의 clock 선을 공유하지 않는 비동기 통신
  4. 따라서 송신기와 수신기는 통신 전에 다음 설정을 동일하게 맞춰야 한다.
    • baud rate
    • data bit 수
    • parity 방식
    • stop bit 수
    • 필요한 경우 RTS/CTS flow control

※ 중요한 점 ※
UART 자체가 센서 패킷의 의미까지 정하지는 않는다.
명령 코드, 헤더, 길이, checksum/CRC, 응답 시간은 각 센서의 제조사가 별도의 프로토콜로 정의한다.

UART는 언제 사용하는가?

배선과 구현이 단순한 point-to-point 직렬 통신에 사용

사용 상황UART가 적합한 이유대표 예
디버그 로그와 명령 콘솔PC terminal에서 바로 확인 가능printf, boot log, CLI
MCU와 통신 모듈 연결TX/RX/GND만으로 구현 가능GNSS, Bluetooth, Wi-Fi/LTE modem
비교적 느린 센서 데이터9600~115200 baud로 충분한 경우가 많음CO₂, 미세먼지, LiDAR
펌웨어 다운로드와 부트로더단순하고 널리 지원됨STM32 system bootloader
두 MCU 간 통신주소가 필요 없는 1:1 연결에 간편main MCU ↔ sub MCU

반대로 다음 상황에서는 다른 인터페이스도 고려해야 한다.

  • 여러 장치를 같은 두 선에 주소로 연결: I²C
  • 더 높은 처리량과 짧은 지연이 필요: SPI
  • 센서가 전압을 직접 출력: ADC
  • 긴 케이블이나 노이즈가 심한 산업 환경: UART 신호를 그대로 연장하지 말고 RS-485 transceiver 사용 검토

UART의 동작 원리

1. 배선

STM32 TX  ─────────>  센서 RX
STM32 RX  <─────────  센서 TX
STM32 GND ──────────  센서 GND

TX와 RX를 서로 교차 연결하고 GND를 공통으로 묶는다. TX와 RX가 분리되어 있으므로 일반 UART는 동시에 송수신할 수 있는 full duplex 통신이다.

센서 전원 전압과 UART 신호 전압은 별개다. 예를 들어 센서 전원이 5 V이더라도 UART 신호는 3.3 V일 수 있다. 반드시 데이터시트의 I/O voltage를 확인해야 한다.

2. 8N1 프레임

Idle(1) │ Start(0) │ D0 D1 D2 D3 D4 D5 D6 D7 │ Stop(1)
  • 유휴 상태의 선로는 High다.
  • Start bit의 High → Low 전환으로 수신기가 프레임 시작을 검출한다.
  • 데이터는 일반적으로 LSB인 D0부터 전송한다.
  • 8N1은 8 data bits, No parity, 1 stop bit를 의미한다.
  • Stop bit가 High가 아니면 framing error가 발생할 수 있다.

115200 baud에서 한 bit 시간은 약 8.68 μs다. 8N1은 한 바이트를 보내는 데 start 1 + data 8 + stop 1, 총 10 bit가 필요하므로 이론적인 payload 상한은 다음과 같다.

115200 bit/s ÷ 10 bit/byte = 11,520 byte/s

실제 센서 패킷에는 헤더와 checksum 등이 포함되므로 유효 데이터율은 이보다 낮다.

3. STM32 내부의 송수신 과정

송신 과정

  1. CPU가 USART_DR에 한 바이트를 기록한다.
  2. USART 하드웨어가 start/data/stop bit를 붙여 TX로 직렬 전송한다.
  3. TXE=1이면 data register가 비어 다음 바이트를 기록할 수 있다.
  4. 마지막 stop bit까지 전송됐는지는 TC=1로 확인한다.

수신 과정

  1. USART가 RX의 start edge를 검출한다.
  2. bit 중앙을 oversampling해 값을 판정한다.
  3. 완성된 바이트를 USART_DR로 옮기고 RXNE=1을 설정한다.
  4. CPU가 USART_DR을 읽으면 RXNE가 해제된다.
  5. 이전 바이트를 읽기 전에 새 바이트가 오면 ORE overrun이 발생한다.

주요 오류 flag

  • STM32F4에서는 오류 flag를 정리할 때 SR을 읽은 뒤 DR을 읽는 순서를 사용한다.
플래그SR 비트의미발생 조건
PEbit 0Parity Error수신한 패리티 값이 계산 결과와 다를 때
FEbit 1Framing ErrorStop bit가 정상적인 1로 감지되지 않을 때
NF (NE)bit 2Noise Error수신 신호에서 잡음이 감지됐을 때
OREbit 3Overrun Error기존 데이터를 읽기 전에 다음 데이터가 도착했을 때

STM32F446에서 OVER8=0은 16배 oversampling, OVER8=1은 8배 oversampling이다. 16배 방식은 clock 오차 허용 범위가 더 넓고, 8배 방식은 더 높은 최대 baud rate를 허용한다. ONEBIT=0이면 bit 중앙의 세 sample을 majority vote해 잡음을 판정한다.

UART와 USART의 차이

  • USART는 Universal Synchronous/Asynchronous Receiver/Transmitter의 약자
  • UART의 비동기 통신에 동기식 기능이 추가된 주변장치
  • CubeMX에서 USART1/2/3/6을 Asynchronous로 설정하면 실제 배선과 사용 방식은 일반 UART와 동일하다.
  • synchronous mode는 CK를 출력하는 master 방식이며 완전한 범용 SPI slave와 같은 기능은 아니다.

STM32F446 기준 비교표

구분UART4, UART5USART1, USART2, USART3, USART6
정식 명칭Universal Asynchronous Receiver/TransmitterUniversal Synchronous/Asynchronous Receiver/Transmitter
비동기 통신지원UART와 동일
동기 통신미지원지원, master-only
비동기 기본 배선TX, RX, GNDUART와 동일
RTS/CTS 사용 시 배선TX, RX, RTS, CTS, GNDUART와 동일
동기 통신 배선해당 없음TX, RX, CK, GND
비동기 프레임start + data + parity(선택) + stopUART와 동일
데이터 길이8비트 또는 9비트UART와 동일
Smartcard mode미지원지원
LIN mode지원UART와 동일
IrDA지원UART와 동일
DMA지원UART와 동일
단선 Half-Duplex지원UART와 동일
RTS/CTSUART4, UART5 지원USART1, USART2, USART3, USART6 지원
HAL 비동기 APIHAL_UART_*UART와 동일
HAL 동기 API해당 없음HAL_USART_*

USART가 언제나 UART보다 빠르다는 뜻은 아니다. 최대 baud rate는 peripheral clock과 oversampling 설정에 의해 결정된다.

또 하나의 주의점이 있다. STM32F4에서 parity를 켜면 parity bit가 word length의 MSB 자리를 사용한다. 따라서 8 data + parity가 필요하면 9-bit word length와 parity를 선택해야 한다. 8-bit word length + parity는 실제로 7 data + parity가 된다.

HAL 방식으로 USART2 설정하기

HAL은 register bit와 clock 계산을 driver가 대신 관리한다. HAL_Init()과 SystemClock_Config()를 먼저 실행한 뒤 아래 초기화를 호출한다.

#include "stm32f4xx_hal.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

UART_HandleTypeDef huart2;

bool hal_uart2_init(uint32_t baud)
{
    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_USART2_CLK_ENABLE();

    GPIO_InitTypeDef gpio = {0};
    gpio.Pin       = GPIO_PIN_2 | GPIO_PIN_3;
    gpio.Mode      = GPIO_MODE_AF_PP;
    gpio.Pull      = GPIO_PULLUP;
    gpio.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
    gpio.Alternate = GPIO_AF7_USART2;
    HAL_GPIO_Init(GPIOA, &gpio);

    huart2.Instance          = USART2;
    huart2.Init.BaudRate     = baud;
    huart2.Init.WordLength   = UART_WORDLENGTH_8B;
    huart2.Init.StopBits     = UART_STOPBITS_1;
    huart2.Init.Parity       = UART_PARITY_NONE;
    huart2.Init.Mode         = UART_MODE_TX_RX;
    huart2.Init.HwFlowCtl    = UART_HWCONTROL_NONE;
    huart2.Init.OverSampling = UART_OVERSAMPLING_16;

    return HAL_UART_Init(&huart2) == HAL_OK;
}

bool uart_write_bytes(const uint8_t *data, size_t len, uint32_t timeout_ms)
{
    if (len > UINT16_MAX) return false;
    return HAL_UART_Transmit(&huart2, (uint8_t *)data,
                             (uint16_t)len, timeout_ms) == HAL_OK;
}

bool uart_read_byte(uint8_t *data, uint32_t timeout_ms)
{
    return HAL_UART_Receive(&huart2, data, 1, timeout_ms) == HAL_OK;
}

CubeMX가 이미 MX_USART2_UART_Init()과 HAL_UART_MspInit()을 생성했다면 위 초기화 전체를 중복해서 넣지 말고, 생성된 huart2를 사용해 송수신 wrapper(read, write)만 추가한다.

Bare-Metal 방식으로 USART2 설정하기

GPIO, 클럭, Baud rate, 프레임 설정 등의 기본 초기화는 CubeMX가 생성한 MX_USART2_UART_Init() 사용

따라서 이후 예제에서는 USART2->SR과 USART2->DR을 이용한 송수신 과정만 직접 구현

#include "stm32f4xx_hal.h"
#include <stddef.h>
#include <stdint.h>

/* 1바이트를 DR에 기록 */
static void USART2_WriteByte(uint8_t data)
{
    /* DR이 비워질 때까지 대기 */
    while ((USART2->SR & USART_SR_TXE) == 0U) {
    }

    USART2->DR = data;
}

/* 바이트 배열 송신 */
void USART2_SendBytes(const uint8_t *data, size_t length)
{
    for (size_t i = 0; i < length; i++) {
        USART2_WriteByte(data[i]);
    }

    /* 마지막 Stop bit까지 전송 완료 대기 */
    while ((USART2->SR & USART_SR_TC) == 0U) {
    }
}

/* 문자열 송신 */
void USART2_SendString(const char *str)
{
    while (*str != '\0') {
        USART2_WriteByte((uint8_t)*str);
        str++;
    }

    while ((USART2->SR & USART_SR_TC) == 0U) {
    }
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    MX_GPIO_Init();
    MX_USART2_UART_Init();

    USART2_SendString("Hello UART!\r\n");

    uint8_t command[] = {0xFF, 0x01, 0x86, 0x00};
    USART2_SendBytes(command, sizeof(command));

    while (1) {
    }
}

실제 프로그램에서는 무한 대기 대신 SysTick 기반 timeout을 추가하고, PE/FE/NF/ORE를 확인해야 한다. STM32F4에서 오류를 정리하는 기본 순서는 SR을 읽고 이어서 DR을 읽는 것이다.

NUCLEO-F446RE에서 꼭 확인할 점

NUCLEO-F446RE의 기본 solder bridge 상태는 SB13/SB14 ON, SB62/SB63 OFF다. 따라서 PA2/PA3의 USART2는 on-board ST-LINK Virtual COM Port에 연결되고 Arduino D1/D0 및 Morpho header 쪽에서는 끊겨 있다.

PA2/PA3를 실제 센서 header로 사용하려면 보드 revision을 확인한 뒤 공식 지침에 따라 다음처럼 변경해야 한다.

SB13/SB14: OFF
SB62/SB63: ON

납땜 변경 없이 기본 VCP를 유지하고 싶다면 USART2는 debug console로 남겨두고 센서는 USART1 또는 USART3에 연결하는 구성이 편하다.

0개의 댓글