从零开始手搓一个STM32与机智云的小项目——GPIO模拟时序控制外设1

news2024/11/13 10:40:23

文章目录

  • 前言
    • 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/675973.html

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

相关文章

基于Java+Swing+Mysql实现图书管理系统V2.0

基于JavaSwingMysql实现图书管理系统V2.0 一、系统介绍二、功能展示1.项目内容2.项目骨架3.数据库表4.主界面5.添加6、修改7、查询8、删除 四、其它1.其他系统实现五.获取源码 一、系统介绍 本系统主要有对图书信息的增删改查操作功能。 项目类型&#xff1a;Java SE项目&…

到底还有谁学不会 MySQL 中的视图?

文章目录 MySQL中的视图视图的概念视图的用法简化查询操作提高查询效率保护数据的安全性 视图的代码示例总结 MySQL中的视图 在MySQL中&#xff0c;视图是一种虚拟表&#xff0c;它是由一个或多个基本表的行或列组成的。视图并不实际存储数据&#xff0c;而是根据定义的查询语…

6-端午练习

目录标题 6_221. 进程和线程2. 数据7>>1 6_231. 用户相关指令2. 创建用户(1. 查看id(2. 查看当前用户(3. 创建用户(4. 给新用户添加sudo权限>1 修改sudoers文件 2. 删除用户3. 修改用户名2. 磁盘1. 保证ubuntu链接上U盘(1. VM弹窗&#xff0c;选择链接到虚拟机(2. 虚拟…

DELL的交换机PowerSwitch学习手册-ONIE篇

下面是最近学习DELL的网络交换机PowerSwitch的一些笔记&#xff0c;供朋友们参考。如果还有问题&#xff0c;可以add wechat at StorageExpert。 在具体学习产品之前&#xff0c;先来了解下DELL的ONIE&#xff0c;什么是ONIE&#xff1f;和如何使用ONIE&#xff1f; ONIE是 O…

关于Nginx网站服务

目录 一、首先搭建Nginx服务 二、授权的访问控制 第一步 安装依赖包 第二步 生成用户密码认证文件 第三步 修改文件属性和权限 第四步 修改配置文件 第五步 用浏览器测试网站 三、基于IP地址进行限制 第一步 修改配置文件 第二步 用两台设备进行访问测试 四、基于域…

Linux - 内存、swap、内存回收机制

参考 2023年6月22日 https://zhuanlan.zhihu.com/p/107350459 —— 讨论的swap基于Linux4.4内核代码 内存深度科普: 从堆内存到虚拟内存管理 2023年6月22日 qbittorrent swap 问题 https://github.com/qbittorrent/qBittorrent/issues/12947 massif valgrind --toolmassif qb…

taro实现小程序地图打点

使用taro的map标签&#xff0c;往markers里放入点位&#xff1a; <map v-if"mapLoading" id"mapId":longitude"userPosition.x":latitude"userPosition.y":show-location"false":markers"markerList":scale&q…

Spring Boot 如何使用 JUL 进行日志记录

Spring Boot 如何使用 JUL 进行日志记录 在 Spring Boot 中&#xff0c;我们可以使用多种日志框架进行日志记录。其中&#xff0c;JUL (Java Util Logging) 是 Java 平台自带的日志框架&#xff0c;它提供了简单的 API 和配置&#xff0c;可以轻松地进行日志记录。本文将介绍如…

RocketMQ 常见面试题(一)

RocketMQ Broker 中的消息被消费后会立即删除吗&#xff1f; 不会&#xff0c;每条消息都会持久化到 CommitLog 中&#xff0c;每个 Consumer 连接到 Broker 后会维持消费进度信息&#xff0c;当有消息消费后只是当前Consumer 的消费进度&#xff08;CommitLog 的 offset&…

【计算机视觉】在计算机视觉里,传统卷积已经彻底输给Transformer了吗?

文章目录 一、传统卷积 & Transformer1.1 传统卷积1.2 Transformer 二、知乎高赞回答2.1 作者&#xff1a;知乎用户2.2 作者&#xff1a;王云鹤2.3 作者&#xff1a;知乎用户 一、传统卷积 & Transformer 1.1 传统卷积 传统卷积&#xff08;Traditional Convolution&…

【初识C语言(3)】选择语句+循环语句+函数+数组

文章目录 1. 选择语句2. 循环语句3. 函数4. 数组 C语言是一门结构化的程序设计语言 顺序结构&#xff1b; 选择结构&#xff1b; 循环结构。 1. 选择语句 生活中处处面临着选择&#xff0c;如果你好好学习&#xff0c;校招时拿一个好offer&#xff0c;走上人生巅峰。如果你不学…

案例突破——悲观锁和乐观锁

悲观锁和乐观锁 一、背景介绍二、悲观锁和乐观锁什么是悲观锁什么是乐观锁 三、 在项目中如何使用悲观锁和乐观锁在项目中使用悲观锁实体结构实体对象的xml配置文件对应生成的表结构往表中初始化数据运行之后的结果模拟触发悲观锁的条件核心代码 在项目中使用乐观锁实体结构(添…

【探索 Kubernetes|作业管理 Deployment 篇 系列 12】水平扩展 / 收缩、滚动 / 回滚更新

前言 大家好&#xff0c;我是秋意零。 在上一篇中&#xff0c;我们介绍了控制器的基本设计思想&#xff1a;控制器模式。通过这个 “控制器模式” 我们来看看 Deployment 是如何依靠它来实现的。 最近搞了一个扣扣群&#xff0c;旨在技术交流、博客互助&#xff0c;希望各位…

第40步 深度学习图像识别:DenseNet201建模(Tensorflow)

基于WIN10的64位系统演示 一、写在前面 &#xff08;1&#xff09;DenseNet201 DenseNet201是一种深度卷积神经网络&#xff0c;是DenseNet网络的一种变体。DenseNet&#xff0c;全称Dense Convolutional Network&#xff08;密集卷积网络&#xff09;&#xff0c;是由Faceb…

【VC 7/8】vCenter Server 更新(小版本升级)Ⅱ—— 使用 Shell 命令行更新 vCenter Server

目录 2. 使用 Shell 升级 vCenter Server&#xff08;1&#xff09;下载更新 ISO 镜像&#xff08;2&#xff09;挂载 ISO 镜像&#xff08;3&#xff09;验证 ISO 镜像已被挂载通过VAMI 更新界面将ISO 挂载到 VC 的文件系统 &#xff08;4&#xff09;更新 VC | 安装 vCenter …

Elasticsearch:如何通过 3 个简单步骤从 Elastic 数据中删除个人身份信息

作者&#xff1a;Peter Titov 对于任何组织来说&#xff0c;个人身份信息 (Personally Identifiable information, PII) 合规性都是一个日益严峻的挑战。 无论你是在电子商务、银行、医疗保健还是其他数据敏感的领域&#xff0c;PII 都可能会在无意中被捕获和存储。 拥有结构化…

丰田汽车投资人要求董事长下台

&#x1f699; 丰田电动车推广不力&#xff0c;股东要求董事长下台 Toyota faced down two proxy votes at its annual general meeting. In an unusual challenge to the management of a Japanese company, activist investors in America and Europe recommended voting aga…

跨境电商产品的评价怎么获取?

对于在亚马逊、沃尔玛、eBay、Wish、Newegg、速卖通、阿里国际站、Shopee、Lazada、Temu、乐天、Toktok、Joom、Ozon等跨境电商平台的卖家来说&#xff0c;产品评价和补单&#xff08;增加订单数&#xff09;是一个常见但至关重要的话题 优质的产品评价可以向潜在买家展示我们…

想学习大数据,主要学什么?

什么是大数据 什么是“大数据”呢&#xff1f;如果从字面意思来看&#xff0c;大数据指的是巨量数据。那么可能有人会问&#xff0c;多大量级的数据才叫大数据&#xff1f;不同的机构或学者有不同的理解&#xff0c;难以有一个非常定量的定义&#xff0c;只能说&#xff0c;大…

【技术干货】高精度室内定位方案,影响UWB定位精度的因素分析

物联网时代&#xff0c;室内定位已然成为物联网建设的技术纽带&#xff0c;想要真正发挥位置数据的价值&#xff0c;就需要采集的位置数据有足够精度。基于UWB技术的厘米级UWB高精度室内定位方案已广泛应用于物联网各行业领域的人员定位及资产管理。本篇小编就来带大家了解一下…