笔记(六)RCC,EXTI,SysTick | 您所在的位置:网站首页 › 三代rcc和二代rcc有什么区别 › 笔记(六)RCC,EXTI,SysTick |
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的函数 ![]() ![]() 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稳定的这块例程里和系统时钟初始化函数写的是一样的,但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_Msk) return (1); //否则把初值ticks装载到STK_LOAD寄存器里 SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; //配置中断优先级 NVIC_SetPriority (SysTick_IRQn, (1CTRL = 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 IP[(uint32_t)(IRQn)] = ((priority |
CopyRight 2018-2019 实验室设备网 版权所有 |