本帖最后由 口天土立口 于 2025-11-20 09:00 编辑
#技术资源# #申请原创#
@21小跑堂
1. 外设介绍 APM32F427的ADC外设有三个,其中ADC1和ADC2的输入通道完全一致,而ADC3的输入通道则部分一致。
2. 硬件 APM32F427ZG TINY板
3. 驱动介绍 首先使用结构体信息方式组织代码,是为了初始化时能更精简代码,减少代码的冗余度;同时,数组信息的方式也能更清晰明了的展示ADC各个通道与引脚的对应关系。 另外,对于全新项目的开发,硬件板首版调试完成后,一般都存在调整IO引脚的需求,硬件第二版的ADC使用IO的调整,只需要更改数组的内容即可,无需更改函数接口内部代码。同时,枚举ADC_CH内部的各个成员命名可以更改为对应功能的名称,例如温度为ADC_TEMP,压力为ADC_PRESS,方便见名知义,应用层调用理解方便。 好的代码风格和命名也是提升产品质量的一部分,能避免后续团队维护代码理解错误,进而影响到产品质量。 - typedef struct {
- GPIO_T *port;
- uint16_t pin;
- uint32_t AHB1Periph;
- uint32_t adc_channel;
- } adc_ch_info_t;
- /* ADC1_2信息 */
- static adc_ch_info_t adc1_2_ch_info[] = {
- {GPIOA, GPIO_PIN_0, RCM_AHB1_PERIPH_GPIOA, ADC_CHANNEL_0 },
- {GPIOA, GPIO_PIN_3, RCM_AHB1_PERIPH_GPIOA, ADC_CHANNEL_3 },
- {GPIOA, GPIO_PIN_4, RCM_AHB1_PERIPH_GPIOA, ADC_CHANNEL_4 },
- {GPIOC, GPIO_PIN_0, RCM_AHB1_PERIPH_GPIOC, ADC_CHANNEL_10 },
- {GPIOC, GPIO_PIN_2, RCM_AHB1_PERIPH_GPIOC, ADC_CHANNEL_12 },
- {GPIOC, GPIO_PIN_3, RCM_AHB1_PERIPH_GPIOC, ADC_CHANNEL_13 },
- {NULL, 0, 0, ADC_CHANNEL_16 }, /* 温度 */
- {NULL, 0, 0, ADC_CHANNEL_17 }, /* Vref_in */
- {NULL, 0, 0, ADC_CHANNEL_18 }, /* Vbat */
- };
- /*
- * @brief 引脚初始化
- *
- * @param ch: 通道
- *
- * @retval None
- *
- */
- void bsp_adc_gpio_init(enum ADC_CH ch)
- {
- GPIO_Config_T gpioConfig;
- adc_ch_info_t *adc_ch_info = adc1_2_ch_info;
-
- if ((ch < ADC_CH_NUM) && (adc_ch_info[ch].port != NULL)) {
- RCM_EnableAHB1PeriphClock(adc_ch_info[ch].AHB1Periph);
- GPIO_ConfigStructInit(&gpioConfig);
- gpioConfig.pin = adc_ch_info[ch].pin;
- gpioConfig.mode = GPIO_MODE_AN;
- gpioConfig.speed = GPIO_SPEED_100MHz;
- gpioConfig.otype = GPIO_OTYPE_PP;
- gpioConfig.pupd = GPIO_PUPD_NOPULL;
- GPIO_Config(adc_ch_info[ch].port, &gpioConfig);
- }
- }
- enum ADC_CH {
- ADC_CH0,
- ADC_CH3,
- ADC_CH4,
- ADC_CH10,
- ADC_CH12,
- ADC_CH13,
- ADC_CH16,
- ADC_CH17,
- ADC_CH18,
-
- ADC_CH_NUM
- };
ADC轮询方式的初始化代码比较简单,开启ADC时钟,并初始化对应的通道即可。 注意:ADC的采样时间最短能配置多少,需要根据实际情况调整,时间过短,通道的建立时间不足,将导致转换结果不正确,误差较大。通道16~18,需开启对应的使能位。 轮询方式的ADC转换在需要多通道使用的场景下,效率较低。 - /*
- * @brief ADC初始化
- *
- * @param ch: 通道
- multi: 是否连续采样
- *
- * @retval None
- *
- */
- void bsp_adc_init(enum ADC_CH ch, uint8_t multi)
- {
- ADC_Config_T adcConfig;
- ADC_CommonConfig_T adccommonconfig;
- adc_ch_info_t *adc_ch_info = adc1_2_ch_info;
-
- if (ch < ADC_CH_NUM) {
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);
- /* ADC Configuration */
- ADC_Reset();
- /* 配置为 120MHz / 8 = 15MHz */
- ADC_CommonConfigStructInit(&adccommonconfig);
- adccommonconfig.prescaler = ADC_PRESCALER_DIV8;
- adccommonconfig.mode = ADC_MODE_INDEPENDENT;
- adccommonconfig.accessMode = ADC_ACCESS_MODE_DISABLED;
- adccommonconfig.twoSampling = ADC_TWO_SAMPLING_5CYCLES;
- ADC_CommonConfig(&adccommonconfig);
-
- ADC_ConfigStructInit(&adcConfig);
- adcConfig.resolution = ADC_RESOLUTION_12BIT;
- adcConfig.scanConvMode = DISABLE;
- if (multi == 0) {
- /* Set convMode continous*/
- adcConfig.continuousConvMode = DISABLE;
- } else {
- adcConfig.continuousConvMode = ENABLE;
- }
- adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
- adcConfig.extTrigConv = ADC_EXT_TRIG_CONV_TMR1_CC1;
- adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT;
- adcConfig.nbrOfChannel = 1;
- ADC_Config(ADC_INS, &adcConfig);
- if (ch == ADC_CH16) {
- ADC_EnableTempSensorVrefint();
- } else if (ch == ADC_CH17) {
- ADC_EnableTempSensorVrefint();
- } else if (ch == ADC_CH18) {
- ADC_EnableVbat();
- }
- /* 通道采样周期 */
- ADC_ConfigRegularChannel(ADC_INS, adc_ch_info[ch].adc_channel, 1, ADC_SAMPLETIME_480CYCLES);
- /* 使能 ADC*/
- ADC_Enable(ADC_INS);
- }
- }
轮询方式的ADC转换,使能启动ADC转换即可,等规则通道转换结束标志置位,则可读取规则数据寄存器的转换结果。 - /*
- * @brief ADC启动
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_adc_start(void)
- {
- ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
- ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_REGCS);
- while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_REGCS) == SET);
- ADC_SoftwareStartConv(ADC_INS);
- }
- /*
- * @brief ADC停止
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_adc_stop(void)
- {
- ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_REGCS);
- while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_REGCS) == SET);
- ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
- }
- /*
- * @brief ADC值获取
- *
- * @param None
- *
- * @retval AD值
- *
- */
- uint16_t bsp_adc_get_value(void)
- {
- while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_EOC) == RESET);
- ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
- return ADC_ReadConversionValue(ADC_INS);
- }
DMA方式执行ADC转换,相比轮询方式,需要多配置一个DMA外设,配置代码略微复杂一些,但在多通道使用场景下,DMA方式的效率更高。 通过DMA方式启动ADC转换,只要开启ADC转换即可,通道转换后的ADC数据,DMA将逐个自动搬移到配置的地址空间,按照ADC转换的通道顺序排放,待ADC完成所有通道的转换,DMA也自动关闭,可自行使用数据。 注意:DMA的数据传输方向需配置为外设到存储器的方向,ADC数据为12位有效数据,DMA需配置为半字(16bit)传输,存储数组需为16位;另外可开启DMA的传输完成中断,通过中断获知ADC完成所有通道的转换。 另外使能ADC的DMA功能后,还需调用ADC_EnableDMARequest函数使能连续转换模式,否则DMA传输只执行一次,后续不再传输。 - /*
- * @brief ADC初始化
- *
- * @param membuf: 存储地址
- *
- * @retval None
- *
- */
- void bsp_adc_init(uint16_t *membuf)
- {
- ADC_Config_T adcConfig;
- DMA_Config_T dmaConfig;
- ADC_CommonConfig_T adccommonconfig;
-
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_DMA2);
-
- bsp_adc_gpio_init();
-
- /* DMA配置 */
- DMA_ConfigStructInit(&dmaConfig);
- dmaConfig.channel = DMA_CHANNEL_0;
- dmaConfig.peripheralBaseAddr = (uint32_t)&ADC_INS->REGDATA;
- dmaConfig.memoryBaseAddr = (uint32_t)membuf;
- dmaConfig.dir = DMA_DIR_PERIPHERALTOMEMORY;
- dmaConfig.bufferSize = ADC_CH_NUM;
- dmaConfig.peripheralInc = DMA_PERIPHERAL_INC_DISABLE;
- dmaConfig.memoryInc = DMA_MEMORY_INC_ENABLE;
- dmaConfig.peripheralDataSize = DMA_PERIPHERAL_DATA_SIZE_HALFWORD;
- dmaConfig.memoryDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD;
- dmaConfig.loopMode = DMA_MODE_NORMAL;
- dmaConfig.priority = DMA_PRIORITY_LOW;
- dmaConfig.fifoMode = DMA_FIFOMODE_DISABLE;
- dmaConfig.fifoThreshold = DMA_FIFOTHRESHOLD_QUARTER;
- dmaConfig.peripheralBurst = DMA_PERIPHERALBURST_SINGLE;
- dmaConfig.memoryBurst = DMA_MEMORYBURST_SINGLE;
- DMA_Config(DMA2_Stream0, &dmaConfig);
-
- /* ADC Configuration */
- ADC_Reset();
- /* 配置为 120MHz / 8 = 15MHz */
- ADC_CommonConfigStructInit(&adccommonconfig);
- adccommonconfig.prescaler = ADC_PRESCALER_DIV8;
- adccommonconfig.mode = ADC_MODE_INDEPENDENT;
- adccommonconfig.accessMode = ADC_ACCESS_MODE_DISABLED;
- adccommonconfig.twoSampling = ADC_TWO_SAMPLING_5CYCLES;
- ADC_CommonConfig(&adccommonconfig);
-
- ADC_ConfigStructInit(&adcConfig);
- adcConfig.resolution = ADC_RESOLUTION_12BIT;
- adcConfig.scanConvMode = ENABLE;
- adcConfig.continuousConvMode = DISABLE;
- adcConfig.extTrigEdge = ADC_EXT_TRIG_EDGE_NONE;
- adcConfig.extTrigConv = ADC_EXT_TRIG_CONV_TMR1_CC1;
- adcConfig.dataAlign = ADC_DATA_ALIGN_RIGHT;
- adcConfig.nbrOfChannel = ADC_CH_NUM;
- ADC_Config(ADC_INS, &adcConfig);
-
- ADC_EnableTempSensorVrefint();
- ADC_EnableVbat();
-
- /* 通道采样周期 */
- for (uint8_t ch = 0; ch < ADC_CH_NUM; ch++) {
- ADC_ConfigRegularChannel(ADC_INS, adc1_2_ch_info[ch].adc_channel, (ch + 1), ADC_SAMPLETIME_480CYCLES);
- }
- ADC_EnableDMA(ADC_INS);
- /* 注意需使能DMA请求,否则无法连续重启DMA进行数据传输 */
- ADC_EnableDMARequest(ADC_INS);
- /* Enable ADC*/
- ADC_Enable(ADC_INS);
- }
DMA方式的ADC启动,相比轮询方式,增加多一个DMA的传输数据量配置,而当检查到DMA的传输完成时,则为ADC完成所有通道的ADC转换。 - /*
- * @brief ADC启动
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_adc_start(void)
- {
- DMA_Disable(DMA2_Stream0);
- DMA_ConfigDataNumber(DMA2_Stream0, ADC_CH_NUM);
- DMA_Enable(DMA2_Stream0);
- ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
- ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_REGCS);
- while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_REGCS) == SET);
- ADC_SoftwareStartConv(ADC_INS);
- }
- /*
- * @brief ADC停止
- *
- * @param None
- *
- * @retval None
- *
- */
- void bsp_adc_stop(void)
- {
- ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_REGCS);
- while (ADC_ReadStatusFlag(ADC_INS, ADC_FLAG_REGCS) == SET);
- ADC_ClearStatusFlag(ADC_INS, ADC_FLAG_EOC);
- }
- /*
- * @brief ADC完成
- *
- * @param None
- *
- * @retval 完成结果
- *
- */
- uint8_t bsp_adc_complete(void)
- {
- uint8_t ret = 0;
-
- if (DMA_ReadStatusFlag(DMA2_Stream0, DMA_FLAG_TCI**0) != RESET) {
- DMA_ClearStatusFlag(DMA2_Stream0, DMA_FLAG_TCI**0);
- DMA_ClearStatusFlag(DMA2_Stream0, DMA_FLAG_HTI**0);
- bsp_adc_stop();
- ret = 1;
- }
-
- return ret;
- }
APM32F427内部的TS传感器,对应ADC的内部通道16,将ADC转换值转换为电压后,可通过如下公式转换为温度值,通过这个传感器,可间接估算产品运行的大概环境温度。其中公式内的Vsensor为ADC采样值转换后的电压值,而V25和Slope可查APM32F035的数据手册“5.13.1.2 温度传感器特性”章节获得。
- /*
- * @brief 温度值转换
- *
- * @param value: 码值
- *
- * @retval 温度值
- *
- */
- float bsp_ts_convert(uint16_t value)
- {
- float ts = 0.0f;
- float mv = 0.0f;
- #define V25 (760.0f) /* 25oC 时的电压mV(数据手册) */
- #define SLOPE (2.47f) /* mV/℃,平均斜率(数据手册) */
-
- /* 转为电压值 */
- mv = 3300.0f * value / 4095;
- /* 转为温度值(公式来源为用户手册) */
- ts = (mv - V25) / SLOPE + 25;
-
- return ts;
- }
4. 测试 ADC的DMA方式测试代码如下:循环开启DMA,通道16执行温度转换。 温度的精度不高,但可作为产品运行环温的参考值。 - 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();
- }
- }
5. 移植说明 移植代码,只需要修改结构adc1_2_ch_info即可,同时修改为对应的ADC外设。
6. 详细代码
|