背景
输入采集设备,就是以硬件的方式采集输入PWM信号的周期和占空比的。在RTT里面是比较晚(2019年提交)才被加进去,这类设备的启用,一般都需要PWM模块参与。根据目前遇到的方案上来看,新塘半导体、ST、NXP都有硬件层面支持输入采集设备的实现。而在RTT目前开源的代码上来看,输入采集设备只有新塘半导体有适配。
框架解析
源码路径components/drivers/misc/rt_inputcapture.c注册入口
#ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops inputcapture_ops = { rt_inputcapture_init, rt_inputcapture_open, rt_inputcapture_close, rt_inputcapture_read, RT_NULL, rt_inputcapture_control }; #endif rt_err_t rt_device_inputcapture_register(struct rt_inputcapture_device *inputcapture, const char *name, void *user_data) { struct rt_device *device; RT_ASSERT(inputcapture != RT_NULL); RT_ASSERT(inputcapture->ops != RT_NULL); RT_ASSERT(inputcapture->ops->get_pulsewidth != RT_NULL); device = &(inputcapture->parent); device->type = RT_Device_Class_Miscellaneous; device->rx_indicate = RT_NULL; device->tx_complete = RT_NULL; inputcapture->ringbuff = RT_NULL; #ifdef RT_USING_DEVICE_OPS device->ops = &inputcapture_ops; #else device->init = rt_inputcapture_init; device->open = rt_inputcapture_open; device->close = rt_inputcapture_close; device->read = rt_inputcapture_read; device->write = RT_NULL; device->control = rt_inputcapture_control; #endif device->user_data = user_data; return rt_device_register(device, name, RT_DEVICE_FLAG_RDONLY | RT_DEVICE_FLAG_STANDALONE); }
整体来说,注册入口和脉冲编码器类似,唯一的区别是多了个ringbuffer,这个ringbuffer的作用,可以在后面的解析中看到。
初始化入口
static rt_err_t rt_inputcapture_init(struct rt_device *dev) { rt_err_t ret; struct rt_inputcapture_device *inputcapture; RT_ASSERT(dev != RT_NULL); ret = RT_EOK; inputcapture = (struct rt_inputcapture_device *)dev; inputcapture->watermark = RT_INPUT_CAPTURE_RB_SIZE / 2; if (inputcapture->ops->init) { ret = inputcapture->ops->init(inputcapture); } return ret; }
这里,有个水位控制的标记,从目前的代码上看,看不出有什么功能,但在后续的代码分析中,会发现此标记是用来确保已经采集到的数据达到判定标准时上报的。
打开入口
static rt_err_t rt_inputcapture_open(struct rt_device *dev, rt_uint16_t oflag) { rt_err_t ret; struct rt_inputcapture_device *inputcapture; RT_ASSERT(dev != RT_NULL); ret = RT_EOK; inputcapture = (struct rt_inputcapture_device *)dev; if (inputcapture->ringbuff == RT_NULL) { inputcapture->ringbuff = rt_ringbuffer_create(sizeof(struct rt_inputcapture_data) * RT_INPUT_CAPTURE_RB_SIZE); } if (inputcapture->ops->open) { ret = inputcapture->ops->open(inputcapture); } return ret; }
打开入口可以看到一个很明显的操作,在注册入口中出现ringbuffer在打开入口中被初始化,初始化的大小为水位标记的2倍。但ringbuffer的作用还是需要后续继续看。
控制入口
static rt_err_t rt_inputcapture_control(struct rt_device *dev, int cmd, void *args) { rt_err_t result; struct rt_inputcapture_device *inputcapture; RT_ASSERT(dev != RT_NULL); result = RT_EOK; inputcapture = (struct rt_inputcapture_device *)dev; switch (cmd) { case INPUTCAPTURE_CMD_CLEAR_BUF: if (inputcapture->ringbuff) { rt_ringbuffer_reset(inputcapture->ringbuff); } break; case INPUTCAPTURE_CMD_SET_WATERMARK: inputcapture->watermark = *(rt_size_t *)args; break; default: result = -RT_ENOSYS; break; } return result; }
控制入口预留了两个操作,一个是清空ringbuffer,在后面的分析中,可以知道这个ringbuffer其实就是输入采集的高低电平的持续时间的buffer,因此可以认为是清除已采集的数据。另一个接口是设置水位,不过个人并不觉得这个实现安全,因为在后面的分析中会发现,这个标记的作用是采集到的数据量够了,就通知应用读取数据,而数据存储于ringbuffer中,ringbuffer是由大小限制的,而这个交由应用设置的接口,并没有做任何数据溢出的判定,若设置下来的数据不符合要求,明显会导致整个系统运行不稳定。
读入口
static rt_ssize_t rt_inputcapture_read(struct rt_device *dev, rt_off_t pos, void *buffer, rt_size_t size) { rt_size_t receive_size; struct rt_inputcapture_device *inputcapture; RT_ASSERT(dev != RT_NULL); inputcapture = (struct rt_inputcapture_device *)dev; receive_size = rt_ringbuffer_get(inputcapture->ringbuff, (rt_uint8_t *)buffer, sizeof(struct rt_inputcapture_data) * size); return receive_size / sizeof(struct rt_inputcapture_data); }
从实现上来看,读入口就是我上面提到的,从ringbuffer中读取数据,读取完毕后将实际读取的数量上报至应用端。
关闭入口
static rt_err_t rt_inputcapture_close(struct rt_device *dev) { rt_err_t ret; struct rt_inputcapture_device *inputcapture; RT_ASSERT(dev != RT_NULL); ret = -RT_ERROR; inputcapture = (struct rt_inputcapture_device *)dev; if (inputcapture->ops->close) { ret = inputcapture->ops->close(inputcapture); } if (ret != RT_EOK) { return ret; } if (inputcapture->ringbuff) { rt_ringbuffer_destroy(inputcapture->ringbuff); inputcapture->ringbuff = RT_NULL; } return ret; }
关闭入口,其实大差不差,就是关闭设备并销毁资源。在输入采集设备框架里,唯一的资源便是ringbuffer,因此销毁资源也就是销毁这一部分。
需要驱动实现的中断处理入口
前面的实现,其实会发现,往ringbuffer塞数据的位置并没有体现,另外,通知应用端数据来了的接口也没体现,而这部分的实现,都是在中断处理函数中实现的。
void rt_hw_inputcapture_isr(struct rt_inputcapture_device *inputcapture, rt_bool_t level) { struct rt_inputcapture_data data; rt_size_t receive_size; if (inputcapture->ops->get_pulsewidth(inputcapture, &data.pulsewidth_us) != RT_EOK) {//获取采集到信号的时间,如果未获取正确的数据,则直接退出中断处理 return; } data.is_high = level; // 设置采集数据的电平标记 if (rt_ringbuffer_put(inputcapture->ringbuff, (rt_uint8_t *)&data, sizeof(struct rt_inputcapture_data)) == 0) { //将数据存储至rinbuffer中 LOG_W("inputcapture ringbuffer doesn't have enough space."); } //统计目前已有的数据量,当数据量超过水位时,通知应用读取数据 receive_size = rt_ringbuffer_data_len(inputcapture->ringbuff) / sizeof(struct rt_inputcapture_data); if (receive_size >= inputcapture->watermark) { /* indicate to upper layer application */ if (inputcapture->parent.rx_indicate != RT_NULL) inputcapture->parent.rx_indicate(&inputcapture->parent, receive_size); } }
总结
至此,我们已经分析完输入采集设备的实现了。通过分析此实现,我们可以得到以下输入采集设备实现框架,后面编写驱动,直接套用此模板实现,便可对接RTT的驱动框架。
#define TIMER_CHANNEL_NUM 4 typedef struct _timer { struct rt_inputcapture_device parent; // 驱动内部数据 } capture_t; // TODO:输入捕获中断处理函数,内部调用接口rt_hw_inputcapture_isr,传递参数为读取到的高低电平标记 static rt_err_t capture_init(struct rt_inputcapture_device *inputcapture) { rt_err_t ret = RT_EOK; // TODO:采集设备实现 // ret = xxxx(); return -(ret); } static rt_err_t capture_open(struct rt_inputcapture_device *inputcapture) { rt_err_t ret = RT_EOK; RT_ASSERT(inputcapture != RT_NULL); // TODO: 打开设备实现 // ret = xxxx(); return ret; } static rt_err_t capture_close(struct rt_inputcapture_device *inputcapture) { rt_err_t ret = RT_EOK; RT_ASSERT(inputcapture != RT_NULL); // TODO: 关闭设备实现 // ret = xxxx(); return ret; } static rt_err_t capture_get_pulsewidth(struct rt_inputcapture_device *inputcapture, rt_uint32_t *pulsewidth_us) { rt_err_t ret = RT_EOK; // TODO: 读取实际采集时间,单位us // *pulsewidth_us = xxxx; return -(ret); } static struct rt_inputcapture_ops capture_ops = { .init = capture_init, .open = capture_open, .close = capture_close, .get_pulsewidth = capture_get_pulsewidth, }; static int timer_capture_device_init(void) { char *timer_device_name[TIMER_CHANNEL_NUM] = { "timer0i0", "timer1i0", "timer2i0", "timer3i0"}; for (int i = 0; i < TIMER_CHANNEL_NUM; i++) { timer_capture[i] = (capture_t *)rt_malloc(sizeof(capture_t)); // 驱动内部数据初始化 rt_device_inputcapture_register(&timer_capture[i]->parent, timer_device_name[i], &timer_capture[i]); } return 0; } INIT_DEVICE_EXPORT(timer_capture_device_init);