本篇总结
使用 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 个内部信号源,支持单次、连续、扫描或间断模式。
前置工具准备
开始本例程前,请确认以下工具已安装:
- WIZ UartTool V1.0:串口调试工具:点击下载
硬件连接
本例程使用 ADC1 的通道 6(对应 PA6 引脚)采集模拟电压。PA6 配置为模拟输入模式(AIN),外部施加 0~3.3V 电压即可采集。USART1 的 TX(PA9) 和 RX(PA10) 通过板载 USB 转串口芯片连接到 DEBUG USB 口,用于输出采集结果。
| 引脚 | 功能 | 方向 | 模式 |
| PA6 | ADC1_IN6(模拟电压输入) | 外部 → 开发板 | 模拟输入 (AIN) |
| PA9 | USART1_TX | 开发板 → PC | 复用推挽输出 (AF_PP) |
| PA10 | USART1_RX | PC → 开发板 | 浮空输入 (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 采集