STM32 FFT DMA ADC THD

利用STM32 FFT算法计算THD

一、设备准备

——>粤嵌STM32F429IGT6开发板 1块
——>串口调试助手

二、FFT算法意义

使用FFT算法,是为了获取信号在频域的相关参数,即信号的频谱。包括信号在频谱上各点的频率和该点的幅值。

由上面的调制信号和已调信号的频谱图,我们可以观察到正弦信号在未经过调制前,其频谱图仅仅在0频附近有一条谱线,这条谱线即为未调制信号的频谱。同时,我们可以观察到调制后的正弦信号的频谱发生了搬移,即从零频附近搬移到载波信号的频谱处。
首先让我们理解一个概念,信号的组成:任何连续测量的时序或信号,都可以表示为不同频率的正弦波信号的无限叠加。而根据该原理创立的傅立叶变换算法利用直接测量到的原始信号,以累加方式来计算该信号中不同正弦波信号的频率、振幅和相位。
其实,简单的理解,观察信号的角度从时域转换到频域,对观察者最大的好处是:在频域,信号的频谱图能够反映该信号所含的各种频率成分的信号及它们的幅值。
傅里叶级数的意义

三、DSP实际使用方法

我们使用ST公司提供的DSP库中的FFT算法。
使用步骤如下:

- STM32F4 DSP简介

STM32F4采用Cortex-M4内核,相比Cortex-M3系列除了内置硬件FPU单元(浮点运算单元),FPU是专用于浮点运算的处理器,在数字信号处理方面还增加了DSP指令集,支持诸如单周期乘加指令(MAC),优化的单指令多数据指令(SIMD),饱和算数等多种数字信号处理指令集。Cortex-M4执行所有的DSP指令都可以在单周期内完成,而Cortex-M3需要多个指令和多个周期才能完成同样的功能。

- 下载DSP_Lib源码包

找到DSP_Lib源码包,该文件夹目录结构如下图所示:
在这里插入图片描述
DSP_Lib源码包的Source文件夹是所有DSP库的源码,Examples文件夹是相对应的一些测试实例。这些测试实例都是带main函数的,也就是拿到工程中可以直接使用。
在这里插入图片描述
查阅原子STM32F4开发指南-寄存器版本,P699页,了解各种源码的功能介绍。

- 搭建DSP运行环境
只要DSP库运行环境搭建好了,使用DSP库里面的函数来做相关处理就非常简单。在MDK里面搭建STM32F4的DSP运行环境(使用.lib方式)是很简单的,分为3个步骤:

1、添加文件
在例程工程目录下新建:DSP_LIB文件夹,存放要添加的arm_correxM4lf_math.lib和相关头文件。
在这里插入图片描述
在这里插入图片描述

打开工程,新建DSP_LIB分组,并将arm_cortexM4lf_math.lib添加到工程里面。

在这里插入图片描述
如上面两图所示,这样添加文件就结束了。

2、添加头文件包含路径。
添加好了.lib文件后,我们要添加头文件包含路径,将第一步拷贝的Include文件夹和DSP_LIB文件夹,加入头文件包含路径,如下图所示。
在这里插入图片描述
3.添加全局变量宏
在这里插入图片描述
注意:这里两个宏之间用“,”隔开。这里我添加的宏包括:USE_STDPERIPH_DRIVER,STM32F429_439xx,ARM_MATH_CM4,__CC_ARM,ARM_MATH_MATRIX_CHECK,ARM_MATH_ROUNDING,如果没有在Target选项卡设置Code Generation选择use FPU,则必须在此处手动添加_FPU_USED。

这样,STM32F4DSP库运行环境就搭建完成了。

四、代码介绍

我采用在时域对信号进行采样,将时域采样数据进行4096点FFT变换,得到信号各频率成分的幅值,最后计算该信号的THD。

1.DMA介绍
DMA: 全称为: Direct Memory Access,即直接存储器访问。 DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, 能使 CPU 的效率大为提高。
STM32 最多有 2 个 DMA 控制器(DMA2 仅存在大容量产品中), DMA1 有 7 个通道。 DMA2 有 5个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁起来协调各个 DMA 请求的优先权。

2.编程思想
编程采用ADC+DMA+定时器触发的方式,简单的理解就是定时器具有生成PWM信号的功能,该脉冲信号在上升沿会触发ADC采样,采集的数据会直接被存储在用户自定义的内存单元中,当每次采集完4096个点的采样数据后,才会被再一次传输到用户自定义内存单元,即进行采样数据的更新。

3.采样定理介绍
在进行模拟/数字信号的转换过程中,当采样频率fs.max大于信号中最高频率fmax的2倍时(fs.max>2fmax),采样之后的数字信号完整地保留了原始信号中的信息,一般实际应用中保证采样频率为信号最高频率的2.56~4倍;采样定理又称奈奎斯特定理。即当满足采样定理时,可以不失真的恢复原信号,或者说得到原始信号的有用信息。

采用ADC+DMA+定时器的方式实现信号的采样时,采样频率由定时器的频率决定,需要注意的是,定时器的频率必须小于ADC频率。关于这个问题可以看这里:STM32定时触发ADC 采样频率等问题总结

4、ADC配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
    ADC_InitTypeDef ADC_InitStructure;
    ADC_CommonInitTypeDef ADC_CommonInitStructure;
  // 开启ADC时钟
  RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);
  // -------------------ADC Common 结构体 参数 初始化------------------------
  // 独立ADC模式
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
  // 时钟为fpclk x分频
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
  // 禁止DMA直接访问模式   
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  // 采样时间间隔
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;  
  ADC_CommonInit(&ADC_CommonInitStructure);

  // -------------------ADC Init 结构体 参数 初始化--------------------------
  ADC_StructInit(&ADC_InitStructure);
  // ADC 分辨率
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  // 禁止扫描模式,多通道采集才需要 
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;
  // 连续转换  
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
  //禁止外部边沿触发
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
  //外部触发通道,本例子使用软件触发,此值随便赋值即可
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
  //数据右对齐
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  //转换通道 1个
  ADC_InitStructure.ADC_NbrOfConversion = 1;                                    
  ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
  //---------------------------------------------------------------------------
   
  // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
  ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1,ADC_SampleTime_15Cycles);

  // 使能DMA请求 after last transfer (Single-ADC mode)
  ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);
  // 使能ADC DMA
  ADC_DMACmd(RHEOSTAT_ADC, ENABLE);
 
 // 使能ADC
    ADC_Cmd(RHEOSTAT_ADC, ENABLE);  
  //开始adc转换,软件触发
 //  ADC_SoftwareStartConv(RHEOSTAT_ADC);
}

5、DMA配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
   DMA_InitTypeDef DMA_InitStructure;
   NVIC_InitTypeDef NVIC_InitStructure;
  // ------------------DMA Init 结构体参数 初始化--------------------------
  // ADC1使用DMA2,数据流0,通道0,这个是手册固定死的
  // 开启DMA时钟
    RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE);
    // 外设基址为:ADC 数据寄存器地址
    DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;   
  // 存储器地址,实际上就是一个内部SRAM的变量
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC_ConvertedValue;  
  // 数据传输方向为外设到存储器   
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
    // 缓冲区大小为,指一次传输的数据量
    DMA_InitStructure.DMA_BufferSize = 4096;   
    // 外设寄存器只有一个,地址不用递增
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  // 存储器地址固定
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
   // // 外设数据大小为半字,即两个字节
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
     // 存储器数据大小也为半字,跟外设数据大小相同
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    // 循环传输模式
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
      // 禁止DMA FIFO ,使用直连模式
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;  
      // FIFO 大小,FIFO模式禁止时,这个不用配置
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
        // 选择 DMA 通道,通道存在于流中
    DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL;
      //初始化DMA流,流相当于一个大的管道,管道里面有很多通道
    DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);
   
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
   
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);
   
   
// 使能DMA流
    DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE);
     
   DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);

6、定时器配置

一般情况下,定时器触发ADC转换并不需要中断,因此不需要配置定时器的中断优先级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//使用通用定时器 触发ADC采样  TIM6,由于时钟频率不清楚,本身进行的配置
static void TIM_Mode_Config(void)
{<!-- -->
    //NVIC_InitTypeDef NVIC_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    // 开启TIMx_CLK,x[6,7]
    RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE);

  /* 累计 TIM_Period个后产生一个更新或者中断*/       
    //定时器初值,采样间隔0.05/1024s,初值为(0.05/1024)/(1/10M)=488
    //TIM_TimeBaseStructure.TIM_Period = 81 //(原始设置的数值)      
    TIM_TimeBaseStructure.TIM_Period = 3515;
    // 通用控制定时器时钟源TIMxCLK = HCLK/2=90MHz
    // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10MHz
    TIM_TimeBaseStructure.TIM_Prescaler = 0;   
    // 采样时钟分频
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    // 计数方式
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
   
    // 初始化定时器TIMx, x[1,8]
    TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

    // 清除定时器更新中断标志位
    // TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update);
   
    // 开启定时器更新中断
    // TIM_ITConfig(GENERAL_TIM,TIM_IT_Update,ENABLE);
    TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
   
    //使能定时器
    //TIM_Cmd(GENERAL_TIM, ENABLE);
   
    //使能定时器
    //TIM_Cmd(GENERAL_TIM, DISABLE);   
}

7、进行FFT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//在main.c文件中调用,进行ADC DMA 定时器的先相关初始化
void Rheostat_Init(void)
{<!-- -->
    Rheostat_ADC_GPIO_Config();
    Rheostat_ADC_Mode_Config();
//  TIMx_NVIC_Configuration();
    TIM_Mode_Config();
}

//使能ADC DMA FFT功能,需要进行FFT运算时,调用该语句
void  ENABLE_deinit(void)
{<!-- -->
        TIM_Cmd(GENERAL_TIM, ENABLE);  
        ADC_Cmd(RHEOSTAT_ADC, ENABLE);
        DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
}

//失能ADC DMA FFT功能
void  DISABLE_deinit(void)
{<!-- -->
        TIM_Cmd(GENERAL_TIM, DISABLE); 
        ADC_Cmd(RHEOSTAT_ADC, DISABLE);
        DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM,DISABLE);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
void DMA2_Stream0_IRQHandler(void)
{<!-- -->
   u16 i;
   if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0)) //是否进入中断
   {<!-- -->
        DISABLE_deinit();
       
        sprintf(dispBuff,"%d fft is running... please wait",counts0++);
       
        LCD_ClearLine(LINE(2));
       
        LCD_DisplayStringLine(LINE(2),(uint8_t* )dispBuff);      //在第三行显示THD
           
        if(DMA_GetCurrentMemoryTarget(DMA2_Stream0) == DMA_Memory_0)//
        {<!-- -->
           
            for(i=0;i<4096;i++){<!-- -->    
            ADC_Vol[i] =(float) ADC_ConvertedValue[i]/4096*(float)3.3-1;  //电压转换
//          printf("\r\n ADC_Vol[%d]:%f \r\n",i,ADC_Vol[i] );            //打印采样电压值
                    FFT_IN[2*i]=ADC_Vol[i];
                    FFT_IN[2*i+1]=0;                    //虚部全部为0
         }              
             fft_transformation();                     //基4FFT
             /*用LCD打印相关FFT变换后数组的输出值*/
//                LCD_Clear(LCD_COLOR_WHITE);
//                LCD_DisplayStringLine(LINE(1),(uint8_t* )" FFT ");
                                 
              /*显示频谱*/
//                  for(i=0;i<799;i++){<!-- -->
                LCD_DrawUniLine(i+1, 480, i+1, (u16)(480-FFT_OUT[i]/8.53));
//                printf("\r\n FFTout[%d]=%f\r\n ",i,FFT_OUT[i]);
//             }//先屏蔽串口打印测试
         }  
        //计算各种波形的总谐波失真
        THD=(sqrt(FFT_OUT[320]*FFT_OUT[320]+FFT_OUT[480]*FFT_OUT[480]+FFT_OUT[640]*FFT_OUT[640]+FFT_OUT[800]*FFT_OUT[800]))*100/FFT_OUT[160];
        //根据THD  计算的值 判断谐波失真的类型
                 
         sprintf(dispBuff,"1kHz-FFT_out[160]:%f  2kHz-FFT_out[320]:%f ",FFT_OUT[160],FFT_OUT[320]);
       
        LCD_ClearLine(LINE(4));
     
        LCD_DisplayStringLine(LINE(4),(uint8_t* )dispBuff);
               
        sprintf(dispBuff,"3kHz-FFT_out[480]:%f  4kHz-FFT_out[640]:%f ",FFT_OUT[480],FFT_OUT[640]);
       
        LCD_ClearLine(LINE(5));
     
        LCD_DisplayStringLine(LINE(5),(uint8_t* )dispBuff);                  
           
        sprintf(dispBuff,"5kHz-FFT_out[800]:%f",FFT_OUT[800]);
       
        LCD_ClearLine(LINE(6));
     
        LCD_DisplayStringLine(LINE(6),(uint8_t* )dispBuff);                  
   
        //
        sprintf(dispBuff,"%d  DMA fft finished... a new THD",counts1++);
               
        LCD_ClearLine(LINE(3));
               
        LCD_DisplayStringLine(LINE(3),(uint8_t* )dispBuff);  
        //       
        printf("THD=%f",THD);//串口打印显示
                 
        sprintf(dispBuff,"%d THD=%f",counts2++,THD);
       
        LCD_ClearLine(LINE(7));
     
        LCD_DisplayStringLine(LINE(7),(uint8_t* )dispBuff);      //在第三行显示THD
      DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);   //清除中断标志位
    }
}

8、adc.c源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#include "./adc/bsp_adc.h"

__IO uint16_t ADC_ConvertedValue[4096];
float FFT_IN[4096*2];
float FFT_OUT[4096];
static void Rheostat_ADC_GPIO_Config(void)
{<!-- -->
    GPIO_InitTypeDef GPIO_InitStructure;
   
    // 使能 GPIO 时钟
    RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_GPIO_CLK, ENABLE);
       
    // 配置 IO
    GPIO_InitStructure.GPIO_Pin  = RHEOSTAT_ADC_GPIO_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;      
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ; //不上拉不下拉
    GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure);    
}

static void Rheostat_ADC_Mode_Config(void)
{<!-- -->
    DMA_InitTypeDef DMA_InitStructure;
    ADC_InitTypeDef ADC_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    ADC_CommonInitTypeDef ADC_CommonInitStructure;

  // ------------------DMA Init 结构体参数 初始化--------------------------
  // ADC1使用DMA2,数据流0,通道0,这个是手册固定死的
  // 开启DMA时钟
    RCC_AHB1PeriphClockCmd(RHEOSTAT_ADC_DMA_CLK, ENABLE);
    // 外设基址为:ADC 数据寄存器地址
    DMA_InitStructure.DMA_PeripheralBaseAddr = RHEOSTAT_ADC_DR_ADDR;   
  // 存储器地址,实际上就是一个内部SRAM的变量
    DMA_InitStructure.DMA_Memory0BaseAddr = (u32)&ADC_ConvertedValue;  
  // 数据传输方向为外设到存储器   
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
    // 缓冲区大小为,指一次传输的数据量
    DMA_InitStructure.DMA_BufferSize = 4096;   
    // 外设寄存器只有一个,地址不用递增
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  // 存储器地址固定
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
   // // 外设数据大小为半字,即两个字节
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
     // 存储器数据大小也为半字,跟外设数据大小相同
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    // 循环传输模式
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
      // 禁止DMA FIFO ,使用直连模式
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;  
      // FIFO 大小,FIFO模式禁止时,这个不用配置
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;  
        // 选择 DMA 通道,通道存在于流中
    DMA_InitStructure.DMA_Channel = RHEOSTAT_ADC_DMA_CHANNEL;
      //初始化DMA流,流相当于一个大的管道,管道里面有很多通道
    DMA_Init(RHEOSTAT_ADC_DMA_STREAM, &DMA_InitStructure);
   
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;

    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
 
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
   
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);
   
   
//使能DMA流
    DMA_ITConfig(DMA2_Stream0,DMA_IT_TC,ENABLE);
     
//DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
     
//DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, DISABLE);
     
        // 开启ADC时钟
  RCC_APB2PeriphClockCmd(RHEOSTAT_ADC_CLK , ENABLE);
  // -------------------ADC Common 结构体 参数 初始化------------------------
    // 独立ADC模式
  ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
  // 时钟为fpclk x分频
  ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
  // 禁止DMA直接访问模式   
  ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
  // 采样时间间隔
  ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;  
  ADC_CommonInit(&ADC_CommonInitStructure);

     // -------------------ADC Init 结构体 参数 初始化--------------------------
  ADC_StructInit(&ADC_InitStructure);
  // ADC 分辨率
  ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
  // 禁止扫描模式,多通道采集才需要 
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;
  // 连续转换  
  ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
  //禁止外部边沿触发
  ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
  //外部触发通道,本例子使用软件触发,此值随便赋值即可
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
  //数据右对齐
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  //转换通道 1个
  ADC_InitStructure.ADC_NbrOfConversion = 1;                                    
  ADC_Init(RHEOSTAT_ADC, &ADC_InitStructure);
  //---------------------------------------------------------------------------
   
  // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期
  ADC_RegularChannelConfig(RHEOSTAT_ADC, RHEOSTAT_ADC_CHANNEL, 1, ADC_SampleTime_15Cycles);

  // 使能DMA请求 after last transfer (Single-ADC mode)
  ADC_DMARequestAfterLastTransferCmd(RHEOSTAT_ADC, ENABLE);
  // 使能ADC DMA
  ADC_DMACmd(RHEOSTAT_ADC, ENABLE);
   
    // 使能ADC
//  ADC_Cmd(RHEOSTAT_ADC, ENABLE);  

    // 使能ADC
//  ADC_Cmd(RHEOSTAT_ADC, DISABLE);
  //开始adc转换,软件触发
//  ADC_SoftwareStartConv(RHEOSTAT_ADC);
}


//把DMA 和 ADC 和定时器都失能   在想要使用的时候  将定时器重新使能CMD

//使用通用定时器 触发ADC采样  TIM6,由于时钟频率不清楚,本身进行的配置
static void TIM_Mode_Config(void)
{<!-- -->
    //NVIC_InitTypeDef NVIC_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    // 开启TIMx_CLK,x[6,7]
    RCC_APB1PeriphClockCmd(GENERAL_TIM_CLK, ENABLE);

  /* 累计 TIM_Period个后产生一个更新或者中断*/       
    //定时器初值,采样间隔0.05/1024s,初值为(0.05/1024)/(1/10M)=488
    //TIM_TimeBaseStructure.TIM_Period = 81 //(原始设置的数值)      
    TIM_TimeBaseStructure.TIM_Period = 3515;
    // 通用控制定时器时钟源TIMxCLK = HCLK/2=90MHz
    // 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=10MHz
    TIM_TimeBaseStructure.TIM_Prescaler = 0;   
    // 采样时钟分频
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    // 计数方式
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
   
    // 初始化定时器TIMx, x[1,8]
    TIM_TimeBaseInit(GENERAL_TIM, &TIM_TimeBaseStructure);

    // 清除定时器更新中断标志位
    // TIM_ClearFlag(GENERAL_TIM, TIM_FLAG_Update);
   
    // 开启定时器更新中断
    // TIM_ITConfig(GENERAL_TIM,TIM_IT_Update,ENABLE);
    TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update);
   
    //使能定时器
    //TIM_Cmd(GENERAL_TIM, ENABLE);
   
    //使能定时器
    //TIM_Cmd(GENERAL_TIM, DISABLE);   
}

//static void TIMx_NVIC_Configuration(void)
//{<!-- -->
//    NVIC_InitTypeDef NVIC_InitStructure;
//    // 设置中断组为0
//    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      
//    // 设置中断来源
//    NVIC_InitStructure.NVIC_IRQChannel = GENERAL_TIM_IRQn;    
//  // 设置抢占优先级
//    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  
//  // 设置子优先级
//    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
// 
//    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
// 
//    NVIC_Init(&NVIC_InitStructure);
//}

//采用通用定时器  TIM3 (原本的配置)
void Rheostat_Init(void)
{<!-- -->
    Rheostat_ADC_GPIO_Config();
    Rheostat_ADC_Mode_Config();
//  TIMx_NVIC_Configuration();
    TIM_Mode_Config();
}

void  ENABLE_deinit(void)
{<!-- -->
        TIM_Cmd(GENERAL_TIM, ENABLE);  
        ADC_Cmd(RHEOSTAT_ADC, ENABLE);
        DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM, ENABLE);
}

void  DISABLE_deinit(void)
{<!-- -->
        TIM_Cmd(GENERAL_TIM, DISABLE); 
        ADC_Cmd(RHEOSTAT_ADC, DISABLE);
        DMA_Cmd(RHEOSTAT_ADC_DMA_STREAM,DISABLE);
}

五、需要注意的问题和相关的总结

1、定时器决定采样频率的计算公式:
Ft=TIMxCLK /TIM_ClockDivision/(1+TIM_Prescaler)/(TIM_Prescaler)

2、关于ADC扫描模式设置

1
2
   //禁止扫描模式,多通道采集才需要 
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;

多通道是指一个或多个ADC的多个通道对数据进行采集。在这里我使用的是单通道。记住扫描模式对应着多通道!!果只是用了一个通道的话,DISABLE就可以了,如果使用了多个通道的话,则必须将其设置为ENABLE。
对扫描模式的理解

3、关于ADC独立模式

1
2
// 独立ADC模式
 ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;

在这个模式下,双ADC不能同步,每个ADC接口独立工作。所以如果不需要ADC同步或者只是用了一个ADC的时候,就应该设成独立模式了。

4、关于ADC的连续转换

1
2
  // 连续转换  
  ADC_InitStructure.ADC_ContinuousConvMode =ENABLE;

第四个参数是ADC_ContinuousConvMode,这里设置为ENABLE,即连续转换。如果设置为DISABLE,则是单次转换。两者的区别在于连续转换直到所有的数据转换完成后才停止转换,而单次转换则只转换一次数据就停止,要再次触发转换才可以。所以如果需要一次性采集4096个数据或者更多,则采用连续转换。
关于STM32连续转换和单次转换

5、关于DMA传输模式

1
2
// 循环传输模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;

DMA传输模式工作在正常缓存模式,那么每当ADC转换完4096个采样数据时,传输一次后DMA就会停止工作。
关于DMA

6、关于定时器上升沿触发ADC时,定时器选择的问题

可以选择通用定时器2,3,8,能够生成PWM信号。
在这里插入图片描述

1
2
  //外部触发通道,本例子使用软件触发,此值随便赋值即可
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T3_TRGO;
1
2
3
//定时器触发定义
#define GENERAL_TIM                 TIM3
#define GENERAL_TIM_CLK             RCC_APB1Periph_TIM3

7、DMA1通道资源
在这里插入图片描述
8、DMA2通道资源
在这里插入图片描述
9、ADC资源
在这里插入图片描述
10、ADC_TwoSamplingDelay 用来设置两个采样阶段之间的延迟周期数
第1次采样和第二次采样之间间隔的周期数。计算方法如下:
在这里插入图片描述
x=ADC_TwoSamplingDelay(采样时间)
ADCCLK=45M
在这里插入图片描述
一定要清楚怎么判断每个外设挂载的时钟总线。看英文参考手册!!