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 / 100000 10us中断一次
* SystemFrequency / 1000000 1us中断一次
*/
// if (SysTick_Config(SystemFrequency / 100000)) // ST3.0.0库版本
if (SysTick_Config(SystemCoreClock / 100000)) // 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 / 100000)) // 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需要解释一下