文章目录
- STM32USART介绍
- STM32USART框图
- 第一部分
- 第二部分
- 第三部分
- 发送器
- 时序图
- 接收器
- 第四部分
- 软件部分:
- STM32通过USART与板载ESP8266通讯实验
- 板载WIFI模块电路图
- 实现方式:
- 第一步:配置USART1和USART3的GPIO及其中断
- 第二步:通过中断服务函数实现消息的转发
- 问题
硬件:两根线TXD、RXD,TXD用于发送数据连接接收机的RXD、RXD接受数据,连接发送机的TXD
电平采用标准TTL电平 0V表示0 3.3或者5V表示1
串行、异步、全双工
协议层:
-
起始位:一位逻辑0
-
数据位
在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 5、6、7 或 8 位长。
-
校验位
-
奇校验要求有效数据和校验位中“1”的个数为奇数,比如一个 8 位长的有效数据为:01101001,此时总共有 4 个“1”,为达到奇校验效果,校验位为“1”,最后传输的数据将是 8 位的有效数据加上 1 位的校验位总共 9 位。
-
偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。
-
0 校验是不管有效数据中的内容是什么,校验位总为“0”,1 校验是校验位总为“1”。
-
在无校验的情况下,数据包中不包含校验位。
- 停止位:0.5或1或1.5或2个逻辑1
STM32USART介绍
USART全称 Universal Synchronous Asynchronous Receiverand Transmitter,即同步异步收发器。与学习51时的UART不同的是UART名称中少了个S,即没有同步收发功能。
STM32USART框图
第一部分
-
TX为数据发送引脚、RX为数据接收引脚
-
SW_RX为数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚
-
nRTS:请求以发送 (Request To Send),n 表示低电平有效。如果使能 RTS 流控制,当 USART 接收器准备好接收新数据时就会将nRTS 变成低电平;当接收寄存器已满时,nRTS 将被设置为高电平。该引脚只适用于硬件流控制。
-
nCTS:清除以发送 (Clear To Send),n 表示低电平有效。如果使能 CTS 流控制,发送器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。
-
SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。
STM32F407的USART引脚分布
第二部分
数据寄存器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 个停止位用于智能卡模式。
时序图
四个重要的寄存器功能
接收器
如果将 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 线开始搜索起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到 RDR 内,并把 USART_SR 寄存器的 RXNE 位置 1,同时如果USART_CR2 寄存器的 RXNEIE 置 1 的话可以产生中断
重要寄存器
在中断服务函数中可以通过USART_GetFlagStatus()函数来对以下几个事件进行检测
第四部分
第四部分为时钟以及波特率控制寄存器
暂时还没看懂
软件部分:
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模块电路图
关键引脚: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的头文件中,当其他文件引用这个头文件的时候,也会重复定义一份。所以会有重复定义的问题。
解决方法是把结构体声明在头文件中,定义在C文件里。如果定义的结构体要被其他文件使用,在头文件里extern
已经定义的结构体