STC15F104W驱动WS2812

news/2024/11/16 14:23:15/

STC15F104W驱动WS2812

提示:学习目标:了解WS2812的驱动原理,写出驱动代码。


最近开了个专栏,专门用来学习ESP32S3的,当然,代码也适用于ESP32板子,改下引脚号就可以的。感兴趣的可以看看。
https://blog.csdn.net/weixin_38476200/article/details/129828874


内容:

提示:这里可以添加要学的内容
例如:
1、 连接WS2812时序;
2、 基于STC15F104W写出驱动程序。
3、 软件调试计算程序的机器周期。
4、 测试程序,并根据示波器得到的实际机器周期时间重新调整驱动程序


正文:

1,WS2812时序

WS2812是一个集控制电路与发光电路于一体的智能外控LED光源,每个ws2812均含有4个引脚,引脚功能如下图:
在这里插入图片描述
WS2812可采用级联的方式,将上一个WS2812的DOUT引脚连接到下一个WS2812的DIN引脚,即可实现一个引脚控制多个WS2812。一般自己设计电路时还要在电源输入处添加一个0.1uF的小电容进行滤波。注意,虽然理论上可以连接足够多的ws2812,但是使用时要注意驱动电压和驱动电流是否足够驱动这些彩灯。
在这里插入图片描述
由于只有一个引脚控制ws2812,所以我们只能通过控制引脚输出高、低电平的时间,来让WS2812知道我们想让他显示哪一个灯,显示什么颜色。
想要让1个ws2812显示我们想要的颜色,我们需要给它发送颜色数据。每一个ws2812的颜色数据都是24位:8位绿色+8位红色+8位蓝色。
在这里插入图片描述
如果我们想控制1个ws2812,我们发送24位颜色数据;如果我们想控制2个ws2812,我们需要连续发送48位颜色数据;如果我们想控制n个ws2812,我们需要连续发送n*24位颜色数据。
在这里插入图片描述

那么ws2812如何知道我们发送的每一个位的数据是“1”还是“0”呢?
WS2812手册中对于数据有明确的时间定义:
在这里插入图片描述
在这里插入图片描述
可以看出,当我们想要发送一个位的数据时,如果这个位是“1”,我们就控制单片机的引脚输出高电平0.85us,然后控制引脚输出低电平0.40us;如果这个位是“0”,则控制引脚输出高电平0.40us,然后控制引脚输出低电平0.85us。

而如果我们想控制1个灯显示颜色,我们需要发送24个位的颜色数据给ws2812。如果我们想控制n个灯显示颜色,我们从第1个灯的颜色数据开始发送,直至发送完n*24个数据。最后我们需要控制单片机引脚输出低电平超过50us,让彩灯显示颜色。

2,写出驱动程序

注意,STC15F104W只有8个引脚,我们一般采用内部晶振电路。下面的代码都是基于12MHz的晶振频率写的。WS2812对于单片机的引脚时序要求比较高。我们一般调用intrins.h这个头文件的机器周期函数,nop()函数执行一次占用一个机器周期(此处时钟周期和机器周期相等),所以每条机器周期时间为1/12MHz=83.3us。(代码中很多处使用宏定义,宏定义在预编译阶段程序就完成代码替代工作,不会影响程序的执行时间,且修改方便)
下面是h文件代码:

#ifndef __WS2812_H
#define __WS2812_H//头文件区
#include <STC15F2K60S2.H>
#include <intrins.h>//用户修改参数区
//#define WS2812_FREQUENCY
#define RGB_PIN           P33                       //控制彩灯引脚(需要配置为强推挽输出)
#define WS2812_MAX        25                        //彩灯最大个数
#define WS2812_NUMBERS    8                         //彩灯个数#define RED               0xff0000                  //红色
#define GREEN             0x00ff00                  //绿色
#define BLUE              0x0000ff                  //蓝色
#define BLACK             0x000000                  //熄灭
#define WHITE             0xffffff                  //白色#define RGB_PIN_H()  RGB_PIN = 1
#define RGB_PIN_L()  RGB_PIN = 0
#define delay1NOP()  _nop_();
#define delay1NOP()  _nop_();
#define delay2NOP()  delay1NOP(); _nop_();
#define delay3NOP()  delay2NOP();_nop_();
#define delay5NOP()  delay3NOP();delay2NOP();
#define delay7NOP()  delay5NOP();delay2NOP();void Ws2812b_WriteByte(unsigned char byte);//发送一个字节数据(@12.000MHz,理论每个机器周期83ns,测试约为76ns)                                                      
void setLedCount(unsigned char count);//设置彩灯数目,范围0-25.                                                           
unsigned char getLedCount();//彩灯数目查询函数                                                                     
void rgb_SetColor(unsigned char LedId, unsigned long color);//设置彩灯颜色                                     
void rgb_SetRGB(unsigned char LedId, unsigned long red, unsigned long green, unsigned long blue);//设置彩灯颜色
void rgb_SendArray();//发送彩灯数据                                                                            
#endif                                                                      

接着我们对c代码进行详解。
首先调用h文件,然后定义一个数组,用来存放彩灯的颜色数据。这里数据存放在data空间(空间有限,只能存放25个灯左右的数据),后面有需要再优化到xdata空间。ledsCount 记录实际我们想要控制的彩灯的数目,nbLedsBytes 用来记录彩灯的数据个数(一个灯需要3个字节)。

#include "ws2812.h"unsigned char LedsArray[WS2812_MAX * 3];      //定义颜色数据存储数组
unsigned int ledsCount = WS2812_NUMBERS;      //定义实际彩灯默认个数
unsigned int nbLedsBytes = WS2812_NUMBERS*3;  //定义实际彩灯颜色数据个数

接着我们开始编写彩灯的设置数目函数,查询数目函数,以及两个设置指定彩灯颜色的函数。

//设置彩灯数目,范围0-25.
void setLedCount(unsigned char count)
{ledsCount = WS2812_MAX > count ? count : WS2812_MAX;nbLedsBytes = ledsCount*3;
}//彩灯数目查询函数
unsigned char getLedCount()
{return ledsCount;
}//设置彩灯颜色(在这里我将绿和红色进行颠倒,这样比较符合我们日常生活的红绿蓝的顺序)
void rgb_SetColor(unsigned char LedId, unsigned long color)
{if( LedId > ledsCount ){return;    //to avoid overflow}LedsArray[LedId * 3]     = (color>>8)&0xff;LedsArray[LedId * 3 + 1] = (color>>16)&0xff;LedsArray[LedId * 3 + 2] = (color>>0)&0xff;
}//设置彩灯颜色
void rgb_SetRGB(unsigned char LedId, unsigned long red, unsigned long green, unsigned long blue)
{unsigned long Color=red<<16|green<<8|blue;rgb_SetColor(LedId,Color);
}

然后我们对彩灯数据通过引脚发送出去。注意,发送彩灯数据的过程中,请将中断关闭,否则有可能会导致数据发送到一半被中断打断,导致显示异常。Ws2812b_WriteByte()函数是这个驱动代码的核心。

//发送彩灯数据
void rgb_SendArray()
{unsigned int i;bit a=EA;//发送数据EA=0;for(i=0; i<nbLedsBytes; i++)Ws2812b_WriteByte(LedsArray[i]);EA=a;
}

下面的Ws2812b_WriteByte函数是我基于stc15f104w调试的。我写过不同单片机的驱动。不同单片机的每条指令花费的时间都不一样。如果您想要采用其他系列的单片机,我们只需要修改Ws2812b_WriteByte()函数里面的内容即可。其他的函数可以不做修改。方便代码的移植。
主要修改的地方是高电平总的拉高时间。这里之所以不对“1”和“0”的内容进行函数封装,是为了我自己调试的方便。如果您觉得代码不够简短好看,您可以将判断条件里面的内容用宏定义进行一下封装。

/*
//使用12.000MHz频率,理论每个机器周期83ns,实际测试约为76ns。
//下面都是基于76ns纳秒一个机器周期计算的。
//测试时发现可以支持12-20Mhz频率。
//如果想要使用11.0592Mhz频率,在h文件取消“#define WS2812_FREQUENCY”的注释
//在实际测试中发现,ws2812对高电平时间较为敏感,对低电平时间不敏感
//也就是说,低电平时间可以稍微长一些也不会影响程序,但是高电平时间需要控制的比较准确才行。
*/
void Ws2812b_WriteByte(unsigned char byte)
{
#ifndef WS2812_FREQUENCYif(byte & 0x80){RGB_PIN_H();//4个机器周期(STC15F104W的拉高拉低都需要4个机器周期,其他系列暂时不知道,但是很大可能不是4个机器周期)delay7NOP();//7个机器周期RGB_PIN_L();//4+8(跳出这个if判断需要5个机器周期,再进入下一个if判断需要3个机器周期)}else{RGB_PIN_H();delay2NOP();//4+2RGB_PIN_L();delay3NOP();//4+3+5(跳出这个else需要3个机器周期,进入下一个else则需要2个机器周期)}if(byte & 0x40){RGB_PIN_H();//delay7NOP();//4+7RGB_PIN_L();//4+8}else{RGB_PIN_H();delay2NOP();//4+2RGB_PIN_L();delay3NOP();//4+3+5}if(byte & 0x20){RGB_PIN_H();//delay7NOP();//4+7RGB_PIN_L();//4+8}else{RGB_PIN_H();delay2NOP();//4+2RGB_PIN_L();delay3NOP();//4+3+5}if(byte & 0x10){RGB_PIN_H();//delay7NOP();//4+7RGB_PIN_L();//4+8}else{RGB_PIN_H();delay2NOP();//4+2RGB_PIN_L();delay3NOP();//4+3+5}if(byte & 0x8){RGB_PIN_H();//delay7NOP();//4+7RGB_PIN_L();//4+8}else{RGB_PIN_H();delay2NOP();//4+2RGB_PIN_L();delay3NOP();//4+3+5}if(byte & 0x4){RGB_PIN_H();//delay7NOP();//4+7RGB_PIN_L();//4+8}else{RGB_PIN_H();delay2NOP();//4+2RGB_PIN_L();delay3NOP();//4+3+5}if(byte & 0x2){RGB_PIN_H();//delay7NOP();//4+7RGB_PIN_L();//4+8}else{RGB_PIN_H();delay2NOP();//4+2RGB_PIN_L();delay3NOP();//4+3+5}if(byte & 0x1){RGB_PIN_H();//delay7NOP();//4+7RGB_PIN_L();//4+8}else{RGB_PIN_H();delay2NOP();//4+2RGB_PIN_L();delay3NOP();//4+3+5}
#elseif(byte & 0x80){RGB_PIN_H();//delay5NOP();//4+5RGB_PIN_L();//4+8}else{RGB_PIN_H();delay1NOP();//4+1RGB_PIN_L();delay2NOP();//4+2+5}if(byte & 0x40){RGB_PIN_H();//delay5NOP();//4+5RGB_PIN_L();//4+8}else{RGB_PIN_H();delay1NOP();//4+1RGB_PIN_L();delay2NOP();//4+2+5}if(byte & 0x20){RGB_PIN_H();//delay5NOP();//4+5RGB_PIN_L();//4+8}else{RGB_PIN_H();delay1NOP();//4+1RGB_PIN_L();delay2NOP();//4+2+5}if(byte & 0x10){RGB_PIN_H();//delay5NOP();//4+5RGB_PIN_L();//4+8}else{RGB_PIN_H();delay1NOP();//4+1RGB_PIN_L();delay2NOP();//4+2+5}if(byte & 0x8){RGB_PIN_H();//delay5NOP();//4+5RGB_PIN_L();//4+8}else{RGB_PIN_H();delay1NOP();//4+1RGB_PIN_L();delay2NOP();//4+2+5}if(byte & 0x4){RGB_PIN_H();//delay5NOP();//4+5RGB_PIN_L();//4+8}else{RGB_PIN_H();delay1NOP();//4+1RGB_PIN_L();delay2NOP();//4+2+5}if(byte & 0x2){RGB_PIN_H();//delay5NOP();//4+5RGB_PIN_L();//4+8}else{RGB_PIN_H();delay1NOP();//4+1RGB_PIN_L();delay2NOP();//4+2+5}if(byte & 0x1){RGB_PIN_H();//delay5NOP();//4+5RGB_PIN_L();//4+8}else{RGB_PIN_H();delay1NOP();//4+1RGB_PIN_L();delay2NOP();//4+2+5}
#endif
}

main文件里面,我是用stc生成了一个300ms的延时函数。让程序每隔300ms更换一下颜色并输出。红绿蓝三色交替闪烁。

#include <STC15F2K60S2.H>
#include "ws2812.h"
void Delay300ms();//@12.000MHz
void main()
{int i=0;setLedCount(12);//可以设置0-25之间的任何数目,根据实际情况而定rgb_SetRGB(0,0,0,0);//设置第一个灯关闭(这里作为使用示例,下面的rgb_SetColor覆盖了这个函数的作用)while(1){for(i=0; i<getLedCount(); i++)//设置所有的灯为红色{rgb_SetColor(i,RED);}rgb_SendArray();//发送给ws2812,显示颜色Delay300ms();for(i=0; i<getLedCount(); i++)//设置所有的灯为绿色{rgb_SetColor(i,GREEN);}rgb_SendArray();//发送给ws2812,显示颜色Delay300ms();for(i=0; i<getLedCount(); i++)//设置所有的灯为蓝色{rgb_SetColor(i,BLUE);}rgb_SendArray();//发送给ws2812,显示颜色Delay300ms();}
}
void Delay300ms()//@12.000MHz
{unsigned char i, j, k;_nop_();_nop_();i = 14;j = 174;k = 224;do{do{while (--k);} while (--j);} while (--i);
}

3,软件调试

确保您的芯片型号选择正确。
在这里插入图片描述
填写软件调试的晶振频率,我这里使用16MHz(只是用来软件计算机器周期,和实际晶振无关)。1/16MHz=62.5ns。如果采用12MHz,我计算机器周期不太方便。
在这里插入图片描述
点击调试。没有配置时默认是软件调试模式。在这里插入图片描述
点击引脚信号的逻辑分析。
在这里插入图片描述
在新弹出的窗口中点击Setup。然后输入您的控制引脚。
在这里插入图片描述
使用调试控件,一边观察波形图。
在这里插入图片描述
在程序执行RGB_PIN_H();这一句之前,我用红色的线定位了此时的时间位置,执行这一句后,我用蓝色的线查看它们之间的差值,差值为0.25us。这里我的软件仿真使用的是16MHz晶振,每个机器周期为62.5ns。0.25us相当于4个机器周期。正如我上面的发送函数里面的机器周期备注一样。同理可以计算出每行代码需要花费的机器周期时间,从而准确写出驱动。
在这里插入图片描述
比如,我们需要0.85us的高电平时间,也就是850ns,然后实际上我们使用的晶振是12MHz。每个机器周期的时间是理论是1/12Mhz=83.3ns。那么我们就需要850/83.3=10.2个机器周期。这里取11个机器周期。根据前面我们知道电平拉高需要用去4个机器周期,因此我们还需要让高电平维持7个机器周期的时间,然后将电平拉低。以此原理,写出我们的Ws2812b_WriteByte()函数的驱动。

4,硬件调试

实际硬件调试时,我写了一个测试程序。我用软件调试时,波形为引脚电平拉高10个机器周期,然后拉低10个机器周期。循环动作。

while(1)
{P33=1;//4delay6NOP();//6P33=0;//4delay2NOP();//2+4
}

但是实际用示波器测试时,发现高电平时间和低电平时间均为3.8格*200ns/格=760ns。所以实际上每个机器周期的时间我这里是76ns左右。距离理论值83.3ns相差不多,而且ws2812每个高电平信号都可以有150ns左右的误差,因此这里按照理论计算和按照实际计算均可。我按理论值计算。

代码链接

上面的代码可以直接复制使用的。最近有点穷,设置5个积分,如果各位路过的大佬觉得对您有帮助,跪求各位大佬赏口饭吃。
https://download.csdn.net/download/weixin_38476200/16163812?spm=1001.2014.3001.5501


http://www.ppmy.cn/news/177136.html

相关文章

【STM8S】STM8S之内外部16M晶振

本篇博文最后修改时间&#xff1a;2016年08月30日&#xff0c;10:24。 一、简介 本文介绍STM8S系列如何分别实现内外部的16M晶振的使用。 二、实验平台 编译软件&#xff1a;IAR for STM8 1.42.2 硬件平台&#xff1a;stm8s003f3p6开发板 仿真器&#xff1a;ST-LINK 库函数版本…

ST2021

2021年要过去了&#xff0c;今年过的太快了。 科研 年初跟导师摊牌&#xff0c;原来方向我搞不来。每天对着论文发呆&#xff0c;感觉自己像根废柴。 在帅师兄的指导下&#xff0c;换了个信息抽取的方向&#xff08;超级超级感恩&#xff01;&#xff01;&#xff01;o(╥﹏…

STC8单片机硬件SPI通信例程W25Q16

SPI全双工通信使用起来相当方便&#xff0c;最常见的是W25Q16存储颗粒的使用了&#xff0c;当重新上电时&#xff0c;存储再W25Q16中的数据不会丢失&#xff0c;仍然可以读取出来&#xff0c;下面就简单讲一下如何使用W25Q16存储芯片。 本篇讲的是使用硬件SPI让单片机和W25Q16进…

SPI Flash Rom W25Q16 ----基于STC15

百度文库一个很详细的介绍、、、、https://wenku.baidu.com/view/7db1401e1a37f111f0855b81.html?fromsearch &#xff08;。。。。&#xff09; 先来简单认识一下这个芯片 W25Q16 其实就是以SPI作为通信时序要求的一款储存芯片&#xff0c;升级版的EEPROM&#xff0c;比EEP…

SIT2515T-I/SO,SIT2515T-I/ST,MCP2515T-I/ST,MCP2515T-I/SO

SIT2515T-I/SO,SIT2515T-I/ST 带SPI接口的独立CAN控制器,可以P2P MCP2515 ●描述 ■SIT2515是带有SPI接口的独立局域网(CAN)控制器&#xff0c;它实现了CAN规范&#xff0c;版本2.0B。该芯片主要应用在汽车和工中进行数据接收和传输。 它能够发送和接收标准帧、扩展帧以及远…

HTML5+js+css3开心消消乐手机pc端通用源码|H5小游戏

HTML5jscss开心消消乐小游戏开心消消乐小游戏&#xff0c;支持PC端和移动端。《开心消消乐》是一款三消游戏&#xff0c;游戏画面精美、上手简单&#xff0c;玩家只需滑动手指让三个及以上的同色小动物横竖相连即可消除&#xff0c;完成每关的指定消除目标就可以过关。 效果演示…

谁能破解H5网页游戏

有偿&#xff01;&#xff01;&#xff01;

养成类型游戏风信楼mac版

养成类型游戏风信楼mac版《风信楼》是一部拥有海量选项和丰富剧情的高自由度养成游戏——百种不同结局&#xff0c;尽在你的选择之中。昏君误国&#xff0c;怨声载道&#xff0c;无数野心家暗起反心。在京城一家青楼的掩饰下&#xff0c;你培养手下、积攒钱财、收集情报、建立人…