前言
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,点击安装即可
然后在开发板中选择自己要开发的板子
安装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一般有三个硬件串口,查阅引脚定义图可知
初始化
初始化串口,speed为波特率一般为9600和115200Serial.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设备。
HSPI和VSPI的接口及引脚
SPI名 | MOSI | MISO | SCLK | SS |
---|---|---|---|---|
VSPI | 23 | 19 | 18 | 5 |
HSPI | 13 | 12 | 14 | 15 |
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);
}