历史上的今天
返回首页

历史上的今天

今天是:2025年03月29日(星期六)

2020年03月29日 | 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)

推荐阅读

史海拾趣

Dynachip Corp公司的发展小趣事

Dynachip Corp公司成立于21世纪初,最初是由几位在半导体领域有深厚背景的科学家和工程师共同创立的。他们在大学实验室里共同研发出了一种新型的芯片技术,这种技术具有更高的集成度和更低的能耗。凭借这一创新技术,Dynachip Corp公司正式成立,并开始了从实验室到市场的转化过程。他们积极与投资者接洽,筹集资金,并建立了生产线,将这一技术转化为实际产品。

Electronic Concepts Inc公司的发展小趣事

ECI成立于20世纪90年代初,当时正值电子产业快速发展的浪潮。公司创始人李先生,一位有着丰富电子行业经验的工程师,看准了市场对定制化电子解决方案的需求,决定创立ECI。然而,初创时期资金紧张、人才匮乏,李先生不得不身兼数职,既要做研发,又要跑市场。在一次偶然的机会下,ECI成功为一家大型企业提供了定制化的电子控制模块,这一项目不仅为公司带来了可观的收入,也为ECI赢得了良好的口碑。

CLANDELL公司的发展小趣事

品质是CLANDELL公司一直以来的核心竞争力。公司建立了严格的质量管理体系,从原材料采购到产品生产的每一个环节都进行严格把控。同时,公司还加强了售后服务体系建设,确保客户在使用过程中能够得到及时、有效的支持。这些举措不仅提升了CLANDELL公司的产品品质,也赢得了客户的信任和好评。

HAHN - Elektrobau GmbH & Co KG公司的发展小趣事

1991年,高通的CDMA技术首次被商用于美国卫星通信公司的无线电话网,标志着高通在移动通信领域的重大突破。与此同时,高通开始向智能手机芯片方向转型,目标是利用芯片技术使手机具备更强大的计算能力。随着技术的不断成熟,高通推出了多款性能卓越的移动应用处理器,如Snapdragon系列,迅速占据了智能手机芯片市场的主导地位。这一转型不仅巩固了高通在移动通信领域的地位,也为公司的长期发展奠定了坚实基础。

芯源半导体(CW)公司的发展小趣事

随着公司产品的逐步成熟,芯源半导体(CW)公司开始寻求与行业内其他企业的合作。经过深入的市场调研和谈判,公司成功与多家知名电子企业建立了战略合作伙伴关系。这些合作伙伴为芯源半导体(CW)公司提供了更多的市场机会和技术支持,使公司得以迅速扩大市场份额,提升品牌影响力。

Hirel Systems Ltd公司的发展小趣事

随着市场竞争的加剧,芯源半导体(CW)公司意识到品质管理的重要性。公司加强了对原材料采购、生产过程和产品质量控制的管理,建立了完善的质量管理体系。同时,公司还引入了先进的检测设备和技术手段,以确保产品的稳定性和可靠性。这些措施使芯源半导体(CW)公司的产品质量得到了显著提升,赢得了客户的信任和好评。

问答坊 | AI 解惑

一款新开发的51单片机实验板

一款新开发的单片机实验板一款新开发的单片机实验板,基本功能已经调试成功,欢迎大家批评指正! 下面的程序现在已经调试出来了 文件夹 列表 ├─1. LED │  ├─计数显示 │  ├─随机显示 │  ├─移位 ...…

查看全部问答>

智林测控开发板.强烈推荐

这是个ARM最小系统开发板,大家可以上淘宝买一个玩,不到50元的成本, [ 本帖最后由 jxb01033016 于 2009-9-17 11:57 编辑 ]…

查看全部问答>

欢迎大家到我的博客看看

http://c8051fmcu.blog.sohu.com…

查看全部问答>

EVC和VC2005开发的比较

EVC和VC2005开发智能设备的程序,相互比较各自有什么优点和缺点?谢谢大家了…

查看全部问答>

求STM32MCKIT电机控制的程序和库

怎么ST网站上找不到演示程序和库呢 哪里有下载呢 谢谢啦!准备在万利的STM3210B上跑跑 俺的需求是做3相交流异步的驱动…

查看全部问答>

3517的试用分享咧?

被收起来了么?俺想找找看都找不到嘞,是不是应该归拢一下哦?…

查看全部问答>

给大家转发个MSP430 JTAG(USB的和并口的)与目标板连接需要注意的几点总结

经常看到有初学MSP430的朋友在坛里询问MSP430 JTAG与目标板连接方面的问题,我在这里给大家总结一下,希望能给初学MSP430的朋友拨云见日,不再疑惑。 MSP430的JTAG连接主要有两种方式:4线JTAG方式(包含TDO、TDI、TMS、TCK四路标准JTAG ...…

查看全部问答>

任哲书ucosII提问

任哲书配套例程中每个source文件中有个test.lnk,应该是个链接文件吧?怎么看到其中的内容,并进行修改呢?…

查看全部问答>

Sitara starter kit

求购一块Sitara starter kit开发板,大家有不用的转给我吧。就是这块。 …

查看全部问答>

Altera SoC体验之旅+Enpirion Altera SoC电源上电时序

Lark Board看似简单的评估板的电源上电时序看似简单,里面实际隐藏了很深,看似EN脚都接的5V,却是上电时间却是不一样。 电源输入为笔记本电源的19V DC; 第一级通过LTC3855变换为12和5V; LTC3855的上电时序RUN1,RUN2都是内部上拉,所以一上 ...…

查看全部问答>