거북이처럼 천천히

Structure 본문

Embedded Programming/Atmega 128A (실습)

Structure

유로 청년 2024. 6. 11. 20:50

서론 

 

이번 구현 실습에는 버튼 3개를 통해 LED를 제어하는데, 이 과정에서 구조체와 포인터, 열거형(enum) 을 사용하여 구현하도록 하겠다. 해당 실습을 통해 구조체, 포인터에 대한 이론 및 활용을 공부하고, enum에 대해서 공부하도록 하겠다.

 

 

 

본론

 

이번 구현 환경은 다음과 같다.

 

1. 연결된 상태

  • PORT F 의 8핀을 모두 사용하여 LED를 연결
  • PD0, PD1, PD2 에 버튼 연결
  • 각 버튼들은 Pull-up 회로 연결된 상태이다.
    - 버튼을 누르기 전까지는 회로가 open 상태이기 때문에 5V 전압이 인가된다.
    - 버튼을 누르면 회로가 short되면서 0V 전압으로 변환된다.

 

2. 동작

  • PD0 버튼을 누르면 LED가 켜진다.
  • PD1 버튼을 누르면 LED가 꺼진다.
  • PD2 버튼을 누르면 LED가 Toggle된다.

 

 

3. 구조체 (Button)

 

구조체는 다음과 같이 맴버를 갖는다. 

typedef struct {
   volatile uint8_t *ddr;
   volatile uint8_t *pin;
   uint8_t          btnPin;
   uint8_t          prevState; // 버튼 상태
} Button;

 

  • ddr 포인터 변수 : 버튼의 DDR (Data Direction Register)
  • pin 포인터 변수 : 버튼의 PIN (Input Pins Address Register)
  • btnPin 변수 : 버튼이 연결된 핀 번호
  • prevState 변수 : 버튼의 이전 상태

 

 

4. 열거형         

 

열거형은 다음과 같이 구성했지만, 단순히 순서를 아래와 같이 구현한 것이 아니다. 

주의) 해당 열거형은 Pull-up Switch 회로 구성임을 생각하고, 아래와 같이 열거형을 구성했다.

// Enumeration
enum {PUSHED, RELEASED};
enum {NO_ACT, ACT_PUSHED, ACT_RELEASED};
enum {LED_ON, LED_OFF, LED_TOGGLE};

 

  • enum {PUSHED, RELEASED};
    - Switch circuit은 Pull-Up 회로 이기 때문에 Switch가 Open되면 button의 PIN Register 값은 1(5V 전압)값을 갖는다.
    - 그러나, Switch가 Close되면 button의 PIN Register 값은 0(0V 전압)값을 갖는다.

    Q) 왜 Switch가 Close되면 PIN 값은 0V 전압 값을 갖는가?
    A) Switch가 닫히면 Vcc와 GND 가 Short되기 때문에 버튼의 핀으로 전압이 가지 않기 때문에 0V 전압을 갖는다. 

            (정말 중요)
    - 따라서 버튼이 눌렀을 때, 버튼의 핀의 값은 0이기 때문에 PUSHED 상수 값이 0이여야 한다.
    - 또한 버튼을 누리지 않으면 버튼의 핀의 값이 1이기 때문에 RELEASED 상수 값이 1이여야 한다.

    Q) 왜 해당 내용을 강조하는가? 
    A) 본인이 이 내용때문에 정말 고생했기 때문이다. 해당 경험을 코드를 통해 프로그래밍시 회로의 구성 및 상태를 고려하고, 단순히 프로그래밍하면 안된다는 것을 배웠기 때문에 중요하다고 생각한다.

  • enum {NO_ACT, ACT_PUSHED, ACT_RELEAED}
    - 버튼의 현재 상태와 과거 (이전) 상태를 모두 고려하여 버튼의 상태 변화에 대한 반환값으로 활용



5. 각 버튼의 구조체를 초기화 작업 함수

 

이번 구현에서는 3가지 버튼을 사용한다.1) LED을 켜는 버튼, 2) LED를 끄는 버튼, 3) LED를 Toggle 버튼

// Initialization of button.
void init_button(BUTTON *button, volatile uint8_t *ddr, volatile uint8_t *pin, uint8_t pinNumber) {
	button->ddr = ddr;
	button->pin = pin;
	button->pinNumber = pinNumber;
	button->prev_state = RELEASED;
	
	// 버튼의 출력 핀을 입력으로 초기 설정.
	*button->ddr &= ~(1<<button->pinNumber);
}

 

  • 각각의 버튼의 주소 값와 초기화 작업에 필요한 값을 argument로 전달하면 이를 초기화한다.
  • 그리고, 버튼이 연결된 핀에 대해서 DDR (Data Direction Register) Register 값을 0으로 설정하여 
    입력으로 받도록 초기 설정한다.

 

 

 

6. 버튼의 현재 상태와 과거 상태를 모두 고려하여 버튼이 눌러진 상태인지, 버튼을 눌렀다가 뗀 상태인지 확인

 

해당 함수는 이번 구현에 핵심 중 하나라고 볼 수 있는 함수이다.

// Detecting a button.
uint8_t detecting_button(BUTTON *button) {
	// 현재 스위치 회로는 Pull-Up 회로로 연결된 상태이기 때문에
	// Switch Open : pin 값에 5V 전압 값이 감지
	// Switch Close : pin 값에 0V 전압 값이 감지
	uint8_t current_state = *button->pin & (1<<button->pinNumber);
	
	if(current_state == PUSHED && button->prev_state == RELEASED) {
	    button->prev_state = PUSHED;
	    return ACT_PUSHED;
	}else if(current_state != PUSHED && button->prev_state == PUSHED) {
            button->prev_state = RELEASED;
            return ACT_RELEASED;
	}else return NO_ACT;
}

 

  • 다시 한 번 강조하지만, 해당 스위치 회로는 Pull-up으로 구성되었다.
  • 따라서 버튼을 누르기 전에는 아래 그림과 같이 Vcc와 GND가 회로적으로 연결이 안 된 상태이기 때문에 버튼을 Open 상태에서는 버튼의 핀에는 5V 전압이 들어온다.
  • 하지만, 버튼을 누르면 Vcc와 GND가 회로적으로 연결되기 때문에 Vcc 전압이 버튼의 핀으로 가지 않고, GND로 향하기 때문에 버튼의 핀에는 0V 전압이 들어온다.

Pull up 스위치 회로 구성은 위 같다.

 

 

  • 따라서 current_state 변수는 버튼을 눌렀을 때는 0을 갖지만, 버튼을 누르지 않으면 1을 갖는다.
  • 따라서 버튼이 눌렀는지를 감지하기 위해서 if문 내에서 아래와 같이 구성함으로서 눌렀는지 여부를 확인한다.
if(current_state == PUSHED && button->prev_state == RELEASED)

 

  •                           
    Q) 열거형에서 PUSHED와 RELEASED를 바꾸면 안되는가?

         즉, enum {RELEASED, PUSHED}로 작성하면 안되는가?
    A) 본인도 PUSHED와 RELEASED를 바꾸어보고, 고생(?) 좀 했다.
  • 일단, 이전에도 말했듯이 해당 스위치 회로는 Pull-UP 회로로 구성되어 있다. 즉, 회로를 누르면 버튼의 핀 값이 1이 아니고 0이다. 따라서 위 if문을 이용하여 버튼을 눌렀는지 여부를 확인하려면, PUSHED 상수값이 0이여야 가능하다.

  • Q) 히지만, 만약 스위치 회로가 Pull-down 회로 구성했다면, 가능한가?
  • A) 그땐 바꿔도 좋다. 왜냐하면 Pull-down 스위치 회로에서는 버튼을 누르지 않으면 0V이지만, 버튼을 누르면 5V전압이 버튼의 핀으로 들어오기 때문에 그 때는 버튼을 눌렀는지 여부를 확인하기 위해서는 PUSHED 상수 값이 1이여야 한다.

 

 

본론 (소스 코드)

- 버튼을 누르는 순간에 버튼을 동작하도록 설계

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

// Structure.
typedef struct {
	volatile uint8_t* ddr;
	volatile uint8_t* pin;
	uint8_t pinNumber;
	uint8_t prev_state;
} BUTTON;

// Substitute constant.
#define BUTTON_DDR		DDRD
#define BUTTON_PIN		PIND
#define LED_DDR			DDRF
#define LED_PORT		PORTF
#define TIME			200

// Enumeration
enum {PUSHED, RELEASED};
enum {NO_ACT, ACT_PUSHED, ACT_RELEASED};
enum {LED_ON, LED_OFF, LED_TOGGLE};

// Function prototype.
void init_button(BUTTON *button, volatile uint8_t *ddr, volatile uint8_t *pin, uint8_t pinNumber);
void init_LED();
uint8_t detecting_button(BUTTON *button);

// Main method.
int main(void)
{
	BUTTON BUTTON_LED_ON;
	BUTTON BUTTON_LED_OFF;
	BUTTON BUTTON_LED_TOGGLE;
	
	// Initialization of button.
	init_button(&BUTTON_LED_ON, &BUTTON_DDR, &BUTTON_PIN, LED_ON);
	init_button(&BUTTON_LED_OFF, &BUTTON_DDR, &BUTTON_PIN, LED_OFF);
	init_button(&BUTTON_LED_TOGGLE, &BUTTON_DDR, &BUTTON_PIN, LED_TOGGLE);
	
	// Initialization of LED.
	init_LED();
	
	while(1) {
		// Detect motion of button.
		if(detecting_button(&BUTTON_LED_ON) == ACT_PUSHED) {
			LED_PORT = 0xff;
			_delay_ms(TIME);
		}
		
		if(detecting_button(&BUTTON_LED_OFF) == ACT_PUSHED) {
			LED_PORT = 0x00;
			_delay_ms(TIME);
		}
		
		if(detecting_button(&BUTTON_LED_TOGGLE) == ACT_PUSHED) {
			LED_PORT ^= 0xff;
			_delay_ms(TIME);
		}
	}
	
	return 0;
}

// Initialization of button.
void init_button(BUTTON *button, volatile uint8_t *ddr, volatile uint8_t *pin, uint8_t pinNumber) {
	button->ddr = ddr;
	button->pin = pin;
	button->pinNumber = pinNumber;
	button->prev_state = RELEASED;
	
	// 버튼의 출력 핀을 입력으로 초기 설정.
	*button->ddr &= ~(1<<button->pinNumber);
}

// Initialization of LED.
void init_LED() {
	LED_DDR = 0xff;
	LED_PORT = 0x00;
}

// Detecting a button.
uint8_t detecting_button(BUTTON *button) {
	// 현재 스위치 회로는 Pull-Up 회로로 연결된 상태이기 때문에
	// Switch Open : pin 값에 5V 전압 값이 감지
	// Switch Close : pin 값에 0V 전압 값이 감지
	uint8_t current_state = *button->pin & (1<<button->pinNumber);
	
	if(current_state == PUSHED && button->prev_state == RELEASED) {
		button->prev_state = PUSHED;
		return ACT_PUSHED;
	}else if(current_state != PUSHED && button->prev_state == PUSHED) {
		button->prev_state = RELEASED;
		return ACT_RELEASED;
	}else return NO_ACT;
}

 

 

 

 

- 버튼을 눌렀다가 떼는 순간에 버튼을 동작하도록 설계

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

// Structure.
typedef struct {
	volatile uint8_t* ddr;
	volatile uint8_t* pin;
	uint8_t pinNumber;
	uint8_t prev_state;
} BUTTON;

// Substitute constant.
#define BUTTON_DDR		DDRD
#define BUTTON_PIN		PIND
#define LED_DDR			DDRF
#define LED_PORT		PORTF
#define TIME			200

// Enumeration
enum {PUSHED, RELEASED};
enum {NO_ACT, ACT_PUSHED, ACT_RELEASED};
enum {LED_ON, LED_OFF, LED_TOGGLE};

// Function prototype.
void init_button(BUTTON *button, volatile uint8_t *ddr, volatile uint8_t *pin, uint8_t pinNumber);
void init_LED();
uint8_t detecting_button(BUTTON *button);

// Main method.
int main(void)
{
	BUTTON BUTTON_LED_ON;
	BUTTON BUTTON_LED_OFF;
	BUTTON BUTTON_LED_TOGGLE;
	
	// Initialization of button.
	init_button(&BUTTON_LED_ON, &BUTTON_DDR, &BUTTON_PIN, LED_ON);
	init_button(&BUTTON_LED_OFF, &BUTTON_DDR, &BUTTON_PIN, LED_OFF);
	init_button(&BUTTON_LED_TOGGLE, &BUTTON_DDR, &BUTTON_PIN, LED_TOGGLE);
	
	// Initialization of LED.
	init_LED();
	
	while(1) {
		// Detect motion of button.
		if(detecting_button(&BUTTON_LED_ON) == ACT_RELEASED) {
			LED_PORT = 0xff;
			_delay_ms(TIME);
		}
		
		if(detecting_button(&BUTTON_LED_OFF) == ACT_RELEASED) {
			LED_PORT = 0x00;
			_delay_ms(TIME);
		}
		
		if(detecting_button(&BUTTON_LED_TOGGLE) == ACT_RELEASED) {
			LED_PORT ^= 0xff;
			_delay_ms(TIME);
		}
	}
	
	return 0;
}

// Initialization of button.
void init_button(BUTTON *button, volatile uint8_t *ddr, volatile uint8_t *pin, uint8_t pinNumber) {
	button->ddr = ddr;
	button->pin = pin;
	button->pinNumber = pinNumber;
	button->prev_state = RELEASED;
	
	// 버튼의 출력 핀을 입력으로 초기 설정.
	*button->ddr &= ~(1<<button->pinNumber);
}

// Initialization of LED.
void init_LED() {
	LED_DDR = 0xff;
	LED_PORT = 0x00;
}

// Detecting a button.
uint8_t detecting_button(BUTTON *button) {
	// 현재 스위치 회로는 Pull-Up 회로로 연결된 상태이기 때문에
	// Switch Open : pin 값에 5V 전압 값이 감지
	// Switch Close : pin 값에 0V 전압 값이 감지
	uint8_t current_state = *button->pin & (1<<button->pinNumber);
	
	if(current_state == PUSHED && button->prev_state == RELEASED) {
		button->prev_state = PUSHED;
		return ACT_PUSHED;
	}else if(current_state != PUSHED && button->prev_state == PUSHED) {
		button->prev_state = RELEASED;
		return ACT_RELEASED;
	}else return NO_ACT;
}

 

 

 

 

  • Q) detecting_button 함수에 대해 좀 더 설명해달라.
// Detecting a button.
uint8_t detecting_button(BUTTON *button) {
	// 현재 스위치 회로는 Pull-Up 회로로 연결된 상태이기 때문에
	// Switch Open : pin 값에 5V 전압 값이 감지
	// Switch Close : pin 값에 0V 전압 값이 감지
	uint8_t current_state = *button->pin & (1<<button->pinNumber);
	
	if(current_state == PUSHED && button->prev_state == RELEASED) {
		button->prev_state = PUSHED;
		return ACT_PUSHED;
	}else if(current_state != PUSHED && button->prev_state == PUSHED) {
		button->prev_state = RELEASED;
		return ACT_RELEASED;
	}else return NO_ACT;
}

 

  • current_state 변수는 현재의 버튼 상태를 입력받아 저장한 변수이다.
  • 첫 번째 조건문은 "현재 상태는 버튼을 눌렀는데, 과거 상태는 버튼을 누르지 않는 상태라면"을 의미
    ▶ 조건에 만족하면 "버튼의 과거 상태를 눌린 상태"로 변환
    ACT_PUSHED 상태를 반환한다.
  • 두 번째 조건문은 "현재 상태는 버튼이 눌리지 않는 상태이지만, 과거 상태는 버튼이 눌린 상태라면"를 의미
    ▶조건에 만족하면 "버튼의 과거 상태를 누르지 않은 상태"로 변환
    ▶ ACT_RELEASED 상태로 반환하다.
  • 나머지 경우는 NO_ACT 상태를 반환함으로서 해당 상태에 대해서 반응하지 않도록 한다.
    ▶ 나머지 경우는 다음과 같다.
    ▶ 현재 상태 : 버튼이 눌린 상태, 과거 상태 : 버튼이 눌린 상태
    ▶ 현재 상태 : 버튼이 누르지 않은 상태, 과거 상태 : 버튼이 누르지 않은 상태

 

 

결론

이번 구현은 구조체 개념과 포인트 개념, 회로 개념이 종합적으로 들어갔기 때문에 중요한 구현이였다. 이해하기 어렵거나 스스로 다시 구현하는 것이 힘들 수 도 있지만, 그런 만큼 배우는 것과 실력이 상승할 수 있어 다시 한 번 천천히 생각하고, 스스로 구현해보도록 연습해보자.