search

SW Stack과 모듈화

이번 포스트에서는 ARM 레포지토리에서 실제로 다룬 SW Stack 개념과 폴더 분리를 통한 모듈화 방법을 알아본다.

📁 폴더 분리의 필요성

GPIO 관련 기능 분리

기존에 main.c에 모든 코드가 집중되어 있던 것을 GPIO 관련된 기능들을 따로 분리한다.

분리 전 문제점:

  • 모든 코드가 main.c에 집중
  • 기능별 구분이 어려움
  • 재사용성 낮음
  • 유지보수 어려움

분리 후 장점:

  • 기능별 명확한 구분
  • 코드 재사용 가능
  • 유지보수 용이
  • 협업 효율성 향상

🏗️ SW Stack 개념

SW의 흐름과 계층 구조

SW의 흐름은 위에서 아래로 진행된다. SW가 HW를 제어하는 계층적 구조다.

SW Stack 구조

SW Stack 구조도

+------------------------+
|         SW            |
|  +-----------------+  |
|  |   Application   |  | <- 애플리케이션 레이어
|  +-----------------+  |
|  |     API/HAL     |  | <- 하드웨어 추상화 계층
|  +-----------------+  |
|  |     Driver      |  | <- 드라이버 계층
|  +-----------------+  |
+------------------------+
|         HW            | <- 하드웨어
+------------------------+

📂 폴더 구조 설계

계층별 폴더 분리

project/
├── App/                 # 애플리케이션 계층
│   ├── main.c
│   └── app_config.h
├── HAL/                 # 하드웨어 추상화 계층
│   ├── gpio_hal.c
│   ├── gpio_hal.h
│   ├── led_hal.c
│   └── led_hal.h
├── Driver/              # 드라이버 계층
│   ├── gpio_driver.c
│   ├── gpio_driver.h
│   └── system_config.h
└── Hardware/            # 하드웨어 정의
    ├── stm32f411xx.h
    └── memory_map.h

🔧 GPIO 모듈 분리 실습

1. GPIO 드라이버 계층 (gpio_driver.h)

#ifndef GPIO_DRIVER_H
#define GPIO_DRIVER_H

#include <stdint.h>

// GPIO 베이스 주소 정의
#define GPIOA_BASE    0x40020000
#define GPIOB_BASE    0x40020400
#define GPIOC_BASE    0x40020800

// GPIO 레지스터 구조체
typedef struct {
    volatile uint32_t MODER;    // Mode register
    volatile uint32_t OTYPER;   // Output type register
    volatile uint32_t OSPEEDR;  // Speed register
    volatile uint32_t PUPDR;    // Pull-up/pull-down register
    volatile uint32_t IDR;      // Input data register
    volatile uint32_t ODR;      // Output data register
    volatile uint32_t BSRR;     // Bit set/reset register
    volatile uint32_t LCKR;     // Lock register
    volatile uint32_t AFR[2];   // Alternate function registers
} GPIO_TypeDef;

// GPIO 포트 정의
#define GPIOA  ((GPIO_TypeDef*)GPIOA_BASE)
#define GPIOB  ((GPIO_TypeDef*)GPIOB_BASE)
#define GPIOC  ((GPIO_TypeDef*)GPIOC_BASE)

// 함수 선언
void gpio_clock_enable(GPIO_TypeDef* gpio);
void gpio_set_mode(GPIO_TypeDef* gpio, uint8_t pin, uint8_t mode);
void gpio_write_pin(GPIO_TypeDef* gpio, uint8_t pin, uint8_t state);

#endif

2. GPIO 드라이버 계층 (gpio_driver.c)

#include "gpio_driver.h"

// RCC 레지스터 주소
#define RCC_BASE      0x40023800
#define RCC_AHB1ENR   (*(volatile uint32_t*)(RCC_BASE + 0x30))

void gpio_clock_enable(GPIO_TypeDef* gpio) {
    if (gpio == GPIOA) {
        RCC_AHB1ENR |= (1 << 0);  // GPIOA 클록 활성화
    } else if (gpio == GPIOB) {
        RCC_AHB1ENR |= (1 << 1);  // GPIOB 클록 활성화
    } else if (gpio == GPIOC) {
        RCC_AHB1ENR |= (1 << 2);  // GPIOC 클록 활성화
    }
}

void gpio_set_mode(GPIO_TypeDef* gpio, uint8_t pin, uint8_t mode) {
    gpio->MODER &= ~(3 << (pin * 2));      // 기존 모드 클리어
    gpio->MODER |= (mode << (pin * 2));    // 새 모드 설정
}

void gpio_write_pin(GPIO_TypeDef* gpio, uint8_t pin, uint8_t state) {
    if (state) {
        gpio->BSRR = (1 << pin);           // Set pin
    } else {
        gpio->BSRR = (1 << (pin + 16));    // Reset pin
    }
}

3. LED HAL 계층 (led_hal.h)

#ifndef LED_HAL_H
#define LED_HAL_H

// LED 상태 정의
typedef enum {
    LED_OFF = 0,
    LED_ON  = 1
} LED_State_t;

// LED 초기화 및 제어 함수
void led_init(void);
void led_set_state(LED_State_t state);
void led_toggle(void);

#endif

4. LED HAL 계층 (led_hal.c)

#include "led_hal.h"
#include "gpio_driver.h"

// LED 핀 정의
#define LED_PORT    GPIOA
#define LED_PIN     5

void led_init(void) {
    // GPIO 클록 활성화
    gpio_clock_enable(LED_PORT);
    
    // LED 핀을 출력 모드로 설정
    gpio_set_mode(LED_PORT, LED_PIN, 1);  // 1 = Output mode
}

void led_set_state(LED_State_t state) {
    gpio_write_pin(LED_PORT, LED_PIN, state);
}

void led_toggle(void) {
    static LED_State_t current_state = LED_OFF;
    current_state = (current_state == LED_OFF) ? LED_ON : LED_OFF;
    led_set_state(current_state);
}

5. 애플리케이션 계층 (main.c)

#include "led_hal.h"

// 간단한 딜레이 함수
void delay(volatile uint32_t count) {
    while(count--);
}

int main(void) {
    // LED 초기화
    led_init();
    
    while(1) {
        led_toggle();        // LED 토글
        delay(1000000);      // 딜레이
    }
    
    return 0;
}

🎯 모듈화의 장점

1. 코드 재사용성

  • GPIO 드라이버는 다른 프로젝트에서도 사용 가능
  • LED HAL은 다른 LED 프로젝트에서 재사용 가능

2. 유지보수성

  • 각 계층별로 독립적인 수정 가능
  • 버그 발생 시 해당 계층만 집중 디버깅

3. 확장성

  • 새로운 하드웨어 추가 시 드라이버 계층만 수정
  • 새로운 기능 추가 시 해당 계층에만 추가

4. 협업 효율성

  • 계층별로 업무 분담 가능
  • 인터페이스가 명확해 협업 시 충돌 최소화

📝 계층 간 통신 규칙

1. 단방향 의존성

  • 상위 계층이 하위 계층을 호출
  • 하위 계층은 상위 계층을 직접 호출하지 않음

2. 인터페이스 표준화

  • 각 계층 간 명확한 인터페이스 정의
  • 헤더 파일을 통한 함수 원형 제공

3. 데이터 캡슐화

  • 각 계층의 내부 구현은 숨김
  • 공개 인터페이스만을 통한 접근

🚀 실제 프로젝트 적용 팁

1. 점진적 리팩토링

// Before: 모든 코드가 main.c에
int main(void) {
    // GPIO 클록 설정
    RCC_AHB1ENR |= (1 << 0);
    
    // GPIO 모드 설정
    GPIOA->MODER &= ~(3 << 10);
    GPIOA->MODER |= (1 << 10);
    
    while(1) {
        GPIOA->BSRR = (1 << 5);
        delay(1000000);
        GPIOA->BSRR = (1 << 21);
        delay(1000000);
    }
}

// After: 계층별 분리
int main(void) {
    led_init();
    
    while(1) {
        led_toggle();
        delay(1000000);
    }
}

2. 설정 파일 활용

// config.h
#define LED1_PORT    GPIOA
#define LED1_PIN     5

#define LED2_PORT    GPIOB
#define LED2_PIN     3

3. 에러 처리 추가

typedef enum {
    HAL_OK,
    HAL_ERROR,
    HAL_BUSY,
    HAL_TIMEOUT
} HAL_StatusTypeDef;

HAL_StatusTypeDef led_init(void);

📚 참고 자료

이 포스트는 다음 GitHub 레포지토리의 실습 내용을 바탕으로 작성되었습니다:

다음 포스트에서는 더 복잡한 하드웨어 추상화 계층 구현에 대해 알아보겠습니다.

keyboard_arrow_up