STM32F103C8T6串口通信教程

一、UART协议

        通用异步收发器简称 UART,即“Universal Asynchronous Receiver Transmitter”, 它用来传输串行数据:发送数据时,CPU 将并行数据写入 UART,UART 按照一定的格式在一 根电线上串行发出;接收数据时,UART 检测另一根电线上的信号,将串行数据收集放在缓 冲区中,CPU 即可读取 UART 获得这些数据。UART 之间以
全双工
方式传输数据,最精简的连 线方法只有三根电线:TxD 用于发送数据,RxD 用于接收数据,GND 用于给双方提供参考电 平,连线如图所示:

        UART 使用标准的 TTL/CMOS 逻辑电平(0~5V、0~3.3V、0~2.5V 或 0~1.8V 四种)来表示数据,高电平表示 1,低电平表示 0。进行长距离传输时,为了增强数据的抗干扰能力、 提高传输长度,通常将 TTL/CMOS 逻辑电平转换为 RS-232 逻辑电平,3~12V 表示 0,-3~- 12V 表示1。

        TxD、RxD 数据线以“位”为最小单位传输数据。帧(frame)由具有完整意义的、不可 分割的若干位组成,它包含开始位、数据位、较验位(需要的话)和停止位。发送数据之前, UART 之间要定好数据的传输速率(即每位所占据的时间,其倒数称为波特率)、数据的传 输格式(即有多少个数据位、是否使用较验位、是奇较验还是偶较验、有多少个停止位)。

        

数据传输流程如下:

1) 平时数据线处于“空闭”状态(1 状态)。

2) 当要发送数据时,UART 改变 TxD 数据线的状态(变为 0 状态)并维持 1 位的时间──这 样接收方检测到开始位后,再等待 1.5 位的时间就开始一位一位地检测数据线的状态 得到所传输的数据。

3) UART 一帧中可以有 5、6、7 或 8 位的数据,发送方一位一位地改变数据线的状态将它 们发送出去,首先发送
最低位

4) 如果使用较验功能,UART 在发送完数据位后,还要发送 1 个较验位。有两种较验方法: 奇较验、偶较验──数据位连同较验位中,“1”的数目等于奇数或偶数。

5) 最后,发送停止位,数据线恢复到“空闭”状态(1 状态)。停止位的长度有 3 种:1 位、 1.5 位、2 位。

二、STM32 UART结构框图

三、三种编程方式:

a、概要描述

1、查询方式:要发送数据时,先把数据写入 TDR 寄存器,然后判断 TDR 为空再返回。当然也可以先判断 TDR 为空,再写入。要读取数据时,先判断 RDR 非空,再读取 RDR 得到数据。

2、中断方式:使用中断方式,效率更高,并且可以在接收数据时避免数据丢失。 要发送数据时,使能“TXE”中断(发送寄存器空中断)。在 TXE 中断处理函数里,从 程序的发送 buffer 里取出一个数据,写入 TDR。等再次发生 TXE 中断时,再从程序的发送 buffer 里取出下一个数据写TDR。  对于接收数据,在一开始就使能“RXNE”中断(接收寄存器非空)。这样,UART 接收 到一个数据就会触发中断,在中断程序里读取 RDR 得到数据,存入程序的接收 buffer。当 程序向读取串口数据时,它直接读取接收 buffer 即可。

3.DMA方式:使用中断方式时,在传输、接收数据时,会发生中断,还需要 CPU 执行中断处理函数。 有另外一种方法:DMA(Direct Memory Access),它可以直接在 2 个设备之间传递数据,无需 CPU 参与。

        在DMA模式下接收一定数量的数据,直到接收到预期数量的数据或发生空闲事件(Receive an amount of data in DMA mode till either the expected number of data is received or an IDLE event occurs.)

        参数Size:接收数据的长度,一般大于不定长数据长度,避免遗漏数据

b、硬件连接

c、CubeMX工程建立

1.查询方式实现

配置72Mhz外部时钟,异步USART1,生成工程

打开工程

在main.c中

主要使用到两个函数:

发送函数:Sends an amount of data in blocking mode.

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)

接收函数:Receives an amount of data in blocking mode.

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

代码编译OK下载后观察串口

2.中断方式实现(参考用户Z小璇USART中断博客)

接着用上面的工程,使能USART1中断,后打开工程

实验步骤:

1.在main中第一次调用接收中断函数: Receives an amount of data in non blocking mode.

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

2.进入接收中断

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

3.上面函数调用接收函数

static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)

 这里面会调用中断回调函数

        /*Call legacy weak Rx complete callback*/
        HAL_UART_RxCpltCallback(huart);

函数需要我们去实现处理接收数据,原本是弱定义

__weak void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function should not be modified, when the callback is needed,
           the HAL_UART_RxHalfCpltCallback could be implemented in the user file
   */
}

注意在这个回调函数中在调用一次中断接收函数,使程序可以重新触发接收中断。

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
代码实现:

在主函数外定义一些变量:

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
#define RX_BUFFSIZE 256   //最大接收字节数
char RxBuff[RX_BUFFSIZE]; //接收数据数组
uint8_t RxTempBuff;       //接收中断缓冲
uint8_t Usart1RxCnt = 0;  //接收缓冲计数
 
/* USER CODE ENDPV */

在main()里面开启中断接收

  /* USER CODE BEGIN 2 */
  //1.开启接收中断
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxTempBuff, 1);

  /* USER CODE END 2 */

在下方实现中断回调函数

/* USER CODE BEGIN 4 */

//实现中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
 
	if(Usart1RxCnt >= 255)  //溢出判断
	{
		Usart1RxCnt = 0;
		memset(RxBuff,0x00,sizeof(RxBuff));
		HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF); 	
	}
	else
	{
		RxBuff[Usart1RxCnt++] = RxTempBuff;   //接收数据转存
	
		if((RxBuff[Usart1RxCnt-1] == 0x0A)&&(RxBuff[Usart1RxCnt-2] == 0x0D)) //判断结束位
		{
			HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuff, Usart1RxCnt,0xFFFF); //将收到的信息发送出去
            while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
			Usart1RxCnt = 0;
			memset(RxBuff,0x00,sizeof(RxBuff)); //清空数组
		}
	}
	
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&RxTempBuff, 1);   //再开启接收中断
}

/* USER CODE END 4 */

下载程序,打开串口

3.中断方式改造(fifo实现,来自安富莱串口fifo章节改编)

        串口设计FIFO的目的是为了提高串口的通讯性能。如果没有FIFO或者说缓冲区的长度只有1字节,那么使用接收中断,就意味着每次收到一个字节的数据就要进一次中断,这样频繁进中断会占用CPU资源。另外如果没有及时读走数据,那么下一个字节数据就会覆盖之前的数据,导致数据丢失,这在通讯速率高的场合很有可能出现。使用FIFO,可以在连续接收若干个数据后才产生一次中断,然后一起进行处理。这样可以提高接收效率,避免频繁进中断,适用于大数据传输。

       首先,使用到的主要寄存器:

 状态寄存器(USART_SR)

控制寄存器1(USART_CR1)

3.1实现步骤:

1.RX接收数据串行输入—–接收移位寄存器—-接收数据寄存器(RDR)–数据寄存器(DR)—–进入中断服务程序(USART1_IRQHandler)判断USART_SR_RXNE中断(处理接收中断,实现读数据寄存器到接收FIFO)

2.从接收FIFO里取出要发送的数据(返回值:0表示无数据,1表示读到数据,无论有无数据均立即返回。)

3.把取到的数据填入发送FIFO,使能发送缓冲区空中断使能(USART_CR1_TXEIE)

4.进入中断服务程序(USART1_IRQHandler)判断处理发送缓冲区空中断(USART_CR1_TXEIE),

        处理过程:从发送FIFO取1个字节写入串口发送数据寄存器,再到发送数据寄存器–发送数据移位寄存器–TX发送出去。

5.进入中断服务程序判断数据是否全部发送完毕:

 当发送缓冲区的数据已取完后,

        1).禁止发送缓冲区空中断:CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);

        2).使能数据发送完毕中断:SET_BIT(_pUart->uart->CR1, USART_CR1_TCIE),此时最后1个数据还未真正发送完毕;

        3).确认数据bit位是否全部发送完毕,如果发送FIFO的数据全部发送完毕,禁止数据发送完毕中断:CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TCIE);

3.2代码实现

        单独写一个 .c 实现函数 .h里面定义结构体等。

        3.2.1首先在.h 文件定义fifo结构体等变量
#define UART1_TX_BUF_SIZE	1*1024  //发送数据缓冲大小
#define UART1_RX_BUF_SIZE	1*1024  //接收数据缓冲大小

/* 串口设备结构体 */
typedef struct
{
	USART_TypeDef *uart;	  	/* STM32内部串口设备指针 */
	uint8_t *pTxBuf;			    /* 发送缓冲区 */
	uint8_t *pRxBuf;			    /* 接收缓冲区 */
	uint16_t usTxBufSize;		  /* 发送缓冲区大小 */
	uint16_t usRxBufSize;		  /* 接收缓冲区大小 */
	__IO uint16_t usTxWrite;	/* 发送缓冲区写指针 */
	__IO uint16_t usTxRead;		/* 发送缓冲区读指针 */
	__IO uint16_t usTxCount;	/* 等待发送的数据个数 */

	__IO uint16_t usRxWrite;	/* 接收缓冲区写指针 */
	__IO uint16_t usRxRead;		/* 接收缓冲区读指针 */
	__IO uint16_t usRxCount;	/* 还未读取的新数据个数 */

	uint8_t Sending;			    /* 正在发送中 */
}UART_T; 
3.2.2.c 文件里面实现函数等变量定义
/* 定义串口结构体变量 */
static UART_T g_tUart1;
static uint8_t g_TxBuf1[UART1_TX_BUF_SIZE];   /* 发送缓冲区 */
static uint8_t g_RxBuf1[UART1_RX_BUF_SIZE];		/* 接收缓冲区 */

初始化串口相关的变量,bsp_InitUart()在main函数调用

/*
*********************************************************************************************************
*	函 数 名: bsp_InitUart
*	功能说明: 初始化串口硬件,并对全局变量赋初值.
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void bsp_InitUart(void)
{
	UartVarInit();		/* 必须先初始化全局变量,再配置硬件 */
}  
/*
*********************************************************************************************************
*	函 数 名: UartVarInit
*	功能说明: 初始化串口相关的变量
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
static void UartVarInit(void)
{
	g_tUart1.uart = USART1;						/* STM32 串口设备 */
	g_tUart1.pTxBuf = g_TxBuf1;					/* 发送缓冲区指针 */
	g_tUart1.pRxBuf = g_RxBuf1;					/* 接收缓冲区指针 */
	g_tUart1.usTxBufSize = UART1_TX_BUF_SIZE;	/* 发送缓冲区大小 */
	g_tUart1.usRxBufSize = UART1_RX_BUF_SIZE;	/* 接收缓冲区大小 */
	g_tUart1.usTxWrite = 0;						/* 发送FIFO写索引 */
	g_tUart1.usTxRead = 0;						/* 发送FIFO读索引 */
	g_tUart1.usRxWrite = 0;						/* 接收FIFO写索引 */
	g_tUart1.usRxRead = 0;						/* 接收FIFO读索引 */
	g_tUart1.usRxCount = 0;						/* 接收到的新数据个数 */
	g_tUart1.usTxCount = 0;						/* 待发送的数据个数 */
	g_tUart1.Sending = 0;						  /* 正在发送中标志 */
}
注意:在CubeMX配置后的Usart.c文件中 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)函数中 使能PE. RX接受中断

    CLEAR_BIT(USART1->SR, USART_SR_TC);   /* 清除TC发送完成标志 */
    CLEAR_BIT(USART1->SR, USART_SR_RXNE); /* 清除RXNE接收标志 */
    SET_BIT(USART1->CR1, USART_CR1_RXNEIE);	/* 使能PE. RX接受中断 */
3.2.3 串口接收函数

从串口1接收缓冲区读取1字节数据 (用于主程序调用)

/*
*********************************************************************************************************
*	函 数 名: UartGetChar
*	功能说明: 从串口1接收缓冲区读取1字节数据 (用于主程序调用)
*	形    参: _pUart : 串口设备
*			  _pByte : 存放读取数据的指针
*	返 回 值: 0 表示无数据  1表示读取到数据
*********************************************************************************************************
*/
static uint8_t UartGetChar(UART_T *_pUart, uint8_t *_pByte)
{
	uint16_t usCount;

	/* usRxWrite 变量在中断函数中被改写,主程序读取该变量时,必须进行临界区保护 */
  __set_PRIMASK(1);  /* 禁止全局中断*/
	usCount = _pUart->usRxCount;
  __set_PRIMASK(0);  /*  使能全局中断 */

	/* 如果读和写索引相同,则返回0 */
	//if (_pUart->usRxRead == usRxWrite)
	if (usCount == 0)	/* 已经没有数据 */
	{
		return 0;
	}
	else
	{
		*_pByte = _pUart->pRxBuf[_pUart->usRxRead];		/* 从串口接收FIFO取1个数据 */
    
		/* 改写FIFO读索引 */
  __set_PRIMASK(1);  /* 禁止全局中断*/
		if (++_pUart->usRxRead >= _pUart->usRxBufSize)
		{
			_pUart->usRxRead = 0;
		}
		_pUart->usRxCount--;
  __set_PRIMASK(0);  /*  使能全局中断 */
		return 1;
	}
}

从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回

/*
*********************************************************************************************************
*	函 数 名: comGetChar
*	功能说明: 从接收缓冲区读取1字节,非阻塞。无论有无数据均立即返回。
*	形    参:_pByte: 接收到的数据存放在这个地址
*	返 回 值: 0 表示无数据, 1 表示读取到有效字节
*********************************************************************************************************
*/
uint8_t comGetChar(uint8_t *_pByte)
{
  UART_T *pUart;
  pUart = &g_tUart1;
  
	return UartGetChar(pUart, _pByte);
}
3.2.4 串口发送函数

填写数据到UART1发送缓冲区,并启动发送中断

/*
*********************************************************************************************************
*	函 数 名: UartSend
*	功能说明: 填写数据到UART1发送缓冲区,并启动发送中断。中断处理函数发送完毕后,自动关闭发送中断
*	形    参: _ucaBuf 待发送的数据缓冲区 
            _usLen : 数据长度
*	返 回 值: 无
*********************************************************************************************************
*/
static void UartSend(UART_T *_pUart, uint8_t *_ucaBuf, uint16_t _usLen)
{
	uint16_t i;

	for (i = 0; i < _usLen; i++)
	{
		/* 如果发送缓冲区已经满了,则等待缓冲区空 */
		while (1)
		{
			__IO uint16_t usCount;

    __set_PRIMASK(1);  /* 禁止全局中断*/
			usCount = _pUart->usTxCount;
    __set_PRIMASK(0);  /*  使能全局中断 */
      
			if (usCount < _pUart->usTxBufSize)
			{
				break;
			}
			else if(usCount == _pUart->usTxBufSize)/* 数据已填满缓冲区 */
			{
				if((_pUart->uart->CR1 & USART_CR1_TXEIE) == 0)
				{
					SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);
				}  
			}
		}

		/* 将新数据填入发送缓冲区 */
		_pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf[i];

    __set_PRIMASK(1);  /* 禁止全局中断*/
		if (++_pUart->usTxWrite >= _pUart->usTxBufSize)
		{
			_pUart->usTxWrite = 0;
		}
		_pUart->usTxCount++;
    __set_PRIMASK(0);  /*  使能全局中断 */
	}

	SET_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);	/* 使能发送中断(缓冲区空) */
}

 向串口1发送一组数据

/*
*********************************************************************************************************
*	函 数 名: comSendBuf
*	功能说明: 向串口1发送一组数据。数据放到发送缓冲区后立即返回,由中断服务程序在后台完成发送
*	*	形    参:	_ucaBuf: 待发送的数据缓冲区
*			        _usLen : 数据长度
*	返 回 值: 无
*********************************************************************************************************
*/
void comSendBuf(uint8_t *_ucaBuf, uint16_t _usLen)
{
	UART_T *pUart;
  pUart = &g_tUart1;

	UartSend(pUart, _ucaBuf, _usLen);
}
3.2.5中断服务程序

供中断服务程序调用,通用串口中断处理函数

/*
*********************************************************************************************************
*	函 数 名: UartIRQ
*	功能说明: 供中断服务程序调用,通用串口中断处理函数
*	形    参: _pUart : 串口设备
*	返 回 值: 无
*********************************************************************************************************
*/
static void UartIRQ(UART_T *_pUart)
{
	uint32_t isrflags   = READ_REG(_pUart->uart->SR);
	uint32_t cr1its     = READ_REG(_pUart->uart->CR1);
	uint32_t cr3its     = READ_REG(_pUart->uart->CR3);
	
	/* 处理接收中断  */
	if ((isrflags & USART_SR_RXNE) != RESET)
	{
		/* 从串口接收数据寄存器读取数据存放到接收FIFO */
		uint8_t ch;

		ch = READ_REG(_pUart->uart->DR);
		_pUart->pRxBuf[_pUart->usRxWrite] = ch;
		if (++_pUart->usRxWrite >= _pUart->usRxBufSize)
		{
			_pUart->usRxWrite = 0;
		}
		if (_pUart->usRxCount < _pUart->usRxBufSize)
		{
			_pUart->usRxCount++;
		}
	}

	/* 处理发送缓冲区空中断 */
	if ( ((isrflags & USART_SR_TXE) != RESET) && (cr1its & USART_CR1_TXEIE) != RESET)
	{
		//if (_pUart->usTxRead == _pUart->usTxWrite)
		if (_pUart->usTxCount == 0)
		{
			/* 发送缓冲区的数据已取完时, 禁止发送缓冲区空中断 (注意:此时最后1个数据还未真正发送完毕)*/
			//USART_ITConfig(_pUart->uart, USART_IT_TXE, DISABLE);
			CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TXEIE);

			/* 使能数据发送完毕中断 */
			//USART_ITConfig(_pUart->uart, USART_IT_TC, ENABLE);
			SET_BIT(_pUart->uart->CR1, USART_CR1_TCIE);
		}
		else
		{
			_pUart->Sending = 1;
			
			/* 从发送FIFO取1个字节写入串口发送数据寄存器 */
			//USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);
			_pUart->uart->DR = _pUart->pTxBuf[_pUart->usTxRead];
			if (++_pUart->usTxRead >= _pUart->usTxBufSize)
			{
				_pUart->usTxRead = 0;
			}
			_pUart->usTxCount--;
		}

	}
	/* 数据bit位全部发送完毕的中断 */
	if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
	{
		//if (_pUart->usTxRead == _pUart->usTxWrite)
		if (_pUart->usTxCount == 0)
		{
			/* 如果发送FIFO的数据全部发送完毕,禁止数据发送完毕中断 */
			//USART_ITConfig(_pUart->uart, USART_IT_TC, DISABLE);
			CLEAR_BIT(_pUart->uart->CR1, USART_CR1_TCIE);
			
			_pUart->Sending = 0;
		}
		else
		{
			/* 正常情况下,不会进入此分支 */

			/* 如果发送FIFO的数据还未完毕,则从发送FIFO取1个数据写入发送数据寄存器 */
			//USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);
			_pUart->uart->DR = _pUart->pTxBuf[_pUart->usTxRead];
			if (++_pUart->usTxRead >= _pUart->usTxBufSize)
			{
				_pUart->usTxRead = 0;
			}
			_pUart->usTxCount--;
		}
	}
}

USART1中断服务程序,注意需要把stm32f1xx_it.c里面的USART1_IRQHandler函数屏蔽

/*
*********************************************************************************************************
*	函 数 名: USART1_IRQHandler  
*	功能说明: USART1中断服务程序
*	形    参: 无
*	返 回 值: 无
*********************************************************************************************************
*/
void USART1_IRQHandler(void)
{
	UartIRQ(&g_tUart1);
}
3.2.6主函数里面添加功能

添加一个变量转运数据

  /* USER CODE BEGIN 1 */
	uint8_t read;
  /* USER CODE END 1 */

在循环里面调用接收和发送函数

  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */ 
    if(comGetChar(&read))
    {
      comSendBuf(&read, strlen((const char*)(&read)));
    }
  }
3.2.7串口查看实验现象

4.串口DMA+空闲中断接收不定长数据实现

        系统结构框图:

4.1实现思路(参考github https://github.com/STMicroelectronicsda/STM32CubeF4):

主函数启动接收空闲进程,使用专用的 HAL UART API:HAL_UARTEx_ReceiveToIdle_DMA
​​​​​​​

/**
  * @brief Receive an amount of data in DMA mode till either the expected number of data is received or an IDLE event occurs.
  * @note   Reception is initiated by this function call. Further progress of reception is achieved thanks
  *         to DMA services, transferring automatically received data elements in user reception buffer and
  *         calling registered callbacks at half/end of reception. UART IDLE events are also used to consider
  *         reception phase as ended. In all cases, callback execution will indicate number of received data elements.
  * @note   When the UART parity is enabled (PCE = 1), the received data contain
  *         the parity bit (MSB position).
  * @note   When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M = 01),
  *         the received data is handled as a set of uint16_t. In this case, Size must indicate the number
  *         of uint16_t available through pData.
  * @param huart UART handle.
  * @param pData Pointer to data buffer (uint8_t or uint16_t data elements).
  * @param Size  Amount of data elements (uint8_t or uint16_t) to be received.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_UARTEx_ReceiveToIdle_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

        HAL_UARTEx_ReceiveToIdle_DMA()函数允许使用DMA处理来自超级终端的数据接收,并在接收仍在进行时通知应用程序已经接收到的数据。接收到的字符由DMA处理并存储在用户aRXBufferUser缓冲区中。通知应用程序,接收缓冲区中有一些数据可用,通过执行用户回调:HAL_UARTEx_RxEventCallback()来完成。

        HAL_UARTEx_RxEventCallback()将在以下任何事件发生时执行:

HT (Half Transfer) : Half of Rx buffer is filled-Rx缓冲区的一半被填满

TC (Transfer Complete) : Rx buffer is full–Rx缓冲区已满
如果是Circular DMA,则可以继续接收,下一次接收数据将被DMA存储在接收缓冲区索引0中。
Idle Event on Rx line: Triggered when RX line has been in idle state (normally high state) for 1 frame time, after last received byte–当Rx线上处于空闲状态(通常是高状态)1帧时触发,从最后一个接收到的字节开始。
        当任何HT, TC或Idle事件发生时,HAL_UARTEx_RxEventCallback()被调用,并提供(作为回调参数)用户缓冲区中的索引,直到接收到的数据已经存储。

​​​​​​​例如:IDLE事件发生之前接收到22字节的情况,使用循环DMA和大小为20字节的Rx缓冲区?

– 用户调用HAL_UARTEx_ReceiveToIdle_DMA()提供缓冲区地址和缓冲区大小(20)-
– HAL_UARTEx_RxEventCallback()将在Size = 10HT DMA事件上执行用户Rx缓冲区中的数据可以由应用程序从索引0到9检索
– HAL_UARTEx_RxEventCallback()将在TC DMA事件Size = 20上执行应用程序可以从索引10到19检索用户Rx缓冲区中的新数据
– HAL_UARTEx_RxEventCallback()将在Size = 2的IDLE事件发生后执行应用程序可以从索引0到1检索用户Rx缓冲区中的新数据

4.2实现步骤:

1)接着打开上一个CubeMX生成的工程,只需要打开USART1 继续设置DMA Setting即可,RX和TX都配置Circular循环模式

2)打开工程

        main.c文件主函数外定义一些变量:

#define RX_BUFFER_SIZE   20    //接收缓冲buff大小

/* 获取BUFFER在内存中所占用的存储空间,以字节为单位来计数 */
#define COUNTOF(__BUFFER__)   (sizeof(__BUFFER__) / sizeof(*(__BUFFER__)))

/*打印到串口的数据*/
uint8_t aTextInfoStart[] = "\r\nUSART Example : Enter characters to fill reception buffers.\r\n";

uint8_t aRXBufferUser[RX_BUFFER_SIZE];  //接收数组

/* 用于管理中断例程中接收到的数据的数据缓冲区 */
uint8_t aRXBufferA[RX_BUFFER_SIZE];
uint8_t aRXBufferB[RX_BUFFER_SIZE];

__IO uint32_t   uwNbReceivedChars;//用于接收多少数据

/* 用于在缓冲区交换数据 */
uint8_t *pBufferReadyForUser;        
uint8_t *pBufferReadyForReception;

3)主函数里面启动接收空闲进程,单独封装一个函数并在主函数里面调用

void StartReception(void)
{
  /* 初始化变量 */
  pBufferReadyForReception = aRXBufferA;
  pBufferReadyForUser      = aRXBufferB;
  uwNbReceivedChars        = 0;

  /* 打印提升信息 */
  PrintInfo(&huart1, aTextInfoStart, COUNTOF(aTextInfoStart)-1);

  /* 启动接收空闲进程 */
  if (HAL_OK != HAL_UARTEx_ReceiveToIdle_DMA(&huart1, aRXBufferUser, RX_BUFFER_SIZE))
  {
    Error_Handler();
  }
}
  /* USER CODE BEGIN 2 */
  
 /* Initiate Continuous reception */
  StartReception();
  
  /* USER CODE END 2 */

4)打印信息函数实现

void PrintInfo(UART_HandleTypeDef *huart, uint8_t *String, uint16_t Size)
{
  if (HAL_OK != HAL_UART_Transmit(huart, String, Size, 100))
  {
    Error_Handler();
  }
}

5)处理包含PC com端口接收数据的缓冲区函数,该函数在HAL_UARTEx_RxEventCallback()

中被调用

void UserDataTreatment(UART_HandleTypeDef *huart, uint8_t* pData, uint16_t Size)
{
  uint8_t* pBuff = pData;
  uint8_t  i;

  /*环回的实现是在直接寄存器访问中实现的。
    以便能够在接收到字符时尽快回显接收到的字符。
    等待TC标志在传输结束时升起,然后移除,只检查TXE*/
  for (i = 0; i < Size; i++)
  {
    while (!(__HAL_UART_GET_FLAG(huart, UART_FLAG_TXE))) {}
    huart->Instance->DR = *pBuff;
    pBuff++;
  }

}

6)用户需要实现的中断回调函数

__weak void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  static uint8_t old_pos = 0;
  uint8_t *ptemp;
  uint8_t i;

  /* 检查接收缓冲区中的接收数据数是否已更改 */
  if (Size != old_pos)
  {
    /*检查接收缓冲区中索引的位置是否简单地增加了如果到达缓冲区的末端 */
    if (Size > old_pos)
    {
      /* 现在的位置比以前的位置高时 */
      uwNbReceivedChars = Size - old_pos;
      /* 将接收到的数据复制到“用户”缓冲区以便清空*/
      for (i = 0; i < uwNbReceivedChars; i++)
      {
        pBufferReadyForUser[i] = aRXBufferUser[old_pos + i];
      }
    }
    else
    {
      /* 当前位置低于前一个位置:已到达缓冲区的末端 */
      /* 首先从当前位置复制数据直到缓冲区结束 */
      uwNbReceivedChars = RX_BUFFER_SIZE - old_pos;
      /* 将接收到的数据复制到“用户”缓冲区以便清空 */
      for (i = 0; i < uwNbReceivedChars; i++)
      {
        pBufferReadyForUser[i] = aRXBufferUser[old_pos + i];
      }
      /* 在缓冲的开始检查并继续 */
      if (Size > 0)
      {
        for (i = 0; i < Size; i++)
        {
          pBufferReadyForUser[uwNbReceivedChars + i] = aRXBufferUser[i];
        }
        uwNbReceivedChars += Size;
      }
    }
    /* 处理从Rx用户缓冲区中提取的接收数据 */
    UserDataTreatment(huart, pBufferReadyForUser, uwNbReceivedChars);

    /* 为要处理的下一个字节交换缓冲区 */
    ptemp = pBufferReadyForUser;
    pBufferReadyForUser = pBufferReadyForReception;
    pBufferReadyForReception = ptemp;
  }
  /* 更新old_pos作为用户Rx缓冲区中位置的新引用
     指示数据被处理到的位置 */
  old_pos = Size;
}

7)编译运行打开串口调试助手

四、总结

        我个人使用比较多的是串口中断fifo模式;如果有什么写的不对的地方,欢迎大家评论区讨论。

作者:盖世英雄到来

物联沃分享整理
物联沃-IOTWORD物联网 » STM32F103C8T6串口通信教程

发表评论