Skip to content

概念先知道

  • 校验和(Checksum):把数据按规则求和得到一个短校验值,用于检查数据有没有被改坏(如网络报文、固件完整性)。
  • 硬件加速:CKS 外设用电路直接算校验和,比软件逐字节求和快,DMA 还能进一步减少 CPU 占用。
  • 端序(Endian):数据按大端还是小端解释会得到不同校验值,三种方式必须用同一端序。

例程功能简介

本页对应博流官方 SDK 的 cks_dma 例程(examples/peripherals/cks/cks_dma),演示 CKS 校验和硬件加速

  • 对 512 字节测试数据分别用软件求和硬件 CKSDMA 搬运后 CKS 三种方式计算校验和;
  • 打印每种方式的耗时(微秒)与校验值,可直观对比硬件加速的优势;
  • 校验和常用于网络报文/固件完整性校验。
  • 同族例程examples/peripherals/cks/):cks_normal(普通模式,不经过 DMA)。

操作步骤

1
进入例程目录

本页不需要额外接线。在终端进入 SDK 的 CKS 例程目录(前提:已按快速开始(Linux)Windows搭建好环境):

cd examples/peripherals/cks/cks_dma
2
编译工程

执行编译命令。Ai-M62(BL616)与 Ai-M61(BL618)同属一个系列,统一填写引脚最少的 bl616 即可:

make CHIP=bl616 BOARD=bl616dk
3
烧录固件

用 USB 线连接开发板,按住 BOOT 键(Ai-M61-32S-Kit 为 IO2)不放、短按 EN/RST 进入下载模式,然后执行烧录(把串口号换成实际值):

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
运行验证

打开串口助手(波特率 2000000)。例程对 512 字节数据分别用软件、硬件、DMA 三种方式计算校验和,打印各自的耗时与 sw_cks/hw_cks/dma_cks 结果,最后打印 end

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):

例程调用的 API 介绍

bflb_device_get_by_name("cks")

获取 CKS 外设的设备句柄。

参数

  • name:外设名称,固定 "cks"

返回值struct bflb_device_s * 设备句柄

bflb_cks_reset(cks)

复位 CKS 计算器,开始新一轮校验和计算前调用。

参数

  • cks:CKS 设备句柄

返回值:成功返回 0;失败返回负值错误码

bflb_cks_set_endian(cks, CKS_BIG_ENDIAN)

设置字节序(大端/小端),须与数据实际字节序一致。

参数

  • cks:CKS 设备句柄
  • endianCKS_BIG_ENDIAN / CKS_LITTLE_ENDIAN

返回值:成功返回 0;失败返回负值错误码

bflb_cks_compute(cks, data, len)

对指定数据计算硬件校验和(数据通过 DMA 或直接喂给硬件寄存器)。

参数

  • cks:CKS 设备句柄
  • data:数据缓冲区
  • len:数据长度(字节)

返回值:16 位校验和

完整代码

以下为 cks_dma/main.c 完整源码,与官方示例(examples/peripherals/cks/cks_dma)一致;点灯引脚已适配 Ai-M61/62-32S-Kit 板载 RGB 灯,默认折叠,点击展开:

📜 点击展开 cks_dma/main.c 完整代码
c
#include "board.h"
#include "bflb_cks.h"
#include "bflb_dma.h"
#include "bflb_mtimer.h"
#include "bflb_core.h"

#define DATA_LEN 512

static volatile uint8_t dma_tc_flag0 = 0;
struct bflb_device_s *cks;
struct bflb_device_s *dma0_ch0;
struct bflb_dma_channel_lli_pool_s lli[20]; /* max trasnfer size 4064 * 20 */

void dma0_ch0_isr(void *arg)
{
    dma_tc_flag0++;
    printf("tc done\r\n");
}

uint16_t sw_chksum(uint8_t *data, uint32_t len) {
  uint32_t sum = 0;
  uint16_t chksum = 0;
  uint32_t size = len;

  if (len % 2 == 1) {
    size = len - 1;
    sum += data[size];
  }

  for (uint32_t i = 0; i < size; i = i + 2) {
    sum += ((uint32_t)data[i]);
    sum += ((uint32_t)data[i + 1] << 8);
  }

  while (sum >> 16) {
    sum = (sum >> 16) + (sum & 0x0000FFFF);
  }

  chksum = (uint16_t)sum;
  return ~chksum;
}

uint16_t get_cks_with_dma(uint8_t* data,uint32_t length)
{
    uint16_t checksum = 0;

    struct bflb_dma_channel_lli_transfer_s transfers[1];

    transfers[0].src_addr = (uint32_t)data;
    transfers[0].dst_addr = (uint32_t)(cks->reg_base + 0x4);
    transfers[0].nbytes = length;

    bflb_dma_channel_lli_reload(dma0_ch0, lli, 20, transfers, 1);
    bflb_dma_channel_start(dma0_ch0);

    while(dma_tc_flag0 == 0) {
    }
    dma_tc_flag0 = 0;
    
    checksum = bflb_cks_compute(cks, data, 0);

    return checksum;
}

static void test_case1(void){
    uint16_t dma_cks = 0;
    uint16_t hw_cks = 0;
    uint16_t sw_cks = 0;
    uint32_t time = 0, i;
    struct bflb_dma_channel_config_s config;
    
    uint32_t data_src1[DATA_LEN/4];
    
    for(i = 0;i < DATA_LEN; i++){
        ((uint8_t *)data_src1)[i] = i & 0xff;
    }
    bflb_l1c_dcache_clean_range(data_src1, DATA_LEN);
    
    time = (unsigned int)bflb_mtimer_get_time_us();
    
    sw_cks = sw_chksum((uint8_t *)data_src1, sizeof(data_src1));
    
    printf("software checksum time=%ldus\r\n", (unsigned int)bflb_mtimer_get_time_us() - time);
    
    printf("sw_cks is %04x\r\n", sw_cks);
    
    bflb_cks_reset(cks);
    bflb_cks_set_endian(cks, CKS_BIG_ENDIAN);
    
    time = (unsigned int)bflb_mtimer_get_time_us();
    
    hw_cks = bflb_cks_compute(cks, (uint8_t *)data_src1, sizeof(data_src1));
    
    printf("hardware checksum time=%ldus\r\n", (unsigned int)bflb_mtimer_get_time_us() - time);
    
    printf("hw_cks is %04x\r\n", hw_cks);
    
    bflb_cks_reset(cks);
    bflb_cks_set_endian(cks, CKS_BIG_ENDIAN);
    
    printf("\r\ndma case 1:\n");
    
    config.direction = DMA_MEMORY_TO_MEMORY;
    config.src_req = 0;
    config.dst_req = 0;
    config.src_addr_inc = DMA_ADDR_INCREMENT_ENABLE;
    config.dst_addr_inc = DMA_ADDR_INCREMENT_DISABLE;
    config.src_burst_count = DMA_BURST_INCR1;
    config.dst_burst_count = DMA_BURST_INCR1;
    config.src_width = DMA_DATA_WIDTH_8BIT;
    config.dst_width = DMA_DATA_WIDTH_8BIT;
    bflb_dma_channel_init(dma0_ch0, &config);
    bflb_dma_channel_irq_attach(dma0_ch0, dma0_ch0_isr, NULL);
    
    time = (unsigned int)bflb_mtimer_get_time_us();
    
    dma_cks = get_cks_with_dma((uint8_t *)data_src1, sizeof(data_src1));
    printf("dma checksum time=%ldus\r\n", (unsigned int)bflb_mtimer_get_time_us() - time);
    printf("dma_cks is %04x\r\n", dma_cks);
    
    bflb_cks_reset(cks);
    bflb_cks_set_endian(cks, CKS_BIG_ENDIAN);
    
    printf("\r\ndma case 2:\n");
    config.src_width = DMA_DATA_WIDTH_32BIT;
    config.src_burst_count = DMA_BURST_INCR4;
    config.dst_width = DMA_DATA_WIDTH_8BIT;
    config.dst_burst_count = DMA_BURST_INCR16;
    time = (unsigned int)bflb_mtimer_get_time_us();
    
    dma_cks = get_cks_with_dma((uint8_t *)data_src1, sizeof(data_src1));
    printf("dma checksum time=%ldus\r\n", (unsigned int)bflb_mtimer_get_time_us() - time);
    printf("dma_cks is %04x\r\n", dma_cks);
}

/* main */
int main(void)
{
    board_init();
    
    printf("CKS dma case:\r\n");

    cks = bflb_device_get_by_name("cks");
    dma0_ch0 = bflb_device_get_by_name("dma0_ch0");

    test_case1();

    printf("\r\nend\r\n");

    while (1) {
    }
}

FAQ

三种方式算出的校验和不一样

校验和算法对字节序敏感:确认三种方式都使用相同的端序(例程均为大端);DMA 方式注意缓存一致性(搬运前 bflb_l1c_dcache_clean_range)。

硬件校验和耗时反而更长

小数据量下软硬件耗时接近甚至软件更快属正常;数据量越大硬件/DMA 优势越明显。

想算其他数据的校验和

替换 data_src1 的内容并同步调整 DATA_LEN,重新编译烧录即可。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

Released under the MIT License. Build Time 2026-09-11 14:52:23