全部产品
云市场

MTK2503/MTK6261 + Nucleus 移植示例

更新时间:2019-03-15 17:30:35

移植说明:本文档描述在MTK2503/MTK6261芯片 + Nucleus操作系统的硬件平台上,使用异步通信模式将SDK集成到目标平台,集成时使用MQTT Topic方式与阿里云物联网平台进行通信。

开发环境: Windows XP开发主机及MTK的USB转串口驱动, ARM RVCT3.1/Perl构建环境, FlashTool烧录工具, Catcher调试工具

配置SDK并提取所需文件

运行顶级目录下的 config.bat 脚本, 在出现的界面关闭 FEATURE_DEVICE_MODEL_ENABLED, 然后进入 MQTT Configuations 子菜单

前面已经介绍过, FEATURE_ASYNC_PROTOCOL_STACK 是专为异步TCP/IP协议栈开发的特性, 所以这里选中它, 打开

保存后退出, 运行顶级目录下的 extract.bat 脚本, 根据这种配置抽取移植所需要的源文件到 output 目录下

对接HAL接口

上图中可以看到 extract.bat 已经将需要对接的HAL接口列出并自动整理到 output/eng/wrappers/wrapper.c, 用户现在要根据系统的情况将这个文件中的空函数都进行实现

可以直接使用SDK源码目录 wrappers/os/nucleus 中的参考代码, 根据自己的实际需要做一些调整

将被抽取的文件和实现好的 wrapper.c 加入构建

拷贝 output/eng 下所有目录及文件到 Nucleus 的编译环境中, 确保编译通过, 可以将C-SDK作为一个库集成到原有系统中

参考如下源码编写应用调用C-SDK

以下是我们开发过程中经过实际验证的源文件, 您可参考编写自己的应用程序调用被集成的C-SDK

例程讲解

以下对示例的 aliMqttTask.c 运行流程做一些讲解

监听系统事件, 构造应用程序入口

监听 Nucleus 中系统事件 EVT_ID_SRV_NW_INFO_SERVICE_AVAILABILITY_CHANGED, 当GPRS连接成功, 系统会触发该事件

  1. /* 系统事件回调函数 */
  2. mmi_ret srv_nw_name_main_evt_hdlr(mmi_event_struct *event)
  3. {
  4. ...
  5. switch (event->evt_id)
  6. ...
  7. case EVT_ID_SRV_NW_INFO_SERVICE_AVAILABILITY_CHANGED:
  8. ret = srv_nw_name_handle_service_availability_changed(event);
  9. ...

把事件传递给状态机处理函数: aliyun_network_changed()

  1. static mmi_ret srv_nw_name_handle_service_availability_changed(
  2. mmi_event_struct *event)
  3. {
  4. ...
  5. #if defined(__LL_ALIYUN_SUPPORT__)
  6. /* 进入业务回调函数,处理系统事件 */
  7. aliyun_network_changed((kal_int32)evt->new_status);
  8. #endif /*__LL_MYCOMMON_SUPPORT__*/
  9. ...
  10. }

如果 aliyun_network_changed() 发现事件是表达 GPRS 连接已就绪, 则启动应用程序 ali_app_start_fun()

  1. #define ALIYUN_NETWORK_GPRS_OK 3
  2. void aliyun_network_changed(kal_int8 status)
  3. {
  4. ...
  5. /* srv_nw_info_location_info_struct info; */
  6. DEBUG_TRACE("service_changedstatus.status=%d",status);
  7. g_gprs_status = status;
  8. /* 该事件表示GPRS连接成功 */
  9. if (g_gprs_status == ALIYUN_NETWORK_GPRS_OK)
  10. {
  11. if(g_sim_init_complete == KAL_FALSE)
  12. {
  13. g_sim_init_complete = KAL_TRUE;
  14. StopTimer(ALIYUN_START_TIMER);
  15. /* 这里开始执行业务逻辑 */
  16. StartTimer(ALIYUN_START_TIMER,1000*20,ali_app_start_fun);
  17. }
  18. }
  19. ...
  20. }

同时, 我们也构造了一个专用于MQTT通信的任务: mqtt_task_main()

  1. kal_bool mqtt_create(comptask_handler_struct **handle)
  2. {
  3. static const comptask_handler_struct mqtt_handler_info =
  4. {
  5. mqtt_task_main, /* task entry function */
  6. mqtt_task_init, /* task initialization function */
  7. NULL, /* task configuration function */
  8. NULL, /* task reset handler */
  9. NULL, /* task termination handler */
  10. };
  11. *handle = (comptask_handler_struct*)&mqtt_handler_info;
  12. return KAL_TRUE;
  13. }

GPRS连接成功, 计算签名信息, 并解析签名中的服务器域名为IP

ali_app_start_fun() 首先会调用SDK提供的接口 IOT_MQTT_Sign() 获取签名信息, 其中含有服务器域名

  1. static void ali_app_start_fun(void)
  2. {
  3. uint32_t res = 0;
  4. iotx_dev_meta_info_t meta;
  5. ...
  6. res = IOT_Sign_MQTT(IOTX_CLOUD_REGION_SHANGHAI,&meta,&g_mqtt_signout);
  7. if (res == 0) {
  8. DEBUG_TRACE("signout.hostname: %s",g_mqtt_signout.hostname);
  9. DEBUG_TRACE("signout.port : %d",g_mqtt_signout.port);
  10. DEBUG_TRACE("signout.clientid: %s",g_mqtt_signout.clientid);
  11. DEBUG_TRACE("signout.username: %s",g_mqtt_signout.username);
  12. DEBUG_TRACE("signout.password: %s",g_mqtt_signout.password);

Nucleus 的系统接口 soc_gethostbyname() 把域名解析成IP地址

  1. ...
  2. ...
  3. res = soc_gethostbyname(KAL_FALSE,MOD_MQTT,g_ali_request_id,g_mqtt_signout.hostname,
  4. (kal_uint8 *)addr_buff, &addr_len,0,g_ali_nwk_account_id);

若域名成功解析, 则调用SDK提供的接口 IOT_MQTT_Construct() 发起TCP连接

  1. if (res >= SOC_SUCCESS) // success
  2. {
  3. DEBUG_TRACE("ali task socket_host_by_name SOC_SUCCESS");
  4. if(addr_len !=0 && addr_len<MAX_SOCK_ADDR_LEN)
  5. {
  6. //connect....
  7. sprintf(ipaddr,"%d.%d.%d.%d",addr_buff[0],addr_buff[1],addr_buff[2],addr_buff[3]);
  8. DEBUG_TRACE("ipaddr: %s[%d.%d.%d.%d]",ipaddr,addr_buff[0],addr_buff[1],addr_buff[2],addr_buff[3]);
  9. HAL_Free(g_mqtt_signout.hostname);
  10. g_mqtt_signout.hostname = HAL_Malloc(strlen(ipaddr) + 1);
  11. if (g_mqtt_signout.hostname == NULL) {
  12. DEBUG_TRACE("HAL_Malloc failed");
  13. return;
  14. }
  15. memset(g_mqtt_signout.hostname,0,strlen(ipaddr) + 1);
  16. memcpy(g_mqtt_signout.hostname,ipaddr,strlen(ipaddr));
  17. DEBUG_TRACE("g_mqtt_signout.hostname: %s",g_mqtt_signout.hostname);
  18. memset(&mqtt_params,0,sizeof(iotx_mqtt_param_t));
  19. mqtt_params.port = g_mqtt_signout.port;
  20. mqtt_params.host = g_mqtt_signout.hostname;
  21. mqtt_params.client_id = g_mqtt_signout.clientid;
  22. mqtt_params.username = g_mqtt_signout.username;
  23. mqtt_params.password = g_mqtt_signout.password;
  24. mqtt_params.request_timeout_ms = 2000;
  25. mqtt_params.clean_session = 0;
  26. mqtt_params.keepalive_interval_ms = 60000;
  27. mqtt_params.read_buf_size = 1024;
  28. mqtt_params.write_buf_size = 1024;
  29. mqtt_params.handle_event.h_fp = example_event_handle;
  30. mqtt_params.handle_event.pcontext = NULL;
  31. g_mqtt_handle = IOT_MQTT_Construct(&mqtt_params);
  32. if (g_mqtt_handle != NULL){
  33. g_mqtt_connect_status = 1;
  34. DEBUG_TRACE("IOT_MQTT_Construct Success");
  35. }
  36. }
  37. }

比较特别的是

  • 在异步模式下, 如果 IOT_MQTT_Construct() 返回成功, 仅表示socket连接函数调用成功, 而不代表MQTT长连接已经建立
  • 调用结果是通过 MSG_ID_APP_SOC_NOTIFY_IND 事件返回的, 由上面提到的 mqtt_task_main() 任务处理
    1. void mqtt_task_main(task_entry_struct *task_entry_ptr)
    2. {
    3. ...
    4. while(1)
    5. {
    6. receive_msg_ext_q( task_info_g[task_entry_ptr->task_indx].task_ext_qid, &current_ilm);
    7. stack_set_active_module_id(my_index, current_ilm.dest_mod_id);
    8. switch (current_ilm.msg_id)
    9. {
    10. case MSG_ID_APP_SOC_NOTIFY_IND:
    11. {
    12. ERROR_TRACE("%s,%d", __FUNCTION__,__LINE__);
    13. ali_socket_notify(current_ilm.local_para_ptr);
    14. break;
    15. }

TCP连接建立成功时, 调用SDK提供的接口 IOT_MQTT_Nwk_Event_Handler() 建立MQTT连接

TCP建连成功时, Nucleus系统会产生 SOC_CONNECT 事件, 我们编写了 ali_socket_notify() 函数包装 IOT_MQTT_Nwk_Event_Handler()

  1. void ali_socket_notify(void *msg_ptr)
  2. {
  3. ...
  4. switch (soc_notify->event_type)
  5. {
  6. ...
  7. case SOC_CONNECT:
  8. {
  9. DEBUG_TRACE("Ali SOC_CONNECT Event");
  10. ret = IOT_MQTT_Nwk_Event_Handler(g_mqtt_handle, IOTX_MQTT_SOC_CONNECTED ,&nwk_param);
  11. if (ret == SUCCESS_RETURN) {
  12. g_mqtt_connect_status = 2;
  13. DEBUG_TRACE("Aliyun connect success,start yield");
  14. example_subscribe(g_mqtt_handle);
  15. StopTimer(ALIYUN_YIELD_TIMER);
  16. StartTimer(ALIYUN_YIELD_TIMER,1000*2,ali_mqtt_yield);
  17. }
  18. }

以上的代码除了通过 IOTX_MQTT_SOC_CONNECTED 事件驱动 IOT_MQTT_Nwk_Event_Handler() 建立MQTT连接, 也订阅了Topic, 并启动了定时器 ali_mqtt_yield() 来维持心跳

启动心跳定时器, 调用SDK提供的接口 IOT_MQTT_Yield() 维持长连接

  1. void ali_mqtt_yield(void)
  2. {
  3. static int send_interval = 0;
  4. DEBUG_TRACE("ali_mqtt_yield...");
  5. IOT_MQTT_Yield(g_mqtt_handle,200);
  6. send_interval+=5;
  7. if (send_interval == 20) {
  8. send_interval = 0;
  9. example_publish(g_mqtt_handle);
  10. }
  11. StartTimer(ALIYUN_YIELD_TIMER,1000*5,ali_mqtt_yield);
  12. }

在异步模式下, IOT_MQTT_Yield() 已经变得 只发不收 了, 它只会起到维持心跳的作用

在心跳的定时器中, 调用SDK提供的接口 IOT_MQTT_Publish() 发出上行的报文消息给自己

  1. int example_publish(void *handle)
  2. {
  3. int res = 0;
  4. iotx_mqtt_topic_info_t topic_msg;
  5. char product_key[IOTX_PRODUCT_KEY_LEN] = {0};
  6. char device_name[IOTX_DEVICE_NAME_LEN] = {0};
  7. const char *fmt = "/%s/%s/get";
  8. char topic[128] = {0};
  9. char *payload = "hello,world";
  10. HAL_GetProductKey(product_key);
  11. HAL_GetDeviceName(device_name);
  12. HAL_Snprintf(topic, 128, fmt, product_key, device_name);
  13. memset(&topic_msg, 0x0, sizeof(iotx_mqtt_topic_info_t));
  14. topic_msg.qos = IOTX_MQTT_QOS0;
  15. topic_msg.retain = 0;
  16. topic_msg.dup = 0;
  17. topic_msg.payload = (void *)payload;
  18. topic_msg.payload_len = strlen(payload);
  19. res = IOT_MQTT_Publish(handle, topic, &topic_msg);
  20. if (res < 0) {
  21. DEBUG_TRACE("publish failed\n");
  22. return -1;
  23. }
  24. return 0;
  25. }

由于例程所使用的设备是特别在控制台配置过的, 它的 /${productKey}/${deviceName}/get 可订阅也可发布, 我们前面建连成功时已经订阅了它, 现在又向它发送报文, 因此可以看到

通过协议栈底层驱动, 接收被订阅Topic上的报文

这仍然是通过包装了 IOT_MQTT_Nwk_Event_Handler()ali_socket_notify() 函数来处理的

  1. void ali_socket_notify(void *msg_ptr)
  2. {
  3. ...
  4. switch (soc_notify->event_type)
  5. {
  6. case SOC_READ:
  7. {
  8. DEBUG_TRACE("Ali SOC_READ Event");
  9. IOT_MQTT_Nwk_Event_Handler(g_mqtt_handle,IOTX_MQTT_SOC_READ,&nwk_param);
  10. DEBUG_TRACE("Ali SOC_READ End");
  11. }
  12. break;

当有数据可读的时候, Nucleus的协议栈会以 SOC_READ 事件通知, 在此如果不想做其它处理, 直接用 IOT_MQTT_Nwk_Event_Handler() 交给SDK, 即可分发给当初订阅相应Topic时注册的函数处理

烧录固件

在本示例程序的开发环境中, 编译固件的环境是

打开 cmd.exe 命令行, 通过MTK提供的编译脚本启动固件的构建过程

然后将编译出来的固件用 FlashTool 工具烧录到设备上

  • 选择 config file, 在 build 目录下后缀 .cfg结尾文件
  • 选择 option 设置, 第一次烧录或有异常问题时, 勾选 Format FAT
  • 点击 download, 将设备的USB口连接电脑即开始下载

观察运行效果

打开 Catcher 调试工具, 由于 MOD_NIL 标记下的日志较少, 例程的日志打印到 MOD_NIL 这个filter上, 对其筛选可查看例程输出的日志

用串口工具操作GPRS设备连网成功, 例程则开始工作, 可在 Catcher 中观察到它建立MQTT连接, 订阅, 发布和接收到报文过程中的日志

同时若登录到IoT云端控制台(https://iot.console.aliyun.com), 也可以看到对应的设备上线, 并看到它发上来的报文, 表明接入成功