一、实验目的
- 掌握Proteus和Keil联调的方法。
- 掌握STM32中NVIC的特点、内部结构、工作原理和主要特性。
- 熟悉STM32上NVIC和EXTI相关的常用库函数的使用。
二、实验环境
OS:Windows11
软件:Keil uVision5,Proteus
三、实验内容
根据实验原理图配置对应的GPIO
口和定时器,实现下述功能:
- 配置三个
KEY
、四个LED
和串口相关的IO
功能;
- 配置
TIM3
,根据系统8MHz
时钟,配置定时周期为0.5
秒,向上计数模式;
- 配置
TIM3
的中断,在中断响应函数中翻转PA8
的输出状态,实现LED1
的闪烁;
- 配置
PB4
、PB5
、PB6
为上拉输入,使能AFIO
;
- 配置
EXTI4
和EXTI9_5
的中断优先级;
- 配置
PB4
、PB5
、PB6
的中断模式为下降沿中断;
- 在
PB4
、PB5
、PB6
的中断响应函数中分别翻转LED2
、LED3
、LED4
的状态,实现按一下灯亮,再按一下灯灭。
四、实验原理
在Proteus
中的电路图中,可见PA8~11
与LED灯相连。对于内容①要求配置三个KEY
、四个LED
和串口相关的IO
功能:首先配置四个LED灯和串口相关的IO功能,此处使用的是实验一中介绍的片内外设GPIO来配置。使能APB1总线上的引脚PA8~11所属端口GPIOA的时钟,设置需要使用的GPIOA端口,将PA8~11配置为普通推挽输出。
1 2 3 4
| GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11; GPIO_Init(GPIOA, &GPIO_InitStructure);
|
对于串口相关的功能:GPIOA用于USART模块实现双向通信,任何USART双向通信至少需要2个引脚:接受数据输入(RX)和发送数据输出(TX):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure);
USART_Cmd(USART2, ENABLE); printf("USART2 Init Completely\r\n");
|
接下来将完成内容②配置TIM3:由于要求配置的定时周期为0.5s,根据如下的公式:
$$
T=\frac{(TIM_{Prescaler}+1)(TIM_{Period}+1)}{TIMxCLK}
$$
TIMxCLK
在软件仿真中取值8MHZ,因此为使T=0.5s,则$(TIM_{Prescaler}+1)(TIM_{Period}+1)$要取值为4M。此处取Prescaler
预分频为4k,那么Period
持续时间选择1k。
对于TIM3
计数模式的选择,此处选择的是边沿对齐的向上计数。然后通过TIM_TimeBaseInit
函数对TIM3
进行初始化设置;通过TIM_ITConfig
,使能TIM3
的指定中断。此处选择的是TIM_IT_Update
TIM更新中断。最后开启TIM3
的时钟计数。
1 2 3 4 5 6 7 8 9
| TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseStructure.TIM_Prescaler = 4000-1; TIM_TimeBaseStructure.TIM_Period = 1000-1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); TIM_Cmd(TIM3, ENABLE);
|
1.内部中断
对于内部中断来说,根据定时器每到一次时间,产生一次中断。内容③要求在中断响应函数中翻转PA8
的输出状态,实现LED1
的闪烁。在写中断响应函数之前,首先应该进行NVIC
的配置来关联TIM3
的时钟中断:
对于优先级的设置,此处选择的是NVIC_PriorityGroup_1
,对应抢占优先级1位,子优先级3位。然后使能IRQ
通道为TIM3_IRQn
,即TIM3
的全局中断,从而使NVIC与TIM3的中断相关联。设置IRQ
通道的抢占优先级为1,子优先级为0。最后使能指定NVIC_IRQChannel
中定义的IRQ通道,即TIM3_IRQn
。并通过NVIC_Init
来将配置信息写入NVIC
中。
1 2 3 4 5 6 7 8
| NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
|
完成TIM3的计时器和NVIC中断向量控制器相关联后,在TIM3的发生中断响应时,就可以触发TIM3的中断响应函数。而在TIM3的中断相应函数中就可以反转PA8的状态,实现LED1的闪烁。若要实现循环闪烁,则在每次反转后都需要清除TIM的中断标志。
1 2 3 4 5
| void TIM3_IRQHandler(void){ if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_8) == Bit_SET)GPIO_ResetBits(GPIOA, GPIO_Pin_8); else GPIO_SetBits(GPIOA, GPIO_Pin_8); TIM_ClearITPendingBit(TIM3, TIM_IT_Update); }
|
2.外部中断
若使用STM32F103引脚的外部中断功能,需打开APB2总线上该引脚对应的端口时钟和AFTO功能时钟。
1
| RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);
|
内容④要求配置PB4~6为上拉输入,并使能AFIO。则要求我们通过GPIO来进行外部中断的控制。通过上述函数已将该引脚对应的端口时钟和AFTO功能时钟使能。然后进行关于PB4~6的GPIO口的设置。由于使用KEY来控制输入,因此输入模式的配置为上拉输入。
1 2 3 4
| GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6; GPIO_Init(GPIOB, &GPIO_InitStructure);
|
内容⑤要求配置EXTI4
和EXTI9_5
的中断优先级;对于EXTI0~4的中断是独立的,自己拥有独占的中断;而对于EXTI9~5,则需要共享一个EXTI9_5_IRQn
通道以及中断响应函数EXTI9_5_IRQHandler
。设置二者的抢占优先级均为1,子优先级为1;然后使能IRQ通道。
1 2 3 4 5 6 7 8 9 10 11
| NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure);
|
内容⑥要求配置PB4
、PB5
、PB6
的中断模式为下降沿中断;首先要先配置PB4~6为EXTI_Line4~6的引脚,只需要将外部中断线和GPIO对应的引脚相关联即可。通过EXTI_InitStructure.EXTI_Trigger
设置PB4~66`的中断模式为下降沿中断,即按下按键时会产生中断;最后对外部中断进行使能即可。当GPIO引脚相关的KEY被按下,就会触发相应的中断,并执行相应的中断响应函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| EXTI_InitTypeDef EXTI_InitStructure;
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource4); EXTI_InitStructure.EXTI_Line = EXTI_Line4; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource5); EXTI_InitStructure.EXTI_Line = EXTI_Line5; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource6); EXTI_InitStructure.EXTI_Line = EXTI_Line6; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure);
|
内容⑦要求在PB4
、PB5
、PB6
的中断响应函数中分别翻转LED2
、LED3
、LED4
的状态,实现按一下灯亮,再按一下灯灭。
对于EXTI4外部中断,其中断响应函数是独立的,因此只要PB4
触发EXTI4
的中断,就只需要判断与PB4
相关联的PA9
的状态即可,这样就能实现PB4
对PA9
所连的LED灯的控制。
1 2 3 4 5
| void EXTI4_IRQHandler(void){ if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_9) == Bit_SET)GPIO_ResetBits(GPIOA, GPIO_Pin_9); else GPIO_SetBits(GPIOA, GPIO_Pin_9); EXTI_ClearITPendingBit(EXTI_Line4); }
|
而对于PB5
和PB6
,它们所绑定的中断事件则是共用一个中断响应函数的。因此在EXTI9_5_IRQHandler
中,我们需要检测是哪个外部中断被触发:
PB5
控制PA10
,因此如果外部中断5发生,则检测PA10
的状态;
PB6
控制PA11
,因此如果外部中断6发生,则检测PA11
的状态。
1 2 3 4 5 6 7 8 9 10 11 12 13
| void EXTI9_5_IRQHandler(void){ if(EXTI_GetITStatus(EXTI_Line5) == SET){ if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_10) == Bit_SET)GPIO_ResetBits(GPIOA, GPIO_Pin_10); else GPIO_SetBits(GPIOA, GPIO_Pin_10); EXTI_ClearITPendingBit(EXTI_Line5); } if(EXTI_GetITStatus(EXTI_Line6) == SET){ if(GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_11) == Bit_SET)GPIO_ResetBits(GPIOA, GPIO_Pin_11); else GPIO_SetBits(GPIOA, GPIO_Pin_11); EXTI_ClearITPendingBit(EXTI_Line6); } }
|
五、实验结果及其分析
具体结果见视频。
六、心得体会与建议
通过本次实验,深入学习掌握了TIM定时器系统和NVIC的中断控制原理。