esp32 arduino开发常用函数(需要和乐鑫的arduino文档配合使用)

说明:1、由于写函数参数浪费时间并且没有说明只有参数意义不大,所以在此函数一般只以函数名出现。

2、esp32有两个核心,编号为0和1,如果启动了wifi和蓝牙,则会默认将wifi和蓝牙运行在编号为0的核心上。

3、esp32adc2的引脚尽量不使用,因为wifi会用到。esp32引脚图和硬件资源如下所示。

硬件资源如下,其中I2C、I2S、UART等外设可以被定义到任意管脚上。

• 34 个 GPIO 口
• 12-bit SAR ADC,多达 18 个通道
• 2 个 8-bit D/A 转换器
• 10 个触摸传感器
• 4 个 SPI
• 2 个 I²S
• 2 个 I²C
• 3 个 UART
• 1 个 Host SD/eMMC/SDIO
• 1 个 Slave SDIO/SPI
• 带有专用 DMA 的以太网 MAC 接口,支持 IEEE 1588
• 双线汽车接口(TWAI®,兼容 ISO11898-1)
• IR (TX/RX)
• 电机 PWM
• LED PWM,多达 16 个通道
• 霍尔传感器

看门狗wdt

特别注意这个看门狗,在esp32中有两种看门狗,一种是中断看门狗(IWDT),一种是任务看门狗(TWDT)。下面分别介绍:

1、中断看门狗:当中断函数运行超过800ms时,中断看门狗会重启esp32;

2、esp32有两个CPU,在每一个CPU上,都有一个预先创建的任务即空闲任务,这个任务的优先级非常低(为0),并且它的作用有如下几个:

  • 当esp32处于空闲状态时,自动降低功耗,增加续航时间;
  • 当自己CPU下有任务被删除时,主动清理相关内存资源;
  • 重置看门狗定时器

默认情况下,esp32为0号CPU上的空闲任务添加了看门狗监管逻辑,如果CPU0上的空闲任务,在5秒钟之内获取不到CPU资源,没有机会运行,它将触发看门狗重启机制,导致esp32重启。

特别注意:

1、对于任务看门狗只要任务在5秒内让出cpu资源就可以,比如使用串口打印或者延时,串口打印是io操作不占用cpu所以可以让出cpu资源。

2、cpu内核1上有空闲任务,但是这个内核上默认并没有启动任务看门狗。

3、setup和loop函数是被一个运行在内核1上的任务调用,并且它的优先级是1。如果启动内核1上的看门狗,即使运行在内核1上的函数让出cpu资源也会重启,这是因为会让出cpu后,cpu马上会去执行loop函数导致空闲任务无法被执行,可以在loop函数中加一个延时就解决了。

  disableCore0WDT(); 禁用内核0上的任务看门狗,直接写在setup函数中就可以。

  enableCore0WDT(); 启用内核0上的任务看门狗

  disableCore1WDT();

  enableCore1WDT();

esp_task_wdt_add(NULL); 申请看门狗看护当前任务。

esp_task_wdt_reset();看门狗喂狗操作即重置看门狗定时器。

延时函数

说明:在多任务场景下,调用下面的两个函数,当前任务将会让出CPU资源,其他任务可以借机获取CPU资源

delay(uint32_t ms);

delayMicroseconds(uint32_t us);

led control/pwm

特别注意只有34到39引脚不支持pwm,因为它们只支持输入模式

analogWrite(uint8_t pin, int value);用于设置占空比,位宽为8位,即value取值范围为0到255,在使用之前可能需要配置gpio输出模式

ledcWrite,用于设置占空比,一般在ledcAttach函数之后使用,比如如果ledcAttachs设置位        宽为10即电压0到3.3v对应0到1023,共1024个数,此时如果ledcWrite(1,50)则表示占空        比为50/1024

ledcRead

ledcAttach

gpio

pinMode,如果引脚只支持输入模式则不能配置成输出模式

digitalWrite(uint8_t pin, uint8_t val);在输入模式下不能使用此函数,val值有LOW和HIGH

digitalRead,无论引脚配置成输入还是输出都可以使用这个函数

中断相关函数:

attachInterrupt

attachInterruptArg

detachInterrupt

dac

dac也可以实现呼吸灯效果,它与pwm波不同,dac是直接能输出一个0v到3.3v,而pwm是方波它是通过调整占空比来实现的呼吸灯。

使用dac时,不需要再配置引脚模式,直接用dacWrite函数就可以。

dacWrite,用来设置引脚dac的值,值范围为0到255,对应的电压是0v到3.3v

adc

1、esp32上有18个12位的adc输入通道,特别注意在使用wifi时不能使用adc2的引脚。

2、使用adc时要特别注意,使用的引脚必须是支持adc转换的引脚,不支持adc的引脚使用下面函数会报错。

3、特别注意,esp32内置的adc不够准,所以在adc精度有要求的场景下很多人都会选择外置adc芯片。

analogRead

串口

硬件串口

esp32能直接使用的有两个串口,一个是串口0,另一个是串口2,它们分别对应代码中的类Serial和Serial2。还有一个串口是串口1,由于它连接的到模组中的flash,所以不能直接使用,可以重映射到其他引脚上。

Serial.begin(115200);设置串口波特率为115200

Serial.readStringUntil('\n');读取字符串,读到\n结束。

Serial.println,打印一行数据,1个参数,可以填数字包括整数和浮点数,也可以填字符串。

Serial.printf,与c语言中的printf使用一样。

重映射串口1

需要引入头文件HardwareSerial.h,创建对象HardwareSerial mySerial(1);这里参数填1表示创建一个串口1的对象。

mySerial.begin(9600,SERIAL_8N1,18,19);SERIAL_8N1表示8位数据位无校验位1位停止位,18和19表示重映射的引脚。之后就可以正常使用串口1了。

软件串口

软件串口不如硬件串口好,因为它是通过软件模拟出来的串口,不过当硬件资源不够时可以考虑使用软件串口,使用时需要引入一个第三方库,详细步骤请看b站课程。

wifi(wifi有两种模式,一种是sta模式(客户端模式),一种是ap模式(热点模式)

支持sta和ap同时打开。使用时需要引入WiFi.h头文件

sta模式:

WiFi.begin(ssid, password);

WiFi.begin,它能配置是否自动连接wifi

WiFi.reconnect

WiFi.disconnect

WiFi.isConnected,返回连接状态,如果连接成功则返回true,否则返回false

WiFi.setAutoReconnect

WiFi.getAutoReconnect

ap模式:

WiFi.softAP(ssid, password);密码可以设成空即NULL,如果设置密码则密码不能少于8位。

WiFi.softAP,它能配置最大连接数和wifi通道。

WiFi.softAPgetStationNum,返回当前连接的数目

WiFi.softAPBroadcastIP,返回ipv4地址

WiFi.softAPenableIPv6(bool enable=true);使能ipv6的支持,如果配置成功则返回真

WiFi.softAPlinkLocalIPv6,返回ipv6地址

WiFi.softAPgetHostname();返回热点名称

WiFi.softAPsetHostname(const char * hostname);设置热点名称

WiFi.softAPSSID,返回热点ssid

WiFi.softAPmacAddress(void);返回mac地址,返回值类型为String

WiFiManager(wifi库,它需要下载的)

这个库非常好用能够自动连接,当连接成功后它会自动将WiFi账号和密码保存到本地用于下次连接,如果连接不成功它会启动一个热点,手机连上这个热点就能配置联网。以后用WiFi外设可以用这个库和上面的wifi函数结合使用。-*

使用这个库时需要引入WiFiManager.h的头文件。然后在setup函数中写入以下两行代码即可。

WiFiManager manager;

manager.autoConnect("连接不成功时启动热点的名称");

如果想清除存储器中存储的wifi信息再连接wifi可以使用下面代码


WiFiManager manager;
manager.resetSettings();
manager.autoConnect("连接不成功时启动热点的名称");

esp-now

1、需要引入esp_now.h头文件。最大传输有效载荷250个字节,使用esp—now,每块开发板既可以是发送方也可以是接收方。

2、注意:在esp-now中一个设备既可以发送数据友可以接受数据,并且它又不像iic和spi一样有scl引脚发送时钟信号(产生时钟信号的设备为主机,另一个为从机),所以esp-now本身并没有从机和主机概念,但是这里为了好描述,将发送数据的一端称为主机,将接受数据的一端称为从机。

3、通信流程:如果a设备和b设备要通信。首先,需要让a向mac地址为FF.FF.FF.FF.FF.FF的设备进行广播(即向同一网段内的所有主机进行广播)直到b设备向a设备发送应答信号后才停止广播,此时b会接受到广播,然后让b记录下a的mac地址然后将a设备注册并向a设备发送应答数据,这里数据定义为了hehe,a设备接受到b设备的应答后,记录下b的mac地址并将b设备注册。完成了以上步骤就算配对完了,a设备能向b设备发送数据,a也能接受b的数据,同理b也是如此。下面代码实现了这个过程,可以直接使用。

ESP_NOW.begin(const uint8_t *pmk = NULL);初始化esp-now,在使用其他esp-now函数之前必须要调用它,如果初始化成功则返回true。特别注意使用这个函数之前应该先初始化wifi

ESP_NOW.end();结束通信。

ESP_NOW.getTotalPeerCount(); 获取配对设备数量

ESP_NOW.getEncryptedPeerCount();获取加密配对设备数量

ESP_NOW.onNewPeer(接受数据回调函数, 回调函数参数);回调函数格式为

void cb(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg);

ESP-NOW Peer,类名称,它是一个抽象类,代表了一个配对设备,要使用它中的add、remove、send成员函数必须由一个类继承它,因为它的这些成员函数在protected关键字中。

a设备代码,可能的错误:hehe数据是否为4位,代码未验证。

#include "ESP32_NOW.h"
#include "WiFi.h"
#include <esp_mac.h>
#include <vector>

// 可以修改
#define ESPNOW_WIFI_CHANNEL 6
class ESP_NOW_Broadcast_Peer : public ESP_NOW_Peer {
public:
  ESP_NOW_Broadcast_Peer(uint8_t channel, wifi_interface_t iface, const uint8_t *lmk): ESP_NOW_Peer(ESP_NOW.BROADCAST_ADDR, channel, iface, lmk) {}
  ~ESP_NOW_Broadcast_Peer() {
    remove();
  }
  bool begin() {
    if (!ESP_NOW.begin() || !add()) {
      return false;
    }
    return true;
  }
  bool send_message(const uint8_t *data, size_t len) {
    if (!send(data, len)) {
      return false;
    }
    return true;
  }
};

class ESP_NOW_Peer_Class : public ESP_NOW_Peer {
public:
  ESP_NOW_Peer_Class(const uint8_t *mac_addr, uint8_t channel, wifi_interface_t iface, const uint8_t *lmk) : ESP_NOW_Peer(mac_addr, channel, iface, lmk) {}
  ~ESP_NOW_Peer_Class() {}
  bool add_peer() {
    if (!add()) {
      return false;
    }
    return true;
  }
  bool send_message(const uint8_t *data, size_t len) {
    if (!send(data, len)) {
      return false;
    }
    return true;
  }
  void onReceive(const uint8_t *data, size_t len, bool broadcast) {
    // 可以修改
    // 这里的通讯模式有广播和单播,MACSTR是让mac地址格式化输出的。
    Serial.printf("发送端的mac地址及通讯模式" MACSTR " (%s)\n", MAC2STR(addr()), broadcast ? "broadcast" : "unicast");
    Serial.printf("数据: %s\n", (char *)data);
  }
};

int PEIDUI_SUCCESS=0;
ESP_NOW_Broadcast_Peer broadcast_peer(ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);
std::vector<ESP_NOW_Peer_Class> masters;
std::vector<uint8_t> mac_addr;
void register_new_master(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) {
  // 判断数据是通过广播还是单播传过来的,判断发送端mac地址之前是否发过数据
  if (memcmp(info->des_addr, ESP_NOW.BROADCAST_ADDR, 6) == 0 && std::count(mac_addr.begin(),mac_addr.end(),*(info->src_addr))==0) {
    // 发送端第一次广播来的数据
    Serial.printf("Unknown peer " MACSTR " sent a broadcast message\n", MAC2STR(info->src_addr));
    ESP_NOW_Peer_Class new_master(info->src_addr, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);
    // 将新设备添加到vector容器
    masters.push_back(new_master);
    mac_addr.push_back(*(info->src_addr));
    if(data[0]=='h'&&data[1]=='e'&&data[2]=='h'&&data[3]=='e')
    PEIDUI_SUCCESS=1;
    if (!masters.back().add_peer()) {
      // 可以修改
      Serial.println("注册设备失败");
      return;
    }

  } else {
    // 可以修改
    // 单播来的数据
    log_v("Received a unicast message from " MACSTR, MAC2STR(info->src_addr));
  }
}

void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
  while (!WiFi.STA.started()) {
    delay(100);
  }

  // Register the broadcast peer
  if (!broadcast_peer.begin()) {
    //注册设备失败进行重启
    delay(2000);
    ESP.restart();
  }
  int PEIDUI_SUCCESS =0;
 ESP_NOW.onNewPeer(register_new_master, NULL);
while(!PEIDUI_SUCCESS)
{


  char data[32]="hehe";
  broadcast_peer.send_message((uint8_t *)data, sizeof(data));
}

}




void loop() {


  delay(2000);
}

b设备代码

#include "ESP32_NOW.h"
#include "WiFi.h"
#include <esp_mac.h>
#include <vector>
// 可以修改
#define ESPNOW_WIFI_CHANNEL 6
class ESP_NOW_Peer_Class : public ESP_NOW_Peer {
public:
  ESP_NOW_Peer_Class(const uint8_t *mac_addr, uint8_t channel, wifi_interface_t iface, const uint8_t *lmk) : ESP_NOW_Peer(mac_addr, channel, iface, lmk) {}
  ~ESP_NOW_Peer_Class() {}
  bool add_peer() {
    if (!add()) {
      return false;
    }
    return true;
  }
  bool send_message(const uint8_t *data, size_t len) {
    if (!send(data, len)) {
      return false;
    }
    return true;
  }
  void onReceive(const uint8_t *data, size_t len, bool broadcast) {
    // 可以修改
    // 这里的通讯模式有广播和单播,MACSTR是让mac地址格式化输出的。
    Serial.printf("发送端的mac地址及通讯模式" MACSTR " (%s)\n", MAC2STR(addr()), broadcast ? "broadcast" : "unicast");
    Serial.printf("数据: %s\n", (char *)data);
  }
};

std::vector<ESP_NOW_Peer_Class> masters;
std::vector<uint8_t> mac_addr;
void register_new_master(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) {
  // 判断数据是通过广播还是单播传过来的,判断发送端mac地址之前是否发过数据
  if (memcmp(info->des_addr, ESP_NOW.BROADCAST_ADDR, 6) == 0 && std::count(mac_addr.begin(),mac_addr.end(),*(info->src_addr))==0) {
    // 发送端第一次广播来的数据
    Serial.printf("Unknown peer " MACSTR " sent a broadcast message\n", MAC2STR(info->src_addr));
    ESP_NOW_Peer_Class new_master(info->src_addr, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);
    // 将新设备添加到vector容器
    masters.push_back(new_master);
    mac_addr.push_back(*(info->src_addr));
    const uint8_t* da="hehe";
    new_master.send_message(da,4);
    if (!masters.back().add_peer()) {
      // 可以修改
      Serial.println("注册设备失败");
      return;
    }
  } else {
    // 可以修改
    // 单播来的数据
    log_v("Received a unicast message from " MACSTR, MAC2STR(info->src_addr));
  }
}
void setup() {
  Serial.begin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
  while (!WiFi.STA.started()) {
    delay(100);
  }

  if (!ESP_NOW.begin()) {
    delay(2000);
    ESP.restart();
  }
  ESP_NOW.onNewPeer(register_new_master, NULL);
}

void loop() {
  delay(1000);
}

freertos

在esp32中有时间片的概念即cpu执行一个时间片的时间就会中断当前任务换其他任务执行,而默认一个时间片的时间是1tick,由宏定义portTICK_PERIOD_MS决定,默认1tick为1ms。

 xTaskCreate,创建任务,格式为xTaskCreate(函数指针,任务名,栈空间大小,优先级,函数参数,句柄对象),说明第一个参数是函数的名字,它的返回值必须是void,参数必须是void*;栈空间大小一般设为2024或4048即2k或4k;优先级是0到24,数字越大优先级越高,其中空闲任务的优先级为0;句柄对象一般填NULL。比如xTaskCreate(print_hehe,"task1",2024,1,(void*)"hehe",NULL),虽然这种方式能创建任务,但是不够灵活,比如不能对任务进行像修改优先级这样的操作。

xTaskCreatePinnedToCore,创建任务并指定在哪个核心上运行。格式为:xTaskCreatePinnedToCore(函数指针,任务名,栈空间大小,优先级,函数参数,句柄对象,cpu内核编号0或1)

xPortGetCoreID();返回当前任务的cpu内核编号。

vTaskDelay,延迟作用,调用该函数的任务将让出CPU资源,其他任务可以借机获取CPU资源,单位是tick,其中每个tick占用的ms数,由宏定义portTICK_PERIOD_MS决定。

vTaskDelete,删除任务,一般在xTaskCreate的第一个参数的那个函数中的最后使用,vTaskDelete(NULL),表示任务结束则删除此任务

pcTaskGetName,获取任务名称,如果参数为NULL表示获得当前任务名称

uxTaskPriorityGet,获取任务优先级,如果参数为NULL表示获得当前任务优先级

代码举例:

1、动态创建任务的例子

#include <Arduino.h>

#define LED1 23 // 控制第一颗LED灯的引脚
#define LED2 22 // 控制第二颗LED灯的引脚

void handle_led1(void *ptr)
{
  pinMode(LED1, OUTPUT);
  char *param = (char *)ptr;
  Serial.print(param);
  Serial.println(" started");
  while (1)
  {
    digitalWrite(LED1, HIGH);
    vTaskDelay(1000);
    digitalWrite(LED1, LOW);
    vTaskDelay(1000);
  }
  vTaskDelete(NULL);
}

void handle_led2(void *ptr)
{
  pinMode(LED2, OUTPUT);
  char *param = (char *)ptr;
  Serial.print(param);
  Serial.println(" started");
  while (1)
  {
    digitalWrite(LED2, HIGH);
    vTaskDelay(3000);
    digitalWrite(LED2, LOW);
    vTaskDelay(3000);
  }
  vTaskDelete(NULL);
}

void setup()
{
  Serial.begin(115200);
  xTaskCreate(handle_led1, "task1", 2048, (void *)"led1", 1, NULL);
  xTaskCreate(handle_led2, "task2", 2048, (void *)"led2", 1, NULL);
}

void loop()
{
}

2、比较灵活的动态创建任务的例子

#include <Arduino.h>

#define LED1 23 // 控制第一颗LED灯的引脚
#define LED2 22 // 控制第二颗LED灯的引脚

TaskHandle_t task1;
TaskHandle_t task2;

void handle_led1(void *ptr)
{
  pinMode(LED1, OUTPUT);
  char *param = (char *)ptr;
  Serial.print(param);
  Serial.println(" started");
  while (1)
  {
    digitalWrite(LED1, HIGH);
    vTaskDelay(1000);
    digitalWrite(LED1, LOW);
    vTaskDelay(1000);
  }
  vTaskDelete(NULL);
}

void handle_led2(void *ptr)
{
  pinMode(LED2, OUTPUT);
  char *param = (char *)ptr;
  Serial.print(param);
  Serial.println(" started");
  while (1)
  {
    digitalWrite(LED2, HIGH);
    vTaskDelay(3000);
    digitalWrite(LED2, LOW);
    vTaskDelay(3000);
  }
  vTaskDelete(NULL);
}

void setup()
{
  Serial.begin(115200);
  xTaskCreate(handle_led1, "task1", 2048, (void *)"led1", 1, &task1);
  xTaskCreate(handle_led2, "task2", 2048, (void *)"led2", 1, &task2);

  //vTaskDelete(task1);
  //vTaskDelete(task2);
}

void loop()
{
}

freertos定时器

定时器有很多应用,比如每30s将数据上传一次服务器。

创建定时器:

TimerHandle_t xTimerCreate( const char * const pcTimerName, 
                                const TickType_t xTimerPeriodInTicks,
                                const UBaseType_t uxAutoReload,
                                void * const pvTimerID,
                                TimerCallbackFunction_t pxCallbackFunction )

该函数用于创建一个定时器,一共有如下几个参数:

  • pcTimerName:定时器的名称,类型为一个字符串
  • xTimerPeriodInTicks:定时器每隔多长时间执行一次,单位为tick,要将时间转换成tick,开始将时间除以portTICK_PERIOD_MS得到
  • uxAutoReload:该定时器是否需要重复触发,为pdTRUE表示重复触发,为pdFALSE表示只触发一次
  • pvTimerID:定时器的标识符,一般不用,除非在多个定时器绑定相同逻辑函数时,才会使用该参数来区分是哪一个定时器触发了逻辑函数;
  • pxCallbackFunction:定时器触发后,需要执行的逻辑函数,返回值为void,只有一个参数,类型为void*

函数的返回值为定时器对象,类型为TimerHandle_t

启动定时器

BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

该函数用于启动一个定时器,有两个参数:

  • xTimer:第一个参数为要启动的定时器的句柄;
  • xTicksToWait:第二个参数为等待时间,表示如果任务队列已满,该函数需要等待的时间,如果设置为0,表示不需要等待
  • 返回值为pdPASS表示定时器启动成功,返回值为pdFAIL表示定时器启动失败

停止定时器

BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );

该函数用于启动一个定时器,有两个参数:

  • xTimer:第一个参数为要启动的定时器的句柄;
  • xTicksToWait:第二个参数为等待时间,表示如果任务队列已满,该函数需要等待的时间,如果设置为0,表示不需要等待
  • 返回值为pdPASS表示定时器启动成功,返回值为pdFAIL表示定时器启动失败

freertos线程间通信、

I2C

使用时要包含Wire.h的头文件

Wire.begin();做主机,默认使用21和22引脚,频率为100KHZ

Wire.begin(sda引脚,scl引脚);做主机,频率为100KHZ

Wire.begin(sda引脚,scl引脚,200000);做主机,频率为200KHZ

Wire.begin(-1,-1,200000);做主机,使用m默认21和22引脚频率为200KHZ

Wire.begin(27);做从机,地址是27,默认使用21和22引脚,频率由主机定

Wire.begin(从机地址,sda引脚,scl引脚);做从机,频率由主机定

Wire.beginTransmission(从机地址);开始传输,值得注意的是这个函数的调用并不会产生 Start 信号 和发送 Slave Address,仅是实现通知 Arduino后面要启动 Master write to Slave 操作。beginTransmission 函数调用后,再调用 write 函数。

Wire.write(val);该函数不直接写入从器件,而是添加到 I2C 缓冲区,因此需要在此函数之后调用Wire.endTransmission(true)h函数,Wire.write(val)必须在Wire.beginTransmission和Wire.endTransmission(true)之间使用。这个函数有3个重载函数,参数可以是uint8_t类型 或字节数据类型即const uint8_t*,size_t 或字符串类型。

Wire.endTransmission(true);存储在 I2C buffer 中的数据将被传输到从机设备。参数如果为true则释放总线,false则不释放总线,参数不写表示会运行重载函数默认也是true。返回值为0表示成功,为1表示数据过长超出缓冲区,为2在地址发送时接受到NACK信号,为3在数据发送时接受到NACk信号,为4其他错误,为5超时错误。通过Wire.beginTransmission和Wire.endTransmission函数可以判断总线上是否有设备。

Wire.requestFrom(address, size);主机向从机读取数据请求并将数据放到缓冲区,第一个参数是从机地址,第二个参数是欲读取数据大小以字节为单位,但是从机不一定会发送size个数据。返回值是收到从机发来的数据量大小。根据返回值可以知道有多少数据要从缓冲区读取。

Wire.readBytes(uint8_t* buffer,size_t length);一次从缓冲区读取length个字节放到buffer数组中。

可以通过以下代码查找设备。

Wire.beginTransmission(address);
uint8_t a= Wire.endTransmission();
if(a==0)
{
  总线上存在地址为address的设备
}

I2s

蓝牙

蓝牙设备使用的地址是mac地址,共6个字节。蓝牙的一些应用场景见下图。

bt classic

spp服务

esp32实现spp服务的是BluetoothSerial类,spp也叫蓝牙串口,简单来说就是让蓝牙向串口一样通信。BluetoothSerial类有两种角色,一种是主设备,一种是从设备,对于主设备,理论上一台主设备最多可以连接7台从设备,但是要注意好像esp32设置了一台主机最多连接4台从设备,对于从设备,一台从设备只能连接一台主设备,并且它也不能搜索主设备,只能被主设备搜索。esp32默认不开启ssp认证,所以默认蓝牙不需要密码就能配对。一般esp32做从机,手机或电脑做主机。

要使用spp功能,需要引入头文件BluetoothSerial.h

先要创建一个BluetoothSerial类的对象,比如BluetoothSerial BTSerial;

常用函数

BTSerial.beigin("蓝牙的名字",是否作为主机);初始化蓝牙,第2个参数如果蓝牙作为主机则填true,否则false,默认为false,即默认做从机。

BTSerial.available();判断蓝牙有多少个数据可以读,返回值为int。

BTSerial.write(const uint8_t *buffer,size_t size);最多发送size字节。

BTSerial.readBytes(char *buffer,size_t length);最多读取length个字节,如果缓冲区数据超出了这个长度则超出部分不读了,如果缓冲区中的数据不到这个长度则没影响,因为这个函数的返回值为读到的数据大小,返回值类型为size_t。

BTSerial.onData(回调函数名称);注册接收回调函数,回调函数有两个参数,比如recvData(const uint8_t *buffer,size_t size)。需要在begin之前运行。

BTSerial.disconnect();关闭当前的spp连接。

BTSerial.end();关闭蓝牙功能。

BTSerial.hasClient();判断是否有设备连接,返回bool值。

BTSerial.isClosed();判断spp连接是否已经关闭,返回bool值。

不太常用的函数

BTSerial.unpairDevice(uint8_t remoteAddress[]);解除指定地址的蓝牙设备的配对。

BTSerial.read();不带参数,返回值是int类型,返回读取的缓冲区的第一个字节,如果错误则返回-1。

BTSerial.peek();和read()类似,不同的是读完数据后不在缓存区中删除读完的数据,而read()会删除。

BTSerial.write(uint8_t c);发送一个字节。

ssp认证

下面的函数都要在BTSerial.begin运行之前运行。

BTSerial.enableSSP(); 启用SSP认证,配对的时候主机会产生一个识别码发送给从机,我们从机需要手动确认,然后主机也得确认。

BTSerial.onConfirmRequest(认证请求回调函数);注册从机将识别码发送给主机后触发的回调函数,认证请求回调函数格式为confimRequest(uint32_t val),其中参数为从机发来的识别码。

BTSerial.confirmReply(是否同意连接);参数为bool值,true表示同意,false表示不同意。常在认证回调函数中使用。

BTSerial.onAuthComplete(认证结果回调函数);注册认证结果回调函数,回调函数格式为btAuthcomplete(boolean success),如果认证成功success为true,否则为false。

经典蓝牙设备搜索

有阻塞式搜索和非阻塞式搜索之分。在使用搜索设备之前要调用BTSerial.beigin将esp32设置为主机。

BTSerial.discover(搜索时间);阻塞式搜索,搜索时间单位为ms,返回值类型为BTScanResults*,它会将搜索到的蓝牙设备保存在蓝牙列表中,所以在下次搜索之前应当清除蓝牙列表中的数据。BTScanResults中有2个常用函数,见下面。

BTScanResults中的函数:

getCout();获取搜索蓝牙的个数。

BTAdvertisedDevice* getDevice(int i);获取蓝牙列表中的第i个设备对象。这个函数返回值中也有几个函数,比如返回值的句柄名字为dev,dev->getName()表示获取设备名称。dev->getRSSI()表示蓝牙强度。dev->getCOD()获取蓝牙cod信息,COD(class of device)是蓝牙设备的类型信息,在搜寻和连接蓝牙设备时,不同的设备类型会显示不同的图标,比如蓝牙键盘、手柄等。dev->getAddress()获取蓝牙mac地址。

BTSerial.discoverClear();清空蓝牙列表的搜索结果。

ble

相对于经典蓝牙,功耗降低了90%。主机可以发起从机的连接,例如手机常作为ble的主机,从机只能等待主机的连接。同一个ble设备既可以作为主机又可以作为从机。

SDMMC

webserver

可以有空再学

分区表与ota

可以有空再学

以下代码是官方示例代码,用来ota升级用的,热点登录密码可以自己修改,默认为test123456。

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <Ticker.h>
#include "html.h"

#define SSID_FORMAT "ESP32-%06lX"  // 12 chars total
#define PASSWORD "test123456"    // generate if remarked

WebServer server(80);
Ticker tkSecond;
uint8_t otaDone = 0;

const char *alphanum = "0123456789!@#$%^&*abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
String generatePass(uint8_t str_len) {
  String buff;
  for (int i = 0; i < str_len; i++) {
    buff += alphanum[random(strlen(alphanum) - 1)];
  }
  return buff;
}

void apMode() {
  char ssid[13];
  char passwd[11];
  long unsigned int espmac = ESP.getEfuseMac() >> 24;
  snprintf(ssid, 13, SSID_FORMAT, espmac);
#ifdef PASSWORD
  snprintf(passwd, 11, PASSWORD);
#else
  snprintf(passwd, 11, generatePass(10).c_str());
#endif
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, passwd);  // Set up the SoftAP
  MDNS.begin("esp32");
  Serial.printf("AP: %s, PASS: %s\n", ssid, passwd);
}

void handleUpdateEnd() {
  server.sendHeader("Connection", "close");
  if (Update.hasError()) {
    server.send(502, "text/plain", Update.errorString());
  } else {
    server.sendHeader("Refresh", "10");
    server.sendHeader("Location", "/");
    server.send(307);
    ESP.restart();
  }
}

void handleUpdate() {
  size_t fsize = UPDATE_SIZE_UNKNOWN;
  if (server.hasArg("size")) {
    fsize = server.arg("size").toInt();
  }
  HTTPUpload &upload = server.upload();
  if (upload.status == UPLOAD_FILE_START) {
    Serial.printf("Receiving Update: %s, Size: %d\n", upload.filename.c_str(), fsize);
    if (!Update.begin(fsize)) {
      otaDone = 0;
      Update.printError(Serial);
    }
  } else if (upload.status == UPLOAD_FILE_WRITE) {
    if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
      Update.printError(Serial);
    } else {
      otaDone = 100 * Update.progress() / Update.size();
    }
  } else if (upload.status == UPLOAD_FILE_END) {
    if (Update.end(true)) {
      Serial.printf("Update Success: %u bytes\nRebooting...\n", upload.totalSize);
    } else {
      Serial.printf("%s\n", Update.errorString());
      otaDone = 0;
    }
  }
}

void webServerInit() {
  server.on(
    "/update", HTTP_POST,
    []() {
      handleUpdateEnd();
    },
    []() {
      handleUpdate();
    }
  );
  server.on("/favicon.ico", HTTP_GET, []() {
    server.sendHeader("Content-Encoding", "gzip");
    server.send_P(200, "image/x-icon", favicon_ico_gz, favicon_ico_gz_len);
  });
  server.onNotFound([]() {
    server.send(200, "text/html", indexHtml);
  });
  server.begin();
  Serial.printf("Web Server ready at http://esp32.local or http://%s\n", WiFi.softAPIP().toString().c_str());
}

void everySecond() {
  if (otaDone > 1) {
    Serial.printf("ota: %d%%\n", otaDone);
  }
}

void setup() {
  Serial.begin(115200);
  apMode();
  webServerInit();
  tkSecond.attach(1, everySecond);
}

void loop() {
  delay(150);
  server.handleClient();
}

preference

能存数据且掉电不丢失,首先要创建一个命名空间,然后可以在命名空间中存储很多键值对。使用时要引入头文件Preferences.h,相应的类为Preferences,头文件中并没有提供相应的类对象,所以要自己创建对象,例如 Preferences preferences,这个对象中有很多方法,其中常用的见下面。

preferences.begin("命名空间的名字");

preferences.isKey("键的名字");如果当前命名空间中有这个键则返回true,否则返回false

preferences.putChar(const char* key, int8_t value);将键值对放到命名空间中保存起来。

preferences.getChar(const char* key, int8_t defaultValue = 0);获得命名空间中这个键对应的值,如果没有这个键则返回所设置的默认的值。

RainMaker

可以有空再学

触摸

可以有空再学

各种好用库

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《鸢尾花数学大系:从加减乘除到机器学习》开源资源

《鸢尾花数学大系&#xff1a;从加减乘除到机器学习》开源资源 Gitee&#xff1a;https://gitee.com/higkoo/ bilibili&#xff1a;https://space.bilibili.com/513194466 GitHub&#xff1a;https://github.com/Visualize-ML

Markdown HTML 图像语法

插入图片 Markdown ![图片描述](图片链接)一般来说&#xff0c;直接复制粘贴过来就行了&#xff0c;部分网页/应用可以拖拽&#xff0c;没人会真敲图片的链接吧…… 示例图片&#xff1a; ![Creeper?](https://i-blog.csdnimg.cn/direct/f5031c8c4f15421c9882d7eb23540b8…

deepseek在pycharm 中的配置和简单应用

对于最常用的调试python脚本开发环境pycharm&#xff0c;如何接入deepseek是我们窥探ai代码编写的第一步&#xff0c;熟悉起来总没坏处。 1、官网安装pycharm社区版&#xff08;免费&#xff09;&#xff0c;如果需要安装专业版&#xff0c;需要另外找破解码。 2、安装Ollama…

华为eNSP:配置单区域OSPF

一、什么是OSPF&#xff1f; OSPF&#xff08;Open Shortest Path First&#xff0c;开放最短路径优先&#xff09;是一种链路状态路由协议&#xff0c;属于内部网关协议&#xff08;IGP&#xff09;&#xff0c;主要用于在单一自治系统&#xff08;AS&#xff09;内部动态发现…

live555推流服务器异常

1.后端异常信息&#xff1a; MultiFramedRTPSink::afterGettingFrame1(): The input frame data was too large for our buffer size (100176). 48899 bytes of trailing data was dropped! Correct this by increasing "OutPacketBuffer::maxSize" to at least m…

ubuntu24.04-系统重装

1.下载系统并安装 参考 Ubuntu-24.04安装教程超详细(2024)_ubuntu24.04-CSDN博客 ubuntu.iso下载地址&#xff1a;https://cn.ubuntu.com/download/desktop 2.添加清华源 1.清华大学开源软件镜像站 | Tsinghua Open Source Mirror sudo passwd root #设置 root 密…

中原银行:从“小机+传统数据库”升级为“OceanBase+通用服务器”,30 +系统成功上线|OceanBase DB大咖说(十五)

OceanBase《DB 大咖说》第 15 期&#xff0c;我们邀请到了中原银行金融科技部数据团队负责人&#xff0c;吕春雷。本文为本期大咖说的精选。 吕春雷是一位资历深厚的数据库专家&#xff0c;从传统制造企业、IT企业、甲骨文公司到中原银行&#xff0c;他在数据库技术与运维管理…

性能测试监控工具jmeter+grafana

1、什么是性能测试监控体系&#xff1f; 为什么要有监控体系&#xff1f; 原因&#xff1a; 1、项目-日益复杂&#xff08;内部除了代码外&#xff0c;还有中间件&#xff0c;数据库&#xff09; 2、一个系统&#xff0c;背后可能有多个软/硬件组合支撑&#xff0c;影响性能的因…

第TR3周:Pytorch复现Transformer

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客 &#x1f356; 原作者&#xff1a;K同学啊 Transformer通过自注意力机制&#xff0c;改变了序列建模的方式&#xff0c;成为AI领域的基础架构 编码器&#xff1a;理解输入&#xff0c;提取上下文特征…

操作系统 2.7-CPU调度策略

什么是CPU调度 这张图展示了操作系统中多进程管理和CPU调度的基本概念。图中显示了三个不同的进程&#xff08;PID:1, PID:2, PID:3&#xff09;&#xff0c;它们各自处于不同的执行状态&#xff0c;并由操作系统的调度器&#xff08;Scheduler&#xff09;进行管理。 图中元素…

TypeError: Cannot assign to read only property ‘xxx‘ of object ‘#<Object>‘

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 &#x1f35a; 蓝桥云课签约作者、…

网络空间安全(16)旁注/跨库/CDN绕过

一、旁注 1. 定义 旁注是一种攻击技术&#xff0c;当黑客无法直接攻击目标网站时&#xff0c;会利用同一服务器上其他网站的安全漏洞&#xff0c;渗透进目标网站&#xff0c;从而获取其权限。这种攻击方式类似于“曲线救国”&#xff0c;通过迂回的方式达成目的。 2. 实现原理 …

ArcGIS操作:13 生成最小外接矩阵

应用情景&#xff1a;筛选出屋面是否能放下12*60m的长方形&#xff0c;作为起降场候选点&#xff08;一个不规则的形状内&#xff0c;判断是否能放下指定长宽的长方形&#xff09; 1、面积初步筛选 Area ≥ 720 ㎡ 面积计算见 2、打开 ArcToolbox → Data Management Tools …

3.6 登录认证

登录功能 登录思路 联调测试 登录校验 问题&#xff1a;在未登录情况下&#xff0c;我们也可以直接访问部门管理、员工管理等功能。 登录标记 用户登录成功之后&#xff0c;每一次请求中&#xff0c;都可以得到该标记。 统一拦截 过滤器Filter拦截器Interceptor 会话技术 会…

基于Spring Boot的校园失物招领系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

Unity光照之Halo组件

简介 Halo 组件 是一种用于在游戏中创建光晕效果的工具&#xff0c;主要用于模拟光源周围的发光区域&#xff08;如太阳、灯泡等&#xff09;或物体表面的光线反射扩散效果。 核心功能 1.光晕生成 Halo 组件会在光源或物体的周围生成一个圆形光晕&#xff0c;模拟光线在空气…

破解透明物体抓取难题,地瓜机器人CASIA 推出几何和语义融合的单目抓取方案|ICRA 2025

概述 近日&#xff0c;全球机器人领域顶会ICRA 2025&#xff08;IEEE机器人与自动化国际会议&#xff09;公布论文录用结果&#xff0c;地瓜机器人主导研发的DOSOD开放词汇目标检测算法与MODEST单目透明物体抓取算法成功入选。前者通过动态语义理解框架提升复杂场景识别准确率…

使用JMeter(组件详细介绍+使用方式及步骤)

JSON操作符 在我们使用请求时,经常会遇到JSON格式的请求体,所以在介绍组件之前我会将介绍部分操作符,在进行操作时是很重要的 Operator Description $ 表示根元素 当前元素 * 通配符,所有节点 .. 选择所有符合条件的节点 .name 子元素,name是子元素名称 [start:e…

AI编程工具-(六)

25030607 这两天依然是用通义灵码做数据分析建模&#xff0c;流程没有改进想法。阻塞感明显&#xff0c;需要更多的动脑了。 数据依然是之前的数据。时序数据B预测时序数据A。 准备工作1 问模型思路&#xff0c;但是我没想出新思路&#xff0c;所以没看出啥。 数据分析1 分…

deepseek使用记录18——艺术的追问

一 好的&#xff0c;基于前面学习结果&#xff0c;再写一篇有艺术美的文章 《美的起义》 凌晨四点的茶摊在电子支付二维码下苏醒&#xff0c;蒸腾的水汽中浮动着八百年前建盏的釉色。老板娘把栀子花插在共享单车车筐里&#xff0c;花瓣的弧度与北宋汝窑青瓷的冰裂纹暗合&…