当前位置: 首页 > news >正文

仿照STM32 HAL库设计思想使用FreeRTOS实现异步非阻塞式设备驱动

仿照STM32 HAL库设计思想使用FreeRTOS实现异步非阻塞式设备驱动

  • Chapter1 仿照STM32 HAL库设计思想使用FreeRTOS实现异步非阻塞式设备驱动
    • 一、通过HAL层和Msp层实现硬件操作与业务逻辑的解耦
    • 二、回调函数配合硬件将同步(阻塞)操作转为异步(非阻塞)操作
    • 三、配置与接口分离,接口与实例分离
    • 四、使用FreeRTOS实现异步非阻塞式设备驱动
      • 4.1 使用接口注入的形式实现硬件解耦
      • 4.2 通过”生产者 - 消费者”任务分离阻塞操作
  • Chapter2 跨平台设备驱动框架设计(一)
    • 一、框架设计概览
    • 二、核心的数据结构
      • 2.1 设备结构体(struct device)
      • 2.2 驱动结构体(struct driver)
      • 2.3 设备类型与操作命令
    • 三、设备管理实现
      • 3.1 全局设备与驱动链表
      • 3.2 设备-驱动匹配机制
    • 四、操作系统抽象层(OSAL)
    • 五、外设驱动实现(GPIO,后续的外设在下一篇文章会一一实现)
      • 5.1 GPIO设备结构体扩展
      • 5.2 GPIO驱动实现
    • 六、应用层API示例
      • 6.1 查找并打开设备
      • 6.2 PWM设备控制
    • 七、框架初始化与启动
  • Chapter3 跨平台设备驱动框架设计(二)——UART/I2C/SPI/ADC/TIMER/CAN驱动实现
    • 一、驱动框架核心结构回顾
    • 二、UART驱动实现
      • 2.1 UART设备结构体
      • 2.2 UART驱动实现
    • 三、IIC驱动实现
      • 3.1 I2C设备结构体
      • 3.2 I2C驱动实现
    • 四、SPI驱动实现
      • 4.1 SPI设备结构体
      • 4.2 SPI设备驱动实现
    • 五、ADC驱动实现
      • 5.1 ADC设备结构体
      • 5.2 ADC设备驱动实现
    • 六、定时器驱动实现
      • 6.1 定时器设备结构体
      • 6.2 定时器设备驱动实现
    • 七、CAN驱动实现
      • 7.1 CAN设备结构体
      • 7.2 CAN设备驱动实现
    • 八、设备注册与使用示例
      • 8.1 注册所有设备


Chapter1 仿照STM32 HAL库设计思想使用FreeRTOS实现异步非阻塞式设备驱动

原文链接:https://blog.csdn.net/qq_41961293/article/details/148059961

硬件抽象层(HAL)库是ST官方提供的一个中间件库,它为上层应用提供了简单易用的API来操作STM32的外设,搭配CubxMX等代码生成工具更是极大提高了使用STM32 MCU的便利性。本文希望能够通过其中的一些代码片段,学习背后的代码设计思想,并仿照这种思想分享一种基于RTOS的设备驱动程序架构。优先的设计思想具有共通性!

一、通过HAL层和Msp层实现硬件操作与业务逻辑的解耦

当我们使用CubeMX生成代码时,只需要图形化界面填写我们希望的参数,工具可以自动生成配置代码。那么HAL是如何分离业务逻辑和硬件操作?以实现串口初始化功能为例。
MX_USART1_UART_Init是USART1最外层初始化函数,使用UART_HandleTypeDef类型的结构体直接对接CubeMX的用户设定参数。使用HAL_UART_Init函数继续向下传参完成底层初始化。HAL_UART_Init在操作硬件之前,将检查参数和外设状态,指定回调函数等,然后通过HAL_UART_MspInit完成底层硬件操作。

void MX_USART1_UART_Init(void) {......huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart1) != HAL_OK) {Error_Handler();}
}
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart) {......HAL_UART_MspInit(huart);......
}

这样做带来的好处是,HAL层只需要关系业务逻辑,不需关心寄存器级别操作,使得HAL层代码可移植性很高,适合代码的复用。

二、回调函数配合硬件将同步(阻塞)操作转为异步(非阻塞)操作

以标准库下串口发送数据的常见代码为例:

void Usart_SendString( USART_TypeDef * pUSARTx, char *str) {unsigned int k=0;do {Usart_SendByte( pUSARTx, *(str + k) );k++;} while(*(str + k)!='\0');/* 等待发送完成 */while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)}

通过接口Usart_SendString用户可以发送一段字符串。但是,由于需要等待逐个字节发送完成,调用Usart_SendString可能阻塞后续程序的执行,导致系统的实时性降低。作为对比,HAL库给用户提供了HAL_UART_Transmit_IT、HAL_UART_Transmit_DMA等非阻塞式的操作方式,API仅启动的数据传输,后台自己执行数据传输任务,达到非阻塞式的效果,同时,可以在回调函数中指定发送结束需要处理的操作。如果发送数据对于接下来的程序执行不可缺少,HAL库同样提供了同步/阻塞式操作HAL_UART_Transmit。
在这里插入图片描述
另外,回调函数的另一个好处是可以屏蔽底层硬件差异。直观看,回调函数的入口函数仍然通过异常向量表指定,和硬件紧密结合。
例如UARTI1和DMA1异常向量表指定的分别是USART1_IRQHandler和DMA1_Stream0_IRQHandler。但是,通过回调函数的注册机制,不同的硬件触发源可以选择相同的回调函数,为上层的业务逻辑处理提供了方便。使用HAL_UARTEx_ReceiveToIdle_DMA启动IDLE-DMA接收模式。此API会同时启动串口外设的IDLE中断,和DMA外设的半满和全满中断,但是共用回调函数HAL_UARTEx_RxEventCallback,方便上层调用者是逻辑处理。

三、配置与接口分离,接口与实例分离

在HAL库中,配置(硬件参数)与接口(操作函数)严格分离:

  • 配置:通过UART_HandleTypeDef结构体定义参数(如波特率、数据位),由用户填写。
  • 接口:HAL_UART_Init等函数仅依赖配置结构体,不直接操作寄存器。
// 用户配置(MX_USART6_UART_Init)
void MX_USART6_UART_Init(void) {huart6.Instance = USART6;                  // 选择硬件实例huart6.Init.BaudRate = 115200;             // 配置波特率huart6.Init.WordLength = UART_WORDLENGTH_8B; // 数据位HAL_UART_Init(&huart6);                    // 调用通用初始化接口
}// HAL接口(HAL_UART_Init)
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart) {// 参数校验assert_param(IS_UART_WORD_LENGTH(huart->Init.WordLength));// 调用硬件相关初始化(Msp层)HAL_UART_MspInit(huart);// 配置寄存器(通过huart->Instance)CLEAR_BIT(huart->Instance->CR2, ...);__HAL_UART_ENABLE(huart);
}

接口与实例分离:HAL库通过接口函数与实例解耦,同一接口支持多个硬件实例:

  • 接口函数:如HAL_UART_Transmit,通过huart参数区分不同UART外设。
  • 实例管理:每个UART实例(USART1、USART6)有独立的UART_HandleTypeDef结构体。
// 定义两个UART实例
UART_HandleTypeDef huart1; // USART1
UART_HandleTypeDef huart6; // USART6// 初始化不同实例(复用同一接口)
HAL_UART_Init(&huart1); // 初始化USART1
HAL_UART_Init(&huart6); // 初始化USART6// 发送数据(接口与实例分离)
HAL_UART_Transmit(&huart1, data1, len, timeout); // 使用USART1发送
HAL_UART_Transmit(&huart6, data2, len, timeout); // 使用USART6发送

四、使用FreeRTOS实现异步非阻塞式设备驱动

驱动代码以一款I2C输出的压力传感器驱动程序为例,使用软件模拟I2C时序的方式,读取压力传感器产生的压力数据和温度数据。

4.1 使用接口注入的形式实现硬件解耦

HAL库使用HAL层和MspInit层操作划分业务逻辑和底层硬件的操作——变更MCU的型号等,不会对用户使用上层API造成影响。类似的,示例代码同样采用了两层划分结构:handler层和bsp进行职责划分——handler层被定义为通过bsp传入的接口,读取压力和温度数据,不关心bsp层的具体操作逻辑,压力传感器类型的变动对用户使用handler层提供的API没有影响。
在这里插入图片描述
bsp层的代码对于I2C驱动,通过定义I2C的操作接口,当APP需要调用传感器驱动程序,需要实现定义相关函数并创建一个接口实例传入初始化函数。传感器驱动程序本身不维护底层硬件操作,这为驱动程序在不同平台的移植提供了便利。

#ifndef HARDWARE_I2C /* True: Use software I2C */
typedef struct {gzp6841_status_t (*pf_iic_init)        (void *); /* Initialize I2C    */gzp6841_status_t (*pf_iic_deinit)      (void *); /* Deinitialize I2C  */gzp6841_status_t (*pf_iic_start)       (void *); /* Start condition   */gzp6841_status_t (*pf_iic_stop)        (void *); /* Stop condition    */gzp6841_status_t (*pf_iic_wait_ack)    (void *); /* Wait for ACK      */    gzp6841_status_t (*pf_iic_send_ack)    (void *); /* Send NACK         */gzp6841_status_t (*pf_iic_send_no_ack) (void *); /* Send NACK         */gzp6841_status_t (*pf_iic_send_byte)   (void *,  /* Send byte         */const uint8_t); gzp6841_status_t (*pf_iic_receive_byte)(void *,  /* Receive byte      */uint8_t *const); #ifdef OS_SUPPORTINGgzp6841_status_t (*pf_critical_enter)  (void);   /*Critical seg. enter*/gzp6841_status_t (*pf_critical_exit)   (void);   /*Critical seg. exit */
#endif /* End of OS_SUPPORTING */
}iic_driver_interface_t;  
#endif /* end of SOFTWARE_I2C */ 
gzp6841_status_t gzp6841_inst(bsp_gzp6841_driver_t *  const p_gzp6841_instance,iic_driver_interface_t *   const p_iic_driver_instance,
#ifdef OS_SUPPORTINGyield_interface_t *             const p_yield_instance,
#endif /* OS_SUPPORTING */									timebase_interface_t *       const p_timebase_instance);

4.2 通过”生产者 - 消费者”任务分离阻塞操作

HAL库提供的非阻塞式API,需要依赖实际的底层硬件和CPU并行操作,如DMA、中断,但是,实际的驱动程序编写场景,例如使用的软件模拟I2C的通信过程很难使用硬件并行的方式消除主程序的阻塞。在RTOS的环境基础上,使用“生产者 - 消费者”模型可以很好解决这一问题:消费者任务(高优先级)通过队列读取生产者任务(低优先级)产生的压力、温度数据。
对于高优先级任务而言,硬件的访问操作(读取压力传感器数据)和使用HAL库提供的非阻塞式API逻辑相同——通过回调函数指定硬件操作的后的处理逻辑,主程序不会因外耗时的底层硬件操作而阻塞。不同的是,在RTOS场景下,处理底层硬件操作的任务是在高优先级任务的主动让出CPU资源后进行。
在这里插入图片描述
以实际的驱动程序为例,对压力传感器的压力和温度的读取操作实际由pres_temp_handler_thread完成。任务内完成读取操作和执行回调函数。

void pres_temp_handler_thread(void *argument) 
{/* Check the parameter */......// Create GZP6841 instance......// Create Handler instance ......bsp_pres_temp_xxx_handler_inst(&pres_temp_xxx_handler_instance,input_arg);               for (;;) {// 1. Read event from the queueret = pres_temp_xxx_handler_instance.os_interface->os_queue_get(pres_temp_xxx_handler_instance.event_queue_handle,&event,MY_MAX_DELAY);// 2. Fetch pressure and  temperature if (PRES_TEMP_OK ==get_temp_humi(&pres_temp_xxx_handler_instance, event, &pres, &temp)){// Data fetched successfully} else {// Data fetch error}// 3. Execute the callback functionevent.pfcallback(&pres, &temp);}
}

Chapter2 跨平台设备驱动框架设计(一)

原文链接:https://blog.csdn.net/qq_56152172/article/details/145664671

设计一个跨平台的设备驱动框架,支持不同的硬件平台和操作系统,同时提供统一的API接口。参考Linux的驱动模型,使用面向对象的思想,并通过链表管理外设设备。

我想要一个类似于Linux的设备驱动框架,这样无论底层是裸机、FreeRTOS还是Linux,上层应用都能使用相同的接口进行开发。这需要抽象出统一的设备模型,每个外设作为设备对象注册到框架中,并通过链表管理。驱动和设备的匹配机制类似于Linux的platform bus模型,驱动通过匹配设备树或ID来绑定设备。

接下来,要设计设备结构体和驱动结构体。设备结构体应包含设备的基本信息(名称、类型、状态)、操作函数指针(open、close、read、write、ioctl等)、私有数据以及链表节点。驱动结构体则需要包含驱动名称、支持的设备类型、probe和remove函数等。

对于不同操作系统的支持,需要抽象出操作系统相关的层。例如,在裸机环境下,直接操作寄存器;在FreeRTOS中,可能需要使用任务和队列;在Linux中,则使用内核提供的API。这里可能需要条件编译或不同的实现文件来区分不同的操作系统。

然后,外设驱动的具体实现。以GPIO为例,需要实现GPIO设备的注册、初始化、读写操作。对于STM32F407,GPIO的操作包括配置引脚模式、设置输出电平、读取输入电平。这些操作需要封装成统一的接口,如gpio_set_value、gpio_get_value等。

链表管理外设设备是关键部分。每个设备注册时,将其添加到全局设备链表中。当应用层需要操作设备时,通过设备名称或类型在链表中查找对应的设备,并调用其操作函数。

当注册一个设备时,框架遍历所有已注册的驱动,检查是否有驱动支持该设备类型。如果找到匹配的驱动,则调用驱动的probe函数进行初始化。类似地,当注册一个驱动时,检查是否有未绑定的设备与之匹配。

那么设备注册失败时如何回滚以及驱动卸载时的资源释放是个大问题。此外,多线程环境下的同步问题,比如在FreeRTOS或Linux中,设备操作可能需要加锁以保证线程安全。

最后,代码的结构需要模块化,便于扩展和维护。每个外设的驱动独立成模块,通过头文件暴露接口,设备框架核心管理设备的注册、匹配和链表操作。

我在实现过程中遇到的问题包括不同操作系统API的差异,如何统一这些接口;设备驱动的兼容性,确保同一驱动在不同平台上能正确工作;以及性能优化,避免抽象层带来的过多开销。

总的来说,我需要做这些内容,包括定义设备与驱动的结构体,实现设备注册与管理链表,抽象操作系统层,实现各外设的驱动,处理驱动与设备的匹配,以及错误处理和资源管理。接口统一,易于扩展和维护。

一、框架设计概览

本框架基于Linux设备模型思想,实现 设备-驱动分离、统一API接口 和 链表管理,支持裸机、FreeRTOS、Linux等多种操作系统。核心组件如下:

  • 设备模型:抽象设备操作(open/close/read/write/ioctl)。

  • 驱动模型:实现硬件相关操作,与设备绑定。

  • 操作系统抽象层:隔离不同OS的底层差异。

  • 设备链表:全局管理所有注册的设备。

二、核心的数据结构

2.1 设备结构体(struct device)

// device.h
typedef struct device {const char *name;               // 设备名称(如 "uart1")enum device_type type;           // 设备类型(GPIO/UART/I2C等)void *driver_data;              // 驱动私有数据const struct device_ops *ops;   // 设备操作函数集struct list_head list;          // 链表节点uint8_t is_registered;          // 设备是否已注册
} device_t;// 设备操作函数集(类似Linux的file_operations)
typedef struct device_ops {int (*open)(struct device *dev);int (*close)(struct device *dev);ssize_t (*read)(struct device *dev, void *buf, size_t count);ssize_t (*write)(struct device *dev, const void *buf, size_t count);int (*ioctl)(struct device *dev, unsigned int cmd, void *arg);
} device_ops_t;

2.2 驱动结构体(struct driver)

// driver.h
typedef struct driver {const char *name;               // 驱动名称(如 "stm32_gpio_driver")enum device_type type;          // 支持的设备类型int (*probe)(struct device *dev);  // 设备探测函数int (*remove)(struct device *dev); // 设备移除函数struct list_head list;          // 链表节点
} driver_t;

2.3 设备类型与操作命令

// 设备类型枚举
enum device_type {DEVICE_TYPE_GPIO,DEVICE_TYPE_UART,DEVICE_TYPE_I2C,DEVICE_TYPE_SPI,DEVICE_TYPE_ADC,DEVICE_TYPE_PWM,DEVICE_TYPE_DAC,DEVICE_TYPE_CAN,DEVICE_TYPE_TIMER,
};// 通用IO控制命令(示例)
#define DEVICE_IOCTL_GET_STATUS   0x1001
#define DEVICE_IOCTL_SET_MODE     0x1002

三、设备管理实现

3.1 全局设备与驱动链表

// device_core.c
static LIST_HEAD(device_list);     // 全局设备链表
static LIST_HEAD(driver_list);     // 全局驱动链表// 注册设备
int device_register(struct device *dev) {if (!dev->ops) return -EINVAL;list_add_tail(&dev->list, &device_list);dev->is_registered = 1;return 0;
}// 注册驱动
int driver_register(struct driver *drv) {list_add_tail(&drv->list, &driver_list);return 0;
}

3.2 设备-驱动匹配机制

// 设备与驱动匹配函数
static int device_match(struct device *dev, struct driver *drv) {return (dev->type == drv->type);
}// 自动绑定设备与驱动
int device_bind_driver(struct device *dev) {struct driver *drv;list_for_each_entry(drv, &driver_list, list) {if (device_match(dev, drv)) {if (drv->probe(dev) == 0) {dev->driver_data = drv;return 0;}}}return -ENODEV;
}

四、操作系统抽象层(OSAL)

为支持不同操作系统,定义统一的OSAL接口:

// osal.h
#ifdef OS_BARE_METAL#include "stm32f4xx_hal.h"#define osal_printf(...)        printf(__VA_ARGS__)#define osal_delay_ms(ms)       HAL_Delay(ms)
#elif OS_FREERTOS#include "FreeRTOS.h"#include "task.h"#define osal_printf(...)        vPrintf(__VA_ARGS__)#define osal_delay_ms(ms)       vTaskDelay(pdMS_TO_TICKS(ms))
#elif OS_LINUX#include <stdio.h>#include <unistd.h>#define osal_printf(...)        printf(__VA_ARGS__)#define osal_delay_ms(ms)       usleep(ms * 1000)
#endif

五、外设驱动实现(GPIO,后续的外设在下一篇文章会一一实现)

5.1 GPIO设备结构体扩展

// gpio_driver.c
#include "device.h"
#include "gpio.h"static int gpio_open(struct device *dev) {gpio_device_t *gpio = dev->driver_data;// 初始化GPIO(裸机示例)GPIO_InitTypeDef init = {.Pin = gpio->pin,.Mode = gpio->mode,.Pull = GPIO_NOPULL,.Speed = GPIO_SPEED_FREQ_HIGH};HAL_GPIO_Init(gpio->port, &init);return 0;
}static ssize_t gpio_read(struct device *dev, void *buf, size_t count) {gpio_device_t *gpio = dev->driver_data;*(uint8_t*)buf = HAL_GPIO_ReadPin(gpio->port, gpio->pin);return sizeof(uint8_t);
}static ssize_t gpio_write(struct device *dev, const void *buf, size_t count) {gpio_device_t *gpio = dev->driver_data;HAL_GPIO_WritePin(gpio->port, gpio->pin, *(uint8_t*)buf);return sizeof(uint8_t);
}static int gpio_ioctl(struct device *dev, unsigned int cmd, void *arg) {gpio_device_t *gpio = dev->driver_data;switch (cmd) {case GPIO_IOCTL_SET_MODE:gpio->mode = *(uint32_t*)arg;HAL_GPIO_Init(gpio->port, &(GPIO_InitTypeDef){gpio->pin, gpio->mode});break;default:return -EINVAL;}return 0;
}// GPIO驱动操作函数集
static const struct device_ops gpio_ops = {.open = gpio_open,.read = gpio_read,.write = gpio_write,.ioctl = gpio_ioctl,
};// 驱动注册
static struct driver gpio_driver = {.name = "stm32_gpio_driver",.type = DEVICE_TYPE_GPIO,.probe = gpio_probe, // 设备探测函数(略)
};void gpio_driver_init(void) {driver_register(&gpio_driver);
}

5.2 GPIO驱动实现

// gpio_driver.c
#include "device.h"
#include "gpio.h"static int gpio_open(struct device *dev) {gpio_device_t *gpio = dev->driver_data;// 初始化GPIO(裸机示例)GPIO_InitTypeDef init = {.Pin = gpio->pin,.Mode = gpio->mode,.Pull = GPIO_NOPULL,.Speed = GPIO_SPEED_FREQ_HIGH};HAL_GPIO_Init(gpio->port, &init);return 0;
}static ssize_t gpio_read(struct device *dev, void *buf, size_t count) {gpio_device_t *gpio = dev->driver_data;*(uint8_t*)buf = HAL_GPIO_ReadPin(gpio->port, gpio->pin);return sizeof(uint8_t);
}static ssize_t gpio_write(struct device *dev, const void *buf, size_t count) {gpio_device_t *gpio = dev->driver_data;HAL_GPIO_WritePin(gpio->port, gpio->pin, *(uint8_t*)buf);return sizeof(uint8_t);
}static int gpio_ioctl(struct device *dev, unsigned int cmd, void *arg) {gpio_device_t *gpio = dev->driver_data;switch (cmd) {case GPIO_IOCTL_SET_MODE:gpio->mode = *(uint32_t*)arg;HAL_GPIO_Init(gpio->port, &(GPIO_InitTypeDef){gpio->pin, gpio->mode});break;default:return -EINVAL;}return 0;
}// GPIO驱动操作函数集
static const struct device_ops gpio_ops = {.open = gpio_open,.read = gpio_read,.write = gpio_write,.ioctl = gpio_ioctl,
};// 驱动注册
static struct driver gpio_driver = {.name = "stm32_gpio_driver",.type = DEVICE_TYPE_GPIO,.probe = gpio_probe, // 设备探测函数(略)
};void gpio_driver_init(void) {driver_register(&gpio_driver);
}

六、应用层API示例

6.1 查找并打开设备

struct device *dev = device_find("gpio1");
if (dev) {dev->ops->open(dev);uint8_t value;dev->ops->read(dev, &value, 1);osal_printf("GPIO1 Value: %d\n", value);
}

6.2 PWM设备控制

struct device *pwm_dev = device_find("pwm0");
if (pwm_dev) {pwm_dev->ops->open(pwm_dev);uint32_t duty = 50; // 50%占空比pwm_dev->ops->ioctl(pwm_dev, PWM_IOCTL_SET_DUTY, &duty);
}

七、框架初始化与启动

// main.c
int main(void) {// 硬件初始化(HAL库、时钟等)HAL_Init();SystemClock_Config();// 注册所有驱动gpio_driver_init();uart_driver_init();// 其他驱动初始化...// 注册设备static gpio_device_t gpio1 = {.port = GPIOA,.pin = GPIO_PIN_0,.mode = GPIO_MODE_INPUT};static device_t gpio1_dev = {.name = "gpio1",.type = DEVICE_TYPE_GPIO,.ops = &gpio_ops,.driver_data = &gpio1};device_register(&gpio1_dev);// 启动任务调度(FreeRTOS示例)#ifdef OS_FREERTOSxTaskCreate(app_task, "App", 128, NULL, 1, NULL);vTaskStartScheduler();#endifwhile (1);
}
  1. 跨平台兼容:通过OSAL层适配裸机、FreeRTOS、Linux。

  2. 统一接口:Linux风格的API(open/read/write/ioctl)。

  3. 模块化设计:设备与驱动解耦,便于扩展新硬件。

  4. 动态管理:链表管理设备,支持运行时添加/移除设备。
    先写到这,后面的各个外设驱动也会在这个专栏更新,包括UART、I2C、SPI、ADC、定时器、PWM、DAC、CAN等等,敬请期待哦。拜拜!

Chapter3 跨平台设备驱动框架设计(二)——UART/I2C/SPI/ADC/TIMER/CAN驱动实现

原文链接:https://blog.csdn.net/qq_56152172/article/details/145667654?spm=1001.2014.3001.5502

一、驱动框架核心结构回顾

设备操作接口(device_ops)

// device.h
typedef struct device_ops {int (*open)(struct device *dev);int (*close)(struct device *dev);ssize_t (*read)(struct device *dev, void *buf, size_t count);ssize_t (*write)(struct device *dev, const void *buf, size_t count);int (*ioctl)(struct device *dev, unsigned int cmd, void *arg);
} device_ops_t;

设备注册与查找

// 注册设备
int device_register(struct device *dev);// 查找设备
struct device *device_find(const char *name);

二、UART驱动实现

2.1 UART设备结构体

// uart.h
typedef struct {USART_TypeDef *instance;    // UART实例(如USART1)uint32_t baudrate;          // 波特率uint8_t data_bits;          // 数据位(8/9)uint8_t stop_bits;          // 停止位(1/2)uint8_t parity;             // 校验位(0:None, 1:Even, 2:Odd)uint8_t *rx_buffer;         // 接收缓冲区uint16_t rx_buffer_size;    // 缓冲区大小uint16_t rx_count;          // 接收数据计数
} uart_device_t;// IOCTL命令
#define UART_IOCTL_SET_BAUDRATE     0x3001
#define UART_IOCTL_SET_PARITY       0x3002

2.2 UART驱动实现

// uart_driver.c
#include "device.h"
#include "uart.h"
#include "stm32f4xx_hal.h"static UART_HandleTypeDef huart;static int uart_open(struct device *dev) {uart_device_t *uart = dev->driver_data;huart.Instance = uart->instance;huart.Init.BaudRate = uart->baudrate;huart.Init.WordLength = (uart->data_bits == 9) ? UART_WORDLENGTH_9B : UART_WORDLENGTH_8B;huart.Init.StopBits = (uart->stop_bits == 2) ? UART_STOPBITS_2 : UART_STOPBITS_1;huart.Init.Parity = (uart->parity == 1) ? UART_PARITY_EVEN : (uart->parity == 2) ? UART_PARITY_ODD : UART_PARITY_NONE;huart.Init.Mode = UART_MODE_TX_RX;huart.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart) != HAL_OK) return -1;// 启用接收中断HAL_UART_Receive_IT(&huart, uart->rx_buffer, 1);return 0;
}static ssize_t uart_read(struct device *dev, void *buf, size_t count) {uart_device_t *uart = dev->driver_data;size_t bytes_to_copy = (count < uart->rx_count) ? count : uart->rx_count;memcpy(buf, uart->rx_buffer, bytes_to_copy);uart->rx_count -= bytes_to_copy;return bytes_to_copy;
}static ssize_t uart_write(struct device *dev, const void *buf, size_t count) {uart_device_t *uart = dev->driver_data;if (HAL_UART_Transmit(&huart, (uint8_t*)buf, count, 1000) != HAL_OK) return -1;return count;
}static int uart_ioctl(struct device *dev, unsigned int cmd, void *arg) {uart_device_t *uart = dev->driver_data;switch (cmd) {case UART_IOCTL_SET_BAUDRATE:uart->baudrate = *(uint32_t*)arg;huart.Init.BaudRate = uart->baudrate;HAL_UART_Init(&huart);break;default:return -EINVAL;}return 0;
}// 中断处理
void USART1_IRQHandler(void) {HAL_UART_IRQHandler(&huart);
}// 接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {uart_device_t *uart = find_uart_by_instance(huart->Instance);if (uart->rx_count < uart->rx_buffer_size) {uart->rx_count++;HAL_UART_Receive_IT(huart, &uart->rx_buffer[uart->rx_count], 1);}
}

三、IIC驱动实现

3.1 I2C设备结构体

// i2c.h
typedef struct {I2C_TypeDef *instance;       // I2C实例(如I2C1)uint32_t clock_speed;        // 时钟速度(Hz)uint8_t address;             // 设备地址(7位)uint8_t addressing_mode;     // 地址模式(7/10位)
} i2c_device_t;// IOCTL命令
#define I2C_IOCTL_SET_CLOCK_SPEED   0x4001

3.2 I2C驱动实现

// i2c_driver.c
#include "device.h"
#include "i2c.h"
#include "stm32f4xx_hal.h"static I2C_HandleTypeDef hi2c;static int i2c_open(struct device *dev) {i2c_device_t *i2c = dev->driver_data;hi2c.Instance = i2c->instance;hi2c.Init.ClockSpeed = i2c->clock_speed;hi2c.Init.DutyCycle = I2C_DUTYCYCLE_2;hi2c.Init.OwnAddress1 = i2c->address << 1;hi2c.Init.AddressingMode = (i2c->addressing_mode == 10) ? I2C_ADDRESSINGMODE_10BIT : I2C_ADDRESSINGMODE_7BIT;hi2c.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;hi2c.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;hi2c.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;if (HAL_I2C_Init(&hi2c) != HAL_OK) return -1;return 0;
}static ssize_t i2c_read(struct device *dev, void *buf, size_t count) {i2c_device_t *i2c = dev->driver_data;if (HAL_I2C_Master_Receive(&hi2c, i2c->address << 1, buf, count, 1000) != HAL_OK)return -1;return count;
}static ssize_t i2c_write(struct device *dev, const void *buf, size_t count) {i2c_device_t *i2c = dev->driver_data;if (HAL_I2C_Master_Transmit(&hi2c, i2c->address << 1, (uint8_t*)buf, count, 1000) != HAL_OK)return -1;return count;
}static int i2c_ioctl(struct device *dev, unsigned int cmd, void *arg) {i2c_device_t *i2c = dev->driver_data;switch (cmd) {case I2C_IOCTL_SET_CLOCK_SPEED:i2c->clock_speed = *(uint32_t*)arg;hi2c.Init.ClockSpeed = i2c->clock_speed;HAL_I2C_Init(&hi2c);break;default:return -EINVAL;}return 0;
}

四、SPI驱动实现

4.1 SPI设备结构体

// spi.h
typedef struct {SPI_TypeDef *instance;       // SPI实例(如SPI1)uint32_t clock_speed;        // 时钟速度(Hz)uint8_t mode;                // 模式(0-3)uint8_t data_size;           // 数据大小(8/16位)
} spi_device_t;// IOCTL命令
#define SPI_IOCTL_SET_CLOCK_SPEED   0x5001
#define SPI_IOCTL_SET_MODE         0x5002

4.2 SPI设备驱动实现

// spi_driver.c
#include "device.h"
#include "spi.h"
#include "stm32f4xx_hal.h"static SPI_HandleTypeDef hspi;static int spi_open(struct device *dev) {spi_device_t *spi = dev->driver_data;hspi.Instance = spi->instance;hspi.Init.Mode = SPI_MODE_MASTER;hspi.Init.Direction = SPI_DIRECTION_2LINES;hspi.Init.DataSize = (spi->data_size == 16) ? SPI_DATASIZE_16BIT : SPI_DATASIZE_8BIT;hspi.Init.CLKPolarity = (spi->mode & 0x02) ? SPI_POLARITY_HIGH : SPI_POLARITY_LOW;hspi.Init.CLKPhase = (spi->mode & 0x01) ? SPI_PHASE_2EDGE : SPI_PHASE_1EDGE;hspi.Init.NSS = SPI_NSS_SOFT;hspi.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 根据clock_speed计算hspi.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi.Init.TIMode = SPI_TIMODE_DISABLE;hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;if (HAL_SPI_Init(&hspi) != HAL_OK) return -1;return 0;
}static ssize_t spi_read(struct device *dev, void *buf, size_t count) {spi_device_t *spi = dev->driver_data;if (HAL_SPI_Receive(&hspi, buf, count, 1000) != HAL_OK) return -1;return count;
}static ssize_t spi_write(struct device *dev, const void *buf, size_t count) {spi_device_t *spi = dev->driver_data;if (HAL_SPI_Transmit(&hspi, (uint8_t*)buf, count, 1000) != HAL_OK) return -1;return count;
}static int spi_ioctl(struct device *dev, unsigned int cmd, void *arg) {spi_device_t *spi = dev->driver_data;switch (cmd) {case SPI_IOCTL_SET_CLOCK_SPEED:spi->clock_speed = *(uint32_t*)arg;// 重新计算分频系数并更新hspi.Init.BaudRatePrescalerbreak;default:return -EINVAL;}return 0;
}

五、ADC驱动实现

5.1 ADC设备结构体

// adc.h
typedef struct {ADC_TypeDef *instance;       // ADC实例(如ADC1)uint32_t channel;            // 通道号(ADC_CHANNEL_0)uint32_t sampling_time;      // 采样时间(ADC_SAMPLETIME_3CYCLES)uint16_t *buffer;            // DMA缓冲区uint16_t buffer_size;        // 缓冲区大小
} adc_device_t;// IOCTL命令
#define ADC_IOCTL_START_CONVERSION  0x6001
#define ADC_IOCTL_STOP_CONVERSION   0x6002

5.2 ADC设备驱动实现

// adc_driver.c
#include "device.h"
#include "adc.h"
#include "stm32f4xx_hal.h"static ADC_HandleTypeDef hadc;
DMA_HandleTypeDef hdma_adc;static int adc_open(struct device *dev) {adc_device_t *adc = dev->driver_data;hadc.Instance = adc->instance;hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;hadc.Init.Resolution = ADC_RESOLUTION_12B;hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;hadc.Init.ScanConvMode = DISABLE;hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;hadc.Init.ContinuousConvMode = DISABLE;hadc.Init.DMAContinuousRequests = ENABLE;hadc.Init.NbrOfConversion = 1;if (HAL_ADC_Init(&hadc) != HAL_OK) return -1;// 配置通道ADC_ChannelConfTypeDef sConfig = {.Channel = adc->channel,.Rank = 1,.SamplingTime = adc->sampling_time};HAL_ADC_ConfigChannel(&hadc, &sConfig);// 初始化DMAhdma_adc.Instance = DMA2_Stream0;hdma_adc.Init.Channel = DMA_CHANNEL_0;hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY;hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE;hdma_adc.Init.MemInc = DMA_MINC_ENABLE;hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;hdma_adc.Init.Mode = DMA_CIRCULAR;hdma_adc.Init.Priority = DMA_PRIORITY_HIGH;HAL_DMA_Init(&hdma_adc);__HAL_LINKDMA(&hadc, DMA_Handle, hdma_adc);HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc->buffer, adc->buffer_size);return 0;
}static ssize_t adc_read(struct device *dev, void *buf, size_t count) {adc_device_t *adc = dev->driver_data;size_t samples = (count < adc->buffer_size) ? count : adc->buffer_size;memcpy(buf, adc->buffer, samples * sizeof(uint16_t));return samples;
}static int adc_ioctl(struct device *dev, unsigned int cmd, void *arg) {adc_device_t *adc = dev->driver_data;switch (cmd) {case ADC_IOCTL_START_CONVERSION:HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc->buffer, adc->buffer_size);break;case ADC_IOCTL_STOP_CONVERSION:HAL_ADC_Stop_DMA(&hadc);break;default:return -EINVAL;}return 0;
}

六、定时器驱动实现

6.1 定时器设备结构体

// timer.h
typedef struct {TIM_TypeDef *instance;       // 定时器实例(如TIM2)uint32_t prescaler;          // 预分频值uint32_t period;             // 自动重载值uint8_t mode;                // 模式(0:定时器, 1:PWM输出)uint8_t channel;             // 通道号(TIM_CHANNEL_1)
} timer_device_t;// IOCTL命令
#define TIMER_IOCTL_SET_PRESCALER   0x7001
#define TIMER_IOCTL_SET_PERIOD      0x7002

6.2 定时器设备驱动实现

// timer_driver.c
#include "device.h"
#include "timer.h"
#include "stm32f4xx_hal.h"static TIM_HandleTypeDef htim;static int timer_open(struct device *dev) {timer_device_t *timer = dev->driver_data;htim.Instance = timer->instance;htim.Init.Prescaler = timer->prescaler;htim.Init.CounterMode = TIM_COUNTERMODE_UP;htim.Init.Period = timer->period;htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;htim.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;if (HAL_TIM_Base_Init(&htim) != HAL_OK) return -1;if (timer->mode == 1) { // PWM模式TIM_OC_InitTypeDef sConfigOC = {.OCMode = TIM_OCMODE_PWM1,.Pulse = timer->period / 2, // 默认50%占空比.OCPolarity = TIM_OCPOLARITY_HIGH,.OCFastMode = TIM_OCFAST_DISABLE};HAL_TIM_PWM_ConfigChannel(&htim, &sConfigOC, timer->channel);HAL_TIM_PWM_Start(&htim, timer->channel);} else {HAL_TIM_Base_Start_IT(&htim);}return 0;
}static int timer_ioctl(struct device *dev, unsigned int cmd, void *arg) {timer_device_t *timer = dev->driver_data;switch (cmd) {case TIMER_IOCTL_SET_PRESCALER:timer->prescaler = *(uint32_t*)arg;__HAL_TIM_SET_PRESCALER(&htim, timer->prescaler);break;case TIMER_IOCTL_SET_PERIOD:timer->period = *(uint32_t*)arg;__HAL_TIM_SET_AUTORELOAD(&htim, timer->period);break;default:return -EINVAL;}return 0;
}// 定时器中断处理
void TIM2_IRQHandler(void) {HAL_TIM_IRQHandler(&htim);
}// 中断回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {// 用户自定义逻辑
}

七、CAN驱动实现

7.1 CAN设备结构体

// can.h
typedef struct {CAN_TypeDef *instance;       // CAN实例(如CAN1)uint32_t baudrate;           // 波特率(bps)CAN_FilterTypeDef filter;    // 过滤器配置CanRxMsgTypeDef rx_msg;      // 接收消息CanTxMsgTypeDef tx_msg;      // 发送消息
} can_device_t;// IOCTL命令
#define CAN_IOCTL_SET_FILTER       0x8001
#define CAN_IOCTL_SET_BAUDRATE     0x8002

7.2 CAN设备驱动实现

// can_driver.c
#include "device.h"
#include "can.h"
#include "stm32f4xx_hal.h"static CAN_HandleTypeDef hcan;static int can_open(struct device *dev) {can_device_t *can = dev->driver_data;hcan.Instance = can->instance;hcan.Init.Prescaler = 16; // 根据波特率计算hcan.Init.Mode = CAN_MODE_NORMAL;hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;hcan.Init.TimeSeg1 = CAN_BS1_4TQ;hcan.Init.TimeSeg2 = CAN_BS2_3TQ;hcan.Init.TimeTriggeredMode = DISABLE;hcan.Init.AutoBusOff = DISABLE;hcan.Init.AutoWakeUp = DISABLE;hcan.Init.AutoRetransmission = ENABLE;hcan.Init.ReceiveFifoLocked = DISABLE;hcan.Init.TransmitFifoPriority = DISABLE;if (HAL_CAN_Init(&hcan) != HAL_OK) return -1;// 配置过滤器HAL_CAN_ConfigFilter(&hcan, &can->filter);// 启动CANHAL_CAN_Start(&hcan);HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);return 0;
}static ssize_t can_read(struct device *dev, void *buf, size_t count) {can_device_t *can = dev->driver_data;if (HAL_CAN_Receive(&hcan, CAN_FIFO0, &can->rx_msg, 1000) != HAL_OK) return -1;memcpy(buf, &can->rx_msg, sizeof(CanRxMsgTypeDef));return sizeof(CanRxMsgTypeDef);
}static ssize_t can_write(struct device *dev, const void *buf, size_t count) {can_device_t *can = dev->driver_data;memcpy(&can->tx_msg, buf, sizeof(CanTxMsgTypeDef));if (HAL_CAN_Transmit(&hcan, &can->tx_msg, 1000) != HAL_OK) return -1;return sizeof(CanTxMsgTypeDef);
}static int can_ioctl(struct device *dev, unsigned int cmd, void *arg) {can_device_t *can = dev->driver_data;switch (cmd) {case CAN_IOCTL_SET_FILTER:memcpy(&can->filter, arg, sizeof(CAN_FilterTypeDef));HAL_CAN_ConfigFilter(&hcan, &can->filter);break;case CAN_IOCTL_SET_BAUDRATE:can->baudrate = *(uint32_t*)arg;// 重新计算Prescaler并更新hcan.Init.Prescalerbreak;default:return -EINVAL;}return 0;
}// CAN中断处理
void CAN1_RX0_IRQHandler(void) {HAL_CAN_IRQHandler(&hcan);
}// 接收完成回调
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan) {// 处理接收到的消息
}

八、设备注册与使用示例

8.1 注册所有设备

// main.c
int main(void) {HAL_Init();SystemClock_Config();// 注册UART1static uart_device_t uart1 = {.instance = USART1,.baudrate = 115200,.data_bits = 8,.stop_bits = 1,.parity = 0,.rx_buffer = malloc(256),.rx_buffer_size = 256};static struct device uart1_dev = {.name = "uart1",.type = DEVICE_TYPE_UART,.ops = &uart_ops,.driver_data = &uart1};device_register(&uart1_dev);// 注册其他设备(I2C/SPI/ADC/TIMER/CAN)...while (1) {// 主循环处理}
}

###8.2 应用层调用

// 发送数据到UART
struct device *uart_dev = device_find("uart1");
if (uart_dev) {const char *msg = "Hello, UART!";uart_dev->ops->write(uart_dev, msg, strlen(msg));
}// 读取ADC数据
struct device *adc_dev = device_find("adc1");
if (adc_dev) {uint16_t adc_value;adc_dev->ops->read(adc_dev, &adc_value, sizeof(adc_value));
}

写完了,但是架构还不够完美,下面我会结合事件驱动模型来重整架构,打造一套适用于任意芯片、任何架构(裸机、RTOS、Linux)跨平台都适用的设备驱动框架。

http://www.dtcms.com/a/445697.html

相关文章:

  • 铜川做网站电话网页设计规范
  • ssc-FinLLM 金融大模型 相关链接
  • 二叉排序树(建树、查找、删除)
  • Linux学习笔记--i2cget 命令
  • 网站建设的标签指的是响应式网页设计图片
  • 用 CodeBuddy CLI + Prompt,从零到可运行:前后端混合管理系统的高效实战
  • 电源——电荷泵详解
  • 榆林国贸网站建设网站的关键词挖掘方式
  • 从思路到落地:用 Redis 搭建超低延迟在线特征存储库
  • sosdp
  • 快速了解BERT
  • 在线Deflate压缩工具(支持添加zlib头及无zlib头模式)
  • 第14节-增强表结构-ALTER-TABLE
  • Ubuntu之apt更新源
  • T527 IR-RX 调试
  • 低成本能谱仪设计:基于分立器件的模拟前端与数字后端实现方案
  • 计算机视觉的数据收集与标注
  • LeetCode:92.最小路径和
  • 百度竞价推广属于什么广告广东网站se0优化公司
  • Anaconda路径配置
  • GitHub 热榜项目 - 日榜(2025-10-05)
  • java中Math.random()和random()方法区别
  • Django SimpleUI 详解:现代化的Django Admin界面美化方案
  • 网站开发客户流程 6个阶段简述常用的网站开发软件
  • 区块链分层学:新的开始
  • Qt与CMakeLists.txt
  • Ubuntu20.04安装Anaconda3-2025.06
  • VirtualBox中ubuntu1804虚拟机共享文件夹设置
  • 基于单片机的环境监测智能报警系统的设计(论文+源码)
  • 成都网页制作推广合肥网站seo报价