거북이처럼 천천히

Data Direction Register의 비트 단위로 Control 본문

Embedded Programming/Atmega 128A (실습)

Data Direction Register의 비트 단위로 Control

유로 청년 2024. 5. 26. 19:05

1. 환경 : Microchip Studio

2. 목표 :

  • DDR 전체를 출력/입력으로 설정하는 것이 아닌 Bit 단위로 설정하여 효율적으로 LED를 출력하도록 설계
  • 구조체(Structure)를 이용하여 구현함으로서 구조체의 기본 지식 정비 및 기본 활용 공부

3. 큰 그림: 

  • 이전까지는 DDRD = 0xff로 선언하여 PORTD의 8핀 전체를 출력으로 사용하거나
    DDRF = 0x00으로 선언하여 PORTF의 8핀 전체를 입력으로 사용했다.
  • 하지만, 이는 비효율적으로 하드웨어를 제어 하는 것이기 때문에 bit 단위로 입력/출력을 설정함으로서
    효율적으로 하드웨어를 제어하도록 설계하겠다.
  • ex) DDRD = 0x18
    → PORTD에서 3번째, 4번째만 출력으로 사용하겠다는 의미
  • 구조체를 정의하여 구조체 내에 1) PORT의 메모리 주소, 2) 출력하고자 하는 핀 넘버를 갖고 있다.

4. Source code 
    ● 구현 목표 : 0핀 -> 7핀 -> 0핀으로 Shift 연산자를 사용하여 출력

     비록 같은 출력이지만, 약간씩 소스를 변형하여 구현하도록 하겠다.


 

4.1) DDR(Data Direction Register) 제어를 통해 특정 bit에서만 출력

#include "personal.h"

int main(void)
{
   uint8_t data = PORTD1;
   
    while (1) 
    {
      for(uint8_t i=0; i<8; i++) {
         init_DDR(i);
         left_shift(&data);
      }   
      
      for(uint8_t i=6; i>0; i--) {
         init_DDR(i);
         right_shift(&data);
      }
   }
}

 

#include "personal.h"

// Initialization Data direction register.
void init_DDR(uint8_t pin_number) {
   // DDRD 레지스터를 7번 shift하여 초기화 한뒤,
   // pin_number 핀만 출력으로 설정
   LED_DDR = (LED_DDR<<8) | (DDD1<<pin_number);
}

// Left Shift
void left_shift(uint8_t *data) {
   // PORTD의 8핀에 High-level signal을 주었음에도
   // DDRD는 특정 bit에서만 출력이 되도록 설정했기 때문에
   // 특정 bit에서만 출력이 발생한다.
   *data = 0xff;
   GPIO_Output(*data);
}

// Right Shift 
void right_shift(uint8_t *data) {
   *data = 0xff;
   GPIO_Output(*data);
}

// GPIO output
void GPIO_Output(uint8_t data) {
   LED_PORT = data;
   _delay_ms(TIME);
}
#ifndef PERSONAL_H_
#define PERSONAL_H_

// Include common.h header file.
#include "common.h"

// Substitute constant
#define LED_DDR DDRD
#define LED_PORT PORTD
#define TIME 200

// Function prototype
void init_DDR(uint8_t pin_number);
void left_shift(uint8_t *data);
void GPIO_Output(uint8_t data);
void right_shift(uint8_t *data);

#endif /* PERSONAL_H_ */
#ifndef COMMON_H_
#define COMMON_H_

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

#endif /* COMMON_H_ */

 

※ 하지만, 여기서 PORTD = 0xff으로 작성할 필요가 있는가?

    어차피 출력되는 핀은 하나에 불과한데? 그래서 PORTD 의 값을 아래와 같이 수정할 수 있다.

 

#include "personal.h"

// Initialization Data direction register.
void init_DDR(uint8_t pin_number) {
   // DDRD 레지스터를 7번 shift하여 초기화 한뒤,
   // pin_number 핀만 출력으로 설정
   LED_DDR = (LED_DDR<<8) | (DDD1<<pin_number);
}

// Left Shift
void left_shift(uint8_t *data, uint8_t pin_number) {
   // 
   *data = (*data<<8) | (PORTD1<<pin_number);
   GPIO_Output(*data);
}

// Right Shift 
void right_shift(uint8_t *data, uint8_t pin_number) {
   *data = PORTD1<<pin_number;
   GPIO_Output(*data);
}

// GPIO output
void GPIO_Output(uint8_t data) {
   LED_PORT = data;
   _delay_ms(TIME);
}

 

※물론 위와 같이 수정하면 그에 따른 함수 원형 선형, main.c 파일 수정도 이루어져야 한다.

 


4.2) 구조체(Structure)의 개념을 도입하여 구초제의 맴버로 1) PORT의 레지스터 주소, 2) 핀 넘버를 삼고, LED를 제어

#include "personal.h"

int main(void) {
   LED led;
   led.port = &PORTD;
   
   while(1) {
      for(uint8_t i=0; i<8; i++) {
         led.pinNumber = i;
         
         init_DDR(&led);
         left_shift(&led);
      }
      
      for(uint8_t i=6; i>0; i--) {
         led.pinNumber = i;
         
         init_DDR(&led);
         right_shift(&led);
      }
   }
}
#include "personal.h"

// Initialization DDR(Data Direction Register)
void init_DDR(LED *led) {
   // Data Sheet에서 Register Summary 파트를 보게 된다면
   // 각각의 PORT들은 각각 3개의 Register를 갖는다.
   // 1) PORT(PORT의 출력값을 담는 레지스터)
   // 2) DDR (Data Direction 레지스터)
   // 3) PIN
   // 각각의 레지스터들은 위 순서대로 연속적으로 존재하며, 이 때문에 각각의 레지스터의 주소값의 차이 1씩난다.
   // 자세한 내용은 Atmega128a 461 ~ 463참고
   *(led->port - 1) = (*(led->port -1)<<8) | (1<<led->pinNumber);
   
   // 위 내용은 *(led->port-1)의 의미는 다음과 같다.
   // - 특정 PORT의 레지스터 주소 - 1 = 특정 PORT의 DDR
   // - *(led->port - 1)은 특정 PORT의 "DDR의 메모리 주소를 통해 DDR의 값을 수정하겠다."는 의미
   // - (1<<led->pin_number)은 "Shift 연산자를 통해 특정 bit만 출력으로 사용하겠다"는 의미
}


// Left shift
void left_shift(LED *led) {
   // *(led->port) == PORTD
   // (1<<led->pinNumber) == 1<<pinNumber
   *(led->port) = (1<<led->pinNumber);
   _delay_ms(TIME);
}

void right_shift(LED *led) {
   *(led->port) = PORTD1<<led->pinNumber;
   _delay_ms(TIME);
}
#ifndef PERSONAL_H_
#define PERSONAL_H_

// Include "common.h" header file.
#include "common.h"

// Structure
typedef struct{
   volatile uint8_t *port;         // LED가 연결된 포트.
   uint8_t pinNumber;      // LED가 연결된 핀 번호.
}LED;

// Substitute constant.
#define LED_DDR DDRD
#define LED_PORT PORTD
#define TIME 200

// Function prototype.
void init_DDR(LED *led);
void left_shift(LED *led);
void right_shift(LED *led);

#endif /* PERSONAL_H_ */
#ifndef COMMON_H_
#define COMMON_H_

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

#endif /* COMMON_H_ */

 

※ 아래는 보는 것과 같이 Atmega 128A의 Data sheet을 보면 각 포트는 3개의 레지스터를 가진다.

    1) PORT(PORT의 각 핀의 출력을 제어하는 레지스터)

    2) DDR (Data Direction Register, PORT의 각 핀을 입력/출력으로 사용할지 여부를 결정하는 레지스터)

    2) PIN 

 

자세히 보면 PORT, DDR, PIN 순서대로 각 포트들은 3개의 레지스터를 갖는다.

 

그래서 위 코드에서 &PORT - 1 = &DDR or &DDR - 1 = &PIN 형식으로 접근이 가능하다.  

LED 구조체는 총 2개의 맴버를 갖는다.

   - volatile uint8_t * port (PORT 레지스터의 주소)

   -               uint8_t pin_number (특정 pin_number)

volatile 은 "휘발성 있는" 뜻으로 해당 키워드를 작성한 변수에 대해서 최적화 작업을 하지 않고,
    항상 메모리에 접근한다.  

    -> 자세한 내용은 해당 홈페이지에서 참고.https://luna-archive.tistory.com/2

※ PORT의 레지스터 주소를 통해 1) PORT의 출력 뿐만 아니라 2) PORT의 레지스터 주소 - 1을 하여 DDR에 접근하여 
    PORT의 8핀의 입출력을 제어 할 수 있다.


5. 구현 영상

 

 


6. 마무리

 

- 구조체를 사용한 거 뿐만 아니라 각 PORT가 갖고 있는 레지스터의 주소를 이용하여 

   1) PORT의 출력 값 제어, 2) PORT의 레지스터 주소 - 1 을 통해 얻은 DDR 레지스터로 8핀의 입출력 제어 하였다.

- 조금 복잡하고, 어렵다고 생각할 수 있지만, 계속 봐보고 시간이 있으면 구현 해봄으로서 익숙해지자.

'Embedded Programming > Atmega 128A (실습)' 카테고리의 다른 글

GPIO - LED control (3)  (0) 2024.05.29
Atmega 128A의 버튼 (기초)  (0) 2024.05.26
궁금증 정리 노트 - Atmega128A  (0) 2024.05.25
Pointer  (0) 2024.05.25
Separate Compilation  (0) 2024.05.25