KungFu32A156MQT 마이크로컨트롤러(MCU)와 TM1638 LED 드라이버 칩을 활용하여 하드웨어 타이머 기반의 디지털 시계와 유동적인 LED 패턴(流水灯)을 구현하는 방법을 다룹니다. 이 시스템은 10ms 단위의 타이머 인터럽트를 통해 시간 데이터와 시각 효과를 정밀하게 관리합니다.
1. TM1638 드라이버 모듈 구현
TM1638은 3선식 직렬 인터페이스를 통해 8개의 세그먼트와 키 스캔을 제어합니다. 아래는 GPIO를 직접 제어하여 통신을 수행하는 드라이버 코드의 핵심 구성입니다.
/* tm1638_driver.h */
#ifndef __TM1638_DRIVER_H__
#define __TM1638_DRIVER_H__
#include "system_init.h"
#include "MYGPIO.h"
// 포트 및 핀 정의
#define PORT_DIO GPIOA_SFR
#define PIN_DIO GPIO_PIN_MASK_1
#define PORT_CLK GPIOD_SFR
#define PIN_CLK GPIO_PIN_MASK_4
#define PORT_STB GPIOD_SFR
#define PIN_STB GPIO_PIN_MASK_5
void TM1638_Core_Init(void);
void TM1638_SendCommand(uint8_t cmd);
void TM1638_WriteByte(uint8_t addr, uint8_t data);
uint8_t TM1638_ReadKey(void);
#endif
통신 프로토콜 구현 시 데이터는 LSB(Least Significant Bit)부터 전송하며, 각 비트 전송 후 클록 신호를 토글하여 동기화합니다.
/* tm1638_driver.c */
#include "tm1638_driver.h"
static const uint8_t SEG_CODES[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x00};
static void DIO_High() { GPIO_Set_Output_Data_Bits(PORT_DIO, PIN_DIO, Bit_SET); }
static void DIO_Low() { GPIO_Set_Output_Data_Bits(PORT_DIO, PIN_DIO, Bit_RESET); }
static void CLK_High() { GPIO_Set_Output_Data_Bits(PORT_CLK, PIN_CLK, Bit_SET); }
static void CLK_Low() { GPIO_Set_Output_Data_Bits(PORT_CLK, PIN_CLK, Bit_RESET); }
static void STB_High() { GPIO_Set_Output_Data_Bits(PORT_STB, PIN_STB, Bit_SET); }
static void STB_Low() { GPIO_Set_Output_Data_Bits(PORT_STB, PIN_STB, Bit_RESET); }
void Serial_Send(uint8_t val) {
for (int i = 0; i < 8; i++) {
CLK_Low();
if (val & 0x01) DIO_High();
else DIO_Low();
val >>= 1;
CLK_High();
}
}
void TM1638_WriteByte(uint8_t addr, uint8_t data) {
STB_Low();
Serial_Send(addr);
Serial_Send(data);
STB_High();
}
void TM1638_Core_Init(void) {
// GPIO 초기화 (Output 설정 생략)
TM1638_SendCommand(0x8F); // 디스플레이 On, 최대 밝기
TM1638_SendCommand(0x40); // 자동 주소 증가 모드
STB_Low();
Serial_Send(0xC0); // 시작 주소 설정
for (int i = 0; i < 16; i++) Serial_Send(0x00);
STB_High();
}
2. 타이머 T14 시스템 구성
시스템 클록 120MHz를 기준으로 10ms 주기의 인터럽트를 발생시키도록 설정합니다. 분주비(Prescaler)를 24로 설정하면 타이머 주파수는 5MHz가 되며, 주기를 50,000으로 설정하여 100Hz(10ms) 간격을 확보합니다.
void Configure_System_Timer(void) {
TIM_Reset(T14_SFR);
BTIM_Work_Mode_Config(T14_SFR, BTIM_TIMER_MODE);
BTIM_Set_Period(T14_SFR, 50000); // 주기 설정
BTIM_Set_Prescaler(T14_SFR, 23); // 24분주 (23+1)
BTIM_Clock_Config(T14_SFR, BTIM_SCLK);
INT_Interrupt_Priority_Config(INT_T14, 4, 0);
BTIM_Overflow_INT_Enable(T14_SFR, TRUE);
INT_Interrupt_Enable(INT_T14, TRUE);
BTIM_Cmd(T14_SFR, TRUE);
}
3. 실시간 시계 및 디스플레이 로직
시계의 시(hh), 분(mm), 초(ss) 데이터를 관리하고 이를 TM1638 세그먼트에 매핑합니다.
/* clock_logic.c */
uint8_t hour = 12, min = 0, sec = 0;
void Display_Time_Update(void) {
TM1638_WriteByte(0xC0, SEG_CODES[hour / 10]);
TM1638_WriteByte(0xC2, SEG_CODES[hour % 10]);
TM1638_WriteByte(0xC4, 0x40); // 구분선 (-)
TM1638_WriteByte(0xC6, SEG_CODES[min / 10]);
TM1638_WriteByte(0xC8, SEG_CODES[min % 10]);
TM1638_WriteByte(0xCA, 0x40); // 구분선 (-)
TM1638_WriteByte(0xCC, SEG_CODES[sec / 10]);
TM1638_WriteByte(0xCE, SEG_CODES[sec % 10]);
}
4. LED 시퀀스 연출
TM1638에 연결된 8개의 LED를 제어하여 특정 패턴을 반복합니다. 아래는 LED가 하나씩 채워지는 애니메이션 예시입니다.
void Render_LED_Pattern(void) {
static uint8_t step = 0;
static uint8_t direction = 1;
for (int i = 0; i < 8; i++) {
uint8_t state = (i <= step) ? 1 : 0;
TM1638_WriteByte(0xC1 + (i * 2), state);
}
if (direction) {
if (++step >= 7) direction = 0;
} else {
if (--step == 0) direction = 1;
}
}
5. 인터럽트 서비스 루틴(ISR) 및 메인 루프
T14 타이머 인터럽트 내에서 카운터를 관리하며, 주기적으로 LED 패턴과 시계 로직을 호출합니다.
volatile uint32_t tick_10ms = 0;
void __attribute__((interrupt)) _T14_exception(void) {
BTIM_Clear_Overflow_INT_Flag(T14_SFR);
tick_10ms++;
// 20ms 마다 LED 갱신
if (tick_10ms % 2 == 0) {
Render_LED_Pattern();
}
// 1000ms(1초) 마다 시계 갱신
if (tick_10ms >= 100) {
tick_10ms = 0;
if (++sec >= 60) {
sec = 0;
if (++min >= 60) {
min = 0;
if (++hour >= 24) hour = 0;
}
}
Display_Time_Update();
}
}
int main(void) {
SystemInit(120);
systick_delay_init(120);
TM1638_Core_Init();
Configure_System_Timer();
INT_All_Enable(TRUE);
while (1) {
// 메인 루프에서는 저전력 대기 또는 기타 비주기 작업 수행
}
}