일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- java
- Pspice
- hc-sr04
- BASYS3
- FND
- i2c 통신
- uart 통신
- structural modeling
- vivado
- D Flip Flop
- behavioral modeling
- test bench
- prescaling
- ATMEGA128A
- verilog
- Recursion
- KEYPAD
- stop watch
- pwm
- dataflow modeling
- soc 설계
- atmega 128a
- gpio
- half adder
- Edge Detector
- LED
- ring counter
- Algorithm
- Linked List
- DHT11
- Today
- Total
거북이처럼 천천히
Structure 본문
서론
이번 구현 실습에는 버튼 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 전압이 들어온다.
- 따라서 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 상태를 반환함으로서 해당 상태에 대해서 반응하지 않도록 한다.
▶ 나머지 경우는 다음과 같다.
▶ 현재 상태 : 버튼이 눌린 상태, 과거 상태 : 버튼이 눌린 상태
▶ 현재 상태 : 버튼이 누르지 않은 상태, 과거 상태 : 버튼이 누르지 않은 상태
결론
이번 구현은 구조체 개념과 포인트 개념, 회로 개념이 종합적으로 들어갔기 때문에 중요한 구현이였다. 이해하기 어렵거나 스스로 다시 구현하는 것이 힘들 수 도 있지만, 그런 만큼 배우는 것과 실력이 상승할 수 있어 다시 한 번 천천히 생각하고, 스스로 구현해보도록 연습해보자.
'Embedded Programming (AVR) > Atmega 128A (실습)' 카테고리의 다른 글
LCD Display (HD44780U) - (1, 8bit mode) (0) | 2024.09.11 |
---|---|
8bit Timer / counter - Phase Correct Fast PWM Mode (0) | 2024.06.19 |
다양한 파형 생성 실험 (8bit Timer/Counter) (0) | 2024.06.09 |
8bit Timer / counter - Fast PWM Mode (1) | 2024.06.04 |
8bit Timer / counter - Normal Mode (1) | 2024.06.03 |