일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |
- hc-sr04
- pwm
- prescaling
- Recursion
- test bench
- DHT11
- KEYPAD
- LED
- uart 통신
- i2c 통신
- BASYS3
- soc 설계
- vivado
- ATMEGA128A
- gpio
- behavioral modeling
- Algorithm
- atmega 128a
- ring counter
- verilog
- Edge Detector
- structural modeling
- half adder
- stop watch
- java
- Pspice
- FND
- dataflow modeling
- D Flip Flop
- Linked List
- Today
- Total
거북이처럼 천천히
GPIO - LED control (3) 본문
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)
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) 구현 영상
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) 구현 영상
5) 마무리
- 구조체를 이용 없이 단순히 구현하는 작업은 쉬울 수 있지만, 구조체의 개념 및 활용을 적용하여 회로를
구성하게 된다면 어려울 수 있다. 따라서 코드를 작성하기 전에 "구조체는 어떤 맴버를 가질 것인가?",
"어떻게 구조체 초기화 할것인가?" 등을 먼저 생각하여 큰 그림을 그린 뒤, 구현하는 연습과 반복 연습을
통해 익숙해져야 할 것 같다.
'Embedded Programming (AVR) > Atmega 128A (실습)' 카테고리의 다른 글
FND (Flexible Numeric Display) - 기초 (1) (0) | 2024.05.30 |
---|---|
Interrupt - 기초 (1) (0) | 2024.05.29 |
Atmega 128A의 버튼 (기초) (0) | 2024.05.26 |
Data Direction Register의 비트 단위로 Control (0) | 2024.05.26 |
궁금증 정리 노트 - Atmega128A (0) | 2024.05.25 |