一、实验条件
将STM32的PA9复用为串口1的TX,PA10复用为串口1的RX。STM32芯片的输出TX和接收RX与CH340的接收RX和发送TX相连(收发交叉且PCB上默认没有相连,所以需要用P3跳线帽进行手动连接),CH340的另一端通过USB口引出与USB线相接。CH340作用:RS232电平标准转USB电平标准)。再使用USB转串口线实现PC与板子的通信。
PC端需要安装CH340虚拟串口驱动,目的是为了有CH340的通信协议。在使用串口调试助手进行通信时注意一下几点。
1.发送英文字符需要用一个字符即8位,发送汉字需要两个字符即16位,如上图,发送汉字“宋”实际是发送“CB(1100 1011)CE(1100 1110)”而发送英文字符S实际是发送“53(0101 0011)”,本质上没有太大区别;
2.勾选了下方“发送新行”后,XCOM就会再你输入的需要发送的数据后自动加上一个回车(0X0D+0X0A),如果不勾选则我们在手动输入完“宋S”后还需敲一个回车键只有这样点击发送后,调试助手上方窗体才能将其显示,这是因为我们在程序的串口中断中自定义了一个数据接收协议,即只有当接受的数据以回车结尾(0X0D+0X0A),串口才认可数据接受完毕。
二、例程软件实现
1、支持printf函数 这个函数大部分无需改动 但是针对不同的串口时:要记得改USART1为对应串口
//加入以下代码,支持printf函数,而不需要选择use MicroLIB
#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
int handle;
};
FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}
#endif
2、初始化
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 aRxBuffer[RXBUFFERSIZE];//HAL库使用的串口接收缓冲
UART_HandleTypeDef UART1_Handler; //UART句柄
//初始化IO 串口1
//bound:波特率
void uart_init(u32 bound)
{
//UART 初始化设置
UART1_Handler.Instance=USART1; //USART1 让Instance指向的寄存器基地址 等于串口1的外设基地址
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,并且设置接收缓冲以及接收缓冲接收最大数据量
}
void uart_init(u32 bound)函数需要注意的点
1、HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
(1)HAL_StatusTypeDef为一个枚举类型,也就是当返回值比较多,就全枚举到HAL_StatusTypeDef里
(2)huart是UART_HandleTypeDef类型的结构体指针变量,也称为串口句柄,指向结构体里第一个成员基地址。一开始就定义了UART_HandleTypeDef UART1_Handler;
UART1_Handler为UART_HandleTypeDef类型的结构体变量,可用于引用结构体里成员,取&为第一个成员基地址
即:huart = &UART1_Handler
所以调用HAL_UART_Init函数时,括号里应该填&UART1_Handler
(3)第二个形参的位置应该填一个指针,数组名可以也看作指向数组里第零个元素的指针,但是这里把aRxBuffer强制转换为指针类型,个人认为只是一种格式规范,已上板验证过,就算没有强制转换,也能实现串口通信。(为什么是u8?因为uint8_t *pData入口参数规定的)
(4)第三个形参,表示每次接收SIZE个字节后标示接收结束,然后会进入接收回调函数。
整个接收的过程卡了我一天......最终总结为以下:
串口调试助手每发送一个帧,接收移位寄存器把这一帧里有效位给接收数据寄存器(此观点为个人理解),然后接收寄存器(RDR)接收到了数据后,产生RXNE中断,进入中断处理函数,中断处理函数对里判断到是接收中断调用 UART_Receive_IT(huart)函数
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))//判断是否为读数据寄存器非空中断
{
UART_Receive_IT(huart); //调用接收函数
return;
}
}
在接收函数里把接收到的值放pRxBuffPtr,同时RxXferCount--,直到RxXferCount减到零,调用接收回调函数。(当RxXferCount=0时表明这次已经传完,进行中断失能的处理)
if (--huart->RxXferCount == 0U)
{
/* Disable the UART Data Register not empty Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);
/* Disable the UART Parity Error Interrupt */
__HAL_UART_DISABLE_IT(huart, UART_IT_PE);
/* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
__HAL_UART_DISABLE_IT(huart, UART_IT_ERR);
/* Rx process is completed, restore huart->RxState to Ready */
huart->RxState = HAL_UART_STATE_READY;
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx complete callback*/
huart->RxCpltCallback(huart);
#else
/*Call legacy weak Rx complete callback*/
HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
return HAL_OK;
}
问题:RxXferCount、pRxBuffPtr、RxXferSize(这三个都是UART_HandleTypeDef结构体里的成员)在哪里被赋值?(pTxBuffPtr,TxXferSize 和 TxXferCount 三个变量分别用来设置串口发送的数据缓存指针,发送的数据量和还剩余的要发送的数据量)
在HAL_UART_Receive_IT()里,把形参给pRxBuffPtr、RxXferSize。(RxXferCount初始值为RxXferSize)。
我们可以看到例程里RXBUFFERSIZE=1,aRxBuffer[]容量也为1,所以RxXferCount=1,每收到1个字节就去一次接收回调函数,那难道说这个例程只能适用于接收1个字节的时候吗?
HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);
答案是否定的,这里只是一个一个的接收,在回调函数里还要及时把接收的这个字节放在数组USART_RX_BUF[USART_REC_LEN]里,USART_REC_LEN为200。
但是这样的话,我没法知道接收完成没有,所以在串口回调函数里设计了小小的通信协议。
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1)//如果是串口1
{
if((USART_RX_STA&0x8000)==0)//USART_RX_STA就是个自己定义的变量 16位
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(aRxBuffer[0]!=0x0a)USART_RX_STA=0;//接收错误,重新开始 字节
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(aRxBuffer[0]==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=aRxBuffer[0] ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
} //接收到的内容就放在USART_RX_BUF里 主函数里再决定怎么处理
}
}
}
当接收到从电脑发过来的数据,把接收到的数据保存在 USART_RX_BUF 中,同时在接收状态寄存器(USART_RX_STA)中计数接收到的有效数据个数,当收到回车(回车的表示由 2个字节组成: 0X0D 和 0X0A)的第一个字节 0X0D 时,计数器将不再增加,等待 0X0A 的到来,而如果 0X0A 没有来到,则认为这次接收失败,重新开始下一次接收。如果顺利接收到 0X0A,则标记 USART_RX_STA 的第 15 位,这样完成一次接收,并等待该位被其他程序清除,从而开始下一次的接收,而如果迟迟没有收到 0X0D,那么在接收数据超过 USART_REC_LEN 的时候,则会丢弃前面的数据,重新接收。
注意:USART_RX_STA[13:0]位用来装已接收的字节个数,可以有16384个,但是USART_RX_BUF[USART_REC_LEN]里,USART_REC_LEN为200,所以最多接收200个字节。
三、常用的串口中断处理函数
我们直接把中断控制逻辑写在中断服务函数内部,不适用中断回调函数
此时不用初始化HAL_UART_Receive_IT(&UART1_Handler, (u8 *)aRxBuffer, RXBUFFERSIZE);
直接在要开启中断的地方使用__HAL_UART_ENABLE_IT()单独开启中断即可。
调用了HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)对接收到的字符进行处理。
//串口1中断服务程序
void USART1_IRQHandler(void)
{
u8 Res;
HAL_StatusTypeDef err;
#if SYSTEM_SUPPORT_OS //使用OS
OSIntEnter();
#endif
if((__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_RXNE)!=RESET)) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{ //UART_FLAG_RXNE产生了 说明接收函数处理了数据
HAL_UART_Receive(&USART1_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
}
#endif
UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus Status, uint32_t Tickstart, uint32_t Timeout)
四、主函数
while(1)
{
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n");
HAL_UART_Transmit(&UART1_Handler,(uint8_t*)USART_RX_BUF,len,1000); //发送接收到的数据
while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET); //等待发送结束
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}else
{
times++;
if(times%5000==0)
{
printf("\r\nALIENTEK 精英STM32开发板 串口实验\r\n");
printf("正点原子@ALIENTEK\r\n\r\n\r\n");
}
if(times%200==0)printf("请输入数据,以回车键结束\r\n");
if(times%30==0)LED0=!LED0;//闪烁LED,提示系统正在运行.
delay_ms(10);
}
}
}
HAL_UART_Transmit(&UART1_Handler,(uint8_t*)USART_RX_BUF,len,1000);
1、第二个形参 为数组名USART_RX_BUF强制转换为的指针,指向数组第0个元素,指针实现自加的语法在这个发送函数里
if (huart->Init.Parity == UART_PARITY_NONE)
{
pData += 2U;
}
else
{
pData += 1U;
}
2、这个是超时,在设置的这个时间内没有发送完成,就返回超时(HAL_TIMEOUT)
如何取:
如果波特率为9600,发送一个位需要的时间为1/9600s=0.0001042s=0.1042ms,这里按数据位为8位,停止位为2位,
加起来就是10位,10个位发送所需的时间为:0.1042*10ms = 1.042ms,如果我要发送10个字节的数据,那发送这10个字节数据给接收方需要
的时间为:10*1.042ms = 10.42ms,这是算实际的发送10个字节的数据所需要的时间。我们在接收方接收数据时可以
把时间再加宽一些,让它有一点余量。让接收方能稳定的把数据从发送方接手过来,可以加个5ms,或更宽一点10ms,
加上发送10个字节所花的时间,就是15ms或20ms。
3、len 是取的USART_RX_STA的【13:0】位,总担心是个十六进制,不要担心,所有进制都会被化为二进制。
4、while(__HAL_UART_GET_FLAG(&UART1_Handler,UART_FLAG_TC)!=SET); 文章来源:https://www.toymoban.com/news/detail-535436.html
这里的传输完成指的是USART_RX_BUF[len]传输完成,而不是发送一个字节寄存器产生那个传输完成。文章来源地址https://www.toymoban.com/news/detail-535436.html
到了这里,关于HAL库 STM32 串口通信的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!