完整版教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547
第6章 ARM DSP源码和库移植方法(MDK5的AC5和AC6)
本期教程主要讲解ARM官方DSP源码和库的移植以及一些相关知识的介绍。
6.1 初学者重要提示
6.2 DSP库的下载和说明
6.3 DSP库版本的区别
6.4 DSP库的几个重要的预定义宏含义
6.5 使用MDK的AC6编译器优势
6.6 DSP库在MDK上的移植(AC5源码移植方式)
6.7 DSP库在MDK上的移植(AC5库移植方式)
6.8 DSP库在MDK上的移植(AC6源码移植方式)
6.9 DSP库在MDK上的移植(AC6库移植方式)
6.10 升级到最新版DSP库的方法
6.11 简易DSP库函数验证
6.12 总结
6.1 初学者重要提示
- MDK请使用5.26及其以上版本,CMSIS软件包请使用5.6.0及其以上版本。
- MDK的工程创建,下载和调试方法,在V7用户手册有详细说明:http://www.armbbs.cn/forum.php?mod=viewthread&tid=93255 。
- 鉴于MDK的AC6(ARM Compiler 6.X)编译器在浮点处理上的强劲性能,每个例子将必做一个AC6版,而且ARM编译好的DSP库也开始直接采用AC6。
- MDK AC6有两个地方在使用的时候要注意:
-
- 工程目录切记不要有中文路径,而且不要太长,否则会导致无法使用go to def以及调试的时候不正常。
- MDK AC6工程模板的汉字编码问题,在本章6.8小节有详细说明。
6.2 DSP库的下载和说明
下面详细的给大家讲解一下官方DSP库的移植。
6.2.1 DSP库的下载
DSP库是包含在CMSIS软件包(Cortex Microcontroller Software Interface Standard)里面,所以下载DSP库也就是下载CMSIS软件包。这里提供三个可以下载的地方:
- 方式一:STM32CubeH7软件包里面。
每个版本的Cube软件包都会携带CMSIS文件夹,只是版本比较老,不推荐。即使是最新的CubeH7
软件包,包含的CMSIS软件包版本也有点低。
- 方式二:MDK安装目录(下面是5.6.0版本的路径)。
大家安装了新版MDK后,CMSIS软件包会存在于路径:ARMPACKARMCMSIS5.6.0CMSIS。
如果有更新的版本,推荐大家使用最新版本,MDK的软件包下载地址:http://www.keil.com/dd2/Pack/ 。
- 方式三:GitHub。
通过GitHub获取也比较方便,地址:https://github.com/ARM-software/CMSIS_5 。点击右上角就可以下载CMSIS软件包了。
当然,也可以在ARM官网下载,只是这两年ARM官网升级得非常频繁,通过检索功能找资料非常麻烦。所以不推荐大家到ARM官网下载资料了。
6.2.2 DSP库的说明
这里我们以CMSIS V5.6.0为标准进行移植。打开固件库里面的CMSIS文件,可以看到如下几个文件:
其中DSP文件夹是我们需要的:
Examples文件夹中的文件如下,主要是提供了一些例子:
Include文件夹里面是DSP库的头文件:
Lib文件夹里面是MDK(ARM),IAR和CGG版库文件:
Projects文件夹里面的文件如下,提供了三个版本的工程模板,每个模板里面都是把所有源码文件添加了进来:
Source文件夹中的文件如下,这个是DSP的源码文件:
6.3 DSP库版本的区别
MDK版本的DSP库如下:
- arm_cortexM7lfdp_math.lib
Cortex-M7内核,l表示小端格式,f表示带FPU单元,dp表示Double Precision双精度浮点。
- arm_cortexM7bfdp_math.lib
Cortex-M7内核,b表示大端格式,f表示带FPU单元,dp表示Double Precision双精度浮点。
- arm_cortexM7lfsp_math.lib
Cortex-M7内核,l表示小端格式,f表示带FPU单元,sp表示Single Precision单精度浮点。
- arm_cortexM7bfsp_math.lib
Cortex-M7内核,b表示大端格式,f表示带FPU单元,sp表示Single Precision单精度浮点。
- arm_cortexM7l_math.lib
Cortex-M7内核,l表示小端格式。
- arm_cortexM7b_math.lib
Cortex-M7内核,b表示大端格式。
STM32H7是M7内核,双精度浮点,一般使用小端格式,所以我们选择库arm_cortexM7lfdp_math.lib
6.4 DSP库的几个重要的预定义宏含义
根据用户的使用要求,这几个预定义宏可以添加到MDK的预定义选项中:
这里将这几个预定义宏做个介绍:
- ARM_MATH_BIG_ENDIAN:
大端格式。
- ARM_MATH_MATRIX_CHECK:
检测矩阵的输入输出大小
- ARM_MATH_NEON:
- ARM_MATH_NEON_EXPERIMENTAL:
这两个暂时用不到,因为M0,M3,M4和M7内核不支持NEON指令,需要等待升级到ARMv8.1-M架构。
- ARM_MATH_ROUNDING:
主要用在浮点数转Q32,Q15和Q7时,类似四舍五入的处理上,其它函数没用到。
- ARM_MATH_LOOPUNROLL:
用于4个为一组的的小批量处理上,加快执行速度。
通过下面的求绝对值函数,可以方便的看出区别:
void arm_abs_f32( const float32_t * pSrc, float32_t * pDst, uint32_t blockSize) { uint32_t blkCnt; /* Loop counter */ #if defined(ARM_MATH_NEON) float32x4_t vec1; float32x4_t res; /* Compute 4 outputs at a time */ blkCnt = blockSize >> 2U; while (blkCnt > 0U) { /* C = |A| */ /* Calculate absolute values and then store the results in the destination buffer. */ vec1 = vld1q_f32(pSrc); res = vabsq_f32(vec1); vst1q_f32(pDst, res); /* Increment pointers */ pSrc += 4; pDst += 4; /* Decrement the loop counter */ blkCnt--; } /* Tail */ blkCnt = blockSize & 0x3; #else #if defined (ARM_MATH_LOOPUNROLL) /* Loop unrolling: Compute 4 outputs at a time */ blkCnt = blockSize >> 2U; while (blkCnt > 0U) { /* C = |A| */ /* Calculate absolute and store result in destination buffer. */ *pDst++ = fabsf(*pSrc++); *pDst++ = fabsf(*pSrc++); *pDst++ = fabsf(*pSrc++); *pDst++ = fabsf(*pSrc++); /* Decrement loop counter */ blkCnt--; } /* Loop unrolling: Compute remaining outputs */ blkCnt = blockSize % 0x4U; #else /* Initialize blkCnt with number of samples */ blkCnt = blockSize; #endif /* #if defined (ARM_MATH_LOOPUNROLL) */ #endif /* #if defined(ARM_MATH_NEON) */ while (blkCnt > 0U) { /* C = |A| */ /* Calculate absolute and store result in destination buffer. */ *pDst++ = fabsf(*pSrc++); /* Decrement loop counter */ blkCnt--; } }
6.5 使用MDK的AC6编译器优势
涉及到的知识点比较多,专门将其整理到附件章节E。
6.6 DSP库在MDK上的移植(AC5源码移植方式)
下面我们讲解下如何在MDK上面移植DSP库源码,DSP库的移植相对比较容易。
6.6.1 第一步:建立MDK工程并添加DSP库
为了方便起见,我们这里不再专门建立一个MDK工程了,直接以V7开发板中的例子:V7-001_跑马灯例程为模板进行添加即可。打开这个实例并在左侧添加分组CMSIS/DSP:
我们这里不需要添加每个C文件源码,仅需添加包含这些C文件的汇总文件,比如BasicMathFunctions.c文件里面包含的C文件就是:
#include "arm_abs_f32.c" #include "arm_abs_q15.c" #include "arm_abs_q31.c" #include "arm_abs_q7.c" #include "arm_add_f32.c" #include "arm_add_q15.c" #include "arm_add_q31.c" #include "arm_add_q7.c" #include "arm_dot_prod_f32.c" #include "arm_dot_prod_q15.c" #include "arm_dot_prod_q31.c" #include "arm_dot_prod_q7.c" #include "arm_mult_f32.c" #include "arm_mult_q15.c" #include "arm_mult_q31.c" #include "arm_mult_q7.c" #include "arm_negate_f32.c" #include "arm_negate_q15.c" #include "arm_negate_q31.c" #include "arm_negate_q7.c" #include "arm_offset_f32.c" #include "arm_offset_q15.c" #include "arm_offset_q31.c" #include "arm_offset_q7.c" #include "arm_scale_f32.c" #include "arm_scale_q15.c" #include "arm_scale_q31.c" #include "arm_scale_q7.c" #include "arm_shift_q15.c" #include "arm_shift_q31.c" #include "arm_shift_q7.c" #include "arm_sub_f32.c" #include "arm_sub_q15.c" #include "arm_sub_q31.c" #include "arm_sub_q7.c"
这样一来,MDK编译后会自动关联,查看源码非常方便:
6.6.2 第二步:添加头文件路径
添加DSP所需的头文件路径,这个头文件路径是已经在模板工程中添加好的,这里只是跟大家强调一下:
这里要注意一点,为什么直接添加路径LibrariesCMSISInclude里面的头文件即可,而没有添加LibrariesCMSISDSPInclude,这是因为路径LibrariesCMSISInclude里面已经包含了DSP库的头文件。
6.6.3 第三步:添加宏定义
我们这里仅使能一个宏定义ARM_MATH_LOOPUNROLL:
6.6.4 第四步:开启FPU
需要客户通过MDK开启FPU,由于STM32H7支持双精度浮点,这里要开启Double Precision。
6.6.5 第五步:添加头文件arm_math.h
用到DSP库函数的文件得添加#include "arm_math.h"就可以调用DSP库的API了。至此就完成了DSP库的移植。
6.7 DSP库在MDK上的移植(AC5库移植方式)
移植方法与本章6.5小节的相同,仅第1步不同,将源码的添加修改为库添加:
6.8 DSP库在MDK上的移植(AC6源码移植方式)
AC6的DSP源码移植与本章6.5小节里面的AC5移植完全相同,没有区别。但AC5和AC6工程上有三处区别,这里着重指出下:
- 第1处,采用AC6编译器:
- 第2处,警告类型选择AC5-like:
- 第3处,MDK的AC6工程代码如果有源文件是GBK编码,而且使用汉字,MDK编译时会报错,需要用记事本打开使用汉字的源码文件,另存为UTF-8。比如main.c文件的串口打印函数printf用到了汉字,那么就需要做如下修改:
然后再重新编译就不会报错了。同时,串口打印时,使用的串口助手要支持UTF-8,推荐用SecureCRT,下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91718 。
设置如下:
6.9 DSP库在MDK上的移植(AC6库移植方式)
AC6的DSP库移植与本章6.6小节里面的AC5移植完全相同,没有区别。不过要注意6.8小节中所讲解的问题即可。
6.10 升级到最新版DSP库的方法
由于CMSIS软件包是实时更新的,这里提供一种升级的简单办法,按照本章6.1小节的说明下载到最新版CMSIS软件包,然后直接覆盖DSP工程里面的CMSIS文件夹即可。
6.11 简易DSP库函数验证
这里我们主要运行arm_abs_f32,arm_abs_q31,arm_abs_q15这三个函数,以此来验证我们移植的DSP库是否正确。
配套例子:
本章配套了如下两个例子:
- V7-200_DSP程序模板(源码方式)
- V7-201_DSP程序模板(库方式)
每个例子都配套了MDK的AC5和AC6两个版本的工程。
实验目的:
1. 学习官方DSP库的移植
实验内容:
1. 按下按键K1, 串口打印函数arm_abs_f32的输出结果
2. 按下按键K2, 串口打印函数arm_abs_q31的输出结果
3. 按下按键K3, 串口打印函数arm_abs_q15的输出结果
实验现象:
通过串口上位机软件SecureCRT看打印信息现象如下(分别按几次K1,K2,K3)。如果编译的是MDK的AC6工程,特别要注意本章6.7小节所说的问题。
程序设计:
程序的设计也比较简单,通过按下不同的按键从而打印不同的DSP库函数执行结果,主程序如下:
#include "bsp.h" /* 底层硬件驱动 */ #include "arm_math.h" /* 定义例程名和例程发布日期 */ #define EXAMPLE_NAME "V7-ARM的DSP移植模板(源码方式)" #define EXAMPLE_DATE "2019-07-31" #define DEMO_VER "1.0" static void PrintfLogo(void); static void PrintfHelp(void); /* ********************************************************************************************************* * 函 数 名: main * 功能说明: c程序入口 * 形 参: 无 * 返 回 值: 错误代码(无需处理) ********************************************************************************************************* */ int main(void) { uint8_t ucKeyCode; /* 按键代码 */ float32_t pSrc; float32_t pDst; q31_t pSrc1; q31_t pDst1; q15_t pSrc2; q15_t pDst2; bsp_Init(); /* 硬件初始化 */ PrintfLogo(); /* 打印例程名称和版本等信息 */ PrintfHelp(); /* 打印操作提示 */ bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */ /* 主程序大循环 */ while (1) { /* CPU空闲时执行的函数,在 bsp.c */ bsp_Idle(); /* 判断定时器超时时间 */ if (bsp_CheckTimer(0)) { /* 每隔100ms 进来一次 */ /* 翻转LED2的状态 */ bsp_LedToggle(2); } /* 处理按键事件 */ ucKeyCode = bsp_GetKey(); if (ucKeyCode > 0) { /* 有键按下 */ switch (ucKeyCode) { case KEY_DOWN_K1: /* K1键按下 */ pSrc -= 1.23f; arm_abs_f32(&pSrc, &pDst, 1); printf("pDst = %f ", pDst); break; case KEY_DOWN_K2: /* K2键按下 */ pSrc1 -= 1; arm_abs_q31(&pSrc1, &pDst1, 1); printf("pDst1 = %d ", pDst1); break; case KEY_DOWN_K3: /* K3键按下 */ pSrc2 -= 1; arm_abs_q15(&pSrc2, &pDst2, 1); printf("pDst2 = %d ", pDst2); break; default: break; } } } }
6.12 总结
本期教程主要跟大家介绍了官方DSP库的移植,相对来说移植也比较简单,建议初学的同学按照这个步骤移植一遍。