-
임베디드 OS개발 프로젝트(CH6)Share/OS 2025. 5. 24. 23:36
Intro
임베디드 시스템 개발에서 UART를 통한 입력/출력은 거의 필수다. 특히 인터럽트 기반으로 입력을 처리하면 불필요한 polling 없이 효율적인 시스템 운영이 가능하다.
이 글에서는 ARM Cortex-A + GIC 기반의 베어메탈 환경에서, UART RX 인터럽트를 통해 입력을 처리하는 흐름을 함수, 구조체, 동작 순서 중심으로 정리한다. RTOS 없이 순수 bare-metal 환경에서 인터럽트를 어떻게 구성하는지, 시스템-장치 간 흐름을 이해하고 싶은 개발자라면 도움이 될 것이다.
예를 들어, 두 개의 MCU가 UART를 통해 서로 데이터를 주고받는 상황을 생각해보자.
이때 양쪽 모두 인터럽트 기반 통신 방식을 사용한다면 다음과 같은 흐름이 이뤄진다:- UART RX 인터럽트를 활성화하고
- HAL 계층에서 제공하는 인터럽트 서비스 루틴(ISR)을 등록한 뒤
- 수신이 완료되면 콜백 함수에서 실제 데이터를 처리
STM32 HAL 기준으로 보면 HAL_UART_Receive_IT()로 비동기 수신을 시작하고,
데이터가 도착하면 내부적으로 인터럽트가 발생하여 HAL_UART_RxCpltCallback() 콜백이 호출된다.이 글에서 설명할 구조를 이해하고 나면,
“지금 이 흐름이 IRQ 핸들러인가? 콜백 함수인가? 아직 GIC에서 IRQ만 발생한 상태인가?”
이런 질문들에 대한 감각이 생긴다.또 하나, 왜 인터럽트 핸들러 안에 오래 머무르면 안 되는지,
ISR의 실행 시간이 전체 시스템 응답성에 얼마나 민감하게 작용하는지도 함께 체감할 수 있을 것이다.물론, 이 글에서 다룰 코드는 STM32 HAL 같은 고수준 API가 아닌,
직접 GIC와 장치를 다루는 로우레벨 베어메탈 코드다. ^^
바닥부터 어떻게 인터럽트를 설정하고 처리하는지를 함께 짚어보자.여기서 잠깐 GIC가 뭐냐면, 간단히 말해 편의를 위한 컨트롤러다 .
GIC란?
GIC (Generic Interrupt Controller)는
ARM 프로세서에서 인터럽트 관리를 담당하는 하드웨어 컨트롤러입니다.GIC가 필요한 이유
단일 코어에서는 단순한 인터럽트 컨트롤러(IC)만으로도 충분했지만,
멀티코어 (SMP: Symmetric Multiprocessing) 환경에서는 다음과 같은 문제가 생깁니다:- 인터럽트를 어떤 CPU에 보낼지 결정 필요
- 동시에 여러 인터럽트 발생 시 우선순위 조정 필요
- 각 CPU별로 인터럽트 마스크 제어 필요
이런 문제를 해결하기 위해 ARM은 GIC를 표준 인터럽트 컨트롤러로 도입함.
GIC 구성 요소
GIC는 크게 두 부분으로 구성됩니다:
구성요소설명GIC Distributor 인터럽트를 전체 시스템에 배포함. 어떤 CPU가 해당 인터럽트를 처리할지 결정 GIC CPU Interface 각 개별 CPU와의 인터페이스. 인터럽트 우선순위 필터링, 인터럽트 인식 및 수신 등을 담당 다시 본론으로..
전체 흐름 개요
- main() → IRQ 시스템 초기화 → UART 초기화 → 인터럽트 발생 → 문자 처리
- 대상 환경: ARM Cortex-A + GIC + 메모리 맵 기반 UART
- 프로젝트 기준: RTOS_Embedded 등 베어메탈 코드 기반 예시
1. main() 시작점
void main(void) { Hw_init(); // 하드웨어 초기화 ... }
2. Hw_init() — 하드웨어 초기화
static void Hw_init(void) { Hal_interrupt_init(); // 인터럽트 컨트롤러 초기화 Hal_uart_init(); // UART 장치 초기화 }
3. Hal_interrupt_init() — GIC 설정
void Hal_interrupt_init(void) { GicCpu->cpucontrol.bits.Enable = 1; GicCpu->prioritymask.bits.Prioritymask = 0xFF; GicDist->distributorctrl.bits.Enable = 1; for (int i = 0; i < INTERRUPT_HANDLER_NUM; i++) sHandlers[i] = NULL; enable_irq(); // __asm__("cpsie i") → 전역 인터럽트 허용 }
이 단계에서 CPU는 IRQ를 받을 준비가 완료된다.
4. Hal_uart_init() — UART 설정 및 인터럽트 등록
void Hal_uart_init(void) { Uart->ctrl.bits.RX_enable = 1; Uart->ctrl.bits.TX_enable = 1; Uart->intmask.bits.RX_interrupt = 1; Hal_interrupt_enable(UART_IRQ_NUM); Hal_interrupt_register_handler(interrupt_handler, UART_IRQ_NUM); }
5. 인터럽트 대기 상태
이 시점까지 시스템은 다음 조건을 만족한다:
- GIC은 IRQ 허용 상태
- UART RX 인터럽트는 마스크 해제됨
- 인터럽트 핸들러가 IRQ 번호에 등록됨
6. 사용자 입력 시퀀스 (예: 'A' 키)
키보드 입력 → UART RX 핀 신호 → RX FIFO 저장 → RX interrupt 발생
7. 인터럽트 발생 → Irq_Handler 진입
void Irq_Handler(void) { uint32_t irq_num = Gic_GetCurrentInterruptID(); if (sHandlers[irq_num]) { sHandlers[irq_num](); // 등록된 핸들러 호출 } }
8. interrupt_handler() — 실제 UART RX 핸들러
void interrupt_handler(void) { uint8_t ch = Hal_uart_get_char(); Hal_uart_put_char(ch); // echo 출력 }
최종 결과 흐름 요약
입력된 문자는 다음 경로를 통해 echo 출력된다.
터미널 입력
→ UART RX FIFO
→ 인터럽트 트리거
→ GIC → CPU → Irq_Handler
→ 등록된 interrupt_handler
→ get_char() → put_char()
→ 출력 완료
핵심 구조체 정리
구조체/배열 목적 GicCpu, GicDist ARM GIC 인터페이스 설정 Uart UART 레지스터 접근 (RX/TX, FIFO 등) sHandlers[] IRQ 번호 ↔ 핸들러 함수 등록 테이블 Irq_Handler() 공통 인터럽트 진입점 interrupt_handler() UART 인터럽트 전용 핸들러 마무리
이 글에서는 베어메탈 환경에서 UART RX 인터럽트를 기반으로 사용자 입력을 처리하는 전체 과정을 다뤘다. 시스템이 어떻게 IRQ를 받고, 어떻게 핸들러가 등록되고 실행되는지를 함수 레벨에서 정리해보면, 인터럽트 기반 시스템의 작동 원리를 보다 명확하게 이해할 수 있다. 이후 실제 RTOS 환경에서도 유사한 구조를 바탕으로 보다 복잡한 인터럽트 기반 입력 처리를 구현할 수 있을 것이다.
참고
임베디드 OS 개발 프로젝트:ARM 기반 펌웨어/RTOS의 원리와 구조 - 컴퓨터공학 | 쿠팡
쿠팡에서 임베디드 OS 개발 프로젝트:ARM 기반 펌웨어/RTOS의 원리와 구조 구매하고 더 많은 혜택을 받으세요! 지금 할인중인 다른 컴퓨터공학 제품도 바로 쿠팡에서 확인할 수 있습니다.
www.coupang.com
'Share > OS' 카테고리의 다른 글
[error] arm-none-eabi-gdb install 설치에러 (1) 2024.09.17 macOS pip 설치오류 해결 (invalid syntex) (0) 2021.12.20 [error] apt-get update 에러 , apt install net-tools 설치 에러, could not get lock /var/lib/dpkg/lock (2) 2021.11.23 VirtualBox 해상도 및 복사붙여넣기 에러 (could not get lock dpkg ) dpkg 오류 (0) 2021.10.01 임베디드 OS 개발 [8,9,10장] 요약 (0) 2021.03.27