Alternative Function

📘 전체 구조 개요

  • 이 회로는 입력과 출력 모두 가능한 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을 읽으면 현재 핀 상태를 알 수 있습니다.

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: 예약


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] 주변 장치와 연결 설정

image.png

image.png

#define RCC_APB1ENR ((volatile uint32_t)0x40023840) ⇒ 0x 40023800 + 0x40

image.png

RCC_APB1ENR |= (1 << 17);

image.png

image.png

✅ 1. USART2 기본 정보

항목 내용
TX 핀 PA2 (AF7)
클럭 APB1 (RCC_APB1ENR)
USART2 베이스 주소 0x4000 4400

image.png

📘 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 전용)

✅ 전체 흐름

  1. RCC로 USART2 / DMA1 / GPIOA 클럭 인가
  2. PA2를 Alternate Function (AF7)로 설정
  3. USART2 보레이트 설정, TE 활성화
  4. DMA1_Stream6 설정 (메모리 → 주변장치, 전송 크기 등)
  5. USART2에 DMA TX 요청 허용
  6. 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 전용)

✅ 전체 흐름

  1. RCC로 USART2 / DMA1 / GPIOA 클럭 인가
  2. PA2를 Alternate Function (AF7)로 설정
  3. USART2 보레이트 설정, TE 활성화
  4. DMA1_Stream6 설정 (메모리 → 주변장치, 전송 크기 등)
  5. USART2에 DMA TX 요청 허용
  6. 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);
}

clock_source.png

USART2를 통해 DMA로 문자열을 송신하고, 수신된 문자를 인터럽트에서 받아 즉시 DMA로 다시 송신(에코)하는 구조입니다.

다운로드.gif