코드를 느껴바라

[임베디드] TC275로 GTM TOM PWM 부저 제어와 GPIO 폴링(Polling) 설계 본문

카테고리 없음

[임베디드] TC275로 GTM TOM PWM 부저 제어와 GPIO 폴링(Polling) 설계

feelTheCode 2026. 2. 25. 14:31

개요

이번 실습에서는 Infineon AURIX TC275 마이크로컨트롤러의 GTM(Generic Timer Module) TOM을 활용하여 피에조 부저(Piezo Buzzer)의 다양한 음계를 출력하는 '미니 피아노' 시스템을 구현해 보았습니다.

외부 인터럽트(ERU)를 사용했던 지난 스톱워치 실습과 달리, 이번에는 메인 루프에서 GPIO 핀의 상태를 지속적으로 읽어들이는 폴링(Polling) 방식을 채택했습니다. 두 개의 하드웨어 버튼(SW1, SW2)을 누르고 있는 동안 각각 '도(C4)'와 '솔(G4)' 음계가 재생되며, 손을 떼면 즉시 소리가 멈추도록 직관적으로 설계했습니다.

1. 핵심 설계 전략: 하드웨어 PWM 제어와 Polling 입력 구조

이 코드는 하드웨어 타이머(GTM TOM) 기반의 주파수 제어와 소프트웨어(메인 루프) 기반의 상태 감지를 결합한 구조를 가집니다.

  • PWM을 통한 음계(Pitch) 제어: 피에조 부저는 입력되는 전기 신호의 주파수(Frequency)에 따라 다른 음을 냅니다. GTM TOM 타이머의 주기(Period) 레지스터 값을 변경하여 주파수를 조절하고, 듀티 사이클(Duty Cycle)을 조절하여 소리의 출력 여부를 제어합니다.
  • 인터럽트(Interrupt) 대신 폴링(Polling) 채택: 버튼을 '누르고 있는 동안' 계속 소리가 나고, '떼면' 소리가 꺼지는 동작을 구현할 때는 ERU 인터럽트(Falling/Rising edge를 각각 따로 처리해야 함)보다 while(1) 루프 안에서 핀의 현재 상태를 실시간으로 읽는 폴링 방식이 훨씬 코드가 간결하고 직관적입니다.

2. 주요 함수 및 코드 분석

initBTN() 및 상태 읽기 : 풀업(Pull-up) 스위치 입력 제어

💡 핵심 포인트: iLLD 레지스터 직접 제어(IOCR) 방식과 고수준 라이브러리 함수(IfxPort_getPinState)를 적절히 혼용하여 구현한 직관적인 GPIO 입력 처리 로직입니다.

void initBTN(void) {
    // P02.0, P02.1 핀을 입력(Input) 및 풀업(Pull-up) 모드로 설정
    P02_IOCR0.U &= ~(0x1F << PCn_1_IDX);
    P02_IOCR0.U |=  (0x02 << PCn_1_IDX); // 0x02 = Input with Pull-up

    P02_IOCR0.U &= ~(0x1F << PCn_0_IDX);
    P02_IOCR0.U |=  (0x02 << PCn_0_IDX); 
}

// 메인 루프 내부
boolean sw1_pressed = (IfxPort_getPinState(IfxPort_P02_0.port, IfxPort_P02_0.pinIndex) == FALSE);

분석: IOCR0 레지스터를 제어하여 버튼 핀을 내부 풀업 저항이 연결된 입력 모드로 설정했습니다. 풀업 회로 특성상 평소에는 HIGH(TRUE) 상태를 유지하다가, 사용자가 스위치를 누르면 GND와 연결되어 LOW(FALSE) 상태가 됩니다.

makeSound() & stopSound() : 부저 주파수 및 듀티 제어

💡 핵심 포인트: 원하는 음계의 주파수(Hz)를 하드웨어 타이머의 주기(Period) 값으로 변환하는 핵심 수학 연산과 예외 처리가 포함되어 있습니다.

void makeSound(unsigned int sound){
    // 도, 레, 미, 파, 솔, 라, 시 주파수 배열 (Hz)
    float fBuzz[7] = {261.6, 293.724, 329.724, 349.309, 392.089, 440, 493.858};

    // 10MHz 타이머 클럭을 목표 주파수로 나누어 Period 값 계산
    unsigned int uPeriod = (unsigned int)(10000000 / fBuzz[sound]);

    g_tomConfig_buzzer.period = uPeriod;
    g_tomConfig_buzzer.dutyCycle = (uPeriod * 0.02f); // 2% 듀티로 소리 출력
    IfxGtm_Tom_Pwm_init(&g_tomDriver_buzzer, &g_tomConfig_buzzer);
}

void stopSound(void){
    g_tomConfig_buzzer.dutyCycle = 0; // 듀티를 0으로 만들어 PWM 출력 차단
    IfxGtm_Tom_Pwm_init(&g_tomDriver_buzzer, &g_tomConfig_buzzer);
}

분석: 타이머의 클럭 속도(10MHz)를 원하는 음계의 주파수로 나누어 타이머가 몇 번 카운트할 때마다 파형을 반복할지(uPeriod)를 결정합니다. 소리를 끌 때는 타이머를 완전히 끄는 대신, 듀티 사이클만 0%로 만들어 출력을 막는 우아한 방식을 사용했습니다.

3. 상태 제어 및 로직 (Main Loop)

메인 루프에서는 매우 빠른 속도로 스위치 상태를 감지하고 부저를 제어합니다.

    while(1){
        boolean sw1_pressed = (IfxPort_getPinState(IfxPort_P02_0.port, IfxPort_P02_0.pinIndex) == FALSE);
        boolean sw2_pressed = (IfxPort_getPinState(IfxPort_P02_1.port, IfxPort_P02_1.pinIndex) == FALSE);

        if (sw1_pressed){
            makeSound(0); // SW1 누름 -> '도' 재생
        }
        else if (sw2_pressed){
            makeSound(4); // SW2 누름 -> '솔' 재생
        }
        else{
            stopSound();  // 아무 버튼도 누르지 않음 -> 소리 끄기
        }
        waitTime(ticksFor10ms); // 디바운스 및 CPU 점유율 하향을 위한 10ms 대기
    }

동작 원리: 우선순위가 if - else if로 구성되어 있어, SW1과 SW2를 동시에 누르면 SW1('도') 소리가 우선적으로 출력됩니다. 매 루프 마지막의 waitTime(10ms)는 단순한 지연이 아니라, 물리적 스위치의 기계적 떨림(Chattering)으로 인한 오동작을 줄여주는 일종의 디바운싱(Debouncing) 역할과 CPU 부하를 낮추는 역할을 동시에 수행합니다.

4. 트러블슈팅 및 개선 아이디어

  • PWM 초기화(Init) 함수 반복 호출로 인한 글리치(Glitch): 현재 로직은 while(1) 안에서 버튼을 누르고 있는 내내 IfxGtm_Tom_Pwm_init() 함수가 10ms마다 반복 호출됩니다. init 함수는 타이머 카운터를 강제로 0으로 리셋하기 때문에, 부저 파형이 온전히 생성되지 못하고 찢어지거나 깜빡거리는 잡음이 발생할 수 있습니다.
  • 개선 방안:* 이전에 어떤 소리가 나고 있었는지 상태 변수(예: current_note)를 만들어, 버튼을 처음 눌렀을 때 딱 한 번만 makeSound()가 호출되도록 로직을 수정하거나, 타이머 섀도우 레지스터(SR0, SR1)에 직접 접근하여 초기화 없이 값만 업데이트하도록 코드를 개선해야 합니다.

5. 메인 소스 코드 (Cpu0_Main.c)

#include "Ifx_Types.h"
#include "IfxCpu.h"
#include "IfxScuWdt.h"
#include "IfxPort.h"
#include "IfxPort_PinMap.h"
#include "GTM_TOM_PWM.h"
#include "Driver_Adc.h"
#include "Driver_Stm.h"
#include "Bsp.h"

#define WAIT_TIME   10
#define PCn_2_IDX 19
#define PCn_1_IDX 11
#define PCn_0_IDX 3

IfxCpu_syncEvent g_cpuSyncEvent = 0;

void initBTN(void);
void makeSound(unsigned int sound);
void stopSound(void);

int core0_main(void)
{
    IfxCpu_enableInterrupts();
    IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
    IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());

    IfxCpu_emitEvent(&g_cpuSyncEvent);
    IfxCpu_waitEvent(&g_cpuSyncEvent, 1);

    Ifx_TickTime ticksFor10ms = IfxStm_getTicksFromMilliseconds(BSP_DEFAULT_TIMER, WAIT_TIME);

    initGtmTomPwm();
    Driver_Adc_Init();
    initBTN();

    while(1){
        // 입력 핀 상태 Polling
        boolean sw1_pressed = (IfxPort_getPinState(IfxPort_P02_0.port, IfxPort_P02_0.pinIndex) == FALSE);
        boolean sw2_pressed = (IfxPort_getPinState(IfxPort_P02_1.port, IfxPort_P02_1.pinIndex) == FALSE);

        if (sw1_pressed){
            makeSound(0); // SW1 -> '도'
        }
        else if (sw2_pressed){
            makeSound(4); // SW2 -> '솔'
        }
        else{
            stopSound();  // 무음
        }
        waitTime(ticksFor10ms);
    }
    return (1);
}

void initBTN(void) {
    P02_IOCR0.U &= ~(0x1F << PCn_1_IDX);
    P02_IOCR0.U |=  (0x02 << PCn_1_IDX); // SW2 (P02.1)

    P02_IOCR0.U &= ~(0x1F << PCn_0_IDX);
    P02_IOCR0.U |=  (0x02 << PCn_0_IDX); // SW1 (P02.0)
}

반응형