前言
在調試之前看這個數據手冊一臉懵,特別是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