USART/UART通信是STM32的一个非常重要的外设,是一种通用串行数据总线,可实现全双工通信。
UART:通用异步收发器,
USART:通用同步/异步收发器,
可以看出USART比UART多了一个同步模式。
异步通信:数据通常以字符或者字节为单位组成字符帧传送。字符帧由发送端逐帧发送,通过传输线被接收设备逐帧接收。发送端和接收端可以由各自的时钟来控制数据的发送和接收,这两个时钟源彼此独立,互不同步。
同步通信:是把许多字符组成一个信息组,这样,字符可以一个接一个地传输,但是,在每组信息(通常称为信息帧)的开始要加上同步字符,在没有信息要传输时,要填上空字符,因为同步传输不允许有间隙。同步方式下,发送方除了发送数据,还要传输同步时钟信号,信息传输的双方用同一个时钟信号确定传输过程中每1位的位置。(SPI就是同步通信方式)
异步通信和同步通信的优缺点
1.异步通信:
异步通信是按字符传输的。每传输一个字符就用起始位来进来收、发双方的同步。不会因收发双方的时钟频率的小的偏差导致错误。
这种传输方式利用每一帧的起、止信号来建立发送与接收之间的同步。特点是:每帧内部各位均采用固定的时间间隔,而帧与帧之间的间隔时随即的。接收机完全靠每一帧的起始位和停止位来识别字符时正在进行传输还是传输结束。
同步通信方式:
进行数据传输时,发送和接收双方要保持完全的同步,因此,要求接收和发送设备必须使用同一时钟。
优点是可以实现高速度、大容量的数据传送;缺点是要求发生时钟和接收时钟保持严格同步,同时硬件复杂。
不管是异步通信还是同步通信都需要进行同步,只是异步通信通过传送字符内的起始位来进行同步,而同步通信采用共用外部时钟来进行同步。所以,可以说前者是自同步,后者是外同步。
对于USART来说大家几乎都用的异步通信。在STM32CubeMX中配置串口。
图中的在Connectivity一栏中可以看到F407ZG有6个串口其中4个USART,2个UART,这里来配置USART1,单击选中USART1,
在Mode里面
1、Disable 关闭串口
2、Asynchronous 异步模式
3、Synchronous 同步模式
4、Single Wire (Half-Duplex) 单线半双工模式,
5、Multiprocessor Communication 多处理器通信模式
6、IrDA 红外通讯
7、LIN 局域互联网模式
8、SmartCard 智能卡模式
Hardware Flow Control (RS232)是串口工作在异步模式下的选项,配置是否需要硬流控RTS 、CTS ;
RTS (Require ToSend,发送请求)为输出信号,用于指示本设备准备好可接收数据,低电平有效,低电平说明本设备可以接收数据。
CTS (Clear ToSend,发送允许)为输入信号,用于判断是否可以向对方发送数据,低电平有效,低电平说明本设备可以向对方发送数据。
假如串口1和串口2相互通信,都开了硬流控,那么就是串口1的RTS接串口2的CTS,串口1的CTS接串口2的RTS,
上图看串口的异步通信不带硬流控,异步通信的传输格式由起始位、数据位、奇偶校验位、停止位组成所以在Parameter Settings 里我们可以设置串口的一些参数:
Baud Rate :波特率,
Word Length:数据位
Parity :校验位
Stop Bits : 停止位
Data Direction :数据方向
Over Sampling:过样率
选中波特率。再点击右上角的感叹号,会在下方给出当前串口所支持的波特率区间。
在NVIC Settings里面 使能中断
点击这里可以设置中断的优先级,这里我们就用了一个串口,所以默认就行。
在参考手册里我们可以看到,串口1的时钟由APB2提供。
这里APB2外设时钟最大为84MHz,这里分屏器配置2,APB2时钟采用最大值。配置完后点击生成。
开始实例,通过上位机给串口1发数据,串口1在把收到的数据返回给上位机。
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();
}
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
}
上面是STM32CubeMX生成的有关USART1配置的代码,
写我们自己的代码:
红色框里是STM32CubeMX生成的一些初始化程序。蓝色框里自己添加的
HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, 1);这个函数的作用是开启接收中断,aRxBuffer这个是我们定义的一个接收缓冲区,uint8_t aRxBuffer[];1是可接收的最大数据量,这里也就是当接收到1个字节后,就会进入中断函数。这里这样设置,是因为我们串口不知道一包会收到多少个数据,所以设置为1方便后面处理函数的编写。
uint8_t这个是无符号的char型,在stdint.h这个头文件里有宏定义
再来看串口的中断函数,在STM32CubeMX中使能USART1的中断,会在stm32f4xx_it.c文件里帮你生成中断函数,
HAL库我们采用回调函数去处理收到的数据,所以在中断函数里我们不需要做任何处理。
回调函数:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1)//如果是串口1
{
if((USART_RX_STA&0x8000)==0)//接收未完成
{
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>(200-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, 1);
}
这是中断回调函数,这个HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);函数需要自己写,不会自动生成,
if(huart->Instance==USART1)这个回调函数是几个串口共用的,所以在这里需要判断是哪个串口接收到了数据,这个接收函数是借用原子哥历程的,在这里我们用了回车键作为发送结束标志,其实这样做有弊端,有些数据里面会夹杂回车但他一包数据并没有完。一般我们使用定时器来做这个结束标识的,在设定的时间内再没有收到数据,那我就认为你这包数据发送完了。
USART_RX_STA,接收标志,有两个作用,一个是计数,记录当前接收了多少个数据了。一个是当判断一包数据接收完了后我们会将USART_RX_STA最高位置1,所以计数的最大值不是65535,而是32767,可根据自己实际接收的一包数据长度来定义USART_RX_STA,在这个历程里这个够用了。
USART_RX_BUF[200];这里是存放数据的缓冲区,这里缓冲区的大小最好也定义成一个常量,方便后期修改等,
这里当接收到数据先去检查一下USART_RX_STA最高位,在不为1时我们才会继续处理,为1说明缓冲区有数据没有处理(我们在处理完缓冲区的数据后会将其最高位置0),然后判断一下是不是回车(在上面的函数回车为结束标志),不是的就将数据放入缓冲区,
USART_RX_BUF[USART_RX_STA&0X3FFF]=aRxBuffer[0] ;
USART_RX_STA++;
if(USART_RX_STA>(200-1))USART_RX_STA=0;这里当接收的数据超过缓冲区的容量时,会把这包数据当成一包错误的数据丢掉,(这里我们是处理完一包数据再去接收,所以这样写,当你是不定时在缓冲区查找你要的数据时,可以做一个环形接收,就是缓冲区满了将位置指针指到开头再去继续接收)
HAL_UART_Receive_IT(&huart1, (uint8_t *)aRxBuffer, 1);这一句很重要,这是清除中断标志,如果不清楚中断标志,程序就会在中断里出不去。
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(USART_RX_STA&0x8000)
{
len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
printf("\r\n您发送的消息为:\r\n");
HAL_UART_Transmit(&huart1,(uint8_t*)USART_RX_BUF,len,1000); //发送接收到的数据
while(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TC)!=SET); //等待发送结束
printf("\r\n\r\n");//插入换行
USART_RX_STA=0;
}
}
这里我们将收到的数据再发送出来(这段代码也是借用原子哥的历程里面的)。
讲一下printf();函数,很好用的一个函数,大家大多叫他打印函数。
在usart.c文件里添加#include "stdio.h"头文件及下面代码
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);//这里串口底层发送函数用的是HAL库
return ch;
}
再看看这里有没有打钩。
这里是将USART1绑在这个函数里,可以根据自己实际情况修改用哪个串口。这样就可以用这个函数了
printf("\r\n您发送的消息为:\r\n");这句代码就是将“您发送的消息为:”这句话通过USART1发送出来,
再来看上面的代码,检测USART_RX_STA最高位有没有置1,有就说明有需要处理的数据,
通过&运算获得长度,
然后通过函数HAL_UART_Transmit(&huart1,(uint8_t*)USART_RX_BUF,len,1000); 将收到的数据发出来。后面的1000,是超时时间。在处理完数据后将USART_RX_STA清0,接收下一包数据。
下载程序验证!