完整版教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=94547
本期教程主要讲解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 总结
下面详细的给大家讲解一下官方DSP库的移植。
DSP库是包含在CMSIS软件包(Cortex Microcontroller Software Interface Standard)里面,所以下载DSP库也就是下载CMSIS软件包。这里提供三个可以下载的地方:
每个版本的Cube软件包都会携带CMSIS文件夹,只是版本比较老,不推荐。即使是最新的CubeH7
软件包,包含的CMSIS软件包版本也有点低。
大家安装了新版MDK后,CMSIS软件包会存在于路径:ARM\PACK\ARM\CMSIS\5.6.0\CMSIS。
如果有更新的版本,推荐大家使用最新版本,MDK的软件包下载地址:http://www.keil.com/dd2/Pack/ 。
通过GitHub获取也比较方便,地址:https://github.com/ARM-software/CMSIS_5 。点击右上角就可以下载CMSIS软件包了。
当然,也可以在ARM官网下载,只是这两年ARM官网升级得非常频繁,通过检索功能找资料非常麻烦。所以不推荐大家到ARM官网下载资料了。
这里我们以CMSIS V5.6.0为标准进行移植。打开固件库里面的CMSIS文件,可以看到如下几个文件:
其中DSP文件夹是我们需要的:
Examples文件夹中的文件如下,主要是提供了一些例子:
Include文件夹里面是DSP库的头文件:
Lib文件夹里面是MDK(ARM),IAR和CGG版库文件:
Projects文件夹里面的文件如下,提供了三个版本的工程模板,每个模板里面都是把所有源码文件添加了进来:
Source文件夹中的文件如下,这个是DSP的源码文件:
MDK版本的DSP库如下:
Cortex-M4内核,l表示小端格式,f表示带FPU单元,M4仅支持Single Precision单精度浮点。
Cortex-M4内核,l表示小端格式。
Cortex-M4内核,b表示大端格式,f表示带FPU单元,M4仅支持Single Precision单精度浮点。
Cortex-M4内核,b表示大端格式。
STM32F4是M4内核,单精度浮点,一般使用小端格式,所以我们选择库arm_cortexM4lf_math.lib
根据用户的使用要求,这几个预定义宏可以添加到MDK的预定义选项中:
这里将这几个预定义宏做个介绍:
大端格式。
检测矩阵的输入输出大小
这两个暂时用不到,因为M0,M3,M4和M7内核不支持NEON指令,需要等待升级到ARMv8.1-M架构。
主要用在浮点数转Q32,Q15和Q7时,类似四舍五入的处理上,其它函数没用到。
用于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--;
}
}
涉及到的知识点比较多,专门将其整理到附件章节E。
下面我们讲解下如何在MDK上面移植DSP库源码,DSP库的移植相对比较容易。
为了方便起见,我们这里不再专门建立一个MDK工程了,直接以V6开发板中的例子:V6-001_跑马灯例程为模板(注意,要使用我们HAL版本例子)进行添加即可。打开这个实例并在左侧添加分组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编译后会自动关联,查看源码非常方便:
添加DSP所需的头文件路径,这个头文件路径是已经在模板工程中添加好的,这里只是跟大家强调一下:
这里要注意一点,为什么直接添加路径Libraries\CMSIS\Include里面的头文件即可,而没有添加Libraries\CMSIS\DSP\Include,这是因为路径Libraries\CMSIS\Include里面已经包含了DSP库的头文件。
我们这里仅使能一个宏定义ARM_MATH_LOOPUNROLL:
需要客户通过MDK开启FPU,由于STM32F4支持单精度浮点,这里要开启Single Precision。
用到DSP库函数的文件得添加#include "arm_math.h"就可以调用DSP库的API了。至此就完成了DSP库的移植。
移植方法与本章6.5小节的相同,仅第1步不同,将源码的添加修改为库添加:
AC6的DSP源码移植与本章6.5小节里面的AC5移植完全相同,没有区别。但AC5和AC6工程上有三处区别,这里着重指出下:
然后再重新编译就不会报错了。同时,串口打印时,使用的串口助手要支持UTF-8,推荐用SecureCRT,下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=91718 。
设置如下:
AC6的DSP库移植与本章6.6小节里面的AC5移植完全相同,没有区别。不过要注意6.8小节中所讲解的问题即可。
由于CMSIS软件包是实时更新的,这里提供一种升级的简单办法,按照本章6.1小节的说明下载到最新版CMSIS软件包,然后直接覆盖DSP工程里面的CMSIS文件夹即可。
这里我们主要运行arm_abs_f32,arm_abs_q31,arm_abs_q15这三个函数,以此来验证我们移植的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\r\n", pDst);
break;
case KEY_DOWN_K2: /* K2键按下 */
pSrc1 -= 1;
arm_abs_q31(&pSrc1, &pDst1, 1);
printf("pDst1 = %d\r\n", pDst1);
break;
case KEY_DOWN_K3: /* K3键按下 */
pSrc2 -= 1;
arm_abs_q15(&pSrc2, &pDst2, 1);
printf("pDst2 = %d\r\n", pDst2);
break;
default:
break;
}
}
}
}
本期教程主要跟大家介绍了官方DSP库的移植,相对来说移植也比较简单,建议初学的同学按照这个步骤移植一遍。