MCU SDK 移植

更新时间:2023-12-08 02:45:17下载pdf

MCU SDK 是根据涂鸦 IoT 平台定义的产品功能自动生成的 MCU 代码,能够协助您快速完成 MCU 程序的开发。本文为您介绍移植 NB-IoT 的 MCU SDK 的流程以及注意事项。

SDK 概述

MCU SDK 是根据涂鸦开发平台定义的产品功能,自动生成的 MCU 代码。为了减少您使用涂鸦 NB-IoT 通用串口协议的对接成本,MCU SDK 已搭建通讯及协议解析架构。将 MCU SDK 添加至工程并配置相关信息后,既可以快速的完成 MCU 程序开发。

资源要求

涂鸦 SDK 包对 MCU 的要求如下。资源不足的用户,可自行对接串口协议,SDK 包中的函数可以作为使用参考。

  • 内存:4 KB
  • RAM:与 DP 数据长度有关,约为 100B,如果开启 OTA 功能,则需大于 260B
  • 函数嵌套级数:9 级

文件结构

文件 说明
mcu_api.c 包含可供调用的函数。
mcu_api.h mcu_api.c中的函数声明。
protocol.c 包含协议数据体的内容处理函数。您可以根据项目需求,在相应函数内添加代码,获取 NB-IoT 模组向 MCU 发送的数据。
protocol.h protocol.h 包含以下信息:MCU 要发送至 NB-IoT 模组初始化所需的参数。SDK 裁剪所定义的宏。用户可根据需要的功能打开相应的宏定义。protocol.c中的函数声明。
system.c 串口通讯协议解析的具体实现。
system.h system.h 包含以下信息:协议命令字的定义。部分全局变量的定义。system.c中的函数声明。
nbiot.h 包含 NB-IoT 相关宏定义。

移植流程

  1. 编写 MCU 基础程序,移植 SDK 文件。
  2. 根据 nbiot.h 中的说明配置产品工作模式与连云方式。
  3. 确认 protocol.h 宏定义。
  4. 移植 protocol.c 文件及函数调用。
  5. 添加 DP 上报下发函数

第一步: 编写程序和移植文件

  1. 在原项目工程中,完成 MCU 相关外设初始化,包括串口,GPIO(配置 NB-IoT 唤醒源)等。

  2. 将 MCU SDK 文件夹中的 .c.h 文件添加至相应头文件引用路径。

    MCU SDK 移植

第二步:确认 nbiot.h 中参数配置

  1. 定义产品工作模式:SIM_MODE 为产品工作模式,可选项为 psmdrxedrx。该项务必与 涂鸦 IoT 开发平台 上创建产品时的工作模式一致。
  2. 定义产品云连接方式:CONNECT_MODE 为产品云连接方式,可选项为 isptuya,该项务必与涂鸦 IoT 开发平台上创建产品时的连云方式一致。
    • isp 为先连接运营商云进而连接云端。
    • tuya 为直连云端。目前仅移动卡支持该功能。
  3. MCU 升级配置:SUPPORT_MCU_FIRM_UPDATE 为 MCU 升级或文件下载功能开关,若需要该功能可开启此宏。

第三步:确认 protocol.h 宏定义

  1. 定义 PIDPRODUCT_KEY 为产品 PID 宏定义。PID 即产品 ID,为每个产品的唯一标识,可在涂鸦 IoT 平台的 产品管理页面 获取。

    如果 PRODUCT_KEY 和产品 PID 不一致,请在 硬件开发 > 下载资料 下载最新 SDK 开发包后重试。

    #define PRODUCT_KEY  "ny18le8hpsb3z97g"
    
  2. 定义版本号MCU_VER 为软件版本,默认为 1.0.0。若 MCU 需要 OTA 功能,需要添加新的 MCU 版本号。

    #define MCU_VER "1.0.0"
    
  3. 配置校时功能SUPPORT_MCU_RTC_CHECK 为校时功能开关,若产品对象需要严格的时间准确性,可以为 NB-IoT 模组校时以保证 NB-IoT 模组 RTC 的准确。如需要可以开启该宏,并且在 Protocol.c 文件内 mcu_write_rtctime 实现代码。

第四步:收发缓存(可选)

  • 串口接收缓存:大小受到串口数据处理被调用的频率影响。如果 MCU 对串口数据的处理较快,串口接收缓存大小可适当减小。

  • 串口发送缓存:大小要大于数据最长的 DP 数据长度。

  • 串口数据处理缓存:大小需要大于数据最长的 DP 数据长度,还要根据是否需要 OTA 功能来调整大小,需要大于最大数据量的大小。

    /******************************************************************************
                            3:定义收发缓存:
                        如当前使用 MCU 的 RAM 不够,可修改为 24
    ******************************************************************************/
    #ifndef SUPPORT_MCU_FIRM_UPDATE
    #define NBIOT_UART_QUEUE_LMT             16              //数据接收队列大小,如 MCU 的 RAM 不够,可缩小
    #define NBIOT_UART_RECV_BUF_LMT          24              //根据用户 DP 数据大小量定,必须大于 24
    #else
    #define NBIOT_UART_QUEUE_LMT             128              //数据接收队列大小,如 MCU 的 RAM 不够,可缩小
    #define NBIOT_UART_RECV_BUF_LMT          300              //根据用户 DP 数据大小量定,必须大于 24
    #endif
    #define NBIOT_UART_SEND_BUF_LMT          64               //根据用户 DP 数据大小量定,必须大于 24
    /******************************************************************************
    

第五步:移植 protocol.c 文件及函数调用

  1. nbiot.h 文件保存至 NB-IoT 相关文件的文件夹中,例如 main.c 文件夹。

  2. 在 MCU 串口及其他外设初始化后调用 mcu_api.c 文件中的 nbiot_protocol_init()函数。

  3. 将 MCU 串口单字节发送函数填入 protocol.c 文件中 uart_transmit_output 函数内,并删除 #error。示例如下:

    void uart_transmit_output(unsigned char value)
    {
      extern void Uart_PutChar(unsigned char value);
      Uart_PutChar(value);	                                //串口发送函数
    }
    
  4. 在串口接收中断服务函数里面调用 mcu_api.c 文件内的 uart_receive_input 函数,并将接收到的字符作为参数传入。示例如下:

    void USART3_IRQHandler(void)
    {
        unsigned char Res=0;
    
        if((USART3->SR&UART_FLAG_RXNE) != 0)
        {
            Res=USART3->DR;
            uart_receive_input(Res);
        }
    }
    
  5. 单片机进入 while(1) 循环后调用 mcu_api.c 文件内的 nbiot_uart_service() 函数。main.c 中示例代码结构如下:

    #include "nbiot.h"
    ...
    void main(void)
    {
        nbiot_protocol_init();
        ...
        while(1)
        {
            nbiot_uart_service();
            ...
        }
        ...
    }
    

    MCU 必须在 while(1) 中直接调用 mcu_api.c 内的 nbiot_uart_service() 函数。程序正常初始化完成后,建议不进行关中断,如必须关中断,关中断时间必须短。关中断会引起串口数据包丢失,请勿在中断内调用上报函数。

注意事项

NB-IoT 睡眠锁逻辑

NB-IoT 固件在空闲状态(没有数据交互)的情况下,会主动进入休眠以节省功耗。

如果 MCU 需要处理较多数据或者长时间不需要模组进入休眠,则可以发送睡眠锁命令,开启睡眠锁定。待数据处理完成,需要 NB-IoT 睡眠时再发送命令解锁进入睡眠。

如果需要 NB-IoT 模组整个产品工作期间不进入休眠,需在收到 NB-IoT 模组查询产品信息后,立即发送睡眠锁命令。

NB-IoT 离散功能

NB-IoT 具备连接多、成本低、功耗低的特点,但是其窄带宽的特性也已经限制了可以并发的数量。

  • 针对安装密集,数量较多的产品,请酌情使用离散功能,使设备启动与数据交互离散开来,优化数据拥堵问题。

  • MCU 端也要针对离散后的效果进行实际验证以达到最稳定可靠的效果。

  • 离散功能仅针对上电启动有效,数据上报的离散则需要 MCU 单独维护。如果产品需要频繁给 NB-IoT 断电,请不要开启离散功能。

/*****************************************************************************
函数名称 : mcu_set_discrete_info
功能描述 : 设置 NB-IoT 模组首次上电注网离散信息
输入参数 : en: 0 关闭离散功能,1 使能离散功能
           duration:随机离散时间最大值,范围为 120-1800,例如当设置值为 150 时,表示随机范围为 0-150
           step:离散范围步进,在相同 duration 情况下,step 越小,离散的越散

返回参数 : 无
使用说明 :
*****************************************************************************/
void mcu_set_discrete_info(unsigned char en, unsigned int duration, DISCRETE_STEP step)
{
    unsigned int  du = 0;
    unsigned int  length = 0;
    unsigned char uart_buff[128] = {0};

    du = duration;
    if (duration < 120){
        du = 120;
    }
    if (duration > 1800){
        du = 1800;
    }

    length = snprintf(uart_buff, sizeof(uart_buff), "{\"enable\":%d,\"duration\":%d,\"step\":%d}",\
                                                   (unsigned int)en, du, (unsigned int)step);

    length = set_nbiot_uart_buffer(0, uart_buff, length);
    nbiot_uart_write_frame(SET_DISCRETE_INFO_CMD, length);
}

调用 DP 上报和下发函数

详情请参考 Wi-Fi 通用对接中的 MCU SDK 移植中 DP 数据上报与下发 章节。

OTA 交互或文件下载

MCU 与 NB-IoT 模组在进行 OTA 数据交互时,数据包交互的数据量比较多,会出现一些异常情况。本小节介绍 NB-IoT 模组的处理逻辑。

文件下载的逻辑和 OTA 实质上一样。可以将本章节中的 OTA 替换成 文件下载,作为文件下载逻辑的介绍。

OTA 过程
说明
OTA 启动 NB-IoT 模组在检测到有 MCU OTA 时,会发送启动升级包。
  • 如果 5 秒内没有收到 MCU 的回复,会进行重发。若重发 3 次之后 MCU 依旧没有回复,就认为 OTA 失败。
  • 如果固件包总字节数超出了 MCU 的处理范围,MCU 可以不做回复,让 NB-IoT 模组自行退出 OTA。此时需检查在涂鸦 IoT 平台上传的文件是否正确。
OTA 数据传输 NB-IoT 模组在发送一包 OTA 数据之后,如果 5 秒内没有收到 MCU 的回复,会进行重发。若重发 3 次之后 MCU 依旧没有回复,就认为 OTA 失败。
  • 如果 MCU 检测到 OTA 数据有误,可以不做回复,让 NB-IoT 模组进行重发。此时需要注意包偏移是否正确,防止重发或漏发。
  • 如果 MCU 对 OTA 数据的处理速度太慢,会导致模组频繁重发数据,此时需要注意包偏移是否正确,防止重发或漏发。如果出现这种情况,建议调小固件包传输的大小。
OTA 传输完成 支持以下两种方式判断 OTA 传输是否完成。
  • 帧长度为 0x0004,即 OTA 数据包长度为 0。
  • 包偏移等于 NB-IoT 模组发送升级启动帧时的固件包总长度。
建议同时使用以上两种方式检测 OTA 传输情况。

在 MCU OTA 启动的时候以及固件更新的时候需要重点处理重复下载的情况。

在 MCU OTA 启动的时候为避免重复下载需进行固件 CRC(Cyclic Redundancy Check)校验比对。

case MCU_OTA_START_CMD:
...
//回复模组
    #error "请完成传输包大小/断点地址的回复,完成后请删除该行"
    /*
      1.计算当前正在运行 app 固件的 crc 值 crc32_app
      2.与升级开始时给出的固件 firm_crc32 比较
      if (firm_crc32 == crc32_app) {
        //固件已存在,无需再次下载,直接回复文件长度即可
        mcu_firm_update_start_ack(unsigned char unit_len, firm_length);
      }
      else {
        //可根据实际情况回复断点偏移地址(从头下载为 0,断点下载则传入偏移地址)
        mcu_firm_update_start_ack(unsigned char unit_len, unsigned long received_len);
      }
    */
...
break;

在 MCU 固件下载完成进入固件更新的时候如果存在重复进入的情况则需要过滤处理,可通过对比固件 CRC 来确定。

/*****************************************************************************
函数名称 : mcu_firm_update_handle
功能描述 : MCU 进入固件升级模式
输入参数 : value:固件缓冲区
           position:当前数据包在于固件位置
           length:当前固件包长度(固件包长度为 0 时,表示固件包发送完成)
返回参数 : 无
使用说明 : MCU 需要自行实现该功能
*****************************************************************************/
unsigned char mcu_firm_update_handle(const unsigned char value[],unsigned long position,unsigned short length)
{
  #error "请自行完成 MCU 固件升级代码,完成后请删除该行"
  if(length == 0)
  {
    //固件数据发送完成

    #error "请按照以下步骤完成 crc32 校验,结果返回 0 或者 1,完成后请删除该行"
    /*
      1.计算已经下载完成固件的 crc 值 crc32_dl
      2.计算当前正在运行 app 固件的 crc 值 crc32_app
      3.与升级开始时给出的固件 crc32 比较
      if (crc32 == crc32_dl) {        //当前固件下载成功,即将跳转运行最新固件
        mcu_firm_update_data_ack(1, 0);        //crc 校准成功
        //按照应用需求执行程序跳转
      }
      else if (crc32 == crc32_app){        //当前运行的固件为最新固件,仅需回复响应
        mcu_firm_update_data_ack(1, 0);        //crc 校准成功
      }
      else {
        mcu_firm_update_data_ack(1, 1);        //crc 校准失败
      }
    */

  }
  else
  {
    //mcu_firm_update_data_ack(0, 0);

    //固件数据处理

  }

  return SUCCESS;
}