背景
输入采集设备,就是以硬件的方式采集输入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);
我要赚赏金
