docs: add detailed Chinese comments for code files and correct GPIO mapping table in main.md

This commit is contained in:
Antigravity IDE
2026-07-16 17:52:52 +08:00
parent afd59d28ee
commit ddd001b90b
26 changed files with 32803 additions and 53495 deletions

View File

@@ -1,47 +1,77 @@
#include "rgb.h"
/* ====== SPI 寄存器定义 (如果 stc32g.h 定义) ====== */
/* ====== SPI 寄存器映射地址定义 (防止 stc32g.h 库版本中未显式定义) ====== */
#ifndef P_SW1
sfr P_SW1 = 0xA2;
sfr SPCTL = 0xCE;
sfr SPSTAT = 0xCD;
sfr SPDAT = 0xCF;
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 = (P_SW1 & ~0x0C) | 0x04; // 映射 SPI 至第二组 (P2.2~P2.5)
SPCTL = 0xD1; // SSIG=1, SPEN=1, MSTR=1, SPR0=1 (3.0 MHz)
SPSTAT = 0xC0; // 清除标志
// 将 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;
u32 spi_val = 0; // 用于拼接 24 位 SPI 映射串
u8 i;
for (i = 0; i < 8; i++)
{
spi_val <<= 3;
spi_val <<= 3; // 每次左移 3 位,为下一个像素的波形让出空间
if (val & (0x80 >> i))
{
spi_val |= 6; // 1码 -> 110
spi_val |= 6; // 该 bit 为 1对应 WS2812B "1" 码时序,用二进制 '110' (即数值6) 填充
}
else
{
spi_val |= 4; // 0码 -> 100
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];
u8 idata spi_buf[18]; // 2 颗灯 * 3 个基色通道 * 3 字节编码 = 18 字节缓冲
u8 i;
// 转码 3-bit 编码
// 分别转码两颗灯珠的 G、R、B 通道,顺序填充进 SPI 缓冲区
Encode_Byte(g1, &spi_buf[0]);
Encode_Byte(r1, &spi_buf[3]);
Encode_Byte(b1, &spi_buf[6]);
@@ -49,36 +79,44 @@ void RGB_Send(u8 g1, u8 r1, u8 b1, u8 g2, u8 r2, u8 b2)
Encode_Byte(r2, &spi_buf[12]);
Encode_Byte(b2, &spi_buf[15]);
EA = 0; // 关中断
EA = 0; // 关闭全局中断,防止 SPI 数据发送被高频中断打断,造成 WS2812B 的时序畸变
// 临时将 P2.5 (MOTOR) 配置为高阻输入以隔离时钟信号
// 【硬件复用隔离机制】:
// 物理引脚 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
SPI_Init(); // 配置并启动 SPI 外设控制器
// 循环发送 18 字节的 WS2812B 脉冲编码数据
for (i = 0; i < 18; i++)
{
SPDAT = spi_buf[i];
while (!(SPSTAT & 0x80)); // 等待发送完毕
SPSTAT = 0xC0;
SPDAT = spi_buf[i]; // 装载待发数据到数据寄存器
while (!(SPSTAT & 0x80)); // 等待 SPI 状态寄存器第 7 位 (SPIF) 变为 1代表发送结束
SPSTAT = 0xC0; // 往状态寄存器写入1以清零 SPIF 和 WCOL 标志
}
SPCTL = 0; // 关闭 SPI
SPCTL = 0; // 彻底关闭 SPI 控制器,释放总线
// 配置 P2.3 (RGB_DIN) 为推挽输出并拉低 100us 做 Reset
// 【发送 RESET 复位低电平帧】:
// WS2812B 驱动芯片要求在所有色彩帧数据发完后DIN 必须维持大于 80us 的低电平以将数据刷入锁存。
// 首先将 P2.3 (RGB_DIN) 重新配置为推挽输出模式 (M1=0, M0=1)
P2M1 &= ~(1 << 3);
P2M0 |= (1 << 3);
RGB_DIN = 0;
RGB_DIN = 0; // 强行拉低 DIN 信号线
for (i = 0; i < 200; i++)
{
_nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); // 软件延时,保持低电平时间大于 100微秒
}
// 恢复 P2.5 (MOTOR) 为推挽输出以进行振动控制
// 恢复复用引脚功能】:
// 恢复 P2.5 (MOTOR) 引脚为普通的推挽输出模式 (M1=0, M0=1),以便重新由定时器或应用控制振动马达
P2M1 &= ~(1 << 5);
P2M0 |= (1 << 5);
MOTOR = 0;
MOTOR = 0; // 默认拉低马达引脚,防止发声
EA = 1; // 开中断
EA = 1; // 恢复全局中断使能
}