前言
在调试之前看这个数据手册一脸懵,特别是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