本笔记是基于有一定嵌入式基础的学习比较,但门槛不高
1. 基本的语法
1.1 IO口的基本操作
对于ESP32这类开发板而言,我们开发的本质逻辑其实就是控制IO口的操作,譬如输出一个高电平,或者在某个时候输出一个低电平、PWM,输入如采样AD、检测电平状态等等。因此对于IO口的操作语法是最基本的操作。
首先,Arduino语法与C++类似,与micropython对于Pin脚的操作不同。在Arduino中都是通过函数对Pin脚操作,比如:
pinMode(2, OUTPUT);
这里就是用函数来对Pin2操作
下面列出一些IO的常规操作(只列出比较常用的);
操作指令 | 细化指令 | 对应的Arduino命令 | 备注 |
---|---|---|---|
初始化端口 | 输出 | | 端口2配置成输出模式; 要使用端口就必须先初始化,先给端口赋予其基本的功能,如输出、输入、AD采样、PWM等等 |
输入 | | 端口2配置成输入;可以读出端口的高低电平状态 | |
端口操作 | 输出高低电平 | | |
读取引脚电平 | | 返回的是HIGH或者LOW 注意:设置因为为OUT类型也可以用这个函数读取它的值。 INTPUT与OUTPUT最大的差别是INPUT不会主动改变端口的值,仅仅作为输入 | |
AD采样-读取 | | ||
AD采样-写 | | ||
低频PWM | | 这个方法输出一个定频的PWM信号(频率大约为490Hz),value为占空比 不同开发板的脚位不同需要按照支持的脚位来设置 | |
专用PWM | 主要通过LEDC这个外设来驱动,内容较多,后面章节会详细说明 | LED PWM 控制器可以生成 16 路通道(0 ~ 15),波形的周期和占空比可配置。分为高低速两组,高速通道(0 ~ 7)由 80MHz 时钟驱动,低速通道(8 ~ 15)由 1MHz 时钟驱动。另外,每路 LED PWM 支持自动步进式地增加或减少占空比,可以用于 LED RGB 彩色梯度发生器。 | |
串口 | 初始化及打开串口 | | |
打印串口 | | ||
系统函数 | 获取机器运行时间 | | |
另外我们还可以先把端口设置成一个变量,下次可以通过变量来配置IO口,这样可以增加程序的可读性。
比如:
int LED = 2;//
pinMode(LED,OUTPUT);
1.2 中断
简单来说,当触发中断,就会执行中断的函数,而这个中断的触发方式就有低电平触发、高电平触发等等,一般来常用来检测按键、或者一些触发信号。
怎么理解呢,就是说中断就是无论你主程序跑到哪里,一旦检测到中断信号,它就得从主程序中跳出来,先执行你的中断函数,完了之后再回到之前主程序跳出来的位置继续执行主程序。
外部中断:
不同的开发板触发中断的引脚都不一样,一般至少有两个外部中断,需要根据自己开发板来做设置。
void attachInterrupt (uint8_t interruptNum, void(*)(void)userFunc, int mode);/*设置中断
指定中断函数. 外部中断有0和1两种, 一般对应2号和3号数字引脚.
参数:
interrupt 中断类型, 0或1
fun 对应函数
mode 触发方式. 有以下几种:
LOW 低电平触发中断
CHANGE 变化时触发中断
RISING 低电平变为高电平触发中断
FALLING 高电平变为低电平触发中断
*/
例子:
注解:
在中断函数中 delay 函数不能使用, millis 始终返回进入中断前的值. 读串口数据的话, 可能会丢失. 中断函数中使用的变量需要定义为 volatile 类型.
下面的例子如果通过外部引脚触发中断函数, 然后控制LED的闪烁.
int pin = 13;
volatile int state = LOW;
void setup()
{
pinMode(pin, OUTPUT);
attachInterrupt(0, blink, CHANGE);
}
void loop()
{
digitalWrite(pin, state);
}
void blink()
{
state = !state;
}
定时中断:
定时中断涉及的内容就比较多也比较复杂,后面单开来做笔记,有两种定时器,一种是硬件定时器,一种是软件定时器。
硬件定时器:简而言之通过自带的时钟信号计时,精确度较高,不受代码干扰,用于高精度和实时性的定时任务,例如 PWM 输出、捕获输入脉冲等。
软件定时器:软件定时器简单来说(个人理解)就是通过软件来识别时间,类似与跑这段代码大致需要1ms,程序估会算各个代码的时间来确定当前的时间间隔。精度相对没那么高,对于高速型号等会产生不少误差。
,简单来概述下硬件定时器的使用步骤:详细看这里https://docs.geeksman.com/esp32/Arduino/16.esp32-arduino-timer.html#_1-%E7%A1%AC%E4%BB%B6%E5%AE%9A%E6%97%B6%E5%99%A8
- 初始化定时器:使用
timerBegin()
函数初始化所需的硬件定时器; - 注册中断处理函数:使用
timerAttachInterrupt()
函数将中断处理函数与定时器关联起来,简而言之就是触发定时中断之后你要他做什么; - 设置定时器模式:使用
timerAlarmWrite()
,设置触发一次,还是周期性触发; - 启动定时器:使用
timerAlarmEnable()
函数启动定时器,使其开始计数。
实例:
#define LED 2
#define LED_ONCE 4
hw_timer_t *timer = NULL;
hw_timer_t *timer_once=NULL;
// 定时器中断处理函数
void timer_interrupt(){
digitalWrite(LED, !digitalRead(LED));
}
void timer_once_interrupt() {
digitalWrite(LED_ONCE, !digitalRead(LED_ONCE));
}
void setup() {
pinMode(LED, OUTPUT);
pinMode(LED_ONCE, OUTPUT);
// 初始化定时器
timer = timerBegin(0,80,true);
timer_once = timerBegin(1, 80, true);
// 配置定时器
timerAttachInterrupt(timer,timer_interrupt,true);
timerAttachInterrupt(timer_once, timer_once_interrupt, true);
// 定时模式,单位us,只触发一次
timerAlarmWrite(timer,1000000,true);
timerAlarmWrite(timer_once, 3000000, false);
// 启动定时器
timerAlarmEnable(timer);
timerAlarmEnable(timer_once);
}
void loop() {
}
1.2 一些小技巧
在编程过程中可以通过串口将某些信息打印出来,由于ESP32开发板的内部串口与调试烧录串口是复用的,可以直接调用。比如:
#define POT 26
// 初始化电位计输入信号
int pot_value;
void setup() {
// 设置串口通信波特率 9600
Serial.begin(9600);
pinMode(POT, INPUT);
}
void loop() {
// 读取电位计模拟输入值
pot_value = analogRead(POT);
// 打印模拟值在串口屏上
Serial.println(pot_value);
delay(50);
}
1.3 PWM
这里单开一章来说明下。PWM主要属性无非就是:频率,占空比。这两个核心参数,对于一些精度要求高的还有一个关键属性就是分辨率。分辨率简而言之就是,将方波分为多少份,如8位,就是2^8=256,就是你调节占空比的时候最小可以调节1/256格,100%占空比就是256格。
初始函数如下,下面的channel是指PWM的通道,不是指具体的IO脚,譬如ESP32内部有16个通道的PWM,它可以输出16个PWM。分为高低速两组,高速通道(0 ~ 7)由 80MHz 时钟驱动,低速通道(8 ~ 15)由 1MHz 时钟驱动。
实现步骤如下:
使用 ledcSetup;//(通道,频率,分辨率) 函数建立 LEDC 通道,设置频率、分辨率
通过 ledcAttachPin(Pin脚号); //将 GPIO 口与 LEDC 通道关联;
通过 ledcWrite(通道,占空比); //设置占空比,注意这里的占空比不是0~100%,是根据你的分辨率来换算,譬如分辨率是8也就是256格,想要10%,占空比这里写入25
相关的函数说明如下:
可以在<esp32_hal_led.h>中找到。
// 设置 LEDC 通道对应的频率和计数位数(占空比分辨率),返回最终频率
// 分辨率的意思就是把一个周期分成 2 的 resolution_bits 份。
uint32_t ledcSetup(uint8_t channel, uint32_t freq, uint8_t resolution_bits);
// 指定通道输出一定占空比波形
void ledcWrite(uint8_t channel, uint32_t duty);
// 类似于 arduino 的 tone ,当外接无源蜂鸣器的时候可以发出某个声音(根据频率不同而不同)
uint32_t ledcWriteTone(uint8_t channel, uint32_t freq);
// 该方法是上面方法的进一步封装,可以直接输出指定调式和音阶声音的信号
uint32_t ledcWriteNote(uint8_t channel, note_t note, uint8_t octave);
// 返回指定通道占空比的值
uint32_t ledcRead(uint8_t channel);
// 返回指定通道当前频率(如果当前占空比为0 则该方法返回0)
uint32_t ledcReadFreq(uint8_t channel);
// 将 LEDC 通道绑定到指定 IO 口上以实现输出
void ledcAttachPin(uint8_t pin, uint8_t channel);
// 解除 IO 口的 LEDC 功能
void ledcDetachPin(uint8_t pin);
#define FREQ 2000 // 频率
#define CHANNEL 0 // 通道
#define RESOLUTION 8 // 分辨率
#define LED 12 // LED 引脚
void setup()
{
ledcSetup(CHANNEL, FREQ, RESOLUTION); // 设置通道
ledcAttachPin(LED, CHANNEL); // 将通道与对应的引脚连接
}
void loop()
{
// 逐渐变亮
for (int i=0;i<pow(2, RESOLUTION); i++)
{
ledcWrite(CHANNEL, i); // 输出PWM
delay(5);
}
// 逐渐变暗
for (int i=pow(2, RESOLUTION)-1;i>=0;i--)
{
ledcWrite(CHANNEL, i); // 输出PWM
delay(5);
}
}
//这里通过改变占空比的值实现的呼吸灯效果