Files
stc32g128k/App/rgb.c

123 lines
5.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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; // 恢复全局中断使能
}