stm32驱动框架

1,293 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

此方法实现一种类似linux驱动的框架,用于对硬件设备管理,对每个硬件设备独立封装起来,成为一个个模组。该框架存在:1.统一接口;2.移植扩展性好;3.容易实现动态加载硬件等优点。相对应的,因为接口统一,在一些情况下使用起来不方便。

1. 定义驱动结构体

/* 驱动设备 */
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;

一个设备为一个device_t结构体,接口函数指针指向具体的实现函数。

2. 以串口为例,实现驱动模型

新建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,
};

为了更好具备封装性,所有的函数,变量声明为static。

3. 驱动管理

新建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;
  }

4. 设备自动注册

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. 使用

应用层使用串口驱动过程。

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);

6. 过程

整个串口驱动使用的过程。

  1. 把stm32_usart1_dev添加到g_device_array数组里统一管理,stm32_usart1_dev设备包含了设备名,open,read,write,ioctl,close接口函数。
  2. 使用时,open函数通过name设备名,在g_device_array数组里寻找到对应的设备,执行对应设备的open函数,返回设备所有数组中的下标位置。
  3. 通过open函数返回的下标,间接调用设备对应的read,write,ioctl,close函数。