一、前言
近年来,随着国民经济的发展,交通拥堵和环境污染问题越来越突出,自行车在改善交通和环境方面发挥了重要作用。随着自行车的发展,自行车的科技含量越来越高,但自行车安全问题却十分突出。目前市场上的自行车锁大多是传统的机械结构车锁,尚未智能化,急需解决。本文提出了一个基础STM32单片机智能自行车锁(马蹄锁)的设计方法,提高了自行车锁的智能化和安全性。
硬件选项说明:单片机采用STM32F103RCT6,GSM模块采用SIM800C,完成网络连接和数据上传,GPS华为云用于物联网平台IOT,蓝牙模块作为数据存储端,采用正点原子低功耗BLE蓝牙,支持蓝牙解锁解锁,使用车辆状态ADXL345三轴加速度传感器检测,电容矩阵键盘用于密码键盘。
二、总结设计思路
需要设计一个Android手机APP,可以远程开锁解锁,手机APP对接华为云物联网平台,实现远程与自行车锁的数据交互,下达命令。智能锁和华为云IOT服务器之间的通信协议采用MQTT协议,手机APP与华为云IOT在服务器之间使用HTTP协议。除了支持远程解锁和关锁外,智能锁还支持蓝牙解锁和输入密码开始。APP支持蓝牙功能,可以连接智能锁上的蓝牙,完成解锁和关锁。如果没有手机,可以输入密码解锁。
通过车辆状态检测ADXL345三轴加速度计检测,如果车辆锁定,发现车辆移动会触发报警,锁中的蜂鸣器会继续响,SIM800C会向指定的手机号码发送短信,提示车辆可能被盗,同时上传GPS从经纬度到云服务器,手机APP智能锁上传可获得智能锁上传GPS调用百度地图显示车辆位置,方便寻车。
三、硬件选择
(1) 加速度计传感器
ADXL345是一种小尺寸、薄、低功耗、完整的三轴加速度计,提供信号调节的电压输出。
说明:CS选择高电平IIC通信,反之亦然SPI通信。SDO(地址引脚)根据手册器件的7位连接高电平I2C地址是0x1D,跟上阅读/写位(R/W),寄存器为0x3A,读寄存器为0x3B;接低电平,7位I2C地址是0x53.同理,跟上读写标志位后,写寄存器为0xA6.读寄存器为0xA7;
(2) STM32开发板
STM32F103RCT6芯体规格为32位,速度为72位MHz,程序存储容量为256KB,程序存储器的类型是FLASH,RAM容量是48K。
(3) BLE低功耗蓝牙模块
(4) SIM800C
1、支持极限DC5V-18V宽电压输入
3、支持锂电池供电接口VBAT3.5-4.5V
4.输入支持移动和联通手机卡Micro SIM卡
5、送51/STM32/ARDUINO驱动例程
2.电源开始默认使用能量引脚
6.数据终端准备
7.内核音频输出引脚
8.内核音频输出引脚
9、锂电池输入引脚,DC 3.5 - 4.5V
10、电源地
13、RTC外部电池引脚
14内核振铃提示引脚
15内合音频输入引脚
16内核音频输入引脚
建议使用V_IN单独供电DC5-18V输入(推荐9V),或者VBAT锂电池的供电方式是最稳定的。如果只是简单的调试,也可以使用USB-TTL或开发板5V直接给模块供电。不过一般电脑或者开发板的功率有限,可能会不稳定。请根据具体情况自己取舍选择合适电源。
3. 手机APP软件设计
3.1 通信说明
通过支持上位机与设备之间的BLE低功耗串口蓝牙通信,支持华为云服务器通过网络通信,手机APP下发open_lock和close_lock关锁开锁。
3.2 建立开发环境
采用上位机软件Qt框架设计,Qt是跨平台的C 图形用户界面应用程序框架。Qt是一个1991年由Qt Company跨平台开发C 图形用户界面应用程序开发框架。它可以开发GUI也可用于开发非程序GUI程序,如控制台工具和服务器。简单来说,QT它可以很容易地帮助面的软件,甚至不需要投入太多精力。
https://www.qt.io/
QT学习入门实战专栏文章: https://blog.csdn.net/xiaolong1126626497/category_11400392.html
QT5.12.6下载地址: https://download.qt.io/archive/qt/5.12/5.12.6/
4. 创建云设备
4.1 创建产品
登录官网: https://www.huaweicloud.com/product/iothub.html
直接搜索物联网,打开页面。
4.2 自定义模型
4.3 注册设备
设备创建成功:
{
"device_id": "6274b1d62d5e854503d3a67e_lock", "secret": "12345678" }
4.4 MQTT设备密匙
创建产品和设备后,你需要知道如何通过它MQTT协议登录华为云服务器。 官方的详细介绍在这里: https://support.huaweicloud.com/devg-iothub/iot_01_2127.html#ZH-CN_TOPIC_0240834853__zh-cn_topic_0251997880_li365284516112
属性报告格式: https://support.huaweicloud.com/api-iothub/iot_06_v5_3010.html
MQTT设备登录密钥生成地址:
DeviceId 6274b1d62d5e854503d3a67e_lock DeviceSecret 12345678 ClientId 6274b1d62d5e854503d3a67e_lock_0_0_2022050605 Username 6274b1d62d5e854503d3a67e_lock Password 334dd7c0c10e47280880e9dd004ae0d8c5abc24dbbc9daa735315722707fe13b
< id="45_MQTT_186">4.5 使用MQTT客户端软件登录
所有的参数已经得到,接下来采用MQTT客户端登录华为云进行测试。 华为云物联网平台的域名是: 161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com
华为云物联网平台的IP地址是:121.36.42.100
在软件里参数填充正确之后,就看到设备已经连接成功了。 接下来打开设备页面,可以看到设备已经在线了。
4.6 数据上报测试
//订阅主题: 平台下发消息给设备
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/messages/down
//设备上报数据
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/properties/report
//上报的属性消息 (一次可以上报多个属性,在json里增加就行了)
{
"services": [{
"service_id": "lock","properties":{
"lock":1}}]}
//订阅主题: 平台下发消息给设备
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/messages/down
//设备上报数据
$oc/devices/6274b1d62d5e854503d3a67e_lock/sys/properties/report
//上报的属性消息 (一次可以上报多个属性,在json里增加就行了)
{
"services": [{
"service_id": "lock","properties":{
"GPS信息":"lat:12.345,lng:45.678"}}]}
4.7 应用侧开发
为了更方便的展示设备数据,与设备完成交互,还需要开发一个配套的上位机,官方提供了应用侧开发的API接口、SDK接口,为了方便通用一点,我这里采用了API接口完成数据交互,上位机软件采用QT开发。
帮助文档地址: ttps://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html
设备属性就是设备上传的传感器状态数据信息,应用侧提供了API接口,可以主动向设备端下发请求指令;设备端收到指令之后需要按照约定的数据格式上报数据;所以,要实现应用层与设备端的数据交互,需要应用层与设备端配合才能完成。
5. STM32开发
STM32连接华为云IOT的工程代码Get: https://download.csdn.net/download/xiaolong1126626497/81993720
5.1 ADXL345.c
#include "app.h" /* 函数功能: 各种硬初始化
继电器模块--DAT--->PA4 PB12-----输入引脚,检测模块是否连接或者断开 */ void Hardware_Init(void) { RCC->APB2ENR|=1<<2; GPIOA->CRL&=0xFFF0FFFF; GPIOA->CRL|=0x00030000; RCC->APB2ENR|=1<<3; GPIOB->CRH&=0xFFF0FFFF; GPIOB->CRH|=0x00080000; } // //初始化ADXL345. //返回值:0,初始化成功;1,初始化失败. u8 ADXL345_Init(void) { IIC_Init(); //初始化IIC总线 if(ADXL345_RD_Reg(DEVICE_ID)==0XE5) //读取器件ID { ADXL345_WR_Reg(DATA_FORMAT,0X2B); //低电平中断输出,13位全分辨率,输出数据右对齐,16g量程 ADXL345_WR_Reg(BW_RATE,0x0A); //数据输出速度为100Hz ADXL345_WR_Reg(POWER_CTL,0x28); //链接使能,测量模式 ADXL345_WR_Reg(INT_ENABLE,0x00); //不使用中断 ADXL345_WR_Reg(OFSX,0x00); ADXL345_WR_Reg(OFSY,0x00); ADXL345_WR_Reg(OFSZ,0x00); return 0; } return 1; } //写ADXL345寄存器 //addr:寄存器地址 //val:要写入的值 //返回值:无 void ADXL345_WR_Reg(u8 addr,u8 val) { IIC_Start(); IIC_Send_Byte(ADXL_WRITE); //发送写器件指令 IIC_Wait_Ack(); IIC_Send_Byte(addr); //发送寄存器地址 IIC_Wait_Ack(); IIC_Send_Byte(val); //发送值 IIC_Wait_Ack(); IIC_Stop(); //产生一个停止条件 } //读ADXL345寄存器 //addr:寄存器地址 //返回值:读到的值 u8 ADXL345_RD_Reg(u8 addr) { u8 temp=0; IIC_Start(); IIC_Send_Byte(ADXL_WRITE); //发送写器件指令 temp=IIC_Wait_Ack(); IIC_Send_Byte(addr); //发送寄存器地址 temp=IIC_Wait_Ack(); IIC_Start(); //重新启动 IIC_Send_Byte(ADXL_READ); //发送读器件指令 temp=IIC_Wait_Ack(); temp=IIC_Read_Byte(0); //读取一个字节,不继续再读,发送NAK IIC_Stop(); //产生一个停止条件 return temp; //返回读到的值 } //读取ADXL的平均值 //x,y,z:读取10次后取平均值 void ADXL345_RD_Avval(short *x,short *y,short *z) { short tx=0,ty=0,tz=0; u8 i; for(i=0;i<10;i++) { ADXL345_RD_XYZ(x,y,z); delay_ms(10); tx+=(short)*x; ty+=(short)*y; tz+=(short)*z; } *x=tx/10; *y=ty/10; *z=tz/10; } //自动校准 //xval,yval,zval:x,y,z轴的校准值 void ADXL345_AUTO_Adjust(char *xval,char *yval,char *zval) { short tx,ty,tz; u8 i; short offx=0,offy=0,offz=0; ADXL345_WR_Reg(POWER_CTL,0x00); //先进入休眠模式. delay_ms(100); ADXL345_WR_Reg(DATA_FORMAT,0X2B); //低电平中断输出,13位全分辨率,输出数据右对齐,16g量程 ADXL345_WR_Reg(BW_RATE,0x0A); //数据输出速度为100Hz ADXL345_WR_Reg(POWER_CTL,0x28); //链接使能,测量模式 ADXL345_WR_Reg(INT_ENABLE,0x00); //不使用中断 ADXL345_WR_Reg(OFSX,0x00); ADXL345_WR_Reg(OFSY,0x00); ADXL345_WR_Reg(OFSZ,0x00); delay_ms(12); for(i=0;i<10;i++) { ADXL345_RD_Avval(&tx,&ty,&tz); offx+=tx; offy+=ty; offz+=tz; } offx/=10; offy/=10; offz/=10; *xval=-offx/4; *yval=-offy/4; *zval=-(offz-256)/4; ADXL345_WR_Reg(OFSX,*xval); ADXL345_WR_Reg(OFSY,*yval); ADXL345_WR_Reg(OFSZ,*zval); } //读取3个轴的数据 //x,y,z:读取到的数据 void ADXL345_RD_XYZ(short *x,short *y,short *z) { u8 buf[6]; u8 i; IIC_Start(); IIC_Send_Byte(ADXL_WRITE); //发送写器件指令 IIC_Wait_Ack(); IIC_Send_Byte(0x32); //发送寄存器地址(数据缓存的起始地址为0X32) IIC_Wait_Ack(); IIC_Start(); //重新启动 IIC_Send_Byte(ADXL_READ); //发送读器件指令 IIC_Wait_Ack(); for(i=0;i<6;i++) { if(i==5)buf[i]=IIC_Read_Byte(0);//读取一个字节,不继续再读,发送NACK else buf[i]=IIC_Read_Byte(1); //读取一个字节,继续读,发送ACK } IIC_Stop(); //产生一个停止条件 *x=(short)(((u16)buf[1]<<8)+buf[0]); *y=(short)(((u16)buf[3]<<8)+buf[2]); *z=(short)(((u16)buf[5]<<8)+buf[4]); } //读取ADXL345的数据times次,再取平均 //x,y,z:读到的数据 //times:读取多少次 void ADXL345_Read_Average(short *x,short *y,short *z,u8 times) { u8 i; short tx,ty,tz; *x=0; *y=0; *z=0; if(times)//读取次数不为0 { for(i=0;i<times;i++)//连续读取times次 { ADXL345_RD_XYZ(&tx,&ty,&tz); *x+=tx; *y+=ty; *z+=tz; delay_ms(5); } *x/=times; *y/=times; *z/=times; } } //得到角度 //x,y,z:x,y,z方向的重力加速度分量(不需要单位,直接数值即可) //dir:要获得的角度.0,与Z轴的角度;1,与X轴的角度;2,与Y轴的角度. //返回值:角度值.单位0.1°. short ADXL345_Get_Angle(float x,float y,float z,u8 dir) { float temp; float res=0; switch(dir) { case 0://与自然Z轴的角度 temp=sqrt((x*x+y*y))/z; res=atan(temp); break; case 1://与自然X轴的角度 temp=x/sqrt((y*y+z*z)); res=atan(temp); break; case 2://与自然Y轴的角度 temp=y/sqrt((x*x+z*z)); res=atan(temp); break; } return res*1800/3.14; } //初始化IIC void IIC_Init(void) { RCC->APB2ENR|=1<<3; //先使能外设IO PORTB时钟 GPIOB->CRL&=0X00FFFFFF; //6/7 推挽输出 GPIOB->CRL|=0X33000000; GPIOB->ODR|=3<<6; //6,7 输出高 } //产生IIC起始信号 void IIC_Start(void) { SDA_OUT(); //sda线输出 IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(4); IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 } //产生IIC停止信号 void IIC_Stop(void) { SDA_OUT();//sda线输出 IIC_SCL=0; IIC_SDA=0;//STOP:when CLK is high DATA change form low to high delay_us(4); IIC_SCL=1; IIC_SDA=1;//发送I2C总线结束信号 delay_us(4); } //等待应答信号到来 //返回值:1,接收应答失败 // 0,接收应答成功 u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); //SDA设置为输入 IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL=0;//时钟输出0 return 0; } //产生ACK应答 void IIC_Ack(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //不产生ACK应答 void IIC_NAck(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //IIC发送一个字节 //返回从机有无应答 //1,有应答 //0,无应答 void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0;//拉低时钟开始数据传输 for(t=0;t<8;t++) { IIC_SDA=(txd&0x80)>>7; txd<<=1; delay_us(2); //对TEA5767这三个延时都是必须的 IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } } //读1个字节,ack=1时,发送ACK,ack=0,发送nACK u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN();//SDA设置为输入 for(i=0;i<8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receive<<=1; if(READ_SDA)receive++; delay_us(1); } if (!ack