ATmega128_ 7세그먼트

JS·2023년 2월 22일
0

ATmega128

목록 보기
3/9

세그먼트에는 크게 2가지 종류가 있는데, 하나는 Common Anode 세그먼트이고 Common Cathode 세그먼트이다.

Common Anode 세그먼트는 common이 High_V이므로 1을 입력해야 ON이 되고, a,b,c,d,e,f,g 단에는 0을 인가해야 ON이 된다.

반면에 Common Cathode 세그먼트는 common이 Low_V이므로 0을 입력해야 ON이 되면, a,b,c,d,e,f,g 단에는 1을 인가해야 ON이 된다.

내가 사용했던 세그먼트는 Common Cathode 세그먼트였다.
세그먼트의 불을 켜기 위해서는 LED에 맞는 번호에 입력값을 주어야한다

common 단자인 PE 포트와 LED제어 단자인 PF 포트를 이용하여 7세그먼트를 제어해보자.
PE 포트의 경우, 숫자의 자릿수를 제어한다.
PF 포트는 자릿수의 숫자를 제어한다.

즉, D1에 8을 출력한다면 PE에서 D1만 출력되도록 제어하며, 4번 핀에만 0을 인가해야 한다.
PF에서는 LED를 제어하여 8이라는 모양을 만든다.

순서는 2진수 형식으로 dpGFEDCBA이다
common cathode기준으로 설명하자면 숫자 2를 출력하기 위해서는 16진수로 0x5b를 입력하면 2가 출력된다.
전체 숫자는 다음과 같다.

0x3f = 0
0x06 = 1
0x5b = 2
0x4f = 3
0x66 = 4
0x6d = 5
0x7d = 6
0x27 = 7
0x7f = 8
0x6f = 9

이제 세그먼트를 통해 숫자를 출력해보자

#define F_CPU 16000000UL //delay에서 사용하므로 delay 헤더 위에 선언해야함 -> unsigned long 방식으로 16MHz로 정의 -> 세그먼트가 16MHz로 동작하기 때문이다
#include <avr/io.h>
#include <util/delay.h> //delay를 위한 헤더파일

FND_dispNum(uint16_t fnNum){ //fnData 값을 매게변수 fnNum으로 받아줌  
	uint8_t fndFont[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x6f}; // 세그먼트에 표시할 숫자를 구조체로 선언
		
	PORTE |= ((1<<7)|(1<<6)|(1<<5)|(1<<4)); // 모든자리 OFF(1일 경우 off이므로)
	PORTF = fndFont[fnNum/1000%10]; //배열 fndFont[]값 출력, modular 연산을 하는 이유는 배열을 초과하는 값이 입력 됐을 경우, 오동작할 수 있기 때문임
	//ex) 12415가 입력됐을 경우, 12는 배열을 초과한 값 -> 따라서 modular 연산을 통해 2로 입력
	PORTE &= ~(1<<4); //1000의 자리 ON(해당 자리만 0으로 설정)
	_delay_ms(1);
	
	PORTE |= ((1<<7)|(1<<6)|(1<<5)|(1<<4));
	PORTF = fndFont[fnNum/100%10];
	PORTE &= ~(1<<5);//100의 자리 ON(해당 자리만 0으로 설정)
	//순서는 all off -> 철력할 값을 설정 -> 입력된 값을 display로출력
	_delay_ms(1);
	
	PORTE |= ((1<<7)|(1<<6)|(1<<5)|(1<<4));
	PORTF = fndFont[fnNum/10%10];
	PORTE &= ~(1<<6);//10의 자리 ON(해당 자리만 0으로 설정)
	_delay_ms(1);
	
	PORTE |= ((1<<7)|(1<<6)|(1<<5)|(1<<4));
	PORTF = fndFont[fnNum%10];
	PORTE &= ~(1<<7);//1의 자리 ON(해당 자리만 0으로 설정)
	_delay_ms(1);
}

int main(void)
{
	DDRE = 0xff; // E포트를 모두 off(common cathode이므로 0일 때 on, 1일 떄 off)
	DDRF = 0xff;// F포트를 모두 off
	
	
	uint16_t fnData = 65445;//4자리 수이므로 16bit변수로 선언 
	
	while (1)
	{
		FND_dispNum(fnData);//fnData 값을 FND_dispNum()함수에 넣어줌
		fnData ++;
		_delay_ms(1);
	}
}

__ 이 코드에서 숫자의 밝기를 같게하자

#define F_CPU 16000000UL //delay에서 사용하므로 delay 헤더 위에 선언해야험
#include <avr/io.h>
#include <util/delay.h>

FND_dispNum(uint16_t fnNum){ //fnData 값을 매게변수 fnNum으로 받아줌
	uint8_t fndFont[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x6f};
	static uint8_t fndDigitState = 0;
	fndDigitState = (fndDigitState+1)%4;
	
	switch(fndDigitState){
		case 0:
		PORTE |= ((1<<7)|(1<<6)|(1<<5)|(1<<4)); // 모든자리 OFF(1일 경우 off이므로)
		PORTF = fndFont[fnNum/1000%10]; //배열 fndFont[]값 출력, modular 연산을 하는 이유는 배열을 초과하는 값이 입력 됐을 경우, 오동작할 수 있기 때문임
		//ex) 12415가 입력됐을 경우, 12는 배열을 초과한 값 -> 따라서 modular 연산을 통해 2로 입력
		PORTE &= ~(1<<4); //1000의 자리 ON(해당 자리만 0으로 설정)
		break;
		
		case 1:
		PORTE |= ((1<<7)|(1<<6)|(1<<5)|(1<<4));
		PORTF = fndFont[fnNum/100%10];
		PORTE &= ~(1<<5);//100의 자리 ON(해당 자리만 0으로 설정)
		//순서는 all off -> 철력할 값을 설정 -> 입력된 값을 display로출력
		break;
		
		case 2:
		PORTE |= ((1<<7)|(1<<6)|(1<<5)|(1<<4));
		PORTF = fndFont[fnNum/10%10];
		PORTE &= ~(1<<6);//10의 자리 ON(해당 자리만 0으로 설정)
		break;
		
		case 3:
		PORTE |= ((1<<7)|(1<<6)|(1<<5)|(1<<4));
		PORTF = fndFont[fnNum%10];
		PORTE &= ~(1<<7);//1의 자리 ON(해당 자리만 0으로 설정)
		break;
	}
		
}

int main(void)
{
	DDRE = 0xff; // E포트를 모두 off(common cathode이므로 0일 때 on, 1일 떄 off)
	DDRF = 0xff;// F포트를 모두 off
	
	
	uint16_t fnData = 0;//4자리 수이므로 16bit변수로 선언
	uint32_t timeTick = 0;
	uint32_t prevTime = 0;
	
	while (1)
	{
		FND_dispNum(fnData);//fnData 값을 FND_dispNum()함수에 넣어줌
		if(timeTick - prevTime >100){
			prevTime = timeTick;
			fnData ++;
		}
		_delay_ms(1);
		timeTick++;
	}
}

_ ___Tick함수를 이용해서 개선
//define을 통해 가독성을 개선시켜보자

#define F_CPU 16000000UL //delay에서 사용하므로 delay 헤더 위에 선언해야험
#include <avr/io.h>
#include <util/delay.h>

#define FND_DIGIT_PORT PORTE // 가독성을 위한 선언
#define FND_DIGIT_DDR  DDRE
#define FND_DATA_PORT  PORTF
#define FND_DATA_DDR   DDRF
#define FND_DIGIT_1	   4
#define FND_DIGIT_2	   5
#define FND_DIGIT_3	   6
#define FND_DIGIT_4	   7


FND_dispNum(uint16_t fnNum){ //fnData 값을 매게변수 fnNum으로 받아줌
	uint8_t fndFont[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x6f}; // 0~9까지 숫자 배열
	static uint8_t fndDigitState = 0;
	fndDigitState = (fndDigitState+1)%4;
	FND_DIGIT_PORT |= ((1<<FND_DIGIT_4)|(1<<FND_DIGIT_3)|(1<<FND_DIGIT_2)|(1<<FND_DIGIT_1)); // 모든자리 OFF(1일 경우 off이므로)
	
	switch(fndDigitState){
		case 0:
		FND_DATA_PORT = fndFont[fnNum/1000%10]; //배열 fndFont[]값 출력, modular 연산을 하는 이유는 배열을 초과하는 값이 입력 됐을 경우, 오동작할 수 있기 때문임
		//ex) 12415가 입력됐을 경우, 12는 배열을 초과한 값 -> 따라서 modular 연산을 통해 2로 입력
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_1); //1000의 자리 ON(해당 자리만 0으로 설정)
		break;
		
		case 1:
		FND_DATA_PORT = fndFont[fnNum/100%10];
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_2);//100의 자리 ON(해당 자리만 0으로 설정)
		//순서는 all off -> 철력할 값을 설정 -> 입력된 값을 display로출력
		break;
		
		case 2:
		FND_DATA_PORT = fndFont[fnNum/10%10];
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_3);//10의 자리 ON(해당 자리만 0으로 설정)
		break;
		
		case 3:
		FND_DATA_PORT = fndFont[fnNum%10];
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_4);//1의 자리 ON(해당 자리만 0으로 설정)
		break;
	}
		
}

int main(void)
{
	FND_DIGIT_DDR = 0xff; // E포트를 모두 off(common cathode이므로 0일 때 on, 1일 떄 off)
	FND_DATA_DDR = 0xff;// F포트를 모두 off
	
	
	uint16_t fnData = 0;//4자리 수이므로 16bit변수로 선언
	uint32_t timeTick = 0;
	uint32_t prevTime = 0;
	
	while (1)
	{
		FND_dispNum(fnData);//fnData 값을 FND_dispNum()함수에 넣어줌 -> 자리수 입력
		if(timeTick - prevTime >100){ // non-blocking 방식: Delay를 이영하지 않음 <-> Delay를 이용한 방식인 blocking방식(전체적으로 delay가 커질 수 있으므로 지양하자)
			prevTime = timeTick; 
			fnData ++;
		} // timeTick이 100미만일 때, prevTime = 0이므로 timeTick이 계속 증가 
		  // -> timeTick이 100이 되면 prevTime에 100을 넣어주고 timeTick은 timeTick- prevTime이 100이 될때까지 계속 증가
		_delay_ms(1);
		timeTick++;
	} //timeTick을 0으로 초기화하면 다른 조건과 함께 사용할 경우, 원하는 동작이 안될 수 있음
	  // 오버플로우가 나면 어떡하나 -> 뺄셈의 경우, 보수를 취해 더해주므로 문제되지 않는다 	ex)
	  // timeTick과 prevTime의 자료형은 같아야한다 
}

____가독성 개선

#define F_CPU 16000000UL //delay에서 사용하므로 delay 헤더 위에 선언해야험
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h> // interrupt 명령문을 사용하기 위한 선언

#define FND_DIGIT_PORT PORTE // 가독성을 위한 선언
#define FND_DIGIT_DDR  DDRE
#define FND_DATA_PORT  PORTF
#define FND_DATA_DDR   DDRF
#define FND_DIGIT_1	   4
#define FND_DIGIT_2	   5
#define FND_DIGIT_3	   6
#define FND_DIGIT_4	   7


void FND_dispNum(uint16_t fnNum){ //fnData 값을 매게변수 fnNum으로 받아줌
	uint8_t fndFont[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x6f}; // 0~9까지 숫자 배열
	static uint8_t fndDigitState = 0;
	fndDigitState = (fndDigitState+1)%4;
	FND_DIGIT_PORT |= ((1<<FND_DIGIT_4)|(1<<FND_DIGIT_3)|(1<<FND_DIGIT_2)|(1<<FND_DIGIT_1)); // 모든자리 OFF(1일 경우 off이므로)
	
	switch(fndDigitState){
		case 0:
		FND_DATA_PORT = fndFont[fnNum/1000%10]; //배열 fndFont[]값 출력, modular 연산을 하는 이유는 배열을 초과하는 값이 입력 됐을 경우, 오동작할 수 있기 때문임
		//ex) 12415가 입력됐을 경우, 12는 배열을 초과한 값 -> 따라서 modular 연산을 통해 2로 입력
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_1); //1000의 자리 ON(해당 자리만 0으로 설정)
		break;
		
		case 1:
		FND_DATA_PORT = fndFont[fnNum/100%10];
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_2);//100의 자리 ON(해당 자리만 0으로 설정)
		//순서는 all off -> 철력할 값을 설정 -> 입력된 값을 display로출력
		break;
		
		case 2:
		FND_DATA_PORT = fndFont[fnNum/10%10];
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_3);//10의 자리 ON(해당 자리만 0으로 설정)
		break;
		
		case 3:
		FND_DATA_PORT = fndFont[fnNum%10];
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_4);//1의 자리 ON(해당 자리만 0으로 설정)
		break;
	}
	
}

uint16_t fnData = 0;//4자리 수이므로 16bit변수로 선언

ISR(TIMER0_OVF_vect){//특정위치에 interrupt가 발생하도록 함(timer가 overflow일 경우 동작)
	PORTG ^=0x01;
	FND_dispNum(fnData);
}

int main(void)
{
	FND_DIGIT_DDR = 0xff; // E포트를 모두 off(common cathode이므로 0일 때 on, 1일 떄 off)
	FND_DATA_DDR = 0xff;// F포트를 모두 off
	
	DDRG = 0xff;
	
	TCCR0 |= (1 << CS02)|(0 << CS01)|(1 << CS00); // 
	TIMSK |=(1<<TOIE0); // 0번 타이머의 인터럽트를 허가한다 
	TCNT0 = 130; //(256-125)-1 ->타이머 카운트 레지스터도 메모리이므로 안에 값을 변경하여 인터럽트 시간의 조절이 가능하다
	
	sei(); //global interrupt enable 인터럽드 설정
	

	uint32_t timeTick = 0;
	uint32_t prevTime = 0;
	
	while (1)
	{
		//FND_dispNum(fnData);//fnData 값을 FND_dispNum()함수에 넣어줌 -> 자리수 입력
		if(timeTick - prevTime >100){ // non-blocking 방식: Delay를 이영하지 않음 <-> Delay를 이용한 방식인 blocking방식(전체적으로 delay가 커질 수 있으므로 지양하자)
			prevTime = timeTick;
			fnData ++;
		} // timeTick이 100미만일 때, prevTime = 0이므로 timeTick이 계속 증가
		// -> timeTick이 100이 되면 prevTime에 100을 넣어주고 timeTick은 timeTick- prevTime이 100이 될때까지 계속 증가
		_delay_ms(1);
		timeTick++;
	} //timeTick을 0으로 초기화하면 다른 조건과 함께 사용할 경우, 원하는 동작이 안될 수 있음
	// 오버플로우가 나면 어떡하나 -> 뺄셈의 경우, 보수를 취해 더해주므로 문제되지 않는다 	ex)
	// timeTick과 prevTime의 자료형은 같아야한다
}

//main이 너무길면 동작속도에 문제가 발생 -> interrupt를 사용하여 main에 관계없이 주기적으로 동작시키자
//main을 진행하다가 interrupt 조건에 맞으면 main을 잠시 멈추고 interrupt 위치로 뜀 -> interrupt 종료후, 멈췄던 위치에서 main을 재실행
________________________________________________________________________  inturupt를 사용하여 main에 관계없이 주기적으로 동작시키자

main 함수

#define F_CPU 16000000UL //delay에서 사용하므로 delay 헤더 위에 선언해야험
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h> // interrupt 명령문을 사용하기 위한 선언
#include "driver/FND/FND.h"


ISR(TIMER0_OVF_vect){//특정위치에 interrupt가 발생하도록 함(timer가 overflow일 경우 동작)
	FN_ISR_Process();
	TCNT0 =130; //(256-125)-1 ->타이머 카운트 레지스터도 메모리이므로 안에 값을 변경하여 인터럽트 시간의 조절이 가능하다, 여기서는 1ms로 시간을 조절하기 위해 사용
}

int main(void)
{
	FND_init();
	
	TCCR0 |= (1 << CS02)|(0 << CS01)|(1 << CS00); // 인터럽트가 발생하는 시간을 조절 16Mhz/TCCR0 값으로 결정
	TIMSK |=(1<<TOIE0); // 0번 타이머의 인터럽트를 허가한다 
	TCNT0 = 130; // 첫동작에서 1ms로 동작하기 위함  
	
	sei(); //global interrupt enable 인터럽드 설정
	

	uint16_t counter = 0;
	
	while (1){
		FND_setFndData(counter++);
		_delay_ms(100);
	}
}

FND 헤더 파일

#include "FND.h"

uint16_t fnData = 0;//4자리 수이므로 16bit변수로 선언

void FND_setFndData(uint16_t data){ //함수를 통해 mian에 접근하여 값을 넣어줌
	fnData =data; 
}

void FND_init(){ //FND 값을 초기화 하기 위한 함수
	FND_DIGIT_DDR |= ((1<<FND_DIGIT_4)|(1<<FND_DIGIT_3)|(1<<FND_DIGIT_2)|(1<<FND_DIGIT_1));
	FND_DATA_DDR = 0xff;
}

void FND_dispNum(uint16_t fnNum){ //fnData 값을 매게변수 fnNum으로 받아줌
	
	uint8_t fndFont[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x27, 0x7f, 0x6f}; // 0~9까지 숫자 배열
	static uint8_t fndDigitState = 0;
	
	fndDigitState = (fndDigitState+1)%4;
	FND_DIGIT_PORT |= ((1<<FND_DIGIT_4)|(1<<FND_DIGIT_3)|(1<<FND_DIGIT_2)|(1<<FND_DIGIT_1)); // 모든자리 OFF(1일 경우 off이므로)
	
	switch(fndDigitState){
		case 0:
		FND_DATA_PORT = fndFont[fnNum/1000%10]; //배열 fndFont[]값 출력, modular 연산을 하는 이유는 배열을 초과하는 값이 입력 됐을 경우, 오동작할 수 있기 때문임
		//ex) 12415가 입력됐을 경우, 12는 배열을 초과한 값 -> 따라서 modular 연산을 통해 2로 입력
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_1); //1000의 자리 ON(해당 자리만 0으로 설정)
		break;
		
		case 1:
		FND_DATA_PORT = fndFont[fnNum/100%10];
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_2);//100의 자리 ON(해당 자리만 0으로 설정)
		//순서는 all off -> 철력할 값을 설정 -> 입력된 값을 display로출력
		break;
		
		case 2:
		FND_DATA_PORT = fndFont[fnNum/10%10];
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_3);//10의 자리 ON(해당 자리만 0으로 설정)
		break;
		
		case 3:
		FND_DATA_PORT = fndFont[fnNum%10];
		FND_DIGIT_PORT &= ~(1<<FND_DIGIT_4);//1의 자리 ON(해당 자리만 0으로 설정)
		break;
	}
}

void FN_ISR_Process(){
	FND_dispNum(fnData);
}

FNC.h

#ifndef FND_H_
#define FND_H_

#include <avr/io.h>

#define FND_DIGIT_PORT PORTE // 가독성을 위한 선언
#define FND_DIGIT_DDR  DDRE
#define FND_DATA_PORT  PORTF
#define FND_DATA_DDR   DDRF
#define FND_DIGIT_1	   4
#define FND_DIGIT_2	   5
#define FND_DIGIT_3	   6
#define FND_DIGIT_4	   7

void FND_setFndData(uint16_t data);
void FND_init();
void FND_dispNum(uint16_t fnNum);
void FN_ISR_Process();

#endif /* FND_H_ */

________________________________________________________________________FND driver 생성

0개의 댓글