打印
[Kinetis]

最简单的ADC应用实例:软件触**询采样

[复制链接]
1204|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
芙蓉洞|  楼主 | 2016-1-16 09:29 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
【概述】

ADC通用应用模型】
  【配置阶段】
  【使用阶段】
【在KL26上接地气】
【总结】


----我是可爱的分割线----

【概述】

大多数Kinetis芯片上使用的ADC都是SAR ADC(successive approximation ADC),例如我们熟悉的K60, K64, KL25及最近在社区里比较火的KL26。这个SAR ADC是一个非常好用的模块,一方面是说它在功能上可以很容易地执行模拟量到数字量的转换,但如果你深入了解一下这个ADC模块的软件编程模型,就会发现其中无处不体现着设计模型的经典。在下文中,我们将统一使用“KL26的ADC”指代我们今天要了解的这个漂亮的小东西。


当然,在这个文档里,我们的重点在于如何以最快、最简便和最好用的方式将这个ADC模块添加到应用程序当中,至于这个ADC经典的编程模型,以后我们还会在合适的时间详细讨论。对于忙碌的嵌入式软件工程师来讲,有太多的原理图要看,要写很多关于项目的程序和文档,实在没有时间去深究芯片内部一个功能模块的奥妙,好吧,那么我们这篇文档就有意义了,因为,因为通过这篇文档就是提供了一个“快餐”式的说明,能用起来就OK。


ADC通用应用模型】
在单片机中使用任何一款ADC, 不论是哪家公司设计的IP,对ADC应用的模型基本上是相似的,主要包含两个阶段:配置阶段和使用阶段。呵呵,这里是有点废话了,因为所有的IP模块几乎都是以这两个阶段进行编程的。当我们要在应用程序中使用这些ADC的时候,只要对号入座,找到相应的代码填写到程序中即可。


【配置阶段】
对于一般的ADC来讲,配置阶段要做哪些必要的工作呢?

· 参考电压源。从ADC转换的本质来看,无非是拿外部采样回来的模拟信号同参考电压进行比较,“我是你的几分之几”,最终得到一个量化的参考值。在这个比较过程中,比较的次数就对应着转换的分辨率,然而,能执行更多次比较的关键在于ADC内部能够对参考电压进行细分,从而能够提供精细的分辨粒度。

· 驱动时钟。这是单片机上所有模块都必须的,要驱动数字系统工作。通常情况下,单片机会将某个总线上的时钟引出来一条信号线连接到模块上,如果模块内部希望调整这个时钟,还可以设计一个分频因子的选项,将这个从总线上引出来的时钟信号分频后再输入到模块里。同时,为了低功耗应用的需求(公共使用的总线时钟可能被停掉以降低功耗),模块内部也可以专门设计一个独立的时钟。

· 分辨率。分辨率之于ADC,就如波特率之于串行总线通信类的模块一样,标识了ADC模块的工作性能。


至于其它的功能,各家实现的ADC都有自己的特点,比如说对齐方式、单端差分、硬件平均、比较触发等等,都属于附加功能,用起来可以提升某些应用的性能,不用的话软件也可以搞定。


总之,只要选择好参考电压源、驱动时钟和分辨率,ADC基本上就可以工作了。如果你要跟我抬杠的话,好吧,供电和引脚也要考虑到。

相关帖子

沙发
芙蓉洞|  楼主 | 2016-1-16 09:30 | 只看该作者
【使用阶段】
那么使用阶段呢?


对于ADC来讲,使用ADC就是要读到ADC的转换值,至于怎么才能读到,大体上要考虑两个点:

· 触发方式。毕竟是集成在数字系统中,ADC不会自己无休无止地执行转换,否则芯片会转到火爆,ADC只有在需要的情况下才勉为其难地执行那么一次转换,所以你安排什么人适时地踹它一脚,叫它起来工作,一个任务做晚之后,它又会自动懒在那里。怎么感觉跟猪有点像,哈哈,不踹它就不运动。

· 多通道复用转换器。基于ADC是触发式地工作方式,通常多会将一个转换器复用给多个输入通道,每次在触发时指定转换哪个输入通道的电压值,这样会为芯片制造解决节约很多成本,不需要为每个引脚都安排一个专门的转换器。


【在KL26上接地气】
前面我们已经了解到ADC工作的一般模型,那么在使用KL26的ADC时,只要对号入座就可以了。在文档中贴出了代码,大家一看就明白,多余的话我就不再多说了(为什么要用“再”呢?)。

使用特权

评论回复
板凳
芙蓉洞|  楼主 | 2016-1-16 09:31 | 只看该作者
#include "app_inc.h"
#include "fsl_device_registers.h"

#define APP_ADC_IDX                 0U
#define APP_ADC_CONV_CMD_GROUP_IDX  0U
#define APP_ADC_CONV_CHN 26U /* Internal thermal sensor channel. */

static const ADC_ConverterConfig_T gAdcConverterConfigStruct =
{
    .RefVoltSource = eADC_RefVoltSource_VREF,
    .ClkSource = eADC_ClkSource_ASYNCCLK,
    .ClkDivider = eADC_ClkDivider_1,
    .SampleMode = eADC_SampleSingle_12Bit,
    .HwAverageSampleMode = eADC_HwAverageSampleDisable,
    .enContiConvMode = false
};

/*
static ADC_ConvCmd_T gAdcConvCmdStruct =
{
    .enIntOnComplete = false,
    .enDiffConv = false,
    .ChnIdx = APP_ADC_CONV_CHN
};
*/

static void init_ADC(void);

int main(void)
{
    uint16_t adc_value;

    init_board();
   
    printf("\r\nHello, Board.\r\n");
    printf("Compiled at %s on %s\r\n", __TIME__, __DATE__);
   
    printf("initialization ...\r\n");

    init_ADC();

    printf("system ready.\r\n");

    /* ADC auto calibration. */
    if (!ADC_ExecAutoCalibration(APP_ADC_IDX))
    {
        printf("ADC_ExecAutoCalibration() failed!\r\n");
    }
    else
    {
        printf("ADC_ExecAutoCalibration() done.\r\n");
    }

    while (1)
    {
        printf("\r\nPress any key to trigger the ADC conversion and get value ...\r\n");
        getchar();

/*
        ADC_ExecConvCmd(APP_ADC_IDX, APP_ADC_CONV_CMD_GROUP_IDX, &gAdcConvCmdStruct);
        ADC_WaitConvCmdDone(APP_ADC_IDX, APP_ADC_CONV_CMD_GROUP_IDX);
        adc_value = ADC_GetConvValue(APP_ADC_IDX, APP_ADC_CONV_CMD_GROUP_IDX);
*/
        adc_value = ADC_GetConvSingleEndValueBlocking(APP_ADC_IDX, APP_ADC_CONV_CMD_GROUP_IDX, APP_ADC_CONV_CHN);
        printf("Internal thermal ADC value: %d \r\n", adc_value);
    }
}

static void init_ADC(void)
{
    /* Clock. */
    SIM_EnableClockForADC(APP_ADC_IDX, true);

    /* Pin mux. */
    /* No need, since using internal sensor. */

    /* Configuration. */
    ADC_ResetConverter(APP_ADC_IDX);
    ADC_ConfigConverter(APP_ADC_IDX, &gAdcConverterConfigStruct);

}

/* EOF. */

使用特权

评论回复
地板
芙蓉洞|  楼主 | 2016-1-16 09:32 | 只看该作者
下载运行后,在终端输出的log如下所示:
图0

此处仅列举几个要点:

· 自动校准转换器。对于KL26的ADC,在配置阶段有个特别的要求,就是要执行一下自动校准,对ADC转换器进行调校,虽然也可以手动触发自动校准过程(ADC_GetAutoCalibration)并写数(ADC_SetCalibration),但是既然在硬件上提供了自动校准的机制,不用白不用,省掉我们一部分的麻烦。为此,我专门封装了一次性搞定自动校准的API,ADC_ExecAutoCalibration。

· 软件触发。当KL26的ADC被配置为软件触发时,使用的是“命令式”触发,一旦更新转换通道,向SC1[0]寄存器写数,就会触发一次新的转换。具体地,可以通过调用ADC_ExecConvCmd进行软件触发。

· 封装API。尽管也提供了比较灵活地专门用于软件触发(ADC_ExecConvCmd)、等待转换完成(ADC_WaitConvCmdDone)和读取数据(ADC_GetConvValue)的API,但考虑到这几个函数在比较简单的应用中经常会连在一起用,所以我又把它们3个封装成一个可以直接获取转换结果的API(ADC_GetConvSingleEndValueBlocking)

另外,采样对象上,我设定为转换ADC内部一个温度传感器的值。哈哈,KL26上的ADC确实提供了这么一个信号源,通过26号通道输入,不用任何外部连接就可以完成我们的实验。

使用特权

评论回复
5
芙蓉洞|  楼主 | 2016-1-16 09:34 | 只看该作者

图1

这个时候玩家们可能会想自己动手改造一下样例程序,把YL-KL26Z板子上那个大大的电位器用起来玩耍一番。
图2

小case,只要改变一下采样通道即可。
图3

在代码中将“APP_ADC_CONV_CHN”值改为“0”即可
figure_4.png (14.21 KB, 下载次数: 0)
下载附件
2015-5-23 15:30 上传


图4

使用特权

评论回复
6
芙蓉洞|  楼主 | 2016-1-16 09:36 | 只看该作者
不过看样子还需要重新设定一下pin mux,也不是麻烦事。在init_ADC()函数中新增配置PTE20复用功能为ADC的语句:
    /* Pin mux. */
    PORTE->PCR[0] = PORT_PCR_MUX(0);

在应用程序中直接访问芯片的功能寄存器是比较暴力的做法,我不是很推荐使用,此处仅仅作为快速演示才勉强用一下,若是在工程代码里,这里必须要在bsp_config.h/.c中封装一个专门的函数才能在应用程序中调用。

为了能让应用程序找到寄存器,还需要额外地在main.c中包含寄存器定义文件。
#include "fsl_device_registers.h"

编译OK,下载OK,运行后在终端输出log如下:
图5

看着这张图,童鞋们是不是能想象到我在转动电位器的场景呢。


【总结】
在本文档中,首先说明了使用ADC编程的一般概念,将对ADC的编程过程归类为配置阶段和使用阶段。在配置过程中,需要关注三个必要的内容:参考电压源、模块工作的时钟信号源及ADC采样的分辨率。在转换过程中,需要关注触发方式及了解多通道复用转换器的实现内容。在将这些概念套用到KL26的具体程序中,仅仅使用:ADC_ConfigConverter、ADC_ExecAutoCalibration和ADC_GetConvSingleEndValueBlocking就能够实现一个最简单的ADC采样功能,并演示了如何采样ADC内部温度寄存器和YL-KL26Z开发板上的电位器的实例。


使用特权

评论回复
7
quray1985| | 2016-1-17 20:33 | 只看该作者
用adc的话,如果采样率高的话,一般用什么方式才能把数据转移呢

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

42

主题

398

帖子

3

粉丝