【單片機筆記】上海移遠公司NB-IOT模組 BC26 使用STM32 AT命令實現連接阿里雲數據上傳和下載

前言

在調試之前看這個數據手冊一臉懵,特別是MQTT部分還是獨立的,這個和前接觸到的上海合宙的模塊多少有點出處。另外就是那個AT命令的傳入參數也是一臉懵,後來發現BC26的模塊好像把MQTT部分單獨的做成了支持阿里雲服務器的功能。接觸過阿里雲的設備對接相比都知道,阿里雲要求的是一機一密或者一型一密,這個在對於簡單的成本低廉的MCU來說無疑是一個很大的考驗。而BC26這塊還是做的非常友好的,在MQTT部分只需要傳入產品對應設備下的三元組即可,無需經過哈希算法計算密鑰。

先上圖:

連接及上傳部分:

數據下載部分:

基本上通信也是非常穩定的,不過我這個地方信號賊差,以模塊滿格31來算,這裏測試纔有9,10的樣子。

整體的應用層思路是這樣的:

1、單片機控制模塊自檢

2、單片機控制模塊連接阿里雲

3、單片機控制模塊定時發送數據並接收下發數據

看上面三個步驟雖然簡潔明瞭,單是要做好單片機的應用底層也不是那麼簡單,爲此我專門謝了一個對應BC26傳輸機制的地層代碼,可以檢測BC26模塊的狀態,連接狀態,斷線重連、超時復位等機制。詳細請看下文代碼:

BC26底層驅動部分:

C文件:

#include "fy_bc26.h"
static void ResetModule(void);
static void CheckModule(void);
static void SetCFUN(void);
static void CheckCIMI(void);
static void ActivateNetwork(void);
static void CheckNetwork(void);
static void CheckCSQ(void);
static void QMTCFG(char* PK, char* DN, char* DS);
static void QMTOPEN(void);
static void QMTCONN( char* DN );
static void QMTSUB( char* TOPIC );
static void CheckPubTopic(void);
_typdef_bc26 _bc26;


static void ClearFIFO(void) {
    memset(_bc26.rxbuf,0,256);
    *(_bc26.rxlen) = 0;
}
void BC26_Configuration(u8 *prx,u16 *rxlen)
{
    _bc26.rxbuf = prx;
    *(_bc26.rxlen) = *rxlen;
    _bc26.timeOver=0;
    _bc26.csq = 0;
    _bc26.sta = 0;
	
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_APB2PeriphClockCmd( BC26_RST_RCC,ENABLE);
    GPIO_InitStructure.GPIO_Pin = BC26_RST_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(BC26_RST_PORT, &GPIO_InitStructure);
	GPIO_ResetBits(BC26_RST_PORT,BC26_RST_PIN);
	
}
static void CheckPubTopic(void) {
    if(strstr((char *)_bc26.rxbuf,"+QMTPUB: 0,1,0") != NULL) {
        _bc26.sta = 11;
    }
}


//每300ms執行一次
void BC26_IRQHandler(void) {
    switch(_bc26.sta) {
    case 0:
        ResetModule();
        break;			//復位模塊
    case 1:
        CheckModule();
        break;			//檢查模塊
    case 2:
        SetCFUN();
        break;				//設置全功能
    case 3:
        CheckCIMI();
        break;				//檢查卡
    case 4:
        ActivateNetwork();
        break;		//激活網絡
    case 5:
        CheckNetwork();
        break;			//檢查網絡
    case 6:
        CheckCSQ();
        break;				//檢查信號強度
    case 7:
        QMTCFG(ProductKey,DeviceName,DeviceSecret);
        break;		//配置MQTT參數
    case 8:
        QMTOPEN();
        break;				//打開TCP連接
    case 9:
        QMTCONN(DeviceName);
        break;				//登錄MQTT服務器
    case 10:
        QMTSUB(SubTopic);
        break;				//訂閱主題
    case 11:
        break;
    case 12:
        CheckPubTopic();				//發佈消息狀態檢測
    default:
        break;							//正常運行狀態
    }
}

/**
 * 功能:復位BC26模組
 * 參數:None
 * 返回值:
 *        0 :正常
 *      其他: 異常
 */
static void ResetModule(void) {

    if(_bc26.timeOver==0) {
        _bc26.timeOver = 66;	//20s
		GPIO_SetBits(BC26_RST_PORT,BC26_RST_PIN);
    }
    else {
        --_bc26.timeOver;
		if(_bc26.timeOver == 63){
			GPIO_ResetBits(BC26_RST_PORT,BC26_RST_PIN);
		}
		else if(_bc26.timeOver == 60){
			BC26_SendString("AT+QRST=1\r\n");
			LOG("AT+QRST=1\r\n");
		}
        else if(_bc26.timeOver == 4) {
            BC26_SendString("ATE0\r\n");	//關閉回顯
            LOG("ATE0\r\n");	//關閉回顯
        }
        else if(_bc26.timeOver==0) {
			_bc26.timeOver = 0;       
			_bc26.sta = 1;
            ClearFIFO();
        }
    }
}

/**
 * 功能:檢查BC26模組是否正常
 * 參數:None
 * 返回值:
 *        0 :正常
 *      其他: 異常
 */
static void CheckModule(void) {

    if(strstr((char*)_bc26.rxbuf,"OK") == NULL) {
        if(_bc26.timeOver==0) {
            ClearFIFO();
            BC26_SendString("AT\r\n");
            LOG("AT\r\n");
            _bc26.timeOver = 3;
        }
        else {
            --_bc26.timeOver;
            if(_bc26.timeOver==0) {
                _bc26.sta = 0;
                ClearFIFO();
            }
        }
    }
    else {
		_bc26.timeOver = 0;      
		_bc26.sta=2;
        ClearFIFO();
    }
}

/**
 * 功能:設置BC26的CFUN功能(默認打開全功能)
 * 參數:None
 * 返回值:
 *        0 :正常
 *      其他: 異常
 */
static void SetCFUN(void) {

    if(strstr((char*)_bc26.rxbuf,"OK") == NULL) {
        if(_bc26.timeOver==0) {
            ClearFIFO();
            BC26_SendString("AT+CFUN=1\r\n");
            LOG("AT+CFUN=1\r\n");
            _bc26.timeOver = 3;
        }
        else {
            --_bc26.timeOver;
            if(_bc26.timeOver==0) {
                _bc26.sta = 0;
                ClearFIFO();
            }
        }
    }
    else {
		_bc26.timeOver = 0;      
		_bc26.sta=3;
        ClearFIFO();
    }
}

/**
 * 功能:檢查BC26的SIM卡是否正常
 * 參數:None
 * 返回值:
 *        0 :正常
 *      其他: 異常
 */
static void CheckCIMI(void) {

    if(strstr((char*)_bc26.rxbuf,"460") == NULL) {
        if(_bc26.timeOver==0) {
            ClearFIFO();
            BC26_SendString("AT+CIMI\r\n");
            LOG("AT+CIMI\r\n");
            _bc26.timeOver = 3;
        }
        else {
            --_bc26.timeOver;
            if(_bc26.timeOver==0) {
                _bc26.sta = 0;
                ClearFIFO();
            }
        }
    }
    else {
		_bc26.timeOver = 0;     
		_bc26.sta=4;
        ClearFIFO();
    }
}

/**
 * 功能:設置(激活)網絡
 * 參數:None
 * 返回值:
 *        0 :正常
 *      其他: 異常
 */
static void ActivateNetwork(void) {

    if(strstr((char*)_bc26.rxbuf,"OK") == NULL) {
        if(_bc26.timeOver==0) {
            ClearFIFO();
            BC26_SendString("AT+CGATT=1\r\n");
            LOG("AT+CGATT=1\r\n");
            _bc26.timeOver = 3;
        }
        else {
            --_bc26.timeOver;
            if(_bc26.timeOver==0) {
                _bc26.sta = 0;
                ClearFIFO();
            }
        }
    }
    else {
		_bc26.timeOver = 0;      
		_bc26.sta=5;
        ClearFIFO();
    }
}

/**
 * 功能:檢查網絡激活狀態
 * 參數:None
 * 返回值:
 *        0 :正常
 *      其他: 異常
 */
static void CheckNetwork(void) {

    if(strstr((char*)_bc26.rxbuf,"+CGATT: 1") == NULL) {
        if(_bc26.timeOver==0) {
            ClearFIFO();
            BC26_SendString("AT+CGATT?\r\n");
            LOG("AT+CGATT?\r\n");
            _bc26.timeOver = 3;
        }
        else {
            --_bc26.timeOver;
            if(_bc26.timeOver==0) {
                _bc26.sta = 0;
                ClearFIFO();
            }
        }
    }
    else {
		_bc26.timeOver = 0;     
		_bc26.sta=6;
        ClearFIFO();
    }
}

/**
 * 功能:查看獲取CSQ值
 * 參數:None
 * 返回值:RSSI信號強度
 */
static void CheckCSQ(void) {

    _bc26.csq = _bc26.rxbuf[6]-'0';
    _bc26.csq *= 10;
    _bc26.csq += _bc26.rxbuf[7]-'0';

    if(_bc26.csq == 0 || _bc26.csq == 99) {
        ClearFIFO();
        if(_bc26.timeOver==0) {
            BC26_SendString("AT+CSQ\r\n");
            LOG("AT+CSQ\r\n");
            _bc26.timeOver = 3;
        }
        else {
            --_bc26.timeOver;
            if(_bc26.timeOver==0) {
                _bc26.sta = 0;
                ClearFIFO();
            }
        }
    }
    else {
		_bc26.timeOver = 0;      
		_bc26.sta=7;
        ClearFIFO();
    }
}


static void QMTCFG(char* PK, char* DN, char* DS) {

    if(strstr((char*)_bc26.rxbuf,"OK") == NULL) {

        if(_bc26.timeOver==0) {
            char temp[200];
            memset( temp, 0, sizeof( temp ) );	//清空 temp,避免隱藏錯誤

            strcat( temp, "AT+QMTCFG=\"aliauth\",0,\"" );	//AT+MQTCFG="aliauth",0,"
            strcat( temp, PK );						//AT+MQTCFG="aliauth",0,"PK
            strcat( temp, "\",\"" );						//AT+MQTCFG="aliauth",0,"PK","
            strcat( temp, DN ); 					//AT+MQTCFG="aliauth",0,"PK","DN
            strcat( temp, "\",\"" );						//AT+MQTCFG="aliauth",0,"PK","DN","
            strcat( temp, DS );					//AT+MQTCFG="aliauth",0,"PK","DN","DS
            strcat( temp, "\"\r\n" );						//AT+MQTCFG="aliauth",0,"PK","DN","DS"\r\n
            ClearFIFO();
            BC26_SendString(temp);
            LOG("%s",temp);

            _bc26.timeOver = 3;
        }
        else {
            --_bc26.timeOver;
            if(_bc26.timeOver==0) {
                _bc26.sta = 0;
                ClearFIFO();
            }
        }
    }
    else {
		_bc26.timeOver = 0;       
		_bc26.sta=8;
        ClearFIFO();
    }
}
//TCP連接阿里雲 注意,連接過程時間是比較長的,在連接過程重複發送連接命令會造成錯誤
static void QMTOPEN(void) {
    if(strstr((char*)_bc26.rxbuf,"+QMTOPEN: 0,0") == NULL) {
        ClearFIFO();
        if(_bc26.timeOver == 0) {
            BC26_SendString("AT+QMTOPEN=0,\"iot-as-mqtt.cn-shanghai.aliyuncs.com\",1883\r\n");
            LOG("AT+QMTOPEN=0,\"iot-as-mqtt.cn-shanghai.aliyuncs.com\",1883\r\n");
            _bc26.timeOver = 66;	//TCP連接加入檢測機制 20s時間
        }
        else {
            _bc26.timeOver--;
            if(_bc26.timeOver == 0) {
                _bc26.sta=0;
            }
        }
    }
    else {
        _bc26.timeOver = 0;
        _bc26.sta=9;
        ClearFIFO();
    }
}

//登錄阿里雲MQTT 注意,連接過程時間是比較長的,在連接過程重複發送連接命令會造成錯誤
static void QMTCONN( char* DN )
{
    if(strstr((char*)_bc26.rxbuf,"+QMTCONN: 0,0,0") == NULL) {

        if(_bc26.timeOver == 0) {
            char temp[200];
            memset( temp, 0, sizeof( temp ) );	//清空 temp,避免隱藏錯誤
            strcat( temp, "AT+QMTCONN=0,\"" );	//AT+QMTCONN=0,"
            strcat( temp, DN );			//AT+QMTCONN=0,"DN
            strcat( temp, "\"\r\n" );			//AT+QMTCONN=0,"DN"\r\n
            ClearFIFO();
            BC26_SendString( temp );
            LOG( "%s",temp );
            _bc26.timeOver = 66;				//MQTT登錄連接加入檢測機制 20s時間
        }
        else {
            _bc26.timeOver--;
            if(_bc26.timeOver == 0) {
                _bc26.sta=0;
            }
        }
    }
    else {
        _bc26.sta=10;
        _bc26.timeOver = 0;
        ClearFIFO();
    }
}

void QMTSUB( char* TOPIC )
{
    if(strstr((char*)_bc26.rxbuf,"+QMTSUB: 0,1,0,1") == NULL) {
        if(_bc26.timeOver == 0) {
            char temp[200];
            memset( temp, 0, sizeof( temp ) );	//清空 temp,避免隱藏錯誤

            strcat( temp, "AT+QMTSUB=0,1,\"" );	//AT+QMTSUB=0,1,"
            strcat( temp, TOPIC );			//AT+QMTSUB=0,1,"TOPIC
            strcat( temp, "\",0\r\n" );			//AT+QMTSUB=0,1,"TOPIC",0\r\n

            ClearFIFO();
            BC26_SendString( temp );
            LOG( "%s",temp );
			
			_bc26.timeOver = 66;			//20s
        }
        else {
            _bc26.timeOver--;
            if(_bc26.timeOver == 0) {
                _bc26.sta=8;
            }
        }
    }
    else {
		_bc26.timeOver = 0;    
		_bc26.sta=11;
        ClearFIFO();
    }
}

/* 此處的KeyWord 是阿里雲“功能定義”->“添加功能”->所選功能的標識符號*/
void BC26_PubTopic( char* Topic,char *KeyWord,char* value )
{
    if(_bc26.sta == 11) {	//空閒狀態可以發佈消息
        char temp[200];

        memset( temp, 0, sizeof( temp ) );						//清空 temp,避免隱藏錯誤

        strcat( temp, "AT+QMTPUB=0,1,1,0,\"" );					//AT+QMTPUB=0,0,0,0,"
        strcat( temp, Topic );								//AT+QMTPUB=0,0,0,0,"Topic
        strcat( temp, "\",\"{params:{" ); 						//AT+QMTPUB=0,0,0,0,"Topic","{params:{
        strcat( temp, KeyWord ); 								//AT+QMTPUB=0,0,0,0,"Topic","{params:{KeyWord
        strcat( temp, ":" ); 									//AT+QMTPUB=0,0,0,0,"Topic","{params:{KeyWord:
        strcat( temp, value );									//AT+QMTPUB=0,0,0,0,"Topic","{params:{KeyWord:value
        strcat( temp, "}}\"" );									//AT+QMTPUB=0,0,0,0,"Topic","{params:{KeyWord:value}}"
        strcat( temp, "\r\n" );									//AT+QMTPUB=0,0,0,0,"Topic","{params:{KeyWord:value}}"

        ClearFIFO();
        BC26_SendString( temp );
        LOG( "%s",temp );
        _bc26.timeOver = 33;									//10s
    }
    else if(_bc26.sta == 12) {
        if(_bc26.timeOver) {
            --_bc26.timeOver;
            if(_bc26.timeOver == 0) {
                printf("Topic Error!\r\n");
                _bc26.sta = 11;
            }
        }
    }
}


/*********************************************END OF FILE********************************************/

H文件:

#ifndef __FY_BC26_H
#define __FY_BC26_H

#include "fy_includes.h"

#define BC26_RST_RCC    RCC_APB2Periph_GPIOB
#define BC26_RST_PORT   GPIOB
#define BC26_RST_PIN    GPIO_Pin_3



#define BC26_SendString		Usart2_SendString
#define BC26_SendBuf 		Usart2_SendBuf



/*

	阿里雲服務器地址(華東2):*.iot-as-mqtt.cn-shanghai.aliyuncs.com:1883							*ProductKey

	hmacsha1加密在線計算網站:http://encode.chahuo.com/

	客戶端ID:	*|securemode=3,signmethod=hmacsha1|													*設備名稱

	用戶名	:	*&#																					*設備名稱 #ProductKey

	密碼	:	用DeviceSecret作爲密鑰對clientId*deviceName*productKey#進行hmacsha1加密後的結果		*設備名稱 #ProductKey

*/
////域名(華東2)
//#define DomainName		"%s.iot-as-mqtt.cn-shanghai.aliyuncs.com"	//ProductKey
////端口
//#define Port			1883


//設備三元組 根據自己的填寫就好
#define ProductKey	 	"xxx"
#define DeviceName	 	"xxx"
#define DeviceSecret	"xxx"

//MQTT	發佈主題			
#define PubTopic       	"/sys/xxx/xxx/thing/event/property/post"			//ProductKey	DeviceName
//MQTT	訂閱主題
#define SubTopic       	"/sys/xxx/xxx/thing/service/property/set"			//ProductKey	DeviceName


#define CurrentTemperature 		"CurrentTemperature"
#define CurrentHumidity			"CurrentHumidity"
#define TPSet					"TPSet"


typedef struct {
    u8 *rxbuf;
    u16 *rxlen;
    u16 timeOver;
    u8 csq;
    u8 sta;
} _typdef_bc26;

extern _typdef_bc26 _bc26;

void BC26_Configuration(u8 *rxbuf,u16 *rxlen);
void BC26_IRQHandler(void);
void BC26_PubTopic( char* Topic,char *KeyWord,char* value );


#endif

/*********************************************END OF FILE********************************************/

上述代碼主要的功能實現在於BC26的IRQHandler函數,這個函數在主程序中是每300ms執行一次,爲什麼是300ms?這個數據手冊給出了定義:

可以看到幾乎每條AT指令的響應時間最大都是300ms,所以這裏索性就300ms。

然後再來看看主程序部分:

#include "fy_includes.h"
#include "hmac_sha1.h"

//實驗17
//BC26-MQTT-ALIYUN
//技術交流羣:733945348
//作者:Urien @MARS
//日期:2020/1/3



u8 u2_rxbuf[256];
u16 u2_rxlen=0;
u8 u2_rxok=0;
u16 cnt300ms=0;
u16 cnt5s=0;
int main(void)
{

	float tempvalue;
	char tempstr[20];
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//設置系統中斷優先級分組4	
	SysClock_Configuration(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);	 //開啓AFIO時鐘
    GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);	//禁止JTAG保留SWD
	
    Systick_Configuration();
	Led_Configuration();
	Usart1_Configuration(115200);
	Usart2_Configuration(115200);
	
	printf("this is aliyun test!\n\n");

	BC26_Configuration(u2_rxbuf,&u2_rxlen);

	while(1){
		if(cnt300ms>=300 || u2_rxok){
			Led_Tog();
			if(u2_rxok){
				Usart1_SendBuf(u2_rxbuf,u2_rxlen);
			}
			cnt300ms = 0;
			u2_rxok = 0;
			
			BC26_IRQHandler();
			u2_rxlen=0;
		}

		Led_Tog();
		if(cnt5s == 5000){
			srand(SysTick->VAL);
			tempvalue = 0.1*(rand()%1200);
			sprintf(tempstr,"%.1f",tempvalue);
			
			BC26_PubTopic(PubTopic,CurrentTemperature,tempstr);
		}
		else if(cnt5s == 10000){
			srand(SysTick->VAL);
			tempvalue = 0.1*(rand()%1000);
			sprintf(tempstr,"%.1f",tempvalue);
			
			BC26_PubTopic(PubTopic,CurrentHumidity,tempstr);
		}
		else if(cnt5s == 15000){
			cnt5s=0;
			srand(SysTick->VAL);
			tempvalue = (rand()%200);
			sprintf(tempstr,"%d",(u8)tempvalue);
			
			BC26_PubTopic(PubTopic,TPSet,tempstr);
		}
	}
}

u8 rx_buf[256];
u8 rx_len=0;
//USART1串口中斷函數
void USART1_IRQHandler(void)
{
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){
    
		rx_buf[rx_len++] = USART1->DR;
    }
	if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET){
	
		u8 clear = USART1->DR;//讀DR和SR有效清除中斷
		clear = USART1->SR;
		u2_rxok=1;
	}
}

//USART1串口中斷函數
void USART2_IRQHandler(void)
{
    if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
    {
		u2_rxbuf[u2_rxlen++] = USART2->DR;
		u2_rxlen%=256;
    }
	if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
	{
		u8 clear = USART2->DR;//讀DR和SR有效清除中斷
		clear = USART2->SR;
		u2_rxok=1;
	}
}

主程序比較簡單,使用系統滴答的1ms時基定時中斷用來簡單計時,然後就是每300ms執行BC26的IRQHandler函數,每500ms上傳一次數據。注意這裏因爲簡單測試,上傳我只是把單一的數據點一個個分時上傳,爲了提高效率,完全可以上傳多個或者所有的數據點。

再有就是串口部分,串口部分用到了2個,串口1用來打印調試信息,串口2纔是對接BC26模組的,兩個串口都用到了串口的接收中斷和空閒中斷。空閒中斷不宜耗時太長,所以我這裏只是立了一個flag變量,放在while裏面去處理。

 

最後要說明一下,關於BC26的底層部分,如果是參考上述代碼的話建議多啃啃,難就難在傳輸機制上面,舉一個簡單的例子,TCP連接上如果AT發送了TCP連接後,模組並不是馬上或者就很快能連上服務器的,各種原因都有,網絡延遲啦,信號不好啦等等,那麼這個時候再次發送連接命令是會報錯的,即使連接成功也一樣。MQTT的登錄也一樣。再有就是BC26的復位,建議是軟件復位和硬件復位都接上。

 

By Urien 2020年1月8日 18:44:04

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章