|
- SGP30模块是目前位置接触的比较麻烦的模块,首先芯片手册就比较烦人,没有中文翻译,(文末附加了芯片手册),而且SGP30使用的是I2C通信,所以这就需要了解I2C的通信原理,(文末附加了I2C手册),再者,SGP30模块有较多的模式,需要通过I2C将执行的指令传给模块,才可以选择对应的模式,得到自己想要的结果。每个传输的指令都是由两个字节构成,每个数据的返回也是由两个字节的数据构成,而且还要加一个字节的CRC校验码,(CRC校验来验证数据是否传输正确,芯片生成一个校验码传输给主机,所以必须写一个CRC校验算法,来对两个CRC码进程对比,以此来证明数据传输的正确性)。
- I2C的通信过程,不用自己写,arduino库集成了<Wire.h>来实现,这也省了一部分工作,但是这样也就感觉没有了开发这个模块的乐趣,不过可以看看<Wire>的源码来膜拜一下大神们维护的库。附上源码路径方便查看:/arduino/hardware/arduino/avr/libraries/Wire
- 最重要的一点是SGP30的电气特性,工作电压是1.62v-1.92v(如果没记错的),这个工作电压跟板子上的电压完全不匹配啊,不过问题不大,可以自己链接5v-1.8v的稳压芯片,(但是,焊好稳压芯片,接好电路,烧好代码之后,却发现模块不工作,索性把稳压芯片去掉,将模块接到3.3v的电压上,竟然神奇般的工作正常了)。
[mw_shl_code=arduino,true]#include "Adafruit_SGP30.h"
//#ifdef I2C_DEBUG
Adafruit_SGP30::Adafruit_SGP30() {
}
boolean Adafruit_SGP30::sgp_begin(TwoWire *theWire) {
_i2caddr = SGP30_I2CADDR_DEFAULT;
if (theWire == NULL) {
_i2c = &Wire;
} else {
_i2c = theWire;
}
// 初始化wire库,并且加入到i2c网络,主机只能调用一次
_i2c->begin();
// 0x3682 指令: 获取序列号
uint8_t command[2];
command[0] = 0x36;
command[1] = 0x82;
if (! readWordFromCommand(command, 2, 10, serialnumber, 3))
return false;
// 0x202f 指令: 获取版本号
uint16_t featureset;
command[0] = 0x20;
command[1] = 0x2F;
if (! readWordFromCommand(command, 2, 10, &featureset, 1))
return false;
//Serial.print("Featureset 0x"); Serial.println(featureset, HEX);
// 0x0020 版本号
if (featureset != SGP30_FEATURESET)
return false;
if (! IAQinit())
return false;
return true;
}
// 0x2003 指令: 初始化空气质量
//
boolean Adafruit_SGP30::IAQinit(void) {
uint8_t command[2];
command[0] = 0x20;
command[1] = 0x03;
return readWordFromCommand(command, 2, 10);
}
// 返回 6 字节,包括CRC 2字节
boolean Adafruit_SGP30::IAQmeasure(void) {
uint8_t command[2];
command[0] = 0x20;
command[1] = 0x08;
uint16_t reply[2];
if (! readWordFromCommand(command, 2, 12, reply, 2))
return false;
TVOC = reply[1];
eCO2 = reply[0];
return true;
}
// 0x2015 指令: 获取基准线
// 返回 6 字节,包括CRC 2字节
boolean Adafruit_SGP30::getIAQBaseline(uint16_t *eco2_base, uint16_t *tvoc_base) {
uint8_t command[2];
command[0] = 0x20;
command[1] = 0x15;
uint16_t reply[2];
if (! readWordFromCommand(command, 2, 10, reply, 2))
return false;
*eco2_base = reply[0];
*tvoc_base = reply[1];
return true;
}
// 0x201e 指令: 设置基准线
// 参数 6 字节,包括CRC两字节
//
boolean Adafruit_SGP30::setIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base) {
uint8_t command[8];
command[0] = 0x20;
command[1] = 0x1e;
command[2] = tvoc_base >> 8;
command[3] = tvoc_base & 0xFF;
command[4] = generateCRC(command+2, 2);
command[5] = eco2_base >> 8;
command[6] = eco2_base & 0xFF;
command[7] = generateCRC(command+5, 2);
return readWordFromCommand(command, 8, 10);
}
// 0x2061 指令: 设置温度补偿
// 参数 3 字节 包括 CRC
boolean Adafruit_SGP30::setHumidity(uint32_t absolute_humidity) {
if (absolute_humidity > 256000) {
return false;
}
uint16_t ah_scaled = (uint16_t)(((uint64_t)absolute_humidity * 256 * 16777) >> 24);
uint8_t command[5];
command[0] = 0x20;
command[1] = 0x61;
command[2] = ah_scaled >> 8;
command[3] = ah_scaled & 0xFF;
command[4] = generateCRC(command+2, 2);
return readWordFromCommand(command, 5, 10);
}
boolean Adafruit_SGP30::readWordFromCommand(uint8_t command[], uint8_t commandLength, uint16_t delayms, uint16_t *readdata, uint8_t readlen)
{
uint8_t data;
// 开始传输数据,发送一个i2c开始字符
_i2c->beginTransmission(_i2caddr);
#ifdef I2C_DEBUG
Serial.print("\t\t-> ");
#endif
for (uint8_t i=0; i<commandLength; i++) {
// 向从机发送数据
_i2c->write(command);
#ifdef I2C_DEBUG
Serial.print("0x"); Serial.print(command, HEX); Serial.print(", ");
#endif
}
#ifdef I2C_DEBUG
Serial.println();
#endif
// 结束发送
_i2c->endTransmission();
delay(delayms);
if (readlen == 0)
return true;
// replylen : 从从机请求到的数据的字节数
// sgp30 芯片手册得知: 每个数据由两个字节组成,并且附加一个字节的CRC校验
uint8_t replylen = readlen * (SGP30_WORD_LEN +1);
// requestFrom : 主设备请求从设备一个字节,这个字节可以被主设备用 read() 和 available() 接受
// 参数 : 从设备地址 请求得到的数量
// 返回值 : 从从机接受到的字节数目
if (_i2c->requestFrom(_i2caddr, replylen) != replylen)
return false;
uint8_t replybuffer[replylen];
#ifdef I2C_DEBUG
Serial.print("\t\t<- ");
#endif
for (uint8_t i=0; i<replylen; i++) {
// read() : 用于接受 requestFrom 请求的数据
replybuffer = _i2c->read();
#ifdef I2C_DEBUG
Serial.print("0x"); Serial.print(replybuffer, HEX); Serial.print(", ");
#endif
}
#ifdef I2C_DEBUG
Serial.println();
#endif
for (uint8_t i=0; i<readlen; i++) {
uint8_t crc = generateCRC(replybuffer+i*3, 2);
#ifdef I2C_DEBUG
Serial.print("\t\tCRC calced: 0x"); Serial.print(crc, HEX);
Serial.print(" vs. 0x"); Serial.println(replybuffer[i * 3 + 2], HEX);
#endif
if (crc != replybuffer[i * 3 + 2])
return false;
// success! store it
readdata = replybuffer[i*3];
readdata <<= 8;
readdata |= replybuffer[i*3 + 1];
#ifdef I2C_DEBUG
Serial.print("\t\tRead: 0x"); Serial.println(readdata, HEX);
#endif
}
return true;
}
// generateCRC 进行数据的校验
// data : 数据
// datalen : 数据字长
uint8_t Adafruit_SGP30::generateCRC(uint8_t *data, uint8_t datalen) {
// calculates 8-Bit checksum with given polynomial
uint8_t crc = SGP30_CRC8_INIT;
for (uint8_t i=0; i<datalen; i++) {
crc ^= data;
for (uint8_t b=0; b<8; b++) {
if (crc & 0x80)
crc = (crc << 1) ^ SGP30_CRC8_POLYNOMIAL;
else
crc <<= 1;
}
}
return crc;
}
[/mw_shl_code]
|
|