222
在 STM32CubeMX 生成的代码框架里,你只要保证下面几个步骤都做了,就能让 ADC+TIM 通过 DMA 连续触发采样,并在“半传输”和“整传输”时回调处理。
1. 在 MX_DMA_Init()
打开 DMA 时钟并配置流/通道
CubeMX 里打开 DMA 控制器的时钟(比如 DMA2),并在 ADC 的 “DMA Settings” 里:
- Stream:选
DMA2_Stream0
- Channel:
Channel 0
- Direction:
Peripheral 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
}
小结
-
CubeMX
- 打开 DMA 时钟,配置 ADC 的 DMA Circular,开启 Half/Complete 中断
- 配置 TIMx 为 PWM,输出 OCxREF 作为 ADC 触发源
-
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);
-
回调
- 在
HAL_ADC_ConvHalfCpltCallback()
里设置标志处理半缓冲 - 在
HAL_ADC_ConvCpltCallback()
里处理全缓冲
- 在
这样一来,DMA 会在后台不断填充 adc_val_i[0..2047]
,每填满 1024 个样本就触发一次中断,你就可以在中断外的主循环中立刻把那 1024 点送去做 FFT。