一,实验准备
外设接口
• 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库中没有找到相应的函数(应该是我水平不高导致的),于是选择了高频的定时中断作为代替,实际效果也还可以。
二,实验过程
包含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;iByte->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
#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<=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_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=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=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=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=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=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(带有蓝牙模块)的单片机与安卓手机进行收发调试显示的功能,文中涉及的许多代码许多来自互联网和其他大佬的开源代码,在此对这些资源与知识的分享者表示感谢。