2011-08-24

NXT與Arduino多重I2C Master之實作測試




Arduino
有許多的I2C週邊或擴充板,如何來讓NXT也能使用這些週邊? 若是能夠藉由Arduino主板來存取,那就大大的提昇了這些週邊的使用範圍與彈性。

對於這樣的應用,首先要確認的是:Wire.h library能否支援多個Master的運作

更明確的說法是:Arduino主板是否可以同時身兼MasterSalve



當它與NXT進行通訊時須擔任Salve,但當要存取I2C週邊時又得作為Master




幾週前由Digital Cave這個網站,查到了以下對於Multi Master的說明資料:

The I2C protocol
is a multi-master protocol; however, the Wire library in the Arduino
distribution does not make it clear on how to implement this functionality.



 the documentation on the Arduino webpage
is slightly misleading in how
you would go about doing it. The documentation as currently written states that
for Wire.begin(address), the address is optional and 'if not specified, join
the bus as a master'. However, what is not stated is that if you transmit something from a device which has already
joined as a slave, it will assume the role of Master when it sends the data to another slave.


以上算是 Wire.h libraryMissing Manual

為了驗證以上的說法,延續之前NXTArduino之間的I2C通訊測試,實作了以下的測試Case

(1)
使用兩部UNO,都模擬成I2C Slave裝置,其Address分別是0x310x33,而uploadsketch功能皆相同:

當它們接收到0x42+0x01/0x02的控制指令資料時,就會去開/關接在D13LED(0x31為紅色,0x33為綠色),此外,又多增加了一個0x43的控制指令,附加的指令資料包含:另一部Slave的位址、要傳送的控制指令值以及LED的開關指令值。

(2)
將以上兩部UNONXT串接成I2C通訊架構。




首先由NXTLowspeedwrite()傳送[0x62, 0x43, 0x33, 0x42,
0x01]
的資料,


這時候0x31 Slave會接收到[0x43, 0x33, 0x42,
0x01]
的控制指令,


當它處理完onReceive() Handler之後,

接著它就會扮演起Master角色,send()[0x33, 0x42, 0x01]的資料,

這時候0x33 Slave就會接收到[0x42, 0x01]的資料而去執行開啟LED(綠色)的指令。

所以依據以上的測式情境,0x31 Slave就是身兼SlaveMaster的雙重角色。



NXT
新增的Function與呼叫程式碼:

void sendTransferCommand(const byte
deviceAddress,

                       const byte
deviceRegister,const byte anotherDevice,

                       const byte
anotherRegister, const byte anotherCommand)

{



     byte I2C_req_buf[];




     ArrayBuild(I2C_req_buf,
deviceAddress, deviceRegister,

              anotherDevice,
anotherRegister, anotherCommand);




     while(I2CCheckStatus(I2C_PORT)
== STAT_COMM_PENDING);




     LowspeedWrite(I2C_PORT,
0, I2C_req_buf);




}



sendTransferCommand(ARDUINO_RED_SEND_ADDRESS,

                   ARDUINO_TRANSFER_CMD,

                   ARDUINO_GREEN_ADDRESS,

                   ARDUINO_COMMAND_REG,
LED_ON);




Arduino
新增的Function與呼叫程式碼:

void loop()




{




     if
(isTransfer) {




       Wire.beginTransmission(anotherAddress);




      
Wire.send(anotherRegister);




      
Wire.send(anotherCommand);




      
Wire.endTransmission();




      
isTransfer = false;




     } 




}

void receiveEvent(int howMany)




{ 




   
requestRegister = Wire.receive();




    if
(requestRegister == 0x42) {




      
requestCommand = Wire.receive();




       if
(requestCommand == LED_ON) {




          digitalWrite(LED_PIN, HIGH);




       }




       else
if (requestCommand == LED_OFF) {




              
digitalWrite(LED_PIN, LOW);




           
}




    }




    else if
(requestRegister == 0x43) {




           
anotherAddress= Wire.receive();




           
anotherRegister = Wire.receive();




           
anotherCommand= Wire.receive();




           
isTransfer = true;




         }




}




2011-08-21

Mindstorms NXT與Arduino之間的I2C通訊





建立NXTArduino之間的I2C通訊,主要重點在於瞭解:

NXT Master
端程式如何傳送
以及 Arduino Slave端如何接收與回應


I2C Address
對於7-bits定址當以一個byte(8-bits)來傳送時,需要將位址放在較高的7bits(bit 1-bit 7)bit 0(LSB)是用來通知Slave目前Master是要傳送(0)或讀取(1),舉例:
Slave ArduinoI2C 位址為0x31(Wire.begin(0x31))時,NXTSlave 8-bits I2C位址就須設成0x62(0x31 << 1)

NXC I2C Buffer
NXCLowspeedWrite(port,retrunLength, I2C_input_buffer )來啟始並傳送資料至Slave,傳送的資料存放在I2C_input_buffer的陣列中,第一個元素所存的就是Slave8-bits address,第二個元素及以後的資料就是要傳送至Slave的資料,SlaveArduino會觸發Wire.onReceive (handler)所註冊的handler接收來自NXT所傳送的資料
而當NXCLowspeedRead(port, bufferLength,I2C_output_buffer)要求Slave Arduino回傳資料時,會觸發Wire.onRequest (handler)所註冊的handler所以要回傳給NXT的資料需要在onRequest
 handler
使用Wire. send()傳送

測試案例
Arduino模擬成NXTI2C裝置,除了可以回傳裝置資訊外,另外若寫入0x42再加上LED的控制指令,可以開關Arduino上紅色與綠色的LED

就如同前面所提,由onReceive handler接收來自NXC input buffer的資料,若判斷接收到的第一個byte0x42則表示後續還須接收一個byteLED控制指令,而若為要求回應裝置資訊的指令(0x00, 0x08, 0x10)時,則在NXT傳送 LowspeedRead()時於onRequest()handler中回傳資料。
在進行這個測試時,原來使用的接線方式與兩部Arduino之間的I2C通訊相同,但就是一直連不上,後來才發現問題出在Pull-up,拿掉後就一切OK
更改後的接線圖如下:


2011-08-20

兩部Arduino之間的I2C通訊



Arduino I2C通訊使用A4(SDA)A5(SCL)兩個腳位,API library則是<Wire.h>

兩部Arduino之間的I2C通訊,需要有一方擔任Master負責主控資料通訊,另一方擔任Slave負責接收及回應來自Master的需求,而Master是依據SlaveI2C Bus中的位址來傳送與要求回應資料。

<Wire.h>Slave Arduino使用begin(address)來宣告加入I2C bus的位址,之後Master Arduino就可以使用:

(1)
beginTransmission(address)+send(data)+endTransmission()
傳送資料至Slave Arduino

(2)
requestFrom(address,count)
Slave Arduino要求回應資料。

Slave Arduino
處理來自Master
Arduino
的資料通訊需求是透過:


(1)
onReceive(handler) 註冊處理當接收到來自Master Arduino send(data)所傳送資料時會觸發的處理程序(handler)

(2)
onRequest(handler) 註冊處理當接收到來自Master Arduino requestFrom(address,count)要求回應資料時會觸發的處理程序(handler)






I2C bus address




<Wire.h>使用7-bits定址,參考有些I2C裝置在data sheet所載其實是8-bits定址,所以呼叫<Wire.h>函數時須要右移一個bit(除以2),像MindsensorsHiTechnic裝置的手冊都是8-bits定址,還好是如果使用<NXTI2CDevice.h>會處理掉定址問題。

不過,如果將Arduino作為I2C SlaveNXT作為I2C Master,定址問題就很重要。






Arduino to Arduino I2C通訊線路接線




兩部Arduino之間的I2C通訊接線方式除了連接兩方的SDA(A4)SCL(A5)之外,還須對這兩條線加pull-up,我是使用4.7k歐姆,另外還須要common
ground
,總之就是要確保電壓準位。


以下就是接線圖:








而測試案例則以Slave Arduino模擬NXTI2C register layout,由Master Arduino讀取其裝置資訊




Slave Arduino程式



#include
<Wire.h>




#define I2C_SLAVE_ADDRESS 0x31




uint8_t softwareVersion[9] =
"V1.00   ";




uint8_t vendorID[9] = "Arduino
";




uint8_t deviceID[9= "UNO     ";




byte  requestRegister;




void setup()




{ 




    
Wire.begin(I2C_SLAVE_ADDRESS);




    
Wire.onReceive(receiveEvent);




    
Wire.onRequest(requestEvent);




}






void loop() { }




 




void receiveEvent(int howMany)




{




   
requestRegister = Wire.receive();




}




 




void requestEvent()




{




   
if (requestRegister == 0x00) {




      
Wire.send(softwareVersion, 8);




   
}




   
else if (requestRegister == 0x08) {




            Wire.send(vendorID, 8);




        
}




        
else if (requestRegister == 0x10) {




                 Wire.send(deviceID, 8);




              }




}






Master端程式



#include
<Wire.h>




const byte ARDUINO_SLAVE= 0x31;




const byte SOFTWARE_VERSION = 0x00;




const byte VENDOR_ID= 0x08;




const byte DEVICE_ID= 0x10;




const byte NUM_OF_BYTES= 8;




 




void setup()




{




   
Serial.begin(9600);




   
Wire.begin();   




}




 




void loop()




{     




   
requestFromSlave(SOFTWARE_VERSION);




   
delay(1000);




   
requestFromSlave(VENDOR_ID);




   
delay(1000);




   
requestFromSlave(DEVICE_ID);




   
delay(1000);




}




 




void requestFromSlave(const byte
slaveRegister)




{




   
byte nbytesReady= 0;




   
char strBuffer[9] = "       
";




 




   
Wire.beginTransmission(ARDUINO_SLAVE);




   
Wire.send(slaveRegister);




   
Wire.endTransmission();




   




   
Wire.requestFrom(ARDUINO_SLAVE, NUM_OF_BYTES);




   
nbytesReady = Wire.available();




   




   
for  (byte xi=0;
xi





        
strBuffer[xi] = Wire.receive();




   
Serial.println(strBuffer);          




}