printf("ho_tari\n");
4일차 본문
2025.03.07
오늘의 학습 목표
1. Timer INT
2. delay 함수를 Timer로 변경 처리
3. FND timer 처리
4. 과제 code review
폴링 vs. 인터럽트
폴링
- 코드 나열 순서에 의해 실행 순서 결정
- 모든 코드는 동일한 실행 우선 순위를 가짐
- 코드 A에 의해 코드 B의 실행이 지연될 수 있음
- 정해진 순서에 따라 실행되는 구조로 하드웨어의 지원은 필요하지 않음
- 코드 작성 및 이해가 쉬움
인터럽트
- 우선 순위에 따라 실행 순서 결정
- 인터럽트에 따라 서로 다른 실행 우선 순위를 가짐
- (우선 순위가 낮은) 코드 A에 의해 (우선 순위가 높은) 코드 B의 실행이 지연되지 않음
- (우선 순위가 높은) 비정상적인 코드를 먼저 실행되는 구조로 하드웨어에 의해 우선 순위에 따른 처리 지원
- 코드 작성 및 이해가 복잡하고 어려움
정리
- 폴링 : S/W가 주기적으로 event를 check해서 실행하는 것
예시) while(1)
getbutton();
- 인터럽트 : event를 H/W가 S/W에게 알려주는 방식
예시) H/W 버튼이 눌려지면 → getbutton();
스택
- 함수 호출 후 return address를 저장
- 지역 변수의 메모리 할당 공간
인터럽트
- 하드웨어에 의해 호출되는 “함수”로 이해 가능
- 인터럽트가 발생하면 현재 실행 중인 코드를 정지하고 인터럽트 처리 루틴(ISR)으로 즉시 이동하여 인터럽트를 먼저 처리
- 동시에 여러 개의 인터럽트가 발생하면 우선 순위가 높은 인터럽트를 우선 처리
- 35개의 인터럽트 사용 가능
- 인터럽트 벡터 테이블
- 인터럽트가 처리되기 위한 조건
1. 전역적 인터럽트 비트 세트
2. 개별 인터럽트 활성화 비트 세트
3. 인터럽트 발생 조건 충족
상태 레지스터
.ISR (Interrupt Service Routine)
- 인터럽트가 발생하였을 때 처리 루틴
- 하드웨어에 의해 호출 (코드 상에서는 ISR을 호출하는 부분이 없음)
- 반환값이 없고 매개변수의 데이터 형이 없는 함수
- 모든 ISR은 동일한 이름을 가짐
- 처리할 인터럽트의 종류를 인터럽트 벡터 이름인 매개변수로 구분
외부 인터럽트
- RESET을 제외하고 가장 우선순위가 높은 인터럽트
- 범용 입출력 핀의 값이나 상태 변화 시에 인터럽트 발생 (정해진 8개 핀으로만 외부 인터럽트 발생 가능)
타이머 / 카운터
- 입력 펄스를 세는 장치
즉, 카운터
- 일정한 주기의 펄스를 셈으로써 시간 측정
즉, 타이머 역할 수행 가능
- 마이크로컨트롤러의 시스템 클록 사용 가능 (1초에 16MHz를 가지고 MCU 동작)
- ATmega128 : 4개의 타이머 / 카운터 제공
- 0번과 2번의 8비트 타이머 / 카운터 (255)
- 1번과 3번의 16비트 타이머 /카운터 (65535)
- 8비트 타이머 / 카운터
- 0 ~ 255 까지 세기 반복
- 16MHz 시스템 클록을 사용하는 경우 256개 클록의 발생 시간은 0.016ms
- 분주기를 통해 클록의 속도를 늦춤으로써 보다 긴 시간 측정 가능
분주
- 주파수를 1/n로 하는 일
system clock = 16MHz
T = 1 / f = 1 / 16000000 = 0.0000000625 sec = 62.5 ns
8 비트 256개 클록 : 62.5 ns * 256 = 0.016ms
분주를 하는 이유
STM32F411RE에 입력되는 RC 오실레이터의 주파수(XTAL OSC)가 32KHZ이고 system clock이 최대 100MHZ로 동작 된다고 했을 때 32KHZ를 입력 받아서 100MHZ의 시스템 클럭을 만드는 것을 주파수를 체배(Frequency Multiplier) 한다고 한다. 반대로 시스템 클럭 100MHZ를 받아서 1MHZ 낮춰서 timer clock에 공급 하는 작업을 분주 한다고 한다. 여기서 시스템 클럭 100MHZ를 그대로 사용 하지 않고 분주를 하는 이유와 분주를 하지 않고 timer를 가동하면 어떤 문제가 발생 되는지 아는대로 설명 하시오
→ 시스템 클럭 100MHZ를 그대로 받아서 1ms timer를 생성 한다고 가정을 하자 이때 100MHZ 주파수의 1 clok 주기는 t = 1/f에서 1/100000000hz -> 0.00000001 sec 10ns 이다 여기서 1ms를 count하기 위해서는 10ns의 펄스를 100,000번을 세어야 1ms가 된다. 그 만큼 자주 count를 해야 하므로 CPU에 부하가 걸린다. 그러나 100MHZ를 1MHZ로 낮추는 작업(분주)을 하면 1개의 펄스 소요 시간 은 T = 1/f에서 1/1000000HZ ==> 0.000001sec (1us) 이다. 1000번을 count하면 1ms가 된다. 결론적으로 100MHZ에서 1ms를 재기위해서 100,000번을 세어야 했으나 1MZ 에서는 1000번만 count를 하면 되므로 그만큼 cpu의 부하가 적게 걸리기 때문이다.
TCCR0 레지스터 - 분주비 설정
LED 제어 (delay_ms 제거, 인터럽트 사용)
<led.c>
/*
* led.c
*
* Created: 2025-03-05 오전 10:21:53
* Author: microsoft
*/
#include "led.h"
int led_main(void); // 선언
void led_all_on(void);
void led_all_off(void);
void shift_left_ledon(void);
void shift_right_ledon(void);
void shift_left_keep_ledon(void);
void shift_right_keep_ledon(void);
void flower_on(void);
void flower_off(void);
void state_transition(void);
extern volatile int msec_count;
#define FUNC_NUMBER 6
int state = 0;
void (*fp[]) (void)=
{
shift_left_ledon,
shift_right_ledon,
shift_left_keep_ledon, // _delay_ms(200)
shift_right_keep_ledon,
flower_on,
flower_off
};
int led_main(void) // 정의
{
DDRA = 0xff; // PORTA에 연결된 pin8개를 output mode로
#if 1
while(1)
{
fp[state]();
//if(state == 6)
//{
//state = 0;
//}
}
#else
while(1)
{
shift_left_ledon();
shift_right_ledon();
shift_left_keep_ledon(); // _delay_ms(200)
shift_right_keep_ledon();
flower_on();
flower_off();
}
#endif
}
void shift_left_ledon(void)
{
#if 1
// (1) for문 제거 (2) _delay_ms(30) 제거
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 8)
{
i = 0;
PORTA = 0;
//state++;
state_transition();
}
else
{
PORTA = 0b00000001 << i++; // (1) PORTA = 0b00000001 << i (2) i++
}
}
#else // org
for (int i=0; i < 8; i++)
{
PORTA = 0b00000001 << i;
// 1 i=0;
// 00000010 i=1;
// 10000000 i=7;
_delay_ms(30);
}
#endif
}
void shift_right_ledon(void)
{
#if 1
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 8)
{
i = 0;
PORTA = 0;
// state++;
state_transition();
}
else
{
PORTA = 0b10000000 >> i++;
}
}
#else
PORTA=0;
for (int i=0; i < 8; i++)
{
PORTA = 0b10000000 >> i;
// 10000000 i=0;
// 01000000 i=1;
_delay_ms(30);
}
#endif
}
void shift_left_keep_ledon(void)
{
#if 1
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 8)
{
i = 0;
PORTA = 0;
// state++;
state_transition();
}
else
{
PORTA |= 0b00000001 << i;
i++;
}
}
#else
for (int i=0; i < 8; i++)
{
PORTA |= 0b00000001 << i;
// 1 i=0;
// 00000010 i=1;
// 10000000 i=7;
_delay_ms(30);
}
#endif
}
void shift_right_keep_ledon(void)
{
#if 1
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 8)
{
i = 0;
PORTA = 0;
// state++;
state_transition();
}
else
{
PORTA |= 0b10000000 >> i;
i++;
}
}
#else
PORTA=0;
for (int i=0; i < 8; i++)
{
PORTA |= 0b10000000 >> i;
// 10000000 i=0;
// 01000000 i=1;
_delay_ms(30);
}
#endif
}
void flower_on(void)
{
#if 1
// (1) for문 제거 (2) _delay_ms(30) 제거
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 4)
{
i = 0;
PORTA = 0;
// state++;
state_transition();
}
else
{
PORTA |= 0x10 << i | 0x08 >> i;
i++;
}
}
#else
PORTA=0;
for (int i=0; i < 4; i++)
{
PORTA |= 0x10 << i | 0x08 >> i;
_delay_ms(30);
}
#endif
}
void flower_off(void)
{
#if 1
unsigned char h=0xf0;
unsigned char l=0x0f;
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 4)
{
i = 0;
PORTA = 0;
// state++;
state_transition();
}
else
{
PORTA = (h >> i) & 0xf0 | (l << i) & 0x0f;
i++;
}
}
#else
unsigned char h=0xf0;
unsigned char l=0x0f;
PORTA=0;
for (int i=0; i < 4; i++)
{
PORTA = (h >> i) & 0xf0 | (l << i) & 0x0f;
_delay_ms(30);
}
PORTA=0;
_delay_ms(30);
#endif
}
void state_transition(void)
{
state++;
state = state % FUNC_NUMBER;
}
<main.c>
/*
* 01.LED_CONTROL.c
*
* Created: 2025-03-04 오후 4:25:34
* Author : microsoft
*/
#define F_CPU 16000000UL // 16MHZ
#include <avr/io.h>
#include <util/delay.h> // _delay_ms _delay_us
#include <avr/interrupt.h> // sei()
#include "button.h"
extern int led_main(void); // 선언
extern void init_button(void);
extern int get_button(int button_num, int button_pin);
extern void led_all_on(void);
extern void led_all_off(void);
extern void shift_left_ledon(void);
extern void shift_right_ledon(void);
extern void shift_left_keep_ledon(void);
extern void shift_right_keep_ledon(void);
extern void flower_on(void);
extern void flower_off(void);
extern int fnd_main(void);
volatile int msec_count = 0;
// volatile 변수 type 앞에 volatile을 선언하는 이유 : compiler한테 최적화 방지 지시
// cc -O
/*
예) 1 ~ 10까지 합을 구하는 프로그램
int i = 0; int sum = 0;
while( i < 10)
{
i++; sum += i;
}
printf("sum-->%d\n", sum); // 1 ~ 10 : 55가 된다.
*/
// TIMER0_OVF_vect
// ISR(Interrupt Service Routine)(함수)
// --> H/W가 S/W한테 event(상황변화)가 일어났다고 알려 주는 공간
// 250개의 pulse를 count(1ms)하면 이곳으로 자동적으로 들어온다.
// ISR루틴(함수)는 가능한 짧게 작성한다.
int led_toggle = 0;
ISR(TIMER0_OVF_vect)
{
// 6 ~ 256 : 250(1ms) 그래서 TCNT0를 6으로 설정하는 것이다.
TCNT0 = 6;
msec_count++; // 1ms마다 1씩 증가
}
int main(void)
{
init_timer0();
led_main();
while(1)
{
}
}
// none O/S 또는 loop monitor방식
#if 0
int main(void)
{
int button0_state=0; // 초기 상태를 0으로 출발
init_timer0();
led_main();
fnd_main();
void (*fp[]) (void) =
{
led_all_off,
led_all_on,
shift_left_ledon,
shift_right_ledon,
shift_left_keep_ledon,
shift_right_keep_ledon,
flower_on,
flower_off
};
init_button();
// led_main();
//76543210
DDRA = 0b11111111; // PORTA를 출력 모드(1)로 설정
//---- 상위 nibble : 상위 4bits
// ---- 하위 nibble
// DDR(Data Direction Register) : 방향 설정
// 1: 출력 0: 입력을 의미
// 0b : 2진수
// 0x : hex
// DDRA = 0xff;
#if 0 // 함수 포인터 배열
while (1)
{
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state++;
button0_state %= 8;
}
fp[button0_state] ();
}
#endif
#if 0 // switch ~ case
while (1)
{
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state++;
button0_state %= 8;
}
switch (button0_state) {
case 0:
led_all_off();
break;
case 1:
led_all_on();
break;
case 2:
shift_left_ledon();
break;
case 3:
shift_right_ledon();
break;
case 4:
shift_left_keep_ledon();
break;
case 5:
shift_right_keep_ledon();
break;
case 6:
flower_on();
break;
case 7:
flower_off();
break;
}
}
#endif
#if 0 // org
while (1) // for(;;)
{
// 1 button버튼 처리 (toggle)
// button0를 1번 누르면 led_all_on
// led_all_off
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state = !button0_state; // 반전 0 <--> 1
if (button0_state)
led_all_on();
else led_all_off();
}
}
#endif
}
#endif
// timer0를 초기화시키는 부분
// AVR에서 8bit timer 0/2번 중 0번을 초기화
// 임베디드/FPGA 등에서 제일 중요한 것은 초기화를 정확히 해주는 것이다.
// 그래서 이 부분을 특별히 신경을 써서 작성한다.
void init_timer0(void)
{
// 16MHz/64 분주(down) : divider / prescale
// ---------- 분주비 계산 ----------
// (1) 16000000Hz/64 ==> 250,000Hz
// (2) T(주기) 1clock의 소요 시간 : 1/f = 1/250,000 ==> 0.000004sec(4us) : 0.004ms
// (3) 8bit timer OV(OVflow) : 0.004ms x 256 = 0.001024sec --> 1.024ms
// 1ms마다 정확하게 INT를 띄우고 싶으면 0.004ms x 250개를 count = 0.001sec ==> 1ms
TCNT0 = 6; // TCNT : 0 ~ 256 1ms마다 TIMER0_OVF_vect로 진입한다.
// TCNT0 = 6으로 설정한 이유 : 6 ~ 256 : 250개의 펄스를 count하기 때문에 정확히 1ms가 된다.
// (4) 분주비 설정 64분주 (250,000Hz --> 250KHz)
TCCR0 |= 1 << CS02 | 0 << CS01 | 0 << CS00; // TCCR0 |= 0xf4; 보다는 좌측 코드 권장
// (5) Timer0 overflow INT를 허용(enable)
TIMSK |= 1 << TOIE0; // TIMSK |= 0x01;
sei(); // 전역(대문)으로 interrupt 허용
}
<실행 결과>
https://youtube.com/shorts/XOlpDFT_TfY
<led.c>
/*
* led.c
*
* Created: 2025-03-05 오전 10:21:53
* Author: microsoft
*/
#include "led.h"
#include "button.h"
int led_main(void); // 선언
void led_all_on(void);
void led_all_off(void);
void shift_left_ledon(void);
void shift_right_ledon(void);
void shift_left_keep_ledon(void);
void shift_right_keep_ledon(void);
void flower_on(void);
void flower_off(void);
void state_transition(void);
extern void init_button(void);
extern int get_button(int button_num, int button_pin);
extern volatile int msec_count;
#define FUNC_NUMBER 6
int state = 0;
int button0_state = 0;
int button1_state = 0;
void (*fp[]) (void)=
{
shift_left_ledon,
shift_right_ledon,
shift_left_keep_ledon, // _delay_ms(200)
shift_right_keep_ledon,
flower_on,
flower_off
};
int led_main(void) // 정의
{
DDRA = 0xff; // PORTA에 연결된 pin8개를 output mode로
init_button();
#if 1
while(1)
{
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state = !button0_state;
}
if (get_button(BUTTON1, BUTTON1PIN))
{
button1_state++;
button1_state %= FUNC_NUMBER;
}
fp[state]();
}
#else
while(1)
{
shift_left_ledon();
shift_right_ledon();
shift_left_keep_ledon(); // _delay_ms(200)
shift_right_keep_ledon();
flower_on();
flower_off();
}
#endif
}
void shift_left_ledon(void)
{
#if 1
// (1) for문 제거 (2) _delay_ms(30) 제거
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 8)
{
i = 0;
PORTA = 0;
state_transition();
}
else
{
PORTA = 0b00000001 << i++; // (1) PORTA = 0b00000001 << i (2) i++
}
}
#else // org
for (int i=0; i < 8; i++)
{
PORTA = 0b00000001 << i;
// 1 i=0;
// 00000010 i=1;
// 10000000 i=7;
_delay_ms(30);
}
#endif
}
void shift_right_ledon(void)
{
#if 1
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 8)
{
i = 0;
PORTA = 0;
state_transition();
}
else
{
PORTA = 0b10000000 >> i++;
}
}
#else
PORTA=0;
for (int i=0; i < 8; i++)
{
PORTA = 0b10000000 >> i;
// 10000000 i=0;
// 01000000 i=1;
_delay_ms(30);
}
#endif
}
void shift_left_keep_ledon(void)
{
#if 1
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 8)
{
i = 0;
PORTA = 0;
state_transition();
}
else
{
PORTA |= 0b00000001 << i;
i++;
}
}
#else
for (int i=0; i < 8; i++)
{
PORTA |= 0b00000001 << i;
// 1 i=0;
// 00000010 i=1;
// 10000000 i=7;
_delay_ms(30);
}
#endif
}
void shift_right_keep_ledon(void)
{
#if 1
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 8)
{
i = 0;
PORTA = 0;
state_transition();
}
else
{
PORTA |= 0b10000000 >> i;
i++;
}
}
#else
PORTA=0;
for (int i=0; i < 8; i++)
{
PORTA |= 0b10000000 >> i;
// 10000000 i=0;
// 01000000 i=1;
_delay_ms(30);
}
#endif
}
void flower_on(void)
{
#if 1
// (1) for문 제거 (2) _delay_ms(30) 제거
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 4)
{
i = 0;
PORTA = 0;
state_transition();
}
else
{
PORTA |= 0x10 << i | 0x08 >> i;
i++;
}
}
#else
PORTA=0;
for (int i=0; i < 4; i++)
{
PORTA |= 0x10 << i | 0x08 >> i;
_delay_ms(30);
}
#endif
}
void flower_off(void)
{
#if 1
unsigned char h=0xf0;
unsigned char l=0x0f;
static int i = 0;
if(msec_count >= 100)
{
msec_count = 0;
if(i >= 4)
{
i = 0;
PORTA = 0;
state_transition();
}
else
{
PORTA = (h >> i) & 0xf0 | (l << i) & 0x0f;
i++;
}
}
#else
unsigned char h=0xf0;
unsigned char l=0x0f;
PORTA=0;
for (int i=0; i < 4; i++)
{
PORTA = (h >> i) & 0xf0 | (l << i) & 0x0f;
_delay_ms(30);
}
PORTA=0;
_delay_ms(30);
#endif
}
void state_transition(void)
{
if(!button0_state)
{
state++;
state = state % FUNC_NUMBER;
}
else
{
state = button1_state;
}
}
<main.c>
/*
* 01.LED_CONTROL.c
*
* Created: 2025-03-04 오후 4:25:34
* Author : microsoft
*/
#define F_CPU 16000000UL // 16MHZ
#include <avr/io.h>
#include <util/delay.h> // _delay_ms _delay_us
#include <avr/interrupt.h> // sei()
#include "button.h"
extern int led_main(void); // 선언
extern void init_button(void);
extern int get_button(int button_num, int button_pin);
extern void led_all_on(void);
extern void led_all_off(void);
extern void shift_left_ledon(void);
extern void shift_right_ledon(void);
extern void shift_left_keep_ledon(void);
extern void shift_right_keep_ledon(void);
extern void flower_on(void);
extern void flower_off(void);
extern int fnd_main(void);
volatile int msec_count = 0;
// volatile 변수 type 앞에 volatile을 선언하는 이유 : compiler한테 최적화 방지 지시
// cc -O
/*
예) 1 ~ 10까지 합을 구하는 프로그램
int i = 0; int sum = 0;
while( i < 10)
{
i++; sum += i;
}
printf("sum-->%d\n", sum); // 1 ~ 10 : 55가 된다.
*/
// TIMER0_OVF_vect
// ISR(Interrupt Service Routine)(함수)
// --> H/W가 S/W한테 event(상황변화)가 일어났다고 알려 주는 공간
// 250개의 pulse를 count(1ms)하면 이곳으로 자동적으로 들어온다.
// ISR루틴(함수)는 가능한 짧게 작성한다.
int led_toggle = 0;
ISR(TIMER0_OVF_vect)
{
// 6 ~ 256 : 250(1ms) 그래서 TCNT0를 6으로 설정하는 것이다.
TCNT0 = 6;
msec_count++; // 1ms마다 1씩 증가
}
int main(void)
{
init_timer0();
led_main();
while(1)
{
}
}
// none O/S 또는 loop monitor방식
#if 0
int main(void)
{
int button0_state=0; // 초기 상태를 0으로 출발
init_timer0();
led_main();
//fnd_main();
void (*fp[]) (void) =
{
led_all_off,
led_all_on,
shift_left_ledon,
shift_right_ledon,
shift_left_keep_ledon,
shift_right_keep_ledon,
flower_on,
flower_off
};
init_button();
// led_main();
//76543210
DDRA = 0b11111111; // PORTA를 출력 모드(1)로 설정
//---- 상위 nibble : 상위 4bits
// ---- 하위 nibble
// DDR(Data Direction Register) : 방향 설정
// 1: 출력 0: 입력을 의미
// 0b : 2진수
// 0x : hex
// DDRA = 0xff;
#if 0 // 함수 포인터 배열
while (1)
{
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state++;
button0_state %= 8;
}
fp[button0_state] ();
}
#endif
#if 0 // switch ~ case
while (1)
{
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state++;
button0_state %= 8;
}
switch (button0_state) {
case 0:
led_all_off();
break;
case 1:
led_all_on();
break;
case 2:
shift_left_ledon();
break;
case 3:
shift_right_ledon();
break;
case 4:
shift_left_keep_ledon();
break;
case 5:
shift_right_keep_ledon();
break;
case 6:
flower_on();
break;
case 7:
flower_off();
break;
}
}
#endif
#if 0 // org
while (1) // for(;;)
{
// 1 button버튼 처리 (toggle)
// button0를 1번 누르면 led_all_on
// led_all_off
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state = !button0_state; // 반전 0 <--> 1
if (button0_state)
led_all_on();
else led_all_off();
}
}
#endif
}
#endif
// timer0를 초기화시키는 부분
// AVR에서 8bit timer 0/2번 중 0번을 초기화
// 임베디드/FPGA 등에서 제일 중요한 것은 초기화를 정확히 해주는 것이다.
// 그래서 이 부분을 특별히 신경을 써서 작성한다.
void init_timer0(void)
{
// 16MHz/64 분주(down) : divider / prescale
// ---------- 분주비 계산 ----------
// (1) 16000000Hz/64 ==> 250,000Hz
// (2) T(주기) 1clock의 소요 시간 : 1/f = 1/250,000 ==> 0.000004sec(4us) : 0.004ms
// (3) 8bit timer OV(OVflow) : 0.004ms x 256 = 0.001024sec --> 1.024ms
// 1ms마다 정확하게 INT를 띄우고 싶으면 0.004ms x 250개를 count = 0.001sec ==> 1ms
TCNT0 = 6; // TCNT : 0 ~ 256 1ms마다 TIMER0_OVF_vect로 진입한다.
// TCNT0 = 6으로 설정한 이유 : 6 ~ 256 : 250개의 펄스를 count하기 때문에 정확히 1ms가 된다.
// (4) 분주비 설정 64분주 (250,000Hz --> 250KHz)
TCCR0 |= 1 << CS02 | 0 << CS01 | 0 << CS00; // TCCR0 |= 0xf4; 보다는 좌측 코드 권장
// (5) Timer0 overflow INT를 허용(enable)
TIMSK |= 1 << TOIE0; // TIMSK |= 0x01;
sei(); // 전역(대문)으로 interrupt 허용
}
<실행 결과>
<main.c>
/*
* 01.LED_CONTROL.c
*
* Created: 2025-03-04 오후 4:25:34
* Author : microsoft
*/
#define F_CPU 16000000UL // 16MHZ
#include <avr/io.h>
#include <util/delay.h> // _delay_ms _delay_us
#include <avr/interrupt.h> // sei()
#include "button.h"
extern int led_main(void); // 선언
extern void init_button(void);
extern int get_button(int button_num, int button_pin);
extern void led_all_on(void);
extern void led_all_off(void);
extern void shift_left_ledon(void);
extern void shift_right_ledon(void);
extern void shift_left_keep_ledon(void);
extern void shift_right_keep_ledon(void);
extern void flower_on(void);
extern void flower_off(void);
extern int fnd_main(void);
void init_timer0(void);
volatile int msec_count = 0;
// volatile 변수 type 앞에 volatile을 선언하는 이유 : compiler한테 최적화 방지 지시
// cc -O
/*
예) 1 ~ 10까지 합을 구하는 프로그램
int i = 0; int sum = 0;
while( i < 10)
{
i++; sum += i;
}
printf("sum-->%d\n", sum); // 1 ~ 10 : 55가 된다.
*/
// TIMER0_OVF_vect
// ISR(Interrupt Service Routine)(함수)
// --> H/W가 S/W한테 event(상황변화)가 일어났다고 알려 주는 공간
// 250개의 pulse를 count(1ms)하면 이곳으로 자동적으로 들어온다.
// ISR루틴(함수)는 가능한 짧게 작성한다.
int led_toggle = 0;
ISR(TIMER0_OVF_vect)
{
// 6 ~ 256 : 250(1ms) 그래서 TCNT0를 6으로 설정하는 것이다.
TCNT0 = 6;
msec_count++; // 1ms마다 1씩 증가
}
int main(void)
{
init_timer0();
fnd_main();
while(1)
{
}
}
// none O/S 또는 loop monitor방식
#if 0
int main(void)
{
int button0_state=0; // 초기 상태를 0으로 출발
init_timer0();
led_main();
//fnd_main();
void (*fp[]) (void) =
{
led_all_off,
led_all_on,
shift_left_ledon,
shift_right_ledon,
shift_left_keep_ledon,
shift_right_keep_ledon,
flower_on,
flower_off
};
init_button();
// led_main();
//76543210
DDRA = 0b11111111; // PORTA를 출력 모드(1)로 설정
//---- 상위 nibble : 상위 4bits
// ---- 하위 nibble
// DDR(Data Direction Register) : 방향 설정
// 1: 출력 0: 입력을 의미
// 0b : 2진수
// 0x : hex
// DDRA = 0xff;
#if 0 // 함수 포인터 배열
while (1)
{
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state++;
button0_state %= 8;
}
fp[button0_state] ();
}
#endif
#if 0 // switch ~ case
while (1)
{
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state++;
button0_state %= 8;
}
switch (button0_state) {
case 0:
led_all_off();
break;
case 1:
led_all_on();
break;
case 2:
shift_left_ledon();
break;
case 3:
shift_right_ledon();
break;
case 4:
shift_left_keep_ledon();
break;
case 5:
shift_right_keep_ledon();
break;
case 6:
flower_on();
break;
case 7:
flower_off();
break;
}
}
#endif
#if 0 // org
while (1) // for(;;)
{
// 1 button버튼 처리 (toggle)
// button0를 1번 누르면 led_all_on
// led_all_off
if (get_button(BUTTON0, BUTTON0PIN))
{
button0_state = !button0_state; // 반전 0 <--> 1
if (button0_state)
led_all_on();
else led_all_off();
}
}
#endif
}
#endif
// timer0를 초기화시키는 부분
// AVR에서 8bit timer 0/2번 중 0번을 초기화
// 임베디드/FPGA 등에서 제일 중요한 것은 초기화를 정확히 해주는 것이다.
// 그래서 이 부분을 특별히 신경을 써서 작성한다.
void init_timer0(void)
{
// 16MHz/64 분주(down) : divider / prescale
// ---------- 분주비 계산 ----------
// (1) 16000000Hz/64 ==> 250,000Hz
// (2) T(주기) 1clock의 소요 시간 : 1/f = 1/250,000 ==> 0.000004sec(4us) : 0.004ms
// (3) 8bit timer OV(OVflow) : 0.004ms x 256 = 0.001024sec --> 1.024ms
// 1ms마다 정확하게 INT를 띄우고 싶으면 0.004ms x 250개를 count = 0.001sec ==> 1ms
TCNT0 = 6; // TCNT : 0 ~ 256 1ms마다 TIMER0_OVF_vect로 진입한다.
// TCNT0 = 6으로 설정한 이유 : 6 ~ 256 : 250개의 펄스를 count하기 때문에 정확히 1ms가 된다.
// (4) 분주비 설정 64분주 (250,000Hz --> 250KHz)
TCCR0 |= 1 << CS02 | 0 << CS01 | 0 << CS00; // TCCR0 |= 0xf4; 보다는 좌측 코드 권장
// (5) Timer0 overflow INT를 허용(enable)
TIMSK |= 1 << TOIE0; // TIMSK |= 0x01;
sei(); // 전역(대문)으로 interrupt 허용
}
<fnd.c>
/*
* fnd.c
*
* Created: 2025-03-06 오후 12:22:24
* Author: microsoft
*/
#include "fnd.h"
#include "button.h"
#include "led.h"
void init_fnd(void);
extern void init_button(void);
extern int get_button(int button_num, int button_pin);
uint32_t dp_count = 0;
uint32_t sec_count=0;
uint32_t ms_count=0;
uint32_t mil_count = 0;
extern volatile int msec_count;
uint32_t button_state[3] = {0};
uint32_t led_state[3] = {0};
int fnd_main(void);
void fnd_display(void);
void circle(void);
int fnd_main(void)
{
DDRA = 0xff;
init_fnd();
init_button();
while(1)
{
if(msec_count % 500 == 0)
{
led_state[0] = !led_state[0];
}
if(msec_count % 1000 == 0)
{
led_state[1] = !led_state[1];
}
if(msec_count % 100 == 0)
{
led_state[2] = !led_state[2];
}
switch(button_state[0])
{
case 0: PORTA = led_state[0] << button_state[0];
break;
case 1: PORTA = led_state[1] << button_state[0];
break;
case 2: PORTA = led_state[2] << button_state[0];
break;
}
if (get_button(BUTTON0, BUTTON0PIN))
{
button_state[0]++; // 0, 1, 2
button_state[0] %= 3;
}
if (get_button(BUTTON1, BUTTON1PIN))
{
button_state[1] = !button_state[1];
if(button_state[1])
{
sec_count = 0;
button_state[1] = 0;
}
}
if (get_button(BUTTON2, BUTTON2PIN))
{
button_state[2] = !button_state[2];
}
fnd_display();
_delay_ms(1);
if(!button_state[2] && button_state[0] == 2)
{
if(msec_count % 10 == 0)
{
mil_count++;
if(mil_count >= 100)
{
mil_count=0;
}
}
if (msec_count >= 1000)
{
msec_count = 0;
dp_count = !dp_count;
sec_count++;
}
}
else if(!(button_state[0] == 2))
{
if (msec_count >= 1000)
{
msec_count = 0;
dp_count = !dp_count;
sec_count++;
}
}
}
return 0;
}
void init_fnd(void)
{
FND_DATA_DDR = 0xff; // 출력 모드로 설정
// FND_DIGIT_DDR |= 0xf0; // 자릿수 선택 7654
FND_DIGIT_DDR |= 1 << FND_DIGIT_D1 | 1 << FND_DIGIT_D2 | 1 << FND_DIGIT_D3 | 1 << FND_DIGIT_D4;
// fnd를 all off
#if 1 // common cathode 방식
FND_DATA_PORT = 0x00; // fnd를 all off
#else // common anode 방식
FND_DATA_PORT = ~0x00; // fnd를 all off 0xff;
#endif
}
void fnd_display(void)
{
#if 1 // common cathode 방식
// 0 1 2 3 4 5 6 7 8 9 .
uint8_t fnd_font[] = {~0xc0, ~0xf9, ~0xa4, ~0xb0, ~0x99, ~0x92, ~0x82, ~0xd8, ~0x80, ~0x90, ~0x7f};
static uint8_t fnd_font10[] = {~0xff, ~0xff, ~0xfe, ~0xde, ~0xce, ~0xc6, ~0xc6, ~0xc6, ~0xc6};
static uint8_t fnd_font1[] = {~0xff, ~0xfe, ~0xfe, ~0xfe, ~0xfe, ~0xfe, ~0xf6, ~0xf2, ~0xf0};
#else // common anode 방식
// 0 1 2 3 4 5 6 7 8 9 .
uint8_t fnd_font[] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xd8, 0x80, 0x90, 0x7f};
#endif
static int digit_select = 0; // static을 쓰면 전역 변수처럼 함수가 빠져 나갔다가 다시 들어오더라도 값을 유지
switch(digit_select)
{
case 0:
#if 1 // common cathode
FND_DIGIT_PORT = ~(1 << FND_DIGIT_D4); // 01111111 FND_FIGIT_PORT = ~0x80
#else // common anode
FND_DIGIT_PORT = 1 << FND_DIGIT_D4; // 10000000 FND_FIGIT_PORT = 0x80
#endif
if(button_state[0] == 0 || button_state[0] == 1)
{
FND_DATA_PORT = fnd_font[sec_count % 10];
}
else if(button_state[0] == 2)
{
FND_DATA_PORT = fnd_font[mil_count % 10];
}
break;
case 1:
#if 1 // common cathode
FND_DIGIT_PORT = ~(1 << FND_DIGIT_D3); // FND_FIGIT_PORT = ~0x40
#else // common anode
FND_DIGIT_PORT = 1 << FND_DIGIT_D3; // FND_FIGIT_PORT = 0x40
#endif
if(button_state[0] == 0 || button_state[0] == 1)
{
FND_DATA_PORT = fnd_font[sec_count / 10 % 6];
if(dp_count)
{
FND_DATA_PORT |= fnd_font[10];
}
}
else if(button_state[0] == 2)
{
FND_DATA_PORT = fnd_font[mil_count / 10];
}
break;
case 2:
#if 1 // common cathode
FND_DIGIT_PORT = ~(1 << FND_DIGIT_D2); // FND_FIGIT_PORT = ~0x20
#else // common anode
FND_DIGIT_PORT = 1 << FND_DIGIT_D2; // FND_FIGIT_PORT = 0x20
#endif
if(button_state[0] == 1)
FND_DATA_PORT = fnd_font1[sec_count % 9];
else if(button_state[0] == 2)
FND_DATA_PORT = fnd_font[sec_count % 10];
else
FND_DATA_PORT = fnd_font[sec_count / 60 % 10]; // 1단위 분
break;
case 3:
#if 1 // common cathode
FND_DIGIT_PORT = ~(1 << FND_DIGIT_D1); // FND_FIGIT_PORT = ~0x10
#else // common anode
FND_DIGIT_PORT = 1 << FND_DIGIT_D1; // FND_FIGIT_PORT = 0x10
#endif
if(button_state[0] == 1)
FND_DATA_PORT = fnd_font10[sec_count % 9];
else if(button_state[0] == 2)
FND_DATA_PORT = fnd_font[sec_count / 10 % 6];
else
FND_DATA_PORT = fnd_font[sec_count / 600 % 6]; // 10단위 분
break;
}
digit_select++;
digit_select %= 4; // 다음 표시할 자릿수 선택
}
<실행 결과>
https://youtube.com/shorts/6vWpRBLbdfQ
'(Telechips) AI 시스템 반도체 SW 개발자 교육 > ATmega128A 마이크로컨트롤러 프로그래밍' 카테고리의 다른 글
6일차 (0) | 2025.03.11 |
---|---|
5일차 (0) | 2025.03.10 |
3일차 (0) | 2025.03.06 |
2일차 (0) | 2025.03.05 |
1일차 (0) | 2025.03.04 |