實(shí)用 GPIO
用到再查,熟能生巧,別上來就背圖,一天你就忘了!
僅輸入引腳
下面的四個(gè)引腳由于內(nèi)部沒有上拉下拉電阻,所以僅僅支持輸入信號(hào)
GPIO 34
GPIO 35
GPIO 36
GPIO 39
SPI Flash 閃存引腳
這些引腳都是對(duì) ESP32 內(nèi)部 flash 進(jìn)行操作的,最好不要使用這些引腳進(jìn)行輸入輸出操作!
GPIO 6 (SCK/CLK)
GPIO 7 (SDO/SD0)
GPIO 8 (SDI/SD1)
GPIO 9 (SHD/SD2)
GPIO 10 (SWP/SD3)
GPIO 11 (CSC/CMD)
電容觸摸引腳
這個(gè)引腳比較有意思,他們自帶了電容觸摸傳感器,當(dāng)我們直接用手觸摸引腳時(shí)會(huì)發(fā)生電荷改變,從而傳感器接收到并輸出大小不一的信號(hào)脈沖
Arduino IDE 中的觸摸引腳分配存在問題。GPIO 33 在分配中與 GPIO 32 交換。這意味著,如果要引用 GPIO 32,則應(yīng)在代碼中使用 T8;對(duì)于 GPIO 33,則使用 T9
T0 (GPIO 4)
T1 (GPIO 0)
T2 (GPIO 2)
T3 (GPIO 15)
T4 (GPIO 13)
T5 (GPIO 12)
T6 (GPIO 14)
T7 (GPIO 27)
T8 (GPIO 33)
T9 (GPIO 32)
下面是一個(gè)小實(shí)驗(yàn),我們要實(shí)現(xiàn)觸摸 GPIO4(對(duì)應(yīng) T0)口實(shí)現(xiàn)板上 LED 亮滅;
僅需使用一個(gè)公對(duì)母線,連接到 GPIO4 口上,然后用手指觸摸引出的公端口即可進(jìn)行測(cè)試;
使用 touchRead
方法,檢測(cè)對(duì)應(yīng) GPIO 口上的傳感器對(duì)應(yīng)返回?cái)?shù)值;
經(jīng)過測(cè)試,得出結(jié)果:當(dāng)手指觸摸時(shí)數(shù)值 10-20,手指移開后數(shù)值 70-80;所以可以得到以下檢測(cè)代碼
const int LED = 2;
void setup()
{
pinMode(LED,OUTPUT);
Serial.begin(115200);
delay(1000); // give me time to bring up serial monitor
Serial.println("ESP32 Touch Test");
}
void loop()
{
// touchRead(T0)直接讀取對(duì)應(yīng)帶觸摸傳感器GPIO的代號(hào)
Serial.println(touchRead(T0));
// touchRead(4)或者直接指定GPIO口
touchRead(4)<=20 ? digitalWrite(LED,HIGH) : digitalWrite(LED,LOW);
}
ADC 模數(shù)轉(zhuǎn)換器引腳
基本上半數(shù)以上引腳都支持模數(shù)轉(zhuǎn)換,具體引腳請(qǐng)看圖片,這里空間有限不一一指出
在使用 WIFI 時(shí)建議僅使用 ADC1 類型的引腳,因?yàn)?ADC2 類型的引腳大概率會(huì)出錯(cuò);
ADC 引腳用于將電壓值轉(zhuǎn)換為數(shù)值,但是實(shí)際情況不是線性的,ESP32 存在以下特殊情況:
- 0.0v-0.1v 時(shí),轉(zhuǎn)換數(shù)值均為 0
- 3.2v-3.3v 時(shí),轉(zhuǎn)換數(shù)值均為 4095
DAC 數(shù)模轉(zhuǎn)換器引腳
這個(gè)就比較少了,只有倆
DAC1 (GPIO25)
DAC2 (GPIO26)
RTC
用于睡眠喚醒以及時(shí)鐘操作
IIC
在某些板子上,SDA 線也可能標(biāo)記為 SDI,而 SCL 線標(biāo)記為 SCK。
ESP32 自帶兩個(gè) I2C 接口
GPIO 21 (SDA)
GPIO 22 (SCL)
PWM 脈沖寬度調(diào)制
所有具有 OUTPUT 特性的引腳均可使用 PWM
PWM
// 定義LED引腳的編號(hào)
const int ledPin = 2; // 15 對(duì)應(yīng)GPIO16
// 設(shè)置PWM屬性
const int freq = 5000; // PWM頻率
const int ledChannel = 0; // PWM通道
const int resolution = 8; // 分辨率
void setup(){
// 配置LED的PWM功能
ledcSetup(ledChannel, freq, resolution);
// 將PWM通道附加到要控制的GPIO引腳
ledcAttachPin(ledPin, ledChannel);
}
// 增加LED亮度
void loop(){
for(int dutyCycle = 0; dutyCycle <= 255; dutyCycle++){
// 使用PWM改變LED亮度
ledcWrite(ledChannel, dutyCycle);
delay(15);
}
// 減小LED亮度
for(int dutyCycle = 255; dutyCycle >= 0; dutyCycle--){
// 使用PWM改變LED亮度
ledcWrite(ledChannel, dutyCycle);
delay(15);
}
}
IIC
在此之前需要下載六個(gè)重要的外部庫(kù)到 arduino 里面才可以
Adafruit GFX Library
Adafruit SSD1306
Adafruit Sensor Calibration
Adafruit Sensor Lab
Adafruit Unified Sensor
檢測(cè)處于 IIC 線路上的設(shè)備
回顧一下兩個(gè) I2C 輸出引腳:GPIO 21 (SDA) ;GPIO 22 (SCL)
如果要是有 IIC,就必須導(dǎo)入頭文件 Wire.h
#include <Wire.h>
void setup() {
Wire.begin(); // 初始化I2C總線
Serial.begin(115200); // 初始化串口通信,波特率為115200
Serial.println("\nI2C掃描程序"); // 輸出提示信息
}
void loop() {
byte error, address; // 定義錯(cuò)誤碼和設(shè)備地址變量
int nDevices; // 定義設(shè)備數(shù)量變量
Serial.println("掃描中..."); // 輸出掃描提示信息
nDevices = 0; // 設(shè)備數(shù)量初始化為0
for(address = 1; address < 127; address++ ) { // 循環(huán)掃描從1到127的設(shè)備地址
Wire.beginTransmission(address); // 開始傳輸數(shù)據(jù)到設(shè)備地址
error = Wire.endTransmission(); // 結(jié)束傳輸并獲取錯(cuò)誤碼
if (error == 0) { // 如果錯(cuò)誤碼為0,表示找到了設(shè)備
Serial.print("在地址0x"); // 輸出設(shè)備地址提示信息
if (address<16) { // 如果設(shè)備地址小于16,前面補(bǔ)0
Serial.print("0");
}
Serial.println(address,HEX); // 輸出設(shè)備地址
nDevices++; // 設(shè)備數(shù)量加1
}
else if (error==4) { // 如果錯(cuò)誤碼為4,表示設(shè)備沒有響應(yīng)
Serial.print("在地址0x"); // 輸出設(shè)備地址提示信息
if (address<16) { // 如果設(shè)備地址小于16,前面補(bǔ)0
Serial.print("0");
}
Serial.println(address,HEX); // 輸出設(shè)備地址
Serial.println("發(fā)生未知錯(cuò)誤"); // 輸出錯(cuò)誤信息
}
}
if (nDevices == 0) { // 如果設(shè)備數(shù)量為0,表示沒有找到設(shè)備
Serial.println("沒有找到I2C設(shè)備\n"); // 輸出提示信息
}
else { // 否則表示找到了設(shè)備
Serial.println("掃描完成\n"); // 輸出提示信息
}
delay(5000); // 延時(shí)5秒
}
最簡(jiǎn) SSD1306 屏顯
#include <Wire.h> // 引用 Wire 庫(kù),用于 I2C 通訊
#include <Adafruit_GFX.h> // 引用 Adafruit_GFX 庫(kù),用于 OLED 顯示屏圖形操作
#include <Adafruit_SSD1306.h> // 引用 Adafruit_SSD1306 庫(kù),用于 OLED 顯示屏驅(qū)動(dòng)
#include <Adafruit_Sensor.h> // 引用 Adafruit_Sensor 庫(kù),用于傳感器操作
#define SCREEN_WIDTH 128 // OLED 顯示屏寬度,以像素為單位
#define SCREEN_HEIGHT 64 // OLED 顯示屏高度,以像素為單位
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); // 創(chuàng)建 OLED 顯示屏對(duì)象
void setup() {
Serial.begin(115200); // 初始化串口通訊,波特率為 115200
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // 初始化 OLED 顯示屏
Serial.println(F("SSD1306 allocation failed")); // 如果初始化失敗,打印錯(cuò)誤信息
for(;;); // 程序進(jìn)入死循環(huán)
}
delay(2000); // 延時(shí) 2 秒
display.clearDisplay(); // 清空 OLED 顯示屏
display.setTextColor(WHITE); // 設(shè)置 OLED 顯示屏文本顏色為白色
}
void loop() {
display.clearDisplay(); // 清空 OLED 顯示屏
display.setTextSize(2); // 設(shè)置文本字體大小為 2
display.setCursor(0,0); // 設(shè)置文本顯示位置為 (0, 0)
display.print("Hello!"); // 顯示 "Hello!" 文本
display.display(); // 將圖像顯示在 OLED 顯示屏上
delay(1000); // 延時(shí) 1 秒
}
SPI
SPI 是一種主從交換的通信方式,一個(gè) master 可以具有 n 個(gè) slave
簡(jiǎn)介
ESP32-DEVKIT-V1 標(biāo)準(zhǔn)開發(fā)板,定義了 2 組 SPI 引腳,下面所有的分析都按照該開發(fā)板解釋
(如果對(duì)引腳不熟悉的,可以直接移步頂部看引腳定義圖)
VSPI:主 SPI 引腳;
HSPI:副 SPI 引腳,默認(rèn)情況不會(huì)被啟用;
VSPI
對(duì)應(yīng)的四個(gè)引腳,這些引腳都可直接使用字母表達(dá),無需記住對(duì)應(yīng)的引腳索引
MOSI: 23
MISO: 19
SCK: 18
NSS: 5
HSPI
對(duì)應(yīng)的四個(gè)引腳,如果想要字母表達(dá),請(qǐng)使用 define 宏定義實(shí)現(xiàn)
MOSI: 13
MISO: 12
SCK: 14
NSS: 15
檢測(cè)開發(fā)板的 SPI 引腳
由于不同的開發(fā)板具有不同的 SPI 引腳,我們?cè)谑褂弥靶枰刃写_定 SCK\MOSI\MISO\NSS
四個(gè)引腳對(duì)應(yīng)的端口
#include <SPI.h> // SPI頭文件在esp32庫(kù)中自帶,無需新下載
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
delay(1000); // 延遲1s,為了讓我們?cè)诖诒O(jiān)視器上看到
Serial.print("MOSI: ");
Serial.println(MOSI);
Serial.print("MISO: ");
Serial.println(MISO);
Serial.print("SCK: ");
Serial.println(SCK);
Serial.print("SS: ");
Serial.println(SS);
}
void loop() {
// put your main code here, to run repeatedly:
}
我的監(jiān)測(cè)結(jié)果如下(開發(fā)板:ESP32-DEVKIT-V1
):
MOSI: 23
MISO: 19
SCK: 18
SS: 5
雙 SPI
ESP32 自帶兩組 SPI,你可以通過以下方法使用這兩種 SPI
自定義一個(gè) SPI 或者使用現(xiàn)存的 SPI,主要分為以下幾步
- define 對(duì)應(yīng)的引腳作為 SPI 的四個(gè)引腳
- 定義 SPIClass 對(duì)應(yīng)變量,首先給予空引用
- new 一個(gè) SPIClass 對(duì)象
- 開始數(shù)據(jù)讀寫操作
#include <SPI.h>
// ifdef是為了放置開發(fā)板不同導(dǎo)致引腳不同,適配多樣SPI
#ifdef ALTERNATE_PINS
#define VSPI_MISO 2
#define VSPI_MOSI 4
#define VSPI_SCLK 0
#define VSPI_SS 33
#define HSPI_MISO 26
#define HSPI_MOSI 27
#define HSPI_SCLK 25
#define HSPI_SS 32
#else
// 主VSPI可以使用字母直接表示
#define VSPI_MISO MISO
#define VSPI_MOSI MOSI
#define VSPI_SCLK SCK
#define VSPI_SS SS
// 副HSPI只能使用GPIO口數(shù)字表示
#define HSPI_MISO 12
#define HSPI_MOSI 13
#define HSPI_SCLK 14
#define HSPI_SS 15
#endif
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
#define VSPI FSPI
#endif
static const int spiClk = 1000000; // 1 MHz
// 第一步:定義SPI對(duì)象,首先給予空引用
SPIClass * vspi = NULL;
SPIClass * hspi = NULL;
void setup() {
// 初始化對(duì)應(yīng)VSPI接口,得到SPI對(duì)象
vspi = new SPIClass(VSPI);
hspi = new SPIClass(HSPI);
#ifndef ALTERNATE_PINS
//initialise vspi with default pins
//SCLK = 18, MISO = 19, MOSI = 23, SS = 5
vspi->begin();
#else
//alternatively route through GPIO pins of your choice
vspi->begin(VSPI_SCLK, VSPI_MISO, VSPI_MOSI, VSPI_SS); //SCLK, MISO, MOSI, SS
#endif
#ifndef ALTERNATE_PINS
//initialise hspi with default pins
//SCLK = 14, MISO = 12, MOSI = 13, SS = 15
hspi->begin();
#else
//alternatively route through GPIO pins
hspi->begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI, HSPI_SS); //SCLK, MISO, MOSI, SS
#endif
//set up slave select pins as outputs as the Arduino API
//doesn't handle automatically pulling SS low
pinMode(vspi->pinSS(), OUTPUT); //VSPI SS
pinMode(hspi->pinSS(), OUTPUT); //HSPI SS
}
// the loop function runs over and over again until power down or reset
void loop() {
//use the SPI buses
spiCommand(vspi, 0b01010101); // junk data to illustrate usage
spiCommand(hspi, 0b11001100);
delay(100);
}
// SPI數(shù)據(jù)傳輸流程外部函數(shù)
void spiCommand(SPIClass *spi, byte data) {
// 設(shè)置SPI對(duì)應(yīng)模式
spi->beginTransaction(SPISettings(spiClk, MSBFIRST, SPI_MODE0));
digitalWrite(spi->pinSS(), LOW); // NSS低電平,數(shù)據(jù)開始傳輸
spi->transfer(data);
digitalWrite(spi->pinSS(), HIGH); // NSS高電平,數(shù)據(jù)截止傳輸
spi->endTransaction();
}
中斷處理
中斷步驟
attachInterrupt 用于關(guān)聯(lián)中斷觸發(fā)端口以及中斷觸發(fā)函數(shù)
以上函數(shù)的第三個(gè)參數(shù)分別由以下幾個(gè)待選常量組成
- HIGH 高電平觸發(fā)
- LOW 低電平觸發(fā)
- CHANGE 當(dāng)電平有高向低或者有低向高轉(zhuǎn)換時(shí)觸發(fā)
- RISING 電平由低到高轉(zhuǎn)變時(shí)觸發(fā)
- FALLING 電平由高到低轉(zhuǎn)變時(shí)觸發(fā)
// 欲操縱的GPIO
const int led = 2;
// 中斷檢測(cè)的GPIO
const int motionSensor = 4;
// IRAM_ATTR專用表示中斷類型函數(shù)
// 定義觸發(fā)中斷后執(zhí)行的函數(shù)
void IRAM_ATTR detectsTouch(){
digitalWrite(led,HIGH);
delay(3000);
digitalWrite(led,LOW);
}
void setup() {
// LED設(shè)置默認(rèn)低電平,即關(guān)閉
pinMode(led,OUTPUT);
digitalWrite(led,LOW);
// 檢測(cè)中斷的GPIO必須設(shè)置為輸入INPUT類型的,通過對(duì)輸入電平變化檢測(cè)來判斷是否觸發(fā)中斷
pinMode(motionSensor,INPUT_PULLUP);
// attachInterrupt關(guān)聯(lián)中斷檢測(cè)GPIO以及對(duì)應(yīng)的中斷函數(shù)
// 參數(shù)一:中斷檢測(cè)引腳,digitalPinToInterrupt將對(duì)應(yīng)GPIO輸入信號(hào)轉(zhuǎn)化為中斷信號(hào)
// 參數(shù)二:中斷觸發(fā)函數(shù)
// 參數(shù)三:何時(shí)觸發(fā)中斷
attachInterrupt(digitalPinToInterrupt(motionSensor),detectsTouch,RISING);
}
void loop() {
// 在這里寫針對(duì)中斷觸發(fā)的函數(shù)
}
定時(shí)器
const int ledPin = 2; // LED引腳號(hào)
int ledState = LOW; // ledState用于設(shè)置LED
// 通常,應(yīng)使用“unsigned long”存儲(chǔ)時(shí)間變量
// 值很快就會(huì)變得太大,超出int所能存儲(chǔ)的范圍
unsigned long previousMillis = 0; // 將存儲(chǔ)上次更新LED的時(shí)間
const long interval = 1000; // 閃爍的時(shí)間間隔(毫秒)
void setup() {
// 將數(shù)字引腳設(shè)為輸出:
pinMode(ledPin, OUTPUT);
}
void loop() {
// 在這里放需要一直運(yùn)行的代碼。
// 檢查是否到了閃爍LED的時(shí)間;即,如果
// 當(dāng)前時(shí)間與上次閃爍LED的時(shí)間之差大于你想要的
// 閃爍LED的時(shí)間間隔。
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// 保存上次閃爍LED的時(shí)間
previousMillis = currentMillis;
// 如果LED處于關(guān)閉狀態(tài),則打開它,反之亦然:
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
// 使用變量ledState設(shè)置LED的狀態(tài):
digitalWrite(ledPin, ledState);
}
}
深度睡眠
定時(shí)器睡眠喚醒
ESP32 自帶一個(gè) RTC 數(shù)據(jù)存儲(chǔ),他是一個(gè) SRAM,只有當(dāng)我們按下 EN 鍵后才會(huì)清空存儲(chǔ)內(nèi)容
為任意變量前加上 RTC_DATA_ATTR
修飾即可將該變量存儲(chǔ)到 RTC
如下面的 RTC_DATA_ATTR int bootCount
uS_TO_S_FACTOR
定義睡眠的單位時(shí)間,而 TIME_TO_SLEEP
定義需要睡眠的總時(shí)長(zhǎng)(乘以單位時(shí)間)
#define uS_TO_S_FACTOR 1000000ULL /* 微秒到秒的轉(zhuǎn)換系數(shù) */
#define TIME_TO_SLEEP 5 /* ESP32將進(jìn)入睡眠狀態(tài)的時(shí)間(秒) */
RTC_DATA_ATTR int bootCount = 0; // 存儲(chǔ)該變量到RTC內(nèi)部
/*
打印ESP32被喚醒的原因
*/
void print_wakeup_reason(){
esp_sleep_wakeup_cause_t wakeup_reason;
wakeup_reason = esp_sleep_get_wakeup_cause();
switch(wakeup_reason)
{
case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("RTC_IO引腳的外部信號(hào)喚醒"); break;
case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("RTC_CNTL引腳的外部信號(hào)喚醒"); break;
case ESP_SLEEP_WAKEUP_TIMER : Serial.println("定時(shí)器喚醒"); break;
case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("觸摸板喚醒"); break;
case ESP_SLEEP_WAKEUP_ULP : Serial.println("ULP程序喚醒"); break;
default : Serial.printf("喚醒不是由深度睡眠引起的:%d\n",wakeup_reason); break;
}
}
void setup(){
Serial.begin(115200);
delay(1000); //打開串口監(jiān)視器需要一些時(shí)間
//每次重啟都增加引導(dǎo)次數(shù)并打印
++bootCount;
Serial.println("引導(dǎo)次數(shù):" + String(bootCount));
//打印ESP32的喚醒原因
print_wakeup_reason();
/*
首先配置喚醒源
我們?cè)O(shè)置ESP32每5秒喚醒一次
1*1000000us = 1s
*/
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("設(shè)置ESP32每" + String(TIME_TO_SLEEP) +
"秒進(jìn)入睡眠狀態(tài)");
Serial.println("現(xiàn)在進(jìn)入睡眠狀態(tài)");
Serial.flush(); // 清空串口通信緩沖區(qū)
// 引導(dǎo)進(jìn)入深入睡眠狀態(tài)
esp_deep_sleep_start();
Serial.println("這將永遠(yuǎn)不會(huì)被打印");
}
void loop(){
//這個(gè)函數(shù)不會(huì)被調(diào)用
}
觸摸板喚醒
下文實(shí)現(xiàn)用手按壓 GPIO4 引腳,產(chǎn)生電荷差異,喚醒
touchAttachInterrupt
關(guān)聯(lián)觸摸按鍵與中斷esp_sleep_enable_touchpad_wakeup
開啟 touch 喚醒模式esp_deep_sleep_start
令 esp32 進(jìn)入睡眠模式
#define THRE 30
// 存儲(chǔ)bootCount的值
RTC_DATA_ATTR int bootCount = 0;
// 喚醒狀態(tài),touch引腳信息
touch_pad_t touchPin;
void print_wakeup_touched(){
touchPin = esp_sleep_get_touchpad_wakeup_status();
switch(touchPin){
case 0: Serial.println("出沒了GPIO4");break;
default: Serial.println("無法識(shí)別你摁到了什么");break;
}
}
void touchCallback(){
Serial.println("您點(diǎn)擊了");
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
delay(1000);
++bootCount;
Serial.println("看看這是第幾次了:"+String(bootCount));
print_wakeup_touched();
touchAttachInterrupt(T0,touchCallback,THRE);
esp_sleep_enable_touchpad_wakeup();
Serial.println("又要睡著了");
esp_deep_sleep_start();
}
void loop() {
// put your main code here, to run repeatedly:
}
外部喚醒
外部喚醒重要特性文章來源:http://www.zghlxwxcb.cn/news/detail-503490.html
- 您只能將 RTC GPIO 用作外部喚醒;
- 您可以使用兩種不同的方法:ext0 和 ext1;
- ext0 允許您使用單個(gè) GPIO 引腳喚醒 ESP32;
- ext1 允許您使用多個(gè) GPIO 引腳喚醒 ESP32。
限于時(shí)間問題,更多更新內(nèi)容將會(huì)在近期補(bǔ)全~文章來源地址http://www.zghlxwxcb.cn/news/detail-503490.html
到了這里,關(guān)于[Arduino] ESP32開發(fā) - 基礎(chǔ)入門與原理分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!