用cube生成一个用定时器触发ADC1,ADC2同步采集的程序,单片机选择的是STM32L476RGT6,用定时器2进行ADC采集触发,更改定时器2的定时周期便可以更改ADC的采样周期,ADC1和ADC2使用同步规则模式,并用DMA进行数据的传输。
STM32的ADC采样完成总共需要的时间是
ADC完成采样时间=采样周期+12个转换周期
举个例子,假如ADC的时钟是15MHz,采样周期是3个周期,3个采样周期加上12个转换周期,一共是15个周期,因为时钟是15MHz,所以完成一次ADC转换总共需要的时间就是1us。
STM32L476RGT6的ADC时钟是32MHZ,采样周期最短是2.5个周期,最快完成一次采集的时间大约是0.45us,因此,定时器触发的周期一定要大于这个时长。
参考 STM32参考手册 里面对于ADC同步规则模式的介绍。
定时器1选择PWM输出模式,定时器的时钟是80MHz,进行2分频,计数周期是80,生成一个频率是500KHz,占空比为50%的PWM波,用于验证ADC的采样速率。
定时器2的时钟源选择内部时钟,2分频,40计数周期,实现1MHz的ADC触发。Trigger Event Selection TRGO 一定要选择 Update Event ,不然不会触发ADC。
ADC1的通道是12,ADC1和ADC2都有通道打开的情况才会出现双通道的模式选择,如果只有一个独立模式,可以先打开一个ADC2的通道,再来ADC1里面进行模式选择。模式选择双通道同步规则模式,触发源选择定时器2上升沿触发,采样周期是2.5个周期,其余的选项默认设置就行。
ADC2的通道是15,配置默认,ADC2一定要与ADC1的采样周期相同
DMA只需要给ADC1添加一个DMA通道,ADC2不用配置。ADC1添加的DMA模式选择循环传输,数据长度都选择Word。
在初始化完成后,while循环之前添加该段代码,
/* USER CODE BEGIN 2 */
HAL_TIMEx_PWMN_Start(&htim1,TIM_CHANNEL_1); // 开启频率500KHz占空比为50%的PWM波
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED); // 对ADC进行校准,如果没有进行校准采集到的数据会有偏差
HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);
HAL_TIM_Base_Start(&htim2); // 开启定时器2 用于触发ADC采集, 更改定时器2的频率便可以更改ADC的采样速率
HAL_ADC_Start(&hadc2); // 开启ADC2
/* USER CODE END 2 */
在while循环里面添加下段代码
/* USER CODE BEGIN 3 */
printf("开始数据采集\r\n");
adc_complete = 0; // ADC采集完成标志 置零
HAL_ADCEx_MultiModeStart_DMA(&hadc1, (uint32_t*)&ADC_Value, sample_point); //开始同步采集ADC
while(adc_complete == 0){HAL_Delay(5);} //等待ADC采集完成
printf("数据采集完成\r\n");
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED); // 对ADC进行校准
HAL_ADCEx_Calibration_Start(&hadc2, ADC_SINGLE_ENDED);
for(i = 0, ad1 = 0, ad2 = 0; i<sample_point; i++) //分解数据, ADC_Value的高16位是ADC2的数据,ADC_Value的低16位是ADC1的数据
{
ADC_1[ad1++] = (uint16_t)ADC_Value[i];
ADC_2[ad2++] = (uint16_t)(ADC_Value[i]>>16);
}
for(i = 0; i<sample_point; i++){printf("ADC[%02d] = %x \r\n", i, ADC_Value[i]);}
printf("\r\n");
for(i = 0; i<sample_point; i++){printf("AD1[%02d] = %d \r\n", i, ADC_1[i]);}
printf("\r\n");
for(i = 0; i<sample_point; i++){printf("AD2[%02d] = %d \r\n", i, ADC_2[i]);}
printf("\r\n");
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(8000);
/* USER CODE END 3 */
/* USER CODE BEGIN 1 */
#ifdef __GNUC__
/*** With GCC/RAISONANCE, small printf (option LD Linker->Libraries->Small printf
set to 'Yes') calls __io_putchar() */
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif /* __GNUC__ */
/*** @brief Retargets the C library printf function to the USART.
* @param None
* @retval None*/
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF); //printf使用串口2输出
return ch;
}
/* USER CODE END 1 */
/* USER CODE BEGIN 1 */
/* ADC采集完成后会进入该回调函数,
标记ADC转换完成并停止转换,
以免之前采集到的数据被覆盖*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance==ADC1)
{
adc_complete = 1; // 标记ADC采集完成
HAL_ADCEx_MultiModeStop_DMA(&hadc1); // 停止ADC采集
}
}
/* USER CODE END 1 */
由于在家里没有信号源,也没有其他的单片机,验证就简单的输出了一个PWM波给ADC1,2进行采集来看现象。
将程序编译下载到单片机上,由于ADC1的12通道直接连在了定时器1输出PWM波的引脚上,PWM频率是500KHz,占空比是50%,所以ADC1应该采集到的的数据,是一个4095,之后一个0,ADC2的15通道悬空,所以ADC2的数据应该是乱的,串口显示的数据如图所示。
**现在将ADC2的15通道与ADC1的12通道连接在一起,采集到的数据ADC1和ADC2应该保持一致。**串口输出的数据如下图所示。
可以看出ADC1和ADC2采集到的数据保持一致。
现在更改定时器1的PWM波的频率和占空比,频率改为250KHz,占空比改为25%,ADC1和ADC2同时连接到该引脚,采集到的数据应该是1个4095, 3个0,并且ADC1和ADC2采集到的数据保持一致。串口输出的数据如下图所示。
从采集到的数据可以看出,验证基本成功。
完整的工程代码放到下面的链接里面了。工程代码