wukongxuetang 发表于 2020-4-24 17:02:11

楼梯交互项目样板HCSR04 WS2812 VL53L0X JQ8400提案-悟空学堂

本帖最后由 wukongxuetang 于 2020-4-25 14:05 编辑

项目背景
疫情期间,各行各业都受到不同程度影响,交互行业多集中景区,网红打卡地,展馆展厅以及餐饮婚庆KTV等娱乐场所,多为人群集中场合,很多合作的伙伴处于半复工状态,圈里有转行卫材的,大部分开始沉淀进行技术储备和项目储备。
多年合作的一个朋友,最近接了个项目,是一个楼梯交互的项目,让给开发的意见。其实此类项目在2012年前后是比较热门的项目,不过项目落地的并不多,技术简单但实施落地的门槛并不低,交互行业的现状就是两头强,中间弱,搞硬件的玩不了终端应用,搞软件开发的搞不定硬件。
为了帮助他及早搞定项目,就给他列出了单套样板的组合清单(先测试原理,pcb打板子是后面的,后面在更新),并写了代码程序给他。

项目要求
在一定距离感应有没有人经过或者站立(楼梯的话其实就是人的腿或者经过的宠物,移动的物体等),判断是路过还是停留,然后播放对应的音乐,控制灯光效果。
一句话概括就是:有人经过放音乐闪灯
(体外话:领导动动嘴,当兵的跑断腿,每一个简单需求的背后都是技术大拿的黑眼圈苦咖啡,是技术小白的面壁求索百度扫盲贴吧求助,项目结束造就一个手拿电烙铁的程序猿)

项目清单
1、arduino nano 一个               12.00
2、夏普20-180cm光学测距模块一个23.00
3、HC SR04 超声模块一个             4.00
4、VL53L0X TOF光学测距模块 一个    40.00
5、WS2812 一米30灯珠 5v          8.00
6、JQ8400FL mp3播放模块一个      12.00
7、导线若干,10KR电阻一个下拉用,电烙铁等工具

备注:2、3、4都属于测距模块,有其中一个即可,为了帮助朋友比对选择,就选了三个,程序中可以选择最终方案,价格只做参考,pcb打板价格会直线降低

模块特性
1、arduinio
Arduino属于比较容易入门的单片机交互平台,有IIC接口,数字引脚可以使用的有2,3,4,5,6,7,8,9,10,11,12,13,A0,A1,A2,A3,A4,A5共18个引脚,其中A0,A1,A2,A3,A4,A5属于analog引脚,nano和mini板上还有A6,A7两个adc引脚(特殊提示:mini版本烧录程序时需要外接usb串口模块又涉及DTR信号同步问题,且A6,A7在板子中间开孔,优点是小巧)。此次项目中未涉及spi,只使用板上的几个引脚,RX,TX串口,以及软串口TX(控制mp3模块)。
2、夏普20-180cm光学测距模块
该模块属于比较早期的测距类产品,模块比较大,根据距离输出电压信号,只有vcc,gnd和out三个脚,out接arduino的analog引脚就可以。最近距离的输出电压大概在3.1v,最远距离输出电压0.5v左右;arduino的adc是10级adc输入分辨率,3.1/5*1024,输入最大值应该在600多点,0.5/5*1024,输入值100左右。但实际测试中,在120左右后就会跳变比较严重。
3、HC SR04超声波模块
超声波模块属于比较常用的测距模块,也是非常常见的传感器,有收发一体单头模块,有收发分体双头模块,也有一发多收,或者多发一收的应用模块。HCSr04属于收发分体双头模块,模块有四个引脚,分别为vcc,trig,echo,gnd。trig发送超声脉冲,然后echo检测回波信号,根据时间差判断障碍物的距离。网上有很多案例,也能找到arduino库文件可以用,因为此超声波模块的应用以及原理不复杂,此项目中直接用代码实现。
4、VL53L0X TOF光学测距模块
VL53L0x tof光学测距模块,模块尺寸不大,方便项目集成,该模块属于iic模块,需要使用arduino单片机中的A4,A5引脚。最早的vl6180模块属于短距离测量模块,VL53L0X属于意法半导体的升级换代芯片,理论上测试距离可以达到200cm,精度1mm。该模块涉及iic通讯以及寄存器读写,建议直接使用arduino库文件,直接按范例进行调用,此项目中使用了ADAFRUIT_VL53L0X库。
5、WS2812
Ws2812属于可寻址控制RGB灯珠,可以直接通过一个io控制灯珠的色彩变幻。Ws2812有一米30灯珠,一米60灯珠等,又有背胶,防水等各种类型,此项目样板使用5伏电压一米30灯珠的普通灯带。Ws2812灯带有VCC,GND,DIN三个脚,din脚接arduino上数字引脚,此处不讨论模块的本身特性与寻址原理,可以直接使用Adafruit_NeoPixel库,库中含有例程,可以直接驱动并带有几个基础变幻应用模式。
6、JQ8400FL mp3模块
JQ8400FL芯片比较容易使用,自带FLASH,和8002A功放,测试中可以驱动8欧10w喇叭。模块音量30级可调,内置音乐可以进行组合播放,有一线串口和两线串口控制模式,音乐播放中busy引脚为高电平。为了方便操作,此项目中使用两线串口模式,考虑串口使用中只是用模块的RX接收串口指令,而arduino只使用软串口TX连接到模块的RX就可以,所以也算是一线控制了。项目中使用单串口模拟库SoftSerialTx。另外为了增加交互应用中对音乐播放中的可控性,增加使用该模块的busy引脚,用单片机引脚监控mp3模块的播放状态。调整音量串口发送指令与组合播放指令可查手册。

项目思路
首先,Arduino单片机不断读取传感器的测距模块,对读取的实时数据与存储的上一个数据进行比对,数据在一个区间内,用于传感器临界测量时跳变数据的处理,减少误判
对整理后的数据根据最小距离和最大距离判断测距区间内是否有物体存在,如果有物体存在就播放音乐,闪烁灯光
利用播放器的busy高电平判断是否正在播放,如果正在播放,再次触发的情况下,根据需求判断要不要继续播放或者中断重新播放
对最小距离,最大距离,灯光闪烁模式,音量大小,测距硬件三选一,已播放是否中断设置可以控制的变量,利用arduino串口进行设置并保存到arduino的eeprom中。协议设置为发送v30调整音量(数字部分是音量值0-30);发送l5设置最近距离(hcsr40 vl51l0x数字是厘米,夏普是analog值);发送b5设置最远距离;发送t8设置ws2812效果,数字范围1-9;发送m1选择测距传感器(0是夏普测距,1是超声波测距,2是vl51l0x);
设置距离最小值和最大值要考虑Arduino的eeprom存储范围,EEPROM存储是0-255,夏普光学模块analogRead范围在100-600之间,将此值除以10进行处理,范围降到10-60;HCSR40的距离采用厘米数据,范围5-200

项目准备
5v供电电源,各模块都使用5v电源,此处不用考虑电压转换,电脑测试时可以直接用供电线,项目上用时可以接大电流5V4A适配器模块

Usb线,连接arduino烧录程序用

下载arduino IDE,安装SoftSerialTx,ADAFRUIT_VL53L0X和Adafruit_NeoPixel库文件
集成文件包下载地址:链接:https://pan.baidu.com/s/1DzX-97-hZEcZ_A3jQzjLbA 提取码:uros

JQ8400FL SPEAKER+-引脚接双线喇叭,不用分正负极,模块RX引脚接arduino的D9,模块BUSY接arduino的10

使用usb线连接电脑,会识别成u盘模式,把对应音乐改成mp3格式,音乐名字用数字或者大写字母进行组合,两个字符,(比如00.mp3或者AS.mp3或者1B.mp3),此处只需要两个音乐,分别命名为WK.mp3,OK.mp3,根目录建立ZH文件夹(大写),把WK.mp3,OK.mp3拷入ZH文件夹

HCSR40的TRIG接arduino的D2,ECHO接arduino的D3

夏普光学测距模块out接arduino的A0

VL53L0x的SCL接arduino的A5,sda接arduino的A4

WS2812的DIN接arduino的D6

HCSR40/VL53L0X/SHARP与JQ8400、arduino、ws2812的vcc接5v正极,,gnd接负极
JQ8400启动时需要大电流,尽量不要接arduino上自带的5v接口,ws2812的正负极一定要单独供电

所有模块的负极与arduino的负极尽量共地

最终接线图如下:

图中1为usb供电,此时2不用接
图中2为arduino外部供电,此时1不用接
图中3为ws2812的单独供电

红外线为正极 绿色为负极,蓝色为引脚连接线 黄色为模块说明白色为传感器

项目代码
#include <Wire.h>
#include <VL53L0X.h>
VL53L0X sensor;
int vldis = 0;

#include <EEPROM.h>
#include <SoftSerialTx.h>
int volnum = 20;//eidt music volume 0
String comdata = "";
SoftSerialTx mySerial(9);
int serialv = 0;

#include <Adafruit_NeoPixel.h>
#include <avr/power.h>
int PIXEL_PIN = 6;
int PIXEL_COUNT = 30;//edit ws2812 num
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);
int showType = 7;//edit show type 1

int EchoPin = 3;
int TrigPin = 2;
long Time_Echo_us = 0;
long Len_mm_X100 = 0;
long Len_Integer = 0;

int nowfind = 0;
int oldfind = 0;

int delay1 = 7;
int delay2 = 8;

int playstatus = 10;
int avnow = 0;
int avold = 0;
int avabs = 0;
int minvalue = 25;//edit min distance 2
int maxvalue = 50;//edit max distance 3
int mode = 0;//choose distance sensor 4

unsigned char playdata = {
};
unsigned char volueset = {
};

void setvolue(int v){
volueset = 0xAA;
volueset = 0x13;
volueset = 0x01;
volueset = v;
volueset = (volueset+volueset+volueset+volueset)&0xff;
mySerial.write(volueset,5);
}

void playmusic(String filename){
playdata = 0xAA;
playdata = 0x1B;
playdata = 0x02;
playdata = filename.charAt(0);
playdata = filename.charAt(1);
playdata = (playdata+playdata+playdata+playdata+playdata)&0xff;//0x28
mySerial.write(playdata,6);
}

void setup(){
Serial.begin(9600);
delay(5);
mySerial.begin(9600);
delay(5);

pinMode(EchoPin, INPUT);
pinMode(TrigPin, OUTPUT);

pinMode(playstatus,INPUT);

pinMode(delay1, OUTPUT);
pinMode(delay2, OUTPUT);

strip.begin();
strip.show();

volnum = EEPROM.read(0);
if(volnum<0||volnum>30){
    volnum = 20;
}
setvolue(volnum);
playmusic("WK");

showType = EEPROM.read(1);
if(showType<1||showType>9){
    showType = 7;
}

minvalue = EEPROM.read(2);
if(minvalue<1||minvalue>200){
    minvalue = 25;
}
maxvalue = EEPROM.read(3);
if(maxvalue<1||maxvalue>250){
    maxvalue = 50;
}
mode = EEPROM.read(4);
if(mode>2){
    mode = 0;
}

Wire.begin();
sensor.setTimeout(500);
sensor.startContinuous();
}

void serialEvent() {
while(Serial.available() > 0){
    comdata += char(Serial.read());
    delay(2);
}
int comdatal = comdata.length();
if(comdatal > 0) {
    Serial.println(comdata);
    switch (comdata){
    case 'v':
      serialv = comdata.substring(1,comdatal).toInt();
      if(serialv>=0&&serialv<=30){
      volnum = serialv;
      setvolue(serialv);
      EEPROM.update(0, serialv);
      }
      break;
    case 't':
      serialv = comdata.substring(1,comdatal).toInt();
      if(serialv>0&&serialv<10){
      showType = serialv;
      EEPROM.update(1, serialv);
      }
      break;
    case 'l':
      serialv = comdata.substring(1,comdatal).toInt();
      if(serialv>0&&serialv<255){
      minvalue = serialv;
      EEPROM.update(2, serialv);
      }
      break;
    case 'b':
      serialv = comdata.substring(1,comdatal).toInt();
      if(serialv>0&&serialv<255){
      maxvalue = serialv;
      EEPROM.update(3, serialv);
      }
      break;
    case 'm':
      serialv = comdata.substring(1,comdatal).toInt();
      if(serialv>=00&&serialv<3){
      mode = serialv;
      EEPROM.update(4, serialv);
      }
      break;
    default:
      playmusic(comdata);
      break;
    }
    comdata = "";
}
}

void loop(){
if(!digitalRead(playstatus)){
    startShow(0);
    digitalWrite(delay1,0);
    digitalWrite(delay2,1);
}
else{
    startShow(showType);
    digitalWrite(delay1,1);
    digitalWrite(delay2,0);
}
switch (mode){
case 0:
    avnow = analogRead(A0)/10;
    avabs = abs(avnow-avold);
    if(avold>minvalue&&avold<maxvalue&&avabs<5){
      if(avnow>minvalue&&avnow<maxvalue){
      Serial.print("now value is ");
      Serial.print(avnow);
      Serial.print(" old value is ");
      Serial.print(avold);
      Serial.print(" have human ");
      Serial.print("1");
      nowfind = 1;
      if(nowfind==1&&oldfind==0&&!digitalRead(playstatus)){
          playmusic("OK");
          Serial.println(" play");
      }
      else{
          Serial.println("");
      }
      }
    }
    else{
      nowfind = 0;
    }
    oldfind = nowfind;
    avold = avnow;
    break;
case 1:
    digitalWrite(TrigPin, HIGH);
    delayMicroseconds(50);
    digitalWrite(TrigPin, LOW);
    Time_Echo_us = pulseIn(EchoPin, HIGH);
    if((Time_Echo_us < 60000) && (Time_Echo_us > 1)){
      Len_mm_X100 = (Time_Echo_us*34)/2;
      Len_Integer = Len_mm_X100/1000;
      if(Len_Integer>minvalue&&Len_Integer<maxvalue){
      Serial.print("Present Length is: ");
      Serial.print(Len_Integer);
      Serial.print("cm");
      Serial.print(" have human ");
      Serial.print("1");
      nowfind = 1;
      if(nowfind==1&&oldfind==0&&!digitalRead(playstatus)){
          playmusic("OK");
          Serial.println(" play");
      }
      else{
          Serial.println("");
      }
      }
      else{
      nowfind = 0;
      }
      oldfind = nowfind;
    }
    break;
case 2:
    vldis = sensor.readRangeContinuousMillimeters()/10;
    if(vldis>minvalue&&vldis<maxvalue){
      Serial.print("Present Length is: ");
      Serial.print(vldis);
      Serial.print("cm");
      Serial.print(" have human ");
      Serial.print("1");
      nowfind = 1;
      if(nowfind==1&&oldfind==0&&!digitalRead(playstatus)){
      playmusic("OK");
      Serial.println(" play");
      }
      else{
      Serial.println("");
      }
    }
    else{
      nowfind = 0;
    }
    oldfind = nowfind;
    break;
}
}

void startShow(int i) {
switch(i){
case 0:
    colorWipe(strip.Color(0, 0, 0), 1);
    break;
case 1:
    colorWipe(strip.Color(255, 0, 0), 1);
    break;
case 2:
    colorWipe(strip.Color(0, 255, 0), 1);
    break;
case 3:
    colorWipe(strip.Color(0, 0, 255), 1);
    break;
case 4:
    theaterChase(strip.Color(127, 127, 127), 10);
    break;
case 5:
    theaterChase(strip.Color(127,   0,   0), 10);
    break;
case 6:
    theaterChase(strip.Color(0,   0, 127), 10);
    break;
case 7:
    rainbow(10);
    break;
case 8:
    rainbowCycle(10);
    break;
case 9:
    theaterChaseRainbow(10);
    break;
}
}

void colorWipe(uint32_t c, uint8_t wait) {
for(uint16_t i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
}
}

void rainbow(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
    delay(wait);
}
}

void rainbowCycle(uint8_t wait) {
uint16_t i, j;
for(j=0; j<256*5; j++) {
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
}
}

void theaterChase(uint32_t c, uint8_t wait) {
for (int j=0; j<10; j++) {
    for (int q=0; q < 3; q++) {
      for (int i=0; i < strip.numPixels(); i=i+3) {
      strip.setPixelColor(i+q, c);
      }
      strip.show();
      delay(wait);
      for (int i=0; i < strip.numPixels(); i=i+3) {
      strip.setPixelColor(i+q, 0);
      }
    }
}
}

void theaterChaseRainbow(uint8_t wait) {
for (int j=0; j < 256; j++) {
    for (int q=0; q < 3; q++) {
      for (int i=0; i < strip.numPixels(); i=i+3) {
      strip.setPixelColor(i+q, Wheel( (i+j) % 255));
      }
      strip.show();

      delay(wait);

      for (int i=0; i < strip.numPixels(); i=i+3) {
      strip.setPixelColor(i+q, 0);
      }
    }
}
}

uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if(WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
else if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
else {
    WheelPos -= 170;
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
}
页: [1]
查看完整版本: 楼梯交互项目样板HCSR04 WS2812 VL53L0X JQ8400提案-悟空学堂