레이블이 아뒤노인 게시물을 표시합니다. 모든 게시물 표시
레이블이 아뒤노인 게시물을 표시합니다. 모든 게시물 표시

2015년 8월 10일 월요일

아두이노에서 여러개의 스위치를 1개의 analog input핀으로 검사하기

아두이노를 사용하다 보면 Digital I/O핀이 모자라게 되는 경우가 있다. 이럴 때 1개의 analog input 핀으로 여러개의 스위치 입력을 받을 수 있다.
이 방식을 이해하려면 가장 먼저 이해해야 하는 것이 voltage divider 회로이다. 




Vin(여기서는 +5V)과 GND 사이에 두개의 저항이 있으면 두 저항 사이의 전압은 두 저항값의 비율로 결정되게 된다.



R1=1K, R2=1K인 경우




R1=1K, R2=4K인 경우


R1=4K, R2=1K인 경우


그러므로 이 성질을 이용해 여러개의 스위치를 연결하는데 각각의 스위치에 저항값이 다른 저항을 붙여주면 각 스위치를 누를 때 마다 출력되는 전압값이 달라지게 되므로 그 전압값을 읽어보면 어떤 스위치가 눌렸는지 판단이 가능해진다.



만일 S1이 눌리면 R1=82K, R2=100K가 되고, S5가 눌리면 R1=10K, R2=100K가 된다. 즉 각각의 스위치가 눌린 경우 출력전압과 그에 해당하는 ADC 값을 계산하면 다음과 같다. (아래 표는 Vin이 3.3V일 때의 계산결과임. 하지만 Vin이 5V이건 3.3V이건 ADC value는 변하지 않음)




그러므로 analogRead()로 읽은 값이 대략 910~950 범위 내의 값이면 S5가 눌렸다고 판단하고, 820~860 범위 내의 값이면 S4가 눌렸다고 판단하면 된다. 

또한 각각의 저항값을 잘 결정해주면 동시에 2개의 버튼이 눌렸을 때도 어떤 버튼이 눌렸는지 판단할 수 있다.

아두이노에서 RTOS 사용하기 (FreeRTOS in Arduino)

아두이노에 포팅된 FreeRTOS를 사용하면 아두이노에서도 RTOS를 사용해 멀티 쓰레드로 프로그램을 만들 수 있다. FreeRTOS를 설치하려면 먼저 브라우져에서 아래 주소로 이동한다.




Download ZIP을 클릭해 zip 파일을 다운받는다. 다운받은 파일의 압축을 풀면 디렉토리 내에 아래와 같은 파일들이 만들어진다.



이 중 libraries 디렉토리 내에 아래와 같은 3개의 서브디렉토리가 들어있다.



이 3개의 서브디렉토리를 자신의 아두이노 라이브러리 디렉토리에 복사 해 주면 된다. (아두이노에 외부 라이브러리 설치하는 법 참조)
아두이노 DUE를 사용하면 FreeRTOS_ARM을 사용하고 아두이노 UNO, MEGA등 AVR 프로세서를 사용한 아두이노의 경우 FreeRTOS_AVR을 사용하면 된다. 

라이브러리 설치가 끝나면 아두이노를 재시동하고 예제 -> FreeRTOS_AVR -> frBlink 파일을 선택해 보드에 업로드 해서 LED가 깜빡이면 FreeRTOS가 정상적으로 동작하고 있는 것이다. 

#include <FreeRTOS_AVR.h>

const uint8_t LED_PIN = 13;
SemaphoreHandle_t sem;

static void Thread1(void* arg) {
  while (1) {
    xSemaphoreTake(sem, portMAX_DELAY);
    digitalWrite(LED_PIN, LOW);
  }
}

static void Thread2(void* arg) {
  pinMode(LED_PIN, OUTPUT);
  while (1) {
    digitalWrite(LED_PIN, HIGH);
    vTaskDelay((200L * configTICK_RATE_HZ) / 1000L);
    xSemaphoreGive(sem);
    vTaskDelay((200L * configTICK_RATE_HZ) / 1000L);
  }
}

void setup() {
  portBASE_TYPE s1, s2;

  sem = xSemaphoreCreateCounting(1, 0);  // 세마포어를 생성
  s1 = xTaskCreate(Thread1, NULL, configMINIMAL_STACK_SIZE, NULL, 2, NULL); // Thread1 생성
  s2 = xTaskCreate(Thread2, NULL, configMINIMAL_STACK_SIZE, NULL, 1, NULL); // Thread2 생성
  vTaskStartScheduler();  // 태스크 스케쥴러 시작
  while(1) {}
}

void loop() {}

코드에서는 두개의 쓰레드 (Thread1, Thread2)가 동시에 실행되어 Thread2에서 LED를 켜고 200ms 후에 semaphore를 POST 해주어 Thread1이 semaphore를 받으면 LED를 끄게 되어 있다. Thread1은 LED를 끄고 나면 곧바로 다시 semaphore를 기다리지만 Thread2에서 200ms를 기다린 후 LED를 켜고 다시 200ms 후에 semaphore를 POST 하기 전에는 계속 semaphore를 기다리게 된다. 



그리고 일반 스케치와 다르게 loop() 에서는 아무 동작을 하지 않는다.

아두이노에서의 delay() 함수

아두이노를 처음 시작하면 누구나 한번은 보게 되는 가장 쉬운 프로그램(아두이노의 ‘hello world’)는 아마도 아래 코드일 것이다. 

void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
  digitalWrite(13, HIGH);   // LED ON
  delay(1000);              // 1초 대기
  digitalWrite(13, LOW);    // LED OFF
  delay(1000);              // 1초 대기
}

예제 -> 01. Basics -> Blink 코드로 아두이노 보드에 붙어있는 LED를 깜빡이게 하는 코드이다.



위의 사진에 ‘L’자 옆의 LED가 digital I/O 13번 핀에 연결되어 있는 것으로 2초 간격으로 깜빡이게 된다.
코드는 매우 직관적이어서 곧바로 이해할 수 있고 아두이노에서 프로그램을 만들면 delay() 함수를 많이 사용하게 된다.
하지만 위와 같이 단순하게 한가지 작업만 하는 경우는 상관없지만 일반적인 프로그램의 경우 동시에 여러가지 작업을 수행해야만 한다. 그 경우 delay() 함수가 사용되면 delay()에 지정된 시간만큼 대기하는 동안 프로그램 실행이 중단되어 다른 작업을 할 수가 없게 된다. 

그러므로 처음 코드를 이해할 때는 delay()함수가 편해도 실제 프로그램을 만들 때는 delay() 함수 사용을 최소화 해야만 한다. 예를 들어 2개의 스위치를 연결해 LED가 깜빡이는 속도를 조절하는 프로그램을 만든다고 생각해 보자.

단순하게 생각한다면 다음과 같은 식으로 코드를 만들 수 있을 것이다.

int gDelay = 1000;

void setup() {
  pinMode(13, OUTPUT);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
}

void loop() {
  if (LOW == digitalRead(7)) {
    gDelay += 100;
  }
  if (LOW == digitalRead(8)) {
    gDelay -= 100;
  }
  gDelay = constrain(gDelay, 100, 2000);
    
  digitalWrite(13, HIGH);   // LED ON
  delay(gDelay);              
  digitalWrite(13, LOW);    // LED OFF
  delay(gDelay);              
}

위의 경우 스위치를 검사해 스위치가 눌리면 각각 delay 시간을 늘리거나 줄여주고, LED를 깜빡이는 코드가 다 포함되어 있다. 하지만 실행시켜 보면 스위치를 눌러도 깜빡이는 속도가 원하는대로 바뀌지 않을 것이다. 



위의 그림에서 볼 수 있는 것 처럼 (1)과 (2)에서 스위치가 놀렸는지 검사하는데 만일 gDelay 변수 값이 1000이었다고 하면 2초에 한번씩만 스위치가 눌렸는지 검사하게 된다. 스위치를 검사하고 다음번에 다시 검사하는 2초 사이에 스위치를 눌러다 떼면 프로그램은 스위치가 눌린걸 알수가 없게 된다. 즉 delay()로 프로그램 실행이 중단되어 있는 동안에는 다른 작업을 하지 못하게 된다.



이 문제에 대한 해결책으로 millis() 함수를 사용하면 된다. 아두이노에는 전원이 들어와 스케치가 시작되면 0에서 시작되어 1/1000초 단위로 1씩 증가하는 카운터가 있다. millis() 함수를 호출하면 호출된 시점의 카운트 값(unsigned long 타입)을 알려준다.



위의 그림에서 b에서 a 값을 빼면 그 값이 a에서 b까지의 시간(ms 단위)이 된다. 

그러므로 위의 코드를 delay()를 사용하지 않도록 다음과 같이 바꿀 수 있다.

int gDelay = 1000;

void setup() {
  pinMode(13, OUTPUT);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
}

void loop() {
  static unsigned long last = 0;
  static boolean ledStat = LOW;

  if (LOW == digitalRead(7)) {
    gDelay += 100;
  }
  if (LOW == digitalRead(8)) {
    gDelay -= 100;
  }
  gDelay = constrain(gDelay, 100, 2000);

  if ((millis()-last) >= gDelay) {
    ledStat = !ledStat;
    digitalWrite(13, ledStat);
    last = millis();
  } 
}

delay() 함수를 사용한 스케치에서는 loop() 함수가 한번 실행되는데 2초(gDelay가 1000인 경우)가 걸리지만, delay()함수를 사용하지 않은 스케치는 loop() 함수가 한번 실행되는데는 매우 짧은 시간이 걸려 2초동안 loop()함수가 수만~수십만번 실행되므로 스위치 상태도 그 횟수만큼 검사가 되기 때문에 사람이 아무리 빠르게 스위치를 눌렀다 떼더라도 감지하지 못하는 경우는 없다. 하지만 LED의 상태를 토글하는 코드(위에서 빨간색 부분)는 그 중 두번만 실행되게 된다.


 
(물론 위의 코드는 스위치의 debouncing 처리가 안되어 있고 한번 눌린 동안 너무 여러번 스위치 상태를 감지하게 되기 때문에 스위치를 한번 눌렀을 때 gDelay 값이 크게 변하게 된다. 이런 문제들은 이 글의 주제와 다른 부분이라 다른 포스트에서 설명할 것이다.)

참고로 이런 문제들을 모두 해결한 코드는 아래와 같다.

#define SW1  8
#define SW2  9
#define LED  13

boolean gLedStat = HIGH;
int gDelay = 500;

void setup() {
  pinMode(SW1, INPUT_PULLUP);
  pinMode(SW2, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  static unsigned long last = 0;
  static unsigned long lastSw = 0;
  unsigned long now = millis();
  static boolean prev1 = HIGH;
  static boolean prev2 = HIGH;
  boolean cur;

  if ((now - lastSw) >= 10) {   // Check switch for every 10ms
    cur = digitalRead(SW1);
    if ((HIGH == prev1) && (LOW == cur)) {
      gDelay -= 100;
      gDelay = constrain(gDelay, 100, 2000);
      Serial.print("Delay up to ");
      Serial.println(gDelay);
      prev1 = LOW;
    } else if ((LOW == prev1) && (HIGH == cur)) {
      prev1 = HIGH;
    }
    cur = digitalRead(SW2);
    if ((HIGH == prev2) && (LOW == cur)) {
      gDelay += 100;
      gDelay = constrain(gDelay, 100, 2000);
      Serial.print("Delay down to ");
      Serial.println(gDelay);
      prev2 = LOW;
    } else if ((LOW == prev2) && (HIGH == cur)) {
      prev2 = HIGH;
    }
    lastSw = now;
  }
  if ((now - last) >= gDelay) {
    gLedStat = !gLedStat;
    digitalWrite(LED, gLedStat);
    last = now;
  }

}

2015년 8월 6일 목요일

아두이노의 pinMode()에서 INPUT과 INPUT_PULLUP의 차이

아두이노의 digital I/O핀을 사용하려면 먼저 pinMode() 함수로 각각의 핀을 입력 또는 출력 모드로 사용하겠다고 먼저 선언해 줘야 한다.
그런데 입력으로 사용하는 경우 'INPUT'과 'INPUT_PULLUP' 두가지를 선택할 수 있다.
INPUT_PULLUP의 경우 프로세서 내부에 있는 pull-up 저항을 사용하겠다는걸 알려주는 것이다.

일반적으로 아두이노에 스위치를 연결하는 경우를 생각해 보자. 보통 아래 그림과 같이 외부에 pull-up 저항을 연결해 주는것이 보통이다. 하지만 ATmega168/328의 각 I/O 핀에는 노란색 부분의 회로가 들어있다. 하지만 디폴트는 노란색 부분을 사용하지 않게 되어 있다.

pinMode(7, INPUT); 

그래서 INPUT 대신 INPUT_PULLUP을 사용하면 녹색 부분을 사용하게 되어 외부에 별도의 pull-up 저항을 붙이지 않아도 된다. (그림을 보면 알겠지만 아두이노 바깥쪽에 있던 저항이 아두이노 안쪽으로 들어간 것 뿐이다.)

pinMode(7, INPUT_PULLUP);


이렇게 하면 외부에 연결해야 하는 부품이 줄어들고 배선도 간단해진다.

아래는 INPUT_PULLUP으로 두개의 스위치를 연결해 LED 깜빡이는 속도를 조절하는 예제이다.


사진에서 볼 수 있는것 처럼 외부 저항이 없어 케이블이 훨씬 간편해졌다.

#define SW1  8
#define SW2  9
#define LED  13

boolean gLedStat = HIGH;
int gSpd = 500;

void setup() {
  pinMode(SW1, INPUT_PULLUP);
  pinMode(SW2, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  static unsigned long last = 0;
  static unsigned long lastSw = 0;
  unsigned long now = millis();
  static boolean prev1 = HIGH;
  static boolean prev2 = HIGH;
  boolean cur;

  if ((now - lastSw) >= 10) {   // Check switch for every 10ms
    cur = digitalRead(SW1);
    if ((HIGH == prev1) && (LOW == cur)) {
      gSpd -= 100;
      gSpd = constrain(gSpd, 100, 2000);
      Serial.print("Delay up to ");
      Serial.println(gSpd);
      prev1 = LOW;
    } else if ((LOW == prev1) && (HIGH == cur)) {
      prev1 = HIGH;
    }
    cur = digitalRead(SW2);
    if ((HIGH == prev2) && (LOW == cur)) {
      gSpd += 100;
      gSpd = constrain(gSpd, 100, 2000);
      Serial.print("Delay down to ");
      Serial.println(gSpd);
      prev2 = LOW;
    } else if ((LOW == prev2) && (HIGH == cur)) {
      prev2 = HIGH;
    }
    lastSw = now;
  }
  if ((now - last) >= gSpd) {
    gLedStat = !gLedStat;
    digitalWrite(LED, gLedStat);
    last = now;
  }
}




2011년 10월 24일 월요일

Mac OS X Lion(10.7)에서 USB-to-Serial 사용

맥 OS가 라이언으로 올라오면서 가장 크게 바뀐 부분이 커널이 64비트로 가면서 Snow Leopard까지 문제없이 동작하던 드라이버들이 호환이 되지 않는 것들이 생긴 것이다.

대부분의 문제는 32-bit용 드라이버라 64-bit 커널에 로드가 되지 않아서 문제가 발생한다.
Arduino 역시 보드에 FTDI 칩을 사용하고 있어 아뒤노가 인식되지 않는다.

USB-to-Serial 컨버터를 맥에 연결하면 System Information에서 디바이스가 연결된 걸 확인할 수 있다.

디바이스 드라이버가 정상적으로 로드되었으면 /dev 디렉토리에 그에 해당하는 디바이스파일이 생성된다.

만일 System Information에서 장치가 연결된 건 확인되었는데 /dev 디렉토리에 디바이스파일이 생성되지 않았다면 드라이버가 제대로 로드되지 않은 것이다.

현재 대부분의 쉽고 저렴하게 구할 수 있는 USB-to-Serial 컨버터는 FTDI의 FT232x 또는 Prolific의 PL-2303를 사용하고 있다.

이 둘 다 라이언으로 올라가면 드라이버가 로드되지 않기 때문에 디바이스를 억세스 할 수가 없다.

다행히 FTDI는 올 2월에 나온 드라이버 팩에 64비트 드라이버(http://www.ftdichip.com/Drivers/VCP/MacOSX/FTDIUSBSerialDriver_v2_2_16.dmg)를 가지고 있다. 다운 받아보면 10.3용과 10.4~10.6용 드라이버가 있는데 이 중 10.4~10.6용 64-bit 드라이버를 설치해주면 라이언에서도 아무 문제 없이 동작한다.

Prolific의 경우는 공식적으로 나온 드라이버는 없고 failberg가 만든 드라이버(https://github.com/failberg/osx-pl2303)를 설치해주면 된다. 비공식 드라이버이긴 해도 설치해 본 결과 아무 문제 없이 사용할 수 있었다.

2011년 2월 26일 토요일

탈옥하지 않은 아이폰에 시리얼포트 사용하기: HiJack (Add serial port to un-jailbroken iPhone: HiJack)

UMich에서 아주 재미있는 프로젝트가 진행되었다. HiJack이라고 아이폰의 헤드폰 단자에 꼽아 외부 주변기기와 통신을 할 수 있는 디바이스를 만든 것이다.



아이폰의 30핀 독 커넥터에도 시리얼 포트가 있기 때문에 외부 디바이스를 연결할 수 있지만 이 경우 탈옥을 하지 않은 아이폰/터치팟의 경우 애플의 인증칩이 붙어 있어야만 동작하고 관련 자료를 구하는것도 개발자 프로그램에 가입해야만 가능하기 때문에 제약이 많았다.
물론 탈옥을 하고 나면 모든 제한이 사라지고 리눅스에서 시리얼 포트 사용하는것과 동일하게 사용할 수 있게 된다.

하지만 HiJack을 사용하면 탈옥을 하지 않은 순정 아이폰/터치팟에 시리얼포트 뿐 아니고 digital I/O를 제공해주기 때문에 여러가지 외부 디바이스를 손쉽게 아이폰에 인터페이스 할 수 있다.

당연히 지금까지 이런 방식을 사용하지 않았던건 아니다.
이미 위의 사진같이 Square라는 회사에서 아이폰용 신용카드 리더를 만들어 제공하고 있는데 이 역시 기본적으로 동일한 방식을 사용해서 통신을 하는 것이다.

단 차이는 사진의 리더기는 완성된 제품인데 비해 HiJack은 모든 관련자료와 필요한 소스코드까지 완전히 공개되어 있다는 점이다.

그리고 이 회로의 훌륭한 점은 헤드폰 잭은 스테레오라는걸 이용해서 아이폰에서 HiJack으로 데이터 전송은 왼쪽 모노 채널을 통해 보내고 오른쪽 모노 채널로는 지속적으로 22KHz 톤을 보내 HiJack에서는 그 톤은 energy harvesting회로를 통해 자신에게 전원을 공급하는데 사용한다.
약 47%의 변환효율을 보여 7.4mW의 전력을 공급한다.

데이터 전송 속도는 Bell 202 FSK modulation을 사용할 때는 300bps, Manchester encoding된 디지털 신호를 직접 사용하는 경우는 8.82kbps가 된다.

시리얼 포트 뿐 아니고 MSP430의 A/D나 digital I/O신호도 사용할 수 있기 때문에 아래 사진은 HiJack에 EKG모니터를 연결한 경우이다.




ECG Extension for HiJack from Thomas Schmid on Vimeo.



Hijacking Power and Bandwidth from the Mobile Phone's Audio Interface - Integrated Prototype from Thomas Schmid on Vimeo.



Hijacking Power and Bandwidth from the Mobile Phone's Audio Interface from Thomas Schmid on Vimeo.



SoilJack - A Low-Cost Phone Soil Moisture Meter from Thomas Schmid on Vimeo.

회로와 관련 소스는 http://code.google.com/p/hijack-main/ 에서 다운받을 수 있다.

좀 더 자세한 정보와 관련 논문은 프로젝트 홈페이지인 http://eecs.umich.edu/~prabal/projects/hijack/ 를 참조하면 된다.

2010년 12월 30일 목요일

맥에서 아뒤노에 라이브러리 추가하기 (Add libraries into the arduino in the Mac OS X)

아뒤노에 외부 라이브러리를 추가하는 방법은 다음과 같다.

먼저 원하는 라이브러리를 다운받아 압축을 풀어준다.


Finder를 열어 Applications 폴더에서 Arduino.app를 찾는다.





Arduino.app 폴더 위에서 마우스 오른쪽 버튼을 클릭해 메뉴가 나오면 'Show Package Contents'를 선택한다.


 Contents -> Resources -> Java로 찾아간다.


압축을 풀어놓은 라이브러리를 libraries 폴더에 집어넣어 주면 된다.
아뒤노를 종료했다가 다시 재시작 하면 Files -> Examples 에서 추가된 라이브러리를 확인할 수 있다.

2010년 10월 14일 목요일

CdS 광센서 사용하기 (Using CdS photoresistor)


CdS는 빛의 세기에 따라 저항값이 변하는 센서이다.

밝기에 따른 저항값의 변화는 다음과 같다. 참고로 y-축(저항값)은 로그 스케일이다.

아뒤노등의 마이크로 컨트롤러에 연결할 때는 다음과 같이 회로를 구성하고 V1을 마이크로컨트롤러의 Analog input에 연결해주면 된다.




위와 같은 회로일 때 출력 전압값을 구하는 공식은 다음과 같다.

여기서는 Vcc=5V, R1=10Kohm을 사용하고 있기 때문에 그 값을 위의 식에 대입해주면 된다.



즉 10 Lux정도의 밝기라 CdS의 저항값이 10Kohm인 경우 V1은 2.5V가 되고 매우 어두워서 CdS의 저항값이 200Kohm인 경우 V1은 4.76V가 된다.

2010년 6월 13일 일요일

아뒤노에서 긴 문자열 사용하기 (How to do big strings in Arduino)


아뒤노는 문자 스트링을 매우 쉽게 저장하고 사용할 수 있게 해 주지만 이 문자열들은 변수와 동일하게 램에 저장되기 때문에 많은 양의 문자열을 저장할 수 없다. 그러므로 긴 문자열을 read-only인 프로그램 메모리에 집어넣고 사용하는 법을 설명하겠다.

많은 스케치(아뒤노 프로그램)은 긴 문자열을 가지고 있는 경우가 많다. 간단한 CLI를 만들었거나 작은 웹 페이지를 저장하고 있을수도 있다. 일반적으로 아래와 같은 식으로 문자열을 저장한다.

char hellostr[] =  "<html><head><title>hello world</title></head>
                   <body><p align=center><h1>hello world</h1></p>
                   </body></html>";

// and then sometime later

Serial.println( hellostr );

문제는 hellostr이 다른 변수와 함께 램에 저장된다는 것이다. 아뒤노의 ATmega 칩은 단지 1KB의 램을 가지고 있다. 코드가 더 복잡해져서 긴 문자열과 여러개의 라이브러리를 사용하면 이해할 수 없는 신기한 문제가 생기기 시작할 것이다. 아뒤노는 프로그램이 칩이 가지고 있는것보다 더 많은 램을 사용하기 시작해도 경고를 해 주지 못한다.

이 문제를 해결하기 위해 문자열을 PROGMEM(PROGram MEMory)에 저장할 수 있다. 즉 프로그램 코드를 저장하는 플래쉬 롬을 사용하는 것이다. PROGMEM 문자열을 사용하는건 약간의 편법을 사용해야 하지만 아래의 printProgStr()이라는 함수를 사용하면 별다른 문제 없이 쉽게 사용할 수 있다.

const char hellostr[] PROGMEM = "...";     // notice added 'const' and 'PROGMEM'

// given a PROGMEM string, use Serial.print() to send it out
void printProgStr(const prog_char str[])
{
  char c;
  if(!str) return;
  while((c = pgm_read_byte(str++)))
    Serial.print(c,BYTE);
}

// and then at some point

printProgStr( hellostr );

문자열을 Serial.print() 이외에서 사용하려면 별도 함수를 만들어 거기에 글자별로 처리하는 코드를 넣어주면 된다.




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로 바꿔줄 수 있다.
  • 프로그램 메모리가 부족한 경우 이 방식을 사용하면 코드 크기를 작게 줄일 수 있다. 루프를 사용해 각 핀을 독립적으로 조작하는것에 비해 포트 레지스터를 통해 동시에 여러 하드웨어 핀에 값을 쓰는게 훨씬 작은 명령어를 사용하게 된다. 때로는 이 방식을 사용여부가 프로그램이 플래쉬 메모리에 들어갈 수 있는가 못들어가는가를 결정하는 차이가 될 수도 있다.



2010년 1월 20일 수요일

저항타입의 센서 사용하기 (how to use resistor-type sensor)

임베디드 보드에서 저항 타입의 센서를 사용하는 방법에 대해 알아보기로 하겠다.


위의 사진은 온도센서이다. 온도의 변화에 따라 저항값이 달라진다.

센서의 값을 읽으려면 기본적으로 다음과 같은 회로를 사용하면 된다.


위의 그림에 있는 공식과 같이 두개의 저항값에 따라 출력되는 전압이 달라지게 된다.


출력 전압은 위의 예제와 같이 두 저항값의 비율에 따라 결정된다.


위의 그림과 같이 하나의 저항값(R1)은 고정시켜 놓고 다른 저항(R2) 대신 저항 타입의 센서를 연결해 주면 온도의 변화에 따라 저항값이 변하고 결국 출력 전압이 따라 변하게 된다. 이 전압을 A/D 컨버터를 통해 읽어주면 된다.




센서의 온도의 변화에 따른 저항값의 변화표이다. 대략 -20도에서 125도 까지의 온도를 측정하고 싶다면 센서의 저항값은 100Kohm에서 0.33Kohm까지 변하게 된다.
이 센서를 위의 회로에 사용하려면 R1값을 결정해 줘야 한다.


R1의 값을 10 Kohm으로 했을 때 온도의 변화에 따른 출력전압의 변화를 계산한 것이다.


R1의 값을 1 Kohm으로 했을 때 온도의 변화에 따른 출력전압의 변화를 계산한 것이다.


동일한 정도의 온도변화에 대해 전압의 변화가 크고 리니어해야 온도를 읽는데 오차가 작아지고 정확하게 읽을 수 있다.
그러므로 위의 두개의 그래프에서 보듯이 주 사용환경이 -10~70도 사이라면 R1 저항으로 10 Kohm을 사용하는게 좋고 사용환경이 10~120도 사이라면 R1 저항으로 1 Kohm을 사용하는게 좋다.

오차를 최소화해서 정확하게 온도를 읽으려면 전압-온도 형태의 lookup table을 사용하면 되고 그 정도까지 정확할 필요가 없어 간단하게 하려면 저 구간에서는 온도-전압의 변화가 거의 리니어하기 때문에 간단한 수식으로 계산해도 된다.

R1이 1 Kohm인 경우 25도인 경우 전압이 4.545V, 125도인 경우 전압이 1.24V니까 직선의 기울기는 (1.24-4.545)/(125-25) = -0.033이 된다. 이 경우 Vo = -0.033T+5.365 라는 공식이 나오기 때문에 T = (5.365-Vo) / 0.033 이 된다. 즉 A/D컨버터의 전압값을 읽어 저 공식에 집어 넣으면 온도를 구할 수 있게 된다.

* 물론 좀 더 정확하게 하기 위해서는 calibration 과정을 거쳐줘야만 한다.

이 포스팅에서는 온도센서를 예로 들었지만 그 이외에도 환경의 변화에 따라 저항값이 변하는 타입의 센서(CDS photocell 등)는 모두 동일한 방식으로 사용하면 된다.

2009년 8월 22일 토요일

초 저가 SD 메모리 커넥터 제작 (DIY inexpensive SD card connector)

Instructable 사이트에 쉽게 만들 수 있는 SD 카드 소켓 제작법이 있길래 따라해 봤다.
단 이 사이트에 있는건 제작하려면 강력접착제와 바이스 같은 공구가 필요하기 때문에 저 내용을 약간 응용해서 그냥 만능기판에 직접 제작하기로 했다.

필요한 부품은 7핀 헤더 2개만 있으면 된다.

SD카드의 핀은 총 9개이지만 SPI모드인 경우 7개만 사용하면 되고 핀의 피치가 0.1"(2.54mm)이기 때문에 두개의 헤더를 만능기판에 꼽아주고 헤더의 핀을 적당히 구부려 주면 된다. SD 카드에 대한 자세한 이야기는 이전 포스트를 참조하면 된다.
옆쪽에서 봤을 때 위와 같이 되도록 핀을 구부려 주면 된다. 기판에 헤더를 땜질 한 다음에 저런식으로 구부리기는 힘들기 때문에 특히 왼쪽 헤더는 땜질하기 전에 미리 구부린 다음에 기판에 고정시키는 것이 좋다.




만들어 진 모습이다.


완성된 커넥터에 실제 SD 카드를 꼽은 모습이다.

위에서 이야기 했던것처럼 9핀중에 SPI모드에서 사용하는건 실제 7핀 뿐이다. 주의해야 할 점은 사진에서 맨 오른쪽 2핀이다. 다른 핀들 사이에는 플라스틱으로 칸막이가 되어 있기 때문에 문제가 없는데 마지막 2핀 사이에는 칸막이가 없다. 그래서 SD카드를 꼽을 때 헤더의 맨 오른쪽이 제일 오른쪽 핀에 닿지 않도록 주의해 줘야 한다.