N32G45x的ADC有三个时钟,分别是工作时钟源、采样时钟源和计时时钟源。计时时钟源必须为1M,可以不配置,不配置的话默认是使用的HSI,8分频。
因为项目需要进行电池电源的监测,对应的ADC引脚在ADC2上,此外还需要采集Vrefint通道,对应的ADC通道位于ADC1,因此可以用这款MCU的双ADC模式。双ADC为主从模式,ADC1(主)和 ADC2(从),ADC3(主)和 ADC4(从)可以组成双 ADC 模式,一共有6种模式,还可以组合使用6种模式,具体可以查阅手册。
初始化并启动转换过程:
void Dev_ADCInit(void)
{
ADC_InitType ADC_InitStructure;
GPIO_InitType GPIO_InitStructure;
RCC_EnableAPB2PeriphClk(PWR_ADC_RCC, ENABLE);
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_ADC1,ENABLE);
RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_ADC2,ENABLE);
RCC_ConfigAdc1mClk(RCC_ADC1MCLK_SRC_HSE,RCC_ADC1MCLK_DIV8);
/* RCC_ADCHCLK_DIV8*/
ADC_ConfigClk(ADC_CTRL3_CKMOD_AHB,RCC_ADCHCLK_DIV4);
/* Configure PWR_ADC_Pin as analog input -------------------------*/
GPIO_InitStructure.Pin = PWR_ADC_Pin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Speed = GPIO_INPUT;
GPIO_InitPeripheral(PWR_ADC_Port, &GPIO_InitStructure);
/* ADC1 configuration ------------------------------------------------------*/
ADC_InitStructure.WorkMode = ADC_WORKMODE_REG_SIMULT; //规则同步模式
ADC_InitStructure.MultiChEn = DISABLE; //关闭扫描模式
ADC_InitStructure.ContinueConvEn = DISABLE; //单次转换
ADC_InitStructure.ExtTrigSelect = ADC_EXT_TRIGCONV_NONE; //SWSTRRCH
ADC_InitStructure.DatAlign = ADC_DAT_ALIGN_R; //右对齐
ADC_InitStructure.ChsNumber = 1; //规则通道序列长度
ADC_Init(ADC1, &ADC_InitStructure);
/* ADC1 regular configuration */
ADC_ConfigRegularChannel(ADC1, ADC_CH_INT_VREF, 1, ADC_SAMP_TIME_71CYCLES5);//ADC_CH_18:测内部1.2V基准
/* ADC1 temp sensor enable */
ADC_EnableTempSensorVrefint(ENABLE); //使能温度传感器和VREFINT通道
/* ADC2 configuration ------------------------------------------------------*/
ADC_InitStructure.WorkMode = ADC_WORKMODE_REG_SIMULT; //规则同步模式
ADC_InitStructure.MultiChEn = DISABLE; //关闭扫描模式
ADC_InitStructure.ContinueConvEn = DISABLE; //单次转换
ADC_InitStructure.ExtTrigSelect = ADC_EXT_TRIGCONV_NONE; //SWSTRRCH
ADC_InitStructure.DatAlign = ADC_DAT_ALIGN_R; //右对齐
ADC_InitStructure.ChsNumber = 1; //规则通道序列长度
ADC_Init(ADC2, &ADC_InitStructure);
/* ADC2 regular configuration */
ADC_ConfigRegularChannel(ADC2, PWR_ADC_CHANNEL, 1, ADC_SAMP_TIME_71CYCLES5);//vbat监测
/* Enable ADC2 external trigger conversion */
ADC_EnableExternalTrigConv(ADC2, ENABLE);
/* Enable ADC1 */
ADC_Enable(ADC1, ENABLE);
/*Check ADC Ready*/
while(ADC_GetFlagStatusNew(ADC1,ADC_FLAG_RDY) == RESET);
/* Start ADC1 calibration */
ADC_StartCalibration(ADC1);
/* Check the end of ADC1 calibration */
while (ADC_GetCalibrationStatus(ADC1));
/* Enable ADC2 */
ADC_Enable(ADC2, ENABLE);
/*Check ADC Ready*/
while(ADC_GetFlagStatusNew(ADC2,ADC_FLAG_RDY) == RESET);
/* Start ADC2 calibration */
ADC_StartCalibration(ADC2);
/* Check the end of ADC2 calibration */
while (ADC_GetCalibrationStatus(ADC2));
/* Start ADC1 Software Conversion */
ADC_EnableSoftwareStartConv(ADC1, ENABLE);
}
项目中MCU的主频使用的是48M。
计时时钟源采用的是HSE(8M)的8分频,为1M;
RCC_ConfigAdc1mClk(RCC_ADC1MCLK_SRC_HSE,RCC_ADC1MCLK_DIV8);
采样时钟使用的是AHB时钟的4分频,也就是12M。
ADC_ConfigClk(ADC_CTRL3_CKMOD_AHB,RCC_ADCHCLK_DIV4);
ADC1和ADC2各采集一个通道,获取数据过程:
uint16_t ADC_Read(void)
{
uint32_t adc_data = 0;
uint16_t i = 0,BattAdVal = 0,VrefAdVal = 0,Battery = 0;
Dev_ADCInit();
while((ADC_GetFlagStatus(ADC2, ADC_FLAG_ENDC)==RESET)&&((i++) <= 200));
ADC_ClearFlag(ADC1, ADC_FLAG_ENDC);
ADC_ClearFlag(ADC1, ADC_FLAG_STR);
ADC_ClearFlag(ADC2, ADC_FLAG_ENDC);
ADC_ClearFlag(ADC2, ADC_FLAG_STR);
adc_data = ADC_GetDualModeConversionDat(ADC1);
BattAdVal = (uint16_t)(adc_data>>16);
VrefAdVal = (uint16_t)adc_data;
Dev_ADCDeInit();
Battery = Get_Battery(BattAdVal,VrefAdVal);
return Battery;
}
读取数据使用ADC_GetDualModeConversionDat()函数,此函数会读取主ADC(ADC1/ADC3)的规则数据寄存器,该寄存器的高16位在双模式下,包含了从ADC转换的规则通道数据,低16位为主ADC转换的规则通道数据。至此就可以实现双ADC采集,通过Vbat和Vrefint通道的转换结果就可以求出当前电池电压。
笔记:STM32的ADC参考电压与参照电压(电源监测)


疑问:
在ADC的DMA请求章节中有双 ADC 模式时, 由 ADC2 转化的数据在 ADC1 的数据寄存器中,由 ADC4 转化的数据在 ADC3 的数据寄存器中的描述。在ADC的双ADC模式章节中有在双ADC 模式里,为了在主数据寄存器上读取从转换数据,必须使能DMA 位,即使不使用DMA 传输规则通道数据,从ADC 不能使能DMA的描述。
但是实际使用时发现没有使能DMA位也能在主数据寄存器上读取从转换数据,至于什么原因暂时还没有深究,有时间再看吧。
