字符设备驱动实战教程(GPIO/串口/定时器驱动封装)附STM32可复现代码
一、前言:为什么要做字符设备驱动封装?
在嵌入式裸机开发中,GPIO、串口、定时器是最基础、最常用的三大字符设备(操作以字节/字符为单位,无复杂块设备逻辑)。很多新手初期会直接操作寄存器或调用库函数零散实现功能,导致代码存在耦合度高、可复用性差、维护困难、移植繁琐等问题。
字符设备驱动封装的核心价值,是将硬件操作与业务逻辑解耦,提供统一、简洁的上层操作接口,屏蔽底层硬件细节。比如:封装后的GPIO驱动,更换MCU型号时,仅需修改驱动底层实现,上层业务代码(如LED控制、按键读取)无需改动;封装后的串口驱动,可快速实现日志打印、指令接收等功能,无需重复编写中断与缓冲区逻辑。
本文基于STM32F103(最主流、资料最丰富的入门MCU),采用标准库(兼顾HAL库思路),逐一实现GPIO、串口、定时器的驱动封装,所有代码均可直接复现,符合工程实战规范,同时兼顾搜索引擎友好性,核心关键词全覆盖。
二、字符设备驱动封装通用思想与流程
2.1 核心封装原则(工程必备)
在开始具体设备封装前,先掌握3个通用原则,确保驱动的可复用性与健壮性:
- 高内聚、低耦合:硬件操作细节封装在驱动内部,上层仅调用统一接口,不直接操作寄存器;
- 可配置化:通过结构体、宏定义配置设备参数(如GPIO引脚、串口波特率、定时器分频系数),方便快速修改;
- 简洁易用:上层接口命名规范、参数清晰,避免复杂的参数传递,降低业务层使用门槛。
2.2 通用封装流程(三步法)
无论GPIO、串口还是定时器,驱动封装都遵循以下三步流程,形成标准化开发范式:
- 定义设备结构体:封装设备的核心配置参数、状态信息、句柄等,作为设备的“身份档案”;
- 实现底层硬件初始化:完成时钟使能、寄存器配置、中断配置等硬件相关操作,基于设备结构体参数进行初始化;
- 封装上层操作接口:实现设备的核心功能接口(如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 避坑要点
- 输入模式下,上拉/下拉配置需与硬件电路匹配(按键电路若外接下拉电阻,软件应配置为浮空输入);
- 输出模式下,推挽输出适合直接驱动负载(如LED),开漏输出适合线与逻辑(如I2C总线);
- 时钟使能是必做步骤,遗漏会导致GPIO配置无效,这是新手最常见的错误;
- 结构体参数初始化时,确保端口与引脚的合法性(避免超出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 避坑要点
- 波特率配置需与外部设备一致,且需匹配MCU时钟(STM32F103主频72MHz,波特率115200需确保分频准确);
- 中断优先级配置需合理,避免串口中断被其他高优先级中断阻塞,导致数据丢失;
- 接收缓冲区大小需根据业务调整,过大浪费RAM,过小易溢出;
- 中断服务函数中需快速处理数据,避免执行耗时操作(如复杂运算、延时),否则会导致中断阻塞;
- 发送数据后需等待发送完成标志(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 避坑要点
- 分频系数(Prescaler)与自动重装值(AutoReload)的计算需准确,公式:
定时时间 = (Prescaler * AutoReload) / 定时器时钟频率; - 定时器时钟来源需注意(TIM1/TIM8为APB2总线,频率72MHz;TIM2/TIM3/TIM4为APB1总线,频率36MHz);
- 中断回调函数需简洁高效,避免执行耗时操作,否则会影响定时精度;
- 基本模式下,定时完成后需及时停止定时器并清除标志位,避免重复触发;
- 自动重装预装载使能(TIM_ARRPreloadConfig)默认关闭,如需固定定时周期,可开启该功能。
六、三大字符设备驱动封装总结与进阶方向
6.1 封装核心总结
- 统一封装范式:结构体定义→硬件初始化→上层接口封装,适用于绝大多数字符设备;
- 核心价值:解耦硬件与业务,提高代码可复用性、可维护性,降低移植成本;
- 关键要点:参数合法性判断、时钟使能、中断配置、缓冲区防溢出,是驱动健壮性的保障。
6.2 进阶学习方向
- 设备链表管理:将多个同类型设备(如多个GPIO、多个串口)加入链表,实现批量初始化、批量操作;
- 错误处理机制:增加驱动错误码(如初始化失败、缓冲区溢出),方便上层调试;
- HAL库适配:基于STM32 HAL库重新封装,适配最新MCU型号,提高兼容性;
- 多设备协同:实现GPIO、串口、定时器的协同工作(如定时通过串口发送GPIO采集数据);
- 驱动移植:将封装好的驱动移植到其他MCU(如STM32F407、ESP32),验证驱动的可复用性。
浙公网安备 33010602011771号