单片机
返回首页

STM32 ESP8266和Java服务器透传模式下的双向通信

2020-03-29 来源:eefocus

标注:注意大家一般的得到的STM32程序中的延迟函数delay_ms()中的入口参数值是有限制的,他最大值只能是1864,我之前不知道,程序中一直错误地使用它,所以导致延时不准确。


//延时nms

//注意nms的范围

//SysTick->LOAD为24位寄存器,所以,最大延时为:

//nms<=0xffffff*8*1000/SYSCLK

//SYSCLK单位为Hz,nms单位为ms

//对72M条件下,nms<=1864 

void delay_ms(u16 nms)

{     

u32 temp;    

SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)

SysTick->VAL =0x00; //清空计数器

SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数  

do

{

temp=SysTick->CTRL;

}while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达   

SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器

SysTick->VAL =0X00;        //清空计数器       


对72M条件下,nms<=1864 


大家可以自行改装,例如:


void delay_nms(u16 n)

{

while(n--)

{

delay_ms(1);

}

}


注意:串口接收处理程序以该博客中的为准


本文主要实现的功能是:一个ESP8266模块接到stm32f103c8t6单片机的串口1上,然后用Eclipse创建一个服务器,使8266和服务器能够在透传模式下进行双向通信(通信接口就是Socket)。


先来说一下透传与非透传的区别,所谓透传就是STM32发送的数据先发给8266,然后8266不对数据进行任何处理,就立即转发给服务器;反过来就是服务器发送的数据先发给8266,然后8266不对数据进行任何处理,立马就转发给STM32。


透传与非透传8266发给单片机的数据格式是不同的,如下图所示:

在这里插入图片描述

上面是透传模式下8266发给单片机(也就是单片机串口接收到的数据)的样子,下面是非透传模式下8266发给单片机的样子。即非透传模式下,在真实数据的前面又添加了头部,在接收数据的时候应该根据需求对其进行相应的截取。


本文是透传模式下传输数据,好了,进入正题。。。


首先我们先来看一下服务器的搭建,直接上Java代码:


package Socket;


import java.io.BufferedReader;

import java.io.DataInputStream;

import java.io.DataOutputStream;

import java.io.IOException;

import java.io.InputStreamReader;

import java.net.ServerSocket;

import java.net.Socket;

public class ServerSocketTest {

public static final int PORT = 12444;//端口号,可以随便设定,尽量避开一些重要的端口号,比如8080等   

public static void main(String[] args) {

try {

ServerSocket serverSocket=new ServerSocket(PORT);//新建一个serverSocket,并且与指定端口号进行绑定

System.out.println('服务器已启动,等待客户端连接...n'); 

Socket Client = serverSocket.accept();//在这里一直等待客户端的接入请求,如果客户端没有接入请求,程序会一直停在这里等待,如果有客户端的接入请求,那么ServerSocket就返回一个Socket,我这里把返回的Socket命名为Client

System.out.println('Socket client' + Client.getRemoteSocketAddress() + '成功连接');

DataInputStream input = new DataInputStream(Client.getInputStream());//新建一个输入流,用来读取客户端发来的数据 

DataOutputStream out = new DataOutputStream(Client.getOutputStream());//新建一个输出流,用来向客户端发送数据  

System.out.print('请向客户端发送数据:n');  

String s = new BufferedReader(new InputStreamReader(System.in)).readLine();//程序停在这里,等待用户在控制台上输入要发给客户端的数据  

out.writeUTF(s);  //把刚才从控制台输入的数据发送给客户端8266.注意服务器向8266发送数据,一定要采用UTF-8的格式,我也不知道为啥,可能其他格式也行,不过还得在单片机中进行啥处理,此处不作深究

            

            //以下为接收客户端8266发给服务器的数据

            System.out.println('正在接受客户端的数据...'); 

           

            byte[] msg = new byte[6];//声明一个数组用于接收客户端8266发来的数据

            input.read(msg);//注意8266发给服务器的数据在这里一定要使用read函数来接收,并且把接收到的数据存储到一个数组里面,不能再使用readUTF函数来读取了,可能单片机通过串口发送的数据不是UTF格式

            System.out.println('客户端发过来的内容:' + new String(msg)+ 'n');

            input.close();//关闭输入流

            out.close(); //关闭输出流

}catch (IOException e) {

e.printStackTrace();

}

}

}


其实这里面就是用到了所谓的Socket通信,Socket称为套接字,关于套接字更多官方解释在这里,在我们这个实验中Socket就是来完成服务器和客户端进行通信的一个接口,它更像一个媒婆一样,双方的消息都是得通过中间人(媒婆)来进行传达。ServerSocket和Socket不同,服务器套接字(ServerSocket)的角色是等待来自客户端的连接请求。一旦服务器套接字获得一个连接请求,它创建一个Socket实例来与客户端进行通信(也就是说客户端要想去找服务器玩耍,他必须去找Socket当这个中间人,但是在找Socket之前还必须得先去找Socket的妈妈ServerSocket,让ServerSocket给他创造出一个Socket)。


一个特别、必须注意的是一定要使用数组来接收客户端8266发给服务器的数据,我也不知道为啥,可能是因为单片机串口发送数据的编码格式的问题,我之前多次试验一直接收不到客户端8266发给服务器的数据,就是因为这里没有用数组来进行接收,一定不要像服务器给8266发数据那样使用UTF格式(但是当你使用Eclipse创建一个服务器一个客户端的时候,双方发送和接收的数据格式一定要保持一致,一般都是UTF格式,这里和单片机与服务器通信有所不同)


服务器创建完了,再来看STM32端的代码:

usart.c


#include 'sys.h'

#include 'usart.h'   

#include 'oled.h'

#include 'string.h'

  

char Rx_Buff[200];//串口接收数组

int  Rx_count=0;   //用于ESP8266判断接受数据的多少

int ok_flag=0;

extern u8 AT_Mode,send_flag;


u8 Data_Count = 0,Receive_Data_Flag = 0,Receive_Data_Over = 0;

u8  Rx_Len=0;



void uart_init(u32 bound){

//GPIO端口设置

GPIO_InitTypeDef GPIO_InitStructure;

USART_InitTypeDef USART_InitStructure;

NVIC_InitTypeDef NVIC_InitStructure;

 

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟


//USART1_TX   GPIOA.9

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出

GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9


//USART1_RX   GPIOA.10初始化

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入

GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  


//Usart1 NVIC 配置

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能

NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器


//USART 初始化设置


USART_InitStructure.USART_BaudRate = bound;//串口波特率

USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式

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_Rx | USART_Mode_Tx; //收发模式


USART_Init(USART1, &USART_InitStructure); //初始化串口1

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接收中断

USART_Cmd(USART1, ENABLE);                    //使能串口1 


}


//透传模式的数据接收


void USART1_IRQHandler(void)                //串口1中断服务程序

{

u8 Res;

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)应该是AT指令必须是以rn结尾,数据应该没有这个要求

{

    Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据

Rx_Buff[Rx_count]=Res;

if(AT_Mode==1)                          //AT指令模式

{

Rx_count++;//注意此处只是索引值增加,接收数据的判断在Hand函数中进行

}

else if(AT_Mode==0)               //接收数据模式(非AT指令模式)

{

Rx_count++;  

Receive_Data_Flag = 1;//用来标志开始接收数据了,置1后让OLED开始显示接收的数据

}

// USART_ClearITPendingBit(USART1,USART_IT_RXNE);//这句话若不注释掉,不知道程序为啥会莫名其妙地卡在这

}

}


usart.h


#ifndef __USART_H

#define __USART_H

#include 'stdio.h'

#include 'sys.h' 


#define USART_REC_LEN  200  //定义最大接收字节数 200

#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收

 

extern u8  USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.末字节为换行符 

extern u16 USART_RX_STA;          //接收状态标记


extern int ok_flag;

//如果想串口中断接收,请不要注释以下宏定义

void uart_init(u32 bound);

#endif


esp8266.c


//单片机头文件

#include 'stm32f10x.h'


//网络设备驱动

#include 'esp8266.h'


//硬件驱动

#include 'delay.h'

#include 'usart.h'

#include 'led.h'

#include 'oled.h'


//C库

#include

#include



extern u8 send_flag,Receive_Data_Flag;



//串口发送一组数据的函数


void USART_Write(unsigned char *cmd, int len)

{  

  int i;

   USART_ClearFlag(USART1,USART_FLAG_TC);    //发送之前清空发送标志  没有这一句 很容易丢包 第一个数据容易丢失

   for(i=0;i    {

   USART_SendData(USART1,*cmd);   //发送当前数据

while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET);  //发送完成

cmd++;      //指针自加 准备发送下一个数据

   

}

 

}

//握手函数,也就是通过AT指令初始化8266

u8 Hand(char* cmd, char* result,int timeOut)//向8266发送AT指令,并判断8266是否返回正确的应答

memset(Rx_Buff, 0, sizeof(Rx_Buff)); //发送数据之前,先清空接收数组,数据在串口中接收。

Rx_count=0;

USART_Write((unsigned char *)cmd,strlen((const char *)cmd));   //用串口把cmd命令写给ESP8266

delay_ms(timeOut);

  if(strstr(Rx_Buff,result)!=NULL)    //判断指针a中的字符串是否是Rx232buffer数组中字符串的子串

return 1; 

else

return 0;

}


void ESP8266Mode_init(void)

{

AT_Mode = 1;  //进入发AT指令模式

     

//电脑作为服务器,wifi模块与其通信  

OLED_ShowString(0,0,'AT...',16);

while(!(Hand(AT,'OK',1000)))

{

}


OLED_Clear();

OLED_ShowString(0,0,'MODE...',16);

while(!(Hand(CWMODE1,'OK',1000)))//模块工作模式:STA模式,该模式的配置掉电不丢失

{

}

OLED_Clear();

OLED_ShowString(0,0,'JAP...',16);

while(!(Hand(CWJAP,'OK',3000)))//配置需要连接的WIFI热点SSID和密码 ,账号密码掉电不丢失

{

}

OLED_Clear();

OLED_ShowString(0,0,'MUX...',16);

while(!(Hand(CIPMUX0,'OK',1000)))//设置单链接模式,掉电之后再上电,该模式重归单链接模式

{

}   

OLED_Clear();

OLED_ShowString(0,0,'CIPMODE...',16);//掉电之后再上电,该模式重归非透传模式

while(!(Hand(CIPMODE1,'OK',1000)))//开启透传模式,透传模式也不用要求电脑和8266共同连接一个热点,只不过都得是同一个路由器,即连接党的热点都得是同一个路由器发出的

{

}   

OLED_Clear();

OLED_ShowString(0,0,'Connect...',16);

// SendCmd(CIPSTART, 'OK',6000);   //接入电脑服务器 

while(!(Hand(CIPSTART,'OK',3000)))//配置需要连接的WIFI热点SSID和密码 ,账号密码掉电不丢失

{

}    

OLED_Clear();

OLED_ShowString(0,0,'Init Success!',16);

delay_ms(8000);

OLED_Clear();

OLED_ShowString(0,0,'CIPSEND...',16);

while(!(Hand(CIPSEND,'>',3000)))//开启发送数据

{

}

OLED_ShowString(0,0,'CIPSEND Success',16);

delay_ms(10000);

OLED_Clear();

OLED_ShowString(0,0,'Receice...',16);

AT_Mode = 0;//AT指令发送完毕 退出该模式

Receive_Data_Flag = 0;//注意该标志位在完成所有的8266配置之后一定要再次清零,否则有时会收到一些莫名其妙的数据

memset(Rx_Buff, 0, sizeof(Rx_Buff)); //清空接收数组

Rx_count=0;

}



/** 

* 函数功能: 发送Cmd命令的函数

  * CMD: 需要发送的AT指令

  * result : 发送成功时返回的数值与result期望结果对比

  * timeOut :延迟时间

  *

*/

void SendCmd(char* cmd, char* result, int timeOut)

{

    while(1)

    {

memset(Rx_Buff, 0, sizeof(Rx_Buff)); //发送数据之前,先清空接收数组,数据在串口中接收。

Rx_count=0;

USART_Write((unsigned char *)cmd,strlen((const char *)cmd));   //用串口把cmd命令写给ESP8266

delay_ms(timeOut);                                          //延迟等待

LED=~LED;        //闪灯提醒

        if(ok_flag==1) //比较两个指针里面的数据是否一样,判断是否有预期的结果  和预期结果相同,表示指令设置成功,跳出

        {  

ok_flag=0;   //清空标志

            break;

        }

        else

        {

            delay_ms(100);

        }

    }

}



void ESP8266_SendData(unsigned char *data, unsigned short len)

{

 

  USART_Write(data,len);   //发送数据


}


AT模式下感觉delay_ms()函数延时时间缩短的10倍,反正极其不准确。

补充:不准确的原因可能就是因为文章开头说的那回事,delay_ms()入口参数最大值为1864,超过1864延时就乱完了。

esp8266.h


#ifndef _ESP8266_H_

#define _ESP8266_H_


extern unsigned char  AT_Mode;

extern char Rx_Buff[200];

extern int  Rx_count;


#define AT          'ATrn'

#define CWMODE1      'AT+CWMODE=1rn' //STA模式

#define CWMODE2      'AT+CWMODE=2rn' //AP模式

#define CWMODE3      'AT+CWMODE=3rn' //STA+AP模式

#define wifi_RST    'AT+RSTrn'

#define CIFSR       'AT+CIFSRrn'

#define CWJAP       'AT+CWJAP='Time','666666'rn'

#define CIPSTART    'AT+CIPSTART='TCP','192.173.23.67',12444rn'

//#define CIPSTART    'AT+CIPSTART='TCP','183.230.40.33',80rn'   

#define CIPMUX0       'AT+CIPMUX=0rn'  //开启单连模式

#define CIPMODE0    'AT+CIPMODE=0rn' //非透传模式

#define CIPMODE1    'AT+CIPMODE=1rn' //透传模式

#define CIPSEND     'AT+CIPSENDrn'   

#define Out_CIPSEND     '+++' 

#define CIPSTATUS   'AT+CIPSTATUSrn' //网络状态查询


void SendCmd(char* cmd, char* result, int timeOut);


void ESP8266_SendData(unsigned char *data, unsigned short len);

void ESP8266Mode_init(void);

void USART_Write(unsigned char *cmd, int len);


#endif


show.c


#include 'show.h'

#include 'usart.h'

#include 'led.h'

#include 'string.h'

float temperature = 0.31,speed = 0.24,s = 0.0,battery = 0.52;

extern u8 USART_RX_BUF[USART_REC_LEN];

int count = 12;

extern int  Rx_count;

extern char Rx_Buff[200];

extern u8 AT_Mode,Rx_Len,Receive_Data_Over,Receive_Data_Flag;

char Receive_Server_Data[5];

u8 send_flag = 0;

//显示带有两位小数的数,默认大小为16,参数num表示要显示的float类型的数,len为该数的长度

void OLED_Show_Float(u8 x,u8 y,float num,u8 len)

{

OLED_ShowNum(x,y,(int)(num),len-2,16);

OLED_ShowString(x+(len-2)*9,y,'.',16);

OLED_ShowNum(x+(len-2)*9+9,y,(int)(num*100),2,16);

}



//透传模式下的显示函数

//注意透传模式下,串口收到的数据的前两个元素不知道是啥,真正的数据是从第三个元素开始的。

void OLED_Show(void)

{

u8 i = 0;

OLED_Clear();

OLED_ShowString(0,0,'Receice...',16);

OLED_ShowString(0,2,'DataLength:',16);

OLED_ShowNum(99,2,Rx_count-2,1,16);//显示纯数据长度

OLED_ShowString(0,4,'Data:',16);

for(i=0;i {

OLED_ShowChar(45+i*9,4,Rx_Buff[2+i],16);//注意透传模式下,串口收到的数据的前两个元素不知道是啥,真正的数据是从第三个元素开始的。

}

for(i=0;i<5;i++)

{

Receive_Server_Data[i] = Rx_Buff[i+2];

}

if(Receive_Server_Data[0] == 'O' && Receive_Server_Data[1] == 'K')

{

send_flag = 1;//当客户端收到服务器发过来数据为OK的时候,置位标志位send_flag,表示此时客户端可以向服务器发送数据了

}

// OLED_ShowChar(0,6,send[0],16);

// OLED_ShowChar(9,6,send[1],16);

// if((strcmp(send,'OK')==0) && (send_flag == 0))

// {

// send_flag = 1;

// send[0] = 0;

// send[1] = 0;

// }

memset(Rx_Buff, 0, sizeof(Rx_Buff));  //清空Rx_Buff数组

Rx_count = 0;

Receive_Data_Flag = 0;//准备下一次接收

// OLED_ShowString一个字符串占两行,一个字母占2行9列

//  OLED_ShowCHinese一个汉字占2行18列

// OLED_ShowString(0,0,'SmartCar',16);


}


main.c


#include 'stm32f10x.h'

#include 'sys.h'

#include 'delay.h'

#include 'usart.h'

#include 'led.h'

#include 'show.h'

#include 'oled.h'

#include 'esp8266.h'


u8  AT_Mode=0; 

char Send_Data[6];

extern float temperature,speed,s,battery;

extern u8 Receive_Data_Over,send_flag,Receive_Data_Flag;

unsigned char dat[6] = '023.57';//客户端发送给服务器的数据

//透传

int main()

{

u8 i = 0;

delay_init();

LED_Init();

uart_init(115200);

OLED_Init();

OLED_Clear(); 

ESP8266Mode_init();

LED_OFF;

while(1)

{

if(Receive_Data_Flag == 1)

{

OLED_Clear();

OLED_Show();

}

if(send_flag == 1)

{

LED = ~LED;

OLED_Clear();

OLED_ShowString(0,0,'Send...',16);

ESP8266_SendData(dat,6);//向服务器发送数据

OLED_ShowString(0,0,'SendData:',16);

for(i=0;i<6;i++)//此时的Rx_count就是真实的纯数据长度再加上固定的两个字符

{

OLED_ShowChar(0+i*9,2,dat[i],16);

}

OLED_ShowString(0,4,'Send Over',16);

send_flag = 0;

}

}

}


Eclipse运行结果:

在这里插入图片描述

由于STM32端代码太多,故需要的请自行下载,程序里面会有一些用不到的代码,请找核心的看即可。若百度云中的程序中没有注释的,请来看下博客中的程序是不是有注释,我是有的注释写在了博客上,但是百度云文件中没写。

百度云下载链接:

链接:https://pan.baidu.com/s/1rA7QoVRTTbnyXX9UdNkn0Q

提取码:0v9y


如果需要更完整的STM32端的代码,即包括接收服务器的数据以及向服务器发送数据的完整代码,请下载:


链接:https://pan.baidu.com/s/1reuUT-HItQsyJ7YcqcBdfQ 

提取码:4r5b


注意:串口接收处理程序以该博客中的为准

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 家用电源无载自动断电装置的设计与制作

  • PIC单片机控制的遥控防盗报警器电路

  • 使用ESP8266从NTP服务器获取时间并在OLED显示器上显示

  • 开关电源的基本组成及工作原理

  • RS-485基础知识:处理空闲总线条件的两种常见方法

  • 基于TDA2003的简单低功耗汽车立体声放大器电路

    相关电子头条文章