# 感 - 传感器框架

多数嵌入式操作系统对传感器的抽象采用以物理传感器设备为中心的方式,在应用开发时无法绕过传感器设备的多样性,进而增加了传感器应用的开发难度和周期。这种抽象带来的缺陷在面对一些可以同时采集多种物理量的传感器(如温湿度传感器)时尤为突出,因为应用开发者不得不考虑每种传感器设备的采集数据的能力。

XiUOS的传感器框架以用户为中心,采用了以物理量为中心的抽象方式,在开发应用时只需要考虑所需感知的物理量种类,无需把重点放在实际采集物理量的传感器设备。这种抽象方式有效地隐藏了底层传感器设备的硬件细节,对外提供统一的数据采集接口,可以简化传感器应用与驱动的开发。为实现以物理量为中心的抽象,传感器框架对传感器设备进行两层抽象:

  • 一个物理传感器测量一种物理量的能力(ability)被抽象为一个SensorQuantity结构
  • 一个物理传感器本身被抽象为一个SensorDevice结构

其中SensorQuantity被设计为可以采用类似面向对象的方法、针对不同物理量扩展其数据成员与接口,从而为众多不同性质的物理量实现统一的管理架构。在应用开发的过程中只需要使用对应物理量的SensorQuantity实例,无需关心传感器的硬件细节,从而实现数据采样功能与底层硬件的解耦。

从关联关系上来看,一个SensorQuantity对应一个SensorDevice,一个SensorDevice对应一个或多个SensorQuantity。例如,对于一个可以测量温度与湿度的传感器设备,该设备唯一对应一个SensorDevice结构,而该设备测量温度与湿度的能力分别对应一个SensorQuantity结构。两种数据结构的具体定义如下。

# 1. XiUOS传感器框架的关键数据结构定义和解析

  • struct SensorQuantity结构
struct SensorQuantity {
    char *name;
    enum SensorQuantityType type;
    struct SensorQuantityValue value;
    struct SensorDevice *sdev;

    int32 (*ReadValue)(struct SensorQuantity *quant);

    struct SysDoubleLinklistNode quant_link;
    struct SysDoubleLinklistNode link;
};

name成员是一个可读的名字,用于唯一标识一个SensorQuantity结构。

type成员表示该SensorQuantity可测量的物理量,用一个枚举变量表示:

enum SensorQuantityType {
    SENSOR_QUANTITY_CO2 = 0,
    SENSOR_QUANTITY_TEMP,
    SENSOR_QUANTITY_HUMI,
    SENSOR_QUANTITY_HCHO,
    SENSOR_QUANTITY_CO,
    SENSOR_QUANTITY_PM,
    SENSOR_QUANTITY_VOICE,
    /* ...... */
    SENSOR_QUANTITY_END,
};

value成员记录与该SensorQuantity相关的值,包括结果的小数位数、采集历史的最大值和最小值、国家标准的最大值和最小值、最近一次采集的值。取值时需要注意处理小数点,用变量的值除以十的小数位数次幂。如果某一个值不存在,则为SENSOR_QUANTITY_VALUE_ERROR。

#define SENSOR_QUANTITY_VALUE_ERROR ((uint32)0xffffffff)

struct SensorQuantityValue {
    uint8 decimal_places;   /* The decimal place of the result */
    uint32 last_value;      /* The last read value, if it does not exist, is SENSOR_QUANTITY_VALUE_ERROR */
    uint32 min_value;       /* The minimum read value, if it does not exist, is SENSOR_QUANTITY_VALUE_ERROR */
    uint32 max_value;       /* The maximum read value, if it does not exist, is SENSOR_QUANTITY_VALUE_ERROR */
    uint32 min_std;         /* The minimum standard value, if it does not exist, is SENSOR_QUANTITY_VALUE_ERROR */
    uint32 max_std;         /* The maximum standard value, if it does not exist, is SENSOR_QUANTITY_VALUE_ERROR */
};

sdev成员表示该SensorQuantity所属的SensorDevice结构,其具体定义在下文给出。

ReadValue指向SensorQuantity的解析函数,根据每个传感器模块的协议不同而不同,解析结果会存入value成员的last_value中。

最后,在系统中每种物理量的SensorQuantity会注册到两个链表中,quant_link注册到所属的SensorDevice下,link注册到对应的物理量链表,如二氧化碳浓度SensorQuantity链表、温度SensorQuantity链表等。

  • struct SensorDevice结构
struct SensorDevice {
    char *name;                             /* Name of sensor */
    struct SensorProductInfo *info;         /* Sensor model info */
    struct SensorDone *done;
    int fd;                                 /* File descriptor */
    int status;                             /* Sensor work mode */
    uint8 buffer[SENSOR_RECEIVE_BUFFSIZE];  /* Buffer for read data */

    int ref_cnt;                            /* Reference count */
    DoubleLinklistType quant_list;          /* Sensor quantity link */
    struct SysDoubleLinklistNode link;      /* Sensors link node */
};

name成员记录传感器设备在系统中的名字,用于唯一标识一个SensorDevice结构。

info成员记录传感器设备的一些属性信息,包括传感器的采集能力ability、厂家名vendor name与产品型号model name,其中ability用一个位图表示该传感器设备可以测量的物理量:

#define SENSOR_ABILITY_CO2      ((uint32_t)(1 << SENSOR_QUANTITY_CO2))
#define SENSOR_ABILITY_TEMP     ((uint32_t)(1 << SENSOR_QUANTITY_TEMP))
#define SENSOR_ABILITY_HUMI     ((uint32_t)(1 << SENSOR_QUANTITY_HUMI))
#define SENSOR_ABILITY_HCHO     ((uint32_t)(1 << SENSOR_QUANTITY_HCHO))
#define SENSOR_ABILITY_CO       ((uint32_t)(1 << SENSOR_QUANTITY_CO))
#define SENSOR_ABILITY_PM       ((uint32_t)(1 << SENSOR_QUANTITY_PM))
#define SENSOR_ABILITY_VOICE    ((uint32_t)(1 << SENSOR_QUANTITY_VOICE))
/* ...... */

struct SensorProductInfo {
    uint32_t ability;           /* Bitwise OR of sensor ability */
    const char *vendor_name;
    const char *model_name;
};

done成员包含统一的、类似文件系统的API,用于对传感器进行实际的数据读写。在使用一个传感器前后需要打开(open)/关闭(close)该传感器,read、write分别用与从传感器接收数据与向传感器发送数据,ioctl用于配置传感器工作模式(如主动上报或一问一答):

struct SensorDone {
    int (*open)(struct SensorDevice *sdev);
    int (*close)(struct SensorDevice *sdev);
    x_size_t (*read)(struct SensorDevice *sdev, size_t len);
    int (*write)(struct SensorDevice *sdev, const void *buf, size_t len);
    int (*ioctl)(struct SensorDevice *sdev, int cmd);
};

fd成员表示用于与传感器进行通信的设备的文件描述符。

status成员标识传感器的工作模式:

/* Sensor quantity report mode */
#define SENSOR_DEVICE_PASSIVE    0x00
#define SENSOR_DEVICE_ACTIVE     0x01

buffer成员用来存放从传感器中读取到的数据。

ref_cnt成员记录SensorDevice被打开的次数,当该值为0时传感器将被关闭。

quant_list链表中注册了该传感器支持采集的物理量。

最后,系统中所有注册过的传感器设备被组织成一个双链表,即link成员。

# 2. XiUOS传感器框架驱动开发

以二氧化碳传感器ZG09为例。实现SensorDone中的API,具体实现细节取决于传感器型号,无法实现的API可以置为NONE:

static struct SensorDone done =
{
    SensorDeviceOpen,
    NONE,
    SensorDeviceRead,
    NONE,
    SensorDeviceIoctl,
};

实现SensorQuantity中的ReadValue接口,该接口会解析出当前空气中的二氧化碳浓度。在实现过程中可以使用SensorDone中的接口与传感器进行通信。最后,将传感器设备添加到传感器框架。分别填充SensorDevice与对应物理量的SensorQuantity结构,并依次使用SensorDeviceRegister和SensorQuantityRegister函数将其注册到传感器框架:

static struct SensorDevice zg09;

static void SensorDeviceZg09Init(void)
{
    zg09.name = SENSOR_DEVICE_ZG09;
    zg09.info = &info;
    zg09.done = &done;

    SensorDeviceRegister(&zg09);
}

static struct SensorQuantity zg09_co2;

int Zg09Co2Init(void)
{
    SensorDeviceZg09Init();
    
    zg09_co2.name = SENSOR_QUANTITY_ZG09_CO2;
    zg09_co2.type = SENSOR_QUANTITY_CO2;
    zg09_co2.value.decimal_places = 0;
    zg09_co2.value.max_std = 1000;
    zg09_co2.value.min_std = 350;
    zg09_co2.value.last_value = SENSOR_QUANTITY_VALUE_ERROR;
    zg09_co2.value.max_value = SENSOR_QUANTITY_VALUE_ERROR;
    zg09_co2.value.min_value = SENSOR_QUANTITY_VALUE_ERROR;
    zg09_co2.sdev = &zg09;
    zg09_co2.ReadValue = QuantityRead;

    SensorQuantityRegister(&zg09_co2);

    return 0;
}

# 3. XiUOS传感器框架的使用实例

以二氧化碳传感器为例,编译之前在menuconfig中打开Applications --> Using sensor apps --> Using sensor CO2 apps --> Using sensor ZG09 apps,main函数会调用框架初始化函数FrameworkInit初始化“感”框架并将CO2物理量注册到框架中,传感器应用开发者使用传感器框架提供的API操作传感器:

/* API: find a sensor quantity instance by its name and type */
struct SensorQuantity *SensorQuantityFind(const char *name, enum SensorQuantityType type);

/* API: open/close a sensor quantity instance */
int SensorQuantityOpen(struct SensorQuantity *quant);
int SensorQuantityClose(struct SensorQuantity *quant);

/* API: get current CO2 concentration reading */
int32 SensorQuantityRead(struct SensorQuantity *quant);

在获取数据前需要先获取并打开要使用的物理量,打开后可以随时对传感器数据进行读取,使用完毕必须关闭传感器。完整的使用过程示例如下:

void Co2Zg09(void)
{
    struct SensorQuantity *co2 = SensorQuantityFind(SENSOR_QUANTITY_ZG09_CO2, SENSOR_QUANTITY_CO2);
    SensorQuantityOpen(co2);
    printf("CO2 : %d ppm\n", SensorQuantityRead(co2));
    SensorQuantityClose(co2);
}

int main(void)
{
	UserPrintf("Hello, world!\n");
	FrameworkInit();

    Co2Zg09();

    return 0;
}
Last Updated: 9/26/2021, 8:43:32 PM