全部例程

ADC - 电压采集

W55MH32

更新于 2026年8月19日

本篇总结

使用 ADC + DMA,采集模拟电压并串口打印。

🔑 KEIL MDK、UART调试、ADC、DMA

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

本示例演示 W55MH32 的 12 位逐次逼近型 ADC(模拟数字转换器)配合 DMA 实现单通道电压采集。ADC 将 PA6 引脚上的模拟电压转换为 12 位数字值,DMA 自动将转换结果从 ADC 数据寄存器搬运到内存数组,转换完成后触发中断,在串口打印原始码值与换算后的电压值。
W55MH32 的 ADC 有多达 18 个通道,可测量 16 个外部和 2 个内部信号源,支持单次、连续、扫描或间断模式。

前置工具准备

开始本例程前,请确认以下工具已安装:

硬件连接

本例程使用 ADC1 的通道 6(对应 PA6 引脚)采集模拟电压。PA6 配置为模拟输入模式(AIN),外部施加 0~3.3V 电压即可采集。USART1 的 TX(PA9) 和 RX(PA10) 通过板载 USB 转串口芯片连接到 DEBUG USB 口,用于输出采集结果。

引脚功能方向模式
PA6ADC1_IN6(模拟电压输入)外部 → 开发板模拟输入 (AIN)
PA9USART1_TX开发板 → PC复用推挽输出 (AF_PP)
PA10USART1_RXPC → 开发板浮空输入 (IN_FLOATING)

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\ADC\,核心文件如下:

ADC/
├── USER/
│   ├── main.c            ← ADC初始化 + DMA配置 + 电压采集循环 + printf重定向
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

步骤1:USART 初始化

文件:USER/main.c | 函数:UART_Configuration() — 配置 USART1 用于串口输出调试信息。

void UART_Configuration(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);

    /* PA9: USART1_TX → 复用推挽输出 */
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* PA10: USART1_RX → 浮空输入 */
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* USART 参数:115200 / 8N1 / 全双工 */
    USART_InitStructure.USART_BaudRate            = 115200;
    USART_InitStructure.USART_WordLength          = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits            = USART_StopBits_1;
    USART_InitStructure.USART_Parity              = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;

    USART_Init(USART1, &USART_InitStructure);
    USART_Cmd(USART1, ENABLE);
}

使能 USART1 和 GPIOA 时钟,配置 PA9 为复用推挽输出(发送)、PA10 为浮空输入(接收),设置 115200 / 8N1 参数并使能 USART1。

步骤2:ADC 初始化配置

函数:ADC_Configuration() — 配置 ADC1 为独立模式,使能 PA6 模拟输入,设置扫描模式 + 单次转换 + 软件触发,并执行 ADC 校准。

void ADC_Configuration(void)
{
    uint32_t         i;
    ADC_InitTypeDef  ADC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    /* 使能 GPIOA 和 ADC1 时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);

    /* PA6: 模拟输入模式 */
    GPIO_InitStructure.GPIO_Pin   = ADC_TEST_CHANNEL_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AIN;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* ADC 时钟:PCLK2 的 8 分频 */
    RCC_ADCCLKConfig(RCC_PCLK2_Div8);
    ADC_DeInit(ADC1);

    /* ADC 参数:独立模式 / 扫描 / 单次转换 / 软件触发 / 右对齐 */
    ADC_InitStructure.ADC_Mode               = ADC_Mode_Independent;
    ADC_InitStructure.ADC_ScanConvMode       = ENABLE;
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
    ADC_InitStructure.ADC_ExternalTrigConv   = ADC_ExternalTrigConv_None;
    ADC_InitStructure.ADC_DataAlign          = ADC_DataAlign_Right;
    ADC_InitStructure.ADC_NbrOfChannel       = CONV_CHANNEL_NUM;
    ADC_Init(ADC1, &ADC_InitStructure);

    /* 配置规则通道:通道6,转换顺序1,采样时间239.5周期 */
    for (i = 0; i < CONV_CHANNEL_NUM; i++)
    {
        ADC_RegularChannelConfig(ADC1, ADC_CovChannel[i], i + 1, ADC_SampleTIME[i]);
    }

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    ADC_Cmd(ADC1, ENABLE);

    /* ADC 校准(必须步骤) */
    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1));

    /* 配置 DMA 搬运转换结果 */
    DMA_Configuration();
}

关键配置:ADC 工作在独立模式,使能扫描模式(多通道),单次转换(每次软件触发后转换一次),软件触发(SWSTART),数据右对齐。采样时间设为 239.5 个 ADCCLK 周期,保证采样精度。校准流程(复位校准 → 等待完成 → 开始校准 → 等待完成)是 ADC 正常工作的前提。

如果你想自定义

  • 换采集通道 → 修改 ADC_CovChannel[] 数组和对应的 GPIO 引脚,如改为 ADC_Channel_0 对应 PA0
  • 改采样精度 → 修改 ADC_SampleTIME[],可选 1.5/7.5/13.5/28.5/41.5/55.5/71.5/239.5 周期,周期越长精度越高
  • 改为连续转换 → 将 ADC_ContinuousConvMode 设为 ENABLE,ADC 会自动连续转换,无需软件反复触发

步骤3:DMA 配置

函数:DMA_Configuration() — 配置 DMA1 Channel1 将 ADC1 转换结果自动搬运到内存数组,循环模式 + 传输完成中断。

void DMA_Configuration(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    DMA_InitTypeDef  DMA_InitStructure;

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
    DMA_DeInit(DMA1_Channel1);

    /* 外设地址:ADC1 数据寄存器 */
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
    /* 内存地址:存储数组 */
    DMA_InitStructure.DMA_MemoryBaseAddr     = (uint32_t)DAM_ADC_Value;
    /* 方向:外设 → 内存 */
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralSRC;
    DMA_InitStructure.DMA_BufferSize         = CONV_CHANNEL_NUM;
    DMA_InitStructure.DMA_PeripheralInc      = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_MemoryInc          = DMA_MemoryInc_Enable;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
    DMA_InitStructure.DMA_MemoryDataSize     = DMA_MemoryDataSize_Word;
    /* 循环模式:完成一次后自动重新开始 */
    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);
    ADC_DMACmd(ADC1, ENABLE);

    /* 配置 DMA 传输完成中断 */
    NVIC_InitStructure.NVIC_IRQChannel                   = DMA1_Channel1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority        = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd                = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    DMA_ITConfig(DMA1_Channel1, DMA1_IT_TC1, ENABLE);
}

DMA1 Channel1 是 ADC1 的专用 DMA 通道。DMA_Mode_Circular 循环模式保证每次转换完成后 DMA 自动重新等待下一次数据。开启传输完成中断(TC1)后,每次搬运完成都会触发中断,在中断中读取并打印电压值。

步骤4:DMA 中断处理与电压换算

函数:DMA1_Channel1_IRQHandler() — DMA 传输完成后,清除中断标志并打印 ADC 原始码值与换算电压值。

void DMA1_Channel1_IRQHandler(void)
{
    if (DMA_GetITStatus(DMA1_IT_TC1) != RESET)
    {
        DMA_ClearITPendingBit(DMA1_IT_TC1);
        DMA_ClearFlag(DMA1_FLAG_TC1);

        /* 打印原始码值和换算电压值 */
        printf("Code Value = %d ,voltage value = %2.4f\n", DAM_ADC_Value[0],
               (float)VREF * DAM_ADC_Value[0] / 4095 / 1000);
    }
}

电压换算公式:Y = (VREF × X) / 4095 / 1000,其中 VREF = 3300(mV),X 为 12 位 ADC 原始码值(0~4095),Y 为换算后的电压值(V)。12 位满量程 4095 对应 3.3V。

如果你想自定义

  • 改参考电压 → 修改 VREF 宏定义,如外部参考电压为 3.0V 则设为 3000
  • 多通道采集 → 增大 CONV_CHANNEL_NUM,在 ADC_CovChannel[] 中添加更多通道,中断中遍历数组打印每个通道的值

步骤5:printf 重定向

重写 fputc() 将 printf 输出重定向到 USART1,并自动在 \n 前补 \r 确保串口工具正确换行。

int SER_PutChar(int ch)
{
    while (!USART_GetFlagStatus(USART1, USART_FLAG_TC));
    USART_SendData(USART1, (uint8_t)ch);
    return ch;
}

int fputc(int c, FILE *f)
{
    if (c == '\n')
        SER_PutChar('\r');         // 补回车,确保串口工具正确换行
    return (SER_PutChar(c));
}

步骤6:主函数逻辑

文件:USER/main.c | 函数:main() — 初始化延时和串口,打印时钟信息,配置 ADC,每秒软件触发一次转换。

int main(void)
{
    RCC_ClocksTypeDef clocks;
    delay_init();

    UART_Configuration();
    RCC_GetClocksFreq(&clocks);

    printf("\n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, "
           "PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000,
           (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000,
           (float)clocks.PCLK2_Frequency / 1000000,
           (float)clocks.ADCCLK_Frequency / 1000000);
    printf("ADC Single Test\n");

    ADC_Configuration();

    while (1)
    {
        /* 软件触发 ADC 转换,每秒一次 */
        ADC_SoftwareStartConvCmd(ADC1, ENABLE);
        delay_ms(1000);
    }
}

主循环每秒调用 ADC_SoftwareStartConvCmd() 触发一次转换。ADC 转换完成后 DMA 自动搬运数据并触发中断,在中断中打印结果。整个采集过程 CPU 无需轮询等待。

如果你想自定义

  • 改采集频率 → 修改 delay_ms() 的参数,如 delay_ms(100) 为每 100ms 采集一次
  • 使用定时器触发 → 将 ADC_ExternalTrigConv 改为定时器触发源(如 ADC_ExternalTrigConv_T1_CC1),实现精确定时采集

运行验证

打开 WIZ UartTool V1.0,选择开发板对应 COM 口,波特率 115200 / 8N1,打开串口。下载固件并按 RESET 后,串口会先打印系统时钟信息,随后每秒输出一行 ADC 采集结果,包含原始码值和换算后的电压值。

WIZ UartTool V1.0 显示 ADC 单通道采集结果:原始码值与换算电压值

本例程还包含双重 ADC 同步规则模式、模拟看门狗、内部温度传感器/VREFINT 三个扩展实验,串口输出分别如下:

双重 ADC 同步规则模式:ADC1 与 ADC2 同时采集不同通道,分别打印码值与电压
ADC 模拟看门狗:电压超出阈值(1024~2048)时触发中断并打印告警
内部通道采集:通道16(温度传感器)与通道17(VREFINT)的电压值输出

常见问题

Q: ADC 读取的电压值始终为 0 或 3.3V

检查 PA6 引脚是否正确接入模拟电压源。若引脚悬空,读数会不稳定。确保 GPIO 配置为 GPIO_Mode_AIN(模拟输入),否则数字部分会干扰模拟信号。

Q: 串口无任何输出

确认 COM 口号正确、波特率 115200,下载后需按 RESET 键启动。检查 printf 重定向的 fputc 函数是否正确实现。

Q: ADC 校准卡住不动

确认 ADC 时钟已正确使能(RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE)),且 ADC 已使能(ADC_Cmd(ADC1, ENABLE))后再执行校准。

Q: 电压值不准确,偏差较大

检查 VREF 宏定义是否与实际参考电压一致(默认 3300mV = 3.3V)。增大采样时间(如 239.5 周期)可提高精度。ADC 时钟不要超过 14MHz,建议 PCLK2 的 8 分频。

总结

本文基于 W55MH32 的 ADC1 + DMA 实现了单通道电压采集:

  • ADC1 配置为独立模式、扫描模式、单次转换、软件触发,采集 PA6(通道6)的模拟电压,采样时间 239.5 周期
  • DMA1 Channel1 循环模式自动搬运转换结果到内存数组,传输完成中断中打印原始码值与换算电压
  • 电压换算公式 Y = (3.3 × X) / 4095,12 位满量程对应 3.3V
  • 扩展实验包含双重 ADC 同步规则模式(ADC1+ADC2 同时采集)、模拟看门狗(电压越界告警)、内部温度传感器/VREFINT 采集

 

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家