第三讲:ADC单通道转换配置实战

好,咱们今天来点真格的。

前面两讲把ADC的原理和寄存器讲了个七七八八,有朋友私信我说:「老师,能不能直接上手干一个?」

没问题。这一讲,我就带你手把手配置一个单通道,实现最基本的电压采集。

说白了,就是让ADC动起来,采一个值回来。

3.1 硬件连接与引脚确认

动手之前,先看看硬件。

我习惯用TC375 Lite Kit做演示。这块板子上,P10.2引脚默认接到了电位器的中间抽头。你拧电位器,电压就会变。

嗯,这里要注意:不是所有引脚都能当ADC输入。你得查数据手册,确认引脚有没有AN0、AN1这样的标识。

我曾经犯过一个低级错误:把信号接到了普通GPIO上,折腾了半天采不到值。后来才发现,那个引脚压根没有ADC功能。所以,第一步永远是——查引脚复用表。

确认好引脚后,记住它的Group和Channel号。比如P10.2对应的是Group 0,Channel 2。

3.2 初始化ADC模块

初始化,说白了就是告诉硬件:「我要用你了,你准备一下。」

我个人习惯分三步走:

  1. 使能时钟——不给时钟,ADC不干活。
  2. 配置全局控制寄存器——设定分辨率、转换模式这些大方向。
  3. 配置具体通道——告诉它采哪个引脚,用什么参考电压。

咱们直接看代码。我用的是iLLD库,这样更直观。

// 第一步:使能ADC时钟
IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());

// 第二步:初始化ADC模块
IfxEvadc_Adc adc;
IfxEvadc_Adc_Config adcConfig;
IfxEvadc_Adc_initModuleConfig(&adcConfig, &MODULE_EVADC);
IfxEvadc_Adc_initModule(&adc, &adcConfig);

// 第三步:配置队列和通道
IfxEvadc_Adc_Group group;
IfxEvadc_Adc_GroupConfig groupConfig;
IfxEvadc_Adc_initGroupConfig(&groupConfig, &adc);

// 指定Group 0
groupConfig.groupId = IfxEvadc_GroupId_0;
// 设置参考电压为内部5V基准
groupConfig.reference = IfxEvadc_Reference_Internal;
// 12位分辨率
groupConfig.resolution = IfxEvadc_Resolution_12bit;

IfxEvadc_Adc_initGroup(&group, &groupConfig);

你可能会问:「为什么先关看门狗?」

因为初始化过程中,如果时间太长,看门狗会复位。我刚开始学的时候没关,程序跑着跑着就重启了,查了半天才发现是看门狗在捣乱。

3.3 配置单通道

模块初始化好了,接下来就是配置具体通道。

这里有个关键点:TC3xx的ADC不是你想采就采的,你得把通道加到「请求队列」里

// 配置通道
IfxEvadc_Adc_Channel channel;
IfxEvadc_Adc_ChannelConfig channelConfig;
IfxEvadc_Adc_initChannelConfig(&channelConfig, &group);

// 指定通道2(对应P10.2)
channelConfig.channelId = IfxEvadc_ChannelId_2;
// 使能该通道
channelConfig.enabled = TRUE;
// 设置结果寄存器编号
channelConfig.resultRegister = IfxEvadc_ResultRegister_0;

IfxEvadc_Adc_initChannel(&channel, &channelConfig);

你看,就这么几行。但有个细节:resultRegister 决定了转换结果存到哪个寄存器里。如果你同时采多个通道,每个通道最好用不同的结果寄存器,免得数据被覆盖。

小技巧:我习惯把通道号和结果寄存器编号设成一样。比如通道2就用结果寄存器0,通道3就用结果寄存器1。这样调试的时候一眼就能对上,省心。

3.4 启动转换与读取结果

配置完了,该干活了。

启动转换有两种方式:

  • 手动触发——你调用一次函数,它转换一次。
  • 自动连续转换——配置好后,它自己不停采。

咱们先用手动触发,这样好理解。

// 启动一次转换
IfxEvadc_Adc_startSingleConversion(&group, IfxEvadc_ChannelId_2);

// 等待转换完成
while (!IfxEvadc_Adc_getResult(&channel, &result))
{
    // 空等待,或者你可以加个超时保护
}

// 读取结果
uint16 adcValue = result;

这里有个坑:while 循环等待的时候,如果ADC出问题了,程序就卡死了。我建议加个超时机制。

// 带超时的等待
uint32 timeout = 10000;
while (!IfxEvadc_Adc_getResult(&channel, &result) && timeout--)
{
    // 等待
}
if (timeout == 0)
{
    // 处理超时错误
    printf("ADC转换超时!\n");
}

嗯,这样安全多了。

3.5 将原始值转换为电压

ADC采回来的值是个数字,比如12位分辨率下,范围是0~4095。

要换算成实际电压,公式很简单:

电压 = (adcValue / 4095.0) * 参考电压

假设参考电压是5V,adcValue是2048,那电压就是2.5V。

float voltage = (adcValue / 4095.0f) * 5.0f;
printf("ADC原始值: %d, 电压: %.3f V\n", adcValue, voltage);

你想想看,是不是很简单?

重要提醒:参考电压一定要和实际硬件一致。如果你用的是外部参考电压,比如3.3V,那公式里的5.0就要改成3.3。否则算出来的电压全是错的。

3.6 完整代码示例

我把上面的代码拼到一起,就是一个完整的单通道采集程序。

#include "IfxEvadc_Adc.h"
#include "IfxScuWdt.h"

void adc_single_channel_demo(void)
{
    // 关看门狗
    IfxScuWdt_disableCpuWatchdog(IfxScuWdt_getCpuWatchdogPassword());
    IfxScuWdt_disableSafetyWatchdog(IfxScuWdt_getSafetyWatchdogPassword());

    // 初始化ADC模块
    IfxEvadc_Adc adc;
    IfxEvadc_Adc_Config adcConfig;
    IfxEvadc_Adc_initModuleConfig(&adcConfig, &MODULE_EVADC);
    IfxEvadc_Adc_initModule(&adc, &adcConfig);

    // 初始化Group 0
    IfxEvadc_Adc_Group group;
    IfxEvadc_Adc_GroupConfig groupConfig;
    IfxEvadc_Adc_initGroupConfig(&groupConfig, &adc);
    groupConfig.groupId = IfxEvadc_GroupId_0;
    groupConfig.reference = IfxEvadc_Reference_Internal;
    groupConfig.resolution = IfxEvadc_Resolution_12bit;
    IfxEvadc_Adc_initGroup(&group, &groupConfig);

    // 初始化通道2
    IfxEvadc_Adc_Channel channel;
    IfxEvadc_Adc_ChannelConfig channelConfig;
    IfxEvadc_Adc_initChannelConfig(&channelConfig, &group);
    channelConfig.channelId = IfxEvadc_ChannelId_2;
    channelConfig.enabled = TRUE;
    channelConfig.resultRegister = IfxEvadc_ResultRegister_0;
    IfxEvadc_Adc_initChannel(&channel, &channelConfig);

    // 主循环:不断采集
    while (1)
    {
        uint16 adcValue;
        float voltage;

        // 启动转换
        IfxEvadc_Adc_startSingleConversion(&group, IfxEvadc_ChannelId_2);

        // 等待结果(带超时)
        uint32 timeout = 10000;
        while (!IfxEvadc_Adc_getResult(&channel, &adcValue) && timeout--);

        if (timeout)
        {
            voltage = (adcValue / 4095.0f) * 5.0f;
            printf("电压: %.3f V\n", voltage);
        }
        else
        {
            printf("转换超时!\n");
        }

        // 延时一会儿,别采太快
        for (uint32 i = 0; i < 1000000; i++);
    }
}

这段代码,你直接复制到工程里,改一下引脚配置,就能跑起来。

3.7 常见问题与避坑

最后,我总结几个我踩过的坑:

问题 原因 解决办法
一直读到0 引脚没使能ADC功能 检查引脚复用表,配置为AN模式
一直读到4095 输入电压超过参考电压 用万用表量一下实际电压
转换结果跳动很大 没有加硬件滤波 在引脚上加个100nF电容到地
程序卡死在等待循环 看门狗复位或ADC配置错误 加超时保护,检查初始化顺序

我曾经在一个项目里,ADC读数一直跳,从1000跳到3000。查了两天,最后发现是电源纹波太大。加了个电容,立马稳了。

所以,硬件上的小细节,往往比软件更关键。

3.8 小结

这一讲,咱们从硬件确认开始,到初始化、配置通道、启动转换、读取结果,再到电压换算,完整走了一遍。

你跟着代码敲一遍,应该就能让ADC跑起来了。

下一讲,我会讲多通道同步采集。到时候你会看到,单通道只是开胃菜,多通道才是真正的硬仗。

好,今天就到这儿。有问题欢迎留言,咱们下期见。