随着物联网应用越来越广泛,涌现各种各样的传感器,如温度、气压、重力、陀螺仪、光照传感器等等,这类传感器接口形式、控制方式、数据精度,因为不同厂商而存在一定差异。另一方面,用户在实时调整传感器功耗、灵敏度、数据速率等会因为传感器差异导致大量重复编码工作,降低开发效率。
sensor框架是RT-Thread是针对物联网应用中各类传感器适配的一个框架,目的就是解决上述问题,降低驱动和应用的耦合度。一方面对于应用层来说,统一访问接口(“open/read/write”),提高应用程序的可读性和可移植性。另一方面对于驱动层来说,只需实现sensor的驱动回调接口,降低驱动开发难度,提高驱动程序的可以重用性。RT-Thread 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框架的详细介绍,可以参考RT-Thread官方文档:
https://www.rt-thread.org/document/site/development-guide/sensor/sensor_driver/
bmp180 是 bosch(博世)公司开发的一款环境传感器,支持气压和温度测量。bmp180是一款上市比较久的传感器,很多功能并未支持,如电源模式、数据输出速率等不支持。bmp180支持spi接口和i2c接口访问,本驱动程序使用的是i2c接口。
功能 | 量程 | 精度 |
---|---|---|
气压 | 300—1100hPa | 0.01hPa |
温度 | -40—80℃ | 0.1℃ |
使用ENV工具开启sensor框架组件
RT-Thread Components --->
Device Drivers --->
[*] Using Sensor device drivers
关于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);
};
对于数据读取接口,根据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,
};
sensor设备注册,大体可分为这几个步骤:
一个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);
注:
rt_hw_sensor_register函数name参数是待注册sensor设备名称,sensor 框架会根据不同的传感器类型,在设备名称的基础上添加一个前缀。前缀名称位于“sensor.c”定义,对于气压传感器添加“baro_”,对于温度传感器则添加 “temp_”。因此通过“rt_device_find ”函数查找设备时,应传入带前缀的完整设备名称。
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设备注册。
编写一个测试用于程序,创建一个任务线程周期读取bmp180数据。
配置设备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(外设类)”初始化。
线程处理函数如下
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);
【1】https://github.com/Prry/rtt-bmp180
【1】传感器驱动开发指南
【2】智能家居 DIY 教程连载(1)
RT-Thread的设备驱动框架与linux的设备框架类似,可以对比下linux下bmp180的驱动程序
Linux i2c设备驱动——BMP180