# ARM CMSIS-DSP测试 **Repository Path**: zhang_en/arm-cmsis-dsp-test ## Basic Information - **Project Name**: ARM CMSIS-DSP测试 - **Description**: ARM DSP基本就完了,包含了: 电机变换(克拉克变换、帕克变换)、卷积、 离散余弦变换、傅里叶变换、滤波、高斯朴素贝叶斯估计、 插值、矩阵运算、PID、支持向量机 - **Primary Language**: C - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 82 - **Forks**: 49 - **Created**: 2022-11-22 - **Last Updated**: 2025-06-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ARM CMSIS-DSP测试 #### 介绍 ARM DSP基本就完了,包含了: 电机变换(克拉克变换、帕克变换)、卷积、 离散余弦变换、傅里叶变换、滤波、高斯朴素贝叶斯估计、 插值、矩阵运算、PID、支持向量机 #### 软件架构 软件架构说明 #### 安装教程 1. 克拉克变换:在三相电系统里,经常使用坐标变换,实现三相变两相、两相变三相,测试采用正弦函数模拟三相电, 幅值为1,频率为50Hz,相位差互差120度,采样频率为6.4kHz,调用函数arm_sin_f32即可,由于三相电瞬时电流代数和为0, 只用给出A、B两相即可,克拉克变换函数arm_clarke_f32。 ``` float32_t ia[128]={0.}; //输入A相电流 float32_t ib[128]={0.}; //输入B相电流 float32_t ia1[128]={0.}; //输出阿法轴电流 float32_t ib1[128]={0.}; //输出贝塔轴电流 void dsp_test(void) { u8 i=0; for(i=0;i<128;i++) { ia[i]=arm_sin_f32(100*PI*i/6400); ib[i]=arm_sin_f32(100*PI*i/6400+PI*2/3); } for(i=0;i<128;i++) { arm_clarke_f32(ia[i],ib[i],&ia1[i],&ib1[i]); } for(i=0;i<128;i++) { printf("%f %f %f %f\r\n",ia[i],ib[i],ia1[i],ib1[i]); } } ``` ![输入图片说明](clarc/clarke_test.jpg) ![输入图片说明](clarc/clarke.jpg) 2. 卷积:卷积在数字信号处理算是一个重点内容,在各个变换里基本都讨论到卷积。卷积函数arm_conv_f32。测试采用 一个频率为50Hz的正弦波与一个长度为64的矩形窗函数进行卷积。 ``` float32_t data[64]={0.}; //输入序列1 float32_t data1[32]={0.}; //输入序列2 float32_t data2[95]={0.}; //输出序列 void dsp_test(void) { u8 i=0; printf("***************\r\n"); for(i=0;i<64;i++) { data[i]=arm_sin_f32(100*PI*i/6400); } for(i=0;i<64;i++) { printf("%f\r\n",data[i]); } printf("***************\r\n"); for(i=0;i<32;i++) { data1[i]=1; } for(i=0;i<32;i++) { printf("%f\r\n",data1[i]); } arm_conv_f32(data,64,data1,32,data2); printf("***************\r\n"); for(i=0;i<95;i++) { printf("%f\r\n",data2[i]); } } ``` ![输入图片说明](conv/conv_test.jpg) 3. 离散余弦变换:离散余弦变换在图像处理中比较常用,主要是图像压缩邻域,例如JPG格式,图像清晰度与原图差不多, 但占用的存储空间却比原图少了很多。离散余弦变换初始化函数arm_dct4_init_f32,离散余弦变换函数arm_dct4_f32。测试 采用对输入序列为1.5+arm_sin_f32(100*PI*i/6400)进行离散余弦变换。 ``` arm_dct4_instance_f32 S; //DCT 实例化结构体 arm_rfft_instance_f32 S_RFFT; //实序列傅里叶变换实例化结构体 arm_cfft_radix4_instance_f32 S_CFFT; //复数序列傅里叶变换实例化结构体 float32_t normalize=0.125; //归一化因子 float32_t pInlineBuffer[128]; //输入输出 float32_t pState[128]; //state缓存 float32_t data[128]={0.f}; void dsp_test(void) { u16 i=0; for(i=0;i<128;i++) { data[i]=1.5f+arm_sin_f32(100*PI*i/6400); printf("%f\r\n",data[i]); pInlineBuffer[i]=data[i]; } arm_dct4_init_f32(&S,&S_RFFT,&S_CFFT,128,64,normalize); arm_dct4_f32(&S,pState,pInlineBuffer); for(i=0;i<128;i++) { printf("%f\r\n",pInlineBuffer[i]); } } ``` 4.傅里叶变换:傅里叶变换在各个领域都有运用,尤其是在频谱分析和谐波检测行业中,傅里叶变换实序列 初始化函数为arm_rfft_init_f32,实序列傅里叶变换函数为arm_rfft_f32。测试采用直流分量为1.5,基波频率 为50Hz,幅值为1,采样率为6.4KHZ,叠加幅值为0.3的3次谐波和幅值为0.5的7次谐波,通过傅里叶变换准确获得 各次谐波的幅值。 ``` arm_rfft_instance_f32 S; //实序列傅里叶变换结构体 arm_cfft_radix4_instance_f32 S_CFFT; //傅里叶变换结果复序列结构体 static float32_t pSrc[128]={0.0}; //输入 static float32_t pDst[256]={0.0}; //输出 static float32_t out[128]={0}; //结果 void dsp_test(void) { u8 i=0,j=0; for(i=0;i<128;i++) { pSrc[i]=1.6f+arm_sin_f32(100*PI*i/6400)+0.3f*arm_sin_f32(300*PI*i/6400)+0.5f*arm_sin_f32(700*PI*i/6400); } arm_rfft_init_f32(&S,&S_CFFT,128,0,1); //初始化 arm_rfft_f32(&S,pSrc,pDst); //傅里叶变换 for(j=0;j<127;j++) { arm_sqrt_f32(pDst[2*j]*pDst[2*j]+pDst[2*j+1]*pDst[2*j+1],&out[j]);//获得幅值 if(j==0) { out[j]=out[j]/128;//直流分量需要特殊处理 } else { out[j]=out[j]/64;//交流分量 } } for(j=0;j<64;j++) { printf("%f\r\n",out[j]); } } ``` ![输入图片说明](fft/fft_test.jpg) ![输入图片说明](fft/fft_test1.jpg) 5.滤波:滤波在信号处理中特别重要,在模拟前端经硬件滤波后,流入ADC,采集到的数据可能还达不到直接使用 的目的,这时需要进行软件滤波。包括有限冲激响应滤波和无限冲激响应滤波,测试采用50Hz基波叠加9次谐波, 通过IIR滤波将9次谐波滤掉。 ``` //arm_biquad_cascade_df1_init_f32 parameter(初始化结构体) static arm_biquad_casd_df1_inst_f32 S; //structure static uint8_t numStages=2; //nums of 2 order filter (2阶滤波器) //5*numStages xishu static float32_t pCoeffs[5*2]={1,2,1,1.9184107565980049,-0.92769312589129826, 1,2,1,1.8250960051409635,-0.83392686425555462}; static float32_t pState[4*2]; //4*numStages //arm_biquad_cascade_df1_f32 parameter static float32_t pSrc[512]; static float32_t pDst[512]; static uint32_t blockSize=1; void dsp_test(void) { u16 i=0; for(i=0;i<512;i++) { pSrc[i]=1.5f*arm_sin_f32(100*PI*i/6400)+0.5f*arm_sin_f32(900*PI*i/6400); } arm_biquad_cascade_df1_init_f32(&S,numStages,pCoeffs,pState); for(i=0;i<(512/blockSize);i++) { arm_biquad_cascade_df1_f32(&S,pSrc+i*blockSize,pDst+i*blockSize,blockSize); } printf("************\r\n"); for(i=0;i<512;i++) { //Scale Values: //0.0023205923233234451 //0.0022077147786478436 printf("%f %f\r\n",pSrc[i],pDst[i]*0.0023205923233234451f*0.0022077147786478436f); } } ``` ![输入图片说明](filter/filter.jpg) ![输入图片说明](filter/filter_test.jpg) 6.高斯朴素贝叶斯分类器:高斯朴素贝叶斯分类器属于偏机器学习领域,通过给出符合高斯分布的均值和方差及分类, 输入待分类数据,即可将输入数据按先验数据进行分类。 ``` arm_gaussian_naive_bayes_instance_f32 S; #define SEMIHOSTING 1 //使能 #define NB_OF_CLASSES 3 //类目 #define VECTOR_DIMENSION 2 //向量维数 const float32_t theta[NB_OF_CLASSES*VECTOR_DIMENSION] = { 1.4539529436590528f, 0.8722776016801852f, -1.5267934452462473f, 0.903204577814203f, -0.15338006360932258f, -2.9997913665803964f }; /**< 高斯分布的均值 */ const float32_t sigma[NB_OF_CLASSES*VECTOR_DIMENSION] = { 1.0063470889514925f, 0.9038018246524426f, 1.0224479953244736f, 0.7768764290432544f, 1.1217662403241206f, 1.2303890106020325f }; /**< 高斯分布的方差 */ const float32_t classPriors[NB_OF_CLASSES] = { 0.3333333333333333f, 0.3333333333333333f, 0.3333333333333333f }; /**< 类先验概率 */ void dsp_test(void) { /* 输入数组 */ float32_t in[2]; /* 分类器结果 */ float32_t result[NB_OF_CLASSES]; float32_t maxProba; uint32_t index; S.vectorDimension = VECTOR_DIMENSION; //向量空间维数 S.numberOfClasses = NB_OF_CLASSES; //不同类数目 S.theta = theta; //高斯分布均值 S.sigma = sigma; //高斯分布方差 S.classPriors = classPriors; //先验概率 S.epsilon=4.328939296523643e-09f; //方差的叠加值 in[0] = 1.5f; in[1] = 1.0f; index = arm_gaussian_naive_bayes_predict_f32(&S, in,result); maxProba = result[index]; #if defined(SEMIHOSTING) printf("Class = %d\n", index); printf("Max proba = %f\n", (double)maxProba); #endif in[0] = -1.5f; in[1] = 1.0f; index = arm_gaussian_naive_bayes_predict_f32(&S, in,result); maxProba = result[index]; #if defined(SEMIHOSTING) printf("Class = %d\n", index); printf("Max proba = %f\n", (double)maxProba); #endif in[0] = 0.0f; in[1] = -3.0f; index = arm_gaussian_naive_bayes_predict_f32(&S, in,result); maxProba = result[index]; #if defined(SEMIHOSTING) printf("Class = %d\n", index); printf("Max proba = %f\n", (double)maxProba); #endif } ``` 7.插值:插值在数据分析和处理中较常用,有时数据量不够,可以通过一些插值手段,将数据进行丰富,方便分析。 常规的插值有线性插值、样条插值。测试主要针对样条插值,给出32点的正弦信号,通过插值方法将数据点拓展到 128点,分别对自然样条插值和抛物样条插值进行测试和对比。 ``` void dsp_test(void) { u8 i=0; float32_t data[128]={0.}; for(i=0;i<128;i++) { data[i]=1.6f+arm_sin_f32(100*PI*i/6400)+0.3f*arm_sin_f32(300*PI*i/6400)+0.5f*arm_sin_f32(700*PI*i/6400); printf("%f\r\n",data[i]); } } //arm_spline_init_f32 parameter static arm_spline_instance_f32 S; //样条插值结构体 static arm_spline_type type=ARM_SPLINE_NATURAL; //自然样条插值 static float32_t x[32]; //原始数据x static float32_t y[32]; //原始数据y static uint32_t n=32; //原始数据个数 static float32_t coeffs[3*(32-1)]; static float32_t tempBuffer[2*(32-1)]; //arm_spline_f32 parameter static float32_t xq[128]; static float32_t pDst[128]; static uint32_t blockSize=128; #define num_tab 128/32 void interp_test(u8 mode) { if(mode) { type=ARM_SPLINE_NATURAL;//自然样条插值 } else { type=ARM_SPLINE_PARABOLIC_RUNOUT;//抛物样条插值 } u8 i=0; for(i=0;i<32;i++) { x[i]=i*num_tab; y[i]=1.f+arm_sin_f32(100.f*PI*i/256.f+PI/3.f); } for(i=0;i<128;i++) { xq[i]=i; } arm_spline_init_f32(&S,type,x,y,n,coeffs,tempBuffer); arm_spline_f32(&S,xq,pDst,blockSize); printf("*****x********\r\n"); for(i=0;i<32;i++) { printf("%f\r\n",x[i]); } printf("*****y********\r\n"); for(i=0;i<32;i++) { printf("%f\r\n",y[i]); } printf("*****x1********\r\n"); for(i=0;i<128;i++) { printf("%f\r\n",xq[i]); } printf("*****y1********\r\n"); for(i=0;i<128;i++) { printf("%f\r\n",pDst[i]); } } ``` ![输入图片说明](inter/inter_test.jpg) ![输入图片说明](inter/inter_test1.jpg) 8.矩阵运算:矩阵运算包括矩阵加、减、乘、逆运算、转置、数乘等 ``` arm_matrix_instance_f32 S; arm_matrix_instance_f32 S1; arm_matrix_instance_f32 S2; uint16_t nRows=5; uint16_t nColumns=5; static void dsp_test1(void) { float32_t pData[25]={17,24,1,8,15, 23,5,7,14,16, 4,6,13,20,22, 10,12,19,21,3, 11,18,25,2,9}; float32_t pData1[25]={3,3,3,3,3, 3,3,3,3,3, 3,3,3,3,3, 3,3,3,3,3, 3,3,3,3,3}; float32_t pData2[25]={0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0}; u8 i=0; arm_mat_init_f32(&S,nRows,nColumns,pData); arm_mat_init_f32(&S1,nRows,nColumns,pData1); arm_mat_init_f32(&S2,nRows,nColumns,pData2); printf("矩阵A=\r\n"); for(i=0;i<25;i++) { printf("%f ",S.pData[i]); if(i%5==4) { printf("\r\n"); } } printf("\r\n"); printf("矩阵B=\r\n"); for(i=0;i<25;i++) { printf("%f ",S1.pData[i]); if(i%5==4) { printf("\r\n"); } } arm_mat_add_f32(&S,&S1,&S2); printf("\r\n"); printf("矩阵A+B=\r\n"); for(i=0;i<25;i++) { printf("%f ",S2.pData[i]); if(i%5==4) { printf("\r\n"); } } arm_mat_mult_f32(&S,&S1,&S2); printf("\r\n"); printf("矩阵A*B=\r\n"); for(i=0;i<25;i++) { printf("%f ",S2.pData[i]); if(i%5==4) { printf("\r\n"); } } arm_mat_inverse_f32(&S,&S2); printf("\r\n"); printf("矩阵A的逆矩阵=\r\n"); for(i=0;i<25;i++) { printf("%f ",S2.pData[i]); if(i%5==4) { printf("\r\n"); } } } static void dsp_test2(void) { u8 i=0; float32_t pData[25]={17,24,1,8,15, 23,5,7,14,16, 4,6,13,20,22, 10,12,19,21,3, 11,18,25,2,9}; float32_t pData1[25]={3,3,3,3,3, 3,3,3,3,3, 3,3,3,3,3, 3,3,3,3,3, 3,3,3,3,3}; float32_t pData2[25]={0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0}; arm_mat_init_f32(&S,nRows,nColumns,pData); arm_mat_init_f32(&S1,nRows,nColumns,pData1); arm_mat_init_f32(&S2,nRows,nColumns,pData2); arm_mat_scale_f32(&S,0.1f,&S2); printf("\r\n"); printf("矩阵A*0.1=\r\n"); for(i=0;i<25;i++) { printf("%f ",S2.pData[i]); if(i%5==4) { printf("\r\n"); } } arm_mat_sub_f32(&S,&S1,&S2); printf("\r\n"); printf("矩阵A-B=\r\n"); for(i=0;i<25;i++) { printf("%f ",S2.pData[i]); if(i%5==4) { printf("\r\n"); } } arm_mat_trans_f32(&S,&S2); printf("\r\n"); printf("矩阵A的转置=\r\n"); for(i=0;i<25;i++) { printf("%f ",S2.pData[i]); if(i%5==4) { printf("\r\n"); } } } void dsp_test(void) { dsp_test1(); dsp_test2(); } ``` 9.PID控制:PID控制在工业领域较为常用,尤其是自动化控制。PID初始化函数arm_pid_init_f32,PID控制函数为 arm_pid_f32,测试采用DAC输出连接到ADC输入,PID控制调节DAC输入参数,让ADC采集结果稳定输出在2678。 ``` arm_pid_instance_f32 S; //定义PID结构体 float ref=2678./4095*3.3; //ADC参考目标值:2678 void ad_da_init(void) { //时钟配置 rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_AF); rcu_periph_clock_enable(RCU_ADC0); rcu_periph_clock_enable(RCU_DAC); rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV12); //接口配置 gpio_init(GPIOA,GPIO_MODE_AIN,GPIO_OSPEED_50MHZ,GPIO_PIN_4); gpio_init(GPIOA,GPIO_MODE_AIN,GPIO_OSPEED_50MHZ,GPIO_PIN_6); //ADC输入配置 adc_special_function_config(ADC0,ADC_CONTINUOUS_MODE,ENABLE); adc_data_alignment_config(ADC0,ADC_DATAALIGN_RIGHT); adc_channel_length_config(ADC0,ADC_REGULAR_CHANNEL,1); adc_regular_channel_config(ADC0,0,ADC_CHANNEL_6,ADC_SAMPLETIME_55POINT5); adc_external_trigger_config(ADC0,ADC_REGULAR_CHANNEL,DISABLE); adc_enable(ADC0); delay_ms(1); adc_calibration_enable(ADC0); //DAC输出配置 dac_trigger_source_config(DAC0,DAC_TRIGGER_SOFTWARE); dac_trigger_enable(DAC0); dac_wave_mode_config(DAC0,DAC_WAVE_DISABLE); dac_output_buffer_enable(DAC0); dac_enable(DAC0); //PID参数初始化 S.Kd=0; S.Ki=16; S.Kp=100; arm_pid_init_f32(&S,1); } volatile float adc_get=0.; void dsp_test(void) { float32_t pid_num=0.; pid_num=arm_pid_f32(&S,ref-adc_get); //PID处理,输入为误差信号 if(pid_num>=4095) { pid_num=4095; } dac_data_set(DAC0, DAC_ALIGN_12B_R, (u16)pid_num); dac_software_trigger_enable(DAC0); adc_software_trigger_enable(ADC0,ADC_REGULAR_CHANNEL); if(adc_flag_get(ADC0,ADC_FLAG_EOC)) { adc_flag_clear(ADC0,ADC_FLAG_EOC); adc_get = adc_regular_data_read(ADC0)/4095.*3.3; } printf("%f %d %d\r\n",adc_get,(u16)pid_num,adc_regular_data_read(ADC0)); } ``` ![输入图片说明](PID/pid.jpg) ![输入图片说明](PID/pid_test.jpg) 10。支持向量机分类器:支持向量机与贝叶斯分类器同属于机器学习领域,支持向量机分类器有线性和非线性, 对类A和类B进行标识,输入数据A和B,通过分类器计算结果,对输入数据进行分类,因为例程提供的是线性的SVM分类器, 截距为-1.6617。 ``` #define SEMIHOSTING 1 //使能打印 //支持向量机实例化结构体,所有参数可以通过python生成 arm_svm_polynomial_instance_f32 params; //通过使用scikit-learn包和一些随机输入数据对SVM分类器进行训练生成的参数 #define NB_SUPPORT_VECTORS 11 //向量个数 //向量空间的维数 #define VECTOR_DIMENSION 2 const float32_t dualCoefficients[NB_SUPPORT_VECTORS]={-0.01628988f, -0.0971605f, -0.02707579f, 0.0249406f, 0.00223095f, 0.04117345f, 0.0262687f, 0.00800358f, 0.00581823f, 0.02346904f, 0.00862162f}; /* Dual coefficients */ const float32_t supportVectors[NB_SUPPORT_VECTORS*VECTOR_DIMENSION]={ 1.2510991f, 0.47782799f, -0.32711859f, -1.49880648f, -0.08905047f, 1.31907242f, 1.14059333f, 2.63443767f, -2.62561524f, 1.02120701f, -1.2361353f, -2.53145187f, 2.28308122f, -1.58185875f, 2.73955981f, 0.35759327f, 0.56662986f, 2.79702016f, -2.51380816f, 1.29295364f, -0.56658669f, -2.81944734f}; /* Support vectors */ //类A标识为“0”,类B标识为“1” const int32_t classes[2]={0,1}; void dsp_test(void) { /* 输入数据 */ float32_t in[VECTOR_DIMENSION]; /* 分类器结果 */ int32_t result; //初始化支持向量机实例化结构体 arm_svm_polynomial_init_f32(¶ms, //实例化结构体 NB_SUPPORT_VECTORS, //向量个数 VECTOR_DIMENSION, //向量维数 -1.661719f, //截距 dualCoefficients, //双系数 supportVectors, //支持向量 classes, //类ID标识 3, //多项式次数 1.100000f, //python scikit-learn包专用系数 0.500000f //python scikit-learn包专用系数 ); //输入测试数据,类A in[0] = 0.4f; in[1] = 0.1f; arm_svm_polynomial_predict_f32(¶ms, in, &result); //根据分类标识:如果分类器结果为A,则标识必为“0” #if defined(SEMIHOSTING) printf("Result = %d\n", result); #endif //输入测试数据,类B in[0] = 3.0f; in[1] = 0.0f; arm_svm_polynomial_predict_f32(¶ms, in, &result); //根据分类标识:如果分类器结果为A,则标识必为“1” #if defined(SEMIHOSTING) printf("Result = %d\n", result); #endif } ``` #### 使用说明 1. 采用ARM官方提供的DSP库 2. 所有测试经过与MATLAB测试对比 3. 软件采用MDK 5.35,DSP采用1.8.0版本,单片机采用GD32F103VKT6 #### 参与贡献 1. Fork 本仓库 2. 新建 Feat_xxx 分支 3. 提交代码 4. 新建 Pull Request #### 特技 1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md 2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com) 3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目 4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目 5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) 6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)