ESP32外设的简单使用

news2024/11/4 21:21:07

前言
本文是博主在准备全国大学生物联网设计竞赛(获得国一)作品时,以项目需求驱动的形式,根据需要用到的内容学习整理成的文档(并非所有内容,部分我自己没有搞懂的内容没放上来),仅涉及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 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引脚。
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);            // 发送字节值 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 电平变化触发

注意事项

  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.//如果没有启动,则报错
#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 地址

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

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

相关文章

数据结构与算法——Java实现 53.力扣938题——二叉搜索树的范围和

生命的意义 在于活出自我 而不是成为别人眼中的你 —— 24.11.3 938. 二叉搜索树的范围和 给定二叉搜索树的根结点 root&#xff0c;返回值位于范围 [low, high] 之间的所有结点的值的和。 示例 1&#xff1a; 输入&#xff1a;root [10,5,15,3,7,null,18], low 7, high 15 …

TensorRT-LLM的k8s弹性伸缩部署方案

Scaling LLMs with NVIDIA Triton and NVIDIA TensorRT-LLM Using Kubernetes | NVIDIA Technical Blog 一共涉及4个k8s组件&#xff1a; 1. Deployment&#xff1a;跑起来N个pod&#xff1b;指定NVIDIA官方的triton&trt-llm的docker image&#xff0c;指定好model放在哪个…

高亮无惧烈日,强力巨彩租赁屏点亮户外“视”界

在户外显示领域&#xff0c;一款性能出色、适应性强、维护便捷的租赁屏无疑是众多主办方和广告商的首选。强力巨彩旗下的幻云系列租赁屏具备画面清晰、无水波纹、性能稳定、高亮度等诸多优势&#xff0c;可应用于各大户外显示场所&#xff0c;是户外租赁屏市场的明星产品。   …

批量删除redis数据【亲测可用】

文章目录 引言I redis客户端基础操作key的命名规则批量查询keyII 批量删除key使用连接工具进行分组shell脚本示例其他方法III 知识扩展:控制短信验证码获取频率引言 批量删除redis数据的应用: 例如缓存数据使用了新的key存储,需要删除废弃的key。RedisTemplate的key序列化采…

Mysql开发规范

开发规范 对象命名 命名规范的对象&#xff0c;是指数据库SCHEMA、表TABLE、字段COLUMN、索引INDEX、约束CONSTRAINTS等 【强制】凡是需要命名的对象&#xff0c;其标识符不能超过30个字符【强制】名称必须以英文字母开头&#xff0c;不得以 _(下划线) 作为起始和终止字母【…

Web应用性能测试工具 - httpstat

在数字化时代&#xff0c;网站的性能直接影响用户体验和业务成功。你是否曾经在浏览网页时&#xff0c;遇到加载缓慢的困扰&#xff1f;在这个快速变化的互联网环境中&#xff0c;如何快速诊断和优化Web应用的性能呢&#xff1f;今天&#xff0c;我们将探讨一个强大的工具——h…

(57)MATLAB使用迫零均衡器和MMSE均衡器的BPSK调制系统仿真

文章目录 前言一、仿真测试模型二、仿真代码三、仿真结果四、迫零均衡器和MMSE均衡器的实现1.均衡器的MATLAB实现2.均衡器的性能测试 总结 前言 本文给出仿真模型与MATLAB代码&#xff0c;分别使用具有ISI的三个不同传输特性的信道&#xff0c;仿真测试了使用迫零均衡器和MMSE…

用ChatGPT提升工作效率:从理论到实际应用

伴人工智能技术的迅速演进&#xff0c;像ChatGPT这类语言模型已成为提升工作效率的关键工具。这类模型不仅具备处理海量数据的能力&#xff0c;还能自动化许多日常任务&#xff0c;从而提高决策的准确性。本文将深入探讨如何在工作中利用ChatGPT等AI工具提升效率&#xff0c;涵…

MySQL FIND_IN_SET 函数详解

文章目录 1. 基本语法2. 使用场景3. 实战示例3.1 基础查询示例3.2 与其他函数结合使用3.3 动态条件查询 4. 性能考虑5. 常见问题和解决方案5.1 大小写敏感问题5.2 空值处理5.3 模糊匹配 6. 总结 1. 基本语法 FIND_IN_SET 函数的基本语法如下&#xff1a; FIND_IN_SET(str, st…

「Mac畅玩鸿蒙与硬件15」鸿蒙UI组件篇5 - Slider 和 Progress 组件

Slider 和 Progress 是鸿蒙系统中的常用 UI 组件。Slider 控制数值输入&#xff0c;如音量调节&#xff1b;Progress 显示任务的完成状态&#xff0c;如下载进度。本文通过代码示例展示如何使用这些组件&#xff0c;并涵盖 进度条类型介绍、节流优化、状态同步 和 定时器动态更…

ZDH权限-扩展支持数据权限

目录 项目源码 预览地址 安装包下载地址 ZDH权限模块 ZDH权限扩展更细粒度方案 第一种方案&#xff1a; 第二种方案&#xff1a; ZDH权限扩展支持数据权限-新增属性 总结 感谢支持 项目源码 zdh_web: GitHub - zhaoyachao/zdh_web: 大数据采集,抽取平台 预览地址 后…

私有化视频平台EasyCVR海康大华宇视视频平台视频诊断技术是如何实时监测视频质量的?

在现代视频监控系统中&#xff0c;确保视频流的质量和稳定性至关重要。随着技术的进步&#xff0c;视频诊断技术已经成为实时监测视频质量的关键工具。这种技术通过智能分析算法对视频流进行实时评估和处理&#xff0c;能够自动识别视频中的各种质量问题&#xff0c;并给出相应…

Java 用户随机选择导入ZIP文件,解压内部word模板并入库,Windows/可视化Linux系统某麒麟国防系统...均可适配

1.效果 压缩包内部文件 2.依赖 <!--支持Zip--><dependency><groupId>net.lingala.zip4j</groupId><artifactId>zip4j</artifactId><version>2.11.5</version></dependency>总之是要File类变MultipartFile类型的 好像是…

论文笔记(五十四)pi0: A Vision-Language-Action Flow Model for General Robot Control

π0: A Vision-Language-Action Flow Model for General Robot Control 文章概括摘要I. INTRODUCTIONII. RELATED WORKIII. OVERVIEWIV. π 0 \pi_0 π0​模型V. 数据收集和培训配方A. 预训练和后训练B. 语言和高级策略C. 机器人系统细节 VI. 实验评估A. 基础模型评估B. 遵循语…

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

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

论文略读:Self-Knowledge Guided Retrieval Augmentation for Large Language Models

2023 emnlp findings RAG 召回的辅助信息不总是有用&#xff0c;甚至可能起负作用 原本对“德牧能不能进机场”这样的问题&#xff0c;ChatGPT是高度认可德牧作为导盲犬的但是检索模块召回了一段“老德牧是一类 balabala 某种狗的争议性名称”的百科介绍作为额外上文输入后&am…

使用Postman进行API测试

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Postman进行API测试 Postman 简介 安装 Postman 创建请求 组织请求 发送请求 查看响应 使用环境变量 编写测试脚本 示例测试…

鸿蒙系统的优势 不足以及兼容性与未来发展前景分析

2024 年 10 月 22 日&#xff1a;华为正式发布原生鸿蒙操作系统 HarmonyOS next&#xff0c;并正式命名为 HarmonyOS 5&#xff0c;这是鸿蒙系统史上最大的升级&#xff0c;实现了国产操作系统从底层架构到应用生态的全面自主可控。 鸿蒙系统与安卓、iOS 相比&#xff0c;具有…

MT1421-MT1430 码题集 (c 语言详解)

目录 MT1421异或 MT1422总位数 MT1423被3整除 MT1424卡特兰序列 MT1425小码哥的序列 MT1426普洛尼克数 MT1427素数序列 MT1428最小素数因子 MT1429最小正整数 MT1430回文数组 MT1421异或 给定一个由N(<1000)个整数组成的数组&#xff0c;把数组元素任意两两进行异或&#x…

游游的游戏大礼包

游游的游戏大礼包 import java.util.*; public class Main {public static void main(String[] args) {Scanner in new Scanner(System.in);long n in.nextInt();long m in.nextInt();long a in.nextInt();long b in.nextInt();long ret 0;for(long x 0; x < Math.…