宿舍智能门锁·开发经验

宿舍智能门锁·开发经验

前言

祖国七十诞辰之际,为参加校智能车竞赛,需开发一硬件设备以通过面试选拔,因私以为所给题目太过简单且无用,故突发奇想,欲成一智能门锁,以解带钥匙之劳。

准备

  • 控制板:Arduino nano V3.0 ATMEGA328P
  • 振动传感器:压电陶瓷震动传感器
  • 步进电机:28BYJ48步进电机及ULN2003驱动板
  • 蓝牙模块:BLE TLSR8266低功耗蓝牙4.0串口模块
  • 其他耗材:面包板,杜邦线,二极管若干,门锁部分使用3D打印 (感谢金通赛学长的帮忙)

敲门密码的实现

在未收到所需材料之前,我先完成了敲门密码的实现,由于未使用过Arduino,故先用JavaScript简单实现。

大致思路如下:

  • 按下空格(相当于敲门),执行一次敲门事件,一次敲门事件进行一次总敲门数的计数。
  • 使用setInterval()来模拟Arduino中的loop()函数,时间间隔经测试可以设为100ms(过短浪费资源,过长会降低精度)。函数中对当前总敲门次数进行判断,若为零,说明还没有敲门,等于最大敲门次数,则敲门完毕,若处在0与最大次数之间,则记录此次敲门与上次敲门的时间间隔,并判断是否超时(可看作实际情况中的误敲门或风吹草动造成的敲门)。
  • 在敲门完毕后,将每次时间间隔转换为用‘长’和‘短’表示的信号,最后与预设的密码进行比较,判断密码是否正确。
  • 提示密码正确或错误,最后重新计数 // 以下代代码中并未实现。

附上原JS代码:

<script
  src="https://code.jquery.com/jquery-3.4.1.min.js"
  integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
  crossorigin="anonymous">
</script>
<script>
    var intv;
    var interval_ms = 100;
    var max_interval_ms = 1500;
    var knock_count = 0;
    var knock_password = [0,0,1,0,0,1];//可记作123 123 1
    //var knock_password = [0,0,0,1,0,0,1,0,1];//可记作1234 123 12 1
    //var knock_password = [1,0,1,0,0,1,0,0,0];//可记作1 12 123 1234
    var max_knock = knock_password.length+1;
    var knock_time = new Array(max_knock-1);
    var max_error = 0;
    
    $(document).ready(function(){
        for(i=0;i<max_knock-1;i++)knock_time[i]=0;
        intv = setInterval(function(){
            if(knock_count == 0){
                console.log('waiting.');
            }else if(knock_count <= max_knock - 1){
                knock_time[knock_count-1] += interval_ms;
                console.log('knock count : '+knock_count+', time: '+ knock_time[knock_count-1]);
                if(knock_time[knock_count-1] >= max_interval_ms){
                    clearInterval(intv);
                    console.log(knock_count);
                    console.log(knock_time);
                    console.log('knocks stopped. Wrong knocks.');
                }
            }else{
                clearInterval(intv);
                console.log(knock_count);
                console.log(knock_time);
                console.log('reach max knock. Kncok finished.');
                verifyKnocks();
            }
        },interval_ms)
    });
    $(document).keydown(function(event){
     if(event.keyCode == 32) knock();
    });

    function knock(){
        knock_count++;
    }
    function verifyKnocks(){
        var max_time=0;
        var min_time=0;
        var mid_time;
        var knock_word = [];
        var error_count = 0;
        for(i=0;i<max_knock-1;i++){
            if(knock_time[i] >= max_time)max_time=knock_time[i];
            if(knock_time[i] <= min_time)min_time=knock_time[i];
        }
        mid_time = (max_time+min_time)/2;
        for(i=0;i<max_knock-1;i++){
            if(knock_time[i] >= mid_time){
                knock_word[i]=1;
            }else{
                knock_word[i]=0;
            }
        }
        for(i=0;i<max_knock-1;i++){
            if(knock_word[i] != knock_password[i]){
                error_count++;
            }
        }
        console.log('knocks interpreter:')
        console.log(knock_word);
        console.log('verification result:');
        if(error_count <= max_error){
            console.log('PASSED');
        }else{
            console.log('WRONG');
        }
    }
</script>

硬件的开发

1.如何感知敲门?

使用振动传感器,调节电位器调节其灵敏度。震动传感器上V脚接5V电源,G脚接地,D0为中断(Interrupt)脚,接在Arduino Nano 的D2或D3引脚。(ArduinoNano引脚分布见文章)每震动一次,D2/D3(即Interrupt0/Interrupt1)串口电位下降(Falling)一次,通过中断即可实现震动的感知。

2.如何开门?

使用步进电机。此处直接使用了ULN2003的步进电机驱动板。但是注意,此淘宝店铺的步进电机和驱动板的排线并不一致,由于开始时并未注意到,以致一直无法驱动,后看到这篇文章,发现问题所在,重新用杜邦线将排线对应起来,方可驱动电机。

电机上12345分别对应驱动板上的42135

3.如何连接蓝牙模块

此处使用的是BLE(bluetooth low energy / 低功耗蓝牙),BLE与普通的蓝牙略有不同。

将蓝牙的RX/TX引脚分别接到ArduinoNano的TX/RX(注意,RX接TX,TX接RX)。(也可以接到普通数字引脚,使用SoftwareSerial,这样不会影响到电脑上串口监视器的数据)。

在loop中监视串口数据(此处有技巧,详见代码)。

4.安卓APP的开发

由于仅仅是一个实验性的作品,所以Android APP的开发较为简陋,直接使用了FastBle库(GitHub)。

最后,祭上Arduino代码

#include <Stepper.h>
#include <SoftwareSerial.h>
#include <EEPROM.h>

#define LED_PIN 13
#define SENSOR_INTERRUPT_PORT 0
#define STEPPER_1_PIN 5
#define STEPPER_2_PIN 6
#define STEPPER_3_PIN 7
#define STEPPER_4_PIN 8
#define STEPS 64 //定义旋转一圈是多少步,此数据要根据电机的参数确定,不可随意填写。
#define BT_RX_PIN 9
#define BT_TX_PIN 10

Stepper stepper(STEPS,STEPPER_1_PIN,STEPPER_2_PIN,STEPPER_3_PIN,STEPPER_4_PIN);
SoftwareSerial BT(BT_RX_PIN,BT_TX_PIN);

int knock_count = 0;//记录敲击次数
int interval_ms = 100;//刷新间隔
int max_interval_ms = 1500;//最大间隔
int knock_password[] = {0,0,1,0,0,1}; //敲门密码
int max_knock = (sizeof(knock_password) / sizeof(knock_password[0])) + 1; //密码长度
int knock_time[sizeof(knock_password) / sizeof(knock_password[0])]; //记录每次敲击时间
int max_error = 0;//容错数

int p;//蓝牙串口数据

void setup() {
  pinMode(LED_PIN, OUTPUT);
  attachInterrupt(SENSOR_INTERRUPT_PORT, knock, FALLING);
  for (int i = 0; i < max_knock - 1; i++) {
    knock_time[i] = 0;
  }
  Serial.begin(9600);
  Serial.println("Start.");

  BT.begin(9600);

  stepper.setSpeed(420);
}

void loop() {
  detectKnock();
  detectSerial();
  delay(interval_ms);
}

void detectKnock(){
  if (knock_count == 0) { //等待中
  } else if (knock_count <= max_knock - 1) {
    knock_time[knock_count - 1] += interval_ms;
    if (knock_time[knock_count - 1] >= max_interval_ms) {
      timeout();
    }
  } else {
    verify();
  }
}

void detectSerial(){
  while (BT.available() > 0){
    p = BT.parseInt();
    char r = BT.read();
    if(r == 'X'){
      switch(p){
        case 0://收到0(开门信号)
          pass();
          break;
        default://收到的为步数
          opendoor((int)p);
          break;
      }
    }
    else if(r == 'R'){//反转
      closedoor((int)p);
    }
  }  
}

void knock() {
  knock_count++;
  Serial.println("knocked");
}

void timeout() {
  Serial.println("timeout");
  for (int j = 0; j < 3; j++) {
    digitalWrite(LED_PIN, HIGH);
    delay(250);
    digitalWrite(LED_PIN, LOW);
    delay(250);
  }
  restart();
}

void verify() {
  Serial.println("verifying");
  int max_time = 0;
  int min_time = max_interval_ms;
  int mid_time = 0;
  int knock_code[max_knock-1];
  int error_count = 0;
  for (int i = 0; i < max_knock - 1; i++) {
    if (knock_time[i] >= max_time) max_time = knock_time[i];
    if (knock_time[i] < min_time) min_time = knock_time[i];
  }
  mid_time = (max_time + min_time) / 2;
  for (int i = 0; i < max_knock - 1; i++) {
    if (knock_time[i] >= mid_time) {
      knock_code[i] = 1;
    } else {
      knock_code[i] = 0;
    }
  }
  for (int i = 0; i < max_knock - 1; i++) {
    if (knock_code[i] != knock_password[i]) error_count++;
  }
  if (error_count <= max_error) {
    pass();
  } else {
    wrong();
  }
}

void pass() {
  Serial.println("passed");
  opendoor(1250);//实际测试数据,1250刚好开门。
  delay(5000);
  closedoor(1250);
  restart();
}

void wrong() {
  Serial.println("wrong");
  for(int i=0; i<3; i++){
    digitalWrite(LED_PIN, HIGH);
    delay(500);
    digitalWrite(LED_PIN, LOW);
    delay(500);  
  }
  restart();
}
void restart() {
  knock_count = 0;
  for (int i = 0; i < max_knock - 1; i++) {
    knock_time[i] = 0;
  }
  Serial.println("restart.");
}

void opendoor(int s){
  stepper.step(s);  //2048为一圈
}
void closedoor(int s){
  stepper.step(-s);  //2048为一圈
}

5.门锁部分的3D打印

初识3D建模,使用Blender画了三个模型,请金通赛学长帮忙打印(再次感谢)。

此处就不附上stl了。

压末一个,附上几张照片

以上