开发手册+标准库底层实现入门STM32二(NVIC+EXTI)

NVIC

嵌套向量中断控制器,是用于管理和控制中断的组件。

NVIC在stm32开发手册中没有太多介绍,需要跳转到Cortex-M3手册

NVIC有ISER[2](使能中断)、ICER[2](禁用中断)、ISPR[2](设置中断挂起)、ICPR[2](清除中断挂起)、IABR[2](检测中断状态)、IPR[68](中断优先级配置)、STIR(软件触发中断)

在NVIC_IPR寄存器中,有IPRn和IP[n]。

IP[n]是储存优先级信息,n为中断编号(如EXTI3中断编号为9,对应IP[9])IP[n]有八位,0-3位未使用总是读0,4-7位是定义中断的优先级。高四位中,0写最高级,15写最低级。

ISER到IABR都有0、1、2三组,根据对应的中断优先级向量表,第0组对应IPR寄存器中的0-31位,第1组对应IPR寄存器中的32-63位。中断优先级就是通过配置对应的IPR寄存器相关位实现。位数越低,优先级越高。

ISER等都是32位寄存器:ISER[0]:覆盖中断0至31。ISER[1]:覆盖中断32至63。ISER[2]:覆盖中断64至67。

也就是说stm32芯片一共可以有68个中断,每个中断都有一组ISER+ICER+ISPR+ICPR+IABR控制,对应一个IP,所有中断再由IPR分配优先级。每一组都对应一个外设,ISER[9]这组对应的外设就是EXTI3.

通过查阅stm32开发手册上的中断向量表

可以看到,stm32上的外设基本上都已经被默认分配中断优先级,大部分的优先级都是可以根据用户需要自己设置的。

如要重新设置EXTI3的优先级。EXTI3的中断号为EXTI3_IRQn(也就是表中的9)

//标准库操作
NVIC_SetPriority(EXTI3_IRQn, 3); 

//寄存器操作
// 计算EXTI3在IPR寄存器中的位置
uint32_t ipr_index = EXTI3_IRQn / 4; // 计算IPR寄存器索引
uint32_t ipr_offset = (EXTI3_IRQn % 4) * 8; // 计算IPR寄存器中的位偏移
// 设置EXTI3的优先级
// 假设优先级分组为0,即全部8位用于优先级
uint32_t priority = 3; // 设置的优先级
NVIC->IPR[ipr_index] = (NVIC->IPR[ipr_index] & ~(0xFF << ipr_offset)) |

系统滴答校准寄存器SysTick

SysTick是一个24位自动递减计数器,可以被编程为以不同时钟频率进行计数。当计数器减到0时,会触发一个中断,可以在中断处理函数SysTick_Handler中进行相应操作。

看起来和定时中断很像。但区别是,SysTick只有一个,TIM有多个,而且SysTick配置更为简单,几乎不占用多少硬件资源。

SysTick在stm32的中文参考上没有过多介绍,需要到Cortex-M3权威指南第八章寻找。

SysTick有四个寄存器

CTRL

控制和状态寄存器

LOAD

自动重装载除值寄存器

VAL

当前值寄存器

CALIB

校准值寄存器

常用来编写精准的Delay函数,如编写us级和ms级的的延迟函数

void Delay_us(uint32_t cnt)
{//假设系统时钟为72MHZ,也就是每秒计数72000000次,每us计数72次
        SysTick->LOAD = 72 * cnt;//设置定时器重装值,cnt us
        SysTick->VAL = 0x00;//清空当前计数值
        SysTick->CTRL = 0x00000005;//设置时钟源为HCLK,启动定时器
        while(!(SysTick->CTRL & 0x00010000));//等待计数到0
        SysTick->CTRL = 0x00000004;//关闭定时器
}
void Delay_ms(uint32_t cnt)
{//假设系统时钟为72MHZ,也就是每秒计数72000000次,每ms计数72000次
        SysTick->LOAD = 72000 * cnt;//设置定时器重装值,cnt ms
        SysTick->VAL = 0x00;//清空当前计数值
        SysTick->CTRL = 0x00000005;//设置时钟源为HCLK,启动定时器
        while(!(SysTick->CTRL & 0x00010000));//等待计数到0
        SysTick->CTRL = 0x00000004;//关闭定时器
}
void SysTick_Handler(void)//如无特殊操作需求,为空就可以了,因为CTRL会自动自减
{
}

外部中断/事件控制器(EXTI)

EXTI是通过能产生中断请求的边沿检测器,检测上升沿/下降沿等方式,来挂起中断标志位,进入中断。在进入中断后,需要对挂起的中断标志位置0,以表示出中断。

EXTI属于APB2总线的外设,可以通过、SysTick、TIM、DMA、I2C、USART、SPI等触发中断。

产生中断的配置过程为:配置并使能中断线——>配置触发寄存器——>在中断屏蔽寄存器相应位写“1”允许中断

EXTI寄存器

中断屏蔽寄存器(EXTI_IMR)

中断屏蔽寄存器IMR是用于选择使能中断的线路,0-19位,共20条线路。置“1”代表开启。共20条线路,意味着软件触发+上升沿|下降沿触发一共只能有20个中断。20条线就是这三类中断的线不能重合。软件触发选了第1条,那么其它两种中断就不能选第一条线。

事件屏蔽寄存器EMR是用于使能事件中断,和IMR同样20条线路,且三种中断方式的线路不能重合。

区别:IMR是通过检测上升沿下降沿,如按下按键导致引脚高低电平变化,以此触发中断。EMR是通过软件,例如某个变量的值a>=10,触发中断。事件中断所对应的“脉冲发生器”

上升沿触发选择寄存器(EXTI_RTSR)

下降沿触发选择寄存器(EXTI_FTSR)

上升沿触发器和下降沿触发器一致,用于配置中断触发模式。不同于软件中断寄存器,在20条线中,同一线路上升沿和下降沿的寄存器都可以置“1”,也就是上升沿和下降沿都触发中断。

0-15是对应GPIO的0-15号引脚,分别触发EXTI0-EXTI15。如RTSR和FTSR的0位都是GPIOA-GPIOG的Pin0,触发EXTI0。对这16个中断线的选择,就是AFIO的功能之一。

需要注意的是,在stm32c8t6中,EXTI9-5、EXTI10-15的中断函数是同一个,也就是说,EXTI8和EXTI6会触发同一个中断函数。

软件中断事件寄存器(EXTI_SWIER)

软件中断和上升沿下降沿触发中断之间是或门,因此不建议和其它中断寄存器位重复。上升下降沿一般占用0-15位,因此建议软件中断选择16-19位。

挂起寄存器(EXTI_PR)

当触发中断时(20条线路的软件触发、上升沿触发、下降沿触发),PR相应线路的位置就会置“1”,置“1”后就会进入到中断函数中,如9号线路触发EXTI9_5_IRQHandler。当标志位置1,并执行完中断函数后,需要在中断函数最后将标志位手动清零。

代码实现

配置中断的流程为

GPIO引脚选择——>AFIO中断引脚选择——>EXTI边沿检测——>NVIC优先级设置

标准库配置

例如在GPIOB_Pin10上放了个按钮,按下输入高电平。即GPIOB_Pin10上升沿触发中断

void GPIO_EXTI_Init(void)
{
    //GPIO引脚选择
    // 使能GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    //AFIO中断引脚选择,配置Pin10对应中断线10
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource10);
    
    //EXTI边沿检测触发中断
    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_InitStructure.EXTI_Line = EXTI_Line10;//中断线路10
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;// 上升沿触发
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能
    EXTI_Init(&EXTI_InitStructure); 
    
    //中断优先级分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    //配置中断优先级为3
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = EXTI10_IRQn;//配置为EXTI10线路
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//NVIC使能
    NVIC_Init(&NVIC_InitStructure);
}

//中断执行函数
void EXTI10_IRQHandler(void)
{
//执行的程序
//清除EXTI10中断标志位
    EXTI_ClearITPendingBit(EXTI_Line10);
}

这里可以看看标准库中的函数是如何实现的(省略断言)

typedef struct
{
  uint32_t EXTI_Line;//中断线选择
  EXTIMode_TypeDef EXTI_Mode;//中断模式(软件/上下沿)
  EXTITrigger_TypeDef EXTI_Trigger;//中断触发模式
  FunctionalState EXTI_LineCmd;//中断线路使能
}EXTI_InitTypeDef;
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
{
  uint32_t tmp = 0;
  tmp = (uint32_t)EXTI_BASE;//tmp设置为中断寄存器基地址
     
  if (EXTI_InitStruct->EXTI_LineCmd != DISABLE)//如果使能中断线
  {
    /* 设置EXTI中断线路配置 */
    EXTI->IMR &= ~EXTI_InitStruct->EXTI_Line;//使能/失能中断线路
    EXTI->EMR &= ~EXTI_InitStruct->EXTI_Line;//失能/使能事件线路
    、
    tmp += EXTI_InitStruct->EXTI_Mode;//通过地址偏移,设置tmp为中断模式地址
    *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    /* 配置上升沿或下降沿触发 */
    EXTI->RTSR &= ~EXTI_InitStruct->EXTI_Line;//上升沿
    EXTI->FTSR &= ~EXTI_InitStruct->EXTI_Line;//下降沿
    
    /* 选择外部中断触发器 */
    if (EXTI_InitStruct->EXTI_Trigger == EXTI_Trigger_Rising_Falling)
    {//如果是上升沿或者下降沿触发
      /* 上升沿或下降沿触发 */
      EXTI->RTSR |= EXTI_InitStruct->EXTI_Line;
      EXTI->FTSR |= EXTI_InitStruct->EXTI_Line;
    }
    else//如果是事件触发
    {
      tmp = (uint32_t)EXTI_BASE;
      tmp += EXTI_InitStruct->EXTI_Trigger;
      *(__IO uint32_t *) tmp |= EXTI_InitStruct->EXTI_Line;
    }
  }
  else//如果失能中断线
  {
    tmp += EXTI_InitStruct->EXTI_Mode;
    /* 清除中断线 */
    *(__IO uint32_t *) tmp &= ~EXTI_InitStruct->EXTI_Line;
  }
}
typedef struct
{
  uint8_t NVIC_IRQChannel;//中断号
  uint8_t NVIC_IRQChannelPreemptionPriority;//抢占优先级  
  uint8_t NVIC_IRQChannelSubPriority;//响应优先级
  FunctionalState NVIC_IRQChannelCmd;//中断使能
} NVIC_InitTypeDef;
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
{
  uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;   
  if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)//如果要使使能中断
  {
    /* 计算IRQ优先级 */    
    tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
    tmppre = (0x4 - tmppriority);//计算抢占优先级位移
    tmpsub = tmpsub >> tmppriority;//计算响应优先级位移

    tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
    tmppriority |=  NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
    tmppriority = tmppriority << 0x04;
    /* 将优先级位移计算结构传给IPR */
    NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
    /* 使能中断 */
    NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
      (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  }
  else
  {
    /* 失能中断 */
    NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
      (uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
  }
}

寄存器配置

寄存器配置就简洁了许多

void GPIO_EXTI_Init(void)
{
    //GPIO引脚选择
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    GPIOB->CRH |= ((uint32_t)0x0000001A << 4*(10-8))//0000 0000 0000 0000 0000 0110 0000 0000
    
    //EXTI配置
    EXTI->IMR |= (1<<10);//使能通道10,即EXTI10
    EXTI->RTSR |=(1<<10);//通道10配置为上升沿触发
    EXTI->EMR &= ~(1<<10);//关闭EMR事件使能,防止冲突
    
    //NVIC配置
    NVIC->ISER[0] |= (1<<10);//使能EXTI中断
    NVIC->IPR[4] = (NVIC->IPR[4] & ~(0xFF<<8)) | (3<<8);//配置EXTI10的优先级为3    
}

void EXTI10_IRQHandler(void)
{
    //EXTI10要执行的程序
    
    //清除EXTI10中断挂起标志位
    EXTI->PR |= (1<<10);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值