初探STM32F4(4)--USART(2)

USART

  • 概述
  • 串口发送数据的配置流程
  • 串口接收数据的配置与工作流程

概述

本文是对UART外设的加深学习(参考正点原子的教学),文章架构如下:

  1. 研究串口发送数据配置流程
  2. 研究串口接收数据配置流程
  3. 研究串口收发数据中断处理流程

阅读完本文,要能回答以下问题:

  1. 简述串口发送数据的配置与工作流程。
  2. 前文HAL库通过HAL_UART_Init()配置好串口数据格式,简要阐述一下如何实现串口使能、串口发送使能的。
  3. 配置好串口后,通过HAL_UART_Transmit()函数进行USART异步发送数据,从该函数接收参数、返回参数的角度简述该函数是如何发送数据的。
  4. 从该函数具体实现何种功能的角度简述该函数是如何发送数据的。
  5. 该函数实现发送数据功能,需要UART_HandleTypeDef的成员变量*pTxBuffPtr、TxXferSize、 TxXferCount支持,阐述下这三个变量的功能。
  6. 简述串口开启接收中断时接收数据的初始化配置与工作流程
  7. 配置好串口相关特性后,接下来就是要开启中断,简述开启接收中断的过程。
  8. 开启中断过程中涉及到锁的概念,对关键资源池进行保护,防止抢占,基于进程相关概念分析下锁的作用。
  9. 开启接收中断后,中断函数的内部需要进行哪些操作
  10. 为了测试串口是否正确读取到了数据,通常会将读到的数据回调到串口输出,阐述下这一过程。

文章最后,列出了一些值得进一步研究的内容,包括对中断函数内部的优化、进程管理等,后续敬请期待。

串口发送数据的配置流程

1、简述串口发送数据的配置流程。

  1. 配置串口数据格式,例如字长、停止位、奇偶校验位和波特率
  2. 串口使能、串口发送使能
  3. 向发送数据寄存器TDR写入待发送的数据
  4. 等待状态寄存器USARTx_SR(ISR)的TC位置1,表征串口发送数据完成
int main(void)
{
    u8 buff[]="test";
    HAL_Init();                     //³õʼ»¯HAL¿â    
    Stm32_Clock_Init(360,25,2,8);   //ÉèÖÃʱÖÓ,180Mhz
    delay_init(180);
	
	  uart1_init();
	
    while(1)
		{
		  HAL_UART_Transmit(&usart1_handler,buff,sizeof(buff),1000);
			delay_ms(3000);
		
		}
	
	}

2、前文已经讲解如何通过HAL_UART_Init()配置串口数据格式,但是却没有注意串口使能、串口发送使能是如何实现的,简要阐述一下。

  • 对于串口使能,是通过宏定义的,如下所示
#define __HAL_UART_ENABLE(__HANDLE__)               ((__HANDLE__)->Instance->CR1 |=  USART_CR1_UE)
  • 在配置串口初始化时,宏定义如下使用:首先关闭串口使能,然后使用 UART_SetConfig()配置串口特性,最后打开串口使能。
 /* Disable the peripheral */
  __HAL_UART_DISABLE(huart);

  /* Set the UART Communication parameters */
  UART_SetConfig(huart);

//......

  /* Enable the peripheral */
  __HAL_UART_ENABLE(huart);
  
  • 对于串口发送的使能,是通过上文介绍的init成员变量Mode设置好特性,然后通过UART_SetConfig()函数传递Mode取值给配置寄存器相关位。
//通过这里的huart->Init.Mode确定是否使能串口读或写
tmpreg |= (uint32_t)huart->Init.WordLength | huart->Init.Parity | huart->Init.Mode | huart->Init.OverSampling;

//Mode的取值如下
#define UART_MODE_RX                        ((uint32_t)USART_CR1_RE)
#define UART_MODE_TX                        ((uint32_t)USART_CR1_TE)
#define UART_MODE_TX_RX                     ((uint32_t)(USART_CR1_TE |USART_CR1_RE))

3、通过HAL_UART_Transmit()函数进行USART异步发送数据,从该函数接收参数、返回参数的角度简述该函数是如何发送数据的。

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
  • 返回参数HAL_StatusTypeDef标志函数执行的状态结果。
  • 第一个接收参数是UART_HandleTypeDef数据类型,通过上文分析,知道了USART相关的发送数据寄存器的地址信息在这个参数里面。
  • 第二个接收参数是指向char类型的指针,给出了待发送数据的地址
  • 第三个接收参数要给出待发送数据的大小
  • 第四个接收参数是预定发送数据的超时门槛

4、从该函数具体实现何种功能的角度简述该函数是如何发送数据的。

  • 发送数据的主体部分如下所示,只要pdata指向的数据没有发送完,就向数据寄存器里面写pdata指向的数据。
  • 地址每次自加1,指向下一个字节,表明数据寄存器一次只发送一个字节的数据,符合数据寄存器低8(9)位工作的特性。
    huart->TxXferSize = Size;
    huart->TxXferCount = Size;
    while(huart->TxXferCount > 0)
    {
      huart->TxXferCount--;
      //...
      huart->Instance->DR = (*pData++ & (uint8_t)0xFF);//8位数据位
      

5、该函数实现发送数据功能,需要UART_HandleTypeDef的成员变量*pTxBuffPtr、TxXferSize、 TxXferCount支持,阐述下这三个变量的功能。

  • pTxBuffPtr是串口发送数据的数据缓存指针,只有在开启中断后,将pdata指针传递给pTxBuffPtr(为啥开启中断后才这样做?)
  • TxXferSize设置需要发送的数据量
  • TxXferCount设置还剩余要发送的数据量
  • RX对应的三个变量同理解释

串口接收数据的配置与工作流程

上文介绍串口发送数据时,没有涉及中断的概念。本节在开启接收中断的前提下,研究程序流程。

1、简述串口开启接收中断时接收数据的初始化配置与工作流程

  1. 配置串口数据格式,例如字长、停止位、奇偶校验位和波特率
  2. 在GPIO复用配置时,需要使能串口x的中断通道,并且设置中断优先级
  3. 串口使能、串口接收使能
  4. 开启接收中断
  5. 进入主循环,等待响应读中断
int main(void)
{
 
    HAL_Init();                     //初始化HAL库
    Stm32_Clock_Init(360,25,2,8);   //设置时钟,180Mhz
    delay_init(180);
	
	uart1_init();//初始化串口参数

	HAL_UART_Receive_IT(&usart1_handler, (u8 *)rdata, 1);//该函数会开启接收中断,并且设置接收缓冲以及接收最大数据量

	
    while(1)
	{
		
	}
	
	}

uart1_init()函数调用HAL_UART_Init(),设置好了串口特性、数据格式,并且对串口以及串口接收数据进行了使能。同时在GPIO复用配置时,使能串口x的中断通道,并且设置中断优先级。中断配置相关概念在中断一文进行说明。

void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
   
	GPIO_InitTypeDef GPIO_Initure;
	
	if(huart->Instance==USART1)//配置对应串口
	{
		//......
		
		HAL_NVIC_EnableIRQ(USART1_IRQn);		//使能串口1中断通道
		HAL_NVIC_SetPriority(USART1_IRQn,3,3);	//设置串口1的中断优先级
	}
 	
}

2、配置好串口相关特性后,接下来就是要开启中断,简要阐述开启接收中断的过程。

  • 通过HAL_UART_Receive_IT()开启接收中断,并且设置接收缓冲以及接收最大数据量
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
  • 研究该函数接收参数,第一个参数包含串口寄存器的实际地址,第二个参数是希望存放接收数据的地址,第三个参数是数据大小
    /* Process Locked */
    __HAL_LOCK(huart);
    
    huart->pRxBuffPtr = pData;
    huart->RxXferSize = Size;
    huart->RxXferCount = Size;

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Enable the UART Data Register not empty Interrupt */
    __HAL_UART_ENABLE_IT(huart, UART_IT_RXNE);
  • 研究该函数实现的功能:
    • 首先指定接收到的数据该存在内存地址哪里,每次接收多少数据。
    • 然后开启数据接收中断,使能RXNE的功能,一旦状态寄存器的RXNE由硬件置1了,表明有数据待读取了,产生中断。只要对数据寄存器执行读操作后,就会自动清零该标志位
    • 进一步可以推断出,进入接收中断,要执行的一步重要操作是:将数据寄存器huart->Instance->DR的值送入到待存放地址huart->pRxBuffPtr中去

3、这里涉及到锁的概念,对资源池进行保护,防止抢占,基于进程相关概念分析下锁的作用。

4、开启接收中断后,中断函数的内部需要进行哪些操作?

  • 调用HAL_UART_IRQHandler()函数执行需要执行的功能
  • 恢复状态标志位并退出中断。
  • HAL_UART_IRQHandler()内部,又调用UART_Receive_IT()函数,最终实现将数据寄存器huart->Instance->DR的值送入到待存放地址huart->pRxBuffPtr中去
void USART1_IRQHandler(void)   
{
	HAL_UART_IRQHandler(&usart1_handler);
	while (HAL_UART_GetState(&usart1_handler) != HAL_UART_STATE_READY);//µÈ´ý¾ÍÐ÷
	while(HAL_UART_Receive_IT(&usart1_handler, (u8 *)rdata, 1) != HAL_OK);	
}

void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
//......
/* UART in mode Receiver --------------*/
  if((tmp1 != RESET) && (tmp2 != RESET))
  { 
    UART_Receive_IT(huart);
  }
  //......
}

static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
	//...
	*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
	//......
}

5、为了测试串口是否正确读取到了数据,通常会将读到的数据回调到串口输出,阐述下这一过程。

  • 通过调用HAL_UART_RxCpltCallback()函数将接收到的数据通过串口1发送,函数如下所示:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  u8 rec;
  if(huart->Instance==USART1)//如果串口1
  {
	rec=*(--(huart->pRxBuffPtr));
    HAL_UART_Transmit(&usart1_handler,&rec,1,1000); 
  }

}
  • 这个函数该在代码哪里被调用呢?在UART_Receive_IT()函数内部被调用,即在接收中断函数里面被调用,再将数据寄存器huart->Instance->DR的值送入到待存放地址huart->pRxBuffPtr中去之后,调用HAL_UART_RxCpltCallback()函数,发送数据。
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
	//...
	*huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
	//......
	HAL_UART_RxCpltCallback(huart);
	//......
}

在中断函数中使用HAL库执行中断操作并不是一个好的选择,因为在工控领域,中断常常需要高频响应的,这要求中断程序必须足够简洁,如果不够简洁,会造成高优先级中断始终抢占、低优先级中断始终死等。

后续工作包括对中断函数内部的优化、进程管理,有机会再深入研究

你可能感兴趣的:(STM32开发)