基于JavaSwing坦克大戰游戲的設計和實現
789
2025-04-04
一、前言
近年來隨著國民經濟的發展,交通擁堵和環境污染問題越來越突出,而自行車對改善交通與環境起到了重要作用。中國本身是一個自行車使用大國,隨著自行車的發展,自行車的科技含量越來越高,然而自行車安防問題突出。目前市場上自行車鎖大多是傳統機械結構車鎖,沒有實現智能化,急需解決。本文提出一種基于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位,速度是72MHz,程序存儲器容量是256KB,程序存儲器類型是FLASH,RAM容量是48K。
(3) BLE低功耗藍牙模塊
(4) SIM800C
模塊特點:
1、支持極限DC5V-18V寬電壓輸入
2、有電源使能開關引腳EN
3、支持鋰電池供電接口VBAT3.5-4.5V
4、輸入支持移動和聯通手機卡Micro SIM卡
5、送51/STM32/ARDUINO驅動例程
1、DC 5V-18V電源輸入,推薦使用DC 9V
2、電源開始使能引腳默認使能
3、電源地
4、GSM模塊的TXD引腳接其它模塊的RXD
5、GSM模塊的RXD引腳接其它模塊的TXD
6、數據終端準備
7、內核音頻輸出引腳
8、內核音頻輸出引腳
9、鋰電池輸入引腳,DC 3.5 - 4.5V
10、電源地
11、啟動引腳和GND短路可實現開機自啟動
12、電源地
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可以很輕松的幫你做帶界面的軟件,甚至不需要你投入很大精力。
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
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開發
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
5.2 sim800.c
#include "sim800c.h" /* 函數功能:向SIM800C模塊發送指令 函數參數: char *cmd 發送的命令 char *check_data 檢測返回的數據 返回值: 0表示成功 1表示失敗 */ u8 SIM800C_SendCmd(char *cmd,char *check_data) { u16 i,j; for(i=0;i<5;i++) //測試的總次數 { USART2_RX_FLAG=0; USART2_RX_CNT=0; memset(USART2_RX_BUFFER,0,sizeof(USART2_RX_BUFFER)); USARTx_StringSend(USART2,cmd); //發送指令 for(j=0;j<500;j++) //等待的時間(ms單位) { if(USART2_RX_FLAG) { USART2_RX_BUFFER[USART2_RX_CNT]='\0'; if(strstr((char*)USART2_RX_BUFFER,check_data)) { return 0; } else break; } delay_ms(20); //一次的時間 } } return 1; } /* 函數 功能:GSM模塊初始化檢測 函數返回值:1表示模塊檢測失敗,0表示成功 */ u8 SIM800C_InitCheck(void) { if(SIM800C_SendCmd("AT\r\n","OK"))return 1; else printf("SIM800模塊正常!\r\n"); if(SIM800C_SendCmd("ATE0\r\n","OK"))return 2; else printf("設置模塊不回顯成功!\r\n"); if(SIM800C_SendCmd("AT+CGMI\r\n","OK"))return 3; else printf("查詢制造商名稱成功!%s\r\n",USART2_RX_BUFFER); if(SIM800C_SendCmd("AT+CGMM\r\n","OK"))return 4; else printf("查詢模塊型號成功!%s\r\n",USART2_RX_BUFFER); DelayMs(1000); DelayMs(1000); if(SIM800C_SendCmd("AT+CNUM\r\n","+CNUM:"))return 5; else printf("獲取本機號碼成功!%s\r\n",USART2_RX_BUFFER); /* 返回格式如下: +CNUM: "","+8613086989413",145,7,4 OK */ return 0; } /* 函數 功能:GSM模塊短信模式設置 函數返回值:0表示模塊設置成功 */ u8 SIM800C_SetNoteTextMode(void) { if(SIM800C_SendCmd("AT+CSCS=\"GSM\"\r\n","OK"))return 1;// "GSM"字符集 else printf("短信GSM字符集設置成功!\r\n"); if(SIM800C_SendCmd("AT+CMGF=1\r\n","OK"))return 2; //文本模式 else printf("短信文本模式設置成功!\r\n"); return 0; } /* 函數功能:發送短信 函數參數: num:電話號碼 text:短信內容 函數返回值:0表示發送成功 */ u8 SIM800C_SendNote(u8 *num,u8 *text,u16 len) { char data[50]; char send_buf[2]; sprintf(data,"AT+CMGS=\"%s\"\r\n",num); if(SIM800C_SendCmd(data,">"))return 1; //設置發送的手機號 USARTx_DataSend(USART2,text,len); //發送短信內容 send_buf[0] = 0x1a; send_buf[1] = '\0'; if(SIM800C_SendCmd(send_buf,"+CMGS"))return 2; //發送結束符號 return 0; }
5.3 MQTT信息
//華為物聯網服務器的設備信息 #define MQTT_ClientID "62381267575fb713ee164ad2_xl_1_0_0_2022032106" #define MQTT_UserName "62381267575fb713ee164ad2_xl_1" #define MQTT_PassWord "124344feff3e3d96ff6af13cf36af36766619ff1eeee40e99cbae9b7b9739fe4" //訂閱與發布的主題 #define SET_TOPIC "$oc/devices/62381267575fb713ee164ad2_xl_1/sys/messages/down" //訂閱 #define POST_TOPIC "$oc/devices/62381267575fb713ee164ad2_xl_1/sys/properties/report" //發布 //設置連接的路由器信息 #define CONNECT_WIFI "abc" //將要連接的路由器名稱 --不要出現中文、空格等特殊字符 #define CONNECT_PASS "1234567890" //將要連接的路由器密碼 #define CONNECT_SERVER_IP "a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com" //服務器IP地址 #define CONNECT_SERVER_PORT 1883 //服務器端口號 u8 *mqtt_rxbuf; u8 *mqtt_txbuf; u16 mqtt_rxlen; u16 mqtt_txlen; u8 _mqtt_txbuf[256];//發送數據緩存區 u8 _mqtt_rxbuf[256];//接收數據緩存區 typedef enum { //名字 值 報文流動方向 描述 M_RESERVED1 =0 , // 禁止 保留 M_CONNECT , // 客戶端到服務端 客戶端請求連接服務端 M_CONNACK , // 服務端到客戶端 連接報文確認 M_PUBLISH , // 兩個方向都允許 發布消息 M_PUBACK , // 兩個方向都允許 QoS 1消息發布收到確認 M_PUBREC , // 兩個方向都允許 發布收到(保證交付第一步) M_PUBREL , // 兩個方向都允許 發布釋放(保證交付第二步) M_PUBCOMP , // 兩個方向都允許 QoS 2消息發布完成(保證交互第三步) M_SUBSCRIBE , // 客戶端到服務端 客戶端訂閱請求 M_SUBACK , // 服務端到客戶端 訂閱請求報文確認 M_UNSUBSCRIBE , // 客戶端到服務端 客戶端取消訂閱請求 M_UNSUBACK , // 服務端到客戶端 取消訂閱報文確認 M_PINGREQ , // 客戶端到服務端 心跳請求 M_PINGRESP , // 服務端到客戶端 心跳響應 M_DISCONNECT , // 客戶端到服務端 客戶端斷開連接 M_RESERVED2 , // 禁止 保留 }_typdef_mqtt_message; //連接成功服務器回應 20 02 00 00 //客戶端主動斷開連接 e0 00 const u8 parket_connetAck[] = {0x20,0x02,0x00,0x00}; const u8 parket_disconnet[] = {0xe0,0x00}; const u8 parket_heart[] = {0xc0,0x00}; const u8 parket_heart_reply[] = {0xc0,0x00}; const u8 parket_subAck[] = {0x90,0x03}; void MQTT_Init(void) { //緩沖區賦值 mqtt_rxbuf = _mqtt_rxbuf; mqtt_rxlen = sizeof(_mqtt_rxbuf); mqtt_txbuf = _mqtt_txbuf; mqtt_txlen = sizeof(_mqtt_txbuf); memset(mqtt_rxbuf,0,mqtt_rxlen); memset(mqtt_txbuf,0,mqtt_txlen); //無條件先主動斷開 MQTT_Disconnect(); delay_ms(100); MQTT_Disconnect(); delay_ms(100); } /* 函數功能: 登錄服務器 函數返回值: 0表示成功 1表示失敗 */ u8 MQTT_Connect(char *ClientID,char *Username,char *Password) { u8 i,j; int ClientIDLen = strlen(ClientID); int UsernameLen = strlen(Username); int PasswordLen = strlen(Password); int DataLen; mqtt_txlen=0; //可變報頭+Payload 每個字段包含兩個字節的長度標識 DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2); //固定報頭 //控制報文類型 mqtt_txbuf[mqtt_txlen++] = 0x10; //MQTT Message Type CONNECT //剩余長度(不包括固定頭部) do { u8 encodedByte = DataLen % 128; DataLen = DataLen / 128; // if there are more data to encode, set the top bit of this byte if ( DataLen > 0 ) encodedByte = encodedByte | 128; mqtt_txbuf[mqtt_txlen++] = encodedByte; }while ( DataLen > 0 ); //可變報頭 //協議名 mqtt_txbuf[mqtt_txlen++] = 0; // Protocol Name Length MSB mqtt_txbuf[mqtt_txlen++] = 4; // Protocol Name Length LSB mqtt_txbuf[mqtt_txlen++] = 'M'; // ASCII Code for M mqtt_txbuf[mqtt_txlen++] = 'Q'; // ASCII Code for Q mqtt_txbuf[mqtt_txlen++] = 'T'; // ASCII Code for T mqtt_txbuf[mqtt_txlen++] = 'T'; // ASCII Code for T //協議級別 mqtt_txbuf[mqtt_txlen++] = 4; // MQTT Protocol version = 4 對于 3.1.1 版協議,協議級別字段的值是 4(0x04) //連接標志 mqtt_txbuf[mqtt_txlen++] = 0xc2; // conn flags mqtt_txbuf[mqtt_txlen++] = 0; // Keep-alive Time Length MSB mqtt_txbuf[mqtt_txlen++] = 100; // Keep-alive Time Length LSB 100S心跳包 保活時間 mqtt_txbuf[mqtt_txlen++] = BYTE1(ClientIDLen);// Client ID length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(ClientIDLen);// Client ID length LSB memcpy(&mqtt_txbuf[mqtt_txlen],ClientID,ClientIDLen); mqtt_txlen += ClientIDLen; if(UsernameLen > 0) { mqtt_txbuf[mqtt_txlen++] = BYTE1(UsernameLen); //username length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(UsernameLen); //username length LSB memcpy(&mqtt_txbuf[mqtt_txlen],Username,UsernameLen); mqtt_txlen += UsernameLen; } if(PasswordLen > 0) { mqtt_txbuf[mqtt_txlen++] = BYTE1(PasswordLen); //password length MSB mqtt_txbuf[mqtt_txlen++] = BYTE0(PasswordLen); //password length LSB memcpy(&mqtt_txbuf[mqtt_txlen],Password,PasswordLen); mqtt_txlen += PasswordLen; } memset(mqtt_rxbuf,0,mqtt_rxlen); MQTT_SendBuf(mqtt_txbuf,mqtt_txlen); for(j=0;j<10;j++) { delay_ms(50); if(USART2_RX_FLAG) { memcpy((char *)mqtt_rxbuf,USART2_RX_BUFFER,USART2_RX_CNT); //memcpy for(i=0;i
云端實踐 移動APP IoT
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。