[APM32F4] APM32F427的ADC驱动(轮询方式和DMA传输方式)

[复制链接]
55|0
口天土立口 发表于 2025-11-19 18:42 | 显示全部楼层 |阅读模式
, , , ,
本帖最后由 口天土立口 于 2025-11-20 09:00 编辑

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

@21小跑堂

1. 外设介绍
97032691d9d42a8c5a.png
APM32F427ADC外设有三个,其中ADC1ADC2的输入通道完全一致,而ADC3的输入通道则部分一致。
1225691d9d5eb9c18.png

2. 硬件
APM32F427ZG TINY

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    AHB1Periph;
  5.     uint32_t    adc_channel;
  6. } adc_ch_info_t;
  7. /* ADC1_2信息 */
  8. static adc_ch_info_t adc1_2_ch_info[] = {
  9.     {GPIOA,     GPIO_PIN_0,    RCM_AHB1_PERIPH_GPIOA,    ADC_CHANNEL_0 },
  10.     {GPIOA,     GPIO_PIN_3,    RCM_AHB1_PERIPH_GPIOA,    ADC_CHANNEL_3 },
  11.     {GPIOA,     GPIO_PIN_4,    RCM_AHB1_PERIPH_GPIOA,    ADC_CHANNEL_4 },
  12.     {GPIOC,     GPIO_PIN_0,    RCM_AHB1_PERIPH_GPIOC,    ADC_CHANNEL_10 },
  13.     {GPIOC,     GPIO_PIN_2,    RCM_AHB1_PERIPH_GPIOC,    ADC_CHANNEL_12 },
  14.     {GPIOC,     GPIO_PIN_3,    RCM_AHB1_PERIPH_GPIOC,    ADC_CHANNEL_13 },
  15.     {NULL,      0,               0,                      ADC_CHANNEL_16 },   /* 温度 */
  16.     {NULL,      0,               0,                      ADC_CHANNEL_17 },   /* Vref_in */
  17.     {NULL,      0,               0,                      ADC_CHANNEL_18 },   /* Vbat */
  18. };

  19. /*
  20. * @brief       引脚初始化
  21. *
  22. * @param       ch: 通道
  23. *
  24. * @retval      None
  25. *
  26. */
  27. void bsp_adc_gpio_init(enum ADC_CH ch)
  28. {
  29.         GPIO_Config_T gpioConfig;
  30.     adc_ch_info_t *adc_ch_info = adc1_2_ch_info;
  31.    
  32.     if ((ch < ADC_CH_NUM) && (adc_ch_info[ch].port != NULL)) {
  33.         RCM_EnableAHB1PeriphClock(adc_ch_info[ch].AHB1Periph);
  34.         GPIO_ConfigStructInit(&gpioConfig);
  35.         gpioConfig.pin  = adc_ch_info[ch].pin;
  36.         gpioConfig.mode = GPIO_MODE_AN;
  37.         gpioConfig.speed = GPIO_SPEED_100MHz;
  38.         gpioConfig.otype = GPIO_OTYPE_PP;
  39.         gpioConfig.pupd = GPIO_PUPD_NOPULL;
  40.         GPIO_Config(adc_ch_info[ch].port, &gpioConfig);
  41.     }
  42. }
  1. enum ADC_CH {
  2.     ADC_CH0,
  3.     ADC_CH3,
  4.     ADC_CH4,
  5.     ADC_CH10,
  6.     ADC_CH12,
  7.     ADC_CH13,
  8.     ADC_CH16,
  9.     ADC_CH17,
  10.     ADC_CH18,
  11.    
  12.     ADC_CH_NUM
  13. };

ADC轮询方式的初始化代码比较简单,开启ADC时钟,并初始化对应的通道即可。
注意:ADC的采样时间最短能配置多少,需要根据实际情况调整,时间过短,通道的建立时间不足,将导致转换结果不正确,误差较大。通道16~18,需开启对应的使能位。
轮询方式的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.     ADC_CommonConfig_T adccommonconfig;
  14.     adc_ch_info_t *adc_ch_info = adc1_2_ch_info;
  15.    
  16.     if (ch < ADC_CH_NUM) {   
  17.         RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);
  18.         /* ADC Configuration */
  19.         ADC_Reset();
  20.         /* 配置为 120MHz / 8 = 15MHz */
  21.         ADC_CommonConfigStructInit(&adccommonconfig);
  22.         adccommonconfig.prescaler = ADC_PRESCALER_DIV8;
  23.         adccommonconfig.mode = ADC_MODE_INDEPENDENT;
  24.         adccommonconfig.accessMode = ADC_ACCESS_MODE_DISABLED;
  25.         adccommonconfig.twoSampling = ADC_TWO_SAMPLING_5CYCLES;
  26.         ADC_CommonConfig(&adccommonconfig);
  27.         
  28.         ADC_ConfigStructInit(&adcConfig);
  29.         adcConfig.resolution = ADC_RESOLUTION_12BIT;
  30.         adcConfig.scanConvMode = DISABLE;      
  31.         if (multi == 0) {
  32.             /* Set convMode continous*/
  33.             adcConfig.continuousConvMode   = DISABLE;
  34.         } else {
  35.             adcConfig.continuousConvMode   = ENABLE;
  36.         }
  37.         adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
  38.         adcConfig.extTrigConv = ADC_EXT_TRIG_CONV_TMR1_CC1;
  39.         adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT;
  40.         adcConfig.nbrOfChannel = 1;
  41.         ADC_Config(ADC_INS, &adcConfig);
  42.         if (ch == ADC_CH16) {
  43.             ADC_EnableTempSensorVrefint();
  44.         } else if (ch == ADC_CH17) {
  45.             ADC_EnableTempSensorVrefint();
  46.         } else if (ch == ADC_CH18) {
  47.             ADC_EnableVbat();
  48.         }
  49.         /* 通道采样周期 */
  50.         ADC_ConfigRegularChannel(ADC_INS, adc_ch_info[ch].adc_channel, 1, ADC_SAMPLETIME_480CYCLES);
  51.         /* 使能 ADC*/
  52.         ADC_Enable(ADC_INS);   
  53.     }
  54. }

轮询方式的ADC转换,使能启动ADC转换即可,等规则通道转换结束标志置位,则可读取规则数据寄存器的转换结果。
  1. /*
  2. * @brief       ADC启动
  3. *
  4. * @param       None
  5. *
  6. * @retval      None
  7. *
  8. */
  9. void bsp_adc_start(void)
  10. {
  11.     ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
  12.     ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_REGCS);
  13.     while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_REGCS) == SET);
  14.     ADC_SoftwareStartConv(ADC_INS);
  15. }

  16. /*
  17. * @brief       ADC停止
  18. *
  19. * @param       None
  20. *
  21. * @retval      None
  22. *
  23. */
  24. void bsp_adc_stop(void)
  25. {
  26.     ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_REGCS);
  27.     while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_REGCS) == SET);
  28.     ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
  29. }

  30. /*
  31. * @brief       ADC值获取
  32. *
  33. * @param       None
  34. *
  35. * @retval      AD值
  36. *
  37. */
  38. uint16_t bsp_adc_get_value(void)
  39. {
  40.     while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_EOC) == RESET);
  41.     ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
  42.     return ADC_ReadConversionValue(ADC_INS);
  43. }

DMA方式执行ADC转换,相比轮询方式,需要多配置一个DMA外设,配置代码略微复杂一些,但在多通道使用场景下,DMA方式的效率更高。
通过DMA方式启动ADC转换,只要开启ADC转换即可,通道转换后的ADC数据,DMA将逐个自动搬移到配置的地址空间,按照ADC转换的通道顺序排放,待ADC完成所有通道的转换,DMA也自动关闭,可自行使用数据。
注意:DMA的数据传输方向需配置为外设到存储器的方向,ADC数据为12位有效数据,DMA需配置为半字(16bit)传输,存储数组需为16位;另外可开启DMA的传输完成中断,通过中断获知ADC完成所有通道的转换。
另外使能ADCDMA功能后,还需调用ADC_EnableDMARequest函数使能连续转换模式,否则DMA传输只执行一次,后续不再传输。
  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.     ADC_CommonConfig_T adccommonconfig;
  14.    
  15.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);
  16.     RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_DMA2);
  17.    
  18.     bsp_adc_gpio_init();
  19.    
  20.     /* DMA配置 */
  21.     DMA_ConfigStructInit(&dmaConfig);
  22.     dmaConfig.channel = DMA_CHANNEL_0;
  23.     dmaConfig.peripheralBaseAddr = (uint32_t)&ADC_INS->REGDATA;
  24.     dmaConfig.memoryBaseAddr = (uint32_t)membuf;
  25.     dmaConfig.dir = DMA_DIR_PERIPHERALTOMEMORY;
  26.     dmaConfig.bufferSize = ADC_CH_NUM;
  27.     dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
  28.     dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;
  29.     dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
  30.     dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
  31.     dmaConfig.loopMode = DMA_MODE_NORMAL;
  32.     dmaConfig.priority = DMA_PRIORITY_LOW;
  33.     dmaConfig.fifoMode = DMA_FIFOMODE_DISABLE;
  34.     dmaConfig.fifoThreshold = DMA_FIFOTHRESHOLD_QUARTER;
  35.     dmaConfig.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
  36.     dmaConfig.memoryBurst = DMA_MEMORYBURST_SINGLE;
  37.     DMA_Config(DMA2_Stream0, &dmaConfig);
  38.    
  39.     /* ADC Configuration */
  40.     ADC_Reset();
  41.     /* 配置为 120MHz / 8 = 15MHz */
  42.     ADC_CommonConfigStructInit(&adccommonconfig);
  43.     adccommonconfig.prescaler = ADC_PRESCALER_DIV8;
  44.     adccommonconfig.mode = ADC_MODE_INDEPENDENT;
  45.     adccommonconfig.accessMode = ADC_ACCESS_MODE_DISABLED;
  46.     adccommonconfig.twoSampling = ADC_TWO_SAMPLING_5CYCLES;
  47.     ADC_CommonConfig(&adccommonconfig);
  48.         
  49.     ADC_ConfigStructInit(&adcConfig);
  50.     adcConfig.resolution = ADC_RESOLUTION_12BIT;
  51.     adcConfig.scanConvMode = ENABLE;
  52.     adcConfig.continuousConvMode = DISABLE;
  53.     adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
  54.     adcConfig.extTrigConv = ADC_EXT_TRIG_CONV_TMR1_CC1;
  55.     adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT;
  56.     adcConfig.nbrOfChannel = ADC_CH_NUM;
  57.     ADC_Config(ADC_INS, &adcConfig);
  58.    
  59.     ADC_EnableTempSensorVrefint();
  60.     ADC_EnableVbat();
  61.    
  62.     /* 通道采样周期 */
  63.     for (uint8_t ch = 0; ch < ADC_CH_NUM; ch++) {
  64.         ADC_ConfigRegularChannel(ADC_INS, adc1_2_ch_info[ch].adc_channel, (ch + 1), ADC_SAMPLETIME_480CYCLES);
  65.     }
  66.     ADC_EnableDMA(ADC_INS);
  67.     /* 注意需使能DMA请求,否则无法连续重启DMA进行数据传输 */
  68.     ADC_EnableDMARequest(ADC_INS);
  69.     /* Enable ADC*/
  70.     ADC_Enable(ADC_INS);  
  71. }

DMA方式的ADC启动,相比轮询方式,增加多一个DMA的传输数据量配置,而当检查到DMA的传输完成时,则为ADC完成所有通道的ADC转换。
  1. /*
  2. * @brief       ADC启动
  3. *
  4. * @param       None
  5. *
  6. * @retval      None
  7. *
  8. */
  9. void bsp_adc_start(void)
  10. {   
  11.     DMA_Disable(DMA2_Stream0);
  12.     DMA_ConfigDataNumber(DMA2_Stream0, ADC_CH_NUM);
  13.     DMA_Enable(DMA2_Stream0);
  14.     ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
  15.     ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_REGCS);
  16.     while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_REGCS) == SET);
  17.     ADC_SoftwareStartConv(ADC_INS);
  18. }

  19. /*
  20. * @brief       ADC停止
  21. *
  22. * @param       None
  23. *
  24. * @retval      None
  25. *
  26. */
  27. void bsp_adc_stop(void)
  28. {
  29.     ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_REGCS);
  30.     while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_REGCS) == SET);
  31.     ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
  32. }

  33. /*
  34. * @brief       ADC完成
  35. *
  36. * @param       None
  37. *
  38. * @retval      完成结果
  39. *
  40. */
  41. uint8_t bsp_adc_complete(void)
  42. {
  43.     uint8_t ret = 0;
  44.    
  45.     if (DMA_ReadStatusFlag(DMA2_Stream0, DMA_FLAG_TCI**0) != RESET) {
  46.         DMA_ClearStatusFlag(DMA2_Stream0, DMA_FLAG_TCI**0);
  47.         DMA_ClearStatusFlag(DMA2_Stream0, DMA_FLAG_HTI**0);
  48.         bsp_adc_stop();
  49.         ret = 1;
  50.     }
  51.    
  52.     return ret;
  53. }

APM32F427内部的TS传感器,对应ADC的内部通道16,将ADC转换值转换为电压后,可通过如下公式转换为温度值,通过这个传感器,可间接估算产品运行的大概环境温度。其中公式内的VsensorADC采样值转换后的电压值,而V25Slope可查APM32F035的数据手册“5.13.1.2 温度传感器特性”章节获得。
99192691d9e487d295.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     (760.0f)    /* 25oC 时的电压mV(数据手册) */
  14. #define SLOPE   (2.47f)     /* mV/℃,平均斜率(数据手册) */
  15.    
  16.     /* 转为电压值 */
  17.     mv = 3300.0f * value / 4095;
  18.     /* 转为温度值(公式来源为用户手册) */
  19.     ts = (mv - V25) / SLOPE + 25;
  20.    
  21.     return ts;
  22. }

4. 测试
ADCDMA方式测试代码如下:循环开启DMA,通道16执行温度转换。
温度的精度不高,但可作为产品运行环温的参考值。
  1. 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. }
73946691d9e7258a3c.png

5. 移植说明
移植代码,只需要修改结构adc1_2_ch_info即可,同时修改为对应的ADC外设。

6. 详细代码
DMA.zip (6.59 MB, 下载次数: 0) Poll.zip (6.56 MB, 下载次数: 0)
您需要登录后才可以回帖 登录 | 注册

本版积分规则

34

主题

64

帖子

0

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