2010년 6월 12일 토요일

아뒤노에서 포트를 직접 억세스 (Direct Port Manipulation in Arduino)



아뒤노에서도 포트 레지스터를 사용하면 마이크로 컨트롤러의 I/O핀을 로우레벨에서 고속으로 조작할 수 있다. 아뒤노 보드(ATmega8과 ATmega168)에 사용된 칩은 3개의 포트를 가지고 있다.

  • B (digital pin 8~13)
  • C (analog input pins)
  • D (digital pin 0~7)

각 포트는 3개의 레지스터로 제어되고 레지스터들은 아뒤노에 변수로 정의되어 있다. DDR 레지스터는 각 핀이 INPUT 또는 OUTPUT인지를 결정한다. PORT레지스터는 핀의 출력값을 HIGH또는 LOW로 제어한다. PIN레지스터는 INPUT모드인 핀의 상태값을 읽는다.

DDR과 PORT레지스터는 읽고 쓰기가 가능하다. PIN레지스터는 입력 상태에 대응하며 읽기 전용이다.



PORTD는 아뒤노의 digital pin 0~7에 매핑된다.
  •   DDRD - 포트 D 데이터 방향 레지스터 (R/W)
  •   PORTD - 포트 D 데이터 레지스터 (R/W)
  •   PIND - 포트 D 입력 핀 레지스터 (R)

PORTB는 아뒤노의 digital pin 8~13에 매핑된다. 상위 2 비트는 crystal핀에 매핑되고 사용할 수 없다.
  •   DDRB - 포트 B 데이터 방향 레지스터 (R/W)
  •   PORTB - 포트 B 데이터 레지스터 (R/W)
  •   PINB - 포트 B 입력 핀 레지스터 (R)

PORTD는 아뒤노의 analog pin 0~5에 매핑된다. 핀 6, 7은 아뒤노 미니에서만 억세스할 수 있다.
  •   DDRC - 포트 C 데이터 방향 레지스터 (R/W)
  •   PORTC - 포트 C 데이터 레지스터 (R/W)
  •   PINC - 포트 C 입력 핀 레지스터 (R)
 
이 레지스터들의 각 비트는 핀 하나에 대응한다. 아뒤노 핀 번호와 포트/비트간의 전체 매핑은 칩의 다이어그램을 보면 된다. (포트의 일부 비트는 I/O이외의 용도로 사용될 수도 있기 때문에 그에 해당하는 레지스터 비트 값을 바꾸지 않도록 주의해야 한다.)

예제

위의 핀 맵을 참조하면 PortD 레지스터는 아뒤노 디지털 핀 0~7을 제어한다.

하지만 핀 0과 1은 아뒤노 프로그래밍과 디버깅을 위한 시리얼 통신에 사용되기 때문에 시리얼 입/출력 함수를 위해 필요하지 않는 이상 이 핀을 바꾸는건 피하는게 좋다. 이 값을 잘못 바꾸면 프로그램 다운로드나 디버깅을 방해받을 수 있다.

DDRD는 포트 D의 방향 레지스터이다. 레지스터의 각 비트는 포트 D의 핀이 INPUT 또는 OUTPUT으로 설정되도록 제어한다.

DDRD = B11111110;  // 아뒤노 핀 1~7은 출력으로, 핀 0는 입력으로 설정
DDRD = DDRD | B11111100;  // 더 안전한 방법. Rx/Tx로 사용되는 핀0과 1의 값은 변경하지 않고 핀 2~7을 출력으로 설정

PORTD는 출력 상태를 위한 레지스터이다.

PORTD = B10101000;  // 디지털 핀 7,5,3을 HIGH로 만듬

DDRD레지스터 또는 pinMode()를 사용해 저 핀들을 OUTPUT으로 설정해 놓은 경우 핀에서 5V가 출력되는걸 볼 수 있다.

PIND는 입력 레지스터 변수로 모든 디지털 입력 핀의 값을 동시에 읽는다.

포트 조작 팁

#define B0 B00000001
#define B1 B00000010
#define B2 B00000100
#define B3 B00001000
#define B4 B00010000
#define B5 B00100000
#define B6 B01000000
#define B7 B10000000

이렇게 비트를 정의해 놓으면 여러 핀을 동시에 H로 만들 경우는

PORTD |= B7 | B5 | B4;  // 디지털 핀 7, 5, 4를 HIGH로

여러 핀을 동시에 L로 만들 경우는

PORTD &= ~(B7 | B5 | B4);  // 디지털 핀 7, 5, 4를 LOW로

여러 핀의 값을 토글(현재값이 H면 L로, 현재값이 L이면 H로) 시키는 경우는

PORTD ^= B7 | B5 | B4;  // 디지털 핀 7, 5, 4의 값을 토글

Port manipulation을 사용하는 이유

일반적으로 이런식으로 포트를 직접 조작하는건 별로 좋은 생각이 아니다. 그 이유는

  • 코드 디버깅 및 관리하기가 더 힘들어지고 다른 사람이 이해하기 힘들다. 마이크로 프로세서가 코드를 실행하는건 몇 마이크로초밖에 안 걸리지만 그 코드가 왜 동작하지 않는가 찾아내고 수정하는데는 몇시간이 걸린다. 시간이 중요하지 않은가? 컴퓨터의 시간은 사용전력으로 따져보면 매우 싸다. 가능한 가장 확실한 방법으로 코드를 작성하는게 좋다.
  • 코드의 이식성이 떨어진다. digitalRead()와 digitalWrite()를 사용하면 모든 Atmel 마이크로 컨트롤러에서 실행시키는게 훨씬 쉽지만 컨트롤과 포트 레지스터는 각 마이크로컨트롤러마다 다를 수 있어 코드를 바로 사용하기 힘들수 있다.
  • 직접 포트 레지스터를 억세스하다 보면 의도하지 않은 문제를 만들기 쉬워진다. 위의 예제에서 DDRD = B11111110; 에서처럼 핀 0은 입력으로 남겨둬야만 한다. 핀 0는 시리얼 포트의 Rx이다. 하지만 실수로 핀 0을 OUTPUT으로 만들어버리면 갑자기 시리얼포트가 동작하지 않게 만들기 쉽다. 갑자기 시리얼 포트에서 데이터를 받을 수 없으면 매우 혼란스러울 것이다.

그럼에도 불구하고 왜 포트 레지스터를 직접 조작해야 하는가? 포트 레지스터를 직접 억세스하면 다음의 장점이 있다.

  • 핀의 출력을 H/L로 매우 빠르게(몇 나노초 이내에) 바꾸는게 필요한 경우가 있다. lib/targets/arduino/wiring.c 소스코드를 보면 digitalRead()와 digitalWrite()는 각각 몇십줄의 코드로 된 걸 볼 수 있는데 이는 컴파일되면 상당한 양의 기계어 명령어가 된다. 각 기계어 명령어는 16MHz 클럭사이클에서 한 클럭사이클이 필요한데 시간에 민감한 어플리케이션에서는 상당한 부담이 된다. 그에 비해 포트를 직접 억세스하면 훨씬 적은 클럭 사이클로 동일한 작업을 할 수 있다.
  • 때로는 여러개의 출력핀의 값을 정확히 동시에 바꿔줘야 할 필요가 있다. digitalWrite(10, HIGH); 다음에 digitalWrite(11, HIGH)를 호출하면 먼저 핀 10이 HIGH로 바뀌고 몇 마이크로초 후에 핀 11이 HIGH로 바뀌게 된다. 때로는 외부의 시간에 민감한 디지털 회로의 동작에 혼란을 줄 수 있다. 이 경우 PORTB |= B1100; 을 사용하면 동시에 두 핀을 HIGH로 바꿔줄 수 있다.
  • 프로그램 메모리가 부족한 경우 이 방식을 사용하면 코드 크기를 작게 줄일 수 있다. 루프를 사용해 각 핀을 독립적으로 조작하는것에 비해 포트 레지스터를 통해 동시에 여러 하드웨어 핀에 값을 쓰는게 훨씬 작은 명령어를 사용하게 된다. 때로는 이 방식을 사용여부가 프로그램이 플래쉬 메모리에 들어갈 수 있는가 못들어가는가를 결정하는 차이가 될 수도 있다.



댓글 없음:

댓글 쓰기