用 Arduino 做一只会撒娇的狐狸:小主休息一下吧~


MAKER: ReinKTH/译:趣无尽 Cherry(转载请注明出处)
这是一只会撒娇求安抚的绯红狐狸。用萌动得让你难以忽视的姿势撩你,提醒你给它安抚。
在它的央求下,你暂时放下手上的枯燥工作,和它来个互动、小憩一下,你原本机械的工作状态也平添了几分乐趣和安慰……(有创造力的朋友当然可以发挥一下,直接做个小姐姐也没有什么不可以。)

咳……进入正题。这只可爱的小狐狸会像动物一样呼吸。当你开始工作时,轻轻地抚摸它,表示你开始工作了。当你工作许久后,小狐狸就会躁动起来,呼吸好像会变得沉重。发生这种情况,你得再次抚摸它,可以让它恢复平静。你也会马上意识到自己也需要休息调剂一下,喝个茶、吃点东西亦或是起身做个瑜伽。

经过短暂休憩,你可以再次轻轻地抚摸狐狸,告诉它你将继续工作。当你再次忽略它的时候,它会再次来讨你的宠爱,十分乖巧!

先看一段视频吧!

材料清单


Arduino pro micro ×1
micro USB转USB电缆×1
步进电机×1
电机驱动器×1
小面包板×2
彩色LED灯带×2
电线×若干
10M电阻×1
100M电阻×2
9伏电池×1
9伏电极夹×1
人造革×1
胶合板×1
纸板×1
铝箔纸×1
胶水×1
针和线×若干

操作预警:使用的人造革确保它不含任何PVC成分。人造皮将经过激光切割,如果含有PVC,切割时会释放氯化物的有毒气体。如你不能确定所用皮革是否含有PVC,可用剪刀来切割线条。

切割原材料



根据模板用激光切割机切割所有材料。如图所示,所有红线都需要切割,所有黑线需要雕刻。根据自己的要求设置好激光切割机。我建议,可先用一张人造革测试一下,以测定雕刻部件的深度是否足够。
如果没有激光切割机,也可以在纸上打印模板并手动切割所有模板,只是会花费更长的时间。
切割文件可在本项目文件库中下载。
http://make.quwj.com/project/81

将皮革粘贴到纸板上




纸板用作皮革的支撑材料,方便弯曲。除六边形外,皮革的每个表面都有匹配的纸板。根据皮革的形状将它们粘在皮革上。边缘处的纸板之间应该预留大约3mm的空间向外折叠,在向内折叠的纸板之间几乎没有空间。
同时,向内折叠的地方用工具刀稍微切割一下皮革,使皮革更容易折叠。确保你没有彻底切断它!

将铝箔纸粘合到内面



创建一个触摸传感器,感应是否有人正在触摸或抚摸狐狸。将4层铝箔纸粘贴在里面以便制作成电容式传感器。对于 Arduino 电容式触控开关原理可以参考这里
将长边缝合在一起然后将铝箔纸粘在纸板上。确保内部折叠部分留出一些空间,以便铝箔纸不会破裂。预留一个地方连接到 Arduino 的接线处。

搭建电路并上传代码


代码可在本项目文件库中下载。
http://make.quwj.com/project/81


/**
   capacitive sensing code with servo motor for Crimson Fox
*/

#include <Servo.h>
#include <CapacitiveSensor.h>
#include <HampelFilter.h>
#include <Filters.h>

#define dir 5
#define stp 6
#define capin 8
#define capout 9
#define ledWork 14
#define ledPanic 15


//Initializing touch sensor
CapacitiveSensor   cap_sense = CapacitiveSensor(capin, capout);       // 10 megohm resistor between pins capin & capout

//Initialiying Filters
HampelFilter dataBuffer = HampelFilter(0.00, 3, 3.50);
FilterOnePole lowpassFilter( LOWPASS, 5);

//Initialiying threasholding for touch
int upperThreashold = 8000;
int lowerThreashold = 8000;
int baseline = 100;
int summedThreashold = 16000;
int csSum = 0;
long dynThreasholdCount = 0;
long dynThreasholdTimeout = 6000;

//Initializing Motor
int pos = 0;    // variable to store the servo position
bool exhale = false; // false for inhale, true for exhale
int breathDepthTop;
int breathDepthBottom;
int breathDelay;

//Initializing session and fox status
enum WorkingStatus {
  pause,
  work,
  panic,
  standby
};
WorkingStatus foxStatus;
bool touched = false; // status if touch was detected

//Initialize Timing
long sessionStart; //time at session start
long sessionLength = 30000; //180000ms = 3min, time after which fox panics, one hour would be 3600000, 30 min would be 1800000
long REACTIVITYDELAY = 2000;
long lastTouchTime;
long breathStepStart;

void setup()
{
  cap_sense.set_CS_AutocaL_Millis(0xFFFFFFFF);     // turn off autocalibrate on channel 1 - just as an example
  Serial.begin(9600);

  pinMode(stp, OUTPUT);
  pinMode(dir, OUTPUT);

  pinMode(ledWork, OUTPUT);
  pinMode(ledPanic, OUTPUT);

  enterPauseStatus();
  breathStepStart = millis();
  lastTouchTime = millis();
}

void loop() {

  //read and filter touch sensor signal
  long total1 =  lowpassFilter.input(cap_sense.capacitiveSensor(30));//30
  dataBuffer.write(total1);

/*
  if(!(foxStatus == standby)){
    Serial.print(lastTouchTime);
    Serial.print(", ");
    Serial.print(millis());
    Serial.print(", ");
    Serial.println(lastTouchTime + REACTIVITYDELAY - millis());
  }*/
  
  if ((lastTouchTime + REACTIVITYDELAY - millis()) > 4294000000) {
    //Serial.println("checking status");
    // state machine
    if (foxStatus == pause) {
      //enter work status if touched
      if (detectTouch(total1)) {
        enterWorkStatus();
      }
    } else if (foxStatus == work) {
      //inter pause status if touched
      if (detectTouch(total1)) {
        enterPauseStatus();
      }
      //check if it is time to panic
      if ((millis() - sessionStart) > sessionLength) {
        enterPanicStatus();
      }
    } else if(foxStatus == panic) { //panic
      //enter work status if touched
      if (detectTouch(total1)) {
        enterWorkStatus();
      }
    }
  }

  if(foxStatus != standby){
      breathing();
  }

  if(Serial.available() > 0){
    String userInput = Serial.readString();
    Serial.print(userInput);
    userInput.trim();
    if(userInput.equals("q") || userInput == "quit"){
      exhaleAndDie();
      foxStatus = standby;
    }else{
      Serial.println(userInput + " could not be recogninzed use 'q' or 'quit' to shut down the fox.");
    }
  }

}

bool detectTouch(int cs) {
  bool touchTriggered = false;
  if (!dataBuffer.checkIfOutlier(cs)) {
    Serial.print(cs);
    Serial.print("\t");
    if (cs > upperThreashold) {
      csSum += cs;
      Serial.print(cs);
      Serial.print("\t");
      if (csSum >= summedThreashold) //c: This value is the threshold, a High value means it takes longer to trigger
      {
        Serial.print("Trigger: ");
        Serial.print(csSum);
        touchTriggered = true;
        if (csSum > 0) {
          csSum = 0;  //Reset
        }
        //cs_7_8.reset_CS_AutoCal(); //Stops readings
      }
    } else if (cs < lowerThreashold) {
      csSum = 0; //Timeout caused by bad readings
    }
    Serial.println("");
  }
  return touchTriggered;
}

void breathing() {
  //Serial.println(millis()-breathStepStart);
  
  /*if(!(foxStatus == standby)){
    Serial.print(breathStepStart);
    Serial.print(", ");
    Serial.print(millis());
    Serial.print(", ");
    Serial.println(millis()-breathStepStart);
  }*/
  
  //if(breathDelay < (millis()-breathStepStart)){
    breathStepStart = millis();
    
    if (pos > breathDepthTop) {
      exhale = true;
    } else if (pos <= breathDepthBottom) {
      exhale = false;
    }
  
    //set motor direction
    if (exhale) {
      pos = pos - breathDelay;
      digitalWrite(dir, LOW);
    } else {
      pos = pos + breathDelay;
      //Serial.println(pos);
      digitalWrite(dir, HIGH);
    }
  
    //do a breath step before returning to measuring touch again
    for(int i = 0; i < breathDelay; i++){
      digitalWrite(stp, HIGH);
      delayMicroseconds(1);
      digitalWrite(stp, LOW);
      delayMicroseconds(1);
    }

  //}
}

void enterWorkStatus() {
  Serial.println("Enter work");
  sessionStart = millis();
  touched = false;
  foxStatus = work;
  breathDelay = 6;
  breathDepthTop = 800;
  breathDepthBottom = 0;
  digitalWrite(ledWork, HIGH); //TODO turn on again
  digitalWrite(ledPanic, LOW);
  lastTouchTime = millis();
}

void enterPanicStatus() {
  Serial.println("Enter panic");
  touched = false;
  foxStatus = panic;
  breathDelay = 10;
  breathDepthTop = 1500;
  breathDepthBottom = 1000;
  digitalWrite(ledWork, LOW);
  digitalWrite(ledPanic, HIGH);
}

void enterPauseStatus() {
  Serial.println("Enter pause");
  touched = false;
  foxStatus = pause;
  breathDelay = 2;
  breathDepthTop = 800; 
  breathDepthBottom = 0;
  digitalWrite(ledWork, LOW);
  digitalWrite(ledPanic, LOW);
  lastTouchTime = millis();
}

void exhaleAndDie(){
  digitalWrite(dir, LOW);
  while(pos > 0){    
    digitalWrite(stp, HIGH);
    delayMicroseconds(1);
    digitalWrite(stp, LOW);
    delayMicroseconds(1);
    pos--;
  }
}

void handleThreashold(){
  
}
}

粘合下部并连线



1、将齿轮粘连到步进电机和胶合板的下面,并将步进电机粘在上面。
2、使用针和线,每个角内折叠,形成狐狸的形状。
3、将电线缠绕步进电机本两圈,然后稍微拉紧一下。另外,确保可逆时针旋转。

组装所有部件



部件布局如下:
1、底部预留9伏电池的地方。
2、中间为步进电机。
3、顶部为Arduino pro micro和电机驱动器。
4、将皮革的开口边缘缝合在一起。

最后,将所有剩余的开口侧缝合在一起,项目就完成了。
你还可以连上 USB 数据线,对小狐狸重新编程做一番个性化的调整!

http://www.quwj.com/2018/11/28/crimson-fox-raising-awareness-to-take-a-break-whil.html



坐沙发

发表评论