作为一个刚接触STM32的新手,我最初对中断和引脚复用的概念感到非常困惑。直到完成这个项目后,才真正理解了如何让外部中断、定时器中断和引脚复用协同工作。这个"小白版"教程,就是把我踩过的坑和总结的经验,用最直白的方式分享给同样刚入门的朋友们。
这个项目的核心价值在于:通过一个完整的示例,展示如何配置STM32的外部中断(比如按键触发)、定时器中断(周期性任务)以及引脚复用功能(优化IO资源分配)。这三种技术在实际项目中经常需要配合使用,比如用外部中断响应紧急事件,用定时器中断处理周期性数据采集,同时合理复用引脚以节省硬件资源。
我使用的是Keil MDK-ARM开发环境,配合ST-Link下载器。安装时需要注意:
注意:不同STM32系列的库函数可能有差异,本教程基于STM32F1xx标准外设库。如果使用HAL库,原理相同但函数名称会有变化。
首先配置用于外部中断的GPIO引脚。我选择PA0作为外部中断输入引脚:
c复制void EXTI_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 开启GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA0为输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
STM32的外部中断线(EXTI)与GPIO引脚有固定映射关系:
code复制PA0 ~ PX0 -> EXTI0
PA1 ~ PX1 -> EXTI1
...
PA15 ~ PX15 -> EXTI15
配置代码示例:
c复制void EXTI_Config(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 连接EXTI线到PA0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
// 配置EXTI0
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
在stm32f10x_it.c文件中添加中断服务函数:
c复制void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0) != RESET)
{
// 处理中断事件
LED_Toggle(); // 示例:切换LED状态
// 清除中断标志
EXTI_ClearITPendingBit(EXTI_Line0);
}
}
我使用TIM2作为定时器中断源,配置为1ms中断一次:
c复制void TIM_Config(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 开启TIM2时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 定时器基础配置
TIM_TimeBaseStructure.TIM_Period = 1000 - 1; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1; // 预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 使能TIM2中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 启动定时器
TIM_Cmd(TIM2, ENABLE);
}
c复制void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)
{
// 处理定时中断
static uint32_t timeCount = 0;
timeCount++;
if(timeCount >= 1000) // 1秒到
{
timeCount = 0;
// 执行周期性任务
}
// 清除中断标志
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
STM32的许多引脚都有多种功能,通过AFIO(Alternate Function I/O)模块进行配置。常见的复用功能包括:
以配置PA9和PA10为USART1的TX和RX为例:
c复制void GPIO_AF_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 开启GPIOA和AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
// 配置PA9为复用推挽输出(USART1_TX)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置PA10为浮空输入(USART1_RX)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
某些外设的引脚位置可以通过重映射改变:
c复制// 重映射USART1到PB6/PB7
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
STM32使用4位来表示优先级,分为抢占优先级和子优先级:
c复制NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 2位抢占,2位子优先级
c复制// 在中断服务函数中临时调整优先级
void EXTI0_IRQHandler(void)
{
// 临时提高某个中断的优先级
NVIC_SetPriority(TIM2_IRQn, 0);
// 处理中断...
// 恢复原优先级
NVIC_SetPriority(TIM2_IRQn, 2);
}
c复制int main(void)
{
// 初始化各模块
LED_GPIO_Config();
EXTI_GPIO_Config();
EXTI_Config();
TIM_Config();
GPIO_AF_Config();
USART_Config(115200);
printf("System Start!\r\n");
while(1)
{
// 主循环处理非紧急任务
__WFI(); // 进入低功耗模式,等待中断唤醒
}
}
使用LED指示灯辅助调试:
串口打印调试信息:
c复制printf("EXTI0 triggered at %d ms\r\n", millis);
使用逻辑分析仪捕捉中断时序
中断不触发:
定时器不准:
引脚复用失败:
精简中断服务函数:
使用DMA减轻CPU负担:
c复制DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART_TX_Buffer;
// ...其他DMA配置
对于新手,推荐使用STM32CubeMX工具图形化配置:
在实际项目中,我通常会先使用CubeMX生成基础框架,然后手动优化关键部分的中断处理代码。记住一点:中断服务函数应该尽可能短小精悍,复杂的数据处理可以放到主循环中通过标志位来触发。