#include "rgb.h" /* ====== SPI 寄存器映射地址定义 (防止 stc32g.h 库版本中未显式定义) ====== */ #ifndef P_SW1 sfr P_SW1 = 0xA2; // 外设端口切换寄存器 1 sfr SPCTL = 0xCE; // SPI 控制寄存器 sfr SPSTAT = 0xCD; // SPI 状态寄存器 sfr SPDAT = 0xCF; // SPI 数据寄存器 #endif /** * @brief 初始化内核 SPI 控制器 * @details 将 SPI 的物理管脚切换至 P2 端口的第二组外设映射位置(SCLK=P2.5, MOSI=P2.3), * 并配置 SPI 传输速率,使其产生的脉冲宽满足 WS2812B/FCOB 的 1 码和 0 码时序。 */ void SPI_Init(void) { // 将 P_SW1 寄存器的第 2、3 位 (SPI_S) 配置为 01,即将 SPI 外设映射至 P2.2~P2.5 端口上 P_SW1 = (P_SW1 & ~0x0C) | 0x04; // 配置 SPCTL (SPI Control Register): // SSIG = 1 (忽略SS引脚,置为主机) // SPEN = 1 (启用SPI模块) // MSTR = 1 (配置为主机模式) // SPR0 = 1 (3MHz communication speed @ 24MHz System Clock) SPCTL = 0xD1; // 清除 SPI 状态标志 (写入1清除 SPIF 和 WCOL 标志位) SPSTAT = 0xC0; } /** * @brief 字节数据转 3-bit 编码的 WS2812B 驱动波形数据 * @param val 要转码的一个 8 位色彩数值 (如R、G、B颜色分量) * @param out 转换后存储目标的高速缓冲缓冲区首地址,1 字节拆分成 3 字节表示物理波形 * @details WS2812B 通讯协议需要通过高低电平的持续脉冲时间比来判断 "1" 码或 "0" 码。 * 通过将 SPI 速率设为 3.0 Mbps(即每个 SPI bit 占 ~333ns): * - 一个 WS2812B 协议的 "1" 码(800ns高, 450ns低)可用二进制 `110` (高高低,~666ns高, ~333ns低) 来模拟; * - 一个 WS2812B 协议的 "0" 码(400ns高, 850ns低)可用二进制 `100` (高低低,~333ns高, ~666ns低) 来模拟; * 因此,1 个字节(8 bits)的颜色深度数据被完美重映射编码为 24 个 SPI 数据 bits(共需 3 个 SPI 字节发送)。 */ void Encode_Byte(u8 val, u8 *out) { u32 spi_val = 0; // 用于拼接 24 位 SPI 映射串 u8 i; for (i = 0; i < 8; i++) { spi_val <<= 3; // 每次左移 3 位,为下一个像素的波形让出空间 if (val & (0x80 >> i)) { spi_val |= 6; // 该 bit 为 1,对应 WS2812B "1" 码时序,用二进制 '110' (即数值6) 填充 } else { spi_val |= 4; // 该 bit 为 0,对应 WS2812B "0" 码时序,用二进制 '100' (即数值4) 填充 } } // 将拼接好的 24 位波形数据(前高位后低位)拆分装载进输出缓冲区中 out[0] = (u8)(spi_val >> 16); out[1] = (u8)(spi_val >> 8); out[2] = (u8)spi_val; } /** * @brief 发送两颗级联 RGB 灯珠的驱动命令 * @details 负责将两组 GRB 格式色彩数据(g1, r1, b1 和 g2, r2, b2)转换为 WS2812B 的 SPI 脉冲编码, * 并在发送期间实施硬件引脚复用隔离,发送完成后发出复位复位电平。 */ void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2) { u8 idata spi_buf[18]; // 2 颗灯 * 3 个基色通道 * 3 字节编码 = 18 字节缓冲 u8 i; // 分别转码两颗灯珠的 G、R、B 通道,顺序填充进 SPI 缓冲区 Encode_Byte(g1, &spi_buf[0]); Encode_Byte(r1, &spi_buf[3]); Encode_Byte(b1, &spi_buf[6]); Encode_Byte(g2, &spi_buf[9]); Encode_Byte(r2, &spi_buf[12]); Encode_Byte(b2, &spi_buf[15]); EA = 0; // 关闭全局中断,防止 SPI 数据发送被高频中断打断,造成 WS2812B 的时序畸变 // 【硬件复用隔离机制】: // 物理引脚 P2.5 同时被用作“振动马达控制端 (MOTOR)”和“SPI SCLK 时钟信号输出”。 // 在 SPI 发送 RGB 期间,SCLK 将高速振荡,这会导致马达发生异常高频振动甚至噪音。 // 因此在启动 SPI 发送前,必须先将 P2.5 (MOTOR) 临时配置为高阻输入模式(M1=1, M0=0), // 从而与 SPI 时钟引脚在电气上实现物理隔离,防止杂散时钟信号驱动马达。 P2M1 |= (1 << 5); P2M0 &= ~(1 << 5); SPI_Init(); // 配置并启动 SPI 外设控制器 // 循环发送 18 字节的 WS2812B 脉冲编码数据 for (i = 0; i < 18; i++) { SPDAT = spi_buf[i]; // 装载待发数据到数据寄存器 while (!(SPSTAT & 0x80)); // 等待 SPI 状态寄存器第 7 位 (SPIF) 变为 1,代表发送结束 SPSTAT = 0xC0; // 往状态寄存器写入1以清零 SPIF 和 WCOL 标志 } SPCTL = 0; // 彻底关闭 SPI 控制器,释放总线 // 【发送 RESET 复位低电平帧】: // WS2812B 驱动芯片要求在所有色彩帧数据发完后,DIN 必须维持大于 80us 的低电平以将数据刷入锁存。 // 首先将 P2.3 (RGB_DIN) 重新配置为推挽输出模式 (M1=0, M0=1) P2M1 &= ~(1 << 3); P2M0 |= (1 << 3); RGB_DIN = 0; // 强行拉低 DIN 信号线 for (i = 0; i < 200; i++) { _nop_(); _nop_(); _nop_(); // 软件延时,保持低电平时间大于 100微秒 } // 【恢复复用引脚功能】: // 恢复 P2.5 (MOTOR) 引脚为普通的推挽输出模式 (M1=0, M0=1),以便重新由定时器或应用控制振动马达 P2M1 &= ~(1 << 5); P2M0 |= (1 << 5); MOTOR = 0; // 默认拉低马达引脚,防止发声 EA = 1; // 恢复全局中断使能 }