stm32f103c8t6芯片基于江科协的学习个人理解( 超详解,包你会的)更新中........

        绝对对你有帮助,喜欢的请点个关注和赞啦,当作分享费用了,谢谢。

EXTI外部中断

        基于对射式红外传感器计次实验对于外部中断的代码实现的理解

        对于外部中断,最主要的应用是当外部引脚电平发生变化时要求单片机立即处理当前信号所谓规定的事情。

        对于外部引脚的实现可以这样去理解,我们是接收到外部外部引脚的电平变化,进入中断去处理我们规定好的程序,如何实现?用一个流程图来描述

        GPIO→AFIO→EXTI→NVIC

        为什么是这个流程呢?我们要用到外部引脚去感知电平变化肯定要用到I\O引脚所以就会用到GPIO而用GPIO就首先需要开启时钟,配置结构体;用到AFIO首先想到的是开启AFIO的时钟,AFIO的作用是这么多的I\O口(每一个Pin都会有3个引脚例如PA0、PB0、PC0具体是哪个?确定后定为EXTI0、EXTI1如下图所示),通过这个函数GPIO_EXTILineConfig来确定那个Pin,谁的Pin;EXTI更好理解,我们要用到EXTI这个外设来感知通过GPIO引脚输入的电平变化(上升沿、下降沿、上升沿和下降沿)三种模式来进入中断,所以也要开启EXTI的时钟,但EXTI的时钟不需要我们手动开启,系统已经帮我们开启了,然后配置结构体;NVIC的作用就是这么多的中断我应该交给CPU处理哪一个?中断会有不同的优先级(响应优先级和抢占优先级),根据不同的优先级规则确定好事情的轻重缓急交给CPU处理这个中断,用到NVIC也要开启NVIC的时钟,但NVIC也不需要我们开启时钟(EXTI和NVIC是少有的不用开启时钟的),所以配置好结构体就完事了。

        注意:需要注意的是我们要将中断定义为不同的优先级,整个工程只能定义一次优先级的分组,建议放在main中只执行一次,不要在每个功能函数中都实现会造成不必要的错误。

        下面是每个结构体的具体配置详解。 

        GPIO结构体的配置,是最简单的

        GPIO_Pin 是选择那个Pin引脚

        GPIO_Speed  是选择频率,99%情况下默认GPIO_Speed_50MHz 选他就完了

        GPIO_Mode 是选择GPIO的模式,当前我们要用这个Pin引脚来接收外部信号的电平“输入”变化,既然是输入只有我选出来的四种模式,GPIO_Mode_AIN 这个模式是模拟量输入,显然不是我们需要的模式,其余的三种模式(GPIO_Mode_IN_FLOATING 浮空输入、GPIO_Mode_IPD下拉输入、GPIO_Mode_IPU上拉输入)我们都可以选择,但我们要保持与外部输入的默认电平一直。

        举个例子:假如外部电平默认是高电平,你设置的模式是下拉输入,当Pin引脚变成低电平是因为外部输入了低电平还是你设置的低电平?例子不一定恰当,但其实你设置不相同的话问题也不大,毕竟外部输入的电平强于单片机的电平。

  

        对于EXTI结构体配置的理解

        EXTI_Line 是通过AFIO确定的是Pin几

        EXTI_LineCmd就是对这个EXTI_Line进行使能,凡是带有什么CMD的函数都是对某个东西进行是能与否的,你用它肯定给使能ENABLE就完了

        EXTI_Mode是选择EXTi的模式分为中断模式(就是普通的中断,写一个函数,然后让CPU出处理中断函数)和事件模式(事件模式主要是为了外设之间实现自动化的联动,一般不需要CPU的处理)

        EXTI_Trigger 是选择什么样的触发方式有上升沿、下降沿、上升沿和下降沿,根据需求选择

        

        对于NVIC的结构体配置的理解

        NVIC_IRQChannel 是对前面EXTI选出来的中断通道的选择IRQ就是中断Channel是通道。

        NVIC_IRQChannelCmd 是中断通道的使能,又是什么CMD你用它肯定使能ENABLE。

        下图是STM32F103C8T6的全部中断通道,跳转时注意选择是这个STM32F10X_MD里面的。

        NVIC_IRQChannelPreemptionPriority 是抢占优先级,NVIC_IRQChannelSubPriority 是相应优先级,他们俩个的取值取决于前面NVIC的分组,不同的分组的取值范围不同,细则如下图所示。

AFIO的功能图 

        经过以上对为什么这样选择和对结构体的配置下面的代码很容易就能理解了,当我们看代码的时候不要一行一行的去读,而是要一块一块的读,把一个功能分为一个块,比如开启时钟一块,配置结构体一块,这么去看代码其实就非常的清晰明了了。

以下是代码实现

main.c 

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "CountSensor.h"

int main(void)
{
	/*模块初始化*/
	OLED_Init();			//OLED初始化
	CountSensor_Init();		//计数传感器初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Count:");	//1行1列显示字符串Count:
	
	while (1)
	{
		OLED_ShowNum(1, 7, CountSensor_Get(), 5);		//OLED不断刷新显示CountSensor_Get的返回值
	}
}

CountSensor.h 

#ifndef __COUNT_SENSOR_H
#define __COUNT_SENSOR_H

void CountSensor_Init(void);
uint16_t CountSensor_Get(void);

#endif

CountSensor.c 

#include "stm32f10x.h"                  // Device header

uint16_t CountSensor_Count;				//全局变量,用于计数

/**
  * 函    数:计数传感器初始化
  * 参    数:无
  * 返 回 值:无
  */
void CountSensor_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);		//开启GPIOB的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);		//开启AFIO的时钟,外部中断必须开启AFIO的时钟
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB14引脚初始化为上拉输入
	
	/*AFIO选择中断引脚*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);//将外部中断的14号线映射到GPIOB,即选择PB14为外部中断引脚
	
	/*EXTI初始化*/
	EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量
	EXTI_InitStructure.EXTI_Line = EXTI_Line14;					//选择配置外部中断的14号线
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//指定外部中断线为下降沿触发
	EXTI_Init(&EXTI_InitStructure);								//将结构体变量交给EXTI_Init,配置EXTI外设
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;		//选择配置NVIC的EXTI15_10线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
}

/**
  * 函    数:获取计数传感器的计数值
  * 参    数:无
  * 返 回 值:计数值,范围:0~65535
  */
uint16_t CountSensor_Get(void)
{
	return CountSensor_Count;
}

/**
  * 函    数:EXTI15_10外部中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void EXTI15_10_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line14) == SET)		//判断是否是外部中断14号线触发的中断
	{
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0)
		{
			CountSensor_Count ++;					//计数值自增一次
		}
		EXTI_ClearITPendingBit(EXTI_Line14);		//清除外部中断14号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
}

TIMER定时器

        基于定时器定时中断探究定时器的使用

        定时器顾名思义是用来计时的,时间到了去干某个事情,比如:一个闹钟就是是一个定时器,定个闹钟要叫我们起床,闹钟响了我们起床;在程序中就是我们提前写好的程序,时间到了单片机去执行这个程序。

        对于定时器中断我们来画一个流程图,根据这个流程图来讲解,读完后理解了你自然就明白为什么是这个流程了。

        TIM→NVIC

        是不是超级简单,相对于EXTI中断少了GPIO和AFIO,我们思考一下为什么没有这两个,定时器是我们单片机内部的外设,不需要外部输入自然而然也就不需要配置GPIO,那为啥没有EXTI呢?我们用的是TIM定时器外设的中断啊,不是外部中断自然没有EXTI啦。

        TIM的配置:很简单,固定不变的套路,我们要用到定时器,首先开启TIM的时钟,我们的时钟是谁说了算?根据谁的始终标准跑?所以我们要选择定时器的时钟源;我们的闹钟大家都知道过了12点变成0,要重置吧,所以定时器就要配置时基单元;你不是要定时器中断?开启定时器中断使能;最后开启TIM使能,OVER!

        NVIC的作用和结构体在EXTI的外部中断有介绍,如有不会请自行查找啦,这里简单说下不同,因为EXTI不同有很多个外部引脚需要通过AFIO来选出那个Pin交给NVIC配置优先级,而TIM定时器是外设的中断直接连接到NVIC的,所以直接来告诉NVIC我那个定时器要处理中断,中断的内容是什么即可。        

        TIMBASE时基单元结构体配置

        TIM_ClockDivision 与后面的预分频效果一样都是对我们前面选择的时钟源进行分频,通常默认不分频TIM_CKD_DIV1 选这个参数就完了,选这个参数的主要原因是为了方便计算,方便计算什么?请看下文。

         

        TIM_Prescaler 通常我们把这个参数称之为PSC预分频,预分频它的作用是将我们前面选择的时钟源进行分频(分频就是时钟源的信号你想它是输入1KHZ输出1KHZ或者输入1KHZ输出10HZ)通常我们的时钟源是几十MHZ,这个频率很快我们需要调整,取值范围如图所示0X0000~0XFFFF,我们配置的时候直接写10进制数即可,也就是0~65535,调整公式:想要的频率-1            

        TIM_CounterMode 是计数模式大体上分为三类向上计数、向下计数、中央对齐。分别是什么意思呢?向上计数就是从零开始每次加一计数计到某个特定的值下一个数从零开始重新记往复,向下计数就是从某个特定的值递减每次减一减到0时下一个数是这个特定值往复,中央对其是前面的结合从零开始计,计到特定值再减到零往复。我们一般选择向上计数或者向下计数(下图标红)。

        TIM_Period 通常我们把这个参数称之为ARR重装值,计数周期,也就是前面在TIM_CounterMode介绍里的特定值,我们要计到哪个数重新开始呢?就是这个结构体成员来配置的。取值范围如图所示0X0000~0XFFFF,我们配置的时候直接写10进制数即可,也就是0~65535,调整公式:想要的重装值-1 

        TIM_RepetitionCounter 重复计数器,这个成员的作用是再把每次计数计到重装值(向上计数模式)产生一个计数,也就是再预分频一次,通常试图用来计数器之间的级联,且只有高级定时器(TIM1和TIM8)才会生效。本示例用的是通用定时器,所以没啥用。

        下图是整个芯片的时钟树(相当于整个单片机的心脏分配,没有他就不工作了),只需要看红框内的内容即可,我们看到TIM1是在性能更好的APB2总线上,其余的定时器TIM2~7都是在APB1总线上,所以在开启定时器时钟是大家要看好所用的定时器在那个总线上。

        时钟树(红框为此节定时器标识)

        举个栗子来解释一下TIMEBASE时基单元是怎么用的什么意思。假如我们要用TIM2通用定时器,根据上图红框内提示,我们没有把APB1预分频所以获得的时钟源信号是72MHZ,想要频率为10000HZ的信号就需要把前面提到的TIM的PSC设置成7200-1(为什么请看上面),我们就获得了72MHZ/7200HZ=10000HZ的信号,假如我们设置每一秒进一次中断,就需要把ARR重装值设置成10000-1,意思就是以10000HZ的信号计10000次产生一次中断,所以我们就产生了1秒的中断。

        在下图中,红色箭头指到两个图中的箭头,分别是什么意思呢?UI向上的箭头表示的是更新事件(通常用于外设计之间的互联,比如是时间到了我要去干嘛), u向下的箭头表示的是更新中断,时间到了处理中断。

        这就需要告诉单片机我们是要哪一种呢?怎么做?很简单调用一下TIM_ITConfig()这个函数填个参数就可以了。第一个参数是选择哪个定时器?第二个参数是哪种模式(更新中断,更新事件等等)?第三个参数使能(必须ENABLE)。

        Timer.c

#include "stm32f10x.h"                  // Device header

/**
  * 函    数:定时中断初始化
  * 参    数:无
  * 返 回 值:无
  */
void Timer_Init(void)
{
	/*开启时钟*/
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);			//开启TIM2的时钟
	
	/*配置时钟源*/
	TIM_InternalClockConfig(TIM2);		//选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
	
	/*时基单元初始化*/
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;				//定义结构体变量
	TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;		//时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
	TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	//计数器模式,选择向上计数
	TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;				//计数周期,即ARR的值
	TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;				//预分频器,即PSC的值
	TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;			//重复计数器,高级定时器才会用到
	TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);				//将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元	
	
	/*中断输出配置*/
	TIM_ClearFlag(TIM2, TIM_FLAG_Update);						//清除定时器更新标志位
																//TIM_TimeBaseInit函数末尾,手动产生了更新事件
																//若不清除此标志位,则开启中断后,会立刻进入一次中断
																//如果不介意此问题,则不清除此标志位也可
	
	TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);					//开启TIM2的更新中断
	
	/*NVIC中断分组*/
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				//配置NVIC为分组2
																//即抢占优先级范围:0~3,响应优先级范围:0~3
																//此分组配置在整个工程中仅需调用一次
																//若有多个中断,可以把此代码放在main函数内,while循环之前
																//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
	
	/*NVIC配置*/
	NVIC_InitTypeDef NVIC_InitStructure;						//定义结构体变量
	NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;				//选择配置NVIC的TIM2线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;	//指定NVIC线路的抢占优先级为2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;			//指定NVIC线路的响应优先级为1
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
	
	/*TIM使能*/
	TIM_Cmd(TIM2, ENABLE);			//使能TIM2,定时器开始运行
}

/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
	{
		
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}
}
*/

         Timer.h

#ifndef __TIMER_H
#define __TIMER_H

void Timer_Init(void);

#endif

        main.c 

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;			//定义在定时器中断里自增的变量

int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	Timer_Init();		//定时中断初始化
	
	/*显示静态字符串*/
	OLED_ShowString(1, 1, "Num:");			//1行1列显示字符串Num:
	
	while (1)
	{
		OLED_ShowNum(1, 5, Num, 5);			//不断刷新显示Num变量
	}
}

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Num ++;												//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

ADC(模数转换)

        基于ADC多通道实验对ADC的理解

        ADC其实就是模数转换,如何将一个

         

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值