[技术讨论]stm32wb55使用C++开发驱动程序实践之驱动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中有详细描述。头文件设计如下:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值