前言
本文是博主在准备全国大学生物联网设计竞赛(获得国一)作品时,以项目需求驱动的形式,根据需要用到的内容学习整理成的文档(并非所有内容,部分我自己没有搞懂的内容没放上来),仅涉及ESP32的简单使用,开发环境为Arduino(目前正在系统学习esp-idf开发,后面会放上来),希望对各位有帮助。
WiFi
下面是一些 ESP32 Arduino 库中常用的 Wi-Fi 相关函数的介绍:
WiFi.begin(ssid, password)
:该函数用于连接到 Wi-Fi 网络。需要提供要连接的网络的 SSID 和密码作为参数。WiFi.disconnect()
:该函数用于断开当前的 Wi-Fi 连接。WiFi.status()
:该函数返回当前 Wi-Fi 连接的状态。返回值可能是以下之一:WL_CONNECTED
:已连接到 Wi-Fi 网络。WL_DISCONNECTED
:未连接到 Wi-Fi 网络。WL_IDLE_STATUS
:Wi-Fi 处于空闲状态。WL_NO_SSID_AVAIL
:未找到指定的 Wi-Fi 网络。
WiFi.localIP()
:该函数返回 ESP32 设备在 Wi-Fi 网络中分配的本地 IP 地址。WiFi.macAddress()
:该函数返回 ESP32 设备的 MAC 地址。WiFi.scanNetworks()
:该函数用于扫描周围可用的 Wi-Fi 网络。它返回一个整数,表示扫描到的网络数量。可以使用其他函数(如WiFi.SSID()
和WiFi.RSSI()
)来获取每个网络的详细信息。WiFi.SSID(networkIndex)
:该函数返回指定索引的扫描到的 Wi-Fi 网络的 SSID。WiFi.RSSI(networkIndex)
:该函数返回指定索引的扫描到的 Wi-Fi 网络的信号强度(RSSI)。
连接WiFi
#include <Arduino.h>
#include <WiFi.h>
// WiFi网络的SSID和密码
const char *ssid = "Xiaomi 6";
const char *password = "12345678";
// 定义LED引脚
#define LED_pin 2
void setup()
{
// 初始化串口通信,波特率为9600
Serial.begin(9600);
// 连接到WiFi
WiFi.begin(ssid, password);
// 输出连接状态信息到串口
Serial.println("正在连接WiFi");
// 等待WiFi连接成功
while(WiFi.status() != WL_CONNECTED)
{
delay(500); // 延迟500毫秒
Serial.print("."); // 输出"."表示等待
}
// WiFi连接成功后的信息输出
Serial.print("连接成功"); // 输出连接成功提示
Serial.print("IP:"); // 输出IP地址提示
Serial.println(WiFi.localIP()); // 输出设备的IP地址
// 配置LED引脚模式为输出模式
pinMode(LED_pin, OUTPUT);
// 进行LED闪烁以指示连接成功
digitalWrite(LED_pin, HIGH); // 点亮LED
delay(100); // 延迟100毫秒
digitalWrite(LED_pin, LOW); // 关闭LED
delay(1000); // 延迟1秒
digitalWrite(LED_pin, HIGH); // 再次点亮LED
}
void loop() {
// 主循环代码可以在此添加,目前为空
}
串口通信
UART 使用两根不同的数据线,一根用于发送数据(TX),另一根用于接收数据(RX)。UART 一般与其它外围设备(如鼠标、打印机和外部存储设备)连接,允许从计算机发送和接收数据。 UART 是一种可配置的接口,允许用户配置各种通信参数,如传输速率(称为波特率)、数据位数、奇偶校验、停止位和流控制。UART 也可以采用其它的通信协议,比如 RS-232 、 RS-422 和 RS-485 。
ESP32 提供了三个工作在 3.3V TTL 电平的通用同步接收器和发送器 (UART):UART0、UART1 和 UART2。这三个串行接口是硬件支持的。它们每个都暴露 4 个引脚:RX、TX、RTS 和 CTS。然而,Arduino IDE 仅使用 RX 和 TX 引脚。它们每个都分配有默认的 GPIO,如下表:
- RTS(请求发送)引脚和CTS(清除发送)引脚:用于流控制。这些引脚在ESP32硬件上是可用的,但在Arduino IDE中通常只使用RX和TX引脚。
UART0 | UART1 | UART2 | |
---|---|---|---|
TX | 1 | 10 | 17 |
RX | 3 | 9 | 16 |
UART0 用于下载和 REPL(交互式解释器) 调试,UART1 用于模块内部连接 FLASH,通常也不使用,因此可以使用 UART2 与外部串口设备进行通信
Serial1.---
//串口1相关操作
Serial2.---
//串口2相关操作
Serial.begin();//配置串口
Serial.print();//打印信息
Serial.read();//读取一个字节的数据
Serial.available();//检查是否有可用数据(返回可用字节数)
//串口的开启,这里还可以传一些别的参数,下面四个最重要的:波特率,默认SERIAL_8N1为8位数据位、无校验、1位停止位,后面两个分别为 RXD,TXD 引脚
Serial2.begin(115200, SERIAL_8N1, 14, 15);
在 ESP32 中,除了 SERIAL_8N1 之外,还可以使用以下配置参数来设置串口通信的格式:
- SERIAL_8E1:8 个数据位,偶校验位,1 个停止位
- SERIAL_8O1:8 个数据位,奇校验位,1 个停止位
- SERIAL_8N2:8 个数据位,无校验位,2 个停止位
- SERIAL_8E2:8 个数据位,偶校验位,2 个停止位
- SERIAL_8O2:8 个数据位,奇校验位,2 个停止位
这些参数可以在调用 Serial.begin()函数时作为第二个参数传递,以配置串口通信的格式。
UATR1 的使用
UART1的默认引脚是GPIO9和GPIO10,但在ESP32硬件设计中,这些引脚被用来连接SPI闪存(用于存储程序代码和数据),导致它们无法直接用作UART的TX和RX引脚。为了在Arduino IDE中使用UART1,必须在代码中手动重新定义UART1的TX和RX引脚,选择不影响其他功能的GPIO引脚,以避免与SPI闪存冲突。
- 加载 HardwareSerial.h 库
#include <HardwareSerial.h>
- 创建一个 HardwareSerial 实例对象
HardwareSerial SerialPort(1);//use Uart1
- 初始化 SerialPort
在 void setup() 中初始化 SerialPort
//初始化串口,并重新定义引脚
//函数格式如下
Serial1.begin(115200, SERIAL_8N1, new_rx_pin, new_tx_pin);
//参数包括串行通信的波特率、串行模式、使用的RX、TX引脚
SerialPort.begin(115200, SERIAL_8N1, 4, 2);
//这里把4设为RX,2设为TX
这里将 GPIO4 重新分配为 RX 引脚,将 GPIO2 重新分配为 TX 引脚
4. 使用 SerialPort
在代码的其它地方,SerialPort 的使用方法和 Serial、Serial2 的方法相同
在定义 SerialPort 以外的文件使用 SerialPort,需要使用 extern 关键字进行声明
extern HardwareSerial SerialPort;
串口输出相关功能
Serial.write()
print 和 write 的区别
Serial.print: 用于发送数据以人类可读的形式输出。它会将数据格式化为字符串并发送。例如,如果你发送一个整数 123,它会被转换为字符串 “123” 并发送。通常用于打印调试信息。
Serial.write: 直接发送二进制数据。它不会对数据进行任何格式化。例如,如果你发送整数 123,它会作为单个字节值(ASCII字符)发送,而不是字符串 “123”。通常用于发送原始数据或控制字符。
在 Arduino 中,Serial.write 的参数有以下几种:
Serial.write(val)
val 是一个字节(byte)或字符(char)。
Serial.write(buffer, length)
buffer 是一个存储要发送数据的字节数组,length 是数组的长度。
清除屏幕
Serial.print("\033[2J\033[H"); // ANSI序列,清除屏幕并将光标移动到左上角
ANSI 转义序列(由 GPT-4o 生成)
ANSI 转义序列是一种用于控制文本终端行为的特殊字符序列。这些序列通常以 \033(也可以写成 \e 或 \x1B)开头,后面跟着用于控制终端的具体指令。具体指令解释
\033[2J:
\033 是 ANSI 转义序列的起始字符,表示开始一个控制序列。
[ 表示序列的开始。
2J 是指令的一部分,用于清除屏幕。
2J 的具体含义是清除整个屏幕。数字 2 表示清除整个屏幕,J 表示擦除命令。
所以 \033[2J 的效果是清除整个屏幕上的内容。\033[H:
\033 是 ANSI 转义序列的起始字符。
[ 表示序列的开始。
H 是指令的一部分,用于将光标移动到屏幕的左上角位置。
所以 \033[H 的效果是将光标移动到屏幕的左上角。总体原理
当你使用 Serial.print(“\033[2J\033[H”); 时,Arduino 的串口模块会发送这个字符序列到连接的串口设备(比如串口监视器软件或其他终端设备)。如果目标设备支持 ANSI 转义序列(如大多数终端模拟器和串口监视器软件),它会解析这个序列并执行相应的操作:
首先,\033[2J 序列告诉终端清除屏幕上的所有内容。
然后,\033[H 序列告诉终端将光标移动到左上角的位置(通常是第一行第一列)。
这样一来,屏幕上的所有内容都会被清除,并且光标会回到屏幕的起始位置,以便进行新的输出。注意事项
ANSI 转义序列的可移植性很好,但不是所有终端软件都完全支持所有的 ANSI 控制序列。大多数现代终端仿真器和串口监视器支持基本的 ANSI 转义序列,但在特定的硬件或软件环境中可能会有所不同。
在使用 ANSI 转义序列时,确保目标设备和串口通信设置(如波特率和数据位)正确配置,以确保命令能够被正确解释和执行。
通过理解和利用 ANSI 转义序列,你可以在串口通信中实现各种控制和显示效果,这对于调试和交互式控制非常有用。
I2C
ESP32有2个硬件I2C总线接口,接口可以配置为主机或从机模式,支持如下特性:
- 标准模式 (100 Kbit/s)
- 快速模式 (400 Kbit/s)
- 高达 5 MHz,但受 SDA 上拉强度的限制
- 7位/10位寻址模式
- 双寻址模式,用户可以通过编程命令寄存器来控制 I²C 接口,让他们有更大的灵活性
引脚定义
默认引脚
- GPIO21作为SDA
- GPIO22作为SCL
这些引脚是可配置的,可以在代码中通过Wire.begin(SDA, SCL);
函数来更改I2C接口的SDA和SCL引脚
bool begin(int sda, int scl, uint32_t frequency = 0U)
sda
(类型:int
):- 指定I2C数据线(SDA)的GPIO引脚编号。
scl
(类型:int
):- 指定I2C时钟线(SCL)的GPIO引脚编号。
frequency
(类型:uint32_t
,默认为0U
):- 设置I2C的时钟频率(即波特率),单位为赫兹(Hz)。
- 可选值通常包括100000(100 KHz,标准模式)和400000(400 KHz,快速模式)。
- 如果未指定频率,默认频率将根据硬件特性自动设定。
- 返回值(类型:
bool
):- 如果I2C初始化成功,返回
true
;否则返回false
。
- 如果I2C初始化成功,返回
启动I2C
启动Wire库并作为主机或者从机加入总线,这个函数调用一次即可,参数为7位从机地址,不带参数就以主机的形式加入总线。
Wire.begin();
Wire.begin(address)
Wire.begin
是一个重载函数。在 Wire
库中,begin
函数有多个重载版本,以支持不同的初始化方式:
Wire.begin()
:- 无参数调用,将设备配置为主设备模式。
- 使用默认的I2C引脚和标准频率,适用于常规I2C主机模式初始化。
Wire.begin(int address)
:- 传入从设备地址,将设备配置为从设备模式。
- 参数
address
是从设备的7位I2C地址。使用此版本时,设备作为从机运行,响应其他主设备的请求。
Wire.begin(int sda, int scl, uint32_t frequency = 0U)
(仅适用于ESP32等支持自定义I2C引脚的设备):- 接收三个参数:SDA引脚、SCL引脚和可选的频率参数。
- 用于主设备模式下自定义I2C引脚和频率,适合ESP32这种I2C引脚灵活配置的设备。
主设备从从设备请求字节
由主设备向从设备请求字节,之后用available()和read()函数读取字节,第三个参数位为stop,在请求后会发送停止消息,释放I2C总线,否则总线就不会被释放。
Wire.requestFrom(address, quantity);
Wire.requestFrom(address, quantity, stop);
address
指定从设备的I2C地址(7位)。
quantity
请求的数据字节数。
stop
可选参数,为布尔值,默认为 true
。true
表示传输完成后发送停止条件(终止通信),false
则保持连接,允许在同一会话中继续通信
给指定地址的从设备传输数据
给指定地址的从设备传输数据,之后调用write()函数排队传输字节,要通过endTransmission()结束传输。
Wire.beginTransmission(address);
uint8_t endTransmission(bool sendStop);
//true表示传输完成后发送停止条件,释放I2C总线。
//false表示保持I2C总线连接,以便继续进行下一次传输(用于多字节或多条指令的连续通信)
endTransmission()有以下几个返回结果:
- 0:成功
- 1:数据太长,无法放入发送缓冲区
- 2:在发送地址时收到 NACK
- 3:在发送数据时收到 NACK
- 4:其他错误
写数据
向从设备写入数据,在调用 beginTransmission() 和 endTransmission() 之间。
Wire.write(value); // 发送一个字节的数值数据(value)给I2C从设备
Wire.write(string); // 发送一个字符串(string)数据给I2C从设备,字符串会逐字节发送直到字符串结束
Wire.write(data, length); // 发送一个字节数组(data)给I2C从设备,数组长度由length指定
Wire.write(value)
:用于发送单个字节数据。value
可以是0-255
之间的整数或单个字符。Wire.write(string)
:用于发送字符串数据,字符串会逐字节发送,直到遇到字符串结束符\0
。Wire.write(data, length)
:用于发送一个字节数组,data
是指向数据的指针,length
是发送的字节数。适合发送多字节数据,例如多个传感器值或字符串片段。
举个例子
#include <Wire.h>
byte val = 0;// 无符号的8位整数
void setup()
{
Wire.begin(); // 加入 I2C 总线
}
void loop()
{
Wire.beginTransmission(44); // 开始传输到设备 #44 (0x2C)
// 设备地址在数据手册中指定
Wire.write(val); // 发送字节值 val
Wire.endTransmission(); // 停止传输
val++; // 增加 val 的值
if(val == 64) // 如果达到了第 64 个位置 (最大值)
{
val = 0; // 从最小值重新开始
}
delay(500); // 延迟 500 毫秒
}
读数据
调用requestFrom()后从从设备读取数据。
uint8_t requestFrom(int address, int size);
Wire.read()
- address:指定从设备的I2C地址(7位地址)。
- size:请求的数据字节数,即希望接收的数据字节数量。
- 返回值:
uint8_t
类型,表示成功接收的字节数。如果返回的字节数小于size
,则表示读取操作没有获取到足够的数据(允许用户检查是否请求的数据数量和接收到的数据数量一致)。 requestFrom()
并不返回实际的数据,而是请求数据并将其放入一个缓冲区中,等待读取。
举个例子
#include <Wire.h>
void setup()
{
Wire.begin(); // 加入 I2C 总线
Serial.begin(9600); // 启动串口通信,波特率为9600,用于输出数据
}
void loop()
{
Wire.requestFrom(2, 6); // 向地址为2的从设备请求6字节数据
while(Wire.available()) // 从设备可能会发送少于请求的数据
{
char c = Wire.read(); // 接收一个字节并将其作为字符
Serial.print(c); // 打印字符到串口
}
delay(500); // 延迟500毫秒
}
I2C地址搜索
#include <Arduino.h>
#include <Wire.h>
void setup()
{
Wire.begin(); // 初始化I2C总线
Serial.begin(115200); // 初始化串口通信,波特率为115200
while (!Serial); // 等待串口连接成功
Serial.println("\nI2C Scanner"); // 输出提示信息到串口,表明程序启动
}
void loop()
{
byte error, address; // 错误代码和I2C设备地址
int deviceCount = 0; // 设备计数器,用于记录找到的I2C设备数量
Serial.println("Scanning..."); // 向串口输出正在扫描的提示信息
// 扫描I2C地址范围,从1到127
for (address = 1; address < 127; address++)
{
Wire.beginTransmission(address); // 向当前地址发送I2C传输开始信号
error = Wire.endTransmission(); // 结束传输并获取错误码
// 检查传输结果,如果没有错误则表示发现设备
if (error == 0)
{
Serial.print("I2C device found at address 0x"); // 输出已找到设备的提示信息
if (address < 16) Serial.print("0"); // 地址小于16时补充0以保持格式一致
Serial.print(address, HEX); // 以16进制格式输出设备地址
deviceCount++; // 增加设备计数
}
else if (error == 4) // 错误代码4表示找到设备但发生未知错误
{
Serial.print("Unknown error at address 0x"); // 输出地址未知错误信息
if (address < 16) Serial.print("0"); // 地址小于16时补充0以保持格式一致
Serial.println(address, HEX); // 以16进制格式输出地址
}
}
// 根据扫描结果输出总结信息
if (deviceCount == 0)
{
Serial.println("No I2C devices found\n"); // 如果未找到设备,输出相应信息
}
else
{
Serial.println("Scan complete\n"); // 如果找到设备,输出扫描完成信息
}
delay(5000); // 等待5秒后再次进行扫描
}
micros()函数
在 Arduino 编程中,micros ()
函数用于返回自从 Arduino 板启动以来经过的微秒数。它的精度和范围取决于具体的 Arduino 型号,在许多常见的Arduino板子上,micros()
的精度为 4 微秒,也就是说,每次调用的返回值会增加 4 的倍数。
这个函数不接受参数,返回一个 unsigned long 类型的值,表示自 Arduino 开始运行以来经过的微秒数,可用于一些需要定时触发的事件的标志。由于 unsigned long
的最大值限制(4294967295 微秒),micros()
的计数会在约 71 分钟后溢出,并从 0 重新开始。
unsigned long time = micros();
中断
在单片机中,中断是指当 CPU 在正常处理主程序时,突然发生了另一件事件 A(中断发生)需要 CPU 去处理,这时 CPU 就会暂停处理主程序(中断响应),转而去处理事件 A(中断服务)。当事件 A 处理完以后,再回到主程序原来中断的地方继续执行主程序(中断返回)。这一整个过程称为中断
中断的嵌套:在一个中断过程中,发生了一个更高级别的中断事件,CUP优先处理高优先级中断
硬件中断:也被称为外部中断,硬件中断响应外部硬件事件而发生。例如,当检测到触摸时会发生触摸中断,而当 GPIO 引脚的状态发生变化时会发生 GPIO 中断。GPIO 中断和触摸中断属于这一类
软件中断:当触发软件事件(例如定时器溢出)时,会发生这种类型的中断。定时器中断是软件中断的一个例子
外部中断
可以通过外部中断等方式,避免对按键等的重复扫描,从而节省 CPU 资源
ESP 32 的外部中断有上升沿、下降沿、低电平、高电平触发模式。
比如下降沿触发就是当按键按下后触发中断
程序配置
Arduino 中的外部中断配置函数
void attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)
包括 3 个参数:
Pin
GPIO 端口号
ISR
中断服务程序,没有参数与返回值的函数
Mode
中断触发的方式,支持以下触发方式
- LOW 低电平触发
- HIGH 高电平触发
- RISING 上升沿触发
- FALLING 下降沿触发
- CHANGE 电平变化触发
注意事项
- 尽量保证中断程序内容少避免在中断处理函数中使用阻塞函数(如 delay ()),使用非阻塞的延迟方法来处理需要延迟的操作(micros () 函数),以保证中断的正常执行和系统的稳定性
这是因为 delay () 函数会阻塞整个系统,包括中断的正常执行 - 当中断触发时,处理函数应该尽快执行完毕,以确保及时响应并避免中断积压
- 与主程序共享的变量要加上 volatile 关键字
- 在 Arduino 中使用中断时,应尽量避免在中断处理函数中使用 Serial 对象的打印函数
需要在中断中对数据进行调试时,可以通过在中断中改变标记位,再在主函数中打印的方法
当在中断处理函数中使用 Serial 打印函数时,会导致以下问题:时间延迟:Serial 打印函数通常是比较耗时的操作,它会阻塞中断的执行时间,导致中断响应的延迟。这可能会导致在中断期间丢失其他重要的中断事件或导致系统不稳定。缓冲区溢出:Serial 对象在内部使用一个缓冲区来存储要发送的数据。如果在中断处理函数中频繁调用 Serial 打印函数,可能会导致缓冲区溢出,造成数据丢失或不可预测的行为
定时器中断
在使用 Arduino 操控 ESP-32 时,定时器分为硬件定时器和软件定时器
硬件定时器
硬件定时器是 ESP-32 芯片上的内置计时器,它们是专门设计用于定时和计时任务的硬件模块。硬件定时器可以通过设置特定的寄存器来配置和控制,通常具有更高的精确度和稳定性。它们不受软件的影响,可以在后台独立运行,不会受到其他代码的干扰。硬件定时器适用于需要高精度和实时性的定时任务,例如 PWM 输出、捕获输入脉冲等
ESP-32 具有 4 个硬件定时器,具体需要参考技术文档
程序配置
初始化硬件定时器
void timerBegin(timer_num_t timer_num, uint32_t divider, bool count_up);
//eg:
hw_timer_t* timer = NULL;//在Arduino库中一定要用hw_timer_t* 这个类型
timer = timerBegin(0, 80, True);
timer_num
定时器编号,可选值为 0-3 等。divider
定时器的分频系数,用于设置定时器的时钟频率。较大的分频系数将降低定时器的时钟频率。可以根据需要选择合适的值,一般设置为 80 即可;count_up
指定定时器是否为向上计数模式。设置为 true 表示向上计数,设置为 false 表示向下计数。
将中断处理函数与特定定时器关联
void timerAttachInterrupt(hw_timer_t *timer, void (*isr)(void *), void *arg, int intr_type);
//eg:
void timerAttachInterrupt(timer, timer_interrupt, NULL, true);
timer
定时器指针isr
中断处理函数arg
传递给中断处理函数的参数intr_type
中断类型,可选值为 true(边沿触发)或 false(电平触发)
边沿触发指的是中断在信号的变化(即上升沿或下降沿)时触发。例如,当信号从低电平变为高电平(上升沿)或者从高电平变为低电平(下降沿)时触发中断。边沿触发适用于捕捉信号变化的瞬间。(用于捕捉事件的快速变化)
电平触发指的是中断在信号处于某个特定电平(高电平或低电平)时触发。例如,当信号维持在高电平或低电平时,持续触发中断。电平触发适用于检测信号是否处于某个稳定状态。
设定定时器的计数值(事件间隔)
void timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload);
//事件单位为μs
//eg:
void timerAlarmWrite(timer, 1000000, true);
timer
定时器指针alarm_value
:定时器的计数值,即触发时间间隔autoreload
是否自动重载计数值,可选值为 true(自动重载)或 false(单次触发)
其它
void timerAlarmEnable(hw_timer_t *timer);// 用于启动定时器,使其开始计数;
void timerAlarmDisable(hw_timer_t *timer);// 用于禁用定时器,停止计数;
bool timerGetAutoReload(hw_timer_t *timer);// 获取定时器是否自动重新加载;
// 获取定时器的自动重载模式状态,返回 true 表示自动重载,false 表示单次触发
uint64_t timerAlarmRead(hw_timer_t *timer);// 获取定时器计数器报警值;
void timerStart(hw_timer_t *timer);// 计数器开始计数;
void timerStop(hw_timer_t *timer);// 计数器停止计数;
void timerRestart(hw_timer_t *timer);// 计数器重新开始计数,从 0 开始;
bool timerStarted(hw_timer_t *timer);// 计数器是否开始计数
// 检查定时器是否已启动,返回 true 表示计数器正在计数,false 表示未启动
使用步骤
- 初始化定时器 timerBegin ()
- 注册中断处理函数 timerAttachInterrupt ()
- 设置定时器模式 timerAlarmWrite ()
- 启动定时器 timerAlarmEnable ()
蓝牙
基础知识
蓝牙分为 低功耗蓝牙 BLE
和 经典蓝牙 BT
BLE 是蓝牙 4.0 标准中的一个子集,也就是说,蓝牙 4.0 标准包含了 BLE 和经典蓝牙(BR/EDR)两种模式。BLE 是低功耗蓝牙,而经典蓝牙则是一种传统的蓝牙技术,适用于传输音频和文件等大数据量数据。
低功耗蓝牙 BLE
主打低功耗,多用于物联网
经典蓝牙 BT
主打近距离高速传输,多用于蓝牙耳机等
双模蓝牙
兼容 BLE 和 BT,ESP-32 支持双模蓝牙通信
[!note] 单模的 BLE 和单模的 BT 之间不能进行通信
在蓝牙通信中,设备分为主设备和从设备,主设备负责发起连接请求和管理连接状态,从设备在收到请求后进行确认和连接操作,主从设备之间可以是一对一或是一对多
在蓝牙连接中,从设备可以设置一个验证用的 PIN(配对码/密码),防止别人误连设备,在 ESP-32 中,可以使用 SerialBT.setPin() 设置
const char* pin = "1234";
SerialBT.setPin(pin);
主从设备都可以使用这个方法来设置PIN码,从设备使用这个方法设置的时候,就是要求发起链接的设备要输入这个PIN码。而主设备使用这个方法设置的时候,则表示将会使用这个PIN码去建立与从设备的连接
经典蓝牙 BT
经典蓝牙的使用方法类似异步串行通信 Serial
[!warning] 经典蓝牙在与手机、电脑通信时,需要保持串口打开状态,否则会显示“未连接”
#include <Arduino.h>
#include <BluetoothSerial.h>
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)//检查蓝牙配置是否启动
#error Bluetooth is not enabled! Please run `make menuconfig` to and enabled it.//如果没有启动,则报错
#endif
BluetoothSerial SerialBT;//声明一个BluetoothSerial对象,通过这个对象,可以调用类中的方法来管理蓝牙通信
void setup()
{
Serial.begin(115200);
SerialBT.begin("ESP-32test");//设置设备名
}
void Loop()
{
if (Serial.available())
{
SerialBT.write(Serial.read()); //将串口收到的数据,再通过蓝牙串口转发出去
}
if (SerialBT.available())
{ //将蓝牙串口收到的数据,再通过串口把信息发回给电脑
Serial.println(SerialBT.read());
}
}
EPS-32 之间通过经典蓝牙通信
#include <Arduino.h>
#include "BluetoothSerial.h"
BluetoothSerial SerialBT;//声明一个BluetoothSerial对象,通过这个对象,可以调用类中的方法来管理蓝牙通信
#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)//检查蓝牙配置是否启动
#error Bluetooth is not enabled! Please run `make menuconfig` to and enabled it.//如果没有启动,则报错
#endif
#if !defined(CONFIG_BT_SPP_ENABLED)
#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.
#endif
#define Master 1 //1主机 0从机
void Bluetooth_Event(esp_spp_cb_event_t event, esp_spp_cb_param_t *param);
uint8_t address[6]={0xcc,0xdb,0xa7,0x30,0xea,0x06}; //从机MAC地址 不同的蓝牙地址不同 需要自己修改
//目前是00002的MAC地址
void setup()
{
Serial.begin(115200);
SerialBT.register_callback(Bluetooth_Event);//设置回调函数 连接、断开、发送、接收
if(Master)
{
SerialBT.begin("Esp-32_Master", true);
Serial.println("Init complete -Master.");
SerialBT.connect(address);//尝试联机到给定地址的蓝牙设备
}
else
{
SerialBT.begin("Esp-32_slave");
Serial.println("Init comlete -Slave.");
}
Serial.println("The device started, now you can pair it with bluetooth.");
}
void loop()
{
if (SerialBT.available())
{
char c = SerialBT.read();
Serial.println(c);
SerialBT.write(toupper(c));
}
if(Master)
{
SerialBT.write('A');
delay(500);
}
}
void Bluetooth_Event(esp_spp_cb_event_t event, esp_spp_cb_param_t *param)//蓝牙事件回调函数
{
if(event == ESP_SPP_OPEN_EVT || event == ESP_SPP_SRV_OPEN_EVT)//蓝牙连接成功的标志
{ //蓝牙主机和从机模式对应的标志不同,前面是主机,后面是从机
Serial.println("Connection successful.");
}
else if(event == ESP_SPP_DATA_IND_EVT)//数据接收标志
{
while(SerialBT.available())
{
Serial.println(SerialBT.read());
}
Serial.println("Receive complete.");
}
else if(event == ESP_SPP_CLOSE_EVT)//蓝牙连接断开标志
{
Serial.println("Disconnect successful.");
}
else if(event == ESP_SPP_WRITE_EVT)//数据发送标志
{
Serial.println("Send complete.");
}
}
- 通过修改 Master 的值,决定是主机还是从机模式
- 使用 register_callback() 注册一个回调函数,当连接成功、断开连接、接收/发送数据时,回调函数将被启用
- 回调函数的注册需要在 SerialBT.begin() 之前
可以在手机上通过 nRF connect APP 或者其它方式查看设备的 MAC 地址