222

在 STM32CubeMX 生成的代码框架里,你只要保证下面几个步骤都做了,就能让 ADC+TIM 通过 DMA 连续触发采样,并在“半传输”和“整传输”时回调处理。


1. 在 MX_DMA_Init() 打开 DMA 时钟并配置流/通道

CubeMX 里打开 DMA 控制器的时钟(比如 DMA2),并在 ADC 的 “DMA Settings” 里:

  • Stream:选 DMA2_Stream0
  • ChannelChannel 0
  • DirectionPeripheral to memory
  • Peripheral increment:Disabled
  • Memory increment:Enabled
  • Peripheral data alignment:Half-word
  • Memory data alignment:Half-word
  • Mode:Circular
  • Enable “Half Transfer complete interrupt”
  • Enable “Transfer complete interrupt”

CubeMX 会在 dma.c 里生成类似:

/* DMA2_Stream0 init */
hdma_adc1.Instance = DMA2_Stream0;
hdma_adc1.Init.Channel             = DMA_CHANNEL_0;
hdma_adc1.Init.Direction           = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc           = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc              = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment    = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode                = DMA_CIRCULAR;
hdma_adc1.Init.Priority            = DMA_PRIORITY_LOW;
hdma_adc1.Init.FIFOMode            = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) { Error_Handler(); }
__HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);

2. 在 MX_ADC1_Init() 里打开 DMA 请求

CubeMX 在 adc.c 中会有:

hadc1.Init.DMAContinuousRequests = ENABLE;        // 循环 DMA
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.ExternalTrigConv     = ADC_EXTERNALTRIGCONV_T1_CC1; // TIM1.CH1 触发
...
if (HAL_ADC_Init(&hadc1) != HAL_OK) Error_Handler();

并在 MSP 回调里使能中断:

/* ADC1 interrupt Init */
HAL_NVIC_SetPriority(ADC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(ADC_IRQn);

3. 在 MX_TIMx_Init() 里配置 TIM 触发

以 TIM1 为例(CubeMX “Clock Configuration” 里设置好时钟之后):

htim1.Init.Prescaler         = 0;          // PSC = 0
htim1.Init.CounterMode       = TIM_COUNTERMODE_UP;
htim1.Init.Period            = 125-1;      // ARR = 124 → PWM 1.024 MHz
htim1.Init.ClockDivision     = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim1);

TIM_MasterConfigTypeDef mc = {0};
mc.MasterOutputTrigger = TIM_TRGO_OC1REF;   // OC1REF 产生 TRGO
mc.MasterSlaveMode   = TIM_MASTERSLAVEMODE_ENABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim1, &mc);

TIM_OC_InitTypeDef oc = {0};
oc.OCMode     = TIM_OCMODE_PWM1;
oc.Pulse      = (125/2)-1;                  // 50% 占空比
oc.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim1, &oc, TIM_CHANNEL_1);

4. 在 main.c/* USER CODE BEGIN 2 */ 里启动 DMA 和定时器

/* 初始化所有外设 */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_TIM1_Init();

/* 启动 ADC+DMA */
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_val_i, BUF_SIZE);

/* 启动 TIM1 PWM(产生 ADC 触发) */
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);

5. 在回调里处理半/全传输

void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) {
  if (hadc->Instance == ADC1) {
    adc_half_conv_cplt = 1;    // 触发半缓冲处理
  }
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
  if (hadc->Instance == ADC1) {
    adc_conv_cplt = 1;         // 触发全缓冲处理
  }
}

然后在 while(1) 里:

if (adc_half_conv_cplt) {
  adc_half_conv_cplt = 0;
  process_fft(&adc_val_i[0]);    // 对前 1024 点做 FFT
}
if (adc_conv_cplt) {
  adc_conv_cplt = 0;
  process_fft(&adc_val_i[1024]); // 对后 1024 点做 FFT
}

小结

  1. CubeMX

    • 打开 DMA 时钟,配置 ADC 的 DMA Circular,开启 Half/Complete 中断
    • 配置 TIMx 为 PWM,输出 OCxREF 作为 ADC 触发源
  2. main.c

    • MX_DMA_Init(); MX_ADC1_Init(); MX_TIM1_Init();
    • HAL_ADC_Start_DMA(&hadc1, buf, BUF_SIZE); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  3. 回调

    • HAL_ADC_ConvHalfCpltCallback() 里设置标志处理半缓冲
    • HAL_ADC_ConvCpltCallback() 里处理全缓冲

这样一来,DMA 会在后台不断填充 adc_val_i[0..2047],每填满 1024 个样本就触发一次中断,你就可以在中断外的主循环中立刻把那 1024 点送去做 FFT。

posted @ 2025-07-20 12:42  无敌烤肉大王  阅读(28)  评论(0)    收藏  举报