GPIO模拟时序控制外设1——WS2812B

news2024/9/20 16:49:27

文章目录

  • 前言
    • WS2812B
      • 1.模块简介
      • 2.时序介绍
      • 3.硬件介绍
      • 4.传输速率,以及帧数要求
    • 代码
      • 1.初始化
      • 2.模拟时序
        • 1.复位函数
        • 2.发送0、1码
        • 3.封装发送函数
  • 总结

前言

上一篇文章中介绍了整个板子的最基本功能模块——使用GPIO的通用输入输出实现简单的按键输入以及推挽输出控制的功能。本文深入一步,在只使用GPIO的输入输出功能的基础上,通过查看对应模块的芯片手册,模拟其对应的通信时序来驱动对应的模块。

WS2812B

首先来个网红模块——WS2812B的彩灯,它在RGB灯的邻域可以说是一方霸主的存在,内部集成了驱动,可以实现三色(255 * 255 * 255=16777216种颜色)的全真色彩,且支持串行控制,单片机可以通过一个GPIO实现对一组灯的控制。详细的特征可以看芯片手册的介绍。
在这里插入图片描述
在这里插入图片描述

1.模块简介

在这里插入图片描述
通过手册的产品概述,可以看出其大致的控制逻辑:
1.单个灯需要一个24位的数据来控制;
2.同时控制多个点时,需要根据串联的灯个数来发送对应个数的24bit控制数据;
3.其复位时间需要280us以上的时间。
在这里插入图片描述
上面提到了一个24bit的控制数据,那么这个24bit的数据每一位代表的含义是什么呢?
在数据手册中也有介绍,如下图所示:24bit的数据是由 绿 红 蓝 三个颜色的色度数据拼接而成的,当数据是0xFF 00 00时,对应的灯会亮起绿色,当数据是0x 00 FF 00时小灯是红色,当数据是0x 00 00 FF 时小灯是蓝色。根据数值的不同可以组合出不同的颜色。
在这里插入图片描述
这里给个颜色表供大家去查找自己想要的颜色——https://tool.oschina.net/commons?type=3
在这里插入图片描述
需要注意的是,写入的24bit数据的顺序是G R B (绿红蓝),而在此查询到的颜色数据是R G B(红绿蓝),在控制时需要调换一下顺序。
那么到此是不是就可以开始写代码了呢?

2.时序介绍

答案是否定的,为了提高数据传输的稳定性,尽可能的消除信号传输过程中的干扰;模块会采取特殊的输出格式来区分电平的0与1。在上一篇的GPIO输入输出中,STM32的“0”就是对应低电平,“1”就是对应高电平,但是对于WS2812B来说,“0”与“1”对应的并不是单纯的高低电平,而是需要根据如下的时序波形图:
在这里插入图片描述
直接看这个描述可能会有点抽象,这里笔者用逻辑分析仪抓取了一段波形,我们一起结合上面的描述来分析一下:
在这里插入图片描述
这是使用STM32模拟时序来控制一个RGB显示113355的蓝色的数据脚的实际采样波形。根据上面的时序波形图做个对照,
在这里插入图片描述
可以依次读出整段波形发送的数据内容是
0011 0011 0001 0001 0101 0101
分别对应三个颜色 G R B ()绿 红 蓝
而波形的最开头和最后结尾的一个很长的低电平就是对应的复位信号。
最终逻辑分析仪解析出来的数据如下图所示:
在这里插入图片描述
至此,就已经基本上那个搞清楚了WS2812B单个灯的通信时序了。唯一还没确定的数据是发送0和1分别对应的具体高低电平时间,时序图中给定的是一个范围,实际使用时需要自己进行微调,这个我们放到后面的代码编写中再定。
搞定了单个灯的控制,接下里就是多个灯的串行控制了
在这里插入图片描述
根据上图的数据传输逻辑,可以知道,控制多个灯时,只需要连续发送多组24位数据就可以了,比如说要控制八个灯,就一次发送8个24位数据,中间不断,当需要修改时,在最后加一个大于280us的复位信号即可。
下图中,红色部分是实际发送数据的波形,中间的低电平端就是复位信号。
在这里插入图片描述
为了方便理解,我们将上图中中间的那一段红色波形给放大一点,此时就可以看出,一次发送了两个24bit的控制数据,此时会有两个串联的小灯被点亮。
在这里插入图片描述
好了,基本捋清控制的思路了,接下来再来看看手册中的其他需要注意的参数。

3.硬件介绍

每个灯珠都是如下所示的四个脚位,其中,VDD与VSS是提供电源的,DIN是输入信号,而DOUT则是数据输出脚,需要一次控制多个灯时,只需要将前一个灯的数据输出脚与后一个灯的数据输入脚连接在一起即可实现串行控制。这里我们可以对照引脚介绍图,找到对应的管脚,方便使用逻辑分析仪抓取波形以及其他的调试。
在这里插入图片描述
在这里插入图片描述

4.传输速率,以及帧数要求

手册里的其他内容大部分都和硬件相关了,这里不做赘述,需要的同学自己去查看,这里还有两个编程需要注意的重要参数,一个就是但刷速录大于30帧/秒时,至少要有1024个灯,而我们这里只有八个灯,所以1s内控制的灯数据包不能超过30个。
另一个就更重要了,数据的发送速度可达800Kbps

插补一点小知识:bps是bit/s ,叫做比特率,也叫码率就是每秒可以传输的数据位数
而baud/s,是波特率,字节每秒的意思,也就是一秒可以传输多少个字节的数据。(一个字节等于八位数据位)。
在这里插入图片描述

也就是说,WS2812B最高支持的数据包传输速率是1s接收800 000 位个2进制的数据,换算下来就是每个二进制位最少最少要保持1/800 000=1.25us的时间,还记得前面的时序图吗,单个数据“0”或者单个数据“1”是由不同的高低电平时间段组合成的,也就是说,单个数据“1”或者单个数据“0”的高低电平时间之和不能少于1250ns。
T0H+T0L>1250ns
T1H+T1L>1250ns
在这里插入图片描述
搞清楚这些以后,差不多就可以开始写代码了。
在这之前还需要去瞧一眼原理图,找到这个板子中WS2812B的控制引脚,通过原理图可以看出,控制脚是GPIOA_8。
在这里插入图片描述
为啥选用PA8呢,这里笔者做了两手准备,WS2812B的常用驱动方式有PWM、SPI来配合DMA的高效率方式,这种驱动方案不占用CPU的资源;另外一种就是使用纯GPIO模拟输出的方案,为了兼顾,所以笔者选了个有复用功能的IO口。只不过,我们这里灯少,8个灯,选则软件模拟的方案也没啥大问题,加上这里的四个定时器都有他用了,为了避免干扰,所以最终选择软件模拟的方案来实现。想看PWM+DMA或者SPI+DMA的方案的可以自己去搜索一下哈,我看CSDN很多大佬都是用的这两种方案,讲的也很细致。
在这里插入图片描述
另外就是供电部分,这里笔者选用的是5V供电。

代码

1.初始化

根据上面的总结,这里选用GPIO模拟时序图的方案来进行,首先既要输出高又要输出低,所以GPIOA8需要配置为通用推挽输出模式,这个不再赘述了。

/*********************************
函数名:RGB_Init
函数功能:RGB初始化
形参:void
返回值:void
备注:
RGB-----PA8--------通用推挽输出
**********************************/
void RGB_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;//定义一个结构体的变量
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//初始化GPIOA端口的时钟
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode =  GPIO_Mode_Out_PP;//通用推挽输出
	GPIO_Init(GPIOA,&GPIO_InitStructure );
}

为了方便操作,这里最好是进行宏定义输出高输出低
这里需要注意,结合上一篇的介绍,我们知道,控制GPIO的输出可以使用库函数,也可以使用位带操作,除此之外还有一种执行效率更高,时延更低的方案,直接使用寄存器来驱动。这里笔者把三种宏定义都贴出来,原因是,这个模块它设计到ns的延时,多运行一条代码都会跑过30ns-50ns,这是会严重影响控制效果的。三种宏定义大家自己选择一种即可,最快的是寄存器的,然后位带操作与库函数的差不多。

//位带操作
#define RGB_H   PAout(8)=1
#define RGB_L   PAout(8)=0
//库函数
#define RGB_H   GPIO_SetBits(GPIOA,GPIO_Pin_8)
#define RGB_L   GPIO_ResetBits(GPIOA,GPIO_Pin_8)
//直接操作寄存器(最快)
#define RGB_H   GPIOA->ODR |= (1<<8)
#define RGB_L   GPIOA->ODR &= ~(1<<8)

2.模拟时序

搞定了初始化以后,就该开始最重要的部分了,对照时序图,模拟出时序。
对于这类需要模拟时序的模块,我们一定要自底向上的思想,从最基础的发送“0”,发送“1”开始写,将发0与发1封装成对应的函数;除此之外还需要根据模块的实际需求封装复位、初始化之类的最底层函数,这里WS2812B就还需要一个复位的函数。封装好这些函数后,再来根据时序进行数据包发送的函数封装,进而拼凑出整个模块的时序代码。

1.复位函数

先来最简单的,复位函数,根据数据手册介绍,复位电路的时序就是将信号脚拉低,拉低时间不少于280us,这里我们给个350us。这里的延时是us级的,所以可以使用系统滴答的us延时来实现,系统滴答是所以延时中最准的了,定时器都没他准,所以这里最好是用系统滴答,关于系统滴答的介绍估计得放到和GPIO复用也就是定时器那一部分一起介绍,这里先用。

/*********************************
函数名:RGB_Reset
函数功能:RGB复位信号
形参:void
返回值:void
备注:
**********************************/
void RGB_Reset(void)
{
	RGB_L;
	Systick_Delay_us(350);//调用系统滴答来实现
}

2.发送0、1码

首先需要解决的是ns的延时问题,STM32F103C8T6的主频是72MHZ,也就是1s可以运行72 000 000条机器指令。系统滴答和定时器只能提供us级的延时,这个ns延时只能采用运行固定数量的机器指令来进行了。
计算过程如下:
1s 可以运行72 000 000 条机器指令,那么
1us 可以运行 72条机器指令,换句话说,CPU执行72个机器指令花费的时间是1us。那么1个机器指令花费的时间就是1us/72=13.88889ns;也就是说,CPU每运行一个机器指令,时间过去了13.8889ns。
然后再来看具体的时序图:
在这里插入图片描述
这里0码的高电平时间T0H是要在220ns~380ns之间的,而低电平时间T0L要控制在580ns-1600ns之间,而且T0H+T0L>1250ns
这里我们先定T0H,折中,选择300ns,
300/13.889=21.5999,四舍五入,取22个机器指令,
那么问题来了,我们平时都是用的C代码,一条C代码等于一个机器指令吗?
答案是否定的,一条C代码与机器指令之间没有固定的关系,这是因为每条C代码的底层汇编代码都不一样。但是好在留有专门的机器指令__nop()
一个__nop()就是一个机器指令。
于是得到了第一个延时:

//220-380 ns折中 300 13.89*22=305.5558
void delay_300ns(void)  //72 000 000MHZ  ==1s  72hz 1us  一个机器指令周期要耗时13.89ns
{
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();
}

然后来解决第二个延时还是采取去中间值的方案,取1090ns,
1090/13.889=78.4,四舍五入取78个
于是得到第二个延时

//折中 1090 78*13.889
void delay_1090ns(void)  //72 000 000MHZ  ==1s  72hz 1us  一个机器指令周期要耗时13.8ns
{
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
	__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
}

于是0码的发送代码就有了

/*********************************
函数名:RGB_Send0
函数功能:RGB发送0
形参:void
返回值:void
备注:
**********************************/
void RGB_Send0()
{
	RGB_H;
	delay_300ns();
	RGB_L;
	delay_1090ns();
}

依次类推可以退出1码的发送,延时可以就用上面的,不过最好是再弄一个delay_320ns的。实测delay_320的效果会好一点。

/*********************************
函数名:RGB_Send1
函数功能:RGB发送1
形参:void
返回值:void
备注:
**********************************/
void RGB_Send1()
{
	RGB_H;
	delay_1090ns();
	RGB_L;
	delay_320ns();
}

3.封装发送函数

前面介绍过24bit的数据分别对应着GRB的8位,所以这里先整个8位数据的发送函数,高位先发,于是可以得到如下代码:


/*********************************
函数名:RGB_Send_Data
函数功能:RGB发送8位数据
形参:u8 data需要发送的数据
返回值:void
备注:
**********************************/
void RGB_Send_Data(u8 data) 
{ 
   uint8_t i;
   for(i=8;i>0;i--) 
   {
     if(data & 0x80)//按位与,为真发送1,为假发送零
     {
        RGB_Send1();
     }
     else
     {
        RGB_Send0();
     }
     data <<=1;//
   }
}

然后再来封装一个发送24bit的函数

/*********************************
函数名:Send_GRB
函数功能:GRB发送24位数据
形参:u8 G,u8 R,u8 B
返回值:void
备注:
**********************************/
void Send_GRB(uint8_t G,uint8_t R,uint8_t B)
{
	RGB_Send_Data(G);
	RGB_Send_Data(R);
	RGB_Send_Data(B);
	RGB_Reset();
}

有了这个函数就已经可以实现单个灯的控制了,
在这里插入图片描述
在这里插入图片描述
然后为了控制后面的灯,我们需要封装一个控制多个灯的函数,根据前面分析的时序,
代码如下:


/*********************************
函数名:Continuous_Set_LED
函数功能:设置n个灯为 GRB的颜色
形参:u8 G,u8 R,u8 B
返回值:uint8_t n多少个灯
,uint32_t GRB设置的颜色
备注:
**********************************/
void Continuous_Set_LED(uint8_t n,uint32_t GRB)
{
	while(n--)
	{
		RGB_Send_Data((GRB>>16)&0xFF);
		RGB_Send_Data((GRB>>8)&0xFF);
		RGB_Send_Data((GRB>>0)&0xFF);
	}
	RGB_Reset();
}

调用这个函数就可以实现对指定个数的灯实现控制了,当然进一步还可以做流水,滚动,等等功能,这个有需要的就去查阅一下其他大佬的代码吧。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

至此,关于WS2812B的模拟驱动就介绍完了,文如有不足欢迎批评指正,下一篇继续使用模拟时序完成对DHT11的温湿度数据获取,先放个逻辑分析抓取的数据波形解析在这里供大家参考。
在这里插入图片描述

在这里插入图片描述

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

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

相关文章

【若依分离版操作方法日志与其它业务数据(如入库操作)关联的实现】

若依框架业务表和操作日志表关联 需求&#xff1a;分析&#xff1a;思路&#xff1a;实现: 需求&#xff1a; 基于若依分离版框架业务数据与操作日志的数据做个关联 入库表 /*入库业务表 */ CREATE TABLE [BS_In]( [Id] int IDENTITY(1,1)…

【MySQL 主从复制与读写分离】

目录 一、MySQL主从复制1.1、主从复制架构和原理1.2、MySQL的扩展1.2.1、什么是读写分离1. 读写分离的基本原理2. MySQL 读写分离原理 1.2.2、为什么要读写分离1.2.3、什么时候要读写分离1.2.4、主从复制与读写分离1.2.5、mysql支持的复制类型 1.3、复制的共用1.4、复制架构1.5…

2023年下半年北京/上海/深圳NPDP产品经理认证招生

产品经理国际资格认证NPDP是新产品开发方面的认证&#xff0c;集理论、方法与实践为一体的全方位的知识体系&#xff0c;为公司组织层级进行规划、决策、执行提供良好的方法体系支撑。 【认证机构】 产品开发与管理协会&#xff08;PDMA&#xff09;成立于1979年&#xff0c;是…

java学习记录之DBUtils

1 jdbc回顾 1.1 批处理  Statement 执行多条sql addBatch(sql) clearBatch() int[] executeBatch()  PreparedStatement 执行一条sql语句&#xff0c;多组参数 addBatch() 执行之前必须设置实际参数 psmt.setXxxx() clearBatch() executeBatch() 1.2 事务  事务&…

SpringBoot 整合redis + Aop防止重复提交 (简易)

1.redis的安装 redis下载 解压 安装 # wget http://download.redis.io/releases/redis-6.0.8.tar.gz # tar xzf redis-6.0.8.tar.gz # cd redis-6.0.8 # make 看一下就会有 进入redis-6.0.8下的src目录 [rootVM-16-8-centos redis]# cd redis-6.0.8 [rootVM-16-8-centos re…

【Java】Java 中的引用类型

本文仅供学习参考&#xff01; Java是一种类型化语言&#xff0c;这本质上意味着声明的每个变量都有与之关联的特定类型。此类型确定它可以存储的值。例如&#xff0c;整数类型可以存储非小数。也称为数据类型&#xff0c;这可以大致分为两类&#xff1a;基元和引用。基元类型是…

华为云GaussDB,能否成为数据库国产化替代的“更优选择”?

没有一个行业比数据库更需要长期主义&#xff0c;而在践行长期主义的道路上&#xff0c;国内数据库厂商中华为是不可忽视的存在。 近日&#xff0c;Gartner Peer Insights《Voice of the Customer for Cloud Database Management Systems&#xff0c;2023》报告发布&#xff0…

使用python unittest从零构建web应用的自动化测试用例

文章目录 必要性使用pycharm搭建unittest框架selenium下载web driverweb driver的基本使用driver 定位元素driver使用事件处理下拉框处理复选框 sshftp数据库sqlserveroracle安装使用 mongodb excelpycurl 必要性 大部分团队起始对于要不要投入资源进行UI自动化测试的开发都是…

泰裤辣!Corona 10强势来袭,蓝海创意云同步更新支持

2023年6月22日&#xff0c;Chaos Corona官网发布了Chaos Corona 10 for 3ds Max and Cinema 4D版本。本次不仅更新了新功能&#xff0c;还优化了渲染卡顿的问题&#xff0c;一起来看看有哪些实用的好玩意吧&#xff01; 1.贴花影响特定通道 CR10在你的场景中焕然一新&#xff…

Git报错: Please move or remove them before you switch branches.

Bug记录&#xff1a;在我写需求的时候&#xff0c;产品说上个包有崩溃&#xff0c;于是我就控制台 git checkout切分支&#xff0c;结果报错Please move or remove them before you switch branches.下面是被改动的文件&#xff0c;因为是项目build的时候产生的临时文件&#x…

在虚幻引擎中创建大气的HIMIL电影作品

今天瑞云渲染小编给大家带来了关于电影制片人Tiziano Fioriti展示了《H I M I L》项目背后的工作流程&#xff0c;解释了人工智能是如何用于细节的&#xff0c;并谈到了设置火光的问题。 介绍 大家好&#xff0c;我叫Tiziano Fioriti&#xff0c;是来自意大利的自由电影制作人…

RabbitMQ管理界面介绍

1.管理界面概览 connections&#xff1a; 无论生产者还是消费者&#xff0c;都需要与RabbitMQ建立连接后才可以完成消息的生产和消费&#xff0c;在这里可以查看连接情况 channels&#xff1a; 通道&#xff0c;建立连接后&#xff0c;会形成通道&#xff0c;消息的投递获取依…

高阶常系数微分方程——笔记整理

首先&#xff0c;介绍基本知识&#xff1a; 首先&#xff0c;我们对式子进行处理得到&#xff1a; 得到特征根是 和 0, 所以其通解 假设特解是,带入方程所得&#xff1a;

Nik Color Efex 滤镜库

Nik Color Efex 滤镜库中提供了 55 个滤镜。有关这些滤镜的详细参数说明请分别参阅&#xff1a; 1 ~ 11 《Nik Color Efex 滤镜详解&#xff08;1/5&#xff09;》 12 ~ 22 《Nik Color Efex 滤镜详解&#xff08;2/5&#xff09;》 23 ~ 33 《Nik Color Efex 滤镜详解&#x…

2023年java还是golang还是c#?

前言 我们可以先来看一下这三门语言各自的优劣 学习曲线&#xff1a;如果你是初学者或对编程相对陌生&#xff0c;Java可能是一个较好的选择。它有广泛的学习资源和社区支持&#xff0c;易于上手。Go也有简单易学的特点&#xff0c;但由于相对较年轻&#xff0c;相关的学习资…

为何收入或存款增量难找存量告急

其实&#xff0c;和年纪关系不太大吧&#xff0c;平凡的普通人都没多少存款。 *近日&#xff0c;有调查称“大概五分之一的年轻人存款在一万元以内。10万元存款是一个“坎”&#xff0c;存款超过10万就会超过53.7%的人。”“年轻人”“存款”两个词碰撞在一起&#xff0c;引来了…

python3中http协议提供文件服务器功能

http协议是互联网的通用基础协议&#xff0c;也可以利用其来开发文件服务器&#xff0c;给客户提供文件浏览&#xff0c;查看&#xff0c;下载&#xff0c;上传等功能。 目录 1.python3自带http文件服务 2.python3从头开发http文件服务 1.python3自带http文件服务 python3中…

资源调度框架 YARN

3.1.1 什么是YARN Yet Another Resource Negotiator, 另一种资源协调者通用资源管理系统为上层应用提供统一的资源管理和调度&#xff0c;为集群在利用率、资源统一管理和数据共享等方面带来了巨大好处 3.1.2 YARN产生背景 通用资源管理系统 Hadoop数据分布式存储&#xff08…

【打杂记录】-能否开发一个报账系统,自动批量识别与完成报账任务?

能否开发一个报账系统&#xff0c;自动批量识别与完成报账任务&#xff1f; 今天这篇博客&#xff0c;我想说些非技术性语言。我在研二一年负责实验室的报账工作&#xff0c;近期终于有机会将工作交接给下一位负责人&#xff0c;我的科研时间又回来了。 在这一年里&#xff0c…

centos7 挂载未分配的空间新增卷

一、系统环境 操作系统&#xff1a;Centos 7 已配置环境&#xff1a;空 二、磁盘挂载到新目录&#xff08;磁盘挂载&#xff09; 2.1 查找新硬盘 查看机器所挂硬盘及分区情况&#xff1a; fdisk -l 复制 红框圈中的即是本次要挂载的磁盘&#xff0c;与 /dev/sda 和 /de…