问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

STM32笔记:ADC的采样(软件/TIM+DMA)

创作时间:
作者:
@小白创作中心

STM32笔记:ADC的采样(软件/TIM+DMA)

引用
CSDN
1.
https://m.blog.csdn.net/2301_76587898/article/details/139060627

STM32系列微控制器是意法半导体(STMicroelectronics)推出的一款高性能、低成本的32位微控制器,广泛应用于各种嵌入式系统中。其中,ADC(模数转换器)是STM32的重要外设之一,用于将模拟信号转换为数字信号,是实现各种测量和控制功能的基础。本文将详细介绍STM32F407系列微控制器的ADC采样技术,包括软件触发、定时器触发和DMA模式的配置和使用。

ADC采样速率最大化

ADC主频介绍

STM32F407的ADC连接在APB2总线上的PCLK线上,支持的最大时钟为36 MHz。PCLK时钟是由HCLK固定的二分频得到,因此如果总线频率设置为168 MHz,APB2只能得到84 MHz,这会导致ADC内部再分频时无法得到36 MHz。因此,需要将HCLK设置为144 MHz,这样可以得到72 MHz,进而ADC二分频得到36 MHz。

此时,如果设置ADC的转换周期为15个时钟周期,就可以得到理论最大采样率为:

$$
\frac{36}{15} = 2.4 \text{ MHz}
$$

2.4 MHz极限采样率

要实现2.4 MHz的极限采样率,需要使用定时器+DMA的方式。定时器用于使采样间隔一致可控,DMA则用于数据搬运,避免CPU参与。

参数设置

  • ADC

  • 时钟频率:36 MHz

  • 分辨率:8位(11个ADC时钟周期)

  • 采样时间:3个时钟周期

  • 采一个点最快需要11个时钟周期,理论最大采样率为:
    $$
    \frac{36}{11} \approx 3.27 \text{ MHz}
    $$

  • 定时器

  • 时钟频率:72 MHz(APB1)

  • 预分频器:3

  • 计数器周期:8

  • 实际速率为:
    $$
    \frac{72}{4 \times 9} = 2 \text{ MHz}
    $$

实际测试

实际测试发现,测得的数据确实为2 MHz。输入100 kHz的信号时,一个周期可以测得20个点。

软件触发转换

ADC设置

选择了ADC2的Channel 7,这个可以随意选择。将External Trigger Conversion Source设置为by software(软件触发)。

代码部分

  • MX_ADC2_Init:CubeMX会自动帮我们生成ADC的Init函数。
  • HAL_ADC_Start(&hadc2):手动启动ADC的转换,启动时会有校准的时间,所以第一次启动时所需要的时间会长一些。如果只测一次,则使用完后用HAL_ADC_Stop(&hadc2)来关闭,否则不用关闭,它会一直转换。
  • HAL_ADC_PollForConversion(&hadc2, 200):这个函数用来检测ADC转换是否完成,如果完成就返回HAL_OK,之后就可以用HAL_ADC_GetValue(&hadc2)读出ADC内部寄存器dr的值了。其中第二个参数是允许等待ADC转换完成的时间,即超时时间。

用软件触发读取多数据的代码编写思路

for (i = 0; i < FFT_LENGTH; i++) /* 生成信号序列 */
{
    dac_value = dac_vol * 4095 / 3.3;
    HAL_ADC_Start(&hadc2);
    while (HAL_ADC_PollForConversion(&hadc2, 200) != HAL_OK)
    {
    }
    uint32_t adc_value = HAL_ADC_GetValue(&hadc2);
    uint32_t Volt = 3300 * adc_value >> 12;
    printf("%d\r\n",Volt);
    fft_inputbuf[2 * i] =Volt; /* 生成输入信号实部 */
    fft_inputbuf[2 * i + 1] = 0; /* 虚部全部0 */
}
arm_cfft_radix4_f32(&scfft, fft_inputbuf); /* FFT计算(基4) */
arm_cmplx_mag_f32(fft_inputbuf, fft_outputbuf, FFT_LENGTH); /* 把运算结果复数求模得幅 */
for (i = 0; i < FFT_LENGTH; i++)
{
    printf("fft_outputbuf[%d]:%f\r\n", i, fft_outputbuf[i]);
}

重点是这一句:

while (HAL_ADC_PollForConversion(&hadc2, 200) != HAL_OK)
{
}

即如果没转换完成,就一直卡在while里。

软件触发的问题

似乎用软件触发会使得采样率提不上来。如果想要较高采样率,可以用TIM触发,如果想要极限采样率,则用DMA搬运数据。

定时器TRGO信号触发转换

定时器设置

  • Clock Source设置为internal Clock
  • Prescaler和Counter Period根据时钟频率和定时器期望周期计算得到
  • auto-reload preload关闭,否则更新不能及时生效
  • TriggerOutput(TRGO) Parameters设置
  • 主从模式关闭(我也不知道这是干嘛的)
  • Trigger Event Selection
  • 这个是设置什么东西会产生TRGO信号
  • 设置为UEV事件的产生会产生TRGO信号,这样就使得计数器记满一个周期后,就有一个TRGO信号传给ADC进行转换
  • TRGO是一个短的脉冲信号
  • TIM3的全局中断不打开

ADC设置

  • 打开任意一个ADC的任意一个通道,我这里打开ADC2-IN7
  • Mode模式设置为独立模式
  • Continuous Conversion Mode
  • 开启了之后,ADC无需Start,ADC会自动连续转换
  • 问题:此时ADC转换的速率是不是可以拉到最快?也就是2.4M极限不用tim触发,用这个模式配合dma
  • HAL ADC连续转换模式
  • End Of Conversion Selection
  • 有两个选项
  • 一个通道转换完成就发出转换完成信号(HAL_OK)
  • 所有通道转换完成才发出转换完成信号
  • 这里设置为一个通道就发出,因为我只开了一个通道
  • External Trigger Conversion Source
  • 这个是用来设置触发ADC转换的方式的
  • 常用的有软件触发
  • 我们这里使用Timer3的TRGO信号触发
  • External Trigger Conversion Edge
  • TRGO信号是一个短时的正脉冲
  • 我们这里设置脉冲的上升沿触发转换
  • 打开ADC的全局中断
  • 注意:ADC1-2-3共用一个全局中断,即一个中断号ISR

NVIC设置

最后来到NVIC,设置ADC和滴答定时器的中断优先级。

代码流程

  • HAL_ADC_GetValue(&hadc2):这个函数是在adc转换完成之后,将adc的值读出来
  • HAL_ADC_Start_IT(&hadc2):将ADC以中断方式启动,即ADC开始测量
  • HAL_TIM_Base_Start(&htim3):开启定时器的函数,执行后定时器就开始计时了
  • HAL_ADC_IRQHandler:ADC的中断函数,即ADC采集转换完成后,执行这个中断函数
  • __weak void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef * hadc):这个函数在HAL_ADC_IRQHandler里,是ADC转换完成的回调函数,用户在需要编写在ADC转换完成后需要执行什么操作
    由于多个ADC共用一个中断号,所以他们的中断回调函数也是相同的,我们在编写这个函数的时候需要首先判断传入的hadc是哪个,再在if里执行对应的操作
    这个函数有时候编写的时候在main函数里会识别不出来,然后编译就报错,点击报错会跳转到一个error handler的函数,这个时候没有别的办法,只能把整个工程删除重开,就会好了(这个bug浪费了我一整天事件,不过似乎也有可能是因为我确实漏看了某个括号)

ADC的DMA模式

CubeMX配置

Timer

Timer部分很熟悉了,就不再详细说了

ADC

这里着重介绍一下DMA Continuous RequestsContinuous Conversion Mode

  • DMA Continuous Requests(这个理解好像不对)
  • 例如,HAL_ADC_START_DMA (&hadc 1, (uint 32_t *) buf, 200) 这个语句,使用ADC1和DMA,数据放入buf数组内,放200个数据。如果DMA连续请求去能的话,这个语句传完200个数据后自动关闭ADC1和DMA。反之,使能后,语句执行完后会又会连续从头开始传输数据,实际为buf数组中的值一直在更新
  • Continuous Conversion Mode
  • 开了这个会连续触发采样,一般在软件触发里常用;但是开了定时器就不需要了
  • 如果开了软件,开了这个,那么start一次后ADC还会一直采样;如果开了软件,没开这个,那么start一次以后ADC不会采样,需要手动start(换句话说,采样一次完成后ADC会自动stop)
  • ADC还需要
  • 开启NVIC,从而在DMA的buffer满了以后,触发ADC的中断回调函数
  • 开启DMA,设置ADC传输数据的DMA通道

NVIC与DMA

  • 开启ADC的NVIC
  • 创建一个DMA通道
  1. 添加一个通道
  2. 添加完成
  3. 模式
  • 分为normal和circular
  • normal指buffer满了以后,就停止DMA传输了,即只传一次;circular指循环传输,即满了以后从头开始再传输,覆盖以前的内容
  1. 地址自增
  • 第一个是外设地址自增,本次ADC的数据寄存器只有dr一个,即采集到的电压,所以不需要自增,始终读取该值即可
  • 第二个是内存地址自增,即DMA传输到的地址是否自增,我们进行fft,这里需要传入1024个数据,即buffer的大小是1024,所以肯定要地址自增,才能让数据一个一个的按顺序传入buffer
  1. 传输的数据宽度
  • 数据宽度和传入的内存地址的类型有关,我们这里是uint32_t,所以就选word(一个字);如果是uint16_t或者uint8_t之类的,就选half word或者byte就行

具体流程的理解(ADC的DMA模式流程图)

我先hal_adc_start_dma,然后hal_time_base_start,然后定时器产生trgo信号后就会自动adc采集,adc采集完成就会自动把数据搬运到缓冲区,缓冲区满了以后adc就自动停止了,进到adc的中断处理函数void HAL_ADC_ConvCpltCallback (ADC_HandleTypeDef* hadc)了,这个也是我们需要重写的回调函数

  • 如果我不想让DMAcircular采集,而是DMA模式手动控制采集了一轮以后继续采集的话,调整成normal之后再把hal_adc_start_dma重新开启,不过可能需要先手动stop一下?(HAL_ADC_Stop_DMA)(待定)


© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号