ESP32开发帮助


前言

ESP32是一款性能较强,带WiFi和蓝牙模块,开发工具链众多的国产MCU
本文介绍其在Aruduino IDE中的开发方法,因为Arduino代码风格清晰,使用简单
而且Arudino IDE中有非常多的例程可以作为参考

资源下载

Arduino IDE: 官网地址
ESP32 Arduino支持库地址:
https://dl.espressif.com/dl/package_esp32_index.json
ESP8266 Arduino支持库地址:
http://arduino.esp8266.com/stable/package_esp8266com_index.json

资料汇总

ESP32的GPIO-参考博客
IO口与相关外设说明与记录
GPIO与串口
硬件定时器, IIC, SPI

配置Arduino开发环境

添加esp32包,esp8266同理
文件-首选项

开发板管理器网址输入: https://dl.espressif.com/dl/package_esp32_index.json
开发板管理器网址

添加完成后重启IDE,点击工具-开发板管理器
工具-开发板管理器

搜索esp32,点击安装即可
开发板管理器搜索

然后在开发板中选择自己要开发的板子
20220131011259

安装CH340串口芯片驱动
官网地址
下载后安装即可

尝试烧录Blink例程,烧录成功后看到板载Led闪烁即可

GPIO口

基本使用
IO口基本使用方式如下:

  • 使用pinMode(pin, mode)来设置GPIO口工作模式
    pin是标识的管脚数字,
    mode有INPUT、OUTPUT、INPUT_PULLUP、INPUT_PULLDOWN模式(输入、输出、上拉输入、下拉输入,另外还有开漏等模式)
    具体是否能设置对应模式还得参考技术规格书(一般的GPIO0 ~ 33可以设置为输出,基本上都可以设置为输入,GPIO6 ~ 11一般不推荐使用,因为这几个口接了存储程序用的Flash,不当使用可能引起程序崩溃)

  • 使用digitalWrite(pin, value)来设置输出状态,value可选值为HIGH或LOW,即1和0

  • 使用digitalRead(pin)来读取GPIO口电平,返回值为HIGH或LOW,即1和0

  • 使用analogRead(pin)来读取IO口电压高低,返回值区间0~4095

  • 使用dacWrite(pin,value);来输出模拟电压,value值区间0~255

PS:请注意ESP32的IO12,这个IO口上上电时的电平会决定外部flash(存放程序的那颗)的工作电压,上电时该脚为高则认为flash工作于1.8V,为低则认为flash工作于3.3V。常用的像是Wroom-32系列模块该脚内部已下拉,即flash是工作于3.3V的,若外部电路接强上拉则可能导致模块工作异常。

外部中断

外部中断使用方式如下:

  • 使用attachInterrupt(uint8_t pin, void (*)(void), int mode)

    attachInterruptArg(uint8_t pin, void (*)(void*), void * arg, int mode)
    来设置外部中断
    输入参数有gpio号、中断触发时的回调函数、回调函数输入参数、外部中断触发模式(RISING、FALLING、CHANGE……上升沿、下降沿、改变时、低电平、高电平等);
  • 使用detachInterrupt(uint8_t pin)来关闭外部中断;

中断使用示例

使用下面代码进行测试:

// IO14 输出
// IO12 下拉输入模式 电平改变触发中断
// 使用导线连接 IO14 和 IO12

void callBack(void)
{
  int lv = digitalRead(12); //读取加载到IO12上的电平
  Serial.printf("触发了中断,当前电平是: %d\n", lv);
}

void setup()
{
  Serial.begin(115200);
  Serial.println();

  pinMode(14, OUTPUT);
  digitalWrite(14, LOW);

  pinMode(12, INPUT_PULLDOWN);
  attachInterrupt(12, callBack, CHANGE); //使能中断,电平改变触发

  for (int i = 0; i < 5; i++)
  {
    delay(1000);
    digitalWrite(14, 1 ^ digitalRead(14)); //翻转 IO14 输出电平
  }

  detachInterrupt(12); //失能中断
}

void loop()
{
  delay(1000);
  digitalWrite(14, ~digitalRead(14));
}

串口

ESP32一般有三个硬件串口,查阅引脚定义图可知
ESP32-wroom

初始化

初始化串口,speed为波特率一般为9600和115200
Serial.begin(speed)

输出数据

Serial.println(val, format)
描述:打印数据并换行

  • val:打印的值,任意数据类型。
  • format(可选):输出的数据格式。BIN(二进制)、OCT(八进制)、DEC(十进制)、HEX(十六进制)。对于浮点数,此参数指定要使用的小数位数。

Serial.printf("%d",val)
描述:格式化输出

  • %d: 格式化方法
  • val:输出的值
字符 说明
%d 十进制整数输出
%x 十六进制整数输出
%f 浮点输出,默认6位小数
%c 单字符输出
%s 字符串输出
\n 换行
\r 回车
\t Tab制表符

Serial.write()
描述:
将二进制数据写入串行端口。该数据以字节或一系列字节的形式发送
句法
Serial.write(val)
Serial.write(str)
Serial.write(buf, len)

参量

  • Serial:串行端口对象,可以为Serial1,Seria2,Serial3等
    val:要作为单个字节发送的值。
    str:作为一系列字节发送的字符串。
    buf:要作为一系列字节发送的数组。
    len:要从数组发送的字节数。

返回值
返回写入的字节数

读入数据

Serial.available()
描述:判断串口缓冲区的状态,返回从串口缓冲区读取的字节数。
参数:无。
返回值:可读取的字节数。

Serial.read()
描述:读取串口数据,一次读一个字符,读完后删除已读数据。
参数:无。
返回值:返回串口缓存中第一个可读字节,当没有可读数据时返回-1,整数类型。

Serial.readBytes()
描述:从串口读取指定长度的字符到缓存数组。
原型:Serial.readBytes(buffer, length)
参数:
buffer:缓存变量。
length:设定的读取长度。
返回值:返回存入缓存的字符数。

软串口

实际上有两种串口
硬件串口:查阅板子的引脚定义图以确定串口和相应的引脚
软件串口:利用SoftWareSerial库,可以自定义软串口的引脚

定时器

https://www.jianshu.com/p/a67cf88d7e8c

ESP32 芯片包含两个硬件定时器组。每组有两个通用硬件定时器。它们都是基于 16 位预分频器和 64 位自动重载功能的向上/向下计数器的 64 位通用定时器。

基本用法:先初始化定时器,再配置定时器中断,最后编写定时器回调函数

初始化定时器
timerBegin(tim1, divider, true)

原型 hw_timer_t * timerBegin(uint8_t num, uint16_t divider, bool countUp)

参数:

  • num : 定时器编号
    divider:分频数
    countUp: 是否是累加模式
    返回值:返回一个计时器结构体指针 hw_timer_t * ,我们预定义一个指针接收他

示例

hw_timer_t* tim1= NULL; // 定时器结构体
tim1 = timerBegin(0, 80, true); //ESP32主频80MHz,分频到1MHz

取消初始化定时器
void timerEnd(hw_timer_t *timer)

参数:
timer : 目标定时器 (计时器结构体指针 hw_timer_t *)

配置定时器中断
timerAttachInterrupt()

原型void timerAttachInterrupt(hw_timer_t timer, void (fn)(void), bool edge)
参数:

  • timer : 目标定时器 ( 计时器结构体指针 hw_timer_t)
    void (
    fn)(void) : 中断函数入口地址
    中断边沿触发 : 是否跳变沿触发中断 定时器中断触发方式有: 电平触发中断(level type) 边缘触发中断(edge type)

示例

timerAttachInterrupt(tim1,tim1Interrupt,true);

取消定时器中断 timerDetachInterrupt()
void timerDetachInterrupt(hw_timer_t *timer)

定时器设置
timerAlarmWrite()

原型 void timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload)
参数:

  • timer : 目标定时器 ( 计时器结构体指针 hw_timer_t * )
    alarm_value : 计数上限值
    autoreload : 是否重装载
    timerAlarmWrite(tim1, 100000, true);

使能定时器
timerAlarmEnable()

原型 void timerAlarmEnable(hw_timer_t timer)
参数
timer : 目标定时器 ( 计时器结构体指针 hw_timer_t
)
示例

timerAlarmEnable(tim1);

除能定时器
timerAlarmDisable()
原型 void timerAlarmDisable(hw_timer_t timer)
参数
timer : 目标定时器 ( 计时器结构体指针 hw_timer_t
)
示例

timerDisableEnable(tim1);

判断定时器是否启动
timerAlarmEnabled()
bool timerAlarmEnabled(hw_timer_t *timer)

示例

Serial.println(timerAlarmEnabled(tim1));

例子:

\#include <Arduino.h>
hw_timer_t *tim1 = NULL;   // 定时器的句柄
int tim1_IRQ_count = 0;  // 全局计数变量

void tim1Interrupt()
{
  Serial.println("haha");
  tim1_IRQ_count++;
  Serial.println(timerAlarmEnabled(tim1));
}

void setup()
{
  Serial.begin(115200);
  tim1 = timerBegin(0, 80, true);   // 初始化定时器,1MHz频率,是累加模式
  timerAttachInterrupt(tim1, tim1Interrupt, true); // 设置定时器中断和回调函数,是边沿触发
  timerAlarmWrite(tim1, 100000, true);  // 设置触发值 1/1M*100000 = 100ms,打开重装载
  timerAlarmEnable(tim1);  // 使能定时器
}

void loop()
{
  if (tim1_IRQ_count > 10)
  {
    Serial.println("count trigger");
    tim1_IRQ_count = 0;
  }
}

I2C

ESP32有两个I2C控制器(也称为端口),负责处理两条I2C总线上的通信。每个I2C控制器都可以作为主机或从机运行。引脚21 默认的SDA, 引脚22是默认的SCL

IIC需要#include自带库 Wire.h

主机IIC

初始化IIC (以主机身份)
Wire.begin();

以主机身份向从机请求数据
Wire.requestFrom();

原型void requestFrom(uint16_t address, uint8_t size, bool sendStop)

请求完成后 主机可以用Wire.available()Wire.read()等函数等待并获取从机的回答

参数:

  • address : 从机地址
    size: 请求字节数
    sendStop : 是否发送停止 , 如果为true, 释放IIC总线. 如果为false, 发送一个重新开始的信息, 并继续保持IIC总线的连接.

实例

Wire.requestFrom(38, 10, true);

解释:向地址为38的从机请求10字节的数据,数据传输完成后释放IIC总线

主机开始传输
Wire.beginTransmission()

原型 void beginTransmission(int address)
设定要传输的从机地址,随后, 主机可以使用Wire.write();写数据并使用Wire.endTransmission();结束传输

参数:

  • address : 从机地址

示例

Wire.beginTransmission(120);

解释:设定要传输的从机地址为120

写数据
Wire.write()

当作为主机时: 主机将要发送的数据加入发送队列;
当作为从机时: 从机发送的数据给主机;

参数:

  • Wire.write(value); //单字节发送
    Wire.write(string); //以一系列字节发送
    Wire.write(data,length); //以字节形式发送,指定长度
    返回值: byte类型
    传输的字节数

结束数据传输
Wire.endTransmission()

结束传输, 并释放IIC
结束数据传输但不释放IIC占用 endTransmission(false)
返回值: uint8_t
0 成功
1 数据过长,超出发送缓冲区
2 在地址发送时接收到NACK信号
3 在数据发送时接收到NACK信号
4 其他错误

接收数据寄存器是否有值?
Wire.available()

返回接收到的字节数
返回值: byte类型
可读字节数

读取1byte数据
Wire.read()

当作为主机时: 主机使用requestFrom()后 要使用此函数获取数据;
当作为从机时: 从机读取主机给的数据;
返回值: 读到的字节数据 byte

读取多个字节的数据
Wire.readBytes()

原型 size_t readBytes(char *buffer, size_t length)

参数:

  • buffer: 接收缓冲区, 一个char型指针
    length: 数据长度
    返回值: 数据长度

读取直到遇到某字符
原型 size_t readBytesUntil(char terminator, char *buffer, size_t length)

参数:

  • terminator : 终结字符 char类型
    buffer: 接收缓冲区, 一个char型指针
    length: 数据长度
    返回值: 数据长度

当前IIC忙线中?
Wire.busy();

返回布尔值。

从机IIC

初始化IIC (以从机身份)
Wire.begin(adress);

adress取值0~127

示例

Wire.begin(120);

当从机被请求时触发函数onRequest()
void onRequest(void (*)())

参数:
回调函数

当从机收到数据时触发函数
void onReceive(void (*)(int))

参数:
回调函数 (接收一个int类型的参数,代表接收的字节数)

SPI

ESP32有四个SPI外设,分别为SPI0、SPI1、HSPI和VSPI。

  • SPI0是专用于Flash的缓存,ESP32将连接的SPI Flash设备映射到内存中。
  • SPI1和SPI0 使用相同的硬件线,SPI1用于写入flash芯片。
  • HSPI和VSPI可以任意使用。
  • SPI1、HSPI和VSPI共有三条片选线,因此作为SPI主机允许ESP32 至多驱动三个SPI设备。
    SPI拓补结构图

HSPI和VSPI的接口及引脚

SPI名 MOSI MISO SCLK SS
VSPI 23 19 18 5
HSPI 13 12 14 15

SPI通讯流程如下:
SPI通讯流程

相关函数

SPI初始化
SPI.begin();

此初始化方式SPI接口默认VSPI. 接口频率1 000 000, 数据默认采用MSBFIRST(低有效位优先), 时钟模式:SPI_MODE0(SCLK闲置为0, SCLK上升沿采样)

设置数据在SPI上的传输方式
SPI.setBitOrder(bitOrder);

参数:
bitOrder : 传输方式, 可选: LSBFIRST 低有效位先传 ; HSBFIRST 高有效位先传

设置SPI频率
SPI.setFrequency(freq)

参数:
freq 频率
SPI.setFrequency(1000000);

设置SPI的时钟模式
SPI.setDataMode(dataMode);

参数:
dataMode: 时钟模式, 可以取以下值
模式 说明
SPI_MODE0 SCLK闲置为低电平,上升沿采样(默认)
SPI_MODE1 SCLK闲置为低电平,下降沿采样
SPI_MODE2 SCLK闲置为高电平,上升沿采样
SPI_MODE3 SCLK闲置为高电平,下降沿采样

按照setting的设置启动SPI通信
SPI.beginTransaction(setting);

采用该函数,可以代替上面三个函数

参数:

  • setting 设置. 是SPISettings类型的对象, 有_bitOrder ,_clock ,_dataMode 这三个属性.
    setting1._bitOrder = LSBFIRST;
    setting1._clock = 1000000;
    setting1._dataMode = SPI_MODE0;
    SPI.beginTransaction(setting1);

结束SPI通信
SPI.endTransaction();

接收/发送一个字节的数据
SPI.transfer(data);

参数:
data: 要发送的数据
返回值: 接收到的数据

示例代码

uint8_t SPIClass::transfer(uint8_t data)
SPI.transfer(0x01);
SPI.transfer16(0x0102);
SPI.transfer32(0x01020304);

uint8_t byte1;
uint16_t bytes2;
uint32_t bytes3;
byte1 = SPI.transfer();
bytes2 = SPI.transfer16();
bytes3 = SPI.transfer32();

SPI相关驱动和资料
SPI读写SD卡

WiFi

基础用法,连接Wifi

首先,我们需要包含WiFi.h库

#include

为了使代码易于编辑,声明两个全局变量,用于保存我们要连接的WiFi网络的名称及其密码。需要修改成自己的wifi网络名称和密码:

const char *ssid = “yourNetworkName”;
const char *password = “yourNetworkPass”;

然后,我们在WiFi对象上调用begin方法,将之前指定的SSID(网络名称)和密码变量作为参数传递。这将启动与网络的连接:

Serial.begin(115200);
WiFi.begin(ssid, password);

之后,我们将执行while循环,直到有效建立连接。为此,我们可以在WiFi对象上调用status方法,并等待结果与WL_CONNECTED枚举匹配。在每次循环之间,我们引入一个小延迟,以避免不断查询。

while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.println(“Connecting to WiFi..”);
}

循环后,ESP32应成功连接到WiFi网络。查看下面的完整源代码。

#include <WiFi.h>
const char* ssid = "yourNetworkName";
const char* password =  "yourNetworkPass";

void setup()
{
 
  Serial.begin(115200);
 
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.println("Connecting to WiFi...");
  }
 
  Serial.println("Connected to the WiFi network");
 
}
 
void loop()
{
 
}

WiFi其他用法

参考Arduino中的例程,可连接网页并发送http get、post等请求,并对返回的结果进行解析

蓝牙

参考资料
蓝牙BLE基础说明与作为服务器使用
ESP32实现蓝牙主从机连接-BluetoothSerial蓝牙库

蓝牙主机

#include <Arduino.h>
#include "BluetoothSerial.h"

BluetoothSerial SerialBT;

#define Master 1    //主从机模式选择 1主机 0从机

void Bluetooth_Event(esp_spp_cb_event_t event, esp_spp_cb_param_t *param);  //蓝牙事件回调函数

uint8_t address[6]={0x30,0x83,0x98,0xC3,0x50,0xDA}; //从机MAC地址 不同的蓝牙地址不同 需要自己修改

void setup()
{
    Serial.begin(115200);
    SerialBT.register_callback(Bluetooth_Event); //设置事件回调函数 连接 断开 发送 接收
    
    if(Master)
    {
        SerialBT.begin("ESP32_MASTER",true); //开启蓝牙 名称为:"ESP32_MASTER" 主机
        Serial.printf("Init Successful - Master\r\n");
        SerialBT.connect(address);
        Serial.printf("Connect Successful\r\n");
    }
    else
    {
        SerialBT.begin("ESP32_SLAVE");       //开启蓝牙 名称为:"ESP32_SLAVE" 从机
        Serial.printf("Init Successful - Slave\r\n");
    }

}

void loop()
{
    if(Master)
        SerialBT.write('A');
    
    delay(300);
}

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.write("connection successful!\r\n");
    }
    else if(event == ESP_SPP_CLOSE_EVT)     //蓝牙断开连接标志
    {
        Serial.write("disconnect successful!\r\n");
    }
    else if(event == ESP_SPP_DATA_IND_EVT)  //数据接收标志
    {
        while(SerialBT.available())
        {
            Serial.write(SerialBT.read());
        }
        Serial.write("  receive complete! \r\n");
    }
    else if(event == ESP_SPP_WRITE_EVT)     //数据发送标志
    {
        Serial.write("  send complete! \r\n");
    }
}

蓝牙从机

#include "BluetoothSerial.h"

#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
#endif

BluetoothSerial SerialBT;

void setup()
{
  Serial.begin(115200);
  SerialBT.begin("ESP32test"); //Bluetooth device name
  Serial.println("The device started, now you can pair it with bluetooth!");
}

void loop()
{
  if (Serial.available())
  {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available())
  {
    Serial.write(SerialBT.read());
  }
  delay(20);
}

开发过程中遇到的坑(持续更新)

一些用法整理(持续更新)


文章作者: Allen Hong
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Allen Hong !
  目录