总结——TI_音频信号分析仪

打印 上一主题 下一主题

主题 1024|帖子 1024|积分 3072

一、简介

装备:MSPM0G3507
:CMSIS-DSP        TI
数据分析:FFT
软件:CCS        CLion        MATLAB
目标:对音频信号进行采样(滤波+偏置处理),通过FFT获取信号的频率身分,得到频率谱、幅值谱和功率谱

二、可行性分析

1,MATLAB

①先设置一个序列模仿实际采样数据

        假设采样频率为20.48KHz,那么我们可以设置一个频率为1/20Hz的正弦函数来生成采样数据,采样数据所代表的波形为1.024KHz的正弦波
  1. f = 1/20;   
  2. X = (i*sin(2*pi*f*t) + 1000)*0.001; %电压
复制代码

②将采样数据通过FFT转为频谱

  1. Y = fft(X);
复制代码

 ③对频谱进行取模处理,得到幅值谱

  1. A = abs(Y); % 幅度谱
复制代码

④通过帕斯瓦尔定理,将幅值谱转为功率谱

        根据帕斯瓦尔定理,可得
,即幅值谱数据的平方除以2即是采样数据的平方。
        但由于只取正频率,那么还需要乘以2
  1. P = A.^2 / (N/2);
复制代码
 ⑤注意事项

        得到的功率谱由于是直接通过模长的平方得到,那么其单位应为
(采样数据的单位V),并非


2,代码

  1. clc;
  2. clear all;
  3. close all;
  4. % 1. 构建一个序列x
  5. N = 1024;
  6. t = (0:N-1)'; % 创建时间向量
  7. f = 1/20;     % 假设ADC采样频率为20.48KHz,那么这个序列为20.48/20KHz的正弦波
  8. yuan=[2.5,10,22.5,40,62.5,90,122.5,160];%源自实际信号功率
  9. j=1;
  10. for i=100:100:800
  11. X = (i*sin(2*pi*f*t) + 1000)*0.001; %电压
  12. % 2. 转为频域
  13. Y = fft(X);
  14. % 3. 把Y转为幅值谱
  15. A = abs(Y); % 幅度谱
  16. frequencies = (1:1: N/2+1); % 频率轴
  17. A = A(2:N/2+1); % 只取正频率部分,去除直流分量
  18. % 4. 使用帕斯瓦尔定理转为功率谱
  19. % Parseval定理表明时域能量等于频域能量
  20. % 所以功率谱可以通过幅度谱的平方获得
  21. P = A.^2 / (N/2); % 功率谱,归一化因子为N/2,因为我们只考虑正频率部分
  22. % 画图
  23. figure;
  24. subplot(2,1,1);
  25. plot(frequencies(2:end), A); % 从第二个点开始绘制,排除直流分量
  26. title('Amplitude Spectrum');
  27. xlabel('Frequency (Hz)');
  28. ylabel('Magnitude');
  29. subplot(2,1,2);
  30. plot(frequencies(2:end), P); % 从第二个点开始绘制,排除直流分量
  31. title('Power Spectrum');
  32. xlabel('Frequency (Hz)');
  33. ylabel('Power');
  34. % 打印功率谱中的极大值
  35. [maxP, indexMaxP] = max(P);
  36. fprintf('%dmV:功率谱中的极大值位于频率 %d ,功率值为 %fV(或者V^2)\n', i,frequencies(indexMaxP), maxP);
  37. fprintf('实际功率值为 %fmW,则阻抗为:%f\n\n',yuan(j),maxP/yuan(j)*1000);
  38. j=j+1;
  39. end
复制代码

3,仿真结果

        图像中舍去了直流身分,正弦波的频率身分只有一个尖峰,很合理



三、代码计划

1,库函数介绍

        这次编程一共涉及到CMSIS-DSP的这两个函数,背面将以采样1024个数据为例。
        同时要注意哈,由于音频信号频率一样寻常在几十Hz到10KHz以内,根据奈奎斯特定律,采样频率应为20KHz以上。由于采样数据为1024个,想要让分辨率整一点,所以选用20.48KHz。分辨率为20.48K/1024=20
arm:表现适用arm平台
cfft:c即complex(复数),fft:快速傅里叶变换,f32:float32数据范例
 mag:幅值?反正把频谱转为幅值谱
  1. arm_cfft_f32(&arm_cfft_sR_f32_len1024, fftArray, IFFTFLAG, BITREVERSE);
复制代码
  1. arm_cmplx_mag_f32(inputArray, outputArray, NUM_SAMPLES / 2);
复制代码

①arm_cfft_f32

        第一个参数是个 CMSIS-DSP里已经界说好的实例,在arm_const_structs.h头文件里,且见名知义。由于采样数据是1024个(一样寻常选用4的倍数,基4更快嘛),所以选用len1024
        第二个参数就是输入的数组,准确来说是复数数组,因此你需要一个更大的数组,1024*2的数组大小。每两个相邻数组分别存放复数的实部和虚部,实部就是ADC的采样数据,虚部放0.
        第三个参数是表明进行的是正变换照旧逆变换。我们需要进行正变换,添0
        第四个参数是序列翻转,添1就行。详情请见

② arm_cmplx_mag_f32

        第一个参数是从arm_cfft_f32得到的复数数组(频谱)
        第二个参数是接收转换后的幅值谱,是实数数组,只不外数组大小为1024/2,因为对称性,除了直流身分,数组前半部分与后半部分对称。
        第三个参数就是数组大小,添1024/2

2,用户函数计划

        除了幅值谱外,我们还需要失真度分析、电压幅值谱、功率谱、幅值谱中极大值点等

①获取幅值谱中极大值点

        跟AI(智谱、通义)进行一顿交互、辩说得到的极大值寻找函数(不是最优),用于获取幅值谱中峰值的索引。窗口大小不能太小,否则容易误判,太大则会找不到几个极大值点。
  1. static inline void find_peaks(const float32_t *fftOutput, uint16_t fftLength, uint16_t *peaks, uint32_t NumPeaks, uint16_t windowSize)
  2. {
  3.     float32_t maxVal;
  4.     uint16_t maxIdx;
  5.     uint16_t halfWindowSize = windowSize / 2;//以一般理性而言,不会为0
  6.     uint16_t numPeaks = 1;                   //从一次谐波开始
  7.     //重置
  8.     for (uint16_t i = 0; i <NumPeaks ; ++i)
  9.     {
  10.         peaks[i] = 0;
  11.     }
  12.     // 开始寻找交流成分
  13.     for (uint16_t i = halfWindowSize; i < 512 - halfWindowSize; i++)
  14.     {
  15.         maxVal = fftOutput[i];
  16.         maxIdx = i;
  17.         // 在滑动窗口内查找最大值
  18.         for (uint16_t j = i - halfWindowSize; j <= i + halfWindowSize; j++)
  19.         {
  20.             if (fftOutput[j] > maxVal)
  21.             {
  22.                 maxVal = fftOutput[j];
  23.                 maxIdx = j;
  24.             }
  25.         }
  26.         // 检查窗口中心点是否为窗口内的最大值
  27.         if (maxIdx == i)
  28.         {
  29.             // 检查是否与最近的极大值足够远
  30.             if (peaks[numPeaks] == 0 || (i - peaks[numPeaks - 1]) > windowSize)
  31.             {
  32.                 peaks[numPeaks] = i;//peaks存的是索引
  33.                 ++numPeaks;
  34.                 //存满以后就退出
  35.                 if (numPeaks == NumPeaks)
  36.                     return;
  37.             }
  38.         }
  39.     }
  40. }
复制代码

②功率谱

        根据前面MATLAB分析,只需对幅值谱(准确说是幅值谱中的极大值)进行平方和归一化处理即可
   Tips:
          之前做功率谱分析时,错误地将已转换为电压幅值谱当做输入。应将经由arm_cmplx_mag_f32得到的幅值谱直接转换为功率谱,不必经由第三方。
  1. static inline float32_t powerCalculate(const float32_t *fft_Array, const uint16_t *peaks, uint16_t NumPeaks, float *power)
  2. {
  3.     float sum = 0;
  4.     //总功率应去除直流量
  5.     for (uint16_t i = 0; i <NumPeaks ; ++i)
  6.     {
  7.         power[i]=0;
  8.     }
  9.     for (uint16_t i = 1; i < NumPeaks; i++)
  10.     {
  11.         if (peaks[i] == 0)
  12.         {
  13.             break;//说明谐波已经取完
  14.         }
  15.         power[i] = fft_Array[peaks[i]] * fft_Array[peaks[i]] * 2 / NUM_SAMPLES;//由于只取正频率,故进行归一化处理
  16.         sum += power[i];
  17.     }
  18.     return sum;
  19. }
复制代码

③幅值谱转为对应的电压幅值谱

        听起来拗口,其实还挺变扭。就是把幅值谱中的幅值转为实际上的电压,因为幅值谱中的幅值代表的物理意义并非是电压,不外它与电压有个对应关系(解释里)。
  1. static inline void voltageAmplitude_Convert(float32_t inputArray[], float32_t outputArray[])
  2. {
  3.     //假设原始信号的峰值为A,那么FFT的结果的每个点(除了第一个点直流分量之外)的模值就是A的N/2倍。而第一个点就是直流分量,它的模值就是直流分量的N倍。
  4.     /**将幅值谱转为电压幅值谱*/
  5.     for (uint16_t i = 1; i < NUM_SAMPLES / 2; ++i)
  6.     {
  7.         //理论上模值为峰峰值的N/2倍,实测中还应再除以0.75,后来发现就不需要了
  8.         fft_outputbuf[i] = (float32_t) (fft_outputbuf[i] * 2 / NUM_SAMPLES);
  9.     }
  10.     fft_outputbuf[0] /= NUM_SAMPLES;
  11. }
复制代码

④失真度盘算

        二次及二次以上的谐波身分的平方和除以基波的平方,然后再开方。
        此时你会发现,谐波应为基波的整数倍,也就是说除了通过查找多个峰值的办法,还可以先找到第一个峰值的索引(需先去除直流量),然后把这个索引乘以相应倍数获得对应谐波。CMSIS-DSP有这个找峰值的函数,叫arm_max_f32,记得要去除直流身分。
  1. static inline void signalDistortionDegree(const float32_t *fft_Array, const uint16_t *peaks, uint16_t NumPeaks, float *thd)
  2. {
  3.     float sum = 0;
  4.     //从二次谐波开始计算平方和
  5.     for (uint16_t i = 2; i < NumPeaks; i++)
  6.     {
  7.         if (peaks[i] == 0)
  8.         {
  9.             break;//说明谐波已经取完
  10.         }
  11.         sum += fft_Array[peaks[i]] * fft_Array[peaks[i]];//平方和
  12.     }
  13.     sum /= (fft_Array[peaks[1]] * fft_Array[peaks[1]]);//除以1次谐波的平方
  14.     *thd = sqrtf(sum) * 100;                           //计算出失真度,并转为百分比表示
  15. }
复制代码

3,代码

        这里面需要关注的主体函数是 void SignalAnalyzer_handler();这个函数是放到while大循环里的(MSPM0的内存实在太小,只能先放弃RTOS)
        里面可能有部分解释忘记修改,还请见谅。
  1. //// Created by 34753 on 2024/7/18.//#include "SignalAnalyzer.h"#if SignalAnalyzer_Open#include "ADC.h"#include "LED.h"#include "OPA.h"#include "arm_const_structs.h"#include "arm_math.h"/**宏界说*/#define NUM_SAMPLES 1024//采样点#define AV 1           //放大增益#define NUM_PEAKS 9     //取9-1个基波和谐波身分#define IFFTFLAG 0  //正变换#define BITREVERSE 1//逆序排列/**变量*/int16_t ADC_Data[1024];                  //ADC采样数据float32_t fft_inputBuff[NUM_SAMPLES * 2];//存储复数的数组float32_t fft_outputbuf[NUM_SAMPLES / 2];//存储实数的数组,由于奈奎斯特的特性,需要除以2// 为了某种目标,包罗了直流身分uint16_t peaks[NUM_PEAKS];//幅值谱的极大值点float power[NUM_PEAKS];   //功率谱float totalPower;         //总功率单位为V^2float thd;                //失真度volatile bool waitADCData_Flag = true;//用于检测是否需要等待void SignalAnalyzer_Init(){    OPA_Init();    ADC_DMA_Init(ADC_Data, 1024);}#if 0//盘算失真度void THD(void){    thd_basic = fft_outputbuf[10];    u[0] = fft_outputbuf[20];    u[1] = fft_outputbuf[30];    u[2] = fft_outputbuf[40];    u[3] = fft_outputbuf[50];    u[4] = fft_outputbuf[60];    arm_power_f32(u, 4, &sum);    arm_sqrt_f32(sum, &thd_high);    thd = thd_high / thd_basic;}//盘算总功率和各频率分量的频率和功率void MW(void){    thd_basic = fft_outputbuf[10];    u[0] = fft_outputbuf[20];    u[1] = fft_outputbuf[30];    u[2] = fft_outputbuf[40];    u[3] = fft_outputbuf[50];    u[4] = fft_outputbuf[60];    arm_power_f32(&u[0], 4, &sum);    MW_total = sum / 5;}#endif/** * @brief   频率谱转为电压幅值谱 * @param inputArray 经fft转换后的频率谱数组 * @param outputArray 输出的电压幅值谱数组 * @note    本来需要在实测中除以0.75,后来就不需要了,原因未知 */static inline void voltageAmplitude_Convert(float32_t inputArray[], float32_t outputArray[]){    //假设原始信号的峰值为A,那么FFT的结果的每个点(除了第一个点直流分量之外)的模值就是A的N/2倍。而第一个点就是直流分量,它的模值就是直流分量的N倍。    /**将幅值谱转为电压幅值谱*/    for (uint16_t i = 1; i < NUM_SAMPLES / 2; ++i)    {        //理论上模值为峰峰值的N/2倍,实测中还应再除以0.75,后来发现就不需要了        fft_outputbuf[i] = (float32_t) (fft_outputbuf[i] * 2 / NUM_SAMPLES);//384    }    fft_outputbuf[0] /= NUM_SAMPLES;}static inline void Amplitude_Convert(float32_t inputArray[], float32_t outputArray[]){    //盘算信号的幅度谱  第一个参数指定了需要盘算复数模的数组指针,第二个参数指定了盘算结果存放的数组指针,第三个参数是需要盘算复数模的数据个数。    // 分辨率=fs(采样频率)/N(采样点数)  output输出数组,索引*分辨率=频率身分    arm_cmplx_mag_f32(inputArray, outputArray, NUM_SAMPLES / 2);}/** * @brief   ADC数据转为频谱 * @param ADCdata   ADC采样数据 * @param fftArray   频谱数组 */static inline void ADCdataToSpectrum(const int16_t ADCdata[], float32_t fftArray[]){    /**将实数序列转为复数序列*/    for (uint32_t i = 0; i < NUM_SAMPLES; ++i)    {        fftArray[i * 2] = (float) (ADCdata[i] * 3.3 / (4095 * AV));//转为实际电压,单位为V        fftArray[i * 2 + 1] = 0;                                   //虚部为零    }    假设ADC里的就是电压值V,可以为mV,不外功率的单位应该为uW    //    for (uint32_t i = 0; i < NUM_SAMPLES; ++i)    //    {    //        //w[i]=0.5*(1-arm_sin_f32(2*PI*i/(NUM_SAMPLES-1)));    //        fftArray[i * 2] = (float) (ADCdata[i] * 0.001);    //        //  fftArray[i * 2]=fftArray[i * 2]*w[i];    //        fftArray[i * 2 + 1] = 0;    //    }    /** * 对ADC数据进行1024点的复数快速傅里叶变换(FFT)。 * * ADC数据被转换到频域,存储了1024个复数样本。 * 每个元素存储复数的实部和虚部,实部存储在偶数索引位置,虚部存储在奇数索引位置。 * * FFT输出存储在ADC_DataOut中,包罗了每个频率分量的幅度信息。 * 返回ADC_DataOut中幅度最大的频率分量的索引,即第5个FFT点(0索引)。 * * @param ADC_Data      输入的ADC数据数组 * @param ADC_DataOut   输出的FFT结果数组 * @param arm_max_q15    用于在ADC_DataOut中找到最大幅度值及其索引的函数 */    arm_cfft_f32(&arm_cfft_sR_f32_len1024, fftArray, IFFTFLAG, BITREVERSE);}/** * @brief   寻找峰值 * @param fftOutput * @param fftLength * @param peaks 存储极大值点的数组 * @param numPeaks  极大值点个数 * @param windowSize 窗口大小应为奇数,太小会错过极大值,太大会判断错误极大值 */static inline void find_peaks(const float32_t *fftOutput, uint16_t fftLength, uint16_t *peaks, uint32_t NumPeaks, uint16_t windowSize)
  2. {
  3.     float32_t maxVal;
  4.     uint16_t maxIdx;
  5.     uint16_t halfWindowSize = windowSize / 2;//以一般理性而言,不会为0
  6.     uint16_t numPeaks = 1;                   //从一次谐波开始
  7.     //重置
  8.     for (uint16_t i = 0; i <NumPeaks ; ++i)
  9.     {
  10.         peaks[i] = 0;
  11.     }
  12.     // 开始寻找交流成分
  13.     for (uint16_t i = halfWindowSize; i < 512 - halfWindowSize; i++)
  14.     {
  15.         maxVal = fftOutput[i];
  16.         maxIdx = i;
  17.         // 在滑动窗口内查找最大值
  18.         for (uint16_t j = i - halfWindowSize; j <= i + halfWindowSize; j++)
  19.         {
  20.             if (fftOutput[j] > maxVal)
  21.             {
  22.                 maxVal = fftOutput[j];
  23.                 maxIdx = j;
  24.             }
  25.         }
  26.         // 检查窗口中心点是否为窗口内的最大值
  27.         if (maxIdx == i)
  28.         {
  29.             // 检查是否与最近的极大值足够远
  30.             if (peaks[numPeaks] == 0 || (i - peaks[numPeaks - 1]) > windowSize)
  31.             {
  32.                 peaks[numPeaks] = i;//peaks存的是索引
  33.                 ++numPeaks;
  34.                 //存满以后就退出
  35.                 if (numPeaks == NumPeaks)
  36.                     return;
  37.             }
  38.         }
  39.     }
  40. }/** * @brief  盘算失真度 * @param fft_Array * @param peaks * @param NumPeaks * @param thd * @note    失真度的单位是百分比 */static inline void signalDistortionDegree(const float32_t *fft_Array, const uint16_t *peaks, uint16_t NumPeaks, float *thd){    float sum = 0;    //从二次谐波开始盘算平方和    for (uint16_t i = 2; i < NumPeaks; i++)    {        if (peaks[i] == 0)        {            break;//说明谐波已经取完        }        sum += fft_Array[peaks[i]] * fft_Array[peaks[i]];//平方和    }    sum /= (fft_Array[peaks[1]] * fft_Array[peaks[1]]);//除以1次谐波的平方    *thd = sqrtf(sum) * 100;                           //盘算出失真度}/** * @brief  盘算功率 * @param fft_Array * @param peaks * @param NumPeaks * @param power * @return * @note    为了整齐划一,我把直流量放在power[0]中。同时要注意 * ①由于数据是采集的电压,那么盘算出的功率的单位其实是V^2(不思量功率谱密度),若想得到mW,那么需要再除以阻抗 * ②想通过帕斯瓦尔定理得到功率,那么必须是直接从频率谱转换得到幅值谱,而非是电压幅值谱 * ③若电压的单位是mV,那么盘算出的功率的单位是uW */static inline float32_t powerCalculate(const float32_t *fft_Array, const uint16_t *peaks, uint16_t NumPeaks, float *power)
  41. {
  42.     float sum = 0;
  43.     //总功率应去除直流量
  44.     for (uint16_t i = 0; i <NumPeaks ; ++i)
  45.     {
  46.         power[i]=0;
  47.     }
  48.     for (uint16_t i = 1; i < NumPeaks; i++)
  49.     {
  50.         if (peaks[i] == 0)
  51.         {
  52.             break;//说明谐波已经取完
  53.         }
  54.         power[i] = fft_Array[peaks[i]] * fft_Array[peaks[i]] * 2 / NUM_SAMPLES;//由于只取正频率,故进行归一化处理
  55.         sum += power[i];
  56.     }
  57.     return sum;
  58. }///**// * @brief  盘算指定频率下的功率// * @param fft_Array// * @param fft_output// */////static inline void powerSpecialCalculate(const float32_t *fft_Array, float *fft_output)//{//    float sum = 0;//    for (uint16_t i = 50; i < 500; i+=50)//    {//        power[i] = fft_Array[i] * fft_Array[i] * 2 / NUM_SAMPLES;//由于只取正频率,故进行归一化处理//        sum += power[i];//    }//    return sum;//}/** * @brief  转为实际功率 * @param totalPower * @param power * @param Z * @param NumPeaks */static inline void realPower_Convert(float *totalPower, float *power, float Z, uint16_t NumPeaks){    for (uint16_t i = 0; i < NumPeaks; i++)    {        power[i] /= Z;    }    *totalPower /= Z;}void SignalAnalyzer_handler(){    while (waitADCData_Flag)        ;    waitADCData_Flag = true;    //    LED_ON(LED2_Blue);//表明转换开启    ADCdataToSpectrum(ADC_Data, fft_inputBuff);//将ADC数据经FFT 转为频谱    Amplitude_Convert(fft_inputBuff, fft_outputbuf);//转为幅值谱    find_peaks(fft_outputbuf, NUM_SAMPLES / 2, peaks, NUM_PEAKS, 41);//寻找极大值点    totalPower = powerCalculate(fft_outputbuf, peaks, NUM_PEAKS, power);//盘算出功率V^2    realPower_Convert(&totalPower, power, 1788.1, NUM_PEAKS);//转为实际功率W    voltageAmplitude_Convert(fft_inputBuff, fft_outputbuf);//把频率谱转为电压幅值谱(可以不用加,因为交换身分彼此成齐次性)    signalDistortionDegree(fft_outputbuf, peaks, NUM_PEAKS, &thd);//盘算出失真度    //    LED_OFF(LED2_Blue);    //    __BKPT(1);}/**ISR停止服务*/void ADC12_0_INST_IRQHandler(void){    switch (DL_ADC12_getPendingInterrupt(ADC12_0_INST))    {        case DL_ADC12_IIDX_DMA_DONE:            /**开启数据分析*/            ADC_DMA_Stop();            waitADCData_Flag = false;            LED_Toggle(LED2_Blue);            break;        default:            break;    }}#if 0/***********************************************找最大值,次大值……对应的频率,分析波形*************************************************/void select_max(float *f, float *a){    int i, j;    float k, k1, m;    float aMax = 0.0, aSecondMax = 0.0, aThirdMax = 0.0, aFourthMax = 0.0;    float fMax = 0.0, fSecondMax = 0.0, fThirdMax = 0.0, fFourthMax = 0.0;    int nMax = 0, nSecondMax = 0, nThirdMax = 0, nFourthMax = 0;    for (i = 1; i < NUM_SAMPLES / 2; i++)//i必须是1,是0的话,会把直流分量加进去!!!!    {        if (a[i] > aMax)        {            aMax = a[i];            nMax = i;            fMax = f[nMax];        }    }    for (i = 1; i < NUM_SAMPLES / 2; i++)    {        if (nMax == i)        {            continue;//跳过原来最大值的下标,直接开始i+1的循环        }        if (a[i] > aSecondMax && a[i] > a[i + 1] && a[i] > a[i - 1])        {            aSecondMax = a[i];            nSecondMax = i;            fSecondMax = f[nSecondMax];        }    }    for (i = 1; i < NUM_SAMPLES / 2; i++)    {        if (nMax == i || nSecondMax == i)        {            continue;//跳过原来最大值的下标,直接开始i+1的循环        }        if (a[i] > aThirdMax && a[i] > a[i + 1] && a[i] > a[i - 1])        {            aThirdMax = a[i];            nThirdMax = i;            fThirdMax = f[nThirdMax];        }    }    for (i = 1; i < NUM_SAMPLES / 2; i++)    {        if (nMax == i || nSecondMax == i || nThirdMax == i)        {            continue;//跳过原来最大值的下标,直接开始i+1的循环        }        if (a[i] > aFourthMax && a[i] > a[i + 1] && a[i] > a[i - 1])        {            aFourthMax = a[i];            nFourthMax = i;            fFourthMax = f[nFourthMax];        }    }    k = fabsf(2 * fMax - fSecondMax);    k1 = fabsf(3 * fMax - fSecondMax);    m = fabsf((float) (aMax - 3.0 * aSecondMax));    //    if(k<=5)    //        LCD_ShowString(275,230,12*4,12,12,"JvChi  ");    //    else if(k1<=5&&m<0.4)    //        LCD_ShowString(275,230,12*4,12,12,"Fang   ");    //    else if(k1<=5&&m>=0.4)    //        LCD_ShowString(275,230,12*4,12,12,"SanJiao");    //    else LCD_ShowString(275,230,12*4,12,12,"Sin    ");}#endif#endif
复制代码

四、总结(吐槽)

        从STM32换成TI开发挺不风俗的,在STM32里可以各种手搓寄存器,结果到TI这里有一种到处受限的感觉。最直接的表现就是创建工程,充分让你体会到了什么叫不得自由,什么工程必须从模板库里创建否则用不了sysconfig(除非想与TI库绝缘)、想利用gcc编译器就必须下载9.2版本等等,稍微对工程做了一点点修改就直接崩溃了,照旧修不好的那种。同时TI又不支持OpenOCD,想利用GDB但板子好像只支持TI官方的调试工具(总之很难),导致CLion直接被禁。然后就是几天的挣扎,好不容易才得到一个可以运行、稍微符合开发风俗的工程,没敢改太多。
        然后就是TI的驱动库,虽然模板、文档许多,但是国内网上的教程过少(无论中英文),到处是坑。最让我无法理解的就是GPIO的配置,在stm32中利用寄存器配置GPIO时,只需要找到对应端口和引脚数就行,结果TI里还需要下面这个东西,what can I say?这除了sysconfig和直接查芯片,根本不知道啊
  1. #define GPIO_OLED_DC_IOMUX (IOMUX_PINCM35)
复制代码
         吐槽归吐槽,经过一段时间的熟悉后,CCS照旧很好用的,好比谁人图形化展示数据,分析信号时简直如同神助。当初觉得难估计是从stm32猛地转ti导致的,反过来大概也是一样的吧。

        除此之外,再说一下浅显的个人履历,再利用sysconfig初始化工程时,由于风俗了stm32的开发,对于这种纯图形化反而不顺应(与CubeMX差别),有时看着配置选项并不如直接看代码清晰,偏偏偏置选项和生成的代码还有些反差萌。所以和之前利用CubeMX一样,只是把它当做一个学习的工具:
        导入示例工程,咔咔乱改,生成符合需求的可用代码,把代码添加到自己新建的文件中,并标上解释,趁热打铁。
        就好比下面的SPI.c/h文件:
  1. //
  2. // Created by 34753 on 2024/7/20.
  3. //
  4. #ifndef ISB_TI_SPI_H
  5. #define ISB_TI_SPI_H
  6. #include "ZQ_Conf.h"
  7. #if USE_SPI
  8. #include "ti_init.h"
  9. #define SPI_0_INST SPI1
  10. void SPI_Init(void);
  11. void SPI_DMA_Init(void);/*不可用*/
  12. void SPI_DMA_Send(uint8_t *data, uint16_t len);/*不可用*/
  13. void SPI_DMA_Repeat_SendWord(uint16_t data, uint16_t times);/**不可用*/
  14. void SPI_DMA_WriteByte(uint8_t data);
  15. __STATIC_INLINE void SPI_WriteByte(uint8_t data)
  16. {
  17.     while (DL_SPI_isBusy(SPI_0_INST))
  18.         ;
  19.     DL_SPI_transmitData8(SPI_0_INST, data);
  20. }
  21. //void SPI_WriteWord(uint16_t data);
  22. void SPI_SendData(uint8_t *data, uint16_t len);
  23. #endif
  24. #endif//ISB_TI_SPI_H
复制代码
  1. //
  2. // Created by 34753 on 2024/7/20.
  3. //
  4. #include "SPI.h"
  5. #if USE_SPI
  6. #include "LED.h"
  7. /* Defines for SPI_0 */
  8. #define SPI_0_INST_IRQHandler SPI1_IRQHandler
  9. #define SPI_0_INST_INT_IRQN SPI1_INT_IRQn
  10. #define GPIO_SPI_0_PICO_PORT GPIOB
  11. #define GPIO_SPI_0_PICO_PIN DL_GPIO_PIN_8
  12. #define GPIO_SPI_0_IOMUX_PICO (IOMUX_PINCM25)
  13. #define GPIO_SPI_0_IOMUX_PICO_FUNC IOMUX_PINCM25_PF_SPI1_PICO
  14. #define GPIO_SPI_0_POCI_PORT GPIOB
  15. #define GPIO_SPI_0_POCI_PIN DL_GPIO_PIN_7
  16. #define GPIO_SPI_0_IOMUX_POCI (IOMUX_PINCM24)
  17. #define GPIO_SPI_0_IOMUX_POCI_FUNC IOMUX_PINCM24_PF_SPI1_POCI
  18. /* GPIO configuration for SPI_0 */
  19. #define GPIO_SPI_0_SCLK_PORT GPIOB
  20. #define GPIO_SPI_0_SCLK_PIN DL_GPIO_PIN_16
  21. #define GPIO_SPI_0_IOMUX_SCLK (IOMUX_PINCM33)
  22. #define GPIO_SPI_0_IOMUX_SCLK_FUNC IOMUX_PINCM33_PF_SPI1_SCLK
  23. #define GPIO_SPI_0_CS1_PORT GPIOB
  24. #define GPIO_SPI_0_CS1_PIN DL_GPIO_PIN_17
  25. #define GPIO_SPI_0_IOMUX_CS1 (IOMUX_PINCM43)
  26. #define GPIO_SPI_0_IOMUX_CS1_FUNC IOMUX_PINCM43_PF_SPI1_CS1_POCI1
  27. /* Defines for DMA_CH1 */
  28. #define DMA_CH1_CHAN_ID (1)
  29. #define SPI_0_INST_DMA_TRIGGER (DMA_SPI1_TX_TRIG)
  30. static volatile bool SPI_DMA_Mode_Flag = false;//默认模式
  31. static const DL_SPI_Config gSPI_0_config = {
  32.         .mode = DL_SPI_MODE_CONTROLLER,
  33.         .frameFormat = DL_SPI_FRAME_FORMAT_MOTO4_POL1_PHA1,
  34.         .parity = DL_SPI_PARITY_NONE,
  35.         .dataSize = DL_SPI_DATA_SIZE_8,
  36.         .bitOrder = DL_SPI_BIT_ORDER_MSB_FIRST,
  37.         .chipSelectPin = DL_SPI_CHIP_SELECT_1,
  38. };
  39. static const DL_SPI_ClockConfig gSPI_0_clockConfig = {
  40.         .clockSel = DL_SPI_CLOCK_BUSCLK,
  41.         .divideRatio = DL_SPI_CLOCK_DIVIDE_RATIO_1};
  42. static const DL_DMA_Config gDMA_CH1Config = {
  43.         .transferMode = DL_DMA_SINGLE_TRANSFER_MODE,//不可使用RepeatSingle,否则会出现乱波
  44.         .extendedMode = DL_DMA_NORMAL_MODE,
  45.         .destIncrement = DL_DMA_ADDR_UNCHANGED,
  46.         .srcIncrement = DL_DMA_ADDR_INCREMENT,
  47.         .destWidth = DL_DMA_WIDTH_BYTE,
  48.         .srcWidth = DL_DMA_WIDTH_BYTE,
  49.         .trigger = SPI_0_INST_DMA_TRIGGER,
  50.         .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL,
  51. };
  52. void SPI_Init(void)
  53. {
  54.     DL_SPI_reset(SPI_0_INST);
  55.     DL_SPI_enablePower(SPI_0_INST);
  56.     /**GPIO初始化*/
  57.     DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_SCLK, GPIO_SPI_0_IOMUX_SCLK_FUNC);
  58.     DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_PICO, GPIO_SPI_0_IOMUX_PICO_FUNC);
  59.     DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_CS1, GPIO_SPI_0_IOMUX_CS1_FUNC);
  60.     DL_SPI_Config gSPI_0_config = {
  61.             .mode = DL_SPI_MODE_CONTROLLER,
  62.             .frameFormat = DL_SPI_FRAME_FORMAT_MOTO4_POL0_PHA0,
  63.             .parity = DL_SPI_PARITY_NONE,
  64.             .dataSize = DL_SPI_DATA_SIZE_8,
  65.             .bitOrder = DL_SPI_BIT_ORDER_MSB_FIRST,
  66.             .chipSelectPin = DL_SPI_CHIP_SELECT_1,
  67.     };
  68.     DL_SPI_setClockConfig(SPI_0_INST, (DL_SPI_ClockConfig *) &gSPI_0_clockConfig);
  69.     DL_SPI_init(SPI_0_INST, &gSPI_0_config);
  70.     /* Configure Controller mode */
  71.     /*
  72.      * Set the bit rate clock divider to generate the serial output clock
  73.      *     outputBitRate = (spiInputClock) / ((1 + SCR) * 2)
  74.      *     500000 = (32000000)/((1 + 31) * 2)
  75.      */
  76.     //  TFT_4SPI最快为  BUS_CLK:80MHz,不分频;SPI: 2分频(添1)
  77.     //  OLED_4SPI最快为    BUS_CLK:80MHz,不分频;SPI: 4分频(添3)
  78.     DL_SPI_setBitRateSerialClockDivider(SPI_0_INST, 3);
  79.     /* Set RX and TX FIFO threshold levels */
  80.     DL_SPI_setFIFOThreshold(SPI_0_INST, DL_SPI_RX_FIFO_LEVEL_1_2_FULL, DL_SPI_TX_FIFO_LEVEL_1_2_EMPTY);
  81.     /* Enable module */
  82.     DL_SPI_enable(SPI_0_INST);
  83. }
  84. //void SPI_WriteWord(uint16_t data)
  85. //{
  86. //    while (DL_SPI_isBusy(SPI_0_INST))
  87. //        ;
  88. //    DL_SPI_transmitData16(SPI_0_INST, data);
  89. //}
  90. void SPI_SendData(uint8_t *data, uint16_t len)
  91. {
  92.     uint16_t i = 0;
  93.     for (i = 0; i < len; i++)
  94.     {
  95.         while (DL_SPI_isBusy(SPI_0_INST))
  96.             ;
  97.         DL_SPI_transmitData8(SPI_0_INST, data[i]);
  98.     }
  99. }
  100. void SPI_DMA_Init(void)
  101. {
  102. #if !USE_TI_SYSCONFIG
  103.     DL_SPI_reset(SPI_0_INST);
  104.     DL_SPI_enablePower(SPI_0_INST);
  105.     /**GPIO初始化*/
  106.     DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_SCLK, GPIO_SPI_0_IOMUX_SCLK_FUNC);
  107.     DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_PICO, GPIO_SPI_0_IOMUX_PICO_FUNC);
  108.     DL_GPIO_initPeripheralInputFunction(GPIO_SPI_0_IOMUX_POCI, GPIO_SPI_0_IOMUX_POCI_FUNC);
  109.     DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_CS1, GPIO_SPI_0_IOMUX_CS1_FUNC);
  110.     /**SPI初始化*/
  111.     DL_SPI_setClockConfig(SPI_0_INST, (DL_SPI_ClockConfig *) &gSPI_0_clockConfig);
  112.     DL_SPI_init(SPI_0_INST, (DL_SPI_Config *) &gSPI_0_config);
  113.     /* Configure Controller mode */
  114.     /*
  115.      * Set the bit rate clock divider to generate the serial output clock
  116.      *     outputBitRate = (spiInputClock) / ((1 + SCR) * 2)
  117.      *     5000000 = (80000000)/((1 + 7) * 2)
  118.      */
  119.     DL_SPI_setBitRateSerialClockDivider(SPI_0_INST, 7);
  120.     /* Enable SPI TX interrupt as a trigger for DMA */
  121.     DL_SPI_enableDMATransmitEvent(SPI_0_INST);
  122.     /* Set RX and TX FIFO threshold levels */
  123.     DL_SPI_setFIFOThreshold(SPI_0_INST, DL_SPI_RX_FIFO_LEVEL_ONE_FRAME, DL_SPI_TX_FIFO_LEVEL_ONE_FRAME);
  124.     DL_SPI_enableInterrupt(SPI_0_INST, (DL_SPI_INTERRUPT_DMA_DONE_TX |
  125.                                         DL_SPI_INTERRUPT_TX_EMPTY));
  126.     /* Enable module */
  127.     DL_SPI_enable(SPI_0_INST);
  128.     /**DMA初始化*/
  129.     DL_DMA_initChannel(DMA, DMA_CH1_CHAN_ID, (DL_DMA_Config *) &gDMA_CH1Config);
  130. #endif
  131.     /**开启SPI中断*/
  132.     NVIC_EnableIRQ(SPI_0_INST_INT_IRQN);
  133. }
  134. void SPI_DMA_Send(uint8_t *data, uint16_t len)
  135. {
  136.     DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) data);
  137.     DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) (&SPI_0_INST->TXDATA));
  138.     DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, len);
  139.     DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID);
  140. }
  141. void SPI_DMA_WriteByte(uint8_t data)
  142. {
  143.     DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) data);
  144.     DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) (&SPI_0_INST->TXDATA));
  145.     DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, 1);
  146.     DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID);
  147. }
  148. /**
  149. * @brief   重复传送某字节
  150. * @param data
  151. * @param times
  152. * @note    首先会把模式调为单次模式,然后再把数据写入DMA,最后再开启DMA传输结束后注意把模式切换回来。
  153. */
  154. void SPI_DMA_Repeat_SendWord(uint16_t data, uint16_t times)
  155. {
  156.     DL_DMA_Config gDMA_CH1Config_temp = {
  157.             .transferMode = DL_DMA_SINGLE_TRANSFER_MODE,//不可使用RepeatSingle,否则会出现乱波
  158.             .extendedMode = DL_DMA_NORMAL_MODE,
  159.             .destIncrement = DL_DMA_ADDR_UNCHANGED,
  160.             .srcIncrement = DL_DMA_ADDR_UNCHANGED,
  161.             .destWidth = DL_DMA_WIDTH_HALF_WORD,
  162.             .srcWidth = DL_DMA_WIDTH_HALF_WORD,
  163.             .trigger = SPI_0_INST_DMA_TRIGGER,
  164.             .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL,
  165.     };
  166.     SPI_DMA_Mode_Flag = true;//模式改变
  167.     DL_DMA_initChannel(DMA, DMA_CH1_CHAN_ID, &gDMA_CH1Config_temp);
  168.     DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) &data);
  169.     DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) (&SPI_0_INST->TXDATA));
  170.     DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, times);
  171.     DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID);
  172. }
  173. void SPI_RecoverMode(void)
  174. {
  175.     DL_DMA_initChannel(DMA, DMA_CH1_CHAN_ID, (DL_DMA_Config *) &gDMA_CH1Config);
  176. }
  177. void SPI_0_INST_IRQHandler(void)
  178. {
  179.     switch (DL_SPI_getPendingInterrupt(SPI_0_INST))
  180.     {
  181.         case DL_SPI_IIDX_DMA_DONE_TX:
  182.             LED_OFF(LED2_Green);
  183.             LED_ON(LED2_Red);
  184.             break;
  185.         case DL_SPI_IIDX_TX_EMPTY:
  186.             //重置DMA模式
  187.             if (SPI_DMA_Mode_Flag)
  188.                 SPI_RecoverMode();
  189.             LED_OFF(LED2_Red);
  190.             break;
  191.         default:
  192.             break;
  193.     }
  194. }
  195. #endif
复制代码


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

石小疯

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表