【ARM课】10-WS2812

1.cubemx设置

参考文章

手把手教你玩转WS2812B灯

时钟设置

高速时钟配置
在这里插入图片描述

将HCLK设置为最大频率72MHz

在这里插入图片描述

GPIO设置

在这里插入图片描述

参数设置

GPIO output level: Low
GPIO mode: Output Push Pull
GPIO Pull-up/Pull down: No ull-up and no ull-down
Maximum Output speed: High


2.SPI基础知识

SPI 是英语 Serial Peripheral Interface 的缩写,顾名思义就是串行外围设备接口,是一种高速的,全双工,同步的通信总线。

SPI 可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。

2.1. SPI 基本特性

  1. 全双工通信:SPI 总线支持全双工通信,即数据可以在两个方向上同时传输。这使得 SPI 在数据传输速度上具有很大的优势。

  2. 主从模式:SPI 支持主设备和从设备模式。主设备生成时钟信号,从设备根据时钟信号进行数据同步。一个 SPI 总线系统中可以有多个从设备,但同时只能有一个主设备。

  3. 可编程时钟:SPI 的时钟频率是可编程的,主设备可以根据需要配置适合的时钟频率,以适应不同的从设备和应用场景。

  4. 简单硬件连接:SPI 通常使用 4 根线进行通信:

    • MISO(Master In Slave Out):主设备数据输入,从设备数据输出。
    • MOSI(Master Out Slave In):主设备数据输出,从设备数据输入。
    • SCLK(Serial Clock):时钟信号,由主设备生成。
    • SS(Slave Select):从设备选择信号,由主设备控制,低电平有效。

在这里插入图片描述

2.2. SPI 工作原理

SPI 通信过程包括以下步骤:

  1. 主设备拉低 SS 信号,选择从设备。
  2. 主设备生成时钟信号,并通过 MOSI 发送数据。
  3. 从设备在时钟信号的同步下,通过 MISO 返回数据。
  4. 数据传输完成后,主设备释放 SS 信号,从设备解除选择状态。

2.3. SPI 数据传输模式

SPI 有四种不同的数据传输模式,由时钟极性(CPOL)和时钟相位(CPHA)两个参数决定:

  • 模式 0(CPOL = 0, CPHA = 0):时钟空闲状态为低电平,数据在时钟上升沿采样。
  • 模式 1(CPOL = 0, CPHA = 1):时钟空闲状态为低电平,数据在时钟下降沿采样。
  • 模式 2(CPOL = 1, CPHA = 0):时钟空闲状态为高电平,数据在时钟下降沿采样。
  • 模式 3(CPOL = 1, CPHA = 1):时钟空闲状态为高电平,数据在时钟上升沿采样。

在这里插入图片描述


3.代码

最早用非SPI通信方式实现。软件SPI实现的代码放在附录里面

main

WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);

WS2812.c

#include "stm32f1xx_hal.h"
#include "WS2812.h"

void WS2812_delay_us(uint32_t us)
{
    int32_t temp;
    // 假设系统时钟为72MHz,SysTick计时器每1/72微秒递减一次
    SysTick->LOAD = 72 * us - 1; // 设置LOAD的值为需要的微秒数乘以72,然后减去1
    SysTick->VAL = 0x00; // 清空计数器
    SysTick->CTRL = 0x01; // 使能SysTick,采用内核时钟源

    do
    {
        temp = SysTick->CTRL; // 读取当前控制寄存器的值
    }
    while((temp & 0x01) && (!(temp & (1 << 16)))); // 等待计时器标志位,直到计时结束

    SysTick->CTRL = 0x00; // 关闭计数器
    SysTick->VAL = 0x00; // 清空计数器
}

void WS2812_delay_ns(uint32_t ns)
{
    int32_t temp;
    // 假设系统时钟为72MHz,SysTick计时器每13.89纳秒递减一次
    // 最接近的计数是 ns / 13.89,向下取整
    uint32_t count = (ns * 72) / 1000; // 转换为等效的计数,忽略小数部分

    SysTick->LOAD = count - 1; // 设置LOAD的值为需要的纳秒数对应的计数值,然后减去1
    SysTick->VAL = 0x00; // 清空计数器
    SysTick->CTRL = 0x01; // 使能SysTick,采用内核时钟源

    do
    {
        temp = SysTick->CTRL; // 读取当前控制寄存器的值
    }
    while((temp & 0x01) && (!(temp & (1 << 16)))); // 等待计时器标志位,直到计时结束

    SysTick->CTRL = 0x00; // 关闭计数器
    SysTick->VAL = 0x00; // 清空计数器
}


void WS2812_Send_0(void)
{
    DI_ON;
    WS2812_delay_ns(300);   // 220ns~380ns
    DI_OFF;
    WS2812_delay_us(1);     // 580ns~1600ns
}

void WS2812_Send_1(void)
{
    DI_ON;
    WS2812_delay_us(1);     // 580ns~1600ns
    DI_OFF;
    WS2812_delay_ns(320);   // 220ns~420ns
}

void WS2812_Send_Reset(void)
{
    DI_OFF;
    WS2812_delay_ns(300);   // >280ns
}


void WS2812_SendRGB(unsigned char R,unsigned char G,unsigned char B)
{
    unsigned char i,j,temp[3];
    temp[0]=G;
    temp[1]=R;  
    temp[2]=B; 
    for(j=0;j<3;j++)
    for(i=0;i<8;i++)
    {
        if(temp[j] & (0x80>>i))   //发送1
            WS2812_Send_1();
        else                      //发送0
            WS2812_Send_0();
    }
}

WS2812.h

#ifndef __WS2812_H_
#define __WS2812_H_


#define DI_Pin  			 GPIO_PIN_0
#define DI_Port  			 GPIOD
#define DI_ON  			     HAL_GPIO_WritePin(DI_Port, DI_Pin, GPIO_PIN_SET)   //定义TRIG输出高电平
#define DI_OFF  			 HAL_GPIO_WritePin(DI_Port, DI_Pin, GPIO_PIN_RESET) //定义TRIG输出低电平


void WS2812_delay_us(uint32_t us);
void WS2812_delay_ns(uint32_t ns);

void WS2812_Send_0(void);
void WS2812_Send_1(void);
void WS2812_Send_Reset(void);

void WS2812_SendRGB(unsigned char R,unsigned char G,unsigned char B);

#define WS2812_BLACK    WS2812_SendRGB(0,0,0)
#define WS2812_WHITE    WS2812_SendRGB(255,255,255)

#endif 

4.代码解析

代码整体由GPT撰写,我负责细节部分的修改。

几个Delay函数不再赘述,Systick文章中已经详细解释了。

void WS2812_Send_0(void);
void WS2812_Send_1(void);
void WS2812_Send_Reset(void);

这三个函数分别对应下面图中的三种时序波形。

在这里插入图片描述

主函数中发送的六个RGB信号会依次传递下去

WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);
WS2812_SendRGB(255,255,255);

在这里插入图片描述

5.实验现象


6. 附

SPI代码

① MySPI.c
void Soft_SPI_WriteBit(uint8_t bit) {
    if (bit)
        HAL_GPIO_WritePin(SPI_PORT, MOSI_PIN, GPIO_PIN_SET);
    else 
        HAL_GPIO_WritePin(SPI_PORT, MOSI_PIN, GPIO_PIN_RESET);

    HAL_GPIO_WritePin(SPI_PORT, SCK_PIN, GPIO_PIN_SET);
    HAL_GPIO_WritePin(SPI_PORT, SCK_PIN, GPIO_PIN_RESET);
}

void Soft_SPI_WriteByte(uint8_t byte) 
{
    for (int i = 0; i < 8; i++) 
        Soft_SPI_WriteBit(byte & (0x80 >> i));
}
② WS2812.c
void WS2812_Send_0(void) 
{
    Soft_SPI_WriteBit(1);
    for (int i = 0; i < 3; i++)
        Soft_SPI_WriteBit(0);
}

void WS2812_Send_1(void) 
{
    Soft_SPI_WriteBit(1);
    Soft_SPI_WriteBit(1);
    Soft_SPI_WriteBit(0);
}

void WS2812_Send_Reset(void) 
{
    for (int i = 0; i < 50; i++) 
        Soft_SPI_WriteBit(0);
}

void WS2812_SendRGB(uint8_t R, uint8_t G, uint8_t B) {
    uint8_t temp[3] = {G, R, B};

    // 拉低 CS 引脚以开始传输
    HAL_GPIO_WritePin(SPI_PORT, CS_PIN, GPIO_PIN_RESET);

    for (int j = 0; j < 3; j++) {
        for (int i = 0; i < 8; i++) {
            if (temp[j] & (0x80 >> i)) {
                WS2812_Send_1();
            } else {
                WS2812_Send_0();
            }
        }
    }

    // 发送完数据后拉高 CS 引脚
    HAL_GPIO_WritePin(SPI_PORT, CS_PIN, GPIO_PIN_SET);
}
③ 宏(在合适位置添加,根据自己的引脚修改)
#define MOSI_PIN GPIO_PIN_0
#define SCK_PIN GPIO_PIN_1
#define CS_PIN GPIO_PIN_2
#define SPI_PORT GPIOD
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值