HAL库串口分析


前言

串口的发送包括:

  • 阻塞式的发送
  • 中断发送
  • DMA发送

串口的接收包括:

  • 扫描接收
  • 中断接收
  • DMA接收

一、阻塞发送函数:HAL_UART_Transmit

1、函数原型:

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
参数:

huart: 指向uart _ handletypedef结构的huart指针,该结构包含指定uart模块的配置信息。

PData: 指向数据缓冲区的pData指针(U8或u16数据元素)。

Size: 要发送的数据元素( u8或U16 )的大小

Timeout:超时持续时间,单位ms,0就是0ms超时,数据最大发送时间,超过则返回异常
//返回:
//HAL 状态
typedef enum
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

举例:

HAL_UART_Transmit(&huart1,"ok\r\n",strlen("ok\r\n"),0xFFFF);

2、Timeout的意义

这里先搞懂什么是波特率:

波特率:发送二进制数据位的速率,习惯上用 baud 表示,即我们发送一位二进制数据的持续时间=1/baud。

如果波特率为9600,发送一个位需要1/9600s=0.0001042s=0.1042ms,这里按数据位为8位,停止位为2位,加起来就是10位,10个位发送所需的时间为:0.104210ms = 1.042ms,如果我要发送10个字节的数据,那发送这10个字节数据给接收方需要的时间为:101.042ms = 10.42ms。
这是算实际的发送10个字节的数据所需要的时间。我们在接收方接收数据时可以把时间再加宽一些,让它有一点余量。让接收方能稳定的把数据从发送方接手过来,可以加个5ms,或更宽一点10ms,加上发送10个字节所花的时间,就是15ms或20ms。

3、注意的问题

  • 在HAL_UART_RxCpltCallback函数中不可使用会发送乱码。

二、串口扫描接收:HAL_UART_Receive

HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
参数:
huart: 指向uart _ handletypedef结构的huart指针,该结构包含指定uart模块的配置信息。
pData:指向数据缓冲区的指针( u8或U16数据元素)。
Size: 要接收的数据元素数量( u8或U16 )。
Timeout:超时持续时间,单位ms,0就是0ms超时,数据接收最大等待时间,超出则异常

HAL 状态

typedef enum
{
  HAL_OK       = 0x00U,
  HAL_ERROR    = 0x01U,
  HAL_BUSY     = 0x02U,
  HAL_TIMEOUT  = 0x03U
} HAL_StatusTypeDef;

例如:

uint8_t data=0;
while (1)
{
if(HAL_UART_Receive(&huart1,&data,1,0)==HAL_OK){
    }
}

代码实现:


uint8_t data=0;
while (1)
{
    //串口接收数据
    if(HAL_UART_Receive(&huart1,&data,1,0)==HAL_OK){
            //将接收的数据发送
             HAL_UART_Transmit(&huart1,&data,1,0);
        }
}

其中timeout为0表示没有延时,所以串口接收函数是不阻塞的,while循环将一直轮询。
加个延时函数
在这里插入图片描述
这样一来的话,接收数据就异常了,会接收数据不全,所以这样是不可靠的

那改成这样呢?

uint8_t data[100]={0};
if(HAL_UART_Receive(&huart1,data,100,1000)==HAL_OK){
            
}

接收100个数据,等待时间为1秒,这样的话接收区没满时,每次运行这条语句都要延时等待1S,这时相当于一个HAL_Dealy(1000),这会阻塞while循环。只有数据接收到刚刚等于100才能返回HAL_OK,所以不能用于接收变成数据,这是不理想的。

三、中断发送函数:HAL_UART_Transmit

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

在main函数中运行

 while (1)
    {
      HAL_UART_Transmit_IT(&huart1, "OK\r\n", 4);
      HAL_Delay(500);
    }

则在中断正确发送完数据后会进入中断响应函数:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
  HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
  HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
}

此时会处理两个小灯会反转颜色,所为成功发送的标志

四、串口中断接收:HAL_UART_Receive_IT

打开中断需要调用以下的函数:

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

例如:

HAL_UART_Receive_IT(&huart1, buffer, 1);

当接受数据完整或者出现错误会调用以下的callback函数。

//发生溢出中断或DMA中断时
uint8_t Rx_HAL_Buff_Temp[2];	//HAL函数接收临时缓存
uint8_t Rx_Buff[100];			//接收帧缓存,自己定义大小
uint8_t RxCounter = 0;			//接收计数器
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  huart->ErrorCode = HAL_UART_ERROR_NONE;
  HAL_UART_Receive_IT(&huart6,Rx_HAL_Buff_Temp,1);
}
//接收到1字节数据,回调函数如下
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if(huart->Instance==USART6)
  {
	Rx_Buff[[RxCounter++] = Rx_HAL_Buff_Temp[0];
	//加上自己的代码
  	HAL_UART_Receive_IT(&huart6,Rx_HAL_Buff_Temp,1);
  } 
}

串口中断的接收流程:

USART1_IRQHandler(void)(中断服务函数)    
    ->    HAL_UART_IRQHandler(UART_HandleTypeDef *huart)(中断处理函数)    
        ->    UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数)   
            ->    HAL_UART_RxCpltCallback(huart);(中断回调函数)

五、串口DMA接收数据

1、cubemx配置

串口配置成全局中断模式
在这里插入图片描述
DMA的RX请求设置为循环模式,TX请求设置为正常。
在这里插入图片描述

在这里插入图片描述

2、程序配置

当DMA串口接收开始后,DMA通道会不断的将发送来的数据转移到内存,那该如何判断串口接收是否完成从而及时关闭DMA通道?如何知道接收到数据的长度?答案便是使用串口空闲中断。

思路流程:

1、开启串口DMA接收
2、串口收到数据,DMA不断传输数据到内存
3、一帧数据发送完毕,串口暂时空闲,触发串口空闲中断
4、在中断服务函数中,可以计算刚才收到了多少个字节的数据
5、存储接收到的数据,清除标志位,开始下一帧接收

串口设置:


volatile uint8_t rx_len=0;
volatile uint8_t recv_end_flag=0;
uint8_t rx_buffer[200];
static void MX_USART1_UART_Init(void)
{

  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
  //上面的usart配置代码为cubemx自动生成的,在下方添加使能idle中断和打开串口DMA接收语句
	__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//使能idle中断
	HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//打开DMA接收,数据存入rx_buffer数组中。	
}

只有加上了:

__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//使能idle中断

才会进入到串口中断函数,接下来修改串口中断函数:

void USART1_IRQHandler(void)
{
	uint32_t tmp_flag = 0;
	uint32_t temp;
	tmp_flag =__HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE); //获取IDLE标志位
	if((tmp_flag != RESET))//idle标志被置位
	{ 
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除标志位
		temp = huart1.Instance->SR;  //清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能
		temp = huart1.Instance->DR; //读取数据寄存器中的数据
		HAL_UART_DMAStop(&huart1); //
		temp  = hdma_usart1_rx.Instance->CNDTR;// 获取DMA中未传输的数据个数,NDTR寄存器分析见下面
		rx_len =  BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
		recv_end_flag = 1;	// 接受完成标志位置1	
	 }
  HAL_UART_IRQHandler(&huart1);

}

在这里插入图片描述

主程序:

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  while (1)
  {
		if(recv_end_flag ==1)
		{
			printf("rx_len=%d\r\n",rx_len);//打印接收长度
			HAL_UART_Transmit(&huart1,rx_buffer, rx_len,200);接收数据打印出来
			for(uint8_t i=0;i<rx_len;i++)
				{
					rx_buffer[i]=0;//清接收缓存
				}
			rx_len=0;//清除计数
			recv_end_flag=0;//清除接收结束标志位
		}
		HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//重新打开DMA接收		
  }
}

六、串口DMA发送

1、在main.c中添加

uint8_t Senbuff[] = "fsdfdsfdsfsdfsd \r\n";   

2、在while中添加

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

    /* USER CODE BEGIN 3 */
		HAL_UART_Transmit_DMA(&huart1, (uint8_t *)Senbuff, sizeof(Senbuff));
		HAL_Delay(1000);		

  }


总结需要注意的点


标准库中打开串口的函数:

USART_Cmd(USART1, ENABLE);

标准库中打开中断的函数:需要单独设置被打开的中断的类型:

USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);    

在HAL中,打开串口和中断合并成了一个函数:发送就打开发送中断,接收就打开接收中断:

HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);

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

标准库和HAL库中都有阻塞式发送接收和非阻塞式发送接收(中断或DMA)
标准库中阻塞式发送和接收函数:

void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
uint16_t USART_ReceiveData(USART_TypeDef* USARTx)

HAL库中阻塞式发送和接收

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

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

阻塞式发送和接收在使用上都没有太大的差别,发送就等着发送完,HAL中阻塞式发送函数的第4个参数Timeout,可以设置一个超时时间,超时后没发完就不再阻塞。

接收没有差别。超时时间根据systic的计数频率和计数值确定


中断式发送和接收

先说接收

标准库中接收的话需要自己在中断函数里面判断中断类型,清标志位在HAL中void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);这个库函数帮我们完成了中断类型判断和清除标志位,我们只需要在具体的函数中写逻辑即可。

上面这个库函数判断出不同的类型,然后调用不同的回调函数,我们处理接收中断回调函数HAL_UART_TxCpltCallback即可。

全部回调函数如下:

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
void HAL_UART_AbortCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_AbortTransmitCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart);

发送和接收中断的一些问题:

HAL_UART_Transmit_IT(huart11,datass,2 ); 
HAL_UART_Receive_IT(&huart1,recivedatass,3);

关于中断式发送和接收,可以看到这两个函数的第3个参数,是设置要接收多少个、发送多少个字节。发送的中断就不用说了,有个全部发送完成和发送一半。接收的话,每接收到一个字节就会产生一个接收中断,但是我们可能需要接收完3个字节的数据才想处理一次。HAL_UART_IRQHandler内部做了判断,当接收到3个字节后才会调用一个回调函数 HAL_UART_RxCpltCallback

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值