(2015.5.17:本日志的內容有所更新,參見《使用Arduino Wire Library讀取溫濕度傳感器AM2321》。)
AM2321是廣州奧松電子生產的數字式溫濕度傳感器。雖是國產品牌,其精度也可以與國外的主流溫濕度傳感IC媲美。
- 尺寸:11.3x7.8x4mm(長x寬x高)
- 封裝:0.05 pitch PTH
- 工作電壓:2.6~5V
- 功耗:測量時0.5mA,休眠狀態10μA
- 接口:I2C,最大速率100kbps;或單總線通訊
- 分辨率:溫度0.1°C,相對濕度0.1%RH
- 精度:出廠前已校正,室溫時溫度誤差+/-0.3°C,相對濕度誤差+/-3%RH(皆典型值)
- 重復性:溫度+/-0.2°C,相對濕度+/-0.1%RH
美中不足:與國外同精度產品相比,AM2321的重復性和漂移指標偏大;功耗偏高;只能手動焊接,給產品量產帶來不便。
電路連接
AM2321支持5V工作,將電源、地、SCL、SDA四個管腳直接與UNO板子的對應管腳相連。對於Arduino UNO,I2C總線的SDA信號線對應A4管腳,SCL時鍾線對應A5管腳。之后,SCL、SDA線需要通過上拉電阻連接到5V電源,電阻值可取4.7k或10k。
功能調試
第一次調試花了不少時間,最終借助示波器才搞定。需留意的幾個問題:
1. I2C地址問題。雖然手冊里寫的地址是0xB8(0b10111000),但實際上器件是采用的7位地址,應該表述成0x5C(0b1011100),代碼中的地址也應寫成0x5C,否則無法通信。
2. 喚醒AM2321時的時序問題。器件不回ACK,且最后一個時鍾下降沿到發stop信號需間隔0.8~3ms。這個時序條件在Arduino的Wire庫中沒有處理的函數,因此只能將A4、A5設置成GPIO,利用bit-banging實現。shiftOut()函數可以實現字節的串行輸出,且速率剛好也是100kbps左右。[注:后面發現即使沒有這個等待時間,傳感器也能正常工作,詭異。]
3. A4、A5管腳在GPIO和硬件I2C之間的功能切換問題。在調用Wire.begin()函數之后,再使用pinMode()或digitalWrite()函數就無效了。發現在Wire.begin()函數中設置了I2C的控制寄存器TWCR,需將TWCR恢復到調用Wire.begin()前的狀態,才可以用GPIO的方式操作A4、A5。
4. 讀返回數據時的時序問題。手冊要求發送地址后,需要等待至少30μs后才能讀取數據。這個功能在Wire庫里也不支持,但直接用庫里的函數(間隔約10μs)讀取,沒有發現有通信錯誤的問題。
5. 傳感器發送數據之后,會觸發下一次溫濕度測量,測量結果供下次數據讀取。因此連續讀取兩次才能獲得當前的溫濕度值,即:第一次讀取的是上一次測量的值,第二次讀取的才是當前測量值。兩次讀取的最小間隔為2秒。
測試代碼

1 /* 2 Measurement of temperature and humidity using the AM2321 sensor 3 Attention: 4 The protocol AM2321 used is not a standard i2c. 5 Bit-banging is used to wake up the sensor, 6 and then i2c functions are used to communicate. 7 Connection: 8 AM2321 UNO 9 VDD <---------> 5V 10 GND <---------> GND 11 SCL <---------> SCL(A5) 12 SDA <---------> SDA(A4) 13 */ 14 15 #include <Wire.h> 16 17 #define ADDRESS_AM2321 0x5C //not 0xB8 18 #define SIGN_WRITE 0x00 19 #define SDA_PIN A4 20 #define SCL_PIN A5 21 22 byte fuctionCode = 0; 23 byte dataLength = 0; 24 byte humiHigh = 0; 25 byte humiLow = 0; 26 byte tempHigh = 0; 27 byte tempLow = 0; 28 byte crcHigh = 0; 29 byte crcLow = 0; 30 31 int humidity = 0; 32 int temperature = 0; 33 unsigned int crcCode = 0; 34 35 byte backupTWCR = 0; 36 37 void setup() 38 { 39 Serial.begin(115200); 40 } 41 42 void loop() 43 { 44 //step 1. wake up the sensor 45 SendWakeUp(); 46 backupTWCR = TWCR; 47 48 //step 2. send command 49 Wire.begin(); 50 Wire.beginTransmission(ADDRESS_AM2321); 51 Wire.write(0x03); 52 Wire.write(0x00); 53 Wire.write(0x04); 54 Wire.endTransmission(); 55 56 delayMicroseconds(1500); 57 58 //step 3. read data, and recover the TWCR register 59 Wire.requestFrom(ADDRESS_AM2321, 8); 60 fuctionCode = Wire.read(); 61 dataLength = Wire.read(); 62 humiHigh = Wire.read(); 63 humiLow = Wire.read(); 64 tempHigh = Wire.read(); 65 tempLow = Wire.read(); 66 crcLow = Wire.read(); 67 crcHigh = Wire.read(); 68 69 //get the result 70 humidity = (humiHigh<<8) | humiLow; 71 temperature = (tempHigh<<8) | tempLow; 72 crcCode = (crcHigh<<8) | crcLow; 73 74 Serial.print(temperature/10.0, 1); Serial.println(" `C"); 75 Serial.print(humidity/10.0, 1); Serial.println(" \%RH"); 76 CheckCRC(); 77 78 //recover the TWCR register, e.g. disable the I2C bus 79 TWCR = backupTWCR; 80 81 delay(4000); 82 } 83 84 void SendWakeUp() 85 { 86 //set pinmode 87 pinMode(SCL_PIN, OUTPUT); 88 pinMode(SDA_PIN, OUTPUT); 89 digitalWrite(SCL_PIN, HIGH); 90 digitalWrite(SDA_PIN, HIGH); 91 92 //issue a START condition 93 delayMicroseconds(5); 94 digitalWrite(SDA_PIN, LOW); 95 delayMicroseconds(5); 96 digitalWrite(SCL_PIN, LOW); 97 delayMicroseconds(5); 98 99 //send ADDRESS+W 100 shiftOut(SDA_PIN, SCL_PIN, MSBFIRST, ((ADDRESS_AM2321<<1) | SIGN_WRITE)); 101 102 //send clock for ack 103 pinMode(SDA_PIN, INPUT_PULLUP);// or INPUT mode 104 delayMicroseconds(5); 105 digitalWrite(SCL_PIN, HIGH); 106 delayMicroseconds(5); 107 digitalWrite(SCL_PIN, LOW); 108 pinMode(SDA_PIN, OUTPUT); 109 digitalWrite(SDA_PIN, LOW); 110 111 delayMicroseconds(1000); 112 113 //issue a STOP condition 114 digitalWrite(SCL_PIN, HIGH); 115 delayMicroseconds(5); 116 digitalWrite(SDA_PIN, HIGH); 117 } 118 119 void CheckCRC() //from the datesheet 120 { 121 byte backValues[] = {fuctionCode, dataLength, humiHigh, \ 122 humiLow, tempHigh, tempLow}; 123 unsigned int crc = 0xFFFF; 124 int i; 125 int len = 6; 126 int j = 0; 127 while (len--) 128 { 129 crc ^= backValues[j]; 130 j++; 131 for (i=0; i<8; i++) 132 { 133 if (crc & 0x01) 134 { 135 crc >>= 1; 136 crc ^= 0xA001; 137 } 138 else 139 { 140 crc >>= 1; 141 } 142 } 143 } 144 if (crc == crcCode) 145 { 146 Serial.println("CRC checked."); 147 } 148 else 149 { 150 Serial.println("CRC Error!"); 151 } 152 }
[注] 后面發現,第一步喚醒時即使直接用Wire庫中的beginTransmission()和endTransmission()函數即可,即使沒有手冊中要求的0.8~3ms等待時間,傳感器也能正常運行。從示波器上看,傳感器不回ACK,硬件I2C等待的時間僅10us左右,卻不影響工作。看來手冊的描述有問題。使用Wire Library標准庫來讀取AM2321,可以參照另一篇日志。
參考資料
奧松官網信息 - AM2321數字溫濕度傳感器
TI - Troubleshooting I2C Bus Protocol 關於I2C調試問題處理的文檔,推薦
拆解國產奧松微小型濕度傳感器AM2321
wangdong/AM2321 - GitHub