[APM32F0] APM32F035的ADC驱动

[复制链接]
67|3
口天土立口 发表于 2025-11-5 14:24 | 显示全部楼层 |阅读模式
本帖最后由 口天土立口 于 2025-11-5 14:35 编辑

#技术资源# #申请原创#

@21小跑堂

1. 外设介绍
根据APM32F035的用户手册描述,ADC12位精度,16个外部通道和3个内部通道,即总共19个通道。检查APM32F035的数据手册,得到如下的通道情况:
ADC通道
引脚/功能
说明
ADC_CH0
PA2
引脚需配置为模拟
ADC_CH1
PA3
引脚需配置为模拟
ADC_CH2
PA4
引脚需配置为模拟
ADC_CH3
PA5
引脚需配置为模拟
ADC_CH4
PA0
引脚需配置为模拟
ADC_CH5
PA1
引脚需配置为模拟
ADC_CH6
PA6
引脚需配置为模拟
ADC_CH7
PA7
引脚需配置为模拟
ADC_CH8
PB0
引脚需配置为模拟
ADC_CH9
PB1
引脚需配置为模拟
ADC_CH10
PC0
引脚需配置为模拟
ADC_CH11
PC1
引脚需配置为模拟
ADC_CH12
PB10
引脚需配置为模拟
ADC_CH13
PC3
引脚需配置为模拟
ADC_CH14
PC4
引脚需配置为模拟
ADC_CH15
PC5
引脚需配置为模拟
ADC_CH16
温度传感器
需使能寄存器位ADC_CCFG.TSEN
ADC_CH17
参考电压
需使能寄存器位ADC_CCFG.VREFEN
ADC_CH18
VDD/2
需使能寄存器位ADC_CCFG.HLAF_VDDEN
根据用户手册24.3.1 ADC引脚和内部信号”章节介绍得知,ADC的参考电压为VDDA,即对应MCU的引脚号9,同时ADC输入范围为:VSSA ~ VDDA

2. 硬件
APM32F035x8 MINI

3. 驱动介绍
首先使用结构体信息方式组织代码,是为了初始化时能更精简代码,减少代码的冗余度;同时,数组信息的方式也能更清晰明了的展示ADC各个通道与引脚的对应关系。
另外,对于全新项目的开发,硬件板首版调试完成后,一般都存在调整IO引脚的需求,硬件第二版的ADC使用IO的调整,只需要更改数组的内容即可,无需更改函数接口内部代码。同时,枚举ADC_CH内部的各个成员命名可以更改为对应功能的名称,例如温度为ADC_TEMP,压力为ADC_PRESS,方便见名知义,应用层调用理解方便。
好的代码风格和命名也是提升产品质量的一部分,能避免后续团队维护代码理解错误,进而影响到产品质量。
  1. typedef struct {
  2.     GPIO_T      *port;           
  3.     uint16_t    pin;
  4.     uint32_t    AHBPeriph;
  5.     uint32_t    adc_channel;
  6. } adc_ch_info_t;
  7. /* ADC信息 */
  8. static adc_ch_info_t adc_ch_info[] = {
  9.     {GPIOA,     GPIO_PIN_2,    RCM_AHB_PERIPH_GPIOA,    ADC_CHANNEL_0 },
  10.     {GPIOA,     GPIO_PIN_3,    RCM_AHB_PERIPH_GPIOA,    ADC_CHANNEL_1 },
  11.     {GPIOA,     GPIO_PIN_4,    RCM_AHB_PERIPH_GPIOA,    ADC_CHANNEL_2 },
  12.     {GPIOA,     GPIO_PIN_5,    RCM_AHB_PERIPH_GPIOA,    ADC_CHANNEL_3 },
  13.     {GPIOA,     GPIO_PIN_0,    RCM_AHB_PERIPH_GPIOA,    ADC_CHANNEL_4 },
  14.     {GPIOA,     GPIO_PIN_1,    RCM_AHB_PERIPH_GPIOA,    ADC_CHANNEL_5 },
  15.     {GPIOA,     GPIO_PIN_6,    RCM_AHB_PERIPH_GPIOA,    ADC_CHANNEL_6 },
  16.     {GPIOA,     GPIO_PIN_7,    RCM_AHB_PERIPH_GPIOA,    ADC_CHANNEL_7 },   
  17.     {GPIOB,     GPIO_PIN_0,    RCM_AHB_PERIPH_GPIOB,    ADC_CHANNEL_8 },
  18.     {GPIOB,     GPIO_PIN_1,    RCM_AHB_PERIPH_GPIOB,    ADC_CHANNEL_9 },
  19.     {GPIOC,     GPIO_PIN_0,    RCM_AHB_PERIPH_GPIOC,    ADC_CHANNEL_10 },
  20.     {GPIOC,     GPIO_PIN_1,    RCM_AHB_PERIPH_GPIOC,    ADC_CHANNEL_11 },
  21.     {GPIOB,     GPIO_PIN_10,   RCM_AHB_PERIPH_GPIOB,    ADC_CHANNEL_12 },
  22.     {GPIOC,     GPIO_PIN_3,    RCM_AHB_PERIPH_GPIOC,    ADC_CHANNEL_13 },
  23.     {GPIOC,     GPIO_PIN_4,    RCM_AHB_PERIPH_GPIOC,    ADC_CHANNEL_14 },
  24.     {GPIOC,     GPIO_PIN_5,    RCM_AHB_PERIPH_GPIOC,    ADC_CHANNEL_15 },
  25.     {NULL,     0,               0,                      ADC_CHANNEL_16 },   /* 温度 */
  26.     {NULL,     0,               0,                      ADC_CHANNEL_17 },   /* Vref_in */
  27.     {NULL,     0,               0,                      ADC_CHANNEL_18 },   /* VDD/2 */
  28. };

  29. /*
  30. * @brief       引脚初始化
  31. *
  32. * @param       ch: 通道
  33. *
  34. * @retval      None
  35. *
  36. */
  37. void bsp_adc_gpio_init(enum ADC_CH ch)
  38. {
  39.         GPIO_Config_T gpioConfig;
  40.    
  41.     if ((ch < ADC_CH_NUM) && (adc_ch_info[ch].port != NULL)) {
  42.         RCM_EnableAHBPeriphClock(adc_ch_info[ch].AHBPeriph);
  43.         GPIO_ConfigStructInit(&gpioConfig);
  44.         gpioConfig.pin     = adc_ch_info[ch].pin;
  45.         gpioConfig.mode    = GPIO_MODE_AN;
  46.         gpioConfig.outtype = GPIO_OUT_TYPE_PP;
  47.         gpioConfig.speed   = GPIO_SPEED_50MHz;
  48.         gpioConfig.pupd    = GPIO_PUPD_NO;
  49.         GPIO_Config(adc_ch_info[ch].port, &gpioConfig);
  50.     }
  51. }
  1. enum ADC_CH {
  2.     ADC_CH0,
  3.     ADC_CH1,
  4.     ADC_CH2,
  5.     ADC_CH3,
  6.     ADC_CH4,
  7.     ADC_CH5,
  8.     ADC_CH6,
  9.     ADC_CH7,
  10.     ADC_CH8,
  11.     ADC_CH9,
  12.     ADC_CH10,
  13.     ADC_CH11,
  14.     ADC_CH12,
  15.     ADC_CH13,
  16.     ADC_CH14,
  17.     ADC_CH15,
  18.     ADC_CH16,
  19.     ADC_CH17,
  20.     ADC_CH18,
  21.    
  22.     ADC_CH_NUM
  23. };

如下为轮询方式执行ADC转换,开始ADC转换前,必须确保寄存器位ADC_STS.ADCRDY**为置1已准备好状态,当ADC_STS.EOC**1表明ADC已经转换完成,可以从寄存器ADC_DATA获取转换之后的数据。
注意:ADC的采样时间最短能配置多少,需要根据实际情况调整,时间过短,通道的建立时间不足,将导致转换结果不正确,误差较大。通道16~19,需开启对应的使能位。
轮询方式的ADC转换代码比较简单,但在需要多通道使用的场景下,此方式效率较低。
  1. /*
  2. * @brief       ADC初始化
  3. *
  4. * @param       ch: 通道
  5.                 multi: 是否连续采样
  6. *
  7. * @retval      None
  8. *
  9. */
  10. void bsp_adc_init(enum ADC_CH ch, uint8_t multi)
  11. {
  12.     ADC_Config_T adcConfig;
  13.    
  14.     if (ch < ADC_CH_NUM) {   
  15.         RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC);
  16.         /* ADC Configuration */
  17.         ADC_Reset();
  18.         ADC_ConfigStructInit(&adcConfig);
  19.         adcConfig.resolution   = ADC_RESOLUTION_12B;
  20.         adcConfig.dataAlign    = ADC_DATA_ALIGN_RIGHT;
  21.         adcConfig.scanDir      = ADC_SCAN_DIR_UPWARD;        
  22.         if (multi == 0) {
  23.             /* Set convMode continous*/
  24.             adcConfig.convMode   = ADC_CONVERSION_SINGLE;
  25.         } else {
  26.             adcConfig.convMode   = ADC_CONVERSION_CONTINUOUS;
  27.         }
  28.         adcConfig.seqMode      = ADC_SEQ_MODE_DISABLE;
  29.         adcConfig.seqGapTime   = 0;
  30.         adcConfig.extTrigConv1 = ADC_EXT_TRIG_CONV_TRG0;
  31.         adcConfig.extTrigEdge1 = ADC_EXT_TRIG_EDGE_NONE;
  32.         adcConfig.extTrigConv2 = ADC_EXT_TRIG_CONV_TRG0;
  33.         adcConfig.extTrigEdge2 = ADC_EXT_TRIG_EDGE_NONE;
  34.         adcConfig.extTrigConv3 = ADC_EXT_TRIG_CONV_TRG0;
  35.         adcConfig.extTrigEdge3 = ADC_EXT_TRIG_EDGE_NONE;
  36.         ADC_Config(&adcConfig);
  37.         if (ch == ADC_CH16) {
  38.             ADC_EnableTempSensor();
  39.         } else if (ch == ADC_CH17) {
  40.             ADC_EnableVrefint();
  41.         } else if (ch == ADC_CH18) {
  42.             ADC_EnableHalfVDD();
  43.         }
  44.         ADC_ConfigChannel(adc_ch_info[ch].adc_channel, ADC_SAMPLE_TIME_41_5);
  45.         /* Calibration*/
  46.         ADC_ReadCalibrationFactor();
  47.         /* Enable ADC*/
  48.         ADC_Enable();   
  49.     }
  50. }

  51. /*
  52. * @brief       ADC启动
  53. *
  54. * @param       None
  55. *
  56. * @retval      None
  57. *
  58. */
  59. void bsp_adc_start(void)
  60. {
  61.     /* Wait until ADC is ready */
  62.     while (!ADC_ReadStatusFlag(ADC_FLAG_ADRDY));
  63.     ADC_StartConversion();
  64. }

  65. /*
  66. * @brief       ADC停止
  67. *
  68. * @param       None
  69. *
  70. * @retval      None
  71. *
  72. */
  73. void bsp_adc_stop(void)
  74. {
  75.     ADC_StopConversion();
  76. }

  77. /*
  78. * @brief       ADC值获取
  79. *
  80. * @param       None
  81. *
  82. * @retval      AD值
  83. *
  84. */
  85. uint16_t bsp_adc_get_value(void)
  86. {
  87.     while (ADC_ReadStatusFlag(ADC_FLAG_CC) == RESET);
  88.     ADC_ClearStatusFlag(ADC_FLAG_CC);
  89.     return ADC_ReadConversionValue();
  90. }

如下为通过DMA方式执行ADC转换,相比轮询方式,需要多配置一个DMA外设,配置代码略微复杂一些,但在多通道使用场景下,DMA方式的效率更高。
通过DMA方式启动ADC转换,只要开启ADC转换即可,通道转换后的ADC数据,DMA将逐个自动搬移到配置的地址空间,按照ADC转换的通道顺序排放,待ADC完成所有通道的转换,DMA也自动关闭,可自行使用数据。
注意:DMA的数据传输方向需配置为外设到存储器的方向,ADC数据为12位有效数据,DMA需配置为半字(16bit)传输,存储数组需为16位;另外可开启DMA的传输完成中断,通过中断获知ADC完成所有通道的转换。
  1. /*
  2. * @brief       ADC初始化
  3. *
  4. * @param       membuf: 存储地址
  5. *
  6. * @retval      None
  7. *
  8. */
  9. void bsp_adc_init(uint16_t *membuf)
  10. {
  11.     ADC_Config_T adcConfig;
  12.     DMA_Config_T dmaConfig;
  13.    
  14.     uint32_t ch = ADC_CHANNEL_0 | ADC_CHANNEL_1 | ADC_CHANNEL_2 | ADC_CHANNEL_3 | ADC_CHANNEL_4 | \
  15.             ADC_CHANNEL_5 | ADC_CHANNEL_6 | ADC_CHANNEL_7 | ADC_CHANNEL_8 | ADC_CHANNEL_9 | \
  16.             ADC_CHANNEL_10 | ADC_CHANNEL_11 | ADC_CHANNEL_12 | ADC_CHANNEL_13 | ADC_CHANNEL_14 | \
  17.             ADC_CHANNEL_15 | ADC_CHANNEL_16 | ADC_CHANNEL_17 | ADC_CHANNEL_18;
  18.    
  19.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC);
  20.     RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_DMA);
  21.    
  22.     /* DMA配置 */
  23.     DMA_ConfigStructInit(&dmaConfig);
  24.     dmaConfig.direction = DMA_DIR_PERIPHERAL;
  25.     dmaConfig.circular = DMA_CIRCULAR_DISABLE;
  26.     dmaConfig.memoryTomemory = DMA_M2M_DISABLE;
  27.     dmaConfig.priority = DMA_PRIORITY_LEVEL_LOW;
  28.     dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;
  29.     dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
  30.     dmaConfig.memoryDataSize = DMA_MEMORY_DATASIZE_HALFWORD;
  31.     dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATASIZE_HALFWORD;
  32.     dmaConfig.bufferSize = ADC_CH_NUM;
  33.     dmaConfig.memoryAddress = (uint32_t)membuf;
  34.     dmaConfig.peripheralAddress = (uint32_t)&ADC->DATA;
  35.     DMA_Config(DMA_CHANNEL_1, &dmaConfig);
  36.     DMA_Enable(DMA_CHANNEL_1);
  37.    
  38.     /* ADC Configuration */
  39.     ADC_Reset();
  40.     ADC_ConfigStructInit(&adcConfig);
  41.     /* Set resolution*/
  42.     adcConfig.resolution = ADC_RESOLUTION_12B;
  43.     /* Set dataAlign*/
  44.     adcConfig.dataAlign  = ADC_DATA_ALIGN_RIGHT;
  45.     /* Set scanDir*/
  46.     adcConfig.scanDir    = ADC_SCAN_DIR_UPWARD;
  47.     /* Set single */
  48.     adcConfig.convMode   = ADC_CONVERSION_SINGLE;
  49.     adcConfig.seqMode      = ADC_SEQ_MODE_DISABLE;
  50.     adcConfig.seqGapTime   = 0;
  51.     adcConfig.extTrigConv1 = ADC_EXT_TRIG_CONV_TRG0;
  52.     adcConfig.extTrigEdge1 = ADC_EXT_TRIG_EDGE_NONE;
  53.     adcConfig.extTrigConv2 = ADC_EXT_TRIG_CONV_TRG0;
  54.     adcConfig.extTrigEdge2 = ADC_EXT_TRIG_EDGE_NONE;
  55.     adcConfig.extTrigConv3 = ADC_EXT_TRIG_CONV_TRG0;
  56.     adcConfig.extTrigEdge3 = ADC_EXT_TRIG_EDGE_NONE;
  57.     ADC_Config(&adcConfig);
  58.     ADC_EnableTempSensor();
  59.     ADC_EnableVrefint();
  60.     ADC_EnableHalfVDD();
  61.     ADC_ConfigChannel(ch, ADC_SAMPLE_TIME_71_5);
  62.     /* Calibration*/
  63.     ADC_ReadCalibrationFactor();
  64.     /* Enable ADC*/
  65.     ADC_Enable();   
  66.     ADC_EnableDMA();
  67. }

  68. /*
  69. * @brief       ADC启动
  70. *
  71. * @param       None
  72. *
  73. * @retval      None
  74. *
  75. */
  76. void bsp_adc_start(void)
  77. {
  78.     DMA_Disable(DMA_CHANNEL_1);
  79.     DMA_SetDataNumber(DMA_CHANNEL_1, ADC_CH_NUM);
  80.     DMA_Enable(DMA_CHANNEL_1);
  81.     /* Wait until ADC is ready */
  82.     while (!ADC_ReadStatusFlag(ADC_FLAG_ADRDY));
  83.     ADC_StartConversion();
  84. }

  85. /*
  86. * @brief       ADC停止
  87. *
  88. * @param       None
  89. *
  90. * @retval      None
  91. *
  92. */
  93. void bsp_adc_stop(void)
  94. {
  95.     ADC_StopConversion();
  96. }

  97. /*
  98. * @brief       ADC完成
  99. *
  100. * @param       None
  101. *
  102. * @retval      完成结果
  103. *
  104. */
  105. uint8_t bsp_adc_complete(void)
  106. {
  107.     uint8_t ret = 0;
  108.    
  109.     if (DMA_ReadStatusFlag(DMA_FLAG_TF1) != RESET) {
  110.         DMA_ClearStatusFlag(DMA_FLAG_TF1);
  111.         ret = 1;
  112.     }
  113.    
  114.     return ret;
  115. }

APM32F035内部的TS传感器,对应ADC的内部通道16,将ADC转换值转换为电压后,可通过如下公式转换为温度值,通过这个传感器,可间接估算产品运行的大概环境温度。其中公式内的VsensorADC采样值转换后的电压值,而V25Slope可查APM32F035的数据手册“6.10.2 温度传感器特性”章节获得。
79053690aeceaa7de9.png
  1. /*
  2. * @brief       温度值转换
  3. *
  4. * @param       value: 码值
  5. *
  6. * @retval      温度值
  7. *
  8. */
  9. float bsp_ts_convert(uint16_t value)
  10. {
  11.     float ts = 0.0f;
  12.     float mv = 0.0f;
  13. #define V25     (1420.0f)   /* 25oC 时的电压(数据手册) */
  14. #define SLOPE   (4)         /* mV/℃,平均斜率(数据手册) */
  15.    
  16.     /* 转为电压值 */
  17.     mv = 3300.0f * value / 4095;
  18.     /* 转为温度值(公式来源为用户手册) */
  19.     ts = (V25 - mv) / SLOPE + 25;
  20.    
  21.     return ts;
  22. }

4. 测试
测试代码如下,需注意开启新一轮ADC转换前,需等上一次的转换结束。同时,在将ADC转换值转换为电压时,需注意计算公式内的数据精度丢失问题,所以adc_ch_value先转为float类型再开始计算。
  1. <i>uint16_t adc_ch_value[ADC_CH_NUM];
  2. float adc_ch_voltage[ADC_CH_NUM];
  3. float temp_sensor;

  4. // 应用初始化
  5. void app_init(void)
  6. {
  7.     bsp_adc_gpio_init();
  8.     bsp_adc_init(adc_ch_value);
  9.     bsp_adc_start();
  10. }

  11. // 应用任务
  12. void app_task(void)
  13. {
  14.     if (bsp_adc_complete() != 0) {
  15.         for (uint8_t i = 0; i < ADC_CH_NUM; i++) {
  16.             adc_ch_voltage[i] = ((float)adc_ch_value[i]) / 4095 * 3.3f;
  17.             temp_sensor = bsp_ts_convert(adc_ch_value[ADC_CH16]);
  18.         }
  19.         bsp_adc_start();
  20.     }
  21. }</i>
20359690aed1be76de.png

5. 详细代码
ADC轮询驱动代码: Poll.zip (2.91 MB, 下载次数: 0)
ADC使用DMA驱动代码: DMA.zip (2.6 MB, 下载次数: 2)


亿年回响 发表于 2025-11-5 17:01 | 显示全部楼层
这代码与分层设计又简洁,又工整
钓鱼大师 发表于 2025-11-6 11:05 | 显示全部楼层
谢谢分享
神话编织者 发表于 2025-11-6 18:02 | 显示全部楼层
楼主的代码写得真是规矩。赞一下
您需要登录后才可以回帖 登录 | 注册

本版积分规则

27

主题

57

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部