宿舍智能门锁·开发经验
前言
祖国七十诞辰之际,为参加校智能车竞赛,需开发一硬件设备以通过面试选拔,因私以为所给题目太过简单且无用,故突发奇想,欲成一智能门锁,以解带钥匙之劳。
准备
- 控制板: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了。