一、简介
设备:MSPM0G3507
库:CMSIS-DSP TI
数据分析:FFT
软件:CCS CLion MATLAB
目的:对音频信号进行采样(滤波+偏置处理),通过FFT获取信号的频率成分,得到频率谱、幅值谱和功率谱
二、可行性分析
1,MATLAB
①先设置一个序列模拟实际采样数据
假设采样频率为20.48KHz,那么我们可以设置一个频率为1/20Hz的正弦函数来生成采样数据,采样数据所代表的波形为1.024KHz的正弦波
f = 1/20; X = (i*sin(2*pi*f*t) + 1000)*0.001; %电压
②将采样数据通过FFT转为频谱
Y = fft(X);
③对频谱进行取模处理,得到幅值谱
A = abs(Y); % 幅度谱
④通过帕斯瓦尔定理,将幅值谱转为功率谱
根据帕斯瓦尔定理,可得,即幅值谱数据的平方除以2等于采样数据的平方。
但由于只取正频率,那么还需要乘以2
P = A.^2 / (N/2);
⑤注意事项
得到的功率谱由于是直接通过模长的平方得到,那么其单位应为(采样数据的单位V),并非
2,代码
clc; clear all; close all; % 1. 构建一个序列x N = 1024; t = (0:N-1)'; % 创建时间向量 f = 1/20; % 假设ADC采样频率为20.48KHz,那么这个序列为20.48/20KHz的正弦波 yuan=[2.5,10,22.5,40,62.5,90,122.5,160];%源自实际信号功率 j=1; for i=100:100:800 X = (i*sin(2*pi*f*t) + 1000)*0.001; %电压 % 2. 转为频域 Y = fft(X); % 3. 把Y转为幅值谱 A = abs(Y); % 幅度谱 frequencies = (1:1: N/2+1); % 频率轴 A = A(2:N/2+1); % 只取正频率部分,去除直流分量 % 4. 使用帕斯瓦尔定理转为功率谱 % Parseval定理表明时域能量等于频域能量 % 所以功率谱可以通过幅度谱的平方获得 P = A.^2 / (N/2); % 功率谱,归一化因子为N/2,因为我们只考虑正频率部分 % 画图 figure; subplot(2,1,1); plot(frequencies(2:end), A); % 从第二个点开始绘制,排除直流分量 title('Amplitude Spectrum'); xlabel('Frequency (Hz)'); ylabel('Magnitude'); subplot(2,1,2); plot(frequencies(2:end), P); % 从第二个点开始绘制,排除直流分量 title('Power Spectrum'); xlabel('Frequency (Hz)'); ylabel('Power'); % 打印功率谱中的极大值 [maxP, indexMaxP] = max(P); fprintf('%dmV:功率谱中的极大值位于频率 %d ,功率值为 %fV(或者V^2)\n', i,frequencies(indexMaxP), maxP); fprintf('实际功率值为 %fmW,则阻抗为:%f\n\n',yuan(j),maxP/yuan(j)*1000); j=j+1; 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:幅值?反正把频谱转为幅值谱
arm_cfft_f32(&arm_cfft_sR_f32_len1024, fftArray, IFFTFLAG, BITREVERSE);
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(智谱、通义)进行一顿交互、辩论得到的极大值寻找函数(不是最优),用于获取幅值谱中峰值的索引。窗口大小不能太小,否则容易误判,太大则会找不到几个极大值点。
static inline void find_peaks(const float32_t *fftOutput, uint16_t fftLength, uint16_t *peaks, uint32_t NumPeaks, uint16_t windowSize) { float32_t maxVal; uint16_t maxIdx; uint16_t halfWindowSize = windowSize / 2;//以一般理性而言,不会为0 uint16_t numPeaks = 1; //从一次谐波开始 //重置 for (uint16_t i = 0; i <NumPeaks ; ++i) { peaks[i] = 0; } // 开始寻找交流成分 for (uint16_t i = halfWindowSize; i < 512 - halfWindowSize; i++) { maxVal = fftOutput[i]; maxIdx = i; // 在滑动窗口内查找最大值 for (uint16_t j = i - halfWindowSize; j <= i + halfWindowSize; j++) { if (fftOutput[j] > maxVal) { maxVal = fftOutput[j]; maxIdx = j; } } // 检查窗口中心点是否为窗口内的最大值 if (maxIdx == i) { // 检查是否与最近的极大值足够远 if (peaks[numPeaks] == 0 || (i - peaks[numPeaks - 1]) > windowSize) { peaks[numPeaks] = i;//peaks存的是索引 ++numPeaks; //存满以后就退出 if (numPeaks == NumPeaks) return; } } } }
②功率谱
根据前面MATLAB分析,只需对幅值谱(准确说是幅值谱中的极大值)进行平方和归一化处理即可
Tips:
之前做功率谱分析时,错误地将已转换为电压幅值谱当做输入。应将经由arm_cmplx_mag_f32得到的幅值谱直接转换为功率谱,不必经由第三方。
static inline float32_t powerCalculate(const float32_t *fft_Array, const uint16_t *peaks, uint16_t NumPeaks, float *power) { float sum = 0; //总功率应去除直流量 for (uint16_t i = 0; i <NumPeaks ; ++i) { power[i]=0; } for (uint16_t i = 1; i < NumPeaks; i++) { if (peaks[i] == 0) { break;//说明谐波已经取完 } power[i] = fft_Array[peaks[i]] * fft_Array[peaks[i]] * 2 / NUM_SAMPLES;//由于只取正频率,故进行归一化处理 sum += power[i]; } return sum; }
③幅值谱转为对应的电压幅值谱
听起来拗口,其实还挺变扭。就是把幅值谱中的幅值转为实际上的电压,因为幅值谱中的幅值代表的物理意义并非是电压,不过它与电压有个对应关系(注释里)。
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); } fft_outputbuf[0] /= NUM_SAMPLES; }
④失真度计算(正弦波)
二次及二次以上的谐波成分的平方和除以基波的平方,然后再开方。
此时你会发现,谐波应为基波的整数倍,也就是说除了通过查找多个峰值的办法,还可以先找到第一个峰值的索引(需先去除直流量),然后把这个索引乘以相应倍数获得对应谐波。CMSIS-DSP有这个找峰值的函数,叫arm_max_f32,记得要去除直流成分。
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; //计算出失真度,并转为百分比表示 }
3,代码
这里面需要关注的主体函数是 void SignalAnalyzer_handler();这个函数是放到while大循环里的(MSPM0的内存实在太小,只能先放弃RTOS)
里面可能有部分注释忘记修改,还请见谅。
// // 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^2 float 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) { float32_t maxVal; uint16_t maxIdx; uint16_t halfWindowSize = windowSize / 2;//以一般理性而言,不会为0 uint16_t numPeaks = 1; //从一次谐波开始 //重置 for (uint16_t i = 0; i <NumPeaks ; ++i) { peaks[i] = 0; } // 开始寻找交流成分 for (uint16_t i = halfWindowSize; i < 512 - halfWindowSize; i++) { maxVal = fftOutput[i]; maxIdx = i; // 在滑动窗口内查找最大值 for (uint16_t j = i - halfWindowSize; j <= i + halfWindowSize; j++) { if (fftOutput[j] > maxVal) { maxVal = fftOutput[j]; maxIdx = j; } } // 检查窗口中心点是否为窗口内的最大值 if (maxIdx == i) { // 检查是否与最近的极大值足够远 if (peaks[numPeaks] == 0 || (i - peaks[numPeaks - 1]) > windowSize) { peaks[numPeaks] = i;//peaks存的是索引 ++numPeaks; //存满以后就退出 if (numPeaks == NumPeaks) return; } } } } /** * @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) { float sum = 0; //总功率应去除直流量 for (uint16_t i = 0; i <NumPeaks ; ++i) { power[i]=0; } for (uint16_t i = 1; i < NumPeaks; i++) { if (peaks[i] == 0) { break;//说明谐波已经取完 } power[i] = fft_Array[peaks[i]] * fft_Array[peaks[i]] * 2 / NUM_SAMPLES;//由于只取正频率,故进行归一化处理 sum += power[i]; } return sum; } ///** // * @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和直接查芯片,根本不知道啊
#define GPIO_OLED_DC_IOMUX (IOMUX_PINCM35)
吐槽归吐槽,经过一段时间的熟悉后,CCS还是很好用的,比如那个图形化展示数据,分析信号时简直犹如神助。当初觉得难估计是从stm32猛地转ti导致的,反过来大概也是一样的吧。
除此之外,再说一下浅显的个人经验,再使用sysconfig初始化工程时,由于习惯了stm32的开发,对于这种纯图形化反而不适应(与CubeMX不同),有时看着配置选项并不如直接看代码清晰,偏偏偏置选项和生成的代码还有些反差萌。所以和之前使用CubeMX一样,只是把它当做一个学习的工具:
导入示例工程,咔咔乱改,生成符合需求的可用代码,把代码添加到自己新建的文件中,并标上注释,一气呵成。
就比如下面的SPI.c/h文件:
// // Created by 34753 on 2024/7/20. // #ifndef ISB_TI_SPI_H #define ISB_TI_SPI_H #include "ZQ_Conf.h" #if USE_SPI #include "ti_init.h" #define SPI_0_INST SPI1 void SPI_Init(void); void SPI_DMA_Init(void);/*不可用*/ void SPI_DMA_Send(uint8_t *data, uint16_t len);/*不可用*/ void SPI_DMA_Repeat_SendWord(uint16_t data, uint16_t times);/**不可用*/ void SPI_DMA_WriteByte(uint8_t data); __STATIC_INLINE void SPI_WriteByte(uint8_t data) { while (DL_SPI_isBusy(SPI_0_INST)) ; DL_SPI_transmitData8(SPI_0_INST, data); } //void SPI_WriteWord(uint16_t data); void SPI_SendData(uint8_t *data, uint16_t len); #endif #endif//ISB_TI_SPI_H
// // Created by 34753 on 2024/7/20. // #include "SPI.h" #if USE_SPI #include "LED.h" /* Defines for SPI_0 */ #define SPI_0_INST_IRQHandler SPI1_IRQHandler #define SPI_0_INST_INT_IRQN SPI1_INT_IRQn #define GPIO_SPI_0_PICO_PORT GPIOB #define GPIO_SPI_0_PICO_PIN DL_GPIO_PIN_8 #define GPIO_SPI_0_IOMUX_PICO (IOMUX_PINCM25) #define GPIO_SPI_0_IOMUX_PICO_FUNC IOMUX_PINCM25_PF_SPI1_PICO #define GPIO_SPI_0_POCI_PORT GPIOB #define GPIO_SPI_0_POCI_PIN DL_GPIO_PIN_7 #define GPIO_SPI_0_IOMUX_POCI (IOMUX_PINCM24) #define GPIO_SPI_0_IOMUX_POCI_FUNC IOMUX_PINCM24_PF_SPI1_POCI /* GPIO configuration for SPI_0 */ #define GPIO_SPI_0_SCLK_PORT GPIOB #define GPIO_SPI_0_SCLK_PIN DL_GPIO_PIN_16 #define GPIO_SPI_0_IOMUX_SCLK (IOMUX_PINCM33) #define GPIO_SPI_0_IOMUX_SCLK_FUNC IOMUX_PINCM33_PF_SPI1_SCLK #define GPIO_SPI_0_CS1_PORT GPIOB #define GPIO_SPI_0_CS1_PIN DL_GPIO_PIN_17 #define GPIO_SPI_0_IOMUX_CS1 (IOMUX_PINCM43) #define GPIO_SPI_0_IOMUX_CS1_FUNC IOMUX_PINCM43_PF_SPI1_CS1_POCI1 /* Defines for DMA_CH1 */ #define DMA_CH1_CHAN_ID (1) #define SPI_0_INST_DMA_TRIGGER (DMA_SPI1_TX_TRIG) static volatile bool SPI_DMA_Mode_Flag = false;//默认模式 static const DL_SPI_Config gSPI_0_config = { .mode = DL_SPI_MODE_CONTROLLER, .frameFormat = DL_SPI_FRAME_FORMAT_MOTO4_POL1_PHA1, .parity = DL_SPI_PARITY_NONE, .dataSize = DL_SPI_DATA_SIZE_8, .bitOrder = DL_SPI_BIT_ORDER_MSB_FIRST, .chipSelectPin = DL_SPI_CHIP_SELECT_1, }; static const DL_SPI_ClockConfig gSPI_0_clockConfig = { .clockSel = DL_SPI_CLOCK_BUSCLK, .divideRatio = DL_SPI_CLOCK_DIVIDE_RATIO_1}; static const DL_DMA_Config gDMA_CH1Config = { .transferMode = DL_DMA_SINGLE_TRANSFER_MODE,//不可使用RepeatSingle,否则会出现乱波 .extendedMode = DL_DMA_NORMAL_MODE, .destIncrement = DL_DMA_ADDR_UNCHANGED, .srcIncrement = DL_DMA_ADDR_INCREMENT, .destWidth = DL_DMA_WIDTH_BYTE, .srcWidth = DL_DMA_WIDTH_BYTE, .trigger = SPI_0_INST_DMA_TRIGGER, .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, }; void SPI_Init(void) { DL_SPI_reset(SPI_0_INST); DL_SPI_enablePower(SPI_0_INST); /**GPIO初始化*/ DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_SCLK, GPIO_SPI_0_IOMUX_SCLK_FUNC); DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_PICO, GPIO_SPI_0_IOMUX_PICO_FUNC); DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_CS1, GPIO_SPI_0_IOMUX_CS1_FUNC); DL_SPI_Config gSPI_0_config = { .mode = DL_SPI_MODE_CONTROLLER, .frameFormat = DL_SPI_FRAME_FORMAT_MOTO4_POL0_PHA0, .parity = DL_SPI_PARITY_NONE, .dataSize = DL_SPI_DATA_SIZE_8, .bitOrder = DL_SPI_BIT_ORDER_MSB_FIRST, .chipSelectPin = DL_SPI_CHIP_SELECT_1, }; DL_SPI_setClockConfig(SPI_0_INST, (DL_SPI_ClockConfig *) &gSPI_0_clockConfig); DL_SPI_init(SPI_0_INST, &gSPI_0_config); /* Configure Controller mode */ /* * Set the bit rate clock divider to generate the serial output clock * outputBitRate = (spiInputClock) / ((1 + SCR) * 2) * 500000 = (32000000)/((1 + 31) * 2) */ // TFT_4SPI最快为 BUS_CLK:80MHz,不分频;SPI: 2分频(添1) // OLED_4SPI最快为 BUS_CLK:80MHz,不分频;SPI: 4分频(添3) DL_SPI_setBitRateSerialClockDivider(SPI_0_INST, 3); /* Set RX and TX FIFO threshold levels */ DL_SPI_setFIFOThreshold(SPI_0_INST, DL_SPI_RX_FIFO_LEVEL_1_2_FULL, DL_SPI_TX_FIFO_LEVEL_1_2_EMPTY); /* Enable module */ DL_SPI_enable(SPI_0_INST); } //void SPI_WriteWord(uint16_t data) //{ // while (DL_SPI_isBusy(SPI_0_INST)) // ; // DL_SPI_transmitData16(SPI_0_INST, data); //} void SPI_SendData(uint8_t *data, uint16_t len) { uint16_t i = 0; for (i = 0; i < len; i++) { while (DL_SPI_isBusy(SPI_0_INST)) ; DL_SPI_transmitData8(SPI_0_INST, data[i]); } } void SPI_DMA_Init(void) { #if !USE_TI_SYSCONFIG DL_SPI_reset(SPI_0_INST); DL_SPI_enablePower(SPI_0_INST); /**GPIO初始化*/ DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_SCLK, GPIO_SPI_0_IOMUX_SCLK_FUNC); DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_PICO, GPIO_SPI_0_IOMUX_PICO_FUNC); DL_GPIO_initPeripheralInputFunction(GPIO_SPI_0_IOMUX_POCI, GPIO_SPI_0_IOMUX_POCI_FUNC); DL_GPIO_initPeripheralOutputFunction(GPIO_SPI_0_IOMUX_CS1, GPIO_SPI_0_IOMUX_CS1_FUNC); /**SPI初始化*/ DL_SPI_setClockConfig(SPI_0_INST, (DL_SPI_ClockConfig *) &gSPI_0_clockConfig); DL_SPI_init(SPI_0_INST, (DL_SPI_Config *) &gSPI_0_config); /* Configure Controller mode */ /* * Set the bit rate clock divider to generate the serial output clock * outputBitRate = (spiInputClock) / ((1 + SCR) * 2) * 5000000 = (80000000)/((1 + 7) * 2) */ DL_SPI_setBitRateSerialClockDivider(SPI_0_INST, 7); /* Enable SPI TX interrupt as a trigger for DMA */ DL_SPI_enableDMATransmitEvent(SPI_0_INST); /* Set RX and TX FIFO threshold levels */ DL_SPI_setFIFOThreshold(SPI_0_INST, DL_SPI_RX_FIFO_LEVEL_ONE_FRAME, DL_SPI_TX_FIFO_LEVEL_ONE_FRAME); DL_SPI_enableInterrupt(SPI_0_INST, (DL_SPI_INTERRUPT_DMA_DONE_TX | DL_SPI_INTERRUPT_TX_EMPTY)); /* Enable module */ DL_SPI_enable(SPI_0_INST); /**DMA初始化*/ DL_DMA_initChannel(DMA, DMA_CH1_CHAN_ID, (DL_DMA_Config *) &gDMA_CH1Config); #endif /**开启SPI中断*/ NVIC_EnableIRQ(SPI_0_INST_INT_IRQN); } void SPI_DMA_Send(uint8_t *data, uint16_t len) { DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) data); DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) (&SPI_0_INST->TXDATA)); DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, len); DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID); } void SPI_DMA_WriteByte(uint8_t data) { DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) data); DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) (&SPI_0_INST->TXDATA)); DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, 1); DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID); } /** * @brief 重复传送某字节 * @param data * @param times * @note 首先会把模式调为单次模式,然后再把数据写入DMA,最后再开启DMA传输结束后注意把模式切换回来。 */ void SPI_DMA_Repeat_SendWord(uint16_t data, uint16_t times) { DL_DMA_Config gDMA_CH1Config_temp = { .transferMode = DL_DMA_SINGLE_TRANSFER_MODE,//不可使用RepeatSingle,否则会出现乱波 .extendedMode = DL_DMA_NORMAL_MODE, .destIncrement = DL_DMA_ADDR_UNCHANGED, .srcIncrement = DL_DMA_ADDR_UNCHANGED, .destWidth = DL_DMA_WIDTH_HALF_WORD, .srcWidth = DL_DMA_WIDTH_HALF_WORD, .trigger = SPI_0_INST_DMA_TRIGGER, .triggerType = DL_DMA_TRIGGER_TYPE_EXTERNAL, }; SPI_DMA_Mode_Flag = true;//模式改变 DL_DMA_initChannel(DMA, DMA_CH1_CHAN_ID, &gDMA_CH1Config_temp); DL_DMA_setSrcAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) &data); DL_DMA_setDestAddr(DMA, DMA_CH1_CHAN_ID, (uint32_t) (&SPI_0_INST->TXDATA)); DL_DMA_setTransferSize(DMA, DMA_CH1_CHAN_ID, times); DL_DMA_enableChannel(DMA, DMA_CH1_CHAN_ID); } void SPI_RecoverMode(void) { DL_DMA_initChannel(DMA, DMA_CH1_CHAN_ID, (DL_DMA_Config *) &gDMA_CH1Config); } void SPI_0_INST_IRQHandler(void) { switch (DL_SPI_getPendingInterrupt(SPI_0_INST)) { case DL_SPI_IIDX_DMA_DONE_TX: LED_OFF(LED2_Green); LED_ON(LED2_Red); break; case DL_SPI_IIDX_TX_EMPTY: //重置DMA模式 if (SPI_DMA_Mode_Flag) SPI_RecoverMode(); LED_OFF(LED2_Red); break; default: break; } } #endif