其实这个在之前已经发过,见https://forum.eepw.com.cn/thread/388069/1,但由于那一篇由于内容比较多,因此框架适配部分略过了,仅仅是贴出了适配后的完整代码,因此在此进一步的解析一下当时的适配过程。
适配过程
首先,经过v1版sensor框架的整理,我们已经获得了v1版sensor框架适配的模板,如下:
#include <drivers/sensor.h> struct sensor_device { // driver Param }; static rt_size_t _polling_get_data(rt_sensor_t sensor, struct rt_sensor_data *data) { struct sensor_device *dev = sensor->parent.user_data; if (sensor->info.type == RT_SENSOR_CLASS_NONE) { // TODO: Read and fill data to struct rt_sensor_data *data return 1; } return 0; } static rt_size_t _get_data(rt_sensor_t sensor, struct rt_sensor_data *data) { struct sensor_device *dev = sensor->parent.user_data; if (sensor->info.type == RT_SENSOR_CLASS_NONE) { // TODO: Read and fill data to struct rt_sensor_data *data return 1; } return 0; } static rt_size_t fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len) { RT_ASSERT(buf); if (sensor->config.mode == RT_SENSOR_MODE_POLLING) return _polling_get_data(sensor, buf); else return _get_data(sensor, buf); } static rt_err_t control(struct rt_sensor_device *sensor, int cmd, void *args) { rt_err_t result = RT_EOK; switch (cmd) { case RT_SENSOR_CTRL_GET_ID: // TODO: get device id // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_SET_RANGE: // TODO: set test range // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_SET_ODR: // TODO: set frequency // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_SET_MODE: // TODO: set work mode // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_SET_POWER: // TODO: set power mode // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_SELF_TEST: // TODO: process self test // result = xxxx(sensor); break; default: return -RT_ERROR; } return result; } static struct rt_sensor_ops sensor_ops = { fetch_data, control }; int rt_hw_init(const char *name, struct rt_sensor_config *cfg) { rt_int8_t result; rt_sensor_t sensor = RT_NULL; struct sensor_device *dev; // TODO: dev init /* sensor register */ sensor = rt_calloc(1, sizeof(struct rt_sensor_device)); if (sensor == RT_NULL) goto __exit; sensor->info.type = RT_SENSOR_CLASS_NONE; // Set real type sensor->info.vendor = RT_SENSOR_VENDOR_UNKNOWN; // Set real vendor sensor->info.model = "xxxx"; // set real model name sensor->info.unit = RT_SENSOR_UNIT_NONE; // set to real unit flag sensor->info.intf_type = RT_SENSOR_INTF_SPI; // Set interface type sensor->info.range_max = SENSOR_RANGE_MAX; // Set to range max sensor->info.range_min = SENSOR_RANGE_MIN; // Set to range min sensor->info.period_min = 50; // Set frequency rt_memcpy(&sensor->config, cfg, sizeof(struct rt_sensor_config)); sensor->ops = &sensor_ops; result = rt_hw_sensor_register(sensor, name, RT_DEVICE_FLAG_RDONLY, dev); if (result != RT_EOK) { goto __exit; } return RT_EOK; __exit: if (sensor) rt_free(sensor); // TODO: dev deinit return -RT_ERROR; }
在整理出这份文档后,后续的适配基本上就属于填空题了。
模块驱动代码编写
在填空前,需要实现rrh62000的驱动代码,这部分没别的招,只能自己对着规格书一点一点的写(瑞萨做这方案的团队在美国,技术支持几乎没有,github上的驱动代码又和瑞萨的框架绑定过深,无法顺利剥离)。
v1传感器注册函数实现
前期开发,需要聚焦,因此在适配时最好不要一次适配多个传感器功能,在这里,我优先适配了温度传感器的部分。
首先,按照框架调用INIT_DEVICE_EXPORT(rrh62000_device_register)。
其次,在这个函数中,先实现读取传感器关键信息,确保传感器受控。
最后,按照框架所需信息,一个一个的修改传递给传感器注册函数rt_hw_sensor_register的信息,并调用注册接口实现传感器的注册。
static struct rt_sensor_ops sensor_ops = { fetch_data, control }; struct sensor_device * rrh62000_param_init(void) { struct sensor_device *dev; struct rrh62000_firmwareversion fwVersion; struct rrh62000_algoversion algoVersion; dev = rt_calloc(1, sizeof(struct sensor_device)); if(!dev) { goto exit; } rt_memset(dev, 0x00, sizeof(struct sensor_device)); dev->dev = (struct rt_i2c_bus_device *)rt_device_find("i2c1"); if(dev->dev == RT_NULL) { goto exit; } if(rrh62000_read_firmware_verison(dev->dev, &fwVersion) != RT_EOK) { goto exit; } if(rrh62000_read_algoritm_verison(dev->dev, &algoVersion) != RT_EOK) { goto exit; } rt_kprintf("rrh62000 firmware version %d.%d\n\r", fwVersion.major, fwVersion.minor); rt_kprintf("rrh62000 algoritm version %d.%d.%d\n\r", algoVersion.major, algoVersion.minor, algoVersion.patch); return dev; exit: if(dev) rt_free(dev); return RT_NULL; } int rt_hw_init(const char *name, struct rt_sensor_config *cfg) { rt_int8_t result; rt_sensor_t sensor = RT_NULL; struct sensor_device *dev; dev = rrh62000_param_init(); if(dev == RT_NULL) { goto __exit; } /* sensor register */ sensor = rt_calloc(1, sizeof(struct rt_sensor_device)); if (sensor == RT_NULL) goto __exit; sensor->info.type = RT_SENSOR_CLASS_TEMP; // Set real type sensor->info.vendor = RT_SENSOR_VENDOR_UNKNOWN; // Set real vendor sensor->info.model = name; // set real model name sensor->info.unit = RT_SENSOR_UNIT_DCELSIUS; // set to real unit flag sensor->info.intf_type = RT_SENSOR_INTF_I2C; // Set interface type sensor->info.range_max = 0xFFFF; // Set to range max sensor->info.range_min = 0x0000; // Set to range min sensor->info.period_min = 50; // Set frequency rt_memcpy(&sensor->config, cfg, sizeof(struct rt_sensor_config)); sensor->ops = &sensor_ops; result = rt_hw_sensor_register(sensor, name, RT_DEVICE_FLAG_RDONLY, dev); if (result != RT_EOK) { goto __exit; } return RT_EOK; __exit: if (sensor) rt_free(sensor); if(dev) rt_free(dev); return -RT_ERROR; } static int rrh62000_device_register(void) { struct rt_sensor_config cfg = { .mode = RT_SENSOR_MODE_POLLING, .power = RT_SENSOR_POWER_DOWN, }; rt_hw_init("rrh62000", &cfg); return RT_EOK; } INIT_DEVICE_EXPORT(rrh62000_device_register);
读取数据接口实现
struct sensor_device { rt_bool_t openCount; struct rt_i2c_bus_device * dev; struct rrh62000_data data; }; static rt_ssize_t _get_data(rt_sensor_t sensor, struct rt_sensor_data *data) { struct sensor_device *dev = sensor->parent.user_data; rt_bool_t isDataReady = RT_FALSE; if (sensor->info.type == RT_SENSOR_CLASS_TEMP) { if((rrh62000_read_data_status(dev->dev, &isDataReady) == RT_EOK) && (isDataReady)) { rrh62000_read_measured_value(dev->dev, &dev->data); } } if (sensor->info.type == RT_SENSOR_CLASS_TEMP) { data->data.temp = (dev->data.temperature.integer_part << 16) + dev->data.temperature.decimal_part; data->timestamp = rt_sensor_get_ts(); return 1; } return 0; } static rt_ssize_t fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len) { RT_ASSERT(buf); if (sensor->config.mode == RT_SENSOR_MODE_POLLING) return _get_data(sensor, buf); else return 0; }
注册函数实现后,后面就需要实现两个关键的函数了,一个时读取数据的接口fetch_data,一个是对传感器各项功能设置的control接口。读取数据的实现如下:
需要注意的是,由于读数据只有一个接口,因此在实现时,需根据当前传感器的工作状态调用不同的读取实现,由于RRH62000只支持轮询读取的方式,因此读取实现就相对简单的多,仅仅需要读出数据并转换成struct rt_sensor_data格式的数据即可,对此结构体观察会发现,其实此结构体内部是个共用体,因此向上传递的数据需要按照温度传感器数据的格式上报,并记录读取数据的时间戳,若能读到传感器的时间使用传感器的时间,读不到,则使用rtt提供的时间。
控制接口实现
控制接口需要实现的功能不少,有设置工作状态的命令set_power,有设置频率的接口set_odr,有设置两成的接口set_range,还有设置工作模式的接口set_mode。对于常见的传感器来说,这些接口都需要实现,但是呢,RRH62000除了传感器的开关,其他几个接口都只能使用默认参数去使用,因此适配起来就相对简单点了,仅仅需要适配set_power接口即可。
static rt_err_t _set_power(struct rt_sensor_device *sensor, void *args) { struct sensor_device *dev = sensor->parent.user_data; rt_uint8_t power = *(uint8_t *)args; //fsp_err_t ret; rt_err_t ret; rt_bool_t isTVOCCLean = RT_FALSE; int count = 70; // 70s if(power == RT_SENSOR_POWER_DOWN) { if(dev->openCount != 0) { dev->openCount--; } } else { if(dev->openCount == 0) { do { if(rrh62000_read_TVOC_sensor_clean_status(dev->dev, &isTVOCCLean) == RT_EOK) { if(isTVOCCLean == RT_FALSE) { rt_thread_mdelay(1000); count--; } } }while((isTVOCCLean == RT_FALSE) && (count--)); if(count == 0) { goto RET; } ret = rrh62000_reset(dev->dev); if(ret != RT_EOK) { rt_kprintf("open failed %d\n\r",ret); goto RET; } rt_thread_mdelay(1000); ret = rrh62000_moving_average_set(dev->dev, 1); ret = rrh62000_fan_speed_set(dev->dev, 60); } dev->openCount++; } return RT_EOK; RET: return -RT_ERROR; } static rt_err_t control(struct rt_sensor_device *sensor, int cmd, void *args) { rt_err_t result = RT_EOK; switch (cmd) { case RT_SENSOR_CTRL_GET_ID: // TODO: get device id // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_GET_INFO: // TODO: get info // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_SET_RANGE: // TODO: set test range // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_SET_ODR: // TODO: set frequency // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_SET_MODE: // TODO: set work mode // result = xxxx(sensor, args); break; case RT_SENSOR_CTRL_SET_POWER: // TODO: set power mode result = _set_power(sensor, args); break; case RT_SENSOR_CTRL_SELF_TEST: // TODO: process self test // result = xxxx(sensor); break; default: return -RT_ERROR; } return result; }
测试接口编写
对于格式接口,个人比较喜欢写成命令行命令的方式调用,这样可以通过传参的方式灵活验证验证接口的有效性,在这里也不例外,我写了如下代码:
#if 1 #define RRH_TEMP_DEVICE_NAME "temp_rrh" void rrh6200_temp_read(void) { rt_device_t dev = rt_device_find(RRH_TEMP_DEVICE_NAME); rt_err_t result; rt_uint32_t len; struct rt_sensor_data data; if(!dev) { rt_kprintf("No device name %s\n\r", RRH_TEMP_DEVICE_NAME); return; } result = rt_device_open(dev,RT_DEVICE_FLAG_RDONLY); if(result != RT_EOK) { rt_kprintf("Open %s Fail\n\r", RRH_TEMP_DEVICE_NAME); return; } len = rt_device_read(dev, 0 ,&data,1); if(len) { rt_kprintf("Temp %d.%d \n\r", data.data.temp >> 16, data.data.temp & 0x0000FFFF); } result = rt_device_close(dev); if(result != RT_EOK) { rt_kprintf("Close %s Fail\n\r", RRH_TEMP_DEVICE_NAME); return; } } MSH_CMD_EXPORT(rrh6200_temp_read, rrh62000 temprate sample); #endif
其中:MSH_CMD_EXPORT便是把此函数编译成命令行代码的实现入口。
总结
编译验证部分,在之前发布的帖子中已经实现,在这里就不再说明。后续增加新的传感器类型,其实也是一样的套路,唯一的区别是,control和fetch_data中需要识别当前的传感器,根据传感器进行功能的实现。