STM32串口驱动与ESP8266的使用指南

写在前面

本文并不对相关知识进行讲解,只是这次的实验课要实现的任务有些复杂,我也踩了一些坑,对代码实现思路进行复现和记录,并不是技术科普性文章,基础知识还是要自己有所掌握。


1.stm32的串口通讯
  • 开发板:stm32f407zgt6课程学习板
  • 下载器:j-link
  • 串口通信是单片机一种基础的通信协议,对时序要求比较严格,一般都是通过硬件实现。stm32初始化串口通讯:

    首先查看原理图type-b接口对应的单片机引脚(这一步老师初始化里已经配置好:
    type-b的接口连线

    跳帽连线,可以看到需要将uart3 tx/rx的引脚通过跳帽选择连接到单片机

    对应单片机的引脚:为tx-pc10 rx-pc11

    之后我们就可以通过库函数对应配置

    void UART3_Configuration(void)
    {
        GPIO_InitTypeDef    GPIO_InitStructure;
        USART_InitTypeDef   USART_InitStructure;
        //  开启GPIO_D的时钟
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
        //  开启串口3的时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//复用推挽输出
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;
        GPIO_Init(GPIOC, &GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
        GPIO_Init(GPIOC, &GPIO_InitStructure);
    
        GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_USART3);
        GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_USART3);
    	//串口对应设置
        USART_InitStructure.USART_BaudRate   = 115200;//波特率
        //波特率是一个知识点,可能之后考试会考(确信)
        //不使用校验位,所以是八位
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        USART_InitStructure.USART_StopBits   = USART_StopBits_1;//停止位
        //不使用校验位
        USART_InitStructure.USART_Parity     = USART_Parity_No;
        //流控信号选择
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    	//发送和接收同时使用过
        USART_InitStructure.USART_Mode                = USART_Mode_Tx | USART_Mode_Rx;
        USART_Init(USART3, &USART_InitStructure);
            //接收中断使能,可以跳转到函数定义的头文件看第二个参数的作用
        USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
        /* Enable the USARTx Interrupt */
        NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
    	/* 使能串口3 */
        USART_Cmd(USART3, ENABLE);
    }
    

    配置好串口,相当于我们把消息通讯的机器组装好,接下来就要进行发送和接收消息的配置。

    发送函数:

    void USART3_Senddata(unsigned char *Data, unsigned int length)
    {
        while(length--)
        {
            USART_SendData(USART3,*Data++);
            while (USART_GetFlagStatus(USART3, USART_FLAG_TC)==RESET);
        }
    }
    

    这里USART_SendData是标准库提供的函数,发送一个字节,硬件在发送数据时,发送状态会进行标志位置位,我们只需要进行判断读取是否发送完成再进行下一个字节的发送就好了,所以这里用while()循环一下。

    printf的重定向:这里有一个小应用是关于c语言中printf函数的使用,看到这里想必大家都会明白,数据传输的底层就是对每一个字节的传输,如果我们只是对字符char的传输,那么一个字节就可以实现很好通讯,但是如果我们想对整数和浮点数进行信息传递,那么势必要从他们的存储方式进行转换,这是很麻烦的一件事。那么我们就会想到c语言里好像无所不能打印的printf,如果我们能直接使用他不就能解决大部分事情了。所以很多单片机的示例代码里都会对printf进行重定向配合串口实现和上位机的通讯,以便于方便的对单片机进行debug。而printf的底层就是调用fputc对文件写下一个字符。因此我们可以看到以下代码:

    int fputc(int ch, FILE *f)
    {
        USART3->SR;              // 防止复位后无法打印首字符
        USART_SendData(USART3, (u8) ch);
        while(USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
    
        return (ch);
    }
    

    这样我们就可以使用printf和电脑进行通讯了。
    进行初步尝试,查看是否配置正确。

    int main()
    {
        SysTick_Init();
        UART3_Configuration();
    	printf("hello world\n");
    	while(1){
    	
    	}
    }
    

    下载程序,把使用type-b线连接电脑和板子,打开上位机连接显示ch340对应的com口。按下复位键,看到上位机接收到hello world,表示成功。(记得将跳帽插到232的那一侧)。


    以上是发送
    同时还有接收部分

    可以在接收startup_stm32f4xx.s文件中找到USART3_IRQHandler中断函数

    void USART3_IRQHandler(void)
    {
        if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)                //  判断中断标志位
        {
            USART_ClearITPendingBit(USART3, USART_IT_RXNE);
            USART_ReceiveData(USART3);  //把发来的消息接收一下
            printf("123");
        }
    }
    

    再次连接单片机和电脑,使用上位机向单片机发送消息,得到以下现象:

    说明接收中断配置成功。

    2.esp8266

    在配置好串口之后,对于与单片机进行串口通讯的其他外设,就只需要进行发送和接收消息的处理了。

    初始化

    看看wifi模块对应的引脚

    可以看到这里使用的是uart5串口,对应的单片机引脚是rx-pd2,tx-pc12。我们只需要对上面的usart3对应的初始化函数进行修改,就可以配置成功,这里不在赘述,值得一提的是,uart5资源在stm32f4中只有异步串口功能,因此在英文组成上是uart而非usart。
    uart5的配置:

    void UART5_Configuration(unsigned int baud)
    {
        GPIO_InitTypeDef    GPIO_InitStructure;
        USART_InitTypeDef   USART_InitStructure;
        NVIC_InitTypeDef    NVIC_InitStructure;
        Uart5.ReceiveFinish = 0;
        Uart5.RXlenth = 0;
        Uart5.Time = 0;
        Uart5.Rxbuf = Uart5ReceiveBuf;
        //  开启GPIOA的时钟  
          RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD|RCC_AHB1Periph_GPIOC, ENABLE);
        //  开启串口1的时钟
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_UART5, ENABLE);
        GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_2;
        GPIO_Init(GPIOD, &GPIO_InitStructure);
        GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_12;
        GPIO_Init(GPIOC, &GPIO_InitStructure);
        GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_UART5);
        GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_UART5);
      
        USART_InitStructure.USART_BaudRate   = baud;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        USART_InitStructure.USART_StopBits   = USART_StopBits_1;
        USART_InitStructure.USART_Parity     = USART_Parity_No;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode       = USART_Mode_Tx | USART_Mode_Rx;
        USART_Init(UART5, &USART_InitStructure);
        /* 使能串口1 */   /* Enable the USARTx Interrupt */
        USART_Cmd(UART5, ENABLE);
        USART_ITConfig(UART5, USART_IT_RXNE, ENABLE);
        
        /* NVIC configuration */
        NVIC_InitStructure.NVIC_IRQChannel = UART5_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);
    
        USART_Cmd(UART5, ENABLE);
    }
    
    void UART5_Senddata(unsigned char *Data, unsigned int length)
    {
        while(length--)
        {
            USART_SendData(UART5,*Data++);
            while (USART_GetFlagStatus(UART5, USART_FLAG_TC)==RESET);
        }
    }
    
    与esp8288进行通讯

    大家在做这块实验的时候,最多的是不知道模块是否正确的工作了,只看手机有没有搜到热点,如果没有又不知道哪里出现了问题,手足无措。我们可以在网上搜以下这个模块的使用方法,看一些特性,可以让我们更好的进行调试。
    对于esp8266,它具有回显功能,即他接收到消息就会将消息原封不动的返回,并且返回对应的应答。利用这个我们可以看到我们的消息发送和接收是否正常。因此我们首先要处理从模块中接收的应答消息,并且如果能把他想办法打印出来,不就可以看到哪里出现了问题。

    首先定义一个接收消息的结构体。然后在接收中断里使用。

    struct UsartData                                          
    {      
        unsigned char *Rxbuf;
        unsigned int   RXlenth;
        unsigned char  Time;
        unsigned char  ReceiveFinish;
    };
    USARTDATA   Uart5;
    void UART5_IRQHandler(void)
    {
        if (USART_GetITStatus(UART5, USART_IT_RXNE) != RESET)         
        {
            USART_ClearITPendingBit(UART5, USART_IT_RXNE);
            Uart5.Rxbuf[Uart5.RXlenth++] = USART_ReceiveData(UART5);
            Uart5.Time = 5; //表示正在接收
        }
    }
    

    然后在系统滴答定时器里发送这个接受的数据到uart3

    void SysTick_Handler(void)
    {    
        if (Uart5.Time > 0)
        {
            Uart5.Time--;
            if (Uart5.Time == 0)
            {
                Uart5.ReceiveFinish = 1;
                USART3_Senddata(Uart5.Rxbuf, Uart5.RXlenth);
                Uart5.RXlenth = 0;
            }
        }
    }
    

    然后我们就可以在主函数里对模块发消息进行控制。

    在烧录初始化模块的代码之后要重新上电,否则模块不会发送消息,很奇怪
    先发个“AT”看看是否有响应

    int main(void)
    {
        unsigned char test[11] = "WIFI test\r\n";
        SysTick_Init();             //  系统滴答定时器初始化
        // LEDGpio_Init();
        // KEYGpio_Init();
        UART3_Configuration();                                              //  USART3配置
        UART5_Configuration(115200);
        printf("hello world\n");
      
        UART5_Senddata("AT\r\n",4);
        delay_ms(5);
        //WIFI_Configuration();  
        while (1)
        {
    
        }
    }
    
    发送的消息不完整

    发现他有反应,虽然但是很奇怪,我们明明发的“AT”,他却只给我们回了一个“T”,这个不知道问题出在哪里,我试了很多方法,但是每次初始化完成之后的第一条语句之后都是这样,会丢到一个字母。

        UART5_Senddata("AT\r\n",4);
        delay_ms(5);
        UART5_Senddata("AT\r\n",4);
        delay_ms(5);
    

    所以我们把这句话发两次,再看

    发现第二句话反应就正常了

    配置wifi名和密码消息接收不全

    然后我们再配置wifi名和密码

    unsigned char CWSAP[38]     = "AT+CWSAP=\"Ctrl\",\"0123456789\",5,3\r\n";
    UART5_Senddata(CWSAP,sizeof(CWSAP));
    delay_ms(5);
    

    这里他虽然接收到了消息并且回了ok,但是他回显有缺失,虽然不太明白为什么,但是只要把这句话的发送位置放在配置消息第一个,回显就会正常,而且正常配置密码的命令是后面跟的命令是5,3 改掉之后重新上电就会看到有密码的wifi了。

    回显busy的问题

    大家也看到了我在每次发送消息之间都会延时5ms,这是因为虽然你发送指令是按照一句一句发的,但是他本质还是在连续的发送char,每个语句之后模块会进行响应的设置,无法快速的接收下一条指令。

    其实到这里我们的模块就已经调通了,之后只需要根据任务手册做下去实验就已经能够完成。


    还有一些值得说的东西。

    esp8266是现在很火的带wifi的mcu,性价比很高成本也很低,感兴趣的话可以学习一下他的at指令。

    打算从他开始,入手以下esp32家族,最近势头很猛的国产32位单片机,在生态活跃上和物联网方面都势头很猛。

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32串口驱动与ESP8266的使用指南

    发表评论