普冉单片机PY32F002BF的ADC内部参考电压VREFINT检测详解

第一次使用普冉的单片机,因为太便宜了,20脚的32位单片机PY32F002B只需要几毛钱。

刚好项目需要用到低成本方案。

这篇主要是记录使用ADC内部参考电压遇到的问题。

因为用到了锂电池电压是浮动的3V-4.2V,再加上是低成本方案,没有稳压3.3V芯片,所以不适合使用ADC外部参考电压,也不适合增加一个运放跟随器,所以精度完全靠mcu本身adc的精度。

PY32F002B标的ADC是12位的,实际上可能达不到12位。

遇到的问题:

1.adc模式输入上下拉问题。

2.输入阻抗问题。

3.采样时间设置

4.使用内部1.5V的参考电压后数据偏大。

功能要求,低功耗,adc检测精度在10mV左右。

问题1,adc的输入模式设置不对,导致adc值误差。

py_gpio_config(GPIOB, GPIO_PIN_1, GPIO_MODE_ANALOG, GPIO_SPEED_FREQ_HIGH, GPIO_NOPULL);

这是输入模式的配置,主要是上下拉模式的配置,上下拉配置成上拉或者下拉时,会出现adc偏大或者偏小,所以需要配置不上下拉模式。GPIO_NOPULL。问题解决。

问题2.输入阻抗问题。电路采样串联电阻分压方式。原来为了低功耗设置,用了两个2M:1M的电阻,导致的后果就是输入阻抗过大,adc直接读取到电源的电压值,无法分压。

最后修改成20k:10k分压电阻。

为了低功耗,多加一个IO 口控制接地。这样只需要检测的时候才打开,就可以减低功耗了。然后输入主控是≈6.6k.F002B的数据手册没写输入阻抗是多少,但是最好别超过100K。问题2解决。

问题3,采样时间设置。因为是采样的电池的电压,为了防止尖刺,增加了一个RC滤波。R=100R,C=0.1uf。R*C=10us,充电时间是10us,再加上MCU本身的adc有一个5PF的电容,加上电路寄生电容和噪声,索引采样时间大概=5倍*10us=50us。采样时间设置50us。

我这边用到了低功耗,所以减低了AHP,APB总线上的频率。

//配置RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 时钟源为内部高速HSI
 void py_SystemClockConfig_HSI(void)
 {
     RCC_OscInitTypeDef RCC_OscInitStruct = {0};
     RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
     
     /* 振荡器配置 */
     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE | RCC_OSCILLATORTYPE_HSI | RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_LSE; /* 选择振荡器 HSE, HSI, LSI, LSE */
     RCC_OscInitStruct.HSIState = RCC_HSI_ON;                           /* 打开 HSI */
     RCC_OscInitStruct.HSIDiv = RCC_HSI_DIV1;                           /* HSI 1 分频 */
     RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_24MHz;  /* 配置 HSI 时钟 24MHz */
     RCC_OscInitStruct.HSEState = RCC_HSE_BYPASS_DISABLE;               /* 关闭 HSE bypass */
     RCC_OscInitStruct.LSIState = RCC_LSI_OFF;                           /* 打开 LSI */
     /*RCC_OscInitStruct.LSICalibrationValue = RCC_LSICALIBRATION_32768Hz;*/
     RCC_OscInitStruct.LSEState = RCC_LSE_OFF;                          /* 关闭 LSE */
     /*RCC_OscInitStruct.LSEDriver = RCC_LSEDRIVE_MEDIUM;*/
     
     HAL_RCC_OscConfig(&RCC_OscInitStruct);
     
     /* 配置时钟源 */
     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1; /* 选择配置时钟: HCLK, SYSCLK, PCLK1 */
     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSISYS;    /* 设置HSI为系统时钟 */
     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV4;        /* AHB 时钟一分频*/
     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;         /* APB 时钟一分频 */
     
     HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
 }

主频是24M,AHP四分频=6M,APB四分频=1.5M.所有挂载在APB上的外设最高不超过1.5M.而ADC1就是挂载到APB总线上

    //初始化IO口
    py_adc_gpioconfig();

    //adc配置
    __HAL_RCC_ADC_CLK_ENABLE();                                                /* 使能 ADC 时钟 */
    hadc.Instance = ADC1;   
    hadc.Init.ClockPrescaler        = ADC_CLOCK_SYNC_PCLK_DIV1;               /* 设置adc时钟*/
    hadc.Init.Resolution            = ADC_RESOLUTION_12B;                      /* 12位 转换模式 */
    hadc.Init.DataAlign             = ADC_DATAALIGN_RIGHT;                     /* 数据右对齐 */
    hadc.Init.ScanConvMode          = ADC_SCAN_DIRECTION_FORWARD;              /* 顺序扫描*/
    hadc.Init.EOCSelection          = ADC_EOC_SINGLE_CONV;                     /* 单次转换 */
    hadc.Init.LowPowerAutoWait      = ENABLE;                                 /* 自动延迟转换功能禁用 */
    hadc.Init.ContinuousConvMode    = DISABLE;                                 /* ENABLE:连续模式, DISABLE:单次模式 */
    hadc.Init.DiscontinuousConvMode = DISABLE;                                 /* 禁用不连续模式 */
    hadc.Init.ExternalTrigConv      = ADC_SOFTWARE_START;                      /* 软件触发 */
    hadc.Init.ExternalTrigConvEdge  = ADC_EXTERNALTRIGCONVEDGE_NONE;           /* 软件触发时,该参数无效 */
    hadc.Init.Overrun               = ADC_OVR_DATA_OVERWRITTEN;                /* 在溢出的情况下,DR寄存器被最后一次转换结果覆盖 */
    hadc.Init.SamplingTimeCommon    = ADC_SAMPLETIME_239CYCLES_5;               /* 通道采样时间为41.5 ADC时钟周期 */
  
    if (HAL_ADC_Init(&hadc) != HAL_OK)                                         /* ADC initialization */
    {
      printf("HAL_ADC_Init error1 \r\n");

      return 0;
    }
    

ADC的时钟设置1分频,精度12位,采样时间设置239.5个adc周期。

转换时间=(12.5+239.5)*(1/1.5M)=168US.转换一次adc需要168us,大于50us可以用。

问题4,最麻烦的,就是adc内部参考电压存在误差。

    HAL_ADC_ConfigVrefBuf(&hadc,ADC_VREFBUF_1P5V);

设置为使用内部1.5V参考电压。

刚开始是直接使用的电压转换公式是  实际电压mV=(adc*1500)/4095;

然后发现计算的的电压比万用表量到的电压大十几mv,1000mV时大了十几mv,再加是这个是电池2:1分压后的,电池电压还需要*3,那电池电压就偏大了30-40mV,这有点夸张了。本来以为是adc采样精度或者是采样电路噪声过大问题,然后通过测量,使用了软件滤波后,电压跳动只有1-3mv的误差,而且还是比万用表测得的电压大十几个毫伏。然后用一样的代码,放到一个最小系统板上测试,反向误差小了一点。那么应该就是板子环境影响的误差。

终于在数据手册上找到了ADC的内部参考电压不是1500MV固定不变,是有误差的。

那么上面的计算公式存在有问题。

    HAL_ADC_Start(&hadc);                             /* Start ADC Conversion */
    HAL_ADC_PollForConversion(&hadc, 100000);        /* Polling for ADC Conversion */
    adc_value = HAL_ADC_GetValue(&hadc);              /* Get ADC Value */
    VrefBuf_Value = (4095 * 1200) / adc_value;                /* Calculate the data */
    adc_vref_1500_calibrate = VrefBuf_Value;
    /* print VrefBuf value */
    printf("VrefBuf:%dmV \r\n",VrefBuf_Value);

     return VrefBuf_Value;

通过检测内部通道的参考电压通道VREFINT的电压值,我这块板计算得到1491和1500小了9MV,怪不得大那么多。修改公式 实际电压mV=(adc*1491)/4095;

得到的电压比原来的小了很多,但是电压值还是存在偏大。

而且这个偏大是程线性的,就是电压越大的时候,adc误差越大。查了一些资料,说的是使用内部参考电压时,会存在一个权重的问题,那么就需要软件上做补偿。或许也可能是温漂的问题。通过软件补偿后电压目前控制在5mV以内,还没做环境测试,不清楚误差是否会增大,不过这个误差目前够项目使用了。

这种模式只适合低成本,低功耗,对adc精度和实时性要求不高的时候使用。主要后面还有没有问题,还需要继续开发测试。

作者:芯芯点灯

物联沃分享整理
物联沃-IOTWORD物联网 » 普冉单片机PY32F002BF的ADC内部参考电压VREFINT检测详解

发表回复