1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
| /// @brief ws2812初始化
/// @param
void ws2812_init(void)
{
// 配置RMT TX通道参数
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // 选择默认的源时钟
.gpio_num = WS2812_PIN, // 设置GPIO引脚号
.mem_block_symbols = 64, // 增加内存块大小可减少LED闪烁
.resolution_hz = 10000000, // 设置分辨率
.trans_queue_depth = 4, // 设置后台可挂起的事务数量
};
// 创建RMT TX通道
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &led_chan));
// 配置LED灯带编码器参数
led_strip_encoder_config_t encoder_config = {
.resolution = 10000000, // 设置编码器分辨率
};
// 创建LED灯带编码器
ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&encoder_config, &led_encoder));
// 启用RMT TX通道
ESP_ERROR_CHECK(rmt_enable(led_chan));
// 配置RMT传输参数
tx_config.loop_count = 0; // 不进行传输循环
}
/// @brief ws2812显示单一颜色
/// @param colour 颜色
void ws2812_one_colour(uint32_t colour){
//制作灯带的像素数据
for (uint8_t i = 0; i < WS2812_NUM; i++)
{
led_strip_pixels[i * 3 + 0] = (colour >> 16) & 0xFF; // 提取绿色分量
led_strip_pixels[i * 3 + 1] = (colour >> 8) & 0xFF; // 提取红色分量
led_strip_pixels[i * 3 + 2] = colour & 0xFF; // 提取蓝色分量
}
// 将RGB值发送到LED灯带
ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_strip_pixels, sizeof(led_strip_pixels), &tx_config));
// 等待所有传输完成,并检查是否完成
ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, portMAX_DELAY));
}
/**
* @brief 彩虹灯
* @param light 亮度
*/
void ws2812_coloful(uint8_t light){
static uint32_t red = 0;
static uint32_t green = 0;
static uint32_t blue = 0;
static uint16_t hue = 0;
static uint16_t start_rgb = 0;
for (int i = 0; i < 3; i++) {
for (int j = i; j < WS2812_NUM; j += 3) {
hue = j * 360 / WS2812_NUM + start_rgb;
led_strip_hsv2rgb(hue, 100, light, &red, &green, &blue);
led_strip_pixels[j * 3 + 0] = green;
led_strip_pixels[j * 3 + 1] = blue;
led_strip_pixels[j * 3 + 2] = red;
}
ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_strip_pixels, sizeof(led_strip_pixels), &tx_config));
ESP_ERROR_CHECK(rmt_tx_wait_all_done(led_chan, portMAX_DELAY));
vTaskDelay(pdMS_TO_TICKS(WS2812_SPEED));
}
start_rgb += 5;
}
/**
* @brief 对 WS2812 LED 灯带数据进行编码
*
* 该函数负责将输入的 RGB 数据编码为适合 RMT 通道发送的信号,并在数据发送完成后发送重置信号。
*
* @param encoder 编码器句柄,指向 rmt_encoder_t 结构体
* @param channel RMT 通道句柄,用于发送编码后的数据
* @param primary_data 指向待编码的 RGB 数据的指针
* @param data_size 待编码的 RGB 数据的大小(字节)
* @param ret_state 指向编码状态变量的指针,用于返回编码的最终状态
* @return size_t 编码生成的 RMT 符号数量
*/
static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
// 通过基类指针获取 rmt_led_strip_encoder_t 结构体实例
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
// 获取字节编码器句柄
rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder;
// 获取复制编码器句柄
rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder;
// 初始化会话编码状态为重置状态
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
// 初始化最终编码状态为重置状态
rmt_encode_state_t state = RMT_ENCODING_RESET;
// 初始化编码生成的 RMT 符号数量为 0
size_t encoded_symbols = 0;
// 根据编码器的当前状态进行不同的编码操作
switch (led_encoder->state) {
case 0: //// 发送 RGB 数据
// 调用字节编码器对 RGB 数据进行编码,并累加生成的 RMT 符号数量
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state);
// 检查当前编码会话是否完成
if (session_state & RMT_ENCODING_COMPLETE) {
// 若完成,切换到下一个状态(发送重置信号)
led_encoder->state = 1;
}
// 检查 RMT 内存是否已满
if (session_state & RMT_ENCODING_MEM_FULL) {
// 若已满,标记最终状态为内存已满
state |= RMT_ENCODING_MEM_FULL;
// 跳转到退出标签,结束本次编码操作
goto out;
}
case 1: // 发送重置信号
// 调用复制编码器发送重置信号,并累加生成的 RMT 符号数量
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code,
sizeof(led_encoder->reset_code), &session_state);
// 检查重置信号是否发送完成
if (session_state & RMT_ENCODING_COMPLETE) {
// 标记最终状态为编码完成
led_encoder->state = RMT_ENCODING_RESET;
// 标记最终状态为编码完成
state |= RMT_ENCODING_COMPLETE;
}
// 检查 RMT 内存是否已满
if (session_state & RMT_ENCODING_MEM_FULL) {
// 若已满,标记最终状态为内存已满
state |= RMT_ENCODING_MEM_FULL;
// 跳转到退出标签,结束本次编码操作
goto out;
}
}
out:
// 将最终编码状态返回给调用者
*ret_state = state;
// 返回编码生成的 RMT 符号数量
return encoded_symbols;
}
/**
* @brief 删除 WS2812 LED 灯带编码器并释放相关资源
*
* 该函数用于删除指定的 WS2812 LED 灯带编码器,会依次删除其内部的字节编码器和复制编码器,
* 最后释放 LED 灯带编码器本身占用的内存。
*
* @param encoder 指向基础编码器结构体的指针,用于获取实际的 LED 灯带编码器实例
* @return esp_err_t 操作结果,成功时返回 ESP_OK
*/
static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder)
{
// 通过基类指针获取 rmt_led_strip_encoder_t 结构体实例
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
rmt_del_encoder(led_encoder->bytes_encoder);// 删除字节编码器
rmt_del_encoder(led_encoder->copy_encoder);// 删除复制编码器
free(led_encoder);// 释放 LED 灯带编码器本身占用的内存
return ESP_OK;
}
/**
* @brief 重置 WS2812 LED 灯带编码器的状态
*
* 该函数用于将指定的 WS2812 LED 灯带编码器重置到初始状态,
* 会依次重置内部的字节编码器和复制编码器,并将编码器的状态变量置为初始值。
*
* @param encoder 指向基础编码器结构体的指针,用于获取实际的 LED 灯带编码器实例
* @return esp_err_t 操作结果,成功时返回 ESP_OK
*/
static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder)
{
// 通过基类指针获取 rmt_led_strip_encoder_t 结构体实例
rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base);
// 重置字节编码器,将其状态恢复到初始状态
rmt_encoder_reset(led_encoder->bytes_encoder);
// 重置复制编码器,将其状态恢复到初始状态
rmt_encoder_reset(led_encoder->copy_encoder);
// 将 LED 灯带编码器的状态变量设置为初始状态
led_encoder->state = RMT_ENCODING_RESET;
return ESP_OK;
}
/**
* @brief 创建一个新的 WS2812 LED 灯带编码器。
*
* 此函数用于分配并初始化一个新的 LED 灯带编码器,该编码器可用于将 RGB 数据编码为适合 WS2812 LED 灯带的信号。
*
* @param config 指向 LED 灯带编码器配置结构体的指针,包含编码器的配置参数。
* @param ret_encoder 指向编码器句柄指针的指针,用于返回新创建的编码器句柄。
* @return esp_err_t 操作结果,ESP_OK 表示成功,其他错误码表示失败。
*/
esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder)
{
// 初始化返回值为成功状态
esp_err_t ret = ESP_OK;
// 定义一个指向 LED 灯带编码器结构体的指针
rmt_led_strip_encoder_t *led_encoder = NULL;
// 检查输入参数是否有效,如果 config 或 ret_encoder 为 NULL,则跳转到错误处理标签 err
ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
// 为 LED 灯带编码器分配内存
led_encoder = rmt_alloc_encoder_mem(sizeof(rmt_led_strip_encoder_t));
// 检查是否分配成功,如果失败,则跳转到错误处理标签 err
ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder");
// 初始化 LED 灯带编码器结构体的成员变量
// 设置编码器的编码函数
led_encoder->base.encode = rmt_encode_led_strip;
// 设置编码器的删除函数
led_encoder->base.del = rmt_del_led_strip_encoder;
// 设置编码器的重置函数
led_encoder->base.reset = rmt_led_strip_encoder_reset;
// 不同的 LED 灯带可能有不同的时序要求,以下参数是针对 WS2812 的
rmt_bytes_encoder_config_t bytes_encoder_config = {
.bit0 = {//编码0的时序
.level0 = 1,
.duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us
.level1 = 0,
.duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us
},
.bit1 = {//编码1的时序
.level0 = 1,
.duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us
.level1 = 0,
.duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us
},
.flags.msb_first = 1 // WS2812 传输位顺序: G7...G0R7...R0B7...B0
};
// 创建字节编码器,如果失败则跳转到错误处理标签 err
ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed");
// 初始化复制编码器配置
rmt_copy_encoder_config_t copy_encoder_config = {};
// 创建复制编码器,如果失败则跳转到错误处理标签 err
ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed");
// 计算重置信号的时钟周期数,默认重置信号持续时间为 50us
uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2;
// 设置重置信号的符号字
led_encoder->reset_code = (rmt_symbol_word_t) {
.level0 = 0,
.duration0 = reset_ticks,
.level1 = 0,
.duration1 = reset_ticks,
};
// 将新创建的编码器句柄返回给调用者
*ret_encoder = &led_encoder->base;
return ESP_OK;
// 错误处理标签
err:
if (led_encoder) {
// 如果字节编码器存在,则删除字节编码器
if (led_encoder->bytes_encoder) {
rmt_del_encoder(led_encoder->bytes_encoder);
}
// 如果复制编码器存在,则删除复制编码器
if (led_encoder->copy_encoder) {
rmt_del_encoder(led_encoder->copy_encoder);
}
free(led_encoder);
}
return ret;
}
/**
* @brief 将 HSV(色相、饱和度、明度)颜色空间转换为 RGB(红、绿、蓝)颜色空间。
*
* 该函数接收 HSV 颜色模型的三个参数,通过一系列计算将其转换为 RGB 颜色模型的三个参数。
* HSV 颜色模型更符合人类对颜色的感知,而 RGB 颜色模型常用于数字显示设备。
*
* @param h 色相,取值范围为 0 到 360,表示颜色的种类,例如 0 代表红色,120 代表绿色,240 代表蓝色。
* @param s 饱和度,取值范围为 0 到 100,表示颜色的纯度,值越大颜色越鲜艳。
* @param v 明度,取值范围为 0 到 100,表示颜色的明亮程度,值越大颜色越亮。
* @param r 指向存储转换后红色分量的指针,取值范围为 0 到 255。
* @param g 指向存储转换后绿色分量的指针,取值范围为 0 到 255。
* @param b 指向存储转换后蓝色分量的指针,取值范围为 0 到 255。
*/
static void led_strip_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b)
{
h %= 360; // 将色相值限制在 0 到 360 的范围内
// 计算 RGB 颜色的最大值,将明度值从 0-100 转换为 0-255 范围
uint32_t rgb_max = v * 2.55f;
// 计算 RGB 颜色的最小值,根据饱和度调整颜色纯度
uint32_t rgb_min = rgb_max * (100 - s) / 100.0f;
// 计算色相所在的 6 个区域中的哪一个,每个区域 60 度
uint32_t i = h / 60;
// 计算当前色相在所在区域内的偏移量
uint32_t diff = h % 60;
// 根据色相偏移量计算 RGB 颜色的调整值
uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60;
// 根据色相所在的区域,计算对应的 RGB 颜色分量
switch (i) {
case 0: // 色相在 0 到 60 度之间,红色为主
*r = rgb_max;
*g = rgb_min + rgb_adj;
*b = rgb_min;
break;
case 1: // 色相在 60 到 120 度之间,绿色和红色混合
*r = rgb_max - rgb_adj;
*g = rgb_max;
*b = rgb_min;
break;
case 2: // 色相在 120 到 180 度之间,绿色为主
*r = rgb_min;
*g = rgb_max;
*b = rgb_min + rgb_adj;
break;
case 3: // 色相在 180 到 240 度之间,蓝色和绿色混合
*r = rgb_min;
*g = rgb_max - rgb_adj;
*b = rgb_max;
break;
case 4: // 色相在 240 到 300 度之间,蓝色为主
*r = rgb_min + rgb_adj;
*g = rgb_min;
*b = rgb_max;
break;
default: // 色相在 300 到 360 度之间,红色和蓝色混合
*r = rgb_max;
*g = rgb_min;
*b = rgb_max - rgb_adj;
break;
}
}
|