51单片机 + ESP8266 实现远程控制小灯、温度采集

目录

 

1、准备工作

1.1、软件

1.2、硬件

2、硬件连线

3、程序实现

3.1、单片机程序实现

main.c

public.h

public.c

temp.h

temp.c

3.2、服务端实现

4、效果展示

5、总结


1、准备工作

1.1、软件

  • keil 4 --- 单片机程序编译、构建
  • 串口调试助手 --- 初始化 ESP8266
  • java8 运行环境 --- 服务端运行环境(可以在 oracle 官网下载,也可以直接在360软件管家上下载)
  • maven 3 --- 构建项目(配置远程仓库为阿里公共仓库)
  • IDEA 2019.3 --- Java IDE,用于直接运行服务端。

1.2、硬件

  • STC89C516
  • 跳线若干
  • ESP8266
  • DS18B20

笔者手里面的硬件是 普中51-单核-A4 51单片机开发学习板,上面的硬件都已经集成在开发学习板上了。

2、硬件连线

可以参考我的上一篇文章 硬件连线 部分,没有变动。

3、程序实现

3.1、单片机程序实现

  • main.c

#include "public.h"
#include "temp.h"

sbit LED1 = P2^0;
sbit LED2 = P2^1;
sbit LED3 = P2^2;
sbit LED4 = P2^3;
sbit LED5 = P2^4;
sbit LED6 = P2^5;
sbit LED7 = P2^6;
sbit LED8 = P2^7;

// 全局配置信息
// wifi 名称
//ucharx* WIFI_NAME = "";
// wifi 密码
//ucharx* WIFI_PWD = "";
// tcp server 地址
uchar* xdata TCP_SERVER_ADDRESS = "192.168.0.6";
// tcp server 端口
uint TCP_SERVER_PORT = 8888;
// 设备名称
uchar* xdata DEVICE_NAME = "test-01";
// 连接状态校验间隔时间, 单位: 秒
uintx CHECK_TCP_STATUS_INTERVAL = 30;
// 数据采集间隔时间, 单位: 秒
uintx DATA_COLLECT_INTERVAL = 5;

// 串口中断接收相关
uintx scount = 0, maxLen = 150, tscount = 0;
ucharx srdatas[151];

// 全局外部 RAM 暂存指针
ucharx __rdatasCpy__[151];

// 计时器 0, 多大代表有多少个 50ms(0 ~ 65535)
uintx time0Count = 0;
// 串口接收数据已处理标志
bit sflag = 0;
// 校验连接发送标志(1-需要发送查询指令, 2-查询指令已经发送)
ushortx tsflag = 0;
// 是否需要读取温度信息并发送
bit tempFlag = 0;

void SysInit();
void SetWifi();

void DealReceiveData();
ushort DealReceiveControlCommand(uchar* line, uint length);
ushort DealReceiveRemoteConnectInfo(uchar* line, uint length);

void ValidWifiConnectInfo(ushort action);

uchar* GenerateDeviceStatusReportInfo(uchar type);
void SendDeviceStatusInfo2Server(uchar type, uchar forceSend);

uchar* GetSerialReceiveData();

// 深度拷贝出一份串口当前接收到的数据
uchar* GetSerialReceiveData() {
	 uintx i = 0;
	 clearStr(__rdatasCpy__, 151);
	
	 for(i = 0; i < scount; i++) {
	 	 __rdatasCpy__[i] = srdatas[i];
	 }

	 return __rdatasCpy__;
}

// 生成设备端的报告信息
uchar* GenerateDeviceStatusReportInfo(uchar type) {
	ucharx str[50];
	int xdata temp;
	clearStr(str, 50);

	// 只上报 LED 状态
	if(type == 0) {
		sprintf(str, "data-reporting %s led=%c%c%c%c%c%c%c%c\r\n", 
		DEVICE_NAME, 
		!LED1 ? '1' : '0', !LED2 ? '1' : '0', !LED3 ? '1' : '0', !LED4 ? '1' : '0', 
		!LED5 ? '1' : '0', !LED6 ? '1' : '0', !LED7 ? '1' : '0', !LED8 ? '1' : '0');
		return str;	
	}
	
	EA = 0;
	temp = Ds18b20ReadTemp();
	EA = 1;

	sprintf(str, "data-reporting %s led=%c%c%c%c%c%c%c%c;temp=%d\r\n",
		DEVICE_NAME, 
		!LED1 ? '1' : '0', !LED2 ? '1' : '0', !LED3 ? '1' : '0', !LED4 ? '1' : '0', 
		!LED5 ? '1' : '0', !LED6 ? '1' : '0', !LED7 ? '1' : '0', !LED8 ? '1' : '0',
		temp);

	return str;
}


// 接收到字符串出现 /r/n, 视为新行标志
void DealReceiveData() {
	uintx t = 0, len = 0;
	ushortx executeResult = 0;
	uchar* xdata newLine;
	uchar* xdata tempStr;
	uchar* tline;
	
	// 判定为未接收到有效数据
	if(sflag == 1 || scount < 4) {
		return;
	}

	// 延时之后依然没有数据, 既断定为串口有数据接收到
	tscount = scount;
	// 使用 Delay2 有问题
	// Delay2(100);
	t = time0Count;
	// 延时 50 ms
	while(abs(time0Count - t) <= 0);

	if(scount != tscount) {
	   return;
	}
   	
	// 数据处理期间暂停串口中断使能
	ES = 0;
	/**/
	newLine = GetSerialReceiveData();

	// 处理完之后清空串口接收缓存
	clearStr(srdatas, maxLen + 1);
	scount = 0;
	sflag = 1;
	ES = 1;
	
	newLine = trimStr(newLine);
	// trim 之后会拷贝已经接收的数据, 之后开启串口继续监听接口接收远程数据
	len = strlen(newLine);
	
	// 无有效数据
	if(len < 4) {
		return;
	}

	t = 0;
	do {
		if(t == 0) {
			tempStr = strtok(newLine, "\r\n");	
		} else {
			tempStr = strtok(NULL, "\r\n");
		}

		t++;
		
		if(tempStr != NULL) {
			tempStr = trimStr(tempStr);
			len = strlen(tempStr);

			if(len > 0) {	
			   executeResult = DealReceiveControlCommand(tempStr, len);
			   
			   if(executeResult == 1) {
			   	   continue;
			   }

			   DealReceiveRemoteConnectInfo(tempStr, len);
			}
		}	
	} while(tempStr != NULL);
}

ushort DealReceiveControlCommand(uchar* line, uintx length) {
	uintx i = 0, t = 0, len = 0;
	ucharx controlCommand;
	uchar* xdata tempStr;
	uchar* xdata command;

	if(length <= 3) {
		return 0;
	}

	// 不是以 "+IPD" 开头
	if(memcmp(line, "+IPD", 4) != 0) {
		return 0;
	}

	do {
		if(t == 0) {
			tempStr = strtok(line, ":");	
		} else {
			tempStr = strtok(NULL, ":");
		}
		
		if(tempStr != NULL) {
			t++;
			if(t == 2) {
				command = trimStr(tempStr);
			}
		}
	} while(tempStr != NULL);

	if(t == 2) {
		t = 0;
		do {
			if(t == 0) {
				tempStr = strtok(command, " ");
				if(strcmp(tempStr, "led") != 0) {
					return 0;
				}	
			} else {
				tempStr = strtok(NULL, " ");
			}
			
			if(tempStr != NULL) {
				t++;
				if(t == 2) {
					command = trimStr(tempStr);
				}
			}
		} while(tempStr != NULL);
		
		if(t == 2) {
			len = strlen(command);
			len = len > 8 ? 8 : len;

			for(i < 0; i < len; i++) {
				if(command[i] == '/') {
					continue;
				}
		   		controlCommand = command[i] - 0x30;
				switch(i + 1) {
					case 1:
						LED1 = !controlCommand;
						break;
					case 2:
						LED2 = !controlCommand;
						break;
				    case 3:
						LED3 = !controlCommand;
						break;
					case 4:
						LED4 = !controlCommand;
						break;
					case 5:
						LED5 = !controlCommand;
						break;
					case 6:
						LED6 = !controlCommand;
						break;
					case 7:
						LED7 = !controlCommand;
						break;
					case 8:
						LED8 = !controlCommand;
						break;
				}	
			}

			// 重新上传 LED 状态
			SendDeviceStatusInfo2Server(0, 1);
			return 1;	
		}	
	}

	return 0;
}

// 处理接收连接状态信息
ushort DealReceiveRemoteConnectInfo(uchar* line, uint length) {
	// 未发送查询指令, 跳过
	if(tsflag != 2) {
		return 0;	
	}
	
	// 接收到连接状态响应信息
	if(memcmp(line, "STATUS", 6) == 0) {
	   // 远程未连接
	   if(strcmp(line, "STATUS:3") != 0) {
	   	 // 重连
		 ValidWifiConnectInfo(2);
	   } 
	   // 远程连接有效
	   else {
	   	  ValidWifiConnectInfo(3);
	   }

	   tsflag = 3;
	   return 1;
	}

	return 0;
}

// action: 1 表示发送发送状态查询指令, action: 2 表示重新连接远程服务器, 并上送客户端 ID
// action: 3 表示重新上送客户端 ID
void ValidWifiConnectInfo(ushort action) {
	 uchar xdata tempStr[100];
	 uintx len = 0;

	 if(action == 1) {
		 printf("AT+CIPSTATUS\r\n");
		 tsflag = 2;
		 return;
	 }

	 if(action == 2) {
	 	clearStr(tempStr, 100);
	 	ET0 = 0;
		
		printf("AT+CIPCLOSE=2\r\n");
		
		Delay2(5);
		printf("AT+CIPSTART=2,\"TCP\",\"%s\",%d\r\n", TCP_SERVER_ADDRESS, TCP_SERVER_PORT);
		
		Delay2(20);
		sprintf(tempStr, "register %s\r\n", DEVICE_NAME);
		len = strlen(tempStr);
		printf("AT+CIPSEND=2,%d\r\n", len + 2);

		Delay2(20);
		printf("%s\r\n\r\n", tempStr);

		tsflag = 0;
		ET0 = 1;
	 }

	 if(action == 3) {
	 	 clearStr(tempStr, 100);
		 ET0 = 0;
		 
		 sprintf(tempStr, "register %s\r\n", DEVICE_NAME);
		 len = strlen(tempStr);
		 printf("AT+CIPSEND=2,%d\r\n", len + 2);

		 Delay2(20);
		 printf("%s\r\n\r\n", tempStr);

		 tsflag = 0;
		 ET0 = 1;
	 }

}

void SendDeviceStatusInfo2Server(uchar type, uchar forceSend) {
	uchar* xdata reportInfo;
	uintx len = 0;

	if(forceSend == 0 && tempFlag == 0) {
		return;
	}
	
	// 读取温度期间关闭中断使能
	reportInfo = GenerateDeviceStatusReportInfo(type);
	len = strlen(reportInfo);
	printf("AT+CIPSEND=2,%d\r\n", len + 2);

	Delay2(20);
	printf("%s\r\n\r\n", reportInfo);

	tempFlag = 0;
}

void SysInit() {
  	// 初始化定时器1, 配置波特率发生器
	TH1 = 0xFD;	 //晶振11.0592mhz 波特率设为9600
	TL1 = TH1;
	//定时器1方式2
	TMOD |= 0x20;
	//串口接收使能	 
	SCON = 0x50;
	//串口中断使能	 
	ES = 1;
	//定时器1使能			 
	TR1 = 1;
	//发送中断标记位,必须设置		 
	TI = 1;	
			 
	// 初始化定时器0, 做系统定时任务(11.0592MHz, 定时 50ms)
	/* */
	TH0 = 0x4C;
	TL0 = 0x00;
	// 工作在方式2
	TMOD |= 0x01;
	// 定时器0使能
	TR0 = 1; 
	ET0 = 1;

	// 初始化串口数据接收缓存
	clearStr(srdatas, maxLen + 1);
	
	// 初始化 wifi 模块
	printf("begin init wifi module...\r\n");
	SetWifi();
	printf("wifi module inited...\r\n");

	EA=1;
}

void SetWifi() {
   ucharx tempStr[100];
   uintx len = 0;
   clearStr(tempStr, 100);

   // 预留足够的时间让 wifi 启动
   Delay2(3000);		
   printf("AT+CIPMUX=1\r\n");
   
   Delay2(1000);		
   printf("AT+CIPSERVER=1\r\n");

   Delay2(1000);
   printf("AT+CIPCLOSE=2\r\n");

   Delay2(2000);
   printf("AT+CIPSTART=2,\"TCP\",\"%s\",%d\r\n", TCP_SERVER_ADDRESS, TCP_SERVER_PORT);;

   Delay2(2000);
   sprintf(tempStr, "register %s\r\n", DEVICE_NAME);
   len = strlen(tempStr);
   printf("AT+CIPSEND=2,%d\r\n", len + 2);
	
   Delay2(200);
   printf("%s\r\n\r\n", tempStr);

   Delay2(10);
}

/**/
void Timer0() interrupt 1 {
	uintx t = 0;
	ET0 = 0;
	// 继续下一轮定时
	TH0 = 0x4C;
	TL0 = 0x00;
	time0Count = (time0Count + 1) % 65536;
	
	// 大概 30s 判断一下连接是否断开, 断开之后需要重连
	if(time0Count % (20 * CHECK_TCP_STATUS_INTERVAL) == 0) {
		tsflag = 1;
	}

	// 大概 5s 上报一次温度信息
	if(tempFlag == 0 && time0Count % (20 * DATA_COLLECT_INTERVAL) == 0) {
		tempFlag = 1;
	}

	ET0 = 1;
}

void Usart() interrupt 4 {
    if(RI == 1)
    {
		RI = 0;
		srdatas[scount] = SBUF;
		scount += 1;
		if(scount >= maxLen) {
			scount = 0;	
		}
		sflag = 0;
	}
}

void main() {
	SysInit();
	while(1) {
		DealReceiveData();
		ValidWifiConnectInfo(tsflag == 1 ? 1 : 0);
		SendDeviceStatusInfo2Server(1, 0);	
	}
}
  • public.h

#ifndef __TEMP_H_
#define __TEMP_H_

#include "public.h"
//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif

#ifndef uint 
#define uint unsigned int
#endif

//--定义使用的IO口--//
sbit DSPORT=P3^7;

//--声明全局函数--//
void Delay1ms(uint );
uchar Ds18b20Init();
void Ds18b20WriteByte(uchar com);
uchar Ds18b20ReadByte();
void  Ds18b20ChangTemp();
void  Ds18b20ReadTempCom();
int Ds18b20ReadTemp();

#endif
  • public.c

#include "public.h"

// 延时 2 毫秒
void Delay2(ulong cnt) {
	ulongx i = 0, size = cnt * 10;
 	for(i=0; i < size; i++);
}


// 10 进制转 2 进制
uchar* decimal2binary(uint val) {
	uchar chs[8];
    uint i = 0, tmp = val;
	for(i = 0; i < 8; i++) {
		if(tmp == 0) {
			chs[i] = 0;
			continue;
		}
		chs[i] = (tmp % 2) + 48;
		tmp = tmp / 2;
	}
	
	for(i = 0; i < 4; i++) {
		tmp = chs[i];
		chs[i] = chs[8-i-1];
		chs[8-i-1] = tmp;	
	}
	return chs;
}

// 将字符串转换为 int 类型
uint parseInt(uchar* str, uint len) {
	uint i = 0, resVal = 0;
	for(i = 0; i < len; i++) {
		resVal = resVal + ((str[i] - 0x30) * pow(10, len - i - 1)); 	
	}
	return resVal;
}


// 反转字符串
uchar* reversalStr(uchar* str) {
	uintx len = strlen(str);
	uintx hlen = len / 2;
	uintx i = 0;
	ucharx tch;
	 
	for(i = 0; i < hlen; i++) {
		tch = str[i];
		str[i] = str[len - i - 1];
		str[len - i - 1] = str[i]; 	
	}

	return str;
}

//去除尾部空格
uchar* rtrimStr(uchar* str)
{
	uintx len = strlen(str);
	uchar* xdata p = str + len - 1;

	if (str == NULL || *str == '\0')
	{
		return str;
	}
 
	
	while (p >= str  && isspace(*p))
	{
		*p = '\0';
		--p;
	}
 
	return str;
}
 
//去除首部空格
uchar* ltrimStr(uchar* str)
{
	uintx len = 0;
	uchar* xdata p = str;

	if (str == NULL || *str == '\0')
	{
		return str;
	}
	
	while (*p != '\0' && isspace(*p))
	{
		++p;
		++len;
	}
 
	memmove(str, p, strlen(str) - len + 1);
 
	return str;
}
 
//去除首尾空格
uchar* trimStr(uchar* str)
{
	str = rtrimStr(str);
	str = ltrimStr(str);
	
	return str;
}

// 清空字符数组
uchar* clearStr(uchar* str, uint len) {
	uintx i = 0;
	if(len == -1) {
		len = strlen(str);
	}
	for(i = 0; i < len; i++) {
		str[i] = '\0';	
	}
	return str;
}
  • temp.h

#ifndef __TEMP_H_
#define __TEMP_H_

#include "public.h"
//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif

#ifndef uint 
#define uint unsigned int
#endif

//--定义使用的IO口--//
sbit DSPORT=P3^7;

//--声明全局函数--//
void Delay1ms(uint );
uchar Ds18b20Init();
void Ds18b20WriteByte(uchar com);
uchar Ds18b20ReadByte();
void  Ds18b20ChangTemp();
void  Ds18b20ReadTempCom();
int Ds18b20ReadTemp();

#endif
  • temp.c

#include"temp.h"

/*******************************************************************************
* 函 数 名         : Delay1ms
* 函数功能		   : 延时函数
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

void Delay1ms(uint y)
{
	uint x;
	for( ; y>0; y--)
	{
		for(x=110; x>0; x--);
	}
}
/*******************************************************************************
* 函 数 名         : Ds18b20Init
* 函数功能		   : 初始化
* 输    入         : 无
* 输    出         : 初始化成功返回1,失败返回0
*******************************************************************************/

uchar Ds18b20Init()
{
	uchar i;
	DSPORT = 0;			 //将总线拉低480us~960us
	i = 70;	
	while(i--);//延时642us
	DSPORT = 1;			//然后拉高总线,如果DS18B20做出反应会将在15us~60us后总线拉低
	i = 0;
	while(DSPORT)	//等待DS18B20拉低总线
	{
		Delay1ms(1);
		i++;
		if(i>5)//等待>5MS
		{
			return 0;//初始化失败
		}
	
	}
	return 1;//初始化成功
}

/*******************************************************************************
* 函 数 名         : Ds18b20WriteByte
* 函数功能		   : 向18B20写入一个字节
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

void Ds18b20WriteByte(uchar dat)
{
	uint i, j;

	for(j=0; j<8; j++)
	{
		DSPORT = 0;	     	  //每写入一位数据之前先把总线拉低1us
		i++;
		DSPORT = dat & 0x01;  //然后写入一个数据,从最低位开始
		i=6;
		while(i--); //延时68us,持续时间最少60us
		DSPORT = 1;	//然后释放总线,至少1us给总线恢复时间才能接着写入第二个数值
		dat >>= 1;
	}
}
/*******************************************************************************
* 函 数 名         : Ds18b20ReadByte
* 函数功能		   : 读取一个字节
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/


uchar Ds18b20ReadByte()
{
	uchar byte, bi;
	uint i, j;	
	for(j=8; j>0; j--)
	{
		DSPORT = 0;//先将总线拉低1us
		i++;
		DSPORT = 1;//然后释放总线
		i++;
		i++;//延时6us等待数据稳定
		bi = DSPORT;	 //读取数据,从最低位开始读取
		/*将byte左移一位,然后与上右移7位后的bi,注意移动之后移掉那位补0。*/
		byte = (byte >> 1) | (bi << 7);						  
		i = 4;		//读取完之后等待48us再接着读取下一个数
		while(i--);
	}				
	return byte;
}
/*******************************************************************************
* 函 数 名         : Ds18b20ChangTemp
* 函数功能		   : 让18b20开始转换温度
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

void  Ds18b20ChangTemp()
{
	Ds18b20Init();
	Delay1ms(1);
	Ds18b20WriteByte(0xcc);		//跳过ROM操作命令		 
	Ds18b20WriteByte(0x44);	    //温度转换命令
	//Delay1ms(100);	//等待转换成功,而如果你是一直刷着的话,就不用这个延时了
   
}
/*******************************************************************************
* 函 数 名         : Ds18b20ReadTempCom
* 函数功能		   : 发送读取温度命令
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

void  Ds18b20ReadTempCom()
{	

	Ds18b20Init();
	Delay1ms(1);
	Ds18b20WriteByte(0xcc);	 //跳过ROM操作命令
	Ds18b20WriteByte(0xbe);	 //发送读取温度命令
}
/*******************************************************************************
* 函 数 名         : Ds18b20ReadTemp
* 函数功能		   : 读取温度
* 输    入         : 无
* 输    出         : 无
*******************************************************************************/

int Ds18b20ReadTemp()
{
	int xdata temp = 0;
	uchar xdata tmh, tml;
	float xdata tp;
	Ds18b20ChangTemp();			 	//先写入转换命令
	Ds18b20ReadTempCom();			//然后等待转换完后发送读取温度命令
	tml = Ds18b20ReadByte();		//读取温度值共16位,先读低字节
	tmh = Ds18b20ReadByte();		//再读高字节
	temp = tmh;
	temp <<= 8;
	temp |= tml;

	// 此时得到的温度是实际数值的补码,需要转换
	if(temp < 0)				//当温度值为负数
  	{
		//因为读取的温度是实际温度的补码,所以减1,再取反求出原码
		temp=temp-1;
		temp=~temp;
		tp=temp;
		temp=tp*0.0625*100+0.5;	
		//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
		//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
		//算加上0.5,还是在小数点后面。
 
  	}
 	else
  	{			
		tp=temp;//因为数据处理有小数点所以将温度赋给一个浮点型变量
		//如果温度是正的那么,那么正数的原码就是补码它本身
		temp=tp*0.0625*100+0.5;	
		//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
		//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
		//算加上0.5,还是在小数点后面。
	}

	return temp;
}

上面是所有的单片机端程序实现,具体细节和笔者的上一篇文章 51 单片机 + ESP8266 实现互联网远程控制小灯 程序讲解部分大体相同,但是在该程序的基础上做了以下优化:

  • 串口接收数据缓存长度提高到 150 个字节
  • 支持处理多行数据
  • 远程重连方式修改为远程连接校验、断开重连方式
  • 添加客户端小灯状态、温度上传功能

注意:程序编译选项要按照如下的配置选择!!!

51单片机 + ESP8266 实现远程控制小灯、温度采集_第1张图片

构建结果如下:

51单片机 + ESP8266 实现远程控制小灯、温度采集_第2张图片

将构建好的目标文件烧入单片机即可。

3.2、服务端实现

服务端采用的技术栈如下:

  1. netty --- 做 tcp server (嵌入式启动,能够单机保持大量 tcp 长连接而不占用大量内存,很赞)
  2. jetty9 --- 做 http server(嵌入式启动,很赞)
  3. hutool --- java 后端工具类(很赞)
  4. maven3 --- 项目构建工具
  5. jquery --- 涉及过一点点前端的都应该知道这位老将军的大名,虽然现在有点过时了,但是并不代表它不好用,更不能代表它不适合现在 web 网站开发。
  6. vue --- 尤大大 神作,web 前端 mvvm 框架主流框架之一,国内很出名。
  7. vant --- 有赞前端团队基于 vue 打造的移动端 UI 库,这里不和其他移动端 UI 库比较,个人觉得好用到哭,大赞有 赞前端团队 的开源精神。

服务端代码这里就不贴出来了,个人会上传到 gitee 和 github 上面,小伙伴们克隆下载即可。

4、效果展示

51单片机 + ESP8266 实现远程控制小灯、温度采集_第3张图片

51单片机 + ESP8266 实现远程控制小灯、温度采集_第4张图片

 

开发板

5、总结

按照自己的设想,服务端还有许多需要实现的功能,这里指示做了一个简单的架子,小伙伴们有兴趣可以在这个架子上添加更多功能,如果功能复杂建议引入 J2EE 框架,方便后期开发。

有问题可以留言评论,笔者会尽量为各位解答。

你可能感兴趣的:(单片机)