STM32LL库编程系列第二讲——蓝牙+USART串口通信(步骤详细、原理清晰)

第二讲——蓝牙+USART串口通信(步骤详细、原理清晰)



前言

接第一讲内容继续学习,这次使用蓝牙模块搭配开发板完成串口通行。通信流程如下:
发送流程:手机APP——>蓝牙——>开发板——>上位机
接收流程:上位机——>开发板——>蓝牙——>手机APP


一、什么是USART

USART是通用同步/异步收发器(Universal Synchronous/Asynchronous Receiver/Transmitter)的缩写。它是一种用于串行通信的接口,可以同时支持同步和异步通信。USART可以用于连接微控制器、传感器、模块和外部设备,实现数据的收发和通信。
USART是通用同步/异步收发器,可以同时支持同步和异步通信;而UART是通用异步收发器,只支持异步通信。USART可以通过硬件配置为同步通信模式,可以通过外部时钟信号进行数据传输;而UART只支持异步通信,数据传输是通过起始位、数据位、校验位和停止位组成的数据帧来实现。可以认为UART是USART的简配版。

1.同步通信和异步通信的区别

在同步通信中,发送方发送数据后需要等待接收方对数据的确认或响应,发送方和接收方需要保持一致的速度和节奏。如果接收方没有及时响应,发送方会一直等待,直到接收到响应。也就是说同步通信双方需要使用同一条时钟线。同步通信通常用于实时性要求高的场景,如音视频通话、实时游戏等。
在异步通信中,发送方发送数据后不需要等待接收方的响应,可以继续执行其他任务。接收方在接收到数据后会立即进行处理,不需要发送方的同步操作。
异步通信通常用于数据量大、延迟要求不高的场景,如文件传输、电子邮件等。

2.那经常听到的RS422、RS485、RS232串行通信又是什么呢?

USART是一种通用的串行通信模块,而RS232、RS422和RS485是特定的串行通信标准。可以理解为USART是一种接口,RS232、RS422和RS485是数据线。
比如说A机和B机都有USART,现在双方需要通信。
如果距离很近,可以直接利用杜邦线对双方USART的RX接TX、TX接RX即可通信。这是因为双方有相同接口

3.但现在双方有一定的距离,又该如何呢?

有同学会说拿一根很长的杜邦线,理论上不存在问题,但是实际中有以下弊端。
1、长距离的传输会导致信号衰减,杜邦线本身并不具备很好的抗干扰和信号传输特性,长距离传输会导致信号失真或丢失。
2、信号干扰:长线路会受到外部环境的电磁干扰,杜邦线的屏蔽性能较差,容易受到干扰影响,导致通信质量下降。
3、传输速率限制:长距离传输会受到传输速率的限制,杜邦线的传输速率有限,长距离传输时可能无法满足通信需求。
所以就需要使用RS232、RS422和RS485这种专业的数据线来通信。

4.那这三种数据线有什么区别呢?

RS232(Recommended Standard 232)用于在较短距离内(通常为50英尺)进行数据传输。RS232使用单端信号传输,一般是单双工和半双工。通常用于连接计算机、调制解调器、打印机等设备。
RS422(Recommended Standard 422)用于在较长距离内(最高可达4000英尺)进行高速数据传输。RS422使用差分信号传输,可以抵抗噪声和干扰,一般是全双工。适用于工业环境中的长距离通信。
RS485(Recommended Standard 485)类似于RS422,也用于在长距离内进行数据传输,也是全双工通信。与RS422不同的是,RS485支持多点通信,可以连接多个设备进行通信。适用于工业控制系统、仪器仪表等领域。
这就是三种数据线的区别,对于上述说的单双工、半双工和全双工不了解的可以上网查阅。

5.那USART怎么和这些数据线连接呢?

电路板上一般用RS422/485或RS232芯片生成接口,一段接入USART的RX和TX,另一端引出A、B(互补输出信号)和Y、Z(互补输入信号),将A、B、Y、Z接入通信接口,就可以接入这些数据线了。感兴趣可以找一些这些芯片,看看芯片手册。
相信到这,同学们对串口通行有了一些理解,虽然我的解释不够标准,但应该是通俗易懂的,初学的话呢前后都有大致的了解,流程都清楚我觉得就够了。这要学起来不会懵,知道自己在干嘛。接下来就是正式使用了。我这实例,通信距离很断,可以理解位用杜邦线通信,但是杜邦线连接不牢靠,所以一般用CH340转串口芯片转成了USB,这样就行了。

二、使用CubeMX建立工程

使用CubeMX的注意细节这里不再重复了,不清楚可以去看第一讲。
第一讲——Delay精准延时函数(详细,适合新手)
进入工程页面后,配置好RCC和SYS后进入connectivity
将USART1的模式使能为Asynchronous,使用异步通信(我的开发板引出的USB串口接在USART1上,同学们可以根据实际情况更改)
在这里插入图片描述
可以看到默认的波特率为115200、8位数据流、不效验、1个停止位。这里很通用的配置,不需要更改,不理解的同学可以看看正点原子的编程手册类书籍,都有很多讲解。
进入NVIC Settings勾选USART1中断
在这里插入图片描述
用同样方式配置UART5(我的蓝牙模块串口接在UART5引脚上,可根据实际情况更改)。UART5的波特率要和自己蓝牙模块通讯的波特率保持一致,不然传输的是乱码。UART5和USART1的波特率可以不一致,但那样快的就要等慢的,一致最好。
在这里插入图片描述
我这里还将配置了引脚PF9和PF10,用于控制LED灯的亮灭,可以不操作
在这里插入图片描述
可以在User Label对引脚进行宏定义,如下
在这里插入图片描述
在这里插入图片描述
进入NVIC,可以将USART1和UART5抢占式中断设为1,低于系统保留中断就好
在这里插入图片描述
配置好了,全部选为LL库生成工程(中间不清楚参考第一讲)
在这里插入图片描述

三、keil工程代码编写

1.准备工作

首先加入delay.h和printf.h文件,delay.h第一讲详细介绍了,printf.h对printf重定义了,可以在工程中使用printf函数不报错。printf.h文件代码参考了正点原子。简单说明以下printf相关知识。
代码中如果直接使用printf会进入半主机模式,这是一种调试模式,下载后代码无法运行,但是用调试器进入Debug可以运行。如果想要下载后独立环境就能运行,需要把printf对字符ch处理后写入文件f,最后使用fputc将文件f输出到显示设备。这里需要做的就是吧fputc函数重新定向输出,同时避免进入半主机模式。
要避免进入半主机模式,主要有两种方式,一是使用MIcroLib(微库),在keil中勾选Use MicroLIB即使用MIcroLib
在这里插入图片描述
但使用MIcroLib有很多弊端和注意事项,如main函数不能声明带参数,也无须返回;不支持stdio,除了无缓冲的stdin、stdout和syderr;微库不支持操作系统函数;微库不支持可选的单或两区存储模式;微库只提供分离的堆和栈两区存储模式等,裁减了很多函数,而且还有很多东西不支持。如果原来工程可以跑,选择MicroLib后却突然不行了,这是很常见的。
所以一般使用第二种方式:通过代码取消ARM的半主机工作模式,就是需要重新补充定义fputc,代码如下(再次声明,代码来自正点原子),这也是printf.c文件代码

#include "printf.h"	  
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 
	/* Whatever you require here. If the only file you are using is */ 
	/* standard output using printf() for debugging, no file handling */ 
	/* is required. */ 
}; 
/* FILE is typedef’ d in stdio.h. */ 
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 = (uint8_t) ch;      
	return ch;
}
#endif 

printf.h如下

#ifndef __PRINTF_H
#define __PRINTF_H 
#include "stdio.h"
#include "stm32f407xx.h"
#endif	

不需要把fputc函数引用出,工程内其他.c文件引用头文件#include "printf.h" 就可以使用printf()函数了,函数使用说明和C语言的printf一致。
注意:要正确把.c文件加入工程和说明.h头文件行,不了解的同学可以看我工程配置,末尾会免费提供下载地址

2.加入使能

首先进入usart.c文件,找到函数void MX_UART5_Init(void)void MX_USART1_UART_Init(void)在末尾加入函数
void LL_USART_EnableIT_RXNE(USART_TypeDef *USARTx)
如图
在这里插入图片描述
在这里插入图片描述
可以进入这个函数看到函数定义(启用RX非空中断)
在这里插入图片描述
STM32F4 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包含了 TDR 和 RDR。当向 DR 寄存器写数据的时候,实际是写入 TDR,串口就会自动发送数据;当收到数据,读 DR 寄存器的时候,实际读取的是 RDR。TDR 和 RDR
对外是不可见的,所以我们操作的就只有 DR 寄存器,该寄存器的描述如图所示:
在这里插入图片描述
可以看出,虽然是一个 32 位寄存器,但是只用了低 9 位(DR[8:0]),其他都是保留。DR[8:0]为串口数据,包含了发送或接收的数据。由于它是由两个寄存器(TDR 和 RDR)组成的,一个给发送用(TDR),一个给接收用(RDR),该寄存器兼具读和写的功能。TDR 寄存器提供了内部总线和输出移位寄存器之间的并行接口。RDR 寄存器提供了输入移位寄存器和内部总线之间的并行接口。
所以我们需要使能DR非空中断,当有数据进入DR就会进入中断,告诉我们有数据进来了,我们可以在中断将数据提取出来,从而完成数据接收;同样我们将数据写入DR寄存器,USART就可以自动将数据发送出去。

2.中断函数编写

在工程文件Application/User/Core中双击stm32f4xx_it.c文件,进入中断函数文件,里面都是中断处理函数,前面的都是系统默认中断,翻到最后找到void USARTx_IRQHandler(void)这是我们开启的串口中断
在这里插入图片描述
写入函数如下

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
	if(LL_USART_IsActiveFlag_RXNE(USART1) == SET)
	{
		LL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
		uart1_read = LL_USART_ReceiveData8(USART1);
		LL_USART_TransmitData8(UART5,uart1_read);
	}
	
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

/**
  * @brief This function handles UART5 global interrupt.
  */
void UART5_IRQHandler(void)
{
  /* USER CODE BEGIN UART5_IRQn 0 */
	if(LL_USART_IsActiveFlag_RXNE(UART5) == SET)
	{
		LL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);
		BLE_read = LL_USART_ReceiveData8(UART5);
		LL_USART_TransmitData8(USART1,BLE_read);
	}
  /* USER CODE END UART5_IRQn 0 */
  /* USER CODE BEGIN UART5_IRQn 1 */
  /* USER CODE END UART5_IRQn 1 */
}

void USART1_IRQHandler(void)中断函数为例说明
if(LL_USART_IsActiveFlag_RXNE(USART1) == SET)
判断中断是否是非空中断,虽然在这里判不判断无所谓,因为只使能了这一个中断。但是使能了USART多种中断,都会进入该中断服务函数,所以多中断使能是一定要判断的。
LL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
对IO口电平取反,就是每运行一个PF9电平就会变成对应状态,这里的宏都是cubemx配置时自己写的,具体说明在main.h中
uart1_read = LL_USART_ReceiveData8(USART1);
读取USART1的DR寄存器,也就是将收到的数据(该数据由上位机发送)读出来存到全局变量uart1_read中,读DR寄存器会自动清除该中断标志位,所以不用手动清除
LL_USART_TransmitData8(UART5,uart1_read);
将uart1_read的数据发送到UART5的DR寄存器,也就是将数据发送蓝牙,通过手机连接蓝牙接口就可以接收了。
在stm32f4xx_it.c起始处定义全局变量

/* USER CODE BEGIN Includes */
unsigned char uart1_read,BLE_read;
unsigned char uart1_NC = 0;
unsigned char BLE_NC = 0;
/* USER CODE END Includes */

注意:函数最后写到USER CODE BEGIN xx和USER CODE END xx之间,这样可以避免cubemx重新生成时被清除

3.mian函数编写

在while循环前加入下列函数

  /* USER CODE BEGIN 2 */
	printf("启动蓝牙\r\n");
	LL_GPIO_SetOutputPin(LED0_GPIO_Port,LED0_Pin);
	LL_GPIO_SetOutputPin(LED1_GPIO_Port,LED1_Pin);
  /* USER CODE END 2 */

printf("启动蓝牙\r\n");重定义后,printf会发送字符到上位机
LL_GPIO_SetOutputPin(LED0_GPIO_Port,LED0_Pin);对PF9置高电平,灯低电平亮,这里“关灯”
while循环可以什么都不放,这样工程就好了,将工程下载到开发板,LL库是不是即操作便捷又逻辑清晰

4视频验证

蓝牙+串口通信验证

四、工程资料下载链接

链接:https://pan.baidu.com/s/1OYPmlCq3WiSYb1uonn8OIg?pwd=1234
提取码:1234

五、尾言

有几点这里要说明以下
1、中断服务函数尽量不要耗时过多,最好进去就出来。因为当有多个中断,中断之间互相抢断,如果在中断服务函数中处理数据,容易造成数据丢失,其次可以看出我这工程main中的while循环是一个空循环,浪费了CPU资源,最好是在中断服务函数里只写标志位,然后再while循环里if判断该标志位,再进行数据处理。这里之所以没这么写,担心操作过多,难以理解。
2、仔细的同学会发现每次发送数据是8bit,也就是一个字节,每发送一个字节就要进入中断一次。而一个汉字就占有两个字节,那么发送一句话就要频繁进入中断,这对CPU的消耗是很大的,不利于大工程运行。stm32提供了一种解决方式,那就是有DMA进行数据转移,关于DMA后面再讲。
最后,希望这篇文章对同学们能有所帮助,有错误纰漏之处,还请见谅(应该有些错别字,不影响阅读就好)。

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在使用STM32LL进行串口接收中断时,首先需要对串口进行初始化设置。可以使用`LL_USART_Init()`函数对串口进行初始化,设置波特率、数据位、停止位、校验位等参数。 接下来,需要使能串口接收中断。可以使用`LL_USART_EnableIT_RXNE()`函数来使能接收中断。该函数会将接收寄存器非空中断打开,当接收寄存器中有数据时,中断标志位将被置位,触发中断。 在串口接收中断的处理函数中,可以使用`LL_USART_ReceiveData8()`函数来读取接收寄存器中的数据。该函数会返回接收到的8位数据。可以将读取到的数据保存到缓冲区中以便后续处理。 在处理完接收到的数据后,需要清除接收中断标志位,以便下一次接收中断触发。可以使用`LL_USART_ClearFlag_RXNE()`函数清除接收中断标志位。 在主程序中,可以调用`LL_USART_IsActiveFlag_ORE()`函数来检测是否发生了接收溢出错误。如果接收溢出错误发生,则需要调用`LL_USART_ClearFlag_ORE()`函数清除溢出错误标志位。 需要注意的是,在使用STM32LL进行串口接收中断时,需要根据具体的硬件和需求进行相关的配置和判断。可以查阅STM32相关的参考手册和官方文档来获取更详细的信息和使用示例。 ### 回答2: stm32ll提供了用于串口接收中断的函数。在使用串口接收中断功能时,需要先初始化串口并设置中断优先级。以下是一个简单的示例代码: 1. 首先,需要在代码中引入必要的头文件: #include "stm32l4xx_ll_usart.h" #include "stm32l4xx_ll_gpio.h" #include "stm32l4xx_ll_rcc.h" #include "stm32l4xx_ll_utils.h" 2. 然后,在初始化函数中对串口进行配置: void USART_Config(void) { // 使能串口时钟 LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_USART1); // 配置串口引脚 LL_GPIO_InitTypeDef GPIO_InitStruct = {0}; RCC->AHB2ENR |= RCC_AHB2ENR_GPIOCEN; GPIO_InitStruct.Pin = LL_GPIO_PIN_5 | LL_GPIO_PIN_6; GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE; GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; GPIO_InitStruct.Alternate = LL_GPIO_AF_7; LL_GPIO_Init(GPIOC, &GPIO_InitStruct); // 配置串口参数 LL_USART_InitTypeDef USART_InitStruct = {0}; USART_InitStruct.BaudRate = 115200; USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B; USART_InitStruct.StopBits = LL_USART_STOPBITS_1; USART_InitStruct.Parity = LL_USART_PARITY_NONE; USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX; USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE; LL_USART_Init(USART1, &USART_InitStruct); // 使能串口接收中断 LL_USART_EnableIT_RXNE(USART1); // 配置串口中断优先级 NVIC_SetPriority(USART1_IRQn, 0); NVIC_EnableIRQ(USART1_IRQn); // 使能串口 LL_USART_Enable(USART1); } 3. 最后,实现串口接收中断函数USART1_IRQHandler来处理接收到的数据: void USART1_IRQHandler(void) { if(LL_USART_IsActiveFlag_RXNE(USART1) && LL_USART_IsEnabledIT_RXNE(USART1)) { // 从串口缓冲区中读取接收到的数据 uint8_t receivedData = LL_USART_ReceiveData8(USART1); // 在这里进行接收到数据的处理 // ... // 清除接收中断标志位 LL_USART_ClearFlag_RXNE(USART1); } } 通过上述步骤,我们就可以实现基于stm32ll的串口接收中断功能。在中断函数中,我们可以处理接收到的数据,并及时清除接收中断标志位,保证下一次中断可以正常触发。 ### 回答3: STM32LL是针对低功耗微控制器的一套函数,可以方便地使用其提供的功能进行开发。而串口是常用的数据通信方式之一,通过串口接收中断可以实现在数据接收时触发中断处理函数,提高系统的实时性和效率。 使用STM32LL进行串口接收中断的步骤如下: 1. 初始化串口:首先需要通过LL_USART_Init函数对串口进行初始化,设置波特率、数据位、校验位等参数,并使能串口。 2. 配置中断:使用LL_USART_EnableIT_RXNE函数使能串口接收中断(RXNE中断),然后通过LL_USART_ClearFlag_IDLE函数清除空闲线路检测标志位。 3. 编写中断处理函数:在串口接收中断触发时,会进入中断处理函数。用户需要在中断处理函数中编写实际的数据接收和处理逻辑。可以使用LL_USART_ReceiveData8或LL_USART_ReceiveData9函数读取接收到的数据,并进行后续的处理操作。 4. 中断优先级和使能:根据实际需求,可以使用LL_NVIC_SetPriority和LL_NVIC_EnableIRQ函数设置中断优先级,并使能NVIC中断。 5. 进入主循环:在主循环中,可以进行其他的任务处理或休眠等操作,当串口接收到数据时,会触发中断,执行中断处理函数。 需要注意的是,在串口接收中断处理函数中,为了避免数据丢失或溢出,应尽可能及时读取接收到的数据,并进行相应处理。此外,应尽量减少中断处理函数的执行时间,避免影响系统的实时性和效率。 总之,使用STM32LL进行串口接收中断的步骤包括初始化串口、配置中断、编写中断处理函数、设置中断优先级和使能,并在主循环中处理其他任务。通过串口接收中断,可以实现实时接收和处理数据的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值