这篇博客从串口通信的接口讲起,阐述原理,介绍通信方式,最后上机测试。
本篇博客主要以异步通信为例。
串口分为很多种,我们STM32学习过程中常见的就是UART/USART,前者是同步模式,后者是异步模式。还有RS485,RS232这种也是串口。我们平常使用的串口模块,大多都是类似于USB转TTL模块,为什么叫USB转TTL而不是USB转串口,因为MCU上的串口是逻辑电平(TTL或者CMOS电平),MCU和模块的通信通过逻辑电平的串口信号线直接连接。所以两种叫法,后者包含前者。前者更为常用。
下面是USART/UART对应的一般接口图
各个引脚说明如下:
请注意各个箭头方向。
引脚标识 | 引脚含义 | 备注 |
---|---|---|
TX | 串行输出信号 | MCU向对方设备发送数据 |
RX | 串行输入信号 | MCU接收对方设备发送的数据 |
nCTS | 允许发送信号 | 低电平有效,是对方设备发来的信号。如果nCTS为低电平,则表示对方设备准备好了接受数据,本机可以发送数据了;否则,不能发送数据。 |
nRTS | 请求发送信号 | 低电平有效,是发送给给对方设备的一个信号。如果本机准备好了接受数据,则将nRTS置为低电平,通知对方设备可以发送数据了 |
SCLK | 发送器输出的时钟信号 | 时钟信号线仅用于同步模式 ,USART没有这个引脚,UART有。 |
上面的图和表是串口最全的情况,是带有硬件流控制,同时又是同步模式的情况。
其实一般我们使用异步模式,并且没有硬件流控制。所以上面的5个引脚,最常用的只有RX,TX。
拿USB转TTL模块来举例,我们只需要接RX,TX,GND,VCC四个脚就行。这里RX和TX注意要交叉接,主机的TX接从机的RX,从机的TX接主机的RX。这个也很好理解,就是数据发送与接收的方向。
串口发送数据是以数据帧的方式发送数据,下图是以传输一个8为字长的数据帧的时序图。
参数说明:
数据位:
8位/9位,一般设置为8位,即8bit,即1byte。这样一帧传输1字节的有效数据。
奇偶校验位:
可以无奇偶校验位,也可以设置奇校验或者偶校验。
停止位:
1个或者2个停止位,一般设置为1个停止位。
波特率:
串行数据传输的速率,单位是bit/s,常用的波特率有9600、19200、115200等。最高波特率的计算是根据时钟来的。一个串口单元的时钟由APB1或APB2总线提供,所以挂在不同APB总线上的串口单元的最高波特率不同。
波特率 = 时钟频率 ( UART的分频因子 × 过采样次数 ) \text{波特率} = \frac{\text{时钟频率}}{(\text{UART的分频因子} \times \text{过采样次数})} 波特率=(UART的分频因子×过采样次数)时钟频率
分频因子我查了一下,使用STM32CubeMX似乎是自动配置的,我就没有管了。
不过手动配置多少的话,可以算。
比如波特率115200,时钟频率100MHz,过采样次数16
算出来的UART的分频因子大约为54.2534,四舍五入到最近的整数。即54
这一小节主要介绍一下串口传输数据的三种方式对应的函数
传输类别 | 函数名 | 功能说明 |
阻塞式传输 | HAL_UART_Transmit() | 阻塞方式发送一个缓冲区的数据,发送完成或超时后才返回 |
HAL_UART_Receive() | 阻塞方式将数据接收到一个缓冲区,接收完成或擦手后才返回 | |
中断方式传输 | HAL_UART_Transmit_IT() | 以中断方式(非阻塞式)发送一个缓冲区的数据 |
HAL_UART_Receive_IT() | 以中断方式(非阻塞方式)将指定长度的数据接收到一个缓冲区 | |
DMA方式传输 | HAL_UART_Transmit_DMA() | 以DMA方式发送一个缓冲区数据 |
HAL_UART_Receive_DMA() | 以DMA方式将指定长度的数据接收到缓冲区 | |
HAL_UART_DMAPause() | 暂停DMA传输过程 | |
HAL_UART_DMAResume() | 继续先前暂停的DMA传输过程 | |
HAL_UART_DMAStop | 停止DMA传输过程 |
上表中我只列举了函数名称,对于其中的参数并没有详细介绍,具体参数可以参考对应的驱动文件。
简单的回环测试,先接收上位机PC用XCOM发来的信息,再把数据发回去。
使用的是CH340,安装驱动。
如果出现预安装成功,但是你用USB接开发板之后没反应,不妨换一根数据线(我这里踩坑饿了),有些垃圾数据线不支持协议。
使用到的函数为
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
函数对应的参数说明如下:
UART_HandleTypeDef *huart:
串口响应对象
const uint8_t *pData:
发送/接受的数据
uint16_t Size:
发送/接收数据的大小
uint32_t Timeout:
发送或接收数据超时时间。超时后结束函数。
测试代码
uint8_t testBuf[5]="hello";
uint8_t receiveBuf[20]={0};
while (1)
{
if(HAL_UART_Receive(&huart1,receiveBuf,sizeof(testBuf),100)==HAL_OK)
{
HAL_UART_Transmit(&huart1,receiveBuf,sizeof(testBuf),100);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
使用到的函数为
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size)
函数对应的参数说明如下:
UART_HandleTypeDef *huart:
串口响应对象
uint8_t *pData:
发送/接受的数据
uint16_t Size:
发送/接收数据的大小
测试代码
uint8_t testBuf[5]="hello";
uint8_t receiveBuf[20]={0};
while (1)
{
if(HAL_UART_Receive_IT(&huart1,receiveBuf,sizeof(testBuf))==HAL_OK)
{
HAL_UART_Transmit_IT(&huart1,receiveBuf,sizeof(testBuf));
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
开启中断
原始版本,这个代码和上面的很类似,不同的地方在于硬件的连接。
uint8_t testBuf[5]="hello";
uint8_t receiveBuf[20]={0};
while (1)
{
HAL_UART_Transmit(&huart1,testBuf,sizeof(testBuf),100);
HAL_UART_Receive(&huart1,receiveBuf,sizeof(testBuf),100);
}
我这里将STM32F407ZET6的TX1直接接到RX1上了,按道理来说这样的效果使用debug来看的话,receiveBuf接收到的值应该是"hello",但是实际效果却不是这样的,我用debug来看,得到的数据却一直是h
。后面的数据根本过不来。我一开始以为是发送需要时间,我没给延时,后来给了延时也是一样的效果。
按照显示的现象,我修改了部分代码
for(int i=0;i
改完后,再用debug来看,发现数据正常了。
一直不太明白为什么,目前自己暂时认为,GPIO脚一次只能接受1bit数据,虽然每次发送了5个数据,但是只能收一个数据,这就导致只能收到h
这个数据。
后来用了个for循环,其实就是多次发送,每次接受不同位置的数据,达到我们最终想要的效果。
这个平时用的时候没有遇到第六小节的情况,因为都是借助上位机以及对应的USB转TTL做的测试。只关注了波特率,校验位等配置。没有过多考虑TX直接RX这种情况。这次出现的问题还有待确切的答案,暂时只能先这样认定。