stm32提供了许多种的ADC模式,在电赛中做信号处理使用到的大多是定时器驱动的ADC采样,这样会有更加准确的采样率,便于做fft时进行分析,规避频谱泄露等问题。现对f103(标准库)和f407(hal库)的相关配置和代码做介绍和比较。
f103
f103的ADCCLK最大为14Mhz,挂载在APB2总线上。实际在标准库中一般采用6分频,就是72M/6=12M。
这里设置了ADC的采样周期是55.5cycle,再加上12.5周期的ADC转换时间,总共是68个周期。一共是(1/12M)*68=5.7 μs → 最大触发频率约 175 kHz。也就是说,启用外部时钟触发的时候时钟的频率不能超过175khz.否则ADC还没有转换完下一个时钟上升沿就又来了。
DMA能够加速数据的传输效率,从外设到内存。缓存区域的大小与希望缓存的ADC采样个数有关,这里nn是1024。
然后进行ADC的电压校准。
void fre_calcu(void)
{
signed short lX,lY;//存放变换后信号的实部和虚部
float X,Y;//存放变换为浮点型数据的信号的实部和虚部
float Mag;//存放信号的模值
unsigned short i;
fus=72000000/(arr1*psc1); //9 71 //采样频率计算公式:fus=ck_psc/(psc+1)/(arr+1),ck_psc=72Mhz
for(i=0;i<nn;i++)
{
fftin[i] = 0;
fftin[i] =((u32)(AD_Value[i] ))<< 16;//将AD_Value[i]左移16位存入fftin数组中,实部为AD_Value[i],虚部为0
//为什么是左移16位?因为fftin数组是一个32位的数组,左移16位后,低16位为0,高16位为AD_Value[i],符合FFT变换的输入格式。
fftbuffer[i] = 0; //清空fftbuffer数组
}
cr4_fft_1024_stm32(fftout ,fftin ,nn);//调用FFT变换函数,进行FFT变换,输出的内容是频域
for(i=0; i<nn/2; i++)
{
lX = (fftout [i] << 16) >> 16;//取变换后数据的虚部
lY = (fftout [i] >> 16); //取变换后数据的实部
/*将信号变换为浮点型数据*/
X = nn * ((float)lX) / 32768; //定点数还原为浮点数 Q15格式 一位是符号位
Y = nn * ((float)lY) / 32768;
Mag = sqrt(X * X + Y * Y) / nn;
fftbuffer[i] = (unsigned long)(Mag * 32768);//将模值变回长整型存入数组中,存入的是频谱幅度信息
}
Mag=fm =0;
for(i=1;i<nn/2;i++)//找到模值最大的点
//为什么从1开始?因为第0个点是直流分量,频率为0Hz,不需要计算
//为什么是nn/2?因为FFT变换后,频率范围是0~fs/2,fs为采样频率,nn为采样点数,所以只需要计算前一半的点
{
if(Mag<fftbuffer [i])
{
Mag=fftbuffer [i];
fm =i;
}
}
F=(u32)(fm*fus/nn);//计算频率,公式为F=fm*fs/nn,其中fs为采样频率,fm为模值最大的点的索引,nn为采样点数
//找到能量最大的(幅值最大的)频率
}
首先要对ADC收集到的数据进行处理。f103是12位逐次逼近形ADC,采集到的数据为16位(对应DMA中的halfword。void cr4_fft_1024_stm32(void *pssOUT, void *pssIN, uint16_t Nbin);应该要填入32位参数,左移16位满足数据要求。执行fft之后的fftout[k] = Real + jImag。也就是说还是输入进去的数据格式,高16位是实数,低16位是虚数。
(例如:1010 1010 1010 1010 1111 1111 1111 1111 左移16位后,变成 1111 1111 1111 1111 0000 0000 0000 0000 再右移16位,变成 1111 1111 1111 1111,即提取了虚部。实部直接右移16位即可)。
由于采用的是Q15的定点数格式(避免使用浮点数),要将它还原。nn是缩放因子。
Q15 格式特点:
-
使用 16 位有符号整数(int16_t) 表示一个小数。
-
数值范围是:
接下来就是找到幅值最大的频谱。
f407
对hal库的参数配置做如下解释:Mode 只开启一个ADC就是独立模式 ADCCLK 最大值是36Mhz,把时钟倍频到最大值168Mhz后,APB2上的外设时钟84Mhz,四分频后21Mhz。这里先计算一下。采样时间3cycle+12cycle(区别于f103的12.5cycle),一共是15个周期。(1/21M)*15=0.714微秒,大约允许最大值1.4M的采样频率。
数据对齐方式为右对齐。
扫描模式只有多个adc通道才开启。
持续转换模式不用开启,这里使用的是定时器外部触发,即硬件触发。
不连续转换模式不用开启。有特定的触发事件才会开启。
DMA连续请求不用开启。开启的意思是:软件或者硬件触发一次,ADC连续采集,DMA也连续传输数据。而这里是每个时钟的上升沿触发。触发一次,DMA转运一次。
选择ADC的规则通道,外部时钟触发。
开启DMA,模式选择为循环。地址自增不要选外设。因为ADC的地址没有变化。半字对应ADC的16位位宽.
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim3);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buffer, sample_num);
while(!adc_flag){}
adc_flag = 0; // Reset the flag for next ADC conversion
/* USER CODE END 2 */
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
if (hadc == &hadc1){
adc_flag = 1; // Set the flag to indicate ADC conversion is complete
}
}
/* USER CODE END 4 */
这里加了ADC转换完成后的标志位,实际影响不大。因为采样时填满缓冲区即可。
hal库中的函数都封装好了,也无需去自己寻找最大幅值。逻辑和标准库是一样的。