1.预备知识
通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统
通信协议:制定通信的规则,通信双方按照协议规则进行数据收发
全/半双工:通信双方能否同时进行双向通信,即收发同时进行
单工:仅支持单向通信
同步异步:是否有时钟信号帮助同步,异步通信依靠约定好的采样频率不需要时钟信号同步
多设备:总线挂载多设备
阻塞/非阻塞模式:关注的是程序在等待调用结果 (消息,返回值) 时的状态阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回;非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
阻塞通信(Blocking Communication):当进行阻塞通信时,调用者在发起一个I/O操作后会被阻塞,直到该操作完成返回才能继续执行后续代码。在阻塞状态下,调用者一般会进入休眠或等待的状态,无法进行其他任务。例如,在网络编程中,当使用阻塞套接字进行数据传输时,发送和接收操作都会阻塞当前线程,直到数据完成传输。
非阻塞通信(Non-blocking Communication):相反,当进行非阻塞通信时,调用者发起一个I/O操作后可以立即返回,并继续执行后续代码,而不需要等待操作完成。如果I/O操作不能立即完成,调用者可以通过轮询或其他方式来检查操作是否完成。常见的方法是使用非阻塞I/O函数进行通信操作,它们会立即返回一个状态或结果,告诉调用者该操作是否完成。如果操作未完成,调用者可以选择等待或进行其他任务,而不会被阻塞。
电平标准:电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
TTL电平:+3.3V或+5V表示1,0V表示0
RS232电平:-3~-15V表示1,+3~+15V表示0
RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
串口参数及时序:
波特率:串口通信的速率
起始位:标志一个数据帧的开始,固定为低电平
数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
校验位:用于数据验证,根据数据位计算得来
停止位:用于数据帧间隔,固定为高电平
USART简介:
USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里
自带波特率发生器,最高达4.5Mbits/s
可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2)
可选校验位(无校验/奇校验/偶校验)
支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN
数据模式:
HEX模式/十六进制模式/二进制模式:以原始数据的形式显示
文本模式/字符模式:以原始数据编码后的形式显示
数据包:
作用:打包分割单独的数据,方便进行多字节数据通信
数据包分割方法:额外添加包头包尾作为标志位实现数据分割打包
2.CubMX配置内容
常用波特率:串口常见的波特率为9600,14400,57600、115200等。
以9600为例,意思为1秒传输了9600bit=9600/8=1200字节
over samping:采样频率是信号传输频率的16倍
发送数据类型为uint_8
发送类型有三种:轮询,中断和DMA。轮询方式是你一遍遍的询问前台你的外卖到了吗,在这期间你无法腾出时间去干其他事情,而中断则是外卖到了,前台告诉你,然后你去拿,在此期间你是可以做其他的事情的,但是这样子一种情况就是你现在在干一些其他的紧急事情(比如说你在考试),这时候你就需要其他人帮你拿一下外卖,DMA就是充当这个角色
3.相关函数
3.1发送数据函数
HAL_StatusTypeDef HAL_UART_Transmit (UART_HandleTypeDef * husart,
uint8_t * pTxData, uint16_t Size, uint32_t Timeout);
串口发送数据的函数,单工以阻塞模式发送大量数据。
参数含义:
huart :要发送数据的串口指针
ptxData:要发送的数据,注意此处的指针形式,注意此处发送数据的大小要注意是u8类型的(0-255)
Size:发送数据的长度(字节数)
Timeout:发送数据超时时间
注意:使用本函数发送十六位或者二十四位数据的时候下面的方法是错误的:
HAL_UART_Transmit(&huart4,"222",3,2);
//发送一个字符串
u8 num = 10;
HAL_UART_Transmit(&huart4,&num,1,2);
//发送一个数字
u16 num = 0xffde;
u8 temp;
temp = num>>8;
HAL_UART_Transmit(&huart4,&temp,1,1);
temp = num;
HAL_UART_Transmit(&huart4,&temp,1,1);
//发送一个十六位的数据
HAL_StatusTypeDef HAL_UART_Transmit_IT (UART_HandleTypeDef * huart,
uint8_t * pData, uint16_t Size)
使用中断的方式发送数据,对应上面的阻塞方式发送数据
本函数的使用需要慎重考虑前后程序,要注意本函数发送方式为中断方式,如果本函数后面直接跟着用HAL_UART_Transmit函数,那么后面数据不会发送。可通过增加短延时解决
每当执行完成一次 HAL_UART_Transmit_IT() 函数之后都会调用 HAL_UART_TxCpltCallback() 发送中断回调函数。 HAL_UART_Transmit_IT() 函数主要用来打开 TXE 中断。
uint16_t numd[3];
HAL_UART_Receive(&huart4,numd,3,1000);
while(HAL_UART_Transmit_IT(&huart4,numd,3)==HAL_OK); //发送一个三字节数据
HAL_Delay(10); //不加延时以下数据无法使用阻塞方式发送
num1= num23>>8;
HAL_UART_Transmit(&huart4,&num1,1,1);
num1 = num23;
HAL_UART_Transmit(&huart4,&num1,1,1);
HAL_StatusTypeDef HAL_UART_Transmit_DMA (UART_HandleTypeDef * huart,
uint8_t * pData, uint16_t Size)
使用DMA的方式发送数据。
huart :发送数据串口指针。
pdata :发送数据数组的首地址
size :发送数据量字节长度
使用这个函数之前需要做uart函数关于DMA设置的初始化。使用本函数发送完毕之后需要清除传输完成标志关闭DMA后才能够再次调用本函数。
因为DMA发送数据可以有两种形式一种是循环发送,另一种是单次发送。如果使用单次发送的情况下,本函数发送完毕之后就无法再次通过调用的方式发送数据。如果需要通过不断调用的方法发送数据可以将清除标志等函数放置在本函数之后,中间必须要隔着其他的函数(实现延时的功能)此方法适用于发送数据量不多的情况下。如果发送的数据量大的情况需要配合DMA的发送完成中断,在发送完成中断中清除标志位等操作。
3.2接收数据函数
HAL_StatusTypeDef HAL_UART_Receive (UART_HandleTypeDef * huart,
uint8_t * pData, uint16_t Size, uint32_t Timeout)
串口接收数据的库函数,阻塞的方式接收数据。
huart :要发送数据的串口指针
pData:接收数据缓存地址,注意此处的指针形式
Size:接收数据的长度(字节数)
Timeout:数据接收等待时间,CPU等待这个时间用来接收数据。
注意本函数不会因为设置接收接收字符数和实际接收到的数据量不一致而发生冲突,接收到的数据
小于设定接收量时少的那部分补零,多于设定量时直截取需要的数据量。
uint16_t numd[3];
HAL_UART_Receive(&huart4,numd,3,1000);
HAL_UART_Transmit(&huart4,numd,3,1);
//等一秒时间接收三位数据
HAL_StatusTypeDef HAL_UART_Receive_IT (UART_HandleTypeDef * huart, uint8_t * pData, uint16_t Size)
一种中断模式接收数据的函数。
huart :接收数据串口指针。
pdata :接收区的指针
size :接受数据量字节长度,一般设置接收长度为1,每次接受一个字节的数据就进入中断处理。
注意如果设置接收数据不为1,而是某个固定的值,当串口接受的数据量不满足这个值的时候,串口中断不会被触发。一直等待接收数据满足设定值才会触发中断。如果出现接收出错可以使用HAL_USART_ErrorCallback函数来重置接收。
本函数必须在接受回调函数中再次调用才能经行下一次的接收。
即:HAL_UART_Receive_IT() 函数常被用于在 HAL_UART_RxCpltCallback() 串口接收中断回调函数中,因为中断接收函数 HAL_UART_Receive_IT() 只能触发一次接收中断,所以我们需要在中断回调函数 HAL_UART_Receive_IT() 中再调用一次中断接收函数 HAL_UART_Receive_IT()。但在外部还是需要执行一次 HAL_UART_Receive_IT() 函数的,用来打开 PE、ERR、RXNE 中断。
uint16_t numd[1];
void UART_Init()
{
.....
HAL_UART_Receive_IT(&huart4,numd,1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);//接收完成回调函数
{
HAL_UART_Receive_IT(&huart,numd,1);
}
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart,
uint8_t * pData, uint16_t Size)
使用DMA的方式接收数据。
#define SEND_BUF_SIZE 6
u8 SendBuff[SEND_BUF_SIZE]=""; //发送数据缓冲区
UART_HandleTypeDef UART1_Handler;
HAL_UART_Receive_DMA(&UART1_Handler,SendBuff,SEND_BUF_SIZE);//开启DMA传输
3.3中断回调函数
void HAL_UART_TxCpltCallback (UART_HandleTypeDef * huart)
串口发送中断回调函数,只有 HAL_UART_Transmit_IT() 函数执行成功之后才会调用,
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
当使用 HAL_UART_Receive_IT
函数启动串口接收并且接收到指定数量的数据后,HAL 库会自动调用 HAL_UART_RxCpltCallback
函数。需要开启发送中断(调用 HAL_UART_Receive_IT 函数),当串口使用中断模式发送完毕后才能自动调用本函数。
4.中断接收发送
CubeMX配置
打开中断
补充一个函数:中断方式串口接收触发中断(接收完成)所调用的函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
用这个回调函数实现中断后的动作
代码编写
在 main.c 中,while 循环前,串口初始化后,添加接收中断开启函数,这样在第一次接收到数据的时候才会触发中断,切记要添加该函数。
HAL_UART_Receive_IT(&huart1,(uint8_t*)data,sizeof(data));//data是自己定义的接收中断缓冲
在main.c下方添加中断回调函数,实现串口的发送和接收
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1) //结构体huart成员Instance
{
HAL_UART_Transmit_IT(&huart1,data,sizeof(data));//发送你输入的内容
HAL_UART_Receive_IT(&huart1,(uint8_t*)data,sizeof(data));//打开下一次串口接收中断
}
}
5.DAM接收发送
CubeMX配置
使能串口中断
Cubemax中点击DMA中的ADD,添加RX通道和TX通道
DMA设置
代码编写
在 main.c 中,while 循环前,串口初始化后,添加DMA方式接收中断开启函数,这样在第一次接收到数据的时候才会触发中断,切记要添加该函数。
//开启一次接收中断
HAL_UART_Receive_DMA(&huart1,data,sizeof(data)); //data是自己定义的接收中断缓冲
在main.c下方添加中断回调函数,实现串口的发送和接收(利用DMA收发)
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance==USART1)
{
HAL_UART_Transmit_DMA(&huart1,data,sizeof(data));
HAL_UART_Receive_DMA(&huart1,data,sizeof(data));
}
}
=========================================================================
主函数开启串口接收中断(只有开启中断才能进入中断回调函数)
uint8_t redata[5]={0};//接收缓存
uint8_t Buffer[5]="yupeng";//发送的数据
//开启一次DMA接收中断
HAL_UART_Receive_DMA(&huart1,&redata,5);
//等待DMA状态OK
DMA_HandleTypeDef DMA_Status;
while(HAL_DMA_GetState(&DMA_Status) == HAL_DMA_STATE_BUSY) HAL_Delay(1);//更正
//while(HAL_DMA_GetState(&DMA_HandleTypeDef) == HAL_DMA_STATE_BUSY) HAL_Delay(1);
//DMA发送数据
HAL_UART_Transmit_DMA(&huart1,Buffer,5);//DMA后台直接运行,所以DMA再次发送要等上次完成后再发送下一次
HAL_Delay(300);//延时等发送完成
串口接收中断回调函数
//串口收到数据回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
if(huart == &huart1){ //判断串口号
//对redata缓存接收的数据进行处理
HAL_UART_Receive_DMA(&huart1,&redata,5);//再次开启一次DMA接收中断
}
}
6.阻塞模式收发
uint8_t data=0;
while (1)
{
//串口uart1接收数据
if(HAL_UART_Receive(&huart1,&data,1,100)==HAL_OK){//接收的数据放入data
HAL_UART_Transmit(&huart1,&data,1,100);//将接收的数据再发出
}
}
7.中断接收、阻塞发送模式
使能串口中断
主函数调用函数开启串口中断
uint8_t my_uart1_redata=0; //接收数据缓存区
HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1);//开启串口接收中断
串口接收中断回调函数
//串口收到数据回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart == &huart1) { //判断串口号
HAL_UART_Transmit(&huart1,&my_uart1_redata,1,100);//把接收到的数据通过串口发送
HAL_UART_Receive_IT(&huart1,&my_uart1_redata,1); //再次开启一次中断
}
}
⭐8. DMA串口发送 和 串口中断接收
主函数开启串口接收中断
uint8_t redata[5]={0};//接收缓存
uint8_t Buffer[5]="yupeng";//发送的数据
//开启一次中断
HAL_UART_Receive_IT(&huart1,&redata,5);
//等待DMA发送状态OK
while(HAL_DMA_GetState(&DMA_HandleTypeDef) == HAL_DMA_STATE_BUSY) HAL_Delay(1);
//DMA发送数据
HAL_UART_Transmit_DMA(&huart1,Buffer,5);//DMA后台直接运行,所以DMA再次发送要等上次完成后再发送下一次
HAL_Delay(300);//延时等发送完成
串口接收中断回调函数
/串口收到数据回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
if(huart == &huart1){ //判断串口号
//对redata缓存接收的数据进行处理
HAL_UART_Receive_IT(&huart1,&redata,5);//再次开启一次中断
}
}