字符设备驱动实战教程(GPIO/串口/定时器驱动封装)附STM32可复现代码

一、前言:为什么要做字符设备驱动封装?

在嵌入式裸机开发中,GPIO、串口、定时器是最基础、最常用的三大字符设备(操作以字节/字符为单位,无复杂块设备逻辑)。很多新手初期会直接操作寄存器或调用库函数零散实现功能,导致代码存在耦合度高、可复用性差、维护困难、移植繁琐等问题。

字符设备驱动封装的核心价值,是将硬件操作与业务逻辑解耦,提供统一、简洁的上层操作接口,屏蔽底层硬件细节。比如:封装后的GPIO驱动,更换MCU型号时,仅需修改驱动底层实现,上层业务代码(如LED控制、按键读取)无需改动;封装后的串口驱动,可快速实现日志打印、指令接收等功能,无需重复编写中断与缓冲区逻辑。

本文基于STM32F103(最主流、资料最丰富的入门MCU),采用标准库(兼顾HAL库思路),逐一实现GPIO、串口、定时器的驱动封装,所有代码均可直接复现,符合工程实战规范,同时兼顾搜索引擎友好性,核心关键词全覆盖。

二、字符设备驱动封装通用思想与流程

2.1 核心封装原则(工程必备)

在开始具体设备封装前,先掌握3个通用原则,确保驱动的可复用性与健壮性:

  1. 高内聚、低耦合:硬件操作细节封装在驱动内部,上层仅调用统一接口,不直接操作寄存器;
  2. 可配置化:通过结构体、宏定义配置设备参数(如GPIO引脚、串口波特率、定时器分频系数),方便快速修改;
  3. 简洁易用:上层接口命名规范、参数清晰,避免复杂的参数传递,降低业务层使用门槛。

2.2 通用封装流程(三步法)

无论GPIO、串口还是定时器,驱动封装都遵循以下三步流程,形成标准化开发范式:

  1. 定义设备结构体:封装设备的核心配置参数、状态信息、句柄等,作为设备的“身份档案”;
  2. 实现底层硬件初始化:完成时钟使能、寄存器配置、中断配置等硬件相关操作,基于设备结构体参数进行初始化;
  3. 封装上层操作接口:实现设备的核心功能接口(如GPIO置高/置低、串口发送/接收、定时器启动/停止),上层业务直接调用这些接口。

三、实战一:GPIO驱动封装(最基础、应用最广泛)

3.1 封装思路

GPIO的核心操作包括:引脚初始化(模式、速率)、输出电平置高/置低/翻转、输入电平读取。封装时,通过结构体管理每个GPIO设备的端口、引脚、配置参数,提供统一的操作接口,支持多GPIO设备独立管理,避免引脚冲突。

3.2 步骤1:定义GPIO设备结构体与宏定义

// 头文件包含(STM32标准库)
#include "stm32f10x.h"

// GPIO模式枚举(简化配置,对应标准库模式)
typedef enum
{
    GPIO_MODE_INPUT_FLOATING = 0,  // 浮空输入
    GPIO_MODE_INPUT_PULLUP,        // 上拉输入
    GPIO_MODE_INPUT_PULLDOWN,      // 下拉输入
    GPIO_MODE_OUTPUT_PUSHPULL,     // 推挽输出
    GPIO_MODE_OUTPUT_OPENDRAIN     // 开漏输出
} GPIO_Mode_Custom;

// GPIO速率枚举
typedef enum
{
    GPIO_SPEED_10MHZ = 0,
    GPIO_SPEED_2MHZ,
    GPIO_SPEED_50MHZ
} GPIO_Speed_Custom;

// GPIO设备结构体(核心:配置参数+硬件句柄)
typedef struct
{
    GPIO_TypeDef* GPIOx;            // GPIO端口(GPIOA/GPIOB/GPIOC等)
    uint16_t GPIO_Pin;              // GPIO引脚(GPIO_Pin_0 ~ GPIO_Pin_15)
    GPIO_Mode_Custom GPIO_Mode;     // GPIO工作模式
    GPIO_Speed_Custom GPIO_Speed;   // GPIO输出速率(仅输出模式有效)
} GPIO_Device_T;

3.3 步骤2:实现GPIO底层初始化接口

// GPIO设备初始化函数
void GPIO_Device_Init(GPIO_Device_T* gpio_dev)
{
    // 参数合法性判断(避免空指针)
    if(gpio_dev == NULL || gpio_dev->GPIOx == NULL)
    {
        return;
    }
    
    // 1. 使能GPIO时钟(根据端口判断)
    if(gpio_dev->GPIOx == GPIOA)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    }
    else if(gpio_dev->GPIOx == GPIOB)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    }
    else if(gpio_dev->GPIOx == GPIOC)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
    }
    else if(gpio_dev->GPIOx == GPIOD)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
    }
    
    // 2. 配置GPIO初始化结构体
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin = gpio_dev->GPIO_Pin;
    GPIO_InitStruct.GPIO_Speed = (GPIOSpeed_TypeDef)gpio_dev->GPIO_Speed;
    
    // 3. 根据自定义模式配置GPIO工作模式
    switch(gpio_dev->GPIO_Mode)
    {
        case GPIO_MODE_INPUT_FLOATING:
            GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
            break;
        case GPIO_MODE_INPUT_PULLUP:
            GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
            break;
        case GPIO_MODE_INPUT_PULLDOWN:
            GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
            break;
        case GPIO_MODE_OUTPUT_PUSHPULL:
            GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
            break;
        case GPIO_MODE_OUTPUT_OPENDRAIN:
            GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
            break;
        default:
            GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
            break;
    }
    
    // 4. 完成GPIO硬件初始化
    GPIO_Init(gpio_dev->GPIOx, &GPIO_InitStruct);
}

3.4 步骤3:封装上层通用操作接口

// 1. GPIO输出置高
void GPIO_Set_High(GPIO_Device_T* gpio_dev)
{
    if(gpio_dev == NULL || gpio_dev->GPIOx == NULL)
    {
        return;
    }
    GPIO_SetBits(gpio_dev->GPIOx, gpio_dev->GPIO_Pin);
}

// 2. GPIO输出置低
void GPIO_Set_Low(GPIO_Device_T* gpio_dev)
{
    if(gpio_dev == NULL || gpio_dev->GPIOx == NULL)
    {
        return;
    }
    GPIO_ResetBits(gpio_dev->GPIOx, gpio_dev->GPIO_Pin);
}

// 3. GPIO输出电平翻转
void GPIO_Toggle(GPIO_Device_T* gpio_dev)
{
    if(gpio_dev == NULL || gpio_dev->GPIOx == NULL)
    {
        return;
    }
    gpio_dev->GPIOx->ODR ^= gpio_dev->GPIO_Pin;
}

// 4. GPIO输入电平读取
uint8_t GPIO_Read_Level(GPIO_Device_T* gpio_dev)
{
    if(gpio_dev == NULL || gpio_dev->GPIOx == NULL)
    {
        return 0;
    }
    return GPIO_ReadInputDataBit(gpio_dev->GPIOx, gpio_dev->GPIO_Pin);
}

3.5 实战使用示例(LED控制+按键读取)

// 1. 定义设备实例(LED:PB0 推挽输出 50MHZ)
GPIO_Device_T LED_Dev = {
    .GPIOx = GPIOB,
    .GPIO_Pin = GPIO_Pin_0,
    .GPIO_Mode = GPIO_MODE_OUTPUT_PUSHPULL,
    .GPIO_Speed = GPIO_SPEED_50MHZ
};

// 2. 定义设备实例(按键:PA0 上拉输入)
GPIO_Device_T KEY_Dev = {
    .GPIOx = GPIOA,
    .GPIO_Pin = GPIO_Pin_0,
    .GPIO_Mode = GPIO_MODE_INPUT_PULLUP,
    .GPIO_Speed = GPIO_SPEED_50MHZ  // 输入模式下该参数无效
};

// 3. 业务层使用(无需关心底层硬件配置)
int main(void)
{
    // 初始化GPIO设备
    GPIO_Device_Init(&LED_Dev);
    GPIO_Device_Init(&KEY_Dev);
    
    while(1)
    {
        // 读取按键状态(低电平表示按下)
        if(GPIO_Read_Level(&KEY_Dev) == 0)
        {
            // 延时消抖
            for(uint32_t i=0; i<100000; i++);
            if(GPIO_Read_Level(&KEY_Dev) == 0)
            {
                // LED电平翻转
                GPIO_Toggle(&LED_Dev);
                // 等待按键释放
                while(GPIO_Read_Level(&KEY_Dev) == 0);
            }
        }
    }
}

3.6 避坑要点

  1. 输入模式下,上拉/下拉配置需与硬件电路匹配(按键电路若外接下拉电阻,软件应配置为浮空输入);
  2. 输出模式下,推挽输出适合直接驱动负载(如LED),开漏输出适合线与逻辑(如I2C总线);
  3. 时钟使能是必做步骤,遗漏会导致GPIO配置无效,这是新手最常见的错误;
  4. 结构体参数初始化时,确保端口与引脚的合法性(避免超出MCU支持范围,如STM32F103无GPIOE则不可配置)。

四、实战二:串口驱动封装(带中断接收,解决数据丢失)

4.1 封装思路

串口的核心操作包括:初始化(波特率、数据位、停止位、校验位)、字节/字符串发送、数据接收。由于串口接收具有异步性,直接查询接收易导致数据丢失,因此封装时加入环形接收缓冲区+中断接收机制,同时提供统一的发送/接收接口,屏蔽中断与缓冲区的底层细节。

4.2 步骤1:定义串口设备结构体与宏定义

// 头文件包含
#include "stm32f10x.h"
#include <string.h>

// 串口接收缓冲区大小配置(根据业务需求调整,默认64字节)
#define UART_RX_BUF_SIZE  64

// 串口停止位枚举
typedef enum
{
    UART_STOPBITS_1 = 0,
    UART_STOPBITS_0_5,
    UART_STOPBITS_2,
    UART_STOPBITS_1_5
} UART_StopBits_Custom;

// 串口校验位枚举
typedef enum
{
    UART_PARITY_NONE = 0,
    UART_PARITY_ODD,
    UART_PARITY_EVEN
} UART_Parity_Custom;

// 串口设备结构体(配置参数+状态+缓冲区)
typedef struct
{
    USART_TypeDef* USARTx;          // 串口号(USART1/USART2/USART3)
    uint32_t BaudRate;              // 波特率(9600/19200/38400/115200等)
    UART_StopBits_Custom StopBits;  // 停止位
    UART_Parity_Custom Parity;      // 校验位
    uint8_t Rx_Buf[UART_RX_BUF_SIZE];// 接收缓冲区
    uint16_t Rx_Idx;                // 接收缓冲区索引
    uint8_t Rx_Flag;                // 接收完成标志(0:未完成,1:完成)
} UART_Device_T;

4.3 步骤2:实现串口底层初始化与中断配置

// 全局串口设备指针(用于中断服务函数中访问)
static UART_Device_T* g_uart_dev = NULL;

// 串口设备初始化函数
void UART_Device_Init(UART_Device_T* uart_dev)
{
    // 参数合法性判断
    if(uart_dev == NULL || uart_dev->USARTx == NULL)
    {
        return;
    }
    
    // 保存全局设备指针(供中断服务函数使用)
    g_uart_dev = uart_dev;
    // 初始化接收缓冲区与状态
    memset(uart_dev->Rx_Buf, 0, UART_RX_BUF_SIZE);
    uart_dev->Rx_Idx = 0;
    uart_dev->Rx_Flag = 0;
    
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;
    
    // 1. 使能时钟(串口时钟+GPIO时钟)
    if(uart_dev->USARTx == USART1)
    {
        // USART1时钟使能
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
        // GPIOA时钟使能(USART1:TX=PA9,RX=PA10)
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    }
    else if(uart_dev->USARTx == USART2)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // USART2:TX=PA2,RX=PA3
    }
    else if(uart_dev->USARTx == USART3)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // USART3:TX=PB10,RX=PB11
    }
    
    // 2. 配置GPIO(TX:推挽输出,RX:浮空输入)
    if(uart_dev->USARTx == USART1)
    {
        // TX=PA9 推挽输出
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // RX=PA10 浮空输入
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
    else if(uart_dev->USARTx == USART2)
    {
        // TX=PA2 推挽输出
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_2;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOA, &GPIO_InitStruct);
        
        // RX=PA3 浮空输入
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
    else if(uart_dev->USARTx == USART3)
    {
        // TX=PB10 推挽输出
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
        GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(GPIOB, &GPIO_InitStruct);
        
        // RX=PB11 浮空输入
        GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11;
        GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
        GPIO_Init(GPIOB, &GPIO_InitStruct);
    }
    
    // 3. 配置串口核心参数
    USART_InitStruct.USART_BaudRate = uart_dev->BaudRate;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b; // 固定8位数据位
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
    USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 收发使能
    
    // 配置停止位
    switch(uart_dev->StopBits)
    {
        case UART_STOPBITS_1:
            USART_InitStruct.USART_StopBits = USART_StopBits_1;
            break;
        case UART_STOPBITS_0_5:
            USART_InitStruct.USART_StopBits = USART_StopBits_0_5;
            break;
        case UART_STOPBITS_2:
            USART_InitStruct.USART_StopBits = USART_StopBits_2;
            break;
        case UART_STOPBITS_1_5:
            USART_InitStruct.USART_StopBits = USART_StopBits_1_5;
            break;
        default:
            USART_InitStruct.USART_StopBits = USART_StopBits_1;
            break;
    }
    
    // 配置校验位
    switch(uart_dev->Parity)
    {
        case UART_PARITY_NONE:
            USART_InitStruct.USART_Parity = USART_Parity_No;
            break;
        case UART_PARITY_ODD:
            USART_InitStruct.USART_Parity = USART_Parity_Odd;
            USART_InitStruct.USART_WordLength = USART_WordLength_9b; // 校验位使能时,数据位为9位
            break;
        case UART_PARITY_EVEN:
            USART_InitStruct.USART_Parity = USART_Parity_Even;
            USART_InitStruct.USART_WordLength = USART_WordLength_9b;
            break;
        default:
            USART_InitStruct.USART_Parity = USART_Parity_No;
            break;
    }
    
    // 4. 初始化串口
    USART_Init(uart_dev->USARTx, &USART_InitStruct);
    
    // 5. 配置串口接收中断
    USART_ITConfig(uart_dev->USARTx, USART_IT_RXNE, ENABLE); // 使能接收非空中断
    
    // 6. 配置NVIC中断优先级
    NVIC_InitStruct.NVIC_IRQChannel = (uart_dev->USARTx == USART1) ? USART1_IRQn : 
                                      (uart_dev->USARTx == USART2) ? USART2_IRQn : USART3_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;        // 子优先级0
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
    
    // 7. 使能串口
    USART_Cmd(uart_dev->USARTx, ENABLE);
}

// 串口中断服务函数(以USART1为例,USART2/3类似)
void USART1_IRQHandler(void)
{
    uint8_t rx_data;
    
    // 接收非空中断判断
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
    {
        // 读取接收数据
        rx_data = USART_ReceiveData(USART1);
        
        // 写入接收缓冲区(防止缓冲区溢出)
        if(g_uart_dev != NULL && g_uart_dev->Rx_Idx < UART_RX_BUF_SIZE)
        {
            // 遇到换行符,标记接收完成(根据业务调整结束标志)
            if(rx_data == '\n' || rx_data == '\r')
            {
                g_uart_dev->Rx_Buf[g_uart_dev->Rx_Idx] = '\0'; // 字符串结束符
                g_uart_dev->Rx_Flag = 1;
                g_uart_dev->Rx_Idx = 0; // 重置索引
            }
            else
            {
                g_uart_dev->Rx_Buf[g_uart_dev->Rx_Idx++] = rx_data;
            }
        }
        
        // 清除中断标志位
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    }
}

4.4 步骤3:封装上层发送/接收接口

// 1. 串口发送单个字节
void UART_Send_Byte(UART_Device_T* uart_dev, uint8_t data)
{
    if(uart_dev == NULL || uart_dev->USARTx == NULL)
    {
        return;
    }
    
    // 等待发送缓冲区为空
    while(USART_GetFlagStatus(uart_dev->USARTx, USART_FLAG_TXE) == RESET);
    // 发送数据
    USART_SendData(uart_dev->USARTx, data);
    // 等待发送完成
    while(USART_GetFlagStatus(uart_dev->USARTx, USART_FLAG_TC) == RESET);
}

// 2. 串口发送字符串
void UART_Send_String(UART_Device_T* uart_dev, char* str)
{
    if(uart_dev == NULL || uart_dev->USARTx == NULL || str == NULL)
    {
        return;
    }
    
    // 逐个字节发送
    while(*str != '\0')
    {
        UART_Send_Byte(uart_dev, *str);
        str++;
    }
}

// 3. 串口获取接收数据
uint8_t UART_Get_Receive_Data(UART_Device_T* uart_dev, char* rx_buf, uint16_t buf_size)
{
    if(uart_dev == NULL || rx_buf == NULL || buf_size == 0)
    {
        return 0;
    }
    
    // 接收未完成,直接返回
    if(uart_dev->Rx_Flag == 0)
    {
        return 0;
    }
    
    // 复制接收缓冲区数据到上层缓冲区(防止溢出)
    strncpy(rx_buf, (char*)uart_dev->Rx_Buf, buf_size-1);
    rx_buf[buf_size-1] = '\0';
    
    // 清除接收完成标志
    uart_dev->Rx_Flag = 0;
    
    return 1;
}

4.5 实战使用示例(串口日志打印+指令接收)

// 1. 定义串口设备实例(USART1 115200 1停止位 无校验)
UART_Device_T UART1_Dev = {
    .USARTx = USART1,
    .BaudRate = 115200,
    .StopBits = UART_STOPBITS_1,
    .Parity = UART_PARITY_NONE
};

// 2. 业务层使用
int main(void)
{
    char rx_buf[UART_RX_BUF_SIZE];
    
    // 初始化串口设备
    UART_Device_Init(&UART1_Dev);
    
    // 发送欢迎信息
    UART_Send_String(&UART1_Dev, "Hello UART Driver!\r\n");
    
    while(1)
    {
        // 获取接收数据
        if(UART_Get_Receive_Data(&UART1_Dev, rx_buf, UART_RX_BUF_SIZE))
        {
            // 回显接收数据
            UART_Send_String(&UART1_Dev, "Receive: ");
            UART_Send_String(&UART1_Dev, rx_buf);
            UART_Send_String(&UART1_Dev, "\r\n");
        }
    }
}

4.6 避坑要点

  1. 波特率配置需与外部设备一致,且需匹配MCU时钟(STM32F103主频72MHz,波特率115200需确保分频准确);
  2. 中断优先级配置需合理,避免串口中断被其他高优先级中断阻塞,导致数据丢失;
  3. 接收缓冲区大小需根据业务调整,过大浪费RAM,过小易溢出;
  4. 中断服务函数中需快速处理数据,避免执行耗时操作(如复杂运算、延时),否则会导致中断阻塞;
  5. 发送数据后需等待发送完成标志(TC),避免数据未发送完毕就进行下一次操作。

五、实战三:定时器驱动封装(基本定时+中断回调)

5.1 封装思路

定时器的核心操作包括:初始化(分频、自动重装、定时模式)、启动/停止、定时中断回调、获取计数值。封装时,支持两种工作模式:基本定时(查询模式,用于简单延时)中断定时(周期性任务,如定时采集、定时上报),通过回调函数解耦中断处理与业务逻辑,提高驱动的灵活性。

5.2 步骤1:定义定时器设备结构体与宏定义

// 头文件包含
#include "stm32f10x.h"

// 定时器模式枚举
typedef enum
{
    TIM_MODE_BASE = 0,        // 基本定时模式(查询)
    TIM_MODE_INTERRUPT        // 中断定时模式(周期性回调)
} TIM_Mode_Custom;

// 定时器设备结构体(配置参数+回调函数+状态)
typedef struct
{
    TIM_TypeDef* TIMx;            // 定时器(TIM1/TIM2/TIM3/TIM4)
    uint32_t Prescaler;           // 分频系数(PSC)
    uint32_t AutoReload;          // 自动重装值(ARR)
    TIM_Mode_Custom TIM_Mode;     // 定时器工作模式
    void (*TIM_Callback)(void);   // 中断回调函数(仅中断模式有效)
    uint8_t TIM_Flag;             // 定时完成标志(仅基本模式有效)
} TIM_Device_T;

5.3 步骤2:实现定时器底层初始化与中断配置

// 全局定时器设备指针(用于中断服务函数)
static TIM_Device_T* g_tim_dev = NULL;

// 定时器设备初始化函数
void TIM_Device_Init(TIM_Device_T* tim_dev)
{
    // 参数合法性判断
    if(tim_dev == NULL || tim_dev->TIMx == NULL)
    {
        return;
    }
    
    // 保存全局设备指针
    g_tim_dev = tim_dev;
    tim_dev->TIM_Flag = 0;
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
    NVIC_InitTypeDef NVIC_InitStruct;
    
    // 1. 使能定时器时钟
    if(tim_dev->TIMx == TIM1)
    {
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
    }
    else if(tim_dev->TIMx == TIM2)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    }
    else if(tim_dev->TIMx == TIM3)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    }
    else if(tim_dev->TIMx == TIM4)
    {
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    }
    
    // 2. 配置定时器时基参数
    TIM_TimeBaseStruct.TIM_Prescaler = tim_dev->Prescaler - 1; // 分频系数-1(计数器从0开始)
    TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式
    TIM_TimeBaseStruct.TIM_Period = tim_dev->AutoReload - 1; // 自动重装值-1
    TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频1
    TIM_TimeBaseStruct.TIM_RepetitionCounter = 0; // 重复计数器(仅TIM1/TIM8有效)
    
    // 3. 初始化定时器
    TIM_TimeBaseInit(tim_dev->TIMx, &TIM_TimeBaseStruct);
    
    // 4. 配置中断(仅中断模式有效)
    if(tim_dev->TIM_Mode == TIM_MODE_INTERRUPT)
    {
        // 使能更新中断(定时溢出中断)
        TIM_ITConfig(tim_dev->TIMx, TIM_IT_Update, ENABLE);
        
        // 配置NVIC中断优先级
        NVIC_InitStruct.NVIC_IRQChannel = (tim_dev->TIMx == TIM1) ? TIM1_UP_IRQn : 
                                          (tim_dev->TIMx == TIM2) ? TIM2_IRQn : 
                                          (tim_dev->TIMx == TIM3) ? TIM3_IRQn : TIM4_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
        NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
    }
    
    // 5. 关闭定时器(等待上层启动)
    TIM_Cmd(tim_dev->TIMx, DISABLE);
}

// 定时器中断服务函数(以TIM3为例)
void TIM3_IRQHandler(void)
{
    // 更新中断判断
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
    {
        // 调用回调函数(上层业务实现)
        if(g_tim_dev != NULL && g_tim_dev->TIM_Callback != NULL)
        {
            g_tim_dev->TIM_Callback();
        }
        
        // 清除中断标志位
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}

5.4 步骤3:封装上层启动/停止/回调配置接口

// 1. 启动定时器
void TIM_Start(TIM_Device_T* tim_dev)
{
    if(tim_dev == NULL || tim_dev->TIMx == NULL)
    {
        return;
    }
    
    // 重置计数器
    TIM_SetCounter(tim_dev->TIMx, 0);
    // 清除定时完成标志
    tim_dev->TIM_Flag = 0;
    // 启动定时器
    TIM_Cmd(tim_dev->TIMx, ENABLE);
}

// 2. 停止定时器
void TIM_Stop(TIM_Device_T* tim_dev)
{
    if(tim_dev == NULL || tim_dev->TIMx == NULL)
    {
        return;
    }
    
    // 停止定时器
    TIM_Cmd(tim_dev->TIMx, DISABLE);
    // 重置计数器
    TIM_SetCounter(tim_dev->TIMx, 0);
}

// 3. 设置定时器中断回调函数
void TIM_Set_Callback(TIM_Device_T* tim_dev, void (*callback)(void))
{
    if(tim_dev == NULL)
    {
        return;
    }
    
    tim_dev->TIM_Callback = callback;
}

// 4. 基本模式下查询定时完成
uint8_t TIM_Check_Complete(TIM_Device_T* tim_dev)
{
    if(tim_dev == NULL || tim_dev->TIMx == NULL)
    {
        return 0;
    }
    
    // 基本模式下,查询更新标志位
    if(tim_dev->TIM_Mode == TIM_MODE_BASE)
    {
        if(TIM_GetFlagStatus(tim_dev->TIMx, TIM_FLAG_Update) != RESET)
        {
            TIM_ClearFlag(tim_dev->TIMx, TIM_FLAG_Update);
            tim_dev->TIM_Flag = 1;
            TIM_Stop(tim_dev);
            return 1;
        }
    }
    
    return tim_dev->TIM_Flag;
}

5.5 实战使用示例(周期性LED翻转+基本定时延时)

// 1. 声明GPIO设备(复用之前的LED设备)
extern GPIO_Device_T LED_Dev;

// 2. 定义定时器设备实例(TIM3 1ms中断 分频72 自动重装1000)
TIM_Device_T TIM3_Dev = {
    .TIMx = TIM3,
    .Prescaler = 72,        // 72MHz / 72 = 1MHz(计数器时钟1us)
    .AutoReload = 1000,     // 1MHz * 1000 = 1s(定时1秒)
    .TIM_Mode = TIM_MODE_INTERRUPT
};

// 3. 定时器中断回调函数(业务层实现:LED翻转)
void TIM3_Callback_Func(void)
{
    GPIO_Toggle(&LED_Dev);
}

// 4. 业务层使用
int main(void)
{
    // 初始化GPIO与定时器
    GPIO_Device_Init(&LED_Dev);
    TIM_Device_Init(&TIM3_Dev);
    
    // 设置定时器回调函数
    TIM_Set_Callback(&TIM3_Dev, TIM3_Callback_Func);
    
    // 启动定时器(周期性LED翻转)
    TIM_Start(&TIM3_Dev);
    
    while(1)
    {
        // 无需额外操作,中断自动回调
    }
}

5.6 避坑要点

  1. 分频系数(Prescaler)与自动重装值(AutoReload)的计算需准确,公式:定时时间 = (Prescaler * AutoReload) / 定时器时钟频率
  2. 定时器时钟来源需注意(TIM1/TIM8为APB2总线,频率72MHz;TIM2/TIM3/TIM4为APB1总线,频率36MHz);
  3. 中断回调函数需简洁高效,避免执行耗时操作,否则会影响定时精度;
  4. 基本模式下,定时完成后需及时停止定时器并清除标志位,避免重复触发;
  5. 自动重装预装载使能(TIM_ARRPreloadConfig)默认关闭,如需固定定时周期,可开启该功能。

六、三大字符设备驱动封装总结与进阶方向

6.1 封装核心总结

  1. 统一封装范式:结构体定义→硬件初始化→上层接口封装,适用于绝大多数字符设备;
  2. 核心价值:解耦硬件与业务,提高代码可复用性、可维护性,降低移植成本;
  3. 关键要点:参数合法性判断、时钟使能、中断配置、缓冲区防溢出,是驱动健壮性的保障。

6.2 进阶学习方向

  1. 设备链表管理:将多个同类型设备(如多个GPIO、多个串口)加入链表,实现批量初始化、批量操作;
  2. 错误处理机制:增加驱动错误码(如初始化失败、缓冲区溢出),方便上层调试;
  3. HAL库适配:基于STM32 HAL库重新封装,适配最新MCU型号,提高兼容性;
  4. 多设备协同:实现GPIO、串口、定时器的协同工作(如定时通过串口发送GPIO采集数据);
  5. 驱动移植:将封装好的驱动移植到其他MCU(如STM32F407、ESP32),验证驱动的可复用性。
posted @ 2025-12-28 22:59  好汉技术  阅读(38)  评论(0)    收藏  举报