单片机入门学习九 STM32单片机学习六 延时函数、端口复用和重映射、中断

前面几篇文章涉及到stm32编程需要了解的一些基础知识,本篇在记录一些编程中常用到的基础概念。

1、中断

本篇记录的是NVIC(Nested Vectored Interrupt Controller嵌套向量中断控制器),NVIC属于Cortex内核的器件,不可屏蔽中断 (NMI)和外部中断都由它来处理(SYSTICK不是由NVIC来控制的)。
Cortex™-M3内核支持256个中断,其中包含了16个内核中断和240个外部中断,并且具有256级的可编程中断设置。但STM32并没有使用CM3内核的全部东西,而是只用了它的一部分,STM32F103系列上面,只有60个可屏蔽中断、16个内核中断,具有16级可编程的中断优先级(使用了4位中断优先级)。
NVIC 的寄存器以存储器映射的方式来访问,除了包含控制寄存器和中断处理的控制逻辑之外,NVIC 还包含了MPU的控制寄存器、SysTick 定时器以及调试控制。本小节仅记录中断相关的内容。
《STM32中文手册》60个可屏蔽中断如下图:
单片机入门学习九 STM32单片机学习六 延时函数、端口复用和重映射、中断_第1张图片单片机入门学习九 STM32单片机学习六 延时函数、端口复用和重映射、中断_第2张图片

1)中断管理方法

STM32中断有5组,即组0、组1、…、组4。每个中断设置一个抢占优先级和一个响应优先级。如下表(通过SCB->AIRCR寄存器对中断优先级进行配置)
单片机入门学习九 STM32单片机学习六 延时函数、端口复用和重映射、中断_第3张图片
表中的AIRCR的10到8位 用以表示分组,组0到组4。
表中的IP 的7到4位 用以表示 优先级。IP全称为Interrupt Priority Registers

抢占优先级是指 当 优先级更高的中断来了可以打断 低优先级的中断。
响应优先级是指 抢占优先级相同的情况下,若多个中断同时发生,高响应优先级的先运行,注意该优先级的高的不能打断正在运行的低优先级的中断。

2)设置中断分组

//misc.h
#define NVIC_PriorityGroup_0         ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
                                                            4 bits for subpriority */
#define NVIC_PriorityGroup_1         ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
                                                            3 bits for subpriority */
#define NVIC_PriorityGroup_2         ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
                                                            2 bits for subpriority */
#define NVIC_PriorityGroup_3         ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
                                                            1 bits for subpriority */
#define NVIC_PriorityGroup_4         ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
                                                           0 bits for subpriority */

#define AIRCR_VECTKEY_MASK    ((uint32_t)0x05FA0000)

/**中断分组设置函数
  * @brief  Configures the priority grouping: pre-emption priority and subpriority.
  * @param  NVIC_PriorityGroup: specifies the priority grouping bits length. 
  *   This parameter can be one of the following values:
  *     @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority
  *                                4 bits for subpriority
  *     @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
  *                                3 bits for subpriority
  *     @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
  *                                2 bits for subpriority
  *     @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
  *                                1 bits for subpriority
  *     @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
  *                                0 bits for subpriority
  * @retval None
  */

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

//core_cm3.h
/** @addtogroup CMSIS_CM3_SCB CMSIS CM3 SCB
  memory mapped structure for System Control Block (SCB)
  @{
 */
typedef struct
{
  __I  uint32_t CPUID;                        /*!< Offset: 0x00  CPU ID Base Register                                  */
  __IO uint32_t ICSR;                         /*!< Offset: 0x04  Interrupt Control State Register                      */
  __IO uint32_t VTOR;                         /*!< Offset: 0x08  Vector Table Offset Register                          */
  __IO uint32_t AIRCR;                        /*!< Offset: 0x0C  Application Interrupt / Reset Control Register        */
  __IO uint32_t SCR;                          /*!< Offset: 0x10  System Control Register                               */
  __IO uint32_t CCR;                          /*!< Offset: 0x14  Configuration Control Register                        */
  __IO uint8_t  SHP[12];                      /*!< Offset: 0x18  System Handlers Priority Registers (4-7, 8-11, 12-15) */
  __IO uint32_t SHCSR;                        /*!< Offset: 0x24  System Handler Control and State Register             */
  __IO uint32_t CFSR;                         /*!< Offset: 0x28  Configurable Fault Status Register                    */
  __IO uint32_t HFSR;                         /*!< Offset: 0x2C  Hard Fault Status Register                            */
  __IO uint32_t DFSR;                         /*!< Offset: 0x30  Debug Fault Status Register                           */
  __IO uint32_t MMFAR;                        /*!< Offset: 0x34  Mem Manage Address Register                           */
  __IO uint32_t BFAR;                         /*!< Offset: 0x38  Bus Fault Address Register                            */
  __IO uint32_t AFSR;                         /*!< Offset: 0x3C  Auxiliary Fault Status Register                       */
  __I  uint32_t PFR[2];                       /*!< Offset: 0x40  Processor Feature Register                            */
  __I  uint32_t DFR;                          /*!< Offset: 0x48  Debug Feature Register                                */
  __I  uint32_t ADR;                          /*!< Offset: 0x4C  Auxiliary Feature Register                            */
  __I  uint32_t MMFR[4];                      /*!< Offset: 0x50  Memory Model Feature Register                         */
  __I  uint32_t ISAR[5];                      /*!< Offset: 0x60  ISA Feature Register                                  */
} SCB_Type;  

//main.c
//设置中断分组为 组2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

3)设置中断优先级

中断优先级的设置 NVIC为我们提供了 寄存器,其寄存器机构如下:

typedef struct
{
  __IO uint32_t ISER[8]; //中断使能寄存器组            
       uint32_t RESERVED0[24];                                   
  __IO uint32_t ICER[8]; //中断失能寄存器组                   
       uint32_t RSERVED1[24];                                    
  __IO uint32_t ISPR[8];  //中断挂起寄存器组                   
       uint32_t RESERVED2[24];                                   
  __IO uint32_t ICPR[8];  //中断解挂寄存器组                 
       uint32_t RESERVED3[24];                                   
  __IO uint32_t IABR[8];  //中断激活标志位寄存器组                   
       uint32_t RESERVED4[56];                                   
  __IO uint8_t  IP[240];  //中断优先级控制的寄存器组                    
       uint32_t RESERVED5[644];                                  
  __O  uint32_t STIR;                         
}  NVIC_Type; 

①IP[240](中断优先级控制器组)
中断优先级的设置 使用的是 IP[240],看结构体定义我们知道 系统定义了240个8位寄存器,每个中断使用一个寄存器来确定优先级。
stm32f10x系列一共有60个可屏蔽中断,使用IP[0]~IP[59]。每个IP寄存器仅用到了高四位,低4位不用。
②ISER[8](中断使能寄存器组)
用来使能中断,32位寄存器,每个位控制一个中断的使能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ISER[0]和ISER[1]。ISER[0]的bit0~bit31分别对应中断0~31。ISER[1]的bit0~27对应中断32~59;
③ICER[8](中断失能寄存器组)
用来失能中断,32位寄存器,每个位控制一个中断的失能。STM32F10x只有60个可屏蔽中断,所以只使用了其中的ICER[0]和ICER[1]。ICER[0]的bit0~bit31分别对应中断0~31。ICER[1]的bit0~27对应中断32~59;

①②③使用库函数 void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);进行配置

④ISPR[8](中断挂起控制寄存器组)
⑤ICPR[8](中断解挂控制寄存器组)

④⑤使用到的库函数有:
static __INLINE void NVIC_SetPendingIRQ(IRQn_Type IRQn);
static __INLINE uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn);
static __INLINE void NVIC_ClearPendingIRQ(IRQn_Type IRQn)

⑥IABR [8](中断激活标志位寄存器组)
作用:只读,通过它可以知道当前在执行的中断是哪一个。
如果对应位为1,说明该中断正在执行。
该寄存器配置涉及到的库函数为 static __INLINE uint32_t NVIC_GetActive(IRQn_Type IRQn)

4)库函数 设置 中断

中断优先级设置步骤如下:
①系统运行后先设置中断优先级分组。调用函数:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); 整个系统执行过程中,只设置一次中断分组。
②针对每个中断,设置对应的抢占优先级和响应优先级:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
③如果需要挂起/解挂,查看中断当前激活状态,分别调用相关函数即可。

//设置中断分组为 组2 ,仅设置一次
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitTypeDef   NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;//抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 子优先级位2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据上面指定的参数初始化NVIC寄存器

2、延时

本小节定义的延时函数使用到了Systick定时器。Cortex™-M3,Cortex™-M4内核芯片,都有Systick定时器。Systick定时器常用来做延时,或者实时系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如UCOS中,分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用Systick做UCOS心跳时钟。

Systick定时器就是系统滴答定时器,一个24 位的倒计数定时器,计到0 时,将从RELOAD 寄存器中自动重装载定时初值。只要不把它在SysTick 控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式下也能工作。

SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号:15)。

misc.h为我们定义了Systick寄存器,如下

/******************************************************************************/
/*                                                                            */
/*                               SystemTick                                   */
/*                                                                            */
/******************************************************************************/

/*****************  Bit definition for SysTick_CTRL register  *****************/
#define  SysTick_CTRL_ENABLE                 ((uint32_t)0x00000001)        /*!< Counter enable */
#define  SysTick_CTRL_TICKINT                ((uint32_t)0x00000002)        /*!< Counting down to 0 pends the SysTick handler */
#define  SysTick_CTRL_CLKSOURCE              ((uint32_t)0x00000004)        /*!< Clock source */
#define  SysTick_CTRL_COUNTFLAG              ((uint32_t)0x00010000)        /*!< Count Flag */

/*****************  Bit definition for SysTick_LOAD register  *****************/
#define  SysTick_LOAD_RELOAD                 ((uint32_t)0x00FFFFFF)        /*!< Value to load into the SysTick Current Value Register when the counter reaches 0 */

/*****************  Bit definition for SysTick_VAL register  ******************/
#define  SysTick_VAL_CURRENT                 ((uint32_t)0x00FFFFFF)        /*!< Current value at the time the register is accessed */

/*****************  Bit definition for SysTick_CALIB register  ****************/
#define  SysTick_CALIB_TENMS                 ((uint32_t)0x00FFFFFF)        /*!< Reload value to use for 10ms timing */
#define  SysTick_CALIB_SKEW                  ((uint32_t)0x40000000)        /*!< Calibration value is not exactly 10 ms */
#define  SysTick_CALIB_NOREF                 ((uint32_t)0x80000000)        /*!< The reference clock is not provided */

从上面可看出Cortex™-M3内核设计有4个Systick寄存器
1)CTRL
CTRL是SysTick控制和状态寄存器
单片机入门学习九 STM32单片机学习六 延时函数、端口复用和重映射、中断_第4张图片
对于STM32,外部时钟源是 HCLK(AHB总线时钟)的1/8
内核时钟是 HCLK时钟
配置函数:SysTick_CLKSourceConfig();
2)LOAD
LOAD是SysTick重装载数值寄存器
LOAD
3)VAL
VAL是SysTick当前值寄存器
单片机入门学习九 STM32单片机学习六 延时函数、端口复用和重映射、中断_第5张图片
4)CALIB
CALIB是SysTick校准值寄存器
单片机入门学习九 STM32单片机学习六 延时函数、端口复用和重映射、中断_第6张图片

常用SysTick相关函数
①SysTick_CLKSourceConfig() //Systick时钟源选择 misc.c文件中

/** @defgroup SysTick_clock_source  **/
#define SysTick_CLKSource_HCLK_Div8    ((uint32_t)0xFFFFFFFB)
#define SysTick_CLKSource_HCLK         ((uint32_t)0x00000004)
#define IS_SYSTICK_CLK_SOURCE(SOURCE) (((SOURCE) == SysTick_CLKSource_HCLK) || \
                                       ((SOURCE) == SysTick_CLKSource_HCLK_Div8))

/**
  * @brief  Configures the SysTick clock source.
  * @param  SysTick_CLKSource: specifies the SysTick clock source.
  *   This parameter can be one of the following values:
  *     @arg SysTick_CLKSource_HCLK_Div8: AHB clock divided by 8 selected as SysTick clock source.
  *     @arg SysTick_CLKSource_HCLK: AHB clock selected as SysTick clock source.
  * @retval None
  */
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)
{
  /* Check the parameters */
  assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
  if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
  {
    SysTick->CTRL |= SysTick_CLKSource_HCLK;
  }
  else
  {
    SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
  }
}

②SysTick_Config(uint32_t ticks) //初始化systick,时钟为HCLK,并开启中断
//core_cm3.h/core_cm4.h文件中

/**
 * @brief  Initialize and start the SysTick counter and its interrupt.
 *
 * @param   ticks   number of ticks between two interrupts
 * @return  1 = failed, 0 = successful
 *
 * Initialise the system tick timer and its interrupt and start the
 * system tick timer / counter in free running mode to generate 
 * periodical interrupts.
 */
static __INLINE uint32_t SysTick_Config(uint32_t ticks)
{ 
  if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */

  SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
  NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */
  SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */
  SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk | 
                   SysTick_CTRL_TICKINT_Msk   | 
                   SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
  return (0);                                                  /* Function successful */
}

③void SysTick_Handler(void) //中断服务函数

下面的程序即是一个简单的使用SysTick利用中断实现的delay延时,如下:

static __IO uint32_t TimingDelay;
void Delay(__IO uint32_t nTime)
{ 
   TimingDelay = nTime;
   while(TimingDelay != 0);
}
void SysTick_Handler(void)
{
    if (TimingDelay != 0x00) 
     { 
       TimingDelay--;
     }
}
 int main(void)
 {  …
    if (SysTick_Config(SystemCoreClock / 1000)) //systick时钟为HCLK,中断时间间隔1ms,我们知道SystemCoreClock在system_stm32f10x.c文件总定义为uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz;SystemClock=7200000,SystemCoreClock/1000=7200,而系统频率使72MHz,即周期T=1/(72*1000*1000)s,则SystemCoreClock/1000*T=1/1000s=1ms
    {
     while (1);
    }
    while(1)
    { 
        Delay(200);//2ms
        … 
    }
}

3、端口复用和重映射

1)端口复用

端口复用就是指用GPIO复用为内置外设引脚。如stm32f103的PA9、PA10可以不作为GPIO而复用为串口1的发送接收引脚。

端口复用配置过程如下:
①GPIO端口时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
②复用外设时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
③端口模式配置
GPIO_Init();

下面以串口1复用为例,我们从《STM32中文参考手册V10》P110的表格“8.1.11外设的GPIO配置”可知GPIO作为串口使用时 GPIO的模式为什么,如下表
单片机入门学习九 STM32单片机学习六 延时函数、端口复用和重映射、中断_第7张图片

//①IO时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//②外设时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
//③初始化IO为对应的模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9//复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10 PA.10 浮空输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);  

2)端口映射

每个内置外设都有若干个输入输出引脚,一般这些引脚的输出端口都是固定不变的,为了让设计工程师可以更好地安排引脚的走向和功能,在STM32中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。
为了使不同器件封装的外设IO功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。STM32中有很多内置外设的输入输出引脚都具有重映射(remap)的功能。

重映射操作步骤
①使能被重新映射的I/O端口时钟
②使能被重映射的外设时钟
使能AFIO功能的时钟
④进行重映射

如串口1重映射步骤如下:
①使能GPIO时钟(重映射后的IO);
②使能功能外设时钟(例如串口1);
③使能AFIO时钟。重映射必须使能AFIO时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
④开启重映射。
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
根据第一个参数,来确定是部分重映射还是全部重映射

上面看到两个词 部分重映射、全部重映射,下面说一下这两个的概念

  • 部分重映射:功能外设的部分引脚重新映射,还有一部分引脚是原来的默认引脚。
  • 完全重映射:功能外设的所有引脚都重新映射。

USART3中可看出两者的区别
单片机入门学习九 STM32单片机学习六 延时函数、端口复用和重映射、中断_第8张图片

在stm32f10x_gpio.h中定义了重映射的类别,下面是串口的定义

#define GPIO_Remap_USART1           ((uint32_t)0x00000004)  /*!< USART1 Alternate Function mapping */
#define GPIO_Remap_USART2           ((uint32_t)0x00000008)  /*!< USART2 Alternate Function mapping */
#define GPIO_PartialRemap_USART3    ((uint32_t)0x00140010)  /*!< USART3 Partial Alternate Function mapping */
#define GPIO_FullRemap_USART3       ((uint32_t)0x00140030)  /*!< USART3 Full Alternate Function mapping */

端口重映射寄存器

  • AFIO_MAPR:配置复用功能重映射
  • AFIO_EXTICRX:配置外部中断线映射
  • AFIO_EVCR: 配置EVENTOUT事件输出

对用使用AFIO寄存器需要首先开启AFIO时钟

你可能感兴趣的:(楼宇自控,单片机,单片机入门学习)