STM32CUBEMX_SPI_驱动WS2811灯带

news2024/9/20 6:23:09

STM32CUBEMX_SPI_驱动WS2811灯带

前言:
关于这种带芯片的之前我都是使用GPIO模拟时序,但是带来一个很大的弊端,那就是严重占用CPU资源,使得其他代码逻辑没办法正常执行了,想办法搞一个单片机的外设使用DMA功能,就可以解决占用资源的问题了,去网上了解了,还真有网友这么干的,参考完之后,自己来干一遍

WS2811芯片的一些重要参数:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

讲解一下怎么使用SPI的发送数据来模拟这个时序:

1、首先说明一下,WS2812的时序中一个位大约需要1.25us的时间,这里面当高电平的时间占用时间位0.85us,低电平占用时间为0.4us时表示传输位为1,反过来则表示传输位为0。
2、我们使用SPI不是用他的SCLK引脚的信号,而是MOSI引脚的信号,因为SCK引脚的脉冲宽度时固定的,但是我们可以控制MOSI输出信号。例如我们输出 b1111 1000 时,(MOSI原本是低电平状态)产生了一个高电平脉冲,高电平的宽度与地电平的比值为 5:3,然后我们可以再输出 b1110 0000,这时候输出脉冲的高低电平的比值为3:5,好巧哦,我们可以控制输出信号的脉宽比了,如果觉得这个比值还是太粗糙可以使用多个字节进行脉冲高低电平时间的比例分配。
恰巧前面说过了,WS2812的位1是由0.85us的高电平加上0.4us的低电平组成的,高低电平的占例大约为2多一点,其实和上面说的输出 b1111 1000 数据时生成的脉冲高低电平比例差不多,反过来WS2812的位0也和 b1110 0000 数据的输出效果差不多。我们通过控制SPI的时钟频率可以实现SPI传输8个位的数据使用的时间大致等于1.25us,例如STM32的SPI1挂载的APB2总线频率为72Mhz,8分频之后得到的SPI1的SCK的频率为9Mhz,周期为0.11us,传输8个位的时间为0.89微妙,没法正好等于1.25us,所以取一个大致相近的值即可。还有要注意设置SPI的工作模式为SCK在第一个上升沿采样数据,SCK的极性无所谓。
3、基于上面的论证,假设我们需要控制的WS2812灯一共有60颗,我们可以建立一个 60 * 24 个字节的数组A,这个数组存放的都是 b1111 1000 或者 b1110 0000,也就是每个字节存放的其实是一个WS2812的单个位的数据,我们将这个数组都写成 b1111 1000 的时候,灯光效果就都是白色,数组全部写 b1110 0000 的时候,灯光效果就都是黑色的。但是好像控制灯光的时候有点不方便啊!我们可以再建立一个数组B,用于存放每个灯(像素)的颜色信息,这个数组是32位类型的,存放RGB888的数据,我们修改灯光效果的时候可以先修改数组B的内容,然后通过运算将数组B中的数据映射到数组A中去,然后在将数组A通过SPI发送出去。
到这里基本上就可以实现SPI控制WS2812灯了,但是每次调用SPI发送数据是有间隔时间的,并且会被中断打断,这时候DMA的作用就体现出来了。将DMA的内存地址设置为上面的数组A,长度设置为数组A的大小,开启DMA发送,你只需要喝杯咖啡,等待DMA发送完成标志位置位即可。
4、还有一个需要注意的点是WS2812需要有复位操作,也就是发送50us以上的低电平进行复位,每次传输数据的时候都需要先复位,要实现复位功能,我们可以发送 b0000 0000 即可,由于一个位的时间为0.89us,要实现50us以上,需要至少发送57次b0000 0000 ,我们可以创建一个数组C,C的内容全是 b0000 0000,C的长度设置为100保证有效复位,然后开启DMA,传输数组C的内容即可完成复位。
Surprise,没想到SPI+DMA可以这样用,其实同样的技巧可以用在很多其他的应用场景中去,例如控制步进电机需要特定个数的脉冲,我们也可以使用SPI的MOSI来实现(SCK可能频率太高,步进电机控制器无法接受),例如发送 b0011 1100 这个数据就可以在MOSI上产生一个高脉冲,通过DMA可以实现输出特定个数脉冲的功能。

实践我们的做法:
1、SPI模式使用
在这里插入图片描述
在这里插入图片描述

void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
  hspi1.Init.NSS = SPI_NSS_SOFT;
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

下面是驱动源码:
ws281x.c

#include "ws281x.h"
#include "spi.h"
#include "log.h"
#include "string.h"

const ws2812xTypeDef_t RED      = {255,0,0};			//全为最大亮度
const ws2812xTypeDef_t GREEN    = {0,255,0};
const ws2812xTypeDef_t BLUE     = {0,0,255};
const ws2812xTypeDef_t YELLOW   = {255,255,0};
const ws2812xTypeDef_t MAGENTA  = {255,0,255};
const ws2812xTypeDef_t BLACK    = {0,0,0};
const ws2812xTypeDef_t WHITE    = {255,255,255};

unsigned char ws2812x_array[WS281X_ARRAY_NUM] = {0};

void ws2812xInit(void)
{ 
  ws2812xRgbBlack(WS281X_NUM);
	HAL_Delay(2);
}

void ws2812xRgbRed(unsigned short num)
{
  ws2812xSetColor(num,RED);
	ws2812xUpdateDisplay();
}

void ws2812xRgbGreen(unsigned short num)
{
  ws2812xSetColor(num,GREEN);
  ws2812xUpdateDisplay();
}

void ws2812xRgbBlue(unsigned short num)
{
  ws2812xSetColor(num,BLUE);
  ws2812xUpdateDisplay();
}

void ws2812xRgbYellow(unsigned short num)
{
  ws2812xSetColor(num,YELLOW);
  ws2812xUpdateDisplay();
}

void ws2812xRgbMagenta(unsigned short num)
{
  ws2812xSetColor(num,MAGENTA);
  ws2812xUpdateDisplay();
}

void ws2812xRgbBlack(unsigned short num)
{
  ws2812xSetColor(num,BLACK);
  ws2812xUpdateDisplay();
}

void ws2812xRgbWhite(unsigned short num)
{
  ws2812xSetColor(num,WHITE);
  ws2812xUpdateDisplay();
}

void ws2812xGreenFlow(unsigned short num,unsigned short time)
{
	for(int i = 0;i < num; i++)
	{
		ws2812xRgbRed(i+1);
		Debug_debug("ws2812xRgbGreen:%d\r\n",i);
		HAL_Delay(time);
	}
}

void ws2812xSetColor(unsigned short numId, ws2812xTypeDef_t Color)  
{
  if(numId > WS281X_NUM) return;

	int array_count = 0;
	
	memset(ws2812x_array,CODE0,WS281X_ARRAY_NUM);
	
	for(int i = 0; i < numId; i++)
	{
		for(int j = 7; j >= 0; j--)
		{
			if((Color.R & (1<<j)) == 0){
				ws2812x_array[array_count] = CODE0;
			}
			else{
				ws2812x_array[array_count] = CODE1;
			}
			array_count++;
		}
	
		for(int j = 7; j >= 0; j--)
		{
			if((Color.G & (1<<j)) == 0){
				ws2812x_array[array_count] = CODE0;
			}
			else{
				ws2812x_array[array_count] = CODE1;
			}
			array_count++;
		}
	
		for(int j = 7; j >= 0; j--)
		{
			if((Color.B & (1<<j)) == 0){
				ws2812x_array[array_count] = CODE0;
			}
			else{
				ws2812x_array[array_count] = CODE1;
			}
			array_count++;
		}
	}
}

void ws2812xUpdateDisplay(void)
{
//	HAL_SPI_Transmit(&hspi1, ws2812x_array, WS281X_ARRAY_NUM, 10);
	HAL_SPI_Transmit_DMA(&hspi1, ws2812x_array, WS281X_ARRAY_NUM);
}

ws281x.h

#ifndef __WS2812X_H
#define __WS2812X_H

typedef struct//颜色结构体
{
  unsigned char R;
  unsigned char G;
  unsigned char B;
}ws2812xTypeDef_t;

#define WS281X_NUM    			42	//灯RGB数量
#define WS281X_ARRAY_NUM    WS281X_NUM*3*8 //14*3*8

//用SPI字节 来模拟bit(CODEx)码
#define CODE0 0xE0
#define CODE1 0xF8

void ws2812xRgbRed(unsigned short num);		//红
void ws2812xRgbGreen(unsigned short num);	//绿
void ws2812xRgbBlue(unsigned short num);	//蓝
void ws2812xRgbYellow(unsigned short num);//黄
void ws2812xRgbMagenta(unsigned short num);//紫
void ws2812xRgbBlack(unsigned short num);	//黑
void ws2812xRgbWhite(unsigned short num);	//白

void ws2812xInit(void);
void ws2812xUpdateDisplay(void);
void ws2812xSetColor(unsigned short numId, ws2812xTypeDef_t Color);
void ws2812xGreenFlow(unsigned short num,unsigned short time);//绿色流动一周
#endif

补充问题:
问题1:出现第一颗灯珠点不亮,其他灯珠均可以正常点亮?
原因:时序有问题,停止信号不对,因为芯片是级联传输信号的,最后的这个停止信号就决定的信号传递的位置,检查SPI的模式是不是 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;

问题2:出现灯珠乱闪,不能正常显示设置的颜色?
原因:时序错误,使用SPI模拟时序的时候被中断打断了,导致时序错乱,使用DMA解决问题

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

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

相关文章

LeetCode-day21-1186. 删除一次得到子数组最大和

LeetCode-day21-1186. 删除一次得到子数组最大和 题目描述示例示例1&#xff1a;示例2&#xff1a;示例3&#xff1a; 思路代码 题目描述 给你一个整数数组&#xff0c;返回它的某个 非空 子数组&#xff08;连续元素&#xff09;在执行一次可选的删除操作后&#xff0c;所能得…

【学术研究、研究热点、最新前沿】如何跟踪最新的论文

1.跟踪arxiv 使用https://www.arxivdaily.com/接收每天的推送。 2.跟踪热点文章的引用 使用semantic scholar。 3.跟踪某个学术大佬或者主题 3.1 使用web of science。 3.2 使用文献鸟 4.跟踪某个期刊

pico+unity3d 射线交互教程

前期配置&#xff1a;环境配置参考教程一&#xff0c;手部模型参考教程二&#xff0c;场景基于上一篇搭建。 最终效果&#xff1a;手部射线&#xff08;初始不可见&#xff09;对准 UI 显示&#xff0c;按下手柄 Trigger 键与可交互 UI&#xff08;如 Button、Toggle、Slider …

Android APP 基于RecyclerView框架工程(知识体系积累)

说明&#xff1a;这个简单的基于RecyclerView的框架作用在于自己可以将平时积累的一些有效demo整合起来&#xff08;比如音视频编解码的、opengles的以及其他也去方向的、随着项目增多&#xff0c;工程量的增加&#xff0c;后期想高效的分析和查找并不容易&#xff09;&#xf…

vscode 环境

这张截图显示的是在VS Code&#xff08;Visual Studio Code&#xff09;中选择Python解释器的界面。不同的Python解释器及其虚拟环境列出了可选项&#xff0c;用户可以根据需要选择合适的解释器来运行Python代码。以下是对截图中信息的详细解释&#xff1a; 解释器选择界面 当…

Java小技能:多级组织机构排序并返回树结构(包含每个层级的子节点和业务数据集合)

文章目录 引言I 实体定义1.1 部门1.2 用户组织机构中间表1.3 树状DTOII 抽取组织机构排序方法2.1 树状排序方法2.2 案例III 查询条件构建3.1 根据部门进行权限控制3.2 注入风险引言 需求: 根据组织机构进行数据授权控制,例如控制船舶、船舶设备、摄像头、港区查看权限。 一…

05_解封装和解码

1. 基本概念 容器就是一种文件格式&#xff0c;比如flv、mkv、mp4等。包含下面5种流以及文件头信息。 流是一种视频数据信息的传输方式&#xff0c;5种流&#xff1a;音频&#xff0c;视频&#xff0c;字幕&#xff0c;附件&#xff0c;数据。 包在ffmpeg中代表已经编码好的一…

【LINUX】pr_info函数开发摸索

1、打印开关可随时控制&#xff0c;开机如果要修改是否打印日志的话&#xff0c;需要修改代码重新编译内核才行&#xff0c;其实如果真要搞&#xff0c;应该有其他方法&#xff1b; 2、打印次数&#xff0c;当前代码里边写的是1000次&#xff0c;其实可以根据传参动态修改打印…

CUDA编程00 - 配置CUDA开发环境

第一步&#xff1a; 在一台装有Nvidia显卡和驱动的机器上&#xff0c;用nvidia-smi命令查看显卡所支持cuda版本 第二步&#xff1a; 到Nvidia官网下载CUDA Toolkit并安装&#xff0c;CUDA Toolkit Archive | NVIDIA Developer 安装时按提示下一步即可&#xff0c;安装完成用 …

Django cursor()增删改查和shell环境执行脚本

在Django中&#xff0c;cursor()方法是DatabaseWrapper对象&#xff08;由django.db.connectio提供&#xff09;的一个方法&#xff0c;用于创建一个游标对象。这个游标对象可以用来执行SQL命令&#xff0c;从而实现对数据库的增删改查操作。 查询&#xff08;Select&#xff0…

C++初学者指南-5.标准库(第一部分)--标准库查询存在算法

C初学者指南-5.标准库(第一部分)–标准库查询存在算法 文章目录 C初学者指南-5.标准库(第一部分)--标准库查询存在算法any_of / all_of / none_ofcountcount_if相关内容 不熟悉 C 的标准库算法&#xff1f; ⇒ 简介 any_of / all_of / none_of 如果在输入范围(所有元素…

2024最新教程,在docker中安装kali,并配置ssh连接

docker的基本使用&#xff1a;搭建高效攻防靶场vulfocus与Docker仓库管理实战&#xff1a;从听说到入门 拉取kali官方镜像 docker pull kalilinux/kali-rolling 启动一个kali镜像&#xff0c;将容器中的22端口映射到主机100端口&#xff0c;方便ssh直接连接 docker run -it…

Unity UGUI 之 Toggle

​本文仅作学习笔记与交流&#xff0c;不作任何商业用途本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正​ 1.什么是Toggle&#xff1f; Unity - Manual: Toggle 带复选框的开关&#xff0c;可…

linux-二元信号量和计数信号量-生产者消费者模型以及用二元信号量实现-死锁(2)-侠义消息队列(fifo)-proc文件系统

二元信号量和计数信号量的区别&#xff1a; 二元信号量和计数信号量在嵌入式系统和多任务环境中都是重要的同步机制&#xff0c;用于控制对共享资源的访问。它们之间的主要区别体现在以下几个方面&#xff1a; 1. 状态表示 二元信号量&#xff08;Binary Semaphore&#xff09;…

[计算机基础]一、计算机组成原理

计算机组成原理的考察目标为&#xff1a; 1. 掌握单处理器计算机系统中主要部件的工作原理、组成结构以及相互连接方式。 2. 掌握指令集体系结构的基本知识和基本实现方法&#xff0c;对计算机硬件相关问题进行分析&#xff0c;并能够对相关部件进行设计。 3. 理解计算机系统的…

HTML5-canvas1

1、canvas&#xff1a;创建画布 <canvas id"canvas"></canvas>2、画一条直线 var canvasdocument.getElementById(cancas&#xff09;; canvas.width800; canvas.height800; var contextcanvas.getContext(2d); //获得2d绘图上下文环境 //画一条直线 c…

算法 - 图论Dijkstra(原理、思路代码实现、以东南大学真题为例讲解手算方法)

Dijkstra 算法原理&#xff1a; Dijkstra算法是一种经典的用于计算单源最短路径的算法。它可以在带权重的图中找到从源节点到所有其他节点的最短路径。Dijkstra算法通过贪心策略&#xff0c;不断选择当前已知最短路径最小的节点&#xff0c;更新其邻接节点的路径长度&#xff…

Linux之旅:常用的指令,热键和权限管理

目录 前言 1. Linux指令 &#xff08;1&#xff09; ls &#xff08;2&#xff09; pwd 和 cd &#xff08;3&#xff09;touch 和 mkdir &#xff08;4&#xff09; rmdir 和 rm &#xff08;5&#xff09;cp &#xff08;6&#xff09;mv &#xff08;7&#xff09;…

Qt窗口介绍

Qt窗口 一、Qt窗口二、菜单栏创建菜单栏在菜单栏中添加菜单创建菜单项在菜单项之间添加分割线综合练习 三、工具栏创建工具栏设置停靠位置设置浮动属性设置移动属性综合练习 四、状态栏状态栏的创建在状态栏中显示实时消息在状态栏显示永久的消息 五、浮动窗口浮动窗口的创建设…

达梦数据库系列—29. DTS迁移ORACLE到DM

目录 1.ORACLE源端信息 2.DM目的端信息 3.DTS 迁移评估 4.数据库迁移 4.1 Oracle 源端数据库准备 4.2 目的端达梦数据库准备 初始化参数设置 兼容性参数设置 表空间规划 用户规划 创建迁移用户和表空间 4.3迁移步骤 创建迁移 配置数据源 配置迁移对象及策略 开…