一起玩儿物联网人工智能小车(ESP32)——73 ESP32间蓝牙通信功能的实现

摘要:本文介绍ESP32处理器之间如何进行蓝牙通信

关于蓝牙通信的基本知识在前边已经介绍过了,并且还详细说明了与电脑的连接方法,并通过串口调试终端程序实现了电脑与ESP32模块的交互。如果想了解更基础的内容,请参考之前的文档。

那么在今天所要讲述的是如何实现ESP32之间的蓝牙通信。在进行通信之前,先来了解一下蓝牙设备的主从关系。像前面学习的I2C、SPI、I2S通信协议一样,蓝牙通信技术中也是存在主从设备概念的。蓝牙通信中的主设备通常是指主动发起蓝牙连接的设备,而从设备则是被动接受连接的设备。例如,在手机与蓝牙耳机之间的连接中,手机充当主设备,而耳机则是从设备。在之前进行的ESP32与电脑或者手机的蓝牙通信中,电脑或者手机是主设备,发起蓝牙连接。而ESP32处理器则是从设备,等待主设备发起连接。

主设备负责发起连接请求和管理连接状态,从设备在收到连接请求后进行确认和连接操作。主从设备之间的连接可以是一对一的连接,也可以是一对多的连接,这取决于具体应用场景的需求。

在蓝牙通信中,从设备可以设置一个验证的PIN码,也叫配对码或者密码。其目的是为了放置别人误接入或者恶意接入你的蓝牙设备,是一种安全措施,具体是否使用看个人情况。PIN码为4-16位的数字,可以根据自身情况设置,一般蓝牙耳机的PIN码是“0000”或者“1234”。在ESP32中,需要调用setPin()方法来进行设置,如下所示:

const char *pin = "1234";

SerialBT.setPin(pin);

主从设备都可以使用这个方法来设置PIN码,从设备使用这个方法设置的时候,就是要求发起链接的设备要输入这个PIN码。而主设备使用这个方法设置的时候,则表示将会使用这个PIN码去建立与从设备的连接。

今天来开发一个双ESP32设备使用蓝牙进行通信的样例程序,有了这个程序,相信大家可以很方便的实现一个蓝牙遥控器,再接入上一期介绍的游戏手柄之后,就可以用蓝牙遥控你的小车了。

这个程序的功能是一个ESP32所为从设备(模拟小车),接受来自主设备的连接请求,在收到主设备发来的字符串信息后,将其转换为大写后再发送回去(这相当于接受主设备的命令,并执行)。而另一个ESP32设备则是主设备(模拟遥控器),建立与从设备的连接后,将串口收到的字符发送给从设备,并显示从设备返回的结果(这相当于发送指令,并查看执行结果)。

下面先看一下蓝牙从机程序,如下所示:

/*******************************

 **  蓝牙通信从机测试程序    ****

 *******************************

 *****  一起玩儿科技  **********

 *******************************/

#include "BluetoothSerial.h"

#include <ctype.h>

#define USE_PIN 

const char *pin = "1234";

// 蓝牙名称

String device_name = "ESP32-BT-Slave";

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)

#error Bluetooth is not enabled! Please run `make menuconfig` to and enable 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

BluetoothSerial SerialBT;

void setup() {

  Serial.begin(115200);

  SerialBT.begin(device_name); //蓝牙名称

  Serial.printf("设备\"%s\"已启动。\n可以配对与我连接了!\n", device_name.c_str());

  #ifdef USE_PIN

    SerialBT.setPin(pin);

    Serial.println("启用PIN!");

  #endif

}

void loop() {

  if (SerialBT.available()) {

    char c = SerialBT.read();

    Serial.write(c);

    SerialBT.write(toupper(c));

  }

  delay(20);

}

这个程序除了增加了一个PIN的设置功能外,和之前的程序没有太大的差别,在这里就不进一步的解释了。

下面来看一下蓝牙主机的程序,如下所示:

/*******************************

 **  蓝牙通信主机测试程序    ****

 *******************************

 *****  一起玩儿科技  **********

 *******************************/

#include "BluetoothSerial.h"

#define USE_NAME

const char *pin = "1234";

#if !defined(CONFIG_BT_SPP_ENABLED)

#error Serial Bluetooth not available or not enabled. It is only available for the ESP32 chip.

#endif

BluetoothSerial SerialBT;

// 定义从机蓝牙名称

String slaveName = "ESP32-BT-Slave";

// 定义蓝牙主机名称

String myName = "ESP32-BT-Master";

void setup() {

  bool connected;

  Serial.begin(115200);

  SerialBT.begin(myName, true);

  Serial.printf("主设备\"%s\"已启动, 请确保从设备已启动!\n", myName.c_str());

#ifndef USE_NAME

  SerialBT.setPin(pin);

  Serial.println("启用PIN!");

#endif

  connected = SerialBT.connect(slaveName);

  Serial.printf("正在连接到从设备:\"%s\"\n", slaveName.c_str());

  if (connected) {

    Serial.println("连接成功!");

  } else {

    while (!SerialBT.connected(10000)) {

      Serial.println("连接失败,请确保从设备已经启动并在范围之内!");

    }

  }

  // 断开连接

  if (SerialBT.disconnect()) {

    Serial.println("断开成功!");

  }

  // 再次连接

  SerialBT.connect();

  if (connected) {

    Serial.println("再次连接成功!");

  } else {

    while (!SerialBT.connected(10000)) {

      Serial.println("连接失败,请确保从设备已经启动并在范围之内!");

    }

  }

}

void loop() {

  if (Serial.available()) {

    char c = Serial.read();

    Serial.write(c);

    SerialBT.write(c);

  }

  if (SerialBT.available()) {

    //Serial.write("收到:");

    Serial.write(SerialBT.read());

  }

  delay(20);

}

作为主设备的初始化与从设备没什么区别,不一样的是,在初始化之后,主设备要主动的去连接从设备,调用的方法为connect(),其参数为从设备的名称。除了使用从设备的名称与从设备进行连接外,还可以使用从设备的MAC地址与从设备进行连接。

MAC地址(英语:Media Access Control Address),直译为媒体存取控制位址,也称为局域网地址(LAN Address),MAC位址,以太网地址(Ethernet Address)或物理地址(Physical Address),它是一个用来确认网络设备位置的位址。每一个联网的设备都要有一个MAC地址,并且这个地址在同一个局域网内不能与其他设备相同,否则将无法进行正常的通信。

在上面的程序中,连接成功后,又进行了断开和再次连接的测试。后边关于数据收发的处理就不再解释了,之前已经进行过类似的介绍了。

STM32按键设计通常使用外部中断来实现。具体步骤如下: 1. 初始化GPIO口为输入模式,使能外部中断。 2. 定义中断服务函数,并在函数中判断是哪个按键被按下,然后执行相应的操作。 3. 在主函数中启用全局中断。 4. 循环检测按键状态,判断按键是否按下,可以使用软件消抖。 以下是一个简单的例子,通过2个按键实现8种以上的功能: ```c #include "stm32f10x.h" #define KEY1_GPIO_PORT GPIOA #define KEY1_GPIO_PIN GPIO_Pin_0 #define KEY2_GPIO_PORT GPIOA #define KEY2_GPIO_PIN GPIO_Pin_1 void EXTI0_IRQHandler(void) // 中断服务函数 { if (EXTI_GetITStatus(EXTI_Line0) == SET) // 判断是哪个中断引脚产生的中断 { // KEY1按下时执行的操作 // ... EXTI_ClearITPendingBit(EXTI_Line0); // 清除中断标志位 } } void EXTI1_IRQHandler(void) // 中断服务函数 { if (EXTI_GetITStatus(EXTI_Line1) == SET) // 判断是哪个中断引脚产生的中断 { // KEY2按下时执行的操作 // ... EXTI_ClearITPendingBit(EXTI_Line1); // 清除中断标志位 } } void init_key(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); // 使能GPIOA时钟和复用功能时钟 GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; // 配置KEY1的GPIO口 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = KEY2_GPIO_PIN; // 配置KEY2的GPIO口 GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStructure); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); // 配置中断线 EXTI_InitStructure.EXTI_Line = EXTI_Line0; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发 EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1); // 配置中断线 EXTI_InitStructure.EXTI_Line = EXTI_Line1; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿触发 EXTI_Init(&EXTI_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; // 配置中断优先级和使能中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; NVIC_Init(&NVIC_InitStructure); } int main(void) { init_key(); __enable_irq(); // 启用全局中断 while (1) { if (GPIO_ReadInputDataBit(KEY1_GPIO_PORT, KEY1_GPIO_PIN) == RESET) // 检测KEY1状态 { // KEY1按下时执行的操作 // ... } else if (GPIO_ReadInputDataBit(KEY2_GPIO_PORT, KEY2_GPIO_PIN) == RESET) // 检测KEY2状态 { // KEY2按下时执行的操作 // ... } } } ``` 需要注意的是,这里只是一个简单的例子,实际应用中还需要考虑按键消抖等问题。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一起玩儿科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值