STM32理论 —— 存储、中断

文章目录

  • [1. 存储](#1. 存储)
    • [1.1 EEPROM芯片 - AT24Cxx](#1.1 EEPROM芯片 - AT24Cxx)
      • [1.1.1 写操作](#1.1.1 写操作)
      • [1.1.2 读操作](#1.1.2 读操作)
    • [1.2 代码应用例程](#1.2 代码应用例程)
    • [1.3 延长EEPROM寿命](#1.3 延长EEPROM寿命)
    • [1.4 关于存储器读写地址](#1.4 关于存储器读写地址)
  • [2. 中断](#2. 中断)
    • [2.1 STM32 的中断](#2.1 STM32 的中断)
    • [2.1 嵌套向量中断控制器 NVIC](#2.1 嵌套向量中断控制器 NVIC)
      • [2.1.1 中断优先级分组(中断管理方法)](#2.1.1 中断优先级分组(中断管理方法))
      • [2.1.2 NVIC 参数结构体](#2.1.2 NVIC 参数结构体)
    • [2.2 外部中断/事件控制器 EXTI](#2.2 外部中断/事件控制器 EXTI)
    • [2.3 中断服务函数](#2.3 中断服务函数)
    • [2.4 STM32 中断优先级寄存器配置及其参考代码](#2.4 STM32 中断优先级寄存器配置及其参考代码)
    • [2.5 其他:](#2.5 其他:)

1. 存储


1.1 EEPROM芯片 - AT24Cxx

AT24Cxx常用的IIC通讯的EEPROM 器件;

数据存储根据地址高位在前低位在后的原则;

如使用芯片为AT24C02,最低地址位为0,最高地址位为255,地址为255的数据为0x77,地址为254的数据为0x86,则从地址255 连续读取两个字节时,读到的数据为0x7786;

❗❗❗注意在对芯片进行写入时,为避免数据错位覆盖,应提前规划好写入地址与写入的数据字节长度;

1.1.1 写操作

字节写操作
在该模式下,主器件发送IIC起始信号(通知从器件开始工作)和从器件地址信息(选择与哪个从器件进行通信)给从器件,从器件回应主器件以应答信号后,主器件发送CAT24WC01/02/04/08/16的字节地址(EEPROM内存储单元的地址),从器件回应主器件以应答信号后,主器件发送要写入的数据到被寻址的存储单元,CAT24WC01/02/04/08/16回应主器件以应答信号后,主器件发送IIC停止信号(通知从器件停止工作)给从器件。


页写操作
页写操作同理于字节写操作,只是写入一个字节后不产生停止信号,主器件被允许发送P个额外的字节(CAT24WC01:P=7,CAT24WC02/04/08/16:P=15,即使用写页操作下,CAT24WC0一次可写8个字节,CAT24WC02/04/08/16一次可写16个字节)。

注意,若在发送停止信号前主器件发送的字节超过P+1个,地址计数器将自动翻转,先前写入的数据将被覆盖。

1.1.2 读操作

字节读操作
在该模式下,主器件发送IIC起始信号(通知从器件开始工作)和从器件地址信息(选择与哪个从器件进行通信)给从器件,从器件回应主器件以应答信号后,主器件发送CAT24WC01/02/04/08/16的字节地址(EEPROM内存储单元的地址),CAT24WC01/02/04/08/16回应主器件以应答信号后,从器件向主器件返回所要求的一个字节数据,此后主器件不发送应答,但产生一个停止信号。


页读操作

页读操作就是在字节读操作读完一个字节后,主器件不发送停止信号给从器件,而是发送一个应答信号表示要求进一步读取下一个字节信号,直到主器件向从器件发送停止信号。

注意,若主器件读取的字节超过E个,地址计数器将自动翻转,计数器将翻转到零并继续输出字节数据。(24WC01,E=127;24WC02,E=255;24WC04,E=511;24WC08,E=1023;24WC16,E=2047;)

1.2 代码应用例程

代码备份位置:百度网盘 - 知识资源 - 电子工程 - 单片机 - AT24Cxx 源代码.rar

  1. AT24CXX_WriteOneByte();AT24CXX_ReadOneByte();函数的应用例程,在AT24CXX 指定地址读写1个字节数据(最大值为0xFF)
c 复制代码
		u8 write_data = 255;// 要写入的数据 
		u16 start_address = 0x0000; // 起始地址  
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		u8 read_data = 0x00;
		
		delay_ms(1000);
		AT24CXX_WriteOneByte(start_address,write_data,i2c_Channel);
		delay_ms(1000);
		read_data = AT24CXX_ReadOneByte(start_address,i2c_Channel);
		delay_ms(1000);
    // 验证读取的数据是否与写入的数据一致  
    if (read_data == write_data) {  
        printf("Data read and written are the same.\n");  
    } else {  
        printf("Data read and written are different!\n");  
    } 
		Uart4_Printf("address 0x%04X: 0x%02X\n", start_address, read_data);
  1. AT24CXX_WriteLenByte();AT24CXX_ReadLenByte();函数的应用例程,在AT24CXX 指定地址读写长度为Len 个字节的数据(最大值为0xFFFF FFFF)
c 复制代码
		u32 write_data = 0xFFFFFFFF;// 要写入的数据 
		u16 start_address = 0x0000; // 起始地址  
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		u32 read_data = 0x00;
		
		delay_ms(1000);
		AT24CXX_WriteLenByte(start_address,write_data,4,i2c_Channel);
		delay_ms(1000);
		read_data = AT24CXX_ReadLenByte(start_address,4,i2c_Channel);
		delay_ms(1000);
    // 验证读取的数据是否与写入的数据一致  
    // 验证数据  
    if (read_data == write_data) 
		{  
        Uart4_Printf("Data written and read are same.\n");  
    } 
		else 
		{  
        Uart4_Printf("Data written and read are different.\n");  
    } 
		Uart4_Printf("Written: 0x%08X, Read: 0x%08X\n", write_data, read_data);
  1. AT24CXX_Write();AT24CXX_Read();函数的应用例程,功能与上面AT24CXX_WriteLenByte() 相同,只是传入参数改为指针类型,多用于数组
c 复制代码
		int i = 0;
		u8 data_to_write[] = {0x01, 0x02, 0x03, 0x04}; // 要写入的数据  
		u16 start_address = 0x0000; // 起始地址
		u8 read_buffer[sizeof(data_to_write)]; // 读取数据的缓冲区
		u8 i2cChannel = 0x01; // IIC通道或设备地址 
		
		AT24CXX_Write(start_address, data_to_write,sizeof(data_to_write),i2cChannel);	
		delay_ms(1000);
		delay_ms(1000);
		AT24CXX_Read(start_address,read_buffer,sizeof(read_buffer),i2cChannel);
		delay_ms(1000);
		delay_ms(1000);
		
    // 验证读取的数据是否与写入的数据一致  
    if (memcmp(data_to_write, read_buffer, sizeof(data_to_write)) == 0) 
    {  
        Uart4_Printf("Data read and written are the same.\n");  
    } else {  
        Uart4_Printf("Data read and written are different!\n");  
    }  
  
    // 输出读取的数据 
    Uart4_Printf("Read data: ");  
    for (i = 0; i < sizeof(read_buffer); i++) 
		{  
        Uart4_Printf("0x%02X ", read_buffer[i]);  
    }  
    Uart4_Printf("\n");
	delay_ms(1000);
	delay_ms(1000);
  1. AT24CXX_WriteFloat();AT24CXX_ReadFloat();函数的应用例程,在AT24CXX 指定地址开始读写float 型数据
c 复制代码
		float dataToWrite = 3.14159f; // 要写入的数据 
		u16 start_address = 0x0000; // 起始地址  
    u8 bufferWrite[4];  
    u8 bufferRead[4];
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		float dataRead;
    // 将float转换为字节数组  
    memcpy(bufferWrite, &dataToWrite, 4);
		
		delay_ms(1000);
		AT24CXX_WriteFloat(start_address, bufferWrite, i2c_Channel);  
		delay_ms(1000);
		AT24CXX_ReadFloat(start_address, bufferRead,i2c_Channel); 
		delay_ms(1000);
    // 将字节数组转换回float并验证  
    memcpy(&dataRead, bufferRead, 4); 
    // 验证数据是否一致  
    if(dataRead == dataToWrite)  
    {  
        Uart4_Printf("The read float is the same as the written float.\n");  
    }  
    else  
    {  
        Uart4_Printf("The read float is NOT the same as the written float.\n");   
    }
		Uart4_Printf("Original: %f, Read: %f\n", dataToWrite, dataRead); 
  1. AT24CXX_Write_double();AT24CXX_Read_double();函数的应用例程,在AT24CXX 指定地址开始读写double 型数据
c 复制代码
		double dataToWrite = 3.14159265358979323846; // 要写入的数据 
		u16 start_address = 0x0000; // 起始地址  
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		double dataRead = 0.0;
		
		delay_ms(1000);
		AT24CXX_Write_double(start_address, dataToWrite, i2c_Channel);  
		delay_ms(1000);
		dataRead = AT24CXX_Read_double(start_address, i2c_Channel); 
		delay_ms(1000);
    // 比较两个double值是否相等(由于浮点数的精度问题,不能直接比较)  
    if (dataRead == dataToWrite) {  
        Uart4_Printf("Data written and read are same.\n");  
    } else {  
        Uart4_Printf("Data written and read are different.\n");  
    }
		Uart4_Printf("Written: %f, Read: %f\n", dataToWrite, dataRead);
  1. 读取EEPROM 内所有数据
c 复制代码
		// 读取EEPROM 内所有数据
		u8 i=0;
		u16 addr = 0; // 地址从0开始
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		u8 dataRead;
		
		for(i=0;i<=EE_TYPE;i++) // EE_TYPE 为当前硬件AT24Cxx 的容量
		{
			Uart4_Printf("\r\n");
			delay_ms(10);
			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
			Uart4_Printf("addr%d - %d",i,dataRead);
		}
  1. 排查EEPROM 的读写功能
c 复制代码
		u16 i=0;
		u16 addr = 0;
		u8 i2c_Channel = 0x01; // IIC通道或设备地址 
		u8 dataRead;
		
		// 全写入1
		for(i=0;i<=EE_TYPE;i++) // EE_TYPE 为当前硬件AT24Cxx 的容量
		{
			AT24CXX_WriteOneByte(addr+i,0xff,i2c_Channel);
			if(i==EE_TYPE){break;}
		}
//		// 全读取在串口查看
//		for(i=0;i<=EE_TYPE;i++)
//		{
//			Uart4_Printf("\r\n");
//			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
//			Uart4_Printf("addr%d - %d",i,dataRead);
//			if(i==EE_TYPE){break;}
//		}
		// 自动对比,有错误就报错
		for(i=0;i<=EE_TYPE;i++)
		{
			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
			if (dataRead != 0xff){Uart4_Printf("Error:addr%d - %d",i,dataRead);}
			if(i==EE_TYPE){break;}
		}
		Uart4_Printf("\r\n---------------------------------Write complete---------------------------------------------");
		// 全写入0
		for(i=0;i<=EE_TYPE;i++)
		{
			AT24CXX_WriteOneByte(addr+i,0x00,i2c_Channel);
			if(i==EE_TYPE){break;}
		}
//		// 全读取在串口查看
//		for(i=0;i<=EE_TYPE;i++)
//		{
//			Uart4_Printf("\r\n");
//			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
//			Uart4_Printf("addr%d - %d",i,dataRead);
//			if(i==EE_TYPE){break;}
//		}
		// 自动对比,有错误就报错
		for(i=0;i<=EE_TYPE;i++)
		{
			dataRead = AT24CXX_ReadOneByte(addr+i,i2c_Channel);
			if (dataRead != 0x00){Uart4_Printf("Error:addr%d - %d",i,dataRead);}
			if(i==EE_TYPE){break;}
		}
		Uart4_Printf("\r\n----------------------------Read complete--------------------------------------------------");

1.3 延长EEPROM寿命

对于Flash或EEPROM,读操作对其寿命影响不大,写操作(对Nand Flash则为块擦除)次数基本决定了存储器寿命;而且写入寿命在每个位之间是独立的。延长EEPROM寿命有以下几种方法:

  1. 不固定数据存放的地址,而是用一个固定的基地址加上EEPROM内的一个单元的内容(即偏移地址)作为真正的地址;若发现存储单元已坏(写入和读出的内容不同),则偏移地址加1,重新写入。此方法有一弊端:当某一个EEPROM单元被写坏再用下一个单元时,后面到所有数据都会被覆盖。
  2. 从第一个存储单元开始存储数据N次,然后转到下一个单元再存N次,依次类推,当最后一个单元存放N次之后,再转到第一个单元重新开始。即不重复读写(擦写)某几个存储单元,尽量用到EEPROM上的所有存储单元,防止某几个存储单元反复擦写导致损坏。
  3. 对于一些需要上电初始化的数据,在每次写入EEPROM时,附带写入一个标志位。AT24CXX_WriteOneByte(DATA_FLAG_Addr,0x0A);,上电初始化EEPROM时,读取该位并进行比较,查看EEPROM是否被写入过;
c 复制代码
temp = AT24CXX_ReadOneByte(DATA_FLAG_Addr);
if(temp==0x0A)
{
	// 读取EEPROM中的数据到对应变量中
}
else
{
	// 第一次上电,向EEPROM 写入一些数据的初始值
	...
	AT24CXX_WriteOneByte(DATA_FLAG_Addr,0x0A); // 写入标志位,表示该系统已写入过一些初始化数据
}

1.4 关于存储器读写地址

存储器写入首先不能超过存储器的最高数据位,如AT24C02是2k(256*8)即2k bit(256个字节)的存储芯片,其最高字节地址为256,最后一个位的地址为2048. 其次是每个地址要定义好用于存储多少位的数据,千万不能产生数据存储错乱,比如定义为用于存储一个字节数据的地址,却写入了两个字节数据,那么不仅在下次读该字节地址中的值产生错误,还会导致多出的一个字节数据写入到下一个字节数据地址里去,造成下一个字节数据地址的数据错乱。

c 复制代码
#define xxx0_Addr	0 // xxx0_Addr该地址用于存储一个字节的数据
#define xxx1_Addr	8 // 那么下一个字节数据就要相隔一个字节,xxx1_Addr 该地址用于存储两个字节的数据
#define xxx1_Addr	16 // 那么下一个字节数据就要相隔两个字节,如此类推

参考:AT24C02使用详解

2. 中断


中断是指通过硬件来改变CPU 的运行方向。单片机在执行程序的过程中,外部设备向CPU 发出中断请求信号,要求CPU 暂时中断当前程序的执行而转去执行相应的处理程序,待处理程序执行完毕后,再继续执行原来被中断的程序。这种程序在执行过程中由于外界的原因而被中间打断的情况称为"中断";

  • 主程序:原来正常运行的程序
  • 中断源:引起中断的原因,或者能发出中断请求的来源
  • 中断请求:中断源要求服务的请求称为中断请求(或中断申请)
  • 断点:主程序被断开的位置(或者地址)
  • 中断入口地址:中断服务程序的首地址
  • 中断服务程序:cpu响应中断后,转去执行的相应处理程序

  • 中断的特点
    1. 同步工作:中断是CPU 和接口之间的信息传递方式之一,它使CPU 与外设同步工作,较好地解决了快速CPU 与慢速外设之间的匹配问题;
    2. 异常处理:针对难以预料的异常情况,如掉电、存储出错、运算溢出等,可以通过中断系统由故障源向CPU 发出中断请求,再由CPU 转到相应的故障处理程序进行处理;
    3. 实时处理:CPU 能够及时处理应用系统的随机事件,实时性大大增加;

2.1 STM32 的中断

Cortex-M3(CM3)内核MCU 最多支持256个中断,其中包含了16 个内核中断和240个可屏蔽中断,最多具有 256 级的可编程中断设置,根据不同型号单片机,其支持的中断数量不同 ,具体可查看对应芯片的数据手册中的中断向量表

如::STM32F1 系列芯片只用了CM3内核的部分资源,共有84个中断,包括16个内核中断和68个可屏蔽中断,具有16级可编程的中断优先级;而STM32F103系列又只有60个可屏蔽中断,如下图:

中断优先级数字越小,对应中断优先级越高;


STM32 外部中断功能实现细分如下图:

下面进行逐一讲解:

2.1 嵌套向量中断控制器 NVIC

NVIC(嵌套向量中断控制器) 控制整个芯片中断的相关功能;

2.1.1 中断优先级分组(中断管理方法)

STM32F1系列芯片通过配置应用中断与复位控制寄存器Application interrupt and reset control register AIRCR[10:8] 来对MCU 的中断优先级分组进行配置,中断优先级分组的作用就是决定把IP bit[7:4]这4个位如何分配给抢占优先级和子优先级

  • 配置中断优先级的功能通过函数NVIC_PriorityGroupConfig() 实现,它定义在源文件misc.c中,其函数定义如下:
c 复制代码
/**
  * @brief  Configures the priority grouping: pre-emption priority and subpriority.
  * @param  NVIC_PriorityGroup: specifies the priority grouping bits length. 
  *   This parameter can be one of the following values:
  *     @arg NVIC_PriorityGroup_0: 0 bits for pre-emption priority  级别最高
  *                                4 bits for subpriority
  *     @arg NVIC_PriorityGroup_1: 1 bits for pre-emption priority
  *                                3 bits for subpriority
  *     @arg NVIC_PriorityGroup_2: 2 bits for pre-emption priority
  *                                2 bits for subpriority
  *     @arg NVIC_PriorityGroup_3: 3 bits for pre-emption priority
  *                                1 bits for subpriority
  *     @arg NVIC_PriorityGroup_4: 4 bits for pre-emption priority
  *                                0 bits for subpriority
  * @retval None
  */
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
  SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}



// 如将MCU 的中断优先级分组配置为组0,即IP bit 第4~ 7位为0 位抢占优先级,4位响应优先级:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

中断优先级分组配置应该在MCU 初始化的同时配置好,中途不能对其进行修改,以免造成中断混乱;


  • 抢占优先级和响应优先级的区别
    • 抢占 = 打断别人,高优先级的抢占优先级可以打断正在进行的低抢占优先级中断(值越小说明级越高);
    • 响应 = 抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断;
  • 若需要挂起/解挂中断,查看中断当前激活状态,分别调用相关函数;

应用举例:

  1. 假设MCU 设置中断优先级组为2,然后对以下三个中断进行配置:
  2. 中断4(FLASH中断 )抢占优先级为2,响应优先级为1
  3. 中断5(RCC中断)抢占优先级为3,响应优先级为0
  4. 中断6(EXTIO中断) 抢占优先级为2,响应优先级为0

则,3个中断的优先级顺序为: 中断6>中断4>中断5

2.1.2 NVIC 参数结构体

NVIC的所有需要配置的参数都列举在结构体NVIC_InitTypeDef中,它定义在源文件misc.c中,其结构体如下:

c 复制代码
/** 
  * @brief  NVIC Init Structure definition  
  */

typedef struct
{
  uint8_t NVIC_IRQChannel;                    /* 中断源 !< Specifies the IRQ channel to be enabled or disabled.
                                                   This parameter can be a value of @ref IRQn_Type 
                                                   (For the complete STM32 Devices IRQ Channels list, please
                                                    refer to stm32f10x.h file) */

  uint8_t NVIC_IRQChannelPreemptionPriority;  /* 抢占优先级 !< Specifies the pre-emption priority for the IRQ channel
                                                   specified in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref NVIC_Priority_Table */

  uint8_t NVIC_IRQChannelSubPriority;         /* 子优先级(响应优先级) !< Specifies the subpriority level for the IRQ channel specified
                                                   in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref NVIC_Priority_Table */

  FunctionalState NVIC_IRQChannelCmd;         /* 中断源使能 !< Specifies whether the IRQ channel defined in NVIC_IRQChannel
                                                   will be enabled or disabled. 
                                                   This parameter can be set either to ENABLE or DISABLE */   
} NVIC_InitTypeDef;

2.2 外部中断/事件控制器 EXTI

关于EXTI 的代码都在固件库的 stm32f10x_exti.hstm32f10x_exti.c 文件中;

EXTI(External interrupt/event controller - 外部中断/事件控制器):是 STM32 实现 "外部信号触发硬件响应" 的核心外设,既能触发 CPU 中断(如按键检测、传感器电平变化),也能直接联动其他外设(如触发 ADC 采样、定时器启动),无需 CPU 干预,大幅提升系统效率。

它管理着中断控制器的 20个中断/事件线。每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。 EXTI 可实现对每个中断/事件线进行单独配置,可单独配置为中断或者事件,以及触发事件的属性。

  • EXTI 的功能
  1. 接收外部信号(GPIO 引脚、部分外设内部信号);
  2. 按配置的 "触发条件"(上升沿、下降沿、双边沿)检测信号变化;
  3. 输出两种结果:
    • 中断请求:发送到 NVIC(嵌套向量中断控制器),最终触发 CPU 执行中断服务函数(ISR);
    • 事件请求:直接以事件脉冲方式输出到其他外设(如 ADC、TIM、DMA),触发硬件联动(无 CPU 参与,"零延迟");
  • EXTI功能框图 :其中标黄的 20/ 代表着有20条相同的线路

  • GPIO 与 EXTI_Line 的映射:STM32 的每个 GPIO 都可以作为外部中断的中断输入口,比如STM32F103 的中断控制器支持 19 个外部中断/事件请求。Line 是GPIO 的专用通道,两者实行固定映射规则,即所有 GPIO 端口的 Pin0 都映射到 Line0,Pin1 映射到 Line1,...,Pin15 映射到 Line15;

以STM32F103 的19 个外部中断为例:

  1. 线 0~15:对应外部 GPIO 口的输入中断。
  2. 线 16:连接到 PVD 输出。
  3. 线 17:连接到 RTC 闹钟事件。
  4. 线 18:连接到 USB 唤醒事件。

即stm32f103中外部GPIO 口的输入中断有16条线,但STM32 的GPIO却远远不止16个,其分配方式如下图:

以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0 . 而中断线每次只能连接到 1个 GPIO 口上,这就需要通过配置来决定对应的中断线配置到哪个 GPIO 上;

  • 多GPIO 共享Line 的问题 :同一 Line(如 Line0)可绑定多个 GPIO Pin(如 GPIOA_Pin0、GPIOB_Pin0),但不能同时使能多个 ;若需使用多个 GPIO Pin 触发中断,建议选择不同的 Line(如 GPIOA_Pin0→Line0,GPIOB_Pin1→Line1),或通过 "软件轮询" 在 ISR 中判断是哪个 Pin 触发(需读取 GPIO 电平);

  • STM32 EXTI 中断向量表

    从上图可知,IO 口外部中断在中断向量表中只分配了7个中断向量也就是只能使用7个中断服务函数;从表中可以看出,外部中断线5~ 9分配一个中断向量,共用一个服务函数;外部中断线10~ 15分配一个中断向量,共用一个中断服务函数;

  • EXTI 的典型应用场景
  1. 按键检测(中断方式):传统轮询方式会占用 CPU 资源,用 EXTI 中断可实现 "按键按下时才触发 CPU 处理",大幅降低功耗;

    • 配置:GPIO 上拉输入(避免悬空误触发),EXTI 下降沿触发(按键按下时电平从高变低),ISR 中添加软件消抖,处理按键逻辑(如切换 LED 状态)。
  2. 传感器数据采集(硬件触发):如红外传感器、光敏电阻等,当检测到信号变化时(如红外遮挡),通过 EXTI 触发 ADC 采样,无需 CPU 干预;

    • 配置:EXTI 事件模式(EMR 使能),触发 ADC 外部采样,采样完成后通过 DMA 将数据传输到内存,CPU 仅在 DMA 传输完成后读取数据。
  3. 外设联动(无 CPU 干预):如定时器(TIM)的启动触发:用 EXTI 事件触发 TIM 的计数器启动,实现 "外部信号精准同步"(如电机控制中的位置信号触发 PWM 输出);

    • 配置:EXTI 事件模式,TIM 的触发源选择 "EXTI 事件",当 EXTI 检测到触发信号时,TIM 自动启动计数,无需 CPU 发送启动指令。
  4. 低功耗唤醒(STOP 模式唤醒):STM32 进入 STOP 模式(低功耗模式)后,CPU 停止工作,仅部分外设(如 EXTI)保持运行,可通过 EXTI 中断唤醒 CPU;

    • 配置:选择支持低功耗唤醒的 GPIO Pin(如 PA0),EXTI 配置为上升沿触发,NVIC 使能中断,CPU 进入 STOP 模式后,当 PA0 检测到上升沿,EXTI 触发中断,唤醒 CPU 并恢复运行。

2.3 中断服务函数

中断被成功出发后,代码就会执行中断服务函数中的代码。

每个中断都有其固定的中断服务函数名,只有在这个函数名下编写中断服务函数才是有效的。所有中断服务函数都可在stm32f10x_it.c 的中断向量表中查找。其中EXTI线0到EXTI线4线都是单独的中断函数名、EXTI线5到EXTI线9共用一个中断函数名、EXTI线10线到EXTI线15线共用一个中断函数名。

c 复制代码
// 例程
void EXTI0_IRQHandler(void)
{
	if(EXTI_GetITStatus(KEY1_EXTI_LINE)!=RESET) // 读取中断是否执行
	{
		LED1_TOGGLE;   //LED1的亮灭状态反转
	}
	EXTI_ClearITPendingBit(KEY1_EXTI_LINE); //清除中断标志位
}

在 《STM32中文参考手册 V10》 - 第九章的表55 其它STM32F10xxx产品(小容量、中容量和大容量)的向量表中可查看所有的中断通道;

2.4 STM32 中断优先级寄存器配置及其参考代码

STM32 中断优先级配置一共设计以下7个寄存器:

  1. SCB_AIRCR:32 位寄存器,有效位为第8到10位,用于设置5种中断优先级分组;
  2. IP240个8位寄存器,每个中断使用一个寄存器来确定优先级,每个8位寄存器有效位为第4到7位,用于设置抢占优先级与响应优先级;

如STM32F10x系列一共60个可屏蔽中断,那么它就只使用了IP[59]~IP[0l;

  1. ISER:8个32位寄存器,每个位控制一个中断的使能,写1使能,写0无效;

如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ISER[0]和ISER[1]; ISER[0]的bito~ bit31分别对应中断0~ 31、ISER[1]的bit0~ bit27对应中断32~ 59;

  1. ICER:8个32位寄存器,每个位控制一个中断的失能,写1失能,写0无效;

如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ICER[0]和ICER[1]; ICER[0]的bito~ bit31分别对应中断0~ 31、ICER[1]的bit0~ bit27对应中断32~ 59;

  1. ISPR:8个32位寄存器,每个位控制一个中断的挂起,写1失能,写0无效;

如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ISPR[0]和ISPR[1]; ISPR[0]的bito~ bit31分别对应中断0~ 31、ISPR[1]的bit0~ bit27对应中断32~ 59;

  1. ICPR:8个32位寄存器,每个位控制一个中断的解挂,写1失能,写0无效;

如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的ICPR[0]和ICPR[1]; ICPR[0]的bito~ bit31分别对应中断0~ 31、ICPR[1]的bit0~ bit27对应中断32~ 59;

  1. IABR:8个32位寄存器,只读寄存器,每个位指示一个中断的激活状态,读1表示中断正在执行;

如STM32F10x系列一共60个可屏蔽中断,所以它只使用了其中的IABR[0]和IABR[1]; IABR[0]的bito~ bit31分别对应中断0~ 31、IABR[1]的bit0~ bit27对应中断32~ 59;


  • 参考代码:以配置中断5作为外部中断为例,GPIO 口选择PE9:
  1. 输入配置为浮空输入
  2. 若涉及到端口复用问题,还需要打开相应的端口复用时钟
c 复制代码
void EXTI_Configuration(void) // 配置外部中断/事件
{
	EXTI_InitTypeDef EXTI_InitStructure;  // 定义外部中断参数结构体
	
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource9); // 外部中断线配置,PE9
	
	EXTI_InitStructure.EXTI_Line=EXTI_Line9; // 外部中断线
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 外部中断模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
	EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能外部中断
	EXTI_Init(&EXTI_InitStructure); //初始化外部中断线参数
}


void NVIC_Configuration(void) // 配置NVIC
{
	NVIC_InitTypeDef NVIC_InitStructure;  // 定义中断配置参数结构体
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);          // 设置中断组为0
	NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;       // 设置中断来源(中断通道) ,外部中断线5
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;  // 设置抢占优先级为 0
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=3;         // 设置子优先级为3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           // 使能中断
	NVIC_Init(&NVIC_InitStructure); //初始化NVIC参数
}

void EXTI_init(void) //外部中断初始化
{
	NVIC_Configuration();
	EXTI_Configuration();
}

void EXTI9_5_IRQHandler(void)   // 外部中断5 中断服务函数,记得中断服务函数的函数名是内核规定的,不是自定义的!
{
	if(EXTI_GetITStatus(EXTI_Line9) != RESET) // 注意这里不要写错NVIC的 EXTI9_5_IRQn !!
	sprintfU4("外部中断5已被触发\r\n");
	EXTI_ClearITPendingBit(EXTI_Line9); //清除 LINE9 上的中断标志位
}	

2.5 其他:

  1. 基于CORTEX-M3内核的硬件因素,清除中断标志不会马上生效,需要一段时间,如果你的中断服务程序时间很短,就会出现中断重复进入的异常;这种情况,可以在程序中增加去抖动和延时功能;
相关推荐
报错小能手21 小时前
linux学习笔记(32)网络编程——UDP
单片机·嵌入式硬件
XiangrongZ1 天前
江协科技STM32课程笔记(四)—定时器TIM(输入捕获)
笔记·科技·stm32
xyx-3v1 天前
SPI四种工作模式
stm32·单片机·嵌入式硬件·学习
文火冰糖的硅基工坊1 天前
[嵌入式系统-123]:中高端图形处理器RM Mali-G610 MP4 GPU 是 ARM 公司推出的基于 Valhall 架构 的移动 GPU
arm开发·ai·架构·嵌入式·gpu
BreezeJuvenile1 天前
实验二 呼吸灯功能实验
stm32·单片机·嵌入式系统·流水灯·实验
北京阿尔泰科技厂家1 天前
CPCIe-76F1G控制器:国产化高性能嵌入式解决方案
嵌入式硬件·控制器·工业自动化·数据采集卡·国产化控制器
Truffle7电子1 天前
STM32【H7】理论——通信
stm32·单片机·嵌入式硬件
MAR-Sky1 天前
keil5使用STlink下载程序到stm32后不自动运行的解决办法
stm32·单片机·嵌入式硬件
大聪明-PLUS1 天前
嵌入式Linux简介—第二部分(共3部分)
linux·嵌入式·arm·smarc