本搬砖工开始玩串口转wifi传输模块ESP_8266(以前只会JAVA)。前段时间做了个JAVA服务端控制8盏LED亮灭的demo,美中不足是单片机连接服务器需要预先知道部署在内网的IP地址,所以又做了个局域网内上位机与模块相互发现的服务发现程序。
通讯采用上位机主动UDP广播的模式。java服务器和单片机同时开启UDP广播模式。服务端对192.168.1.255(局域网网段广播地址)发送设备寻找命令,单片机收到后获取IP地址,回复设备应答命令,双方确认后,单片机断开UDP连接,作为TCP客户端连接服务器的TCP socket服务端(随服务器运行启动),后续通过TCP请求通信控制单片机的应用功能。
设备端代码:
main.c
#include "config.h"
#include "delay.h"
#include "USART1.h"
//#include "display.h"
#include <string.h>
#include <stdio.h>
char idata cbuf[16];
char idata remoteIP[32];
unsigned int remotePort;
//串口初始化
void UART_config(void)
{
COMx_InitDefine COMx_InitStructure; //结构定义
COMx_InitStructure.UART_Mode = UART_8bit_BRTx; //模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
COMx_InitStructure.UART_BRT_Use = BRT_Timer1; //使用波特率, BRT_Timer1, BRT_Timer2 (注意: 串口2固定使用BRT_Timer2)
COMx_InitStructure.UART_BaudRate = 115200ul; //波特率, 一般 110 ~ 115200
COMx_InitStructure.UART_RxEnable = ENABLE; //接收允许, ENABLE或DISABLE
COMx_InitStructure.BaudRateDouble = DISABLE; //波特率加倍, ENABLE或DISABLE
COMx_InitStructure.UART_Interrupt = ENABLE; //中断允许, ENABLE或DISABLE
COMx_InitStructure.UART_Polity = PolityLow; //中断优先级, PolityLow,PolityHigh
COMx_InitStructure.UART_P_SW = UART1_SW_P36_P37; //切换端口, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17(必须使用内部时钟)
COMx_InitStructure.UART_RXD_TXD_Short = DISABLE; //内部短路RXD与TXD, 做中继, ENABLE,DISABLE
USART_Configuration(USART1, &COMx_InitStructure); //初始化串口1 USART1,USART2
}
//检测命令,只检测其中2位即判断接收正确
bit checkCmdChars(u8 i1,char c1,u8 i2,char c2){
int i=0,j,k=i2-i1+1;
//char cr;
//cbuf = (char *)malloc(k * sizeof(char));
while(1){
while(!RI);
RI=0;
cbuf[i] = SBUF;
if(cbuf[i] == c2){
j=((i<k-1)?(i+1):0);
if(cbuf[j] == c1){
break;
}
}
i++;
if(i>=k){i=0;}
}
// free(cbuf);
// cbuf = NULL;
return 1;
}
//获取收到的信息中的IP与端口(消息格式见ESP8266文档)
bit checkIPport(){
char i=0;
char f = 0;
char cr;
while(1){
if(i>=32){
return 0;
}
if(remotePort < 0 || remotePort > 65535){
return 0;
}
while(!RI);
RI=0;
cr = SBUF;
if(f==0){
if(cr == ','){
f = 1;
}
continue;
}
if(f>0 && f<5 && cr>='0' && cr<='9'){
remoteIP[i++]=cr;
continue;
}
if(f>0 && f<4 && cr=='.'){
f++;
remoteIP[i++]=cr;
continue;
}
if(f==4 && cr == ','){
f = 5;
remoteIP[i++]=0;
remotePort = 0;
continue;
}
if(f==5 && cr>='0' && cr<='9'){
remotePort = remotePort*10+cr-'0';
continue;
}
if(f==5 && cr==':'){
return 1;
}
return 0;
}
return 0;
}
//比较收到的信息中的IP与端口是否与先前一致(消息格式见ESP8266文档)
bit compIPport(){
char i=0;
char f = 0;
char cr;
unsigned int port = 0;
while(1){
if(i>=32){
return 0;
}
if(port < 0 || port > 65535){
return 0;
}
while(!RI);
RI=0;
cr = SBUF;
if(f==0){
if(cr == ','){
f=1;
}
continue;
}
if(f==1 && cr == ','){
if(remoteIP[i++]==0){
f = 2;
continue;
}
return 0;
}
if(f==1 && remoteIP[i++]==cr){
if(cr!=0 ){
continue;
}
return 0;
}
if(f==2 && cr>='0' && cr<='9'){
port = port*10+cr-'0';
continue;
}
if(f==2 && cr==':'){
if(port == remotePort){
return 1;
}
return 0;
}
return 0;
}
return 0;
}
//读取消息中的端口信息,格式:任意命令字符串[端口]
unsigned int readPort(){
unsigned int port = 0;
char pc = 0;
char f = 0;
while(1){
if(port < 0 || port > 65535){
return 0;
}
while(!RI);
RI=0;
pc = SBUF;
if(f==0 && pc == '['){
f=1;
continue;
}
if(f==1 && pc>='0' && pc<='9'){
port=port*10+pc-'0';
continue;
}
if(f==1 && pc == ']'){
return port;
}
return 0;
}
return 0;
}
//ESP_8266模块初始化
void initESP_8266(){
P0=0x00;
//收到ready
checkCmdChars(0,'r',4,'y');
P0 = 0xfe;
//收到WIFI GOT IP
checkCmdChars(0,'W',10,'P');
P0 = 0xfd;
delay_ms(200);
PrintString1("AT+CIPSTART=\"UDP\",\"192.168.1.255\",8093,8093\r\n");
//收到OK
checkCmdChars(0,'O',1,'K');
delay_ms(200);
PrintString1("AT+CIPDINFO=1\r\n");
//收到OK
checkCmdChars(0,'O',1,'K');
P0 = 0xfb;
delay_ms(200);
}
//等待上位机广播,发现服务
void waitForNet(){
bit f;
unsigned int remoteTCPPort =0;
char xdata concat_name[64];
while(1)
{
P0 = 0xfb;
//收到广播信息,前缀为+IPD,n,IP,port:
checkCmdChars(0,'+',3,'D');
while(!RI);
RI=0;
while(!RI);
RI=0;
f =checkIPport();
if(f==0 || remoteIP[0]==0 || remotePort==0){
continue;
}
收到findNet
checkCmdChars(0,'f',4,'N');
P0 = 0xf7;
delay_ms(200);
//发送IamDevice
sprintf(concat_name,"AT+CIPSEND=9,\"%s\",%u\r\n",remoteIP,remotePort);
PrintString1(concat_name);
delay_ms(200);
PrintString1("IamDevice");
//收到广播信息,前缀为+IPD,n,IP,port:
checkCmdChars(0,'+',3,'D');
while(!RI);
RI=0;
while(!RI);
RI=0;
f =compIPport();
if(f==0){
continue;
}
//收到selectYou[端口]
checkCmdChars(0,'s',6,'Y');
while(!RI);
RI=0;
while(!RI);
RI=0;
remoteTCPPort = readPort();
if(remoteTCPPort==0){
continue;
}
P0 = 0xef;
delay_ms(200);
//发送acceptYou
sprintf(concat_name,"AT+CIPSEND=9,\"%s\",%u\r\n",remoteIP,remotePort);
//PrintString1("AT+CIPSEND=11,\""+remoteIP+"\","+remotePort+"\r\n");
PrintString1(concat_name);
delay_ms(200);
PrintString1("acceptYou");
checkCmdChars(0,'O',1,'K');
P0 = 0xdf;
delay_ms(200);
PrintString1("AT+CIPCLOSE\r\n");
checkCmdChars(0,'O',1,'K');
P0 = 0xbf;
delay_ms(200);
//连接TCP服务器
sprintf(concat_name,"AT+CIPSTART=\"TCP\",\"%s\",%u,1113\r\n",remoteIP,remoteTCPPort);
//PrintString1("AT+CIPSTART=\"TCP\",\""+remoteIP+"\","+remotePort+",8094\r\n");
PrintString1(concat_name);
checkCmdChars(0,'O',1,'K');
P0 = 0x7f;
delay_ms(200);
PrintString1("AT+CIPDINFO=0\r\n");
checkCmdChars(0,'O',1,'K');
P0 = 0xff;
break;
}
}
//字符串前缀判断
bit startsWith(const char *pre, const char *str)
{
size_t lenpre = strlen(pre),
lenstr = strlen(str);
return lenstr < lenpre ? 0 : strncmp(pre, str, lenpre) == 0;
}
//判断接收到的命令并点亮LED
void checkRecvStr(char *mystr){
// int i;
if(strcmp(mystr,"+IPD,6:lton")==0){
P0= 0xff;
}else if(startsWith("+IPD,7:lton",mystr)){
P0 = ~mystr[11];
}
/* if(startsWith("+IPD,"mystr,)){
char num = 0;
for(i=5;i<16;i++){
if(mystr[i] != 0)
{
if ((mystr[i] < '0') || (mystr[i] > '9'))
{
break;
}
// num = num * 10 + (mystr[i] - '0');
}
}
if(i>5 && i<11 && mystr[i]==':'){
if(mystr[i+1]=='l' && mystr[i+2]=='t' && mystr[i+3]=='o' && mystr[i+4]=='n'){
P0=0xff;
delay_ms(200);
P0=mystr[i+5];
}
}
} */
}
/**********************************************/
void main(void)
{
const char xdata RETSTR[5] = {0xCA,0xD5,0xB5,0xBD,0x00};
u8 p2_buf = 0xFF;
char idata get_cmd[16];
//CLK_DIV=0x03;
P0M0=0xff;// 00000000 11111111 高阻 下同
P0M1=0xff;//
P1M0=0xff;// 11111111 11111111
P1M1=0xff;//
P2M0=0xff;// 11111111 111111111
P2M1=0xff;//常规
P3M0=0xff;
P3M1=0xff;//TXD推挽RXD常规 11111111 11111111
P4M0=0xff;
P4M1=0xff;//11111111 11111111
P5M0=0xff;
P5M1=0xff;// 00000000 00000010
P0 = 0xFF; //11111111 灭灯
P0M0=0x00;// 00000000 00000000 准双向 数据口
P0M1=0x00;//
P2 = 0xFF;// 111111111 按钮初始化
P2M0=0xff;// 11111111 11111111 按钮 开漏 // 00000000 00000000 准双向 控制口
P2M1=0xff;//
P3M0=0xfe; //11111110 11111100
P3M1=0xfc; //P31TXD10推挽 P30RXD00常规
recv_buf=0;
// initLedDisplay();
UART_config();
EA = 1;
initESP_8266();
waitForNet();
//发送测试代码
read_info = 1;
PrintString1("AT+CIPSEND=4\r\n");
delay_ms(200);
PrintString1("testInfo");
while (1)
{
//等待LED点亮控制命令,可改为其他应用代码
if(get_line == 1){
strcpy(get_cmd,cmd_str);
if(startsWith("+IPD,",get_cmd)){
delay_ms(200);
checkRecvStr(get_cmd);
PrintString1("AT+CIPSEND=4\r\n");
delay_ms(200);
PrintString1(RETSTR);
}
get_line = 0;
}
}
}
USART1.c
/*---------------------------------------------------------------------*/
/*改造自STC例程*/
/*串口接收通过/r/n换行符结束,接收一条命令。长度<=15*/
/*超时模式请参考前一篇博客*/
/*---------------------------------------------------------------------*/
#include "USART1.h"
#include "delay.h"
#include "string.h"
COMx_Define COM1;
//u8 idata send_buf;
u8 recv_buf;
//u8 idata send_buf;
char idata recv_str[16];
char idata cmd_str[16];
u8 recv_index = 0;
bit get_line = 0;
u8 last_buf=0;
bit read_info = 0;
u8 USART_Configuration(u8 UARTx, COMx_InitDefine *COMx)
{
u32 j;
if(UARTx == USART1)
{
COM1.id = 1;
COM1.TX_read = 0;
COM1.TX_write = 0;
COM1.B_TX_busy = 0;
COM1.RX_Cnt = 0;
COM1.RX_TimeOut = 0;
COM1.B_RX_OK = 0;
if(COMx->UART_Mode > UART_9bit_BRTx) return 1; //模式错误
if(COMx->UART_Polity == PolityHigh) PS = 1; //高优先级中断
else PS = 0; //低优先级中断
SCON = (SCON & 0x3f) | COMx->UART_Mode;
if((COMx->UART_Mode == UART_9bit_BRTx) ||(COMx->UART_Mode == UART_8bit_BRTx)) //可变波特率
{
j = (MAIN_Fosc / 4) / COMx->UART_BaudRate; //按1T计算
if(j >= 65536UL) return 2; //错误
j = 65536UL - j;
if(COMx->UART_BRT_Use == BRT_Timer1)
{
TR1 = 0;
AUXR &= ~0x01; //S1 BRT Use Timer1;
TMOD &= ~(1<<6); //Timer1 set As Timer
TMOD &= ~0x30; //Timer1_16bitAutoReload;
AUXR |= (1<<6); //Timer1 set as 1T mode
TH1 = (u8)(j>>8);
TL1 = (u8)j;
ET1 = 0; //禁止中断
TMOD &= ~0x40; //定时
INT_CLKO &= ~0x02; //不输出时钟
TR1 = 1;
}
else if(COMx->UART_BRT_Use == BRT_Timer2)
{
AUXR &= ~(1<<4); //Timer stop
AUXR |= 0x01; //S1 BRT Use Timer2;
AUXR &= ~(1<<3); //Timer2 set As Timer
AUXR |= (1<<2); //Timer2 set as 1T mode
TH2 = (u8)(j>>8);
TL2 = (u8)j;
IE2 &= ~(1<<2); //禁止中断
AUXR &= ~(1<<3); //定时
AUXR |= (1<<4); //Timer run enable
}
else return 2; //错误
}
else if(COMx->UART_Mode == UART_ShiftRight)
{
if(COMx->BaudRateDouble == ENABLE) AUXR |= (1<<5); //固定波特率SysClk/2
else AUXR &= ~(1<<5); //固定波特率SysClk/12
}
else if(COMx->UART_Mode == UART_9bit) //固定波特率SysClk*2^SMOD/64
{
if(COMx->BaudRateDouble == ENABLE) PCON |= (1<<7); //固定波特率SysClk/32
else PCON &= ~(1<<7); //固定波特率SysClk/64
}
if(COMx->UART_Interrupt == ENABLE) ES = 1; //允许中断
else ES = 0; //禁止中断
if(COMx->UART_RxEnable == ENABLE) REN = 1; //允许接收
else REN = 0; //禁止接收
P_SW1 = (P_SW1 & 0x3f) | (COMx->UART_P_SW & 0xc0); //切换IO
if(COMx->UART_RXD_TXD_Short == ENABLE) PCON2 |= (1<<4); //内部短路RXD与TXD, 做中继, ENABLE,DISABLE
else PCON2 &= ~(1<<4);
return 0;
}
return 3; //其它错误
}
/*************** 装载串口发送缓冲 *******************************/
void TX1_write2buff(u8 dat) //写入发送缓冲,指针+1
{
if(COM1.B_TX_busy == 0) //空闲
{
COM1.B_TX_busy = 1; //标志忙
SBUF = dat; //触发发送中断
}
}
void PrintString1(u8 *puts)
{
for (; *puts != 0; puts++) { TX1_write2buff(*puts); delay_us(150);} //遇到停止符0结束
}
/********************* UART1中断函数************************/
void UART1_int (void) interrupt UART1_VECTOR
{
//u8 last_index;
if(RI)
{
RI = 0;
if(read_info == 1)
{
recv_buf = SBUF;
if(recv_buf == '\n' && last_buf=='\r'){
if(recv_index < 16){
recv_str[recv_index]=0;
strcpy(cmd_str,recv_str);
get_line = 1;
}else{
recv_str[15]=0;
}
last_buf=0;
recv_index = 0;
}else{
if(recv_index < 16){
if(last_buf != 0){
recv_str[recv_index]=last_buf;
recv_index++;
}
}
last_buf=recv_buf;
}
}
COM1.B_RX_OK = 0;
}
if(TI)
{
TI = 0;
COM1.B_TX_busy = 0;
}
}
usart1.h
/*---------------------------------------------------------------------*/
/*改造自STC例程*/
/*---------------------------------------------------------------------*/
#ifndef __USART_H
#define __USART_H
#include "config.h"
#define COM_TX1_Lenth 40
#define COM_RX1_Lenth 40
#define COM_TX2_Lenth 128
#define COM_RX2_Lenth 128
#define USART1 1
#define USART2 2
#define UART_ShiftRight 0 //同步移位输出
#define UART_8bit_BRTx (1<<6) //8位数据,可变波特率
#define UART_9bit (2<<6) //9位数据,固定波特率
#define UART_9bit_BRTx (3<<6) //9位数据,可变波特率
#define UART1_SW_P30_P31 0
#define UART1_SW_P36_P37 (1<<6)
#define UART1_SW_P16_P17 (2<<6) //必须使用内部时钟
#define UART2_SW_P10_P11 0
#define UART2_SW_P46_P47 1
#define TimeOutSet1 5
#define TimeOutSet2 5
#define BRT_Timer1 1
#define BRT_Timer2 2
#define setTimer0() TH0 = 0x00, TL0 = 0x00
typedef struct
{
u8 id; //串口号
u8 TX_read; //发送读指针
u8 TX_write; //发送写指针
u8 B_TX_busy; //忙标志
u8 RX_Cnt; //接收字节计数
u8 RX_TimeOut; //接收超时
u8 B_RX_OK; //接收块完成
} COMx_Define;
typedef struct
{
u8 UART_Mode; //模式, UART_ShiftRight,UART_8bit_BRTx,UART_9bit,UART_9bit_BRTx
u8 UART_BRT_Use; //使用波特率, BRT_Timer1,BRT_Timer2
u32 UART_BaudRate; //波特率, ENABLE,DISABLE
u8 Morecommunicate; //多机通讯允许, ENABLE,DISABLE
u8 UART_RxEnable; //允许接收, ENABLE,DISABLE
u8 BaudRateDouble; //波特率加倍, ENABLE,DISABLE
u8 UART_Interrupt; //中断控制, ENABLE,DISABLE
u8 UART_Polity; //优先级, PolityLow,PolityHigh
u8 UART_P_SW; //切换端口, UART1_SW_P30_P31,UART1_SW_P36_P37,UART1_SW_P16_P17(必须使用内部时钟)
u8 UART_RXD_TXD_Short; //内部短路RXD与TXD, 做中继, ENABLE,DISABLE
} COMx_InitDefine;
extern COMx_Define COM1,COM2;
extern u8 recv_buf;
//extern u8 idata send_buf;
extern char idata recv_str[16];
extern u8 recv_index;
extern char idata cmd_str[16];
extern bit get_line;
extern bit read_info;
u8 USART_Configuration(u8 UARTx, COMx_InitDefine *COMx);
void TX1_write2buff(u8 dat); //写入发送缓冲,指针+1
void TX2_write2buff(u8 dat); //写入发送缓冲,指针+1
void PrintString1(u8 *puts);
void PrintString2(u8 *puts);
void Timer0_init();
//void COMx_write2buff(COMx_Define *COMx, u8 dat); //写入发送缓冲,指针+1
//void PrintString(COMx_Define *COMx, u8 *puts);
#endif
delay.h, delay.c, config.h, stc15fxxxx.h同STC例程,略。
###################################################################
服务端代码
UdpSocketSender.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpSocketHandler implements Runnable {
private DatagramSocket socket;
public UdpSocketHandler(){
try{
socket= new DatagramSocket(8091);
System.out.println("****服务器端已经启动");
}catch(Exception e){
e.printStackTrace();
System.out.println("****服务器端启动失败");
socket.close();
}
}
@Override
public void run() {
try{
if(socket != null && !socket.isClosed()){
UdpSocketSender r = new UdpSocketSender(socket);
r.setHandler(this);
Thread handler = new Thread(r);
handler.start();
while(true){
if(UdpSocketSender.isGlobeSend == true){
System.out.println("****服务器等待客户端发送数据");
byte[] data =new byte[1024];//创建字节数组,指定接收的数据包的大小
DatagramPacket packet=new DatagramPacket(data, data.length);
socket.receive(packet);//此方法在接收到数据报之前会一直阻塞
String info1=new String(data, 0, packet.getLength());
System.out.println("我是服务器,客户端说:"+info1);
if("IamDevice".equals(info1)){
System.out.println("正确的客户端响应参数");
InetAddress address=packet.getAddress();
int port=packet.getPort();
byte[] data2="selectYou[8092]\r\n".getBytes();
//2.创建数据报,包含响应的数据信息
DatagramPacket packet2=new DatagramPacket(data2, data2.length, address, port);
//3.响应客户端
socket.send(packet2);
}
}else{
Thread.sleep(100);
// try{
// this.wait(500);
// }catch(Exception e){
// //e.printStackTrace();
// }
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
UdpSocketSender.java
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpSocketSender implements Runnable {
public static boolean isGlobeSend = false;
private static final String udpIP = "192.168.1.255";
private static final int devUdpPort = 8093;
private DatagramSocket socket;
private UdpSocketHandler handler = null;
private UdpSocketSender(){
}
public UdpSocketSender(DatagramSocket socket){
this.socket = socket;
}
@Override
public void run() {
try{
System.out.println("****循环发送udp广播");
while(true){
byte[] data2="findNet\r\n".getBytes();
//2.创建数据报,包含响应的数据信息
InetAddress udpAddr = InetAddress.getByName(udpIP);
DatagramPacket packet2=new DatagramPacket(data2, data2.length, udpAddr, devUdpPort);
//3.响应客户端
socket.send(packet2);
if(isGlobeSend == false){
// if(handler != null){
// handler.notify();
// }
isGlobeSend = true;
}
Thread.sleep(1000);
}
}catch(Exception e){
e.printStackTrace();
isGlobeSend = false;
socket.close();
}
}
public void setHandler(UdpSocketHandler handler) {
this.handler = handler;
}
}
TcpSocketThread.java
import java.net.ServerSocket;
import com.geye.wx.utils.constants.BaseConstants;
import ch.qos.logback.core.net.server.Client;
//@Component
//@Scope("singleton")
public class TcpSocketThread implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
if(BaseConstants.isRun == 0){
BaseConstants.isRun = 1;
ServerSocket tcpSocket = null;
try{
tcpSocket = new ServerSocket(8092);
while(true){
BaseConstants.client = tcpSocket.accept();
System.out.println("客户端连接");
}
}catch(Exception e){
e.printStackTrace();
try{
BaseConstants.client.close();
tcpSocket.close();
}catch(Exception ee){
ee.printStackTrace();
}
}
}
}
}
在启动代码中添加:
Thread startThread = new Thread(new TcpSocketThread());
startThread.start();
Thread udpThread = new Thread(new UdpSocketHandler());
udpThread.start();
部署在局域网PC,启动服务。
烧写单片机,连接ESP8266模块,上电。
效果是单片机启动后可以找到局域网内部署服务的IP/端口。
用户通过手机或PC访问页面控制灯的亮灭。
以下是服务端控制页面,业余前端水平,css颜值一般般~