LINUX设备的驱动比较复杂,要用到单片机这种小型的控制系统,就不需要搞那么复杂了,只要写个中间件,把上层应用和底层驱动完全隔离开,让字符设备的应用使用一个统一的接口,这里设计思路是把一个字符设备当成一个对象,用结构体cdev表示:
typedef struct cdev
{
int devno;
struct file_operations *ops;
void *private_data;
struct list_head list;
}CDEV;
devno为设备号,一个单片机系统的每个设备号都是唯一,考虑到单片机系统很少用到动态安装和卸载驱动或设备,所以设备号都是系统定死了,可以用enum的方式枚举系统的所有设备。
ops为设备用的驱动,这和linux相似:
struct file_operations
{
char (*open)(struct cdev*);
char (*read)(struct cdev*,int,void *,int);
char (*write)(struct cdev*,int,void *,int);
char (*ioctr)(struct cdev*,int,long);
};
只是操作设备的形式不再是文件,而是cdev,对字符设备的读写控制函数,在用到实时操作系统的时候可以添加设备的互斥回调函数lock,unlock。
private_data是底层与应用之间的数据通道,用于传递私有数据,比如在open一个串口设备的时候,需要传递波特率等参数,这样就应用在调用cdevopen的时候就可以把参数通过private_data传递给底层驱动open来设置。
list是一个双向链表,用于管理系统所有设备,可以遍历任何一个设备,这里的主要目的是在打开一个设备的时候,可以通过devno找到对应的cdev。
所以单片机的字符设备基本中间件设计:
static struct list_head cdevhead;
void cdev_init(struct cdev *cdev, struct file_operations *fops)
{
memset(cdev, 0, sizeof(struct cdev));
INIT_LIST_HEAD(&cdev->list);
cdev->ops = fops;
}
int cdev_add(struct cdev *p, int devno)
{
p->devno = devno;
list_add(&p->list,&cdevhead);
return 0;
}
void cdevheadinit()
{
INIT_LIST_HEAD(&cdevhead);
}
CDEV *cdevopen(int devno)
{
struct list_head *cdevnode;
struct cdev *tmp;
__list_for_each(cdevnode, &cdevhead)
{
tmp = list_entry(cdevnode,struct cdev, list);
if(tmp->devno == devno)
{
tmp->ops->open(tmp);
return tmp;
}
}
return NULL;
}
char cdevread(CDEV *dev,char *buf,int size,int offset)
{
return dev->ops->read(dev,offset,buf,size);
}
char cdevwrite(CDEV *dev,char *buf,int size,int offset)
{
return dev->ops->write(dev,offset,buf,size);
}
char cdevioctr(CDEV *dev,int cmd,long arg)
{
return dev->ops->ioctr(dev,cmd,arg);
}
以如果以串口为例,驱动文件为:
char uartOpen(struct cdev *uartdev,char *para)
{
long bt;
char cReturn=0,dateBit,stopBit;
static char openflag=0;
char chek;
if(openflag != 0)
{
return 0;
}
sscanf((const char*)para,"%d,%1d,%1c,%1d",(int *)&bt,(int *)&dateBit,&chek,(int *)&stopBit);
if(uartdev->devno == DEV_UART0)
{
UARTInit(COM1,bt,dateBit,chek,stopBit);
}
openflag =1;
}
char uartRead(struct cdev *uartdev,int offset,char *buf,int len)
{
if(uartdev->devno == DEV_UART0)
{
while(len--)
{
UARTGet(COM1,buf);
buf++;
}
}
}
char uartWrite(struct cdev *uartdev,int offset,char *buf,int len)
{
if(uartdev->devno == DEV_UART0)
{
while(len--)
{
UARTSend(COM1,*buf);
buf++;
}
}
}
char uartIoctr(struct cdev *uartdev,int cmd,long arg)
{
if(uartdev->devno == DEV_UART0)
{
}
}
那么应用可以这样写:
static struct file_operations uartOpt;
struct uartcdev
{
struct cdev *cdevp;
char uartpara[12];
};
struct cdev uart0Dev;
char cdev_uart_test()
{
struct cdev *uart0;
char buffer[100];
char uartpara[12] = "115200,8,n,1";
struct uartcdev uartcdevs;
int i,j=1000;
memcpy(uartcdevs.uartpara,uartpara,sizeof(uartpara));
/
uartcdevs.cdevp = &uart0Dev;
cdev_init(uartcdevs.cdevp,&uartOpt);
uartOpt.read = uartRead;
uartOpt.write = uartWrite;
uartOpt.open = uartOpen;
uartOpt.ioctr = uartIoctr;
cdev_add(uartcdevs.cdevp,DEV_UART0);
uartcdevs.cdevp->private_data = uartpara;
/
uart0 = cdevopen(DEV_UART0);
while(1)
{
cdevwrite(uart0,"abcdedf\r\n",10,0);
if(checkesc()==1)
{
break;
}
}
}
void main()
{
cdevheadinit();
while(1)
{
<span style="white-space:pre"> </span>cdev_uart_test();
}
}
这样就应用和驱动简单隔离了,换单片机平台直接修改驱动代码,应用不需要做任何改动,提高了单片机系统应用跨平台的能力。同时在系统管理上可以把cdev部分封装成库,对上提供应用层的api,对下提供驱动层的api,统一系统接口规范,方便对字符设备的管理。