jicheng0622

【原创】从零入手Kinetis系统开发(七)之ADC模块

0
阅读(15322) 评论(43)

    今天是2月最后一天,而且还是四年一次的2月29日,为了纪念这难得的一天还是决定写点东西,呵呵。在连续写了几篇有关飞思卡尔开发环境(IDE)的博客之后,觉着虽然有点新意有点小技巧在里面,不过看多了估计大家也看腻歪了也会觉着其实写了那么多没实际内容(其实真有的,这些小技巧我还是花费了一些心思专研了一番的,哈哈),所以这次就再爆点实料,继续更新从零入手系列。

    前一阵儿,自己的确在找状态,因为放假回来之后觉着对Kinetis生疏了不少,思路有些空白,所以私底下弥补了一番,真的是台上十分钟,台下十年功,当然不是在给开源者邀功颂德之类的,就是明白了为什么好多人不愿意开源,其实一方面可能的确涉及到自己的知识产权和核心的东西,还有就是想要开源不单单是简单的贴出源代码这么简单(这点很同意AET博主说的,其实作为开源者也是),作为开源者,开源常常会拖带一些麻烦的义务还有因为自己辛苦的劳动被别人简单的贴出去连转载两个字都不加的委屈,说真的,简单的贴出源代码,估计没几个人能看懂,当然代码风格好的就不算了,嘿嘿,所以开源需要很大的勇气。晕,又扯偏了,呵呵,还是继续说正事吧,哈哈~

    又是老惯例,啰嗦了一堆,所以老朋友可以直接跳的该段了,哈哈,本篇就拿Kinetis的ADC模块开刀,其实有过网友说急需DMA的例程还有说写写细说寄存器使用方法之类的,不是不想写,是的确我还没用到DMA之类的,所以可能还需一些时日,不过保证肯定会有的,如果有先写出来的还是欢迎分享出来,独乐乐不如众乐乐嘛,哈哈,下面进入正题:

1.Kinetis的ADC模块的一些特点(以K60为例),挑重要的说了,一些AD模块常见的特点就不提了,呵呵:

(1)最高16位AD转换精度,逐次逼近型的,所以速度没问题,而且能做到如此精度已实属不易了,再往高估计就该换Σ-Δ的了;

(2)最多4对差分模拟输入通道和24个单端模拟输入通道,对一般工业应用足够了,其实在对AD要求不高的应用还是用内部AD好些,选外部AD的话一是操作起来麻烦,还有就是对模拟电路布局布线上还是有些技巧的;

(3)输出精度可选,例如差分的话可编程16位,13位,11位和9位模式,单端的话可编程为16位,12位,10位和8位,不明白为什么差分精度非要比单端多1位,有知道的希望不吝告诉一下,在下面留言即可,呵呵;

(4)输入时钟可选四种时钟,即bus_clock,bus_clock/2,ALTCK,ADACK;

(5)内设内部温度传感器,用于监控芯片温度,这个还是有一定用途的,不过对他的精度不要有过高期望;

(6)硬件平均功能,对AD转换结果在队列里平均之后输出,挺实用的,要是再能对列排列一下就好了,咳咳,有点要求太高了,哈哈;

(7)带自校准模式,用过外部AD的知道,这个功能是必须的但又颇有点小道道的,深入做过项目的肯定知道,呵呵;

(8)最高64倍PGA(programable gain Amplifier),加上这个还是让俺们给飞思卡尔加点满意分的,哈哈。

2.Kinetis的ADC内部结构框图,是我们接下来的编程的依据,老套路,上图:

可能第一眼我们的感觉都是。。。有点复杂哈,咳咳,所以我在图中对重要的部分用红线圈出来逐个分析了下,怎样,应该初步了然了吧,图中我圈出来的是重点抓住的,下面软件编程的时候需要用到,其他的可以随便看看,前提是你有足够的耐心去看一堆英文说明了,哈哈。

3.软件编程部分,其实无非就是操作寄存器,其实按照datasheet的来就可以,另外强调一下,官方例程给出的是PDB硬件触发,觉着有点麻烦,所以按照平时常用的方法(对我来说,还是习惯软件触发)针对Tower系统写了下,采用的是单端模式。如下:

 

/**********************************************************************************
**Routine:ADC_Init
**input:    ADC---ADC0 and ADC1 module selection
  bits:     0--8bit, 1--12bit, 2--10bit, 3--16bit
  channel:  0~23 in total of 24 channels in single-ended convertion mode
  mode:     INT_MODE, SEARCH_MODE(母语解释一下吧,哈哈,这里分为中断方式和查询方式)
**********************************************************************************/
void ADC_Init(ADC_MemMapPtr ADC, uint8 bits, uint8 channel, uint8 mode)
{
    if(ADC == ADC0_BASE_PTR)
      SIM_SCGC6 |= SIM_SCGC6_ADC0_MASK;              /* turn on the ADC0 clock */ 
    else if(ADC == ADC1_BASE_PTR)
      SIM_SCGC3 |= (SIM_SCGC3_ADC1_MASK );           /* turn on the ADC1 clock */
    
    ADC_CFG1_REG(ADC) |= ADC_CFG1_ADIV(3)            /* normal power, clock rate is (inputclock)/8 */
                         + ADC_CFG1_ADLSMP_MASK      /* long sample time */
                         + ADC_CFG1_MODE(bits)       /* bits range 0 to 3(0--8bit, 1--12bit, 2--10bit, 3--16bit) */
                         + ADC_CFG1_ADICLK(1);       /* inputclock is busclock/2 */
                                                                                                                                              
    ADC_CFG2_REG(ADC) &= ~(ADC_CFG2_MUXSEL_MASK      /* a registers is selected */
                         + ADC_CFG2_ADACKEN_MASK     /* Asynchronous clock output disabled */
                         + ADC_CFG2_ADHSC_MASK       /* Normal conversion sequence selected */
                         + ADC_CFG2_ADLSTS_MASK);    /* Default longest sample time. */               
/***********************************************************************************
**set default status:Software triger(a convertion is initated following a write to 
  SC1A)compare function disabled, DMA is disabled, default voltage reference pin
  (external pins VREFH and VREFL).
***********************************************************************************/
    ADC_SC2_REG(ADC) = 0;
    
    ADC_SC3_REG(ADC) |= ADC_SC3_ADCO_MASK            /* continuous conversions */
                        + ADC_SC3_AVGE_MASK          /* hardware averages enabled. */
                        + ADC_SC3_AVGS(3);           /* 4 samples average */
                 
  //---when in software triger mode,  a conversion is actived after the ADC_SC1A is writed.   
    ADC_SC1_REG(ADC,0)  = ADC_SC1_ADCH(channel);     /* single-ended AD20 channel is selected */
    
    if(mode == INT_MODE)
    {
      ADC_SC1_REG(ADC,0)  |= ADC_SC1_AIEN_MASK;      /* conversion complete interrrupt enabled */
      if(ADC == ADC0_BASE_PTR)
        enable_irq(adc0_isr_no);                     /* enable the ADC0 IRQ interrupt */
      else if(ADC == ADC1_BASE_PTR)
        enable_irq(adc1_isr_no);                     /* enable the ADC1 IRQ interrupt */
    }
    else if(mode == SEARCH_MODE)
      ADC_SC1_REG(ADC,0)  &= ~ADC_SC1_AIEN_MASK;     /* conversion complete interrrupt disabled */
}
/**********************************************************************************
**Routine:Read_ADC
**input:    ADC---ADC0 and ADC1 module selection
  result:   the pointer of the return value of the convertion result.
**********************************************************************************/
void Read_ADC(ADC_MemMapPtr ADC, uint16 * result)
{
    while(!(ADC_SC1_REG(ADC,0)&ADC_SC1_COCO_MASK));  /* wait until the selected numbers of convertion(determined by the AGVS bits) */
    *result = ADC_R_REG(ADC,0);                      /* clear the COCO flag by reading the corresponding data register. */
}
/**********************************************************************************
**Routine:adc0_isr
**Description: the interrupt service routine of ADC0
**********************************************************************************/  
void adc0_isr(void)
{
    (void) ADC0_RA;   
}
/**********************************************************************************
**Routine:adc1_isr
**Description: the interrupt service routine of ADC1
**********************************************************************************/
void adc1_isr(void)
{
    (void) ADC1_RA;
}
在这里解释下上面代码,其实看英文注释就行,我每步都做了简单的注释,按照步骤来即可。其实主要分两部分,一个就是其AD初始化部分,主要操作寄存器为ADC_SC1~ADC_SC3和ADC_CFG1~ADC_CFG2。输入参数包括ADC模块选择(包括两个,即ADC0和ADC1),转换精度选择(8位,10位,12位,16位),通道号(由于是单端模式,所以范围为0~23共24个通道)和输出模式(查询或者中断模式);另外就是读结果寄存器部分,包括了查询方式读和中断方式读(我使用了查询方式,所以中断方式我设置了空读,呵呵,其实根本没用到),完整文件见附件。
4.再补充一部分,即主函数内部的调用方法,由于本例程是根据Tower系统写的,而系统板上电位器的分压端是连在ADC1_DM1端上的,不过对单端模式来说其实是连在了AD20这个通道上的,如下图通道安排(看到Chapter3了吧,呵呵,find it in chapter3, you can get it,哈哈):
下面是具体在main函数里的调用方法,如下:
void main(void)
{
  //---------------insert your code in the following--------------
  uint16 AD_Result;       /* 声明AD转换结果变量,存储AD值 */
  
  ADC_Init(ADC1, 3, 20, SEARCH_MODE);   /* 初始化AD1位单通道20输入,转换结果16bit, 查询模式 */
  EnableInterrupts;
  
  while(1)
  {
    Read_ADC(ADC1, &AD_Result);         /* 查询方式读取AD转换结果 */
  }
}
    呵呵,这里ADC模块部分就写这么多了,没办法逐个说寄存器(那样的话篇幅太长了,而且的确心有余力不足),不过觉着如果
顺序看下来还是大概能看懂的,如果有不明白的或者疑问可以留言,会尽量解答,呵呵。好了,老话再次重现江湖,未完待续,哈哈~
    附件为完整的ADC底层文件,需要的话可以瞅瞅,其实这部分我早就在当初开源那个开发底层框架的时候一块传上去了,这里单独传一下AD部分~
  1. 博主,在AD部分的datasheet中说"calibration must be run,.........",你的驱动程序中并没有使用AD模块的自校正功能,也能正常采样吗? 
  2. @snakeemail

    谢谢你的意见,的确存在不方便,之后的版本我会改过来的。

    至于ADHSC这个位,在手册上是有句“ADHSC should be used when the ADCLK exceeds the limit for ADHSC = 0”的,意思就是正常模式下如果ADCLK过高会造成AD转换精度不够,选择高速模式的话额外增加两个ADCLK保证转换精度,这样的话输入ADCLK可以很高,满足AD转换的实时性~

  3. 建议你不要用 ADC_MemMapPtr ADC当入参,自己用别人用都麻烦,建议入参用enum,枚举两个常量代表ADC0,和1,然后程序内部根据输入得到指针。

    另外这个ADHSC我就觉得飞思卡尔没说明白,为什么要这个?高速转换你还要加两个延时干嘛,采样加延时是为给保持电容充电,转换的时间是不能太长的,太长电容上的电都漏了

  4. @snakeemail
    呵呵,我来回对了好几次,不过多谢你的提醒,很认真~
  5. @jicheng0622
    呵呵,我没看清
  6. @snakeemail
    我前面是与非的,所以ADHSC位相当于清零,选择Normal conversion sequence selected
  7. ADHSC configures the ADC for very high speed operation. The conversion sequence is altered (2 ADCK cycles added to the conversion time) to allow higher speed conversion clocks. 0 Normal conversion sequence selected. 1 High speed conversion sequence selected (2 additional ADCK cycles to total conversion time). #define ADC_CFG2_ADHSC_MASK 0x4u 你的注释写错了,前面没写清
  8. @snakeemail
    我看了下,没有错吧~
  9. ADC_CFG2_ADHSC_MASK       /* Normal conversion sequence selected */写错了
  10. 回复:回复

    大侠,K60的ADC在IAR环境下可以设置参考电压的吧,要怎么设置呢

    呵呵,刚出差回来,我没用过内部的参考电压源,平时都是直接用外部参考电压

  11. 求大神QQ号啊,有好多问题想请教

  12. 大侠,K60的ADC在IAR环境下可以设置参考电压的吧,要怎么设置呢

  13. 回复:回复

    kinetis的ad有没有内部参考电压?看手册上有这么一句:Alternate reference pair (V ALTH and V ALTL ). This pair may be additional external pins or internal sources depending on MCU configuration. 博主知道VALTH和VALTL怎么用吗?难道只是用来接一个备用的参考电压?这样用处不大啊。

    这个我还真没有深究,一般就直接用外部参考源VREFH和VREFL了,呵呵,你挺仔细的来~

  14. kinetis的ad有没有内部参考电压?看手册上有这么一句:Alternate reference pair (V ALTH and V ALTL ). This pair may be additional external pins or internal sources depending on MCU configuration. 博主知道VALTH和VALTL怎么用吗?难道只是用来接一个备用的参考电压?这样用处不大啊。

  15. 回复:回复

    在这个k60参考手册上面,电气特性这部分

    嗯,看到了,谢谢,这个818.33Ksps是采样率,当然也可以当做转换数据流的速率,在小于13位精度的时候最高是818.33Ksps,在16位精度的时候是416.467K。另外如果你想算出确切的单次转换速率,你可以看下我的另一篇关于AD转换速率计算工具的说明http://blog.chinaaet.com/detail/27778.html