文章目录
- 1. 综述:
- 1.1 资料获取
- 1.2 Arduino 简介
- 1.2.1 Arduino 开发环境搭建
- 1.2.2 安装 arduino-esp32 库
- 1.2.3 完成开发板配置
- 1.2.4 工程内全局搜索
- 1.3 ESP32简介
- 1.3.1 ESP32-S3
- 1.3.2 S3 系列模组
- 1.4 boot 启动流程
- 1.5 Arduino-esp32 库
- 3. GPIO
- 3.1 核心代码
- 4. 中断
- 4.1 核心代码
- 4.2 应用例程
- 5. 串口UART
- 5.1 核心代码
- 6. 定时器及其中断
- 6.1 核心代码
- 6.2 应用例程
- 6.3 PWM
- 6.3.1 核心代码
- 6.3.2 应用例程
- 7. 看门狗
- 6.2 应用例程
- 8. IIC
- 8.1 EEPROM
- 8.1.1 核心代码
- 8.1.2 24CXX 应用例程
- 8.1.3 IO 扩展芯片XL9555驱动例程
- 9. RTC - 实时时钟
- 10. WIFI
- 10.1 WIFI SCAN 实验
- 10.1.1 例程
- 10.2 WIFI WEBSERVER 实验
- 10.2.1 互联网络和 TCP/IP 协议
- 10.2.2 IP 地址和端口号
- 10.2.3 客户端-服务器模式(C/S)
- 10.2.3 ESP32-S3 Web 服务器
- 10.2.4 HTML 基础
- 10.2.5 例程
- 10.3 WIFI_CLIENT 实验
- 11. 蓝牙
- 11.1 蓝牙协议
- 11.2 工作状态和工作角色介绍
- 11.1 BLE_SCAN 实验
参考:(正点原子)DNESP32S3 V1.0 硬件参考手册、(正点原子)DNESP32S3使用指南-Arduino版本_V1.1、ESP32-S3 技术规格书、(正点原子)标准例程-Arduino版
1. 综述:
1.1 资料获取
在Arduino 官网中找到官方开发板资料和Arduino 函数说明:
- 打开Arduino 官网 ;
- 点击Documentation;
- 点击Tutorials;
- 搜索关键字“ESP32”
乐鑫公司提供的 arduino_esp32库的资料:
https://github/espressif/arduino-esp32
这是乐鑫公司专门为了 ESP32 能在 Arduino IDE 软件上运行专门编写的库包。后续在 Arduino IDE 上开发 ESP32 是依靠该库包,并不是 Arduino 官方提供的 Arduino ESP32 Boards 软件包
正点原子的学习资料:
http://www.openedv/docs/boards/esp32/ATK-DNESP32S3.html
1.2 Arduino 简介
Arduino(Arduino Integrated Development Environment 、集成开发环境)是一个开源电子原型平台,由硬件和软件组成,旨在让任何人都能轻松创建交互式电子项目;
简单来说,只要是搭载 Arduino 支持芯片的开发板都可以称为 Arduino 开发板。目前支持 Arduino 开发的芯片有很多,比如 Mega 系列芯片(Mega328p/Mega2560/Mega32u4 等)、STM32 系列芯片(STM32F0/F1/F2/F3/F4/F7/H7 等)、ESP 系列芯片(esp32/esp32s2/esp32c3/esp32s3 等)以及树莓派系列芯片等。
这里的实质就是有每个系列芯片对应要有一个 Arduino 库,比如乐鑫官方的arduino-esp32 库,ST 的 stm32duino 库,只要在 Arduino 安装这种芯片库便可以使用 Arduino 的语法在 Arduino IDE 上对芯片进行开发。
- –
- Arduino IDE 可以在 Windows、Mac OS 和 Linux 三大主流操作系统上运行;
- Arduino 使用 C/C++语言编写程序,以下是在不同框架内开发的例程:
- 在使用 ESP-IDF(乐鑫科技开发的官方物联网开发框架)对 ESP32 进行开发中,将一个 I/O 口设置为输出高电平状态需要以下操作:
gpio_set_direction(GPIO_NUM_1, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_1, PIN_SET);
- 在 Arduino 中,采用的是如下代码去设置 IO 口输出高电平:
PinMode(1, OUTPUT);
digitalWrite(1, HIGH);
- 在 Arduino 程序的应用层中,是没有 main 函数的,这跟传统的 C/C++程序结构有所不同。在进行 Arduino 开发时一般不直接操作 main 函数,而使用 setup()和 loop()这两个函数:
void setup() {
// 在这里填写 setup 函数代码,它只会运行一次
}
void loop() {
// 在这里编写 loop 函数代码,它会不断重复运行
}
1.2.1 Arduino 开发环境搭建
ESP32 的开发方式主要有三种:ESP-IDF、Arduino 和 MicroPython:
- ESP-IDF:ESP-IDF 是乐鑫官方推出的开发框架,专门为 ESP32 和其他一些 ESP 系列芯片设计。它提供了一套完整的开发工具和库,可以帮助开发者快速地开发和调试 ESP32 应用程序。ESP-IDF 支持 C/C++语言,并提供了一套完整的 API,可以控制 ESP32 的各种功能和外设。此外,ESP-IDF 还提供了一个在线编译器和调试器,可以让开发者在云端进行开发和调试。
- Arduino:Arduino 是一种流行的开源电子原型平台,包括一系列的开发板和开发环境。Arduino 提供了一种基于 C/C++的语言,使得开发者可以更容易地控制和编程 ESP32。Arduino开发环境还提供了大量的库和函数,可以帮助开发者快速地构建和测试他们的代码。Arduino 还支持图形化编程,使得初学者和非专业人士也可以轻松地进行开发。本教程选择此开发方式。
- MicroPython:MicroPython 是一种精简的 Python 3 语言,可以运行在 ESP32 和其他一些微控制器上。它提供了一种简单的方式来编程和控制 ESP32,而且由于 Python 是一种高级语言,它使得开发过程相对快速和简单。开发者可以使用 MicroPython 进行快速原型设计和开发,并且由于 Python 是一种解释型语言,所以可以直接在 ESP32 上运行代码,无需进行编译。
-
软件下载与安装:到Arduino 官网下载并安装软件;
-
配置首选项:文件 - 首选项:
1.2.2 安装 arduino-esp32 库
- 到https://github/arduino/Arduino/wiki/Unofficial-list-of-3rd-party-boards-support-urls搜索ESP32,找到如下链接:
- 在IDE 中的首选项中的“其他开发板管理器地址”中添加:
- 此时在本机路径“C:\Users\用户名\AppData\Local\Arduino15”中会多出一个文件“package_esp32_index.json”;
- 将12个离线库文件复制粘贴到路径“C:\Users\用户名\AppData\Local\Arduino15\staging\packages”
- 回到IDE,在侧边栏开发板管理器中,搜索“ESP32”,选择对应版本点安装,即可完成本地库安装;(注意版本要和本地库版本一致)
如果网络允许,可直接点击上图“安装”,完成在线安装;
- 安装完成后,在对应路径即可看到esp32 的库文件夹:
在开发板选项中,也多了esp32的选项;
1.2.3 完成开发板配置
- 选择开发板:工具 - 开发板 - esp32 - ESP32S3 Dev Module;
- 此后,工具栏则多了很多配置项,配置完成后如下图:
配置参数说明可查看该网站https://www.corvin/3258.html
1.2.4 工程内全局搜索
1.3 ESP32简介
ESP32是乐鑫公司开发的物联网芯片:
ESP32官方选型页面
1.3.1 ESP32-S3
ESP32-S3 是一款由乐鑫公司开发的物联网芯片:
- 架构和性能:ESP32-S3 采用 Xtensa® LX7 CPU,这是一个哈佛结构的双核系统。它具有独立的指令总线和数据总线,所有的内部存储器、外部存储器以及外设都分布在这两条总线上。这种架构使得 CPU 可以同时读取指令和数据,从而提高了处理速度。
- 存储:ESP32-S3 具有丰富的存储空间。它内部有 384 KB 的内部 ROM,512 KB 的内部SRAM,以及 8 KB 的 RTC 快速存储器和 8 KB 的 RTC 慢速存储器。此外,它还支持最大 1 GB的片外 FLASH 和最大 1 GB 的片外 RAM。
- 外设:ESP32-S3 具有许多外设,总计有 45 个模块/外设。其中 11 个具有 GDMA(Generic DMA)功能,可以用来进行数据块的传输,减轻 CPU 的负担,提高整体性能。
- 通信:ESP32-S3 同时支持 WIFI 和蓝牙功能。在 2.4GHz 频带支持 20MHz 和 40MHz 频宽。
- 向量指令:ESP32-S3 增加了用于加速神经网络计算和信号处理等工作的向量指令。这些向量指令可以大大提高芯片在 AI 方面的计算速度和效率。
ESP32-S3 资源简介:
乐鑫 S3 系列型号包括 ESP32-S3、ESP32-S3R2、ESP32-S3R8 和 ESP32-S3FN8 等,不同型号的 MCU 有不同的应用场景,这些型号的命名规则如下:
以ESP32-S3FH4R2这一芯片为例:
1.3.2 S3 系列模组
除了 S3 系列的芯片之外,乐鑫还推出了 S3 系列的模组,它是 S3 系列芯片的简易系统。乐鑫 S3 系列模组是基于 S3 系列芯片的子系统,它已经设计好了外围电路,简化了开发过程,让开发者可以更快速地使用 S3 系列芯片进行开发。
乐鑫推出了 ESP32-S3-WROOM-1 和 ESP32-S3-WROOM-1U 两款通用型 Wi-Fi+低功耗蓝牙MCU 模组,如下图所示,它们搭载 ESP32-S3 系列芯片。除具有丰富的外设接口外,模组还拥有强大的神经网络运算能力和信号处理能力,适用于 AIoT 领域的多种应用场景,例如唤醒词检测和语音命令识别、人脸检测和识别、智能家居、智能家电、智能控制面板、智能扬声器等。
可见,ESP32-S3-WROOM-1 采用 PCB 板载天线,而 ESP32-S3-WROOM-1U 采用连接器连接外部天线。两款模组均有多种芯片型号可供选择,具体见下表所示:
1.4 boot 启动流程
从宏观上,该启动流程可分为如下 3 个步骤:
- 一级引导程序,它被固化在 ESP32-S3 内部的 ROM 中,它会从 flash 的 0x00 处地址加载二级引导程序至 RAM 中。
- 二级引导程序从 flash 中加载分区表和主程序镜像至内存中,主程序中包含了 RAM 段和通过 flash 高速缓存映射的只读段。
- 应用程序启动阶段运行,这时第二个 CPU 和 freeRTOS 的调度器启动,最后进入app_main 函数执行用户代码。
1.5 Arduino-esp32 库
Arduino-esp32 库是一个在 Arduino 平台上开发 ESP32 的插件,它为 Arduino 环境下的ESP32 芯片提供了支持。它允许使用熟悉的 Arduino 函数和库编写代码,并直接在 ESP32 上运行。
Arduino-esp32 库支持对 ESP32、ESP32-S2、ESP32-S3、ESP32-C3、ESP32-C6 和 ESP32-H2进行开发,还提供了很多基础库:
3. GPIO
对于 ESP32-S3 模组引出的 IO,每个管脚都可用作一个通用 IO,或连接一个内部外设信号;
GPIO 口输出来的信号是以 0,1 表示的不连续数字信号。在 Arduino 中数字信号用高低电平来表示,高电平为数字信号 1,低电平为数字信号 0;Arduino 输出的低电平为 0V,输出的高电平为当前 Arduino 的工作电压,对于 ESP32-S3 模组的工作电压为 3.3V,则其高电平为 3.3V;
3.1 核心代码
新建工程后,选择对应串口和对应芯片:
- 硬件上使用IO1控制LED,IO0控制按钮;
- 新建一个工程,新建“led.h”头文件,并编写:
#define LED_PIN 1
void led_init(void)
{
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
}
- 新建一个工程,新建“key.h”头文件,并编写:
#define KEY_PIN 0
#define KEY digitalRead(KEY_PIN) /* 读取 KEY 引脚的状态 */
void key_init(void)
{
/* 结合原理图设计,按键没有按下时,KEY 引脚检测到的是高电平 */
pinMode(KEY_PIN, INPUT_PULLUP); /* 设置 key 引脚为上拉输入模式 */
}
- 在工程文件中编写:
#include "led.h"
#include "key.h"
#define LED(x) digitalWrite(LED_PIN, x)
#define LED_TOGGLE() digitalWrite(LED_PIN, !digitalRead(LED_PIN))
void setup() {
// put your setup code here, to run once:
led_init(); /* LED 初始化 */
key_init(); /* KEY 初始化 */
}
void loop() {
if (KEY == 0) /* 读取 KEY 状态,如果按下 KEY */
{
delay(10);
if (KEY == 0)
{
LED(0); /* LED 引脚输出接低电平,点亮 */
}
}
else /* 读取 KEY 状态,如果 KEY 没有按下 */
{
LED(1); /* LED 引脚输出接高电平,熄灭 */
}
}
- 点击IDE 上传按钮,编译通过后,即烧录到ESP32中,观察效果;
4. 中断
外部中断是由外部设备发起请求的中断。
每个中断对应一个中断程序,中断程序可以看作一段独立于主程序之外的程序,也称为中断回调函数。
当中断被触发时,控制器会暂停当前正在运行的主程序,而跳转去运行中断程序。当中断程序运行完毕,则返回到先前主程序暂停的位置,继续运行主程序,如此便可达到实时响应处理事件的效果。
ESP32-S3 的中断:ESP32-S3 有 99 个外部中断源,但是 CPU0 或 CPU1 只能够处理 32 个中断。ESP32-S3 将外部中断映射到 CPU0 或 CPU1 中断就需要用到中断矩阵。
ESP32-S3 中断矩阵会将任一外部中断源单独分配到双核 CPU 的任一外部中断上,以便在外设中断信号产生后,及时通知 CPU0 或 CPU1 进行处理。
ESP32-S3 的中断模式:
4.1 核心代码
attachInterrupt
:该函数功能是指定中断引脚,并对中断引脚进行初始化设置;
void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode);
/*
参数 pin 为要设置中断触发输入的引脚,ESP32-S3 所有引脚均可以配置为外部中断引脚。
参数 handler 为中断回调函数,当引脚中断触发时,会终止当前运行的程序,转而执行该程序。
参数 mode 为 5 种中断触发模式
*/
// 很多时候,attachInterrupt 函数的使用也采用以下方式。
attachInterrupt(digitalPinToInterrupt(pin), handler, mode);
/* 在一些 Arduino 开发板中比如 Arduino Uno、Leonardo,只有 2 和 3引脚有外部中断功能,而中断编号对应为 0 和 1。在 Arduino Uno 开发板中,attachInterrupt 函数第一个参数为中断编号,第二个参数为中断回调函数,第三个参数为触发模式,所以为了避免硬件引脚和中断编号,直接通过 digitalPinToInterrupt 函数解决。*/
detachInterrupt
:关闭外部中断:
void detachInterrupt(uint8_t pin);
/*
参数 pin 为已经设置中断触发输入的引脚。
*/
4.2 应用例程
exti.ino
:
#include "led.h"
#include "exti.h"
/**
* @brief 当程序开始执行时,将调用setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
led_init(); /* LED初始化 */
exti_init(); /* 外部中断引脚初始化 */
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
LED(led_state); /* 灯的亮灭由led_state值决定,led_state变化在key_isr函数中实现 */
}
exti.cpp
:
#include "exti.h"
#include "key.h"
uint8_t led_state = 0; /* 决定灯亮灭状态变量,初始值为0 */
/**
* @brief 初始化外部中断相关IO口
* @param 无
* @retval 无
*/
void exti_init(void)
{
key_init(); /* KEY初始化 */
attachInterrupt(digitalPinToInterrupt(KEY_INT_PIN), key_isr, FALLING); /* 设置KEY引脚为中断引脚,下降沿触发 */
}
/**
* @brief KEY外部中断回调函数
* @param 无
* @retval 无
*/
void key_isr(void)
{
delay(10);
if (KEY == 0)
{
led_state =! led_state; /* 两种情况:从0变1,从1变为0 */
}
}
exti.h
:
#ifndef __EXTI_H
#define __EXTI_H
#include "Arduino.h"
extern uint8_t led_state;
/* 引脚定义 */
#define KEY_INT_PIN 0 /* 外部中断引脚GPIO0 */
/* 函数声明 */
void exti_init(void); /* 外部中断初始化函数 */
void key_isr(void); /* KEY外部中断回调函数 */
#endif
5. 串口UART
由于大部分 Arduino 开发板都没有调试功能,所以在开发中,最常用的就是串口打印信息到串口助手去调试程序。
在 ESP32-S3 中,是有 3 个 UART 控制器,即 UART0、UART1 和 UART2。3 个 UART 端口对应的引脚如下表所示:
上表带有具体 IO 口是默认使用 IO,但是 ESP32-S3 有 IO MUX,所以是可以选择任意 GPIO管脚作为 UART 的引脚。使用 Arduino,调用串口初始化函数时,可以指定发送引脚和接收引脚。
IDE 右上角的串口监视器可进行开发板串口数据收发:
选好波特率后,可在消息框内进行数据发送:
5.1 核心代码
begin
:初始化串口功能;
void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert, unsigned long timeout_ms,uint8_t rxfifo_full_thrhd);
参数说明如下:
通常情况下,通过如下语句便可以初始化串口 0,默认使用 IO43 作为串口 0 的发送引脚,使用 IO44 作为串口 0 的接收引脚,8 位数据位,无奇偶检验位,1 位停止位;
Serial.begin(115200);
Serial1.begin(115200); // 初始化串口1
Serial2.begin(115200); // 初始化串口2
串口发送:
Serial.print(val);
:其中参数 val 是要输出的数据,各种类型数据都可以;Serial.println(val);
:输出完指定数据后,再多输出回车换行符;Serial.printf(char * format, ...);
:输出一个字符串,或者按指定格式和数据类型输出若干变量的值,函数返回值为输出字符的个数;在Serial.printf()
函数使用中,会涉及到比较多的格式字符“%d、%c、%f”,“\n、\r”等为转义字符;
建议统一使用
Serial.printf(char * format, ...);
串口接收:
Serial.read();
:调用该函数,每次都会返回 1 字节数据,该返回值便是当前串口读取到的数据;Serial.available();
:通常在使用串口读取数据时,需要搭配使用该函数;该函数的返回值为当前缓冲区中接收到的数据字节数。通常该函数会搭配 if 或者 while 语句来使用,先检测缓冲区中是否有可读数据,如果有数据,再读取;如果没有数据,跳过读取或等待读取,如下所示:
while (Serial.available() > 0)
{
char c = Serial.read();
Serial.print(c);
}
6. 定时器及其中断
ESP32-S3 有通用定时器、系统定时器和看门狗定时器;
ESP32-S3 有两个硬件定时器组,定时器组 0 和定时器组 1,每组有两个硬件通用定时器,所以总共是有 4 个硬件通用定时器。它们都是基于 16 位预分频器和 54 位可自动重载的向上/向下计数器实现定时功能;
ESP32-S3 的计数频率为 80MHz,假如对 16 位预分频器设置预分频系数为 80,那么可得到1MHz 的计数信号,每个计数信号的周期为 1us,即每个计数单位为 1us。基于要设定的时间,就可以对计数器进行设置;
如:要定时 10ms,而每个计数周期为 1us,这里得计算 10ms需要多少个这样的 1us 周期:10ms/ 1us = 10000,计数器就需要设置为 10000,实现 10ms 定时;当设置好定时器的预分频器以及计数器以及开启定时器,这时候定时开始,当计数值达到9999 时,即到达设定时间,就会跳进中断回调函数中执行,执行完毕再回到主程序中运行
6.1 核心代码
timerBegin
:初始化一个定时器对象;
hw_timer_t * timerBegin(uint8_t num, uint16_t divider, bool countUp);
参数 num 为定时器编号,0 到 3,对应 4 个硬件通用定时器;
参数 divider 为预分频系数;
参数 countUp 为计数器计数方向标志,true:向上计数;false:向下计数
返回值:定时器结构体指针
timerAttachInterrupt
:为目标定时器绑定一个中断回调函数,配置定时器中断。
void timerAttachInterrupt(hw_timer_t *timer, void (*fn)(), bool edge);
参数*timer 为已初始化的目标定时器结构体指针;
参数(*fn)()为定时器中断回调函数的函数指针;
参数 edge 为中断触发类型,true:边沿触发,false:电平触发;
timerAlarmWrite
:为目标定时器设置间隔定时参数和是否自动重装载。
void timerAlarmWrite(hw_timer_t *timer, uint64_t alarm_value, bool autoreload);
参数*timer 为已初始化的目标定时器结构体指针;
参数 alarm_value 为最大计数值。向上计数到达该数值溢出,触发中断;
参数 autoreload 为定时器在产生中断时是否重新加载的标志。true:自动加载,循环间隔定时,false:不自动加载,只进行一次间隔定时。
timerAlarmEnable
:使能定时器,开始间隔定时
void timerAlarmEnable(hw_timer_t *timer);
参数*timer 为已初始化的目标定时器结构体指针;
6.2 应用例程
timer_it_ino
:
#include "tim.h"
#include "led.h"
/**
* @brief 当程序开始执行时,将调用setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
led_init(); /* LED初始化 */
timx_int_init(5000, 8000); /* 定时器初始化,定时时间为500ms */
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
/* 死循环,不做事情,等待定时器中断触发 */
delay(1000);
}
tim.cpp
:
#include "tim.h"
#include "led.h"
hw_timer_t *timer = NULL;
/**
* @brief 定时器TIMX定时中断初始化函数
* @note
* 定时器的时钟来自APB,而APB为80M
* 所以定时器时钟 = (80/psc)Mhz, 单位时间为 1 / (80 / psc) = x us
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值
* @param psc: 时钟预分频数
* @retval 无
*/
void timx_int_init(uint16_t arr, uint16_t psc)
{
timer = timerBegin(TIMx_INT, psc, true); /* 初始化定时器0 */
timerAlarmWrite(timer, arr, true); /* 设置中断时间 */
timerAttachInterrupt(timer, &TIMx_ISR, true); /* 配置定时器中断回调函数 */
timerAlarmEnable(timer); /* 使能定时器中断 */
}
/**
* @brief 定时器TIMX中断回调函数
* @param 无
* @retval 无
*/
void TIMx_ISR(void)
{
LED_TOGGLE();
}
tim.h
:
#ifndef __TIM_H
#define __TIM_H
#include "Arduino.h"
/* 定时器中断定义 */
#define TIMx_INT 0
#define TIMx_ISR tim0_ISR
/* 函数声明 */
void timx_int_init(uint16_t arr, uint16_t psc); /* 定时器中断初始化函数 */
void TIMx_ISR(void); /* 定时器中断回调函数 */
#endif
6.3 PWM
PWM(Pulse Width Modulation),简称脉宽调制,是一种将模拟信号变为脉冲信号的技术。
PWM 周期是一个 PWM 信号的时间:脉宽时间是指高电平时间;脉宽时间占 PWM 周期的比例就是占空比。
在使用 PWM 控制 LED 时,亮 1s 后灭 1s,往复循环,就可以看到 LED 在闪烁。如果把这个周期缩小到 200ms,亮 100ms 后灭 100ms,往复循环,就可以看到 LED 灯在高频闪烁。继续把这个周期持续缩小,总有一个临界值使人眼分辨不出 LED 在闪烁,此时 LED 的亮度处于灭与亮之间亮度的中间值,达到了 1/2 亮度。
- LED_PWM 控制器:ESP32-S3 的 LED PWM 控制器,简写为 LEDC,用于生成控制 LED 的脉冲宽度调制信号。LED PWM 控制器具有八个独立的 PWM 生成器(即八个通道)。每个 PWM 生成器会从四个通用定时器中选择一个,以该定时器的计数值作为基准生成 PWM 信号。
为了实现 PWM 输出,先需要设置指定通道的 PWM 参数:频率、分辨率、占空比,然后将该通道映射到指定引脚,该引脚输出对应通道的 PWM 信号;
6.3.1 核心代码
ledcSetup
:指定 LEDC 通道的 PWM 信号频率和占空比分辨率;
double ledcSetup(uint8_t chan, double freq, uint8_t bit_num);
参数 chan 为 LEDC 通道号,取值为 0~7,共 8 个通道;
参数 freq 为待设置的 PWM 脉宽信号的频率;
参数 bit_num 为计数位数,即 PWM 信号占空比的分辨率;
返回值:通道 PWM 信号的频率。
ledcAttachPin
:该函数功能是将指定的 LEDC 通道绑定到指定 GPIO 引脚上,即由该引脚输出 LEDC 的 PWM 信号;
void ledcAttachPin(uint8_t pin, uint8_t chan);
参数 pin 为数字引脚编号;
参数 chan 为 LEDC 通道号,取值为 0~7,共 8 个通道;
ledcWrite
:设置指定通道输出的占空比数值;
void ledcWrite(uint8_t chan, uint32_t duty);
参数 chan 为 LEDC 通道号,取值为 0~7,共 8 个通道;
参数 duty 为待设置的 PWM 占空比数值。该数值的范围由通道初始化设置函数 ledcSetup()中的计数位数决定。例如,计数位数为 8,那么占空比设置值的范围就为 0~255。要输出占空比50%的 PWM 信号,该参数应设置为 128。
6.3.2 应用例程
led_pwm.ino
:
#include "pwm.h"
uint16_t g_ledpwmval = 0; /* 占空比值 */
uint8_t g_dir = 1; /* 变化方向(1增大 0减小) */
/**
* @brief 当程序开始执行时,将调用setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
led_pwm_init(1000, 10); /* LED PWM初始化,PWM输出频率为1000HZ,占空比分辨率为10 */
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
if (g_dir)
{
g_ledpwmval += 5;
}
else
{
g_ledpwmval -= 5;
}
if (g_ledpwmval > 1005)
{
g_dir = 0;
}
if (g_ledpwmval < 5)
{
g_dir = 1;
}
pwm_set_duty(g_ledpwmval);
delay(10);
}
pwm.cpp
:
#include "pwm.h"
/**
* @brief LED PWM初始化函数
* @param frequency: PWM输出频率,单位HZ
* @param resolution: PWM占空比的分辨率1-16,比如设置8,分辨率范围0~255
* @retval 无
*/
void led_pwm_init(uint16_t frequency, uint8_t resolution)
{
ledcSetup(LED_PWM_CHANNEL, frequency, resolution); /* PWM初始化,引脚和通道由pwm.h的LED_PWM_PIN和LED_PWM_CHANNEL宏修改 */
ledcAttachPin(LED_PWM_PIN, LED_PWM_CHANNEL); /* 绑定PWM通道到LED_PWM_PIN上 */
}
/**
* @brief PWM占空比设置
* @param duty: PWM占空比
* @retval 无
*/
void pwm_set_duty(uint16_t duty)
{
ledcWrite(LED_PWM_CHANNEL, duty); /* 改变PWM的占空比,通道由pwm.h的LED_PWM_CHANNEL宏修改 */
}
pwm.h
:
#ifndef __PWM_H
#define __PWM_H
#include "Arduino.h"
/* LED PWM定义 */
#define LED_PWM_PIN 1 /* PWM信号输出的引脚 */
#define LED_PWM_CHANNEL 0 /* LED PWM通道号 */
/* 函数声明 */
void led_pwm_init(uint16_t frequency, uint8_t resolution); /* LED PWM初始化函数 */
void pwm_set_duty(uint16_t duty); /* PWM占空比设置 */
#endif
7. 看门狗
MCU 可能工作在一些复杂环境,可能受到某些电磁干扰出现程序跑飞,导致死循环无法继续执行工作,看门狗的作用就是为了避免这种情况。看门狗的本质也是一个定时器,在程序启动后,需要在一定的时间内再给它一个信号,俗称“喂狗”。如果没有按时“喂狗”,说明系统或软件出现了不可预知的问题(比如软件卡在某个循环或逾期事件中),这时看门狗就向系统发送个复位信号,使整个系统重启,重新进入正常的工作状态。看门狗有助于检测、处理系统或软件的错误行为。
ESP32-S3 中有3个数字看门狗定时器、1 个模拟看门狗定时器和1个 XTAL32K 看门狗定时器,他们在各自有特定条件运行;
6.2 应用例程
这里以通用定时器模拟看门狗为例,使用外部中断触发延时;
watch_dog.ino
:
#include "uart.h"
#include "key.h"
#include "watchdog.h"
#include "exti.h"
#define wdg_timeout 1200 /* 看门狗定时时间,1200ms */
/**
* @brief 当程序开始执行时,将调用setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
key_init(); /* KEY初始化 */
uart_init(0, 115200); /* 串口0初始化 */
exti_init(); /* 外部中断引脚初始化 */
Serial.println("running setup"); /* 打印标志性信息 方便查看系统开始 */
wdg_init(wdg_timeout * 1000, 80); /* 初始化看门狗,80分频,定时时间1.2秒 */
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
// Serial.println("running main loop"); /* 打印标志性信息 方便查看系统开始 */
timerWrite(wdg_timer, 0); /* 复位定时器(喂狗) */
long looptime = millis(); /* 通过millis函数获取开始运行程序以来经过的毫秒数 */
// while (!KEY) /* 按下按键会延时500ms,最终会导致looptime时间变为1.5秒,还没有来得及喂狗,就进入到定时器中断回调函数中复位 */
// {
// Serial.println("key pressed, delay_500ms");
// delay(500);
// }
delay(1001);
looptime = millis() - looptime; /* 计算两次millis 函数之间的时间差 */
// Serial.print("loop time is = "); /* 打印上过程运行时间 */
// Serial.println(looptime);
}
watchdog.cpp
:
#include "watchdog.h"
hw_timer_t *wdg_timer = NULL;
/**
* @brief 初始化看门狗
* @note
* 定时器的时钟来自APB,而APB为80M
* 所以定时器时钟 = (80/psc)Mhz, 单位时间为 1 / (80 / psc) = x us
* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us
* Ft=定时器工作频率,单位:Mhz
*
* @param arr: 自动重装值
* @param psc: 时钟预分频数
* @retval 无
*/
void wdg_init(uint32_t arr, uint16_t psc)
{
wdg_timer = timerBegin(WDG_TIMx, psc, true); /* 初始化定时器1 */
timerAlarmWrite(wdg_timer, arr, true); /* 设置中断时间 */
timerAttachInterrupt(wdg_timer, &WDG_ISR, true); /* 配置定时器中断回调函数 */
timerAlarmEnable(wdg_timer); /* 使能定时器中断 */
}
/**
* @brief 看门狗定时器中断回调函数
* @param 无
* @retval 无
*/
void WDG_ISR(void)
{
ets_printf("reboot\n");
esp_restart();
}
watchdog.h
:
#ifndef __WATCHDOG_H
#define __WATCHDOG_H
#include "Arduino.h"
#include "esp_system.h"
extern hw_timer_t *wdg_timer;
/* 看门狗定时器定义 */
#define WDG_TIMx 1 /* 模拟看门狗用到的定时器 */
#define WDG_ISR tim1_ISR /* 定时器中断服务函数 */
/* 函数声明 */
void wdg_init(uint32_t arr, uint16_t psc); /* 看门狗初始化函数 */
void WDG_ISR(void); /* 看门狗定时器中断回调函数 */
#endif
8. IIC
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器以及其外围设备。它是由数据线 SDA 和时钟线 SCL 构成的串行总线,可发送和接收数据,在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送。
IIC 总线有如下特点:
- 总线由数据线 SDA 和时钟线 SCL 构成的串行总线,数据线用来传输数据,时钟线用来同步数据收发。
- 总线上每一个器件都有一个唯一的地址识别,所以我们只需要知道器件的地址,根据时序就可以实现微控制器与器件之间的通信。
- 数据线 SDA 和时钟线 SCL 都是双向线路,都通过一个电流源或上拉电阻连接到正的电压,所以当总线空闲的时候,这两条线路都是高电平。
- 总线上数据的传输速率在标准模式下可达 100kbit/s,在快速模式下可达 400kbit/s,在高速模式下可达 3.4Mbit/s。
- 总线支持设备连接。在使用 IIC 通信总线时,可以有多个具备 IIC 通信能力的设备挂载在上面,同时支持多个主机和多个从机,连接到总线的接口数量只由总线电容 400pF 的限制决定。IIC 总线挂载多个器件的示意图,如下图所示。
ESP32-S3 有两个 IIC 总线接口,根据用户的配置,总线接口可以用作 IIC 主机或从机模式。
可支持 7 位寻址模式和 10 位寻址模式;
可支持双地址(从机地址和从机寄存器地址)寻址模式;
8.1 EEPROM
EEPROM 全程是“电可擦除可编程只读存储器”,即“Electrically Erasable Programmable Read-Only Memory”,特性就是数据掉电不丢失。
24C02 是一个 2K bit 的串行 EEPROM 存储器,内部含有 256 个字节。在 24C02 里面还有一个 8 字节的页写缓冲器。
8.1.1 核心代码
begin
:初始化 IIC 连接,并作为主设备加入 IIC;
bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency);
参数 sdaPin 为 IIC 总线的数据线引脚;
参数 sclPin 为 IIC 总线的时钟线引脚;
参数 frequency 为 IIC 总线通信频率;
返回值:布尔类型。初始化成功返回 true,否则返回 false。
beginTransmission
:初始化一个 I2C 数据传输过程,并指定要与之通信的 I2C 从设备地址。数据队列的长度默认为 128 字节。
void TwoWire::beginTransmission(uint16_t address);
参数 address 为要发送的从设备的地址;
write
:将向从机发送的数据加入发送数据队列;
size_t TwoWire::write(uint8_t data);
参数 data 为要发送的一个字节数据;
返回值:size_t 类型。加入成功返回 1,否则返回 0。
endTransmission
:写入数据,主设备将发送数据队列中的数据发送给从设备。
uin8_t TwoWire::endTransmission(bool sendStop);
参数 sendStop 为 0 时,将在通讯结束后,不产生 STOP 信号;为 1 时,在通讯结束后,生成 STOP 信号,释放总线。该函数也可不传参数,当无输入参数时,在通讯结束后,产生 STOP 信号,释放总线。
返回值:表示本次传输的状态,写入数据成功返回 0,数据太长无法加入到发送数据缓冲区返回 1,发送地址时收到 NACK 返回 2,数据发送时收到 NACK 返回 3,其他错误返回 4,超时返回 5;
requestFrom
:读取数据,主设备向从设备发送读取数据请求,并将读取的数据保存到缓冲区。缓冲区的默认长度为 128 字节。
uint8_t TwoWire::requestFrom(uint8_t address, uint8_t len);
参数 address 为从设备的地址;
参数 len 为读取的字节数;
返回值:读取数据成功返回 0。
available
:返回缓冲区中数据的字节数;
int TwoWire::available(void);
返回值:字节数。
read
:从缓冲区读取一个字节的数据。主设备中使用requestFrom 函数发送数据读取请求信号后,需要使用 read 函数来获取数据。
int TwoWire::read(void);
返回值:读到的字节数据。
8.1.2 24CXX 应用例程
iic_eeprom.ino
:
#include "24c02.h"
#include "key.h"
#include "uart.h"
const uint8_t g_text_buf[] = {"ESP32S3 IIC TEST"}; /* 要写入到24c02的字符串数组 */
const uint8_t j_text_buf[] = {"24C02 Ready!"}; /* 要写入到24c02的字符串数组 */
#define TEXT_SIZE sizeof(g_text_buf) /* TEXT字符串长度 */
uint8_t datatemp[TEXT_SIZE]; /* 从EEPROM读取到的数据 */
/**
* @brief 当程序开始执行时,将调用setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
key_init(); /* KEY初始化 */
uart_init(0, 115200); /* 串口0初始化 */
at24c02_init(); /* 初始化24CXX */
while (at24c02_check()) /* 检测不到24c02 */
{
Serial.println("24C02 Check Failed!");
delay(500);
}
at24c02_write(0, (uint8_t *)j_text_buf, sizeof(j_text_buf)); // 上电先写一次24CXX
Serial.println("24C02 Ready!");
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
at24c02_read(0, datatemp, TEXT_SIZE); /* 从24C02的0地址处中读取TEXT_SIZE长度数据 */
Serial.printf("The Data Readed Is:%s \r\n", datatemp);
if (KEY == 0)
{
at24c02_write(0, (uint8_t *)g_text_buf, TEXT_SIZE); /* 向24C02的0地址处写入TEXT_SIZE长度数据 */
Serial.printf("24C02 Write %s Finished! \r\n", g_text_buf);
}
delay(1000);
}
8.1.3 IO 扩展芯片XL9555驱动例程
XL9555 可使用 400kHz 速率的 IIC 通信接口与微控制器进行连接,也就是用 2 根通信线可扩展使用 16 个 IO。XL9555 器件地址会由三个硬件地址引脚决定,理论上在这个 IIC 总线上可挂载 8 个 XL9555 器件,足以满足 IO 引脚需求;
XL9555 上电进行复位,16 个I/O 口默认为输入模式,当输入模式的 IO 口状态发生变化时,即发生从高电平变低电平或者从低电平变高电平,中断脚会拉低。当中断有效后,必须对 XL9555 进行一次读取/写入操作,复位中断,才可以输出下一次中断,否则中断将一直保持。
为了提高对按键的实时检测,使用到了中断引脚。但是这里的中断引脚并没有直接与 ESP32-S3 引脚相连,而是需要通过跳线帽对 IIC_INT 和 BOOT 进行连接,如下图:
(程序下载报错)
iic_exio.ino
:
#include "xl9555.h"
#include "led.h"
#include "uart.h"
/**
* @brief 当程序开始执行时,将调用setup()函数,通常用来初始化变量、函数等
* @param 无
* @retval 无
*/
void setup()
{
led_init(); /* LED初始化 */
uart_init(0, 115200); /* 串口0初始化 */
xl9555_init(); /* IO扩展芯片初始化 */
xl9555_io_config(KEY0 | KEY1 | KEY2 | KEY3, IO_SET_INPUT); /* 初始化IO扩展芯片用作按键的引脚为输入状态 */
xl9555_io_config(BEEP, IO_SET_OUTPUT); /* 初始化IO扩展芯片用作蜂鸣器控制的引脚为输出状态 */
}
/**
* @brief 循环函数,通常放程序的主体或者需要不断刷新的语句
* @param 无
* @retval 无
*/
void loop()
{
if (!IIC_INT) /* 端口有发生变化变为低电平 */
{
if (xl9555_get_pin(KEY0) == 0)
{
Serial.printf("KEY0 is pressed, BEEP is on \r\n");
xl9555_pin_set(BEEP, IO_SET_LOW);
}
if (xl9555_get_pin(KEY1) == 0)
{
Serial.printf("KEY1 is pressed, BEEP is off \r\n");
xl9555_pin_set(BEEP, IO_SET_HIGH);
}
if (xl9555_get_pin(KEY2) == 0)
{
Serial.printf("KEY2 is pressed, LED is on \r\n");
LED(0);
}
if (xl9555_get_pin(KEY3) == 0)
{
Serial.printf("KEY3 is pressed, LED is off \r\n");
LED(1);
}
}
delay(200);
}
9. RTC - 实时时钟
RTC,Real Time Clock,实时时钟,专门用来记录时间;
ESP32-S3 使用两种硬件时钟源建立和保持系统时间。根据应用目的及对系统时间的精度要求,既可以仅使用其中一种时钟源,也可以同时使用两种时钟源。这两种硬件时钟源为 RTC 定时器和高分辨率定时器。
安装RTC 软件库:
10. WIFI
WiFi 工作模式:ESP32-S3 是一款集成了 WiFi+蓝牙功能的双模芯片,ESP32-S3 提供了 3 种WiFi 工作模式,分别是 Station(STA)、Access Point(AP)、STA+AP;
-
STA 模式(站点模式):当 ESP32-S3 工作于 STA 模式时,ESP32-S3 作为一个站点接入到接入点,最常见的接入点就是路由器,如下图所示:
-
AP 模式(WiFi 热点模式):当 ESP32-S3 工作于 AP 模式时,ESP32-S3 就是接入点,类似于路由器。手机和计算机都可以连接到该 ESP32-S3,ESP32-S3 的 AP 功能是通过软件来实现的,所以也称为 softAP。如下图所示:
-
STA+AP 模式:当 ESP32-S3 工作于 STA+AP 模式时,首先,作为接入点,手机等其他终端可以连接到该ESP32-S3,同时该 ESP32-S3 还可以作为断点连接到其他接入点,如路由器,STA+AP 模式通常适用于 MESH 网络;如下图所示:
网络扫描:通常,无线网络提供的 WiFi 热点,大部分都是开放了网络名称(SSID)广播,而手机打开WiFi 设置功能,这时候就会传送到手机端即可显示出 WiFi 热点列表。WiFi 的 Scan 功能就是扫描出附近的 WiFi 热点的 SSID 信息。
ESP32-S3 实现 WiFi Scan 功能,首先得把模式设置为 STA 模式,然后再进行扫描网络。一般的扫描网络需要几百毫秒才能完成。而扫描网络的过程就包括:触发扫描过程、等待完成和提供结果。
WiFi 的 Scan 库提供了两种方式实现上面的扫描过程:
- 同步扫描:通过单个函数在一次运行中完成,需要等待完成所有操作才能继续运行下面的操作。
- 异步扫描:把上面的过程分为几个步骤,每个步骤由单个函数完成。
10.1 WIFI SCAN 实验
WiFiScan 库:本实验实现的网络扫描主要依赖的是 WiFiScan 库,还会涉及到 WiFiGeneric 库和 WiFiSTA库。WiFiGeneric 库为 WiFi 基础功能库,而 WiFiSTA 库为 station 模式专用库。
使用 WiFiGeneric 库中的 mode 函数设置 WiFi 模式;使用 WiFiSTA 库中的 disconnect 函数断开之前的 WiFi 连接。
WiFiScan 库的函数主要分为两类:扫描操作和获取扫描结果,如下图所示:
10.1.1 例程
代码备份路径:百度网盘 - 知识资源 - ESP32 - Arduino - WIFI-BLE - 01_wifi_scan.rar
10.2 WIFI WEBSERVER 实验
在 ESP32-S3 上搭建 Web 服务器,然后使用设备访问Web 服务器控制 ESP32-S3 开发板的 LED 灯。
10.2.1 互联网络和 TCP/IP 协议
通过线路简单将计算机连接到一起是远远不够的,就好像语言不通的两个人见了面,不能完全交流信息,因此需要定义一些共同的规则以方便交流,TCP/IP 就是为此而生的。TCP/IP 是一个协议家族的统称,除了包含了 IP 协议、TCP 协议,及我们比较熟悉的 HTTP、FTP、POP3协议等。计算机有了这些协议,就好像学会了外语,就可以和其他的计算机进行信息传输和数据交流。
TCP/IP 协议家族的协议成员是分层次的,我们称之为 TCP/IP 模式,共分为 4 层,从底向上依次是网络接口层、网络层、传输层和应用层,如下图所示:
- TCP/UDP:TCP(Transmission Control Protocol,传输控制协议)和UDP(User Datagram Protocol,用户数据报协议)是两种常见的传输层协议,用于在计算机网络中传输数据。
- TCP:面向连接的协议,在数据传输前需要通过三次握手建立连接,并在传输结束后通过四次挥手断开连接;提供可靠的数据传输,通过确认机制、重传机制和错误检测确保数据的完整性和顺序性;由于复杂的控制机制(如流量控制、拥塞控制),传输速度较慢;适用于需要可靠传输的应用,如网页浏览、文件传输、电子邮件等
- UDP:无连接的协议,直接发送数据包,无需建立连接;不保证可靠性,数据包可能丢失、重复或乱序;没有这些机制,传输速度快,适合实时性要求高的场景;适用于实时应用,如音视频流、在线游戏、DNS查询等;
10.2.2 IP 地址和端口号
- IP 地址:IP 地址指互联网协议地址(Internet Protocol Address),是 IP 协议提供的一种统一地址格式,它为互联网上每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽各种不同类型计算机物理地址的差异。
IP 地址的定义不止一个版本,目前的主流版本为 IPv4。IPv4 地址的长度为 32 位,分为 4段,每段 8 位,用十进制数字表示,每段数字范围为 0 ~ 255,段与段之间用句点隔开,如202.102.10.1。可见,IPv4 共有 232 个,接近 43 亿个 IP 地址。IPv6 的地址长度为 128 位,其地址数量号称“可以为全世界的每一粒沙子拥有一个 IP 地址”,足以支撑未来相当长时间的发展需要。目前,IPv4 正在向 IPv6 应用的过渡过程中。
- 端口号:IP 地址与网络服务的关系是一对多的关系,现实中是主机通过“IP 地址+端口号”来区分不同的服务。常用端口号(Port)对应的应用列表如下表所示:
便于理解“IP 地址+端口号”来区分不同服务,可以把一个 IP地址看作一个医院,医院有内科、外科、牙科、皮肤科等科室。挂号时,会根据你的症状安排到不同的科室进行医治,这些科室就和端口号类似,不同的端口号就对应着不同的网络服务(Web服务、FTP 服务等)。
10.2.3 客户端-服务器模式(C/S)
互联网络把计算机连起来的目的主要是向计算机提供信息服务,互联的计算机通常可分为两类:
- 信息服务的请求者(Requestor),被称为客户端(Client);
- 信息服务的响应者(Responsor),被称为服务器(Server)。
这种主要由客户端和服务器组成的网络架构称为客户端-服务器模式(简称 C/S 模式),如下图所示:
在计算机的浏览器输入百度并访问网站,此时计算机上的浏览器就是客户端,而百度网站的计算机和数据库则是服务器。当网页浏览器向百度发送一个查询请求时,百度服务器从百度的数据库中找出该请求所对应的信息,组成一个网页,再发送回浏览器。
- HTTP 协议:指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则。HTTP 协议是 Hyper Text Transfer Protocol(超文本传输协议)的缩写,是从万维网(WWW:World Wide Web)服务器传输超文本到本地浏览器的传送协议。超文本是指用超链接的方法将各种不同空间的文字信息组织在一起而形成的网状文本。这里的文字信息包含文字、图片、音频、视频、文件等数据。
HTTP 协议是基于客户端/服务端(C/S)的架构模型。
HTTP 的访问由客户端发起,通过一种叫统一资源定位符(URL:Uniform Resource Locator,比如 www.baidu/duty)的标识来找到服务器,建立连接并传输数据。
HTTP 默认端口号为 80。
10.2.3 ESP32-S3 Web 服务器
Web 服务器也称为HTTP 服务器 ,用于接收客户端 HTTP 请求消息并回复响应消息,是一个运行在硬件服务器的软件。
ESP32-S3 Web 服务器就是一个运行在 ESP32-S3 硬件平台上的 Web 服务器程序。自底向上与 TCP/IP 的 4 层模型有着对应关系。
客户端与 Web 服务器通过 HTTP 协议进行交互,如下图所示:
- URL 和域名、IP 之间的关系:
http://www.baidu/duty/
是 URL;www.baidu
就是域名;183.2.172.42 就是 IP 地址。在计算机的浏览器输入http://www.baidu/duty/
(URL),其中,http 为传输协议;www.baidu
为域名,计算机会通过域名解析系统(DNS:Domain name resolution),把www.baidu
(域名)解析成 183.2.172.42(IP 地址),然后和 183.2.172.42 建立连接,并告诉183.2.172.42:“我要看/duty(资源路径下)网页的内容”。
之所以在 IP 地址之外还引入域名,主要是为了帮助记忆,IP 地址作为一个抽象的数字不太容易记住,而 www.baidu 则更容易记住多了。
- ESP32-S3 WEBSERVER 介绍:本实验,开发板为 Web 服务器。实现 Web 服务器主要依赖 WIFISTA 和WIFISERVER 库。
10.2.4 HTML 基础
HTML(Hyper Text Markup Language),即超文本标记语言,是用于创建网页的主要标记语言。浏览网页时,每一个网页对应一个 HTML 文档。Web 浏览器是为了解释 HTML 文件而生的,HTML 文件中的标签告诉 Web 浏览器如何在页面上显示内容。HTML 文档的后缀为.html或者.htm,这两种后缀都一样,没有区别。
HTML 文档是纯文本格式,可以使用电脑自带的记事本来编辑 HTML 文档,当然也可以使用 VScode 或者 notepad++等等。
- HTML 文档基本结构:上图中,不同的“< >”及其包围的关键词称为 HTML 标记标签,简称为 HTML 标签,如
<html>
、<head>
、<title>
、<body>
等。需要注意:绝大多数标签成对出现。我们手动输入的内容称为文本,文本和 HTML 标签构成了 HTML 文档。在 HTML 文件中,使用<!--内容-->
这个格式作为注释。
10.2.5 例程
- 将需要连接的WIFI 账号和密码写入到代码对应的变量中:
char* ssid = "WIFINAME"; /* ESP32要连接网络的名称 */
char* password = "1234567890"; /* ESP32要连接网络的密码 */
- 下载ESP32程序到ESP32 开发板,然后开发板自动连接目标WIFI,ESP32串口输出WIFI 连接成功的状态信息和ESP32的硬件地址:
WiFi connected.
IP address:172.20.10.8
- 使用另外一台设备连接同一WIFI,然后使用该设备的浏览器访问上述IP 地址,进入如下图网页则表示访问成功,点击ON 和OFF 按钮,观察ESP32开发板上的LED 是否对应亮灭:
代码备份路径:百度网盘 - 知识资源 - ESP32 - Arduino - WIFI-BLE - 02_wifi_webserver.rar
10.3 WIFI_CLIENT 实验
使用ESP32-S3 作为客户端连接服务器获取数据。
11. 蓝牙
蓝牙,是一种支持设备短距离通信的无线通信技术,最早是由爱立信公司于 1994 年发明。蓝牙的目标是使各类移动设备、嵌入式设备、计算机外设和家用电器等众多设备之间在没有电缆连接的情况下能够在短距离范围内实现信息的自由传输与分享。相比较其他无线通信技术,蓝牙具有安全性高、易于连接等优势。
蓝牙支持点对点及点对多点的通信。可分为经典蓝牙和低功耗蓝牙,其中,蓝牙 1.1、1.2、2.0、2.1、3.0 版本属于经典蓝牙,4.0 版本(含)以后的蓝牙添加了低功耗蓝牙。:
- 经典蓝牙:简称 BT,泛指支持蓝牙协议在 4.0 版本以下的模块,一般用于如语音、音乐等大数据量的传输。经典蓝牙的协议包含了个人局域网的各种规范,不同的规范对应于不同的应用场景;
- 低功耗蓝牙:简称 BLE,是一种新型的超低功耗无线通信技术,主要针对低成本、低复杂度的无线体域网和无线个域网设计;
11.1 蓝牙协议
蓝牙协议规定了两个层次,分别为蓝牙核心协议和蓝牙应用层协议。蓝牙核心协议是对蓝牙技术本身的规范,主要包括控制器(Controller)和主机(Host),不涉及其应用方式;蓝牙应用层协议是在蓝牙核心协议的基础上,根据具体的应用需求定义出的特定策略。蓝牙协议栈如下图所示:
11.2 工作状态和工作角色介绍
链路层定义了蓝牙的五种状态:就绪态、广播态、扫描态、发起态和链接态,如下图所示:
- 处于就绪态(Standby)时,链路层不收发报文,任何状态都可以进入到就绪态。
- 处于广播态(Advertising)的链路层可以发送广播报文,也可以监听以及响应这些广播报文触发的响应报文。可被发现(也就是可以被扫描到)或可被链接(后面进行数据通信)的设备必须处于广播态,而需要向一定范围内的其他蓝牙设备广播数据的设备也必须处于广播态。
- 处于扫描态(Scanning)的设备能够接收广播报文。扫描又分为两种,主动扫描和被动扫描。主动扫描可以发送扫描请求给广播态设备,并获取额外的扫描响应数据,而被动扫描仅仅是接收到广播报文。
- 处于发起态(Initiating)设备可以发起链接请求。如果处于发起态的发起者接收到了来自其他设备的广播报文,链路层会向其发送链接请求并进入链接态。如果发起者不再发起链接,也可进入到就绪态。
- 处于链接态(Connection)下的设备才是我们经常看到的数据传输状态。并且又会区分为主设备(Master)和从设备(Slave)。由发起态进入链接态的设备是主设备,由广播态进入链接态的设备是从设备。
根据链路层处于状态不同,可分为 5 种不同的工作角色:广播者、扫描者、发起者、主设备和从设备。
链路层不能同时执行主设备和从设备两个角色。执行主设备角色的链路层可以同时执行广播者角色,或者扫描者角色,或者发起者角色。执行从设备角色的链路层可以同时执行广播者角色或扫描者角色。从设备不能发送可链接的广播报文,但可以发送不可链接的广播报文或可发现的广播报文。
11.1 BLE_SCAN 实验
介绍如何使用 ESP32-S3 把附近的蓝牙设备扫描出来。