【单片机笔记】上海移远公司NB-IOT模组 BC26 使用STM32 AT命令实现连接阿里云数据上传和下载

前言

在调试之前看这个数据手册一脸懵,特别是MQTT部分还是独立的,这个和前接触到的上海合宙的模块多少有点出处。另外就是那个AT命令的传入参数也是一脸懵,后来发现BC26的模块好像把MQTT部分单独的做成了支持阿里云服务器的功能。接触过阿里云的设备对接相比都知道,阿里云要求的是一机一密或者一型一密,这个在对于简单的成本低廉的MCU来说无疑是一个很大的考验。而BC26这块还是做的非常友好的,在MQTT部分只需要传入产品对应设备下的三元组即可,无需经过哈希算法计算密钥。

先上图:

连接及上传部分:

【单片机笔记】上海移远公司NB-IOT模组 BC26 使用STM32 AT命令实现连接阿里云数据上传和下载_第1张图片

【单片机笔记】上海移远公司NB-IOT模组 BC26 使用STM32 AT命令实现连接阿里云数据上传和下载_第2张图片

数据下载部分:

【单片机笔记】上海移远公司NB-IOT模组 BC26 使用STM32 AT命令实现连接阿里云数据上传和下载_第3张图片

基本上通信也是非常稳定的,不过我这个地方信号贼差,以模块满格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?这个数据手册给出了定义:

【单片机笔记】上海移远公司NB-IOT模组 BC26 使用STM32 AT命令实现连接阿里云数据上传和下载_第4张图片

可以看到几乎每条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

 

你可能感兴趣的:(单片机,NBIOT,MQTT)