ESP32外设的简单使用

前言
本文是博主在准备全国大学生物联网设计竞赛(获得国一)作品时,以项目需求驱动的形式,根据需要用到的内容学习整理成的文档(并非所有内容,部分我自己没有搞懂的内容没放上来),仅涉及ESP32的简单使用,开发环境为Arduino(目前正在系统学习esp-idf开发,后面会放上来),希望对各位有帮助。

WiFi

下面是一些 ESP32 Arduino 库中常用的 Wi-Fi 相关函数的介绍:

  1. WiFi.begin(ssid, password):该函数用于连接到 Wi-Fi 网络。需要提供要连接的网络的 SSID 和密码作为参数。
  2. WiFi.disconnect():该函数用于断开当前的 Wi-Fi 连接。
  3. WiFi.status():该函数返回当前 Wi-Fi 连接的状态。返回值可能是以下之一:
    • WL_CONNECTED:已连接到 Wi-Fi 网络。
    • WL_DISCONNECTED:未连接到 Wi-Fi 网络。
    • WL_IDLE_STATUS:Wi-Fi 处于空闲状态。
    • WL_NO_SSID_AVAIL:未找到指定的 Wi-Fi 网络。
  4. WiFi.localIP():该函数返回 ESP32 设备在 Wi-Fi 网络中分配的本地 IP 地址。
  5. WiFi.macAddress():该函数返回 ESP32 设备的 MAC 地址。
  6. WiFi.scanNetworks():该函数用于扫描周围可用的 Wi-Fi 网络。它返回一个整数,表示扫描到的网络数量。可以使用其他函数(如 WiFi.SSID()WiFi.RSSI())来获取每个网络的详细信息。
  7. WiFi.SSID(networkIndex):该函数返回指定索引的扫描到的 Wi-Fi 网络的 SSID。
  8. 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 2void setup() 
{// 初始化串口通信,波特率为9600Serial.begin(9600);// 连接到WiFiWiFi.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);  // 点亮LEDdelay(100);                   // 延迟100毫秒digitalWrite(LED_pin, LOW);   // 关闭LEDdelay(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引脚。
UART0UART1UART2
TX11017
RX3916

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闪存冲突。

  1. 加载 HardwareSerial.h 库
#include <HardwareSerial.h>
  1. 创建一个 HardwareSerial 实例对象
HardwareSerial SerialPort(1);//use Uart1
  1. 初始化 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
启动Wire库并作为主机或者从机加入总线,这个函数调用一次即可,参数为7位从机地址,不带参数就以主机的形式加入总线。

Wire.begin();
Wire.begin(address)

Wire.begin 是一个重载函数。在 Wire 库中,begin 函数有多个重载版本,以支持不同的初始化方式:

  1. Wire.begin()
    • 无参数调用,将设备配置为主设备模式。
    • 使用默认的I2C引脚和标准频率,适用于常规I2C主机模式初始化。
  2. Wire.begin(int address)
    • 传入从设备地址,将设备配置为从设备模式。
    • 参数 address 是从设备的7位I2C地址。使用此版本时,设备作为从机运行,响应其他主设备的请求。
  3. 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 可选参数,为布尔值,默认为 truetrue 表示传输完成后发送停止条件(终止通信),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);            // 发送字节值 valWire.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);  // 初始化串口通信,波特率为115200while (!Serial);  // 等待串口连接成功Serial.println("\nI2C Scanner");  // 输出提示信息到串口,表明程序启动
}void loop() 
{byte error, address;  // 错误代码和I2C设备地址int deviceCount = 0;  // 设备计数器,用于记录找到的I2C设备数量Serial.println("Scanning...");  // 向串口输出正在扫描的提示信息// 扫描I2C地址范围,从1到127for (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 电平变化触发

注意事项

  1. 尽量保证中断程序内容少避免在中断处理函数中使用阻塞函数(如 delay ()),使用非阻塞的延迟方法来处理需要延迟的操作(micros () 函数),以保证中断的正常执行和系统的稳定性
    这是因为 delay () 函数会阻塞整个系统,包括中断的正常执行
  2. 当中断触发时,处理函数应该尽快执行完毕,以确保及时响应并避免中断积压
  3. 与主程序共享的变量要加上 volatile 关键字
  4. 在 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 表示未启动
使用步骤
  1. 初始化定时器 timerBegin ()
  2. 注册中断处理函数 timerAttachInterrupt ()
  3. 设置定时器模式 timerAlarmWrite ()
  4. 启动定时器 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.//如果没有启动,则报错
#endifBluetoothSerial 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 地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/57612.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【python ASR】win11-从0到1使用funasr实现本地离线音频转文本

文章目录 前言一、前提条件安装环境Python 安装安装依赖,使用工业预训练模型最后安装 - torch1. 安装前查看显卡支持的最高CUDA的版本&#xff0c;以便下载torch 对应的版本的安装包。torch 中的CUDA版本要低于显卡最高的CUDA版本。2. 前往网站下载[Pytorch](https://pytorch.o…

mysql8.0.32升级到8.0.40

上篇8.0.32库的准备&#xff1a;mysql: error while loading shared libraries: libncurses.so.5: cannot open shared object file: No suc-CSDN博客 此篇测试升级到8.0.40 MySQL :: Download MySQL Community Server rootjyc:~# mysql -u root -pabcd1234 mysql: [Warning]…

【python GUI编码入门-07】如何在Tkinter中使用线程进行后台任务处理

哈喽,大家好,我是木头左! 当涉及到耗时操作时,如文件I/O、网络请求或复杂计算,直接在主线程中执行这些操作会导致GUI冻结,影响用户体验。为了克服这一问题,可以使用多线程技术将耗时任务移到后台执行,从而保持主线程的响应性。本文将详细探讨如何在Tkinter中使用线程进…

从此告别图床失效:一个几乎可以代理所有图床的工具!

原项目 Github: https://github.com/webp-sh/webp_server_go 根据此项目制作的脚本工具 一键脚本&#xff1a; curl -sS -O https://raw.githubusercontent.com/woniu336/open_shell/main/webp-server.sh && chmod x webp-server.sh && ./webp-server.sh是的&…

JavaScript语法基础——变量,数据类型,运算符和程序控制语句(小白友好篇,手把手教你学会!)

一、JavaScript概述 JavaScript是一种高级编程语言&#xff0c;常用于网页开发和服务器端应用程序。它是一种动态类型语言&#xff0c;可以在浏览器中直接解释执行&#xff0c;而不需要编译。 脚本&#xff08;Script&#xff09;是一种与计算机程序相关的指令集或代码块&…

Jenkins-配置使用ssh拉取仓库代码,配置自动化构建打包(三)(云效)

文章目录 准备注意&#xff1a;流程&#xff1a; 检查之前是否配置过与代码仓库的连接生成密钥对手动在服务器中与代码仓库建立连接以生成 Known hosts file代码仓库端添加公钥Jenkins System-Publish over SSH中添加私钥Jenkins中创建项目&#xff0c;并使用私钥添加ssh凭据构…

镭眸ILS-F13测量型激光雷达:超远距激光雷达专家

镭眸F13是一款超远距离测量型激光雷达&#xff0c;专为需要在超远距离内实现移动物体轮廓数据测量的用户设计。它能够以25Hz的扫描频率&#xff0c;在120米内提供3厘米的扫描精度&#xff0c;解决了现有传感器无法满足的测量距离问题。与市场上其他产品如西克&#xff08;SICK&…

windows安装deepin双系统

最近入手了一台中柏N100的小主机&#xff0c;本来只想当个机顶盒&#xff0c;没想到性能还可以&#xff0c;就打算用它做一些日常的办公&#xff0c;无聊时想着能不能再装个Linux&#xff0c;就去Deepin官网查看了下方法&#xff0c; 在此记录 另外&#xff0c;欢迎来我的博客…

SAP-FICO 月结流程

一、财务月结 1、资产会计-固定资产折旧计提AFAB 正式运行之前&#xff0c;先测试运行&#xff0c;没有问题就正式运行 可以看到&#xff0c;没有错误 因为正式系统的资产会过于庞大&#xff0c;一般都是后台运行资产的折旧 点击程序--后台执行 AFBP查看运行日志&#xff0c…

深度学习(八) TensorFlow、PyTorch、Keras框架大比拼(8/10)

一、深度学习框架概述 深度学习框架在当今人工智能和机器学习领域中占据着至关重要的地位。其中&#xff0c;TensorFlow 由 Google 开发&#xff0c;自 2015 年发布以来&#xff0c;凭借其灵活的计算图、自动微分功能以及跨平台支持等特点&#xff0c;迅速成为主流深度学习框架…

JVM—类加载器、双亲委派机制

目录 什么是类加载器 类加载器的分类 Bootstrap启动类加载器 通过启动类加载器加载用户jar包 Extension扩展类加载器和Application应用程序类加载器 通过扩展类加载器加载用户jar包 双亲委派机制 打破双亲委派机制 自定义类加载器 线程上下文类加载器 Osgi框架的类加…

flask第一个应用

文章目录 安装一、编程第一步二、引入配置三、代码解析 安装 python环境安装的过程就不重复赘述了&#xff0c;flask安装使用命令pip install Flask即可&#xff0c;使用命令pip show Flask查看flask版本信息 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供…

享元模式-实现大颗粒度对象缓存机制

详解 享元模式是一种结构型设计模式&#xff0c;其主要目的是通过共享尽可能多的相同部分来有效地支持大量细粒度的对象。它通过将对象的属性分为内在属性&#xff08;可以共享、不随环境变化的部分&#xff09;和外在属性&#xff08;根据场景变化、不能共享的部分&#xff0…

【系统设计】Merkle 算法在 Git 中的应用:深入理解与实践

引言 在现代软件开发中&#xff0c;Git 已成为版本控制的事实标准。Git 能够快速处理项目的变化&#xff0c;确保代码的完整性&#xff0c;其中一个关键技术就是 Merkle 树。本文将深入探讨 Merkle 算法的原理&#xff0c;以及其在 Git 中的具体应用。 1. Merkle 算法的原理 …

Flutter学习笔记(一)-----环境配置

一、android 环境 android这边可以参照godot的配置 1.装java Java Downloads | Oracle x64 Compressed Archive &#xff1a;下载后直接解压到某个位置&#xff0c;不用安装 x64 installer: 下载后双击安装 注意&#xff1a;不要去百度直接搜Java安装&#xff0c;这样你最多安…

JetBrains Clion Idea 等缓存文件和配置文件迁移

JetBrains 缓存文件和配置文件迁移 文件默认路径 缓存文件默认路径&#xff1a; %userprofile%/AppData/Local/JetBrains/应用名 如 C:/Users/wbl/AppData/Local/JetBrains/CLion2021.3日志文件默认路径&#xff1a;默认在配置文件目录下的log文件夹 %userprofile%/AppData…

《AI产品经理手册》——解锁AI时代的商业密钥

在当今这个日新月异的AI时代&#xff0c;每一位产品经理都面临着前所未有的挑战与机遇&#xff0c;唯有紧跟时代潮流&#xff0c;深入掌握AI技术的精髓&#xff0c;才能在激烈的市场竞争中独占鳌头。《AI产品经理手册》正是这样一部为AI产品经理量身定制的实战宝典&#xff0c;…

uniapp中skymap.html(8100端口)提示未登录的排查与解决方法

问题&#xff1a; 目前账号已经登录&#xff0c;uniapp的其他端口均可以访问到数据&#xff0c;唯独skymap.html中的8100会提示未登录。&#xff08;8100是后端网关gateway端口&#xff09; 分析&#xff1a; 在 skymap.html 中遇到未登录提示的问题&#xff0c;通常是由于该…

2024年最全2024年最系统的网络安全自学路线,学完即可就业_安全学习路线(2),2024年最新你掌握了多少

一个人可以走的很快&#xff0c;但一群人才能走的更远&#xff01;不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人&#xff0c;都欢迎加入我们的的圈子&#xff08;技术交流、学习资源、职场吐槽、大厂内推、面试辅导&#xff09;&#xff0c;让我们一起学习成长&#xf…

前端拖拽库方案之react-beautiful-dnd

近期&#xff0c;知名 React 拖拽库 react-beautiful-dnd 宣布了项目弃用的决定&#xff0c;未来将不再维护。这一决定源于其存在的缺陷与局限性&#xff0c;促使作者转向开发一个更加现代化的拖拽解决方案——Pragmatic drag and drop&#xff08;下面会介绍&#xff09;&…