深入理解STM32空闲中断(Idle Interrupt)机制

一、空闲中断基本框架

STM32的空闲中断(Idle Interrupt)通常用于在CPU空闲时执行任务,例如在低功耗模式下减少功耗等。当CPU完成当前任务后,会进入空闲状态,此时会触发空闲中断。在空闲中断中,可以执行一些需要在CPU空闲时执行的任务,例如读取传感器数据、更新LCD显示等。

要使用STM32的空闲中断,需要进行以下步骤:

1.配置NVIC优先级:将空闲中断的优先级设置为较低的值。

2.启用空闲中断:在代码中启用空闲中断,并设置空闲中断的处理函数。

以下是使用STM32 HAL库实现空闲中断的简单示例代码:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  //串口接收中断回调函数
}

int main(void)
{
  HAL_Init(); //初始化HAL库

  __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //启用空闲中断

  while (1)
  {
    //主程序逻辑
  }
}

void HAL_UART_IdleCallback(UART_HandleTypeDef *huart)
{
  //空闲中断处理函数
  //更新LCD显示、读取传感器数据等任务
}
 

以上代码中,当串口接收完成时,会触发串口接收中断回调函数
(HAL_UART_RxCpltCallback)
在主程序中,会一直循环执行,直到CPU进入空闲状态触发空闲中断。
空闲中断处理函数 (HAL_UART_IdleCallback),会在空闲中断触发时被调用,执行一些需要在CPU空闲时执行的任务。

二、空闲中断(接收/发送)循环缓冲区

本次的文章主要是记录学习超子老师的代码。
他的B站链接:https://space.bilibili.com/517828171?spm_id_from=333.337.0.0

1、 定义一个数据管理结构体,一个总的功能结构体

#define U1_TX_SIZE 2048
#define U1_RX_SIZE 2048
#define U1_RX_MAX  256

typedef struct{//记录数据的开始位置和结束位置
	uint8_t  *start;
	uint8_t  *end;
}LCB;

typedef struct{
	uint32_t  RxCounter;//记录存入缓冲区的数据量
	uint32_t  TxCounter;
	uint32_t  TxState;//发送状态
	LCB       RxLocation[10];
	LCB       TxLocation[10];
	LCB      *RxInPtr;
	LCB      *RxOutPtr;
	LCB      *RxEndPtr;
	
	LCB      *TxInPtr;
	LCB      *TxOutPtr;
	LCB      *TxEndPtr;
	UART_HandleTypeDef uart;
}UCB;

2、然后对这个缓冲区进行一个初始化的工作,这个函数需要在(MX_USART1_UART_Init)进行调用。并且缓冲区初始化函数已经开启了空闲中断,开启了中断接收。

void U1_PtrInit(void)//发送和接收缓冲区初始化函数
{
	uart1.RxInPtr = &uart1.RxLocation[0];//数据输入指针指向数据开始和结束位置管理数组的首地址
	uart1.RxOutPtr = &uart1.RxLocation[0];
	uart1.RxEndPtr = &uart1.RxLocation[9];//数据输入指针指向数据开始和结束位置管理数组的尾地址
	uart1.RxCounter = 0;//存储到缓冲区的数据量
	uart1.RxInPtr->start = U1_RxBuff;//让第一个数据开始指针指向缓冲区的首地址
	
	uart1.TxInPtr = &uart1.TxLocation[0];
	uart1.TxOutPtr = &uart1.TxLocation[0];
	uart1.TxEndPtr = &uart1.TxLocation[9];
	uart1.TxCounter = 0;
	uart1.TxInPtr->start = U1_TxBuff;	
	
	__HAL_UART_ENABLE_IT(&uart1.uart, UART_IT_IDLE);//开启空闲中断
	HAL_UART_Receive_IT(&uart1.uart,uart1.RxInPtr->start,U1_RX_MAX);//开始接收
}

3、假设,现在已经有数据接收了,那么我们需要实现串口中断函数(注意看代码里的注释)

void USART1_IRQHandler(void)//串口中断函数
{							//当串口中断发生后会进入到此函数,但是我们接收的数据小于RX_MAX,所以不会发生串口接收完成中断.
	HAL_UART_IRQHandler(&uart1.uart);
	//由于程序的规定是接收和发送小于256字节的数据,所以不会发生串口接收完成中断。如果接收到45个字节,那么我理论上是根据接收函数要接收256个,
	//但是接收不到256个字节,所以再接收到45个个字节之后,串口线就处于空闲状态,也就是这个时候,空闲中断就发生了,也就是进入到USART1_IRQHandler函数
	//由于HAL没有处理空闲中断的程序,所以我们需要模仿它处理接收完成中断的程序,实现一个处理空闲中断的程序
	if(__HAL_UART_GET_FLAG(&uart1.uart, UART_FLAG_IDLE)){//判断是否是这个中断发生
		__HAL_UART_CLEAR_IDLEFLAG(&uart1.uart);//清楚中断标志位
		uart1.RxCounter += (U1_RX_MAX - uart1.uart.RxXferCount);//通过uart1.uart.RxXferCount(这个值表示还有多少个字节需要接收)我们用U1_RX_MAX减去就能得到接收的字节数量
		HAL_UART_AbortReceive_IT(&uart1.uart);最后再调用终止接收中断函数
	}
}

4、实现中断回调函数

void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)//终止串口接收中断回调函数
{
	if(huart->Instance == USART1){
		uart1.RxInPtr->end = &U1_RxBuff[uart1.RxCounter - 1];//将数据结束处的地址赋值给uart1.RxInPtr->end
		uart1.RxInPtr++;
		if(uart1.RxInPtr == uart1.RxEndPtr){
			uart1.RxInPtr = &uart1.RxLocation[0];
		}
		if((U1_RX_SIZE - uart1.RxCounter)<U1_RX_MAX){//再判断缓冲区剩下的空间是否能存储下U1_RX_MAX字节,不能则回滚
			uart1.RxCounter = 0;
			uart1.RxInPtr->start = U1_RxBuff;
		}else{//可以放下就让下一个数据输入指针指向下一个可以存储的地址
			uart1.RxInPtr->start = &U1_RxBuff[uart1.RxCounter];
		}
		HAL_UART_Receive_IT(&uart1.uart,uart1.RxInPtr->start,U1_RX_MAX);//然后再次开启中断接收函数
	}
}

5、实现发送函数

void U1_Txdata(uint8_t *data, uint32_t data_len)//串口发送函数
{
	if((U1_TX_SIZE - uart1.TxCounter )>=data_len){//判断发送数据是否可以存放在缓冲区内
		uart1.TxInPtr->start = &U1_TxBuff[uart1.TxCounter];//通过uart1.TxCounter可以获取到缓冲区内的数据有多少,
														   //从而可以将下一个下标的地址给uart1.TxInPtr->star
	}else{//说明缓冲区内的剩余空间已经不能放下我们所要发送的数据,所以要回滚到前面
		uart1.TxCounter = 0;
		uart1.TxInPtr->start = U1_TxBuff;//将缓冲区的地址给uart1.TxInPtr->star
	}
	memcpy(uart1.TxInPtr->start,data,data_len);//把data放在uart1.TxInPtr->start这个地方,长度是data_len
	uart1.TxCounter += data_len;//缓冲区计数加上放入的长度data_len
	uart1.TxInPtr->end = &U1_TxBuff[uart1.TxCounter - 1];//标记此次数据的结束位置,通过uart1.TxCounter可以计算出在缓冲区内的下标位置
	uart1.TxInPtr++;//让数据输入指针加一,指向下一个的TxLocation数组的地址
	if(uart1.TxInPtr == uart1.TxEndPtr){//判断数据输入指针是否到达10个的极限,如果是就会进行回滚,指向TxLocation数组的首地址
		uart1.TxInPtr = &uart1.TxLocation[0];
	}
}

6、实现发送中断回调函数

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) //串口发送中断回调函数
{
    if(huart->Instance == USART1) {
        if(huart->Instance == USART1){
		uart1.TxState = 0;
		}
    }
}

7、在while(1)添加以下代码,这个代码实现的功能是:接收到数据后立马发送回去

if(uart1.RxOutPtr != uart1.RxInPtr){
			U1_Txdata(uart1.RxOutPtr->start,uart1.RxOutPtr->end - uart1.RxOutPtr->start + 1);
			uart1.RxOutPtr++;
			if(uart1.RxOutPtr == uart1.RxEndPtr){
				uart1.RxOutPtr = &uart1.RxLocation[0];
			}
		}
		if((uart1.TxOutPtr != uart1.TxInPtr)&&(uart1.TxState==0)){
			uart1.TxState = 1;
			HAL_UART_Transmit_IT(&uart1.uart,uart1.TxOutPtr->start,uart1.TxOutPtr->end - uart1.TxOutPtr->start + 1);
			uart1.TxOutPtr++;
			if(uart1.TxOutPtr == uart1.TxEndPtr){
				uart1.TxOutPtr = &uart1.TxLocation[0];
			}
		}

8、缓冲区管理结构图


9、下面是所以整理超子老师的代码

/***************在usart.h文件添加以下代码************************/
#ifndef __UART_H
#define __UART_H

#include "string.h"
#include "stdint.h"
#include "stm32f1xx_hal_uart.h"

#define U1_TX_SIZE 2048
#define U1_RX_SIZE 2048
#define U1_RX_MAX  256

typedef struct{//记录数据的开始位置和结束位置
	uint8_t  *start;
	uint8_t  *end;
}LCB;

typedef struct{
	uint32_t  RxCounter;//记录存入缓冲区的数据量
	uint32_t  TxCounter;
	uint32_t  TxState;//发送状态
	LCB       RxLocation[10];
	LCB       TxLocation[10];
	LCB      *RxInPtr;
	LCB      *RxOutPtr;
	LCB      *RxEndPtr;
	
	LCB      *TxInPtr;
	LCB      *TxOutPtr;
	LCB      *TxEndPtr;
	UART_HandleTypeDef uart;
}UCB;

void MX_USART1_UART_Init(uint32_t bandrate);

void U1_PtrInit(void);

void U1_Txdata(uint8_t *data, uint32_t data_len);


extern UCB  uart1;


#end
/***********************************************************/



//在usart.c文件里自行斟酌添加
/***************.C********************************************/
#include "stm32f1xx_hal.h"
#include "uart.h"

UCB  uart1;

uint8_t U1_RxBuff[U1_RX_SIZE];
uint8_t U1_TxBuff[U1_TX_SIZE];


void MX_USART1_UART_Init(void)//串口功能初始化
{
  uart1.uart.Instance = USART1;
  uart1.uart.Init.BaudRate = 115200;
  uart1.uart.Init.WordLength = UART_WORDLENGTH_8B;
  uart1.uart.Init.StopBits = UART_STOPBITS_1;
  uart1.uart.Init.Parity = UART_PARITY_NONE;
  uart1.uart.Init.Mode = UART_MODE_TX_RX;
  uart1.uart.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  uart1.uart.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&uart1.uart) != HAL_OK)
  {
    Error_Handler();
  }
  U1_PtrInit();//发送和接收缓冲区初始化函数
}

void U1_PtrInit(void)//发送和接收缓冲区初始化函数
{
	uart1.RxInPtr = &uart1.RxLocation[0];//数据输入指针指向数据开始和结束位置管理数组的首地址
	uart1.RxOutPtr = &uart1.RxLocation[0];
	uart1.RxEndPtr = &uart1.RxLocation[9];//数据输入指针指向数据开始和结束位置管理数组的尾地址
	uart1.RxCounter = 0;//存储到缓冲区的数据量
	uart1.RxInPtr->start = U1_RxBuff;//让第一个数据开始指针指向缓冲区的首地址
	
	uart1.TxInPtr = &uart1.TxLocation[0];
	uart1.TxOutPtr = &uart1.TxLocation[0];
	uart1.TxEndPtr = &uart1.TxLocation[9];
	uart1.TxCounter = 0;
	uart1.TxInPtr->start = U1_TxBuff;	
	
	__HAL_UART_ENABLE_IT(&uart1.uart, UART_IT_IDLE);//开启空闲中断
	HAL_UART_Receive_IT(&uart1.uart,uart1.RxInPtr->start,U1_RX_MAX);//开始接收
}
void U1_Txdata(uint8_t *data, uint32_t data_len)//串口发送函数
{
	if((U1_TX_SIZE - uart1.TxCounter )>=data_len){//判断发送数据是否可以存放在缓冲区内
		uart1.TxInPtr->start = &U1_TxBuff[uart1.TxCounter];//通过uart1.TxCounter可以获取到缓冲区内的数据有多少,
														   //从而可以将下一个下标的地址给uart1.TxInPtr->star
	}else{//说明缓冲区内的剩余空间已经不能放下我们所要发送的数据,所以要回滚到前面
		uart1.TxCounter = 0;
		uart1.TxInPtr->start = U1_TxBuff;//将缓冲区的地址给uart1.TxInPtr->star
	}
	memcpy(uart1.TxInPtr->start,data,data_len);//把data放在uart1.TxInPtr->start这个地方,长度是data_len
	uart1.TxCounter += data_len;//缓冲区计数加上放入的长度data_len
	uart1.TxInPtr->end = &U1_TxBuff[uart1.TxCounter - 1];//标记此次数据的结束位置,通过uart1.TxCounter可以计算出在缓冲区内的下标位置
	uart1.TxInPtr++;//让数据输入指针加一,指向下一个的TxLocation数组的地址
	if(uart1.TxInPtr == uart1.TxEndPtr){//判断数据输入指针是否到达10个的极限,如果是就会进行回滚,指向TxLocation数组的首地址
		uart1.TxInPtr = &uart1.TxLocation[0];
	}
}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)//串口底层硬件初始化
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {

    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);

  }
}

void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)//串口底层硬件复位
{

  if(uartHandle->Instance==USART1)
  {
    __HAL_RCC_USART1_CLK_DISABLE();

    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);

    HAL_NVIC_DisableIRQ(USART1_IRQn);
  }
}

void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)//终止串口接收中断回调函数
{
	if(huart->Instance == USART1){
		uart1.RxInPtr->end = &U1_RxBuff[uart1.RxCounter - 1];//将数据结束处的地址赋值给uart1.RxInPtr->end
		uart1.RxInPtr++;
		if(uart1.RxInPtr == uart1.RxEndPtr){
			uart1.RxInPtr = &uart1.RxLocation[0];
		}
		if((U1_RX_SIZE - uart1.RxCounter)<U1_RX_MAX){//再判断缓冲区剩下的空间是否能存储下U1_RX_MAX字节,不能则回滚
			uart1.RxCounter = 0;
			uart1.RxInPtr->start = U1_RxBuff;
		}else{//可以放下就让下一个数据输入指针指向下一个可以存储的地址
			uart1.RxInPtr->start = &U1_RxBuff[uart1.RxCounter];
		}
		HAL_UART_Receive_IT(&uart1.uart,uart1.RxInPtr->start,U1_RX_MAX);//然后再次开启中断接收函数
	}
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //串口接收中断回调函数
{
    if(huart->Instance == USART1) {
		
    }
}

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) //串口发送中断回调函数
{
    if(huart->Instance == USART1) {
        if(huart->Instance == USART1){
		uart1.TxState = 0;
		}
    }
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {//串口接收数据出错回调函数
    if(huart->Instance == USART1) {
        
    }
}
/***************.C********************************************/

//在中断函数文件里添加
/***************.IT********************************************/
void USART1_IRQHandler(void)//串口中断函数
{							//当串口中断发生后会进入到此函数,但是我们接收的数据小于RX_MAX,所以不会发生串口接收完成中断.
	HAL_UART_IRQHandler(&uart1.uart);
	//由于程序的规定是接收和发送小于256字节的数据,所以不会发生串口接收完成中断。如果接收到45个字节,那么我理论上是根据接收函数要接收256个,
	//但是接收不到256个字节,所以再接收到45个个字节之后,串口线就处于空闲状态,也就是这个时候,空闲中断就发生了,也就是进入到USART1_IRQHandler函数
	//由于HAL没有处理空闲中断的程序,所以我们需要模仿它处理接收完成中断的程序,实现一个处理空闲中断的程序
	if(__HAL_UART_GET_FLAG(&uart1.uart, UART_FLAG_IDLE)){//判断是否是这个中断发生
		__HAL_UART_CLEAR_IDLEFLAG(&uart1.uart);//清楚中断标志位
		uart1.RxCounter += (U1_RX_MAX - uart1.uart.RxXferCount);//通过uart1.uart.RxXferCount(这个值表示还有多少个字节需要接收)我们用U1_RX_MAX减去就能得到接收的字节数量
		HAL_UART_AbortReceive_IT(&uart1.uart);最后再调用终止接收中断函数
	}
}
/***************.IT********************************************/



//主函数里添加
		if(uart1.RxOutPtr != uart1.RxInPtr){
			U1_Txdata(uart1.RxOutPtr->start,uart1.RxOutPtr->end - uart1.RxOutPtr->start + 1);
			uart1.RxOutPtr++;
			if(uart1.RxOutPtr == uart1.RxEndPtr){
				uart1.RxOutPtr = &uart1.RxLocation[0];
			}
		}
		if((uart1.TxOutPtr != uart1.TxInPtr)&&(uart1.TxState==0)){
			uart1.TxState = 1;
			HAL_UART_Transmit_IT(&uart1.uart,uart1.TxOutPtr->start,uart1.TxOutPtr->end - uart1.TxOutPtr->start + 1);
			uart1.TxOutPtr++;
			if(uart1.TxOutPtr == uart1.TxEndPtr){
				uart1.TxOutPtr = &uart1.TxLocation[0];
			}
		}
物联沃分享整理
物联沃-IOTWORD物联网 » 深入理解STM32空闲中断(Idle Interrupt)机制

发表评论