复位和时钟控制(RCC)
系统复位
当发生以下任一事件时,产生一个系统复位:
1. NRST引脚上的低电平(外部复位)
2. 窗口看门狗计数终止(WWDG复位)
3. 独立看门狗计数终止(IWDG复位)
4. 软件复位(SW复位)
5. 低功耗管理复位
电源复位
当以下事件中之一发生时,产生电源复位:
1. 上电/掉电复位(POR/PDR复位)
2. 从待机模式中返回
备份区复位
备份区域拥有两个专门的复位,它们只影响备份区域。
当以下事件中之一发生时,产生备份区域复位。
1. 软件复位,备份区域复位可由设置备份域控制寄存器 (RCC_BDCR)(见6.3.9节)中的 BDRST位产生。
2. 在VDD和VBAT两者掉电的前提下,VDD或VBAT上电将引发备份区域复位。
时钟控制
什么是时钟?时钟打开,相应的设备工作。
时钟来源?
三种不同的时钟源可被用来驱动系统时钟(SYSCLK)
1. HSI振荡器时钟(高速内部时钟)
2. HSE振荡器时钟(高速外部时钟)
3. PLL时钟(锁相环倍频时钟)
二级时钟源:
1. 40kHz低速内部RC(LSIRC)振荡器
2. 32.768kHz低速外部晶体(LSE晶体)
使用CubeMX来配置时钟:
1. 创建一个新的工程:
2. 打开SYS,将Debug改成 Serial Wire:
3. 打开RCC配置时钟,将HSE振荡器时钟 配置成 “Crystal ....”:
4. 打开上方的“Clock Configuration"大选项:
4.1 按照下图,橙色圈中是需要修改的配置,橙色圈中修改完毕后按下回车其他会自动配置:
4.2 配置完之后的时钟图:
5. 点击上方的“Project Manager"大选项, 进行每个项目都需要的设置:
6. 生成代码:
7. Keil被自动打开,查看main函数中关于时钟部分的代码:
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
中断和事件
什么是中断?
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的 程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
什么是EXTI?
外部中断/事件控制器(EXTI)管理了控制器的 23 个中断/事件线。每个中断/事件线都对应有一 个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。 EXTI 可以实现对每个中断/事 件线进行单独配置,可以单独配置为中断或者事件,以及触发事件的属性。
EXTI的结构体:
1. EXTI_Line 就是中断/事件线,共23条
2. EXTIMode_TypeDef EXTI_Mode 就是EXTI模式
3. EXTITrigger_TypeDef EXTI_Trigger 就是触发的类型
4. FunctionalState EXTI_LineCmd 就是EXTI的控制
什么是优先级?
抢占优先级和响应优先级的区别:
高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。
抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断。
抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行。
(这两句也就是说,响应优先级再高也不能打断原有的中断,但是当抢占优先级相同时,响应优先级的高低可以决定中断执行的顺序)
如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行
什么是优先级分组?
Cortex-M3允许具有较少中断源时使用较少的寄存器位指定中断源的优先级,因此STM32把 指定中断优先级的寄存器位减少到4位,这4个寄存器位的分组方式如下:
第一种分组 第0组:所有4位用于指定响应优先级
第二种分组 第1组:最高1位用于指定抢占式优先级,最低3位用于指定响应优先级
第三种分组 第2组:最高2位用于指定抢占式优先级,最低2位用于指定响应优先级
第四种分组 第3组:最高3位用于指定抢占式优先级,最低1位用于指定响应优先级
第五种分组 第4组:所有4位用于指定抢占式优先级
什么是NVIC?
STM32通过中断控制器NVIC(Nested Vectored Interrupt Controller)进行中断的管理 。 NVIC是属于Cortex内核的器件,不可屏蔽中断(NMI)和外部中断都由它来处理,但是SYSTICK 不是由NVIC控制的
NVIC的结构体:
1. NVIC_IRQChannel 就是选择中断中断/事件线,共23条
2/3. 分别配置抢占优先级和响应优先级
4. FunctionalState NVIC_IRQChannelCmd 就是NVIC的控制(ENABLE 代表 打开)
什么是中断向量表?
每个中断源,都有对应的处理程序,这个处理程序称为中断服务程序,其入口地址称为中断向量。所有中断的中断服务程序入口地址构成一个表,称为中断向量表;也有的机器把中断服务程 序入口的跳转指令构成一张表,称为中断向量跳转表。
使用中断的方式点亮LED
之前使用的是轮询的方式,即不断WHILE(1)中查询按钮是否被按下,这样实现虽然看起来没什么问题,但是其实 实际会有一些小小的时间延迟,比如我按下按钮2,系统还是会先检测是否按下按钮1,如果代码一多,时间的滞后就会更加明显,所以使用中断的方式是一种更好的解决方法。
1. 在CubeMX进行时钟和GPIO口的配置:
1.1
1.2
1.3
1.4
1.5(这步很重要,因为之前的轮询法此处配置的是GPIO_Input)
根据原理图可知,按键按下时对应GPIO口会被拉低,所以可以将PA0和PA1的EXTI模式配置成下降沿触发:
1.6 (打开中断)
由于现在只有两个中断,所以抢占优先级和响应优先级可以先不用设置!
1.7
1.8
2. 在Keil5进行main函数的编写:
2.1 打开gpio.c 可以看到对于刚刚四个GPIO口的设置:
2.2 打开stm32f1xx_it.c 可以看到各种中断相关的函数(Handler),拉到最低,可以看到EXTI0和EXTI1的中断相关函数:
可见,两个中断处理函数都跳转到了一个函数,不同的就是输入的参数,右键跳转这个HAL_GPIO_EXTI_IRQHandler():(跳转前先编译,否则识别不到)
可见,这个函数又在调用这个HAL_GPIO_EXTI_Callback的函数,而这个函数的定义也就在下面,并附上了前缀“__weak”,这个意思是这个函数可以被重写,其实这就是中断处理函数,只需要在main函数中,重写这个函数达到想要的目的就可以:
2.3 编写中断处理函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
switch(GPIO_Pin)
{
HAL_Delay(50); //在检测到按键被按下的低电平的时候,先延迟50ms,再进行判断
case GPIO_PIN_0: //如果A0对应的按钮按下
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){ //如果延迟过后依然是低电平
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8); //反转LED1的状态
}
break;
case GPIO_PIN_1: //如果A1对应的按钮按下
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){ //如果延迟过后依然是低电平
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9); //反转LED2的状态
}
break;
}
}
2.4 编译并烧录
3. 实现效果
用更好的方法实现了之前轮询实现的按键点灯!