本帖最后由 口天土立口 于 2025-11-5 14:35 编辑
#技术资源# #申请原创#
@21小跑堂
1. 外设介绍 根据APM32F035的用户手册描述,ADC为12位精度,16个外部通道和3个内部通道,即总共19个通道。检查APM32F035的数据手册,得到如下的通道情况: | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 需使能寄存器位ADC_CCFG.HLAF_VDDEN |
根据用户手册“24.3.1 ADC引脚和内部信号”章节介绍得知,ADC的参考电压为VDDA,即对应MCU的引脚号9,同时ADC输入范围为:VSSA ~ VDDA。
3. 驱动介绍 首先使用结构体信息方式组织代码,是为了初始化时能更精简代码,减少代码的冗余度;同时,数组信息的方式也能更清晰明了的展示ADC各个通道与引脚的对应关系。 另外,对于全新项目的开发,硬件板首版调试完成后,一般都存在调整IO引脚的需求,硬件第二版的ADC使用IO的调整,只需要更改数组的内容即可,无需更改函数接口内部代码。同时,枚举ADC_CH内部的各个成员命名可以更改为对应功能的名称,例如温度为ADC_TEMP,压力为ADC_PRESS,方便见名知义,应用层调用理解方便。 好的代码风格和命名也是提升产品质量的一部分,能避免后续团队维护代码理解错误,进而影响到产品质量。 - typedef struct {
- GPIO_T *port;
- uint16_t pin;
- uint32_t AHBPeriph;
- uint32_t adc_channel;
- } adc_ch_info_t;
- /* ADC信息 */
- static adc_ch_info_t adc_ch_info[] = {
- {GPIOA, GPIO_PIN_2, RCM_AHB_PERIPH_GPIOA, ADC_CHANNEL_0 },
- {GPIOA, GPIO_PIN_3, RCM_AHB_PERIPH_GPIOA, ADC_CHANNEL_1 },
- {GPIOA, GPIO_PIN_4, RCM_AHB_PERIPH_GPIOA, ADC_CHANNEL_2 },
- {GPIOA, GPIO_PIN_5, RCM_AHB_PERIPH_GPIOA, ADC_CHANNEL_3 },
- {GPIOA, GPIO_PIN_0, RCM_AHB_PERIPH_GPIOA, ADC_CHANNEL_4 },
- {GPIOA, GPIO_PIN_1, RCM_AHB_PERIPH_GPIOA, ADC_CHANNEL_5 },
- {GPIOA, GPIO_PIN_6, RCM_AHB_PERIPH_GPIOA, ADC_CHANNEL_6 },
- {GPIOA, GPIO_PIN_7, RCM_AHB_PERIPH_GPIOA, ADC_CHANNEL_7 },
- {GPIOB, GPIO_PIN_0, RCM_AHB_PERIPH_GPIOB, ADC_CHANNEL_8 },
- {GPIOB, GPIO_PIN_1, RCM_AHB_PERIPH_GPIOB, ADC_CHANNEL_9 },
- {GPIOC, GPIO_PIN_0, RCM_AHB_PERIPH_GPIOC, ADC_CHANNEL_10 },
- {GPIOC, GPIO_PIN_1, RCM_AHB_PERIPH_GPIOC, ADC_CHANNEL_11 },
- {GPIOB, GPIO_PIN_10, RCM_AHB_PERIPH_GPIOB, ADC_CHANNEL_12 },
- {GPIOC, GPIO_PIN_3, RCM_AHB_PERIPH_GPIOC, ADC_CHANNEL_13 },
- {GPIOC, GPIO_PIN_4, RCM_AHB_PERIPH_GPIOC, ADC_CHANNEL_14 },
- {GPIOC, GPIO_PIN_5, RCM_AHB_PERIPH_GPIOC, ADC_CHANNEL_15 },
- {NULL, 0, 0, ADC_CHANNEL_16 }, /* 温度 */
- {NULL, 0, 0, ADC_CHANNEL_17 }, /* Vref_in */
- {NULL, 0, 0, ADC_CHANNEL_18 }, /* VDD/2 */
- };
- /*
- * @brief 引脚初始化
- *
- * @param ch: 通道
- *
- * @retval None
- *
- */
- void bsp_adc_gpio_init(enum ADC_CH ch)
- {
- GPIO_Config_T gpioConfig;
-
- if ((ch < ADC_CH_NUM) && (adc_ch_info[ch].port != NULL)) {
- RCM_EnableAHBPeriphClock(adc_ch_info[ch].AHBPeriph);
- GPIO_ConfigStructInit(&gpioConfig);
- gpioConfig.pin = adc_ch_info[ch].pin;
- gpioConfig.mode = GPIO_MODE_AN;
- gpioConfig.outtype = GPIO_OUT_TYPE_PP;
- gpioConfig.speed = GPIO_SPEED_50MHz;
- gpioConfig.pupd = GPIO_PUPD_NO;
- GPIO_Config(adc_ch_info[ch].port, &gpioConfig);
- }
- }
- enum ADC_CH {
- ADC_CH0,
- ADC_CH1,
- ADC_CH2,
- ADC_CH3,
- ADC_CH4,
- ADC_CH5,
- ADC_CH6,
- ADC_CH7,
- ADC_CH8,
- ADC_CH9,
- ADC_CH10,
- ADC_CH11,
- ADC_CH12,
- ADC_CH13,
- ADC_CH14,
- ADC_CH15,
- ADC_CH16,
- ADC_CH17,
- ADC_CH18,
-
- ADC_CH_NUM
- };
如下为轮询方式执行ADC转换,开始ADC转换前,必须确保寄存器位ADC_STS.ADCRDY**为置1已准备好状态,当ADC_STS.EOC**为1表明ADC已经转换完成,可以从寄存器ADC_DATA获取转换之后的数据。 注意:ADC的采样时间最短能配置多少,需要根据实际情况调整,时间过短,通道的建立时间不足,将导致转换结果不正确,误差较大。通道16~19,需开启对应的使能位。 轮询方式的ADC转换代码比较简单,但在需要多通道使用的场景下,此方式效率较低。 - /*
- * @brief ADC初始化
- *
- * @param ch: 通道
- multi: 是否连续采样
- *
- * @retval None
- *
- */
- void bsp_adc_init(enum ADC_CH ch, uint8_t multi)
- {
- ADC_Config_T adcConfig;
-
- if (ch < ADC_CH_NUM) {
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC);
- /* ADC Configuration */
- ADC_Reset();
- ADC_ConfigStructInit(&adcConfig);
- adcConfig.resolution = ADC_RESOLUTION_12B;
- adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT;
- adcConfig.scanDir = ADC_SCAN_DIR_UPWARD;
- if (multi == 0) {
- /* Set convMode continous*/
- adcConfig.convMode = ADC_CONVERSION_SINGLE;
- } else {
- adcConfig.convMode = ADC_CONVERSION_CONTINUOUS;
- }
- adcConfig.seqMode = ADC_SEQ_MODE_DISABLE;
- adcConfig.seqGapTime = 0;
- adcConfig.extTrigConv1 = ADC_EXT_TRIG_CONV_TRG0;
- adcConfig.extTrigEdge1 = ADC_EXT_TRIG_EDGE_NONE;
- adcConfig.extTrigConv2 = ADC_EXT_TRIG_CONV_TRG0;
- adcConfig.extTrigEdge2 = ADC_EXT_TRIG_EDGE_NONE;
- adcConfig.extTrigConv3 = ADC_EXT_TRIG_CONV_TRG0;
- adcConfig.extTrigEdge3 = ADC_EXT_TRIG_EDGE_NONE;
- ADC_Config(&adcConfig);
- if (ch == ADC_CH16) {
- ADC_EnableTempSensor();
- } else if (ch == ADC_CH17) {
- ADC_EnableVrefint();
- } else if (ch == ADC_CH18) {
- ADC_EnableHalfVDD();
- }
- ADC_ConfigChannel(adc_ch_info[ch].adc_channel, ADC_SAMPLE_TIME_41_5);
- /* Calibration*/
- ADC_ReadCalibrationFactor();
- /* Enable ADC*/
- ADC_Enable();
- }
- }
- /*
- * @brief ADC启动
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_adc_start(void)
- {
- /* Wait until ADC is ready */
- while (!ADC_ReadStatusFlag(ADC_FLAG_ADRDY));
- ADC_StartConversion();
- }
- /*
- * @brief ADC停止
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_adc_stop(void)
- {
- ADC_StopConversion();
- }
- /*
- * @brief ADC值获取
- *
- * @param None
- *
- * @retval AD值
- *
- */
- uint16_t bsp_adc_get_value(void)
- {
- while (ADC_ReadStatusFlag(ADC_FLAG_CC) == RESET);
- ADC_ClearStatusFlag(ADC_FLAG_CC);
- return ADC_ReadConversionValue();
- }
如下为通过DMA方式执行ADC转换,相比轮询方式,需要多配置一个DMA外设,配置代码略微复杂一些,但在多通道使用场景下,DMA方式的效率更高。 通过DMA方式启动ADC转换,只要开启ADC转换即可,通道转换后的ADC数据,DMA将逐个自动搬移到配置的地址空间,按照ADC转换的通道顺序排放,待ADC完成所有通道的转换,DMA也自动关闭,可自行使用数据。 注意:DMA的数据传输方向需配置为外设到存储器的方向,ADC数据为12位有效数据,DMA需配置为半字(16bit)传输,存储数组需为16位;另外可开启DMA的传输完成中断,通过中断获知ADC完成所有通道的转换。
APM32F035内部的TS传感器,对应ADC的内部通道16,将ADC转换值转换为电压后,可通过如下公式转换为温度值,通过这个传感器,可间接估算产品运行的大概环境温度。其中公式内的Vsensor问ADC采样值转换后的电压值,而V25和Slope可查APM32F035的数据手册“6.10.2 温度传感器特性”章节获得。 - /*
- * @brief 温度值转换
- *
- * @param value: 码值
- *
- * @retval 温度值
- *
- */
- float bsp_ts_convert(uint16_t value)
- {
- float ts = 0.0f;
- float mv = 0.0f;
- #define V25 (1420.0f) /* 25oC 时的电压(数据手册) */
- #define SLOPE (4) /* mV/℃,平均斜率(数据手册) */
-
- /* 转为电压值 */
- mv = 3300.0f * value / 4095;
- /* 转为温度值(公式来源为用户手册) */
- ts = (V25 - mv) / SLOPE + 25;
-
- return ts;
- }
4. 测试 测试代码如下,需注意开启新一轮ADC转换前,需等上一次的转换结束。同时,在将ADC转换值转换为电压时,需注意计算公式内的数据精度丢失问题,所以adc_ch_value先转为float类型再开始计算。 - <i>uint16_t adc_ch_value[ADC_CH_NUM];
- float adc_ch_voltage[ADC_CH_NUM];
- float temp_sensor;
- // 应用初始化
- void app_init(void)
- {
- bsp_adc_gpio_init();
- bsp_adc_init(adc_ch_value);
- bsp_adc_start();
- }
- // 应用任务
- void app_task(void)
- {
- if (bsp_adc_complete() != 0) {
- for (uint8_t i = 0; i < ADC_CH_NUM; i++) {
- adc_ch_voltage[i] = ((float)adc_ch_value[i]) / 4095 * 3.3f;
- temp_sensor = bsp_ts_convert(adc_ch_value[ADC_CH16]);
- }
- bsp_adc_start();
- }
- }</i>
5. 详细代码 ADC使用DMA驱动代码:
DMA.zip
(2.6 MB, 下载次数: 3)
|
打赏榜单
21小跑堂 打赏了 50.00 元 2025-11-24 理由:恭喜通过原创审核!期待您更多的原创作品~~
评论
|