基于CubeMX的串口收发笔记
一、什么是串口通信
串口通信是一种通信协议,有些宝子们可能会疑惑什么是通信协议,那么我先说一下通信协议是什么。
通信协议就是通信双方在数据传输过程中需要共同遵守的规定。数据的发送方按照某种协议把数据调制成可以被传输的信号,接收方就可以使用相同的协议从信号中解析出数据。举个栗子~,我们人在对话的过程中,语言就可以看作是一种通信协议。说话的人会把想说的话调制在声波上进行传输,收听方就会从声波上解析出这句话。这个过程就是用到了 “语言” 这个协议。
言归正传,通过百度百科我们知道,串口通信(Serial Communications)的概念非常简单,串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送数据的同时用另一根线接收数据。它很简单并且能够实现远距离通信。比如IEEE488定义并行通行状态时,规定设备线总长不得超过20米,并且任意两个设备间的长度不得超过2米;而对于串口而言,长度可达1200米。典型地,串口用于ASCII码字符的传输。通信使用3根线完成,分别是地线、发送、接收。由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。其他线用于握手,但不是必须的。串口通信最重要的参数是波特率、数据位、停止位和奇偶校验。对于两个进行通信的端口,这些参数必须匹配。
二、三种方式使用串口通信
2.1 阻塞方式
2.1.1 工程的配置
2.1.2 工程代码(发送)
- 法1: 阻塞式发送:当要发送的数据都传输完了,这个函数才会执行结束。
/*一个参数(&huart):串口句柄
第二个参数:要发送的数据首地址,这里填入一个字符串常量
第三个参数是我们要发送的数据长度,这里填入字符串的长度
第四个参数是超时时间,单位毫秒
*/
while (1)
{
HAL_UART_Transmit(&huart1, (uint8_t*)"hello", 5, 200);
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
每隔一秒钟就会发送一次hello
-
法2: printf函数来进行串口的输出
第一步: 引用标准库的头文件
/* USER CODE BEGIN Includes */ #include "stdio.h" /* USER CODE END Includes */
第二步:重写fputc函数,printf内部就是调用这个函数来进行单个字符输出的
/* USER CODE BEGIN 0 */ int fputc(int c, FILE *stream) { uint8_t ch[1] = {c}; HAL_UART_Transmit(&huart1, ch, 1, 0xffff); return c; } /* USER CODE END 0 */ //在主函数中使用printf while (1) { printf("Hello %d\r\n", 123); HAL_Delay(1000); /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ }
2.1.3 工程代码(接收)
- 方法1:
while (1)
{
uint8_t buf[5];
HAL_UART_Receive(&huart1, buf, 3, 0xffff);//接受3个字节
HAL_UART_Transmit(&huart1, buf, 3, 0xffff);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
-
方法2:使用scanf这个函数来进行串口的接收
/* USER CODE BEGIN 0 */ int fputc(int c, FILE *stream) { uint8_t ch[1] = {c}; HAL_UART_Transmit(&huart1, ch, 1, 0xffff); return c; } int fgetc(FILE *stream) { uint8_t ch[1]; HAL_UART_Receive(&huart1, ch, 1, 0xffff); return ch[0]; } /* USER CODE END 0 */ //在主函数中 while (1) { int val = 0; scanf("%d", &val);//读入数组 printf("%d\r\n", val);//发送回来 /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ }
2.2 中断方式
2.2.1 工程配置
2.2.2 工程代码
/* USER CODE BEGIN 0 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit_IT(&huart1, buffer, 3);
HAL_UART_Receive_IT(&huart1, buffer, 3);
}
/* USER CODE END 0 */
//主函数中
/* USER CODE BEGIN 2 */
HAL_UART_Receive_IT(&huart1, buffer, 3);
/* USER CODE END 2 */
2.3 DMA方式
2.3.1 什么是DMA
DMA是单片机中的一个数据搬运的助手,它可以在内存和内存之间或者内存与外设之间进行数据的搬运。
2.3.2 工程配置
2.3.3 工程代码
/* USER CODE BEGIN 0 */
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
HAL_UART_Transmit_DMA(&huart1, buffer, 3);
HAL_UART_Receive_DMA(&huart1, buffer, 3);
}
/* USER CODE END 0 */
//主函数中
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart1, buffer, 3);
/* USER CODE END 2 */
三、不定长接收
根据上面的学习,宝子们可能会发现,只有我们发送三个字符的时候,才能进入回调。我们知道,与单片机进行通信的设备并不都是发送固定长度的数据的,而单片机需要对这些数据都做出及时的响应,这个就是不定长接收。要做到不定长接收就需要用到空闲中断。开启这个中断之后,当传输线变成空闲状态的时候,就会进入中断,这个时候我们就知道对方发送过来的数据已经传输完了,可以进行处理了。下面我们就在DMA的基础上进行修改。
3.1 工程代码(main.c)
/* USER CODE BEGIN 2 */
HAL_UART_Receive_DMA(&huart1, buffer, 3);
__HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);//开启空闲中断
/* USER CODE END 2 */
3.2 工程代码(stm32f1xx_it.c)
/* USER CODE BEGIN PV */
extern uint8_t buffer[30];
/* USER CODE END PV */
//串口1的中断函数服务中进行手动修改
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if(__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除空闲中断标志位
HAL_UART_DMAStop(&huart1);
uint8_t len = 3 - __HAL_DMA_GET_COUNTER(huart1.hdmarx);//获取DMA计数值
HAL_UART_Transmit_DMA(&huart1, buffer, len);
HAL_UART_Receive_DMA(&huart1, buffer, 3);
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
本篇文章是在b站学习后整理的笔记,希望帮助更多的宝子。