当前位置:   article > 正文

[技术讨论]stm32wb55使用C++开发驱动程序实践之驱动SGP30传感器_sgp30传感器程序

sgp30传感器程序

mbed os是ARM以C++为主要开发语言的RTOS,提供Mbed Studio集成开发环境,支持很多开发板,屏蔽了底层驱动,给应用层提供统一的接口。楼主抽空体验了基于mbed os平台使用C++开发驱动程序, 测试使用stm32wb55驱动I2C接口的CO2、TVOC传感器SGP30,使用GPIO模拟I2C和硬件I2C两种方法实现。使用C++更加直观,可读性更强,封装性更强,更方便维护,尤其在多人协同开发优势更明显。
首先设计一个基类GPIO_I2C ,私有成员变量_scl_io和_sda_io用于操作GPIO,提供GPIO输入输出设置、上下拉设置、高低电平输出、读取输入电平等基本操作方法;然后设计一个SGP30类,继承自GPIO_I2C,提供SGP30初始化、序列号获取、传感器数据获取、软复位等方法。关于SGP30的寄存器和驱动时序本帖不再赘述,https://bbs.21ic.com/icview-3273394-1-1.html中有详细描述。头文件设计如下:

  1. #ifndef MBED_SGP30_H
  2. #define MBED_SGP30_H
  3. #include "mbed.h"
  4. //启用硬件I2C去掉注释,启用GPIO软件模拟模拟I2C保留注释
  5. //#define HARD_I2C 1
  6. //SGP30 I2C设备地址
  7. #define SGP30_ADDR 0x58
  8. //SGP30 I2C写地址
  9. #define SGP30_ADDR_WRITE (SGP30_ADDR << 1)      // 0xb0
  10. //SGP30 I2C读地址
  11. #define SGP30_ADDR_READ ((SGP30_ADDR << 1) + 1) // 0xb1
  12. /* SGP30初始化空气质量测量寄存器 */
  13. #deSGP30fine SGP30_CMD_INIT_AIR_QUALITY 0x2003
  14. /* 开始空气质量测量寄存器  */
  15. #define SGP30_CMD_MEASURE_AIR_QUALITY 0x2008
  16. /* SGP30获取串号寄存器  */
  17. #define SGP30_CMD_GET_SERIAL_ID 0X3682
  18. //GPIO基类
  19. class GPIO_I2C {
  20.         //公有成员
  21. public:
  22.   GPIO_I2C(PinName scl, PinName sda);//构造函数
  23.   ~GPIO_I2C();//析构函数
  24.   void SDA_SET_OUT();//设置SDA输出模式方法
  25.   void SDA_SET_IN();//设置SDA输入模式方法
  26.   void SGP30_SCK_L();//设置SCL输出低电平方法
  27.   void SGP30_SCK_H();//设置SCL输出高电平方法
  28.   void SGP30_SDA_L();//设置SDA输出低电平方法
  29.   void SGP30_SDA_H();//设置SDA输出高电平方法
  30.   int SGP30_READ_SDA();//读取SDA输入电平方法
  31.   void sgp30_delay_us(uint32_t us);//延时微秒方法
  32.   void sgp30_delay_ms(uint32_t nms);//延时毫秒方法
  33.   void IIC_Start(void);//I2C开始信号方法
  34.   void IIC_Stop(void);//I2C停止信号方法
  35.   uint8_t IIC_Wait_Ack(void);//I2C等待应答方法
  36.   void IIC_Ack(void);//I2C应答方法
  37.   void IIC_NAck(void);//I2C无应答方法
  38.   void IIC_Send_Byte(uint8_t txd);//I2C发送1字节数据方法
  39.   uint8_t IIC_Read_Byte(uint8_t ack);//I2C接收1字节数据方法
  40. //私有成员
  41. private:
  42.   DigitalOut *_scl_io;//scl操作对象指针成员变量
  43.   DigitalInOut *_sda_io;//sda操作对象指针成员变量
  44. };
  45. //SGP30对象继承自GPIO_I2C对象
  46. class SGP30 : GPIO_I2C {
  47. public:
  48.   SGP30(PinName scl, PinName sda);//构造函数
  49.   ~SGP30();//析构函数
  50.   int sgp30_init(void);//SGP30初始化方法
  51.   int sgp30_read(uint16_t *CO2, uint16_t *TVOC);//SGP30读取传感器数据方法
  52.   int sgp30_get_serial_id(uint8_t id[6]);//SGP30读取序列化方法
  53.   int sgp30_soft_reset(void);//SGP30软复位方法
  54. private:
  55. #ifdef HARD_I2C
  56.   I2C *_i2c;//硬件I2C对象指针
  57.   #endif
  58.   int sgp30_iic_write(uint8_t addr, const uint8_t* buf, uint32_t len);//SGP30 I2C写数据方法
  59.   int sgp30_iic_read(uint8_t addr, uint8_t* buf, uint32_t len);//SGP30 I2C读数据方法
  60.   uint8_t sgp30_checksum(const uint8_t* buf, uint32_t len);//SGP30和校验方法
  61. };
  62. #endif


cpp文件实现各个成员函数和构造函数:

  1. #include "SGP30.h"
  2. void GPIO_I2C::SGP30_SCK_L() { _scl_io->write(0); }
  3. void GPIO_I2C::SGP30_SCK_H() { _scl_io->write(1); }
  4. void GPIO_I2C::SGP30_SDA_L() { _sda_io->write(0); }
  5. void GPIO_I2C::SGP30_SDA_H() { _sda_io->write(1); }
  6. int GPIO_I2C::SGP30_READ_SDA() {
  7.   if (_sda_io->read() == 1)
  8.     return 1;
  9.   else
  10.     return 0;
  11. }
  12. //构造函数
  13. GPIO_I2C::GPIO_I2C(PinName scl, PinName sda) {
  14. #ifndef HARD_I2C
  15.   printf("i2c use %d for scl,%d for sda\r\n", scl, sda);
  16.   //scl对象指针实例化
  17.   _scl_io = new DigitalOut(scl);
  18.   //sda对象指针实例化
  19.   _sda_io = new DigitalInOut(sda);
  20.   //设置sda输出模式
  21.   _sda_io->output();
  22.   //拉高时钟引脚
  23.   _scl_io->write(1);
  24.   //拉高数据引脚
  25.   _sda_io->write(1);
  26. #endif
  27. }
  28. //析构函数
  29. GPIO_I2C::~GPIO_I2C() {
  30. //释放资源       
  31. #ifndef HARD_I2C
  32.   delete _scl_io;
  33.   delete _sda_io;
  34. #endif
  35. }
  36. //设置SDA输出模式方法
  37. void GPIO_I2C::SDA_SET_OUT() {
  38.   //设置SDA输出模式
  39.   _sda_io->output();
  40.   //设置SDA输出低电平
  41.   _sda_io->write(0);
  42. }
  43. //设置SDA输入模式方法
  44. void GPIO_I2C::SDA_SET_IN() {
  45.   //设置SDA无上、下拉
  46.   _sda_io->mode(PullNone);
  47.   //设置SDA输入模式
  48.   _sda_io->input();
  49. }
  50. void GPIO_I2C::sgp30_delay_us(uint32_t us) { wait_us(us); }
  51. void GPIO_I2C::sgp30_delay_ms(uint32_t nms) { wait_ms(nms); }
  52. void GPIO_I2C::IIC_Start(void) {
  53.   SDA_SET_OUT();
  54.   SGP30_SDA_H();
  55.   SGP30_SCK_H();
  56.   sgp30_delay_us(5);
  57.   SGP30_SDA_L(); // START:when CLK is high,DATA change form high to low
  58.   sgp30_delay_us(6);
  59.   SGP30_SCK_L();
  60. }
  61. void GPIO_I2C::IIC_Stop(void) {
  62.   SDA_SET_OUT();
  63.   SGP30_SCK_L();
  64.   SGP30_SDA_L(); // STOP:when CLK is high DATA change form low to high
  65.   SGP30_SCK_H();
  66.   sgp30_delay_us(6);
  67.   SGP30_SDA_H();
  68.   sgp30_delay_us(6);
  69. }
  70. uint8_t GPIO_I2C::IIC_Wait_Ack(void) {
  71.   uint16_t tempTime = 0;
  72.   SGP30_SDA_H();
  73.   sgp30_delay_us(1);
  74.   SDA_SET_IN();
  75.   SGP30_SCK_H();
  76.   sgp30_delay_us(1);
  77.   while (SGP30_READ_SDA()) {
  78.     tempTime++;
  79.     wait_ms(10);
  80.     if (tempTime > 250) {
  81.       IIC_Stop();
  82.       return 1;
  83.     }
  84.   }
  85.   SGP30_SCK_L();
  86.   return 0;
  87. }
  88. void GPIO_I2C::IIC_Ack(void) {
  89.   SGP30_SCK_L();
  90.   SDA_SET_OUT();
  91.   SGP30_SDA_L();
  92.   sgp30_delay_us(2);
  93.   SGP30_SCK_H();
  94.   sgp30_delay_us(5);
  95.   SGP30_SCK_L();
  96. }
  97. void GPIO_I2C::IIC_NAck(void) {
  98.   SGP30_SCK_L();
  99.   SDA_SET_OUT();
  100.   SGP30_SDA_H();
  101.   sgp30_delay_us(2);
  102.   SGP30_SCK_H();
  103.   sgp30_delay_us(5);
  104.   SGP30_SCK_L();
  105. }
  106. void GPIO_I2C::IIC_Send_Byte(uint8_t txd) {
  107.   uint8_t t;
  108.   SDA_SET_OUT();
  109.   SGP30_SCK_L();
  110.   for (t = 0; t < 8; t++) {
  111.     if ((txd & 0x80) > 0) // 0x80  1000 0000
  112.       SGP30_SDA_H();
  113.     else
  114.       SGP30_SDA_L();
  115.     txd <<= 1;
  116.     sgp30_delay_us(2);
  117.     SGP30_SCK_H();
  118.     sgp30_delay_us(2);
  119.     SGP30_SCK_L();
  120.     sgp30_delay_us(2);
  121.   }
  122. }
  123. uint8_t GPIO_I2C::IIC_Read_Byte(uint8_t ack) {
  124.   uint8_t i, receive = 0;
  125.   SDA_SET_IN();
  126.   for (i = 0; i < 8; i++) {
  127.     SGP30_SCK_L();
  128.     sgp30_delay_us(2);
  129.     SGP30_SCK_H();
  130.     receive <<= 1;
  131.     if (SGP30_READ_SDA())
  132.       receive++;
  133.     sgp30_delay_us(1);
  134.   }
  135.   if (!ack)
  136.     IIC_NAck();
  137.   else
  138.     IIC_Ack();
  139.   return receive;
  140. }
  141. SGP30::SGP30(PinName scl, PinName sda) : GPIO_I2C(scl, sda) {
  142. #ifdef HARD_I2C
  143.   _i2c = new I2C(I2C_SDA, I2C_SCL);
  144.   _i2c->frequency(100000);
  145. #endif
  146. }
  147. SGP30::~SGP30() {}
  148. int SGP30::sgp30_iic_write(uint8_t addr, const uint8_t *buf, uint32_t len) {
  149. #ifdef HARD_I2C
  150.   //_i2c->write(addr, buf, len,false);
  151.   _i2c->write((int)addr, (char *)buf, (int)len, false);
  152.   return 0;
  153. #else
  154.   int i;
  155.   IIC_Start();
  156.   IIC_Send_Byte(addr);
  157.   IIC_Wait_Ack();
  158.   for (i = 0; i < len; i++) {
  159.     IIC_Send_Byte(buf[i]);
  160.     IIC_Wait_Ack();
  161.   }
  162.   IIC_Stop();
  163.   return 0;
  164. #endif
  165. }
  166. int SGP30::sgp30_iic_read(uint8_t addr, uint8_t *buf, uint32_t len) {
  167. #ifdef HARD_I2C
  168.   _i2c->read((int)addr, (char *)buf, (int)len, false);
  169. #else
  170.   int i;
  171.   IIC_Start();
  172.   IIC_Send_Byte(addr);
  173.   IIC_Wait_Ack();
  174.   for (i = 0; i < len - 1; i++) {
  175.     buf[i] = IIC_Read_Byte(1);
  176.   }
  177.   buf[i] = IIC_Read_Byte(0); // SGP30接收数据时候的最后一个字节不需要等待ACK
  178.   IIC_Stop();
  179. #endif
  180.   return 0;
  181. }
  182. int SGP30::sgp30_get_serial_id(uint8_t id[6]) {
  183.   uint8_t buf[32];
  184.   uint8_t crc[3];
  185.   buf[0] = (SGP30_CMD_GET_SERIAL_ID & 0XFF00) >> 8;
  186.   buf[1] = (SGP30_CMD_GET_SERIAL_ID & 0X00FF);
  187.   if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
  188.     return -1;
  189.   if (sgp30_iic_read(SGP30_ADDR_READ, buf, 9) < 0)
  190.     return -2;
  191.   crc[0] = buf[2];
  192.   crc[1] = buf[5];
  193.   crc[2] = buf[8];
  194.   id[0] = buf[0];
  195.   id[1] = buf[1];
  196.   id[2] = buf[3];
  197.   id[3] = buf[4];
  198.   id[4] = buf[6];
  199.   id[5] = buf[7];
  200.   if (sgp30_checksum(&id[0], 2) != crc[0] ||
  201.       sgp30_checksum(&id[2], 2) != crc[1] ||
  202.       sgp30_checksum(&id[4], 2) != crc[2])
  203.     return -3;
  204.   return 0;
  205. }
  206. uint8_t SGP30::sgp30_checksum(const uint8_t *buf, uint32_t len) {
  207.   const uint8_t Polynomial = 0x31;
  208.   uint8_t Initialization = 0XFF;
  209.   uint8_t i = 0, k = 0;
  210.   while (i < len) {
  211.     Initialization ^= buf[i++];
  212.     for (k = 0; k < 8; k++) {
  213.       if (Initialization & 0X80)
  214.         Initialization = (Initialization << 1) ^ Polynomial;
  215.       else
  216.         Initialization = (Initialization << 1);
  217.     }
  218.   }
  219.   return Initialization;
  220. }
  221. int SGP30::sgp30_soft_reset(void) {
  222.   uint8_t cmd = 0X06;
  223.   return sgp30_iic_write(0X00, &cmd, 1);
  224. }
  225. int SGP30::sgp30_init(void) {
  226.   uint8_t buf[2];
  227.   // 软件复位
  228.   if (sgp30_soft_reset() < 0)
  229.     return -2;
  230.   // 等待复位完成
  231.   sgp30_delay_ms(50);
  232.   buf[0] = (SGP30_CMD_INIT_AIR_QUALITY & 0XFF00) >> 8;
  233.   buf[1] = (SGP30_CMD_INIT_AIR_QUALITY & 0X00FF);
  234.   // 初始化控制测量参数
  235.   if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
  236.     return -3;
  237.   printf("sgp30 init end\r\n");
  238.   return 0;
  239. }
  240. int SGP30::sgp30_read(uint16_t *CO2, uint16_t *TVOC) {
  241.   uint8_t buf[8] = {0};
  242.   buf[0] = (SGP30_CMD_MEASURE_AIR_QUALITY & 0XFF00) >> 8;
  243.   buf[1] = (SGP30_CMD_MEASURE_AIR_QUALITY & 0X00FF);
  244.   // 启动空气质量测量
  245.   if (sgp30_iic_write(SGP30_ADDR_WRITE, buf, 2) < 0)
  246.     return -1;
  247.   // 等待测量完成
  248.   sgp30_delay_ms(1000);
  249.   // 读取收到的数据
  250.   if (sgp30_iic_read(SGP30_ADDR_READ, buf, 6) < 0)
  251.     return -2;
  252.   // 校验CRC
  253.   if (sgp30_checksum(&buf[3], 2) != buf[5])
  254.     return -3;
  255.   if (CO2 != NULL)
  256.     *CO2 = (buf[0] << 8) | buf[1];
  257.   if (TVOC != NULL)
  258.     *TVOC = (buf[3] << 8) | buf[4];
  259.   return 0;
  260. }



工作线程sgp30_work_thread设计如下,首先new一个SGP30对象,然后调用初始化方法,读取序列号,读取传感器数据:

  1. void sgp30_work_thread() {
  2.   int ret;
  3.   SGP30 *sgp30 = new SGP30(I2C_SCL, I2C_SDA);
  4.   DigitalOut led1(LED1);
  5.   uint16_t TVOC = 0, CO2 = 0;
  6.   uint8_t ID[6] = {0};
  7.   while (sgp30->sgp30_init() < 0) {
  8.     printf(" sgp30 init fail\r\n");
  9.     wait_ms(1000);
  10.   }
  11.   if (sgp30->sgp30_get_serial_id(ID) < 0) {
  12.     printf(" sgp30 read serial id failed\r\n");
  13.   } else {
  14.     printf("SGP30 Serial number: ");
  15.     for (int i = 0; i < 6; i++)
  16.       printf("%02X", ID[i]);
  17.     printf("\r\n");
  18.   }
  19.   printf("sgp30 wait air for init");
  20.   fflush(stdout);
  21.   do {
  22.     ret = sgp30->sgp30_read(&CO2, &TVOC);
  23.     if (ret < 0) {
  24.       printf("SGP30 read failed,ret=%d\r\n", ret);
  25.     } else {
  26.       printf("-");
  27.       fflush(stdout);
  28.     }
  29.   } while (TVOC == 0 && CO2 == 400);
  30.   printf("\r\n");
  31.   while (true) {
  32.     ret = sgp30->sgp30_read(&CO2, &TVOC);
  33.     if (ret < 0) {
  34.       printf(" sgp30 read fail,ret=%d\r\n", ret);
  35.     } else {
  36.       printf("CO2:%5dppm TVOC:%5dppb\r\n", CO2, TVOC);
  37.     }
  38.     led1 = !led1;
  39.     ThisThread::sleep_for(1000);
  40.   }
  41. }

编译结果:STM32WB55拥有1 MB flash,256 KB SRAM,运行C++写的RTOS+蓝牙协议栈毫无压力。


Mbed Studio界面类似vscode,智能提示、自动补全等功能十分完善,代码编辑体验吊打自家的KEIL MDK。


连接stm32wb55开发板运行运行结果:

总结:从编译出的二进制文件大小,RAM使用情况,运行速度可以看出,使用C++开发并不会造成资源消耗过大的情况(主要看交叉编译器的性能),且C++能够和C混合编程,能显著的提高C在处理面向对象类问题上开发效率不足的缺点,尤其是在处理复杂一点的通信协议上如加解密,往往能达到事半功倍的效果。
然而主大多数MCUflash容量在32KB~2MB左右,SRAM通常低于1MB(不考虑外部扩展),这样就限制C++中的STL模板,运行时多态等会造成内存爆炸的高级特性无法使用,如果我们只使用支持classC++,这样就不必利用C结构体+函数指针的写法来实现面向对象的特性。:
---------------------
作者:dql2015
链接:https://bbs.21ic.com/icview-3278652-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/604075
推荐阅读
相关标签
  

闽ICP备14008679号