STM32学习笔记4-OLED&外部中断和中断系统

发布于:2025-08-08 ⋅ 阅读:(16) ⋅ 点赞:(0)

OLED

OLED(Organic Light Emitting Diode):有机发光二极管

OLED显示屏:性能优异的新型显示屏,具有功耗低、相应速度快、宽视角、轻薄柔韧等特点

0.96寸OLED模块:小巧玲珑、占用接口少、简单易用,是电子设计中非常常见的显示屏模块

供电:3~5.5V,通信协议:I2C/SPI,分辨率:128*64

常用于调试。           

通常是I2C协议                    SPI协议

调式方法

串口调试:通过串口通信,将调试信息发送到电脑端,电脑使用串口助手显示调试信息

        可以借助电脑来调试,可以更丰富的参数设置,只能以信息流的方式呈现(也就是一行一行打印),所以只能刷屏进行显示;

显示屏调试:直接将显示屏连接到单片机,将调试信息打印在显示屏上

        对于不断变化的数据,可覆盖刷新显示,显示方式直接,但是显示屏幕有限

Keil调试模式:借助Keil软件的调试模式,可使用单步运行、设置断点、查看寄存器及变量等功能

电灯调试法:在不确定的地方进行加入电灯的代码,来测试是否有电流

注释调式法

硬件电路:

通信引脚需要放到单片机I2C通信的引脚上,在STM32上用江协科技的GPIO模拟库出来的通信引脚,所以可以任意接入。

OLED驱动参数

将屏幕分划成4X16的小模块

相关函数

接线图

4-1 OLED显示屏

main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
//#include "OLED_Font.h"
//4-1 OLED显示屏
int main(void){
	OLED_Init();
	OLED_ShowChar(1,1,'a');
	OLED_ShowString(1,3,"hello world!"); //如果超出16个字,会从头到尾重新输入
	OLED_ShowNum(2,1,12345,5);//如果长度比输入的数字大,则会在输入的数字前自动补0;如果比其小,则会向最高位进行切割
  OLED_ShowSignedNum(2,7,12345,6);
	OLED_ShowHexNum(3,1,0xAA55,4);
	OLED_ShowBinNum(4,1,0xAA55,16);
	
	//OLED_Clear();
	
	while(1){

	}
}

调试程序:

外部中断和中断系统

      简介 

       中断系统:管理和执行中断的逻辑结构

        外部中断:是众多能产生中断的外设之一

        中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行

        中断触发条件:对于外部中断来说,可以是引脚发生了电平跳变

                                 对于定时器来说,可以是定时的时间到了

                                 对于串口通信来说,可以是接受到了数据

若不做中断处理,当下一个电平跳变等来临时,没及时处理原数据被新数据覆盖或被忽略

        中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。由自己设定优先级。

        中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。

        中断执行流程

        68个可屏蔽中断通道(中断源),包含EXTI(外部中断)、TIM(定时器)、ADC(模数转换器)、USART(串口)、SPI(通信)、I2C(通信)、RTC(实时时钟)等多个外设

        使用NVIC(STM32中用来管理中断、分配优先级)统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。

STM32的中断资源

内核的中断

外设中断资源:

0:WWDG 窗口看门狗,用来检测程序运行状态的中断;如果没有及时喂狗,则看门狗会自动申请中断

补:看门狗之后会接触,现在只是方便理解

可以把 窗口看门狗(WWDG) 想象成一个 严格的闹钟,而 “喂狗” 就是按时按点去关掉它。如果不按它的规矩来,它就会 “发火”,直接重启你的系统!

1:PVD电源电压检测,如果供电电压不足,PVD电路就会申请中断

6~10 & 23 & 40:EXTI0~10为本节课的对应中断

地址部分:

       中断的地址:在程序中的中断函数,它的地址是由编译器来分配的,是不固定的;但是中断跳转,由于硬件的限制,只能跳到固定的地址执行程序。为了能让硬件跳转到一个不固定的中断函数里,就需要在内存中定义一个地址的列表(中断向量表,相当于中断跳转的一个跳板),这个列表地址是固定的,中断发生后,就跳到这个固定位置,然后在这个固定位置,由编译器,再加上一条跳转到中断函数的代码,这样可以中断跳转跳转到任意位置了。

一般C语言的编译器会自动完成这个功能,所以一般不会考虑。

NVIC

NVIC(嵌套中断向量控制器)基本结构:用来统一分配中断优先级和管理中断的;

是一个内核外设,是CPU的小助手;

参考:Cortex-M3编程手册

:一个外设可能会同时占用多个中断通道,所以这里有n条线

NVIC只有一个输出口,NVIC根据每个中断的优先级分配中断的先后顺序,告诉CPU应该处理哪个中断。所以关于中断先后顺序分配的任务,cpu是不知道的

中断分组:

抢占优先级:插队+立马执行的优先级:当CPU中正在执行一个中断时,拥有抢占优先级的中断会直接占进来,让CPU停下对原先的中断执行,来执行此中断(中断嵌套);

响应优先级:插队的优先级;

NVIC的中断优先级由优先级寄存器的4位(0~15的数:值越小,优先级越高)决定,这4位可以进行切分,分为:1.高n位的抢占优先级和2.低4-n位的响应优先级

抢占优先级高的可以中断嵌套,响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队(越小,优先级越高)

EXTI(Extern Interrupt)外部中断

        EXTI可以监测指定GPIO口的电平信号,当其指定的GPIO口产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序(引脚电平变化,申请中断),属于内核外设。

        支持的触发方式:上升沿(低电平变到高电平时瞬间触发中断)/下降沿(反之)/双边沿(上升沿和下降沿都可以触发中断)/软件触发(引脚啥事没有,程序执行一句代码,就触发中断)

        支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(如:PA0和PB0)

        通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒(后四个属于蹭网:因为外部中断有个功能:从低功耗模式的停止模式下唤醒STM32;

        对于PVD电源电压检测,当电源从电压过低恢复,就需要PVD外部中断退出停止模式;

        对于RTC闹钟,为了省电,RTC定一个闹钟之后,STM32会进入停止模式,等到闹钟响的时候再唤醒;)

        触发响应方式:中断响应/事件响应(不会触发中断,而会触发其他外设事件)

外部中断的基本结构体

        AFIO:数据选择器,会在前面的n个GPIO外设的16个引脚里选择其中一个连接到后面EXTI的通道里,所以相同的Pin不能同时触发

经过EXTI电路之后,分为两种输出:

        1.指向NVIC:用来触发中断

其中EXTI9_5和EXTI15_10:指外部中断的9~5会触发同一个中断函数,同理;所以在中断函数里,需要再根据标志位来区分到底哪个中断进来的

        2.事件响应

AFIO复用IO口:

AFIO主要用于引脚复用功能的选择和重定义

在STM32中,AFIO主要完成两个任务:复用功能引脚重映射

和中断引脚选择

EXTI框架图:

或门:可以有多个输入,但只能有一个输出,执行或的逻辑,只要有一个高电平(1),输出就是高电平

与门:可以有多个输入,但只能有一个输出,执行与的逻辑,只要有一个低电平(0),输出就是低电平

非门:只有一个输入和一个输入,输入0就是1

数据选择器:侧边有选择控制端,根据控制端的数据,从输入选择一个接到输出

硬件模块:

使用中断模块的特性:对于STM32,想要获取的信号是外部驱动的很快的突发信号,STM32稍微晚一点,就会错过,所以需要中断模块:当有脉冲过来,STM32立马进入中断函数处理。

旋转编码器:用来测量位置、速度或旋转方向的装置,当其旋转轴旋转时,其输出端可以输出与旋转速度和方向对应的方波信号,读取方波信号的频率和相位信息即可得知旋转轴的速度和方向。

类型:机械触点式/霍尔传感器式/光栅式

光栅式旋转编码器工作:

对射式红外传感器用来测速,同时还需要用一个光栅编码盘:当编码盘转动时,红外传感器的红外光就会出现遮挡、透过这样的现象,对应模块输出的电平就是高低电平交替的方波

这个方波的个数表示转过的角度,方波的频率表示转速

通过外部中断来捕获这个方波的边沿,以此判断位置和速度,由于此设备正转反转输出波形无法区分,所以只有一路输出,只能用来测量位置和速度,不能测量旋转方向

机械触点式旋转编码器,

一般用于调节

这种波形就叫为正交波形,可以用来测方向

另一种测量方向的方式是一个触电来测量速度,另一个触电来测量方向

霍尔传感器式旋转编码器

用于机电

旋转编码器的硬件电路

接线图

5-1 对射式红外传感器计次

AO为什么不用:

AO口输出的是模拟信号,一般不用

GPIO中有关AFIO的函数:

void GPIO_AFIODeInit(void); //用来复位AFIO外设的

void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //锁定AFIO的配置,防止意外更改

//两个函数用来配置AFIO的事件输出功能的

void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);

void GPIO_EventOutputCmd(FunctionalState NewState);

void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState); //用来引脚重映射,第一个参数选择重映射的方式,第二个参数是表示是否启用或禁用指定的重映射功能

void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource); //配置AFIO的数据选择器,来选择我们想要的中断引脚

void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface);  //与以太网有关

EXTI的库函数:

void EXTI_DeInit(void); //复位,默认上电

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);  //初始化

void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct); //把结构体变量赋值

void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line); //用来软件触发外部中断的,EXTI_Line选择中断线,就能软件触发一次外部中断

//与标志位有关的四个函数

//主程序中查看和清除

FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);  //获取指定的标志位是否被置1了

void EXTI_ClearFlag(uint32_t EXTI_Line);  //可以对置1的标志位进行清除

//中断函数中查看和清除,并做出判断

ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);  //获取中断标志位是否被置1了

void EXTI_ClearITPendingBit(uint32_t EXTI_Line);  //清除中断挂起标志位

NVIC的库函数:

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);  //用来中断分组,参数是中断分组方式

void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);  //初始化

void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);  //设置中断向量表

void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);  //系统低功耗配置

void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);  

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

//对射式红外传感器计次
int main(void){
	OLED_Init();
	CountSensor_Init();
	OLED_ShowString(1,3,"Hello world!");
	OLED_ShowString(2,1,"Count");
	while(1){
		OLED_ShowNum(2,7,Get_Count(),5);
	}
}

CountSensor.c

#include "stm32f10x.h"        // Device header
uint16_t Count;
void CountSensor_Init(void){
	//EXTI基本结构:GPIOA——AFIO——EXTI——NVIC
	//1.配置RCC
	//2.配置GPIO,选择端口为输入模式
	//3.配置AFIO,选择我们用的GPIO,连接后面的EXTI
	//4.配置EXTI,选择边沿触发方式,比如上升沿、下降沿或双边沿
	//5.配置NVIC,给我们中断选择一个合适的优先级
	//最后,通过NVIC,外部中断进入CPU
	//1
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//注意APB2与APB1的总线
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	//不需要配置EXTI,NVIC(内核的外设均不需要)的时钟
	
	//2
	GPIO_InitTypeDef GPIO_InitStructure;
	//具体要什么模式,可以去参考手册中的GPIO的8.1.11 外设GPIO配置
	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);
	
	//3配置AFIO的输入断引脚选择,输出端固定到EXTI电路
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource14);
	
	//4 配置EXTI
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line=EXTI_Line14; //配置EXTI的第14个线路为中断模式
	EXTI_InitStructure.EXTI_LineCmd=ENABLE;  //开启中断,使PB14的电平信号通过EXTI通向下一级NVIC
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
	//EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising_Falling;  //双边沿触发
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;
	EXTI_Init(&EXTI_InitStructure);
	//5 配置NVIC,由于NVIC为内核外设,所以放到misc.h
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=EXTI15_10_IRQn; //配置通道
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; //开启触发
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;  //配置抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;  //配置响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
}
//在中断向量表中对应的函数内编写处理逻辑。
//清除中断标志位(防止重复触发)。
//要配置中断程序,中断程序在STM32中的固定的,需要去启动Start文件夹里的启动文件里找
//中断函数不需要声明,它自动会执行
void EXTI15_10_IRQHandler(void){
	//要先这个中断点是不是我们想要的EXTI14
	if(EXTI_GetFlagStatus(EXTI_Line14)==SET){//查看EXTI_Line14是不是为1,返回值是SET or RESET
		//执行中断程序
		Count++;
		//在中断程序结束后,要再调用一下清除中断标志位的函数,不然程序会不断响应中断,执行函数,最后程序卡死在中断函数
		EXTI_ClearITPendingBit(EXTI_Line14);
	}
}
//返回Count
uint16_t Get_Count(void){
	return Count;
}

5-2 旋转编码器计次

main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Encoder.h"

//对射式红外传感器计次
int16_t Num;
int main(void){
	OLED_Init();
	Encoder_Init();
	OLED_ShowString(1,3,"Hello world!");
	OLED_ShowString(2,1,"Num:");
	while(1){
		Num+=Get_Count();
		OLED_ShowSignedNum(2,5,Num,5);
	}
}

Encoder.c
#include "stm32f10x.h"                  // Device header

int16_t Encoder_Count;					//全局变量,用于计数旋转编码器的增量值

/**
  * 函    数:旋转编码器初始化
  * 参    数:无
  * 返 回 值:无
  */
void Encoder_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_0 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);						//将PB0和PB1引脚初始化为上拉输入
	
	/*AFIO选择中断引脚*/
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);//将外部中断的0号线映射到GPIOB,即选择PB0为外部中断引脚
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//将外部中断的1号线映射到GPIOB,即选择PB1为外部中断引脚
	
	/*EXTI初始化*/
	EXTI_InitTypeDef EXTI_InitStructure;						//定义结构体变量
	EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1;		//选择配置外部中断的0号线和1号线
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;					//指定外部中断线使能
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;			//指定外部中断线为中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;		//指定外部中断线为下降沿触发
	//EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising_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 = EXTI0_IRQn;			//选择配置NVIC的EXTI0线
	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外设

	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;			//选择配置NVIC的EXTI1线
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;				//指定NVIC线路使能
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;	//指定NVIC线路的抢占优先级为1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;			//指定NVIC线路的响应优先级为2
	NVIC_Init(&NVIC_InitStructure);								//将结构体变量交给NVIC_Init,配置NVIC外设
}

/**
  * 函    数:旋转编码器获取增量值
  * 参    数:无
  * 返 回 值:自上此调用此函数后,旋转编码器的增量值
  */
int16_t Encoder_Get(void)
{
	/*使用Temp变量作为中继,目的是返回Encoder_Count后将其清零*/
	/*在这里,也可以直接返回Encoder_Count
	  但这样就不是获取增量值的操作方法了
	  也可以实现功能,只是思路不一样*/
	int16_t Temp;
	Temp = Encoder_Count;
	Encoder_Count = 0;
	return Temp;
}

/**
  * 函    数:EXTI0外部中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void EXTI0_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line0) == SET)		//判断是否是外部中断0号线触发的中断
	{
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)		//PB0的下降沿触发中断,此时检测另一相PB1的电平,目的是判断旋转方向
			{
				Encoder_Count --;					//此方向定义为反转,计数变量自减
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line0);			//清除外部中断0号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
}

/**
  * 函    数:EXTI1外部中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void EXTI1_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line1) == SET)		//判断是否是外部中断1号线触发的中断
	{
		/*如果出现数据乱跳的现象,可再次判断引脚电平,以避免抖动*/
		if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
		{
			if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)		//PB1的下降沿触发中断,此时检测另一相PB0的电平,目的是判断旋转方向
			{
				Encoder_Count ++;					//此方向定义为正转,计数变量自增
			}
		}
		EXTI_ClearITPendingBit(EXTI_Line1);			//清除外部中断1号线的中断标志位
													//中断标志位必须清除
													//否则中断将连续不断地触发,导致主程序卡死
	}
//解释:
//正转:A相下降沿时,B相为低 → Encoder_Count++
//反转:B相下降沿时,A相为低 → Encoder_Count--
//因为A与B是正交波形:
//正转: A相 _|‾|__|‾|__  
//       B相 ‾|__|‾|__|‾  (A下降沿时B为低)  
//反转: A相 _|‾|__|‾|__  
//      B相 __|‾|__|‾|__ (B下降沿时A为低)  
//
}

建议

中断函数里不要耗时过长的代码;

中断函数与主函数不要调用同一个硬件;


网站公告

今日签到

点亮在社区的每一天
去签到