ESP32的LED_PWM实现呼吸灯效果

          点亮一盏led灯再简单不过了,貌似没什么好写的,但今天要说的灯涉及到了一个常用的原理那就是pwm。乐鑫的ESP32的esp_idf实现了一个呼吸灯的接口;我按着接口写了个小程序去实现闪烁和呼吸的效果,但调了半天才调出了想要的呼吸效果;故作此笔记;先来说下几个简单的概念;

PWM:Pulse Width Modulation 的缩写,即脉冲宽度调制;它的作用可以简单的说使用数字信号来实现模拟信号;在这假设pwm的周期是100ms,那么他的频率就是10hz,每个周期内会有高电平和低电平即0和1,这0和1在一个周期内的所占的时间比可以不同也可以相同,这就是pwm 的存在意义或者说本质;

占空比 :高电平时间占整个周期的比例;比如1占50ms,0占50ms那上例的占空比就是50%了。

esp32的呼吸灯就是基于pwm原理实现,只不过牛人写的底层接口代码就是难懂,但仔细看也很好理解;ESP32的LED_PWM主要用于控制 LED 的亮度和颜色,也可以产生 PWM 信号用于其他用途,有16路通道,能够产生独立的数字波形来驱动led设备,呼吸灯的效果就是通过该PWM 控制器自动逐渐增加或减少占空比,在无须处理器干预的情况下实现亮度和颜色渐变,这就是呼吸灯的来源了。

ESP32的LED_PWM实现呼吸灯效果_第1张图片

以上是官方的pwm 实现原理,一句话:系统时钟源xCLK--->分频器Divider---->计数器Counter--->比较器--->输出的pwm信号sig-out. 其中分频器的输出时钟作为计数器的基准时钟,该分频器为18位高A10位整数部分,B低8位小数部分,所以分频系数计算公式:

LEDC_CLK_DIV _NUM_HSTIMERx = A*B/256

再者就是计数器,计数范围由 LEDC_HSTIMERx_DUTY_RES 进行配置每次计数达到最大值 2 ^LEDC_HSTIMERx_DUTY _RES −1 时,产生溢出中断,并且计数值回归到 0。因此pwm的频率就可以确定(请注意下面的“!”号,它实现两个时钟源的二选一):

      顺便说下那两个比较器high_level_comparator和low_level_comparator,当计数器的值达到 hpoint 时,输出信号翻转为高电平, hpoint,由 LEDC_HPOINT_HSCHn 配置。当计数器的值等于 lpoint 时,输出信号翻转为低电平, 其中lpoint,由以下几个寄存器LEDC_DUTY_HSCHn,LEDC_DUTY_START_HSCHn,LEDC_DUTY_INC_HSCHn,LEDC_DUTY_NUM_HSCHn 以及 LEDC_DUTY_SCALE_HSCHn 共同决定,请注意这几个寄存器的配置可实现渐变占空比,呼吸灯就依赖它们了。

至此pwm 的实现原理就说得差不多了,所以的pwm基本都这套路,大学时的51和stm32都是这么玩的了。下面是我随意写的一个呼吸灯程序。

esp_err_t led_custom_breathing(gpio_num_t gpio_num){
    esp_err_t ret = ESP_OK;
    ledc_channel_config_t ledc_conf;
    ledc_timer_config_t ledc_timer_conf;
    printf("led_custom led init!\n");
    ledc_conf.gpio_num=gpio_num;
    ledc_conf.speed_mode=LEDC_HIGH_SPEED_MODE;
    ledc_conf.intr_type=LEDC_INTR_DISABLE;
    ledc_conf.timer_sel=LEDC_TIMER_0;
    ledc_conf.duty=2047;//在此控制亮度比例 占空比=duty/duty_resolution :一个周期内高电平所占 
                        //的比例 脉宽=占空比/频率 脉宽:脉宽是在一个周期内高电平所占的时间;
    ledc_conf.channel=LEDC_CHANNEL_0;

	ledc_timer_conf.timer_num=LEDC_TIMER_0;
	ledc_timer_conf.speed_mode=LEDC_HIGH_SPEED_MODE;
	ledc_timer_conf.duty_resolution=LEDC_TIMER_11_BIT;
	ledc_timer_conf.freq_hz=500;//一秒内的周期数,所以要达到2047个周期数大概需要4秒,即最亮;

	if(ledc_timer_config(&ledc_timer_conf)){
	  printf("ledc_timer_config failled!!!\n");
	  ret = ESP_FAIL;
	}
	
	if(ESP_OK!=ledc_channel_config(&ledc_conf)){
	  printf("ledc_channel_config failled!!!\n");
	  ret = ESP_FAIL;
	}
	max_fade_time_ms=max_fade_time_ms;

	ledc_fade_func_install(0);
	while(1){//实现呼吸效果
    	ledc_set_fade_with_step(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 2047, 1, 1);
    	ledc_fade_start(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 1);
    	ledc_set_fade_with_step(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 0, 1, 1);
    	ledc_fade_start(LEDC_HIGH_SPEED_MODE, LEDC_CHANNEL_0, 1);
    	vTaskDelay(500 / portTICK_RATE_MS);
    	printf(" led_breathing !!\n");
	}
	return ret;
}

      不管呼吸灯还是闪烁灯都需要先配置定时器ledc_timer_config和通道ledc_channel_config,而且顺序还不能反,因为仔细看ledc_channel_config()函数它是要配置定时器的,就是所谓的依赖关系,很多函数与函数之间都有这种依赖关系,搞反了不是报错就是没法实现特定功能。这里还需注意两个参数ledc_timer_conf.freq_h和ledc_conf.duty,这两个参数的数值设置不好呼吸灯就成了常亮或闪烁。

此外要实现呼吸先ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, int scale, int cycle_num);再ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_fade_mode_t wait_done);当duty达到target_duty时duty保持直到你再次设置ledc_set_fade_with_step;其中scale是duty的增长或减少方向;注意这里当ledc_set_fade_with_step之后必须ledc_fade_start;所以在循坏里就形成了呼吸灯;实现呼吸灯其实还有很多种方式,这里只讲了其中的一种,原理都是一样的,代码实现的比较粗略献丑啦。

 

 

 

你可能感兴趣的:(学习日记)