Esp32总结

ESP32学习笔记

1.基础配置

1)vscode配置

首先要进入vscode进行platform的安装

之后需要进入platform

之后就可以新建工程了(需要科学上网工具)

2)总体框架

Esp32总结_第1张图片

总体框架位于创建的目录下,其中platformio.ini是配置文件
pio\build是编译的一些文件
.vscode是vscode生成的一些配置文件
lib是我们的库
include可以包含一些头文件
src中含有我们的主函数main
(1)程序框架
#include 

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
}

#setup中是放置一些初始化的配置程序,loop相当于C语言的主循环
(2)编译下载
在vscode下方有编译下载和清除的按钮选项,清除能够清理掉中间产生的文件

Esp32总结_第2张图片

3)快捷键

快捷键可以在VScode中进行设置,可以在设置中选择键盘快捷方式
快捷键 作用
编译Build Ctrl + Alt + B
下载Upload Ctrl + Alt + U
注释 ctrl + /

4)预编译

#define PMW_EN 1


#if PMW_EN

    Motor_Init();

#endif

5)platformio.ini

esp32拥有4M的flash,按理来说空间够大够用,但是由于默认分区时只给程序分到了1.4M,因此在使用了BLE功能后很容易程序空间不足导致出错,因此需要更改一下platformio.ini的初始设置,
	
同时还需要更改一下默认的串口助手波特率,这样子可以直接通过vscode来打开串口查看信息,同时还可以更改设定下载速率,能够实现快速下载
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
upload_speed = 921600
board_build.partitions = huge_app.csv

//其中第一行表示开发环境,是在最开始创建工程的时候选择的
//monitor_speed表示默认的串口助手的波特率,设置为115200与程序中对应,这样子才不会乱码
//upload_speed表示上传速度
//board_build.partitions是对esp32的分区的一个设置,这个可以来解决内存撑爆的问题,选择huge_app.csv第一次会下载到本地
	app表示程序占用的空间,选择此模式能够使程序空间为3M,这样子就不用怕程序撑爆	

2.具体操作

1)内部操作

(1)GPIO
【1】初始化GPIO
pinMode(pin, mode)

作用:设置一个引脚(pin)作为GPIO时的I/O模式。

参数:

pin:引脚编号

mode:GPIO的I/O模式,取值有3种

INPUT :作为数字输入

OUTPUT :作为数字输出

INPUT_PULLUP:作为数字输入,且使能引脚的内部上拉电阻

INPUT_PULLDOWN:作为数字输入,且使能引脚的内部下拉电阻

具体是否能设置对应模式还得参考技术规格书(一般的GPIO0 ~ 33可以设置为输出,基本上都可以设置为输入,GPIO6 ~ 11一般不推荐使用,因为这几个口接了存储程序用的Flash,不当使用可能引起程序崩溃)
【2】GPIO数字输入输出
#include 

void setup() {
  // put your setup code here, to run once:
  pinMode(2,OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  digitalWrite(2,HIGH);
  delay(1);
  digitalWrite(2,LOW);
  delay(1);
}

#GPIO先设定好模式,之后可以直接digitalWrite进行输出
void digitalWrite(uint8_t pin, uint8_t val);

使用digitalWrite(pin, value)来设置输出状态,value可选值为HIGH或LOW,即1和0;
使用digitalRead(pin)来读取GPIO口电平,返回值为HIGH或LOW,即1和0;
【3】GPIO模拟输入输出
注意 : 模拟输入是通过PWM实现的, 所以不需要初始化PIN
analogRead(4)
【4】GPIO中断
GPIO中断
arduino对esp32的gpio中断控制非常简单
函数定义
	attachInterrupt(pin, function, mode);
参数详解

pin gpio的pin脚值

function 当中断触发时调用的函数

mode 中断响应的模式

    CHANGE 边沿触发 只要发生电平变化

    RISING 上升沿触发 低电平到高电平

    FALLING 下降沿触发 高电平到低电平
int pin = 13; // gpio pin脚
void setup()
{
  attachInterrupt(pin, func, CHANGE); // 边沿中断
}
void loop()
{
}
void func()    // 中断处理函数
{
  digitalRead(pin); // 获取gpio的电平值做相应的处理
}
【5】电容输入

touchRead(pin)

返回值 0~255. 触摸强度
注意: 摸得越瓷实,数值越小

void setup()
{
  Serial.begin(9600);
}

void loop()
{
   Serial.printf("touch:%d\n",touchRead(4));
}
【6】电容输入中断

touchAttachInterrupt(pin, TSR , threshold)

参数:

  • TSR :中断回调函数, 不能带参数, 不能有返回值。
  • threshold:阈值, 达到该阈值会触发此中断
void TSR()
{
  Serial.printf("我被按下了!\r\n");
  }

void setup()
{
  Serial.begin(9600);
  touchAttachInterrupt(4, TSR , 20);
}

void loop()
{
  
}
(2)ADC
注意ESP32自带的ADC是非线性的,而且要看每个板子的技术指标,有可能ADC会与WIFI冲突

在这里插入图片描述

(3)外部中断
// 定义外部中断的Mode
// 0:无中断,读取Touch值
// 1:Touch中断,执行TouchEvent()
// 2: 外部IO的中断
#define EXT_ISR_MODE 2

//这个是代表的一个宏定义
//定义了两个函数,分别对应了Touch和PinInt引起的中断事件

void TouchEvent()
{
  Serial.printf("Touch Event.\r\n");
}

void PinIntEvent()
{
  Serial.printf("PinInt Event.\r\n");
}
(4)串口
根据 ESP32 的手册,芯片有三组 UART 接口,在 Arduino 环境中,分别对应 Serial, Serial1, Serial2 三个对象,但是使用的时候有些需要注意的地方。

Serial 使用会遇到的问题
大多数开发板的 Serial 都会与 USB 接口联通, 除了提供给开发者使用以外, 还担负着下载程序的功能. 如果开发者的程序只是使用 USB 和上位机通讯,那么一般会很少遇到问题,但是如果程序使用 Serial 和另外的模块通讯,那么在更新程序的时候,需要将和另外的模块的连接断开,不然上传数据和反馈信息会和我们的程序冲突造成无法上传的错误。
ESP32共有3个UART端口, 其中UART1用于Flash读/写.
串口名 Arduino名 TX RX
UART0 Serial pin1 pin3
UART1 Serial1 pin10 pin9
UART2 Serial2 pin17 pin16
【1】串口初始化
Serial.begin(speed, config)

参数:
speed:波特率,一般取值9600,115200等。
config:设置数据位、校验位和停止位。默认SERIAL_8N1表示8个数据位,无校验位,1个停止位。
返回值:无。

Serial.begin(115200);
【2】串口关闭
Serial.end()

描述:禁止串口传输。此时串口Rx和Tx可以作为数字IO引脚使用。
原型:Serial.end()
参数:无。
返回值:无。
【3】串口打印
Serial.print()

描述:串口输出数据,写入字符数据到串口。
原型:
Serial.print(val)
Serial.print(val, format)
参数:
val:打印的值,任意数据类型。
config:输出的数据格式。BIN(二进制)、OCT(八进制)、DEC(十进制)、HEX(十六进制)。对于浮点数,此参数指定要使用的小数位数。

eg.
Serial.printf("PinInt Event.\r\n");
Serial.printf("touch:%d\r\n",touchRead(T0));


***********************************************************************************************************
Serial.println()
Serial.printf()

描述:串口输出数据并换行。
原型:
Serial.println(val)
Serial.println(val, format)
参数:
val:打印的值,任意数据类型。
config:输出的数据格式。
返回值:返回写入的字节数。

附: 常用格式字符及转义字符

字符 说明
%o 八进制整数输出
%d 十进制整数输出
%x 十六进制整数输出
%f 浮点输出,默认6位小数
%c 单字符输出
%s 字符串输出
\n 换行
\r 回车
\t Tab制表符
{1} 以二进制形式打印
Serial.write()

描述
将二进制数据写入串行端口。该数据以字节或一系列字节的形式发送;要发送代表数字数字的字符,请改用print()函数。
句法
Serial.write(val)
Serial.write(str)
Serial.write(buf, len)
参量
Serial:串行端口对象。请参阅“ 串行”主页上每个板的可用串行端口列表。
val:要作为单个字节发送的值。
str:作为一系列字节发送的字符串。
buf:要作为一系列字节发送的数组。
len:要从数组发送的字节数。
退货
write()将返回写入的字节数,尽管读取该数字是可选的。资料类型:size_t。
【4】判断串口缓冲状态
Serial.available()

描述:判断串口缓冲区的状态,返回从串口缓冲区读取的字节数。
原型:Serial.available()
参数:无。
返回值:可读取的字节数。
【5】串口读取数据

Serial.read()
描述:读取串口数据,一次读一个字符,读完后删除已读数据。

  • 原型:Serial.read()
  • 参数:无。
  • 返回值:返回串口缓存中第一个可读字节,当没有可读数据时返回-1,整数类型。
#include 
char rev;
void setup() {
  Serial.begin(115200);
}

void loop() {
  if(Serial.available())
  {
    rev=Serial.read();
    Serial.print("rev=");
    Serial.println(rev);
    }
}
(5)PWM
Arduino core for the ESP32并没有一般Arduino中用来输出PWM的analogWrite(pin, value)方法,取而代之的ESP32有一个LEDC,设计是用来控制LED,像是实现呼吸灯或是控制全彩LED之类,简单的输出PWM当然不在话下。
ESP32的LEDC总共有16个路通道(0 ~ 15),分为高低速两组,高速通道(0 ~ 7)由80MHz时钟驱动,低速通道(8 ~ 15)由1MHz时钟驱动。
LEDC是基于PWM调制实现模拟输出的.
 与arduino uno主板不同, ESP32的PWM模拟是一个个通道 共16个, 通道可以映射到引脚上. 引脚就可以输出PWM信号了.

【1】 设置通道
/*
 * LEDC Chan to Group/Channel/Timer Mapping
** ledc: 0  => Group: 0, Channel: 0, Timer: 0
** ledc: 1  => Group: 0, Channel: 1, Timer: 0
** ledc: 2  => Group: 0, Channel: 2, Timer: 1
** ledc: 3  => Group: 0, Channel: 3, Timer: 1
** ledc: 4  => Group: 0, Channel: 4, Timer: 2
** ledc: 5  => Group: 0, Channel: 5, Timer: 2
** ledc: 6  => Group: 0, Channel: 6, Timer: 3
** ledc: 7  => Group: 0, Channel: 7, Timer: 3
** ledc: 8  => Group: 1, Channel: 0, Timer: 0
** ledc: 9  => Group: 1, Channel: 1, Timer: 0
** ledc: 10 => Group: 1, Channel: 2, Timer: 1
** ledc: 11 => Group: 1, Channel: 3, Timer: 1
** ledc: 12 => Group: 1, Channel: 4, Timer: 2
** ledc: 13 => Group: 1, Channel: 5, Timer: 2
** ledc: 14 => Group: 1, Channel: 6, Timer: 3
** ledc: 15 => Group: 1, Channel: 7, Timer: 3
*/
总共有16个通道,记得不要和已经使用的定时器重复了
// PWM的通道,共16个(0-15),分为高低速两组,
// 高速通道(0-7): 80MHz时钟,低速通道(8-15): 1MHz时钟
// 0-15都可以设置,只要不重复即可,参考上面的列表
// 如果有定时器的使用,千万要避开!!!
ledcSetup(channel,freq,bit_num)

参数:

- channel : LEDC的PWM通道参数,可选0~15
- freq : 10Hz到40MHz , 但较高的频率精确度低
- bit_num: 占空比分辨率(可选1~16), 比如bit_num=8 则范围 0~2的8次方 , 也就是0~255

推荐的配置:
频率 位深 过渡的可用步骤
1220赫兹 16 65536
2441赫兹 15 32768
4882赫兹 14 16384
9765Hz 13 8192
19531赫兹 12 4096
ledcSetup(1,1200,16);
【2】 通道与引脚映射

Esp32总结_第3张图片

ledcAttachPin(pin,channel)
  ledcAttachPin(5,1);

注意: 一个通道可以同时映射多个引脚

【3】 取消引脚的PWM映射
ledcDetachPin(pin)
ledcDetachPin(5);
【4】向指定通道写入占空比
ledcWrite(channel,duty)
- channel : LEDC的PWM通道参数,可选0~15
例: 呼吸灯
bool add_status = true;
void setup()
{
  pinMode(2,OUTPUT);
  ledcSetup(2,1200,8);
  ledcAttachPin(2,2);
}

void loop()
{
   for(int i = 0 ; i<256; i++)
   {
    if(add_status)
    {
      ledcWrite(2,i);
    }
    else
    {
      ledcWrite(2,256-i);
    }
    delay(5);
  }
  add_status = !add_status;
}
(6)定时器
【1】定时器开启
ESP32 芯片包含两个硬件定时器组。每组有两个通用硬件定时器。它们都是基于 16 位预分频器和 64 位自动重载功能的向上/向下计数器的 64 位通用定时器。
初始化定时器 timerBegin
hw_timer_t * timerBegin(uint8_t num, uint16_t divider, bool countUp){}

参数:

num : 定时器编号
divider:分频数
countUp: 是否是累加模式
返回值:
返回一个计时器结构体指针 hw_timer_t * ,我们预定义一个指针接收他


hw_timer_t*  tim1= NULL;
tim1 = timerBegin(0,80,true);  //80MHZ, ESP32主频80MHz

    //	函数名称:timerBegin()
    //	函数功能:Timer初始化,分别有三个参数
    //	函数输入:1. 定时器编号(0到3,对应全部4个硬件定时器)
    //			 2. 预分频器数值(ESP32计数器基频为80M,80分频单位是微秒)
    //			 3. 计数器向上(true)或向下(false)计数的标志
    //	函数返回:一个指向 hw_timer_t 结构类型的指针
    timer = timerBegin(0, 80, true);
【2】定时器中断
IRAM 安全中断处理程序
如果您需要在 flash 操作期间运行中断处理程序(比如低延迟操作),请在 注册中断处理程序 时设置 ESP_INTR_FLAG_IRAM。

请确保中断处理程序访问的所有数据和函数(包括其调用的数据和函数)都存储在 IRAM 或 DRAM 中。有两种方法可供使用:

为使编译器将代码分配到IRAM内,中断处理程序应该具有 IRAM_ATTR 属性,这样子能够使代码更加安全
//因此首先定义一个中断服务函数,自己命名

int interruptCounter = 0; //这是一个全局变量

void IRAM_ATTR TimerEvent()
{
    Serial.println(interruptCounter++);
    if (interruptCounter > 5)
    {
        interruptCounter = 1;
    }
}
配置定时器中断 timerAttachInterrupt

void timerAttachInterrupt(hw_timer_t *timer, void (*fn)(void), bool edge){}

参数:

  • *timer : 目标定时器 ( 计时器结构体指针 hw_timer_t * )
  • void (*fn)(void) : 中断函数入口地址
  • 中断边沿触发 : 是否跳变沿触发中断 定时器中断触发方式有: 电平触发中断(level type) 边缘触发中断(edge type)
timerAttachInterrupt(tim1,tim1Interrupt,true);
    //	函数名称:timerAttachInterrupt()
    //	函数功能:绑定定时器的中断处理函数,分别有三个参数
    //	函数输入:1. 指向已初始化定时器的指针(本例子:timer)
    //			 2. 中断服务函数的函数指针
    //			 3. 表示中断触发类型是边沿(true)还是电平(false)的标志
    //	函数返回:无
    timerAttachInterrupt(timer, &TimerEvent, true);
【3】定时器关闭
取消初始化定时器 timerEnd
void timerEnd(hw_timer_t *timer)

参数:

*timer : 目标定时器 ( 计时器结构体指针 hw_timer_t * )
【4】定时器设置
### 定时器设置timerAlarmWrite

void timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload){}

参数:
  • *timer : 目标定时器 ( 计时器结构体指针 hw_timer_t * )
  • alarm_value : 计数上限值
  • autoreload : 是否重装载.
timerAlarmWrite(tim1, 100000, true);
    //	函数名称:timerAlarmWrite()
    //	函数功能:指定触发定时器中断的计数器值,分别有三个参数
    //	函数输入:1. 指向已初始化定时器的指针(本例子:timer)
    //			 2. 第二个参数是触发中断的计数器值(1000000 us -> 1s)
    //			 3. 定时器在产生中断时是否重新加载的标志
    //	函数返回:无
    timerAlarmWrite(timer, 1000000, true);
【5】定时器使能失能
### 使能定时器 timerAlarmEnable

void timerAlarmEnable(hw_timer_t *timer){}

参数:

  • *timer : 目标定时器 ( 计时器结构体指针 hw_timer_t * )
timerAlarmEnable(tim1);
timer = timerBegin(0, 80, true);

timerAlarmEnable(timer); //	使能定时器
失能定时器 timerAlarmDisable
void timerAlarmDisable(hw_timer_t *timer)
(7)经典蓝牙
泛指蓝牙协议在4.0以下的蓝牙,例如HC-05
【1】开启蓝牙
开启蓝牙首先需要导入蓝牙所需要的头文件
#include 
//之后需要定义一下蓝牙串口
BluetoothSerial SerialBT;
//下一步进行串口的初始化,并不是必须的,只是为了方便调试

Serial.begin(115200);
SerialBT.begin("ESP32test"); //Bluetooth device name
//SerialBT.setPin("1234");   // 蓝牙连接的配对码
Serial.println("The device started, now you can pair it with bluetooth!");

初始化中的函数一目了然
Serial.begin()用于设置波特率;
SerialBT.begin("ESP32test")此函数用于设置其它设备在搜索此设备的蓝牙时显示的名称,这里如果用手机蓝牙搜索的话就会显示ESP32test;
Serial.println("The device started, now you can pair it with bluetooth!")此函数用于在对话框中的输出,这里用于提示蓝牙已开始工作

对于函数SerialBT.begin来说,他正常的参数有两个

bool begin(String localName=String(), bool isMaster=false);
//其中第一个为该蓝牙的用户名,第二个为该蓝牙是否为主机
// 如果没有参数传入则默认是蓝牙名称是: "ESP32"
【2】蓝牙接收与发送
if (SerialBT.available())
{
SerialBT.write(SerialBT.read());
}
下类函数为显示串口缓冲区中当前剩余的字符个数,当它>0时说明串口接收到了信息,
Serial.available()此函数为开发板发送的字符个数
SerialBT.available()此函数为开发板接收的字符个数

下类函数为发送和接收的函数
Serial.read()此函数用于存储开发板发送的数据
SerialBT.read()此函数用于存储开发板接收的数据

下类函数为输出函数,用途相同,都是在对话框中显示相应的数据
Serial.write
SerialBT.write
(8)BLE低功耗蓝牙
【1】基础原理
Bluetooth Low Energy (也被称为Bluetooth 4.0、BLE、BTLE),下面记作BLE,是使用2.4GHz的无线短距离无线通信标准。 迄今为止,虽然高速蓝牙已经实现,但BLE在通讯速度上比较普通,主要强调一个纽扣电池能够工作几年的这种省电性能。

GATT已经成为BLE通信的规定,每一个设备中存在很多的“service”(服务),service中还包含有多个“Characteristic”(特征值)。
在蓝牙实际数据交换中,就是通过读写这些“Characteristic”来实现的。

Esp32总结_第4张图片

  • 一个鼠标是一个BLEDevice
  • 一个BLEDevice建立了一个BLE服务器 BLEServer
  • 一个BLE服务器里有多个服务BLEService
  • 一个服务里有多个特征值BLECharacteristic 每个特征值是一种数据.就是通过读写这些“Characteristic”实现读写数据
每个characteristic的值可以在不加密的状态下读写,但配对的操作是加密的。 还有当characteristic的值已改变时,可接收通知(notify)。

服务和characteristic是通过UUID来进行识别的。

UUID是32位的,但那些被蓝牙技术联盟的标准中定义的UUID是以四个数字来表示的。UUID既有16位的也有128位的,我们需要了解的是16位的UUID是经过蓝牙组织认证的,是需要购买的,当然也有一些通用的16位UUID。

UUID:由蓝牙设备厂商提供的UUID,UUID是在硬件编程里已经确定了的,想要操作特定的服务、特征值都需要通过UUID来找。

如果主机的一个特征值characteristic发生改变, 可以使用通知notify来告诉客户端. 这是服务器主动给客户端发的信息, 并非是响应客户端的请求.

这样做有很多好处, 比如ESP32采集到了温度的变化, 可以将数据写入对应的特征值characteristic,然后notify通知客户端.

我们创建特征值时, 把它规定为通知类型, 当这个特征值发生变化时,可以通知客户端,像这样:


pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY); 
//创建一个(读)特征值, 它是通知下发类型的特征值

其实客户端可以不接受服务器发送的notify,方法是修改自己的Descriptor. BLE服务器看到你对自己的描述中标识了不想接收notify,也就不会再给你发了.

我们可以把特征值定为写入类型, 这样客户端可以给我们写入, 触发写入回调函数

BLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
//创建一个(写)特征, 它是写入类型的特征值
  pCharacteristic->setCallbacks(new MyCallbacks());  
//为特征添加一个回调

除了通知和写入, 还有好几种特征值类型, 请后续了解

Esp32总结_第5张图片

1. Service(服务)
service 可以理解为一个服务,在 BLE 从机中有多个服务,例如:电量信息服务、系统信息服务等;
 每个 service 中又包含多个 characteristic 特征值;
 每个具体的 characteristic 特征值才是 BLE 通信的主题,比如当前的电量是 80%,电量的 characteristic 特征值存在从机的 profile 里,这样主机就可以通过这个 characteristic 来读取 80% 这个数据。
 GATT 服务一般包含几个具有相关的功能,比如特定传感器的读取和设置,人机接口的输入输出。组织具有相关的特性到服务中既实用又有效,因为它使得逻辑上和用户数据上的边界变得更加清晰,同时它也有助于不同应用程序间代码的重用。
2. Characteristic(特征)
characteristic 特征,BLE 主从机的通信均是通过 characteristic 来实现,可以理解为一个标签,通过这个标签可以获取或者写入想要的内容。
【2】初始化
把蓝牙设备看作服务器, 把手机看作一个客户端, 客户端可以给服务器发送数据, 服务器可以给客户端下发通知

实现思路:

T使用步骤:
1. 创建一个 BLE Server
2. 创建一个 BLE Service
3. 创建一个 BLE Characteristic
4. 创建一个 BLE Descriptor
5. 开始服务
6. 开始广播
//首先需要导入和BLE有关的库

#include 
#include 
#include 
#include 
//之后进行一些初始化的定义

uint8_t txValue = 0;				//用于保存需要发送的值
BLEServer *pServer = NULL;                   //BLEServer指针 pServer
BLECharacteristic *pTxCharacteristic = NULL; //BLECharacteristic指针 pTxCharacteristic
bool deviceConnected = false;                //本次连接状态
bool oldDeviceConnected = false;             //上次连接状态


//同时需要确定UUID
// See the following for generating UUIDs: https://www.uuidgenerator.net/
#define SERVICE_UUID "12a59900-17cc-11ec-9621-0242ac130002" // UART service UUID
#define CHARACTERISTIC_UUID_RX "12a59e0a-17cc-11ec-9621-0242ac130002"
#define CHARACTERISTIC_UUID_TX "12a5a148-17cc-11ec-9621-0242ac130002"

//这个UUID可以在上面的网页连接中去生成,但是需要生成三个不一样的
//之后编写调用服务之后的回调函数

/*判断是否连接上并进行状态的改变*/
class MyServerCallbacks : public BLEServerCallbacks
{
    void onConnect(BLEServer *pServer)
    {
        deviceConnected = true;
    };

    void onDisconnect(BLEServer *pServer)
    {
        deviceConnected = false;
    }
};




/*接收到信息的回调函数,采用C++编写*/

class MyCallbacks : public BLECharacteristicCallbacks
{
    void onWrite(BLECharacteristic *pCharacteristic)
    {
        std::string rxValue = pCharacteristic->getValue(); //接收信息,采用C++string字符串

        if (rxValue.length() > 0)
        { //向串口输出收到的值
            Serial.print("RX: ");
            for (int i = 0; i < rxValue.length(); i++)
                Serial.print(rxValue[i]);
            Serial.println();
        }
    }
};

//具体初始化函数

void setup()
{
	//先初始化一个串口,用于传递接收到的信息
    Serial.begin(115200);

    // 创建一个 BLE 设备,UART_BLE为设备名称
    BLEDevice::init("UART_BLE");

    // 创建一个 BLE 服务
    pServer = BLEDevice::createServer();
    pServer->setCallbacks(new MyServerCallbacks()); //设置连接状态回调
    BLEService *pService = pServer->createService(SERVICE_UUID);

    // 创建一个 BLE 特征
    pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
    pTxCharacteristic->addDescriptor(new BLE2902());
    BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
    pRxCharacteristic->setCallbacks(new MyCallbacks()); //设置回调

    pService->start();                  // 开始服务
    pServer->getAdvertising()->start(); // 开始广播
    Serial.println(" 等待一个客户端连接,且发送通知... ");
}
  1. 主函数中
void loop()
{
    // deviceConnected 已连接,执行发送数据的服务
    if (deviceConnected)
    {
        pTxCharacteristic->setValue(&txValue, 1); // 设置要发送的值为1
        pTxCharacteristic->notify();              // 广播
        txValue++;                                // 指针地址自加1
        delay(2000);                              // 如果有太多包要发送,蓝牙会堵塞
    }

    // disconnecting  断开连接,重新广播,使得其他设备能够搜索到此BLE
    if (!deviceConnected && oldDeviceConnected)
    {
        delay(500);                  // 留时间给蓝牙缓冲
        pServer->startAdvertising(); // 重新广播
        Serial.println(" 开始广播 ");
        oldDeviceConnected = deviceConnected;
    }

    // connecting  正在连接,改变连接状态标志
    if (deviceConnected && !oldDeviceConnected)
    {
        // do stuff here on connecting
        oldDeviceConnected = deviceConnected;
    }
}

2)常用外设

(1)库管理
对于platformio他拥有很强的库管理功能,和arduino是一摸一样的,当你需要用到什么库时,可以通过点击platformio的图标,找到库,之后可以进行搜索

Esp32总结_第6张图片

选择好之后就可以添加到工程

3.制作开发板

1)注意事项
(1)启动项相关
有几个脚必须接高电平
(2)程序自动下载

下载模式

ESP8266/ESP32进入下载模式的条件很简单:

EN(也称为RST)上升沿时候GPIO0保持为低电平,如图所示,

在这里插入图片描述

下载电路如下所示,其结构与RS触发器比较类似,注意EN和IO0信号均连接在三极管集电极,通过控制三极管只能拉低此信号,若三极管截止,则此信号的状态由其他电路决定(一般来说,此类信号会默认接电阻上拉到VCC)

Esp32总结_第7张图片

逻辑关系如下

DTR = 0; RTS = 0, 此时Q1截止,Q2截止,EN = 1; IO0 = 1
DTR = 0; RTS = 1,此时Q1截止,Q2导通, EN = 1; IO0 = 0
DTR = 1; RTS = 0, 此时Q1导通,Q2截止, EN = 0; IO0 = 1
DTR = 1; RTS = 1, 此时Q1截止,Q2截止, EN = 1; IO0 = 1

列表如下

DTR RTS  EN  IO0
 0   0    1   1
 0   1    1   0
 1   0    0   1
 1   1    1   1

简单总结:当DTR和RTS同时为0或者同时为1时,三极管Q1和Q2均为截止状态,此时EN和IO0的状态由其他电路决定(内部/外部上拉电阻)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EbxriDda-1651045208781)(img/image-20211123143145229.png)]

当不同时为0或者1时:

EN  = RTS
IO0 = DTR

注意这种逻辑下 EN和IO0是不可能同时为0的,然而进入下载模式则需要如下的序列

1.  IO = 0; EN = 0
2.  IO = 0; EN 0 -> 1

从逻辑表上看是根本无法正常进入下载模式的,此为疑惑1。

再来继续分析一下esptool.py里下载相关的代码

        # issue reset-to-bootloader:
        # RTS = either CH_PD/EN or nRESET (both active low = chip in reset
        # DTR = GPIO0 (active low = boot to flasher)
        #
        # DTR & RTS are active low signals,
        # ie True = pin @ 0V, False = pin @ VCC.
        if mode != 'no_reset':
            self._setDTR(False)  # IO0=HIGH
        1)  self._setRTS(True)   # EN=LOW, chip in reset
            time.sleep(0.1)
        2)  self._setDTR(True)   # IO0=LOW
        3)  self._setRTS(False)  # EN=HIGH, chip out of reset
            time.sleep(0.05)
        4)  self._setDTR(False)  # IO0=HIGH, done

注意True是低电平,False为高电平,另外代码中的setDTR()和setRTS()两条语句之间虽然看上去紧挨着没有延时,然而由于这里是高级语言python,两条语句之间的延时并不能忽略,因此分析的时候必须依次的进行状态分析,以下分为四个阶段依次分析

\1) 设置DTR = 1; RTS = 0, 此时Q1导通,Q2截止, EN = 0; IO0 = 1
\2) 设置DTR = 0; RTS = 0, 此时Q1截止,Q2截止, EN = 1; IO0 = 1
\3) 设置DTR = 0; RTS = 1, 此时Q1截止,Q2导通, EN = 1; IO0 = 0
\4) 设置DTR = 1; RTS = 1, 此时Q1截止,Q2截止, EN = 1; IO0 = 1

如果按照上面的代码分析来做结论,不论如何系统也是不可能进入下载模式的:EN和IO0首先不可能同时为0,EN由0->1的上升沿IO0也并不为0,再次确认之前的疑惑,那么系统究竟是如何进入下载模式的呢?

问题的答案实际在另外一部分电路,原理其实非常简单:EN信号连接在一个电容充放电电路上

Esp32总结_第8张图片

CHIP_PU即EN,代码中23阶段之后会延时一段时间,而EN由于电容充电,电平并不会立马变为高电平,而是缓慢上升,以如上参数为例计算,同时参考芯片电气参数特性

Esp32总结_第9张图片

高电平为0.75VDD,则达到高电平按照如下公式计算:
Esp32总结_第10张图片

解得t = 14ms,即EN经过14ms上升到电平1,在实际代码中延时了50ms的等待时间,以确保延时后EN处于电平1的状态。

另外需要提的是,阶段1需要等待一段时间,让电容放电,保证EN电平下降到电平0,才能保证系统正常复位,在代码中预留了100ms的等待时间,同样可以通过电容放电公式计算出放电到电平0需要的时间,感兴趣的朋友可以自行根据公式计算确认。

因此在选用串口转ttl芯片的时候需要选择带有RTS和DTR,关于自动reset和自动拉低就需要借用串口的流控,RTS和DTR。画PCB时可以利用UMHN3N芯片

在这里插入图片描述

你可能感兴趣的:(单片机,单片机,嵌入式硬件,物联网,mcu)