為了讓女朋友運動起來,小夥兒不僅買單車還設計了智慧防盜單車鎖

語言: CN / TW / HK
摘要:本文提出一種基於STM32微控制器的智慧腳踏車鎖(馬蹄鎖)的設計方法,來提高腳踏車鎖的智慧化及安防等級。

本文分享自華為雲社群《基於STM32+華為雲IOT設計的智慧防盜單車鎖【玩轉華為雲】》,作者: DS小龍哥 。

一、前言

近年來隨著國民經濟的發展,交通擁堵和環境汙染問題越來越突出,而腳踏車對改善交通與環境起到了重要作用。中國本身是一個腳踏車使用大國,隨著腳踏車的發展,腳踏車的科技含量越來越高,然而腳踏車安防問題突出。目前市場上腳踏車鎖大多是傳統機械結構車鎖,沒有實現智慧化,急需解決。本文提出一種基於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直接給模組供電。不過一般電腦或者開發板的功率有限,可能會不穩定。請根據具體情況自己取捨選擇合適電源。

四. 手機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開發。

幫助文件地址: https://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<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)
        IIC_NAck();//傳送nACK
    else
        IIC_Ack(); //傳送ACK   
    return receive;
}

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<USART2_RX_CNT;i++)printf("%#x ",USART2_RX_BUFFER[i]);
            
            USART2_RX_FLAG=0;
            USART2_RX_CNT=0;
        }
        //CONNECT
        if(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1]) //連線成功			   
        {
            return 0;//連線成功
        }
    }
    
	return 1;
}

/*
函式功能: MQTT訂閱/取消訂閱資料打包函式
函式引數:
    topic       主題   
    qos         訊息等級 0:最多分發一次  1: 至少分發一次  2: 僅分發一次
    whether     訂閱/取消訂閱請求包 (1表示訂閱,0表示取消訂閱)
返回值: 0表示成功 1表示失敗
*/
u8 MQTT_SubscribeTopic(char *topic,u8 qos,u8 whether)
{    
    u8 i,j;
	mqtt_txlen=0;
    int topiclen = strlen(topic);
	
	int DataLen = 2 + (topiclen+2) + (whether?1:0);//可變報頭的長度(2位元組)加上有效載荷的長度
	//固定報頭
	//控制報文型別
    if(whether)mqtt_txbuf[mqtt_txlen++] = 0x82; //訊息型別和標誌訂閱
    else	mqtt_txbuf[mqtt_txlen++] = 0xA2;    //取消訂閱

	//剩餘長度
	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;			//訊息識別符號 MSB
    mqtt_txbuf[mqtt_txlen++] = 0x0A;        //訊息識別符號 LSB
	//有效載荷
    mqtt_txbuf[mqtt_txlen++] = BYTE1(topiclen);//主題長度 MSB
    mqtt_txbuf[mqtt_txlen++] = BYTE0(topiclen);//主題長度 LSB   
	memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclen);
    mqtt_txlen += topiclen;
    
    if(whether)
    {
       mqtt_txbuf[mqtt_txlen++] = qos;//QoS級別
    }
    
    for(i=0;i<10;i++)
    {
        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,(char*)USART2_RX_BUFFER,USART2_RX_CNT);
				USART2_RX_FLAG=0;
				USART2_RX_CNT=0;
			}
			
			if(mqtt_rxbuf[0]==parket_subAck[0] && mqtt_rxbuf[1]==parket_subAck[1]) //訂閱成功			   
			{
				return 0;//訂閱成功
			}
        }
    }
	return 1; //失敗
}

//MQTT釋出資料打包函式
//topic   主題 
//message 訊息
//qos     訊息等級 
u8 MQTT_PublishData(char *topic, char *message, u8 qos)
{  
    int topicLength = strlen(topic);    
    int messageLength = strlen(message);     
    static u16 id=0;
	int DataLen;
	mqtt_txlen=0;
	//有效載荷的長度這樣計算:用固定報頭中的剩餘長度欄位的值減去可變報頭的長度
	//QOS為0時沒有識別符號
	//資料長度             主題名   報文識別符號   有效載荷
    if(qos)	DataLen = (2+topicLength) + 2 + messageLength;       
    else	DataLen = (2+topicLength) + messageLength;   

    //固定報頭
	//控制報文型別
    mqtt_txbuf[mqtt_txlen++] = 0x30;    // MQTT Message Type PUBLISH  

	//剩餘長度
	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++] = BYTE1(topicLength);//主題長度MSB
    mqtt_txbuf[mqtt_txlen++] = BYTE0(topicLength);//主題長度LSB 
	memcpy(&mqtt_txbuf[mqtt_txlen],topic,topicLength);//拷貝主題
    mqtt_txlen += topicLength;
        
	//報文識別符號
    if(qos)
    {
        mqtt_txbuf[mqtt_txlen++] = BYTE1(id);
        mqtt_txbuf[mqtt_txlen++] = BYTE0(id);
        id++;
    }
	memcpy(&mqtt_txbuf[mqtt_txlen],message,messageLength);
    mqtt_txlen += messageLength;
        
	MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);
    return mqtt_txlen;
}

更多學習內容,請關注IoT物聯網社群,新增華為雲IoT小助手微訊號(hwc-iot),回覆“閱讀”獲取更多資訊

 

點選關注,第一時間瞭解華為雲新鮮技術~