【自学嵌入式:stm32单片机】AD多通道
AD多通道
接线图

在这里我们使用了4个AD通道,多次转换,连续扫描,会出现数据覆盖问题,需要用到下一节DMA的知识,所以这里用单次转换非扫描模式,只需要在触发转换之前,手动更改一下列表的第一个通道就行了
代码实现
标准库实现
已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32
AD.h
#ifndef __AD_H
#define __AD_H
void AD_Init(void);
uint16_t AD_GetValue(uint8_t ADC_Channel);
#endif
AD.c
#include "stm32f10x.h" // Device header
//初始化AD
void AD_Init(void)
{
//基本步骤
//开启RCC时钟,包括ADC和GPIO的时钟
//ADCCLK的分频器也需要配置一下
//配置GPIO,把需要用的GPIO配置成模拟输入模式
//配置多路开关,把左边的通道接入到右边的规则列表里
//配置ADC转换器
//需要看门狗就配置,需要中断就在中断输出控制里用ITConfig函数开启对应的中断输出
//然后再在NVIC里,配置一下优先级,这样就能触发中断
//开关控制,调用ADC_Cmd函数,开启ADC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟,ADC都是APB2上的设备
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟,准备开启PA0口
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择6分频,上一篇文章说,只能开启6和8分频,最大14MHz
//配置GPIO,让PA0口变为模拟输入的引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; //PA0,1,2,3
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//结构体初始化ADC
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //独立模式,不是双ADC
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发源选择,不使用外部触发,也就是内部软件触发的意思
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换
ADC_InitStructure.ADC_NbrOfChannel = 1; //通道数目,目前是一个通道,所以给1
ADC_InitStructure.ADC_ScanConvMode = DISABLE; //非扫描
ADC_Init(ADC1, &ADC_InitStructure);
//开启ADC电源
ADC_Cmd(ADC1, ENABLE);
//校准ADC
ADC_ResetCalibration(ADC1); //复位
while(ADC_GetResetCalibrationStatus(ADC1) == SET); //返回复位校准的状态,等待复位完成,还需要加一个while循环
//ADC_GetCal...获取的是CR2寄存器的RSTCAL标志位,该位由软件设置并由硬件清除
//在校准寄存器被初始化后,该位将被清除,该位被软件置1,开始复位校准,完成后,该位变0
ADC_StartCalibration(ADC1); //开始校准
while(ADC_GetCalibrationStatus(ADC1) == SET); //获取校准状态
}
//返回AD转换的值,指定对应通道
uint16_t AD_GetValue(uint8_t ADC_Channel)
{
//选择规则组的输入通道
//只有一个通道,使用非扫描模式,指定通道就放在第一个序列1的位置
ADC_RegularChannelConfig(ADC1, ADC_Channel, 1, ADC_SampleTime_55Cycles5); //最后一个参数是采样周期,目前没要求,随便配置,采样时间就是55.5个ADCCLK的周期
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件ADC转换
//ADC_FLAG_EOC规则组转换完成标志位
//状态寄存器里,有EOC转换结束标志位,该位由硬件在(规则或注入)通道组转换结束时设置
//由软件清除或由读取ADC_DR(数据寄存器)时清除
//0:转换未完成
//1:转换完成
while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET); //转换未完成一直循环等待直到转换完成
//具体等待时间是采样周期55.5,转换周期是固定的12.5,加在一起就是68个周期
//前面配置的ADCCLK是72MHz的6分频,也就是12MHz,进行68个周期才能完成
//最终时间是1/12M * 68 ,结果大概是5.6us
return ADC_GetConversionValue(ADC1); //获取转换结果,它直接读取ADC的DR数据寄存器
//因为读取DR寄存器会自动清楚EOC标志位,所以这之后我们就不需要再手动清除标志位了
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "MYOLED.h"
#include "AD.h"
uint16_t AD0, AD1, AD2, AD3;
int main(void)
{
MYOLED_Init();
AD_Init();
MYOLED_ShowString(0,0,"AD0:");
MYOLED_ShowString(0,1,"AD1:");
MYOLED_ShowString(0,2,"AD2:");
MYOLED_ShowString(0,3,"AD3:");
while(1)
{
//单次转换非扫描模式
AD0 = AD_GetValue(ADC_Channel_0);
AD1 = AD_GetValue(ADC_Channel_1);
AD2 = AD_GetValue(ADC_Channel_2);
AD3 = AD_GetValue(ADC_Channel_3);
MYOLED_ShowNum(4,0,AD0,4);
MYOLED_ShowNum(4,1,AD1,4);
MYOLED_ShowNum(4,2,AD2,4);
MYOLED_ShowNum(4,3,AD3,4);
Delay_ms(100);
}
}
HAL库实现
已开源到:https://gitee.com/qin-ruiqian/jiangkeda-stm32-hal
AD.h
/*
* AD.h
*
* Created on: Aug 14, 2025
* Author: Administrator
*/
#ifndef HARDWARE_AD_H_
#define HARDWARE_AD_H_
typedef struct AD
{
ADC_HandleTypeDef hadc; //当前用的是哪个ADC转换器
}AD;
void AD_Init(AD* ad, ADC_HandleTypeDef hadc);
uint16_t AD_GetValue(AD* ad, uint32_t ADC_Channel);
#endif /* HARDWARE_AD_H_ */
AD.c
/*
* AD.c
*
* Created on: Aug 14, 2025
* Author: Administrator
*/
#include "stm32f1xx_hal.h"
#include "AD.h"
//初始化AD
void AD_Init(AD* ad, ADC_HandleTypeDef hadc)
{
ad->hadc = hadc;
//在 HAL 库中,ADC 的校准操作被封装成了一个专门的函数,
//无需像标准库那样分步调用复位校准、等待复位完成、
//开始校准、等待校准完成等多个步骤。
if (HAL_ADCEx_Calibration_Start(&(ad->hadc)) != HAL_OK)
{
// 校准失败处理,没有处理,不写
}
}
//获取AD的值
uint16_t AD_GetValue(AD* ad, uint32_t ADC_Channel)
{
ADC_ChannelConfTypeDef sConfig = {0};
// 配置当前需要转换的通道
sConfig.Channel = ADC_Channel; // 指定通道(如ADC_CHANNEL_0~3)
sConfig.Rank = ADC_REGULAR_RANK_1; // 规则组第1位
sConfig.SamplingTime = ADC_SAMPLETIME_55CYCLES_5; // 采样时间55.5周期
HAL_ADC_ConfigChannel(&(ad->hadc), &sConfig); //设置通道等
HAL_ADC_Start(&ad->hadc); //软件开启ADC
HAL_ADC_PollForConversion(&(ad->hadc), HAL_MAX_DELAY); //等待转换
uint16_t value = (uint16_t)HAL_ADC_GetValue(&(ad->hadc)); // 获取转换结果(自动清除EOC标志位)
return value;
}
main.c
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2025 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "MYOLED.h"
#include "AD.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
/* USER CODE BEGIN PV */
uint16_t AD0, AD1, AD2, AD3;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
MYOLED_Init();
AD ad;
AD_Init(&ad, hadc1);
MYOLED_ShowString(0,0,"AD0:");
MYOLED_ShowString(0,1,"AD1:");
MYOLED_ShowString(0,2,"AD2:");
MYOLED_ShowString(0,3,"AD3:");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
//单次转换非扫描模式
AD0 = AD_GetValue(&ad, ADC_CHANNEL_0);
AD1 = AD_GetValue(&ad, ADC_CHANNEL_1);
AD2 = AD_GetValue(&ad, ADC_CHANNEL_2);
AD3 = AD_GetValue(&ad, ADC_CHANNEL_3);
MYOLED_ShowNum(4,0,AD0,4);
MYOLED_ShowNum(4,1,AD1,4);
MYOLED_ShowNum(4,2,AD2,4);
MYOLED_ShowNum(4,3,AD3,4);
HAL_Delay(100);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Common config
*/
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8|GPIO_PIN_9, GPIO_PIN_SET);
/*Configure GPIO pins : PB8 PB9 */
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
实现效果

浙公网安备 33010602011771号