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

系列文章:
ESP8266的AP模式与STA模式简单测试
简单的Java服务器和客户端的通信
STM32 ESP8266和Java服务器透传模式下的双向通信
jsp向servlet传输数据
Servlet向JSP传递数据以及JSP页面DIV定时局部刷新
JSP向Servlet传递数据以及与STM32、ESP8266通信过程

本文主要实现的功能是:一个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指令必须是以\r\n结尾,数据应该没有这个要求
		{
	    	Res =USART_ReceiveData(USART1);//(USART1->DR);	//读取接收到的数据	
			Rx_Buff[Rx_count]=Res;
			if(AT_Mode==1)			                         //AT指令模式
			{
				if(strstr(Rx_Buff,"OK")||strstr(Rx_Buff,">")//判断ESP8266是否返回相应的正确回应,返回 > 表示透传模式下8266允许串口向其发送数据了
				{
					memset(Rx_Buff, 0, sizeof(Rx_Buff));  //清空Rx_Buff数组
					ok_flag=1;			 
					Rx_count=0;	
				}
				else                                   //否则继续接受数据
				{
					Rx_count++;	
				}
			}
			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 <string.h>
#include <stdio.h>


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<len;i++)
	   {
		   USART_SendData(USART1,*cmd);   //发送当前数据
			 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET);  //发送完成
			 cmd++;      //指针自加 准备发送下一个数据
		   
		 }
 
}


void ESP8266Mode_init(void)
{
	AT_Mode = 1;  //进入发AT指令模式
     
	//电脑作为服务器,wifi模块与其通信  
	OLED_ShowString(0,0,"AT...",16);
	SendCmd(AT, "OK", 3000);		//模块有效性检查
	
	//由于使用AT+RST指令时,8266返回的不是OK,而是一大堆东西,所以这里不能再判断是否返回的是OK了,否则程序会一直死在这里
//	OLED_Clear();
//	OLED_ShowString(0,0,"RST...",16);
//	SendCmd(wifi_RST, "OK", 3000);		//模块重置

	OLED_Clear();
	OLED_ShowString(0,0,"MODE...",16);
	SendCmd(CWMODE1, "OK", 3000);	//模块工作模式:STA模式,该模式的配置掉电不丢失
	
	OLED_Clear();
	OLED_ShowString(0,0,"JAP...",16);
	SendCmd(CWJAP, "OK", 6000);		//配置需要连接的WIFI热点SSID和密码 ,账号密码掉电不丢失
	
	OLED_Clear();
	OLED_ShowString(0,0,"MUX...",16);
	SendCmd(CIPMUX0, "OK", 3000);   //设置单链接模式,掉电之后再上电,该模式重归单链接模式
	
	OLED_Clear();
	OLED_ShowString(0,0,"CIPMODE...",16);//掉电之后再上电,该模式重归非透传模式
	SendCmd(CIPMODE1, "OK", 3000);   //开启透传模式,透传模式也不用要求电脑和8266共同连接一个热点,只不过都得是同一个路由器,即连接党的热点都得是同一个路由器发出的
	
	
	OLED_Clear();
	OLED_ShowString(0,0,"Connect...",16);
	SendCmd(CIPSTART, "OK", 6000);   //接入电脑服务器  
	
	OLED_Clear();
	OLED_ShowString(0,0,"Init Success!",16);	
	delay_ms(8000);
	
	
	OLED_Clear();
	OLED_ShowString(0,0,"CIPSEND...",16);
	SendCmd(CIPSEND, ">", 10000);	//开启发送数据
	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倍,反正极其不准确。
esp8266.h

#ifndef _ESP8266_H_
#define _ESP8266_H_

extern unsigned char  AT_Mode;
extern char Rx_Buff[200];
extern int  Rx_count;



#define AT          "AT\r\n"	
#define CWMODE1      "AT+CWMODE=1\r\n"		//STA模式
#define CWMODE2      "AT+CWMODE=2\r\n"		//AP模式
#define CWMODE3      "AT+CWMODE=3\r\n"		//STA+AP模式
#define wifi_RST    "AT+RST\r\n"
#define CIFSR       "AT+CIFSR\r\n"
#define CWJAP       "AT+CWJAP=\"Time\",\"666666\"\r\n"	
#define CIPSTART    "AT+CIPSTART=\"TCP\",\"192.173.23.67\",12444\r\n"	
//#define CIPSTART    "AT+CIPSTART=\"TCP\",\"183.230.40.33\",80\r\n"		  
#define CIPMUX0       "AT+CIPMUX=0\r\n"  //开启单连模式
#define CIPMODE0    "AT+CIPMODE=0\r\n"		//非透传模式
#define CIPMODE1    "AT+CIPMODE=1\r\n"		//透传模式
#define CIPSEND     "AT+CIPSEND\r\n"   
#define Out_CIPSEND     "+++" 
#define CIPSTATUS   "AT+CIPSTATUS\r\n"		//网络状态查询




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<Rx_count-2;i++)//此时的Rx_count就是真实的纯数据长度再加上固定的两个字符
	{
		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

其他相关的文章:

Servlet向JSP传递数据以及JSP页面DIV定时局部刷新
JSP向Servlet传递数据以及与STM32、ESP8266通信过程

发布了103 篇原创文章 · 获赞 319 · 访问量 36万+

猜你喜欢

转载自blog.csdn.net/qq_36554582/article/details/103897681