1. Home
  2. 博客

    SMP协议分析和解读

SMP协议分析和解读 2024-01-30 William Wei, Nordic Semiconductor

nRF Connect SDK的OTA,默认都是使用MCUboot(或者带B0功能),即一个开源的第三方bootloader程序;在这里我们重新理一下nRF Connect SDK OTA的整个流程

nRF Connect SDK的OTA,默认都是使用MCUboot(或者带B0功能),即一个开源的第三方bootloader程序;在这里我们重新理一下nRF Connect SDK OTA的整个流程:

  1. 签名升级的image,注:app_update.bin已经是签过名的image了
  2. 上传image,即把app_update.bin传送到目标设备
  3. 列出image以获得image的hash值
  4. 测试image,即写magic字段,以让MCUboot进入DFU模式复位设备,以重新进入MCUboot,从而MCUboot进入DFU模式,并执行相应的swap操作,并完成两个slot image之间的交换或者拷贝动作
  5. Confirm image,即新image启动成功后,对其image_ok字段进行置1操作

按照前面所述,为了实现OTA的功能,需要DFU协议来将新的image传输到你的第二分区里面即secondary slot,nRF Connect SDK里面最重要的DFU协议就是SMP——Simple Management Protocol,此协议用来管理整个升级流程,包括设备的分区状态,设备的复位,设备MCUboot交换文件的时候是test还是confirm等。

SMP协议是应用层协议,与链路层不是一个概念,或者说应用层协议是建立在链路层的基础上的,所以物理通道上,最后我们可以使用多种方式,USB, UART,BLE,或者TCP UDP都是可以的。

本文重点讲解的是SMP协议,以及它的数据格式,后面对升级过程中对每条指令进行讲解。在讲解SMP数据格式之前,我们先解说一下CBOR数据格式,因为SMP的payload里面的所有数据都必须使用这个数据格式。

一、CBOR格式和数据解读

CBOR是一种数据格式,其设计目标包括极小的代码大小、相当小的消息大小以及无需版本协商的可扩展性的可能性。该表示必须能够明确地编码互联网标准中使用的最常见的数据格式。

  • 编码器或解码器的代码必须能够紧凑,以便支持内存、处理器能力和指令集非常有限的系统
  • 数据必须能够在没有模式描述的情况下被解码
  • 序列化必须相当紧凑
  • 该格式必须适用于受限节点和大容量应用程序
  • 该格式必须支持所有 JSON 数据类型以便与 JSON 相互转换
  • 格式必须是可扩展的

单个 CBOR ,即一个数据项的结构可以包含零个、一个或多个嵌套数据项,在基本(未扩展)通用数据模型中,数据项是以下之一:

  • 0-正数
  • 1-负数
  • 2-字节串(byte string)
  • 3-UTF-8字符串(text string)
  • 4-数组
  • 5-map(又称字典)
  • 6-tag(这个用得少)
  • 7-浮点数或者特殊类型,其中特殊类型将short count 20–23定义为 false, true, null和undefined

CBOR包含数据格式众多,在nRF Connect SDK里面使用最多的是下面几种格式:

  • 整数
  • 文本字符串
  • 数组
  • 和字典

每个编码数据项的初始字节包含有关主要类型的信息(高位 3 位,用来表示数据项)和附加信息(低位 5 位表示数据值),少数编码例外:

  • 小于 24:参数的值是附加信息的值。
  • 24、25、26 或 27:参数的值按网络字节顺序分别保存在以下 1、2、4 或 8 个字节中。对于主类型 7 和附加信息值 25、26、27,这些字节不用作整数参数,而是用作浮点值。
  • 28、29、30:这些值被保留以供将来添加到 CBOR 格式中。在当前版本的 CBOR 中,编码项的格式不正确。
  • 31:没有导出任何参数值。如果主要类型为 0、1 或 6,则编码项的格式不正确。对于主要类型2至5,该项的长度是不定长的,对于主要类型7,该字节根本不构成数据项,而是终止一个不定长项。

CBOR 解码器实现可以基于包含初始字节的所有 256 个定义值的跳转表,我们经常使用的表格如下:

Byte Value
0x00….0x17 无符号整数 0x00….0x17 (0…...23)
0x18 无符号整数(后面是一字节 uint8_t)
0x19 无符号整数(后面是两字节 uint16_t)
0x1a 无符号整数(后面是四字节 uint32_t)
0x1b 无符号整数(后面是八字节 uint64_t)
0x20….0x37 负整数 -1-0x00….1-0x17 (-1….24)
0x38 负整数-1-n(后面是 n 的一字节 uint8_t)
0x39 负整数 -1-n (后面是 n 的两字节 uint16_t)
0x3a 负整数-1-n(后面是 n 的四字节 uint32_t)
0x3b 负整数 -1-n (后面是 n 的八字节 uint64_t)
0x40….0x57 字节字符串(0x00….0x17 字节跟随)
0x58 字节字符串(n 为一字节 uint8_t,然后是 n 个字节)
0x59 字节字符串(n 为两字节 uint16_t,然后是 n 个字节)
0x5a 字节字符串(四字节 uint32_t 表示 n,然后是 n 个字节)
0x5b 字节字符串(n 为八字节 uint64_t,然后是 n 个字节)
0x5f 字节字符串,字节字符串跟随,以 “break” 终止
0x60….0x77 UTF-8 字符串(0x00….0x17 字节跟随)
0x78 UTF-8字符串(n为一字节uint8_t,然后是n个字节)
0x79 UTF-8字符串(n为两字节uint16_t,然后是n个字节)
0x7a UTF-8字符串(四字节uint32_t表示n,然后是n个字节)
0x7b UTF-8字符串(n为八字节uint64_t,然后是n个字节)
0x7f UTF-8 字符串,UTF-8 字符串跟随,以 “break” 结尾
0x80….0x97 数组(0x00….0x17 数据项跟随)
0x98 数组(n为一字节uint8_t,然后是n个数据项)
0x99 数组(n为两字节uint16_t,然后是n个数据项)
0x9a 数组(n为四字节uint32_t,然后是n个数据项)
0x9b 数组(n为八字节uint64_t,然后是n个数据项)
0x9f 数组,数据项跟随,以 “break” 终止
0xa0….0xb7 映射(0x00….0x17 对数据项跟随)
0xb8 map(n为一字节uint8_t,然后是n对数据项)
0xb9 map(n为两字节uint16_t,然后是n对数据项)
0xba map(n为四字节uint32_t,然后是n对数据项)
0xbb map(n为八字节uint64_t,然后是n对数据项)
0xbf 映射,后面是成对的数据项,以 “break” 终止
0xf4 错误的
0xf5 真的
0xf6 无效的
0xf7 不明确的

示例:

Value Encoding
0 0x00
1 0x01
10 0x0a
23 0x17
24 0x1818
100 0x1864
1000 0x1903e8
1000000 0x1a000f4240
-1 0x20
-10 0x29
-100 0x3863
-1000 0x3903e7
[] 0x80
[1,2,3] 0x83010203
[1,[2,3],[4,5]] 0x8301820203820405
{“a”: 1, “b”: [2,3]} 0xa26161016162820203
[“a”,{“b”: “c”}] 0x826161a161626163
h'01020304' 0x4401020304
1(1363896240) 0xc11a514b67b0
错误的 0xf4
真的 0xf5
无效的 0xf6
[_] 0x9fff
“z” 0x617a
“aa” 0x626161

用定长和不定长数组来表示[1, [2, 3], [4, 5]]:

Value Representation
[1,[2,3],[4,5]] 0x8301820203820405
[1,[2,3],[4,5]] 0x9f018202039f0405ffff
[1,[2,3],[4,5]] 0x9f01820203820405ff
[1,[2,3],[4,5]] 0x83018202039f0405ff
[1,[2,3],[4,5]] 0x83019f0203ff820405

用定长和不定长数组来表示

二、SMP的数据格式如下

SMP的数据格式

注意: 
  原生SMP specification是支持Big-endian 和Little-endian,
  但实际上mcumgr-library已经固定使用Big-endian模式。

2.1 定义里面各数据域的解释如下:

Data Definition
Res 保留,默认是 0. 可能将来给SMP的版本信息使用
OP Operation code
Flags 标志位保留,默认使用 0
Data Length Data field数据块的长度
Group ID Management Group ID’s
Sequence Num frame sequence number应该每发送一包数值增加一,而且应答包的 Sequence Num 应该跟发送包的包号码相对应
Command ID 和Group ID联合使用
Data 可选项,如果Data Length为0的话,这块数据请忽略,表明没有数据块需要传输,只是一个控制指令

2.1.1 Operation Code 对应的定义如下:

  • 0:read request
  • 1:read response
  • 2:write request
  • 3:write response

每一条request都有一条response指令作应:

  • read response就是read request的应答;
  • write response就是write request的应答。

21.2 跟 nRF Connect SDK 相关的 Management Group ID, 以及对应Group下面对应的Command ID如下:

  • 0:Default/OS Management Group
    • 0.Echo
    • 4.System reset
    • 5.MCUMGR parameter
  • 1:Application/software image management group
    • 0.State of images
    • 1.Image upload

2.2 我们再列出 nRF Connect SDK dfu使用的指令

前言里 DFU 流程对应的命令如下:

1. mcumgr add myCOM type=“serial“ connstring=”dev=COM13,baud=115200,mtu=256(UART升级和USB升级)
2. mcumgr parameter request
3. mcumgr image upload app_update.bin
4. mcumgr image list
5. mcumgr image test <hash of slot-1 images>
6. mcumgr reset
7. mcumgr image confirm.

2.3 完整解读SMP指令

2.3.1 mcumgr parameter

request

Type Value Command
Operation Code 0 read request
Group ID 0 OS Management Group
Command id 6 MCUMGR parameter

示例数据为:00 00 00 01 00 FF 06 A0。

对应image header的8个字节就是 00 00 00 01 00 FF 06, A0表示一个空的字典。

response

Type Value Command
Operation Code 1 read response
Group ID 0 OS Management Group
Command id 6 MCUMGR parameter

示例数据为:

01 00 00 19 00 00 FF 06 BF 68 62 75 66 5F 73 69 7A 65 19 09 AB 69 62 75 66 5F 63 6F 75 6E 74 04 FF。

对应image header的8个字节为:01 00 00 19 00 00 FF 06

Payload对应为:

BF 68 62 75 66 5F 73 69 7A 65 19 09 AB 69 62 75 66 5F 63 6F 75 6E 74 04 FF。

我们解析一下这块数据对应的数据格式:

BF                                    --------map 
  68                                  ------数组,8个字节 
      62 75 66 5F 73 69 7A 65         ------“buf_size” 
  19                                  ------整数值,用2字节表示 
      09 AB                           ------2475 
  69                                  ------数组,9个字节 
      62 75 66 5F 63 6F 75 6E 74      ------“buf_count” 
  04                                  ------整数,数值就是4
FF                                    ------结束符 

那么我们如何理解我们这个数据的意思呢,我们再看mcumgr parameter response的数据格式:

{
  (str)"buf_size"   :(uint) 
  (str)"buf_count"  :(uint) 
  (opt,str)"rc"     :(int)
}

​​​这里我们就可以理解了,这条指令就是看设备里面配置的参数,我们的buf_size 设置的是多大,配置了多少个这么大的buf;对应我们再从机代码里面设置的参数是什么呢?就是这两条参数:

CONFIG_MCUMGR_BUF_SIZE=2475
CONFIG_MCUMGR_BUF_COUNT=4

2.3.2 mcumgr image upload

request

Type Value Command
Operation Code 2 write request
Group ID 1 Application/software image management group
Command id 1 Upload images

数据为:02 00 09 A0 00 01 01 01 BF 64 64 61 74 61 59 09 80 ………payload (2432 bytes)…….63 6F 66 66 19 1C A0 FF。

对应image header的8个字节为:02 00 09 A0 00 01 01 01,这是第一包upload指令,sequence number为1。

Payload为:BF 64 64 61 74 61 59 09 80 ………payload (2432 bytes)…….63 6F 66 66 19 1C A0 FF。

我们解析一下这块数据对应的数据格式:

BF                    ------字典的开始 
  64                  ------字节string数组,长度为4 
      64 61 74 61     ------“data” 字段 
  59                  ------长度为2字节字符串 
      09 80           ------这一包数据长度为 “2432” 
  63                  ------字节string数组,长度为3 
      6F 66 66        ------“off” 字段 
  19                  ------整数值,2字节表示 
      1C A0           ------这端数据偏移地址为1CA0 
FF                    ------结束符 

那么我们再来看一下这条写指令对应的CBOR的数据格式:

{
  {
    (str,opt)"image"   :(uint)
    (str,opt)"len"     :(uint) 
    (str)"off"         :(uint)
    (str,opt)"sha"     :(str)
    (str,opt)"data"    :(byte str)
    (str,opt)"upgrade" :(bool)
  }
}

那么这条指令对应的意思就是向设备发送一条长度为2432字节的image数据,它的偏移地址为1CA0;如此循环往复,第二包在第一包的基础上增加偏移量,一直传输到最后一包;对于2432超过MTU的大小的话,那就需要对2432字节再次分包发送,并等待设备的image upload response.其中,sequence number会递增加1。

response

Type Value Command
Operation Code 3 write response
Group ID 1 Application/software image management group
Command id 1 Upload images

数据为:03 00 00 0D 00 01 03 01 BF 62 72 63 00 63 6F 66 66 19 1C A0 FF。

对应image header的8个字节就是 03 00 00 0D 00 01 03 01,当中0x0D表示数据长度为13字节。

对应payload为:BF 62 72 63 00 63 6F 66 66 19 1C A0 FF。

我们解析一下这块数据对应的数据格式:

BF                  ------字典的开始 
  62                ------字节string数组,长度为3 
      72 63         ------“rc” 字段 
  00                ------整数,值为0 
  63                ------字节string数组,长度为3 
      6F 66 66      ------“off” 字段 
  19  
      1C A0         ------这端数据偏移地址为1CA0 
FF                  ------结束符

我们再来看一下这条写应答对应的CBOR的数据格式:

{
  (str,opt)"off"  :(uint) 
  (str)"rc"       :(int)
  (str,opt)"rsn"  :(str)
}

这条应答指令的意思就是,刚才那条写image命令没有出错(此处对应的rc为0),然后它的偏移地址为1CA0。

2.3.3 mcumgr image list

request

Type Value Command
Operation Code 0 read request
Group ID 1 Application/software image management group
Command id 0 Statics of images

数据为:00 00 00 02 00 01 00 00 BF FF。

对应image header的8个字节就是 00 00 00 02 00 01 00 00,数据BF FF表示一个空的字典。

response

Type Value Command
Operation Code 1 read request
Group ID 1 Application/software image management group
Command id 0 Statics of images

数据为:

01 00 00 86 00 01 00 00 BF 66 69 6D 61 67 65 73 9F BF 64 73 6C 6F 74 00 67 76 65 72 73 69 6F 6E 65 30 2E 30 2E 30 64 68 61 73 68 58 20 68 3D BA 00 D2 7C 7A D4 98 A7 D5 BA 55 55 BB B4 E8 CA 9F EA 91 DD CC FC A2 F8 DD 13 40 A0 05 79 68 62 6F 6F 74 61 62 6C 65 F5 67 70 65 6E 64 69 6E 67 F4 69 63 6F 6E 66 69 72 6D 65 64 F5 66 61 63 74 69 76 65 F5 69 70 65 72 6D 61 6E 65 6E 74 F4 FF FF 6B 73 70 6C 69 74 53 74 61 74 75 73 00 FF。

对应image header的8个字节就是 01 00 00 86 00 01 00 00,86表示整段数据长度为0x86字节。

Payload为:

BF 66 69 6D 61 67 65 73 9F BF 64 73 6C 6F 74 00 67 76 65 72 73 69 6F 6E 65 30 2E 30 2E 30 64 68 61 73 68 58 20 68 3D BA 00 D2 7C 7A D4 98 A7 D5 BA 55 55 BB B4 E8 CA 9F EA 91 DD CC FC A2 F8 DD 13 40 A0 05 79 68 62 6F 6F 74 61 62 6C 65 F5 67 70 65 6E 64 69 6E 67 F4 69 63 6F 6E 66 69 72 6D 65 64 F5 66 61 63 74 69 76 65 F5 69 70 65 72 6D 61 6E 65 6E 74 F4 FF FF 6B 73 70 6C 69 74 53 74 61 74 75 73 00 FF。

我们解析一下这块数据对应的数据格式:

BF
  66
      69 6D 61 67 65 73                   ------“images“ 字段 
  9F
        BF
        64
            73 6C 6F 74                   ------ “slot” 字段,值为0 
            00
        67
            76 65 72 73 69 6F 6E          ------“version” 字段 
        65
            30 2E 30 2E 30                ------值为”0.0.0” 
        64
            68 61 73 68                   ------ “hash” 字段 
        58 20
            68 3D BA 00 D2 7C 7A D4 98 A7 D5 BA 55 55 BB B4 E8 CA 9F EA 91 DD CC FC A2 F8 DD 13 40 A0 05 79   ------长度为32字节的hash值 
        68
            62 6F 6F 74 61 62 6C 65       ------ “bootable” 字段,值为false 
            F5
        67
            70 65 6E 64 69 6E 67          ------ “pending” 字段,值为 false. 
            F4
        69
            63 6F 6E 66 69 72 6D 65 64    ------ “confirmed” 字段,值为 true. 
            F5
        66
            61 63 74 69 76 65             ------“active” 字段,值为 true 
            F5
        69
            70 65 72 6D 61 6E 65 6E 74    ------ “permanent” 字段,值为 false 
            F4
        FF
  FF
  6B
      73 70 6C 69 74 53 74 61 74 75 73    ------  “splitstatus”  字段,值为false 
      00
FF 

此命令对应的CBOR数据格式如下:

{
  (str)"images":[
  {
    (str,opt)"image"      :(int)
    (str)"slot"           :(int)
    (str)"version"        :(str)
    (str)"hash"           :(str)
    (str,opt)"bootable"   :(bool)
    (str,opt)"pending"    :(bool) 
    (str,opt) "confirmed" :(bool)
    (str,opt)"active"     :(bool) 
    (str,opt)"permanent"  :(bool)
  }
  ]
  (str,opt)"splitStatus"  :(int)
}

通过这条指令,可以看到1个或2个slot分区里面的image的状态是不是confirmed,是不是active等等。

2.3.4 mcumgr image test

request:

Type Value Command
Operation Code 2 read request
Group ID 1 Application/software image management group
Command id 0 Statics of images

对应的数据为:

02 00 00 32 00 01 61 00 BF 67 63 6F 6E 66 69 72 6D F4 64 68 61 73 68 58 20 EF 71 9F 16 7C 06 8F 44 AC 53 DD 89 1C F9 CD 05 3F 0A 34 B2 A0 75 2F 62 87 C3 97 CC 68 7C AE 4A FF。

对应image header的8个字节就是 02 00 00 32 00 01 61 00。

对应payload为:

BF 67 63 6F 6E 66 69 72 6D F4 64 68 61 73 68 58 20 EF 71 9F 16 7C 06 8F 44 AC 53 DD 89 1C F9 CD 05 3F 0A 34 B2 A0 75 2F 62 87 C3 97 CC 68 7C AE 4A FF。

我们解析一下这块数据对应的数据格式:

BF                            ------字典的开始
  67                          ------字节string数组,长度为7
      63 6F 6E 66 69 72 6D    ------ “confirm”  字段
  F4                          ------值为false
  64                          ------字节string数组,长度为4
      68 61 73 68             ------“hash” 字段
  58 20                       ------长度为0x20的bin string类型
  EF 71 9F 16 7C 06 8F 44 AC 53 DD 89 1C F9 CD 05 3F 0A 34 B2 A0 75 2F 62 87 C3 97 CC 68 7C AE 4A     ------32字节hash值
FF                            ------字典结束 

这条写命令对应的CBOR的数据格式:

{
  (str,opt)"hash" :(str)
  (str)"confirm"  :(bool)
}

可以看出,这条指令对应的意思就是,下发confirm为false的带hash值的写命令。

Mcumgr Confirm request指令与其类似,zephyr文档写到:

如果“confirm”为 false,则将设置带有“hash”的值进行测试,这意味着它将不会被标记为永久,并且在复位之后,以前的应用程序将会回滚到之前的image。如果“confirm” 为 true,则 “hash” 是可选的,当前运行的应用程序将被设置为目标运行程序,意味着不再回滚到之前的版本。

response

Type Value Command
Operation Code 3 read request
Group ID 1 Application/software image management group
Command id 0 Statics of images

其应答与image list response应答相同,不再重复。

2.3.5 mcumgr reset

request

Type Value Command
Operation Code 2 read request
Group ID 0 OS Management Group
Command id 5 Reset Image

数据为:02 00 00 02 00 00 62 05 BF FF。

对应image header的02 00 00 02 00 00 62 05

Payload为:BF FF,表示这是一个空的map,没有实际数据。

response

Type Value Command
Operation Code 3 read request
Group ID 0 OS Management Group
Command id 5 Reset Image

数据为:03 00 00 02 00 00 62 05 BF FF。

对应image header的03 00 00 02 00 00 62 05。

Payload为:BF FF,表示这是一个空的map,没有实际数据。

三、SMP central的实现

SMP central的实现按照上面所述流程操作,那么我们首先就是需要把升级文件放到内部或者外部flash里面,central通过内部代码把flash操作读出来,通过指令完成image upload操作。

上述有几个步骤,可以通过发命令远程去完成,也可以通过调用本地API自己去完成,两种选择都可以。

比如confirm image这一步,你可以等待新image启动成功,然后重连主机,主机再发“confirm image”命令,这个时候升级才算真正完成;也可以在新image启动成功后,在不连主机的情况下,通过调用前述API:boot_write_img_confirmed () 来完成这个确认过程。不管采用那种方法,本质上都是调用 boot_write_img_confirmed () 来实现,不同的是触发方式或者时机,发命令的方式由主机远程触发(SMP DFU 就是选择这种主机远程发命令方式),而本地API方式则是设备自己选择时机来触发(nrf dfu 就是选择这种本地API调用方式。

Mcumgr parameter指令也可以不通过request完成操作,我们只需要在central端和peripheral端统一好参数就可以。

3.1 Mcumgr reset指令:

SMP Payload

zcbor_map_start_encode(zse, CBOR_MAP_MAX_ELEMENT_CNT);
zcbor_map_end_encode(zse, CBOR_MAP_MAX_ELEMENT_CNT);

SMP Header

smp_cmd.header.op = 2; /* Write */
smp_cmd.header.flags = 0;
smp_cmd.header.len_h8 = (uint8_t)((payload_len >> 8) & 0xFF);
smp_cmd.header.len_l8 = (uint8_t)((payload_len >> 0) & 0xFF);
smp_cmd.header.group_h8 = 0;
smp_cmd.header.group_l8 = 0; /* OS */
smp_cmd.header.seq = load_seq;

3.2 Mcumgr test指令:

SMP Payload

zcbor_map_start_encode(zse, CBOR_MAP_MAX_ELEMENT_CNT);
zcbor_tstr_put_lit(zse, "confirm");
zcbor_bool_put(zse, false);
zcbor_tstr_put_lit(zse, "hash");
zcbor_bstr_put_lit(zse, hash_value_secondary_slot);

SMP Header

smp_cmd.header.op = 2; /* Write */
smp_cmd.header.flags = 0;
smp_cmd.header.len_h8 = (uint8_t)((payload_len >> 8) & 0xFF);
smp_cmd.header.len_l8 = (uint8_t)((payload_len >> 0) & 0xFF);
smp_cmd.header.group_h8 = 0;
smp_cmd.header.group_l8 = 1; /* app/image */
smp_cmd.header.seq = load_seq;

3.3 Mcumgr upload指令:

SMP Payload

if(!zcbor_map_start_encode(zse, encode_len))
  LOG_INF("zcbor_map_start_encode(zse, encode_len) fail");
zcbor_tstr_put_lit(zse, "data");
if(!zcbor_bstr_encode_ptr(zse, data, upload_chunk))
  LOG_INF("zcbor_bstr_encode_ptr(zse, data, upload_chunk) fail");
zcbor_tstr_put_lit(zse, "len");
if(!zcbor_uint64_put(zse, (uint64_t)(last_addr-start_addr)))
  LOG_INF("zcbor_uint64_put(zse, (uint64_t)(last_addr-start_addr)) fail");

zcbor_tstr_put_lit(zse, "sha");
zcbor_bstr_put_lit(zse, "123");

zcbor_tstr_put_lit(zse, "off");
if(!zcbor_uint64_put(zse, curr_addr - start_addr))
LOG_INF("zcbor_uint64_put(zse, curr_addr - start_addr) fail");

SMP Header

smp_cmd.header.op = 2; /* write request */
smp_cmd.header.flags = 0;
smp_cmd.header.len_h8 = (uint8_t)((payload_len >> 8) & 0xFF);
smp_cmd.header.len_l8 = (uint8_t)((payload_len >> 0) & 0xFF);
smp_cmd.header.group_h8 = 0;
smp_cmd.header.group_l8 = 1; /* IMAGE */
smp_cmd.header.seq = load_seq;

开始您的Nordic之旅

具体代码实现请参考:

订阅Nordic新闻简报

了解最新信息!订阅后即可获取最新Nordic及物联网资讯

立即订阅