大家好,欢迎来到IT知识分享网。
RCC是复位和时钟控制
首先是复位
1、复位
有三种复位:系统复位、电源复位和后备域复位。
系统复位
系统复位将复位除时钟控制寄存器CSR中的复位标志和备份区域中的寄存器以外的所有寄存器
为它们的复位数值
当以下事件中的一件发生时,产生一个系统复位:
1. NRST引脚上的低电平(外部复位)
2. 窗口看门狗计数终止(WWDG复位)
3. 独立看门狗计数终止(IWDG复位)
4. 软件复位(SW复位)
5. 低功耗管理复位
可通过查看RCC_CSR控制状态寄存器中的复位状态标志位识别复位事件来源。
软件复位
通过将Cortex™-M3中断应用和复位控制寄存器中的SYSRESETREQ位置’1’,可实现软件复位。请参考Cortex™-M3技术参考手册获得进一步信息。
电源复位
当以下事件中之一发生时,产生电源复位:
1. 上电/掉电复位(POR/PDR复位)
2. 从待机模式中返回
下面是时钟
2、时钟
1.三种不同的时钟源可被用来驱动系统时钟(SYSCLK):
● HSI振荡器时钟
● HSE振荡器时钟
● PLL时钟
时钟树
●当HSI被用于作为PLL时钟的输入时,系统时钟能得到的最大频率是36MHz。
● Flash存储器编程接口时钟始终是HSI时钟。
● 全速USB OTG的48MHz时钟是从PCC VCO时钟(2xPLLCLK),和随后可编程预分频器(除3或除2)得到,这是通过RCC_CFGR寄存器的OTGFSPRE位控制。为了正常地操作USB全速OTG,应该配置PLL输出72MHz或48MHz。
● I2S2和I2S3的时钟还可以从PLL3 VCO时钟(2xPLL3CLK)得到,这是通过RCC_CFGR2寄存器的I2SxSRC位控制。更多有关PLL3的内容和如何配置I2S时钟,以得到高质量的音频效果,请参阅第23.4.3节: 时钟发生器。
● 以太网MAC的时钟(TX、 RX和RMII)是由外部PHY提供。更多有关以太网配置的详情,请见第27.4.4节: MII/RMII的选择。当使用以太网模块时, AHB时钟频率必须至少为25MHz。
RCC通过AHB时钟(HCLK)8分频后作为Cortex系统定时器(SysTick)的外部时钟。通过对SysTick控制与状态寄存器的设置,可选择上述时钟或Cortex(HCLK)时钟作为SysTick时钟。 ADC时钟由高速APB2时钟经2、 4、 6或8分频后获得。
定时器时钟频率分配由硬件按以下2种情况自动设置:
1. 如果相应的APB预分频系数是1,定时器的时钟频率与所在APB总线频率一致。
2. 否则,定时器的时钟频率被设为与其相连的APB总线频率的2倍
这一部分正点原子的讲的比较易懂,建议看正点原子,看完正点原子再看野火的就很容易明白了,也可以只看正点原子的
2.时钟源介绍
(1)STM32有五个时钟源::HSI、HSE、LSI、LSE、PLL
①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高,一分频 可作为系统时钟,二分频可作为PLL时钟。能够在不需要任何外部器件的条件下提供系统时钟,启动时间比HSE短,校准后精度较差。如果HSE晶体振荡器失效, HSI时钟会被作为备用时钟源。
②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz.一分频和二分频均可作为PLL时钟。时钟控制寄存器(RCC_CR)中的HSERDY位判断是否稳定。在启动时,直到这一位被硬件置’1’,时钟才被释放出来。如果在时钟中断寄存器(RCC_CIR)中允许产生中断,将会产生相应中断。HSE晶体可以通过设置时钟控制寄存器(RCC_CR)中的HSEON位被启动和关闭。
③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。LSI RC担当一个低功耗时钟源的角色,可以在停机和待机模式下保持运行,为独立看门狗和自动唤醒单元提供时钟。
④、LSE是低速外部时钟,接频率为32. 768kHz的石英晶体。可作为RTC时钟。为实时时钟或者其他定时功能提供一个低功耗且精确的时钟源。
⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2.倍频可选择为2~16倍, 但是其输出频率最大不得超过72MHz.
PLL有三条来源:HSI的二分频,HSE的一分频和二分频
css:时钟监控系统,HSE失效时自动切换为HSI
注意: 一旦CSS被激活,并且HSE时钟出现故障, CSS中断就产生,并且NMI也自动产生。 NMI将被不断执行,直到CSS中断挂起位被清除。因此,在NMI的处理程序中必须通过设置时钟中断寄存器(RCC_CIR)里的CSSC位来清除CSS中断。
如果HSE振荡器被直接或间接地作为系统时钟, (间接的意思是:它被作为PLL输入时钟或通过PLL2,并且PLL时钟被作为系统时钟),时钟故障将导致系统时钟自动切换到HSI振荡器,同时外部HSE振荡器被关闭。在时钟失效时,如果HSE振荡器时钟(直接的或通过PLL2)是作为PLL的输入时钟, PLL也将被关闭。
(2)系统时钟SYSCLK可来源于三个时钟源:①、HSI振荡器时钟不分频②、HSE振荡器时钟不分频③、 PLL时钟
系统复位后, HSI振荡器被选为系统时钟。目标时钟源就绪(经过启动稳定阶段的延迟或PLL稳定),从 一个时钟源到另一个时钟源的切换才会发生。在被选择时钟源没有就绪时,系统时钟的切换不会发 生。直至目标时钟源就绪,才发生切换。
在时钟控制寄存器(RCC_CR)里的状态位指示哪个时钟已经准备好了,哪个时钟目前被用作系统
时钟。
(3)STM32可以选择一个时钟信号输出到MCO脚(PA8)上,可以选择为PLL输出的2分频、HSI、HSE、或者系统时钟。
(4)任何一个外设在使用之前,必须首先使能其相应的时钟。
3.时钟输出
微控制器允许输出时钟信号到外部MCO引脚。
相应的GPIO端口寄存器必须被配置为相应功能。以下8个时钟信号可被选作MCO时钟:
● SYSCLK
● HSI
● HSE
● 除2的PLL时钟
● PLL2时钟
● PLL3时钟除以2
● XT1外部3~25MHz振荡器(用于以太网)
● PLL3时钟(用于以太网)
在MCO上输出的时钟必须小于50MHz(这是I/O端口的最大速度)。
时钟的选择由时钟配置寄存器(RCC_CFGR)中的MCO[3:0]位控制。
3、代码详解
- RCC作用
从RCC结构体里可以看出RCC的作用(在stm32f10x.h里)
设置系统时钟 SYSCLK、设置 AHB 分频因子(决定 HCLK 等于多少) 、 设置 APB2 分频因子(决定 PCLK2 等于多少)、设置 APB1 分频因子(决定 PCLK1 等于多少)、设置各个外设的分频因子;控制AHB、 APB2 和 APB1 这三条总线时钟的开启、控制每个外设的时钟的开启。
RCC相关寄存器很重要,需要看芯片参考手册,下面的系统时钟库函数会用到
- 时钟系统初始化函数剖析
程序里的CL是互联型,我们用到的板子不是互联型,不用考虑CL部分的代码
启动文件的复位程序这里有一个SystemInit,从这里go to definition
就来到时钟系统初始化函数了
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */ /* Set HSION bit */ RCC->CR |= (uint32_t)0x00000001; //默认初始化状态 /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */ #ifndef STM32F10X_CL RCC->CFGR &= (uint32_t)0xF8FF0000; #else RCC->CFGR &= (uint32_t)0xF0FF0000; #endif /* STM32F10X_CL */ /* Reset HSEON, CSSON and PLLON bits */ RCC->CR &= (uint32_t)0xFEF6FFFF; /* Reset HSEBYP bit */ RCC->CR &= (uint32_t)0xFFFBFFFF; /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */ RCC->CFGR &= (uint32_t)0xFF80FFFF;
①首先是CR寄存器的HSION位置一,开启内部
/* Set HSION bit */ RCC->CR |= (uint32_t)0x00000001;
②CFGR寄存器SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO这些位全部清零
#ifndef STM32F10X_CL RCC->CFGR &= (uint32_t)0xF8FF0000; #else RCC->CFGR &= (uint32_t)0xF0FF0000; #endif /* STM32F10X_CL */
根据数值对应寄存器的每一位就可以知道,每一行代码的含义
③HSEON, CSSON,PLLON清零
/* Reset HSEON, CSSON and PLLON bits */ RCC->CR &= (uint32_t)0xFEF6FFFF;
这里视频里都有着带着看,就按照这种方式对应就好
然后就来到SetSysClock();这个函数
这个函数的作用是,根据这个宏定义,选择了系统时钟配置为72M的函数
static void SetSysClockTo72(void) { __IO uint32_t StartUpCounter = 0, HSEStatus = 0; /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/ /* Enable HSE */ RCC->CR |= ((uint32_t)RCC_CR_HSEON); /* Wait till HSE is ready and if Time out is reached exit */ //等待时钟稳定 do { HSEStatus = RCC->CR & RCC_CR_HSERDY;//判断CR寄存器的第17位是否为1 StartUpCounter++; } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT)); if ((RCC->CR & RCC_CR_HSERDY) != RESET) { HSEStatus = (uint32_t)0x01; } else { HSEStatus = (uint32_t)0x00; } if (HSEStatus == (uint32_t)0x01) { //闪存手册,不需要理解 /* Enable Prefetch Buffer */ FLASH->ACR |= FLASH_ACR_PRFTBE; /* Flash 2 wait state */ FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY); FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2; //两个等待状态 //配置三个时钟源的选择,系统时钟,APB1,APB2 /* HCLK = SYSCLK */ //go to definition RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; /* PCLK2 = HCLK */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; /* PCLK1 = HCLK/2 */ RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; #ifdef STM32F10X_CL /* Configure PLLs ------------------------------------------------------*/ /* PLL2 configuration: PLL2CLK = (HSE / 5) * 8 = 40 MHz */ /* PREDIV1 configuration: PREDIV1CLK = PLL2 / 5 = 8 MHz */ RCC->CFGR2 &= (uint32_t)~(RCC_CFGR2_PREDIV2 | RCC_CFGR2_PLL2MUL | RCC_CFGR2_PREDIV1 | RCC_CFGR2_PREDIV1SRC); RCC->CFGR2 |= (uint32_t)(RCC_CFGR2_PREDIV2_DIV5 | RCC_CFGR2_PLL2MUL8 | RCC_CFGR2_PREDIV1SRC_PLL2 | RCC_CFGR2_PREDIV1_DIV5); /* Enable PLL2 */ RCC->CR |= RCC_CR_PLL2ON; /* Wait till PLL2 is ready */ while((RCC->CR & RCC_CR_PLL2RDY) == 0) { } /* PLL configuration: PLLCLK = PREDIV1 * 9 = 72 MHz */ RCC->CFGR &= (uint32_t)~(RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLSRC | RCC_CFGR_PLLMULL); RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLXTPRE_PREDIV1 | RCC_CFGR_PLLSRC_PREDIV1 | RCC_CFGR_PLLMULL9); #else /* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE | RCC_CFGR_PLLMULL)); RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);//9倍 #endif /* STM32F10X_CL */ /* Enable PLL */ RCC->CR |= RCC_CR_PLLON; /* Wait till PLL is ready */ while((RCC->CR & RCC_CR_PLLRDY) == 0) { } /* Select PLL as system clock source */ RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; /* Wait till PLL is used as system clock source */ //等待系统时钟切换完成,SWS检测位 while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) { } } else { /* If HSE fails to start-up, the application will have wrong clock configuration. User can add here some code to deal with this error */ } } #endif
SW:选择系统时钟来源
SWS:SW位选择好时钟来源以后,SWS里面的相应为会被置一,读取SWS位,就可以确保当前时钟设置完毕
//配置三个时钟源的选择,AHB,APB1,APB2
本身注释就很清楚,go to definition去定义位置,后面注释都写了,可以自己根据宏定义的值对应一下相关寄存器
这个函数我只对下面这部分有点疑问,其余部分均能与寄存器对应上
/* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
??//HSE 作为PLL输入时钟,2分频,PLL倍频保留
RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
RCC_CFGR_PLLMULL));
HSE不分频进入锁相环
//选择HSE 作为PLL输入时钟
RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);//9倍
当设置好时钟以后,会有SystemCoreClock,可以通过SystemCoreClock在外部获取到设置的系统时钟是多少
- HSE时钟输入(bsp_rcc.c)
void Sys72_Config(void){ // 把RCC外设初始化成复位状态,这句是必须的 RCC_DeInit(); //使能HSE RCC_HSEConfig(RCC_HSE_ON); //等待时钟稳定,rcc.c里有检测始终是否启动的函数,直接调用就好 while(RCC_WaitForHSEStartUp()==ERROR){ } if (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == (uint32_t)0x01) { //笑死,在rcc里找半天没找着,flash当然在flah.c里找了 //看注释找对应函数 /* Enable Prefetch Buffer */ FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); /* Flash 2 wait state */ FLASH_SetLatency(FLASH_Latency_2); /* HCLK = SYSCLK */ RCC_HCLKConfig(RCC_SYSCLK_Div1); /* PCLK2 = HCLK */ RCC_PCLK2Config(RCC_HCLK_Div1); /* PCLK1 = HCLK/2 */ RCC_PCLK1Config(RCC_HCLK_Div2); /* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */ RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); /* Enable PLL */ RCC_PLLCmd(ENABLE); /* Wait till PLL is ready */ while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) { } /* Select PLL as system clock source */ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); /* Wait till PLL is used as system clock source */ while (RCC_GetSYSCLKSource() != (uint32_t)0x08) { } } else { /* If HSE fails to start-up, the application will have wrong clock configuration. User can add here some code to deal with this error */ } while (1) {} }
改错&修补
我和例程写的不一样在于,例程写的是有参数的函数,我写的函数参数是void
这句话忘写了 // 把RCC外设初始化成复位状态,这句是必须的 RCC_DeInit(); //等待时钟稳定我和例程写的不太一样,如果这样写的话闪灯频率会变慢,现在我没有示波器,波形看不到,就目前的现象来看没有问题,都对应的上 //下面是例程里的 // 等待 HSE 启动稳定 HSEStartUpStatus = RCC_WaitForHSEStartUp(); // 只有 HSE 稳定之后则继续往下执行,这里我写的也不太好,固件库有固件库的 //我写成这样 if (RCC_WaitForHSEStartUp()==SUCCESS) { //例程是这样 if (HSEStartUpStatus == SUCCESS) { //例程里的第二个参数我写的是RCC_PLLMul_9,我看h文件定义如下,我写的是RCC_PLLMul_9
//-----------------设置各种频率主要就是在这里设置-------------------// // 设置PLL时钟来源为HSE,设置PLL倍频因子 // PLLCLK = 8MHz * pllmul RCC_PLLConfig(RCC_PLLSource_HSE_Div1, pllmul); //------------------------------------------------------------------//
等待PLL稳定的这块例程里和系统时钟初始化函数写的是一样的,但RCC_GetSYSCLKSource() 在stm32f10x_rcc.c已经定义好了,所以可以直接用,但是HSERDY并没有(但是可以自己算)
我的main.c里也不需要参数Sys72_Config();,例程里是需要参数的,例程这种放法只改函数里的参数就可以改变频率,我这种需要到函数里改变PLLCLK这里的值
- HSI时钟输入
HSI输入相比HSE输入改的地方不多,换成HSI使能,判断是否开启用寄存器,PLL时钟源换成HSI的2分频即可,HSI用的和例程一样是带参数的
void HSI_Config(uint32_t RCC_PLLMul_x){ __IO uint32_t HSIStatus = 0; // 把RCC外设初始化成复位状态,这句是必须的 RCC_DeInit(); //使能HSI RCC_HSICmd(ENABLE); HSIStatus = RCC->CR & RCC_CR_HSIRDY; if (RCC_FLAG_HSIRDY==SET) { //笑死,在rcc里找半天没找着,flash当然在flah.c里找了 //看注释找对应函数 FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); FLASH_SetLatency(FLASH_Latency_2); RCC_HCLKConfig(RCC_SYSCLK_Div1); RCC_PCLK2Config(RCC_HCLK_Div1); RCC_PCLK1Config(RCC_HCLK_Div2); RCC_PLLConfig(RCC_PLLSource_HSI_Div2, RCC_PLLMul_x); /* Enable PLL */ RCC_PLLCmd(ENABLE); /* Wait till PLL is ready */ while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET) { } /* Select PLL as system clock source */ RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); /* Wait till PLL is used as system clock source */ while (RCC_GetSYSCLKSource() != (uint32_t)0x08) { } } else { /* 如果HSI 启动失败,用户可以在这里添加处理错误的代码 */ } }
现在我还没有示波器,回头用示波器在检验一下现象
中断
数值越小,优先级越高
1。中断类型
- NVIC简介
外设的寄存器在stm32f10x.h,内核相关寄存器在core_cm3.c和misc.c
最常使用中断优先级寄存器
NVIC函数实现在misc.c里
中断优先级的定义
只用高四位,低四位不使用;先比较主优先级,在比较子优先级,如果都一样,就比较硬件编号,硬件终端编号就是前面说的向量表
表55 其它STM32F10xxx产品(小容量、中容量和大容量)的向量表
分组表在misc.h里
1.
先配置NVIC的中断使能寄存器,在配置对应外设的中断位,串口为例
2.
配置SCB_AIRCR位,通过固件库里NVIC_PriorityGroupConfig函数进行分组
3.
初始化结构体(misc.c)
中断源在stm32f10x.h里
下面编程选的是EXTILine0,所以中断源选EXTI0_IRQn,需要注意的是,EXTI0-4有单独的中断源,5-9,10-15是在一块的用的时候别写错了
4.中断服务函数
与启动函数里中断向量表里的中断函数同名,否则会执行启动文件里的,并在此一直循环(因为B.启动文件那一张有说)
中断服务函数写错会报错
在stm32f10x_it.c里写
EXIT(GPIO中断)
- 框图
信号线上打斜杠并标注“ 23”的字样,表示在控制器内部类似的信号线路有 23 个,这与 EXTI 总共有 23 个中断/事件线是吻合的。
EXTI 功能框图
EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。
1.1中断
红色虚线指示的电路流程。它是一个产生中断的线路,最终信号流入到 NVIC 控制器内
编号 1 是输入线, EXTI 控制器有 20 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个 GPIO,EXTI0 至 EXTI15 用于 GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输入源,还有另外四根用于特定的外设事件,输入线一般是存电平变化的信号。
EXTI0 至 EXTI15 用于 GPIO,通过编程控制可以实现任意一个 GPIO 作为 EXTI 的输入源。输入源的选择映像
另外四个EXTI线的连接方式如下:
● EXTI线16连接到PVD输出
● EXTI线17连接到RTC闹钟事件
● EXTI线18连接到USB唤醒事件
● EXTI线19连接到以太网唤醒事件(只适用于互联型产品)
EXTI0 可以通过 AFIO 外部中断配置寄存器AFIO_EXTICR1的 EXTI0[3:0]位配置
根据上面的讲解可以回答下面的问题
20,15个GPIO,PVD,RTC,USB,以太网
AFIO
选择好输入源以后,输入源的电平信号会到达边沿检测器,也就是编号2的位置,边沿检测器可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。边沿检测器将检测到的信号传给编号3电路,有效为1,无效为0。
编号3是一个或门,接收编号 2 电路和软件中断事件寄存器(EXTI_SWIER)的指令 ,EXTI_SWIER 可以通过程序控制就可以启动中断/事件线,该位写’1’将设置EXTI_PR中相应的挂起位,这两个输入随便一个有有效信号 1 就可以输出 1 给编号 4 和编号 6 电路。
编号4是与门,当中屏蔽寄存器和中断挂起寄存器全为1的时候,才会将 EXTI_PR 寄存器内容输出到 NVIC 内,从而实现系统中断事件控制
1.2事件
绿色虚线指示电路它是一个产生事件的线路,最终输出一个脉冲信号(内部)。
之前电路都是共用的。编号6 电路是一个与门,它一个输入编号 3 电路,另外一个输入来自事件屏蔽寄存器(EXTI_EMR)。当中屏蔽寄存器和中断挂起寄存器全为1时,脉冲发生器会产生一个脉冲,脉冲在单片机内部,这个脉冲信号可以给其他外设电路使用,比如定时器 TIM、模拟数字转换器 ADC 等等。
- 初始化结构体(stm32f10x_exti.h里)
1) EXTI_Line: EXTI 中断/事件线选择,可选 EXTI0 至 EXTI22,可参考表 17-1 选择。
2) EXTI_Mode: EXTI 模式选择,可选为产生中断(EXTI_Mode_Interrupt)或者产生事件(EXTI_Mode_Event)。
3) EXTI_Trigger: EXTI 边沿触发事件,可选上升沿触发(EXTI_Trigger_Rising)、下降 沿 触 发 ( EXTI_Trigger_Falling) 或 者 上 升 沿 和 下 降 沿 都 触 发( EXTI_Trigger_Rising_Falling)。
4) EXTI_LineCmd:控制是否使能 EXTI 线,可选使能 EXTI 线(ENABLE)或禁用(DISABLE)。对应中断屏蔽寄存器或事件屏蔽起寄存器
EXIT既然是GPIO的中断,那么GPIO里会有对应的函数,GPIO_EXITConfig()
- 编程
3.1编程要点
GPIO和EXTI的初始化可以写到一个初始化函数里
1、GPIO初始化,和前面按键的一样
2、EXTI初始化,
首先设置gpio.h里有关EXTI的部分,前面说过,这一部分在AFIO寄存器里配置,
然后打开中断的时钟,中断时钟在APB2总线上
GPIO的中断由AFIO控制
然后就是EXTI结构体的初始化,在stm32f10x_exti.h里
EXTI_InitStruct.EXTI_Line=EXTI_Line0; EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Rising; EXTI_InitStruct.EXTI_LineCmd=ENABLE; EXTI_Init(&EXTI_InitStruct);
(GPIO_EXITLineConfig()没用,别忘记打开EXTI的时钟)
3、NVIC初始化
NVIC的初始化单独用一个函数NVIC_Config()完成,KEY1_EXTI_Config调用NVIC_Config(),写在后面前面别忘了声明一下,首先用NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup),根据需求选择,这里选择组1,其余根据misc.h里的结构体定义初始化,为了防止别的文件调用,在前面加一下static
中断源的选择
4、中断服务函数
名字要和启动文件里的同名,里面写你具体要干什么,去启动文件里中断服务函数部分找对应的名字
中断服务函数写到it.c里,写什么呢
我们希望检测到高电平时,灯可以反转一次,那这里就对电平进行判断,检测到一次高电平,反转一次
中断标志位在exti.h里有一个检测标志位的函数
EXTI_GetITStatus(EXTI_Line0)为1,时,反转,灯的反转用ODR的异或,判断完后,还需要清除中断
作业
有一个小问题
看报错,初始化语句不能放在执行语句后,main函数里没有问题,问题在
这行语句挪到61行或62行都行
还有一个小问题,有点乱了,中断服务函数里是灯的反转,所以是GPIOB,写道GPIOC里去了
stm32f10x_it.c
void EXTI0_IRQHandler(void){ if(EXTI_GetITStatus(EXTI_Line0)==1){ LED_G_Toggle; } EXTI_ClearITPendingBit(EXTI_Line0); } void EXTI15_10_IRQHandler(void){ if(EXTI_GetITStatus(EXTI_Line13)!=RESET){ GPIOB->ODR^=GPIO_Pin_1; } EXTI_ClearITPendingBit(EXTI_Line13); }
bsp_key.c按键中断,我写是的这个名字,对应例程bsp_exti.c
#include "bsp_key.h" #include "stm32f10x_exti.h" static void NVIC_KEY1_Config(void){ NVIC_InitTypeDef NVIC_InitStruct; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStruct.NVIC_IRQChannel=EXTI0_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1; NVIC_InitStruct.NVIC_IRQChannelSubPriority=1; NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStruct); } static void NVIC_KEY2_Config(void){ NVIC_InitTypeDef NVIC_InitStruct; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStruct.NVIC_IRQChannel=EXTI15_10_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1; NVIC_InitStruct.NVIC_IRQChannelSubPriority=1; NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStruct); } void KEY1_EXTI_Config(void) { //初始化GPIO GPIO_InitTypeDef GPIO_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStruct); //初始化NVIC NVIC_KEY1_Config(); //初始化EXTI GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); EXTI_InitStruct.EXTI_Line=EXTI_Line0; EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Rising; EXTI_InitStruct.EXTI_LineCmd=ENABLE; EXTI_Init(&EXTI_InitStruct); } //不需要按键扫描函数 void KEY2_EXTI_Config(void){ //初始化GPIO GPIO_InitTypeDef GPIO_InitStruct; EXTI_InitTypeDef EXTI_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStruct.GPIO_Pin=GPIO_Pin_13; GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOC, &GPIO_InitStruct); //初始化NVIC NVIC_KEY2_Config(); //初始化EXTI GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource13); RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); EXTI_InitStruct.EXTI_Line=EXTI_Line13; EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Falling; EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt; EXTI_Init(&EXTI_InitStruct); }
“bsp_key.h”
这里第五行,可以写成下面这样,之前不是说define后面不能有分号,有分号的话需要用大括号括起来
#ifndef __BSP_KEY_H #define __BSP_KEY_H #include "stm32f10x.h" #define LED_G_Toggle GPIOB->ODR^=GPIO_Pin_0 //#define LED_G_Toggle {GPIOB->ODR^=GPIO_Pin_0;} void KEY1_EXTI_Config(void); void KEY2_EXTI_Config(void); #endif /* __BSP_KEY_H */
bsp_led.c
// bsp :board support package 板级支持包 #include "bsp_led.h" void LED_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(LED_G_GPIO_CLK, ENABLE); GPIO_InitStruct.GPIO_Pin = LED_G_GPIO_PIN; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(LED_G_GPIO_PORT, &GPIO_InitStruct); } void LED_B_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); }
bsp_led.h
#ifndef __BSP_LED_H #define __BSP_LED_H #include "stm32f10x.h" #define LED_G_GPIO_PIN GPIO_Pin_0 #define LED_G_GPIO_PORT GPIOB #define LED_G_GPIO_CLK RCC_APB2Periph_GPIOB #define ON 1 #define OFF 0 // \ C语言里面叫续行符,后面不能有任何的东西 #define LED_G(a) if(a) \ GPIO_ResetBits(LED_G_GPIO_PORT, LED_G_GPIO_PIN); \ else GPIO_SetBits(LED_G_GPIO_PORT, LED_G_GPIO_PIN); void LED_GPIO_Config(void); void LED_B_GPIO_Config(void); #endif /* __BSP_LED_H */
SysTick
1.简介
SysTick—系统定时器是属于 CM4 内核中的一个外设,内嵌在 NVIC 中。系统定时器是一个 24bit 的向下递减的计数器,计数器每计数一次的时间为 1/SYSCLK。当重装载数值寄存器的值递减到 0 的时候,系统定时器就产生一次中断,以此循环往复。
因为 SysTick 是属于 CM4 内核的外设,所以所有基于 CM4 内核的单片机都具有这个系统定时器,使得软件在 CM4 单片机中可以很容易的移植。系统定时器一般用于操作系统,用于产生时基,维持操作系统的心跳。
2.SysTick 寄存器介绍
SysTick—系统定时有 4 个寄存器,简要介绍如下。在使用 SysTick 产生定时的时候,只需要配置前三个寄存器,最后一个校准寄存器不需要使用。
STK_CTRL寄存器
COUNTFLAG:递减计数器递减到零时为1,当我们读取这个位以后会清零,然后重新开始计数
CLKSOURCE:可以选择系统时钟,对应时钟树部分如下
TICKINT:0不产生中断,1产生中断
STK_LOAD寄存器
24位有效,计数到零,重装载
STK_VAL寄存器
可以实时读取当前计数的值
功能框图
递减计数器在时钟驱动下,从重装载寄存器的初值开始往下递减计数到0,递减到0时会产生中断,若使能中断,则会执行中断服务程序,并且COUTFLAG会置一。如果未关闭,则从头开始递减并循环下去。递减的值可以从ST_VAL里实时读到。
3.SysTick 定时时间计算
reload:重装载计数器的值
程序一般是毫秒级的,一般设置毫秒
4.SysTick固件库定义
4.1结构体(core_cm3.h)
typedef struct { __IO uint32_t CTRL; /*!< Offset: 0x00 SysTick Control and Status Register */ __IO uint32_t LOAD; /*!< Offset: 0x04 SysTick Reload Value Register */ __IO uint32_t VAL; /*!< Offset: 0x08 SysTick Current Value Register */ __I uint32_t CALIB; /*!< Offset: 0x0C SysTick Calibration Register */ } SysTick_Type;
4.2SysTick配置函数
static __INLINE uint32_t SysTick_Config(uint32_t ticks) { //判断初值ticks,go to definiti过去可知初值是2^24,如果大于初值返回1,不符合规则 #define SysTick_LOAD_RELOAD_Msk (0xFFFFFFul << SysTick_LOAD_RELOAD_Pos) if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); //否则把初值ticks装载到STK_LOAD寄存器里 SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; //配置中断优先级 NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); //初始化counter为0 SysTick->VAL = 0; //配置时钟为72M | 使能中断 | 使能systick SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; return (0); }
配置中断优先级
首先看函数
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) { if(IRQn < 0) { SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M3 System Interrupts */ else { NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for device specific Interrupts */ }
中断源小于0,配置SHP,也就是内核;
表格里写了systick在SCB_SHPR3寄存器里配置,内核和外设一样,也是低四位不用,高四位配置子主优先级
中断源大于0,配置外设IP寄存器
外设是配置SCB_AIRCR寄存器的PRIGROUP位来确定外设的子主优先级的
总的来说就是,中断优先级的分组对于内核和外设同样适用,比较的时候都按照上图表格里的方式分好自主优先级,再根据先主在子的方式进行比较,只要外设的优先级数值小,那么外设的优先级就高于内核,当内核和外设的子主优先级一样时,去向量表里比较硬件编号
5.编程
一共写两个,一个微秒,一个毫秒,两个思路一样,就是微秒的初值是72,毫秒的初值是72000
调用固件库函数SysTick_Config(uint32_t ticks),将初值设置为72SysTick_Config(72)就是1us
通过STK_CTRL的第16位COUNTFLAG判断SysTick_Config计数是否完成,计数完成则走了1us
想要多少微秒,直接改变参数count的值
#include "bsp_systick.h" void systick_us(uint32_t count){ uint16_t i=0; SysTick_Config(72); for(i=0;i<count;i++){ while(!(SysTick->CTRL&(1<<16))); } SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; } void systick_ms(uint32_t count){ uint16_t i=0; SysTick_Config(72000); for(i=0;i<count;i++){ while(!(SysTick->CTRL&(1<<16))); } }
有没有最后关闭systick都不会影响现象,但是打开了,不用的话还是关闭的好。
视频里将的方法比较简单用的查询的方式,例程里有用中断的方式写的
首先编写初始化函数SysTick,在里面调用SysTick_Config(),假设我们设置每10us产生一次中断,
定时时间t=reload/clk,clk=72M,如果想要10us,reload的值就应该是720,也就是说可以直接写成SysTick_Config(720)
产生中断后,中断服务函数TimingDelay_Decrement()对,延时函数里的次数进行递减
延时函数的参数count是递减的次数,count×10us就是最终想要的延时时间
但是我感觉这个延时函数像是固件库里写好的,就是可以直接拿来用的那种,按照例程里SysTick_Init()注释的写法,直接选择中断事件就行,SystemCoreClock的值在system_stm32f10x.c系统文件里已经定义好了,不需要自己设置,前面的逻辑看懂就行,实际会用就可以。
#include "bsp_SysTick.h" #include "core_cm3.h" #include "misc.h" static __IO u32 TimingDelay; / * @brief 启动系统滴答定时器 SysTick * @param 无 * @retval 无 */ void SysTick_Init(void) { /* SystemFrequency / 1000 1ms中断一次 * SystemFrequency / 10us中断一次 * SystemFrequency / 1us中断一次 */ // if (SysTick_Config(SystemFrequency / )) // ST3.0.0库版本 if (SysTick_Config(SystemCoreClock / )) // ST3.5.0库版本 { /* Capture error */ while (1); } } / * @brief us延时程序,10us为一个单位 * @param * @arg nTime: Delay_us( 1 ) 则实现的延时为 1 * 10us = 10us * @retval 无 */ void Delay_us(__IO u32 nTime) { TimingDelay = nTime; // 使能滴答定时器 SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; while(TimingDelay != 0); } / * @brief 获取节拍程序 * @param 无 * @retval 无 * @attention 在 SysTick 中断函数 SysTick_Handler()调用 */ void TimingDelay_Decrement(void) { if (TimingDelay != 0x00) { TimingDelay--; } }
实际写的时候有两点需要注意,因为用了中断服务函数,需要在it.c文件里使用bsp_systick.c文件里的
TimingDelay_Decrement()函数,
还有一点引用外部函数要用extern声明一下
我自己的bsp_systick.c
#include "bsp_systick.h" #include "misc.h" static __IO u32 TimingDelay; void SysTick_Init(void){ if (SysTick_Config(SystemCoreClock / )) // ST3.5.0库版本 { /* Capture error */ while (1); } } void delay_us(__IO u32 count){ TimingDelay=count; SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; while(TimingDelay != 0); } void TimingDelay_Decrement(void) { if (TimingDelay != 0x00) { TimingDelay--; } } #if 0 void systick_us(uint32_t count){ uint16_t i=0; SysTick_Config(72); for(i=0;i<count;i++){ while(!(SysTick->CTRL&(1<<16))); } SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk; } void systick_ms(uint32_t count){ uint16_t i=0; SysTick_Config(72000); for(i=0;i<count;i++){ while(!(SysTick->CTRL&(1<<16))); } } #endif
最后还有__IO 和u32需要解释一下
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/155271.html