본문 바로가기
# Semiconductor/- Semicon Academy

[Harman 세미콘 아카데미] 17일차 - ATmege128 실습(LED shift, Tactile switch 활용), FSM의 개념

by Graffitio 2023. 7. 11.
[Harman 세미콘 아카데미] 17일차 - ATmege128 실습(LED shift, Tactile switch 활용), FSM의 개념
728x90
반응형
[LED shift]

 

예제 1


0000 0000

1000 0001

0100 0010
0010 0100
0001 1000
0000 0000
0001 1000
0010 0100
0100 0010
1000 0001
0000 0000

 

위와 같이 LED가 shift하며 출력되는 코드 작성  

 

여러 가지 방법으로 작성할 수 있다.

 

1번 - 가장 기초적인 방법(case 다 써서 반복) 

더보기
#define  F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

uint8_t ledArr[]={
	0x81, // 1000 0001
	0x42, // 0100 0010
	0x24, // 0010 0100
	0x18, // 0001 1000
	0x24, // 0010 0100
	0x42,
	0x81
	};
	
int main()
{
	DDRD = 0xff;
	
	while(1)
	{
		for(int i = 0 ; i < 8 ; i++)
		{
			PORTD = ledArr[i];
			_delay_ms(200);
		}
	}
}

 

2번 - Shift 연산자로 한 칸씩 shift

더보기
#define  F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "LED.h"

#define LED_DDR  DDRD
#define LED_PORT PORTD


int main(void)
{
	LED_DDR = 0xff;

    while (1) 
    {
		for(uint8_t i = 0 ; i < 4 ; i++)
		{
			LED_PORT = ((0x01 << i) | (0x80 >> i));
			_delay_ms(200);
		}
		for(uint8_t i = 0 ; i < 4 ; i++)
		{
			LED_PORT = ((0x08 >> i) | (0x20 << i));
			_delay_ms(200);
		}
    }
}

 

3번 - 반반 나눠서 비트 시프트

더보기
#define  F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
//#include "LED.h"

int main()
{
	DDRD = 0xff;
	unsigned char L = 0x01; // 0000 0001
	unsigned char R = 0x80; // 0000 1000
	
	while(1)
	{
		PORTD = L | R;
		L <<= 1; // <<= : 비트 왼쪽 시프트 후 대입하는 복합 대입 연산자
                 	// L을 1만큼 좌시프트한 뒤, L에 대입
                 	// (L <<= 1) = (L = (L<<1))
		if(L==0)
		{
			L=0x01;
		}
		R >>= 1;
		if(R==0)
		{
			R = 0x80;
		}
		_delay_ms(300);		
	}
}

 

4번 - 개별 함수로 만들어서  메인 함수에 인스턴스

더보기
#define  F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

#define LED_DDR  DDRD
#define LED_PORT PORTD

// 함수 원형 선언 //
void GPIO_Output(uint8_t *data)
{
	LED_PORT = data;
}

void ledInit()
{
	LED_DDR = 0xff;
}

void ledDirShift(uint8_t i, uint8_t *data)
{
	*data = (1 << i) | (1 << (7-i));
}


// 메인 함수 //
int main()
{
	ledInit();
	uint8_t ledData = 0x81; // 1000 0001
 	
	 while(1)
	 {
		 for(int i = 0 ; i < 8 ; i++)
		 {
			 ledDirShift(i, &ledData);
			 GPIO_Output(ledData);
			 _delay_ms(300);
		 }
	 }
}

 

 

 

5번 - 배열과 구조체 및 비트마스크 활용

더보기
//// LED.h ////

#include <stdio.h>

#ifndef LED_H_
#define LED_H_

#define LED_COUNT 8

typedef struct{
	volatile uint8_t *port;
	uint8_t pin;	
	}LED;
	
void ledInit(LED *led);
void ledOn(LED *led);
void ledOff(LED *led);

#endif


//// LED.c ////

#include "LED.h"

void ledInit(LED *led) // led의 주소를 받을 거야
{
	*(led->port -1) |=(1 << led->pin); // initial value = 0
}

void ledOn(LED *led)
{
	// 해당 핀을 high로 설정
	*(led->port) |= (1<<led->pin);
}

void ledOff(LED *led)
{
	// 해당 핀을 low로 설정
	*(led->port) &= ~(1<<led->pin);
}

//// Main ////

#define  F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "LED.h"

int main()
{
	LED leds[LED_COUNT] = {
		{&PORTD, 0}, // (주소값, 핀번호)
		{&PORTD, 1},
		{&PORTD, 2},
		{&PORTD, 3},
		{&PORTD, 4},
		{&PORTD, 5},
		{&PORTD, 6},
		{&PORTD, 7},
	};
	// 초기화
	for (int i = 0 ; i < LED_COUNT ; i++)
	{
		ledInit(&leds[i]); // i번째 배열의 주소값을 넘겨 줌
	}
	
	while (1)
	{
		for (int i=0 ; i < 6 ; i++)
		{
			ledOn(&leds[i]);
			ledOn(&leds[7-i]);
			_delay_ms(100);
		}
		for (int i=7 ; i>=0 ; i--)
		{
			ledOff(&leds[i]);
			ledOff(&leds[7-i]);
			_delay_ms(100);
		}
		for (int i=0 ; i < 5 ; i++)
		{
			ledOn(&leds[3-i]);
			ledOn(&leds[4+i]);
			_delay_ms(100);
		}
		for (int i= 0; i < 5 ; i++)
		{
			ledOff(&leds[3-i]);
			ledOff(&leds[4+i]);
			_delay_ms(100);
		}
	}
}

 


 

예제 2

0000 0000

1000 0000

1100 0000

1110 0000

....

1111 1111

0111 1111

0011 1111

.....

0000 0001

0000 0000

 

위와 같은 출력 구현해보기

 

#define  F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
	

int main()
{
	DDRD = 0xff;
	PORTD = 0x00; // 꺼진 상태로 출발
	uint8_t led_data = 0x01;
	
	while (1)
	{
		for (int i = 0 ; i < 8 ; i++)
		{
			PORTD = led_data;
			led_data |= (1 << i); // 비트마스킹(set)활용
			_delay_ms(300);
		}
		for(int i = 0 ; i < 8 ; i++)
		{
			PORTD = led_data;
			led_data &= ~(1 << i); // 비트마스킹(clear) 활용
			_delay_ms(300); 			
		}
			
	}
}

 


 

[Tactile switch]

 

③contact dome이 붙었다 떨어졌다 하면서 스위치 작동 &rarr; bounce 발생

①~④ or ②~③ 이렇게 대각선으로 연결하는 것이 안전하다.

 

PORTC 0~3번 pin에 연결한 모습(풀업저항1kΩ 사용)

 


 

[LED 1EA On/Off]

 

버튼 누를 때마다 On/Off

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

// 버튼으로 LED 1EA on/off // 
int main(void)
{
	DDRD = 0xff; // LED 연결된 포트 출력
	DDRC &= ~(1<<0); // Button 연결된 0번 핀 입력으로 설정
                     // 0번 핀을 clear(0)
	// PORTC |= (1<<0); // PORTC의 0번 핀에 내부 풀업 활정
    while (1) 
    {
		if(PINC & (1 << 0)) // "버튼이 떨어진 상태가 되면" 이라는 의미
                            // i) 0b0000_0001 을 0칸만큼 좌시프트 -> 0b0000_0001
                            // ii) PINC와 0b0000_0001을 AND 연산
                            // ∴ (PINC & (1<<0)) = 1 이 되면, if문 실행
                            // 현재 풀업 저항이 연결된 상태이고 normal 상태의 값은 1
                            // 버튼이 떨어지는 순간, 0->1이 되므로 LED 꺼짐
		{
			PORTD &= ~(1 << 0);
		}
		else
		{
			PORTD |= (1 << 0);
		}
    }
}

 


 

[LED 1EA shift]

 

버튼 누르면, LED shift

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

char shiftLeft(char pattern) // 함수 선언
{
	char newPattern = ((pattern << 1) | (pattern >> 7));
                      // 1칸 좌시프트하거나(LED 0~6일때)
                      // 7칸 우시프트(LED 7일때)
                      // 즉, 1칸씩 LED를 미루고 끝까지 가면 처음으로 돌아감
	return newPattern;
}

int main()
{
	DDRD = 0xff; // POTRD all 출력
	DDRC &= ~(1 << PINC0); // DDRC의 0번째 PIN에 해당하는 값을 0으로 clear
                           // PINC0가 0이 된 것이 아님.
                           // PIN 초기값 : N/A
	char pattern = 0x01;   // 0x01부터 시작
	PORTD = pattern; // PORTD를 통해 pattern이 출력된다.
	
	while (1)
	{
		if((~PINC & (1<<PINC0))) // 버튼을 누르면,
                                 // 원래 PIN은 풀업/다운 저항이 없어, 초기값은 N/A
                                 // 우리는 풀업저항을 연결해줬으므로 초기값 : 1(=0x01)
                                 // ~PINC : 0xfe, (1<<PINC0) : 0x01
                                 // 버튼을 누르면, PINC : 0x00
                                 // ∴ 조건문이 참이 되면서 실행문 동작
		{
			pattern = shiftLeft(pattern);
			PORTD = pattern;
			_delay_ms(200);
		}
	}
}

 


 

[LED Shift, Reverse shift, Reset]

 

Button1 : shift

Button2 : reverse shift

Button3 : flag → 그냥 버튼 채우기용(무시)

Button4 : Reset

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

// 버튼 누르면 시프트(1), 반대로 돌고(2), 플래그 세우고(3), 꺼지게끔(4) //

int main() 
{
	DDRD = 0xff; // PORTD all 출력 활성화
	
	uint8_t ledData = 0x01; // ledData 변수는 0x01로 시작
	uint8_t buttonData;
	int flag = 0; // 별 의미 없음
	PORTD = 0x00; // 꺼진 상태로 출발
		
	while(1)
	{
		buttonData = PINC; // 그냥 PINC를 쓰면 되지, 귀찮게 왜 buttonData 변수를 선언해서 쓰냐?
                           // 다른 코드들은 그대로 쓰고, buttonData에 대입할 PIN만 바꿔서 쓸 수 있기 때문
		if((buttonData & (1<<0)) == 0) // 0번 버튼 누르면,
		{
			PORTD = ledData; // 0x01로 시작
			ledData = (ledData >> 7) | (ledData << 1); // 한 칸씩 좌시프트
			_delay_ms(200);
		}
		if ((buttonData & (1<<1)) == 0) // 1번 버튼 누르면,
		{
			PORTD = ledData;
			ledData = (ledData >> 1) | (ledData << 7); // 한 칸씩 우시프트
			_delay_ms(200);
		}
		if ((buttonData & (1<<2)) == 0) // 2번 버튼 누르면,
		{
			flag = 1; // 이거 그냥 의미없음. 무시
		}
		if((buttonData & (1<<3)) == 0) // 3번 버튼 누르면,
		{
			PORTD = 0xff; // all portD의 LED 출력
			_delay_ms(200);
			PORTD = 0x00;
			_delay_ms(200);
			if ((buttonData & (1<<3)) == 0)
			{
				flag = 0;
			}
		}
	}
}

 


 

[Debouncing]

 

 

택타일 스위치는 위와 같은 구조로 인해, 스위치 On-Off 과정에서 Bouncing(=chattering)이 발생한다.

Bouncing이 발생하면, 스위치 동작과는 다른 잘못된 신호들이 출력될 수 있으므로 보정이 필요하다.

 

Bouncing 발생

 

1. 소프트웨어적인 Debounce

    ① delay 함수로 짧은 딜레이를 준다.

    ② if문으로 입력 신호를 줬는지 한 번 더 물어본다.

 

// ① 딜레이함수 사용 //
if((buttonData & (1<<0)) == 0)
		{
			PORTD = ledData;
			_delay_ms(50); // 소프트웨어적으로 만든 간단한 debouncing, Debounce code 
			ledData = (ledData >> 7) | (ledData << 1);
			_delay_ms(100); // 1번 눌렀는데 2칸씩 가버린다.(bouncing 발생)
		}
        
// ② if문 하나 더 추가 //

if((buttonData & (1<<0)) == 0) // if문을 하나 더 써서, 눌렀냐?(60ms) 진짜 눌렀어? -> 출력
                               // 이렇게 디바운싱하면 더 잘 잡힌다.
		{
			_delay_ms(60); // 딜레이 추가
			if((buttonData & (1<<0)) == 0) // 0번 버튼
			{
				PORTD = ledData;
				ledData = (ledData >> 7) | (ledData << 1);
				_delay_ms(100); // 1번 눌렀는데 2칸씩 가버린다.(bouncing 발생)
			}
		}

 

2. 하드웨어적인 Debounce

    ① 커패시터를 달아준다.

 

※ 세 가지 Debounce 방식을 다 사용하면 Bouncing이 거의 잡히게 된다.

 


 

[LED On, Off, Toggle] - 이제까지 배운 것들 활용

 

enum

 

enum은 일종의 data type으로 일련의 이름을 갖는 상수들의 집합이다.

예를 들면, 

enum color {Red, Green, Blue};

이런 식으로 enum data type의 각각 이름을 갖는 color 집합을 선언할 수 있으며,

배열, 구조체와 다른 점은 "Red", "Green", "Blue"는 각각 0, 1, 2 의 값을 가진다는 점이다.

만약 Red가 3으로 시작됐다면, Green은 4, Blue는 5를 가지고 기본적으로 1씩 증가한다. 


<enum 사용 예시>

더보기
#include <stdio.h>

enum Month {
  JANUARY,
  FEBRUARY,
  MARCH,
  APRIL,
  MAY,
  JUNE,
  JULY,
  AUGUST,
  SEPTEMBER,
  OCTOBER,
  NOVEMBER,
  DECEMBER
};

enum Season {
  SPRING,
  SUMMER,
  AUTUMN,
  WINTER
};

enum Season getSeason(enum Month month) {
  switch (month) {
    case MARCH:
    case APRIL:
    case MAY:
      return SPRING;
    case JUNE:
    case JULY:
    case AUGUST:
      return SUMMER;
    case SEPTEMBER:
    case OCTOBER:
    case NOVEMBER:
      return AUTUMN;
    case DECEMBER:
    case JANUARY:
    case FEBRUARY:
      return WINTER;
    default:
      return SPRING;
  }
}

int main() {
  enum Month currentMonth = JULY;
  enum Season currentSeason = getSeason(currentMonth);

  switch (currentSeason) {
    case SPRING:
      printf("It's spring.\n");
      break;
    case SUMMER:
      printf("It's summer.\n");
      break;
    case AUTUMN:
      printf("It's autumn.\n");
      break;
    case WINTER:
      printf("It's winter.\n");
      break;
    default:
      printf("Invalid season.\n");
      break;
  }

  return 0;
}

 

 


 

LED On, Off, Toggle

 

Button1 : LED가 꺼져 있다면, On

Button2 : LED가 켜져 있다면, Off

Button3 : LED Toggle(On/Off)

 

<Button_structure.h>

#ifndef INCFILE1_H_
#define INCFILE1_H_
#include <stdint.h>

#define LED_DDR			DDRD  // 선언한 얘네들만 바꾸면, 나머지들은 손댈 필요 없음.
#define LED_PORT		PORTD // 실무에서는 이런 식으로 코드 작성
#define BUTTON_DDR		DDRC
#define BUTTON_PIN		PINC
#define BUTTON_ON		0
#define BUTTON_OFF		1
#define BUTTON_TOGGLE	        2


enum{PUSHED, RELEASED};
enum{NO_ACT, ACT_PUSH, ACT_RELEASED};
// 문자를 정수처럼 쓸 수 있게 데이터형 변환


typedef struct _button{
	volatile uint8_t *ddr; // volatile : 최적화 하지 마라
	volatile uint8_t *pin;
	uint8_t btnPin;
	uint8_t prevState;
}Button; // Button 구조체 선언


// 함수명 선언해줌 //
void Button_init(Button *button, volatile uint8_t *ddr, volatile uint8_t *pin, uint8_t pinNum);
uint8_t BUTTON_getState(Button *button);


#endif /* INCFILE1_H_ */

 

<Button_structure.c>

#include "Button_structure.h"
#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>

// 초기화 함수 //
void Button_init(Button *button, volatile uint8_t *ddr, volatile uint8_t *pin, uint8_t pinNum)
{
	button->ddr = ddr; // Button의 포인터변수 button)
	button->pin = pin;
	button->btnPin = pinNum;
	button->prevState = RELEASED; // 초기화, 아무것도 안 누른 상태
	*button->ddr &= ~(1 << button->btnPin); // 버튼 핀을 입력으로 설정, ->가 *보다 빠름
                                            // 구조체 Button의 ddr 변수에서 button->btnPin에 해당하는 비트를 0으로 설정
                                            // *button->ddr = *(button->ddr) = *ddr
                                            // 포인터 변수는 연산이 가능
}

// 버튼 작동 함수 //

uint8_t BUTTON_getState(Button *button)
{
	uint8_t curState = *button->pin & (1<<button->btnPin); // 버튼 상태를 읽어옴
                                                           // *pin & (1 << pinNum)
                                                           // 풀업저항이므로 pin의 초기값은 1
                                                           // btnOn일때, curState = 0 =0x00
                                                           // btnOff일때, curState = 1 =0x01
                                                           // btnTog일때, curState = 2 =0x02
                                                           
	if ( (button->prevState == RELEASED) && (curState == PUSHED)) // 버튼을 안 누른 상태에서 누르면
                                                                  // PUSHED : 0, RELEASED : 1
	{
		_delay_ms(50); // 디바운스 코드
		button->prevState = PUSHED; // 버튼을 누른 상태로 변환
		return ACT_PUSH; // 버튼이 눌려 있음을 반환
	}
	else if((button->prevState == PUSHED) && (curState != PUSHED)) // 버튼을 누른 상태에서 떼면,
	{
		_delay_ms(50); // 디바운스 코드
		button->prevState = RELEASED; // 버튼을 뗀 상태로 변환
		return ACT_RELEASED; // 버튼이 떨어진 것을 반환
	}
	return NO_ACT; // 아무 것도 안 했을 때
}

 

<main.c>

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>
#include "Button_structure.h"


int main(void)
{
    LED_DDR = 0xff; // 출력 설정
	Button btnOn;
	Button btnOff;
	Button btnTog;
	
    // 초기화 함수//
	Button_init(&btnOn, &BUTTON_DDR, &BUTTON_PIN, BUTTON_ON); // PORTC 0번 핀 입력 활성화
	Button_init(&btnOff, &BUTTON_DDR, &BUTTON_PIN, BUTTON_OFF); // PORTC 1번 핀 입력 활성화
	Button_init(&btnTog, &BUTTON_DDR, &BUTTON_PIN, BUTTON_TOGGLE); // PORTC 2번 핀 입력 활성화
    	// prevState : 1 (RELEASED)
    	// curState : 2 (btnTOg를 마지막으로 초기화했기 때문에)
		
    while (1)
    // 초기화하고 버튼 안 누르고 있으면, prevState : RELEASED(1), curState : 2 유지
    // 위 값들은 if 조건을 다 거짓으로 출력시키기 때문에 NO_ACT 반환 반복됨.
    // 버튼을 계속 누르고 있으면 0, 떼는 순간 1로 돌아옴
    {
		if (BUTTON_getState(&btnOn) == ACT_RELEASED)
        	//  꺼진 상태에서 버튼1 누름(curState : 0),
            	//   i) prevState가 PUSHED로 바뀜
            	//   ii) 버튼1을 떼는 순간(curState : 1), else if 조건을 만족시킨다
            	//   iii) prevState : RELEASED, ACT_RELEASED 반환하며 LED 켜짐     
            
           	// 켜진 상태에서 버튼1 누름(curState : 0),
           	//  위 sequence 반복하지만 이미 켜져 있어서 불변            
		{
			LED_PORT = 0xff;
		}
		if(BUTTON_getState(&btnOff) == ACT_RELEASED)
        	// 꺼진 상태에서 버튼2 누름(curState : 1)
        	// 해당 조건 만족하여 다 꺼지나, 이미 꺼져 있으므로 불변
            
            	// 켜진 상태에서 버튼2 누름(curState : 1)
           	// prev는 이미 RELEASED 상태이고 cur ≠ PUSHED가 되므로
            	// if문 조건 만족시켜 LED 다 꺼짐
		{
			LED_PORT = 0x00;
		}
		if(BUTTON_getState(&btnTog) == ACT_RELEASED) // XOR 활용하여 토글
        	// 꺼진 상태에서 버튼3 누름(curState : 2)
            	// prev는 이미 RELEASED 상태이고 cur ≠ PUSHED가 되므로
            	// if문 만족하여 0x00 ^= 0xff --> 0xff LED 다 켜짐
            
            	// 켜진 상태에서 버튼3 누름(curState : 2)
            	// prev는 이미 RELEASED 상태이고 cur ≠ PUSHED가 되므로
            	// if문 만족하여 0xff ^= 0xff --> 0x00 LED 다 꺼짐
		{
			LED_PORT ^= 0xff;
		}
    }
}

 


 

[FSM]

 

Finite State Machine

: 유한 상태 머신

 

 

전구를 예시로 들어보자.

1. 전구의 state는 On / Off 두 가지가 존재한다.

    지정된 갯수(유한)의 state를 가진다 → Finite State

 

2. 하나의 입력값에는 두 가지의 상태가 존재할 수 있다.

    ex) State : OFF일때,

          i) Switch ON →  ON으로 state 변화

          ii) Switch OFF → OFF state 유지

 

<결론>

"어떠한 상태는 유한하게 존재하며, 임팩트에 의해서 상태가 변화한다."

 

728x90
반응형