NB模组在广域物联网领域发挥越来越重要的作用,有些NB模组有一个特性:OPENCPU。这个是降低成本和减少布板空间的利器。这里就以移远通信的NB模组——BC26模组来开发OPENCPU应用。
移远的OPENCPU开发环境是GCC加APP烧录工具;虽然有优势,但是开发上做了限制,有些库和功能不能使用。
这里贴一个工程的早期demo的Git,完成了MQTT的连接上传接收等的URC的处理,一些外设的测试:
https://github.com/JetLinWork/BC26_MQTT_OPENCPU
这边做了OPENCPU的应用开发之后的一点经验和看法。
硬件上:虽然单模组的布板空间有所缩小,但没有想象那么多,因为模组电平是1.8V的域,基本的IO输出和外设交互都需要电平转换,这里占用空间就不少(分立或集成)。电源的驱动上确实低了2G模组很多,现在一个USB就能带起来。
软件上:在windows上开发,使用的是移远提供的SDK和Flash烧录工具。按照开发手册的步骤来就可以进行开发。虽然说是使用FreeRTOS但是开放的OS的API很少,只有信号量和TASK之间的message传输。在编译上限制了 像
详细的开发的环境介绍和开发步骤参见Git里面的/doc/下的手册 Quectel_BC26-OpenCPU_User_Guide_V10.pdf 开发手册里面有的内容我这里就不再赘述了。
在SDK里面也提供了很多外设驱动使用的例程。
URC ( Unsolicited Result Code )就是不同于AT指令发了一个就会马上回复一个OK/ERR这种,而是一些类似中断不定时出现不知道对应那条AT发送指令的内容,例如MQTT接收到的数据就会以URC数据的方式发送到URC的处理任务。暂时有的URC的处理有TCP/LWM2M/ONENET等有限的API,不过开放了源码,你只需要按照这些例程去开发自己想要的URC接收处理函数就可以了。
关于URC的开发的内容手册里面基本没有提及,但是这个对于功能的开发来说是至关重要的,因为官方提供的功能不可能尽善尽美,总有需要自己去开发的地方。
这里就以开发MQTT组件的URC为例,URC的接收处理源文件为 ril_urc.c。
有一个结构体数组,存储了对应URC的特征字符和对应的处理函数
//片段1
/****************************************************/
/* Definitions for AT URCs and the handler */
/****************************************************/
const static ST_URC_HDLENTRY m_AtURCHdlEntry[] = {
{"\r\n+QIURC:",OnURCHandler_QIURC_DATA},
{"\r\n+QLWDATARECV:",OnURCHandler_LwM2M_RECV_DATA},
{"\r\n+QLWOBSERVE:",OnURCHandler_LwM2M_OBSERVE},
{"\r\n+MIPLEVENT:",OnURCHandler_ONENET_EVENT},
{"\r\n+MIPLOBSERVE:",OnURCHandler_ONENET_OBSERVER},
{"\r\n+MIPLDISCOVER:",OnURCHandler_ONENET_DISCOVER},
{"\r\n+MIPLWRITE:",OnURCHandler_ONENET_WRITE},
{"\r\n+MIPLREAD:",OnURCHandler_ONENET_READ},
{"\r\n+MIPLEXECUTE:",OnURCHandler_ONENET_EXECUTE},
{"\r\n+QIND: \"FOTA\"",OnURCHandler_DFOTA_Hander},
{"\r\n+QMTRECV:",OnURCHandler_QMTRECV_Hander},
{"\r\n+QMTOPEN:",OnURCHandler_QMTOPEN_Hander},
{"\r\n+QMTCONN:",OnURCHandler_QMTCONNECT_Hander},
{"\r\n+QMTSUB:",OnURCHandler_QMTSUB_Hander},
{"\r\n+QMTSTAT:",OnURCHandler_QMTSTAT_Hander},
};
//片段2
/*****************************************************************
* Function: OnURCHandler
*
* Description:
* This function is the entrance for Unsolicited Result Code (URC) Handler.
*
* Parameters:
* strURC:
* [IN] a URC string terminated by '\0'.
*
* reserved:
* reserved, can be NULL.
* Return:
* The function returns "ptrUrc".
*****************************************************************/
void OnURCHandler(const char* strURC, void* reserved)
{
s32 i;
if (NULL == strURC)
{
return;
}
// For system URCs
for (i = 0; i < NUM_ELEMS(m_SysURCHdlEntry); i++)
{
if (Ql_strstr(strURC, m_SysURCHdlEntry[i].keyword))
{
m_SysURCHdlEntry[i].handler(strURC, reserved);
return;
}
}
// For AT URCs
for (i = 0; i < NUM_ELEMS(m_AtURCHdlEntry); i++)
{
if (Ql_strstr(strURC, m_AtURCHdlEntry[i].keyword)) //这里处理接收到的指定URC
{
m_AtURCHdlEntry[i].handler(strURC, reserved);
return;
}
}
// For undefined URCs
OnURCHandler_Undefined(strURC, reserved);
}
例如接收到的URC字符串包含"\r\n+QIURC:"字段,就执行OnURCHandler_QIURC_DATA(const char* strURC, void* reserved);函数处理对应URC;
static void OnURCHandler_QIURC_DATA(const char* strURC, void* reserved)
{
/*----------------------------------------------------------------*/
/* Local Variables */
/*----------------------------------------------------------------*/
u8* p1 = NULL;
s32 ret;
u8 strTmp[10];
u32 recv_length = *(char*)reserved;
p1 = Ql_strstr(strURC, "+QIURC:");
p1 += Ql_strlen("+QIURC: ");
recv_length -= (Ql_strlen("+QIURC: ") + 2); // two means head '\r\n'
char* param_buffer = (u8*)Ql_MEM_Alloc(SOCKET_RECV_BUFFER_LENGTH);
char* param_list[20];
/*----------------------------------------------------------------*/
/* Code Body */
/*----------------------------------------------------------------*/
if (p1)
{
extern bool recv_data_format;
u32 param_num = open_socket_push_json_param_parse_cmd(p1, recv_length, param_buffer, param_list, 20);
char* prefix = param_list[0]; // recv
Ql_memset(strTmp, 0x0, sizeof(strTmp));
ret = QSDK_Get_Str(p1,strTmp,0);
if(Ql_memcmp(strTmp,"\"recv\"",Ql_strlen("\"recv\"")) == 0)
{
socket_recv_param.connectID = Ql_atoi(param_list[1]);
socket_recv_param.recv_length = Ql_atoi(param_list[2]);
if ( param_num == 4)
{
char* recv_buffer = param_list[3];
Ql_memset(socket_recv_param.recv_buffer, 0x0, SOCKET_RECV_BUFFER_LENGTH);
if ( recv_data_format == 0 ) //text
{
Ql_memcpy(socket_recv_param.recv_buffer, recv_buffer, socket_recv_param.recv_length);
}
else if ( recv_data_format == 1 ) //hex
{
Ql_memcpy(socket_recv_param.recv_buffer, recv_buffer, socket_recv_param.recv_length*2);
}
socket_recv_param.access_mode = SOCKET_ACCESS_MODE_DIRECT;
}
else
{
socket_recv_param.access_mode = SOCKET_ACCESS_MODE_BUFFER;
}
Ql_OS_SendMessage(URC_RCV_TASK_ID, MSG_ID_URC_INDICATION, URC_SOCKET_RECV_DATA, &socket_recv_param);
}
else if(Ql_memcmp(strTmp,"\"closed\"",Ql_strlen("\"closed\"")) == 0)
{
Ql_memset(strTmp, 0x0, sizeof(strTmp));
QSDK_Get_Str(p1,strTmp,1);
socket_recv_param.connectID = Ql_atoi(strTmp);
Ql_OS_SendMessage(URC_RCV_TASK_ID, MSG_ID_URC_INDICATION, URC_SOCKET_CLOSE,socket_recv_param.connectID);
}
}
if ( param_buffer != NULL )
{
Ql_MEM_Free(param_buffer);
param_buffer = NULL;
}
}
自己写URC其实也就是依样画葫芦,将所需要的接收字段的特征字符和处理函数写好即可,可以参见给出的Git工程。
其实OPENCPU的执行AT命令也是极为重要的功能,一下是该函数的片段,调用AT指令其实除了返回的结果有些URC的处理也是其一部分。这个函数相当于是涵盖所有AT手册包含的命令。
/******************************************************************************
* Function: Ql_RIL_SendATCmd
*
* Description:
* This function implements sending AT command with the result
* being returned synchronously.The response of the AT command
* will be reported to the callback function,you can get the results
* you want in it.
*
* Parameters:
* atCmd:
* [in]AT command string.
* atCmdLen:
* [in]The length of AT command string.
* atRsp_callBack:
* [in]Callback function for handle the response of the AT command.
* userData:
* [out]Used to transfer the customer's parameter.
* timeOut:
* [in]Timeout for the AT command, unit in ms. If set it to 0,
* RIL uses the default timeout time (3min).
*
* Return:
* RIL_AT_SUCCESS,succeed in executing AT command, and the response is OK.
* RIL_AT_FAILED, fail to execute AT command, or the response is ERROR.
* you may call Ql_RIL_AT_GetErrCode() to get the
* specific error code.
* RIL_AT_TIMEOUT,send AT timeout.
* RIL_AT_BUSY, sending AT.
* RIL_AT_INVALID_PARAM, invalid input parameter.
* RIL_AT_UNINITIALIZED, RIL is not ready, need to wait for MSG_ID_RIL_READY
* and then call Ql_RIL_Initialize to initialize RIL.
******************************************************************************/
typedef s32 (*Callback_ATResponse)(char* line, u32 len, void* userData);
extern s32 Ql_RIL_SendATCmd(char* atCmd, u32 atCmdLen, Callback_ATResponse atRsp_callBack, void* userData, u32 timeOut);