本帖最后由 Simon21ic 于 2014-10-26 14:46 编辑
VSF中事件驱动的命令行界面作为VSF构架的示例代码,最近给一些人看了一下,虽然代码都看得懂,但是绝大部分人都没有能够真正理解实现的原理,这里就开始把这个命令行界面的功能拆分后,一个一个讲解。
首先,讲命令行界面的数据通道,也就是命令行界面怎么接收用户的输入以及怎么把显示的内容发送给屏幕。
命令行界面,输入是用户的按键事件,输出是屏幕显示的字符,在VSF中,使用事件驱动的流模型来实现上述2个数据通道。
直接上结构:
struct vsf_stream_t
{
struct vsf_fifo_t fifo;
// callback_tx is notification for tx end of the stream
// when rx end read the data out, will notify the tx end
struct
{
void *param;
void (*on_out_int)(void *param);
void (*on_connect_rx)(void *param);
} callback_tx;
// callback_rx is notification for rx end of the stream
// when tx end write the data in, will notify the rx end
struct
{
void *param;
void (*on_in_int)(void *param);
void (*on_connect_tx)(void *param);
} callback_rx;
bool tx_ready;
bool rx_ready;
bool overflow;
};
VSF中的流,由一个FIFO和一些事件通知回调函数组成,流模型中,有发送端和接收端,发送端可以往流里写数据,接收端可以从流读取数据。
流结构中,callback_tx和callback_rx就是用于通知对应的一端有他需要的事件。
启动,callback_tx(用于通知发送端)中,on_out_int(_int表示可能在中断中调用)表示接收端读取了数据,on_connect_rx表示接收端连接事件。
对于命令行界面,就需要实现2个流,一个用于接收串口的数据,发送端是串口接收程序,接收端是命令行界面;另一个用于发送数据给串口,发送端是命令行界面,接收端是串口发送程序。
当然,目前实现的是使用CDC来模拟一个串口。
CDC相关代码如下:
static struct vsfsm_state_t *
vsfusbd_CDCData_evt_handler(struct vsfsm_t *sm, vsfsm_evt_t evt)
{
struct vsfusbd_CDC_param_t *param =
(struct vsfusbd_CDC_param_t *)sm->user_data;
struct vsfusbd_device_t *device = param->device;
switch (evt)
{
case VSFSM_EVT_INIT:
param->stream_tx->callback_rx.param = param;
param->stream_tx->callback_rx.on_in_int =
vsfusbd_CDCData_streamtx_callback_on_in_int;
param->stream_tx->callback_rx.on_connect_tx =
vsfusbd_CDCData_streamtx_callback_on_txconn;
param->stream_rx->callback_tx.param = param;
param->stream_rx->callback_tx.on_out_int =
vsfusbd_CDCData_streamrx_callback_on_out_int;
param->stream_rx->callback_tx.on_connect_rx =
vsfusbd_CDCData_streamrx_callback_on_rxconn;
param->out_enable = false;
param->in_enable = false;
break;
case VSFUSBD_CDC_EVT_STREAMTX_ONCONN:
vsfusbd_set_IN_handler(device, param->ep_in,
vsfusbd_CDCData_IN_hanlder);
break;
case VSFUSBD_CDC_EVT_STREAMRX_ONCONN:
vsfusbd_set_OUT_handler(device, param->ep_out,
vsfusbd_CDCData_OUT_hanlder);
vsfsm_post_evt(sm, VSFUSBD_CDC_EVT_STREAMRX_ONOUT);
break;
case VSFUSBD_CDC_EVT_STREAMTX_ONIN:
if (!param->in_enable)
{
param->in_enable = true;
vsfusbd_CDCData_IN_hanlder(param->device, param->ep_in);
}
break;
case VSFUSBD_CDC_EVT_STREAMRX_ONOUT:
if (!param->out_enable &&
(stream_get_free_size(param->stream_rx) >=
device->drv->ep.get_OUT_epsize(param->ep_out)))
{
param->out_enable = true;
device->drv->ep.enable_OUT(param->ep_out);
}
break;
}
return NULL;
}
这个就是CDC数据类的事件处理程序,在INIT事件的时候,初始化流的对应的端(流的另外一端由命令行界面初始化,和CDC无关),并且判断流的另一端是否已经连接,如果已经连接的话,stream驱动会自动补发连接事件。在流的连接事件中,分别初始化对应的发送和接收硬件部分。在流的in和out事件中,进行数据的收发。
由于CDC在连接上电脑后,其实并没有打开串口终端,所以,这个时候如果接收到命令行的数据的话,实际是被PC忽略的。解决方法是,在PC设置串口波特率的时候,调用stream_connect_tx和stream_connect_rx连接上流,流的另外一段收到这个连接事件后,才会开发发送数据。
命令行界面的流处理和CDC中的类似,不过由于命令行界面支持多任务同时运行,并且各个任务都可以发送信息给屏幕显示,所以,在结构上有一些区别。
1. 各个线程都可以发送字符串给屏幕,所以每个线程都有自己的发送数据子线程,要发送数据的话,必须先得到许可(获得临界),之后才可以发送数据,并且不会被其他现成打断
2. 接收的按键事件只能发送给前台线程处理,所以要简单很多
关于命令行中的流处理代码,且等一下回分析。
|