介绍
炫彩灯珠内置 IC,可以显示 256×256×256 种颜色,仅凭一根信号线即可实现多种多样的效果
硬件电路
根据 SK6812 的电气参数和手册上的典型应用电路来设计其硬件电路
参数 |
符号 |
范围 |
单位 |
电压 |
VDD |
+3.7~+5.5 |
V |
逻辑输入电压 |
VI |
-0.5~VDD+0.5 |
V |
工作温度 |
Topt |
-40~+85 |
℃ |
储存温度 |
Tstg |
-40~+85 |
℃ |
ESD耐压(设备模式) |
VESD |
200 |
V |
ESD耐压(人体模式) |
VESD |
2K |
V |

控制方式
SK6812 的数据传输方式比起一般的外设来说,有点特别,每个位都是一个码元,然后通过控制码元的高低电平时间来控制该位的高低

时序表名称 |
min |
typic |
max |
单位 |
T |
1.20 |
- |
- |
us |
T0H |
0.2 |
0.3 |
0.4 |
us |
T0L |
0.8 |
- |
- |
us |
T1H |
0.6 |
0.67 |
1.0 |
us |
T1L |
0.2 |
- |
- |
us |
Trst |
≥80 |
- |
- |
us |
协议中每个码元都必须有低电平,每个码元起始为高电平,高电平时间宽度决定了该码元是 0 码还是 1 码,每个码元周期最小为 1.2us,具体的传输方式如下图所示

GPIO 翻转
GPIO 可以通过计算系统主频来计算出单条指令的时间,从而实现纳秒级硬延时。例如对于 AT32F403,设置单片机运行主频为 240MHz,所以它单条指令所需要的时间为 240M1=4.167ns 。根据上述分析,设置码元时间为 1.2us ,则 0 码的高电平持续时间为 0.3us ,低电平持续时间为 0.9us ,而 1 码的高电平持续时间为 0.6us ,低电平持续时间为 0.6us
一般可以利用 __nop()
指令,单条 __nop()
指令所需要的时间是 4.167ns ,所以可以利用 72 条指令来模拟延时 0.3us ,对应的代码如下
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
| uint8_t color_data_list[4][] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
void delay_30us() { __nop(); ... __nop(); }
void sk_init() { wk_dma_channel_config(DMA1_CHANNEL1, (uint32_t) &SPI2->dt, (uint32_t) color_data_list, 96); }
static void set_color(uint8_t r, uint8_t g, uint8_t b) { color_data_list[0][0] = color_data_list[1][0] = color_data_list[2][0] = color_data_list[3][0] = g; color_data_list[0][1] = color_data_list[1][1] = color_data_list[2][1] = color_data_list[3][1] = r; color_data_list[0][2] = color_data_list[1][2] = color_data_list[2][2] = color_data_list[3][2] = b; }
static void set_high_level() { gpio_bits_set(GPIOB, GPIO_PINS_13); delay_30us(); delay_30us(); gpio_bits_reset(GPIOB, GPIO_PINS_13); delay_30us(); delay_30us(); }
static void set_low_level() { gpio_bits_set(GPIOB, GPIO_PINS_13); delay_30us(); gpio_bits_reset(GPIOB, GPIO_PINS_13); delay_30us(); delay_30us(); delay_30us(); }
static void send_color() { for(int i = 0; i < 12; ++i) for(int j = 7; j >= 0; --j) { if(color_data_list[i] & (0x01 << j)) set_high_level(); else set_low_level(); } }
|
PWM+DMA
根据上述中每个码元的时间为 1.2us ,因此可以设置 PWM 的周期为 1.25us 即频率为 800KHz,通知通过控制重装载值即可控制高低电平的时间,从而实现码元输出。由于 AT32F403 时钟的主频为 240MHz ,因此可以设置预分频值为 2
,设置周期值为 99
即可。由于 PWM 的输出,当计数值小于比较值时,输出低电平,由于码元需要是高电平在前,所以设置 PWM 计数方式为向下计数
设置 0 码元和 1 码元的高低电平占比
- 0 码元: 0.3us 高电平, 0.95us 低电平,设置比较值为 76
- 1 码元: 0.65us 高电平, 0.6us 低电平,设置比较值为 50
另外打开该通道的 DMA,传输方向设置为从内存到外设,代码实现如下
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
| #define HIGH_LEVEL 76 #define LOW_LEVEL 50
uint8_t color_data_list[4][24] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
void sk_init() { wk_dma_channel_config(DMA1_CHANNEL2, (uint32_t) &TMR3->c1dt, (uint32_t) color_data_list, 96); }
static void set_color(uint8_t r, uint8_t g, uint8_t b) { uint32_t grb = (g << 16) | (r << 8) | b; uint32_t mask = 0x01 << 23; uint8_t i; for (i = 0; i < 24; ++i) color_data_list[0][i] = color_data_list[1][i] = color_data_list[2][i] = color_data_list[3][i] = ((grb & (mask >> i)) ? HIGH_LEVEL : LOW_LEVEL); }
static void send_color() { dma_channel_enable(DMA1_CHANNEL2, TRUE);
while (dma_flag_get(DMA1_FDT2_FLAG) == RESET); dma_flag_clear(DMA1_FDT2_FLAG);
dma_channel_enable(DMA1_CHANNEL2, FALSE); }
|
SPI+DMA
由上述所述,可知每个码元的持续时间大概在 1.2∼∞us 之间, 为了传输的效率,此处要选择尽量小的周期。使用 SPI 控制时,可以通过发送 8 bits 数据来控制,可以利用单个 8 bit 数据所形成的波形来分别表示 0 码和 1 码
- 0 码:前 2 个比特为高电平,后 6 个比特为低电平,因此发送的数据应当是
0b11000000
- 1 码:前 5 个比特为高电平,后 3 个比特为低电平,因此发送的数据为
0b11111000
假设 SPI 的频率为 xMHz (也就是发送一个比特位的频率),则可以调整 x 的大小,使得 SPI 能在 1.2us∼∞ 之间发送一个字节,计算可得 x≤6.67MHz ,所以在设置 SPI 时就不能设置的主频过高,而且需要注意设置 SPI 的帧位个数为 8 位,且高位先传输(MSB First)。对应的代码如下
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
| #define HIGH_LEVEL 0xf8 #define LOW_LEVEL 0xc0
uint8_t color_data_list[4][] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
void sk_init() { wk_dma_channel_config(DMA1_CHANNEL1, (uint32_t) &SPI2->dt, (uint32_t) color_data_list, 96); }
static void set_color(uint8_t r, uint8_t g, uint8_t b) { uint32_t grb = (g << 16) | (r << 8) | b; uint32_t mask = 0x01 << 23; uint8_t i; for (i = 0; i < 24; ++i) color_data_list[0][i] = color_data_list[1][i] = color_data_list[2][i] = color_data_list[3][i] = (grb & (mask >> i)) ? HIGH_LEVEL : LOW_LEVEL; }
static void send_color() { spi_i2s_dma_transmitter_enable(SPI2, TRUE); dma_channel_enable(DMA1_CHANNEL1, TRUE);
while (dma_flag_get(DMA1_FDT1_FLAG) == RESET); dma_flag_clear(DMA1_FDT1_FLAG);
dma_channel_enable(DMA1_CHANNEL1, FALSE); spi_i2s_dma_transmitter_enable(SPI2, FALSE); }
|
进阶控制方式——呼吸灯
SK6812 可以通过控制 RGB 三色值来控制彩灯的颜色,当然还可以通过控制颜色空间 HSV,再转换为 RGB 来控制颜色空间
RGB 转 HSV
max=max(r,g,b)min=min(r,g,b)h=⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧0∘60∘×max−ming−b+0∘60∘×max−ming−b+360∘60∘×max−minb−r+120∘60∘×max−minr−g+240∘max=minmax=rg≥bmax=rg<bmax=gmax=bs=⎩⎪⎨⎪⎧0∘maxmax−minmax=0v=max
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13
| static void rgb_to_hsv(float* h, float* s, float* v, uint8_t r, uint8_t g, uint8_t b) { uint8_t max = (r > g) ? (r > b ? r : b) : (g > b ? g : b); uint8_t min = (r < g) ? (r < b ? r : b) : (g < b ? g : b); float tmp = max - min; *v = max; if (max == 0) *s = 0; else *s = tmp / max; if (max == min) *h = 0; else if (max == r && g >= b) *h = 60.f * (g - b) / tmp; else if (max == r && g < b) *h = 60.f * (g - b) / tmp + 360; else if (max == g) *h = 60.f * (b - r) / tmp + 120; else if (max == b) *h = 60.f * (r - g) / tmp + 240; }
|
HSV 转 RGB
c=v×sx=c×(1−∣mod(60∘h,2)−1∣)m=v−c(r′,g′,b′)=⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧(c,x,0)(x,c,0)(0,c,0)(0,x,c)(x,0,c)(c,0,x)0∘≤h<60∘60∘≤h<120∘120∘≤h<180∘180∘≤h<240∘240∘≤h<300∘300∘≤h<360∘(r,g,b)=((r′+m)×255),(g′+m)×255),(b′+m)×255))
代码实现
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
| static void hsv_to_rgb(float h, float s, float v, uint8_t* r, uint8_t* g, uint8_t* b) { float c, tmp, x, m, r_, g_, b_; c = v * s; tmp = h / 60 - 1; x = c * (1 - (int) tmp % 2); m = v - c; if (h < 60) { r_ = c; g_ = x; b_ = 0; } else if (h < 120) { r_ = x; g_ = c; b_ = 0; } else if (h < 180) { r_ = 0; g_ = c; b_ = x; } else if (h < 240) { r_ = 0; g_ = x; b_ = c; } else if (h < 300) { r_ = x; g_ = 0; b_ = c; } else { r_ = c; g_ = 0; b_ = x; } *r = (r_ + m) * 255; *g = (g_ + m) * 255; *b = (b_ + m) * 255; }
|
一个示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| void func() { float h, s, v; uint8_t r, g, b; h = 0; for (;;) { h = fmod(h + 2, 360); v = 0; while (v < 1) { v += 0.01f; hsv_to_rgb(h, s, v, &r, &g, &b); set_color(r, g, b); send_color(); vTaskDelay(1); } while (v > 0) { v -= 0.01f; hsv_to_rgb(h, s, v, &r, &g, &b); set_color(r, g, b); send_color(); vTaskDelay(1); } } }
|