系列文章:
ESP8266的AP模式与STA模式简单测试
简单的Java服务器和客户端的通信
STM32 ESP8266和Java服务器透传模式下的双向通信
jsp向servlet传输数据
Servlet向JSP传递数据以及JSP页面DIV定时局部刷新
JSP向Servlet传递数据以及与STM32、ESP8266通信过程
标注:注意大家一般的得到的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指令必须是以\r\n结尾,数据应该没有这个要求
{
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",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 "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
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通信过程