STM32学习笔记-USART串口通信+与野火STM32F407板载ESP8266进行通信

文章详细介绍了STM32的USART模块,包括其功能、框图、硬件接口、数据传输时序和控制寄存器。通过USART初始化结构体设置波特率、字长等参数,并展示了如何配置GPIO和中断进行串口通信。实验部分描述了STM32与板载ESP8266的通信,强调了中断服务函数在数据转发中的作用,以及遇到的问题和解决方案。
摘要由CSDN通过智能技术生成


硬件:两根线TXD、RXD,TXD用于发送数据连接接收机的RXD、RXD接受数据,连接发送机的TXD

image-20230226195853803

电平采用标准TTL电平 0V表示0 3.3或者5V表示1

串行、异步、全双工

协议层:

image-20230226200142668

  1. 起始位:一位逻辑0

  2. 数据位

    在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 5、6、7 或 8 位长。

  3. 校验位

  • 奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个 8 位长的有效数据为:01101001,此时总共有 4 个“1”,为达到奇校验效果,校验位为“1”,最后传输的数据将是 8 位的有效数据加上 1 位的校验位总共 9 位。

  • 偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。

  • 0 校验是不管有效数据中的内容是什么,校验位总为“0”,1 校验是校验位总为“1”。

  • 在无校验的情况下,数据包中不包含校验位。

  1. 停止位:0.5或1或1.5或2个逻辑1

STM32USART介绍

USART全称 Universal Synchronous Asynchronous Receiverand Transmitter,即同步异步收发器。与学习51时的UART不同的是UART名称中少了个S,即没有同步收发功能。

STM32USART框图

image-20230226200617463

第一部分
  1. TX为数据发送引脚、RX为数据接收引脚

  2. SW_RX为数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚

  3. nRTS:请求以发送 (Request To Send),n 表示低电平有效。如果使能 RTS 流控制,当 USART 接收器准备好接收新数据时就会将nRTS 变成低电平;当接收寄存器已满时,nRTS 将被设置为高电平。该引脚只适用于硬件流控制。

  4. nCTS:清除以发送 (Clear To Send),n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。

  5. SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。

STM32F407的USART引脚分布

image-20230226201213121

第二部分

数据寄存器USART_DR

数据寄存器里面包含了两个寄存器,发送数据寄存器TDR,接收数据寄存器RDR。

因为USART是串行通讯,所以发送数据时TDR中的数据依次移动至发送移位寄存器,将数据一次发送出去。接收也是如此。

第三部分

控制寄存器USART_CR

USART 有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。使用

USART 之前需要向 USART_CR1 寄存器的 UE 位置 1 使能 USART。发送或者接收数据字长可选

8 位或 9 位,由 USART_CR1 的 M 位控制。

发送器

当 USART_CR1 寄存器的发送使能位 TE 置 1 时,启动数据发送,发送移位寄存器的数据会在 TX引脚输出,如果是同步模式 SCLK 也输出时钟信号。

一个字符帧发送需要三个部分:起始位 + 数据帧 + 停止位。

  • 起始位是一个位周期的低电平,位周期就是每一位占用的时间;
  • 数据帧就是我们要发送的 8 位或 9 位数据,数据是从最低位开始传输的;
  • 停止位是一定时间周期的高电平。停止位时间长短是可以通过 USART 控制寄存器 2(USART_CR2) 的 STOP[1:0] 位控制,可选 0.5个、1 个、1.5 个和 2 个停止位。默认使用 1 个停止位。2 个停止位适用于正常 USART 模式、单线模式和调制解调器模式。0.5 个和 1.5 个停止位用于智能卡模式。
时序图

image-20230226202110313

四个重要的寄存器功能

image-20230226202141135

接收器

如果将 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 线开始搜索起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到 RDR 内,并把 USART_SR 寄存器的 RXNE 位置 1,同时如果USART_CR2 寄存器的 RXNEIE 置 1 的话可以产生中断

重要寄存器

在中断服务函数中可以通过USART_GetFlagStatus()函数来对以下几个事件进行检测

image-20230226202315766

第四部分

第四部分为时钟以及波特率控制寄存器

暂时还没看懂

软件部分:

USART初始化结构体:

typedef struct {
    uint32_t USART_BaudRate; // 波特率
    uint16_t USART_WordLength; // 字长
    uint16_t USART_StopBits; // 停止位
    uint16_t USART_Parity; // 校验位
    uint16_t USART_Mode; // USART 模式
    uint16_t USART_HardwareFlowControl; // 硬件流控制
} USART_InitTypeDef;

当使用同步模式时需要配置 SCLK 引脚输出脉冲的属性,标准库使用一个时钟初始化结构体

USART_ClockInitTypeDef 来设置,因此该结构体内容也只有在同步模式才需要设置。

USART 时钟初始化结构体

typedef struct {

    uint16_t USART_Clock; 

    // 时钟使能控制

    uint16_t USART_CPOL; 

    // 时钟极性

    uint16_t USART_CPHA; 

    // 时钟相位

    uint16_t USART_LastBit; 

    // 最尾位时钟脉冲

} USART_ClockInitTypeDef;

STM32通过USART与板载ESP8266通讯实验

板载WIFI模块电路图

image-20230226203503276

关键引脚:ESP8266通过PB10与PB11与STM32进行连接,除此之外必须通过PE2对WIFI使能,PG15是控制WIFI的复位引脚。

ESP8266在正常工作时复位引脚必须保持高电平,当需要复位时将RST引脚拉低

实现方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C4d0RD05-1677553178575)(https://zhangguosheng.oss-cn-beijing.aliyuncs.com/image-20230226211547475.png)]

第一步:配置USART1和USART3的GPIO及其中断

嵌套向量中断控制器配置 USART1_IRQn和USART3_IRQn

static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* Configure one bit for preemption priority */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
  
  /* 配置中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn|USART3_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  
}

USART初始化,USART1和USART3

void USART_Config()
{
	/* 开启时钟 */
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOB, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB1Periph_USART3, ENABLE);
	
	/* 初始化USART3_GPIO */
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	/* 初始化USART1_GPIO */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	/* 复用GPIO */
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3);
	GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
	
	/* 初始化USART */
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 115200;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
	USART_InitStructure.USART_Parity = USART_Parity_No;
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART3, &USART_InitStructure);
	USART_Init(USART1, &USART_InitStructure);
	
	/* 配置USART中断 */
	NVIC_Configuration();
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	USART_ITConfig(USART3,USART_IT_RXNE,ENABLE);
	
	/* 使能USART */
	USART_Cmd(USART1, ENABLE);	
	USART_Cmd(USART3, ENABLE);	
}
第二步:通过中断服务函数实现消息的转发

串口1和串口3的中断服务函数,串口1接收到数据后触发中断,使用串口3将数据发送出去。但是实测这种方法是不行的,不知道哪里出了问题,然后看野火的示例程序,发现在中断服务函数中将数据保存在一个结构体中,在主循环里将数据发送出去

void USART1_IRQHandler()
{
	uint8_t temp;
	if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
	{
		temp = USART_ReceiveData(USART1);
		USART_SendByte(USART3,temp);
	}
}
void USART3_IRQHandler()
{
	uint8_t temp;
	if(USART_GetITStatus(USART3,USART_IT_RXNE)!=RESET)
	{
		temp = USART_ReceiveData(USART3);
		USART_SendByte(USART3,temp);
	}
}

野火示例代码

// 串口中断服务函数
void DEBUG_USART_IRQHandler(void)
{
  uint8_t ucCh;
	if ( USART_GetITStatus ( DEBUG_USART, USART_IT_RXNE ) != RESET )//通过中断标志位判断是否接收到数据
	{
		ucCh  = USART_ReceiveData( DEBUG_USART );//将数据存放到变量
		
		if ( strUSART_Fram_Record .InfBit .FramLength < ( RX_BUF_MAX_LEN - 1 ) )                       //预留1个字节写结束符
			   strUSART_Fram_Record .Data_RX_BUF [ strUSART_Fram_Record .InfBit .FramLength ++ ]  = ucCh;

	}
	 	 
	if ( USART_GetITStatus( DEBUG_USART, USART_IT_IDLE ) == SET )                                         //数据帧接收完毕
	{
    strUSART_Fram_Record .InfBit .FramFinishFlag = 1;		
		
		ucCh = USART_ReceiveData( DEBUG_USART );                                                              //由软件序列清除中断标志位(先读USART_SR,然后读USART_DR)	
  }	
}

/**
  * @brief  This function handles macESP8266_USARTx Handler.
  * @param  None
  * @retval None
  */
void macESP8266_USART_INT_FUN ( void )
{	
	uint8_t ucCh;
	if ( USART_GetITStatus ( macESP8266_USARTx, USART_IT_RXNE ) != RESET )
	{
		ucCh  = USART_ReceiveData( macESP8266_USARTx );
		
		if ( strEsp8266_Fram_Record .InfBit .FramLength < ( RX_BUF_MAX_LEN - 1 ) )                       //预留1个字节写结束符
			strEsp8266_Fram_Record .Data_RX_BUF [ strEsp8266_Fram_Record .InfBit .FramLength ++ ]  = ucCh;

	}
	 	 
	if ( USART_GetITStatus( macESP8266_USARTx, USART_IT_IDLE ) == SET )                                         //数据帧接收完毕
	{
    strEsp8266_Fram_Record .InfBit .FramFinishFlag = 1;
		
		ucCh = USART_ReceiveData( macESP8266_USARTx );                                                              //由软件序列清除中断标志位(先读USART_SR,然后读USART_DR)
  }	
}

//main函数中的主循环
  while(1)
	{	
		if(strUSART_Fram_Record .InfBit .FramFinishFlag == 1)  //如果接收到了串口调试助手的数据
		{
			strUSART_Fram_Record .Data_RX_BUF[strUSART_Fram_Record .InfBit .FramLength] = '\0';
			Usart_SendString(macESP8266_USARTx ,strUSART_Fram_Record .Data_RX_BUF);      //数据从串口调试助手转发到ESP8266
			strUSART_Fram_Record .InfBit .FramLength = 0;                                //接收数据长度置零
			strUSART_Fram_Record .InfBit .FramFinishFlag = 0;                            //接收标志置零
	  }
		if(strEsp8266_Fram_Record .InfBit .FramFinishFlag)                             //如果接收到了ESP8266的数据
		{                                                      
			 strEsp8266_Fram_Record .Data_RX_BUF[strEsp8266_Fram_Record .InfBit .FramLength] = '\0';
			 Usart_SendString(DEBUG_USART ,strEsp8266_Fram_Record .Data_RX_BUF);        //数据从ESP8266转发到串口调试助手
			 strEsp8266_Fram_Record .InfBit .FramLength = 0;                             //接收数据长度置零
			 strEsp8266_Fram_Record.InfBit.FramFinishFlag = 0;                           //接收标志置零
		}
  }

对照野火示例程序改的中断服务函数

void USART1_IRQHandler()
{
	uint8_t temp;
	if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)
	{
		temp = USART_ReceiveData(USART1);
		usart1_info.data[usart1_info.data_length++] == temp;
		
		if(USART_GetITStatus(USART1,USART_IT_IDLE) == SET)
		{
			usart1_info.finishflag = 1;
			temp = USART_ReceiveData( USART1 );                                                              //由软件序列清除中断标志位(先读USART_SR,然后读USART_DR)	
		}
	}
}
void USART3_IRQHandler()
{
	uint8_t temp;
	if(USART_GetITStatus(USART3,USART_IT_RXNE)!=RESET)
	{
		temp = USART_ReceiveData(USART3);
		usart3_info.data[usart3_info.data_length++] == temp;
		
		if(USART_GetITStatus(USART3,USART_IT_IDLE) == SET)
		{
			usart3_info.finishflag = 1;
			temp = USART_ReceiveData( USART3 );                                                              //由软件序列清除中断标志位(先读USART_SR,然后读USART_DR)	
		}
	}
}
//主函数
int main(void)
{
	USART_Config();
	WIFI_Init();
	
	while(1)
	{
		if(usart1_info.finishflag == 1)  //如果接收到了串口调试助手的数据
		{
			usart1_info.data[usart1_info.data.data_length] = '\0';
			Usart_SendString(USART3 ,usart1_info.data);      //数据从串口调试助手转发到ESP8266
			usart1_info.data.data_length = 0;                                //接收数据长度置零
			usart1_info.finishflag = 0;                            //接收标志置零
		}
		if(usart3_info.finishflag == 1)  //如果接收到了ESP8266的数据
		{
			usart3_info.data[usart3_info.data.data_length] = '\0';
			Usart_SendString(USART3 ,usart3_info.data);      //数据从ESP8266转发到串口调试助手
			usart3_info.data.data_length = 0;                                //接收数据长度置零
			usart3_info.finishflag = 0;                            //接收标志置零
		}
	}

}

这样就能通过串口助手与ESP8266进行通信了

问题

在定义结构体的时候遇到了一个问题,说我重复定义了 info这个结构体,因为我把info这个结构体定义在了esp8266的头文件中,当其他文件引用这个头文件的时候,也会重复定义一份。所以会有重复定义的问题。

image-20230228103000763

解决方法是把结构体声明在头文件中,定义在C文件里。如果定义的结构体要被其他文件使用,在头文件里extern已经定义的结构体

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

指针到处飞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值