📘 전체 구조 개요
- 이 회로는 입력과 출력 모두 가능한 I/O 핀의 내부 구조입니다.
- 내부적으로는 다양한 레지스터와 드라이버 회로, 슈미트 트리거가 구성되어 있습니다.
- 각 기능은 STM32의 GPIOx_MODER, GPIOx_IDR, GPIOx_ODR, GPIOx_AFR, 그리고 PUPDR 같은 레지스터로 제어됩니다.
🔹 주요 블록 설명 (→ 관련 레지스터 연결 포함)
① Input Path (입력 경로)
- TTL Schmitt Trigger: 디지털 입력 신호를 안정적으로 처리하도록 히스테리시스 기능이 있는 비교기입니다.
- 연결된 레지스터:
✅
GPIOx_IDR
- 외부 I/O 핀에서 들어온 신호는 TTL 슈미트 트리거를 거쳐
Input data register
로 전달되고, 소프트웨어에서GPIOx_IDR
을 읽으면 현재 핀 상태를 알 수 있습니다.
- 외부 I/O 핀에서 들어온 신호는 TTL 슈미트 트리거를 거쳐
② Output Path (출력 경로)
Output control
블록은 P-MOS, N-MOS를 제어하여 핀에 출력을 보냅니다.- 연결된 레지스터:
✅
GPIOx_ODR
✅
GPIOx_BSRR
/GPIOx_BRR
(비트 세트/리셋용)- 소프트웨어에서
GPIOx_ODR
또는BSRR/BRR
을 사용하여 핀을 High/Low로 설정합니다.
- 소프트웨어에서
③ Alternate Function (대체 기능 경로)
- 외부 장치(UART, SPI, PWM 등)와 연결 시 사용됩니다.
- 연결된 레지스터:
✅
GPIOx_AFR[0/1]
(Alternate Function Low/High Register)✅
GPIOx_MODER
: 해당 핀을 alternate 기능으로 설정MODER
에서 특정 핀을 Alternate 모드(10
)로 설정하면 이 경로가 활성화됩니다.- 이후
AFRL
/AFRH
를 통해 어떤 주변 기능(UART, SPI 등)에 연결할지 결정합니다.
④ Pull-up / Pull-down 제어
- 내부 풀업/풀다운 저항을 연결하여 입력 안정화 역할을 합니다.
- 연결된 레지스터:
✅
GPIOx_PUPDR
- 각 핀마다 2비트씩 설정:
00
: 없음,01
: Pull-up,10
: Pull-down,11
: 예약
- 각 핀마다 2비트씩 설정:
⑤ Protection Diodes (보호 다이오드)
- ESD(정전기)나 과전압 보호를 위해 VDD, VSS와 연결된 보호 다이오드.
- 하드웨어적 보호장치로, 레지스터와 직접 연결되지는 않지만, 외부 전압 인가 시 중요 역할.
📊 요약: 주요 레지스터 정리
기능 | 관련 레지스터 | 설명 |
---|---|---|
입출력 모드 설정 | GPIOx_MODER |
00: 입력, 01: 출력, 10: AF, 11: 아날로그 |
입력 값 읽기 | GPIOx_IDR |
입력된 High/Low 상태 확인 |
출력 값 설정 | GPIOx_ODR , BSRR , BRR |
핀을 High/Low로 설정 |
Pull-up/down 저항 설정 | GPIOx_PUPDR |
내부 풀업/풀다운 저항 설정 |
Alternate Function 설정 | GPIOx_AFR[0/1] |
주변 장치와 연결 설정 |
#define RCC_APB1ENR ((volatile uint32_t)0x40023840) ⇒ 0x 40023800 + 0x40
RCC_APB1ENR |= (1 << 17);
✅ 1. USART2 기본 정보
항목 | 내용 |
---|---|
TX 핀 | PA2 (AF7) |
클럭 | APB1 (RCC_APB1ENR) |
USART2 베이스 주소 | 0x4000 4400 |
📘 USART_SR (Status Register) 비트 설명
비트 | 이름 | 속성 | 의미 |
---|---|---|---|
15 | Reserved | – | 사용되지 않음 |
14 | Reserved | – | 사용되지 않음 |
13 | CTS (Clear To Send) | rc_w0 | 하드웨어 흐름제어에서 CTS 핀 상태 CTS 이벤트 발생 시 1로 설정됨 |
12 | LBD (LIN Break Detection) | rc_w0 | LIN 프로토콜에서 Break 감지 시 1로 설정됨 |
11 | TXE (Transmit Data Register Empty) | r | TX 버퍼가 비었음을 나타냄1이면 새로운 데이터를 쓸 수 있음 |
10 | TC (Transmission Complete) | rc_w0 | 전송 완료 (shift register까지 전부 전송 완료됨) |
9 | RXNE (Read Data Register Not Empty) | rc_w0 | 수신된 데이터가 DR에 도착함읽을 준비가 되었음을 의미 |
8 | IDLE | r | IDLE 상태 감지 (일정 시간 수신 없음) |
7 | ORE (Overrun Error) | r | 수신 버퍼 오버런 발생 시 1 (데이터 손실) |
6 | NF (Noise Flag) | r | 수신 중 노이즈 감지됨 |
5 | FE (Framing Error) | r | Start/Stop 비트 오류 |
4 | PE (Parity Error) | r | 패리티 오류 발생 (패리티 비트 설정 시) |
3~0 | Reserved | – | 사용하지 않음 (0으로 유지) |
📌 주요 비트 요약 (실제 사용에서 중요한 것)
비트 | 이름 | 언제 확인? | 동작 |
---|---|---|---|
TXE | 전송 준비 | 송신하기 전 | 1이면 USART_DR 에 데이터 써도 됨 |
TC | 전송 완료 | 송신 후 전체 완료 확인 시 | 1이면 모두 전송 완료 |
RXNE | 수신 준비 | 데이터 수신 시 | 1이면 USART_DR 에서 읽기 |
ORE | 오류 | 수신 시 처리 지연되면 발생 | RXNE를 안 읽어서 데이터 유실 |
PE | 오류 | 패리티 설정한 경우 | 잘못된 패리티 수신 |
FE | 오류 | 잘못된 Stop bit 수신 | framing error |
🛠 rc_w0 뜻?
속성 | 의미 |
---|---|
r |
Read-only |
rc_w0 |
읽기 가능, “1”을 써야 클리어됨 (write 0 has no effect) |
즉, 오류 비트나 상태 플래그는 ‘읽기’ + ‘1 쓰기’로 클리어 해야 합니다:
c
복사편집
USART2->SR &= ~(1 << 5); // ❌ 작동안함
USART2->SR = ~(1 << 5); // ❌ 작동안함
// 정석: SR은 읽고 DR도 읽어야 클리어됨 (RXNE 등)
uint8_t dummy = USART2->DR;
✅ 실제 송신 루틴 예시
c
복사편집
// 전송 준비될 때까지 대기
while (!(USART2->SR & (1 << 7))); // TXE
// 데이터 전송
USART2->DR = 'A';
// 전송 완료까지 대기 (옵션)
while (!(USART2->SR & (1 << 6))); // TC
✅ 실제 수신 루틴 예시
c
복사편집
// 수신 데이터 도착 대기
while (!(USART2->SR & (1 << 5))); // RXNE
// 수신 데이터 읽기
char data = USART2->DR;
// RCC 및 GPIO 관련 레지스터 정의
#define RCC_AHB1ENR (*(volatile uint32_t*)0x40023830) // AHB1 클럭 (GPIO용)
#define RCC_APB1ENR (*(volatile uint32_t*)0x40023840) // APB1 클럭 (USART2용)
#define GPIOA_MODER (*(volatile uint32_t*)0x40020000) // GPIOA 모드 설정
#define GPIOA_AFRL (*(volatile uint32_t*)0x40020020) // GPIOA의 AFR[7:0] (PA0 ~ PA7)
#define USART2_BASE 0x40004400 // USART2 베이스 주소
#define USART2_SR (*(volatile uint32_t*)(USART2_BASE + 0x00)) // 상태 레지스터
#define USART2_DR (*(volatile uint32_t*)(USART2_BASE + 0x04)) // 데이터 레지스터
#define USART2_BRR (*(volatile uint32_t*)(USART2_BASE + 0x08)) // 보레이트 설정
#define USART2_CR1 (*(volatile uint32_t*)(USART2_BASE + 0x0C)) // 제어 레지스터 1
// USART2 초기화 함수
void usart2_init() {
// 1. 클럭 인가
RCC_AHB1ENR |= (1 << 0); // GPIOA 클럭 인가
RCC_APB1ENR |= (1 << 17); // USART2 클럭 인가
// 2. GPIO 설정 (PA2를 USART2 TX로 설정)
GPIOA_MODER &= ~(3 << (2 * 2)); // MODER2 클리어
GPIOA_MODER |= (2 << (2 * 2)); // MODER2 = 10 (AF 모드)
GPIOA_AFRL &= ~(0xF << (4 * 2)); // AFRL2 클리어
GPIOA_AFRL |= (7 << (4 * 2)); // AFRL2 = 0111 (AF7: USART2)
// 3. USART 설정 (9600 bps @ 16 MHz)
USART2_BRR = 0x0683; // 9600bps 설정
// 4. USART 및 송신 기능 활성화
USART2_CR1 |= (1 << 13); // UE (USART Enable)
USART2_CR1 |= (1 << 3); // TE (Transmitter Enable)
}
// USART2를 통해 문자 1개 송신
void usart2_write(char ch) {
while (!(USART2_SR & (1 << 7))); // TXE 대기
USART2_DR = ch; // 데이터 전송
}
int main(void) {
usart2_init(); // USART2 초기화
while (1) {
usart2_write('A'); // 문자 전송
for (volatile int i = 0; i < 100000; ++i); // 딜레이
}
}
🧠 이 코드의 핵심 포인트
- GPIOA 클럭, USART2 클럭, GPIO 핀 모드 설정, AF 설정, 보레이트 계산, TX enable, TXE 상태 체크까지 전부 레지스터로 처리
- HAL, CMSIS, StdPeriph 등 어떤 라이브러리도 사용하지 않음
while (!(USART2_SR & (1 << 7)))
는 TX 버퍼가 비었는지 확인하는 코드
✅ 시스템 구성
항목 | 내용 |
---|---|
MCU | STM32F401 |
TX 핀 | PA2 (AF7) |
USART2 | TX 사용 |
DMA | DMA1, Stream 6, Channel 4 (USART2_TX 전용) |
✅ 전체 흐름
- RCC로 USART2 / DMA1 / GPIOA 클럭 인가
- PA2를 Alternate Function (AF7)로 설정
- USART2 보레이트 설정, TE 활성화
- DMA1_Stream6 설정 (메모리 → 주변장치, 전송 크기 등)
- USART2에 DMA TX 요청 허용
- DMA Enable → 자동 송신 시작
#include <stdint.h>
// RCC
#define RCC_AHB1ENR (*(volatile uint32_t*)0x40023830)
#define RCC_APB1ENR (*(volatile uint32_t*)0x40023840)
// GPIO
#define GPIOA_MODER (*(volatile uint32_t*)0x40020000)
#define GPIOA_AFRL (*(volatile uint32_t*)0x40020020)
// USART2
#define USART2_BASE 0x40004400
#define USART2_SR (*(volatile uint32_t*)(USART2_BASE + 0x00))
#define USART2_DR (*(volatile uint32_t*)(USART2_BASE + 0x04))
#define USART2_BRR (*(volatile uint32_t*)(USART2_BASE + 0x08))
#define USART2_CR1 (*(volatile uint32_t*)(USART2_BASE + 0x0C))
#define USART2_CR3 (*(volatile uint32_t*)(USART2_BASE + 0x14))
// DMA1 (USART2_TX → Stream 6, Channel 4)
#define DMA1_BASE 0x40026000
#define DMA1_Stream6_CR (*(volatile uint32_t*)(DMA1_BASE + 0x118))
#define DMA1_Stream6_NDTR (*(volatile uint32_t*)(DMA1_BASE + 0x11C))
#define DMA1_Stream6_PAR (*(volatile uint32_t*)(DMA1_BASE + 0x120))
#define DMA1_Stream6_M0AR (*(volatile uint32_t*)(DMA1_BASE + 0x124))
#define DMA1_HIFCR (*(volatile uint32_t*)(DMA1_BASE + 0x0A8)) // DMA Clear Interrupt Flags
void dma_usart2_init(const char* data, uint32_t len) {
// 1. 클럭 인가
RCC_AHB1ENR |= (1 << 0); // GPIOA
RCC_AHB1ENR |= (1 << 21); // DMA1
RCC_APB1ENR |= (1 << 17); // USART2
// 2. GPIO 설정 (PA2 -> AF7)
GPIOA_MODER &= ~(3 << (2 * 2));
GPIOA_MODER |= (2 << (2 * 2)); // AF 모드
GPIOA_AFRL &= ~(0xF << (4 * 2));
GPIOA_AFRL |= (7 << (4 * 2)); // AF7: USART2
// 3. USART2 보레이트 및 활성화
USART2_BRR = 0x0683; // 9600bps @ 16MHz
USART2_CR3 |= (1 << 7); // DMAT 활성화 (DMA enable transmitter)
USART2_CR1 |= (1 << 13) | (1 << 3); // USART Enable + Transmitter Enable
// 4. DMA 설정 (Stream6, Channel 4)
DMA1_Stream6_CR &= ~(1 << 0); // Stream disable
while (DMA1_Stream6_CR & 1); // Stream이 완전히 꺼질 때까지 대기
DMA1_HIFCR |= (0x3D << 16); // 모든 인터럽트 플래그 클리어
DMA1_Stream6_CR &= ~(7 << 25); // CHSEL[2:0] 클리어
DMA1_Stream6_CR |= (4 << 25); // Channel 4 선택 (USART2_TX)
DMA1_Stream6_CR &= ~(3 << 6); // DIR = 01 (Memory → Peripheral)
DMA1_Stream6_CR |= (1 << 6);
DMA1_Stream6_CR &= ~(1 << 10); // Peripheral increment disable
DMA1_Stream6_CR |= (1 << 10); // Memory increment enable
DMA1_Stream6_CR &= ~(3 << 13); // Memory data size = 8-bit
DMA1_Stream6_CR &= ~(3 << 11); // Peripheral data size = 8-bit
DMA1_Stream6_PAR = (uint32_t)&USART2_DR; // 목적지 주소: USART2_DR
DMA1_Stream6_M0AR = (uint32_t)data; // 소스 주소: 문자열
DMA1_Stream6_NDTR = len; // 전송할 바이트 수
DMA1_Stream6_CR |= (1 << 0); // DMA Stream Enable
}
int main(void) {
const char msg[] = "Hello DMA USART2!\\r\\n";
dma_usart2_init(msg, sizeof(msg) - 1);
while (1); // 전송 완료될 때까지 대기
}
✅ 핵심 요약
항목 | 설명 |
---|---|
DMA 채널 | DMA1 Stream6, Channel 4 (USART2_TX) |
데이터 흐름 | Memory → USART2_DR |
장점 | CPU 개입 없이 문자열 전송 가능 |
DMA 종료 처리 | DMA는 자동 종료, 수동 클리어 가능 |
디버깅 방법 | 오실로스코프나 UART 모니터로 확인 |
✅ 시스템 구성
항목 | 내용 |
---|---|
MCU | STM32F401 |
TX 핀 | PA2 (AF7) |
USART2 | TX 사용 |
DMA | DMA1, Stream 6, Channel 4 (USART2_TX 전용) |
✅ 전체 흐름
- RCC로 USART2 / DMA1 / GPIOA 클럭 인가
- PA2를 Alternate Function (AF7)로 설정
- USART2 보레이트 설정, TE 활성화
- DMA1_Stream6 설정 (메모리 → 주변장치, 전송 크기 등)
- USART2에 DMA TX 요청 허용
- DMA Enable → 자동 송신 시작
#include <stdint.h>
// RCC
#define RCC_AHB1ENR (*(volatile uint32_t*)0x40023830)
#define RCC_APB1ENR (*(volatile uint32_t*)0x40023840)
// GPIO
#define GPIOA_MODER (*(volatile uint32_t*)0x40020000)
#define GPIOA_AFRL (*(volatile uint32_t*)0x40020020)
// USART2
#define USART2_BASE 0x40004400
#define USART2_SR (*(volatile uint32_t*)(USART2_BASE + 0x00))
#define USART2_DR (*(volatile uint32_t*)(USART2_BASE + 0x04))
#define USART2_BRR (*(volatile uint32_t*)(USART2_BASE + 0x08))
#define USART2_CR1 (*(volatile uint32_t*)(USART2_BASE + 0x0C))
#define USART2_CR3 (*(volatile uint32_t*)(USART2_BASE + 0x14))
// DMA1 Stream6 for USART2_TX
#define DMA1_BASE 0x40026000
#define DMA1_Stream6_CR (*(volatile uint32_t*)(DMA1_BASE + 0x118))
#define DMA1_Stream6_NDTR (*(volatile uint32_t*)(DMA1_BASE + 0x11C))
#define DMA1_Stream6_PAR (*(volatile uint32_t*)(DMA1_BASE + 0x120))
#define DMA1_Stream6_M0AR (*(volatile uint32_t*)(DMA1_BASE + 0x124))
#define DMA1_HIFCR (*(volatile uint32_t*)(DMA1_BASE + 0x0A8))
// NVIC
#define NVIC_ISER1 (*(volatile uint32_t*)0xE000E104)
volatile char rx_data = 0;
void dma_usart2_tx(const char* data, uint32_t len) {
DMA1_Stream6_CR &= ~(1 << 0); // Disable DMA stream
while (DMA1_Stream6_CR & 1);
DMA1_HIFCR |= (0x3D << 16); // Clear all DMA interrupt flags
DMA1_Stream6_CR &= ~(7 << 25); // CHSEL = 4
DMA1_Stream6_CR |= (4 << 25);
DMA1_Stream6_CR &= ~(3 << 6);
DMA1_Stream6_CR |= (1 << 6); // Memory to Peripheral
DMA1_Stream6_CR &= ~(1 << 10); // Peripheral increment disable
DMA1_Stream6_CR |= (1 << 10); // Memory increment enable
DMA1_Stream6_CR &= ~(3 << 13); // 8-bit mem
DMA1_Stream6_CR &= ~(3 << 11); // 8-bit periph
DMA1_Stream6_PAR = (uint32_t)&USART2_DR;
DMA1_Stream6_M0AR = (uint32_t)data;
DMA1_Stream6_NDTR = len;
DMA1_Stream6_CR |= (1 << 0); // Enable DMA stream
}
void usart2_init_dma_rx(void) {
// Enable clocks
RCC_AHB1ENR |= (1 << 0) | (1 << 21); // GPIOA, DMA1
RCC_APB1ENR |= (1 << 17); // USART2
// GPIOA2 = TX, A3 = RX
GPIOA_MODER &= ~((3 << (2 * 2)) | (3 << (3 * 2)));
GPIOA_MODER |= ((2 << (2 * 2)) | (2 << (3 * 2))); // AF mode
GPIOA_AFRL &= ~((0xF << (4 * 2)) | (0xF << (4 * 3)));
GPIOA_AFRL |= ((7 << (4 * 2)) | (7 << (4 * 3))); // AF7
USART2_BRR = 0x0683; // 9600 bps
USART2_CR3 |= (1 << 7); // DMAT enable
USART2_CR1 |= (1 << 13) | (1 << 3) | (1 << 2) | (1 << 5); // UE, TE, RE, RXNEIE
NVIC_ISER1 |= (1 << (USART2_IRQn - 32)); // USART2 interrupt enable
}
void USART2_IRQHandler(void) {
if (USART2_SR & (1 << 5)) { // RXNE
rx_data = USART2_DR; // Read to clear RXNE
// echo back
dma_usart2_tx(&rx_data, 1);
}
}
int main(void) {
usart2_init_dma_rx();
const char* msg = "USART2 DMA TX + RX interrupt ready\\r\\n";
dma_usart2_tx(msg, 34);
while (1);
}
USART2를 통해 DMA로 문자열을 송신하고, 수신된 문자를 인터럽트에서 받아 즉시 DMA로 다시 송신(에코)하는 구조입니다.