笔记(六)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、代码详解

  1. RCC作用

从RCC结构体里可以看出RCC的作用(在stm32f10x.h里)

设置系统时钟 SYSCLK、设置 AHB 分频因子(决定 HCLK 等于多少) 、 设置 APB2 分频因子(决定 PCLK2 等于多少)、设置 APB1 分频因子(决定 PCLK1 等于多少)、设置各个外设的分频因子;控制AHB、 APB2 和 APB1 这三条总线时钟的开启、控制每个外设的时钟的开启。

RCC相关寄存器很重要,需要看芯片参考手册,下面的系统时钟库函数会用到

  1. 时钟系统初始化函数剖析

程序里的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在外部获取到设置的系统时钟是多少

  1. 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这里的值

  1. 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。中断类型

  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中断)

  1. 框图

信号线上打斜杠并标注“ 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 等等。

  1. 初始化结构体(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()

  1. 编程

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需要解释一下

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值