【ESP32】ESP-IDF开发 | LED PWM控制器+呼吸灯例程

news2024/10/24 21:23:10

1. 简介

        LED PWM控制器,简称LEDC,主要用于控制LED的亮度和颜色,当然也可以当作普通的PWM进行使用。

        LEDC有16路通道8路高速通道和8路低速通道,16路通道都能够产生独立的数字波形来驱动LED设备。高速和低速通道分别有8个输出通道,每8个通道共享4个定时器,如下图。

        LEDC能够在无须处理器干预的情况下自动逐渐增加或减少占空比,实现亮度和颜色渐变。同时LEDC还支持小数级别的分频

2. 结构

        上面是LEDC一个高速通道的简单框图,分为定时器和通道控制器两部分。

2.1 定时器

        定时器接收外部时钟,可以选择外部参考时钟和APB时钟,接着会经过预分频器进行时钟分频,分频后的时钟会负责驱动一个20bit的计数器

        定时器的预分频器是支持小数级别的分频操作的,它的分频系数寄存器有18位,其中高10位表示整数部分(A),低8位表示小数部分(B),分频满足公式:

DIV=A+\frac{B}{256}

         计数器的计数范围由占空比精度(DUTY_RES)决定,当计数值达到2^{DUTY\_RES-1}时计数器溢出,并重新从0开始计数。

        结合前面提到的参数,我们可以得出定时器的输出频率公式:

f_{sig\_out}=\frac{f_{LEDC\_CLK}}{CLK\_DIV\cdot 2^{DUTY\_RES}}

        通过上面公式,可以推导出占空比精度公式:

DUTY\_RES=log_{2}(\frac{f_{LEDC\_CLK}}{f_{sig_out}\cdot CLK\_DIV}) 

        其实只需要记住,LEDC的输出频率越高,那么占空比的精度就会越低。就比如,输出频率设置为20MHz,占空比精度就只有2位,只能实现25%的倍数的占空比。

 2.2 通道控制器

        通道控制器接收计数器的每一次脉冲更新,将计数器寄存器的值跟内部的比较寄存器的值进行比较,控制高电平和低电平的输出。

         通道控制器内部有2个比较器——高电平比较器和低电平比较器。由上图可以看到,当计数器的值大于高电平比较器值、低于低电平比较器值时才输出高电平,否则输出低电平。

        前面说到,LEDC可以自动更改占空比,实现原理如下图。

        更改占空比是通过改变低电平比较器的值实现的,另外,通过配置相关寄存器可以设置占空比更新的频率、次数和方向。 

        但在使用该功能时要注意,一旦启动了占空比的自动更新,那么必须等到它更新完成才能修改其参数

3. 函数

3.1 定时器初始化

esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf);
  • timer_conf:定时器初始化结构体。
typedef struct {
    ledc_mode_t speed_mode;  // 高速或低速模式
    ledc_timer_bit_t duty_resolution;  // 占空比分辨率
    ledc_timer_t  timer_num;  // 定时器序号
    uint32_t freq_hz;  // 输出频率
    ledc_clk_cfg_t clk_cfg;  // 时钟源选择
    bool deconfigure;  // 是否去初始化
} ledc_timer_config_t;

3.2 通道初始化

esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf);
  • ledc_conf:通道初始化结构体。
typedef struct {
    int gpio_num;  // 输出GPIO口
    ledc_mode_t speed_mode;  // 高速或低速模式
    ledc_channel_t channel;  // 输出通道序号
    ledc_intr_type_t intr_type;  // 中断类型
    ledc_timer_t timer_sel;  // 定时器序号
    uint32_t duty;  // 占空比
    int hpoint;  // 高电平比较器值
    struct {
        unsigned int output_invert: 1;  // 输出极性反转
    } flags;
} ledc_channel_config_t;

3.3 初始化渐变功能

esp_err_t ledc_fade_func_install(int intr_alloc_flags);
  • intr_alloc_flags:中断分配标志,默认为0。

3.4 注册事件回调

esp_err_t ledc_cb_register(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_cbs_t *cbs, void *user_arg);
  • speed_mode:速度模式;
  • channel:输出通道;
  • cbs: 回调函数结构体;
typedef bool (*ledc_cb_t)(const ledc_cb_param_t *param, void *user_arg);

typedef struct {
    ledc_cb_t fade_cb;  // 渐变结束回调
} ledc_cbs_t;
  • user_arg:用户回调函数参数。

3.5 注册中断回调

esp_err_t ledc_isr_register(void (*fn)(void*), void *arg, int intr_alloc_flags, ledc_isr_handle_t *handle);
  • fn:回调函数;
  • arg:回调函数用户参数;
  • intr_alloc_flags:中断分配标志,默认为0;
  • handle:中断句柄。

3.6 根据时长配置渐变

esp_err_t ledc_set_fade_with_time(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, int max_fade_time_ms);
  • speed_mode:速度模式;
  • channel:输出通道;
  • target_duty:目标占空比;
  • max_fade_time_ms:渐变时长,单位毫秒。

3.7 根据步数配置渐变

esp_err_t ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, uint32_t scale, uint32_t cycle_num);
  • speed_mode:速度模式;
  • channel:输出通道;
  • target_duty:目标占空比;
  • scale:占空比更新跨步,即更新前后占空比的差值;
  • cycle_num:占空比更新周期,即当前占空比输出多少次才更新下一个占空比。

3.8 启动渐变更新

esp_err_t ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_fade_mode_t fade_mode);
  • speed_mode:速度模式;
  • channel:输出通道;
  •  fade_mode:渐变模式,阻塞或非阻塞模式。

3.9 寻找最大占空比分辨率

uint32_t ledc_find_suitable_duty_resolution(uint32_t src_clk_freq, uint32_t timer_freq);
  • src_clk_freq:时钟源频率;
  •  timer_freq:定时器频率。

4. 例程

        例程中实现了一个呼吸灯,由熄灭到最亮为3秒,反过来也是3秒的时间。

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_log.h"
#include "driver/ledc.h"

#define TAG "app"

#define LEDC_SPEED_MODE LEDC_HIGH_SPEED_MODE
#define LEDC_CHANNEL LEDC_CHANNEL_0

static uint8_t cnt;
static SemaphoreHandle_t mutex;


static IRAM_ATTR bool cb_ledc_fade_end_event(const ledc_cb_param_t *param, void *user_arg)
{
    BaseType_t taskAwoken = pdFALSE;

    if (param->event == LEDC_FADE_END_EVT) {
        xSemaphoreTakeFromISR(mutex, &taskAwoken);
        ledc_set_fade_with_time(LEDC_SPEED_MODE, LEDC_CHANNEL, cnt == 0 ? 0 : 5000, 3000);
        ledc_fade_start(LEDC_SPEED_MODE, LEDC_CHANNEL, LEDC_FADE_NO_WAIT);
        cnt = (cnt + 1) % 2;
        xSemaphoreGiveFromISR(mutex, &taskAwoken);
    }

    return (taskAwoken == pdTRUE);
}

void app_main()
{
    /* 初始化定时器 */
    ledc_timer_config_t ledc_timer = {
        .duty_resolution = LEDC_TIMER_13_BIT,  // 13位分辨率
        .freq_hz = 5000,  // 5kHz
        .speed_mode = LEDC_SPEED_MODE,  // 高速模式
        .timer_num = LEDC_TIMER_0,  // 定时器0
        .clk_cfg = LEDC_AUTO_CLK,  // 自动时钟源,默认为APB时钟
    };
    ledc_timer_config(&ledc_timer);

    /* 初始化输出通道 */
    ledc_channel_config_t ledc_channel = {
        .channel = LEDC_CHANNEL,  // 通道0
        .duty = 0,
        .gpio_num = 2,
        .speed_mode = LEDC_SPEED_MODE,  // 高速模式
        .hpoint = 0,
        .timer_sel = LEDC_TIMER_0,  // 定时器0
        .intr_type = LEDC_INTR_DISABLE,  // 不使用中断
        .flags.output_invert = 0  // 输出不反转
    };
    ledc_channel_config(&ledc_channel);

    /* 初始化渐变模式 */
    mutex = xSemaphoreCreateMutex();
    ledc_fade_func_install(0);
    ledc_cbs_t callbacks = {
        .fade_cb = cb_ledc_fade_end_event
    };
    ledc_cb_register(LEDC_SPEED_MODE, LEDC_CHANNEL, &callbacks, NULL);

    /* 使能渐变模式 */
    ledc_set_fade_with_time(LEDC_SPEED_MODE, LEDC_CHANNEL, 5000, 3000);
    ledc_fade_start(LEDC_SPEED_MODE, LEDC_CHANNEL, LEDC_FADE_NO_WAIT);
}

        首先初始化定时器,我设置的是高速模式,使用定时器0,占空比分辨率为13位,输出频率为5kHz,时钟源选择APB时钟(80MHz)。

        接着初始化输出通道,我的开发板上的LED是接在IO2上的,所以设置输出管脚为2,使用通道0,不使用中断,初始占空比和高电平比较器值都为0。

        我后面使用的是非阻塞的渐变模式,所以接下来先注册一个回调函数。回调函数的定义前要加上IRAM_ATTR宏定义,把函数放到内部RAM中,提高执行速度。回调函数里面就是设置渐变参数,并启动下一轮的渐变。通过cnt变量判断是从灭到亮还是从亮到灭渐变;因为cnt是全局变量,所以这里用了一个互斥量。这个函数返回是否有更高优先级的任务需要唤醒。

        最后就是配置渐变参数并启动渐变功能了。

        烧录程序到开发板,就能看到呼吸灯了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2211018.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++:模拟stack、queue

目录 容器适配器 定义 特点 deque deque的优势与缺点 选择deque作为stack和queue的底层默认容器的原因 模拟实现stack 模拟实现queue 容器适配器 定义 在C标准模板库(STL)中,容器适配器(Container Adapters)是…

Harmony开发基础

背景介绍 鸿蒙系统的开发一来是为了打破国外垄断,实现操作系统的国产化,另一方面是针对目前市场上出现大量新的移动设备,手表,折叠屏等,移动端程序要适配不同设备,需要维护一套代码下的多个版本&#xff0…

U盘直接拔掉之后数据丢失怎么恢复 U盘数据丢失了怎么恢复

U盘作为一种存储设备,可以帮助人们存储很多资料文件,无论是办公文件,亦或是生活中的照片,所以在存储数据文件时,人们是比较依赖U盘。不过,U盘也存在很多的不确定性,比如数据容易丢失、或者损坏。…

NirCmd-sendkeysendkeypress

引入script [Script File] This command allows you to execute a sequence of commands stored inside the file specified in [Script File] parameter. Example: script "c:\temp\msg.ncl" Example for a script:infobox "Hello !" "This is the …

微知-如何查看服务器CPU当前运行主频?(cat /sys/devices/system/cpu/cpu1/cpufreq/scaling_cur_freq)

关键命令 cat /sys/devices/system/cpu/cpu1/cpufreq/scaling_cur_freq背景 首先lscpu可以查看到有多个cpu,里面也会显示cpu的频率,但是这里显示仅仅是规格,不是实际值。为了查看实际值,需要到/sys文件系统中查看,也…

大数据-167 ELK Elasticsearch 详细介绍 特点 分片 查询

点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…

ARIMA 模型初体验 —— 预测股票数据

第 1 步,从 twelvedata 上获取苹果 11 号 15:30 到 16:00 的 OHLC、成交量 数据。 第 2 步,编写 Python 代码(实际上可以用 R 语言,R 语言从语言的级别对分析预测提供了支持,而 Python 需要第三方库)。 …

Yolov8 搭配 Frequency-aware Feature Fusion for Dense Image Prediction

个人觉得论文赞的地方 https://github.com/Linwei-Chen/FreqFusion https://www.arxiv.org/abs/2408.12879 因为我有个项目需要训练边界模糊的情况,但又需要目标能在模糊里凸显出来,就是看到这张图以后觉得很赞,边界变得清晰有特征是我想要的,所以尝试用了 使后感 哈哈…

【CSS3】css开篇基础(2)

1.❤️❤️前言~🥳🎉🎉🎉 Hello, Hello~ 亲爱的朋友们👋👋,这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章,请别吝啬你的点赞❤️❤️和收藏📖📖。如果你对我的…

数据结构-5.2.树的性质

一.树的常考性质: 性质1:结点数 总度数 1(结点的度:结点分支的数量) 一个分支中,如父结点B,两个子结点为E和F,结点B的度的值为2,等于子结点数量,加上这一个父结点(父结点只能有一…

【计算机网络 - 基础问题】每日 3 题(三十九)

✍个人博客:https://blog.csdn.net/Newin2020?typeblog 📣专栏地址:http://t.csdnimg.cn/fYaBd 📚专栏简介:在这个专栏中,我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话,欢迎点赞…

大型语言模型(LLMs)关键技术指南

在AI这个超火的领域,打好基础真的超级重要,尤其是当你开始研究那些超大型的语言模型,也就是LLMs的时候。这个指南就是想帮新手们把那些听起来高大上的概念变得简单易懂,比如神经网络、自然语言处理(NLP)还有…

【ROS2实操一】话题通信与自定义消息

一、准备工作 1.请先创建工作空间 mkdir -p ws01_plumbing/src //创建工作空间 colcon build //在工作空间目录下编译 2.创建专门的接口功能包定义接口文件(需要注意的是,目前为止无法在Python功能包中定义接口文件),终端下进…

线程基础学习

线程的实现 通过实现Runnable接口的方式,实现其中的run方法。继承Thread类,然后重写其中的run方法。通过线程池创建线程,默认采用DefaultThreadFactory。有返回值的callable,实现callable接口,实行call方法。 本质上…

wait和waitpid

在Linux中,wait 和 waitpid 是用于进程控制的系统调用,它们主要用来让父进程等待子进程的终止,并获取子进程的退出状态。下面详细介绍它们的用法和区别。 1. wait() 函数 wait() 会阻塞调用进程,直到一个子进程终止。它的典型用…

怎么ping网络ip地址通不通

怎么Ping网络IP地址通不通?要检查网络中的IP地址是否连通,可以使用‌Ping命令。Ping命令通过发送ICMP(Internet Control Message Protocol,因特网控制消息协议)Echo请求报文并等待回应,来判断目标主机是否可…

ARP限制网速攻击

ARP限制网速攻击 大家没想到吧,ARP还能限制对方网速。当kali欺骗了网关和受害者的时候,受害者访问网络就需要经过kali的网卡,那我们限制kali网卡的速度或者转发的速度就可以限制对方的网速。这里可以使用的工具有tc、iptables、WonderShaper…

Unix Standardization and Implementations

Unix标准化 在Unix未制定较为完备的标准时,各个平台的系统调用方式各异,所开发出的应用程序存在可移植性差的特点,因此人们呼吁指定一套Unix标准来规范接口,增加应用程序的可移植性。所谓Unix标准即适用于Unix环境下的一系列函数…

AI 时代技术盛宴 —— 稀土掘金 × 豆包 MarsCode 青训营等你来!

各位 CSDN 的小伙伴们,今天我要给大家带来一个超级棒的消息!稀土掘金与豆包 MarsCode 强强联手,共同打造的青训营再次上线啦! 在这个飞速发展的 AI 时代,这场青训营玩出了不一样的精彩。它携手 AI 伙伴豆包 MarsCode&…

一个项目用5款数据库?MySQL、PostgreSQL、ClickHouse、MongoDB区别,适用场景

文章目录 一、常用数据库概览1.1 关系型数据库1.2 非关系型数据库1.2.1 KV数据库1.2.2 文档型数据库1.2.3 列式存储数据库1.2.4 图数据库 1.3 SQL与NoSQL区别1.3.1 结构化与非结构化1.3.2 关联和非关联1.3.3 查询方式1.3.4 事务1.3.5 总结 二、MySQL三、PostgreSQL3.1 特点、适…