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 注意:串口接收处理程序以该博客中的为准