第二天_V2_中断控制、外部中断与系统定时器SysTcik

第二天

第1节: STM32单片机中断控制系统NVIC

中断:一件事情被打断了。龙虾哥开车的时候被路边跑出的人给打断了,那么开车这件事情被打断了就是中断。

单片机:CPU正在执行某个程序,突然有一件事情发生了,这件事情打断了CPU 执行当前的程序,这就叫作CPU发生了中断,CPU被打断了。

中断事件:这件事情

中断(interrupt,IT)源:产生中断事件的硬件电路

中断(请求)标志位:中断事件产生,用一个标志位来记录

如果中断事件产生,那么这个标志位啊我们就让它等于1,没发生就让标志位为0。

通过具体的某一个中断标志位,来判断一件事情是否发生。

中断源可以产生多个中断事件。

一个中断事件对应一个中断标志位,一个中断源它有多个中断标志位,根据中断标志位来辨别是哪一个中断事件产生。

状态寄存器:储存中断标志

每一个中断源,它都有一个状态寄存器,状态寄存器里面每一个二进制都是一个中断标志.

CPU被打断之后,需要去处理这件事。处理完这件事之后,继续执行刚才被打断的程序.

中断处理过程:处理这件事

这个过程是在一个函数里面进行处理的,函数专门用来处理这件事情

中断处理函数:处理这件事情的过程。

总结:

        程序执行中被打断就是中断。中断源产生中断事件(一个中断源可以产生多个中断事件),中断事件又产生中断标志位(事件和标志位一一对应),并且将中断标志位存储在状态寄存器中,根据这个中断标志位我们可以判断产生了中断事件,然后通过中断处理函数(中断处理函数和中断源是对应的)处理这个中断。处理完毕,cpu就继续去处理之前没完成的程序。

优先级:当多个中断事件同时发生时,会根据中断优先级来处理。优先级高的中断事件会被优先处理。

中断数组(中断向量表):存储中断函数首地址(中断函数的名字)的数组。地址的集合。

函数指针,函数名就是地址,然后统一存储,中断数组应该就是函数指针的应用场景。

注意:申请中断数组的内存,在这个文件startup_stm32f10x md.s(启动文件)中申请。中断处理函数的实现在stm32f1xx it.c()

在.s启动文件中找到中断处理函数的名字复制,然后打开这个.C文件,在这里面写定义,定义中要判断是那个中断标志位,不同的中断标志位需要处理的事件是不一样的。

STM32F10xxx产品(小容量、中容量和大容量)的数组/向量表

startup_stm32f10x md.s

中断总结:

         程序执行中被打断就是中断。中断源产生中断事件(一个中断源可以产生多个中断事件),中断事件又产生中断标志位(事件和标志位一一对应),并且将中断标志位存储在状态寄存器中,根据这个中断标志位我们可以判断产生了中断事件,然后通过中断处理函数(中断处理函数和中断源是对应的)处理这个中断。处理完毕,cpu就继续去处理之前没完成的程序。

注意:中断事件想发出中断请求,得经过允许。

中断事件想发出中断请求,得经过允许,有一个寄存器控制位就是用来设置是否允许中断事件。

中断控制器NVIC:

        中断源产生多个中断事件,每一个中断事件要想产生中断请求,需要先设置它的控制位,这个控制位就叫做中断请求允许位,控制位里写1就表示允许他产生终端请求(0则不允许),中断允许也算是一个开关或者说中断使能(中断请求使能)。

        那么这些请求如果被允许,送往CPU,中断请求是通向CPU,所以我们要把这个请求传送到CPU这边。每一个领导他都有自己的秘书,秘书负责接待的,不是让随便一个人都能进来的,有的人是不允许他见到领导的,所以我们CPU有一个秘书叫做中断控制器NVIC,协助CPU处理各种请求。中断控制器就是负责把这些请求进行筛选排序,然后选择一个请求让CPU来处理,NVIC负责多个窗口,每一个中断源都对应一个窗口。比如:这里面有八个窗口(通道),通道都有一个开关。只有开关打开,你的请求才能够通过这个通道(类似得提前预约),开关取了个名字叫做中断通道开关(中断通道使能),多个中断请求进通道时看那个优先级高就先处理那个(多个人预约,先会面重要的人),也可以叫中断通道优先级。每个中断源都有一个通道,每个通道都有一个优先级,谁的高就处理那个的中断请求。优先级又分两个部分组成:第一部分叫做抢占优先级,第二部分叫做响应优先级。当两个通道同时有中断请求的时候,首先比较抢占优先级,然后再比较响应优先级。两个级别都相同的话,看中断函数地址表(中断函数数组/向量表)那个名字靠前就处理那个。

        如果两个通道如3,4优先级比较后,4比较高,还得看CPU工作状态,如果CPU在接待普通老百姓(CPU在执行普通程序),那么直接打断,因为其社会地位是比较低的,通道4是县委书记,则直接打断CPU暂停接待老百姓而且接待县委。如果CPU接待也是一个领导(CPU在执行中断程序),那么NVIC就得判断那个县委书记和在接待领导的地位(比较他们通道的抢占优先级),高才能打断,等同不能打断。打断中断,也算中断嵌套。最后,看这个请求来自哪个通道(中断源、中断通道、中断处理函数一一对应),找到它对应的中断处理函数,紧接着这个CPU的内核就去找那个函数(在中断向量表里面),再通过函数去找这个地址所对应的代码,然后执行程序。执行结束,从哪里被打断的就回到哪里继续原本执行程序。

中断请求:允许这个中断是否能产生的开关。(中断请求使能)

中断响应:优先级比较后去执行相应的函数。

总结:中断源产生产生中断事件(可以产生多个),事件在允许(中断(请求)使能)后产生中断请求,如果有相应的通道开关打开(中断通道使能),则中断请求通过相应的通道(8通道)进入CPU,每个通道有自身的优先级(1.抢占2.相应),当多个请求在不同通道时看那个优先级更高(优先级比较),最后判断CPU工作状态(执行普通、中断程序),普通则直接打断,中断则再比较抢占优先级再看打不打断,打断后就执行相应的中断处理函数。结束中断然后返回CPU中断前的程序继续执行。

注意:2个开关:1.中断请求开关(中断请求使能)        2.中断通道开关(中断通道使能)

4个判断:1.判断请求开关(使能)  2.判断通道开关((总开关)使能)  3.判断通道优先级  4.判断CPU是否能被打断

NVIC:1.控制通道开关、优先级比较 2.判断CPU工作状态

中断(通道)优先级

中断优先级是指中断通道的优先级

优先级:抢占优先级和响应优先级

多个通道同时出现中断请求时,优先级高的被优先处理

中断优先级分组

编号越小优先级越高

分组是根据抢占优先级的位数:

4-0 3-1 2-2 1-3  0-4:分组分别是4组,3组,2组,1组,0组

如下是2组:2-2,抢占2位,响应2位

如下分组3:3 - 1

4位二进制位作为配置位

中断关键词:

1.中断事件

2.中断源,中断请求

3.中断允许,中断(请求)标志位

4.中断响应

5.中断处理函数/中断服务函数

中断的两个特点:

(1)发生的随机性,不确定性。

(2)中断处理(服务)函数的确定性。

第2-1节:STM32外部中断原理分析

一、外部中断

1、从 I/O引脚输入的事件引起的中断,外部中断

2、事件可分为硬件事件、软件事件,能够引起中断的事件叫作中断事件通过硬件电路产生的事件叫作硬件事件,通过软件指令产生的事件叫作软件事件。

3、中断事件是由中断处理函数来处理的;普通事件是由硬件电路处理的

中断事件:由单片机外部(引脚输入),能打断CPU的事件

二、外部中断功能结构简图

引脚:单片机的引脚它是按照并口来分的(ABCDEFG),一个并口由16个引脚组成(0-15)

外部中断:由外部事件从单片机引脚输入进来,产生中断。引脚编号0-15,每个引脚编号对应一个外部中断入口,16个入口对应编号的引脚。

中断线,事件线(EXTin(n:0-19)):入口,20条中断线,每一个中断线就相当于一个外部中断的入口,不同入口连接的中断线入口是不一样。

注意:16个引脚,20个中断线,多出来4个不是和单片机引脚相连接的。连接单片机引脚的中断线编号是EXTI0到EXTI15。EXTI0可以和PA0、B0、C0、D0、E0、F0、G0七个引脚连接,中断线只能选择一个引脚相连接。中断线N只能和编号为N的引脚相连接。

AFIO EXTICR1-4(寄存器):中断线n选择连接引脚(PAn、PBn、PCn...)的寄存器。每个寄存器低16位是用来,选择配置引脚

外部中断引脚配置寄存器

AFIO_EXTICR1:EXTI0-3; 

AFIO_EXTICR2:EXTI4-7;  

AFIO_EXTICR3:EXTI8-11;

AFIO_EXTICR4:EXTI12-15;

引脚配置:

16个中断线EXTI0-15,刚好对应16个引脚。如:你要配置PB5,则选择AFIO_EXTICR2的EXTI5,并且配置EXTI5为0001。

第一步确定中断事件从哪一个编号的引脚进入到单片机内部的,找到中断线(EXTI5),然后再来选择是哪七个角(PB)。

结构简图解析

硬件电路请求:如此则外部中断进入,这边有一个叫做边缘检测电路,用来检测边沿(两种上升沿和下降沿),检测那个沿由上升、下降沿选择寄存器(0-19位可以使用,对应20个中断线)决定,可以同时检测上升下降沿(都写1即可),这个事件请求是由硬件电路产生的,也称为硬件电路请求

软件事件请求:在软件中断事件寄存器某个位写1,如第七位写1,进入到中断线7,那么这个软件事件它是从中断线7过来。

上面是硬件请求,下面是软件请求,进入或门。两个通道只要有一个有请求就会再产生一个事件请求。

往下面的与门(两个请求同时满足才能进入),与门它的下面是一个事件请求允许寄存器(请求开关),开关开启这个事件才能通过这个开关,有事件请求且开关开启两个条件同时满足,才能通过脉冲发生电路。

通往上面的与门,这个请求就比较特殊,可以申请中断,因此叫中断请求,这个开关也叫中断请求允许。当有中断时,请求挂起寄存器,相对应的位有硬件会写一个1(表示有中断请求产生),如果这个请求来自6号引脚,那么这个寄存器的第6位就会写个1。

因此我们可以记录请求挂起寄存器来判断是否有中断产生。并且判断这个寄存器哪一位是1。用来区分这几个共用通道中断,到底是哪一个引脚产生的中断。

7个通道, 每一个通道都有一个中断通道开关,所以我们在写程序的时候,首先要进行中断分组,然后呢要对优先级进行设置,最后要把对应的通道你要打开(使能中断通道)。

总结:

怎么写程序呢?

第一步,首先:检测的是上升沿还是下降沿,如果上升沿,那么单片机的初始状态就是低电平,下拉输入(它的初始状态才是低电平),然后产生高电平,就是低变高(上升沿)。下降沿反之同理。

第二步:要对事件线引脚选择选择,如:5号角,你选择连接的中断线是5号中断线,再选是P(A、B、C....),也就是配置AFIO_EXTICR2。记得打开AFIO时钟

第三步:你是上升沿,上升沿选择寄存器第五位(5号角进来的)写1,不管下降沿选择寄存器。

第四步:我们要产生外部中断,则中断请求允许需要打开,让时间请求通过与门。

第五步:通道的中断要进行初始化(1.分组 2.优先级 3.通道打开)

初始化完毕!

第六步,最后:紧接着就要进行中断处理函数的编写:1.读取请求挂起寄存器判断是哪一个中断请求,不同的中断请求不同的处理 2.请求处理后,请求挂起寄存器清零(特殊:写1清零)。

注:10个通道对应十个中断处理函数 (启动文件.s)

第2-2节:STM32外部中断实验_寄存器

程序流程图:

代码实现

#include "main.h"
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "GPIO.h"

void Led_Init_1(void);
void Key_GPIO_Init_1(void);
void EXTI_Line5_Init_1(void);

int main(void)
{
 Led_Init_1();
 Key_GPIO_Init_1();
 EXTI_Line5_Init_1();
	
	while(1)
	{

	}
}


void Led_Init_1(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
	GPIOB->CRH &= 0XFF0FFFFF;//清零PB13
	GPIOB->CRH |= (GPIO_OUTPUT_PP + GPIO_SPEED_2MHZ)<<((13-8)*4);
	
	GPIOB->ODR |= 1<<13;
}


void Key_GPIO_Init_1(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
	GPIOB->CRL &= 0XFF0FFFFF;//清零PB5
	GPIOB->CRL |= (GPIO_INPUT_PULLUP)<<(5*4);
	
	GPIOB->ODR |= 1<<5;
}


void EXTI_Line5_Init_1(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
	
	//AFIO->EXTICR[1] |= 1<<4;
	AFIO->EXTICR[1] |= AFIO_EXTICR2_EXTI5_PB;
	
	EXTI->FTSR |= 1<<5;
	EXTI->IMR |= 1<<5;
	
	NVIC_SetPriorityGrouping(3);//3bit 抢断,1bit 响应
	NVIC_SetPriority(EXTI9_5_IRQn,5);
	
	NVIC_EnableIRQ(EXTI9_5_IRQn);
}




void EXTI9_5_IRQHandler(void)
{
	if(EXTI->PR&(1<<5))
	{
		EXTI->PR |= (1<<5);
		Delay_ms(100);
		if((GPIOB->IDR&(1<<5)) == 0)//PB5 S2
		{
			if(GPIOB->ODR&(1<<13))//LED PB13,输出极性翻转
			{
				GPIOB->ODR &= ~(1<<13);
			}
			else
			{
				GPIOB->ODR |= (1<<13);
			}
		}
		
	}
}

注意:

中断函数里面尽量不用延迟,CPU阻塞就等你这个延时,啥也不干,浪费资源(性能、功耗)

按键一般不用外部中断。外部中断一般用在比较器,比较器的输出会接一个RC滤波器,硬件滤波,中断里面就不需要通过软件来延迟,通过比较器来比较两个模拟信号,当模拟信号大于特定值。产生特定信号,信号通过滤波器,变成了没有干扰的信号,这个信号就是由外部中断来处理的。

第3-1节: STM32单片机SysTick定时器的介绍与应用

SysTick定时器(24位,系统定时器):在CPU内部,时钟信号在上电的时候就是开启的,不同于其他模块(如:GPIO等),在使用之前需要先打开它的时钟,然后才可以对它进行初始化以及使用。

中断实现延迟:简单来说就是我们先设定一个时间,当这个时间到的时候会产生一个中断,把我们要执行的任务放在中断处理函数里面,这样子就可以实现一个延迟作用。(定时然后时间到就执行中断函数,定时就是延迟的作用,也不影响其他程序在CPU的执行)

中断实现的延迟,不影响其他程序的CPU继续执行其他程序。当中断产生的时候,再来执行我们啊延迟所需要执行的程序。

周期信号:波形按照时间T重复
信号的频率:单位时间内波形重复的次数
周期:波形重复一次的时间

定时器与计数器

计数器:可以计数的电路

定时器:是一种特殊的计数器,时钟是周期性信号。(计数器如果你给他一个周期性信号,就可以定时的作用)。

系统定时器是倒计时,你要定时,那么你首先要给它一个初值。倒计时到0时,如果我们设置中断的话,就会产生中断请求,CPU去处理这个中断请求,调用中断处理函数

重装载寄存器:存储倒计时的初值。如:你要定时50ms(一个周期1ms),就往重装载写50。

中断允许打开开关:当计数器减到零的时候,是否需要中断。

systick启动/停止:倒计时开关,需要定时时就打开开关

初始化设置:1.设置时钟,一个周期是多长时间 2.设置它的初值(重装载寄存器) 3.第三个要设置是否需要中断 4.启动定时器

系统定时器的时钟信号:两种方式  ①HCLK这个时钟(最大72MHZ)②HCLK这个时钟(最大9MHZ)        它里面是有一个寄存器用来设置的(0:9MHZ,1:72MHZ)

1/8 HCLK:外部时钟

HCLK:内部时钟,内核的时钟信号

SysTick定时器

SysTick 为一个24位递减计数器(最大2^{^{24}}-1,0X:0~0XFFF FFF),SysTick设定初值并使能后每经过1个时钟周期,计数值就减1。

计数到0时,SysTick计数器自动重装初值并继续计数,同时内部的COUNTFLAG标志会置位,触发中断(如果允许中断请)

判断COUNTFLAG标志,来判断计数器是否减到零。

SysTick的寄存器

CTRL:控制寄存器,0XEO00E010

BIT16:归零标志位,可以用来中断请求(当我们读这个标志位的时候,这个寄存器这1位就会自动变成零,不需要我们手动清零)  

        1:归零         0:没有归零

BIT2(第二位): 时钟分频选择。1: HCLK,0:HCLK/8

BIT1: 中断请求允许位。1: 允许 0:不允许

BITO: 定时器启动。 1:启动 0:停止

RELOAD: 定时器重装载数值寄存器(存储初值)

地址: 0XE000E0

14

BIT23: BIT0,24位。0XFFFFFF

CNT: 当前计数寄存器,其中的数值,每一个时钟减一。

地址: 0XE000E018。

最大值:0XFFFFFF

初始化:

1.首选选择,定时器的时钟源:

sysclk=1/8HCLK,72/8=9MHZ

1us=9脉冲,9脉冲=1us

1s: 9 000 000 次脉冲,1s=1000 000us .1us=9个脉冲。

2.设置是否需要产生中断

3.关掉定时器关掉。那么72x8分之一就等于九兆

总结:

使用中断实现延迟函数 

1、选择时钟源 HCLK/8

2、设置初值 

3、设置中断请求 允许位

void SysTick_Handle(void)

4、启动定时器

第3-2节:SysTcik采用中断实现LED灯闪烁

首先实现一个10ms延时的中断,然后在中断里面实现led灯闪烁

程序流程图:

代码段

#include "main.h"
#include "stm32f10x.h"                  // Device header
#include "Led.h"
#include "Delay.h"
#include "stdint.h"
#include "GPIO.h"

void delay_init_SysTick(void);
void Led_Init_SysTick(void);

int main(void)
{
	Led_Init_SysTick();
	delay_init_SysTick();
	
	while(1)
	{

	}
}

void Led_Init_SysTick(void)
{
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
	
	GPIOB->CRH &= 0X000FFFFF;//PB13
	GPIOB->CRH |= (GPIO_OUTPUT_PP + GPIO_SPEED_2MHZ)<<(13-8)*4;

	
	GPIOB->ODR |= 1<<13;

}

void delay_init_SysTick()
{
	
	//1.时钟源默认9MHZ
	//2.初值 10ms ,9脉冲=1us
	SysTick->LOAD = 9*10000 - 1;
	//3.是否中断
	SysTick->CTRL = 1<<1;
	//配置中断 分组、优先级、开启
	NVIC_SetPriorityGrouping(15);
	NVIC_SetPriority(SysTick_IRQn, 15);
	NVIC_EnableIRQ(SysTick_IRQn);
	
	SysTick->CTRL |= 1<<0;
	
}

void SysTick_Handler(void)
{
	static uint16_t T_cnt = 0;
	
	T_cnt++;
	GPIOB->ODR &= ~(1<<13);
	if(T_cnt >= 50)//50*10ms = 500ms
	{
		T_cnt = 0;
		if(GPIOB->ODR&(1<<13))
		{
			GPIOB->ODR &= ~(1<<13);
		}
		else
		{
			GPIOB->ODR |= 1<<13;
		}
	}
}

错误总结;

启动定时器哪里: SysTick->CTRL |= 1<<0;

写成:SysTick->CTRL = 1<<0;        少了个|,导致没开启定时器

查bug:用ODR寄存器亮灭灯,看看程序运行到哪里,哪里没有运行,没运行的地方检查语法是不是除了问题导致这个地方没运行。

第4节:SysTick查询标志位实现延迟函数

程序流程框图:

代码段:
#include "main.h"
#include "stm32f10x.h"                  // Device header
#include "Led.h"
#include "Delay.h"
#include "stdint.h"
#include "GPIO.h"

void delay_init_SysTick(void);
uint8_t delay_us_SysTick(uint32_t nus);
uint8_t delay_ms_SysTick(uint32_t nms);

int main(void)
{
	Led_Init();
	delay_init_SysTick();
	
	while(1)
	{
		if(GPIOB->ODR&(1<<13))
		{
			GPIOB->ODR &= ~(1<<13);
		}
		else
		{
			GPIOB->ODR |= 1<<13;
		}
		delay_ms_SysTick(2000);
		//delay_us_SysTick(10000);
	}
}

void delay_init_SysTick()
{
	//初始化阶段不需要开启定时器,直接赋0即可
	SysTick->CTRL = 0;
}

uint8_t delay_us_SysTick(uint32_t nus)
{
	uint32_t temp = 0;//取出SysTick->CTRL的值,防止判断的时候寄存器CTRL变换
	
	if(nus*9 >= 0xFFFFFF)
	{
		return 0;//超过最大值失败
	}
	
	SysTick->LOAD = nus*9;
	SysTick->CTRL |= 1<<0;//开启定时器
	
	do
	{
		temp = SysTick->CTRL;//获取CTRL,防止判断时寄存器CTRL改变
	}
	while(  (temp&0x01)&&!(temp&(1<<16)) );//定时器开启,且判断标志位为1,计数完毕时(!(temp&(1<<16))跳出循环
	
	SysTick->CTRL &= ~(1<<0);//关闭定时器
	SysTick->CTRL &= ~(1<<16);//清除标志位
	return 1;
}

uint8_t delay_ms_SysTick(uint32_t nms)
{

	uint32_t temp = 0;//取出SysTick->CTRL的值,防止判断的时候寄存器CTRL变换

	if(nms<=186)
	{
		SysTick->LOAD = nms*9*1000;//ms是us的1000
		SysTick->CTRL |= 1<<0;//开启定时器
		
		do
		{
		temp = SysTick->CTRL;//获取CTRL,防止判断时寄存器CTRL改变
		}
		while(  (temp&0x01)&&!(temp&(1<<16)) );//定时器开启,且标志位为1,计数完毕时(!(temp&(1<<16))跳出循环
		
		SysTick->CTRL &= ~(1<<0);//关闭定时器
		SysTick->CTRL &= ~(1<<16);//清除标志位

	}
	else 
	{
		for(uint32_t i = 0; i<nms; i++)
		{
		delay_us_SysTick(1000);//1000us=1ms,约等于,不过实际时间因为for循环肯定会大于预计时间
		}
	}
	return 1;
}
错误总结:

1.

写成    while(  (temp&=0x01)&&!(temp&(1<<16)) );

判断&写成&=,判断语句应该是&

2.

清零&=写成=

        SysTick->CTRL = ~(1<<0);//关闭定时器
        SysTick->CTRL = ~(1<<16);//清除标志位

没关闭定时器,会导致循环无法跳出等未知错误!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值