1. 项目概述与核心思路

在嵌入式开发中,数据采集与实时传输是许多项目的基石。无论是监测环境温度、电池电压,还是读取传感器数据,都需要一个稳定、高效且不占用过多CPU资源的方案。今天,我想分享一个基于STM32F103的经典实战案例,它巧妙地整合了ADC(模数转换器)、DMA(直接存储器访问)和USART(通用同步异步收发器)三大模块,实现了一个“后台自动采集、前台随时读取、串口实时上报”的完整数据链路。这个方案的核心价值在于,它解放了主循环,让CPU无需频繁查询ADC状态或搬运数据,从而可以专注于更复杂的业务逻辑。

这个项目的目标是:通过ADC连续采集两路信号(一路外部电压,一路内部温度传感器),利用DMA自动将转换结果搬运到指定的内存数组中,最后通过串口将处理后的数据(电压值和温度值)以固定格式打印出来。整个过程无需在主循环中手动启动ADC或等待转换完成,数据始终是最新的。这不仅是学习STM32外设协同工作的绝佳范例,其设计思路也完全可以移植到产品级的实时监测系统中。

2. 硬件平台与核心外设选型解析

2.1 硬件平台:STM32F103VBT6与万利EK-STM32F开发板

本次实验基于意法半导体的经典款Cortex-M3内核微控制器STM32F103VBT6,它搭载在万利(Manley)的EK-STM32F开发板上。选择这款MCU的原因在于其丰富的外设和极高的性价比,非常适合作为学习和中小型项目的核心。

  • 核心参数 :72MHz主频,128KB Flash,20KB SRAM,足以应对复杂的多外设协同任务。
  • 关键外设
    • ADC1 :12位精度,多达18个输入通道,支持单次、连续、扫描和间断模式,并具备DMA请求功能。
    • DMA1 :拥有7个通道,支持外设到存储器、存储器到外设、存储器到存储器的数据传输,是解放CPU的利器。
    • USART1 :全双工异步串行通信接口,是我们与上位机(如PC串口助手)交互的窗口。

开发板将MCU的引脚引出,并集成了LED、按键、串口转换芯片等基础电路,为我们省去了硬件搭建的麻烦,可以专注于软件逻辑。

2.2 为什么选择ADC+DMA+USART的组合?

这是一个经典的“数据生产者-数据搬运工-数据消费者”模型。

  1. ADC作为生产者 :它的职责是持续地将模拟信号(电压)转换为数字量。如果采用传统的查询或中断方式,每次转换完成都需要CPU介入来读取数据寄存器(ADC_DR),这会频繁打断主程序,在高速连续转换时尤其低效。
  2. DMA作为搬运工 :DMA可以在不占用CPU的情况下,在外设和内存之间直接搬运数据。我们配置DMA,使其在ADC每次转换完成后,自动将ADC_DR寄存器中的值搬运到我们预先定义好的内存数组 AD_Value[] 中。CPU完全不用管这个过程,数据就像流水一样自动存好了。
  3. USART作为消费者 :主循环中的程序可以随时从 AD_Value[] 数组中读取最新的ADC值,经过公式计算转换为有物理意义的电压和温度,然后通过USART发送给上位机显示。我们重写了 fputc 函数,使得可以直接使用C标准库的 printf 进行格式化输出,极大简化了串口打印的代码。

这种架构的优势非常明显: 高实时性、低CPU占用、代码清晰 。ADC和DMA在后台自动运行,保证了数据采集的连续性和及时性;主循环只需在需要时读取内存中的数据并进行处理上报,两者互不干扰。

3. 系统初始化与关键配置详解

任何STM32程序都始于精心的初始化。这里的初始化顺序有讲究,一般遵循“时钟->GPIO->外设功能->中断/NVIC”的流程。

3.1 时钟树配置:一切运行的动力源

STM32的时钟系统相对复杂,配置不当会导致外设无法工作或性能不佳。在 RCC_Configuration 函数中,我们完成了以下关键步骤:

void RCC_Configuration(void) {
    // ... 启用HSE(外部8MHz晶振),配置PLL倍频至72MHz ...
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); // 8MHz * 9 = 72MHz
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // 系统时钟选择PLL输出

    // 关键:为所用到的外设总线开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1 | RCC_APB2Periph_ADC1, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // DMA时钟在AHB总线
}

注意 :STM32F1系列中,GPIO、USART1、ADC1等高速外设挂在APB2总线下,而DMA1挂在AHB总线下。必须分别使能对应总线的外设时钟,否则寄存器访问会失效。这是新手最容易忽略的问题之一,表现为外设配置后毫无反应。

3.2 GPIO引脚功能映射

GPIO是连接芯片内部与外部世界的桥梁。在 GPIO_Configuration 中,我们需要根据数据手册,将具体引脚初始化为对应的功能模式。

// PC0 作为 ADC 通道10 的输入,必须设置为模拟输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入
GPIO_Init(GPIOC, &GPIO_InitStructure);

// PA9 (USART1_TX) 设置为复用推挽输出,PA10 (USART1_RX) 设置为浮空输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 高速,适合串口
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);

实操心得 :引脚模式配置错误是导致外设无法工作的另一大常见原因。记住一个简单原则: 输出信号用 Out_PP Out_OD ,输入信号用 IN_FLOATING IPU/IPD ,模拟信号用 AIN ,复用功能(如USART、SPI)用 AF_PP AF_OD 。对于ADC输入, GPIO_Mode_AIN 会断开内部上/下拉电阻,让引脚直接连接到ADC的模拟开关,这是获得准确采样值的前提。

3.3 ADC模块的深度配置与校准

ADC的配置是本次项目的重点之一,它决定了采样的精度、速度和模式。

void ADC1_Configuration(void) {
    ADC_InitTypeDef ADC_InitStructure;
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;       // 扫描模式(多通道必须开启)
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 数据右对齐
    ADC_InitStructure.ADC_NbrOfChannel = 2; // 规则通道序列长度为2
    ADC_Init(ADC1, &ADC_InitStructure);

    // 特别注意:使能内部温度传感器和参考电压通道
    ADC_TempSensorVrefintCmd(ENABLE);

    // 配置规则通道序列:顺序和采样时间
    ADC_RegularChannelConfig(ADC1, ADC_Channel_10, 1, ADC_SampleTime_13Cycles5); // 通道10,序列1,采样时间13.5周期
    ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 2, ADC_SampleTime_239Cycles5); // 通道16(温度传感器),序列2,采样时间239.5周期

    ADC_Cmd(ADC1, ENABLE); // 使能ADC1
    ADC_DMACmd(ADC1, ENABLE); // 使能ADC的DMA请求!!!

    // --- ADC校准流程(至关重要)---
    ADC_ResetCalibration(ADC1);
    while(ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while(ADC_GetCalibrationStatus(ADC1));
    // --- 校准结束 ---
}

关键点解析:

  1. 扫描模式与连续模式 ScanConvMode 使能后,ADC会按照 ADC_RegularChannelConfig 配置的顺序,自动对多个通道进行转换。 ContinuousConvMode 使能后,完成一轮序列转换后会立即开始下一轮,从而实现不间断采集。两者结合,就是“自动连续扫描转换”。
  2. 采样时间 ADC_SampleTime_xxCycles 决定了ADC内部采样保持电容的充电时间。时间太短,采样不准确;时间太长,转换速度慢。对于内部温度传感器(通道16),STM32手册明确要求采样时间必须大于2.2us。在72MHz的ADC时钟(PCLK2/6=12MHz)下,一个周期约83.3ns,因此选择 239.5Cycles (约20us)是稳妥的。对于外部通道,可以根据信号源阻抗选择更短的采样时间以提高速率。
  3. ADC校准 :这是保证ADC精度的 强制性步骤 。芯片出厂和每次上电后,ADC的偏移和增益会有微小误差。校准过程就是让ADC内部电路测量这些误差并存储在特定的寄存器中,后续转换时会自动补偿。 务必在ADC使能后、第一次转换前执行校准 ,且等待校准完成。
  4. 使能DMA请求 ADC_DMACmd 这一行是连接ADC和DMA的“开关”。只有打开它,ADC在每次转换结束后才会向DMA控制器发出传输请求。

3.4 DMA通道的精准配置

DMA配置的核心是告诉DMA控制器:数据从哪里来(源地址),到哪里去(目标地址),以什么方式搬运。

void DMA_Configuration(void) {
    DMA_InitTypeDef DMA_InitStructure;
    DMA_DeInit(DMA1_Channel1); // 复位DMA1通道1(ADC1对应此通道)

    DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; // 外设地址:ADC1数据寄存器
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&AD_Value;       // 内存地址:数组首地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;          // 传输方向:外设->内存
    DMA_InitStructure.DMA_BufferSize = 2;                       // 缓冲区大小:2个单元(对应2个通道)
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址固定
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;     // 内存地址递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据宽度:半字(16位)
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // 内存数据宽度:半字(16位)
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;             // 循环模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;         // 优先级:高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                // 禁用存储器到存储器模式
    DMA_Init(DMA1_Channel1, &DMA_InitStructure);

    DMA_Cmd(DMA1_Channel1, ENABLE); // 使能DMA通道
}

配置逻辑拆解:

  • 地址与方向 :源地址是ADC1的数据寄存器 ADC1_DR (一个固定的物理地址 0x4001244C ),目标地址是内存中的数组 AD_Value 。方向是外设到内存。
  • 数据宽度与地址递增 :ADC数据寄存器是16位的(虽然ADC是12位,但数据存放在16位寄存器的低12位),所以外设数据宽度设为 HalfWord 。外设地址不递增,因为始终从同一个寄存器读数。内存地址需要递增,这样第一次传输的数据存到 AD_Value[0] ,第二次存到 AD_Value[1]
  • 缓冲区大小与循环模式 BufferSize=2 意味着DMA认为要传输2个数据单元。当传输完2个后,如果模式是 Circular (循环),DMA会自动将传输计数器 CNDTR 重置为2,并将目标地址指针重置回 &AD_Value ,然后开始新一轮传输。这正好匹配ADC连续扫描2个通道的行为,形成了一个完美的闭环:ADC不停地转,DMA就不停地搬,数据在 AD_Value[0] AD_Value[1] 中被循环覆盖更新。
  • 外设与通道映射 :STM32F103的ADC1的DMA请求固定映射到DMA1的通道1。这一点需要查数据手册,不能配错。

3.5 USART与printf重定向

为了方便调试和输出,我们重写了 fputc 函数,将标准库 printf 的输出重定向到USART1。

int fputc(int ch, FILE *f) {
    USART1->DR = (u8) ch; // 直接写数据寄存器
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); // 等待发送完成
    return ch;
}

USART配置 比较简单,主要设置波特率(如19200)、数据位(8)、停止位(1)、无校验。需要注意的是,我们通常使用查询方式发送(如上面的 while 循环等待 TXE 标志),而不是中断方式,因为 printf 可能被多处调用,中断发送容易造成数据覆盖或混乱。对于接收数据,如果需要,则可以开启接收中断。

避坑技巧 :使用 printf 重定向时,由于 printf 内部会调用 malloc 等函数,如果未配置微库(MicroLIB)或堆空间不足,可能导致程序卡死。在Keil MDK中,勾选“Use MicroLIB”可以显著减少代码体积并避免这个问题。同时,确保在初始化代码中调用了 USART1_Configuration() 来正确配置串口硬件。

4. 主程序逻辑与数据处理算法

当所有外设初始化完毕后,主程序变得异常简洁高效。

int main(void) {
    // 1. 初始化所有硬件
    RCC_Configuration();
    GPIO_Configuration();
    NVIC_Configuration();
    USART1_Configuration();
    DMA_Configuration();
    ADC1_Configuration(); // 此函数内包含ADC校准

    // 2. 软件触发第一次ADC转换(此后由于连续模式,ADC将一直转换)
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);

    // 3. 主循环:随时读取、计算、发送
    while (1) {
        Delay(); // 简单的延时,控制打印频率
        Temp = GetTemp(AD_Value[1]); // 从数组读取温度传感器ADC值并计算
        Volt = GetVolt(AD_Value[0]); // 从数组读取通道10的ADC值并计算电压

        // 串口发送清屏指令(可选,依赖终端支持)和结果
        USART_SendData(USART1, 0x0c);
        while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
        printf("电压:%d.%d \t 温度:%d.%d℃ \r\n", Volt/100, Volt%100, Temp/100, Temp%100);
    }
}

主循环的逻辑清晰体现了DMA带来的优势: 无需管理ADC转换过程 AD_Value 数组就像一个由DMA自动更新的“共享变量”,主程序在任何时刻读取它,获得的就是当前时刻最新的转换结果。

4.1 电压计算算法

电压计算基于ADC的基本公式。STM32F103的ADC是12位,参考电压 Vref+ 接在 VDD (即3.3V)。

ADC转换值 / 4095 = 输入电压Vin / Vref+

因此:

Vin = (ADC转换值 * Vref+) / 4095

在代码 GetVolt 函数中,为了避免浮点数运算并保留两位小数,采用了整数先乘后除的方法,并将结果放大了100倍。

u16 GetVolt(u16 advalue) {
    return (u16)(advalue * 330 / 4096); // Vref+ = 3.3V = 330 * 0.01V
}

这里 330 代表3.30V放大100倍。 advalue * 330 可能会超过16位整数范围(最大65535),但 advalue 最大为4095, 4095*330=1351350 ,仍在32位 int 范围内。计算后的结果单位是“厘伏”(centivolt),例如返回值 325 表示3.25V。

4.2 内部温度传感器计算算法

内部温度传感器的计算稍复杂,其输出电压与温度成负相关线性关系。STM32手册给出了典型参数:

  • V25 :温度传感器在25℃时的输出电压,典型值1.43V。
  • Avg_Slope :平均斜率,典型值4.3 mV/℃。

计算公式为:

T(℃) = (V25 - Vsense) / Avg_Slope + 25

其中 Vsense 是温度传感器当前的输出电压,通过ADC值换算得到: Vsense = ADC值 * Vref+ / 4095

GetTemp 函数的实现同样采用了整数运算和放大100倍的处理:

u16 GetTemp(u16 advalue) {
    u32 Vtemp_sensor;
    s32 Current_Temp;
    // 计算Vsense (单位:mV * 100), 3300mV * 100 = 330000
    Vtemp_sensor = advalue * 330 / 4096; // 此时Vtemp_sensor单位是厘伏,即mV*10
    // 转换为公式计算:(1430 - Vsense_in_mV*10) * 1000 / 43 + 2500
    // 1430 是1.43V放大1000倍(为了计算),43是4.3mV/℃放大10倍。
    // 公式整体再放大100倍得到℃*100的结果。
    Current_Temp = (s32)(1430 - Vtemp_sensor*10) * 1000 / 43 + 2500;
    return (s16)Current_Temp;
}

这段代码的数值变换需要仔细理解。核心思想是 将所有物理量统一放大,在整数域完成计算 ,最终返回“摄氏度*100”的值。例如返回值 2530 表示25.30℃。

重要提示 :内部温度传感器精度不高(典型误差±1.5℃),且受芯片自身发热影响大。它适用于监测芯片结温变化趋势,而非高精度温度测量。如需精确测温,必须使用外部专业温度传感器。

5. 方案扩展与高级应用技巧

这个基础框架具有很强的扩展性,只需稍作修改,就能应对更复杂的场景。

5.1 多通道与数字滤波实现

当前 AD_Value 数组和DMA缓冲区大小都是2。如果需要采集更多通道(例如8个),只需:

  1. 增大数组: vu16 AD_Value[8];
  2. 修改ADC规则通道配置,按顺序添加 ADC_RegularChannelConfig
  3. 修改DMA初始化中的 DMA_BufferSize = 8;

更强大的是,我们可以利用DMA的循环模式和更大的缓冲区来实现一个 硬件FIFO(先进先出队列) ,用于软件数字滤波。

例如,将缓冲区设为 100 ,DMA会循环填充这100个单元。主程序可以定期(比如每100ms)从这100个历史数据中,取出最新的N个做平均滤波(滑动平均),或者进行更复杂的IIR/FIR滤波计算。这能有效抑制随机噪声,提高测量稳定性,而所有数据搬运工作仍由DMA自动完成,CPU仅需在固定间隔进行一次性计算。

5.2 定时触发与精确采样率控制

当前ADC由软件触发启动后,就处于自由连续转换模式,其转换速率由ADC时钟和总采样时间决定。如果需要 精确的、固定频率的采样 (例如每秒1000次),可以改用定时器触发。

  1. 配置一个定时器(如TIM2),使其产生一个固定频率的更新事件(如1kHz)。
  2. 将ADC的 ADC_ExternalTrigConv 设置为 ADC_ExternalTrigConv_T2_CC2 (或其他可用触发源)。
  3. 在ADC初始化中,将 ADC_ContinuousConvMode 改为 DISABLE (单次转换模式)。
  4. 这样,每次定时器事件到来,ADC就会精确地执行一次完整的多通道扫描转换,并由DMA搬运结果。采样率完全由定时器频率控制,精度极高。

5.3 双缓冲(Double Buffer)DMA传输

在高速采集场景下,CPU处理数据的速度可能跟不上DMA搬运的速度。为了避免数据被覆盖,可以使用DMA的双缓冲(或称乒乓缓冲)模式。虽然STM32F1的DMA硬件不支持自动双缓冲,但我们可以用软件模拟:

  1. 定义两个缓冲区: AD_Value_BufA[BUFF_SIZE] AD_Value_BufB[BUFF_SIZE]
  2. 配置DMA目标地址指向BufA, BufferSize=BUFF_SIZE ,并使能传输完成中断(DMA_IT_TC)。
  3. 在DMA传输完成中断服务函数中:
    • 将DMA目标地址切换到BufB。
    • 设置一个标志位,通知主程序BufA已满,可以处理。
    • 重新使能DMA。
  4. 主程序检测到标志位后,处理BufA中的数据,同时DMA正在向BufB填充数据。
  5. 当下一次传输完成中断到来时,再切换回BufA,如此往复。

这种方法实现了数据采集与处理的并行,确保了在任意时刻都有一块完整的数据可供处理,另一块正在被填充,非常适合音频采集、波形记录等应用。

6. 常见问题排查与调试心得

在实际操作中,你可能会遇到一些问题。下面是一个快速排查指南:

现象 可能原因 排查步骤与解决方案
ADC读数始终为0或固定值 1. ADC或GPIO时钟未开启。
2. GPIO模式未设置为模拟输入(AIN)。
3. ADC校准未执行或未完成。
4. 采样时间太短(对高阻抗源)。
5. 外部输入电压超出Vref范围或为负压。
1. 检查 RCC_Configuration 中对应外设时钟使能语句。
2. 用调试器查看GPIO相关寄存器模式位。
3. 确保 ADC_StartCalibration 后等待完成。
4. 增加 ADC_SampleTime ,特别是内部温度传感器需>2.2us。
5. 用万用表测量实际输入电压,确保在0~3.3V之间。
ADC读数跳动大(噪声大) 1. 电源或参考电压噪声大。
2. 模拟信号线受到数字信号干扰。
3. 未进行软件滤波。
1. 为Vref和VDDA增加滤波电容(通常并联10uF和0.1uF)。
2. 布局时让模拟走线远离高速数字线(如时钟、PWM)。
3. 在软件中实现多次采样取平均等滤波算法。
DMA不搬运数据,数组值不变 1. DMA时钟未开启。
2. ADC的DMA请求未使能( ADC_DMACmd )。
3. DMA配置错误(如方向、地址、数据大小)。
4. DMA未使能( DMA_Cmd )。
5. ADC未启动转换。
1. 检查 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE)
2. 确认 ADC_DMACmd(ADC1, ENABLE) 已调用。
3. 仔细核对DMA初始化结构体每个成员,特别是 DMA_DIR DMA_PeripheralBaseAddr
4. 确认 DMA_Cmd 在ADC启动前已调用。
5. 确认调用了 ADC_SoftwareStartConvCmd 启动转换。
串口无输出或乱码 1. USART时钟或GPIO时钟未开启。
2. GPIO模式配置错误(TX应为AF_PP)。
3. 波特率设置与上位机不匹配。
4. 硬件连接错误(TX/RX交叉)。
5. 未使能USART( USART_Cmd )。
1. 检查RCC配置。
2. 核对GPIO初始化代码。
3. 双重检查 USART_InitStructure.USART_BaudRate ,并确认系统时钟配置正确(是72MHz吗?)。
4. 确认开发板与PC串口线TX接RX,RX接TX。
5. 确认 USART_Cmd(USART1, ENABLE) 已执行。
printf无法使用或卡死 1. 未包含 stdio.h
2. 未重写 fputc fgetc 函数。
3. 未在工程设置中勾选“Use MicroLIB”。
4. 堆栈空间设置过小。
1. 添加 #include <stdio.h>
2. 确保工程中有 fputc 的实现,且指向正确的USART。
3. 在Keil的“Target”选项下勾选“Use MicroLIB”。
4. 在启动文件或链接脚本中适当增大堆(Heap)和栈(Stack)的大小。

调试心得

  1. 善用调试器 :在IDE(如Keil MDK)中连接仿真器,实时查看 AD_Value 数组、ADC_DR寄存器、DMA_CNDTR(剩余数据量)寄存器的值,是判断ADC和DMA是否工作的最直接方法。
  2. 分步调试 :不要一次性写完所有代码。可以先只调试ADC(不用DMA,用查询方式读值),确保ADC工作正常;然后再加入DMA,观察数组是否被自动填充;最后再加上串口输出。这样一旦出现问题,排查范围会小很多。
  3. 理解数据流 :在脑海中清晰地构建“ADC转换 -> 产生DMA请求 -> DMA搬运至内存 -> 主循环读取内存 -> 计算 -> 串口发送”这条数据流。任何一个环节断掉,最终结果都会异常。
  4. 关注时序 :尤其是ADC校准、DMA使能、ADC启动这三者的顺序。一个稳健的顺序是:初始化ADC并校准 -> 配置并启动DMA -> 最后启动ADC转换。这样可以确保DMA就绪后,ADC一开始转换就能收到请求。

这个综合练习打通了STM32中三个最常用也最重要的外设。掌握了它,你就拥有了处理大多数数据采集任务的能力骨架。剩下的,就是根据具体项目的需求,在这个骨架上增添血肉——更多的通道、更精确的定时、更复杂的算法、更稳定的滤波。希望这篇详细的拆解能帮助你不仅“跑通”代码,更能“吃透”其背后的设计思想与工程细节。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐