串口相关的源码一般在SYSTEM分组之下的usart.c和usart.h中。下面我们讨论如何实现串口通信。
初始化串口相关参数
我们自定义函数uart_init来初始化串口相关参数,如波特率,字长,停止位,奇偶校验位,有无硬件流控,收发模式等,其中也会调用HAL库提供的函数HAL_UART_Init,不需要我们编写,此函数的作用的是初始化串口参数配置,uart_init具体函数代码如下
void uart_init(u32 bound)
{
//UART 初始化设置
UART1_Handler.Instance=USART1; //USART1
UART1_Handler.Init.BaudRate=bound; //波特率
UART1_Handler.Init.WordLength=UART_WORDLENGTH_8B; //字长为8位数据格式
UART1_Handler.Init.StopBits=UART_STOPBITS_1; //一个停止位
UART1_Handler.Init.Parity=UART_PARITY_NONE; //无奇偶校验位
UART1_Handler.Init.HwFlowCtl=UART_HWCONTROL_NONE; //无硬件流控制
UART1_Handler.Init.Mode=UART_MODE_TX_RX; //收发模式
HAL_UART_Init(&UART1_Handler); //HAL_UART_Init()使能UART1
HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);//开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接受最大的数据量
}
我们会看到,在uart_init函数的最后一行调用了 HAL_UART_Receive_IT函数,这个函数的功能是开启接收中断:使得标志位UART_IT_RXNE置1,并且设置接收缓冲以及接受最大的数据量,缓冲区的大小代表一次接收的数据量,我们一般会在usart.h中通过宏定义设置缓冲区大小。如#define RXBUFFERSIZE 1 //缓冲区大小为1,代表一次只能接收一个数据。当然如果我们不需要开启中断,那么就不需要调用HAL_UART_Receive_IT函数。
GPIO初始化
在串口初始化函数HAL_UART_Init内部,会调用串口Msp函数HAL_UART_MspInit(自定义的函数)来设置与MCU相关的配置,来设置GOIO初始化。在开启中断的情况下,此函数中会调用HAL_NVIC_EnableIRQ和HAL_NVIC_SetPriority两个函数,作用分别是 UART中断通道和抢占优先级,子优先级的设置;同理不开中断时并不需要调用,HAL_UART_MspInit函数的具体代码如下
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_Initure;
if(huart->Instance==USART1)//如果是串口1,进行串口1MSP的初始化
{
__HAL_RCC_GPIOA_CLK_ENABLE(); //使能GPIOA时钟
__HAL_RCC_USART1_CLK_ENABLE(); //使能USART1时钟
GPIO_Initure.Pin=GPIO_PIN_9; //PA9
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_FAST; //高速
GPIO_Initure.Alternate=GPIO_AF7_USART1; //复用为USART1
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA9
GPIO_Initure.Pin=GPIO_PIN_10; //PA10
HAL_GPIO_Init(GPIOA,&GPIO_Initure); //初始化PA10
#if EN_USART1_RX
HAL_NVIC_EnableIRQ(USART1_IRQn); //使能UART1中断通道
HAL_NVIC_SetPriority(USART1_IRQn,3,3); //抢占优先级3,子优先级3
#endif
}
}
到目前只要在主函数中调用自定义的uart_init函数,便可以执行以上提到的所有代码实现初始化串口相关参数和GPIO的初始化操作。以上操作属于串口通信的初始化部分,需要要在主函数一开始调用的,代码如下;接下来我们将介绍串口收发程序整个执行流程。
int main(void)
{
Cache_Enable(); //打开L1-Cache
HAL_Init(); //初始化HAL库
Stm32_Clock_Init(432,25,2,9); //设置时钟,216Mhz
delay_init(216); //延时初始化
uart_init(115200); //串口初始化
LED_Init(); //初始化LED
while(1)
{
}
}
串口收发程序执行流程
我们通过电脑给单片机发送数据,单片机首先需要接收电脑发来的数据,接收完成后再发送给电脑端,这就是串口收发的过程,那么单片机是如何实现这一整个过程的呢?开启中断和不开启中断的执行流程有点区别,下面我们都会讨论,执行流程如下图所示。
- 开启中断
电脑发送数据给单片机,会触发单片机启动程序中的中断USART1_IRQHandler,我们会自定义函数USART1_IRQHandler,所以只要单片机接收到数据,便会自动调用我们自定义的函数USART1_IRQHandler,在此函数我们会调用HAL库提供的中断服务函数(不需要我们编写)HAL_UART_IRQHandler(&UART1_Handler),然后关闭中断,至于为什么需要在这个函数中再次开启中断下面会讲解,自定义函数USART1_IRQHandler的代码实现如下
void USART1_IRQHandler(void)
{
u32 timeout=0;
u32 maxDelay=0x1FFFF;
#if SYSTEM_SUPPORT_OS //使用OS
OSIntEnter();
#endif
HAL_UART_IRQHandler(&UART1_Handler); //调用HAL库的中断服务函数
//以下操作是重新开启中断
timeout=0;
while (HAL_UART_GetState(&UART1_Handler)!=HAL_UART_STATE_READY)//等待就绪
{
timeout++;超时处理
if(timeout>maxDelay) break;
}
timeout=0;
//一次处理完成后重新开启中断并设置RxXferCountÎ为1
while(HAL_UART_Receive_IT(&UART1_Handler,(u8 *)aRxBuffer, RXBUFFERSIZE)!=HAL_OK)
{
timeout++; //超时处理
if(timeout>maxDelay) break;
}
#if SYSTEM_SUPPORT_OS //使用OS
OSIntExit();
#endif
}
HAL_UART_IRQHandler函数的作用是判断此次中断属于哪类中断,然后调用相对应的中断函数,实现相对应的操作。比如这里是接受完成中断,那么便会调用HAL库提供的UART_RECEIVE_IT函数,这个函数会把接受到的数据放到串口句柄成员变量pRxBuffPtr接受缓冲区中(缓冲区大小是自己设置的,在串口初始部分已经讲过);每放入一个数据记录缓冲区大小的变量便会减1,不为0便会一直接收数据,减为0后便会调用接收完成回调函数HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart),同时关闭中断,这就是为什么在自定义函数USART1_IRQHandler中需要重新开启中断。HAL_UART_RxCpltCallback函数需要我们自己编写,此函数会将接收缓冲区中的数据取出,然后通过调用HAL库提供的发送函数HAL_UART_Transmit(&UART1_Handler,&rec,1,1000)将数据发送给电脑端。自定义函数HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)的代码实现如下
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
u8 rec;
if(huart->Instance==USART1)//Èç¹ûÊÇ´®¿Ú1
{
rec = *((huart->pRxBuffPtr)-1);
HAL_UART_Transmit(&UART1_Handler,&rec,1,1000);
}
}
- 不开启中断
不开中断时,执行流程比较简单,当电脑发送数据给单片机,会触发单片机启动程序中的USART1_IRQHandler,我们会自定义函数USART1_IRQHandler,所以只要单片机接收到数据,便会自动调用我们自定义的函数USART1_IRQHandler,只是此时的这个函数的编写跟开启中断情况下有点不一样,不开中断时,我们直接把中断控制逻辑写在USART1_IRQHandler中,也就是HAL_UART_RxCpltCallback中的代码会直接写到USART1_IRQHandler中。因为不开中断时,USART1_IRQHandler函数不会调用HAL库提供的中断服务函数HAL_UART_IRQHandler,那么它是如何实现数据的接收操作呢?它是通过在USART1_IRQHandler函数中调用HAL库提供的串口接收函数(不需要我们编写,直接调用即可)HAL_UART_Receive(&UART1_Handler,&Res,1,1000),将数据保存到接收缓冲区中。USART1_IRQHandler函数的代码如下
void USART1_IRQHandler(void)
{
u8 Res;
#if SYSTEM_SUPPORT_OS //ʹÓÃOS
OSIntEnter();
#endif
if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET)) //½ÓÊÕÖжÏ(½ÓÊÕµ½µÄÊý¾Ý±ØÐëÊÇ0x0d 0x0a½áβ)
{
HAL_UART_Receive(&UART1_Handler,&Res,1,1000);
if((USART_RX_STA&0x8000)==0)//½ÓÊÕδÍê³É
{
if(USART_RX_STA&0x4000)//½ÓÊÕµ½ÁË0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//½ÓÊÕ´íÎó,ÖØпªÊ¼
else USART_RX_STA|=0x8000; //½ÓÊÕÍê³ÉÁË
}
else //»¹Ã»ÊÕµ½0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//½ÓÊÕÊý¾Ý´íÎó,ÖØпªÊ¼½ÓÊÕ
}
}
}
}
HAL_UART_IRQHandler(&UART1_Handler);
#if SYSTEM_SUPPORT_OS //ʹÓÃOS
OSIntExit();
#endif
}