?? 此方法实现一种类似linux驱动的框架,用于对硬件设备管理,对每个硬件设备独立封装起来,成为一个个模组。该框架存在:1.统一接口;2.移植扩展性好等优点。 相对应的,因为接口统一,在一些情况下使用起来不方便。 ?
一、定义驱动结构体
/* 驱动设备 */
typedef struct
{
char name[64];
enum device_class_type type; //设备类型
unsigned short flag; //设备参数
unsigned short open_flag; //设备打开标志
const struct _file_operations *fops;//设备操作方法
} device_t, *device_pt;
/* 驱动设备的操作 */
typedef struct _file_operations
{
//设备通用接口
int (*open)(device_pt dev, unsigned short oflag);
int (*close)(device_pt dev);
int (*read)(device_pt dev, unsigned int pos, void *buffer, unsigned int size);
int (*write)(device_pt dev, unsigned int pos, const void *buffer, unsigned int size);
int (*ioctl)(device_pt dev, int cmd, void *args);
} file_operations_t;
二、以串口为例,实现驱动模型
????????新建serial.c文件,定义串口的驱动设备。
//定义所有接口函数
static int stm32Usart1Open(device_pt dev, unsigned short oflag);
static int stm32Usart1Close(device_pt dev);
static int stm32Usart1Read(device_pt dev, unsigned int pos, void *buffer, unsigned int size);
static int stm32Usart1Write(device_pt dev, unsigned int pos, const void *buffer, unsigned int size);
static int stm32Usart1Ioctl(device_pt dev, int cmd, void *args);
//定义接口结构体
static const file_operations_t stm32_usart1_fops =
{
.open = stm32Usart1Open,
.close = stm32Usart1Close,
.read = stm32Usart1Read,
.write = stm32Usart1Write,
.ioctl = stm32Usart1Ioctl,
};
//定义串口驱动设备
static device_t stm32_usart1_dev =
{
.name = "serial/usart1",
.open_flag = 0,
.fops = &stm32_usart1_fops,
};
三、驱动管理
????????新建module.c文件,用于管理所有的驱动设备。?
3.1?定义一个驱动设备的数组。
static device_pt g_device_array[MAX_DEVICE_ARRAY];
????????我们把所有的设备驱动都添加到该数组上。
3.2?驱动注册函数
int moduleRegister(device_pt dev)
{
assert_param(dev != NULL);
if (g_cur_device_index >= MAX_DEVICE_ARRAY - 1)
return R_EFULL;
g_device_array[g_cur_device_index] = dev;
g_cur_device_index++;
return R_EOK;
}
????????? 首先,我们需要把stm32_usart1_dev这个结构体变量添加到驱动设备数组g_device_array中,这样我们可以从数组获取到串口的设备。这样做好处一点是,serial.c里所有的函数,变量都可以定义成static静态的,上层使用不需要直接包含.c里的函数,所有驱动只需通过module.c里的接口可间接的调用。如此我们如果需要移植串口驱动,或者扩展其他驱动,我们只需要把serial.c文件移植,获取新建新驱动的.c文件,无需修改上层代码。
3.3 其他接口函数
/*
* 描述 :打开对应的驱动
* 参数 :
* [in] name 驱动名
* [in] oflag 驱动的状态
* 返回 :
* 驱动设备的描述符
*/
int open(const char *name, unsigned short oflag)
{
assert_param(name != NULL);
int i;
for (i = 0; i < g_cur_device_index; i++)
{
if (strcmp(g_device_array[i]->name, name) == 0)
{
//检测是否已经打开
if (g_device_array[i]->open_flag != 0)
return i;
//调用对应驱动的open函数
if (g_device_array[i]->fops->open(g_device_array[i], oflag) != R_EOK)
{
return R_ERROR;
}
//修改参数
g_device_array[i]->flag = oflag;
g_device_array[i]->open_flag = 1;
return i;
}
}
return R_ERROR;
}
/*
* 描述 :关闭对应的驱动
* 参数 :
* [in] fd 设备描述符
* 返回 :
* 错误描述表
*/
int close(int fd)
{
if (fd == -1 || fd >= MAX_DEVICE_ARRAY)
{
return R_ERROR;
}
//本来就是关闭
if (g_device_array[fd]->open_flag == 0)
return R_EOK;
int ret;
ret = g_device_array[fd]->fops->close(g_device_array[fd]);
g_device_array[fd]->open_flag = 0;
return ret;
}
/*
* 描述 :读取驱动的缓存数据
* 参数 :
* [in] fd 设备描述符
* [in] pos 根据具体设备函数的定义
* [out] buffer 数据指针
* [in] size 读取的大小
* 返回 :
* 错误描述表
*/
int read(int fd, unsigned int pos, void *buffer, unsigned int size)
{
if (fd == -1 || fd >= MAX_DEVICE_ARRAY)
{
return R_ERROR;
}
if (g_device_array[fd]->flag && DEVICE_FLAG_RDONLY == 0)
{
return R_ENOSUPPORT;
}
int ret;
ret = g_device_array[fd]->fops->read(g_device_array[fd], pos, buffer, size);
return ret;
}
/*
* 描述 :写入驱动数据
* 参数 :
* [in] fd 设备描述符
* [in] pos 根据具体设备函数的定义
* [in] buffer 数据指针
* [in] size 写入的大小
* 返回 :
* 错误描述表
*/
int write(int fd, unsigned int pos, const void *buffer, unsigned int size)
{
if (fd == -1 || fd >= MAX_DEVICE_ARRAY)
{
return R_ERROR;
}
if (g_device_array[fd]->flag && DEVICE_FLAG_WRONLY == 0)
{
return R_ENOSUPPORT;
}
int ret;
ret = g_device_array[fd]->fops->write(g_device_array[fd], pos, buffer, size);
return ret;
}
/*
* 描述 :调用驱动的命令
* 参数 :
* [in] fd 设备描述符
* [in] cmd 命令
* [in] args 传入参数
* 返回 :
* 错误描述表
*/
int ioctl(int fd, int cmd, void *args)
{
if (fd == -1 || fd >= MAX_DEVICE_ARRAY)
{
return R_ERROR;
}
int ret;
ret = g_device_array[fd]->fops->ioctl(g_device_array[fd], cmd, args);
return ret;
}
四、设备自动注册
????????linux驱动是使用了module_init的方式。我本来也是想使用这样方式,后面发现有更方便的办法。在serial.c文件里,添加函数
/*
* 描述 :串口注册函数,在main函数之前自动调用
* 参数 :
* 无
* 返回 :
* 无
*/
__attribute__((constructor)) void stm32Usart1Init(void)
{
moduleRegister(&stm32_usart1_dev);
}
????????使用__attribute__((constructor))修饰过的函数,系统执行main()函数之前调用该函数,这样我们就可以让系统自动把stm32_usart1_dev变量添加到g_device_array数组上。注意是main()函数之前,一些系统还没有完全初始化完成,不能使用执行与系统相关的操作。
五、使用
????????上层使用串口驱动过程。
5.1?打开驱动
????????所有的设备使用之前,必须要调用open函数。
int g_usart1_fd = -1;
//可读可写,中断读操作
g_usart1_fd = open("serial/usart1", DEVICE_FLAG_RDWR | DEVICE_FLAG_INT_RX);
if (g_usart1_fd == R_ERROR)
{
Error_Handler();
}
5.2?读取数据
????????这里我使用的是串口中断方式,串口数据先通过中断处理函数存放在缓存里,再通过read函数读取。
unsigned char read_buffer[256] = {0};
int len = read(g_usart1_fd, 0, read_buffer, 256);
if (len > 0)
{
//存在数据,添加处理代码
}
5.3?发送数据
unsigned char write_buffer[10] = {0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9};
write(g_usart1_fd, 0, write_buffer, 10);
六、过程
????????整个串口驱动使用的过程。
- 把stm32_usart1_dev添加到g_device_array数组里统一管理,stm32_usart1_dev设备包含了设备名,open,read,write,ioctl,close接口函数。
- 使用时,open函数通过name设备名,在g_device_array数组里寻找到对应的设备,执行对应设备的open函数,返回设备所有数组中的下标位置。
- 通过open函数返回的下标,间接调用设备对应的read,write,ioctl,close函数。
七、完整代码
完整代码下载地址
|