NVIC中断

内容分享3小时前发布
0 0 0

NVIC 中断(管理外部中断)

NVIC中断

NVIC中断

1,什么是中断?

生活中处处有中断,比如你正在打游戏,此时电话响了,电话中断了游戏;你正要过绿灯,红灯亮了,只能停下脚步,…。

程序中一样也存在中断:

NVIC中断

2,中断响应过程

》每条机器指令执行完毕时,硬件检查是否有中断发生

》如果有并且使能,硬件自动保存现场(一些特殊寄存器)

》然后跳转到这个中断的 ISR 中执行(每个中断都有一个对应的 ISR,保存在中断向量表中)

》ISR 执行完毕,软件恢复现场(还原之前保存的寄存器的值),程序回到被中断的地方继续执行。

NVIC中断

3,NVIC 嵌套向量中断控制器

Cortex-M3 中使用 NVIC 嵌套向量中断控制器来管理外设的中断。NVIC 属于内核外设,资料参考《编程手册》。

Cortex-M3 支持 256 个中断(16 个内核中断 + 240 个外部中断),支持 256 级中断优先级。使用 IPR 寄存器,8 位。

但 STM32F103RCT6 只支持 76 个中断(16 个内核中断 +60 个外部中断),支持 16 级中断优先级。只使用 IPR[7:4] 4 位。

中断优先级数值越小级别越高,数值越大级别越低。IPR[7:4] 可分为两组,称为抢占优先级和响应优先级

将 4 个位如何分配抢占优先级和响应优先级各占的位数,称为优先级分组。

NVIC中断

4,抢占优先级和响应优先级

抢占优先级高的中断,可以中断正在执行的低抢占优先级中断;

在抢占优先级相同的情况下,两个不同响应优先级的中断同时到达,优先执行高响应优先级中断;但如果低响应优先级中断已在执行,此时到达的高响应优先级中断不可中断低响应优先级中断。

5,中断优先级分组



void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);   // 中断优先级分组
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);            // 中断初始化

优先级分组只能分组一次,不能多次分组,一般在程序开始时进行分组。如:



// 设置中断优先级分组2
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

6,USART1 中断配置

》设置中断优先级分组(设置一次即可)(由 SCB_AIRCR 管理)

》设置抢占和响应优先级,使能 USART1 中断(由 NVIC 管理)

》使能 USART1 接收非空中断和空闲中断(由 USART1 管理)

如果是 BEEP_ON
:就会产生9个非空中断,1个空闲中断

》实现 USART1 中断处理函数,在函数中接收数据到 “接收缓冲区”

》更改 USART1 接收函数 usart1_getchar(),改为从 “接收缓冲区” 中读取数据。

中断处理函数注意点:



函数名必须与中断向量表中完全相同:
            即stm32f10x_it.c文件中的中断函数名要与startup_stm32f10x_hd.s文件中 中断向量表 的内容一一匹配
没有返回值
没有参数
必须快速执行完毕,不能:
   执行耗时操作,如休眠、大量浮点运算、调用标准输入输出函数
 
因为中断处理函数是由硬件异步调用的,因此无法传递参数,也无法获得返回值,
因此中断处理函数既无返回值,也无参数。

使用空闲标志来处理数据包,可以提高 CPU 的效率。CPU 只在空闲标志置位后才处理数据包,不用每收到一个字节数据都处理一次,节省了时间。如果不使用空闲标志,则 CPU 必须每收到一个字节数据就要处理一次数据包,浪费了时间。

初始化和接收数据代码:



// USART1 接收缓冲区
volatile char usart1_recv_buf[USART1_RECV_BUF_SIZE];
volatile int usart1_recv_index;  // 中断接收数据下标
volatile int usart1_get_index;   // 应用读取数据下标
volatile int usart1_idle_flag;   // 空闲标志
 
/**
 * 简述:初始化 USART1 波特率为 baudrate
 * 参数:baudrate 波特率
 * 返回值:无
 **/
void usart1_init(uint32_t baudrate)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    USART_InitTypeDef USART_InitStruct;
    NVIC_InitTypeDef NVIC_InitStruct;
    
    // 使能 GPIOA 和 USART1 时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
    
    // 配置 PA10 为输入上拉
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IPU;
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_10;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 配置 PA9 为复用功能推挽输出
    GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // 配置 USART1 的波特率、数据位、奇偶校验位、停止位、硬件流控
    USART_InitStruct.USART_BaudRate   = baudrate;
    USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStruct.USART_Mode       = USART_Mode_Rx | USART_Mode_Tx;
    USART_InitStruct.USART_Parity     = USART_Parity_No;
    USART_InitStruct.USART_StopBits   = USART_StopBits_1;
    USART_InitStruct.USART_WordLength = USART_WordLength_8b;
    // void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
    USART_Init(USART1, &USART_InitStruct);
    
    // NVIC 配置
    NVIC_InitStruct.NVIC_IRQChannel                   = USART1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;  // 抢占优先级
    NVIC_InitStruct.NVIC_IRQChannelSubPriority        = 2;  // 响应优先级
    // void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
    NVIC_Init(&NVIC_InitStruct);
    
    // 使能 USART1 RXNE 和 IDLE 中断,分别使能,不能按位或
    // void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
    
    // 使能 USART1
    // void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
    USART_Cmd(USART1, ENABLE);
}
 
/**
 * 简述:USART1 接收一字节数据
 * 参数:无
 * 返回值:返回收到的字节数据
 **/
int usart1_getchar(void)
{
    int data;
    
    // 等待数据
    while (usart1_get_index >= usart1_recv_index);
    
    // 接收数据
    data = usart1_recv_buf[usart1_get_index];
    usart1_get_index++;
    if (usart1_get_index >= usart1_recv_index) {
        usart1_get_index = usart1_recv_index = usart1_idle_flag = 0;
    }
    
    // 回显
    usart1_putchar(data);
 
    // 返回收到的数据
    return data;
}

中断处理函数:



/**
 * USART1 中断处理函数
 * 参数:无
 * 返回值:无
 **/
void USART1_IRQHandler(void)
{
    // ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
        usart1_recv_buf[usart1_recv_index] = USART_ReceiveData(USART1);
        usart1_recv_index++;
        if (usart1_recv_index >= USART1_RECV_BUF_SIZE) {  // 越界了
            usart1_recv_index = usart1_get_index = usart1_idle_flag = 0;
        }
    } else if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET) {
        usart1_idle_flag = 1;
        // 注意空闲中断标志必须先读 SR 再读 DR 寄存器才可清除,不能手动清除
        USART_ReceiveData(USART1);
    }
}

7.以下图解是以 “BEEP_ON
” 为例,使用NVIC中断发送数据的原理:

一个字节一个字节的发送

NVIC中断

NVIC中断

8.附上源码:



#include "usart.h"
#include "stm32f10x.h"
/*
查看原理图得:
	PA9  - USART1_Tx
	PA10 - USART1_Rx
*/
//-------------------------------------------USART1 使用中断方式-----------------------------------------------------------
#if USART1_USE_IT
	//定义 USART1 中断接收缓冲区
	#define USART1_RECV_BUF_SIZE 32
	volatile uint8_t usart1_recv_buf[USART1_RECV_BUF_SIZE] = "";
	volatile int usart1_recv_index = 0;
	volatile int usart1_idle_flag = 0;
	volatile int usart1_get_index = 0;
 
 
/**
-------------------------------------------------------------------------------------------------------------------
	@brief 初始化usart1为中断方式
	@param baudrate 波特率
	@retval void
	
	
	//1.使能 GPIOA 和 USART1 时钟:
				给 GPIOC 模块开启时钟,否则 IO 口寄存器不工作。
				相当于 要先给外设“供电”,才能配置寄存器。
	
	//2.配置 PA9 为复用功能推挽输出
				设置 GPIO 的工作模式,这里是 复用推挽输出(能输出高电平和低电平)
				因为操作的是 USART1,而不是GPIO,直接操作GPIO使用通用推挽。
				能输出高电平和低电平,常用于驱动 LED。
	//3.配置 PA10 为输入浮空
				
	//4.协议层配置
-------------------------------------------------------------------------------------------------------------------
*/
void usart1_init(uint32_t baudrate){
	GPIO_InitTypeDef GPIO_InitStruct; //初始化
	USART_InitTypeDef USART_InitStruct;
	NVIC_InitTypeDef NVIC_InitStruct;
	//1.使能 GPIOA 和 USART1 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1,ENABLE); 
	
	//2.配置 PA9 为复用功能推挽输出
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP; //复用推挽
	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9;      //IO 口
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//输出速度
	GPIO_Init(GPIOA,&GPIO_InitStruct);						//将上面配置的参数写入寄存器中,真正初始化 GPIO,不设置则以上参数设置无效。
	//3.配置 PA10 为输入浮空
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_10;      //IO 口
	//GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//输出速度
	GPIO_Init(GPIOA,&GPIO_InitStruct);						//将上面配置的参数写入寄存器中,真正初始化 GPIO,不设置则以上参数设置无效。
	//4.USART协议层配置
	USART_InitStruct.USART_BaudRate = baudrate; //波特率
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无流控
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //可收可发
	USART_InitStruct.USART_Parity = USART_Parity_No; //奇偶校验:无奇偶校验
	USART_InitStruct.USART_StopBits = USART_StopBits_1; //停止位:1
	USART_InitStruct.USART_WordLength = USART_WordLength_8b; //字长有八位和九位选择:8位数据位
	USART_Init(USART1,&USART_InitStruct); //初始化 USART:将以上配置的参数写入到寄存器中,真正初始化 USART。如果不调用前面设置的参数就不会生效
	//5.使能 USART1
	USART_Cmd(USART1,ENABLE);
	
	//加 USART1的 NVIC 中断配置
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; //中断向量表入口的编号
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能还是禁用
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; //响应优先级
	NVIC_Init(&NVIC_InitStruct);
	
	//加 USART1_RXNE 和 IDLE 中断使能
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	USART_ITConfig(USART1,USART_IT_IDLE,ENABLE);
}
 
/**
-------------------------------------------------------------------------------------------------------------------
	@brief 	实现 USART1 发送数据函数
	@param  ch 要发送的数据
	@retval int

查询标志位:USART寄存器描述 -> 24.6.1状态寄存器(USART_SR)
-------------------------------------------------------------------------------------------------------------------
*/
int usart1_putChar(int ch){
	//1.等待可发送数据(即等待 TXE 标志位为 1)
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) != SET); //判断标志位不等于1就等待,等于1才可发送数据
	//2.发送数据
	USART_SendData(USART1,ch); //ch为要发送的数据
	//3.返回已发送的数据
	return ch;
}
 
/**
-------------------------------------------------------------------------------------------------------------------
	@brief 	实现 USART1 接收数据函数
	@param  void
	@retval int 接收的数据

查询标志位:USART寄存器描述 -> 24.6.1状态寄存器(USART_SR)-> 
-------------------------------------------------------------------------------------------------------------------
*/
int usart1_getChar(void){
	int data;
	//等待可接收数据(即等待 usart1_get_index < usart1_recv_index)
	while(usart1_get_index >= usart1_recv_index); 
	//接收数据
	data = usart1_recv_buf[usart1_get_index]; //接收到的数据存到data中
	usart1_get_index++; //下标自增往后移位
	if(usart1_get_index >= usart1_recv_index){
		usart1_get_index = usart1_recv_index = usart1_idle_flag = 0;
	}
	//回显示:将接收到的数据再发出去
	usart1_putChar(data);
	//返回收到的数据
	return data;
}
 
 
#else
//-------------------------------------------使用轮循方式-----------------------------------------------------------
/**
-------------------------------------------------------------------------------------------------------------------
	@brief 初始化usart1 为轮循方式
	@param baudrate 波特率
	@retval void
	
	
	//1.使能 GPIOA 和 USART1 时钟:
				给 GPIOC 模块开启时钟,否则 IO 口寄存器不工作。
				相当于 要先给外设“供电”,才能配置寄存器。
	
	//2.配置 PA9 为复用功能推挽输出
				设置 GPIO 的工作模式,这里是 复用推挽输出(能输出高电平和低电平)
				因为操作的是 USART1,而不是GPIO,直接操作GPIO使用通用推挽。
				能输出高电平和低电平,常用于驱动 LED。
	//3.配置 PA10 为输入浮空
				
	//4.协议层配置
-------------------------------------------------------------------------------------------------------------------
*/
void usart1_init(uint32_t baudrate){
	GPIO_InitTypeDef GPIO_InitStruct; //初始化
	USART_InitTypeDef USART_InitStruct;
	//1.使能 GPIOA 和 USART1 时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1,ENABLE); 
	
	//2.配置 PA9 为复用功能推挽输出
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF_PP; //复用推挽
	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_9;      //IO 口
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//输出速度
	GPIO_Init(GPIOA,&GPIO_InitStruct);						//将上面配置的参数写入寄存器中,真正初始化 GPIO,不设置则以上参数设置无效。
	//3.配置 PA10 为输入浮空
	GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_IN_FLOATING; //浮空输入
	GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_10;      //IO 口
	//GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;//输出速度
	GPIO_Init(GPIOA,&GPIO_InitStruct);						//将上面配置的参数写入寄存器中,真正初始化 GPIO,不设置则以上参数设置无效。
	//4.USART协议层配置
	USART_InitStruct.USART_BaudRate = baudrate; //波特率
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无流控
	USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //可收可发
	USART_InitStruct.USART_Parity = USART_Parity_No; //奇偶校验:无奇偶校验
	USART_InitStruct.USART_StopBits = USART_StopBits_1; //停止位:1
	USART_InitStruct.USART_WordLength = USART_WordLength_8b; //字长有八位和九位选择:8位数据位
	USART_Init(USART1,&USART_InitStruct); //初始化 USART:将以上配置的参数写入到寄存器中,真正初始化 USART。如果不调用前面设置的参数就不会生效
	//5.使能 USART1
	USART_Cmd(USART1,ENABLE);
}
 
/**
-------------------------------------------------------------------------------------------------------------------
	@brief 	实现 USART1 发送数据函数
	@param  ch 要发送的数据
	@retval int

查询标志位:USART寄存器描述 -> 24.6.1状态寄存器(USART_SR)
-------------------------------------------------------------------------------------------------------------------
*/
int usart1_putChar(int ch){
	//1.等待可发送数据(即等待 TXE 标志位为 1)
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) != SET); //判断标志位不等于1就等待,等于1才可发送数据
	//2.发送数据
	USART_SendData(USART1,ch); //ch为要发送的数据
	//3.返回已发送的数据
	return ch;
}
 
/**
-------------------------------------------------------------------------------------------------------------------
	@brief 	实现 USART1 接收数据函数
	@param  void
	@retval int 接收的数据

查询标志位:USART寄存器描述 -> 24.6.1状态寄存器(USART_SR)-> 
-------------------------------------------------------------------------------------------------------------------
*/
int usart1_getChar(void){
	int data;
	//等待可接收数据
	while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != SET); //判断标志位不等于1就等待,等于1才可接收数据
	//接收数据
	data = USART_ReceiveData(USART1); //接收到的数据存到data中
	//回显示:将接收到的数据再发出去
	usart1_putChar(data);
	//返回收到的数据
	return data;
}
 
#endif   //USART_USE_IT
 
/*
********************************************************************************************************************
	在单片机上不能直接使用 printf()/scanf() 等标准输入输出函数
	必须经过重定向才能使用它们
	重定向的作用就是将 USART1 与标准输入输出函数关联起来(本来串口和标准输入输出函数没有关系)
	如何理解输出重定向?原理:
			printf()等标准输入输出函数内部会调用 fputc()
			并在 fputc() 函数内调用 usart1_putChar() 即可将要打印的数据通过 USART1 发送出去
	如何理解输入重定向?原理:
			scanf()等标准输入输出函数内部会调用 fgetc()
			并在 fgetc() 函数内调用 usart1_getChar() 即可从 USART1 接收数据
------------------------------------------------------------------------
	两种方法实现输入输出重定向
			方法一:使用微库 	操作:魔术棒->Target标签->勾选 MicroLIB 选项  使用微库就要重写fputc()/fgetc()
			方法二:使用标准库
********************************************************************************************************************
*/
#ifdef __MICROLIB	//使用微库,首先要先去魔术棒打开才能启用
int fputc(int ch){
	return usart1_putChar(ch);
}
int fgetc(void){
	return usart1_getChar();//就可以接收数据
}
#else //使用标准库:打开KEIL软件安装目录下的 ARM/Startup/Retarget.c 文件,复制全部内容到当前位置
//--------------------------------------------------------------------------------------------------------------------------------------------------------
/******************************************************************************/
/* RETARGET.C: 'Retarget' layer for target-dependent low level functions      */
/******************************************************************************/
/* This file is part of the uVision/ARM development tools.                    */
/* Copyright (c) 2005 Keil Software. All rights reserved.                     */
/* This software may only be used under the terms of a valid, current,        */
/* end user licence from KEIL for a compatible version of KEIL software       */
/* development tools. Nothing else gives you the right to use this software.  */
/******************************************************************************/
 
#include <stdio.h>
//#include <time.h>		//屏蔽这两个包含
//#include <rt_misc.h>
 
#pragma import(__use_no_semihosting_swi)
 
//屏蔽这三个声明
//extern int  sendchar(int ch);  /* in Serial.c */
//extern int  getkey(void);      /* in Serial.c */
//extern long timeval;           /* in Time.c   */
 
 
struct __FILE { int handle; /* Add whatever you need here */ };
FILE __stdout;
FILE __stdin;
 
 
int fputc(int ch, FILE *f) {
  //return (sendchar(ch));
	return usart1_putChar(ch);	//将 return (sendchar(ch)); 更改为 return usart1_putChar(ch);
}
 
int fgetc(FILE *f) {
  //return (sendchar(getkey())); //该函数实现了回显,在该文件中已经实现了回显,这里就不需要此函数
	return usart1_getChar();       //更改为 usart1_getChar()
}
 
 
int ferror(FILE *f) {
  /* Your implementation of ferror */
  return EOF;
}
 
 
void _ttywrch(int ch) {
  //sendchar (ch);
	usart1_putChar(ch); //更改为 usart1_putChar()
}
 
 
void _sys_exit(int return_code) {
  while (1);    /* endless loop */
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------
#endif

——————————————–关注点赞不迷路!!!———————————————–

© 版权声明

相关文章

暂无评论

none
暂无评论...