一个cosplay工作室找我做一个游戏中无人机的灯带,要求有组灯,一组是常亮的光圈,另一组是一个在循环的灯带。在这个项目中我分别用到了WS2812的灯带,红外线模组和EEPROM持久化(把数据存储在EEPROM里)。
接下来我会分别列出这三个模块的简单使用方法:
一、灯带模组:
FastLED.h是一个第三方库,编译前要添加库文件,一般灯带使用WS2812。WS2812一般有灯带型,灯珠型,和环形。最常见的就是灯带型的,就先说灯带类型的。
1、 灯带型:灯带有VCC、GND和DIN三个引脚,VCC接正极,GND接负极,DIN接信号。
2、 环形:(同上,多一个DOUT,用来输出信号到下一组灯带或者灯环)。
3、单个灯珠:有VSS、VDD、DIN、DOUT四个引脚。这些引脚有什么用代表什么呢?
VSS:是共用一条地线,把地线并联上整个灯带。
VDD:是共用一条正极,把正极并联上整个灯带。
DIN:是信号输入口,第一颗灯珠接单片机定义的灯带 I/O口,后续的灯珠从第一个灯珠的DOUT接入。
DOUT:是灯珠的信号输出口,用来连接下一个灯珠的DIN口,实现多个灯珠组成灯带。
以下代码是灯带模组常用的设置、指令和一个简单的灯带点亮。
//LED灯带的头文件
#include <FastLED.h>
//定义灯带长度
#define LED_PIN 2 //定义Din的io口
#define NUM_LEDS 255
CRGB leds[NUM_LEDS]; //创建一个CRGB格式的数组,数组长度为255
void setup()
{
//添加一个 WS2812 LED灯组,把之前定义的3个数据填上去.
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.setMaxPowerInVoltsAndMilliamps(5, 1500);//设置灯带电压5V,1500mA
}
void loop()
{
//一个简单的灯带点亮,通过一个for循环和CRGB(r,g,b)定义灯带颜色和亮度,最后通过FastLED.show()
//点亮灯带。
for(int i=0;i<100;i++)
{
leds[i]=CRGB(100,200,100)
}
FastLED.show;
delay(1000);
//LED灯清屏,这里展示一个用于清屏的指令
FastLED.clear();
delay(1000);
}
二、红外遥控模块:
红外模块有红外线接收器和遥控器两个部分,遥控器通过发射红外光进信号传输,每个按键都有一个值,红外线接收器把收到的红外光传输到开发板中,转化成一个数字,我们创建一个switch来判断按下哪个按钮。
红外线接收器有3个引脚,DAT、VCC、GND。VCC和GND分别是正极和负极,DAT是信号,接到开发板定义的红外接收引脚。
以下是红外模组的一些常用代码:
//定义第三方头文件(编译代码时记得添加第三方库)
#include<IRremote.h>
//定义红外接收器引脚为13
int RECV_PIN=13;
IRrecv receiver(RECV_PIN);
void setup()
{
//启动接收器
receiver.enableIRIn();
}
void loop()
{
//把定义的IRkey函数放在loop循环中等待按键按下。
IRkey();
delay(100);
}
//设计一个接收红外信号的函数,首先判断是否接收到红外线,如果接收到就进入translateIR()函数中判断按下
//的按键是哪个,最后允许新信号进入。
void IRkey()
{
//判断是否有红外信号输入
if(receiver.decode())
{
//判断按下按键
translateIR();
delay(100);
//允许新信号进入
receiver.resume();//记录新信号
}
}
//定义判断按键的函数,通过检测不同的值来判断按下哪个按键,可以在每个case x:到break的中间定义按键功能
void translateIR()
{
switch(receiver.decodedIRData.command)
{
case 162:
//POWER
break;
case 226:
break;
case 34:
//TEST
break;
case 2:
//PLUS
break;
case 194:
//BACK
break;
case 224:
//PREV
break;
case 168:
//PLAY
break;
case 144:
//NEXT
break;
case 104:
//0
break;
case 152:
//MINUS
break;
case 176:
//C
break;
case 48:
//1
break;
case 24:
//2
break;
case 122:
//3
break;
case 16:
//4
break;
case 56:
//5
break;
case 90:
//6
break;
case 66:
//7
break;
case 74:
//8
break;
case 82:
//9
break;
}
}
三、持久化EEPROM模块:
通过EEPROM我们可以把变量,数据等信息保留在开发板的闪存中,这使得重启后也得以保留数据,要注意的是闪存的容量不大,而且大量擦写会大幅度降低闪存的寿命。要尽量减少擦写次数,不要不加限制的在各种循环中添加存储函数,最好给一些条件来触发擦写。
EEPROM.h是Arduino自带的库,不需要单独添加库文件。
以下是EEPROM库的一些常用代码(更多可以查阅资料):
//定义头文件
#include <EEPROM.h>
//设置内存区域给不同要存储的变量,内部EEPROM的步长是一个字节,每个变量数字最大为255,超出就无法使用
//此处定义了3个空位。
#define EEPROM_ADDR 0
#define EEPROM_ADDR1 2
#define EEPROM_ADDR2 4
//定义3个变量存放数据
int i,j,k;
void setup()
{
int a=1,b=2,c=3;
//把a、b、c的值存入内存(自定义的函数)
saveValueToEEPROM(a,b,c);
//从内存中读取(自定义的函数)
loadValueFromEEPROM();
//可以自行添加显示i、j、k的其他代码来验证其值
}
//创建一个保存变量的函数,括号中有三个元素,分别对应需要存储的三个数据,可以自己定义存储数量
void saveValueToEEPROM(int value,int value1,int value2)
{
存入数据到空位中
EEPROM.put(EEPROM_ADDR, value);
EEPROM.put(EEPROM_ADDR1, value1);
EEPROM.put(EEPROM_ADDR2, value2);
}
// 从EEPROM加载变量值
void loadValueFromEEPROM()
{
//读取空位数据到变量中
EEPROM.get(EEPROM_ADDR,i);
EEPROM.get(EEPROM_ADDR1,j);
EEPROM.get(EEPROM_ADDR2, k);
}
最后放上整个项目的代码:
#include <FastLED.h>
#include<IRremote.h>
#include <EEPROM.h>
//红外
int RECV_PIN=13;//定义红外接收器引脚为13
IRrecv receiver(RECV_PIN);
//LED
#define NUM_LEDS 255
#define NUM_LEDS2 17
CRGB leds[NUM_LEDS];
CRGB leds2[NUM_LEDS2];
#define LED_PIN 2
#define LED_PIN2 12
int light_PIN1=3;
int light_PIN2=4;
int light_PIN3=5;
int bk=1;
//设置参数
int setLedLong=5,ledLong=10, del=5;
int r=200,g=200,b=200;
//持久化
#define EEPROM_ADDR 0
#define EEPROM_ADDR1 2
#define EEPROM_ADDR2 4
#define EEPROM_ADDR3 6
#define EEPROM_ADDR4 8
#define EEPROM_ADDR5 10
void setup() {
// put your setup code here, to run once:
loadValueFromEEPROM();
//按钮
pinMode(light_PIN1, INPUT_PULLUP);
digitalWrite(light_PIN1,HIGH);
pinMode(light_PIN2, INPUT_PULLUP);
digitalWrite(light_PIN2,HIGH);
pinMode(light_PIN3, INPUT_PULLUP);
digitalWrite(light_PIN3,HIGH);
//LED
FastLED.addLeds<WS2812, LED_PIN, GRB>(leds, NUM_LEDS);
FastLED.addLeds<WS2812, LED_PIN2, GRB>(leds2, NUM_LEDS2);
FastLED.setMaxPowerInVoltsAndMilliamps(5, 1500);
//红外
receiver.enableIRIn();//启动接收器
}
void loop() {
// put your main code here, to run repeatedly:
if(bk==1)
{ringLed();
IRkey();
danR();}
else if(bk==0){IRkey();FastLED.clear();FastLED.show();delay(500);}
}
void danR()//单灯循环 运行模式1
{
//可调整
// setLedLong=4;//设置Led灯带长度
//ledLong=16;//设置灯带总长度
// del =5;//灯行走速度
//不可调整
int ruondFstLed=ledLong+1;//循环的第一个灯
int lastLed=ledLong-1;//最后一个灯的位置
int LightedLong=0;//初始灯带亮的长度
//FastLED.clear();
for(int y=0;y<setLedLong;y++){LightedLong++;ruondFstLed--;}
for (int i=0; i<ledLong; i++)//i+1整个亮起的灯条就往前一格
{if(receiver.decode())//红外遥控退出循环
{FastLED.clear();FastLED.show();break;}//红外遥控退出循环
for( int j=0;j<LightedLong;j++){ IRkey();//j增加亮起的灯条长度增加
leds[i+j] = CRGB(r,g, b);//整条亮起灯条的颜色,以后可以通过增加和j有关的变量来改变边缘颜色
}
for(int k=0;k<setLedLong;k++)//多半是防边缘亮起不灭,并不是,这是整个灯带运行的条件???到底做什么的?
leds[ledLong+k]=CRGB(0,0,0);//没了这两条代码,整条灯带都卡死,wtf,整个代码都基于这个bug运行吗?
if(i>=ruondFstLed)leds[i-ruondFstLed] = CRGB(r,g, b);//在同一个循环中。说不定可以和上面一起改颜色
if (digitalRead(light_PIN2) == LOW||digitalRead(light_PIN3) == LOW)//按键切换,移植改可删
{
break;
}
delay(del*50);
leds[i-1]=CRGB(r/3,g/3,b/3);
FastLED.show();
}
leds[lastLed] = CRGB(r/1.5,g/1.5, b/1.5);
}
void ringLed()
{
for(int i=0;i<17;i++)
leds2[i] = CRGB(r,g, b);
}
void IRkey()
{
if(receiver.decode())
{
translateIR();//按键赋予功能
delay(100);
receiver.resume();//记录新信号
}
}
void translateIR()
{
switch(receiver.decodedIRData.command)
{
case 162:
//POWER 模式按键 进入开关模式
if(bk==1)bk=0;
else if(bk==0)bk=1;
break;
case 226:
//MENU 模式按键 进入设置模式
break;
case 34:
//TEST 模式按键 进入测试模式
saveValueToEEPROM(setLedLong,ledLong,del);
saveValueClToEEPROM(r,g,b);
break;
case 2:
//PLUS 功能按键
setLedLong++;if(setLedLong>ledLong)setLedLong=0;//灯亮的长度不能超过总长度
break;
case 194:
//BACK
bk=0;
break;
case 224:
//PREV
del++;if(del>=255)del=255;
break;
case 168:
//PLAY 模式按键 进入运行模式
break;
case 144:
//NEXT
del--;if(del<=0)del=1;
break;
case 104:
//0
break;
case 152:
//MINUS
setLedLong--;if(setLedLong<0)setLedLong=0;//灯亮的长度不能超过总长度
break;
case 176:
//C重置
ledLong=3;setLedLong=1;del=6;r=200;g=200;b=200;
leds[9]=CRGB(0,0,0);//发现第九格会有暗光,给它办了
break;
case 48:
//1
ledLong++;
break;
case 24:
//2
ledLong--;if(ledLong<1)ledLong=1;
break;
case 122:
//3
break;
case 16:
//4
r=r+5;rgbs();
break;
case 56:
//5
g=g+5;rgbs();
break;
case 90:
//6
b=b+5;rgbs();
break;
case 66:
//7
r=r-5;rgbs();
break;
case 74:
//8
g=g-5;rgbs();
break;
case 82:
//9
b=b-5;rgbs();
break;
}
}
void rgbs()
{
if(r>255)r=0;
else if(r<0)r=255;
if(g>255)g=0;
else if(g<0)g=255;
if(b>255)b=0;
else if(b<0)b=255;
}
// 将小段的灯长和总灯长变量值保存到EEPROM
void saveValueToEEPROM(int value,int value1,int value2) {
EEPROM.put(EEPROM_ADDR, value);
EEPROM.put(EEPROM_ADDR1, value1);
EEPROM.put(EEPROM_ADDR2, value2);
}
void saveValueClToEEPROM(int value3,int value4,int value5) {
EEPROM.put(EEPROM_ADDR3, value3);
EEPROM.put(EEPROM_ADDR4, value4);
EEPROM.put(EEPROM_ADDR5, value5);
}
// 从EEPROM加载变量值
void loadValueFromEEPROM() {
EEPROM.get(EEPROM_ADDR,setLedLong);
EEPROM.get(EEPROM_ADDR1,ledLong);
EEPROM.get(EEPROM_ADDR2, del);
EEPROM.get(EEPROM_ADDR3, r);
EEPROM.get(EEPROM_ADDR4, g);
EEPROM.get(EEPROM_ADDR5, b);
}
项目硬件连接:
运行代码时按c开始运行程序,c是重置灯带,456789是用来控制灯带颜色的,←和→用来控制灯带循环速度,+-和12分别用来控制灯带总长和高亮灯带长度。