(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