아두이노의 TWI로 한 바이트나 문자열을 주고 받는 것은 Wire.read(), Wire.write() 함수를 사용하면 쉽게 수행할 수 있으므로 전혀 문제가 없다. 문제는 멀티 바이트로 구성된 short, long, float 등의 데이터를 주고 받는 것이다. 예를 들어서 signed short형 (아두이노에서는 2 bytes임) 데이터를 전송하려고 하면 바이트로 쪼개서 보낸 다음, 받는 곳에서 다시 이를 조합해서 원래의 데이터로 복구시켜야 하는데 무척 번거롭다.


 이런 경우에 union 이라는 자료형을 사용하면 문제를 쉽게 해결할 수 있다. 예를 들어서 다음과 같이 (signed) short 형 데이터를 저장하기 위한 union을 정의한다. volatile 은 인터럽트 루틴 안에서 사용되기 때문에 붙인 것이다.


union UShort{
   volatile short sVal;
   volatile byte byArr[2];
};


union자료형은 포함된 변수의 메모리를 공유하기 때문에 여기서 보면 byArr[0]은 short형 sVal 변수의 하위바이트를, byArr[1]은 상위 바이트를 가진다.


이제 이것을 이용해서 변수를 생성한다. volatile 을 붙인 이유는 이전과 같다.


volatile UShort uMtr; // pwm value to motor
volatile UShort usv; // encoder value


그러면 usv.sVal 변수를 보통의 short형 변수처럼 사용하다가 TWI로 전송할 때는 usv.byArr[0]과 usv.byArr[1] 두 바이트를 차례로 보내주면 된다. 예를 들어서 슬레이브의 TWI 전송함수는 다음과 같다.


void requestEvent()
{
   Wire.write( (const byte *)usv.byArr, 2);
   usv.sVal = 0;
}


주의할 것은 usv.byArr 을 반드시 (const byte *) 형으로 캐스트해줘야 한다는 점이다. 그렇지 않으면 컴파일 시에 에러가 발생한다.


 수신단에서도 동일한 union을 정의해 놓고, 받은 데이터를 바이트 배열 byArr 에 차례로 넣어주면 곧바로 sVal 변수를 통해서 short형 변수 값을 쓸 수 있다.


void receiveEvent(int howMany)
{
   uMtr.byArr[0] = Wire.read(); // 하위바이트 받음
   uMtr.byArr[1] = Wire.read(); // 상위 바이트 받음
   MotorInput( uMtr.sVal ); // 받은 short형 데이터를 바로 사용한다.
}


이 함수에서 보면 uMtr.byArr[0] 과 uMtr.byArr[1] 을 차례로 전송받은 후 바로 uMtr.sVal 변수값을 사용하였다. 이렇게 별도로 원래 데이터를 쪼개서 보내고, 받은 후에 (비트 연산자 같은 것을 사용해서)복구하는 과정이 전혀 필요가 없는 것이다.



Posted by 살레시오
,

 다음 사진에서 보면 좌측의 모터부가 DC모터-모토쉴드R3-아두이노우노 이렇게 연결되어 있고, 우측의 아두이노프로미니와 TWI로 연결되어 있다. 프로미니에서 모터의 PWM값을 전송하면 그것을 우노가 받아서 모터를 회전시키고 발생하는 엔코더 신호를 우노에서 프로미니로 전송하는데 이 값들이 모두 signed short 형이다.

[그림 1] TWI 통신 실험 세팅


 다음은 아두이노 우노의 전체 소스코드이다. _DEBUG 상수값을 정의시키면 디버그를 위한 시리얼 통신부가 컴파일 되고, 디비깅이 끝났다면 이것을 comment-out 시켜 시리얼 통신부를 컴파일에서 제외시키면 된다.


//#define _DEBUG
#include <PWM.h>
#include <Wire.h>
union UShort{
   volatile short sVal;
   volatile byte byArr[2]; //uint8_t
};
#define ID_TWI 2
// motor shield constants
#define PWM_A 3
#define DIR_A 12
#define BRAKE_A 9
#define SNS_A A0 // current sening pin
// encoder constants
#define phaseA 0 // phase-A of rotary encoder to *INT0*
#define phaseB 7 // phase-B of rotary encoder to D7
volatile UShort usv; // encoder value
volatile UShort uMtr; // pwm value to motor
int32_t frequency = 120000; // PWN frequency in [Hz], maximum: 2,000,000
void setup() {
//(pwm.h)initialize all timers except for 0, to save time keeping functions
InitTimersSafe();
//(pwm.h)sets the frequency for the specified pin
bool success = SetPinFrequencySafe(PWM_A, frequency);
// Configure the motorshield A output
pinMode(BRAKE_A, OUTPUT); // Brake pin on channel A
pinMode(DIR_A, OUTPUT); // Direction pin on channel A
// set ISR for encoder
attachInterrupt(phaseA, EncoderCount, FALLING);
pinMode(phaseB, INPUT);
usv.sVal = 0;
uMtr.sVal = 0;
// TWI setup
Wire.begin(ID_TWI); // join i2c bus as SLAVE with address #4
Wire.onRequest(requestEvent); // register event to send sensor value
Wire.onReceive(receiveEvent); // register event to receive motor pwm value
MotorForcedStop();
#ifdef _DEBUG //////////////////////////////////////////////////////
Serial.begin(115200); // working!!! others: 115200, 250000, 460800
#endif //////////////////////////////////////////////////////////////
}
#ifdef _DEBUG
void ReadEncoder()
{
   Serial.print("en:");
   Serial.println(usv.sVal);
   usv.sVal = 0;
}
#endif
void loop() {
#ifdef _DEBUG /////////////////////////////////////////////////
short sTmp = 0;
if (Serial.available())
{
   sTmp = Serial.parseInt();
   while(Serial.available()) Serial.read(); // empty buffer
   Serial.print("Received: ");
   Serial.println(sTmp);
   MotorInput(sTmp);
}
delay(50);
ReadEncoder();
#endif /////////////////////////////////////////////////////////
}
void EncoderCount() {
   usv.sVal += (1 - digitalRead(phaseB)*2); // LOW: +1, HIGH: -1
}
void MotorInput(short sIn)
{
   if (digitalRead(BRAKE_A) == HIGH)
       digitalWrite(BRAKE_A, LOW); // setting brake LOW disable motor brake
   if (sIn >= 0) {
       digitalWrite(DIR_A, HIGH);
       pwmWrite(PWM_A, sIn); // pwm.h
   } else if (sIn < 0) {
       digitalWrite(DIR_A, LOW);
       pwmWrite(PWM_A, -sIn); // pwm.h
   }
}

void MotorForcedStop()
{
digitalWrite(BRAKE_A, HIGH); // setting brake LOW disable motor brake
// pwmWrite(PWM_A, 0); // pwm.h <- this HINDERS motor from forcing stop
}
void requestEvent()
{
Wire.write((const byte *)usv.byArr, 2);
#ifdef _DEBUG ////////////////////////////////////////////////////
Serial.print("enc : ");
Serial.println(usv.sVal);
#endif ///////////////////////////////////////////////////////////
usv.sVal = 0;
}
void receiveEvent(int howMany)
{
uMtr.byArr[0] = Wire.read();
uMtr.byArr[1] = Wire.read();
#ifdef _DEBUG ///////////////////////////////////////////////////
Serial.print("TWI reads: ");
Serial.println(uMtr.sVal);
#endif //////////////////////////////////////////////////////////
MotorInput(uMtr.sVal);
}



Posted by 살레시오
,

 아두이노로 TWI (또는 I2C) 통신을 하기 위해서는 Wire 라는 라이브러리를 이용해야 한다. 시리얼 통신이 1:1 통신 규약인 반면, TWI는 1:n 통신 규약으로서 하나의 마스터(master) 기기가 여러 개의 슬레이브(slave) 디바이스들과 통신을 할 수 있다는 장점이 있다. Wire라이브러리는 아두이노의 표준 라이브러리이므로 아두이노 IDE에 기본으로 내장되어 있다.


#include <Wire.h>


그리고 통신을 하는 기기들 간에 SDA, SCL, 그리고 GND 끼리 연결하고 전압 레벨이 맞는지 반드시 확인해야 한다. 즉, 5V 기기는 5V 끼리, 3.3V기기는 3,3V 기기 상호간에 연결해야 하면 3.3V와 5V를 연결하려면 레벨 컨버터를 통해야 한다.


 연결된 기기는 하나의 마스터와 다수의 슬레이브로 구분하고 슬레이브들은 7bit 혹은 8bit 아이디(숫자)로 서로를 구분한다. TWI는 모든 통신 주도권을 master가 가진다. 슬레이브에서 데이터를 읽어올 때도 마스터에서 요구하고 슬레이브로 데이터를 보낼 때도 마찬가지로 마스터에서 통신을 요구한 후 보낸다. 마스터에서는 다음과 같이 초기화 함수를 호출한다.


Wire.begin(); // 마스터의 TWI 초기화


슬레이브에서는 다음과 같이 초기화 함수를 아이디를 가지고 호출한다.


Wire.begin(n); //슬레이브의 TWI 초기화


예를 들어서 2번 슬레이브는 Wire.begin(2); 라고 하면 된다. 이 아이디를 가지고 마스터에서 기기간 구별을 하게 된다.

마스터에서 슬레이브로 데이터 보내기

마스터에서 슬레이브로 데이터를 보낼 때는 다음과 같은 순서를 따른다.


//❶ 아이디가 id인 슬레이브와 TWI 통신을 시작한다.
Wire.beginTransmission(id);
Wire.write(by); //❷ byte형 데이터를 보낸다.
Wire.endTransmission(); //❸ 통신을 끝낸다.


Wire.write()함수는 입력 변수의 자료형에 따라 다음과 같이 세 가지가 오버로드 되어 있다.


Wire.write(byte by); // byte형 데이터를 하나를 전송
Wire.write(string str); // 문자열 str을 전송
Wire.write(const byte *ptrByte, int n); // byte형 포인터에 저장된 byte 배열에서 n개의 데이터를 전송


슬레이브 쪽에서는 마스터에서 데이터를 보냈을 때 호출되는 이벤트 함수를 등록해 두면 데이터가 보내졌을 때 이 함수가 호출되어 알맞게 처리를 해주면 된다.


// 데이터가 보내졌을 때 호출될 receiveEvent() 라는 함수를 등록한다.
void setup() {
   Wire.onReceive(receiveEvent);
}
....
....

// 이 이벤트 핸들러는 넘겨받은 바이트 수를 입력으로 건네준다.
void receiveEvent(int iNum)
{
   byte by[iNum];
   for(int n=0; n<iNum; n++)
       by[n] = Wire.read();
}


마스터에서 슬레이브로부터 데이터 읽기

  마스터가 슬레이브에서 데이터를 받을 때도 마스터에서 먼저 요구하며 그 요구에 슬레이브가 반응하도록 되어 있다. 마스터에서 슬레이브에 데이터를 요구하고 읽어오는 과정은 다음과 같다.


byte by[n], m=0;
Wire.requestFrom(id, n); // id를 가지는 슬레이브에 n 바이트를 보내기를 요구한다.
while( Wire.available() ) // 읽어올 데이터가 버퍼에 남아있다면
{
   by[m++]=Wire.read(); // 데이터를 읽는다.
}


슬레이브 측에서는 마스터에서 요구가 올 때 처리할 이벤터 핸들러를 등록해 두면 된다.


// 마스터의 전송 요구시 호출된 requestEvent()라는 함수를 등록
Wire.onRequest( requestEvent );
...
void requestEvent()
{
   Wire.write(100); // (예를 들어) 100이라는 숫자를 전송한다.
}


이와 같이 마스터와 슬레이브 사이에 통신을 할 경우 마스터에서 슬레이브 쪽으로 통신을 하겠다는 요구를 먼저 하게 되어 있다.



Posted by 살레시오
,

 아두이노의 디지털핀 두 개와 저항 하나로 터치 센서를 구현할 수 있는데 관련 객체를 제공하는 것이 바로 Capacitance Sensing Library 이다.

기본 개념과 구성 회로

정전용량식 터치 센서의 개념도는 아래 그림과 같다.

[그림 1] 정전용량식 터치 센서의 개념도


이 개념도를 보면 두 개의 핀을 사용하는데 하나는 발신 핀(send pin) 이고 다른 하나는 수신 핀(receive pin)이다. 이 두 핀을 저항으로 연결하고 수신 핀에 터치할 도체(foil)를 연결한다. 만약 이 도체를 터치를 하면 수신 핀 단의 정전용량이 틀려지고 발신 핀에서 신호를 보낼 때(즉 발신 핀이 on되었을 때) 수신 핀이 기립 시간(rising time, 발신 핀이 on 된 후 수신핀이 on이 되는데 걸리는 시간 간격)이 변하게 된다. 이 변화를 감지하여 터지가 되었는지 안 되었는지를 판단하는 것이다.

해당 라이브리리 설치와 사용법

 이 기능을 아두이노에서 사용하려면 먼저 라이브러리를 다운로드 받아서 설치해야 한다. 아래와 같은 절차를 따른다.


  1. 소스를 다운로드 받는다.( CapacitiveSensor04.zip )

  2. 압축을 푼 후 그 안의 CapacitiveSensor 폴더를 복사해서 Arduino/libraries/ 폴더에 붙인다.

  3. 아두이노 IDE에서 Sketch>Import Library>CapacitiveSensor 메뉴를 선택한다.


위와 같이 했다면 IDE에 다음과 같이 헤더파일이 인클루드 된다.


#include <CapacitiveSensor.h>


이 라이브러리에서는 CapacitiveSensor라는 클래스가 있는데 이것의 인스턴스는 다음과 같이 초기화 할 수 있다.


CapacitiveSensor CapacitiveSensor(byte sendPin, byte receivePin);

 

여기서 sendPin과 receivePin 은 저항이 연결된 두 개의 디지털 핀을 지정해주면 된다. 이제 이 인스턴스의 메소드(함수)로 다음과 같은 것들이 있다.

long capacitiveSensorRaw(byte samples)


이 함수는 감지된 정전용량 값을 반환이며 반환값은 무단위이다. 입력으로 주어지는 samples 는 (byte형이므로 0-255 사이의 값) 샘플링 횟수를 지정해 주는 것이다. 이 값이 커지면 리턴값의 분해능이 커지는 대신 속도가 느려지게 되므로 적당한 값을 선택해야 한다. 리턴값은 각각의 샘플링에서 얻어진 값을 모두 더한 값이며 평균값이 아니다.


long capacitiveSensor(byte samples)


이 함수 역시 감지된 정전용량 값을 반환이며 반환값은 무단위이다. 앞의 함수와 다른 점은 터치가 안 되었을 때의 값(기본값)을 추적하여 현재 감지된 값에서 그 값을 뺀 다음 반환한다. 따라서 터지가 안 된 상태라면 0에 가까운 작은 값을 반환하고 터치가 되었다면 그것 보다는 큰 숫자를 반환한다.


 기본값(터치가 안 되었을 때의 정전용량 값)은 CS_Autocal_Millis 라는 상수에 저장된 시간 마다 자동으로 조정된다. 초기값은 20000 밀리초 (20초)이다. 이 갱신 시간은 set_CS_Autocal_Millis() 함수를 이용해서 변경할 수 있다.


void set_CS_Autocal_Millis(unsigned long autoCal_millis);


자동 갱신 기능을 끄려면 매우 큰 값(0xFFFFFFFF)으로 지정하면 된다.


 만약 즉시 기본값을 조정하려면 다음 함수를 호출하면 된다.


void reset_CS_AutoCal()


그리고 사용 빈도는 낮지만 다음 함수는 capacitiveSensor()함수나 capacitiveSensorRaw()함수의 타임아웃 시간을 지정해주는 함수도 있다.


void set_CS_Timeout_Millis(unsigned long timeout_millis);


이 함수는 CS_Timeout_Millis 내부 변수를 변경하는 함수인데 초기값은 2000 밀리초(2초)이다. 타임아웃시간이란 발신핀이 on 되었는데도 수신핀이 on이 되지 않을 경우 언제까지 기다려야 하는가를 정하는 것이다. 타임아웃 시간이 지나도 수신핀이 on이 되지 않으면 -2를 반환한다.

저항값의 선택

저항값은 다음과 같은 기준으로 선택한다.

  • 1 MΩ (혹은 이것보다 다소 작은 용량) : 완전히 터치되었을 때에만 반응시키고자 할 때

  • 10 MΩ : 4~6인치 정도 떨어진 곳에서도 반응시킬 때

  • 40 MΩ : 12~24인치 정도 떨어진 곳에서도 반응시킬 때 (도체의 크기에 따라 가변적임)


즉, 저항값이 커지면 감도도 높이지고 반응 속도는 느려진다. 또한 터치부가 노출된 금속판이라면 발신부에서서 on 신호를 발생시키지 못 할 가능성도 있다. 수신핀을 작은 용량의 커패시터 (100 pF ~ 0,01uF) 를 통해서 접지시키면 센서의 안정도를 개선시킬 수 있다.

실제 예제 프로그램

 회로도는 다음과 같다. 아두이노 우노와 1MΩ 저항 하나로 구성한다.

[그림 2] 실험 회로도


다음 프로그램을 실행하면 D3핀 쪽의 리드선을 터치할 때 LED가 켜진다.


#include <CapacitiveSensor.h>
#define LED 13
CapacitiveSensor cs23 = CapacitiveSensor(2,3);
void setup() {
   pinMode(LED, OUTPUT);
   cs23.set_CS_Autocal_Millis(0xFFFFFFFF);
   Serial.begin(9600);
}
void loop() {
   long start = millis();
   long total1 = cs23.capacitiveSensor(30);
   (total1>10) ? digitalWrite(LED, HIGH):digitalWrite(LED, LOW);
}



Posted by 살레시오
,

 다음 예제들은 DFRobot사의 LCD쉴드를 이용하여 실험을 진행하였다.

[그림 2] 실험에 사용된 LCD쉴드와 회로도

회로도를 보면 RS가 8번 핀, RW가 9번핀, 그리고 D4~D7번 핀이 4번에서 7번핀까지로 결선되어 있음을 알 수 있다. 따라서 LiquidCrystal 클래스의 인스턴스는 첫 번째 생성자를 이용하여 다음과 같이 생성하면 된다.

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

이제 예제들을 작성해 보도록 하겠다.

첫 번째 예제

 첫 번째 예제로 간단한 문자열을 출력하는 프로그램이다.


// 헤더파일을 인클루드 시킨다.
#include <LiquidCrystal.h>
// 회로도를 참조하여 LCD 객체를 생성/초기화시킨다.
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
void setup() {
   // LED의 행수와 열수를 주어 표시를 시작한다.
   lcd.begin(16, 2);
   // LDC에 문자열을 표시한다.
   lcd.print("hello, world!");
}
void loop() {
   // 커서의 위치를 0열, 1행(두 번재 행)으로 설정한다.
   lcd.setCursor(0, 1);
   // 초를 표시한다.
   lcd.print(millis()/1000);
}

두 번째 예제

 LiquidCrystal 라이브러리에는 blink() 와 noBlink() 함수가 있는데 깜박이는 직사각형의 커서를 표시할 것인지 아닌지를 설정하는 함수이다. 다음 예제를 이것을 이용한 것이다.


#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup() {
   lcd.begin(16, 2);
   lcd.print("hello, world!");
}
void loop() {
   lcd.noBlink();
   delay(3000);
   lcd.blink();
   delay(3000);
}

유사한 함수로 cursor() 함수와 noCursor()함수가 있는데 이것은 밑줄 모양의 커서를 표시할 것인가 말 것인가를 설정하는 것이다. 이 경우 밑줄 커서는 깜박이지 않는다.

세 번째 예제

 LiquidCrystal 라이브러리의 공용 멤버함수 중에는 display()함수와 noDisplay()함수가 있다. 함수 명에서 알 수 있듯이 화면에 표시를 할 것인지 말 것인지를 설정하는 것이다.


#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

void setup() {
   lcd.begin(16, 2);
   lcd.print("hello, world!");
}

void loop() {
   lcd.display();
   delay(3000);
   lcd.noDisplay();
   delay(3000);
}


위 예제어서 noDisplay()함수가 실행되면 화면에 표시된 문자열이 사라지지만 그 내용은 내부 메모리에 여전히 남아있다. 따라서 display()함수가 호출되면 되살아나는 것이다. 또한 이 함수들을 이용하면 화면 전체를 깜박이는 효과를 줄 수도 있다.

네 번째 예제

문자열이 표시되는 방향을 바꾸는 함수로 rightToLeft()함수와 leftToRight()함수가 있다. 다음 예제는 이것을 이용한 것이다.


#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
int thisChar = 'a';
void setup() {
   lcd.begin(16, 2);
   lcd.cursor();
}
void loop() {
   // ‘m’ 이 표시되면 방향을 바꾼다.
   if (thisChar == 'm') {
       lcd.rightToLeft();
   }
   // 's'가 표시되면 방향을 다시 바꾼다.
   if (thisChar == 's') {
       lcd.leftToRight();
   }
   // 'z'가 표시된 후 처음 조건으로 다시 리셋한다.
   if (thisChar > 'z') {
       lcd.clear(); // 화면을 지운 후 커서를 처음 자리로
       thisChar = 'a';
   }
   lcd.write(thisChar);
   delay(1000);
   thisChar++;
}


다섯 번째 예제

 이번에는 터미널에 입력한 문자를 LCD에 표시하는 프로그램을 작성해 보자.

#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
void setup() {
   lcd.begin(16, 2);
   Serial.begin(9600);
}
void loop() {
   // 문자가 전송되면
   if (Serial.available()) {
       // 전체 문자열이 들어올 때까지 잠시 기다린다.
       delay(100);
       lcd.clear();
       // 전송된 모든 문자를 표시한다.
       while (Serial.available() > 0) {
           lcd.write(Serial.read());
       }
   }
}


여섯 번째 예제

 화면의 내용을 스크롤(scroll)하는 예제이다.


#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
void setup() {
   lcd.begin(16, 2);
   lcd.print("hello, world!");
   delay(1000);
}
void loop() {
   // 왼쪽으로 스크롤
   for (int cnt = 0; cnt < 13; cnt++) {
       lcd.scrollDisplayLeft();
       delay(150);
   }
   // 오른쪽으로 스크롤
   for (int cnt = 0; cnt < 29; cnt++) {
       lcd.scrollDisplayRight();
       delay(150);
   }
   // 다시 왼쪽으로 스크롤하여 제자리로
   for (int cnt = 0; cnt < 16; cnt++) {
       lcd.scrollDisplayLeft();
       delay(150);
   }
   delay(1000);
}


이 예제에서는 scrollDisplayLeft() 함수와 scrollDisplayRight() 함수를 이용하여 수동으로 이동시켰다. 반면 autoScroll()함수를 사용하면 문자가 표시되기 전에 왼쪽으로 자동으로 이동이 된다.




Posted by 살레시오
,

 가전제품이나 전자 장치에서 간단한 정보를 표시하는데 많이 사용되는 것이 바로 액정 표시기 (liquid crystal display, LCD)이다. 크게 문자만을 표시할 수 있는 문자형과 선이나 도형 등 그래픽을 그릴 수 있는 그래픽형으로 나뉜다.

[그림 1] LCD 장치의 예

아두이노의 LiquidCrystal 라이브러리는 텍스트 액정(liquid crystal) 표시장치를 구동하기 위한 라이브러리이다. 이 라이브러리를 이용하기 위해서는 다음과 같이 프로그램 서두에 해당 헤더파일을 인클루드시켜야 한다.


#include <LiquidCrystal.h>

이 헤더파일에는 LiquidCrystal 이라는 클래스가 정의되어 있으며 다음과 같은 공용(public) 멤버함수들이 정의되어 있어서 쉽게 사용할 수 있다.

  • 생성자 : LCD 모듈과 아두이노의 연결 방식에 따라서 생성자를 사용하면 된다. 다음과 같은 네 가지 함수가 오버로드되어 있다.

       ❶ LiquidCrystal(rs, enable, d4, d5, d6, d7)

       ❷ LiquidCrystal(rs, rw, enable, d4, d5, d6, d7)

       ❸ LiquidCrystal(rs, enable, d0, d1, d2, d3, d4, d5, d6, d7)

       ❹ LiquidCrystal(rs, rw, enable, d0, d1, d2, d3, d4, d5, d6, d7)

보통은 핀수를 가장 적게 차지하는 ❶번 방식이 많이 사용된다.  멤버함수는 다음과 같다.


[표 1] LiquidCrystal 클래스의 멤버 함수들

멤버 함수

기능

void begin(row, col)

LCD를 초기화 한다.

void clear()

화면을 지운 후 커서를 처음 자리로 옮긴다.

void home()

화면은 그대로 나둔채로 처서만 (0,0)자리로 옮긴다.

setCursor(col, row)

커서를 (col, row)자리로 옮긴다.

byte write(data)

한 문자를 출력한다.

print(data, BASE)

문자열이나 변수 값을 출력한다.

createChar()

사용자 (그림)문자를 생성한다. (8개까지 가능함)

void cursor()

(밑줄)커서를 표시한다.

void noCursor()

커서를 표시하지 않는다

void blink()

깜박이는 커서를 표시한다.

void noBlink()

커서를 표시하지 않는다.

void display()

내용을 표시한다.

void noDisplay()

내용을 표시하지 않는다.(메모리에 내용은 남아 있음)

scrollDisplayLeft()

표시된 내용을 왼쪽으로 한 칸 이동한다.(회전)

scrollDisplayRight()

표시된 내용을 오른쪽으로 한 칸 이동한다.(회전)

void autoscroll()

기존 내용을 좌로 이동시킨 후 현재 내용을 표시.

noAutoscroll()


void leftToRight()

문자 표시 방향을 왼쪽에서 오른쪽으로 설정한다.

void rightToLeft()

문자 표시 방향을 오른쪽에서 왼쪽으로 설정한다.

여기서 write()함수는 입력을 아스키(ASCII)값으로 받으며 print()함수는 문자열이나 상수(변수)값을 받는 것에 유의하자.



Posted by 살레시오
,

 Servo 라이브러리는 서보 모터(servo motor)의 제어를 위한 객체를 정의한 것이다. 서보모터는 R/C카의 조향장치나 마이크로로봇의 구동장치로 쓰이는 모터로서 신호선 한 가닥으로 0도~180도의 각도를 조절할 수 있는 것이다.

[그림 1] 서보 모터의 외형

그림에서 보듯이 전원선인 Vcc/GND와 신호선 세 가닥이 인터페이스의 전부이다. 모터축의 각도는 신호선으로 인가되는 펄스폭으로 조정된다.

[그림 2] 서보 모터의 신호선으로 인가되는 펄스(pulse)

[그림 2]에 서보모터의 신호선으로 인가되는 펄스를 도시하였다. 주기 B는 3ms~20ms 의 범위를 가지며 주기가 짧을 수록 모터축의 회전 속도가 빨라진다. 펄스의 폭 A는 1.5ms 일때 중심각도인 0도를 가리키게 되고 1ms(최소값 0.7ms)일때 –90도, 2ms(최대값 2.3ms)일 때 +90도를 갖는다. 즉, 이 펄스폭을 조절하여 –90도에서 90도 사이의 원하는 각도를 가지도록 제어할 수 있는 것이다.


 아두이노의 Servo 라이브러리는 이러한 펄스를 발생시키는 기능을 구현하고 있으며 사용자는 세부 동작 원리를 알지 못하더라도 라이브러리 함수를 사용하는 방법만 알면 서보모터를 제어할 수 있다. 생성자는 입력인수가 없는 것 하나이며 멤버함수로 다음과 같은 것들이 있다.


  • void attach(pin) : 서보모터의 신호선이 연결된 핀을 지정한다.

  • void attach(pin, min_us, max_us) : 펄스폭 A의 최소, 최대값까지 지정한다.

  • void write(angle) : 각도를 지정한다. (angle: 0~180)

  • void writeMicroseconds(us) :펄스폭 A를 us단위로 지정한다.

  • int read() : 현재 각도를 읽는다.반환값은 [0, 180]의 정수.

  • boolean attached() : 서보모터 핀이 지정되어 있는지 검사한다.

  • void detach() : 서보모터의 지정된 핀을 내부적으로 제거한다.

실험에 사용된 서보모터는 SG-90이라는 초소형 모터이다.

[그림 3] SG-90 초소형 서보 모터

첫 번째 예제

 첫 번째로 서보모터를 설정하고 중심각도를 유지하는 간단한 예제를 작성해 보자.

#include <Servo.h>
Servo sm;
void setup() {
   sm.attach(9); //9번 핀에 서보모터의 신호선을 연결
   sm.write(90); //90도(중심각) 각도로 회전
}
void loop() {
}

두 번째 예제

 두 번째로 서보모터 축이 0도에서 180도로 계속 왕복 운동을 하도록 프로그램을 작성해 보자.


#include <Servo.h>
Servo sm;
void setup() {
   sm.attach(9); //9번 핀에 서보모터의 신호선을 연결
}
void loop() {
   sm.write(0); //0도로 회전
   delay(2000);
   sm.write(180); //180도로 회전
   delay(2000);
}


 이와 같이 servo 라이브러리를 사용하면 간편하게 디지털 핀 하나를 이용하여  서보 모터를 제어할 수 있으며 동시에 다수의 서보 모터를 제어하는 것도 가능하다.



Posted by 살레시오
,

 아두이노는 주변 기기들을 간편하게 제어할 수 있는 다양한 라이브러리가 존재한다. arduino.cc 에서는 이들 라이브러리를 표준 라이브러리와 그외의 것들로 구분하는데 표준 라이브러리는 아두이노 IDE에 포함된 것으로 다음과 같은 것들이 있다.


[표 1] 아두이노 표준 라이브러리

라이브러리명

기능

비고

EEPROM

EEPROM에 읽고 쓰는 기능을 하는 함수들.

Ethernet

Arduino Ethernet Shield를 이용한 인터넷 접속.

Firmata

시리얼 프로토콜을 이용하여 PC와 연결하여 데이터를 주고 받기.

GSM

GSM shield를 이용하여 GSM/GRPS 네트워크에 접속하기.

LiquidCrystal

LCD 제어.

SD

SD카드에 읽고 쓰기.

Servo

서보모터 제어.

SPI

SPI(Serial Peripheral Interface) 통신.

SoftwareSerial

시리얼통신을 소프트웨어적으로 구현한 것.

Stepper

스테핑모터 구동.

TFT

Arduino TFT screen에 문자, 이미지, 도형 표시하기.

WiFi

Arduino WiFi shield를 이용하여 인터넷 접속하기.

Wire

TWI (I2C) 통신.


[표 2] 특수 라이브러리

라이브러리명

기능

비고

Audio

SD카드에 저장된 오디오파일 재싱.

Due

Scheduler

다중 non-blocking 태스크 관리.

USBHost

마우스와 키보드 같은 USB주변장치와의 통신.

Esplora

Esplora 보드의 센서와 작동기를 쉽게 접근하도록 함.

Esplora

Keyboard

연결된 컴퓨터에 키보드 입력 전송.

Leonardo, Micro,Due,Esplora

Mouse

연결된 컴퓨터의 마우스 커서 제어.


[표 3] 기타 라이브러리 (사용하기 위해서는 설치 과정이 필요함)

라이브러리명

기능

비고

Messenger

PC로부터의 텍스트기반 메시지 처리.

통신

NewSoftwareSerial

SoftwareSerial 라이브러리의 개선판

OneWire

1-wire protocol을 사용하는 장치 제어(Dallas Semiconductor)

PS2Keyboard

PS2 키보드로부터 문자 읽어들이기.

Simple Message System

아두이노와 PC간 메시지 보내기.

SSerial2Mobile

휴대폰을 이용하여 문자메세지나 이메일 보내기.

Webduino

확장가능한 웹서버 라이브러리(이더넷쉴드)

X10

AC전원선으로 x10신호 보내기.

XBee

XBee와의 통신.

SerialControl

시리얼 통신으로 다른 아두이노 제어하기


Capacitive Sensing

두 개 혹은 그 이상의 핀을 정전식 터치센서로 사용.

센싱

Debounce

(버튼으로부터) 바운싱을 제거하기.


GFX

기반 클래스(표준 그래픽 루틴)

디스플레이/LED

GLCD

KS0108 칩 기반의 그래픽 LCD 라이브러리

Improved LCD library

LCD라이브러리의 오류 수정 버전

LedControl

LED행렬/7세그먼트 제어 (MAX7221/MAX7219)

LedControl

여러개의 LED를 Maxim칩으로 제어하는 다른 라이브러리

LedDisplay

HCMS-20xx 스크롤링 LED디스틀레이 제어.

Matrix

기본적인 LED매트릭스를 제어하는 라이브러리.

PCD8544

Nokia55100 LCD 제어기(Adafruit ind.)

Sprite

LED매트릭스의 애니메이션 사용을 위한 기반 클래스

ST7735

1.8“ TFT 128x160 스크린 제어 라이브러리(adafruit)


FFT

오디오 혹은 다른 아날로그 신호의 주파수 해석.

audio

Tone

오디오 주파수의 구형파 생성.


TLC5940

16채널 12비트 PWM 제어기

PWM


DateTime

현재 날짜와 시간을 추적.

타이밍

Metro

정해진 시간 간격으로 수행.

MsTimer2

타이머2를 써서 매 N밀리초마다 정해진 일을 수행


PString

버퍼에 프린팅을 하기위한 가벼운 클래스

유틸

Streaming

프린트문을 간략히 하기위한 메쏘드

 여기에 소개된 리스트 외에도 사용자들이 작성한 수 많은 라이브러리들이 공유되고 있다. 따라서 본인이 필요한 기능을 구현하기 위해서 직접 작성하기 전에 그 기능을 지원하는 라이브러리가 있는지 검색해 보는 과정을 거쳐야 한다.



Posted by 살레시오
,

 아두이노 우노의 경우 PWM주파수가 980Hz (5,6번 핀)와 490Hz(3, 9, 10, 11번 핀)로 고정되어 있다. 보통의 경우(LED의 밝기를 제어한다든가 소형 모터를 돌릴 때)에는 이 주파수를 사용하는 것에 별 문제는 없다.

[표 1] 아두이노 우노의 pwm 주파수

핀 번호

PWM 주파수

5, 6

980Hz

3, 9, 10,11

490Hz

하지만 중소형 이상의 DC모터를 구동하는 경우에는 보통 10KHz 이상의 PWM 주파수를 사용하므로 아두이노에서 제공하는 기본 주파수로는 DC모터를 구동하기에 적절하지 않다. 이 주파수가 중요한 이유는 만약 PWM주파수가 너무 낮다면 모터의 속도를 정밀하게 제어할 수 없으며 모터에서 소음이 발생하는 경우도 있기 때문이다.

 

 이런 경우에 PWM 주파수를 변경해야 하는데 아두이노의 표준 API에는 아쉽게도 PWM의 주파수를 조절할 수 있는 함수를 제공하지 않지만 사용자가 만들어 놓은 라이브러리가 있다. 아래의 페이지에 자세하게 설명되어 있다.

다운로드 받은 파일의 압축을 풀면 세 개의 폴더가 있는데 이 중 PWM 폴더를 아래의 폴더에 복사하여 붙여넣는다.

<아두이노 IDE 설치 폴더>\Arduino\libraries

윈도우즈 시스템에서는 보통 아래의 폴더이다.

C:\Program Files\Arduino\libraries

그러면 아두이노 IDE에 다음 [그림 6.6.1]과 같은 항목이 생성된다. 이 항목을 선택하면 텍스트 에디터에 다음과 같이 인클루드문이 추가된다.

#include <PWM.h>

이것으로 이 라이브러리를 사용할 준비가 된 것이다.


[그림 1] pwm.h를 메뉴에서 인클루드하는 방법

이 라이브러리에서는 다음과 같이 다섯 개의 전역 함수를 제공한다.


[표 1] pwm.h에서 제공하는 전역 함수들

함수명

기능

InitTimers()

Initializes all timers. Needs to be called before changing the timers frequency or setting the duty on a pin

InitTimersSafe()

Same as InitTimers() except timer 0 is not initialized in order to preserve time keeping functions

pwmWrite(uint8_t pin, uint8_t val)

Same as 'analogWrite()', but it only works with initialized timers. Continue to use analogWrite() on uninitialized timers

SetPinFrequency(int8_t pin, int32_t frequency)

Sets the pin's frequency (in Hz) and returns a bool for success. 주파수 범위는 31Hz~2MHz 사이이다.

Sets the pin's frequency (in Hz) and returns a bool for success. 주파수 범위는 31Hz~2MHz 사이이다.

SetPinFrequencySafe(int8_t pin, int32_t frequency)

Same as SetPinFrequency except it does not affect timer 0. 주파수 범위는 31Hz~2MHz 사이이다.


여기서 보면 InitTimers() 와 SetPinFrequency() 함수는 함께 사용되는데 이 함수들은 내부적으로 timer0번을 초기화시킨다. 따라서 시간 관련 함수인 millis(), micros(), delay(), delayMicroseconds() 함수들이 정상 동작하지 않는다. 그리고 3, 5, 9, 10번 핀을 PWM 핀으로 사용할 수 있다.

 반면 InitTimersSafe()와 SetPinFrequency() 함수는 역시 쌍으로 사용되는데 timer0를 초기화시키지 않으므로 시간 관련 함수들이 정상적으로 동작한다. 그리고 3,9,10번 핀을 PWM으로 사용할 수 있다. 두 경우 모두 PWM값을 쓰기 위해서는 analogWrite()함수 대신 pwmWrite()함수를 사용한다.


[표 2] pwm.h 의 두 함수 그룹 비교

함수 그룹

특징

장단점

적용 핀

InitTimers()

SetPinFrequency()

timer0

초기화

millis(), micros(), delay(), delayMicroseconds() 함수들이 정상 동작하지 않는다.

3,

5,

9,

10

InitTimersSafe()

SetPinFrequencySafe()

timer0

초기화

안함

millis(), micros(), delay(), delayMicroseconds() 함수들이 정상 동작한다.

3,

9,

10


시간 관련 함수들이 정상적으로 동작 하지 않는 것은 큰 문제이므로 InitTimersSafe() 과 SetPinFrequencySafe() 함수를 사용하는 것이 좋을 것이다.



Posted by 살레시오
,

 아두이노 모터 쉴드 (R3) 사용법을 정리해 보았다. 이것을 이용하면 DC모터를 아두이노로 쉽게 제어할 수 있다.


   Arduino Motor Shield R3 page



일단 기본적인 특성은 다음과 같다.


  • 동작전압 : 5V to 12V

  • 모터제어IC: L298P (두 개의 DC모터 혹은 1개의 스테핑모터 제어 가능)

  • 최대 전류 :채널당 2A 혹은 4A(두 채널을 병렬로 연결시)

  • 전류 센싱 : 1.65V/A

  • 강제 정지(brake) 기능


 모터의 전원은 반드시 별도로 연결해 주어야 하는데 아두이노의 2.1파이 DC잭에 연결해도 되고 쉴드의 스크류단자에 전선으로 연결해도 된다. 그런데 사용 설명에는 모터 전압이 9V 이상이면 아두이노와 쉴드의 전원(Vin)을 분리하는 것이 바람직하며 '쉴드 뒷면의 "Vin Connect" 점퍼를 절단하면 된다'고 설명되어 있다. 이 경우 쉴드에 직접 인가할 수 있는 전압의 최대값은 18V이다.


 사용되는 핀은 다음과 같다.


[표 1] 모터쉴드 R3의 핀 배열

기능

채널A

채널B

기능

방향

D12

D13

회전 방향 제어

PWM

D3

D11

속도 제어

브레이크

D9

D8

HIGH일 때 강제 정지

전류 감지

A0

A1

전류 감지


[그림 1] 모터 쉴드 R3의 핀 기능


모터 회전 시에 PWM을 0 으로 인가하면 전압이 0으로 떨어지고 모터 축은 관성에 따라 천천히 멈추게 되지만 이에 반해서 Brake는 강제 정지를 할 때 사용되는 핀으로 HIGH가 되는 순간 모터가 곧바로 멈추게 된다.


 이것을 아두이노Due 와 연결해서 동작시킬때 Due의 동작 전압이 3.3V이므로 맞지 않을 것이라고 짐작했으나 다음 페이지에 설명된 대로 직접 연결해도 아무런 문제가 없다.


   Arduino Due + Motor Shiled : DC motor


단 모터 전원이 9V이상일 때는 Vin은 분리하는 것이 좋을 것 같다.


 이 쉴드에 대해서 한 가지 아쉬운 점은 PWM(A) 핀으로 3번 핀을 사용하고 있어서 인터럽트 하나를 쓰지 못하게 한다는 것이다.



Posted by 살레시오
,