MCU SDK 移植

更新时间:2024-07-29 06:50:27下载pdf

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

SDK 概述

MCU SDK 是根据涂鸦 IoT 开发平台定义的产品功能,自动生成的 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. MCU 进入 while(1) 循环后,调用 mcu_api.c 文件内的 cellular_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;
}