STM32F103串口通信的配置与实现

STM32F103系列

STM32微控制器提供了灵活的串行通信接口,适用于多种通信需求。本文将介绍如何使用STM32的标准固件库来配置并实现串口通信。



前言

STM32微控制器提供了灵活的串行通信接口,适用于多种通信需求。本文将介绍如何使用STM32的标准固件库来配置并实现串口通信。


一、步骤

1.1 步骤一:串口初始化

串口的初始化是串口通信的基础。以下是串口2的初始化函数Usart2_Init,它设置了串口的基本参数,如波特率、工作模式等。

void Usart2_Init(unsigned int baud) {
    // 定义GPIO初始化结构体,用于配置GPIO
GPIO_InitTypeDef gpio_initstruct;

// 定义USART初始化结构体,用于配置USART
USART_InitTypeDef usart_initstruct;

// 定义NVIC(嵌套向量中断控制器)初始化结构体,用于配置中断
NVIC_InitTypeDef nvic_initstruct;

// 启用GPIOA的时钟,GPIOA位于APB2总线上
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

// 启用USART2的时钟,USART2位于APB1总线上
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);

// 设置GPIOA的第2脚(PA2)为串口2的发送引脚(TXD)
gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;      // 设置为复用推挽输出模式
gpio_initstruct.GPIO_Pin = GPIO_Pin_2;            // 指定为第2脚
gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;    // 设置速度为50MHz
GPIO_Init(GPIOA, &gpio_initstruct);               // 应用设置

// 设置GPIOA的第3脚(PA3)为串口2的接收引脚(RXD)
gpio_initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 设置为浮空输入模式
gpio_initstruct.GPIO_Pin = GPIO_Pin_3;             // 指定为第3脚
gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;     // 速度同样设置为50MHz
GPIO_Init(GPIOA, &gpio_initstruct);                // 应用设置

// 配置USART2
usart_initstruct.USART_BaudRate = baud;                     // 设置波特率
usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 无硬件流控
usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 同时启用接收和发送
usart_initstruct.USART_Parity = USART_Parity_No;             // 无校验位
usart_initstruct.USART_StopBits = USART_StopBits_1;          // 1位停止位
usart_initstruct.USART_WordLength = USART_WordLength_8b;     // 数据位数为8位
USART_Init(USART2, &usart_initstruct);                       // 应用USART设置

// 使能USART2
USART_Cmd(USART2, ENABLE);

// 使能USART2接收中断
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); // USART_IT_RXNE中断在接收到数据时触发

// 配置USART2中断
nvic_initstruct.NVIC_IRQChannel = USART2_IRQn;              // 指定中断通道为USART2
nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;                // 启用该中断通道
nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0;      // 设置抢占优先级
nvic_initstruct.NVIC_IRQChannelSubPriority = 0;             // 设置子优先级
NVIC_Init(&nvic_initstruct);                                 // 应用NVIC设置

}

在这个函数中,我们首先设置GPIO引脚模式,配置PA2为发送引脚(TXD),PA3为接收引脚(RXD)。然后,我们设置串口参数,如波特率、工作模式等。最后,使能串口2,并配置接收中断。

1.2 步骤二:数据发送与格式化打印

数据发送是串口通信的关键操作。我们提供了两个函数:UsartPrintf用于格式化输出,Usart_SendString用于发送字符串。

void UsartPrintf(USART_TypeDef *USARTx, char *fmt,...)
{
    unsigned char UsartPrintfBuf[296]; // 创建一个字符数组,用于存储格式化后的字符串
    va_list ap; // 定义一个va_list类型的变量,用于访问不定长参数
    unsigned char *pStr = UsartPrintfBuf; // 指针pStr指向格式化字符串的开头

    va_start(ap, fmt); // 初始化ap,使其指向第一个不定长参数
    vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), fmt, ap); // 将格式化的数据写入UsartPrintfBuf
    va_end(ap); // 结束访问不定长参数列表

    while(*pStr != 0) // 遍历格式化后的字符串,直到遇到字符串结束符
    {
        USART_SendData(USARTx, *pStr++); // 发送当前字符,然后指针递增
        while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET); // 等待当前字符发送完成
    }
}


void Usart_SendString(USART_TypeDef *USARTx, unsigned char *str, unsigned short len)
{
    unsigned short count = 0; // 初始化计数器

    for(; count < len; count++) // 遍历要发送的数据长度
    {
        USART_SendData(USARTx, *str++); // 发送数据:发送str指向的字符,然后指针递增
        while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET); // 等待发送完成,检查发送完成标志位
    }
}

UsartPrintf函数利用可变参数列表来实现类似C标准库中的printf功能,而Usart_SendString则简单地发送一个字符串。

1.3 步骤三:接收数据处理

数据的接收同样重要。我们使用LORA_WaitRecive函数来处理接收到的数据,并通过检查接收计数器lora_cnt来确定数据是否完整接收。

_Bool LORA_WaitRecive(void)
{
    if(lora_cnt == 0)                           // 检查接收计数器(lora_cnt)是否为0,如果为0,表示目前没有数据正在接收
        return REV_WAIT;                        // 返回REV_WAIT(代表等待接收状态),表示当前没有数据接收

    if(lora_cnt == lora_cntPre)                 // 检查当前接收计数器(lora_cnt)是否与上次检查时的值(lora_cntPre)相同
    {
        lora_cnt = 0;                           // 如果相同,将接收计数器重置为0,表示数据接收已完成
        return REV_OK;                          // 返回REV_OK(代表接收完成状态),表示数据接收已完成
    }

    lora_cntPre = lora_cnt;                     // 如果lora_cnt与lora_cntPre不同,更新lora_cntPre的值为当前lora_cnt的值
    return REV_WAIT;                            // 返回REV_WAIT,表示数据仍在接收中,接收尚未完成
}

此函数用于检测数据是否接收完成。如果接收计数器lora_cnt与上一次检测的值lora_cntPre相同,则认为数据接收完成。

1.4 步骤四:中断处理

最后,我们需要处理串口中断,以接收到来的数据。这是通过USART2_IRQHandler函数实现的

void USART2_IRQHandler(void)
{
    // 检查USART2的接收中断状态(USART_IT_RXNE)是否被触发
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断
    {
        // 如果接收缓冲区的计数器(lora_cnt)超过了缓冲区大小,重置计数器,防止缓冲区溢出
        if(lora_cnt >= sizeof(lora_buf)) lora_cnt = 0; //防止串口被刷爆

        // 从USART2的数据寄存器(DR)中读取一个字节的数据,并将其存储到接收缓冲区(lora_buf)中
        // 同时,增加接收缓冲区的计数器(lora_cnt)
        lora_buf[lora_cnt++] = USART2->DR;

        // 清除USART2的接收中断标志(USART_FLAG_RXNE),以便能够接收下一个字节的数据
        USART_ClearFlag(USART2, USART_FLAG_RXNE);
    }
}

在这个中断处理函数中,我们读取接收到的数据,并将其存储在接收缓冲区lora_buf中。

二、代码

代码如下(示例):

/*
************************************************************
*	函数名称:	Usart2_Init
*
*	函数功能:	串口2初始化
*
*	入口参数:	baud:设定的波特率
*
*	返回参数:	无
*
*	说明:		TX-PA2		RX-PA3
************************************************************
*/
void Usart2_Init(unsigned int baud)
{

	GPIO_InitTypeDef gpio_initstruct;
	USART_InitTypeDef usart_initstruct;
	NVIC_InitTypeDef nvic_initstruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
	
	//PA2	TXD
	gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
	gpio_initstruct.GPIO_Pin = GPIO_Pin_2;
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_initstruct);
	
	//PA3	RXD
	gpio_initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	gpio_initstruct.GPIO_Pin = GPIO_Pin_3;
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_initstruct);
	
	usart_initstruct.USART_BaudRate = baud;
	usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		//无硬件流控
	usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;						//接收和发送
	usart_initstruct.USART_Parity = USART_Parity_No;									//无校验
	usart_initstruct.USART_StopBits = USART_StopBits_1;								//1位停止位
	usart_initstruct.USART_WordLength = USART_WordLength_8b;							//8位数据位
	USART_Init(USART2, &usart_initstruct);
	
	USART_Cmd(USART2, ENABLE);														//使能串口
	
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);									//使能接收中断
	
	nvic_initstruct.NVIC_IRQChannel = USART2_IRQn;
	nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;
	nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0;
	nvic_initstruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&nvic_initstruct);

}
/*
************************************************************
*	函数名称:	UsartPrintf与Usart_SendString选其一
*
*	函数功能:	格式化打印
*
*	入口参数:	USARTx:串口组
*				fmt:不定长参
*
*	返回参数:	无
*
*	说明:		
************************************************************
*/
void UsartPrintf(USART_TypeDef *USARTx, char *fmt,...)
{

	unsigned char UsartPrintfBuf[296];
	va_list ap;
	unsigned char *pStr = UsartPrintfBuf;
	
	va_start(ap, fmt);
	vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), fmt, ap);							//格式化
	va_end(ap);
	
	while(*pStr != 0)
	{
		USART_SendData(USARTx, *pStr++);
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
	}
}
/*
************************************************************
*	函数名称:	Usart_SendString与UsartPrintf选其一
*
*	函数功能:	串口数据发送
*
*	入口参数:	USARTx:串口组
*				str:要发送的数据
*				len:数据长度
*
*	返回参数:	无
*
*	说明:		
************************************************************
*/
void Usart_SendString(USART_TypeDef *USARTx, unsigned char *str, unsigned short len)
{

	unsigned short count = 0;
	
	for(; count < len; count++)
	{
		USART_SendData(USARTx, *str++);									//发送数据
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);		//等待发送完成
	}

}

调用代码如下(示例):

unsigned char lora_buf[ESP_RX_MAX];
unsigned short lora_cnt = 0, lora_cntPre = 0;
#define REV_OK		0	//接收完成标志
#define REV_WAIT	1	//接收未完成标志

#define ESP_RX_MAX	2048//接收最大缓存

//==========================================================
//	函数名称:	LORA_Clear
//
//	函数功能:	清空缓存
//
//	入口参数:	无
//
//	返回参数:	无
//
//	说明:		
//==========================================================
void LORA_Clear(void)
{

	memset(lora_buf, 0, sizeof(lora_buf));
	lora_cnt = 0;

}
//==========================================================
//	函数名称:	LORA_WaitRecive
//
//	函数功能:	等待接收完成
//
//	入口参数:	无
//
//	返回参数:	REV_OK-接收完成		REV_WAIT-接收超时未完成
//
//	说明:		循环调用检测是否接收完成
//==========================================================
_Bool LORA_WaitRecive(void)
{

	if(lora_cnt == 0) 							//如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
		return REV_WAIT;
		
	if(lora_cnt == lora_cntPre)			//如果上一次的值和这次相同,则说明接收完毕
	{
		lora_cnt = 0;									//清0接收计数
			
		return REV_OK;								//返回接收完成标志
	}
		
	lora_cntPre = lora_cnt;					//置为相同
	
	return REV_WAIT;								//返回接收未完成标志

}
//==========================================================
//	函数名称:	LORA_SendCmd
//
//	函数功能:	发送命令
//
//	入口参数:	cmd:命令
//				res:需要检查的返回指令
//
//	返回参数:	0-成功	1-失败
//
//	说明:		
//==========================================================
_Bool LORA_SendCmd(char *cmd, char *res)
{
	
	unsigned char timeOut = 200;

	Usart_SendString(USART2, (unsigned char *)cmd, strlen((const char *)cmd));
	
	while(timeOut--)
	{
		if(LORA_WaitRecive() == REV_OK)							//如果收到数据
		{
			if(strstr((const char *)lora_buf, res) != NULL)		//如果检索到关键词
			{
				LORA_Clear();									//清空缓存
				
				return 0;
			}
		}
		
		delay_ms(10);
	}
	
	return 1;

}
//==========================================================
//	函数名称:	LORA_SendData
//
//	函数功能:	发送数据
//
//	入口参数:	data:数据
//				len:长度
//
//	返回参数:	无
//
//	说明:		
//==========================================================
void LORA_SendData(unsigned char *data, unsigned short len)
{

	LORA_Clear();								//清空接收缓存
	Usart_SendString(USART2, data, len);		//发送设备连接请求数据

}
//==========================================================
//	函数名称:	USART2_IRQHandler
//
//	函数功能:	串口2收发中断
//
//	入口参数:	无
//
//	返回参数:	无
//
//	说明:		
//==========================================================
void USART2_IRQHandler(void)
{
	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断
	{
		if(lora_cnt >= sizeof(lora_buf))	lora_cnt = 0; //防止串口被刷爆
		lora_buf[lora_cnt++] = USART2->DR;
		
		USART_ClearFlag(USART2, USART_FLAG_RXNE);
	}
}

总结

以上就是STM32串口通信配置和实现的基本步骤。通过这些步骤,我们可以实现STM32与外部设备的有效通信。希望这篇文章能帮助你理解STM32串口通信的配置和使用。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值