自建基于STM32与ESP8266的物联网服务器

在这篇文章中我将从头教大家如何自己做一个物联网的demo出来。这个demo对于初学者来说如果是一步步去探索的话真的挺费时间的,期间也会遇到各种各样的问题,这里呢我就把我自己探索的路子分享给大家(demo基于TCP/IP协议,只要配置正确不存在连不上网的问题,因为所有的网络都是支持TCP/IP的)

1.材料准备工作:
购买一块STM32开发板,一个ESP8266模块,杜邦线若干,租用一台阿里云服务器。

2.硬件连接:
ESP8266的VCC接3.3v(5v也还凑合,不至于烧坏)
ESP8266的GND接GND
ESP8266的TXD接板子USART的RXD引脚
ESP8266的RXD接板子USART的TXD引脚
RST引脚与IO_0引脚不需要控制,悬空即可

3.服务器准备
第一步:登陆阿里云官网租用一台服务器,我选用的是学生轻量应用服务器(9.5元每月),操作系统windows server2012R2
第二步:购买完成后会跳转轻量应用服务器的控制台页面,设置远程桌面:
在这里插入图片描述
我选用的是win10自带的远程连接工具:
在这里插入图片描述
通过账号密码连接桌面。
第三步:端口设置
在这里插入图片描述
如图,点击防火墙,为了方便我把TCP和UDP的所有端口都开放了。点击添加规则即可开放端口。
第四步:服务器端安装软件
我把我电脑的D盘通过映射网络驱动器的方式映射到服务器(配置很简单,百度一下你就知道),在服务器内可以直接把D盘的软件复制过去。这里我们需要JDK,eclips,如果后期想运行java web项目的话建议再安装一个tomcat,然后再安装一个MySQL数据库,安装的时候要勾上允许远程访问,端口默认3306就OK了。关于java环境的配置以及软件安装我就不写了,网上到处都是这方面的文章。

4.硬件端的代码编写
基本的原理参见我的上一篇博客(https://blog.csdn.net/naruhina/article/details/104211705),今天的代码与上篇博客的代码略有不同,加入了防TCP自己断掉的心跳包机制,每隔十秒发送一次,由定时器驱动。代码与说明详见上传的资源,这里只放主要代码.

#include "sys.h"
#include "delay.h"
#include "usart.h"  		 	 		
#include "usart3.h"
#include "common.h" 
#include "StaConfig.h"
#include "timer3.h"
/************************************************
 第一次测试完成日期:2020/2/7
 
 第一次描述:通过USART3连接ESP8266的TX与RX。相关AT指令以及数据发送均通过USART3进行
 主函数调用setClient设置为STAClient透传模式与阿里云服务器取得通信,通过在电脑上使用
 串口助手发送字符串到串口1并在串口1中断服务函数内通过调用发送数据函数通过串口3发送给
 ESP8266,然后再经由路由器发送到云服务器。云服务器端通过网络调试助手创建一个TCP SERVER
 然后就可以向STM32发送数据了,接收到的数据由相应函数处理回显到串口调试助手内。
 
 备注:发送成功后atk_8266_send_data函数返回了失败,但实际上发送成功,应该是第二个参数不对应
 导致的,但无关紧要。并且程序内并没有调用退出透传的函数,重新烧录程序完成后掉一次电恢复一下。
 心跳包机制尚未加入
************************************************/

/************************************************
 第二次测试完成日期:2020/2/9
 
 本次主要修改了发送数据受缓存影响的bug,以及与向服务器发送的时候没有0x0a,0x0d的bug。新增防止因长时间无操作的定时发送心跳包的功能,以及确定
 后atk_8266_send_data函数无误,在透传模式下的返回值1是第二个参数不对应的误报,实际没任何错误。新增TCP Client 非透传模式
************************************************/

	
 int main(void)
 {	 
	u8 ipbuf[50];
	delay_init();	    	 //延时函数初始化	  
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
	usart3_init(115200);		//初始化串口3 
	TIM3_Int_Init(49999,14399);//5Khz的计数频率,计数到5000为1s 
	setClient(ipbuf);//TCP Client 透传模式
	//setClientNonPierce(ipbuf);
	while(1)
	{
		atk_8266_at_response(1);
		delay_ms(100);
	}
}

#include "StaConfig.h"
#include "common.h"
/***********************************************************/
//第一次测试完工日期:2020/2/7																	
//作者:lht
//2020/2/9 新增TCP Client 非透传模式
/***********************************************************/
 void setClient(u8 *ipbuf)//设置ESP8266工作在STA模式的CLIENT设置并开启透传
 {
	//printf("AT恢复出厂设置:%d\n",atk_8266_send_cmd("AT+RESTORE","OK",1000));
	 printf("AT测试:%d\n",atk_8266_send_cmd("AT","OK",500));
	 printf("设置为STA模式:%d\n",atk_8266_send_cmd("AT+CWMODE=1","OK",500));//设置为STA模式
	 printf("重启:%d\n",atk_8266_send_cmd("AT+RST","OK",500));//重启生效
		delay_ms(1000);         //延时3S等待重启成功
		delay_ms(1000);
		delay_ms(1000);
	 printf("连接到路由器:%d\n",atk_8266_send_cmd("AT+CWJAP=\"FAST666\",\"你的密码\"","OK",1000));//连接路由器
	 printf("设置单链接:%d\n",atk_8266_send_cmd("AT+CIPMUX=0","OK",3500));
	 atk_8266_get_wanip(ipbuf);
	 printf("IP:%s\n",ipbuf);																														//端口号不要带引号,否则会有意想不到的错误。
	 printf("连接到服务器:%d\n",atk_8266_send_cmd("AT+CIPSTART=\"TCP\",\"服务器公网IP\",8086","OK",3000));//连接到SERVER
	 printf("开启透传模式:%d\n",atk_8266_send_cmd("AT+CIPMODE=1","OK",3500));//开启透传模式
	 printf("开始透传:%d\n",atk_8266_send_cmd("AT+CIPSEND","OK",550));//开始透
 
 }
	 
  void setServer(u8 *ipbuf)//设置ESP8266工作在STA模式的SERVER设置
 {
	 //printf("AT恢复出厂设置:%d\n",atk_8266_send_cmd("AT+RESTORE","OK",1000));
	 printf("AT测试:%d\n",atk_8266_send_cmd("AT","OK",500));
	 printf("设置为STA模式:%d\n",atk_8266_send_cmd("AT+CWMODE=1","OK",500));//设置为STA模式
	 printf("重启:%d\n",atk_8266_send_cmd("AT+RST","OK",500));//重启生效
		delay_ms(1000);         //延时3S等待重启成功
		delay_ms(1000);
		delay_ms(1000);
	 printf("连接到路由器:%d\n",atk_8266_send_cmd("AT+CWJAP=\"FAST666\",\"你的密码\"","OK",1000));//连接路由器
	 printf("设置多链接:%d\n",atk_8266_send_cmd("AT+CIPMUX=1","OK",3500));
	 printf("开启服务器:%d\n",atk_8266_send_cmd("AT+CIPSERVER=1,8086","OK",2000));
	 atk_8266_get_wanip(ipbuf);
	 printf("IP:%s\n",ipbuf);
	 printf("开始传输:%d\n",atk_8266_send_cmd("AT+CIPSEND=3,25","OK",550));//开始透传
 }
 
  void setClientNonPierce(u8 *ipbuf)//STA Client 非透传模式
	{
	 printf("AT测试:%d\n",atk_8266_send_cmd("AT","OK",500));
	 printf("设置为STA模式:%d\n",atk_8266_send_cmd("AT+CWMODE=1","OK",500));//设置为STA模式
	 printf("重启:%d\n",atk_8266_send_cmd("AT+RST","OK",500));//重启生效
		delay_ms(1000);         //延时3S等待重启成功
		delay_ms(1000);
		delay_ms(1000);
	 printf("连接到路由器:%d\n",atk_8266_send_cmd("AT+CWJAP=\"FAST666\",\"你的密码\"","OK",1000));//连接路由器
	 printf("设置单链接:%d\n",atk_8266_send_cmd("AT+CIPMUX=0","OK",3500));
	 atk_8266_get_wanip(ipbuf);
	 printf("IP:%s\n",ipbuf);																														//端口号不要带引号,否则会有意想不到的错误。
	 printf("连接到服务器:%d\n",atk_8266_send_cmd("AT+CIPSTART=\"TCP\",\"服务器公网IP\",8086","OK",3000));//连接到SERVER
	 //printf("开启透传模式:%d\n",atk_8266_send_cmd("AT+CIPMODE=1","OK",3500));//开启透传模式
	 printf("允许数据传输:%d\n",atk_8266_send_cmd("AT+CIPSEND=8","OK",550));//允许数据发送,每次最大8字节
	
	}

	
#include "common.h"


//4个网络模式
const u8 *ATK_ESP8266_CWMODE_TBL[3]={"STA模式 ","AP模式 ","AP&STA模式 "};	//ATK-ESP8266,3种网络模式,默认为路由器(ROUTER)模式 
//4种工作模式
const u8 *ATK_ESP8266_WORKMODE_TBL[3]={"TCP服务器","TCP客户端"," UDP 模式"};	//ATK-ESP8266,4种工作模式
//5种加密方式
const u8 *ATK_ESP8266_ECN_TBL[5]={"OPEN","WEP","WPA_PSK","WPA2_PSK","WPA_WAP2_PSK"};
/////////////////////////////////////////////////////////////////////////////////////////////////////////// 

//usmart支持部分
//将收到的AT指令应答数据返回给电脑串口
//mode:0,不清零USART3_RX_STA;
//     1,清零USART3_RX_STA;
void atk_8266_at_response(u8 mode)
{
	if(USART3_RX_STA&0X8000)		//接收到一次数据了
	{ 
		USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
		printf("%s",USART3_RX_BUF);	//发送到串口
		if(mode)USART3_RX_STA=0;
	} 
}
//ATK-ESP8266发送命令后,检测接收到的应答
//str:期待的应答结果
//返回值:0,没有得到期待的应答结果
//    其他,期待应答结果的位置(str的位置)
u8* atk_8266_check_cmd(u8 *str)
{
	
	char *strx=0;
	if(USART3_RX_STA&0X8000)		//接收到一次数据了
	{ 
		USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
		strx=strstr((const char*)USART3_RX_BUF,(const char*)str);
	} 
	return (u8*)strx;
}
//向ATK-ESP8266发送命令
//cmd:发送的命令字符串
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果)
//       1,发送失败
u8 atk_8266_send_cmd(u8 *cmd,u8 *ack,u16 waittime)
{
	u8 res=0; 
	USART3_RX_STA=0;
	u3_printf("%s\r\n",cmd);	//发送命令
	if(ack&&waittime)		//需要等待应答
	{
		while(--waittime)	//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000)//接收到期待的应答结果
			{
				if(atk_8266_check_cmd(ack))
				{
					printf("ack:%s\r\n",(u8*)ack);
					break;//得到有效数据 
				}
					USART3_RX_STA=0;
			} 
		}
		if(waittime==0)res=1; 
	}
	return res;
} 
//向ATK-ESP8266发送指定数据
//data:发送的数据
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果)
u8 atk_8266_send_data(u8 *data,u8 *ack,u16 waittime)
{
	u8 res=0; 
	USART3_RX_STA=0;
	u3_printf("%s\r\n",data);	//发送数据,为了方便云服务器接收加入了回车换行符
	if(ack&&waittime)		//需要等待应答
	{
		while(--waittime)	//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000)//接收到期待的应答结果
			{
				if(atk_8266_check_cmd(ack))break;//得到有效数据 
				USART3_RX_STA=0;
			} 
		}
		if(waittime==0)res=1; 
	}
	return res;
}
//ATK-ESP8266退出透传模式
//返回值:0,退出成功;
//       1,退出失败
u8 atk_8266_quit_trans(void)
{
	while((USART3->SR&0X40)==0);	//等待发送空
	USART3->DR='+';      
	delay_ms(15);					//大于串口组帧时间(10ms)
	while((USART3->SR&0X40)==0);	//等待发送空
	USART3->DR='+';      
	delay_ms(15);					//大于串口组帧时间(10ms)
	while((USART3->SR&0X40)==0);	//等待发送空
	USART3->DR='+';      
	delay_ms(500);					//等待500ms
	return atk_8266_send_cmd("AT","OK",20);//退出透传判断.
}
//获取ATK-ESP8266模块的AP+STA连接状态
//返回值:0,未连接;1,连接成功
u8 atk_8266_apsta_check(void)
{
	if(atk_8266_quit_trans())return 0;			//退出透传 
	atk_8266_send_cmd("AT+CIPSTATUS",":",50);	//发送AT+CIPSTATUS指令,查询连接状态
	if(atk_8266_check_cmd("+CIPSTATUS:0")&&
		 atk_8266_check_cmd("+CIPSTATUS:1")&&
		 atk_8266_check_cmd("+CIPSTATUS:2")&&
		 atk_8266_check_cmd("+CIPSTATUS:4"))
		return 0;
	else return 1;
}
//获取ATK-ESP8266模块的连接状态
//返回值:0,未连接;1,连接成功.
u8 atk_8266_consta_check(void)
{
	u8 *p;
	u8 res;
	if(atk_8266_quit_trans())return 0;			//退出透传 
	atk_8266_send_cmd("AT+CIPSTATUS",":",50);	//发送AT+CIPSTATUS指令,查询连接状态
	p=atk_8266_check_cmd("+CIPSTATUS:"); 
	res=*p;									//得到连接状态	
	return res;
}


//获取Client ip地址
//ipbuf:ip地址输出缓存区
void atk_8266_get_wanip(u8* ipbuf)
{
	u8 *p,*p1;
		if(atk_8266_send_cmd("AT+CIFSR","OK",500))//获取WAN IP地址失败
		{
			ipbuf[0]=0;
			return;
		}		
		p=atk_8266_check_cmd("\"");
		p1=(u8*)strstr((const char*)(p+1),"\"");
		*p1=0;
		sprintf((char*)ipbuf,"%s",p+1);	
}


u8 atk_8266_chaxun(u8 *cmd,u16 waittime)
{
	u8 res=0; 
	USART3_RX_STA=0;
	u3_printf("%s\r\n",cmd);	//发送命令
	if(waittime)		//需要等待应答
	{
		while(--waittime)	//等待倒计时
		{
			delay_ms(10);
			if(USART3_RX_STA&0X8000)//接收到期待的应答结果
			{
				
					USART3_RX_STA=0;
					printf("查询指令ack:%s\r\n",USART3_RX_BUF);
					break;//得到有效数据
			} 
		}
		if(waittime==0)res=1; 
	}
	return res;

}

5.服务器端
服务器端则是用Socket建立TCP连接并监听指定端口的信息,然后连接MySQL数据库把数据写进去。这里只放Socket部分,jdbc以及其他部分见上传的资源

package main;

import java.io.BufferedReader;

import java.io.BufferedWriter;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.OutputStreamWriter;

import java.net.ServerSocket;

import java.net.Socket;

import DB.DBTool;
import changeCode.ChangeToUtf8;

 /**
  * 
  *双向通信,首先硬件端向服务器发送数据,然后服务器端在控制台输入响应敲回车发送给硬件端完成一次应答。 
  * 
  */

public class DuplexTrans {

    public static void main(String[] args){
    	String sql;
        Socket socket = null;
        BufferedReader in = null;
        BufferedWriter out = null;
        BufferedReader br = null;
        try {
            //创建服务器端套接字:指定监听端口
            ServerSocket server = new ServerSocket(8086);
            //监听客户端的连接
            socket = server.accept();
            //获取socket的输入输出流接收和发送信息
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            br = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                //接收客户端发送的信息
                String str = in.readLine();
                str = ChangeToUtf8.toUTF8(str);//转换编码防止乱码
                
                String str2 = "";
                //如果客户端发送的是“end”则终止连接 
                if (str.equals("end")){
                    break;
                }else if(str.equals("heartBeat")) {
                	
                }else {//非心跳包,发送反馈信息并写入数据库
                	System.out.println("客户端发来:" + str);
                	sql = "insert into iottest(content) values(?)";
                    DBTool.update(sql, str);
                    
                    str2 = br.readLine(); // 读到\n为止,因此一定要输入换行符!
                    out.write(str2 + "\n");
                    out.flush();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(br != null){
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

6.测试
在这里插入图片描述
在左边远程连接窗口内可以看到控制台中输出了fromStm32,它是从本地的串口调试助手输入到USART1然后在到USART3,而USART3连接我的ESP8266。右边串口调试助手收到的fromServer是在服务器的eclipse的控制台输入的,数据到达ESP8266后发送给串口3,再发送到串口1让串口调试助手显示信息。

我们再来看看此时云端数据库内有没有被写入信息:
在这里插入图片描述
可以看到服务器收到的信息已经被写入了云端的数据库

完整代码:https://download.csdn.net/download/naruhina/12146142

顺带一提,可以建一个web项目,2.5版本即可,代码调试完成后导出一个war包放到服务器端的Tomcat里运行,这样就不用在服务器端启动eclipse来看控制台输出了。

发布了75 篇原创文章 · 获赞 23 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/naruhina/article/details/104237504