原创文章,未经许可,禁止转载!

1. DigiSpark 兔子南瓜灯

视频教程:https://www.bilibili.com/video/BV1dy4y1k7mv/

首先来看下今天要做的兔子灯挂件的样子

成品

下图是电路部分需要的材料:

图1 电器元件
  1.  充放电板:充放电板+锂电池就是一个充电宝,既可以稳定的输出5v的电压,又可以安全的为锂电池充电。
  2. DigiSpark ATTINY85:主角芯片
  3. WS2812:信仰灯
  4. MicroUSB模块:可以通过MicroUSB给充电板提供5v的充电电压
  5. 10k电阻:做下拉电阻使用
  6. 锂电池:图片上是160毫安时的电池,制作的时候我换成了80毫安时的,更小一些。
  7. 面包板:用来固定元器件
  8. 轻触开关:用来切换灯光模式

安装驱动

目前驱动我只找到了windows版的,下载地址:https://github.com/digistump/DigistumpArduino/releases,如果下载不下来,可以到我的下载页面去下载:https://www.ubusy.net/download/,安装完成以后最好重启一下电脑。

配置开发环境

下面来安装开发工具,首先要安装 Arduino IDE,这部分内容在我之前将 Esp8266 的时候已经讲过了,可以看我之前写的 Esp8266 开发环境配置,链接:https://www.ubusy.net/2020/01/28/esp8266-1/

然后在开发板管理器(Board)里面装 DigiSpark 的开发工具链。首先要在设置(Preferences)里面添加下载地址:http://digistump.com/package_digistump_index.json,我已经安装了esp8266,所以直接换行写就行,然后去开发板管理器下载对应的工具链,安装digispark,请看下图:

图2 安装工具链

安装完成以后需要选择对应的开发板,见下图:

图3 选择开发板

选择好了 Digispark 后可以写一个空程序试一下,这里要注意这块开发板的上传和其他的板子不一样,首先需要拔下开发板,然后选择编译上传,只有在这个状态下插入开发板才能上传程序,注意上传状态只会保留60s,超时以后插上板子也没有用。看下图,按编译上传按钮,然后等待插板状态,见下图

图4 编译上传代码

然后插入开发板,系统会自动上传程序,完成后显示如下图,板卡上的程序也会进入执行状态。

图5 上传成功

DigiSpark

先看下Digispark的引脚图

图6 DigiSpark 引脚定义

Digispark 有5个gpio口,我这次选择了 P3 和 P4,千万不要选P5,看上面的图,P5还有个功能是RESET,会导致板子一直重启。下面来看一下接线图

图7 接线图

这里充电板所有的GND都是连通的,可以任意使用。电源通过usb模块给充电板供电,电池接在充电板的BAT上充电,充放电板在没有外接电源的情况下可以把电池升压到5v输出,有外接电源的时候一边输出一边给电池充电,充满了会自动断电,电池没电了也会自动断电。充放电板上的灯显示剩余电量。充电板上有个小按钮,在外接电源的时候没有用,不外接电源的时候按一下打开输出,按两下停止输出。

编码

这些工作都准备好了以后就可以开始编码了,代码原理在之前的Esp8266里面已经讲过了,这里直接贴代码,代码里面的说明也很全了。复制黏贴后直接就可以使用。需要注意的地方是刷写程序的时候必须把电池的电断掉,就是按两下充放电板上的按钮,否则无法刷写成功

下面的代码功能是通过微动开关控制灯的模式,我这里写了5种模式,分别是呼吸灯1,呼吸灯2,双闪,惊悚1,惊悚2,大家可以对着代码看看。另外还有个stop模式是为了在充电的情况下把灯关掉的。

// 首先,引入库文件
#include <Adafruit_NeoPixel.h>

// 定义一个led模块
// 参数1:这个模块有几个led 像素
// 参数2:使用哪一个gpio口输出
// 参数3:模式,一般ws2812都是 NEO_GRB + NEO_KHZ800,其他模式的模块可以参考手册
Adafruit_NeoPixel led = Adafruit_NeoPixel(1, PB3, NEO_GRB + NEO_KHZ800);

// 模式的个数
const uint8_t mode_count = 6;

// 当前模式
uint8_t state = 0;

// 当前帧数,每次循环会将frame + 1
uint32_t frame = 0;

// 初始化
void setup() {
    pinMode(PB3,OUTPUT);  // P3 为灯的输出口
    pinMode(PB4,INPUT);   // P4 为微动开关的输入口    
    led.begin();  // 打开led
    led.setBrightness(150); // 设置led的亮度为150,最大为255
    frame = 0;  // 初始化当前帧数为0
}

// 按键检测函数
bool CheckButton() {
  // button_state 用于检测按键的按下和抬起的状态,每一次按下和抬起改变一次模式
  static bool button_state = false;

  // 如果按键是没有按下状态,设置为按下状态并且将状态+1
  if (digitalRead(PB4) == HIGH && button_state == false) { 
      button_state = true;
      state++;
      state %= mode_count;  // 状态循环
    } else if (digitalRead(PB4) == LOW && button_state == true) {
      button_state = false; // 回复状态按键为抬起状态
  }
}

// 三角函数是弧度制,这里提供一个角度转弧度的函数
float Deg2Rad(uint32_t deg) {
  return deg / 180.0f * 3.1415926;
}

// 关闭灯,做这个功能为了充电时可以设置到这个模式让灯可以不亮
void Stop() {
    led.setPixelColor(0, led.Color(0, 0, 0));
    led.show();
}

// 呼吸灯函数,参数是跳帧,默认为2
// 使用sin周期函数,但是由于不要负值用了fabs,因此周期是180
void Breath(uint32_t skip_frame = 2) {
    // 初始化灯的颜色
    static uint8_t r = rand() % 256;
    static uint8_t g = rand() % 256;
    static uint8_t b = rand() % 256;

    // 每180帧换一次颜色
    if ((frame % 180) == 0) {
      r = rand() % 256;
      g = rand() % 256;
      b = rand() % 256;  
    }

    // 第 2n 个周期关灯,让呼吸灯不要常亮,模拟呼吸效果
    if ((frame % 360) > 180) {
      Stop();
      // 这里的跳帧为了控制灯熄灭的时间。
      //用跳帧不用delay是因为delay会让按钮输入无法检测到
      frame += skip_frame;  
    } else {
      // 通过sin函数设置灯的颜色
      float c = fabs(sin(Deg2Rad(frame))) ;
      led.setPixelColor(0, led.Color(c * r, c * g, c * b));   
      led.show();
    }
}

// 第二种呼吸灯模式
void Breath2(){
  // 这里调用Breath函数,并设置跳帧为8帧,缩短呼吸时间
  Breath(8);
}

// 双闪模式
void Blink() {
  uint32_t f = frame % 20;
  // 周期为20帧,在20帧里,第0,1帧和第4,5帧随机闪一个颜色,其余时间关灯
  if (f < 2 || (f >= 4 && f <= 5)) {
    led.setPixelColor(0, led.Color(rand() % 255, rand() % 255, rand() % 255));
    led.show();
  } else {
    led.setPixelColor(0, led.Color(0, 0, 0));
    led.show();
  }
}

// 惊悚灯
void Thriller(uint8_t adjust = 0) {
  // 通过adjust参数随机决定灯是否要闪烁
  bool effect = (rand() % 8) <= adjust;

  // 随机一个颜色,其余时间关灯
  if (effect) {
    led.setPixelColor(0, led.Color(rand() % 255, rand() % 255, rand() % 255));
    led.show();    
  } else {
    led.setPixelColor(0, led.Color(0, 0, 0));
    led.show();    
  }
}

// 惊悚灯升级版,这里的目的是为了让灯在亮和暗的时间相对集中
void Thriller2() {
  // pause参数为了让灯有更多的机会连续熄灭
  static uint8_t pause = 0;
  // 如果pause大于0,就让灯强制熄灭
  if (pause > 0){
    pause--;
    Stop();
    return;
  }

  // 这里随机一个亮度,然后调用Horrify(6),让灯连续亮
  led.setBrightness(rand() % 256);
  Thriller(6);

  // 这里判断是否要暂停亮灯
  if (rand() % 4 == 0 && pause == 0) {
    pause = rand() % 32; 
  }
}

// 循环函数,每帧调用
void loop() {
    // 首先恢复亮度为150,因为惊悚灯2会改变亮度
    led.setBrightness(150); 
    // 设置灯运行状态
    switch(state) {
      case 0:
        Breath(); break;
      case 1:
        Breath2(); break;
      case 2:
        Blink(); break;
      case 3:
        Thriller(); break;
      case 4:
        Thriller2(); break;
      case 5:
        Stop(); break;
    }
    
    // 按键检测
    CheckButton();

    // 延迟50ms,控制帧率为每秒100帧
    delay(50);
    frame++;
}

看下效果,走线不算合理,如果觉得充电板上的灯影响效果,也可以找东西把它遮起来。

图8 电路板成品

3D打印

最后的模型是3D打印出来的,等模型的改的好一点我会放在下载页面给大家下载的。

在兔子眼睛和嘴巴镂空的部分贴一层无纺布作为柔光的效果

图9 用无纺布柔光

效果

来看下兔子灯挂件的最终效果。

图10 兔子灯最终效果

下面是南瓜灯的最终效果。

图11 南瓜灯最终效果

发表评论