【RT-Thread】基于RT-Thread sensor框架的BMP180气压传感器应用

1 RT-Thread sensor框架

  随着物联网应用越来越广泛,涌现各种各样的传感器,如温度、气压、重力、陀螺仪、光照传感器等等,这类传感器接口形式、控制方式、数据精度,因为不同厂商而存在一定差异。另一方面,用户在实时调整传感器功耗、灵敏度、数据速率等会因为传感器差异导致大量重复编码工作,降低开发效率。

  sensor框架是RT-Thread是针对物联网应用中各类传感器适配的一个框架,目的就是解决上述问题,降低驱动和应用的耦合度。一方面对于应用层来说,统一访问接口(“open/read/write”),提高应用程序的可读性和可移植性。另一方面对于驱动层来说,只需实现sensor的驱动回调接口,降低驱动开发难度,提高驱动程序的可以重用性。RT-Thread sensor框架如下图所示。

在这里插入图片描述

  • 对于应用层,是标准RTT 设备接口“ open/close/read/write/control”
  • 对于驱动层,提供ops接口“fetch_data/control”,函数实体在驱动实现
  • 对于module,一个传感器可能同时支持多种物理特性检测,存在数据耦合,可以通过module模块实现。如bmp180支持气压和温度,可以注册两个“module”

1.1 sensor 设备描述

  sensor设备在继承标准RT-Thread标准设备的基础上,增加sensor设备的描述信息、配置信息、数据结构及控制接口。

/* sensor 设备 */
struct rt_sensor_device
{
    struct rt_device             parent;    /* 标准RT-Thread设备 */

    struct rt_sensor_info        info;      /* sensor描述信息 */
    struct rt_sensor_config      config;    /* sensor配置信息 */

    void                        *data_buf;  /* The buf of the data received */
    rt_size_t                    data_len;  /* The size of the data received */

    const struct rt_sensor_ops  *ops;       /* sensor控制接口 */

    struct rt_sensor_module     *module;    /* sensor耦合模块 */

    rt_err_t (*irq_handle)(rt_sensor_t sensor);  /* Called when an interrupt is generated, registered by the driver */
};
typedef struct rt_sensor_device *rt_sensor_t;
/* sensor 数据结构 */
struct rt_sensor_data
{
    rt_uint32_t         timestamp;          /* 时间戳 */
    rt_uint8_t          type;               /* 数据类型 */
    union									/* 数据内容, 共用体 */
    {
        struct sensor_3_axis acce;          /* Accelerometer.       unit: mG          */
        struct sensor_3_axis gyro;          /* Gyroscope.           unit: mdps        */
        struct sensor_3_axis mag;           /* Magnetometer.        unit: mGauss      */
        rt_int32_t           temp;          /* Temperature.         unit: dCelsius    */
        rt_int32_t           humi;          /* Relative humidity.   unit: permillage  */
        rt_int32_t           baro;          /* Pressure.            unit: pascal (Pa) */
        rt_int32_t           light;         /* Light.               unit: lux         */
        rt_int32_t           proximity;     /* Distance.            unit: centimeters */
        rt_int32_t           hr;            /* Heart rate.          unit: bpm         */
        rt_int32_t           tvoc;          /* TVOC.                unit: permillage  */
        rt_int32_t           noise;         /* Noise Loudness.      unit: HZ          */
        rt_uint32_t          step;          /* Step sensor.         unit: 1           */
        rt_int32_t           force;         /* Force sensor.        unit: mN          */
    } data;
};

在这里插入图片描述
sensor除了标准设备的属性外,增加自己特有的信息:

  • 描述信息,描述一个传感的类型、厂商、量程等
  • 配置信息,用户可配置的工作模式、量程范围、数据输出速率
  • 数据结构,传感器有效数据信息
  • 控制接口,函数实体由驱动层实现,最终实现传感器数据读取和配置管理功能

关于sensor框架的详细介绍,可以参考RT-Thread官方文档:
https://www.rt-thread.org/document/site/development-guide/sensor/sensor_driver/


2 基于sensor框架的BMP180驱动

  bmp180 是 bosch(博世)公司开发的一款环境传感器,支持气压和温度测量。bmp180是一款上市比较久的传感器,很多功能并未支持,如电源模式、数据输出速率等不支持。bmp180支持spi接口和i2c接口访问,本驱动程序使用的是i2c接口。

功能 量程 精度
气压 300—1100hPa 0.01hPa
温度 -40—80℃ 0.1℃

2.1 sensor组件支持

使用ENV工具开启sensor框架组件

RT-Thread Components --->
  Device Drivers --->
      [*] Using Sensor device drivers

2.2 ops接口实现

  关于ops接口的抽象,RT-Thread使用“struct rt_sensor_ops”描述,位于“sensor.h”中。

struct rt_sensor_ops
{
    rt_size_t (*fetch_data)(struct rt_sensor_device *sensor, void *buf, rt_size_t len);
    rt_err_t (*control)(struct rt_sensor_device *sensor, int cmd, void *arg);
};
  • “fetch_data”是获取传感器数据接口。
  • “control” 作用是通过控制命令控制传感器

  对于数据读取接口,根据sensor设备配置信息,可以支持三种模式读取数据,分别是轮询读取、中断读取、从FIFO中读取,前提是该传感器支持这三种模式。

#define  RT_SENSOR_MODE_NONE           (0)
#define  RT_SENSOR_MODE_POLLING        (1)  /* One shot only read a data */
#define  RT_SENSOR_MODE_INT            (2)  /* TODO: One shot interrupt only read a data */
#define  RT_SENSOR_MODE_FIFO           (3)  /* TODO: One shot interrupt read all fifo data */

  bmp180只支持轮询读取模式。bmp180支持气压和温度检测,根据不同的设备类型返回对应的数据。

static rt_size_t bmp180_polling_get_data(rt_sensor_t psensor, struct rt_sensor_data *sensor_data)
{
	long x1, x2, b5, b6, x3, b3, p;
	unsigned long b4, b7;
	short temperature=0;
	long ut,up,pressure=0;
	struct bmp180_dev *dev = RT_NULL;
	struct bmp180_calc *param = RT_NULL;
	
	ut = bmp180_read_ut(psensor);
	up = bmp180_read_up(psensor);
	
	dev = (struct bmp180_dev*)psensor->parent.user_data;/* bmp180 private data */
	param = &dev->calc_param;	/* get calc param */
	
	/* temperature calc */
	x1 = (((long)ut - (long)param->ac6)*(long)param->ac5) >> 15;
  	x2 = ((long)param->mc << 11) / (x1 + param->md);
  	b5 = x1 + x2;
  	temperature = ((b5 + 8) >> 4);

	/* pressure calc */
	b6 = b5 - 4000;
	x1 = (param->b2 * (b6 * b6)>>12)>>11;
	x2 = (param->ac2 * b6)>>11;
	x3 = x1 + x2;
	b3 = (((((long)param->ac1)*4 + x3)<<0) + 2)>>2;
	
	x1 = (param->ac3 * b6)>>13;
	x2 = (param->b1 * ((b6 * b6)>>12))>>16;
	x3 = ((x1 + x2) + 2)>>2;
	b4 = (param->ac4 * (unsigned long)(x3 + 32768))>>15;
	b7 = ((unsigned long)(up - b3) * (50000>>0));
	if (b7 < 0x80000000)
	{
		p = (b7<<1)/b4;
	}
	else
	{
		p = (b7/b4)<<1;
	}
	x1 = (p>>8) * (p>>8);
	x1 = (x1 * 3038)>>16;
	x2 = (-7357 * p)>>16;
	pressure = p+((x1 + x2 + 3791)>>4);	
	
	if(psensor->info.type == RT_SENSOR_CLASS_BARO)
	{/* actual barometric */
	  	sensor_data->type = RT_SENSOR_CLASS_BARO;
		sensor_data->data.baro = pressure;
		sensor_data->timestamp = rt_sensor_get_ts();
	}
	else if(psensor->info.type == RT_SENSOR_CLASS_TEMP)
	{/* actual temperature */
		sensor_data->type = RT_SENSOR_CLASS_TEMP;
		sensor_data->data.temp = temperature;
		sensor_data->timestamp = rt_sensor_get_ts();
	}
	else
	{
		return 0;
	}
    return 1;
}

static rt_size_t bmp180_fetch_data(struct rt_sensor_device *psensor, void *buf, rt_size_t len)
{
    RT_ASSERT(buf);
	RT_ASSERT(psensor);
	
	//if(psensor->parent.open_flag & RT_DEVICE_FLAG_RDONLY)
	if(psensor->config.mode == RT_SENSOR_MODE_POLLING)
	{
        return bmp180_polling_get_data(psensor, buf);
    }
    return 0;
}

  对于控制接口,sensor框架支持如下几种控制命令,前提是该传感器支持。

#define  RT_SENSOR_CTRL_GET_ID         (0)  /* 读设备ID */
#define  RT_SENSOR_CTRL_GET_INFO       (1)  /* 获取设备信息 */
#define  RT_SENSOR_CTRL_SET_RANGE      (2)  /* 设置传感器测量范围 */
#define  RT_SENSOR_CTRL_SET_ODR        (3)  /* 设置传感器数据输出速率,unit is HZ */
#define  RT_SENSOR_CTRL_SET_MODE       (4)  /* 设置工作模式 */
#define  RT_SENSOR_CTRL_SET_POWER      (5)  /* 设置电源模式 */
#define  RT_SENSOR_CTRL_SELF_TEST      (6)  /* 自检 */

  bmp180内置设备ID,实现该接口;电源模式、数据输出速率、自检不支持。

static rt_err_t bmp180_control(struct rt_sensor_device *psensor, int cmd, void *args)
{
	rt_err_t	ret = RT_EOK;
    rt_uint8_t 	*chip_id;
	
    RT_ASSERT(psensor);

    switch (cmd)
    {
    	/* read bmp180 id */
        case RT_SENSOR_CTRL_GET_ID:
		  	chip_id = (rt_uint8_t*)args;
	       	ret = bmp180_read_regs(psensor, BMP_REG_CHIP_ID, chip_id, 1);
        break;

        default:
        break;
	}
    return ret;
}

  bmp180 ops回调接口实体实现。

static struct rt_sensor_ops bmp180_ops =
{
    bmp180_fetch_data,
    bmp180_control,
};

2.3 设备注册

  sensor设备注册,大体可分为这几个步骤:

  • 创建“rt_sensor_t”结构体指针
  • “rt_sensor_t ”指针分配内存并初始化配置信息
  • 调用注册接口注册到内核
  • 传感器设备私有信息初始化

设备初始化

  一个sensor的信息,包括描述信息和配置信息,描述信息由“struct rt_sensor_info”结构体描述,包括了传感器类型、厂商名称、型号、量程范围等。配置信息由“struct rt_sensor_config”结构体描述。通过bmp180的初始化信息可以直观体会。

bmp180配置信息:

	rt_memset(sensor_baro, 0x0, sizeof(struct rt_sensor_device));
    sensor_baro->info.type       = RT_SENSOR_CLASS_BARO;	/* 传感器类型 */
    sensor_baro->info.vendor     = RT_SENSOR_VENDOR_BOSCH;	/* 传感器厂商 */
    sensor_baro->info.model      = "bmp180_baro";			/* 传感器型号 */
    sensor_baro->info.unit       = RT_SENSOR_UNIT_PA;		/* 数据单位 */
    sensor_baro->info.intf_type  = RT_SENSOR_INTF_I2C;		/* 访问接口 */
    sensor_baro->info.range_max  = 110000;	/* 1Pa */		/* 最大测量值 */
    sensor_baro->info.range_min  = 30000;					/* 最小测量值 */
    sensor_baro->info.period_min = 100;						/* 刷新周期 */

    rt_memcpy(&sensor_baro->config, cfg, sizeof(struct rt_sensor_config));/* 配置信息 */
    sensor_baro->ops = &bmp180_ops;						 	/* ops接口*/
    sensor_baro->module = module;							/* 耦合模块 */

注:
module 的定义是解决底层有耦合的两个传感器,bmp180支持气压(barometric)和温度(temperature)测量,因此采用了module机制。


设备注册

  本驱动程序使用的是i2c接口,因此需使用RT-Thread i2c设备框架。另外,bmp180内置气压和温度校准参数,该系列参数出厂时已固化,只需初始化时读取一次即可。我们把i2c设备信息和校准参数抽象为bmp180设备的私有数据。

struct bmp180_calc	/* bmp180校准参数 */
{
	short ac1;
	short ac2;
	short ac3;
	short b1;
	short b2;
	short mb;
	short mc;
	short md;
	unsigned short ac4;
	unsigned short ac5;
	unsigned short ac6;	
};
struct bmp180_dev	/* bmp180私有数据 */
{
	struct bmp180_calc calc_param;
	struct rt_i2c_bus_device *i2c_bus;
};

  sensor设备继承于RT-Thread标准设备,我们可以使用标准设备的私有数据指针( void *user_data)保存bmp180的私有数据,使用时强制转换为bmp180私有数据格式。通过sensor设备注册函数“rt_hw_sensor_register”源码可知道,函数第4个参数“data”即是sensor设备私有参数注册。

/*
 * sensor register
 */
int rt_hw_sensor_register(rt_sensor_t sensor,
                          const char              *name,
                          rt_uint32_t              flag,
                          void                    *data)
{
    rt_int8_t result;
    rt_device_t device;
    RT_ASSERT(sensor != RT_NULL);

    char *sensor_name = RT_NULL, *device_name = RT_NULL;
	........
    device->user_data   = data;		/* 私有数据注册 */
	........
    return RT_EOK;
}

bmp180设备注册:

struct bmp180_dev 	*bmp180 = RT_NULL;

bmp180 = rt_calloc(1, sizeof(struct bmp180_dev));
if(bmp180 == RT_NULL)
{
  	LOG_E("malloc memory failed\r\n");
	ret = -RT_ERROR;
	goto __exit;
}
ret = rt_hw_sensor_register(sensor_baro, name, RT_DEVICE_FLAG_RDWR, (void*)bmp180);

注:

  1. rt_hw_sensor_register函数name参数是待注册sensor设备名称,sensor 框架会根据不同的传感器类型,在设备名称的基础上添加一个前缀。前缀名称位于“sensor.c”定义,对于气压传感器添加“baro_”,对于温度传感器则添加 “temp_”。因此通过“rt_device_find ”函数查找设备时,应传入带前缀的完整设备名称。

  2. RT-Thread 系统设备名称最大支持8个字符长度,超过8个字符会被截掉部分,如“baro_temp180”在msh/finsh显示为“baro_bmp”。


设备私有信息初始化

  该部分主要是与具体传感器相关,如传感器初始状态寄存设置、参数读取等。对于bmp180,则是读取气压值和温度值的校准参数到内存中,用于后续换算得到实际气压值和温度值。

/* bmp180 read calc param */
ret = bmp180_read_regs(sensor_baro, BMS_CAL_AC1, bmbuf, 22);
if(ret == RT_EOK)
{
	bmp180->calc_param.ac1 = (bmbuf[0]<<8)|bmbuf[1];
	bmp180->calc_param.ac2 = (bmbuf[2]<<8)|bmbuf[3];
	bmp180->calc_param.ac3 = (bmbuf[4]<<8)|bmbuf[5];
	bmp180->calc_param.ac4 = (bmbuf[6]<<8)|bmbuf[7];
	bmp180->calc_param.ac5 = (bmbuf[8]<<8)|bmbuf[9];
	bmp180->calc_param.ac6 = (bmbuf[10]<<8)|bmbuf[11];
	bmp180->calc_param.b1 = (bmbuf[12]<<8)|bmbuf[13];
	bmp180->calc_param.b2 = (bmbuf[14]<<8)|bmbuf[15];
	bmp180->calc_param.mb = (bmbuf[16]<<8)|bmbuf[17];
	bmp180->calc_param.mc = (bmbuf[18]<<8)|bmbuf[19];
	bmp180->calc_param.md = (bmbuf[20]<<8)|bmbuf[21];
}

  执行完上述步骤,即可完成一个bmp180 sensor设备注册。


3 应用程序

  编写一个测试用于程序,创建一个任务线程周期读取bmp180数据。

  • 通过“rt_device_control”读取bmp180 ID
  • 通过“rt_device_read”读取bmp180气压值和温度值
  • 周期输出到msh/finsh终端

3.1 初始化注册设备

  • 配置设备i2c总线名称、地址,及设备名称等。

  • 调用RT-Thread初始化框架执行注册

static int rt_hw_bmp180_port(void)
{
    struct rt_sensor_config cfg;
    	
	cfg.intf.dev_name = "i2c1"; 		/* i2c bus */
    cfg.intf.user_data = (void *)0x77;	/* i2c slave addr */
    rt_hw_bmp180_init("180", &cfg);		/* bmp180 */

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_bmp180_port);

注:
调用RT-Thread初始化组件初始化bmp180注册时,需注意先后顺序;应在“INIT_DEVICE_EXPORT”之后(因为需先初始化i2c驱动),“INIT_APP_EXPORT”(线程注册)之前,否则导致bmp180初始化失败或者线程读取不到数据。可以使用“INIT_COMPONENT_EXPORT”(组件类)或者“INIT_ENV_EXPORT(外设类)”初始化。


3.2 创建读取线程

线程处理函数如下

static void read_baro_entry(void *parameter)
{
    rt_device_t baro_dev = RT_NULL, temp_dev = RT_NULL;
    struct rt_sensor_data baro_data,temp_data;
    rt_size_t res0 = 0, res1 = 1;
	rt_uint8_t chip_id;
	
    baro_dev = rt_device_find("baro_bmp180");
    if (baro_dev == RT_NULL)
    {
	  	 rt_kprintf("not found baro_bmp180 device\r\n");
        return;
    }

    if (rt_device_open(baro_dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK)
    {
        rt_kprintf("open baro_180 failed\r\n");
        return;
    }

	temp_dev = rt_device_find("temp_bmp180");
    if (temp_dev == RT_NULL)
    {
	  	 rt_kprintf("not found temp_bmp180 device\r\n");
        return;
    }

    if (rt_device_open(temp_dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK)
    {
        rt_kprintf("open temp_bmp180 failed\r\n");
        return;
    }
	
	rt_device_control(baro_dev, RT_SENSOR_CTRL_SET_ODR, (void *)(1));/* 1Hz read */
    rt_device_control(temp_dev, RT_SENSOR_CTRL_SET_ODR, (void *)(1));/* 1Hz read */
	
	rt_device_control(temp_dev, RT_SENSOR_CTRL_GET_ID, (void*)&chip_id);/* get ID */
	rt_kprintf("bmp180 chip ID [0x%X]\n", chip_id);
	while (1)
    {
        res0 = rt_device_read(baro_dev, 0, &baro_data, 1);
		res0 = rt_device_read(temp_dev, 0, &temp_data, 1);
        if (res0==0 || res1==0)
        {
            rt_kprintf("read data failed! result is %d,%d\n", res0, res1);
            rt_device_close(baro_dev);
			rt_device_close(temp_dev);
            return;
        }
        else
        {
        	rt_kprintf("baro[%dPa],temp[%d.%dC],timestamp[%d]\r\n", baro_data.data.baro, 
					   temp_data.data.temp/10, temp_data.data.temp%10,
					   temp_data.timestamp);
        }

        rt_thread_delay(500);
    }
}

创建线程

static int baro_read_sample(void)
{
    rt_thread_t baro_thread;

    baro_thread = rt_thread_create("baro_r", read_baro_entry, RT_NULL, 1024,
                                     RT_THREAD_PRIORITY_MAX / 2, 20);
    if (baro_thread != RT_NULL)
    {
        rt_thread_startup(baro_thread);
    }

    return RT_EOK;
}
INIT_APP_EXPORT(baro_read_sample);

3.3 msh/finsh测试

查看设备注册
在这里插入图片描述
执行预期
在这里插入图片描述

4 代码仓库

【1】https://github.com/Prry/rtt-bmp180


5 参考

【1】传感器驱动开发指南

【2】智能家居 DIY 教程连载(1)


RT-Thread的设备驱动框架与linux的设备框架类似,可以对比下linux下bmp180的驱动程序
Linux i2c设备驱动——BMP180

原创文章 128 获赞 147 访问量 36万+

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/104833495