코드를 느껴바라

[임베디드] TC275 STM 신호등 : 외부 인터럽트(ERU)와 Flag 기반 상태 머신 구현 본문

개발/임베디드(Embedded)

[임베디드] TC275 STM 신호등 : 외부 인터럽트(ERU)와 Flag 기반 상태 머신 구현

feelTheCode 2026. 2. 23. 14:43

개요

이번 실습에서는 Infineon AURIX TC275의 STM(System Timer Module)ERU(External Request Unit)를 결합하여, 단순한 자동 신호등을 넘어 사용자가 제어 가능한 신호등 시스템을 구현해 보았습니다.

1. 핵심 설계 전략: "Flag & Main Loop" 구조

이 코드의 가장 큰 특징은 인터럽트 핸들러에서는 '깃발(Flag)'만 들고, 실제 무거운 로직은 메인 루프에서 처리한다는 점입니다. 이는 시스템 안정성을 높이는 매우 중요한 설계 방식입니다.

 

그런데 만약 무거운 로직을 핸들러에서 처리하다 보면 어떤 문제가 생길까요?

+)

  1. 인터럽트 지연(Latency)과 응답성 저하
    임베디드 시스템은 보통 여러 개의 인터럽트를 관리합니다. 만약 낮은 우선순위의 핸들러가 CPU를 붙잡고 긴 로직을 수행하면, 더 중요한(높은 우선순위) 인터럽트가 제때 처리되지 못하고 밀리게 됩니다.

문제: 긴급한 센서 데이터 수집이나 통신 패킷 수신을 놓칠 수 있습니다.

  1. 데드락(Deadlock) 및 시스템 중단 가능성
    특정 인터럽트 핸들러가 실행 중일 때는 같은 우선순위나 낮은 우선순위의 인터럽트가 차단되는 경우가 많습니다.

문제: 무거운 로직 중에 다른 인터럽트의 결과(예: 특정 플래그가 바뀌길 기다림)를 기다리는 코드가 있다면, 시스템은 영원히 대기 상태에 빠지는 데드락 현상이 발생할 수 있습니다.

  1. 지터(Jitter) 발생
    주기적인 작업(예: 0.5초마다 LED 토글)을 수행할 때, 핸들러가 무거우면 실행 시간이 일정하지 않게 됩니다.

문제: 타이머는 정확히 0.5초마다 신호를 주지만, 핸들러가 끝나는 시간이 매번 달라지면 전체적인 제어 주기(Sampling Rate)가 흔들리는 지터 현상이 발생하여 정밀한 제어가 불가능해집니다.

  1. 메인 루프(Main Loop)의 기아 상태(Starvation)
    메인 함수(while(1))는 시스템의 전반적인 스케줄링을 담당합니다. 핸들러가 CPU 점유율을 너무 많이 가져가면 메인 루프가 실행될 시간이 부족해집니다.

문제: 버튼 입력 감지, 상태 모니터링, 로그 출력 등 메인에서 돌아가야 할 작업들이 버벅거리거나 멈춘 것처럼 보일 수 있습니다.

그렇기 때문에 메인 문에서 작성을 해주었습니다.

2. 주요 함수 및 코드 분석

STM_Int0Handler() : 0.5초 주기의 알람 설정

이 함수는 타이머가 설정된 시간에 도달할 때마다 실행됩니다.

void STM_Int0Handler(void){
    IfxStm_clearCompareFlag(g_Stm.stmSfr, g_Stm.stmConfig.comparator);
    IfxStm_increaseCompare(g_Stm.stmSfr, g_Stm.stmConfig.comparator, 50000000);

    timer_tick = 1; // "0.5초 지났다!" 깃발(Flag)만 들고 끝!
    IfxCpu_enableInterrupts();
}
  • 분석: 직접 로직을 수행하지 않고 timer_tick 변수만 1로 바꿉니다. 이는 메인 루프에게 "이제 일을 할 시간이다"라고 알려주는 신호탄 역할을 합니다.

ISR0() : 외부 버튼을 통한 일시정지 제어

ERU 외부 인터럽트를 통해 버튼(SW2) 입력 시 즉각적으로 동작 상태를 변경합니다.

void ISR0 (void) {
    is_paused = !is_paused; // 일시정지 상태 토글 (0: 실행, 1: 정지)
}
  • 분석: is_paused 변수 하나만 제어하여 메인 루프의 카운트가 멈추게 만듭니다. 하드웨어 버튼이 소프트웨어의 흐름을 직접 제어하는 핵심 연결 고리입니다.

initERU() : 외부 인터럽트 설정 (Register Level)

iLLD 라이브러리를 쓰지 않고 레지스터에 직접 접근하여 하드웨어를 설정하는 부분입니다.

void initERU(void) {
    // ERU 설정 중 하강 엣지(Falling Edge) 감지 및 인터럽트 허용 설정 발췌
    SCU_EICR1.U |= (1 << FEN0_IDX); // Falling Edge Detection
    SCU_EICR1.U |= 1 << EIEN0_IDX;  // External Interrupt Enable

    // SRC(Service Request Control) 설정 : 우선순위 부여
    SRC_SCU_SCU_ERU0.U |= 0x10;     // Priority 0x10
    SRC_SCU_SCU_ERU0.U |= 1 << SRE_IDX; 
}
  • 분석: 비트 연산을 통해 SCU_EICR, SRC_SCU 레지스터를 직접 건드립니다. 이를 통해 AURIX의 인터럽트 시스템이 버튼의 물리적 신호를 어떻게 받아들이는지 알 수 있습니다.

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

메인 루프에서는 0.5초마다 갱신되는 cntflag를 바탕으로 LED를 제어합니다.

flag = (cnt/10) % 3; // 0, 1, 2 상태 결정 (각 5초씩)

if(flag == 0){ // State 0: Red 전용 점등
    IfxPort_setPinLow(IfxPort_P10_2.port, IfxPort_P10_2.pinIndex);
    IfxPort_setPinHigh(IfxPort_P10_1.port, IfxPort_P10_1.pinIndex);
}
else if(flag == 1){ // State 1: Blue 전용 점등
    IfxPort_setPinLow(IfxPort_P10_1.port, IfxPort_P10_1.pinIndex);
    IfxPort_setPinHigh(IfxPort_P10_2.port, IfxPort_P10_2.pinIndex);
}
else{ // State 2: Blue 점멸 (Blink)
    IfxPort_setPinLow(IfxPort_P10_1.port, IfxPort_P10_1.pinIndex);
    if(cnt % 2 == 0){ // 0.5초 주기로 ON/OFF 반복
        IfxPort_setPinHigh(IfxPort_P10_2.port, IfxPort_P10_2.pinIndex);
    } else {
        IfxPort_setPinLow(IfxPort_P10_2.port, IfxPort_P10_2.pinIndex);
    }
}
  • 동작 원리:
  • flag0, 1일 때는 일반적인 신호등처럼 동작합니다.
  • flag2일 때는 cnt % 2 == 0 조건을 활용해 0.5초 간격으로 파란색 LED가 깜빡이도록 설계했습니다.
  • 만약 버튼을 눌러 is_paused가 1이 되면, cnt가 증가하지 않아 현재의 LED 상태(켜짐, 꺼짐, 혹은 깜빡이는 중의 한 지점)에서 그대로 멈추게 됩니다.

실행 장면


4. 전체 소스 코드 (Cpu0_Main.c)

/**********************************************************************************************************************
 * \file Cpu0_Main.c
 * \copyright Copyright (C) Infineon Technologies AG 2019
 * 
 * Use of this file is subject to the terms of use agreed between (i) you or the company in which ordinary course of 
 * business you are acting and (ii) Infineon Technologies AG or its licensees. If and as long as no such terms of use
 * are agreed, use of this file is subject to following:
 * 
 * Boost Software License - Version 1.0 - August 17th, 2003
 * 
 * Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and 
 * accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute,
 * and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the
 * Software is furnished to do so, all subject to the following:
 * 
 * The copyright notices in the Software and this entire statement, including the above license grant, this restriction
 * and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all 
 * derivative works of the Software, unless such copies or derivative works are solely in the form of 
 * machine-executable object code generated by a source language processor.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 
 * COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
 * IN THE SOFTWARE.
 *********************************************************************************************************************/
#include "Ifx_Types.h"
#include "IfxCpu.h"
#include "IfxScuWdt.h"
#include "IfxPort.h"
#include "IfxPort_PinMap.h"
#include "IfxStm.h"
#include "IfxCpu_Irq.h"

#define PCn_2_IDX 19
#define P2_IDX 2
#define PCn_1_IDX 11
#define P1_IDX 1
#define PCn_0_IDX 3
#define BLUE_PIN 2
#define RED_PIN 1

#define EXIS0_IDX 4
#define FEN0_IDX 8
#define REN0_IDX 9
#define EIEN0_IDX 11
#define INP0_IDX 12
#define IGP0_IDX 14

#define EXIS1_IDX 20
#define FEN1_IDX 24
#define REN1_IDX 25
#define EIEN1_IDX 27
#define INP1_IDX 28
#define IGP1_IDX 30

#define SRE_IDX 10
#define TOS_IDX 11

typedef struct{
        Ifx_STM *stmSfr;
        IfxStm_CompareConfig stmConfig;
        volatile uint8 LedBlink;
        volatile uint32 counter;
}App_Stm;

App_Stm g_Stm;

void initERU(void);
void initGPIO(void);
void IfxStmDemo_init(void);

IFX_INTERRUPT(ISR0, 0, 0x10);
IFX_INTERRUPT(ISR1, 0, 0x20);

IFX_INTERRUPT(STM_Int0Handler, 0, 100);


volatile int timer_tick = 0; // 타이머 깃발 (0: 대기, 1: 0.5초 경과)
volatile int is_paused = 0;  // 일시정지 상태 (0: 실행, 1: 정지)

void ISR0 (void) {
    is_paused = !is_paused; // 일시정지 상태만 뒤집고 끝!
}

void ISR1 (void) {
    static int red_state = 0;

    if (red_state){
        P10_OMR.U = (1u << (RED_PIN + 16u));  // RESET
    }
    else{
        P10_OMR.U = (1u << RED_PIN);          // SET
    }

    red_state = 1 - red_state;
}

void STM_Int0Handler(void){
    IfxStm_clearCompareFlag(g_Stm.stmSfr, g_Stm.stmConfig.comparator);
    IfxStm_increaseCompare(g_Stm.stmSfr, g_Stm.stmConfig.comparator, 50000000);

    timer_tick = 1; // "0.5초 지났다!" 깃발만 들고 끝!
    IfxCpu_enableInterrupts();
}

IfxCpu_syncEvent cpuSyncEvent = 0;

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

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

    initGPIO();
    initERU();
    IfxStmDemo_init();

    // 메인 함수용 지역 변수
    static int flag = 0;
    static int cnt = 0;

    while(1)
    {
        // 깃발이 올라왔는지 계속 감시
        if (timer_tick == 1) {
            timer_tick = 0; // 확인했으니 깃발을 다시 내림

            // 여기서부터 기존에 짜셨던 로직 그대로 실행
            if (is_paused == 0) {
                cnt++;
                if (cnt >= 30) cnt = 0; // 오버플로우 방지
            }

            flag = (cnt/10) % 3;

            if(flag == 0){
                IfxPort_setPinLow(IfxPort_P10_2.port, IfxPort_P10_2.pinIndex);
                IfxPort_setPinHigh(IfxPort_P10_1.port, IfxPort_P10_1.pinIndex);
            }
            else if(flag == 1){
                IfxPort_setPinLow(IfxPort_P10_1.port, IfxPort_P10_1.pinIndex);
                IfxPort_setPinHigh(IfxPort_P10_2.port, IfxPort_P10_2.pinIndex);
            }
            else{
                IfxPort_setPinLow(IfxPort_P10_1.port, IfxPort_P10_1.pinIndex);
                if(cnt%2==0){
                    IfxPort_setPinHigh(IfxPort_P10_2.port, IfxPort_P10_2.pinIndex);
                }
                else{
                    IfxPort_setPinLow(IfxPort_P10_2.port, IfxPort_P10_2.pinIndex);
                }
            }
        }
    }
}

void initGPIO(void) {
    // PC2.1 button
    P02_IOCR0.U &= ~(0x1F << PCn_1_IDX);
    P02_IOCR0.U |= 0x02 << PCn_1_IDX;

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

    // PC10.2 blue led
    P10_IOCR0.U &= ~(0x1F << PCn_2_IDX);
    P10_IOCR0.U |= 0x10 << PCn_2_IDX;

    // PC10.1 red led
    P10_IOCR0.U &= ~(0x1F << PCn_1_IDX);
    P10_IOCR0.U |= 0x10 << PCn_1_IDX;
}

void initERU(void) {
    // ERU
    SCU_EICR1.U &= ~(0x7 << EXIS0_IDX);
    SCU_EICR1.U |= 0x1 << EXIS0_IDX;

    SCU_EICR1.U |=  (1 << FEN0_IDX);
    SCU_EICR1.U |= 1 << EIEN0_IDX;

    SCU_EICR1.U &= ~(0x7 << INP0_IDX);

    SCU_IGCR0.U &= ~(0x3 << IGP0_IDX);
    SCU_IGCR0.U |= 0x1 << IGP0_IDX;

    // SW1
    SCU_EICR1.U &= ~(0x7 << EXIS1_IDX);
    SCU_EICR1.U |= 0x02 << EXIS1_IDX;

    SCU_EICR1.U |=  (1 << FEN1_IDX);
    SCU_EICR1.U |= 1 << EIEN1_IDX;

    SCU_EICR1.U &= ~(0x7 << INP1_IDX);
    SCU_EICR1.U |=  0x1 << INP1_IDX;

    SCU_IGCR0.U &= ~(0x3 << IGP1_IDX);
    SCU_IGCR0.U |= 0x1 << IGP1_IDX;

    // SRC (Service Request Control) Setting
    SRC_SCU_SCU_ERU0.U &= ~0xFF;
    SRC_SCU_SCU_ERU0.U |= 0x10;

    SRC_SCU_SCU_ERU0.U |= 1 << SRE_IDX;
    SRC_SCU_SCU_ERU0.U &= ~(0x3 << TOS_IDX);

    //SW1
    SRC_SCU_SCU_ERU1.U &= ~0xFF;
    SRC_SCU_SCU_ERU1.U |= 0x20;

    SRC_SCU_SCU_ERU1.U |= 1 << SRE_IDX;
    SRC_SCU_SCU_ERU1.U &= ~(0x3 << TOS_IDX);
}

void IfxStmDemo_init(void){
    boolean interruptState = IfxCpu_disableInterrupts();

    g_Stm.stmSfr = &MODULE_STM0;
    IfxStm_initCompareConfig(&g_Stm.stmConfig);

    g_Stm.stmConfig.triggerPriority = 100u;
    g_Stm.stmConfig.typeOfService = IfxSrc_Tos_cpu0;
    g_Stm.stmConfig.ticks = 100000000;

    IfxStm_initCompare(g_Stm.stmSfr, &g_Stm.stmConfig);

    IfxCpu_restoreInterrupts(interruptState);
}
반응형