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.rhkb.cn/news/461473.html

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

相关文章

SSID,即Service Set Identifier(服务设置的表示符号)

一、什么是SSID&#xff1f; SSID&#xff0c;即Service Set Identifier&#xff0c;是无线网络中的一个标识符&#xff0c;用于区分不同的无线网络。它可以理解为无线网络的名称&#xff0c;当我们在手机或电脑上搜索可用的无线网络时&#xff0c;就是通过SSID来识别和连接的…

LabVIEW过程控制实验平台

A3000实验平台通过LabVIEW开发&#xff0c;实现了过程控制的虚拟仿真与实时通信&#xff0c;显著提高了教学与实验的互动性和效率。该平台采用模块化设计&#xff0c;支持多种控制策略的实验教学&#xff0c;克服了传统实验设备的不足。项目背景 目前高校过程控制实验设备普遍…

强大的文本编辑器Notepad++8.4.6 最新版

Notepad最新版是一款多功能的代码编辑工具。Notepad官方版支持27种编程语言&#xff0c;涵盖C、C 、Java 、C#,、XML、 HTML,、PHP、python等等&#xff0c;能够帮助程序员提高编辑效率。Notepad软件支持python与sql代码高亮功能&#xff0c;并且免费开源&#xff0c;能够完美地…

Halcon 2D测量Metrology找线/圆/矩形/椭圆

通过2D测量&#xff0c;可以获取物体的范围、方向、角度、位置、尺寸和个数等特征。其中&#xff0c;Halcon的2D Metrology模块提供了亚像素级别的卡尺测量功能&#xff0c;可以测量的几何形状包括直线、圆、椭圆、矩形等。对于2D度量&#xff0c;必须提供要测量的对象的位置&a…

PostgreSQL的学习心得和知识总结(一百五十七)|新的 COPY 选项 LOG_VERBOSITY

目录结构 注&#xff1a;提前言明 本文借鉴了以下博主、书籍或网站的内容&#xff0c;其列表如下&#xff1a; 1、参考书籍&#xff1a;《PostgreSQL数据库内核分析》 2、参考书籍&#xff1a;《数据库事务处理的艺术&#xff1a;事务管理与并发控制》 3、PostgreSQL数据库仓库…

分析 std::optional 的使用与常见错误

文章目录 引言常见错误及解决方案1. 错误使用 std::optional 变量进行算术运算2. 错误检查 std::optional 是否有值3. 忽视 std::optional 的默认值 结论 引言 std::optional 是 C17 引入的一个模板类&#xff0c;用于表示可能有也可能没有值的情况。它特别适用于函数返回值&a…

大模型中的token是什么;常见大语言模型的 token 情况

目录 大模型中的token是什么 常见大语言模型的 token 情况 大模型中的token是什么 定义 在大模型中,token 是文本处理的基本单位。它可以是一个字、一个词,或者是其他被模型定义的语言单元。简单来说,模型在理解和生成文本时,不是以完整的句子或段落为单位进行一次性处理…

深度了解flink(七) JobManager(1) 组件启动流程分析

前言 JobManager是Flink的核心进程&#xff0c;主要负责Flink集群的启动和初始化&#xff0c;包含多个重要的组件(JboMaster&#xff0c;Dispatcher&#xff0c;WebEndpoint等)&#xff0c;本篇文章会基于源码分析JobManagr的启动流程&#xff0c;对其各个组件进行介绍&#x…

深度学习模型入门教程指南

在当前的人工智能生成内容&#xff08;AIGC&#xff09;领域中&#xff0c;深度学习模型无疑是支撑其技术核心的关键组件。深度学习模型的广泛应用极大地推动了图像生成、自然语言处理和自动化工作流的发展&#xff0c;本文将从多个角度介绍深度学习模型的概念、构建过程、实际…

C语言指针的介绍

零.导言 在日常生活中&#xff0c;我们常常在外出时居住酒店&#xff0c;细心的你一定能发现酒店不同的房间上有着不同的门牌号&#xff0c;上面写着像308&#xff0c;512之类的数字。当你定了酒店之后&#xff0c;你就会拿到一个写有门牌号的钥匙&#xff0c;凭着钥匙就能进入…

【Spring MVC】DispatcherServlet 请求处理流程

一、 请求处理 Spring MVC 是 Spring 框架的一部分&#xff0c;用于构建 Web 应用程序。它遵循 MVC&#xff08;Model-View-Controller&#xff09;设计模式&#xff0c;将应用程序分为模型&#xff08;Model&#xff09;、**视图&#xff08;View&#xff09;和控制器&#x…

[ 问题解决篇 ] win11远程桌面报错:出现身份验证错误要求的函数不受支持(附完整解决方案)

&#x1f36c; 博主介绍 &#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 _PowerShell &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 &#x1f389;点赞➕评论➕收藏 养成习…

汽车免拆诊断案例 | 2010款起亚赛拉图车发动机转速表指针不动

故障现象  一辆2010款起亚赛拉图车&#xff0c;搭载G4ED 发动机&#xff0c;累计行驶里程约为17.2万km。车主反映&#xff0c;车辆行驶正常&#xff0c;但组合仪表上的发动机转速表指针始终不动。 故障诊断  接车后进行路试&#xff0c;车速表、燃油存量表及发动机冷却温度…

自动化运维

自动化运维是指使用工具和脚本自动化管理、配置、监控和维护IT基础设施的过程。通过自动化运维&#xff0c;可以提高工作效率&#xff0c;减少人为错误&#xff0c;增加系统的可预测性和稳定性。以下是实现自动化运维的常见步骤和工具&#xff1a; 常见步骤&#xff1a; 1. 定义…

驱动——线程断链和信息获取

实验环境&#xff1a;win7 x32 断链&#xff1a; #include <ntifs.h>NTSTATUS EnumThread(ULONG ulPid, ULONG ulTid) {PEPROCESS pProcessAddr PsGetCurrentProcess();PLIST_ENTRY pHeadlink (PLIST_ENTRY)((ULONG)pProcessAddr 0xb8);PLIST_ENTRY pNextlink pHead…

AWD挨打记录

前言 昨天参加了星盟的AWD集训&#xff0c;本来寻思能猛猛乱杀&#xff0c;结果加固时间只有20分钟&#xff0c;WAF还没push上去就被三家上了不死马QAQ cms是站帮主&#xff0c;之前没打过&#xff0c;D盾啥也没扫出来&#xff0c;还寻思是个贼安全的系统&#xff0c;结果洞满…

鸿蒙打包hvigorw clean报错No npmrc file is matched in the current user folder解决

问题 在执行hvigorw clean等命令时&#xff0c;报错如下&#xff1a; Error: The hvigor depends on the npmrc file. No npmrc file is matched in the current user folder. Configure the npmrc file first解决方案 在用户当前目录下新建.npmrc文件&#xff0c;并配置如下…

前端如何实现进度条

将进度条的宽度动态控制&#xff0c;通过css的transition动画来控制 <template><div class"container"><div class"base-progress"><div class"inner" :style"{ width: w % }"><div class"text&qu…

SWAT-MODFLOW地表水与地下水耦合实践技术

耦合模型被应用到很多科学和工程领域来改善模型的性能、效率和结果&#xff0c;SWAT作为一个地表水模型可以较好的模拟主要的水文过程&#xff0c;包括地表径流、降水、蒸发、风速、温度、渗流、侧向径流等&#xff0c;但是对于地下水部分的模拟相对粗糙&#xff0c;考虑到SWAT…

江协科技STM32学习- P27 实验-串口发送/串口接收

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…