开源小项目-基于STM32F103的频率测量实现 V1.1(ADC+TIM+DMA+FFT)

目录

一、快速傅里叶变换(FFT)

1.1 工作原理

1.2 应用

1.3 FFT官方支持库

二、使用外设简介

2.1 ADC外设

2.2 TIM外设

3.3 DMA外设

三、代码设计过程

3.1 初始化

3.2 DMA中断数据处理

3.3 其他自定义函数

1. 计算信号频率

2. 计算信号幅度

3. 计算信号相位

4. 完整代码流程

5. 注意事项

3.4 采样频率选择及配置      

四、调用示例

五、频率测量现象演示

5.1 采样频率为20Khz

5.2 采样频率为1Khz

六、FFT误差原因分析及减小误差策略

6.1 误差原因分析

6.2 FFT的优缺点补充

1. 优点

2. 缺点

七、总谐波失真计算



        该项目是基于STM32F1的频率测量,采用FFT快速傅里叶变换,配合ADC、DMA、TIM等外设实现频率测量,频率测量偏差小于千分之8。主要运用知识点:

  • 1. ADC单通道配置与使用
  • 2. 定时器配置及使用
  • 3. DMA数据搬运配置与使用
  • 4. C语言结构体、数组使用

项目源码文件下载地址:fft_measure V1.1.zip - 蓝奏云


一、快速傅里叶变换(FFT)

        快速傅里叶变换(Fast Fourier Transform,简称FFT)是一种高效的算法,用于计算离散傅里叶变换(Discrete Fourier Transform,简称DFT)及其逆变换。傅里叶变换是一种将信号从时域(或空间域)转换到频域的数学方法,反之亦然。这意味着它可以将复杂的波形分解成一系列不同频率的正弦波和余弦波的组合,每个波都有其对应的振幅和相位。这一过程对于信号处理、图像处理、数据分析等领域至关重要。

1.1 工作原理

        FFT通过将DFT的计算分解为较小部分的递归算法实现加速。核心思想是利用了傅里叶变换的对称性和周期性,将原问题分解为两个或更多的相似但规模较小的问题,最终通过分治策略合并这些小问题的答案以获得最终结果。特别是,它利用了蝶形运算,减少了乘法和加法的总数,这是FFT效率提升的关键。

1.2 应用

FFT在众多领域有着广泛的应用:

  • 信号处理:分析信号的频率组成,滤波,压缩,降噪等。
  • 图像处理:图像压缩,边缘检测,纹理分析等。
  • 通信:频谱分析,调制解调,信号同步等。
  • 音频处理:音乐合成,音调调整,噪声消除等。
  • 医学:心电图、脑电图等生物信号的分析。
  • 天文学:分析星系光谱,探测宇宙背景辐射等。

1.3 FFT官方支持库

        本例中使用STM32提供的DSP库函数进行FFT运算,仅支持F1系列单片机的计算。下载链接地址:STM32_FFT官方库.zip - 蓝奏云。下载后解压文件会得到如下图1所示的源码。

图1   STM32_FFT官方库

        其中可以看到官方提供了两个FFT计算的.s文件,FFT计算点数不同会直接影响到采集的数据精度,因此本文后面采用的是1024点的.s文件用于FFT计算。

        然后打开stm32_dsp.h文件可以看到,官方提供了三个计算FFT的函数,如下:

/* Radix-4 complex FFT for STM32, in assembly  */
/* 64 points*/
void cr4_fft_64_stm32(void *pssOUT, void *pssIN, u16 Nbin);
/* 256 points */
void cr4_fft_256_stm32(void *pssOUT, void *pssIN, u16 Nbin);
/* 1024 points */
void cr4_fft_1024_stm32(void *pssOUT, void *pssIN, u16 Nbin);

        其中每个参数的详细说明如下:

1. *pssOUT: FFT运算后输出的频域数组

2. *pssIN: 输入的时域采样信号数组

3. Nbin: 需要计算的FFT点数。

二、使用外设简介

2.1 ADC外设

        模数转换,即Analog-to-Digital Converter,常称ADC,是指将连续变量的模拟信号转换为离散的数字信号的器件。其工作原理涉及对输入的模拟电压进行采样,并将其量化为一定位数的数字值。关键参数包括分辨率(通常以位数表示,如8位、10位、12位等)、转换速度(样本每秒,Sps)、精度、输入通道数量以及是否支持差分输入等。

  1. 分辨率:决定了ADC能够区分的最小电压变化量,2^n,其中n是位数。例如,一个12位的ADC可以区分2^12=4096个不同的电压等级。

  2. 转换速率(Conversion Rate/Sampling Rate):单位时间内完成采样并转换的次数,通常以样本每秒(Samples Per Second, Sps)表示。

  3. 精度:除了分辨率外,还涉及到非线性误差、增益误差和偏置误差等因素,决定了转换结果与真实值的接近程度。

  4. 输入多路复用:许多ADC支持多个输入通道,通过多路复用器选择要转换的信号。

  5. 参考电压:决定了ADC能够测量的最大电压范围,通常是ADC的满量程电压。

  6. 电源和噪声抑制:良好的电源去耦和适当的布局对于减少噪声、提高转换精度至关重要。

        其中ADC部分的主要代码初始化如下:

    /* ----------------------- ADC结构体初始化 ----------------------- */
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode               = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode       = DISABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv   = ADC_ExternalTrigConv_T2_CC2;  /* 使用定时器2触发ADC采样 */
    ADC_InitStructure.ADC_DataAlign          = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel       = 1;
    ADC_Init(ADC1, &ADC_InitStructure);
    
    ADC_RegularChannelConfig(ADC1, ADC_GPIO_CH, 1, ADC_SampleTime_28Cycles5);
    
    ADC_Cmd(ADC1,ENABLE);       /* 使能ADC */
    ADC_DMACmd(ADC1, ENABLE);
    ADC_ExternalTrigConvCmd(ADC1, ENABLE);  /* 外部触发使能 */

2.2 TIM外设

        TIM外设,全称为Timer(定时器),是嵌入式系统中常见的外设之一,它主要用于实现定时和计数功能。TIM外设可以实现如下几种功能:

  1. 定时功能:TIM外设可以基于内部时钟或者外部输入信号进行计数,当计数值达到预设值时,可以产生中断或者DMA请求,从而触发处理器执行特定任务。这使得定时器可以用来实现精确的时间延迟、周期性任务调度等。

  2. 计数功能:连接到外部输入引脚时,TIM可以作为计数器,记录外部脉冲的数量,适用于测量频率、脉冲宽度等。

  3. 输出比较:通过比较计数器的值与预设的比较值,可以控制输出引脚的状态,实现PWM(脉宽调制)信号生成,这对于电机控制、LED亮度调节等应用非常有用。

  4. 输入捕获:捕捉外部输入信号的上升沿或下降沿,可用于测量输入信号的频率、脉冲宽度,实现编码器计数等功能。

  5. 触发和同步:高级定时器还可以与其他外设(如DAC、ADC)进行同步,用于复杂的控制应用,如在数据采集时精确控制ADC的启动时间。

        其中TIM外设部分的初始化代码如下:

    /* ----------------------- TIM结构体初始化 ----------------------- */
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Period        = TIM_ARR - 1;          /* 自动重装载寄存器周期 */
    TIM_TimeBaseStructure.TIM_Prescaler     = TIM_PSC - 1;          /* 预分频值 */
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;         /* 设置时钟分割 */
    TIM_TimeBaseStructure.TIM_CounterMode   = TIM_CounterMode_Up;   /* TIM向上计数模式 */
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCInitStructure.TIM_OCMode      = TIM_OCMode_PWM1;          /* 选择定时器模式:TIM脉冲宽度调制模式1 */
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;   /* 比较输出使能 */
    TIM_OCInitStructure.TIM_Pulse       = TIM_ARR/2;
    TIM_OCInitStructure.TIM_OCPolarity  = TIM_OCPolarity_Low;       /* 输出极性:TIM输出比较极性低 */
    TIM_OC2Init(TIM2, & TIM_OCInitStructure);                       /* 初始化外设TIM2_CH2 */
    
    TIM_Cmd(TIM2, ENABLE);

3.3 DMA外设

        DMA(Direct Memory Access,直接存储器访问)外设是一种专为高效数据传输设计的硬件控制器,它允许外部设备(如传感器、ADC、DAC、网络接口等)和系统内存(RAM)之间直接进行高速数据交换,而无需CPU的持续介入。这一机制显著减轻了CPU的负担,使得CPU可以在数据传输的同时执行其他任务,从而提高了系统的整体性能和响应速度。DMA的主要特点包括:

  1. 高效传输:DMA能够在不占用CPU资源的情况下,快速传输大量数据,特别适合于大数据量的连续传输,如音频、视频流处理,大容量存储读写等。

  2. 自动操作:DMA传输由硬件自动完成,一旦配置好DMA控制器(包括源地址、目标地址、传输量和控制参数),数据传输就会在指定的触发条件下自动开始并完成。

  3. 减少CPU干预:通过DMA,CPU不必参与每次数据的读写操作,仅在传输开始和结束时(或发生错误时)接收中断通知,这样就释放了CPU来执行其他计算密集型任务。

  4. 多通道支持:现代DMA控制器通常支持多个独立通道,每个通道可以被分配给不同的外设,同时进行数据传输,增加了系统的并行处理能力。

  5. 优先级管理:DMA控制器能够管理不同通道的优先级,确保关键数据的及时传输,特别是在多个外设同时请求DMA服务时。

  6. 灵活配置:DMA传输可以配置为单次传输或循环传输,对于需要周期性数据更新的应用(如采样数据的连续采集)非常有用。

  7. 对齐要求:为了优化传输效率,源和目标地址通常需要按照数据传输宽度(如字节、半字、全字)对齐。

        其中DMA部分的主要代码初始化如下:

    /* ----------------------- DMA结构体初始化 ----------------------- */
    DMA_InitTypeDef DMA_InitStructure;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);   /* ADC1对应地址 */
    DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)&adcValue[0];  /* 存储数据数组地址 */
    DMA_InitStructure.DMA_DIR                = DM
### 使用 STM32F103C8T6 最小系统板实现信号发生器 #### 设计概述 STM32F103C8T6 是一款基于 ARM Cortex-M3 的微控制器,具有丰富的外设资源。为了将其配置成信号发生器,通常会利用其定时器和 DAC 功能来生成不同类型的模拟信号。 #### 硬件准备 - **STM32F103C8T6 最小系统板** - **DAC 输出接口**:用于输出生成的信号 - **电源模块**:确保稳定供电 - **USB 调试工具**:如 ST-LINK/V2 或其他兼容调试器 #### 配置环境 安装并设置好开发环境,推荐使用官方支持的 IDE 如 Keil MDK 或者免费开源的选择如 VSCode + PlatformIO。还需要下载对应的 HAL 库以便简化编程工作[^4]。 #### 主要功能描述 通过软件控制 ADCTIM 定时器配合 DMA 来完成对 AD9834 波形发生芯片的操作,从而达到产生所需波形的目的。对于简单的正弦波、方波等基础波形可以直接由 MCU 内部资源生成而无需额外器件参与。 #### 关键代码片段 下面给出一段简单示例程序框架,展示如何初始化 DAC 并输出一个固定电压值: ```c #include "stm32f1xx_hal.h" // 初始化函数定义 void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DAC_Init(void); int main(void){ // 初始化HAL库 HAL_Init(); // 设置系统时钟 SystemClock_Config(); // GPIO初始化 MX_GPIO_Init(); // DAC初始化 MX_DAC_Init(); while (1){ uint32_t value = 2047; // 中间电平 // 启动DAC转换 HAL_DAC_Start(&hdac, DAC_CHANNEL_1); // 更新DAC寄存器中的数值 HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, value); // 延迟一段时间保持输出状态 HAL_Delay(1000); } } ``` 此段代码展示了最基本的 DAC 输出操作流程,在实际应用中可以根据需求调整 `value` 变量以改变输出幅度;也可以加入更多逻辑来自动生成连续变化的波形序列。 #### 扩展功能说明 当需要更复杂的功能比如调制解调或是多通道同步输出时,则可能需要用到更多的硬件资源和支持算法处理能力更强的应用层架构。此时可以考虑引入 FFT 计算等功能扩展原有方案。
评论 61
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值