基于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温湿度传感器