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

指针到处飞 2023-02-28 11:03:20

目录

  • STM32USART介绍
  • STM32USART框图
  • 第一部分
  • 第二部分
  • 第三部分
  • 发送器
  • 时序图
  • 接收器
  • 第四部分
  • 软件部分:
  • STM32通过USART与板载ESP8266通讯实验
  • 板载WIFI模块电路图
  • 实现方式:
  • 第一步:配置USART1和USART3的GPIO及其中断
  • 第二步:通过中断服务函数实现消息的转发
  • 问题

硬件:两根线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已经定义的结构体

...全文
490 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
一、项目简介 本项目(HF24075 SDFeatureRivertraining)是一套以多路 PWM 多彩 LED 灯光控制为核心的固件方案,基于 PADAUK PMS 系列单片机。方案对蓝、青、粉等多色 LED 进行独立 PWM 调光,实现呼吸、渐变、流水等细腻灯光效果,并支持按键切换与传感器交互。其 PWM 调光结构清晰,是学习单片机 PWM、色彩混合与灯光算法的理想范例。 核心应用场景: 1. 氛围灯 / 流水灯产品开发 2. PWM 调光与色彩混合学习 3. 电子类课程设计 / 毕业设计 4. 灯光艺术与 DIY 项目 适配使用场景:学习练手、毕业设计、课程设计、创业售卖、实操实训,突出实用性与可落地性。 二、硬件核心配置 · 主控芯片:PADAUK(应广科技)PMS 系列 8 位 OTP 单片机 · 外接外设: - 多色 LED 灯组(蓝 / 青 / 粉等多路独立 PWM) - 轻触按键(模式切换) - 传感器(交互触发) · 操控设备: - 机身实体按键操控 三、核心功能介绍 1. 多路独立 PWM 调光:蓝、青、粉等 LED 各自独立占空比控制,实现精准亮度; 2. 呼吸 / 渐变 / 流水效果:通过 PWM 变化曲线营造丰富灯光律动; 3. 按键切换模式:轻触在多种灯效间循环切换; 4. 传感器交互:外部传感信号触发或联动灯光变化; 5. 低功耗设计:闲置进入低功耗,按需唤醒。 6. 参数可配:亮灭周期、渐变速度等可调,便于定制。 四、项目优势与亮点 · 灯光细腻:多路独立 PWM,效果远胜简单开关灯。 · 算法清晰:呼吸 / 渐变实现简洁,易于理解与移植。 · 扩展性强:可轻松增加颜色与模式,适配各类灯饰。 ·

107

社区成员

发帖
与我相关
我的任务
社区描述
14天挑战赛发文处
学习 个人社区
社区管理员
  • Alita11101_
  • 穆雄雄
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

本次活动的文章请发布在社区内

试试用AI创作助手写篇文章吧