__________

Designing the Future with Circuits

반도체 회로설계 취준기

하만(Harman) 세미콘 반도체 설계 과정/임베디드 시스템을 위한 SW 구조설계

하만(Harman) 세미콘 아카데미 16일차 - SW 구조설계(8비트 타이머/카운터, 16비트 타이머/카운터, 주방 타이머 설계 실습)

semicon_circuitdesigner 2024. 3. 28. 14:08

[2024.03.28.목] 인천인력개발원 하만 세미콘 아카데미


임베디드 시스템을 위한 SW 구조설계


8비트 타이머 / 카운터


1. 타이커/카운터 인터럽트

  • 현재까지의 펄스 수: TCNTn레지스터에 저장
  • 오버플로 인터럽트
    • 최대로 셀 수 있는 펄스 이상이 되면 TCNTn레지스터가 0으로 바뀌며 발생
  • 비교일치 인터럽트
    • TCNTn의 값이 미리 설정된 OCRn레지스터값과 일치하면 발생

2. TCCR0 레지스터: 분주비 설정

  • CLK 주파수: 16,000,000Hz
  • TCNT는 최대 256, TCCR은 16 -> 256*26 = 4096 (≒4K)
  • TCCR이 64면 16K -> 16KHz가 ATmega의 최대 주파수
  • 따라서 분주비를 64로 설정

 
3. TIMSK 레지스터: 인터럽트 활성화

  • EIMSK: External Input Mask
  • TIMSK: Timer Mask
  • TOIE0비트: 오버플로 인터럽트 활성화 비트(TcntOverflow)
  • OCIE0비트: 비교일치 인터럽트 활성화 비트(Ocr Comp)

 
3. 비교 일치 인터럽트

  • TCNTn값과 비교할 값을 OCRn 레지스터에 설정

실습 1: 타이머를 사용하여 진행시간 표시


1. test03 우클릭 - Add - New Item

 
2. C File 선택 후 Segment로 이름 지정

 
4. Segment.c에 붙여넣기

#define F_CPU 16000000L					//Board CLK 정보(16MHz)
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>
#define OPTMAX 3
#define STATEMAX 3

uint8_t digit[] = {	0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x27, 0x7F, 0x67, 0x77, 0x7c, 0x58, 0x5e, 0x79, 0x71 };
char arr[5];		//세그먼트 이미지 정보를 담을 안전공간
volatile int opt = 0, state = 0;


void seg(int sel, uint8_t c){
	PORTC |= 0X0F;
	PORTC &= ~(1 << (3-sel));
	PORTD = c;	//숫자 데이터 출력
	_delay_ms(2);
}


void FND_4(char *inf){	//segment Image 배열
	for (int i = 0; i < 4; i++){
		seg(i, *(inf+i));

	}
}

//16진수 segment image 배열
char* Display(unsigned long num){	//10진 정수를 입력받아 16진수 문자열로 변환 ex)65535 ==> 0xffff, 56506=>0xBCDA
	int n1 = (num / 10) % 10;			//A(10): 문자가 아닌 숫자
	int n2 = (num / 100) % 10;	//B(11)
	int n3 = (num / 100) % 10;	//C(12)
	int n4 = num / 1000;		//D(13)
	
	arr[0] = digit[n1]; arr[1] = digit[n2]; arr[2] = digit[n3] + 0x80; arr[3] = digit[n4];
	
	if ( num<10 ){
		arr[3] = 0; arr[1] = 0; arr[2] = 0x80;
	}
	else if ( num<100 ){
		arr[2] = 0x80; arr[3] = 0;
	}
	else if ( num<1000 ){
		arr[3] = 0;
	}
	
	FND_4(arr);
	return arr;
}

 
5. volatile 변수 외의 함수 선언 내용을 main-intr.c에서 제거 후 상단에  코드 입력

extern char* Display (unsigned long num);

의미:  외부 파일로부터 함수를 불러오는 코드

 
6. solution 우클릭- add - new project - test04-timer 생성

 
7. ATmega128 선택 후 OK
 
8. 헤더파일 만들기:  test04-timer 우클릭 - add- file - myHeader.h파일 생성

 
9. 헤더파일 내에 #define F_CPU 16000000L 입력 후 저장
 
10. 모든 .c파일에 #include "myHeader.h"로 포함
- #include <> 사용: 시스템 헤더 포함
- #include " "사용: 사용자 정의 헤더 포함(현재 프로젝트와 같은 경로에 위치할 때 사용)
 
11. main-timer.c내에 헤더파일 include

#include "myHeader.h"
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>

 
12. main 함수 내에 인터럽트 활성화 코드 및 실행 코드(sei()) 작성

TIMSK |= 0x01;	//Timer Interrupt Mask을 0000 0001b로 설정하여 오버플로 인터럽트 활성화 [Timer 0 TCNT Overflow interrupt] 
TCCR0 |= 0x04; //16KHz를 구하기 위해 분주비 64 필요 -> 비트를 0000 0100b로 설정
SREG |= 0X80;
sei();	//인터럽트 실행 코드

 
 
13. ISR 추가

ISR(TIMER0_OVF_vect){
	tcnt++;
	if (tcnt >= 1000){
		cnt++; tcnt = 0;//1초(1000밀리초)가 지나면 cnt값을 증가시켜라
	}
}

 
[Segment.c 수정]: PORTC와 PORTD를 포인터 변수로 바꿔 입력

 
14. Segment.c 파일을 수정하여 분.초로 나타나도록 수정

char* Display(unsigned long num){	//10진 정수를 입력받아 16진수 문자열로 변환 ex)65535 ==> 0xffff, 56506=>0xBCDA
	int n1 = num % 10;			//A(10): 문자가 아닌 숫자
	int n2 = (num / 10) % 6;	//B(11)
	int n3 = (num / 60) % 10;	//C(12)
	int n4 = (num / 600) % 6;		//D(13)
	
	arr[0] = digit[n1]; arr[1] = digit[n2]; arr[2] = digit[n3] + 0x80; arr[3] = digit[n4];
	
	if ( num< 10 ){
		arr[3] = 0; arr[1] = 0; arr[2] = 0x80;
	}
	else if ( num<60 ){
		arr[2] = 0x80; arr[3] = 0;
	}
	else if ( num<600 ){
		arr[3] = 0;
	}

 
15. 실행 확인(10배속)
 


실습 2: 위 과정을 응용하여 주방 타이머 설계


  • sw1: start/stop/continue 로 시간을 제어할 수 있도록 작동
  • sw2: 시간이 초당 10씩 증가하여 처음 실행 시 원하는 시간을 맞출 수 있도록 설정
  • sw3: 타이머 초기화

1. 버튼을 입력으로 설정하고, 외부 인터럽트가 작동하도록 메인 함수 내에 코드 작성

int main(void)
{
	SegPortSet(&PORTD, &PORTC);
	//7-Segment 사용	: 4 Module - C type
	//	Pin assign	: PDx - Segment img, PCx - module sel
	//Interrupt 사용	: INT4~INT6 (External Interrupt)
	//	Pin assign	: PE4~PE6
	DDRD = 0xFF;
	DDRC = 0x0F;
	DDRE = 0x00;

	//인터럽트 설정
	EIMSK = 0x70;	//0111 0000	//INT 4~INT 6 활성화
	EICRB = 0x2a;	//4개의 B그룹(INT4~INT7)의 인터럽트 발생 시점 결정(00 10 10 10, 각 7 6 5 4에서의 INT발생 시점을 rising edge로 결정)
	DDRD = 0XFF;	//D포트의 모든 비트를 출력으로 설정
	DDRC = 0X0F;	//D포트의 네개 비트를 출력으로 설정
    TIMSK |= 0x01;	//Timer Interrupt Mask을 0000 0001b로 설정하여 오버플로 인터럽트 활성화 [Timer 0 TCNT Overflow interrupt] 
	TCCR0 |= 0x04; //16KHz를 구하기 위해 분주비 (Pre-Scaler) 64 필요 -> 비트를 0000 0100b로 설정 
	SREG |= 0x80;
	sei();
    while (1) 
    {
	    if(cnt > 0x10000) cnt = 0;	//
	    Display(cnt);
    }
}

 
2. 외부 인터럽트 작동 시 첫 번째 버튼을 누르면 시간이 올라가도록 타이머 인터럽트 함수 내에 코드 설정

ISR(TIMER0_OVF_vect){
	if(opt){
		tcnt++;
		if (tcnt >= 1000){
			cnt++; tcnt = 0;//1초(1000밀리초)가 지나면 cnt값을 증가시켜라
		}
	}
}

 
3. opt, set, reset설정값이 버튼에 따라 변경되도록 코드 상단에 volatile로 변수 선언

 
4. ISR 코드 작성

ISR(INT4_vect){	//INT4 인터럽트 처리 루틴: sw1
	opt++;
	if (opt >= 2) opt = 0;
}

ISR(INT5_vect){	//INT5 인터럽트 처리 루틴: sw2
	set++;
	if (set >= 2) set = 0;
}

ISR(INT6_vect){	//INT6 인터럽트 처리 루틴: sw3
	cnt = 0;
}

ISR(TIMER0_OVF_vect){
	if(opt){
		tcnt++;
		if (tcnt >= 1000){
			cnt++; tcnt = 0;//1초(1000밀리초)가 지나면 cnt값을 증가시켜라
		}
	}
}

 
5. 현재까지 진행상황: START, STOP, CONTINUE기능과 RESET기능 구현
 


8비트 타이머 / 카운터 - 2


1. 타이머 2를 사용하는 경우, 다음과 같이 변경

 
2. 위와 같이 변경 후 타이머0을 주석 처리 후 실행 -> 비정상작동(진행시간에 변화)

  • 0번 타이머와 2번 타이머의 분주기 위치 차이로 인해 발생
좌: 8비트 0번 타이머의 구조 / 우: 8비트 2번 타이머의 구조

16비트 타이머 / 카운터


 

16비트 타이머 / 카운터의 구조

1. 16비트 타이머 / 카운터

  • 오버플로 인터럽트: 0~(2^16-1)까지 카운트 가능
  • 3개의 비교 일치 인터럽트 사용 가능
  • 특정 사건 발생 시 현재 카운터 값이 TCNTn 레지스터 값 저장

2. 오버플로 인터럽트

  • TCNTn 레지스터 = TCNTnH + TCNTnL
  • 0~65,535까지 카운트 가능한 16비트 레지스터
  • 분주를 하지 않으면 4.096MS간격의 인터럽트 발생
  • 256으로 분주하면 약 1초 간격으로 인터럽트 발생

 
3. TCCR1B 레지스터

  • 분주비 설정 레지스터

 
 
4. ETIMSK레지스터: 확장 타이머 레지스터


실습 3: 16비트 카운터/타이머 이용


1. 16비트의 타이머 마스크와 분주비를 다음과 같이 적용

 
2. ISR 코드를 변경: tcnt if문 제외(분주비가 256이므로 인터럽트가 1초마다 발생)

ISR(TIMER1_OVF_vect){
		tcnt++;
		//if (tcnt >= 1000){
			cnt++; tcnt = 0;//1초(1000밀리초)가 지나면 cnt값을 증가시켜라
		//}
}

실습 2: 주방 타이머 설계 이어서


  • sw1: start/stop/continue 로 시간을 제어할 수 있도록 작동 - 설정 시간 도달시 LED 점멸
  • sw2: 타이머 설정(시간을 분단위 UP/DOWN으로 설정)
  • sw3: 타이머 초기화
  • 타이머 표시 시간: 분.초
  • 타이머 동작 순서
    1. 1번째 자릿수가 점멸하면 SW1을 눌러 숫자 증가
    2. SW2를 눌러 감소
    3. SW3을 눌러 입력 및 다음 자릿수 설정으로 넘어감
    4. 2, 3, 4번째 자리수도 동일하게 설정
    5. 4자리까지 설정이 완료되면 화면에 ----표시
    6. START(SW1)을 누르면 카운트 시작
    7. 설정 시간에 도달하면 LED 점멸

1. 현재까지의 진행 코드 및 상황: start/pause/continue, reset 구현 완료

/*
 * test04-timer.c
 *
 * Created: 2024-03-28 오전 10:57:17
 * Author : jong9
 */ 

#include "myHeader.h"
#include <avr/io.h>
#include <avr/delay.h>
#include <avr/interrupt.h>
#define _delay_t 500

volatile int opt=0, set=0, reset=0;
volatile unsigned long cnt = 0, tcnt = 0;

int main(void)
{
	SegPortSet(&PORTD, &PORTC);
	//7-Segment 사용	: 4 Module - C type
	//	Pin assign	: PDx - Segment img, PCx - module sel
	//Interrupt 사용	: INT4~INT6 (External Interrupt)
	//	Pin assign	: PE4~PE6
	DDRD = 0xFF;
	DDRC = 0x0F;
	DDRE = 0x00;

	//인터럽트 설정
	EIMSK = 0x70;	//0111 0000	//INT 4~INT 6 활성화
	EICRB = 0x2a;	//4개의 B그룹(INT4~INT7)의 인터럽트 발생 시점 결정(00 10 10 10, 각 7 6 5 4에서의 INT발생 시점을 rising edge로 결정)
	DDRD = 0XFF;	//D포트의 모든 비트를 출력으로 설정
	DDRC = 0X0F;	//D포트의 네개 비트를 출력으로 설정
	
	TIMSK |= 0x04;	//Timer Interrupt Mask을 0000 0100b로 설정하여 오버플로 인터럽트 활성화 [Timer 1 TCNT Overflow interrupt] 
    //TIMSK |= 0x01;	//Timer Interrupt Mask을 0000 0001b로 설정하여 오버플로 인터럽트 활성화 [Timer 0 TCNT Overflow interrupt] 
	//TIMSK |= 0x40;	//Timer Interrupt Mask을 0000 0001b로 설정하여 오버플로 인터럽트 활성화 [Timer 2 TCNT Overflow interrupt] 
	TCCR1B |= 0x04; //16KHz를 구하기 위해 분주비 (Pre-Scaler) 64 필요 -> 비트를 0000 0100b로 설정 (타이머 1에 대한 분주기가 256)
	//TCCR0 |= 0x04; //16KHz를 구하기 위해 분주비 (Pre-Scaler) 64 필요 -> 비트를 0000 0100b로 설정 (타이머 0에 대한 분주기)
	//TCCR2 |= 0x04; //16KHz를 구하기 위해 분주비 (Pre-Scaler) 64 필요 -> 비트를 0000 0100b로 설정 (타이머 2에 대한 분주기)
	SREG |= 0x80;
	sei();
    while (1) 
    {
	    if(cnt > 0x10000) cnt = 0;	//
	    Display(cnt);
    }
}

ISR(INT4_vect){	//INT4 인터럽트 처리 루틴: sw1 (start, pause, continue 버튼)
	opt++;
	if (opt >= 2) opt = 0;
}

ISR(INT5_vect){	//INT5 인터럽트 처리 루틴: sw2 (시간 설정 버튼)
	set++;
	if (set >= 2) set = 0;
}

ISR(INT6_vect){	//INT6 인터럽트 처리 루틴: sw3 (reset 버튼)
	cnt = 0; opt = 0;
}

//ISR(TIMER0_OVF_vect){
	//if(opt){
		//tcnt++;
		//if (tcnt >= 1000){
			//cnt++; tcnt = 0;//1초(1000밀리초)가 지나면 cnt값을 증가시켜라
		//}
	//}
//}

ISR(TIMER1_OVF_vect){
		tcnt++;
		if(opt){
		//if (tcnt >= 1000){
			cnt++; tcnt = 0;//1초(1000밀리초)가 지나면 cnt값을 증가시켜라
		//}
		}
}

 
2. 스위치구문을 통해 초기 실행 시 화면이 깜빡거리며 시간 설정할 수 있도록 작성

switch (mode){	//처음 시작하면 mode가 0이므로 초기화면 코드
			case 0:	//처음 시간 설정 모드
							switch(settimeth){
								case 0:
									timeup = 0; timedown = 0;
									while(1){
										time = timeup - timedown;
										if(timeup <= 0){
											display_character(settimeth, 1);
											_delay_ms(500);
											display_character(settimeth, 0);
											_delay_ms(500);
										}
										else{
											display_digit(settimeth, time);
											_delay_ms(500);
											display_character(settimeth, 1);
											_delay_ms(500);
										}
										if(complete == 1) timearr[settimeth] = time; break;
									} 
									display_character(settimeth, 1); settimeth++; complete = 0;
									break;
								case 1:
								timeup = 0; timedown = 0;
								while(1){
									time = timeup - timedown;
									if(timeup <= 0){
										display_character(settimeth, 1);
										_delay_ms(500);
										display_character(settimeth, 0);
										_delay_ms(500);
									}
									else{
										display_digit(settimeth, time);
										_delay_ms(500);
										display_character(settimeth, 1);
										_delay_ms(500);
									}
									if(complete == 1) timearr[settimeth] = time; break;
								}
								display_character(settimeth, 1); settimeth++; complete = 0;
								break;
								case 2:
								timeup = 0; timedown = 0;
								while(1){
									time = timeup - timedown;
									if(timeup <= 0){
										display_character(settimeth, 1);
										_delay_ms(500);
										display_character(settimeth, 0);
										_delay_ms(500);
									}
									else{
										display_digit(settimeth, time);
										_delay_ms(500);
										display_character(settimeth, 1);
										_delay_ms(500);
									}
									if(complete == 1) timearr[settimeth] = time; break;
								}
								display_character(settimeth, 1); settimeth++; complete = 0;
								break;
								case 3:
								timeup = 0; timedown = 0;
								while(1){
									time = timeup - timedown;
									if(timeup <= 0){
										display_character(settimeth, 1);
										_delay_ms(500);
										display_character(settimeth, 0);
										_delay_ms(500);
									}
									else{
										display_digit(settimeth, time);
										_delay_ms(500);
										display_character(settimeth, 1);
										_delay_ms(500);
									}
									if(complete == 1) timearr[settimeth] = time; break;
								}
								display_character(settimeth, 1); settimeth++; complete = 0;
								break;
							}
					break;
			
			case 1:
			break;
		}
	    if(cnt > 0x10000) cnt = 0;	//
	    //Display(cnt);
    }