[Embedded] UART로 printf() 구현

강지원·2025년 2월 3일

STM32 강의

목록 보기
23/33

1. printf()란?

printf()는 표준 출력을 사용하는 출력함수이다.
임베디드 환경에서는 printf()를 UART 등으로 재정의하여 사용할 수 있다.
따라서, UART를 이용해서 출력을 콘솔 창이 아닌 다른 파일에 하는 것을 할 것이다.

2. 시리얼 통신

1) UART(Universal Asynchronous Receiver Transmit)

UART 통신이란 비동기 모드로서 사용되는 Serial 통신으로,
데이터 송신(TX), 수신(RX) 핀이 하나인 통신이다.
한 번에 1byte씩 보내며 각 bit는 직렬로 전송된다.

클럭신호는 필요 없어서 송신,수신 핀만 필요하고,
데이터 전송 속도는 상대적으로 느리다

2) USART(Universal synchronous Receiver Transmit)

USART 통신이란 동기 모드로서 사용되는 Serial 통신이다.
동기모드에서는 클럭신호가 필요하므로 송수신핀 말고도 추가적인 클럭라인이 필요하다.
데이터 전송속도는 동기모드에서 빠른 전송이 가능하다.

3) Baud Rate와 데이터 전송 속도

  • UART에서 가장 중요한 개념 중 하나는 Baud Rate (전송 속도, bps)임.
  • 일반적인 Baud Rate 값: 9600, 115200, 250000, 500000 등
  • 송신 측과 수신 측이 동일한 Baud Rate로 설정되지 않으면 데이터 수신 오류 발생 가능하다.

Baud Rate 설정 시 주의할 점:

  • 너무 높은 Baud Rate 설정 시 노이즈 영향으로 데이터 손실 가능.
  • 너무 낮은 Baud Rate는 속도를 저하시킴.

디버깅용으로는 115200정도로는 맞춰줘야 함

4) UART 데이터 프레임 구조

Start Bit (1bit)Data Bits (5~9bits)Parity Bit (0~1bit)Stop Bit (1~2bits)

패리티 비트(Parity Bit):

오류 검출을 위한 비트 (Odd / Even / None)

  • None: 패리티 비트 없음
  • Even: 데이터 비트의 1의 개수가 짝수가 되도록 설정
  • Odd: 데이터 비트의 1의 개수가 홀수가 되도록 설정

정지 비트(Stop Bit):

송신 측과 수신 측이 데이터를 구분하는 역할 (1비트 또는 2비트 설정 가능)

5) UART 송수신 방식

폴링(Polling) 방식:

HAL_UART_Transmit(), HAL_UART_Receive()

CPU가 직접 UART 상태를 확인하며 데이터를 송수신하는 방식
쉬운 구현 가능하지만 CPU 사용률 증가

인터럽트(Interrupt) 방식:

HAL_UART_Transmit_IT(), HAL_UART_Receive_IT()

특정 이벤트 발생 시 CPU가 데이터를 처리하는 방식
다른 작업을 수행하면서 데이터 수신 가능

DMA(Direct Memory Access) 방식:

HAL_UART_Transmit_DMA(), HAL_UART_Receive_DMA()

CPU 개입 없이 데이터를 메모리로 직접 송수신
가장 효율적이지만 설정이 복잡함

3. FTDI모듈과 드라이버

1) FTDI232 모듈

FTDI 모듈은 마이크로컨트롤러(MCU)와 PC 간의 데이터 통신을 가능하게 한다.
보드에서 UART로 전송된 데이터를 USB 형식으로 변환하고, PC는 이를 USB 데이터로 받아서 처리합니다.

📌 기본 동작 흐름

  1. MCU → UART 신호(TX, RX) → FTDI 모듈
  2. FTDI 모듈 → USB 신호 변환 → PC
  3. PC → USB 데이터 해석(드라이버) → 프로그램 (Xshell, Tera Term 등)

즉, MCU(임베디드 보드)에서는 UART로 데이터를 전송하지만, PC에서는 USB 포트로 받아들인다는 것이 핵심입니다.

GND : 그라운드
CTS
VCC : 전원 - USB로 전원을 받기 때문에 연결 필요 없음
TX : 송신부 - (보드에서는 RX)
RX : 수신부 - (보드에서는 TX)
DTR

5V - 3.3V 스위치가 있으니 맞는 전원으로 스위치 옮겨주기

2) USB 드라이버의 역할

PC에서는 기본적으로 USB 통신을 처리할 수 있는 기능이 있지만, FTDI 모듈에서 변환된 UART-to-USB 신호를 해석하기 위해서는 전용 드라이버가 필요합니다.

드라이버의 기능

✅ USB 신호를 PC에서 인식할 수 있는 형태로 변환
✅ PC가 FTDI 모듈을 가상의 COM 포트(직렬 포트)로 인식하도록 설정
✅ 사용자가 Xshell, PuTTY 같은 터미널 프로그램에서 COM 포트를 열어 통신할 수 있도록 함

즉, 드라이버가 설치되지 않으면, FTDI 모듈을 연결해도 PC가 이를 USB 장치로만 인식할 뿐, UART 데이터 통신이 불가능합니다.

3) FTDI 모듈의 데이터 흐름 구조

(1) MCU → FTDI 모듈 (UART 신호)

MCU에서 HAL_UART_Transmit() 같은 함수로 TX(송신) 핀을 통해 데이터를 보냄.
FTDI 모듈의 RX(수신) 핀이 이 데이터를 받아들임.

(2) FTDI 모듈 → PC (USB 변환 신호)

FTDI 칩셋이 UART 데이터를 USB 데이터 포맷으로 변환하여 PC로 전달.
USB 프로토콜을 사용하여 송수신을 처리.

(3) PC → 드라이버 (USB → 가상 COM 포트 변환)

FTDI 드라이버가 USB 데이터를 가상의 직렬 포트(COM 포트)로 변환
장치 관리자(Device Manager)에서 "USB Serial Port(COMx)"으로 표시됨.
프로그램(Xshell, Tera Term 등)에서 COM 포트를 선택하면 실제 UART 데이터를 확인 가능.

(4) 프로그램 → 사용자 출력

Xshell, Tera Term, PuTTY 같은 터미널 프로그램이 가상 COM 포트를 통해 데이터를 출력.
UART로 전송된 데이터가 화면에 나타남.

4. 하드웨어 세팅

1) FTDI232 와 USB 연결

장치 관리자 - 포트 - USB Serial Port(COM5)

*COM 포트
: PC에서 FTDI 모듈을 통해 UART 데이터를 송수신하는 가상의 직렬 포트

2) FTDI232와 STM32보드 연결

모듈보드
GNDGND
CTS
VCC전원 - USB로 전원을 받기 때문에 연결 필요 없음
FTDI_TXPA10 (USART_RX)
FTDU_RXPA9 (USART_TX)
DTR

5. 소프트웨어 설정

1) STM32Cube ioc 설정

Baud Rate : 115200Bits/s
Word Length : 한번 데이터를 보낼 때 8bit 씩 보내겠다
Parity : 없음
Stop Bits : 1

Data Direction : 송신, 수신
Over Sampling

2) Xshell7 사용방법

Xshell

: 터미널 프로그램, COM포트를 통해 데이터를 확인하는 프로그램

세션 새로 만들기

세션 연결

이 터미널에는 아스키 코드로만 출력된다.

3) UART 송신 코드

HAL_UART_Transmit

HAL_UART_Transmit(huart, pData, Size, Timeout);
->
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)

huart = *handler
pData = 보낼 데이터
Size = 보낼 데이터 사이즈
Timeout =

UART 전송

	char senddata[15] = "hello world\r\n";
    while (1)
    {
    
    	HAL_UART_Transmit(&huart1, senddata, strlen(senddata), 1000);
    	HAL_Delay(1000);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    }
  /* USER CODE END 3 */
}

Xshell 결과

숙제
PA9, GND를 오실로스코프로 찍어보면 파형이 나올 것이다.
'h' = 아스키 코드 71 = 1101000

6. UART로 printf() 출력

UART로 printf() 출력의 목적

✅ 1) 디버깅 (Debugging)

  • MCU에서 코드가 정상적으로 실행되고 있는지 확인
  • 특정 변수가 예상한 값으로 변경되고 있는지 모니터링
  • printf()를 이용해 함수가 정상적으로 실행되는지 출력(log) 확인

✅ 2) 시스템 로그 출력 (Logging)

  • 시스템의 에러 로그, 상태 정보, 동작 이력 등을 기록하여 분석
  • 예를 들어, 센서 값, 네트워크 연결 상태, 인터럽트 발생 여부 등을 UART로 출력하여 시스템 동작을 추적

✅ 3) 인터페이스 없이 데이터 확인

  • LCD, LED 같은 디스플레이 장치가 없는 경우 UART를 사용하여 데이터를 PC에서 직접 확인
  • 예를 들어, 센서 데이터를 UART로 출력하여 Xshell, PuTTY 같은 터미널에서 확인

✅ 4) 원격 디버깅 및 유지보수

  • UART는 PC뿐만 아니라 무선 모듈(Wi-Fi, 블루투스)과 연동하여 원격 디버깅도 가능
  • 예를 들어, IoT 기기에서 UART로 디버깅 로그를 송신하면, 원격지에서도 장치 상태를 확인할 수 있음

1) _write() 재정의

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int _write(int file,char *p,int len){
	HAL_UART_Transmit(&huart1,(uint8_t *)p,len,10);
	return len;
}
/* USER CODE END 0 */

printf() 안에 _write()가 정의되어 있다.
_write()를 재정의 해주면서 printf 가 uart로 출력되도록 바꿔주는 것이다.

2) UART로 printf() 출력

  /* USER CODE BEGIN WHILE */
  	 
    while (1)
    {
    	printf("hello ~~~ !!!\r\n")
    	HAL_Delay(1000);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    }
  /* USER CODE END 3 */
}

Xshell

3) UART로 실수 출력

링킹 옵션 추가

/* USER CODE BEGIN WHILE */
	float ff = 0.7;
    while (1)
    {
    	printf("hello ~~~ !!!\r\n");
    	printf("f = %f\r\n",ff);
    	HAL_Delay(1000);

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    }
  /* USER CODE END 3 */
}

0개의 댓글