基于esp32的手机蓝牙收发调试器

一,实验准备

  1. 开始之前,先吹一波ESP32,乐鑫YYDS!ESP32包含WIFI模块和蓝牙4.0模块,双核CPU工作频率为80-240Mhz,其大致功能如下图所示:

外设接口

• 34 个 GPIO 口

• 12-bit SAR ADC,多达 18 个通道

• 2 个 8-bit D/A 转换器

• 10 个触摸传感器

• 4 个 SPI

• 2 个 I²S

• 2 个 I²C

• 3 个 UART

• 1 个 Host SD/eMMC/SDIO

• 1 个 Slave SDIO/SPI

• 带有专用 DMA 的以太网 MAC 接口,支持 IEEE 1588

• 双线汽车接口(TWAI®,兼容 ISO11898-1)

• IR (TX/RX)

• 电机 PWM

• LED PWM,多达 16 个通道

• 霍尔传感器

2.开发环境搭建

这里选择vscode+platform IO的组合,别问为什么抛弃上个实验中使用的ardino开发环境,我只想说谁用谁知道!具体搭建步骤可以参考博客:

二、Esp32开发环境快速搭建(vscode+PlatformIO IED)_bug设计工程师的博客-CSDN博客_esp32 开发环境

3.蓝牙串口收发原理   

(1)蓝牙的基本原理(简述)

首先要明白蓝牙通信的原理,简单说来就是通信为主从机模式,主机(这里就是手机,相应的从机就是具有蓝牙模块的esp32,当然主从关系可以进行切换,并不是绝对的。)进行查找,发起配对,建立连接成功双方才可以收发数据。

(2)串口通信

蓝牙数据传输应用中,一对一串口数据通讯是最常见的应用之一,相对应的比较重要的参数是波特率即为串口通信的速率,也就是串口通信时每秒钟可以传输多少个二进制位,通信双方必须事先设定相同的波特率才能成功通信,如果发送方和接收方按照不同的波特率通信则根本收不到,因此波特率最好是大家熟知的而不是随意指定的;常用的波特率经过长久发展,就形成了共识,大家常用的就是9600或者115200。

串口通信时,收发是一个周期一个周期进行的,每个周期传输n个二进制位。这一个周期就叫做一个通信单元,一个通信单元由:起始位+数据位+奇偶校验位+停止位组成的。

起始位:表示发送方要开始发送一个通信单元,起始位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。

数据位:是一个通信单元中发送的有效信息位,是本次通信真正要发送的有效数据,串口通信一次发送多少位有效数据是可以设定的(可选的有6、7、8、9,一般都是选择8位数据位,因为一般通过串口发送的文字信息都是ASCII码编码,而ASCII码中一个字符刚好编码为8位)。

校验位:是用来校验数据位,以防止数据位出错的。

停止位:是发送方用来表示本通信单元结束标志的,停止位的定义是串口通信标准事先指定的,是由通信线上的电平变化来反映的。常见的有1位停止位、1.5位停止位、2位停止位等,一般使用的是1位停止位。

总结:串口通信时因为是异步通信,所以通信双方必须事先约定好通信参数,这些通信参数包括:波特率、数据位、校验位、停止位(串口通信中起始位定义是唯一的,所以一般不用选择)。

以上关于串口通信部分引自博客:串口通信详解_泪无痕z的博客-CSDN博客_串口通信更详细的内容可以进入阅读学习。

4.手机app蓝牙调试器

由大佬免费开源的软件,UI清晰明了,使用方法一目了然。(进入链接可以自行下载apk)

提高开发效率-蓝牙调试器 - 简书 (jianshu.com)

我在此大概解释一下控制逻辑:规定同样的数据包结构(收发一致)

包含:包头 数据位 校验位 包尾

发送数据比较简单,串口发送就行

接收数据示例代码(蓝牙调试器中)用的是串口接收中断或者DMA中断,我在esp32的ardino库中没有找到相应的函数(应该是我水平不高导致的),于是选择了高频的定时中断作为代替,实际效果也还可以。

二,实验过程

  1. 完整工程代码

包含main.c valuepack.c,valuepack.h三个部分

Valuepack里send和receive数据包是根据蓝牙调试器开发者的开源代码进行修改的。

Main里的函数是根据我们功能所需设置的。

代码如下:

main.c




//当前代码的主要问题
//1,蓝牙连接不稳定 有的设备无法连接 
//2,蓝牙接收和发送库函数 BluetoothSerial里的不完善 导致暂时没找到串口接收中断的实现方法 故选择(高速)定时中断来检测并处理接收数据
//3,对于接收和发送缓冲数组好像没有清零,queue是fifo类型的 但对于我使用好像暂时无影响(不知道为啥) 有时候会有warning 但需要改善

//注意事项:1,发送数据直接put**的形式 不用其他操作
//2,接收数据包 需要更改 valuepack.里的 define里的各种数据类型的数目才能使用



//库函数


 #include "Arduino.h"
#include "BluetoothSerial.h"
#include"valuepack.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;
RxPack rxpack;
unsigned char buffer[50];
bool LED1=0,LED2=1;
int Now_Time=13;
float Speed=1.3;
extern unsigned char vp_rxbuff[VALUEPACK_BUFFER_SIZE];
extern long rxIndex;

void TIME_INTERRUPT_2();    // 定时中断函数2(高速)

void sendBuffer(unsigned char *p,unsigned short length)//发送数据包
{   
   if (!(SerialBT.available()))  //此函数为开发板接收的字符个数 没有接受即为0
   {
   for(int i=0;i<length;i++)
   { 
     SerialBT.write(*p++);
      
    }
   }
    

}
void setup() {
  Serial.begin(115200);//蓝牙串口波特率初始化
  SerialBT.begin("BlueTooth_Serial"); //Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
  
  hw_timer_t *timer1 = timerBegin(1, 80, true);    //启动定时器
  timerAttachInterrupt(timer1, &TIME_INTERRUPT_2, true);
  // timerAlarmWrite(timer1, 10000, true);
  timerAlarmWrite(timer1, 1000, true);
  timerAlarmEnable(timer1);
}

void loop() {

  readValuePack(&rxpack);//接收数据包
  

	startValuePack(buffer);//初始化一个发送数据包

  
  	// 将要发送的值放入,注意顺序为 Bool->Byte->Short->Int->Float		
    LED1=rxpack.bools[0];
    LED2=rxpack.bools[1];
    		
			putBool(LED1);
			putBool(LED2);
		
			// putShort(3123);
			putInt(Now_Time);
			putFloat(Speed);

      	// 通过串口发送,endValuePack返回了数据包的总长度
			sendBuffer(buffer,endValuePack());//发送数据包
      delay(200);//delay值可适当调小 但必须
// Speed+=0.3;

     



}




unsigned short vp_circle_rx_index;//环形缓冲区index

void TIME_INTERRUPT_2()//定时中断 中断服务函数
{
  if (SerialBT.available()) 
  {
    vp_rxbuff[vp_circle_rx_index]=SerialBT.read();
	vp_circle_rx_index++;
  	if(vp_circle_rx_index>=VALUEPACK_BUFFER_SIZE)
    	vp_circle_rx_index=0;
      	rxIndex++;
  }

//  Now_Time++;
}

valuepack.h

#ifndef _ESP32_value_pack_
#define _ESP32_value_pack_

#include <Arduino.h>
#define VALUEPACK_BUFFER_SIZE 512
// 3.指定接收数据包的结构-----------------------------------------------------------------------------------
//    根据实际需要的变量,定义数据包中 bool byte short int float 五种类型的数目

#define RX_BOOL_NUM  2
#define RX_BYTE_NUM  0
#define RX_SHORT_NUM 0
#define RX_INT_NUM   0
#define RX_FLOAT_NUM 0



// typedef struct   
// {	
// 	#if TX_BOOL_NUM > 0
// 	unsigned char bools[TX_BOOL_NUM];
// 	#endif
	
// 	#if TX_BYTE_NUM > 0
//   char bytes[TX_BYTE_NUM];
//   #endif
	
// 	#if TX_SHORT_NUM > 0
// 	short shorts[TX_SHORT_NUM];	
// 	#endif
	
// 	#if TX_INT_NUM > 0
// 	int  integers[TX_INT_NUM];
// 	#endif
	
// 	#if TX_FLOAT_NUM > 0
// 	float floats[TX_FLOAT_NUM];
// 	#endif
// 	char space; //只是为了占一个空,当所有变量数目都为0时确保编译成功
// }
// TxPack;
typedef struct 
{	
#if RX_BOOL_NUM > 0
	unsigned char bools[RX_BOOL_NUM];
	#endif
	
	#if RX_BYTE_NUM > 0
  char bytes[RX_BYTE_NUM];
  #endif
	
	#if RX_SHORT_NUM > 0
	short shorts[RX_SHORT_NUM];	
	#endif
	
	#if RX_INT_NUM > 0
	int  integers[RX_INT_NUM];
	#endif
	
	#if RX_FLOAT_NUM > 0
	float floats[RX_FLOAT_NUM];
	#endif
	char space; //只是为了占一个空,当所有变量数目都为0时确保编译成功
}RxPack;
// 初始化 valuepack 包括一些必要的硬件外设配置
// 并指定要传输的数据包
void startValuePack( unsigned char *buffer);

// 2. 向数据包中放入各类数据,由于数据包的结构是固定的,因此注意严格以如下的顺序进行存放,否则会出现错误

void putBool(unsigned char b);
void putByte(char b);
void putShort(short s);
void putInt(int i);
void putFloat(float f);

// 3. 结束打包,函数将返回 数据包的总长度
unsigned short endValuePack(void);

// 4. 发送数据
unsigned char readValuePack(RxPack *rx_pack_ptr);



#define PACK_HEAD 0xa5   
#define PACK_TAIL 0x5a

#endif

Valuepack.cpp

#include "valuepack.h"

unsigned char *valuepack_tx_buffer;
unsigned short valuepack_tx_index;
unsigned char valuepack_tx_bit_index;
unsigned char valuepack_stage;
// 接收数据包的字节长度
const unsigned short  RXPACK_BYTE_SIZE = ((RX_BOOL_NUM+7)>>3)+RX_BYTE_NUM+(RX_SHORT_NUM<<1)+(RX_INT_NUM<<2)+(RX_FLOAT_NUM<<2);

// 接收数据包的原数据加上包头、校验和包尾 之后的字节长度
unsigned short rx_pack_length = RXPACK_BYTE_SIZE+3;

// 接收计数-记录当前的数据接收进度
// 接收计数每次随串口的接收中断后 +1
long rxIndex=0;

// 读取计数-记录当前的数据包读取进度,读取计数会一直落后于接收计数,当读取计数与接收计数之间距离超过一个接收数据包的长度时,会启动一次数据包的读取。
// 读取计数每次在读取数据包后增加 +(数据包长度)
long rdIndex=0;

// 用于环形缓冲区的数组,环形缓冲区的大小可以在.h文件中定义VALUEPACK_BUFFER_SIZE
unsigned char vp_rxbuff[VALUEPACK_BUFFER_SIZE];

// 数据读取涉及到的变量
unsigned short rdi,rdii,idl,idi,bool_index,bool_bit;
// 变量地址
uint32_t  idc;
// 记录读取的错误字节的次数
unsigned int err=0;
// 用于和校验
unsigned char sum=0;

// 存放数据包读取的结果
unsigned char isok;

// 1. 开始将数据打包,需传入定义好的数组(需保证数组长度足以存放要发送的数据)

void startValuePack(unsigned char *buffer)
{
	valuepack_tx_buffer = buffer;
	valuepack_tx_index = 1;
	valuepack_tx_bit_index = 0;
	valuepack_tx_buffer[0] = PACK_HEAD;
	valuepack_stage = 0;
}

// 2. 向数据包中放入各类数据,由于数据包的顺序结构是固定的,因此注意严格以如下的顺序进行存放,否则会出现错误

void putBool(unsigned char b)
{
if(valuepack_stage<=1)
{
   if(b)
      valuepack_tx_buffer[valuepack_tx_index] |= 0x01<<valuepack_tx_bit_index;
	 else
			valuepack_tx_buffer[valuepack_tx_index] &= ~(0x01<<valuepack_tx_bit_index);

  valuepack_tx_bit_index++;
	if(valuepack_tx_bit_index>=8)
	{
		valuepack_tx_bit_index = 0;
		valuepack_tx_index++;
	}
	valuepack_stage = 1;
}
}


void putByte(char b)
{
if(valuepack_stage<=2)
{
	if(valuepack_tx_bit_index!=0)
	{	
		valuepack_tx_index++;
	  valuepack_tx_bit_index = 0;
	}
	valuepack_tx_buffer[valuepack_tx_index] = b;
	valuepack_tx_index++;
	
	valuepack_stage = 2;
}
}
void putShort(short s)
{
if(valuepack_stage<=3)
{
		if(valuepack_tx_bit_index!=0)
	{	
		valuepack_tx_index++;
	  valuepack_tx_bit_index = 0;
	}
	valuepack_tx_buffer[valuepack_tx_index] = s&0xff;
	valuepack_tx_buffer[valuepack_tx_index+1] = s>>8;
	
	valuepack_tx_index +=2;
	valuepack_stage = 3;
}
}
void putInt(int i)
{
if(valuepack_stage<=4)
{
		if(valuepack_tx_bit_index!=0)
	{	
		valuepack_tx_index++;
	  valuepack_tx_bit_index = 0;
	}
	
	valuepack_tx_buffer[valuepack_tx_index] = i&0xff;
	valuepack_tx_buffer[valuepack_tx_index+1] = (i>>8)&0xff;
	valuepack_tx_buffer[valuepack_tx_index+2] = (i>>16)&0xff;
	valuepack_tx_buffer[valuepack_tx_index+3] = (i>>24)&0xff;
	
	valuepack_tx_index +=4;
	
	valuepack_stage = 4;
}
}
int fi;
void putFloat(float f)
{
if(valuepack_stage<=5)
{
		if(valuepack_tx_bit_index!=0)
	{	
		valuepack_tx_index++;
	  valuepack_tx_bit_index = 0;
	}
	
	fi = *(int*)(&f);
	valuepack_tx_buffer[valuepack_tx_index] = fi&0xff;
	valuepack_tx_buffer[valuepack_tx_index+1] = (fi>>8)&0xff;
	valuepack_tx_buffer[valuepack_tx_index+2] = (fi>>16)&0xff;
	valuepack_tx_buffer[valuepack_tx_index+3] = (fi>>24)&0xff;
	valuepack_tx_index +=4;
	valuepack_stage = 5;
}
}




// 3. 结束打包,函数将返回 数据包的总长度
unsigned short endValuePack()
{
  unsigned char sum=0;
	for(int i=1;i<valuepack_tx_index;i++)
	{
		sum+=valuepack_tx_buffer[i];
	}
	valuepack_tx_buffer[valuepack_tx_index] = sum;
	valuepack_tx_buffer[valuepack_tx_index+1] = PACK_TAIL;
	return valuepack_tx_index+2;
}



// unsigned char readValuePack(RxPack *rx_pack_ptr)
// 尝试从缓冲区中读取数据包
// 参数   - RxPack *rx_pack_ptr: 传入接收数据结构体的指针,从环形缓冲区中读取出数据包,并将各类数据存储到rx_pack_ptr指向的结构体中
// 返回值 - 如果成功读取到数据包,则返回1,否则返回0
// 

unsigned char readValuePack(RxPack *rx_pack_ptr)
{
	
	isok = 0;
	
	// 确保读取计数和接收计数之间的距离小于2个数据包的长度
	while(rdIndex<(rxIndex-((rx_pack_length)*2)))
          rdIndex+=rx_pack_length;	
	
	// 如果读取计数落后于接收计数超过 1个 数据包的长度,则尝试读取
	while(rdIndex<=(rxIndex-rx_pack_length))
	{
		rdi = rdIndex % VALUEPACK_BUFFER_SIZE;
		rdii=rdi+1;
		if( vp_rxbuff[rdi]==PACK_HEAD) // 比较包头
		{
			if(vp_rxbuff[(rdi+RXPACK_BYTE_SIZE+2)%VALUEPACK_BUFFER_SIZE]==PACK_TAIL) // 比较包尾 确定包尾后,再计算校验和
			{
				//  计算校验和
				sum=0;
			      for(short s=0;s<RXPACK_BYTE_SIZE;s++)
				{
					rdi++;
					if(rdi>=VALUEPACK_BUFFER_SIZE)
					  rdi -= VALUEPACK_BUFFER_SIZE;
					sum += vp_rxbuff[rdi];
				}	
						rdi++;
					if(rdi>=VALUEPACK_BUFFER_SIZE)
					  rdi -= VALUEPACK_BUFFER_SIZE;
					
                                if(sum==vp_rxbuff[rdi]) // 校验和正确,则开始将缓冲区中的数据读取出来
				{
					//  提取数据包数据 一共有五步, bool byte short int float
					// 1. bool
					#if  RX_BOOL_NUM>0
						
					  idc = (uint32_t)rx_pack_ptr->bools;
					  idl = (RX_BOOL_NUM+7)>>3;
					
					bool_bit = 0;
					for(bool_index=0;bool_index<RX_BOOL_NUM;bool_index++)
					{
						*((unsigned char *)(idc+bool_index)) = (vp_rxbuff[rdii]&(0x01<<bool_bit))?1:0;
						bool_bit++;
						if(bool_bit>=8)
						{
						  bool_bit = 0;
							rdii ++;
						}
					}
					if(bool_bit)
						rdii ++;
					
				        #endif
					// 2.byte
					#if RX_BYTE_NUM>0
						idc = (uint32_t)(rx_pack_ptr->bytes);
					  idl = RX_BYTE_NUM;
					  for(idi=0;idi<idl;idi++)
					  {
					    if(rdii>=VALUEPACK_BUFFER_SIZE)
					      rdii -= VALUEPACK_BUFFER_SIZE;
					    (*((unsigned char *)idc))= vp_rxbuff[rdii];
							rdii++;
							idc++;
					  }
				        #endif
					// 3.short
					#if RX_SHORT_NUM>0
						idc = (uint32_t)(rx_pack_ptr->shorts);
					  idl = RX_SHORT_NUM<<1;
					  for(idi=0;idi<idl;idi++)
					  {
					    if(rdii>=VALUEPACK_BUFFER_SIZE)
					      rdii -= VALUEPACK_BUFFER_SIZE;
					    (*((unsigned char *)idc))= vp_rxbuff[rdii];
							rdii++;
							idc++;
					  }
				        #endif
					// 4.int
					#if RX_INT_NUM>0
						idc = (uint32_t)(&(rx_pack_ptr->integers[0]));
					  idl = RX_INT_NUM<<2;
					  for(idi=0;idi<idl;idi++)
					  {
					    if(rdii>=VALUEPACK_BUFFER_SIZE)
					      rdii -= VALUEPACK_BUFFER_SIZE;
					    (*((unsigned char *)idc))= vp_rxbuff[rdii];
							rdii++;
							idc++;
					  }
				        #endif
					// 5.float
					#if RX_FLOAT_NUM>0
						idc = (uint32_t)(&(rx_pack_ptr->floats[0]));
					  idl = RX_FLOAT_NUM<<2;
					  for(idi=0;idi<idl;idi++)
					  {
					    if(rdii>=VALUEPACK_BUFFER_SIZE)
					      rdii -= VALUEPACK_BUFFER_SIZE;
					    (*((unsigned char *)idc))= vp_rxbuff[rdii];
							rdii++;
							idc++;
					  }
				  #endif
				      
				        // 更新读取计数
					rdIndex+=rx_pack_length;
					isok = 1;
				}else
				{ 
				// 校验值错误 则 err+1 且 更新读取计数
				  rdIndex++;
			          err++;
				}
			}else
			{
				// 包尾错误 则 err+1 且 更新读取计数
				rdIndex++;
				err++;
			}		
		}else
		{ 
			// 包头错误 则 err+1 且 更新读取计数
			rdIndex++;
			err++;
		}		
	}
	return isok;
}

手机端相关sh

注意手机端的接收数据包对应的是esp32代码中的发送数据包

发送数据包对应的接收。

3,最终效果展示

可以实现数据的实时显示以及实时修改(但没能写入flash reset后会回到初始值)

三,结语

本次项目通过对蓝牙调试器的示例代码(stm32版本)进行移植,实现了用esp32(带有蓝牙模块)的单片机与安卓手机进行收发调试显示的功能,文中涉及的许多代码许多来自互联网和其他大佬的开源代码,在此对这些资源与知识的分享者表示感谢。

  • 1
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值