在ARM Linux应用层下使用SPI驱动WS2812

文章目录

  • 1、前言
  • 2、结果展示
  • 3、接线
  • 4、SPI驱动WS2812原理
    • 4.1、0码要发送的字节
    • 4.2、1码要发送的字节
    • 4.3、SPI时钟频率
  • 5、点亮RGB
    • 5.1、亮绿灯
    • 5.2、亮红灯
    • 5.3、亮蓝灯
    • 5.4、完整程序
  • 6、RGB呼吸灯
  • 7、总结

1、前言

事情是这样的,前段时间,写了一个基于RK356x/RK3588的WS2812驱动,实验发现,单独点亮RGB灯倒是没什么问题。但点灯只是第一步,因为后面还要做呼吸灯,所以又经过实验发现,在基于此驱动实现的呼吸灯应用效果差强人意,过程中会频繁出现灯灭和颜色误识别的情况。为什么呢?在之前的驱动里,是利用了程序执行的延时来完成码0和码1的传输,加上呼吸灯需要对WS2812频繁操作,所以容易出现不稳定的情况。

失败的效果如下(四个颜色呼吸渐变):
在这里插入图片描述

2、结果展示

再看SPI方式驱动的WS2812呼吸灯效果(四个颜色呼吸渐变):
在这里插入图片描述

3、接线

SPI控制器的DO接到WS2812的IN

4、SPI驱动WS2812原理

需要确定三个参数:0码要发送的字节、1码要发送的字节、SPI时钟频率

4.1、0码要发送的字节

0码的高电平(T0H)时间需要控制在220ns ~ 380ns,低电平(T0L)时间需要控制在580ns ~ 1.6us。

这里T0H取300ns,T0L取700ns,加起来刚好凑整1us的周期,T0H占30%,T0L占70%。

现在利用各自的占比来确认要发送的8个bit:

T0H所占的bit:(30% / 100%) * 8 = 2.4bit≈2bit

T0L所占的bit:(70% / 100%) * 8 = 5.6bit≈6bit

所以0码使用11000000(0xC0)表示。最后SPI发送0xC0就会先拉高300ns,拉低700ns(这是设想,要确定SPI时钟频率后才能真正实现)。

4.2、1码要发送的字节

1码的高电平(T1H)时间需要控制在580ns ~ 1.6us,低电平(T1L)时间需要控制在220ns ~ 420ns。

这里T1H取700ns,T1L取300ns,加起来刚好凑整1us的周期,T1H占70%,T1L占30%。

现在利用各自的占比来确认要发送的8个bit:

T1H所占的bit:(70% / 100%) * 8 = 5.6bit≈6bit

T1L所占的bit:(30% / 100%) * 8 = 2.4bit≈2bit

所以1码使用11111100(0xFC)表示。最后SPI发送0xFC就会先拉高700ns,拉低300ns(这是设想,要确定SPI时钟频率后才能真正实现)。

4.3、SPI时钟频率

频率的确认是最关键的,这决定了是否能正确发送0码和1码。

上面讲到发送一个字节需要1us的周期,所以SPI传输每bit的时间为:1000/8=125ns,换成频率即是1/125=8Mhz,所以SPI时钟频率需要设置为8Mhz。

5、点亮RGB

SPI发送函数的实现如下:

/* spi.c */

... 

/*****************************
* @brief : 向 SPI 总线写入n个字节数据
* @param : send_buf - 待写入的数据
* @param : send_buf_len - 待写入的数据长度
* @return: 无返回值
* @note  : 通过 SPI 总线发送n个字节的数据。
*****************************/
void spi_write_nbyte_data(unsigned char *send_buf, unsigned int send_buf_len)
{
    struct spi_ioc_transfer	xfer[2];
    unsigned char recv_buf[send_buf_len];
	int status;

    if(send_buf == NULL || send_buf_len < 1)
        return;

    memset(xfer, 0, sizeof(xfer));
    memset(recv_buf, 0, sizeof(send_buf_len));

	xfer[0].tx_buf = (unsigned long)send_buf;
    xfer[0].rx_buf = (unsigned long)recv_buf;
	xfer[0].len = send_buf_len;

	status = ioctl(fd_spidev, SPI_IOC_MESSAGE(1), xfer);
	if (status < 0) {
		perror("SPI_IOC_MESSAGE");
		return;
	}
}

...

5.1、亮绿灯

我这个灯是按照GRB的顺序发送数据。

/* main.c */

...

unsigned char send_buf[24] = {0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc};
spi_write_nbyte_data(send_buf, sizeof(send_buf));

...

5.2、亮红灯

/* main.c */

...

unsigned char send_buf[24] = {0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc};
spi_write_nbyte_data(send_buf, sizeof(send_buf));

...

5.3、亮蓝灯

/* main.c */

...

unsigned char send_buf[24] = {0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc};
spi_write_nbyte_data(send_buf, sizeof(send_buf));

...

5.4、完整程序

spi.h

/*
*
*   file: spi.h
*   updata: 2024-12-05
*
*/

#ifndef _SPI_H
#define _SPI_H

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <gpiod.h>
#include <stdint.h>
#include <linux/spi/spidev.h>
#include <pthread.h>

typedef struct spi_operations
{
    void (*spi_write_then_read)(unsigned char *send_buf, unsigned int send_buf_len, unsigned char *recv_buf, unsigned int recv_buf_len);
    void (*spi_write_byte_data)(unsigned char data);
	void (*spi_write_nbyte_data)(unsigned char *send_buf, unsigned int send_buf_len);
	pthread_mutex_t *mutex;
}spi_operations_t;

typedef enum
{
	SPIMODE0 = SPI_MODE_0,
	SPIMODE1 = SPI_MODE_1,
	SPIMODE2 = SPI_MODE_2,
	SPIMODE3 = SPI_MODE_3,
}SPI_MODE;
 
typedef enum
{
    S_1M    = 1000000,
	S_6_75M = 6750000,
	S_8M    = 8000000,
	S_13_5M = 13500000,
	S_27M   = 27000000,
}SPI_SPEED;

int spi_init(const char *spi_dev);
void spi_exit();
spi_operations_t *get_spi_ops();

#endif

spi.c

/*
*
*   file: spi.c
*   updata: 2024-12-05
*
*/

#include "spi.h"

static int fd_spidev;
static int init_flag = 0;       // 1已初始化 0未初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

/*****************************
 * @brief : 初始化 SPI 设备
 * @param : spi_dev - SPI 设备路径
 * @return: 成功返回 0,失败返回 -1
 * @note  : 初始化 SPI 接口,配置 SPI 参数。
 *****************************/
int spi_init(const char *spi_dev)
{
    int ret; 
    SPI_MODE mode;
    char spi_bits;
    SPI_SPEED spi_speed;

    fd_spidev = open(spi_dev, O_RDWR);
	if (fd_spidev < 0) {
		printf("open %s err\n", spi_dev);
		return -1;
	}

    /* mode */
    mode = SPIMODE0;
    ret = ioctl(fd_spidev, SPI_IOC_WR_MODE, &mode);                //mode 0
    if (ret < 0) {
		printf("SPI_IOC_WR_MODE err\n");
		return -1;
	}

    /* bits per word */
    spi_bits = 8;
    ret = ioctl(fd_spidev, SPI_IOC_WR_BITS_PER_WORD, &spi_bits);   //8bits 
    if (ret < 0) {
		printf("SPI_IOC_WR_BITS_PER_WORD err\n");
		return -1;
	}

    /* speed */
    spi_speed = (uint32_t)S_8M;
    ret = ioctl(fd_spidev, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);    //1MHz    
    if (ret < 0) {
		printf("SPI_IOC_WR_MAX_SPEED_HZ err\n");
		return -1;
	}

    init_flag = 1;
    return 0;
}

/*****************************
 * @brief : 向 SPI 总线写入数据并读取数据
 * @param : send_buf     - 发送数据的缓冲区
 *          send_buf_len - 发送数据的长度
 *          recv_buf     - 接收数据的缓冲区
 *          recv_buf_len - 接收数据的长度
 * @return: 无返回值
 * @note  : 通过 SPI 总线发送和接收数据,发送的数据通过 `send_buf`,接收到的数据存放在 `recv_buf` 中。
 *****************************/
static void spi_write_then_read(unsigned char *send_buf, unsigned int send_buf_len, unsigned char *recv_buf, unsigned int recv_buf_len)
{
    struct spi_ioc_transfer	xfer[2];
	int status;

    if(init_flag == 0)
    {
        perror("spidev can not init!\n");
        return;
    }

    if(send_buf == NULL || recv_buf == NULL)
        return;

    if(send_buf_len < 1 || recv_buf_len < 1)
        return;

    memset(xfer, 0, sizeof(xfer));

	xfer[0].tx_buf = (unsigned long)send_buf;
	xfer[0].len = send_buf_len;

	xfer[1].rx_buf = (unsigned long)recv_buf;
	xfer[1].len = recv_buf_len;

	status = ioctl(fd_spidev, SPI_IOC_MESSAGE(2), xfer);
	if (status < 0) {
		perror("SPI_IOC_MESSAGE");
		return;
	}
}

/*****************************
 * @brief : 向 SPI 总线写入一个字节数据
 * @param : data - 待写入的数据字节
 * @return: 无返回值
 * @note  : 通过 SPI 总线发送一个字节的数据。
 *****************************/
static void spi_write_byte_data(unsigned char data)
{
    unsigned char buff[1] = {data};

    if(init_flag == 0)
    {
        perror("spidev can not init!\n");
        return;
    }

    write(fd_spidev, &buff, 1);
}

/*****************************
 * @brief : 向 SPI 总线写入n个字节数据
 * @param : send_buf - 待写入的数据
 * @param : send_buf_len - 待写入的数据长度
 * @return: 无返回值
 * @note  : 通过 SPI 总线发送n个字节的数据。
 *****************************/
static void spi_write_nbyte_data(unsigned char *send_buf, unsigned int send_buf_len)
{
    struct spi_ioc_transfer	xfer[2];
    unsigned char recv_buf[send_buf_len];
	int status;

    if(init_flag == 0)
    {
        perror("spidev can not init!\n");
        return;
    }

    if(send_buf == NULL || send_buf_len < 1)
        return;

    memset(xfer, 0, sizeof(xfer));
    memset(recv_buf, 0, sizeof(send_buf_len));

	xfer[0].tx_buf = (unsigned long)send_buf;
    xfer[0].rx_buf = (unsigned long)recv_buf;
	xfer[0].len = send_buf_len;

	status = ioctl(fd_spidev, SPI_IOC_MESSAGE(1), xfer);
	if (status < 0) {
		perror("SPI_IOC_MESSAGE");
		return;
	}
}

/*****************************
 * @brief : 关闭 SPI 
 * @param : none
 * @return: 无返回值
 *****************************/
void spi_exit()
{
    if(fd_spidev >= 0)
        close(fd_spidev);
    
    init_flag = 0;
}

static spi_operations_t spi_ops = {
    .spi_write_then_read = spi_write_then_read,
    .spi_write_byte_data = spi_write_byte_data,
    .spi_write_nbyte_data = spi_write_nbyte_data,
    .mutex = &mutex,
};

/*****************************
 * @brief : 获取SPI操作函数
 * @param : none
 * @return: 返回spi_operations_t结构体指针
 *****************************/
spi_operations_t *get_spi_ops()
{
    return &spi_ops;
};

main.c

/*
*
*   file: main.c
*   update: 2024-12-05
*   usage: 
*       sudo gcc -o main main.c
*       sudo ./main FF0000
*
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>
#include <signal.h> 
#include <pthread.h>

#include "spi.h"

unsigned char send_buf[24];
void update_sendbuff(unsigned char r, unsigned char g, unsigned char b)
{
    int i = 0;

    // update g
    for (i = 0; i < 8; i++) 
    {
        send_buf[i] = (g & 0x80) ? (0xFC) : (0xC0);		
        g <<= 1;		
    }

    // update r
    for (i = 8; i < 16; i++) 
    {
        send_buf[i] = (r & 0x80) ? (0xFC) : (0xC0);	
        r <<= 1;			
    }

    // update b
    for (i = 16; i < 24; i++) 
    {
        send_buf[i] = (b & 0x80) ? (0xFC) : (0xC0);	
        b <<= 1;			
    }
}

int main(int argc, char **argv) 
{
    int ret;
    unsigned char r, g, b;
    spi_operations_t *spi_ops;      // spi操作函数

    // 初始化spi
    ret = spi_init("/dev/spidev3.0");
    if(ret < 0)
        return -1;
    spi_ops = get_spi_ops();
    
    // 参数数量检查
    if(argc != 2) 
    {
        printf("Usage: %s <hex_color>\n", argv[0]);
        printf("e.g. : %s FF0000\n", argv[0]);
        return -1;
    }

    /* 参数1检查 */
    if(strlen(argv[1]) != 6)
    {
        printf("Error: The first argument has illegal length.\n");
        printf("e.g. : %s FF0000\n", argv[0]);
        return -1;
    }
    if (sscanf(argv[1], "%2hhx%2hhx%2hhx", &r, &g, &b) != 3) 
    {  
        printf("Error: Invalid hex color format.\n");  
        return -1;  
    }
    
    // 更新颜色数据
    update_sendbuff(r, g, b);

    spi_ops->spi_write_nbyte_data(send_buf, sizeof(send_buf));     

    return 0;
}

6、RGB呼吸灯

RGB三色灯可以通过控制红、绿、蓝三个颜色的分量实现全真色彩显示,同时可实现256级亮度显示。

如熄灭灯是0x000000,点亮蓝灯是0x0000ff,所以可以通过控制低16位来调节蓝灯的亮度,值越小亮度越小。

如发送0x000011时,蓝灯的亮度如下:

发送0x000055时,蓝灯的亮度如下:

发送0x0000ff时,蓝灯的亮度如下:

红灯则是调节中间16位、绿灯是调节高16位。

7、总结

参考文章:SPI驱动ws2812详细解说

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/933532.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

unity3d—demo(2d人物左右移动发射子弹)

目录 人物代码示例&#xff1a; 子弹代码示例&#xff1a; 总结上面代码&#xff1a; 注意点&#xff1a; 人物代码示例&#xff1a; using System.Collections; using System.Collections.Generic; using UnityEngine;public class PlayerTiao : MonoBehaviour {public f…

Python subprocess.run 使用注意事项,避免出现list index out of range

在执行iOS UI 自动化专项测试的时候&#xff0c;在运行第一遍的时候遇到了这样的错误&#xff1a; 2024-12-04 20:22:27 ERROR conftest pytest_runtest_makereport 106 Test test_open_stream.py::TestOpenStream::test_xxx_open_stream[iPhoneX-xxx-1-250] failed with err…

怎么样能使Ubuntu的文件浏览器显示当前目录的路径,而不是只显示一个文件名?

默认情况下Ubuntu的文件浏览器是只显示当前目录的目录名的&#xff0c;这很不便我们查看路径或直接利用路径进行定位&#xff0c;那么怎么样能使Ubuntu的文件浏览器显示当前目录的路径呢&#xff1f; 两种方法&#xff1a; 第1种-临时方法 按下快捷键 Ctrl L&#xff0c;导航…

自制shell命令行解释器,深入理解Linux系统命令行实现原理

个人主页&#xff1a;敲上瘾-CSDN博客 个人专栏&#xff1a;Linux学习、游戏、数据结构、c语言基础、c学习、算法 目录 ​编辑 1.打印命令提示符 ​编辑 2.获取用户输入指令 3.重定向分析 4.命令行参数表与环境变量表 5.命令解析 6.命令执行 6.1.创建子进程 6.2.文件…

LangChain:大模型AI应用开发的强大引擎

文章目录 LangChain的核心功能LangChain的典型使用场景LangChain的未来展望《LangChain大模型AI应用开发实践》编辑推荐内容简介作者简介目录 在人工智能领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;的应用开发正逐渐成为技术前沿的热门话题。随着技术的不断进步…

【MFC】vs2019中使用sqlite3完成学生管理系统

目录 效果图list Contral 控件的简单使用使用sqlite3 效果图 使用sqlite3完成简单的数据库操作。 list Contral 控件的简单使用 本章只介绍基本应用 添加表头&#xff1a;语法&#xff1a; int InsertColumn(int nCol, LPCTSTR lpszColumnHeading, int nFormat LVCFMT_LEFT…

杨振宁大学物理视频中黄色的字,c#写程序去掉

先看一下效果&#xff1a;&#xff08;还有改进的余地&#xff09; 我的方法是笨方法&#xff0c;也比较刻板。 1&#xff0c;首先想到&#xff0c;把屏幕打印下来。c#提供了这样一个函数&#xff1a; Bitmap bmp new Bitmap(640, 480, PixelFormat.Format32bppArgb); // 创…

Android 逆向/反编译/Hook修改应用行为 基础实现

前言&#xff1a;本文通过一个简单的情景案例实现安卓逆向的基本操作 一、情景描述 本文通过一个简单的情景案例来实现安卓逆向的基本操作。在这个案例中所使用的项目程序是我自己的Demo程序&#xff0c;不会造成任何的财产侵害&#xff0c;本文仅作为日常记录及案例分享。实…

OSCP - Proving Grounds - Zino

主要知识点 SMB知识python脚本提权 具体步骤 执行nmap Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-10-10 01:24 UTC Nmap scan report for 192.168.52.64 Host is up (0.00077s latency). Not shown: 65529 filtered tcp ports (no-response) PORT STATE SER…

VLA模型

目录 引言1. 机器人大模型面临的挑战2. 目前的数据集2.1 RT-12.2 Open X-Embedding2.3 DROID 3. 目前的VLA模型3.1 Goat3.2 RT-13.2.1 总体架构3.2.2 效果 3.3 RT-23.3.1 总体架构3.3.2 效果 3.4 RT-X3.4.1 模型效果1). RT-1-X2). RT-2-X 3.5 RT-H3.5.1 总体架构3.5.2 效果 3.6…

aws(学习笔记第十六课) 使用负载均衡器(ELB)解耦webserver以及输出ELB的日志到S3

aws(学习笔记第十六课) 使用负载均衡器(ELB)以及输出ELB的日志到S3 学习内容&#xff1a; 使用负载均衡器(ELB)解耦web server输出ELB的日志到S3 1. 使用负载均衡器(ELB) 全体架构 使用ELB(Elastic Load Balancer)能够解耦外部internet访问和web server之间的耦合&#xff0c…

如何使用Java编写Jmeter函数

Jmeter 自带有各种功能丰富的函数&#xff0c;可以帮助我们进行测试&#xff0c;但有时候提供的这些函数并不能满足我们的要求&#xff0c;这时候就需要我们自己来编写一个自定义的函数了。例如我们在测试时&#xff0c;有时候需要填入当前的时间&#xff0c;虽然我们可以使用p…

实战指南:如何通过WBS提高项目估算准确性?

通过WBS将复杂任务细分为更易管理的任务&#xff0c;这有助于明确每项工作范围、所需资源及时间&#xff0c;从而减少估算误差&#xff0c;制定更现实的预算和时间表&#xff0c;提升团队协作效率。如果没有通过WBS将任务细化&#xff0c;项目范围可能变得模糊不清&#xff0c;…

ECharts实战教程:如何生成动态水波纹效果

导语&#xff1a;在数据可视化领域&#xff0c;ECharts是一款非常强大的图表库。今天&#xff0c;我们将带领大家学习如何使用ECharts生成动态水波纹效果&#xff0c;让我们的图表更加生动有趣。 一、准备工作 首先&#xff0c;我们需要准备一些基础数据&#xff0c;如下所示&…

详解:HTTP/HTTPS协议

HTTP协议 一.HTTP是什么 HTTP&#xff0c;全称超文本传输协议&#xff0c;是一种用于分布式、协作式、超媒体信息系统的应用层协议。HTTP往往是基于传输层TCP协议实现的&#xff0c;采用的一问一答的模式&#xff0c;即发一个请求&#xff0c;返回一个响应。 Q&#xff1a;什…

(0基础保姆教程)-JavaEE开课啦!--13课程(Interception拦截器)-完结

一、Interception(拦截器)是什么&#xff1f; 拦截器&#xff08;Interceptor&#xff09;是一种用于在请求到达目标方法之前或之后执行特定逻辑的机制。它是基于Java反射机制&#xff0c;属于面向切面编程&#xff08;AOP&#xff09;的一种应用。拦截器可以用于多种应用场景&…

vue 封装全局过滤器

1.找到utils下创建fifilter.js 一些常用的过滤方法 export const filters {//url解码urlCode: value > {if (!value) return let v decodeURIComponent(value)let bigIndex v.lastIndexOf(/)let endIndex v.lastIndexOf(.)let url v.substring(bigIndex 1, endIndex)…

Flask返回中文Unicode编码(乱码)解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

VMware:安装centos网络信息不可用

我们今天要处理的就是在vmware中安装centos出现网络不可用&#xff0c;导致无法安装系统的问题直接上图&#xff0c;我们在主机直接 cmdipconfig 发现IPV4地址都不一样&#xff0c;导致我们无法ping通虚拟机 那我们如何解决呢~~~~ 打开自己VM【编辑】【虚拟网络编辑器】【更…

MperReduce学习笔记下

自定义InputFormat合并小文件 案例需求 无论hdfs还是mapreduce&#xff0c;对于小文件都有损效率&#xff0c;实践中&#xff0c;又难免面临处理大量小文件的场景&#xff0c;此时&#xff0c;就需要有相应解决方案。 案例分析 小文件的优化无非以下几种方式&#xff1a; …