基于Freertos的ESP-IDF开发——7.WS2812B彩色灯循环

news/2024/11/22 0:50:11/

基于Freertos的ESP-IDF开发——7.WS2812B彩色灯循环

  • 0. 前言
  • 1. WS2812B简介
  • 2. 完整代码
  • 3. 演示效果
  • 4. 其他FreeRtos文章

0. 前言

本节使用WS2812B实现彩灯循环

开发环境:ESP-IDF 4.3
操作系统:Windows10 专业版
开发板:自制的ESP32-WROOM-32E

1. WS2812B简介

WS2812B彩灯是一种数字可编程LED灯,其中每个LED点都独立可编程控制灯光的颜色、亮度、饱和度、效果等多种参数,它的控制原理是基于控制芯片一般利用串行通讯协议和数据结构,实现对LED灯的控制。

WS2812B控制芯片内部集成了红、绿、蓝三个LED,在集成的控制电路中添加了DC-DC升压电路、正反器、调制驱动电路、信号整形电路、数据锁存控制电路等组成的数字控制电路,这些数字电路共同完成WS2812B的各项任务,其中信号整形电路主要用于提供时钟、数据的整形,实现输出数据的准确性。

通过设置不同的地址、端口和控制命令,可以实现对WS2812B灯的控制,其中控制命令是主控设备生成的,在通过PIN口发送时被WS2812B控制芯片使用,然后通过DC-DC升压电路将电流升压固定至5V,转换成直流电信号AP。在红、绿、蓝三个LED灯中,这些信号被分成三组,分别控制每个LED灯的亮度,改变灯的亮度,就可以实现不同的颜色、亮度、饱和度和效果的灯光表现。

根据以上原理,程序控制芯片就可以实现对灯的颜色、亮度、闪烁等控制,从而实现丰富的视觉效果和花样的变幻效果。

2. 完整代码

为了让你能够把它添加到main函数中,我没有使用编写库函数的方式。

#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <soc/rmt_struct.h>
#include <soc/dport_reg.h>
#include <driver/gpio.h>
#include <soc/gpio_sig_map.h>
#include <esp_intr_alloc.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <driver/rmt.h>
#include <esp_system.h>
#include <nvs_flash.h>
#include "esp_log.h"#define TAG	"WS2812B"
#define delay_ms(ms) vTaskDelay((ms) / portTICK_RATE_MS)#define WS2812_PIN				16			// WS2812 所连接的GPIO
#define DIVIDER					4			/* Above 4, timings start to deviate*/
#define DURATION				12.5		/* minimum time of a single RMT duration in nanoseconds based on clock */// 逻辑0波形
#define PULSE_T0H	(  350 / (DURATION * DIVIDER));
#define PULSE_T0L	(  900 / (DURATION * DIVIDER));
// 逻辑1波形
#define PULSE_T1H	(  900 / (DURATION * DIVIDER));
#define PULSE_T1L	(  350 / (DURATION * DIVIDER));
#define PULSE_TRS	(50000 / (DURATION * DIVIDER));#define MAX_PULSES	32			// 最大脉冲数
#define RMTCHANNEL	0			// RMT通道typedef union {struct {uint32_t duration0:15;uint32_t level0:1;uint32_t duration1:15;uint32_t level1:1;};uint32_t val;
} rmtPulsePair;
// RMT脉冲对#include <stdint.h>typedef unsigned int	uint32_t;typedef union {struct  {uint8_t r, g, b;};uint32_t num;
} rgbVal;extern void WS2812_Init(void);
extern void WS2812_SetColors(unsigned int length, rgbVal *array);inline rgbVal makeRGBVal(uint8_t r, uint8_t g, uint8_t b)
{rgbVal v;v.r = r;v.g = g;v.b = b;return v;
}static uint8_t *ws2812_buffer = NULL;
static unsigned int ws2812_pos, ws2812_len, ws2812_half;
static xSemaphoreHandle ws2812_sem = NULL;
static intr_handle_t rmt_intr_handle = NULL;
static rmtPulsePair ws2812_bits[2];void ws2812_initRMTChannel(int rmtChannel)
{RMT.apb_conf.fifo_mask = 1;  //enable memory access, instead of FIFO mode.RMT.apb_conf.mem_tx_wrap_en = 1; //wrap around when hitting end of bufferRMT.conf_ch[rmtChannel].conf0.div_cnt = DIVIDER;RMT.conf_ch[rmtChannel].conf0.mem_size = 1;RMT.conf_ch[rmtChannel].conf0.carrier_en = 0;RMT.conf_ch[rmtChannel].conf0.carrier_out_lv = 1;RMT.conf_ch[rmtChannel].conf0.mem_pd = 0;RMT.conf_ch[rmtChannel].conf1.rx_en = 0;RMT.conf_ch[rmtChannel].conf1.mem_owner = 0;RMT.conf_ch[rmtChannel].conf1.tx_conti_mode = 0;    //loop back mode.RMT.conf_ch[rmtChannel].conf1.ref_always_on = 1;    // use apb clock: 80MRMT.conf_ch[rmtChannel].conf1.idle_out_en = 1;RMT.conf_ch[rmtChannel].conf1.idle_out_lv = 0;
}// 将要发送的颜色信息
void ws2812_copy()
{unsigned int i, j, offset, len, bit;offset = ws2812_half * MAX_PULSES;ws2812_half = !ws2812_half;len = ws2812_len - ws2812_pos;if (len > (MAX_PULSES / 8)){len = (MAX_PULSES / 8);}if (!len) {for (i = 0; i < MAX_PULSES; i++)RMTMEM.chan[RMTCHANNEL].data32[i + offset].val = 0;return;}for (i = 0; i < len; i++) {bit = ws2812_buffer[i + ws2812_pos];for (j = 0; j < 8; j++, bit <<= 1) {RMTMEM.chan[RMTCHANNEL].data32[j + i * 8 + offset].val =ws2812_bits[(bit >> 7) & 0x01].val;}if (i + ws2812_pos == ws2812_len - 1){RMTMEM.chan[RMTCHANNEL].data32[7 + i * 8 + offset].duration1 = PULSE_TRS;}}for (i *= 8; i < MAX_PULSES; i++){RMTMEM.chan[RMTCHANNEL].data32[i + offset].val = 0;}ws2812_pos += len;return;
}void ws2812_handleInterrupt(void *arg)
{portBASE_TYPE taskAwoken = 0;if (RMT.int_st.ch0_tx_thr_event) {				// 发送事件中断ws2812_copy();					// RMT.int_clr.ch0_tx_thr_event = 1;			// 消除发送事件中断}else if (RMT.int_st.ch0_tx_end && ws2812_sem) {// 发送成功中断xSemaphoreGiveFromISR(ws2812_sem, &taskAwoken);RMT.int_clr.ch0_tx_end = 1;					// 清除发送完成中断}return;
}void WS2812_Init(void)
{DPORT_SET_PERI_REG_MASK(DPORT_PERIP_CLK_EN_REG, DPORT_RMT_CLK_EN);	// 设置RMT时钟使能DPORT_CLEAR_PERI_REG_MASK(DPORT_PERIP_RST_EN_REG, DPORT_RMT_RST);	// 清除RMT重启使能rmt_set_pin((rmt_channel_t)RMTCHANNEL, RMT_MODE_TX, (gpio_num_t)WS2812_PIN);// 设置RMT通道:0,模式:发送,引脚ws2812_initRMTChannel(RMTCHANNEL);					// 初始化RMT的0通道RMT.tx_lim_ch[RMTCHANNEL].limit = MAX_PULSES;		// 发送超过MAX_PULSES个脉冲会产生中断RMT.int_ena.ch0_tx_thr_event = 1;					// 发送事件标志位置1RMT.int_ena.ch0_tx_end = 1;							// 发送完成标志位置1// 配置ws2812的逻辑电平长度和定义ws2812_bits[0].level0 = 1;ws2812_bits[0].level1 = 0;ws2812_bits[0].duration0 = PULSE_T0H;ws2812_bits[0].duration1 = PULSE_T0L;ws2812_bits[1].level0 = 1;ws2812_bits[1].level1 = 0;ws2812_bits[1].duration0 = PULSE_T1H;ws2812_bits[1].duration1 = PULSE_T1L;// ESP分配中断(中断源,标志位,中断处理函数,传入参数,中断句柄)esp_intr_alloc(ETS_RMT_INTR_SOURCE, 0, ws2812_handleInterrupt, NULL, &rmt_intr_handle);
}void WS2812_SetColors(unsigned int length, rgbVal *array)
{unsigned int i;ws2812_len = (length * 3) * sizeof(uint8_t);	// (颜色值长度3*8,三个字节) * 灯数量ws2812_buffer = malloc(ws2812_len);				// 申请(灯数*颜色*3)字节内存for (i = 0; i < length; i++) {					// 把N个灯的RGB颜色按顺序填入ws2812_buffer[0 + i * 3] = array[i].g;ws2812_buffer[1 + i * 3] = array[i].r;ws2812_buffer[2 + i * 3] = array[i].b;}ws2812_pos = 0;ws2812_half = 0;ws2812_copy();if (ws2812_pos < ws2812_len){ws2812_copy();}ws2812_sem = xSemaphoreCreateBinary();			// 创建一个二值信号量RMT.conf_ch[RMTCHANNEL].conf1.mem_rd_rst = 1;	// 设置此位,重置读取内存地址RMT.conf_ch[RMTCHANNEL].conf1.tx_start = 1;		// 设置此位,开始发送数据xSemaphoreTake(ws2812_sem, portMAX_DELAY);		// 获取二值信号量等待发送完成(ws2812_handleInterrupt)vSemaphoreDelete(ws2812_sem);					// 删除二值信号量ws2812_sem = NULL;								// 二值信号量设置空free(ws2812_buffer);							// 删除颜色值缓存return;
}typedef enum {GreenAdd,		// 绿色值+RedMinus,		// 红色值-BlueAdd,		// 蓝色值+GreenMinus,		// 绿色值-RedAdd,			// 红色值+BlueMinus,		// 蓝色值-
} color_change_t;// RGB灯彩虹效果,如果有多个灯串联可以看到彩虹效果
void WS2812_Rainbow_Task(void *pvParameters)
{const uint8_t anim_step = 10;		// 颜色值步进,0-255,每次变化1const uint8_t anim_max = 250;		// 最大值const uint8_t pixel_count = 6;		// 灯的数量(开发板只有一个,WS2812支持单总线串联控制)const uint8_t delay = 20;			// 单次变化间隔延时WS2812_Init();						// 初始化WS2812rgbVal color = makeRGBVal(anim_max, 0, 0);uint8_t step = 0;rgbVal color2 = makeRGBVal(anim_max, 0, 0);uint8_t step2 = 0;rgbVal *pixels;pixels = malloc(sizeof(rgbVal) * pixel_count);while (1) {color = color2;step = step2;for (uint8_t i = 0; i < pixel_count; i++) {pixels[i] = color;if (i == 1) {color2 = color;step2 = step;}switch (step) {case GreenAdd:color.g += anim_step;if (color.g >= anim_max)step++;break;case RedMinus:color.r -= anim_step;if (color.r == 0)step++;break;case BlueAdd:color.b += anim_step;if (color.b >= anim_max)step++;break;case GreenMinus:color.g -= anim_step;if (color.g == 0)step++;break;case RedAdd:color.r += anim_step;if (color.r >= anim_max)step++;break;case BlueMinus:color.b -= anim_step;if (color.b == 0)step = 0;break;}}WS2812_SetColors(pixel_count, pixels);// 写入颜色(灯数量,颜色值数组)ESP_LOGI(TAG, "Color Value R:%d G:%d B:%d",pixels[0].r,pixels[0].g,pixels[0].b);ESP_LOGI(TAG, "Color Value R:%d G:%d B:%d",pixels[1].r,pixels[1].g,pixels[1].b);ESP_LOGI(TAG, "Color Value R:%d G:%d B:%d",pixels[2].r,pixels[2].g,pixels[2].b);delay_ms(delay);}
}void app_main()
{xTaskCreate(WS2812_Rainbow_Task, "WS2812_Rainbow_Task", 4096, NULL, 10, NULL);return;
}

3. 演示效果

效果如下,这颗灯珠一如既往的刺眼。
在这里插入图片描述

4. 其他FreeRtos文章

基于Freertos的ESP-IDF开发——0.Windows下espidf的环境搭建
基于Freertos的ESP-IDF开发——1.HelloWorld
基于Freertos的ESP-IDF开发——2.点亮一颗LED
基于Freertos的ESP-IDF开发——3.使用任务(上)
基于Freertos的ESP-IDF开发——3.使用任务(中)
基于Freertos的ESP-IDF开发——3.使用任务(下)
基于Freertos的ESP-IDF开发——4.使用任务的方式来点亮LED灯
基于Freertos的ESP-IDF开发——5.使用按键[不带消抖、带消抖、长按短按识别]
基于Freertos的ESP-IDF开发——6.使用DHT1温湿度传感器


http://www.ppmy.cn/news/75996.html

相关文章

Python关于Pandas的iterrows、itertuples等遍历表格时读取不到第一行的问题

一、问题原因 df.iterrows() 是用来遍历 Pandas DataFrame 的方法&#xff0c;它会把 DataFrame 中的每一行转换成一个元组&#xff0c;其中第一个元素是行号&#xff0c;第二个元素是该行的数据。行号从 0 开始。 在使用 df.iterrows() 遍历 DataFrame 的时候发现表格第二行…

CMD与DOS脚本编程【第六章】

预计更新 第一章. 简介和基础命令 1.1 介绍cmd/dos脚本语言的概念和基本语法 1.2 讲解常用的基础命令和参数&#xff0c;如echo、dir、cd等 第二章. 变量和运算符 2.1 讲解变量和常量的定义和使用方法 2.2 介绍不同类型的运算符和运算规则 第三章. 控制流程和条件语句 3.1 介…

组合数学第二讲

可以把取出来的数从小到大排序&#xff0c;第一个数不变&#xff0c;第二个数1&#xff0c;以此类推... 总共的情况为&#xff0c;数字取完后可再依次减回去&#xff0c;保证数在100以内 k-element multisets 引出下面的二项式系数 binomial coefficients&#xff08;二项式系…

FAT NTFS Ext3文件系统有什么区别

10 年前 FAT 文件系统还是常见的格式&#xff0c;而现在 Windows 上主要是 NTFS&#xff0c;Linux 上主要是Ext3、Ext4 文件系统。关于这块知识&#xff0c;一般资料只会从支持的磁盘大小、数据保护、文件名等各种维度帮你比较&#xff0c;但是最本质的内容却被一笔带过。它们最…

Glob 文件匹配

前言 glob本质是Unix shell 风格的路径匹配规则。 该规则后续被其它语言支持。 ?&#xff1a;匹配一个任意字符 *&#xff1a;匹配任意个任意字符 [sequence]&#xff1a;匹配出现在sequence里面的一个字符 [!sequence]&#xff1a;匹配没有出现在sequence里面的一个字符 [a…

Spark大数据处理讲课笔记---Spark RDD典型案例

零、本节学习目标 利用RDD计算总分与平均分利用RDD统计每日新增用户利用RDD实现分组排行榜 一、利用RDD计算总分与平均分 &#xff08;一&#xff09;提出任务 针对成绩表&#xff0c;计算每个学生总分和平均分 &#xff08;二&#xff09;实现思路 读取成绩文件&#xff…

java实现url链接的补全,获取到的链接是以/或 ./ 开头的相对链接,不是以http开头的,需要补全

一、实现的目标 在使用爬虫获取网页html数据时,解析到的链接是/或./ 开头的相对链接,不是以http开头的链接,如:/picture/0/cca65350643c441e80d390ded3975db0.png 。此时需要完成对该链接的补全,以得到正确的链接。 二、实现思路 对比完整的url链接和相对链接,进行分析,…

自动化测试框架搭建步骤教程

说起自动化测试&#xff0c;我想大家都会有个疑问&#xff0c;要不要做自动化测试&#xff1f; 自动化测试给我们带来的收益是否会超出在建设时所投入的成本&#xff0c;这个嘛别说是我&#xff0c;即便是高手也很难回答&#xff0c;自动化测试的初衷是美好的&#xff0c;而测试…