一.定时器代码如下:
#include <Arduino.h>
hw_timer_t *timer = NULL;
int interruptCounter = 0;
// 函数名称:onTimer()
// 函数功能:中断服务的功能,它必须是一个返回void(空)且没有输入参数的函数
// 为使编译器将代码分配到IRAM内,中断处理程序应该具有 IRAM_ATTR 属性
// https://docs.espressif.com/projects/esp-idf/zh_CN/release-v4.3/zesp32/api-reference/storage/spi_flash_concurrency.html
void IRAM_ATTR TimerEvent()
{
Serial.println(interruptCounter++);
if (interruptCounter > 5)
{
interruptCounter = 1;
}
}
void setup() {
Serial.begin(115200);
// 函数名称:timerBegin()
// 函数功能:Timer初始化,分别有三个参数
// 函数输入:1. 定时器编号(0到3,对应全部4个硬件定时器)
// 2. 预分频器数值(ESP32计数器基频为80Mhz,80分频得到1Mhz 单位是微秒)1Mhz=1/1000000,单位是微秒
// 3. 计数器向上(true)或向下(false)计数的标志
// 函数返回:一个指向 hw_timer_t 结构类型的指针
timer = timerBegin(0, 80, true);
// 函数名称:timerAttachInterrupt()
// 函数功能:绑定定时器的中断处理函数,分别有三个参数
// 函数输入:1. 指向已初始化定时器的指针(本例子:timer)
// 2. 中断服务函数的函数指针
// 3. 表示中断触发类型是边沿(true)还是电平(false)的标志
// 函数返回:无
timerAttachInterrupt(timer, &TimerEvent, true);
// 函数名称:timerAlarmWrite()
// 函数功能:指定触发定时器中断的计数器值,分别有三个参数
// 函数输入:1. 指向已初始化定时器的指针(本例子:timer)
// 2. 第二个参数是触发中断的计数器值(1000000 us -> 1s)
// 3. 定时器在产生中断时是否重新加载的标志
// 函数返回:无
timerAlarmWrite(timer, 1000000, true);
timerAlarmEnable(timer); // 使能定时器
}
void loop() {
}
代码功能为:用定时器,每隔一秒触发中断,在串口打印interruptCounter的值
结果为(过一会再打开串口,不然会重启):
二.PWM代码如下:
#include <Arduino.h>
/*
* LEDC Chan to Group/Channel/Timer Mapping
** ledc: 0 => Group: 0, Channel: 0, Timer: 0
** ledc: 1 => Group: 0, Channel: 1, Timer: 0
** ledc: 2 => Groupiimer: 1
** ledc: 4 => Group: 0, Channel: 4, Timer: 2
** ledc: 5 => Group: 0, Channel: 5, Timer: 2
** ledc: 6 => Group: 0, Channel: 6, Timer: 3
** ledc: 7 => Group: 0, Channel: 7, Timer: 3
** ledc: 8 => Group: 1, Channel: 0, Timer: 0
** ledc: 9 => Group: 1, Channel: 1, Timer: 0
** ledc: 10 => Group: 1, Channel: 2, Timer: 1
** ledc: 11 => Group: 1, Channel: 3, Timer: 1
** ledc: 12 => Group: 1, Channel: 4, Timer: 2
** ledc: 13 => Group: 1, Channel: 5, Timer: 2
** ledc: 14 => Group: 1, Channel: 6, Timer: 3
** ledc: 15 => Group: 1, Channel: 7, Timer: 3
*/
// 绑定的IO
const int PWM_Pin = 2;
// PWM的通道,共16个(0-15),分为高低速两组,
// 高速通道(0-7): 80MHz时钟,低速通道(8-15): 1MHz时钟
// 0-15都可以设置,只要不重复即可,参考上面的列表
// 如果有定时器的使用,千万要避开!!!
const int LEDC_PWM_Channel = 0;//低速高速最后得到的占空比都一样,没差别,可能80MHz计数更快一点?
// PWM频率,直接设置即可(就是周期,一个周期有多少时间,1000hz代表1ms)
int Base_freq_PWM = 1000;
// PWM分辨率,取值为 0-20 之间,这里填写为10,那么后面的ledcWrite
// 这个里面填写的pwm值就在 0 - 2的10次方 之间 也就是 0-1024
int Freq_resolution_PWM = 10;
void setup() {
pinMode(PWM_Pin, OUTPUT);
ledcSetup(LEDC_PWM_Channel, Base_freq_PWM, Freq_resolution_PWM); // 设置通道
ledcAttachPin(PWM_Pin, LEDC_PWM_Channel); //将 LEDC 通道绑定到指定 IO 口上以实现输出
}
void loop() {
ledcWrite(LEDC_PWM_Channel, 200);//200就是占空比,200/1000*100% = 20%
}
运行结果:周期1ms,高电平0.2ms,占空比20%,通道是8通道,属于低速通道(8-15): 1MHz时钟,周期1ms,高电平0.2ms,占空比20%,通道是8通道,属于高速通道(0-7): 80MHz时钟。低速时钟或者高速时钟最后得到的占空比都一样,没差别,可能80MHz计数更快一点?若200改为1024,就都是高电平。
上图:周期为1ms
上图:占空比为20%,0.2/1 *100% = 20%
再举一个例子:周期为20ms,高电平持续时间为1ms,占空比为0.05。
f=1/T=1/(20/1000)=50,T的单位为秒,所以要将ms转为s。
占空比=duty/T ===》 duty = 2.5
代码如下:
结果为(正确):
三.扩展知识
1、Speed Mode
LED PWM 控制器高速和低速模式,高速模式的优点是可平稳地改变定时器设置。
意思就是说高速模式下,如果定时器的设置发生了改变,那么在下一次定时器的溢出中断中就会自动改变;但是低速模式下不会自动改变的。
2、频率和占空比分辨率支持范围:
这个先了解一下占空比和分辨率
占空比:就是高电平接通时间与周期的比
例如:一个PWM频率为1000hz,那周期是1ms,如果高电平时间是100us,那么占空比就是100us:1ms=1:10;
分辨率:就是占空比的最小值
是根据PWM的位数计算的,1:2^位数,如果位数是8,那么PWM的分辨率就是1:255,要是想要达到这个分辨率那就要计数器从0计算到255才行,如果计数值太小,那么他的分辨率就达不到1:255,那PWM的输出频率就变高了。
对于esp32控制器 PWM 占空比设置的分辨率范围较广。比如,PWM 频率为 5 kHz 时,占空比分辨率最大可为 13 位。这意味着占空比可为 0 至 100% 之间的任意值,分辨率为 ~0.012%(1/(2 * 13 )= 1/8192)。PWM 频率越高,占空比分辨率越低
四.呼吸灯
代码如下:
#include <Arduino.h>
/*
* LEDC Chan to Group/Channel/Timer Mapping
** ledc: 0 => Group: 0, Channel: 0, Timer: 0
** ledc: 1 => Group: 0, Channel: 1, Timer: 0
** ledc: 2 => Groupiimer: 1
** ledc: 4 => Group: 0, Channel: 4, Timer: 2
** ledc: 5 => Group: 0, Channel: 5, Timer: 2
** ledc: 6 => Group: 0, Channel: 6, Timer: 3
** ledc: 7 => Group: 0, Channel: 7, Timer: 3
** ledc: 8 => Group: 1, Channel: 0, Timer: 0
** ledc: 9 => Group: 1, Channel: 1, Timer: 0
** ledc: 10 => Group: 1, Channel: 2, Timer: 1
** ledc: 11 => Group: 1, Channel: 3, Timer: 1
** ledc: 12 => Group: 1, Channel: 4, Timer: 2
** ledc: 13 => Group: 1, Channel: 5, Timer: 2
** ledc: 14 => Group: 1, Channel: 6, Timer: 3
** ledc: 15 => Group: 1, Channel: 7, Timer: 3
*/
// 绑定的IO
const int PWM_Pin = 2;
int i = 1;
// PWM的通道,共16个(0-15),分为高低速两组,
// 高速通道(0-7): 80MHz时钟,低速通道(8-15): 1MHz时钟
// 0-15都可以设置,只要不重复即可,参考上面的列表
// 如果有定时器的使用,千万要避开!!!
const int LEDC_PWM_Channel = 0;//低速时钟或者高速时钟最后得到的占空比都一样,没差别,可能80MHz计数更快一点?
// PWM频率,直接设置即可(就是周期,一个周期有多少时间,1000hz代表1ms)
int Base_freq_PWM = 50;
// PWM分辨率,取值为 0-20 之间,这里填写为10,那么后面的ledcWrite
int Freq_resolution_PWM = 6;
void setup() {
pinMode(PWM_Pin, OUTPUT);
ledcSetup(LEDC_PWM_Channel, Base_freq_PWM, Freq_resolution_PWM); // 设置通道
ledcAttachPin(PWM_Pin, LEDC_PWM_Channel); //将 LEDC 通道绑定到指定 IO 口上以实现输出
}
void loop() {
for(i = 1; i <= 50;i++)
{
ledcWrite(LEDC_PWM_Channel, i);
delay(20);
}
for(; i > 0;i--)
{
ledcWrite(LEDC_PWM_Channel, i);
delay(20);
}
}
五.传统的PWM
1、analogWrite(pin,dutyCycle)
// 引脚命名
# define analogPin 3
void setup()
{
pinMode(analogPin,OUTPUT);
}
void loop()
{
analogWrite(analogPin,100); // 输出PWM,占空比为 100/255
}
2、手动实现 PWM
通过
delayMicroseconds()
手动实现频率可调的 PWM,也被称作数字IO轮转法,使用方法:
- 两次的digitalWrite输出状态必须相反;
- 可以用delay()实现毫秒级延迟,用delayMicroseconds()实现微秒级延迟。
void setup()
{
pinMode(8, OUTPUT); // 设置8号引脚为输出模式
}
void loop()
{
digitalWrite(8, HIGH);
delayMicroseconds(100); // 输出PWM,占空比为100/1000=10%
digitalWrite(8, LOW);
delayMicroseconds(1000 - 100); // 修改这里的1000可以调整频率,总周期为1000us,所以频率为1000Hz.
}
上面这段代码会产生一个PWM=0.1的,周期为1ms的方波(1kHz),这种方式的优缺点很明显:
- PWM的比例可以更精确;
- 周期和频率可控制;
- 所有的pin脚都可以输出,不局限于那几个脚;
- 缺点:CPU干不了其他事情了;