i2c에서 통신은 프로토콜 특성상 항상 마스터에서 요구하며. 마스터에서 전송할 때도 그렇고 슬레이브에서 읽어올 때도 마스터에서 먼저 요구하면 슬레이브에서 그 요구를 받아서 데이터를 전송한다.


 아두이노의 Wire라이브러리를 이용하면 Wire.onReceive() 함수와 Wire.onRequest() 함수를 이용하여 핸들러 함수를 등록해야 한다.


Wire.onReceive( _onReceive ); #데이터를 받는다.
Wire.onRequest( _onRequest ); #데이터를 보낸다.

smbus의 관련함수들은 다음과 같다.


read_i2c_block_data(addr, cmd [, length] )
write_i2c_block_data(addr, cmd, lst)

smbus의 write_2ic_block_data() 함수는 다음과 같이 데이터를 전송하기 위해서 _onReceive()함수를 호출하게 된다. 전송되는 데이터의 첫 바이트가 cmd라는 것을 유의해야 한다. _onReceive( len)함수의 인수도 cmd까지 고려한 길이가 인수로 넘어간다.


cmd

lst[0]

lst[1]

...

lst[-1]


 그런데 smbus의 read_2ic_block_data()함수는 cmd 한 바이트를 먼저 전송한 후 데이터를 읽는다. 즉, 실제 동작은 _onReceive()함수가 먼저 호출되고 그 다음 _onRequest()함수를 호출한다. 따라서 예를 들면 아두이노의 핸들러 함수는 다음과 같이 작성해야 한다.


byte _cmd_i2c;
byte _rcvBuf[32];

void _onReceive(int count) {
   _cmd_i2c = Wire.read(); // 첫 바이트는 *항상* command

   //만약 smbus.read_i2c_block_data()호출이라면 여기서 종료되고
   //바로 _onRequest() 함수가 호출된다.
   if (count > 1) {
       _rcvBuf[0] = _cmd_i2c;
       _idx = 1;
       while(Wire.available())
           _rcvBuf[_idx++] = Wire.read();
   }
}

void _HRP_::_onRequest() {
   switch(_cmd_i2c) {
       // _cmd_i2c 에 따라 해당 데이터를 전
   }
}

위와 같이 작성하면 master에서 전송된 데이터는 command까지 포함해서 모두 _rcvBuf 배열에 저장되고 데이터를 전송하는 경우에는 _cmd_i2c 변수의 내용에 따라 각기 다른 데이터를 전송할 수 있다.

n{rp005}


'연구 > Ardpy' 카테고리의 다른 글

Ardpy API reference  (0) 2016.10.23
Ardpy examples  (0) 2016.10.23
Ardpy setup  (0) 2016.10.23
About Ardpy  (0) 2016.10.23
ardpy 개발 동기  (0) 2016.05.02
Posted by 살레시오

댓글을 달아 주세요

 며칠간 라즈베리파이와 아두이노간 i2c 통신을 위해서 python3-smbus 라이브러리를 사용하였는데 여기에 몇 가지를 기록한다. 설치하는 법은 다음과 같다.


sudo apt-get update (먼저 반드시 수행해야 한다.)
sudo apt-get install python3-smbus


라즈베리파이 B+와 ver. 2 에는 i2c 포트가 두 개가 있는데 각각 i2c-0, i2c-1 이다. 만약 1번을 사용한다면 다음과 같이 초기화 할 수 있다.


import smbus
dev = smbus.SMBus(1)


smbus 레퍼런스에 보면 cmd (command) 파라메터가 있는데 이것은 단순히 보내는 데이터의 맨 앞에 먼저 보내지는 데이터이다. 보통 슬레이브는 이것을 보고 어떤 명령인지(또는 어떤 데이터가 따르는지)를 판별한다.


 그리고 함수들 중에 i2c 가 붙은 것들이 있는데 이것이 더 효율적이라고 한다.


read_i2c_block_data(addr, cmd [, length] )
write_i2c_block_data(addr, cmd, lst)


write_2ic_block_data()함수는 다음과 같이 데이터를 전송한다.


cmd

lst[0]

lst[1]

...

lst[-1]


read_2ic_block_data()함수는 cmd 한 바이트를 먼저 전송한 후 데이터를 읽어온다.


smbus에는 write_block_data()라는 함수도 있는데 write_i2c_block_data()와의 차이점은 전송되는 데이터가 하나 다르다는 것이다. 두 번째 바이트에 데이터의 길이를 전송한다.



cmd

size

lst[0]

lst[1]

...

lst[-1]


만약 i2c 통신을 사용하는 것이라면 read_i2c_block_data(), write_i2c_block_data() 함수를 사용하는 것이 좋다.





'연구 > Ardpy' 카테고리의 다른 글

Ardpy API reference  (0) 2016.10.23
Ardpy examples  (0) 2016.10.23
Ardpy setup  (0) 2016.10.23
About Ardpy  (0) 2016.10.23
ardpy 개발 동기  (0) 2016.05.02
Posted by 살레시오

댓글을 달아 주세요

 아두이노의 TWI 통신 (I2C라고도 한다)은 두 가닥의 선(SDA, SCL)만으로 여러 개의 디바이스와 통신을 할 수 있다는 장점을 가진다. 이것의 통신 속도는 마스터 기기에서 발생시키는 클럭(SCL)신호를 기준으로 정해지며 보통 많이 사용되는 표준 주파수는 100kHz 와 400kHz 이다. 단순하게 이론적으로 계산하면 100kHz 의 주파수라면 초당 100k 비트(바이트 아님)를 전송할 수 있으며 초당 약 12.5k 바이트를 전송할 수 있다.


 아두이노의 I2C 통신에 사용되는 클럭 주파수는 100kHz로 맞추어져 있다. 아두이노에서 쓰이는 AVR은 400kHz 의 주파수도 지원을 하며 대부분의 I2C 통신 기기들이 이 주파수를 지원한다. 그런데 아두이노 API에서는 이 클럭 주파수를 조절하는 함수나 메쏘드가 없다. 이것을 400kHz로 상향시키기 위해서는 다음과 같이 약간 번거로운 과정을 거쳐야 한다.


 아두이노 IDE 1.5.5 와 윈도즈를 기준으로 설명하도록 하겠다. 먼저 다음 파일을 연다

C:\Program Files \ Arduino \ hardware \ arduino \ avr \ libraries \ Wire \ utility \ twi.h

이 코드의 윗 부분에 보면 다음과 같은 상수가 있다.


#ifndef TWI_FREQ
#define TWI_FREQ 100000L
#endif


이름에서 알 수 있듯이 TWI_FREQ 상수가 I2C 통신의 클럭 주파수를 정의한 상수이다. 이 상수를 400000L 로 바꾸면 된다.


#ifndef TWI_FREQ
#define TWI_FREQ 400000L
#endif

이렇게 변경하고 저장한 후 한 가지 과정을 더 거쳐야 한다. 현재 프로젝트의 (과거에 100kHz 상수 값으로 생성되었던)오브젝트 파일들인 wire.cpp.o , twi.c.o 파일들을 제거해야 하는데 이것을 제거하지 않으면 과거에 컴파일된 오브젝트파일을 가지고 링크를 하기 때문에 변경 사항이 적용되지 않는다.

다음의 폴더를 열어보자. (윈도7의 경우임)

C:\ Users \ [user id] \ AppData \ Local \ Temp

이 폴더 하위에 많은 build***********.tmp 폴더는 아두이노 프로젝트가 컴파일되면서 생성되는 임시파일들을 저장하는 폴더이다. 이것들을 모두 삭제한 다음 다시 컴파일하면 변경된 속도가 적용된다. 만약  위와 같이 오브젝트 (임시)파일을 삭제하는 절차가 번거로우면 아예 프로젝트를 새로 생성해서 코드를 붙여넣은 후 컴파일하면 된다.



Posted by 살레시오

댓글을 달아 주세요

 아두이노의 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 살레시오

댓글을 달아 주세요

 아두이노로 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 살레시오

댓글을 달아 주세요