NUCLEO-STM32F429ZI 보드를 사용했습니다.
UART 시리얼 통신
UART와 USART의 차이는 동기화 Clock 소스의 유무이다.
1. USART는 동기화 Clock에 따라 데이터를 전송한다.
2. UART는 협의된 baudrate에 따라 T1bit 간격으로 샘플링된 값을 데이터로 수신하며, START비트와 STOP비트는 1.5bit의 길이를 가진다. (RS-232 통신 프로토콜과 같은 구조)
3. “hello” → logic analyzer로 파형 측정해보기

-구조 : start 1bit → data 8 bit → stop 1bit
- data bit를 보낼 때 LSB를 먼저 보내기 때문에 파형의 오른쪽부터 반대로 읽어야 한다.
- ‘h’ (hex값 0x68) →0110 1000
보드의 RX핀과 로직 애널라이저를 연결하여 파형을 확인할 것이다. GND까지 애널라이저의 집게핀과 연결했음을 꼭 확인한 뒤에 pc에 보드와 애널라이저 각각 usb연결을 해야한다(포트 2개필요)
UART 회로 및 내부 구조의 대략적인 이해
Nucleo-F429ZI 보드와 PC UART 연결 (USB 인터페이스 연결)
ST-Link 파트
STM32F103 ←[USB]→ PC
MCU 파트
STM32F429 ←[UART]→ STM32F103
USART3 인터페이스를 이용해 STLK_TX, STLK_RX의 라인으로 연결한다.

- DCE(Data Communication Equipment): 데이터의 회선 종단 장치 (모뎀, 휴대폰, 세터박스 등)
- DTE(Data Terminal Equipment): 데이터를 생산, 수신하는 장치 (PC, 사람 등)
ST-Link 파트의 STM32F103

MCU 파트의 STM32F249ZI


UART 블록 다이어그램

8개의 외부핀이 있다.
TX : 송신핀
RX : 수신핀
IRDA_IN / IRDA_OUT : 적외선 통신 입출력 핀
RTS : Request to send(수신 가능 상태 시 Low로 signal 변환)
CTS : Clear to send(CTS 신호가 Low 시에 데이터 전송)
CK : USART 시 사용하는 동기 통신 클럭 핀
RCC 구조 대략적 이해
RCC (Reset clock controller)

- STM32의 모든 장치를 동작하게 하는 타이밍(클럭 신호)을 제공한다.
- 클럭 신호는 Low(0), High(1)의 값을 갖는 방형파(square wave)로 표현된다.
- 클럭 소스는 다음과 같이 3가지가 있다.
- HSI(High speed internal) : 내부에서 생성하는 16MHz의 클럭이다. (내부 16MHz RC 발진기)
- HSE(external) : 외부 클럭을 HSEBYPASS로 그대로 사용하거나, (외부 크리스탈 공진기, 외부 클럭)
- PLLCLK : HSE 외부 클럭을 PLL회로를 거쳐 시스템 클럭을 구하고, 스케일된 HCLK를 Cortex system timer, APB,AHB 등 peripheral 클럭으로 사용한다.
-USART3은 APB1 Bus와 연결된다.

그래서 42MHz를 이용할 수 있는데 이 시스템 클럭을 바탕으로 baud rate을 설정할 것이다.
UART 관련 레지스터
① RCC_APB1ENR : USART3에 연결된 APB 클럭을 인가한다

RCC->APB1 |= 0x1 << 18; // enable USART3
② GPIOx_MODER : AF 모드로 비트 10 으로 마스크해준다.

GPIOD->MODER &= ~(0x3 << 16 | 0x3 << 18); // 초기화
GPIOD->MODER |= (0x2 << 16) | (0x2 << 18); // 10 10
③ GPIOx_AFRH : USART3와 연결된 GPIO핀과 맵핑해준다.
- GPIOx_AFRL : 포트x의 0~7번 핀
-
GPIOx_AFRH : 8~15번 핀
- AFRL,AFRH는 32bit 레지스터이다.
- 한 핀당 4bit 사용으로 AFRL, AFRH 각각 32비트로 각각 8개 GPIO핀을 설정할 수 있다.


- PD8 : AFRH8[3:0], PD9 : AFRH9[3:0]
GPIOD→AFR[1] &= ~(0xF << 0 | 0xF << 4); // 초기화 GPIOD→AFR[1] |= (0x7 << 0) | (0x7 <<4); // 0111 0111 - →GPIOD→AFR[1] |= (0x7 <<0) | (0x7 <<4);
④ USART_SR : (TXE가 1이 되어 새 데이터를 버퍼에 쓸 수 있는지 확인한다)


| Bit | 이름 | 설명 | 클리어 방법 |
|---|---|---|---|
| 9 | CTS | CTS 핀의 상태 변화 감지 | 소프트웨어가 0을 써서 클리어 (rc_w0) |
| 7 | TXE | 송신 데이터 레지스터가 비었는지 여부 : TDR레지스터 다 shift되면→ 1 | USART_DR에 쓰기로 클리어(r) |
| 6 | TC | 송신 완료 여부 | USART_SR 읽기 후 USART_DR 쓰기 또는 0 쓰기(rc_w0) |
| 5 | RXNE | 수신 데이터 존재 여부 : 1→ 데이터를 읽으면 자동 클리어. | USART_DR 읽기 또는 0 쓰기(r) |
rc_w0
0을 write해서 clear한다.(or연산 주의. 직접 연산없이 대입하는게 다른 비트에 안전)
⑤ USART_DR : Data 레지스터.

[송신 흐름]
코드: USART->DR = 'H'; DR은 데이터 버퍼 역할.
→ DR = 0x48 (ASCII 'H')
→ 내부 시프트 레지스터로 전송
→ TX 핀으로 직렬 송신
[수신 흐름]
외부에서 'H' 수신
→ 내부 시프트 레지스터 → DR = 0x48
→ RXNE = 1
→ 코드에서 USART->DR 읽기
→ c = 'H', RXNE = 0 (자동 클리어)
** 수신 데이터 읽기 **
UART3으로 데이터를 수신하면, 수신한 데이터는 USART→DR 레지스터에 저장되어 읽어들일 수 있다.
- DR레지스터에서 8비트 값 읽기
uint8_t received_data = UART3->DR;
** 전송 데이터 쓰기 **
UART3으로 전송을 하려면, 먼저 전송할 데이터를 DR 레지스터에 쓴다.
USART3->DR = (uint8_t)(*pdata8bits);
⑥ USART_BRR

)

- baud rate 계산
-

- DIV_Mantissa[ 11 : 0 ] : USARTDIV의 정수 부분 (소수점 앞의 값)
- DIV_Fraction[ 3 : 0 ] :USARTDIV의 소수점 이하 값 (소수점 뒤의 값 × 16)
[16MHz, 115200bps] 내부 클럭 사용시
//[42MHz, 115200bps] // 외부 클럭.APB1의 PCLK 사용시
USARTDIV=f_CLK/(oversampling* Baudrate)
=16000000/(16*115200)
=약 8.6806
DIV_Mantissa=8
DIV_Fraction=0.6806*16= 약10.89 =>11
USART_BRR = (DIV_Mantissa << 4) | DIV_Fraction
= (8 << 4) | 11
= 0x80 | 0x0B
= 0x8B
⑦USART_CR1 : USART의 동작 모드와 주요 기능들을 켜고 끄는 역할.

)

Hello world 출력
#include "stm32f4xx.h"
void USART3_Init(void) {
// 1. GPIOD 클럭 활성화
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
// 2. PD8(RX), PD9(TX) 를 AF 모드로 설정
GPIOD->MODER &= ~((0x3 << (2 * 8)) | (0x3 << (2 * 9))); // 클리어
GPIOD->MODER |= ((0x2 << (2 * 8)) | (0x2 << (2 * 9))); // AF 모드
// 3. AF7(USART3) 설정
GPIOD->AFR[1] &= ~((0xF << (4 * 0)) | (0xF << (4 * 1))); // 클리어
GPIOD->AFR[1] |= ((0x7 << (4 * 0)) | (0x7 << (4 * 1))); // AF7
// 4. USART3 클럭 활성화
RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
// 5. Baud rate 설정 (115200 baud,16mHz)
USART3->BRR = (8 << 4) | 11; // 0x8B
// SystemClock_Config 호출과 함께 이 값을 설정 시->CPU 클럭이 빨라진다.
//당연히 tera term에 나오는 출력도 빨라짐
// USART3->BRR = (22 << 4) | 13;
// 6. 송신 활성화, USART 활성화
USART3->CR1 |= USART_CR1_TE;
USART3->CR1 |= USART_CR1_UE;
}
void USART3_SendChar(char c) {
while (!(USART3->SR & USART_SR_TXE));
USART3->DR = c;
}
void USART3_SendString(const char* str) {
while (*str) {
USART3_SendChar(*str++);
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
USART3_Init(); // 초기화
while (1)
{
USART3_SendString("Hello, World ");
for (volatile uint32_t i = 0; i < 1000000; i++) {
}
}
}
Tera Term에서 echo back 출력해보기
void GPIO_Init(void)
{
// GPIOD 클럭 활성화
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
// PD8 (TX) 설정
GPIOD->MODER &= ~((0x3 << (2 * 8)) | (0x3 << (2 * 9))); // 클리어
GPIOD->MODER |= ((0x2 << (2 * 8)) | (0x2 << (2 * 9))); // AF 모드
// 3. AF7(USART3) 설정
GPIOD->AFR[1] &= ~((0xF << (4 * 0)) | (0xF << (4 * 1))); // 클리어
GPIOD->AFR[1] |= ((0x7 << (4 * 0)) | (0x7 << (4 * 1))); // AF7
}
void USART3_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
// 5. Baud rate 설정 (115200 baud)
USART3->BRR = (8 << 4) | 11; // 0x8B;
// 6. 송신 활성화, USART 활성화
USART3->CR1 |= USART_CR1_TE;
USART3->CR1 |= USART_CR1_RE;
USART3->CR1 |= USART_CR1_UE;
}
void USART3_SendChar(char c)
{
while (!(USART3->SR & USART_SR_TXE));
USART3->DR = c; // TDR 레지스터에 문자 전송
}
char USART3_ReceiveChar(void)
{
while (!(USART3->SR & USART_SR_RXNE));
return (char)(USART3->DR & 0xFF);
}
int main(void)
{
HAL_Init();
GPIO_Init();
USART3_Init();
while (1)
{
char c = USART3_ReceiveChar(); // 문자를 하나 수신
USART3_SendChar(c);
}
}
Tera Term 에서 문자하나를 터미널에 입력하면, 바로 뒤에 입력한 문자가 나타난다.
->1번코드 실행과 달리 두 번씩 쓰여지는 것이 보이면 echo back이 된것이다.
(따로 echo back모드 설정 필요없음)
sprintf로 문자열 출력
void GPIO_Init(void)
{
// GPIOD 클럭 활성화
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
// PD8 (TX) 설정
GPIOD->MODER &= ~((0x3 << (2 * 8)) | (0x3 << (2 * 9))); // 클리어
GPIOD->MODER |= ((0x2 << (2 * 8)) | (0x2 << (2 * 9))); // AF 모드
// 3. AF7(USART3) 설정
GPIOD->AFR[1] &= ~((0xF << (4 * 0)) | (0xF << (4 * 1))); // 클리어
GPIOD->AFR[1] |= ((0x7 << (4 * 0)) | (0x7 << (4 * 1))); // AF7
}
void USART3_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_USART3EN;
// 5. Baud rate 설정 (115200 baud)
USART3->BRR = (22 << 4) | 13; //42MHZ 클럭 이용
// 6. 송신 활성화, USART 활성화
USART3->CR1 |= USART_CR1_TE;
USART3->CR1 |= USART_CR1_RE;
USART3->CR1 |= USART_CR1_UE;
}
char msg[100];
void sendchar(char c){
while(!(USART3->SR & USART_SR_TXE));
USART3->DR =c;
}
void sendstring(const char* str){
while(*str){
sendchar(*str++);
}
}
int main(void){
HAL_Init();
SystemClock_Config();
GPIO_Init();
USART3_Init();
while (1)
{
sprintf(msg, "%d", "uart test ");
sendstring(msg);
HAL_Delay(1000);
}
}
tera term 으로 baud rate을 동일하게 맞춰주고, 출력확인해본다.
'Cortex-M' 카테고리의 다른 글
| TIM9 구조와 활용, 익셉션(인터럽트)활용, PWM 파형 출력 (로직 애널라이저) (0) | 2025.05.25 |
|---|---|
| TIM6 타이머 -polling, 익셉션 핸들러(인터럽트) 이용 | STMF429ZI (0) | 2025.05.25 |
| IWDG Independent watch dog | 레지스터 제어로 코드 구현(STM32F429ZI) (0) | 2025.05.23 |
| button Key 인터럽트로 LED 토글하기(레지스터제어) | STM32cubeIDE(32F429ZI) (0) | 2025.05.23 |
| SysTick 타이머 동작 및 레지스터 이해 | STM32F42ZI (1) | 2025.05.23 |