Schwertlilien
As a recoder: notes and ideas.

嵌入式上机实验3

一、实验目的

  • 掌握Proteus和Keil联调的方法。
  • 掌握STM32中NVIC的特点、内部结构、工作原理和主要特性。
  • 熟悉STM32上NVIC和EXTI相关的常用库函数的使用。

二、实验环境

OS:Windows11

软件:Keil uVision5,Proteus

三、实验内容

根据实验原理图配置对应的GPIO口和定时器,实现下述功能:

  1. 配置三个KEY、四个LED和串口相关的IO功能;
  2. 配置TIM3,根据系统8MHz时钟,配置定时周期为0.5秒,向上计数模式;
  3. 配置TIM3的中断,在中断响应函数中翻转PA8的输出状态,实现LED1的闪烁;
  4. 配置PB4PB5PB6为上拉输入,使能AFIO
  5. 配置EXTI4EXTI9_5的中断优先级;
  6. 配置PB4PB5PB6的中断模式为下降沿中断;
  7. PB4PB5PB6的中断响应函数中分别翻转LED2LED3LED4的状态,实现按一下灯亮,再按一下灯灭。

四、实验原理

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; //PA.8-11
GPIO_Init(GPIOA, &GPIO_InitStructure);

对于串口相关的功能:GPIOA用于USART模块实现双向通信,任何USART双向通信至少需要2个引脚:接受数据输入(RX)和发送数据输出(TX):

  • RX: 接受数据串行输入,使用引脚PA2,连接虚拟串口输出框。

  • TX: 发送数据输出,使用引脚PA3。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//USART2_TX   GPIOA.2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);//GPIOA.2

//Set USART2's Config
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);

//Enable USART2
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_UpdateTIM更新中断。最后开启TIM3的时钟计数。

1
2
3
4
5
6
7
8
9
//config TIM3
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);//Init
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);//定时器中断使能,定时到即产生中断
TIM_Cmd(TIM3, ENABLE);//开启TIM3的时钟计数
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);//抢占优先级1位,子优先级3位。
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能指定NVIC_IRQChannel中定义的IRQ通道,即TIM3_IRQn。
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);

内容⑤要求配置EXTI4EXTI9_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);

内容⑥要求配置PB4PB5PB6的中断模式为下降沿中断;首先要先配置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);

内容⑦要求在PB4PB5PB6的中断响应函数中分别翻转LED2LED3LED4的状态,实现按一下灯亮,再按一下灯灭。

对于EXTI4外部中断,其中断响应函数是独立的,因此只要PB4触发EXTI4的中断,就只需要判断与PB4相关联的PA9的状态即可,这样就能实现PB4PA9所连的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);
}

而对于PB5PB6,它们所绑定的中断事件则是共用一个中断响应函数的。因此在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的中断控制原理。

搜索
匹配结果数:
未搜索到匹配的文章。