코드를 느껴바라

[임베디드] TC275로 스톱워치 구현 : 74HC595 제어와 Flag 기반 인터럽트 설계 본문

개발/임베디드(Embedded)

[임베디드] TC275로 스톱워치 구현 : 74HC595 제어와 Flag 기반 인터럽트 설계

feelTheCode 2026. 2. 23. 17:19

개요

이번 실습에서는 Infineon AURIX TC275 마이크로컨트롤러와 74HC595 시프트 레지스터를 결합하여 4자리 4FND에 시간을 표시하는 스톱워치 시스템을 구현해 보았습니다.

시스템 부팅 후 0000부터 숫자가 순차적으로 증가하며, 두 개의 외부 버튼(SW1, SW2)을 통해 각각 '초기화(Reset)'와 '일시정지(Pause)' 기능을 제어할 수 있도록 설계했습니다. 일시정지 시에는 화면에 "----" 기호가 출력됩니다.


1. 핵심 설계 전략: "Flag & Main Loop" 구조와 멀티플렉싱

이 코드 역시 인터럽트 핸들러(ISR)에서는 '깃발(Flag)'만 들고, 실제 디스플레이 구동 및 숫자 계산과 같은 무거운 로직은 메인 루프에서 처리하는 방식을 엄격하게 따르고 있습니다.

만약 7세그먼트를 제어하기 위한 딜레이 함수나 시프트 레지스터 제어 로직을 외부 인터럽트 핸들러 내부에서 처리했다면 다음과 같은 치명적인 문제가 발생합니다.

  • 응답성 저하 및 데드락: 74HC595로 데이터를 밀어 넣는 데는 시간 지연(Bit-banging)이 발생합니다. ISR 안에서 이 시간을 소모하면 다른 중요한 시스템 타이머나 안전 장치(Watchdog 등)의 인터럽트가 지연되어 시스템이 멈출 수 있습니다.
  • 디스플레이 지터(Jitter): 4개의 7세그먼트를 동시에 켜진 것처럼 보이게 하려면 매우 빠른 속도로 번갈아 켜는 '멀티플렉싱(Multiplexing)'이 필요합니다. 핸들러가 CPU를 점유해버리면 메인 루프의 멀티플렉싱 주기가 틀어져 LED가 심하게 깜빡이는(Flickering) 현상이 발생합니다.

따라서 하드웨어 버튼 입력은 전역 변수(Flag)만 변경하고, 디스플레이 출력은 메인 루프에 온전히 맡기는 구조를 채택했습니다.


2. 주요 함수 및 코드 분석

① ISR0(), ISR1() : 외부 버튼을 통한 상태 제어
발췌 이유: 인터럽트 지연을 최소화하는 모범적인 플래그 기반 처리 로직입니다.

volatile int is_paused = 0, is_reset = 0;

// SW2
void ISR0(void) { is_paused = !is_paused; }

// SW1
void ISR1(void) { is_reset = 1; }
  • 분석: 외부 인터럽트 발생 시 직접 로직을 수행하지 않고 is_pausedis_reset 변수의 상태만 변경합니다. 하드웨어 버튼이 소프트웨어(메인 루프)의 흐름을 직접 제어하는 핵심 연결 고리입니다. 컴파일러 최적화로 인해 변수 값이 무시되는 것을 막기 위해 반드시 volatile로 선언해야 합니다.

② send(), send_port() : 74HC595 시프트 레지스터 제어
발췌 이유: GPIO 핀을 토글하여 SPI 통신을 소프트웨어로 에뮬레이션하는 비트뱅잉(Bit-banging) 로직입니다.

void send(uint8_t X){
    for(int i=8; i>=1; i--){
        if(X & 0x80){
            IfxPort_setPinHigh(DIO.port, DIO.pinIndex);
        }
        else{
            IfxPort_setPinLow(DIO.port, DIO.pinIndex);
        }
        X <<= 1;
        // 시프트 클럭(SCLK) 토글
        IfxPort_setPinLow(SCLK.port, SCLK.pinIndex);
        IfxPort_setPinHigh(SCLK.port, SCLK.pinIndex);
    }
}
  • 분석: 1바이트 데이터(X)를 최상위 비트(MSB)부터 1비트씩 쪼개어 DIO 핀으로 출력합니다. 데이터를 하나 올릴 때마다 SCLK 핀을 Low에서 High로 토글(Rising Edge)하여 시프트 레지스터 내부로 데이터를 밀어 넣습니다. send_port 함수는 이후 RCLK 래치 핀을 토글하여 밀어 넣은 데이터를 실제 출력 핀으로 내보냅니다.

③ initERU() : 외부 인터럽트 하드웨어 설정 (Register Level)
발췌 이유: iLLD 하위 수준 제어를 통해 버튼의 물리적 신호를 어떻게 받아들이는지 보여줍니다.

    // ERU 설정 중 하강 엣지 감지 및 인터럽트 허용
    SCU_EICR1.U |=  (1 << FEN0_IDX);
    SCU_EICR1.U |= 1 << EIEN0_IDX;

    // SRC (Service Request Control) 설정 : 우선순위 부여
    SRC_SCU_SCU_ERU0.U &= ~0xFF;
    SRC_SCU_SCU_ERU0.U |= 0x10;
  • 분석: SCU_EICR1 레지스터의 FEN0 비트를 설정하여 버튼을 누르는 순간(하강 에지)을 감지합니다. SRC_SCU_SCU_ERU0 레지스터를 통해 해당 인터럽트의 우선순위를 0x10으로 설정했습니다. 단, EXIS0_IDX 필드에 할당된 값이 실제 P02.0 핀과 정확히 매핑되는지 여부는 MCU 데이터시트와 iLLD 핀맵 헤더를 통한 확인 필요 항목입니다.

3. 상태 제어 및 멀티플렉싱 로직 (Main Loop)

메인 루프에서는 깃발(Flag) 상태에 따라 숫자를 분리하고 LED로 출력하는 멀티플렉싱을 수행합니다.

        if(!is_paused){
            for(int n=n1+n2*10+n3*100+n4*1000; n<9999; n++){
                if(is_reset || is_paused) break; // 플래그 감지 시 즉시 탈출

                n1 = (int)n%10;
                n2 = (int)(n%100)/10;
                // ... (생략) ...

                // 멀티플렉싱 및 시간 지연 루프
                for(int i=0; i<5000; i++){
                    send_port(~_LED_0F[n1], 0x1);
                    send_port(~_LED_0F[n2], 0x2);
                    send_port(~_LED_0F[n3], 0x4);
                    send_port(~_LED_0F[n4], 0x8);
                }
            }
        }
        else {
            // 일시정지 상태: "----" 표시
            for(int i = 0; i < 5000; i++){
                send_port((uint8)~SEG_DASH, 0x1);
                // ... (생략) ...
                if(is_reset || !is_paused) break;
            }
        }
  • 동작 원리: * !is_paused 상태일 때는 0부터 9999까지 n을 증가시키며 각 자릿수(n1~n4)를 분리합니다.
  • for(int i=0; i<5000; i++) 루프는 두 가지 역할을 합니다. 첫째, 4개의 자릿수를 매우 빠르게 번갈아 켜서 사람의 눈에 동시에 켜진 것처럼 보이게 하는 잔상 효과(Multiplexing)를 만듭니다. 둘째, 숫자가 증가하는 속도(시간 지연)를 조절합니다.
  • 만약 중간에 외부 인터럽트로 인해 is_reset이나 is_paused 플래그가 1로 바뀌면, break 문을 통해 무거운 루프를 즉시 탈출하여 높은 응답성을 확보합니다.

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

  1. 타이머 정확도 개선 (핵심): 현재 코드의 가장 큰 맹점은 스톱워치의 시간 증가를 메인 루프의 for문 5000번 반복 횟수에 의존한다는 점입니다. 이는 컴파일러 최적화나 CPU 클럭에 따라 속도가 심각하게 변합니다. 이미 구현되어 있는 STM_Int0Handlertimer_tick 플래그를 활용하여, 하드웨어 타이머 기반으로 정확히 10ms 또는 100ms마다 숫자가 증가하도록 로직을 수정해야 합니다.
  2. 디바운싱(Debouncing) 부재: 하드웨어 스위치의 기계적 접점 떨림으로 인해 버튼을 한 번 눌러도 ISR0가 여러 번 호출될 수 있습니다. ISR 내부 또는 메인 루프에서 일정 시간(예: 50ms) 동안 중복 입력을 무시하는 디바운스 처리가 필요합니다.

실행 이미지

5. 전체 소스 코드 (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"
#include <stdint.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

#define SCLK IfxPort_P00_0
#define RCLK IfxPort_P00_1
#define DIO IfxPort_P00_2

#define SEG_G 0x40

static const uint8 SEG_DASH = SEG_G;

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, is_reset = 0;  // 일시정지 상태 (0: 실행, 1: 정지)

static const uint8 _LED_0F[16] = {
    0x3F, // 0
    0x06, // 1
    0x5B, // 2
    0x4F, // 3
    0x66, // 4
    0x6D, // 5
    0x7D, // 6
    0x07, // 7
    0x7F, // 8
    0x6F, // 9
    0x77, // A
    0x7C, // b
    0x39, // C
    0x5E, // d
    0x79, // E
    0x71  // F
};

// SW2
void ISR0(void) { is_paused = !is_paused; }

// SW1
void ISR1(void) { is_reset = 1; }

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();
}

void send(uint8_t X){
    for(int i=8; i>=1; i--){
        if(X & 0x80){
            IfxPort_setPinHigh(DIO.port, DIO.pinIndex);
        }
        else{
            IfxPort_setPinLow(DIO.port, DIO.pinIndex);
        }
        X <<= 1;
        IfxPort_setPinLow(SCLK.port, SCLK.pinIndex);
        IfxPort_setPinHigh(SCLK.port, SCLK.pinIndex);
    }
}

void send_port(uint8_t X, uint8_t port){
    send(X);
    send(port);
    IfxPort_setPinLow(RCLK.port, RCLK.pinIndex);
    IfxPort_setPinHigh(RCLK.port, RCLK.pinIndex);
}

static inline void delay_ms(int t){
    for (volatile int i = 0; i < t * 5000; i++);
}

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;


    int n1=0, n2=0, n3=0, n4=0;
    while(1)
    {
        if(!is_paused){
            for(int n=n1+n2*10+n3*100+n4*1000; n<9999; n++){
                if(is_reset || is_paused){
                    break;
                }
                n1 = (int)n%10;
                n2 = (int)(n%100)/10;
                n3 = (int)(n%1000)/100;
                n4 = (int)(n%10000)/1000;
                for(int i=0; i<5000; i++){
                    send_port(~_LED_0F[n1], 0x1);
                    send_port(~_LED_0F[n2], 0x2);
                    send_port(~_LED_0F[n3], 0x4);
                    send_port(~_LED_0F[n4], 0x8);
                }
            }
        }
        else
        {
            // ✅ pause 상태: "----" 표시 유지
            for(int i = 0; i < 5000; i++)
            {
                send_port((uint8)~SEG_DASH, 0x1);
                send_port((uint8)~SEG_DASH, 0x2);
                send_port((uint8)~SEG_DASH, 0x4);
                send_port((uint8)~SEG_DASH, 0x8);

                // reset 또는 resume 감지되면 빠르게 탈출
                if(is_reset || !is_paused) break;
            }
        }
        if(is_reset){
            is_reset = 0;
            n1=0, n2=0, n3=0, n4=0;
        }
        //send_port(0xC0, 0x1);
        /*
        // 깃발이 올라왔는지 계속 감시
        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) {
    // --- P02: 버튼 입력 ---
    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);

    // --- P10: LED 출력 ---
    P10_IOCR0.U &= ~(0x1F << PCn_2_IDX);
    P10_IOCR0.U |=  (0x10 << PCn_2_IDX);

    P10_IOCR0.U &= ~(0x1F << PCn_1_IDX);
    P10_IOCR0.U |=  (0x10 << PCn_1_IDX);

    // --- P00: 74HC595 (SCLK/RCLK/DIO) 출력 ---
    // SCLK = P00.0  / RCLK = P00.1 / DIO = P00.2 라고 했으니
    P00_IOCR0.U &= ~(0x1F << PCn_0_IDX);
    P00_IOCR0.U |=  (0x10 << PCn_0_IDX);  // P00.0 output

    P00_IOCR0.U &= ~(0x1F << PCn_1_IDX);
    P00_IOCR0.U |=  (0x10 << PCn_1_IDX);  // P00.1 output

    P00_IOCR0.U &= ~(0x1F << PCn_2_IDX);
    P00_IOCR0.U |=  (0x10 << PCn_2_IDX);  // P00.2 output
}

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);
}
반응형