单片机(STM32-ADC模数转换器)
一、基础知识
1. 模拟信号(Analog Signal)
- 定义:模拟信号是连续变化的信号,可以取任意数值。
- 特点:幅值和时间都是连续的,没有“跳变”。
- 举例:
- 声音(麦克风采集到的电压)
- 温度传感器输出的电压
- 光敏电阻输出的电压
- 正弦波、三角波等
2. 数字信号(Digital Signal)
- 定义:数字信号是离散的,只能取有限个特定数值(通常是0和1)。
- 特点:幅值和时间都是“跳变”的,只有高低电平(如3.3V/0V)。
- 举例:
- 单片机的GPIO输出
- 计算机中的二进制数据
- 数码管显示的数字
3. 转换器
3.1 ADC(模数转换器,Analog to Digital Converter)
- 作用:把模拟信号转换为数字信号,让单片机、计算机等数字系统能够“理解”模拟世界的信息。
- 举例:STM32的ADC模块可以把0~3.3V的电压转换为0~4095的数字值(12位ADC)。
3.2 DAC(数模转换器,Digital to Analog Converter)
- 作用:把数字信号转换为模拟信号,让数字系统输出连续变化的电压或电流。
- 举例:STM32的DAC模块可以把数字值(如0~4095)转换为0~3.3V的模拟电压,用于音频输出、波形发生等。
二、ADC(模数转换器)
1. 概念
ADC(Analog to Digital Converter,模数转换器)是将模拟信号(如电压)转换为数字信号(如二进制数值)的硬件模块。
在STM32等单片机中,ADC模块可以让你采集外部传感器、信号源等的模拟电压,并在程序中进行处理。
2. ADC的主要作用(应用场景)
采集传感器信号(如温度、光照、湿度、压力等)
检测电池电压、电流等模拟量
实现音频采集、波形分析等功能
3. STM32 ADC的基本特性
(1) 多通道:STM32的ADC通常有多个输入通道(如16、18、24等),可采集多个模拟信号。
(2) 分辨率:常见有14位、12位、10位、8位、6位等,12位ADC的输出范围是0~4095。
(3) 采样速率:支持高速采样,适合音频、波形等应用。
(4) 多种采样模式:单次采样、连续采样、扫描模式、间断模式等。
(5) 支持DMA:可用DMA自动搬运数据,减轻CPU负担。
(6) 内部通道:可采集芯片内部温度、参考电压等。
(7) 转换时间:是指ADC从开始采样到输出数字结果所需的总时间。包含采样时间(ADC转采集输入电压的时间)和转换时间(ADC内部将采样到的模拟信号转换成数字值的时间)。
(8)量程:能测量的电压范围 0 ~ 3.6V(VSS ~VDD) (单片机供电范围是1.71到3.6)
4.ADC工作原理
(1). 多路输入与通道选择
- IN0~IN7:8路模拟输入信号。
- 通道选择开关:通过外部的地址线(ADDA、ADDB、ADDC)和地址锁存允许(ALE)信号,选择其中一路模拟信号输入到后续电路。
(2). 地址锁存和译码
- ADDA、ADDB、ADDC:三根地址线,选择输入通道(000~111对应IN0~IN7)。
- ALE:地址锁存允许信号,锁存当前地址,防止转换过程中地址变化。
(3). 逐次逼近寄存器(SAR, Successive Approximation Register)
- 作用:控制DAC输出的电压,逐步逼近输入的模拟电压,实现A/D转换。
- 原理:
- SAR内部有8位寄存器(对应8位ADC)。
- 转换过程从最高位(MSB)到最低位(LSB)依次判断每一位是1还是0。
- 每判断一位,SAR就把该位暂时置1,其余低位为0,然后控制DAC输出对应的电压,与输入模拟电压比较。
- 如果DAC输出电压小于输入电压,则该位保留为1;否则清零为0。
- 依次判断8位,最终得到8位数字量。
(4). DAC(数模转换器)
- 作用:把SAR当前寄存器的内容(数字量)转换成模拟电压,供比较器与输入模拟电压比较。
- 原理:
- DAC接收SAR输出的8位数字量,输出对应的模拟电压。
- 这个电压和输入的模拟电压一起送到比较器。
- 比较器输出高低电平,反馈给SAR,决定当前位是1还是0。
(5). 逐次逼近A/D转换过程举例(八位)
假设要把某一路输入电压转换为数字量,过程如下:
1. SAR先把最高位(D7)设为1,其余为0,DAC输出对应电压。
2. 比较器比较:
如果DAC电压 < 输入电压,D7保留为1;
否则D7清零为0。
3. SAR再把次高位(D6)设为1,其余低位为0,DAC输出新电压。
4. 重复比较,依次判断D6、D5……直到D0。
5. 8位全部判断完毕,SAR寄存器内容就是输入电压的数字表示。
6. 把数据存入八位三态锁存缓冲器,等待主机获取。
5. U5(STM32U575RIT6)的ADC
6. 工作模式
6.1 工作模式划分
1. 单次转换模式(Single Conversion Mode)
- 说明:ADC只进行一次模数转换,转换完成后自动停止。
- 应用:适合对某一通道偶尔采样的场合,比如需要时才采集一次数据。
2. 连续转换模式(Continuous Conversion Mode)
- 说明:ADC会不断地进行模数转换,转换完成后自动开始下一次转换,循环往复。
- 应用:适合需要实时、连续采集数据的场合,如电压、电流实时监测。
3. 扫描转换模式(Scan Conversion Mode)
- 说明:ADC可以按照预先设定的通道顺序,依次对多个通道进行采样和转换。
- 应用:适合多路信号采集,如多传感器数据采集。
4. 间断转换模式(Discontinuous Conversion Mode)
- 说明:在扫描模式基础上,每次只转换设定数量的通道,然后暂停,等待下次触发再继续转换剩余通道。
- 应用:适合对多通道分批采集,减少瞬时采集压力。
5. 插队转换模式(Injected Conversion Mode)
- 说明:在常规转换过程中,可以被高优先级的“插队”转换打断,优先采集插队通道的数据,插队转换完成后再恢复常规转换。
- 应用:适合需要对某些重要信号进行优先采集的场合,如故障检测、紧急信号采集。
6.2 指示采集过程不同阶段的两个重要标志位EOC和EOS:
1. EOC(End Of Conversion,转换结束)
- 含义:EOC 表示“单次转换结束”。
- 作用:当ADC完成对某一个通道的模数转换后,EOC标志会被置位,表示本次采样的数据已经转换完成,可以读取转换结果了。
- 应用场景:适用于单通道采集或多通道逐个采集时,判断每个通道的数据是否转换完成。
- 举例:假如你配置ADC采集通道1,通道1的电压转换完成后,EOC标志置位,软件可以读取通道1的ADC值。
2. EOS(End Of Sequence,序列结束)
- 含义:EOS 表示“序列采集结束”。
- 作用:当ADC配置为扫描模式(即一次采集多个通道,形成一个采集序列)时,只有当本轮所有通道都采集并转换完成后,EOS标志才会被置位,表示整个采集序列结束。
- 应用场景:适用于多通道扫描采集,判断一轮所有通道的数据是否都已采集完成。
- 举例:假如你配置ADC依次采集通道1、2、3,只有当3个通道都采集完毕后,EOS标志才会置位,软件可以批量读取所有通道的ADC值。
7. 使用ADC进行单通道采集
使用ADC采集VBAT的电压,并且打印到串口。
在底板上,纽扣电池BT1的作用是在外部电源断电时为RTC(实时时钟)模块持续供电,确保保证RTC时钟和时间信息不丢失;而S1是一个与单片机连接的开关,其在原理图中的作用是切换RTC的供电来源,当S1向上拨时,RTC通过外部电源供电,适用于系统正常工作时;当S1向下拨时,RTC则由纽扣电池BT1供电,适用于外部电源断开时保证RTC继续运行,这样设计可以在系统掉电时依然保持实时时钟的计时功能。
此外,S1的状态还可以通过连接到单片机的引脚进行检测,单片机可以根据该引脚的电平判断当前RTC的供电状态,从而在软件上做出相应的处理,比如提示用户更换电池或记录掉电事件,实现对系统供电状态的智能管理。
所以当我们外部不接电并且想让纽扣电池给单片机内部RTC供电就要求我们按钮拨到BAT方向,让纽扣电池供电。
1. 使用ADC4采集纽扣电池BT1的电压,设置ADC4检测的通道为Vbat/4通道,这样ADC4的一个对应的通道就和单片机引脚VBAT连接,可以进行代码编辑采集电压。
其他属性补充,单通按照上面配置:
2.编辑代码:
U5特有步骤:(只需要执行一次,放在主函数内while外面,注释中详细介绍了)
HAL_PWREx_EnableVddA(); //这个函数用于启用VDDA电压域。VDDA是指处理器的模拟电源电压域,用于供电模拟功能模块,例如ADC,DAC等。通过HAL_PWREx_EnableVddA()函数,可以使处理器的VDDA电压域处于启用状态,以供给模拟功能模块所需的电源。
HAL_PWREx_EnableVddIO2(); //这个函数用于启用VDDIO2电压域。VDDIO2是指处理器的I/O引脚电源域,用于供电处理器的I/O引脚。通过调用HAL_PWREx_EnableVddIO2()函数,可以使处理器的VDDIO2电压域处于启用状态,以供给处理器的I/O引脚所需的电源。
HAL_ADCEx_Calibration_Start(&hadc4,ADC_CALIB_OFFSET,ADC_SINGLE_ENDED); //校准单端ADC采样
所有类型芯片采样都要做的事:(放在while循环多次采样,也可以只测一次看个人需要)
HAL_ADC_Start(&hadc4); //启动adc转换
HAL_ADC_PollForConversion(&hadc4,100); //等待转换完成,第二个参数表示超时时间,单位ms
int value=HAL_ADC_GetValue(&hadc4); //获取ADC转换结果
3. 接通好烧录引脚和数据线后,编译后使用debug功能。
把测的的value一定要放到全局变量中,不然不好观测,局部变量每次都重新创建影响测试。
一定要确定线连接无误,右键可视窗口的变量可以取消十六进制显示:
8. 使用ADC进行多通道单次数据采集
找到扩展板上的电压采集:
确定连接的引脚后,选择引脚模式为ADC4,后面就是通道数,可以在左边ADC4,后面就不用设置,因为我们已经设置采集顺序,上面图片已经标注,这样的好处是方便。
完整代码:
多通道单次采集特点是每次采集都要重新打开ADC,接收数据后就需要关闭再次打开采集下一次数据。
/* 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"
#include "adc.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#include "stdio.h"
int fputc(int ch,FILE *p)
{while(!(USART1->ISR &(1<<7))){} //等待发送寄存器空//ISR的第七位是1 ,说明发送寄存器是空,这个时候再去写入要发送的数据//当发送寄存器是空,跳出循环,把数据放好发送数据寄存器里面USART1->TDR = ch;return ch;
}/* 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 ---------------------------------------------------------*//* USER CODE BEGIN PV *//* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void SystemPower_Config(void);
/* USER CODE BEGIN PFP *//* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int value = 0;
int value2 = 0;
float a = 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();/* Configure the System Power */SystemPower_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_ADC4_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */HAL_PWREx_EnableVddA(); //这个函数用于启用VDDA电压域。VDDA是指处理器的模拟电源电压域,用于供电模拟功能模块,例如ADC,DAC等。通过HAL_PWREx_EnableVddA()函数,可以使处理器的VDDA电压域处于启用状态,以供给模拟功能模块所需的电源。HAL_PWREx_EnableVddIO2(); //这个函数用于启用VDDIO2电压域。VDDIO2是指处理器的I/O引脚电源域,用于供电处理器的I/O引脚。通过调用HAL_PWREx_EnableVddIO2()函数,可以使处理器的VDDIO2电压域处于启用状态,以供给处理器的I/O引脚所需的电源。HAL_ADCEx_Calibration_Start(&hadc4,ADC_CALIB_OFFSET,ADC_SINGLE_ENDED); //校准单端ADC采样 /* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){HAL_ADC_Start(&hadc4);//启动adc转换 HAL_ADC_PollForConversion(&hadc4,100); //等待转换完成,第二个参数表value = HAL_ADC_GetValue(&hadc4); //获取ADC第一个转换结果HAL_ADC_PollForConversion(&hadc4,100); //等待转换完成,第二个参数表value2 = HAL_ADC_GetValue(&hadc4); //获取ADC第二个转换结果HAL_ADC_Stop(&hadc4); //停止ADCa = 3.3*(value*4)/4096; //计算电压值//printf("%fV",a);HAL_Delay(2000);/* 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};/** Configure the main internal regulator output voltage*/if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE4) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_MSI;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;RCC_OscInitStruct.MSIState = RCC_MSI_ON;RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_4;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;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_CLOCKTYPE_PCLK3;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;RCC_ClkInitStruct.APB3CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK){Error_Handler();}
}/*** @brief Power Configuration* @retval None*/
static void SystemPower_Config(void)
{/** Disable the internal Pull-Up in Dead Battery pins of UCPD peripheral*/HAL_PWREx_DisableUCPDDeadBattery();
/* USER CODE BEGIN PWR */
/* USER CODE END PWR */
}/* 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 */
9. 使用ADC1进行多通道数据单次采集
我们新建项目,熟悉一下ADC1,使用ADC1实现。
(1) 配置通道,使能ADC1:
(2) 调节转换通道数量,设置基本属性:
等待ADC转换结束信号EOC两种方式:
while等待EOC:适合所有ADC,直接操作寄存器,灵活但需要自己处理细节。
HAL_ADC_PollForConversion:HAL库推荐用法,安全、易用、带超时,但部分老型号或特殊用法下可能不适用。
实际选择:如果HAL库支持,建议用HAL_ADC_PollForConversion;如果遇到兼容性或特殊需求,可以用while轮询。
(3) 编写代码:
结果:
注意:ADC1采集的BAT电压不需要乘以4,ADC4采集BAT电压需要乘以四