거북이처럼 천천히

GPIO - LED control (3) 본문

Embedded Programming/Atmega 128A (실습)

GPIO - LED control (3)

유로 청년 2024. 5. 29. 13:43

Button 3개를 통해 LED를 컨트롤

1) 목표: PORT D에 PD0, PD1, PD2 각각 Button을 연결 한 뒤, PORT A에 연결된 LED를 컨트롤하기.

              PD0 : LED 에서 0, 2, 4, 6번째 다이오드를 출력

              PD1 : LED 7번째 다이오드부터 0번째 다이오드까지 shift하면서 출력

              PD2 : LED 0번째 다이오드부터 7번째 다이오드까지 shift하면서 출력

 

2) 선행 지식 : 해당 회로는 Pull-Up 구조 회로를 구성했기 때문에 Switch를 누르기 전에는 5V 전압이 들어오

                       다가Switch 를 누르면 GND로 short되어 전류는 더이상 Pin쪽으로 향하지 않고, GND으로

                       흐르기 때문에 0V 전압이 측정된다.

                 

                       따라서 Button이 눌러는지 여부는 PIN Register의 값을 통해 확인 가능하다.

                       

                        Pull-down, Pull-up에 대한 지식이 필요하면 아래 홈페이지 참고.

                        - Floating, Pull-up, Pull-down (tistory.com)

 

Floating, Pull-up, Pull-down

목표) 다음과 같은 궁금증을 알아보도록 하겠다.         1. 왜 일반적으로 스위치를 MCU와 연결할 때, Floating 현상을 접하게 되는가?          2. 이 때, Floating 문제를 해결하기 위해 10K Ohm 저항

jbhdeve.tistory.com

 

 

 

3) Source code

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

// Substitute constant
#define BUTTON_DDR		DDRD
#define BUTTON_PIN		PIND
#define LED_DDR			DDRA
#define LED_PORT		PORTA
#define TIME			200

// Function prototype.
void init_button();
void init_led();

// Main method
int main(void) {
	init_button();
	init_led();
	
	// 현재 회로를 pull-up 구조로 연결된 형태다.
	// 이로 인해 Switch 가 열려있는 상태에서는 PIN에 5V 전압이 들어오는 상태이다.
	// 따라서 Switch 가 열려있으면 해당 핀 위치에 BUTTON_PIN = 1이다.
	// ex) PD1 에 연결된 Switch 가 열려 있으면 BUTTON_PIN = xxxx xx1x이다.
	
	// 하지만, Switch 가 닫히면 PIN에 더 이상 5V 전압이 들어오지 않고, 전류가 GND로 흘러 
	// 전압 값이 0V가 되며 해당 핀 위치에 BUTTON_PIN = 0이 된다.
	// ex) PD2 에 연결된 Switch 가 닫혀 있으면 BUTTON_PIN = xxxx x0xx이다.
	while(1) {
		if((BUTTON_PIN & (1<<PORTD0)) == 0) {
			PORTA = (1<<PORTA0 | 1<<PORTA2 | 1<<PORTA4 | 1<<PORTA6);
			_delay_ms(TIME);

			PORTA &= ~(1<<PORTA0 | 1<<PORTA2 | 1<<PORTA4 | 1<<PORTA6);
		}
		
		if((BUTTON_PIN & (1<<PORTD1)) == 0) {
			uint8_t led_data = 0x80;
			
			for(uint8_t i=0; i<9; i++) {
				PORTA = (led_data>>i);
				_delay_ms(TIME);
			}
		}
		
		
		// Flag 를 통해 컨트롤 해보자.
		// 왜 Flag 를 이용하냐? 그냥......  
		uint8_t flag = 0;
		
		if((BUTTON_PIN & (1<<PORTA2))== 0) flag = 1;
		else flag = 0;
		
		if(flag) {
			uint8_t led_data = 0x01;
			
			for(uint8_t i=0; i<9; i++) {
				LED_PORT = (led_data<<i);
				_delay_ms(TIME);
			}
		}
	}
}

// Initialization for button.
void init_button() {
	// PD0, PD1, PD2 에 Switch 연결
	BUTTON_DDR &= ~(1<<DDRD0 | 1<<DDRD1 | 1<<DDRD2);
}

// Initialization for LED.
void init_led() {
	// PORT A 의 8핀에 모두 LED가 연결된 상태
	LED_DDR = 0xff;
}

 

 

4) 구현 영상

 

Button 3개를 이용하여 LED 제어 (Interrupt 는 제외)

 

 

5) 마무리

 

- 해당 구현의 핵심은 Pull-up으로 회로를 구성했는가? Pull-down으로 회로를 구성했는가? 에 따라 버튼이

  누르지않았을 때, PIN에 5V or 0V 전압이 들어가고 있는지 여부와 Button_Pin 값이 어떻게 나오는지를 

  확인한 것에 중점을 두고 있다.

 


구조체와 버튼 3개를 이용하여 LED를 컨트롤

 

1) 목표: PORT D에 PD0, PD1, PD2 각각 Switch를 연결하고, 스위치에 대한 구조체를 정의 한 뒤,

            각 Switch에 대한 인스턴스 생성하여 각 Switch가 눌렸을 때, 그에 따른 동작할 수 있도록 설계

 

2) 선행 지식 : 

    2 - 1) 스위치에 대한 구조체는 다음과 같은 맴버들을 갖는다.

      ▶ DDR (Data Direction Register) 에 대한 포인터

      ▶ PIN (Pin Register) 에 대한 포인터

      ▶ Button이 연결된 Pin number (= 버튼이 연결된 핀 넘버)

      ▶ 이전 상태 

 

    

    2 - 2) enum, 열거형

      ▶ 여러 개의 정수형 상수에 이름을 붙이는 과정이 굉장히 힘들기 때문에 

           이를 보다 쉽게 붙이는 동시에 가독성을 높힐 수 있다.

      ▶ ex) enum = { NO_ACT , PUSHED , RELEASE } 

                위 열거형은 NO_ACT 부터 차례대로 0, 1, 2, 3 인덱스 값을 갖는다.

 

3) Source code

 

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

// Structure.
// Member = DDR(Data direction register)의 포인터
//          PIN(Pin number)의 포인터
//          Button Pin number
//          Previous state
typedef struct {
	volatile uint8_t   *ddr;
	volatile uint8_t   *pin;
	uint8_t            btnPin;
	uint8_t            prevState;
}Button;


// 열거형
// 정수형 상수들을 이름을 붙어 사용하는 과정에서
// 많은 정수형 상수들이 많이 관리하기 힘들때 열거형 자료형을 이용하면
// 구지 각각 상수에 이름을 붙어주지 않아도 아래와 같이 선언함으로서
// 첫번째 원소부터 0, 1, 2, 3, .. index 값을 갖게 된다.
enum {PUSHED, RELEASED};
enum {NO_ACT, ACT_PUSHED, ACT_RELEASE};

// Substitute constant
#define BUTTON_ON         0
#define BUTTON_OFF         1
#define BUTTON_TOGGLE      2

#define BUTTON_DDR         DDRD
#define LED_DDR            DDRA
#define LED_PORT         PORTA

#define TIME 50

// Function prototype.
void init_botton(Button *, volatile uint8_t *, volatile uint8_t *, uint8_t );
uint8_t Button_getState(Button *);

// Main method.
int main(void) {
	Button btnOn;
	Button btnOff;
	Button btnToggle;
	
	init_botton(&btnOn, &DDRD, &PIND, BUTTON_ON);
	init_botton(&btnOff, &DDRD, &PIND, BUTTON_OFF);
	init_botton(&btnToggle, &DDRD, &PIND, BUTTON_TOGGLE);
	
	while(1) {
		if(Button_getState(&btnOn) == PUSHED) {
			LED_PORT = 0xff;
			_delay_ms(TIME);
		}
		
		if(Button_getState(&btnOff) == PUSHED) {
			LED_PORT = 0x00;
			_delay_ms(TIME);
		}
		
		if(Button_getState(&btnToggle) == PUSHED) {
			// ★★★ Toggle 하는 기술 ★★★
			LED_PORT ^= 0xff;
			_delay_ms(TIME);
		}
	}
}

// Initialization of Button.
void init_botton(Button *p_btn, volatile uint8_t *ddr, volatile uint8_t *pin, uint8_t pinNumber) {
	p_btn -> ddr = ddr;
	p_btn -> pin = pin;
	p_btn -> btnPin = pinNumber;
	p_btn -> prevState = RELEASED;
	
	*(p_btn -> ddr) &= ~(1<<pinNumber);
}

// Button 의 상태를 체크한 후, 상태 변환 설정
uint8_t Button_getState(Button *button) {
	// currentState = 1 ---> No Push
	// currentState = 0 ---> Push 
	uint8_t currentState = *button -> pin & (1<<button->btnPin);
	
	// currentState = 0 && previous state 가 released 라면 
	if(currentState && button->prevState == RELEASED) {
		button->prevState = PUSHED;
		return ACT_PUSHED;
	}
	
	// currentState = 1 && previous state 가 pushed 라면
	else if(currentState && button->prevState == PUSHED) {
		button -> prevState = RELEASED;
		return ACT_RELEASE;
	}
	
	// 아무런 상태 변화가 없다면
	else return NO_ACT;
}

 

  • Button 이라는 구조체는 아래와 같이 4개의 맴버를 갖는다.

      ▶버튼의 DDR 포인터 변수 

      ▶버튼의 PIN 포인터 뱐수

      ▶PIN number : 버튼이 연결된 PIN 넘버

      ▶prevState : 버튼의 이전 상태

  • 열거형, enum 키워드를 이용하여 각 상태에 대해서 인덱스를 부여 했다.
  • PORT D에 연결된 3개의 스위치를 다음과 같이 이름을 붙힌 뒤, 아래와 같은 작업을 수행하도록 설계했다. 

      ▶btnOn : 해당 스위치를 누르면 PORTA에 연결된 LED 8개를 전부 켜기.

      ▶btnOff : 해당 스위치를 누르면 PORTA에 연결된 LED 8개를 전부 끄기.

      ▶btnToggle : 해당 스위치를 누르면 PORTA에 연결된 LED 8개를 전부 Toggle 시키기.

 

  • 다음은 각 함수에 대한 기능과 역활에 대해서 작성하면 다음과 같다.

ⓘ init_botton 함수

  • init_botton 함수를 통해 버튼의 구조체의 포인터 값을 매개변수로 받아 버튼의 구조체를 초기화 작업을 수행한다.

      ▶ 'p_btn -> prevState = RELEASED;  의 의미는 "현재 해당 버튼은 누르지 않은 상태"는 것을 의미

      ▶  *(p_btn -> ddr) &= ~(1<<pinNumber); 의 의미는 "버튼이 연결된 PORT에서 입력 값을 받기 위해

           DDR 값에서 PIN 위치를 0으로 설정하겠다." 것을 의미

 

② Button_getState 함수

  • uint8_t currentState = *button -> pin & (1<<button->btnPin);

      ▶*button -> pin 는 "button 포인터 변수의 맴버인 pin 포인터 변수의 값음 참조하겠음"을 의미한다.

      ▶(1<<button->btnPin); 는 "현재 버튼이 존재하는 PIN 위치만큼 Shift 하겠음"을 의미한다.

      ▶*button -> pin & (1<<button->btnPin); 는 두 가지 경우의 수로 나누어 생각할 수 있다.

 

         현재 회로의 구성은 Pull-up 회로 구성이기 때문에 버튼을 누리지 않아 회로가 Open된 상태면 5V

         전압이 PIN으로 들어오고, 버튼을 눌러 회로가 Short되면 0V 전압이 PIN에서 측정할 수 있다.

 

        Case 1) 버튼을 누른 경우

         따라서 버튼을 누를 경우 *button -> pin 에서 버튼이 연결된 PIN 값이 1에서 0로 변환이 될 것이며,  

        (1<<button->btnPin) 버튼이 연결된 PIN 위치의 값이 0이기때문에  AND 연산을 통해 버튼이 눌렀을

        경우에는 전체 조건식이 거짓(0) 이 되게 된다. 따라서 열거형에서 정의된 PUSHED, RELEASED 중

       에서 PUSHED가 선택된다.

 

        Case 2) 버튼을 누르지 않은 경우

          반대로 버튼을 누르지 않은 경우에서는 *button -> pin 에서 버튼이 연결된 PIN 값이 1이기 때문에 

         해당 조건은 참이 되며, AND 연산에 의해 전체 조건식은 참이 된다. 따라서 currentState 변수에는 
         RELEASED 값이 저장된다.

 

 

  • currentState && button->prevState

      ▶currentState 변수는 "현재 버튼의 누린 상태 여부에 대한 데이터"를 갖고 있다.

      ▶currentState 변수와 button->prevState 변수의 값에 따라 총 4가지 경우의 수로 나눌 수 있다.

          - currentState : PUSHED + button->prevState : RELEASED

          - currentState : PUSHED + button->prevState : PUSHED

          - currentState : RELEASED + button->prevState : PUSHED

          - currentState : RELEASED+ button->prevState : RELEASED

 

      ▶여기서 관심 있게 봐야할 경우의 수는 다음 2가지이며, 나머지는 상태 변화가 이전 상태와 비교

          했을 때 없기 때문에 이에 따른 LED 상태를 변경해 줄 필요가 없다.

          - currentState : PUSHED + button->prevState : RELEASED 

             = 이전 상태에서는 해당 버튼이 눌러지지 않았는데, 현 상태에서는 해당 버튼이 눌러진 상태

          - currentState : RELEASED + button->prevState : PUSHED

             =  이전 상태에서는 해당 버튼이 눌러졌는데, 현 상태에서는 해당 버튼이 눌러지지 않은 상태

 

        ▶ 만약 현재 상태에서는 버튼이 PUSEHED된 상태이고, 과거의 상태에서 RELEASE된 상태라면 

            해당 버튼은 눌러진 상태라고 인식하고, 해당 버튼의 기능을 수행한다.

	// currentState = 0 && previous state 가 released 라면 
	if(currentState && button->prevState == RELEASED) {
		button->prevState = PUSHED;
		return ACT_PUSHED;
	}

 

      ▶ 만약 현재 상태에서는 버튼이 RELEASE된 상태이고, 과거의 상태에서 PUSHED된 상태라면

           해당 버튼에서 손가락을 뗀 상태라고 인식한다.

	// currentState = 1 && previous state 가 pushed 라면
	else if(currentState && button->prevState == PUSHED) {
		button -> prevState = RELEASED;
		return ACT_RELEASE;
	}

 

 

  • if(Button_getState(&btnOn) == PUSHED)

      ▶ Button_getState 함수의 Return 값은 uint_8이기 때문에 정수형과 정수형을 비교 연산하는 것같지만, 

          이는 전혀 다른 결과를 야기하며, 참과 거짓을 가지고 비교하는 연산 관점으로 접근해야 한다.

      ▶ 만약 Button_getState 함수의 Return 값이 ACT_PUSHED or ACT_RELEASE를 return 하면 

           해당 값을 enum, 열거형에 따라 Index로 보는 것이 아닌 참과 거짓 관점에서 True라고 봐야 한다.

 

 


4) 구현 영상

 

구조체를 이용하여 버튼을 통한 LED 제어

 

5) 마무리

 

- 구조체를 이용 없이 단순히 구현하는 작업은 쉬울 수 있지만, 구조체의 개념 및 활용을 적용하여 회로를

  구성하게 된다면 어려울 수 있다. 따라서 코드를 작성하기 전에 "구조체는 어떤 맴버를 가질 것인가?",

   "어떻게 구조체 초기화 할것인가?" 등을 먼저 생각하여 큰 그림을 그린 뒤, 구현하는 연습과 반복 연습을

   통해 익숙해져야 할 것 같다.