- #ifndef MBED_SGP30_H
- #define MBED_SGP30_H
- #include "mbed.h"
- //启用硬件I2C去掉注释,启用GPIO软件模拟模拟I2C保留注释
- //#define HARD_I2C 1
- //SGP30 I2C设备地址
- #define SGP30_ADDR 0x58
- //SGP30 I2C写地址
- #define SGP30_ADDR_WRITE (SGP30_ADDR << 1) // 0xb0
- //SGP30 I2C读地址
- #define SGP30_ADDR_READ ((SGP30_ADDR << 1) + 1) // 0xb1
- /* SGP30初始化空气质量测量寄存器 */
- #deSGP30fine SGP30_CMD_INIT_AIR_QUALITY 0x2003
- /* 开始空气质量测量寄存器 */
- #define SGP30_CMD_MEASURE_AIR_QUALITY 0x2008
- /* SGP30获取串号寄存器 */
- #define SGP30_CMD_GET_SERIAL_ID 0X3682
- //GPIO基类
- class GPIO_I2C {
- //公有成员
- public:
- GPIO_I2C(PinName scl, PinName sda);//构造函数
- ~GPIO_I2C();//析构函数
- void SDA_SET_OUT();//设置SDA输出模式方法
- void SDA_SET_IN();//设置SDA输入模式方法
- void SGP30_SCK_L();//设置SCL输出低电平方法
- void SGP30_SCK_H();//设置SCL输出高电平方法
- void SGP30_SDA_L();//设置SDA输出低电平方法
- void SGP30_SDA_H();//设置SDA输出高电平方法
- int SGP30_READ_SDA();//读取SDA输入电平方法
- void sgp30_delay_us(uint32_t us);//延时微秒方法
- void sgp30_delay_ms(uint32_t nms);//延时毫秒方法
- void IIC_Start(void);//I2C开始信号方法
- void IIC_Stop(void);//I2C停止信号方法
- uint8_t IIC_Wait_Ack(void);//I2C等待应答方法
- void IIC_Ack(void);//I2C应答方法
- void IIC_NAck(void);//I2C无应答方法
- void IIC_Send_Byte(uint8_t txd);//I2C发送1字节数据方法
- uint8_t IIC_Read_Byte(uint8_t ack);//I2C接收1字节数据方法
- //私有成员
- private:
- DigitalOut *_scl_io;//scl操作对象指针成员变量
- DigitalInOut *_sda_io;//sda操作对象指针成员变量
- };
- //SGP30对象继承自GPIO_I2C对象
- class SGP30 : GPIO_I2C {
- public:
- SGP30(PinName scl, PinName sda);//构造函数
- ~SGP30();//析构函数
- int sgp30_init(void);//SGP30初始化方法
- int sgp30_read(uint16_t *CO2, uint16_t *TVOC);//SGP30读取传感器数据方法
- int sgp30_get_serial_id(uint8_t id[6]);//SGP30读取序列化方法
- int sgp30_soft_reset(void);//SGP30软复位方法
- private:
- #ifdef HARD_I2C
- I2C *_i2c;//硬件I2C对象指针
- #endif
- int sgp30_iic_write(uint8_t addr, const uint8_t* buf, uint32_t len);//SGP30 I2C写数据方法
- int sgp30_iic_read(uint8_t addr, uint8_t* buf, uint32_t len);//SGP30 I2C读数据方法
- uint8_t sgp30_checksum(const uint8_t* buf, uint32_t len);//SGP30和校验方法
- };
- #endif
cpp文件实现各个成员函数和构造函数:
- #include "SGP30.h"
- void GPIO_I2C::SGP30_SCK_L() { _scl_io->write(0); }
- void GPIO_I2C::SGP30_SCK_H() { _scl_io->write(1); }
- void GPIO_I2C::SGP30_SDA_L() { _sda_io->write(0); }
- void GPIO_I2C::SGP30_SDA_H() { _sda_io->write(1); }
- int GPIO_I2C::SGP30_READ_SDA() {
- if (_sda_io->read() == 1)
- return 1;
- else
- return 0;
- }
- //构造函数
- GPIO_I2C::GPIO_I2C(PinName scl, PinName sda) {
- #ifndef HARD_I2C
- printf("i2c use %d for scl,%d for sda\r\n", scl, sda);
- //scl对象指针实例化
- _scl_io = new DigitalOut(scl);
- //sda对象指针实例化
- _sda_io = new DigitalInOut(sda);
- //设置sda输出模式
- _sda_io->output();
- //拉高时钟引脚
- _scl_io->write(1);
- //拉高数据引脚
- _sda_io->write(1);
- #endif
- }
- //析构函数
- GPIO_I2C::~GPIO_I2C() {
- //释放资源
- #ifndef HARD_I2C
- delete _scl_io;
- delete _sda_io;
- #endif
- }
- //设置SDA输出模式方法
- void GPIO_I2C::SDA_SET_OUT() {
- //设置SDA输出模式
- _sda_io->output();
- //设置SDA输出低电平
- _sda_io->write(0);
- }
- //设置SDA输入模式方法
- void GPIO_I2C::SDA_SET_IN() {
- //设置SDA无上、下拉
- _sda_io->mode(PullNone);
- //设置SDA输入模式
- _sda_io->input();
- }
- void GPIO_I2C::sgp30_delay_us(uint32_t us) { wait_us(us); }
- void GPIO_I2C::sgp30_delay_ms(uint32_t nms) { wait_ms(nms); }
- void GPIO_I2C::IIC_Start(void) {
- SDA_SET_OUT();
- SGP30_SDA_H();
- SGP30_SCK_H();
- sgp30_delay_us(5);
- SGP30_SDA_L(); // START:when CLK is high,DATA change form high to low
- sgp30_delay_us(6);
- SGP30_SCK_L();
- }
- void GPIO_I2C::IIC_Stop(void) {
- SDA_SET_OUT();
- SGP30_SCK_L();
- SGP30_SDA_L(); // STOP:when CLK is high DATA change form low to high
- SGP30_SCK_H();
- sgp30_delay_us(6);
- SGP30_SDA_H();
- sgp30_delay_us(6);
- }
- uint8_t GPIO_I2C::IIC_Wait_Ack(void) {
- uint16_t tempTime = 0;
- SGP30_SDA_H();
- sgp30_delay_us(1);
- SDA_SET_IN();
- SGP30_SCK_H();
- sgp30_delay_us(1);
- while (SGP30_READ_SDA()) {
- tempTime++;
- wait_ms(10);
- if (tempTime > 250) {
- IIC_Stop();
- return 1;
- }
- }
- SGP30_SCK_L();
- return 0;
- }
- void GPIO_I2C::IIC_Ack(void) {
- SGP30_SCK_L();
- SDA_SET_OUT();
- SGP30_SDA_L();
- sgp30_delay_us(2);
- SGP30_SCK_H();
- sgp30_delay_us(5);
- SGP30_SCK_L();
- }
- void GPIO_I2C::IIC_NAck(void) {
- SGP30_SCK_L();
- SDA_SET_OUT();
- SGP30_SDA_H();
- sgp30_delay_us(2);
- SGP30_SCK_H();
- sgp30_delay_us(5);
- SGP30_SCK_L();
- }
- void GPIO_I2C::IIC_Send_Byte(uint8_t txd) {
- uint8_t t;
- SDA_SET_OUT();
- SGP30_SCK_L();
- for (t = 0; t < 8; t++) {
- if ((txd & 0x80) > 0) // 0x80 1000 0000
- SGP30_SDA_H();
- else
- SGP30_SDA_L();
- txd <<= 1;
- sgp30_delay_us(2);
- SGP30_SCK_H();
- sgp30_delay_us(2);
- SGP30_SCK_L();
- sgp30_delay_us(2);
- }
- }
- uint8_t GPIO_I2C::IIC_Read_Byte(uint8_t ack) {
- uint8_t i, receive = 0;
- SDA_SET_IN();
- for (i = 0; i < 8; i++) {
- SGP30_SCK_L();
- sgp30_delay_us(2);
- SGP30_SCK_H();
- receive <<= 1;
- if (SGP30_READ_SDA())
- receive++;
- sgp30_delay_us(1);
- }
- if (!ack)
- IIC_NAck();
- else
- IIC_Ack();
- return receive;
- }
- SGP30::SGP30(PinName scl, PinName sda) : GPIO_I2C(scl, sda) {
- #ifdef HARD_I2C
- _i2c = new I2C(I2C_SDA, I2C_SCL);
- _i2c->frequency(100000);
- #endif
- }
- SGP30::~SGP30() {}
- int SGP30::sgp30_iic_write(uint8_t addr, const uint8_t *buf, uint32_t len) {
- #ifdef HARD_I2C
- //_i2c->write(addr, buf, len,false);
- _i2c->write((int)addr, (char *)buf, (int)len, false);
- return 0;
- #else
- int i;
- IIC_Start();
- IIC_Send_Byte(addr);
- IIC_Wait_Ack();
- for (i = 0; i < len; i++) {
- IIC_Send_Byte(buf[i]);
- IIC_Wait_Ack();
- }
- IIC_Stop();
- return 0;
- #endif
- }
- int SGP30::sgp30_iic_read(uint8_t addr, uint8_t *buf, uint32_t len) {
- #ifdef HARD_I2C
- _i2c->read((int)addr, (char *)buf, (int)len, false);
- #else
- int i;
- IIC_Start();
- IIC_Send_Byte(addr);
- IIC_Wait_Ack();
- for (i = 0; i < len - 1; i++) {
- buf[i] = IIC_Read_Byte(1);
- }
- buf[i] = IIC_Read_Byte(0); // SGP30接收数据时候的最后一个字节不需要等待ACK
- IIC_Stop();
- #endif
- return 0;
- }
- int SGP30::sgp30_get_serial_id(uint8_t id[6]) {
- uint8_t buf[32];
- uint8_t crc[3];
- buf[0] = (SGP30_CMD_GET_SERIAL_ID & 0XFF00) >> 8;
- buf[1] = (SGP30_CMD_GET_SERIAL_ID & 0X00FF);
- if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
- return -1;
- if (sgp30_iic_read(SGP30_ADDR_READ, buf, 9) < 0)
- return -2;
- crc[0] = buf[2];
- crc[1] = buf[5];
- crc[2] = buf[8];
- id[0] = buf[0];
- id[1] = buf[1];
- id[2] = buf[3];
- id[3] = buf[4];
- id[4] = buf[6];
- id[5] = buf[7];
- if (sgp30_checksum(&id[0], 2) != crc[0] ||
- sgp30_checksum(&id[2], 2) != crc[1] ||
- sgp30_checksum(&id[4], 2) != crc[2])
- return -3;
- return 0;
- }
- uint8_t SGP30::sgp30_checksum(const uint8_t *buf, uint32_t len) {
- const uint8_t Polynomial = 0x31;
- uint8_t Initialization = 0XFF;
- uint8_t i = 0, k = 0;
- while (i < len) {
- Initialization ^= buf[i++];
- for (k = 0; k < 8; k++) {
- if (Initialization & 0X80)
- Initialization = (Initialization << 1) ^ Polynomial;
- else
- Initialization = (Initialization << 1);
- }
- }
- return Initialization;
- }
- int SGP30::sgp30_soft_reset(void) {
- uint8_t cmd = 0X06;
- return sgp30_iic_write(0X00, &cmd, 1);
- }
- int SGP30::sgp30_init(void) {
- uint8_t buf[2];
- // 软件复位
- if (sgp30_soft_reset() < 0)
- return -2;
- // 等待复位完成
- sgp30_delay_ms(50);
- buf[0] = (SGP30_CMD_INIT_AIR_QUALITY & 0XFF00) >> 8;
- buf[1] = (SGP30_CMD_INIT_AIR_QUALITY & 0X00FF);
- // 初始化控制测量参数
- if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
- return -3;
- printf("sgp30 init end\r\n");
- return 0;
- }
- int SGP30::sgp30_read(uint16_t *CO2, uint16_t *TVOC) {
- uint8_t buf[8] = {0};
- buf[0] = (SGP30_CMD_MEASURE_AIR_QUALITY & 0XFF00) >> 8;
- buf[1] = (SGP30_CMD_MEASURE_AIR_QUALITY & 0X00FF);
- // 启动空气质量测量
- if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
- return -1;
- // 等待测量完成
- sgp30_delay_ms(1000);
- // 读取收到的数据
- if (sgp30_iic_read(SGP30_ADDR_READ, buf, 6) < 0)
- return -2;
- // 校验CRC
- if (sgp30_checksum(&buf[3], 2) != buf[5])
- return -3;
- if (CO2 != NULL)
- *CO2 = (buf[0] << 8) | buf[1];
- if (TVOC != NULL)
- *TVOC = (buf[3] << 8) | buf[4];
- return 0;
- }
工作线程sgp30_work_thread设计如下,首先new一个SGP30对象,然后调用初始化方法,读取序列号,读取传感器数据:
- void sgp30_work_thread() {
- int ret;
- SGP30 *sgp30 = new SGP30(I2C_SCL, I2C_SDA);
- DigitalOut led1(LED1);
- uint16_t TVOC = 0, CO2 = 0;
- uint8_t ID[6] = {0};
- while (sgp30->sgp30_init() < 0) {
- printf(" sgp30 init fail\r\n");
- wait_ms(1000);
- }
- if (sgp30->sgp30_get_serial_id(ID) < 0) {
- printf(" sgp30 read serial id failed\r\n");
- } else {
- printf("SGP30 Serial number: ");
- for (int i = 0; i < 6; i++)
- printf("%02X", ID[i]);
- printf("\r\n");
- }
- printf("sgp30 wait air for init");
- fflush(stdout);
- do {
- ret = sgp30->sgp30_read(&CO2, &TVOC);
- if (ret < 0) {
- printf("SGP30 read failed,ret=%d\r\n", ret);
- } else {
- printf("-");
- fflush(stdout);
- }
- } while (TVOC == 0 && CO2 == 400);
- printf("\r\n");
- while (true) {
- ret = sgp30->sgp30_read(&CO2, &TVOC);
- if (ret < 0) {
- printf(" sgp30 read fail,ret=%d\r\n", ret);
- } else {
- printf("CO2:%5dppm TVOC:%5dppb\r\n", CO2, TVOC);
- }
- led1 = !led1;
- ThisThread::sleep_for(1000);
- }
- }
编译结果:STM32WB55拥有1 MB flash,256 KB SRAM,运行C++写的RTOS+蓝牙协议栈毫无压力。
Mbed Studio界面类似vscode,智能提示、自动补全等功能十分完善,代码编辑体验吊打自家的KEIL MDK。
连接stm32wb55开发板运行运行结果:
总结:从编译出的二进制文件大小,RAM使用情况,运行速度可以看出,使用C++开发并不会造成资源消耗过大的情况(主要看交叉编译器的性能),且C++能够和C混合编程,能显著的提高C在处理面向对象类问题上开发效率不足的缺点,尤其是在处理复杂一点的通信协议上如加解密,往往能达到事半功倍的效果。
然而主大多数MCU的flash容量在32KB~2MB左右,SRAM通常低于1MB(不考虑外部扩展),这样就限制C++中的STL模板,运行时多态等会造成内存“爆炸”的高级特性无法使用,如果我们只使用支持class的C++,这样就不必利用C结构体+函数指针的写法来实现面向对象的特性。