在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 0if (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 gfor (i = 0; i < 8; i++) {send_buf[i] = (g & 0x80) ? (0xFC) : (0xC0);		g <<= 1;		}// update rfor (i = 8; i < 16; i++) {send_buf[i] = (r & 0x80) ? (0xFC) : (0xC0);	r <<= 1;			}// update bfor (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操作函数// 初始化spiret = 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详细解说

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BERT:用于语言理解的深度双向 Transformer 的预训练。

文章目录 0. 摘要1. 介绍2. 相关工作2.1 无监督的基于特征的方法2.3 无监督微调方法2.3 从受监督数据中迁移学习 3. BERT3.1 预训练 BERT3.2 微调 BERT 4. 实验4.1 GLUE4.2 SQuAD v1.14.3 SQuAD v2.04.4 SWAG 5. 消融研究5.1 预训练任务的影响5.2 模型大小的影响5.3 使用 BERT …

在算网云平台云端在线部署stable diffusion (0基础小白超详细教程)

Stable Diffusion无疑是AIGC领域中的AI绘画利器&#xff0c;具有以下显著优势&#xff1a; 1、开源性质&#xff0c;支持本地部署 2、能够实现对图像生成过程的精确控制 虽然SD在使用上有很多的有点&#xff0c;但缺点也是不言而喻的&#xff0c;由于AI绘画的整个过程以及现…

设计模式——Chain(责任链)设计模式

摘要 责任链设计模式是一种行为设计模式&#xff0c;通过链式调用将请求逐一传递给一系列处理器&#xff0c;直到某个处理器处理了请求或所有处理器都未能处理。它解耦了请求的发送者和接收者&#xff0c;允许动态地将请求处理职责分配给多个对象&#xff0c;支持请求的灵活传…

macOS 15.1.1 (24B2091) 系统中快捷键符号及其代表的按键的对照表

以下是 macOS 15.1.1 (24B2091) 系统中快捷键符号及其代表的按键的对照表&#xff1a; 符号按键名称描述⌘Command (Cmd)常用的功能键&#xff0c;用于执行大多数快捷操作。⌥Option (Alt)Option 键&#xff0c;常用于辅助操作和特殊字符输入。⇧ShiftShift 键&#xff0c;常用…

el-table一键选择全部行,切换分页后无法勾选

el-table一键全选&#xff0c;分页的完美支持 问题背景尝试解决存在问题问题分析 解决方案改进思路如下具体代码实现如下 问题背景 现在有个需求&#xff0c;一个表格有若干条数据(假设数量大于20&#xff0c;每页10条&#xff0c;保证有2个以上分页即可)。 现在需要在表格上方…

【55 Pandas+Pyecharts | 实习僧网Python岗位招聘数据分析可视化】

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 查看数据信息2.3 去除重复数据2.4 调整部分城市名称 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 招聘数量前20岗位3.2 各城市招聘数量3…

JAVA期末复习(30道填空题梳理知识点)

通过梳理一些常见的填空题&#xff0c;有效地复习知识点&#xff0c;帮助大家顺利通过考试。本文将总结30道典型的填空题&#xff0c;并分析其中涉及的知识点。 一、基本语法 JAVA程序的入口方法是&#xff1a; public static void main(String[] args) { }这个题目考察了JAVA…

C++11新特性之线程std::thread

C std::thread的定义和功能 std::thread是C11引入的标准库类&#xff0c;用于创建和管理线程。通过std::thread&#xff0c;程序可以并发执行多个任务&#xff0c;从而提高效率。 功能与作用&#xff1a; 创建线程&#xff1a;可以启动一个线程执行某个函数或任务。管理线程…

【赵渝强老师】PostgreSQL的控制文件

PostgreSQL数据库的物理存储结构主要是指硬盘上存储的文件&#xff0c;包括&#xff1a;数据文件、日志文件、参数文件、控制文件、WAL预写日志文件等等。 下面重点讨论一下PostgreSQL的控制文件。 视频讲解如下 【赵渝强老师】PostgreSQL的控制文件 控制文件记录了数据库运行…

在做题中学习(79):最小K个数

解法&#xff1a;快速选择算法 说明&#xff1a;堆排序也是经典解决问题的算法&#xff0c;但时间复杂度为&#xff1a;O(NlogK)&#xff0c;K为k个元素 而将要介绍的快速选择算法的时间复杂度为: O(N) 先看我的前两篇文章&#xff0c;分别学习&#xff1a;数组分三块&#…

【linux】shell(32)-循环控制

for循环 在 Shell 脚本中&#xff0c;for 循环是一种常见的循环结构&#xff0c;用于遍历列表、数组或命令输出。 基本语法 for 循环的基本语法如下&#xff1a; #!/bin/bash for variable in list docommands donevariable 是一个临时变量&#xff0c;用于存储每次迭代中的…

Pydantic 动态字段:使用和不使用 `@computed_field` 的对比指南

Pydantic 动态字段&#xff1a;使用和不使用 computed_field 的对比指南 安装 Pydantic不使用 computed_field 的实现特性 使用 computed_field 的实现特性 使用和不使用 computed_field 的对比适用场景分析什么时候不需要 computed_field&#xff1f;什么时候使用 computed_fi…

Docker Engine多平台镜像构建(ARM64、x64、riscv64...)

Docker Engine多平台镜像构建(ARM64、x64、riscv64…) 1. Docker Engine安装 设置 Docker 的存储库# Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://do…

连续大涨,汉王科技跑步进入AI应用舒适区

OpenAI正在进行的“12天12场直播”让行业再次沸腾&#xff0c;二级市场也在寻找AI应用的机会。这刺激了12月首周同花顺sora概念涨超11&#xff05;&#xff0c;远超同期大盘指数涨幅。 截至目前&#xff0c;“满血版”推理模型o1和月收费高达200美元的ChatGPT Pro订阅服务&…

[MySQL基础](三)SQL--图形化界面+DML

本专栏内容为&#xff1a;MySQL学习专栏 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;MySql &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &#x1f339;&#x1f339;&#x1f339;关注我带你学习编程知识 目录 图…

基于单片机的智能灯光控制系统

摘要 现在的大部分的大学&#xff0c;都是采用了一种“绿色”的教学方式&#xff0c;再加上现在的大学生缺乏环保意识&#xff0c;所以在学校里很多的教室&#xff0c;在白天的时候灯都会打开&#xff0c;这是一种极大的浪费&#xff0c;而且随时都有可能看到&#xff0c;这是…

数据分析及应用:滴滴出行打车日志数据分析

目录 0 日志数据集介绍 1 构建数据仓库 1.1 ods创建用户打车订单表 1.2 创建分区 1.3 上传到对应分区

解决Windows与Ubuntu云服务器无法通过Socket(udp)通信问题

今天在写Socket通信代码的时候&#xff0c;使用云服务器自己与自己通信没有问题&#xff0c;但是当我们把客户端换为Windows系统的时候却无法发送信息到Linux当中&#xff0c;耗时一上午终于搞定了&#x1f612;。 问题&#xff1a; 如上图&#xff0c;当我在windows的客户端…

网络安全基本命令

网络安全基本命令 想学会网络安全,就必须学会基本的网络常用命令,才能更好的去掌握网络,保护自己的系统&#xff0c;防止入侵。我们必须学会的基本的网络命令主要是基于Windows NT平台下的基本命令&#xff0c;也就是说windows 98/windows ME的下部分命令是不能运行的。所以说&…

帝可得-运营管理App

运营管理App Android模拟器 本项目的App客户端部分已经由前端团队进行开发完成&#xff0c;并且以apk的方式提供出来&#xff0c;供我们测试使用&#xff0c;如果要运行apk&#xff0c;需要先安装安卓的模拟器。 可以选择国内的安卓模拟器产品&#xff0c;比如&#xff1a;网…