参考:(正点原子)DNESP32S3 V1.0 硬件参考手册、(正点原子)DNESP32S3使用指南-Arduino版本_V1.1、ESP32-S3 技术规格书、(正点原子)标准例程-Arduino版
在Arduino 官网中找到官方开发板资料和Arduino 函数说明:
乐鑫公司提供的 arduino_esp32库的资料:
github/espressif/arduino-esp32
这是乐鑫公司专门为了 ESP32 能在 Arduino IDE 软件上运行专门编写的库包。后续在 Arduino IDE 上开发 ESP32 是依靠该库包,并不是 Arduino 官方提供的 Arduino ESP32 Boards 软件包
正点原子的学习资料:
www.openedv/docs/boards/esp32/ATK-DNESP32S3.html
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 上对芯片进行开发。
- –
gpio_set_direction(GPIO_NUM_1, GPIO_MODE_OUTPUT);
gpio_set_level(GPIO_NUM_1, PIN_SET);
PinMode(1, OUTPUT);
digitalWrite(1, HIGH);
void setup() {
// 在这里填写 setup 函数代码,它只会运行一次
}
void loop() {
// 在这里编写 loop 函数代码,它会不断重复运行
}
ESP32 的开发方式主要有三种:ESP-IDF、Arduino 和 MicroPython:
软件下载与安装:到Arduino 官网下载并安装软件;
配置首选项:文件 - 首选项:
如果网络允许,可直接点击上图“安装”,完成在线安装;
配置参数说明可查看该网站vin/3258.html
ESP32是乐鑫公司开发的物联网芯片:
ESP32官方选型页面
ESP32-S3 是一款由乐鑫公司开发的物联网芯片:
ESP32-S3 资源简介:
乐鑫 S3 系列型号包括 ESP32-S3、ESP32-S3R2、ESP32-S3R8 和 ESP32-S3FN8 等,不同型号的 MCU 有不同的应用场景,这些型号的命名规则如下:
以ESP32-S3FH4R2这一芯片为例:
除了 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 采用连接器连接外部天线。两款模组均有多种芯片型号可供选择,具体见下表所示:
从宏观上,该启动流程可分为如下 3 个步骤:
Arduino-esp32 库是一个在 Arduino 平台上开发 ESP32 的插件,它为 Arduino 环境下的ESP32 芯片提供了支持。它允许使用熟悉的 Arduino 函数和库编写代码,并直接在 ESP32 上运行。
Arduino-esp32 库支持对 ESP32、ESP32-S2、ESP32-S3、ESP32-C3、ESP32-C6 和 ESP32-H2进行开发,还提供了很多基础库:
对于 ESP32-S3 模组引出的 IO,每个管脚都可用作一个通用 IO,或连接一个内部外设信号;
GPIO 口输出来的信号是以 0,1 表示的不连续数字信号。在 Arduino 中数字信号用高低电平来表示,高电平为数字信号 1,低电平为数字信号 0;Arduino 输出的低电平为 0V,输出的高电平为当前 Arduino 的工作电压,对于 ESP32-S3 模组的工作电压为 3.3V,则其高电平为 3.3V;
新建工程后,选择对应串口和对应芯片:
#define LED_PIN 1
void led_init(void)
{
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
}
#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 引脚输出接高电平,熄灭 */
}
}
外部中断是由外部设备发起请求的中断。
每个中断对应一个中断程序,中断程序可以看作一段独立于主程序之外的程序,也称为中断回调函数。
当中断被触发时,控制器会暂停当前正在运行的主程序,而跳转去运行中断程序。当中断程序运行完毕,则返回到先前主程序暂停的位置,继续运行主程序,如此便可达到实时响应处理事件的效果。
ESP32-S3 的中断:ESP32-S3 有 99 个外部中断源,但是 CPU0 或 CPU1 只能够处理 32 个中断。ESP32-S3 将外部中断映射到 CPU0 或 CPU1 中断就需要用到中断矩阵。
ESP32-S3 中断矩阵会将任一外部中断源单独分配到双核 CPU 的任一外部中断上,以便在外设中断信号产生后,及时通知 CPU0 或 CPU1 进行处理。
ESP32-S3 的中断模式:
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 为已经设置中断触发输入的引脚。
*/
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
由于大部分 Arduino 开发板都没有调试功能,所以在开发中,最常用的就是串口打印信息到串口助手去调试程序。
在 ESP32-S3 中,是有 3 个 UART 控制器,即 UART0、UART1 和 UART2。3 个 UART 端口对应的引脚如下表所示:
上表带有具体 IO 口是默认使用 IO,但是 ESP32-S3 有 IO MUX,所以是可以选择任意 GPIO管脚作为 UART 的引脚。使用 Arduino,调用串口初始化函数时,可以指定发送引脚和接收引脚。
IDE 右上角的串口监视器可进行开发板串口数据收发:
选好波特率后,可在消息框内进行数据发送:
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.available();
:通常在使用串口读取数据时,需要搭配使用该函数;该函数的返回值为当前缓冲区中接收到的数据字节数。通常该函数会搭配 if 或者 while 语句来使用,先检测缓冲区中是否有可读数据,如果有数据,再读取;如果没有数据,跳过读取或等待读取,如下所示:while (Serial.available() > 0)
{
char c = Serial.read();
Serial.print(c);
}
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 时,即到达设定时间,就会跳进中断回调函数中执行,执行完毕再回到主程序中运行
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 为已初始化的目标定时器结构体指针;
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
PWM(Pulse Width Modulation),简称脉宽调制,是一种将模拟信号变为脉冲信号的技术。
PWM 周期是一个 PWM 信号的时间:脉宽时间是指高电平时间;脉宽时间占 PWM 周期的比例就是占空比。
在使用 PWM 控制 LED 时,亮 1s 后灭 1s,往复循环,就可以看到 LED 在闪烁。如果把这个周期缩小到 200ms,亮 100ms 后灭 100ms,往复循环,就可以看到 LED 灯在高频闪烁。继续把这个周期持续缩小,总有一个临界值使人眼分辨不出 LED 在闪烁,此时 LED 的亮度处于灭与亮之间亮度的中间值,达到了 1/2 亮度。
为了实现 PWM 输出,先需要设置指定通道的 PWM 参数:频率、分辨率、占空比,然后将该通道映射到指定引脚,该引脚输出对应通道的 PWM 信号;
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。
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
MCU 可能工作在一些复杂环境,可能受到某些电磁干扰出现程序跑飞,导致死循环无法继续执行工作,看门狗的作用就是为了避免这种情况。看门狗的本质也是一个定时器,在程序启动后,需要在一定的时间内再给它一个信号,俗称“喂狗”。如果没有按时“喂狗”,说明系统或软件出现了不可预知的问题(比如软件卡在某个循环或逾期事件中),这时看门狗就向系统发送个复位信号,使整个系统重启,重新进入正常的工作状态。看门狗有助于检测、处理系统或软件的错误行为。
ESP32-S3 中有3个数字看门狗定时器、1 个模拟看门狗定时器和1个 XTAL32K 看门狗定时器,他们在各自有特定条件运行;
这里以通用定时器模拟看门狗为例,使用外部中断触发延时;
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("rebootn");
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
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接微控制器以及其外围设备。它是由数据线 SDA 和时钟线 SCL 构成的串行总线,可发送和接收数据,在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送。
IIC 总线有如下特点:
ESP32-S3 有两个 IIC 总线接口,根据用户的配置,总线接口可以用作 IIC 主机或从机模式。
可支持 7 位寻址模式和 10 位寻址模式;
可支持双地址(从机地址和从机寄存器地址)寻址模式;
EEPROM 全程是“电可擦除可编程只读存储器”,即“Electrically Erasable Programmable Read-Only Memory”,特性就是数据掉电不丢失。
24C02 是一个 2K bit 的串行 EEPROM 存储器,内部含有 256 个字节。在 24C02 里面还有一个 8 字节的页写缓冲器。
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);
返回值:读到的字节数据。
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 rn", datatemp);
if (KEY == 0)
{
at24c02_write(0, (uint8_t *)g_text_buf, TEXT_SIZE); /* 向24C02的0地址处写入TEXT_SIZE长度数据 */
Serial.printf("24C02 Write %s Finished! rn", g_text_buf);
}
delay(1000);
}
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 rn");
xl9555_pin_set(BEEP, IO_SET_LOW);
}
if (xl9555_get_pin(KEY1) == 0)
{
Serial.printf("KEY1 is pressed, BEEP is off rn");
xl9555_pin_set(BEEP, IO_SET_HIGH);
}
if (xl9555_get_pin(KEY2) == 0)
{
Serial.printf("KEY2 is pressed, LED is on rn");
LED(0);
}
if (xl9555_get_pin(KEY3) == 0)
{
Serial.printf("KEY3 is pressed, LED is off rn");
LED(1);
}
}
delay(200);
}
RTC,Real Time Clock,实时时钟,专门用来记录时间;
ESP32-S3 使用两种硬件时钟源建立和保持系统时间。根据应用目的及对系统时间的精度要求,既可以仅使用其中一种时钟源,也可以同时使用两种时钟源。这两种硬件时钟源为 RTC 定时器和高分辨率定时器。
安装RTC 软件库:
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 库提供了两种方式实现上面的扫描过程:
WiFiScan 库:本实验实现的网络扫描主要依赖的是 WiFiScan 库,还会涉及到 WiFiGeneric 库和 WiFiSTA库。WiFiGeneric 库为 WiFi 基础功能库,而 WiFiSTA 库为 station 模式专用库。
使用 WiFiGeneric 库中的 mode 函数设置 WiFi 模式;使用 WiFiSTA 库中的 disconnect 函数断开之前的 WiFi 连接。
WiFiScan 库的函数主要分为两类:扫描操作和获取扫描结果,如下图所示:
代码备份路径:百度网盘 - 知识资源 - ESP32 - Arduino - WIFI-BLE - 01_wifi_scan.rar
在 ESP32-S3 上搭建 Web 服务器,然后使用设备访问Web 服务器控制 ESP32-S3 开发板的 LED 灯。
通过线路简单将计算机连接到一起是远远不够的,就好像语言不通的两个人见了面,不能完全交流信息,因此需要定义一些共同的规则以方便交流,TCP/IP 就是为此而生的。TCP/IP 是一个协议家族的统称,除了包含了 IP 协议、TCP 协议,及我们比较熟悉的 HTTP、FTP、POP3协议等。计算机有了这些协议,就好像学会了外语,就可以和其他的计算机进行信息传输和数据交流。
TCP/IP 协议家族的协议成员是分层次的,我们称之为 TCP/IP 模式,共分为 4 层,从底向上依次是网络接口层、网络层、传输层和应用层,如下图所示:
IP 地址的定义不止一个版本,目前的主流版本为 IPv4。IPv4 地址的长度为 32 位,分为 4段,每段 8 位,用十进制数字表示,每段数字范围为 0 ~ 255,段与段之间用句点隔开,如202.102.10.1。可见,IPv4 共有 232 个,接近 43 亿个 IP 地址。IPv6 的地址长度为 128 位,其地址数量号称“可以为全世界的每一粒沙子拥有一个 IP 地址”,足以支撑未来相当长时间的发展需要。目前,IPv4 正在向 IPv6 应用的过渡过程中。
便于理解“IP 地址+端口号”来区分不同服务,可以把一个 IP地址看作一个医院,医院有内科、外科、牙科、皮肤科等科室。挂号时,会根据你的症状安排到不同的科室进行医治,这些科室就和端口号类似,不同的端口号就对应着不同的网络服务(Web服务、FTP 服务等)。
互联网络把计算机连起来的目的主要是向计算机提供信息服务,互联的计算机通常可分为两类:
这种主要由客户端和服务器组成的网络架构称为客户端-服务器模式(简称 C/S 模式),如下图所示:
在计算机的浏览器输入百度并访问网站,此时计算机上的浏览器就是客户端,而百度网站的计算机和数据库则是服务器。当网页浏览器向百度发送一个查询请求时,百度服务器从百度的数据库中找出该请求所对应的信息,组成一个网页,再发送回浏览器。
HTTP 协议是基于客户端/服务端(C/S)的架构模型。
HTTP 的访问由客户端发起,通过一种叫统一资源定位符(URL:Uniform Resource Locator,比如 www.baidu/duty)的标识来找到服务器,建立连接并传输数据。
HTTP 默认端口号为 80。
Web 服务器也称为HTTP 服务器 ,用于接收客户端 HTTP 请求消息并回复响应消息,是一个运行在硬件服务器的软件。
ESP32-S3 Web 服务器就是一个运行在 ESP32-S3 硬件平台上的 Web 服务器程序。自底向上与 TCP/IP 的 4 层模型有着对应关系。
客户端与 Web 服务器通过 HTTP 协议进行交互,如下图所示:
www.baidu/duty/
是 URL;www.baidu
就是域名;183.2.172.42 就是 IP 地址。在计算机的浏览器输入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 则更容易记住多了。
HTML(Hyper Text Markup Language),即超文本标记语言,是用于创建网页的主要标记语言。浏览网页时,每一个网页对应一个 HTML 文档。Web 浏览器是为了解释 HTML 文件而生的,HTML 文件中的标签告诉 Web 浏览器如何在页面上显示内容。HTML 文档的后缀为.html或者.htm,这两种后缀都一样,没有区别。
HTML 文档是纯文本格式,可以使用电脑自带的记事本来编辑 HTML 文档,当然也可以使用 VScode 或者 notepad++等等。
<html>
、<head>
、<title>
、<body>
等。需要注意:绝大多数标签成对出现。我们手动输入的内容称为文本,文本和 HTML 标签构成了 HTML 文档。在 HTML 文件中,使用<!--内容-->
这个格式作为注释。char* ssid = "WIFINAME"; /* ESP32要连接网络的名称 */
char* password = "1234567890"; /* ESP32要连接网络的密码 */
WiFi connected.
IP address:172.20.10.8
代码备份路径:百度网盘 - 知识资源 - ESP32 - Arduino - WIFI-BLE - 02_wifi_webserver.rar
使用ESP32-S3 作为客户端连接服务器获取数据。
蓝牙,是一种支持设备短距离通信的无线通信技术,最早是由爱立信公司于 1994 年发明。蓝牙的目标是使各类移动设备、嵌入式设备、计算机外设和家用电器等众多设备之间在没有电缆连接的情况下能够在短距离范围内实现信息的自由传输与分享。相比较其他无线通信技术,蓝牙具有安全性高、易于连接等优势。
蓝牙支持点对点及点对多点的通信。可分为经典蓝牙和低功耗蓝牙,其中,蓝牙 1.1、1.2、2.0、2.1、3.0 版本属于经典蓝牙,4.0 版本(含)以后的蓝牙添加了低功耗蓝牙。:
蓝牙协议规定了两个层次,分别为蓝牙核心协议和蓝牙应用层协议。蓝牙核心协议是对蓝牙技术本身的规范,主要包括控制器(Controller)和主机(Host),不涉及其应用方式;蓝牙应用层协议是在蓝牙核心协议的基础上,根据具体的应用需求定义出的特定策略。蓝牙协议栈如下图所示:
链路层定义了蓝牙的五种状态:就绪态、广播态、扫描态、发起态和链接态,如下图所示:
根据链路层处于状态不同,可分为 5 种不同的工作角色:广播者、扫描者、发起者、主设备和从设备。
链路层不能同时执行主设备和从设备两个角色。执行主设备角色的链路层可以同时执行广播者角色,或者扫描者角色,或者发起者角色。执行从设备角色的链路层可以同时执行广播者角色或扫描者角色。从设备不能发送可链接的广播报文,但可以发送不可链接的广播报文或可发现的广播报文。
介绍如何使用 ESP32-S3 把附近的蓝牙设备扫描出来。
本文发布于:2025-04-08 00:51:00,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/1744044676585008.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |