2023. 3. 7. 14:25ㆍAVR ATmega128
1. 입출력 사용하기
ATmega128에는 64개의 핀이 있다. 그 중 59개의 사용 가능한 핀이 있으며, 각 핀들은 일반 입출력 핀으로 사용하거나, 제조사에서 미리 설정한 기능을 작동하게 만들 수 있다. 이번에는 제조사의 기능이 아닌 일반 입출력 핀을 사용하는 방법을 알아볼 것이다.
아두이노에서는 핀을 설정하는 함수가 있어 간편하게 사용할 수 있지만 AVR은 아두이노의 간편한 함수들을 사용할 수 없어 레지스터를 직접 건드려야 한다.
void setup() {
// put your setup code here, to run once:
pinMode(3, OUTPUT);
PinMode(4, INPUT);
}
위의 아두이노 함수를 AVR로 바꿔보겠다. 참고로 아두이노 우노, 나노 등 AVR 8비트 MCU를 사용한다면 아래의 방법으로 직접 레지스터를 설정할 수 있다. 대신 약간의 기능 차이나 핀, 레지스터 이름이 다를 수 있다. 설정하는 방법은 같으니 아두이노 우노로 AVR을 공부하고 싶은 사람들은 Atmega328P 데이터시트를 참고하면 된다.
DDRx는 포트 방향을 설정할 수 있는 레지스터이다. DDRx 레지스터의 각 비트들을 1로 설정하게되면 출력으로, 0으로 설정하게 되면 입력으로 동작한다.
포트 A의 모든 핀을 출력으로 설정하는 방법은 다음과 같다.
DDRA = 0b11111111;
//또는
DDRA = 0xff
하지만 위의 방식으로 레지스터를 설정하면 나중에 다시 코드를 수정하거나 코드를 읽을 때 가독성이 떨어진다는 단점이 있고, 특정 핀들만 set하려면 계산이 필요하다는 단점이 있다. 왼쪽 쉬프트 연산을 하면 조금더 가독성이 좋은 코드를 만들 수 있다. 데이터시트의 레지스터 항목을 보면 각 비트마다 이름이 있는것을 볼 수 있는데, 이 이름들은 <avr/io.h>에 이미 정의되어 있다.
핀 이름과 쉬프트 연산을 통해 특정 비트만 set하거나 reset하는 방법은 다음과 같다.
DDRA |= 1<<DDA1; //set
DDRA &= ~(1<<DDA2); //reset
위의 연산은 특정 비트를 1로 만드는 연산이다. =앞에 비트 논리 연산자(|, &)가 붙은 이유는 특정 핀을 제외한 다른 핀들을 간섭하지 않기 위해서이다. 위 코드를 사용하면 1번핀은 1, 2번핀은 0이 된다.
다음은 출력을 담당하는 레지스터이다.
출력하고 싶은 핀을 1로 설정하면 5V가 나가고, 0으로 설정하면 0V가 나간다.
PORTA |= 1<<PORTA1;
_delay_ms(1000);
PORTA &= 1<<PORTA1;
_delay_ms(1000);
아두이노 코드로 작성하면 다음과 같다
digitalWrite(1, HIGH);
delay(1000);
digitalWrite(1, LOW);
delay(1000);
DDRx를 0으로 설정해 핀 모드가 입력상태 일때 PORTx 레지스터를 1로 설정하면 MCU내부의 풀업 저항을 사용한다.
DDRA &= ~(1<<DDA2); //포트 A의 2번핀을 입력으로 설정
PORTA |= 1<<PORTA2 //포트 A의 2번핀의 내부 풀업저항 사용
아두이노 코드로 바꾸면 다음과 같다.
pinMode(2, INPUT_PULLUP);
다음은 입력모드에서 입력값을 저장하는 레지스터이다.
버튼 등으로 입력을 받을 때 입력된 정보가 저장되는 레지스터이다. PINx레지스터의 2번핀이 HIGH 상태면 해당 포트의 2번 핀에 HIGH상태의 입력이 들어온다는 뜻이다. 2번핀에 버튼이 풀업 방식으로 연결되었고, 1번핀에 LED가 연결됐다고 가정해 보자. 버튼을 누르면 PINA레지스터에 0이 입력되고, 버튼에서 손을 떼면 1이 입력될 것이다.
버튼이 눌렸을 때 LED를 켜려면
if(!(PINA & (1<<PINA2))
{
PORTA ^= 1<<PORTA1;
}
이런 식으로 코드를 작성하면 된다. ^는 비트 XOR연산자이다.
2. GPIO 설정하기
이제 레지스터를 직접 사용해서 입출력을 하는 방법을 알아보았다. 하지만 아두이노의 함수를 사용할 때보다 조금 불편하다. 그래서 GPIO를 쉽게 사용할 수 있는 함수를 만들어 사용할 것이다.
void Gpio_initPort(volatile uint8_t *DDR, uint8_t dir)
{
if (dir == OUTPUT) {
*DDR = 0xff;
}
else {
*DDR = 0x00;
}
}
하나의 포트 전체를 입력 또는 출력으로 설정해주는 함수이다. DDRx 함수의 주소값을 받아 해당 레지스터를 직접 설정한다. 다음과 같이 사용한다.
Gpio_initPort(&DDRA, INPUT);
다음은 각각의 핀을 설정하는 함수이다. 아두이노의 pinMode와 같은 역할을 한다.
void Gpio_initPin(volatile uint8_t *DDR, uint8_t pinNum, uint8_t dir)
{
if (dir == OUTPUT) {
*DDR |= (1 << pinNum); // OUTPUT mode
}
else {
*DDR &= ~(1 << pinNum); // INPUT mode
}
}
다음과 같이 사용한다
Gpio_initPin(&DDRA, 1, OUTPUT)
다음은 포트 전체를 입, 출력 해주는 함수들이다.
void Gpio_writePort(volatile uint8_t *PORT, uint8_t data)
{
*PORT = data;
}
uint8_t Gpio_readPort(volatile uint8_t *PIN)
{
return *PIN;
}
Gpio_writePort(&PORTA, HIGH);
Gpio_readPort(&PINA);
다음은 각각의 핀을 입, 출력하는 함수들이다.
void Gpio_writePin(volatile uint8_t *PORT, uint8_t pinNum, uint8_t state)
{
if (state == GPIO_PIN_SET) {
*PORT |= (1 << pinNum); // GPIO_PIN_SET
}
else {
*PORT &= ~(1 << pinNum); // GPIO_PIN_RESET
}
}
uint8_t Gpio_readPin(volatile uint8_t *PIN, uint8_t pinNum)
{
return ((*PIN & (1 << pinNum)) != 0); // xxx1xxxx & 00010000 => 16
}
이 함수들은 digitalWrite, digitalRead 함수와 같은 기능을 한다.
Gpio_writePin(&PORTA, 3, HIGH);
a = Gpio_readPin(&PINA, 2);
이제 버튼이나 LED같은 모듈이나 센서를 사용할 때 이 함수들을 사용하면 코드 관리를 더 수월하게 할 수 있다.
/*
* Gpio.h
*
* Created: 2023-02-21 오전 10:15:35
* Author: rhoblack
*/
#ifndef GPIO_H_
#define GPIO_H_
#include <avr/io.h>
enum {INPUT, OUTPUT};
enum {LOW, HIGH};
void Gpio_initPort(volatile uint8_t *DDR, uint8_t dir);
void Gpio_initPin(volatile uint8_t *DDR, uint8_t pinNum, uint8_t dir);
void Gpio_writePort(volatile uint8_t *PORT, uint8_t data);
uint8_t Gpio_readPort(volatile uint8_t *PIN);
void Gpio_writePin(volatile uint8_t *PORT, uint8_t pinNum, uint8_t state);
uint8_t Gpio_readPin(volatile uint8_t *PIN, uint8_t pinNum);
#endif /* GPIO_H_ */
/*
* Gpio.c
*
* Created: 2023-02-21 오전 10:15:25
* Author: rhoblack
*/
#include "Gpio.h"
void Gpio_initPort(volatile uint8_t *DDR, uint8_t dir)
{
if (dir == OUTPUT) {
*DDR = 0xff;
}
else {
*DDR = 0x00;
}
}
void Gpio_initPin(volatile uint8_t *DDR, uint8_t pinNum, uint8_t dir)
{
if (dir == OUTPUT) {
*DDR |= (1 << pinNum); // OUTPUT mode
}
else {
*DDR &= ~(1 << pinNum); // INPUT mode
}
}
void Gpio_writePort(volatile uint8_t *PORT, uint8_t data)
{
*PORT = data;
}
uint8_t Gpio_readPort(volatile uint8_t *PIN)
{
return *PIN;
}
void Gpio_writePin(volatile uint8_t *PORT, uint8_t pinNum, uint8_t state)
{
if (state == GPIO_PIN_SET) {
*PORT |= (1 << pinNum); // GPIO_PIN_SET
}
else {
*PORT &= ~(1 << pinNum); // GPIO_PIN_RESET
}
}
uint8_t Gpio_readPin(volatile uint8_t *PIN, uint8_t pinNum)
{
return ((*PIN & (1 << pinNum)) != 0); // xxx1xxxx & 00010000 => 16
}
'AVR ATmega128' 카테고리의 다른 글
UART 활용 (0) | 2023.03.06 |
---|---|
UART (0) | 2023.03.06 |
I2C(TWI) LCD (1) | 2023.03.03 |
초음파센서 드라이브 만들기 (0) | 2023.03.03 |
AVR초음파 센서 (0) | 2023.03.03 |