cc2530的时钟

晶振与时钟

cc2530有一个内部系统时钟,或者说是主时钟。系统时钟源是16Mhz的RC晶振或是32MHz晶体震荡器。利用CLKCONCMD特殊功能寄存器可以控制时钟。

此外,芯片还有一个32KHz时钟源可以是RC振荡器或是晶体振荡器,同样也可以用CLKCONCMD寄存器控制。

其中只读寄存器CLKCONSTA可以用来获得当前时钟状态。高精度的晶体振荡器或是低功耗的RC振荡器都可以成为振荡器的选择,需要注意的是:RF接收器要求使用32MHz晶体振荡器。如下图所示即为时钟结构图:


晶体振荡器

在芯片内部,有2个高频振荡器,一个是32MHz的晶体振荡器,另一个是16MHz的RC晶振。32MHz晶振的启动时间对于某些应用显得有些长,因此芯片可以先在16MHz RC晶振上运行,直到晶体振荡器稳定。16MHzd的RC振荡器比晶体振荡器消耗更少能量,但是精度而言比晶体振荡器差一些,并且不能作为RF传输的运行时钟。

此外,芯片还有2个低频振荡器分别是32KHz的晶体振荡器与32KHz的RC振荡器。其中,32KHz XOSC设计运行在32.768KHz频率上,并且为需要时间准确的系统提供了一个稳定的时钟信号。校验后,32KHz的RCOSC在32.753KHz频率上运行。

需要注意的是校验只能在32MHz XOSC使能的情况下才被允许。与32KHz XOSC 振荡器相比,32KHz的RC振荡器更适宜于较少成本与能耗。

请注意:这2个32KHz的振荡器不能同时使用。

系统时钟

系统时钟来自于所选择的系统时钟源,可以是32MHz的XOSC(晶体振荡器)或者是16MHz的RCOSC(RC振荡器).寄存器CLKCONCMD中OSC位选择系统时钟源,需要指出的是,如果要用RF传输,那么32MHz的晶体振荡器必须被选择作为主时钟并且稳定。

改变CLKCONCMD中的OSC位并不能立刻改变系统时钟,只有当CLKCONSTA寄存器中的OSC位与CLKCONCMD中OSC位相同时才能起作用。这归因于对拥有稳定的时钟的需求优先于改变时钟源。此外,CLKCONCMD中的CLKSPD位如同镜子一样反映CLKCONCMD.OSC位。

当32MHz XOSC晶振选为系统时钟并且稳定之后,16MHz RC振荡器进行校验,例如CLKCONSTA寄存器的OSC位从1变为0。

32KHz时钟选择如上所述,在器件上有2个32KHz时钟源,在复位之后默认会使能32KHz的RC振荡器,并作为32KHz的时钟源,32KHz的RC振荡器能耗小,但与32KHz的晶体振荡器相比精度上差许多。被选择的32KHz时钟源用于驱动休眠时钟,为看门狗产生滴答时钟,并可以用作定时器TIM2的闸门(strobe)用来计算休眠定时器的休眠时间。定时器控制寄存器中OSC32K位用于选择振荡器,作为32KHz的时钟源。CLKCONCMD寄存器的OSC32K位可以随时写入。振荡器在活跃的系统时钟源之前是不会起作用的。当系统时钟从16MHz改变为32MHz的晶体振荡器(CLKCONCMD.OSC从1到0)一旦32KHz RC振荡器被选中了它的的校验就启动了并且被执行。在校准期间,32MHz晶体振荡器的一个分频量会被使用。32KHzRCOSC振荡器校准后的结果是它会工作在32.753kHz上。32kHz RC振荡器校准时间可能要2ms时间来完成。可以设置SLEEPCMD.OSC32K_CALDIS位设置为1的话,会关闭校准。在校准结束时,会在32KHz时钟源上产生一个额外的脉冲,会导致睡眠定时器增加1。注意:当切换到32KHz晶体振荡器后和从32KHz晶体振荡器被设置的PM3模式唤醒时,振荡器稳定到准确频率的时间在500 ms以上。睡眠定时器、看门狗定时器和时钟损失探测器在32KHz晶体振荡器稳定之前不能使用。




  cc2530时钟源主要是4个:对于高频时钟(系统时钟)可以是外接的32MHz晶振,也可以是内部的16MHz RC振荡器;对于低频时钟(32kHz),可以是外接32.768kHz晶振,也可以是内部的32.768kHz RC振荡器。

参考帖子可以知道,石英晶体的精度高,但是耗电大、启动缓慢,而芯片内部自带的RC振荡器虽然精度略低,但启动速度快、耗电低,十分适合在低功耗场合,并且无需外接电子器件就可以工作,降低成本!在上电默认使用的是内部的RC振荡器!

为了自由配置所需要的时钟,主要借助于CLKCONCMD.OSC选择系统主时钟,而借助于CLKCONCMD.OSC32K则用于选择芯片32K时钟源!

而低功耗模式设置时,需要借助于SLEEPCMD寄存器,在swru191e(cc2530datasheet)中并没有说明SLEEPCMD第二位功能,如下所示:

但是参考cc2430芯片的说明书可以发现,对应的SLEEP寄存器则有说明,如下所示,小编的理解是,TI收购Chipcon公司,随之将cc2430 2.4GHz Zigbee解决方案也收入,进一步优化芯片,更新电路IC设计,c2530与之前推出的2430芯片相比,应该具有许多相似之处,可能是cc2530推出不久,说明文档不够详细,当然不排除TI有意隐藏芯片细节的可能,字面的解释就是,当SLEEPCMD.OSC_PD为0时,32MHz晶振与16MHz RC振荡器都会起振振


对于SLEEPSTA寄存器中BIT6/BIT5说明在cc2530说明书中也并没有说明,可以参考cc2430说明书中内容,其中第6位XOSC_STB表明外部高速32M晶振是否上电并稳定起振,当稳定时该位为1;同样对于第5位HFRC_STB则表明内部16MHz高速RC振荡器是否起振,并是否稳定,当16MHz RC振荡器稳定时该位为1,如下图所示:


而当需要设置系统时钟时,借助于CLKCONCMD寄存器就可以选择芯片主时钟与32K时钟源,当设置稳定后,可以通过CLKCONSTA寄存器中第6位与第7位来反应当前时钟的选择情况,当时钟稳定后会置位相应的位,依此可以判断当前时钟设置情况!





此外,当配置完SLEEPCMD寄存器后,可以置位OSC_PD来关闭未被选择的高速时钟(16MHz EC振荡器或是32MHz 晶体振荡器)(可能是第三位写1,来实现此功能对于CC2530),如下所示


作为演示,小编写了一个示例代码用于配置cc2530时钟系统,如下所示:void wg_sys_clk_config(uint8 mclk,uint8 lclk){  uint16 i;    SLEEPCMD &= ~OSC_PD;   /* turn on 16MHz RC and 32MHz XOSC */   /* wait for 32MHz XOSC stable */  while (!(SLEEPSTA & XOSC_STB)||!(SLEEPSTA &HFRC_STB));     asm("NOP");           /* chip bug workaround */  for (i=0; i<504; i++) asm("NOP"); /* Require 63us delay for all revs */    /* Select system clock and the source for 32K clock */  CLKCONCMD = (mclk | lclk);   /* Wait for the change to be effective */   while (CLKCONSTA != (mclk | lclk));     SLEEPCMD |= OSC_PD;   /* turn off the oscillator not used*/ }


其中:#ifndef BV#define BV(n)      (1 << (n))#endif/* SLEEPCMD and SLEEPSTA bit definitions */#define OSC_PD     BV(2)  /* 0: Both oscillators powered up and stable                           * 1: oscillators not stable */#define XOSC_STB   BV(6)  /* XOSC: powered, stable=1 */#define HFRC_STB   BV(5)  /* HFRC: powerd , stable=1 */





cc2530有一个内部系统时钟,或者说是主时钟。系统时钟源是16Mhz的RC晶振或是32MHz晶体震荡器。利用CLKCONCMD特殊功能寄存器可以控制时钟。

此外,芯片还有一个32KHz时钟源可以是RC振荡器或是晶体振荡器,同样也可以用CLKCONCMD寄存器控制。

其中只读寄存器CLKCONSTA可以用来获得当前时钟状态。

CLKCONCMD:时钟频率控制寄存器。

D7

D6

D5~D3

D2~D0

32KHZ时间振荡器选择

系统时钟选择

定时器输出标记

系统主时钟选择

D7位为32KHZ时间振荡器选择,,032KRC震荡,132K晶振。默认为1
D6
位为系统时钟选择。032M晶振,116M RC震荡。当D7位为0D6必须为1
D5~D3
为定时器输出标记。00032MHZ00116MHZ0108MHZ0114MHZ1002MHZ101 1MHZ110500KHZ111250KHZ。默认为001。需要注意的是:当D61时,定时器频率最高可采用频率为16MHZ
D2~D0
:系统主时钟选择:00032MHZ00116MHZ0108MHZ0114MHZ1002MHZ1011MHZ110500KHZ111250KHZ。当D61时,系统主时钟最高可采用频率为16MHZ


CLKCONSTA
:时间频率状态寄存器。

D7

D6

D5~D3

D2~D0

当前32KHZ时间振荡器

当前系统时钟

当前定时器输出标记

当前系统主时钟

D7位为当前32KHZ时间振荡器频率。032KRC震荡,132K晶振。
D6
位为当前系统时钟选择。032M晶振,116M RC震荡。
D5~D3
为当前定时器输出标记。00032MHZ00116MHZ0108MHZ0114MHZ1002MHZ101 1MHZ110500KHZ111250KHZ
D2~D0
为当前系统主时钟。00032MHZ00116MHZ0108MHZ0114MHZ1002MHZ1011MHZ110500KHZ111250KHZ



高精度的晶体振荡器或是低功耗的RC振荡器都可以成为振荡器的选择,需要注意的是:RF接收器要求使用32MHz晶体振荡器。如下图所示即为时钟结构图:


在芯片内部,有2个高频振荡器,一个是32MHz的晶体振荡器,另一个是16MHz的RC晶振。32MHz晶振的启动时间对于某些应用显得有些长,因此芯片可以先在16MHz RC晶振上运行,直到晶体振荡器稳定。16MHzd的RC振荡器比晶体振荡器消耗更少能量,但是精度而言比晶体振荡器差一些,并且不能作为RF传输的运行时钟。

此外,芯片还有2个低频振荡器分别是32KHz的晶体振荡器与32KHz的RC振荡器。其中,32KHz XOSC设计运行在32.768KHz频率上,并且为需要时间准确的系统提供了一个稳定的时钟信号。校验后,32KHz的RCOSC在32.753KHz频率上运行。

需要注意的是校验只能在32MHz XOSC使能的情况下才被允许。与32KHz XOSC 振荡器相比,2KHz的RC振荡器更适宜于较少成本与能耗。

请注意:这2个32KHz的振荡器不能同时使用。如下所示即为系统时钟控制寄存器CLKCONCMD中部分控制位:

cc2530的时钟_第1张图片

系统时钟来自于所选择的系统时钟源,可以是32MHz的XOSC(晶体振荡器)或者是16MHz的RCOSC(RC振荡器).寄存器CLKCONCMD中OSC位选择系统时钟源,需要指出的是,如果要用RF传输,那么32MHz的晶体振荡器必须被选择作为主时钟并且稳定。

改变CLKCONCMD中的OSC位并不能立刻改变系统时钟,只有当CLKCONSTA寄存器中的OSC位与CLKCONCMD中OSC位相同时才能起作用。这归因于对拥有稳定的时钟的需求优先于改变时钟源。此外,CLKCONCMD中的CLKSPD位如同镜子一样反映CLKCONCMD.OSC位。

当32MHz XOSC晶振选为系统时钟并且稳定之后,16MHz RC振荡器进行校验,例如CLKCONSTA寄存器的OSC位从1变为0。

cc2530的时钟_第2张图片


cc2530的时钟_第3张图片







cc2530的时钟_第4张图片

32KHz时钟选择

如上所述,在器件上有2个32KHz时钟源,在复位之后默认会使能32KHz的RC振荡器,并作为32KHz的时钟源,32KHz的RC振荡器能耗小,但与32KHz的晶体振荡器相比精度上差许多。被选择的32KHz时钟源用于驱动休眠时钟,为看门狗产生滴答时钟,并可以用作定时器TIM2的闸门(strobe)用来计算休眠定时器的休眠时间。定时器控制寄存器中OSC32K位用于选择振荡器,作为32KHz的时钟源。

CLKCONCMD寄存器的OSC32K位可以随时写入。振荡器在活跃的系统时钟源之前是不会起作用的。当系统时钟从16MHz改变为32MHz的晶体振荡器(CLKCONCMD.OSC从1到0)一旦32KHz RC振荡器被选中了它的的校验就启动了并且被执行。在校准期间,32MHz晶体振荡器的一个分频量会被使用。32KHzRCOSC振荡器校准后的结果是它会工作在32.753kHz上。32kHz RC振荡器校准时间可能要2ms时间来完成。可以设置SLEEPCMD.OSC32K_CALDIS位设置为1的话,会关闭校准。在校准结束时,会在32KHz时钟源上产生一个额外的脉冲,会导致睡眠定时器增加1。注意:当切换到32KHz晶体振荡器后和从32KHz晶体振荡器被设置的PM3模式唤醒时,振荡器稳定到准确频率的时间在500 ms以上。睡眠定时器、看门狗定时器和时钟损失探测器在32KHz晶体振荡器稳定之前不能使用。




定时器滴答值产生器
CLKCONCMD.TICKSPD寄存器控制timer1、timer3和timer4的全局预分频。预分频的值设置范围在0.25MHz和32MHz之间。
需要注意的是如果CLKCONCMD.TICKSPD显示的频率高于系统时钟,则在CLKCONSTA.TICKSPD中的实际的预分频值表明是和系统时钟的值是一样的。

数据滞留
在PM2和PM3电源模式中,绝大多数的内部电路关闭了,然而,SRAM中任保留它的内容,内部寄存器的值也会保留。
保留数据的寄存器是CPU的寄存器、外部寄存器和RF寄存器,除非另一些位域值设置的比较特殊。切换到PM2和PM3模式的现象对于软件而已是透明的。
注意在PM3模式下睡眠定时器的值不会保存。




CC2530的时钟模块

(cc2530_datasheet节选翻译如下)

******************************************************************

* 作        者:fulinux

* 转载声明:点击链接
******************************************************************

  1. 振荡器和时钟
    CC2530设备有一个内部系统时钟,或者主时钟。系统时钟源可以是从16MHz RC振荡器或一个32M晶体振荡器中的一个提供。系统时钟源是由CLKCONCMD SRF控制寄存器。
    还有一个32KHz的时钟源,来源可以是从RC 振荡器或者32KHz的晶体振荡器中过来,同样是由CLKCONCMD寄存器控制。
    CLKCONSTA寄存器是一个制度寄存器,用来获得当前系统时钟的状态。
    时钟源可以在一个精度高的晶体振荡器和一个功耗低的RC振荡器中交替选择使用。注意一点:RF的收发操作是要以32MHz的晶体振荡器为时钟源才行。

  2. 振荡器

    图中给出了时钟系统中可用的时钟源的一个全貌图。
    设备中存在的两个高频振荡器:
    * 32MHz晶体振荡器
    * 16MHz的RC振荡器
    32MHz的晶体振荡器启动时间对于某些应用来说可能太长了;因此设备可以先运行在16MHz的RC振荡器中运行直到晶体振荡器稳定后在使用32MHz晶体振荡器。16MHz的RC振
    荡器功耗低但是不是很准,所以不能为RF模块提供服务,只能用32MHz的晶体振荡器。
    设备中存在的两个低频振荡器:
    * 32 KHz晶体振荡器
    * 32 KHz RC振荡器
    32KHz的XOSC被设计的工作频率频率是32.768KHz并且可以为一些要求时钟准确子系统提供一个稳定的时钟信号。32KHz的RCOSC当校准后可以运行在32.753KHz频率下。校准
    只能发生在当32MHz XOSC使能的情况下,可以通过使能SLEEPCMD.OSC32K_CALDIS位来关闭校准。32KHz RC振荡器相对于32KHz  XOSC晶体振荡器功耗低,应该用在可以降
    低成本情况下。两个振荡器不能同时工作。

  3. 系统时钟
    系统时钟是由32MHz XOSC或者16MHz RCOSC两个时钟源驱动的。CLKCONCMD.OSC位用来选择系统时钟源。注意:使用RF模块时,32MHz晶体振荡器必须被选上并且运行稳定。
    注意:改变CLKCONCMD.OSC位并不能立即导致系统时钟源的改变。当CLKCONSTA.OSC = CLKCONCMD.OSC时时钟源的改变才会发挥作用。这是因为设备在实际改变时钟源之前
    需要稳定的时钟。还有就是注意CLKCONCMD.CLKSPD位反应着系统时钟频率,因此是CLKACONCMD.OSC位的镜子。一旦32MHz的XOSC被选中和稳定,例如,当CLKCONSTA.OSC
    位从1切换到0时。
    注意:从16MHz到32MHz时钟源的改变符合CLKCONCMD.TICKSPD设置。CLKCONCMD.TICKSPD设置的缓慢一些的话,当CLKCONCMD.OSC改变的话会导致实际的时钟源起作用的
    时间会很长。当CLKCONCMD.TICKSPD等于000时会获得最快的切换速度。

  4. 32KHz的振荡器
    默认的或者复位后32KHz RCOSC使能并且被设置作为32KHz的时钟源。其功耗低,但是相对于32KHz晶体振荡器而言精度不高,32KHz时钟源用来驱动睡眠定时器,产生看门狗的滴答值
    和作为timer 2计算睡眠定时器的一个闸门。32KHz时钟源被寄存器CLKCONCMD.OSC32K位用来作为选择振荡器。CLKCONCMD.OSC32K寄存器可以在任意时间写入,但是在16MHz RC
    振荡器是活跃的系统时钟源之前是不会起作用的。当系统时钟从16MHz改变为32MHz的晶体振荡器(CLKCONCMD.OSC从1到0)一旦32KHz RC振荡器被选中了它的的校验就启动了并且被执行。在校准期间,32MHz晶体振荡器的一个分频量会被使用。32KHzRCOSC振荡器校准后的结果是它会工作在32.753kHz上。32kHz RC振荡器校准时间可能要2ms时间来完成。可以设置SLEEPCMD.OSC32K_CALDIS位设置为1的话,会关闭校准。在校准结束时,会在32KHz时钟源上产生一个额外的脉冲,会导致睡眠定时器增加1。
    注意:当切换到32KHz晶体振荡器后和从32KHz晶体振荡器被设置的PM3模式唤醒时,振荡器稳定到准确频率的时间在500 ms以上。睡眠定时器、看门狗定时器和时钟损失探测器在32KHz
    晶体振荡器稳定之前不能使用。

  5. 振荡器和时钟寄存器
    下面是振荡器和时钟寄存器的描述,所有寄存器的位会在进入PM2和PM3时保持不变,除非有异常情况发生。


  6. 定时器滴答值产生器
    CLKCONCMD.TICKSPD寄存器控制timer1、timer3和timer4的全局预分频。预分频的值设置范围在0.25MHz和32MHz之间。
    需要注意的是如果CLKCONCMD.TICKSPD显示的频率高于系统时钟,则在CLKCONSTA.TICKSPD中的实际的预分频值表明是和系统时钟的值是一样的。

  7. 数据滞留
    在PM2和PM3电源模式中,绝大多数的内部电路关闭了,然而,SRAM中任保留它的内容,内部寄存器的值也会保留。
    保留数据的寄存器是CPU的寄存器、外部寄存器和RF寄存器,除非另一些位域值设置的比较特殊。切换到PM2和PM3模式的现象对于软件而已是透明的。
    注意在PM3模式下睡眠定时器的值不会保存。

  1. /********************************************************************************************************************************************************** 
  2. * 文 件 名:main.c 
  3. × 
  4. * 功    能:实验一 系统时钟源的选择 
  5. * 
  6. *           CC2530有1个内部的系统时钟。时钟源可以是1个16MHz的RC振荡器,也可以是1个32MHz的晶体 
  7. *           振荡器。时钟控制是通过使用CLKCON特殊功能寄存器来执行的。系统时钟也提供给所有的8051 
  8. *           外设。 
  9. * 
  10. *           32MHz晶体振荡器的启动时间对于某些应用而言太长了,因此CC2530可以运行在16MHz RC振荡器 
  11. *           直到晶体振荡器稳定。16MHz RC振荡器的功耗要少于晶体振荡器,但是由于它没有晶体振荡器 
  12. *           精确,因此它不适用于射频收发器。 
  13. * 
  14. *           CLKCONCMD.OSC位被用来选择系统时钟源。注意:要使用射频收发器,32MHz晶体振荡器必须被选择 
  15. *           并且稳定。 
  16. * 
  17. *           注意:改变CLKCONCMD.OSC位并不即刻生效。这是因为在实际改变时钟源之前,被选择的时钟源要 
  18. *           首先达到稳定。还要注意:CLKCONSTA.CLKSPD位将反映系统时钟频率,因此它是CLKCON.OSC位的 
  19. *           “镜子”。 
  20. * 
  21. *           当SLEEPSTA.XOSC_STB为1时,表示系统报告32MHz晶体振荡器稳定。然而,这可能并不是实际情况, 
  22. ×          在选择32MHz时钟作为系统时钟源之前,应该等待一个额外的64us的安全时间,可以通过增加一 
  23. *           条空指令"NOP"来实现。如果不等待,可能会造成系统崩溃。 
  24. * 
  25. *           未被选择作为系统时钟源的振荡器,通过设置SLEEP.OSC_PD为1(默认状态)将被设置为掉电模式。 
  26. *           因此,当32MHz晶体振荡器被选择作为系统时钟源后,16MHz RC振荡器可能被关闭,反之亦然。 
  27. *           当SLEEPCMD.OSC_PD为0时,这2个振荡器都被上电并运行。 
  28. *           当32MHz晶体振荡器被选择作为系统时钟源并且16MHz RC振荡器也被上电时,根据供电电压和运 
  29. *           行温度,16MHZ RC振荡器将被不断校准以确保时钟稳定。当16MHz RC振荡器被选择作为系统时钟 
  30. *           源时,该校准不被执行。 
  31. * 
  32. *           本实验将向用户演示选择不同的振荡器作为系统时钟源。本文件中有led闪烁的子程序,用户 
  33. *           可以观察在不同系统时钟源下led的闪烁情况。 
  34. * 
  35. *           在hal.h文件中包含了和系统时钟相关的一些宏,用户使用这些宏可以简化对系统时钟的控制, 
  36. *           提高代码的可读性,本实验中就使用了其中的一些宏。 
  37. * 
  38. * 注    意:本实验可在以下目标板上进行: 
  39. *            
  40. *            
  41. *            
  42. *            
  43. * 
  44. * 版    本:V1.0 
  45. **********************************************************************************************************************************************************/  
  46. #include "hal.h"  
  47.   
  48. #define ON           0x01       //LED状态  
  49. #define OFF          0x00  
  50.   
  51. extern void ctrPCA9554LED(UINT8 led,UINT8 operation);   
  52. extern void PCA9554ledInit();   
  53. /************************************************************************************************** 
  54.  * 函数名称:halWait 
  55.  * 
  56.  * 功能描述:延时 
  57.  * 
  58.  * 参    数:wait - 延时时间 
  59.  * 
  60.  * 返 回 值:无 
  61.  **************************************************************************************************/  
  62. void halWait(BYTE wait){  
  63.    UINT32 largeWait;  
  64.   
  65.    if(wait == 0)  
  66.    {return;}  
  67.    largeWait = ((UINT16) (wait << 7));  
  68.    largeWait += 114*wait;  
  69.   
  70.   
  71.    largeWait = (largeWait >> CLKSPD);  
  72.    while(largeWait--);  
  73.   
  74.    return;  
  75. }  
  76.   
  77.   
  78. /************************************************************************************************** 
  79.  * 函数名称:main 
  80.  * 
  81.  * 功能描述:反复选择不同的振荡器作为系统时钟源,并调用led控制程序,闪烁LED灯。 
  82.  * 
  83.  * 参    数:无 
  84.  * 
  85.  * 返 回 值:无 
  86.  **************************************************************************************************/  
  87. void main(void)  
  88. {  
  89.   UINT8 i;  
  90.   PCA9554ledInit();  
  91.   while(1)  
  92.   {  
  93.     SET_MAIN_CLOCK_SOURCE(CRYSTAL);                      // 设置系统时钟源为32MHz晶体振荡器(大约用时150us),关闭16MHz RC振荡器   
  94.       for (i=0;i<10;i++)  
  95.       {  
  96.        ctrPCA9554LED(0,ON);  
  97.        halWait(200);                        
  98.        ctrPCA9554LED(0,OFF);  
  99.        halWait(200);                        
  100.       }  
  101.      SET_MAIN_CLOCK_SOURCE(RC);                           // 选择16MHz RC振荡器,关闭32MHz晶体振荡器  
  102.       PCA9554ledInit();  
  103.       halWait(200);   
  104.       for (i=0;i<10;i++)  
  105.       {  
  106.        ctrPCA9554LED(1,ON);  
  107.        halWait(200);                        
  108.        ctrPCA9554LED(1,OFF);  
  109.        halWait(200);                        
  110.       }  
  111.   }  
  112. }  


  1. /********************************************************************************************************** 
  2. * 文 件 名:iic.C 
  3. * 功    能:实验二 GPIO控制实验 
  4. *            该实验采用CC2530的I/O口(P1.0和P1.1)模拟IIC总线的SCL和SDA,然后通过IIC总线形式控制GPIO扩展芯片 
  5. *            PCA9554,最后通过扩展的IO来控制LED的亮灭。 
  6. * 
  7. * 硬件连接:将OURS的CC2530RF模块插入到普通电池板或智能电池板上。 
  8. * 
  9. *           P1.0 ------ SCL 
  10. *           P1.1 ------ SDA 
  11. *            
  12. * 版    本:V1.0 
  13. **************************************************************************************************************/  
  14.   
  15. #include "ioCC2530.h"   
  16. #include "hal_mcu.h"  
  17.   
  18. #define SCL          P1_0       //IIC时钟线  
  19. #define SDA          P1_1       //IIC数据线  
  20.   
  21. //定义IO方向控制函数  
  22. #define IO_DIR_PORT_PIN(port, pin, dir)  \  
  23.    do {                                  \  
  24.       if (dir == IO_OUT)                 \  
  25.          P##port##DIR |= (0x01<<(pin));  \  
  26.       else                               \  
  27.          P##port##DIR &= ~(0x01<<(pin)); \  
  28.    }while(0)  
  29.   
  30.   
  31. #define OSC_32KHZ  0x00                //使用外部32K晶体振荡器  
  32.   
  33. //时钟设置函数  
  34. #define HAL_BOARD_INIT()                                         \  
  35. {                                                                \  
  36.   uint16 i;                                                      \  
  37.                                                                  \  
  38.   SLEEPCMD &= ~OSC_PD;                       /* 开启 16MHz RC 和32MHz XOSC */         \  
  39.   while (!(SLEEPSTA & XOSC_STB));            /* 等待 32MHz XOSC 稳定 */               \  
  40.   asm("NOP");                                                                         \  
  41.   for (i=0; i<504; i++) asm("NOP");          /* 延时63us*/                            \  
  42.   CLKCONCMD = (CLKCONCMD_32MHZ | OSC_32KHZ); /* 设置 32MHz XOSC 和 32K 时钟 */        \  
  43.   while (CLKCONSTA != (CLKCONCMD_32MHZ | OSC_32KHZ)); /* 等待时钟生效*/               \  
  44.   SLEEPCMD |= OSC_PD;                        /* 关闭 16MHz RC */                      \  
  45.  
  46.   
  47. #define IO_IN   0           //输入  
  48. #define IO_OUT  1           //输出  
  49.   
  50. uint8 ack;              //应答标志位  
  51. uint8 PCA9554ledstate = 0;  //所有LED当前状态  
  52.   
  53. /****************************************************************************** 
  54.  * 函数名称:QWait 
  55.  * 
  56.  * 功能描述:1us的延时 
  57.  * 
  58.  * 参    数:无 
  59.  * 
  60.  * 返 回 值:无 
  61.  *****************************************************************************/   
  62. void QWait()       
  63. {  
  64.     asm("NOP");asm("NOP");  
  65.     asm("NOP");asm("NOP");  
  66.     asm("NOP");asm("NOP");  
  67.     asm("NOP");asm("NOP");  
  68.     asm("NOP");asm("NOP");  
  69.     asm("NOP");  
  70. }  
  71.   
  72. /****************************************************************************** 
  73.  * 函数名称:Wait 
  74.  * 
  75.  * 功能描述:ms的延时 
  76.  * 
  77.  * 参    数:ms - 延时时间 
  78.  * 
  79.  * 返 回 值:无 
  80.  *****************************************************************************/   
  81. void Wait(unsigned int ms)  
  82. {                      
  83.    unsigned char g,k;  
  84.    while(ms)  
  85.    {  
  86.         
  87.       for(g=0;g<=167;g++)  
  88.        {  
  89.          for(k=0;k<=48;k++);  
  90.        }  
  91.       ms--;                              
  92.    }  
  93. }   
  94.   
  95. /****************************************************************************** 
  96.  * 函数名称:Start_I2c 
  97.  * 
  98.  * 功能描述:启动I2C总线,即发送I2C起始条件. 
  99.  * 
  100.  * 参    数:无 
  101.  * 
  102.  * 返 回 值:无 
  103.  *****************************************************************************/   
  104. void Start_I2c()  
  105. {  
  106.   IO_DIR_PORT_PIN(1, 0, IO_OUT);    //设置P1.0为输出  
  107.   IO_DIR_PORT_PIN(1, 1, IO_OUT);    //设置P1.1为输出  
  108.     
  109.   SDA=1;                   /*发送起始条件的数据信号*/  
  110.   asm("NOP");  
  111.   SCL=1;  
  112.   QWait();                /*起始条件建立时间大于4.7us,延时*/  
  113.   QWait();  
  114.   QWait();  
  115.   QWait();  
  116.   QWait();      
  117.   SDA=0;                   /*发送起始信号*/  
  118.   QWait();                 /* 起始条件锁定时间大于4μs*/  
  119.   QWait();  
  120.   QWait();  
  121.   QWait();  
  122.   QWait();         
  123.   SCL=0;                   /*钳住I2C总线,准备发送或接收数据 */  
  124.   asm("NOP");  
  125.   asm("NOP");  
  126. }  
  127.   
  128. /****************************************************************************** 
  129.  * 函数名称:Stop_I2c 
  130.  * 
  131.  * 功能描述:结束I2C总线,即发送I2C结束条件. 
  132.  * 
  133.  * 参    数:无 
  134.  * 
  135.  * 返 回 值:无 
  136.  *****************************************************************************/   
  137. void Stop_I2c()  
  138. {  
  139.   IO_DIR_PORT_PIN(1, 0, IO_OUT);    //设置P1.0为输出  
  140.   IO_DIR_PORT_PIN(1, 1, IO_OUT);    //设置P1.1为输出  
  141.   SDA=0;                            /*发送结束条件的数据信号*/  
  142.   asm("NOP");                       /*发送结束条件的时钟信号*/  
  143.   SCL=1;                            /*结束条件建立时间大于4μs*/  
  144.   QWait();  
  145.   QWait();  
  146.   QWait();  
  147.   QWait();  
  148.   QWait();  
  149.   SDA=1;                           /*发送I2C总线结束信号*/  
  150.   QWait();  
  151.   QWait();  
  152.   QWait();  
  153.   QWait();  
  154. }  
  155.   
  156. /****************************************************************************** 
  157.  * 函数名称:SendByte 
  158.  * 
  159.  * 功能描述:将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,并对 
  160.  *           此状态位进行操作.(不应答或非应答都使ack=0 假)      
  161.  *           发送数据正常,ack=1; ack=0表示被控器无应答或损坏。 
  162.  * 
  163.  * 参    数:c - 需发送的数据 
  164.  * 
  165.  * 返 回 值:无 
  166.  *****************************************************************************/   
  167. void  SendByte(uint8 c)  
  168. {  
  169.  uint8 BitCnt;  
  170.  IO_DIR_PORT_PIN(1, 0, IO_OUT);    //设置P1.0为输出  
  171.  IO_DIR_PORT_PIN(1, 1, IO_OUT);    //设置P1.1为输出  
  172.  for(BitCnt=0;BitCnt<8;BitCnt++)  /*要传送的数据长度为8位*/  
  173.     {  
  174.      if((c</*判断发送位*/  
  175.        else  SDA=0;                  
  176.       asm("NOP");  
  177.      SCL=1;                       /*置时钟线为高,通知被控器开始接收数据位*/  
  178.       QWait();   
  179.       QWait();                    /*保证时钟高电平周期大于4μs*/  
  180.       QWait();  
  181.       QWait();  
  182.       QWait();           
  183.      SCL=0;   
  184.     }      
  185.     QWait();  
  186.     QWait();  
  187.     QWait();  
  188.     SDA=1;                        /*8位发送完后释放数据线,准备接收应答位*/  
  189.     asm("NOP");  
  190.     IO_DIR_PORT_PIN(1, 1, IO_IN);    
  191.     SCL=1;  
  192.     QWait();  
  193.     QWait();  
  194.     QWait();  
  195.     QWait();  
  196.     if(SDA==1)ack=0;       
  197.     else ack=1;                   /*判断是否接收到应答信号*/  
  198.     SCL=0;     
  199.     QWait();  
  200.     QWait();  
  201.     IO_DIR_PORT_PIN(1, 1, IO_OUT);  
  202. }  
  203.   
  204. /****************************************************************************** 
  205.  * 函数名称:RcvByte 
  206.  * 
  207.  * 功能描述:用来接收从器件传来的数据,并判断总线错误(不发应答信号), 
  208.  *           发完后请用应答函数。 
  209.  * 
  210.  * 参    数:无 
  211.  * 
  212.  * 返 回 值:retc - 从器件传来的数据 
  213.  *****************************************************************************/   
  214. uint8  RcvByte()  
  215. {  
  216.   uint8 retc;  
  217.   uint8 BitCnt;  
  218.   IO_DIR_PORT_PIN(1, 0, IO_OUT);    //设置P1.0为输出  
  219.   IO_DIR_PORT_PIN(1, 1, IO_OUT);    //设置P1.1为输出  
  220.   retc=0;   
  221.   SDA=1;                            /*置数据线为输入方式*/  
  222.   IO_DIR_PORT_PIN(1, 1, IO_IN);  
  223.   for(BitCnt=0;BitCnt<8;BitCnt++)  
  224.       {  
  225.         asm("NOP");            
  226.         SCL=0;                     /*置时钟线为低,准备接收数据位*/  
  227.         QWait();  
  228.         QWait();                   /*时钟低电平周期大于4.7μs*/  
  229.         QWait();  
  230.         QWait();  
  231.         QWait();  
  232.         SCL=1;                    /*置时钟线为高使数据线上数据有效*/  
  233.         QWait();  
  234.         QWait();  
  235.         retc=retc<<1;  
  236.         if(SDA==1)retc=retc+1;   /*读数据位,接收的数据位放入retc中 */  
  237.         QWait();  
  238.         QWait();   
  239.       }  
  240.   SCL=0;      
  241.   QWait();  
  242.   QWait();  
  243.   IO_DIR_PORT_PIN(1, 1, IO_OUT);  
  244.   return(retc);  
  245. }  
  246.   
  247. /****************************************************************************** 
  248.  * 函数名称:Ack_I2c 
  249.  * 
  250.  * 功能描述:主控器进行应答信号,(可以是应答或非应答信号) 
  251.  *            
  252.  * 
  253.  * 参    数:无 
  254.  * 
  255.  * 返 回 值:无 
  256.  *****************************************************************************/   
  257. void Ack_I2c(uint8 a)  
  258. {  
  259.   IO_DIR_PORT_PIN(1, 0, IO_OUT);    //设置P1.0为输出  
  260.   IO_DIR_PORT_PIN(1, 1, IO_OUT);    //设置P1.1为输出  
  261.   if(a==0)SDA=0;                   /*在此发出应答或非应答信号 */  
  262.   else SDA=1;  
  263.   QWait();  
  264.   //QWait();  
  265.   //QWait();        
  266.   SCL=1;  
  267.   QWait();  
  268.   QWait();                         /*时钟低电平周期大于4μs*/  
  269.   QWait();  
  270.   QWait();  
  271.   QWait();    
  272.   SCL=0;                           /*清时钟线,钳住I2C总线以便继续接收*/  
  273.   QWait();  
  274.   //QWait();      
  275. }  
  276.   
  277. /****************************************************************************** 
  278.  * 函数名称:ISendByte 
  279.  * 
  280.  * 功能描述:从启动总线到发送地址,数据,结束总线的全过程,从器件地址sla. 
  281.  *           如果返回1表示操作成功,否则操作有误。 
  282.  *            
  283.  * 
  284.  * 参    数:sla - 从器件地址 
  285.  *           c - 需发送的数据 
  286.  * 
  287.  * 返 回 值:0 -- 失败 
  288.  *           1 -- 成功 
  289.  *****************************************************************************/   
  290. uint8 ISendByte(uint8 sla,uint8 c)  
  291. {  
  292.    Start_I2c();               /*启动总线*/  
  293.    SendByte(sla);             /*发送器件地址*/  
  294.      if(ack==0)return(0);  
  295.    SendByte(c);               /*发送数据*/  
  296.      if(ack==0)return(0);  
  297.   Stop_I2c();                 /*结束总线*/   
  298.   return(1);  
  299. }  
  300.   
  301. /****************************************************************************** 
  302.  * 函数名称:ISendStr 
  303.  * 
  304.  * 功能描述:从启动总线到发送地址,子地址,数据,结束总线的全过程,从器件 
  305.  *            地址sla,子地址suba,发送内容是s指向的内容,发送no个字节。 
  306.  *           如果返回1表示操作成功,否则操作有误。 
  307.  *            
  308.  * 
  309.  * 参    数:sla - 从器件地址 
  310.  *           suba - 从器件子地址 
  311.  *           *s - 数据 
  312.  *           no - 数据字节数目 
  313.  * 
  314.  * 返 回 值:0 -- 失败 
  315.  *           1 -- 成功 
  316.  * 
  317.  * 注    意:使用前必须已结束总线。 
  318.  *****************************************************************************/   
  319. uint8 ISendStr(uint8 sla,uint8 suba,uint8 *s,uint8 no)  
  320. {  
  321.    uint8 i;  
  322.   
  323.    Start_I2c();               /*启动总线*/  
  324.    SendByte(sla);             /*发送器件地址*/  
  325.      if(ack==0)return(0);  
  326.    SendByte(suba);            /*发送器件子地址*/  
  327.      if(ack==0)return(0);  
  328.    for(i=0;i
  329.     {     
  330.      SendByte(*s);            /*发送数据*/  
  331.        if(ack==0)return(0);  
  332.      s++;  
  333.     }   
  334.  Stop_I2c();                  /*结束总线*/   
  335.   return(1);  
  336. }  
  337.   
  338. /****************************************************************************** 
  339.  * 函数名称:IRcvByte 
  340.  * 
  341.  * 功能描述:从启动总线到发送地址,读数据,结束总线的全过程,从器件地 
  342.  *          址sla,返回值在c. 如果返回1表示操作成功,否则操作有误。 
  343.  *            
  344.  * 
  345.  * 参    数:sla - 从器件地址 
  346.  *           *c - 需发送的数据 
  347.  * 
  348.  * 返 回 值:0 -- 失败 
  349.  *           1 -- 成功 
  350.  * 
  351.  *注    意:使用前必须已结束总线。 
  352.  *****************************************************************************/   
  353. uint8 IRcvByte(uint8 sla,uint8 *c)  
  354. {  
  355.    Start_I2c();                /*启动总线*/  
  356.    SendByte(sla+1);            /*发送器件地址*/  
  357.    //SendByte(sla);   
  358.    if(ack==0)return(0);  
  359.    *c=RcvByte();               /*读取数据*/  
  360.    Ack_I2c(1);                 /*发送非就答位*/  
  361.    Stop_I2c();                 /*结束总线*/   
  362.    return(1);  
  363. }  
  364.   
  365. /****************************************************************************** 
  366.  * 函数名称:IRcvStr 
  367.  * 
  368.  * 功能描述:从启动总线到发送地址,子地址,读数据,结束总线的全过程,从器件 
  369.  *          地址sla,子地址suba,读出的内容放入s指向的存储区,读no个字节。 
  370.  *         如果返回1表示操作成功,否则操作有误。 
  371.  *            
  372.  * 
  373.  * 参    数:sla - 从器件地址 
  374.  *           suba - 从器件子地址 
  375.  *           *s - 数据 
  376.  *           no - 数据字节数目 
  377.  * 
  378.  * 返 回 值:0 -- 失败 
  379.  *           1 -- 成功 
  380.  * 
  381.  * 注    意:使用前必须已结束总线。 
  382.  *****************************************************************************/   
  383. uint8 IRcvStr(uint8 sla,uint8 suba,uint8 *s,uint8 no)  
  384. {  
  385.    Start_I2c();               /*启动总线*/  
  386.    SendByte(sla);             /*发送器件地址*/  
  387.    if(ack==0)return(0);  
  388.    SendByte(suba);            /*发送器件子地址*/  
  389.   // if(ack==0)return(0);  
  390.   // SendByte(sla+1);  
  391.    if(ack==0)return(0);  
  392.    while(no > 0)   
  393.    {  
  394.     *s++ = RcvByte();  
  395.      if(no > 1)  Ack_I2c(0);   /*发送就答位*/   
  396.      else Ack_I2c(1);          /*发送非应位*/  
  397.      no--;  
  398.    }  
  399.    Stop_I2c();                 /*结束总线*/   
  400.    return(1);  
  401. }  
  402.   
  403. /****************************************************************************** 
  404.  * 函数名称:ctrPCA9554LED 
  405.  * 
  406.  * 功能描述:通过IIC总线控制PCA9554的输出,进而控制相应的LED。 
  407.  *                     
  408.  * 
  409.  * 参    数:LED - 所控制的LED 
  410.  *           operation - 开或关操作 
  411.  * 
  412.  * 返 回 值:无 
  413.  *            
  414.  * 
  415.  * 注    意:PCA9554的地址为:0x40 
  416.  *****************************************************************************/   
  417. void ctrPCA9554LED(uint8 led,uint8 operation)  
  418. {  
  419.   uint8 output = 0x00;  
  420.   uint8 *data = 0;  
  421.   if(ISendStr(0x40,0x03,&output,1))  //配置PCA9554寄存器  
  422.   {  
  423.     switch(led)  
  424.     {  
  425.       case 0:                        //LED0控制  
  426.         if (operation)  
  427.         {  
  428.           output = PCA9554ledstate & 0xfe;  
  429.         }  
  430.         else  
  431.         {  
  432.           output = PCA9554ledstate | 0x01;  
  433.         }  
  434.       break;  
  435.        case 1:                      //LED1控制  
  436.         if (operation)  
  437.         {  
  438.           output = PCA9554ledstate & 0xfd;  
  439.         }  
  440.         else  
  441.         {  
  442.           output = PCA9554ledstate | 0x02;  
  443.         }  
  444.       break;  
  445.        case 2:                     //LED2控制  
  446.         if (operation)  
  447.         {  
  448.           output = PCA9554ledstate & 0xf7;  
  449.         }  
  450.         else  
  451.         {  
  452.           output = PCA9554ledstate | 0x08;  
  453.         }  
  454.       break;  
  455.        case 3:                     //LED3控制  
  456.         if (operation)  
  457.         {  
  458.           output = PCA9554ledstate & 0xfb;  
  459.         }  
  460.         else  
  461.         {  
  462.           output = PCA9554ledstate | 0x04;  
  463.         }  
  464.       break;  
  465.        case 4:                    //LED4控制  
  466.         if (operation)  
  467.         {  
  468.           output = PCA9554ledstate & 0xdf;  
  469.         }  
  470.         else  
  471.         {  
  472.           output = PCA9554ledstate | 0x20;  
  473.         }  
  474.       break;  
  475.        case 5:                   //LED5控制  
  476.         if (operation)  
  477.         {  
  478.           output = PCA9554ledstate & 0xef;  
  479.         }  
  480.         else  
  481.         {  
  482.           output = PCA9554ledstate | 0x10;  
  483.         }  
  484.       break;  
  485.   
  486.      default:break;  
  487.     }  
  488.     if(ISendStr(0x40,0x01,&output,1)) //写PCA9554输出寄存器  
  489.     {  
  490.       if(IRcvByte(0x40,data))         //读PCA9554输出寄存器  
  491.       {  
  492.         PCA9554ledstate = *data;  
  493.       }  
  494.     }  
  495.   }  
  496. }  
  497.   
  498. /****************************************************************************** 
  499.  * 函数名称:PCA9554ledInit 
  500.  * 
  501.  * 功能描述:初始化6个LED,即关闭所有的LED 
  502.  *                     
  503.  * 参    数:无           
  504.  * 
  505.  * 返 回 值:无 
  506.  *            
  507.  *****************************************************************************/   
  508. void PCA9554ledInit()  
  509. {  
  510.   uint8 output = 0x00;  
  511.   uint8 *data = 0;  
  512.   if(ISendStr(0x40,0x03,&output,1))  //配置PCA9554寄存器  
  513.   {  
  514.     output = 0xbf;  
  515.     if(ISendStr(0x40,0x01,&output,1)) //写输出寄存器  
  516.     {  
  517.       if(IRcvByte(0x40,data))         //读输出寄存器  
  518.       {  
  519.         PCA9554ledstate = *data;  
  520.       }  
  521.     }  
  522.   }  
  523. }  

你可能感兴趣的:(zigbee)