|
SysCtlClockSet函数负责设置LM3S处理器的时钟,大家都知道,现在的ARM芯片越来越强大,时钟系统也越来越复杂。到底它的时钟设置会有多复杂呢,还是深入SysCtlClockSet函数来一探究竟吧。
SysCtlClockSet函数只有一个参数,所有的时钟设置都在这一个数据中表达,但是时钟控制寄存器却有两个,也就是说要通过这一个长整型变量来设置两个寄存器的值。这时候我们不禁要问一句,LM3S所有的芯片都有两个时钟寄存器吗?还有那些低端的101、102也是这样的吗?那TI也真够下本钱的。
事实上财大气粗的TI也还是比较节俭的,并非所有的芯片都有两个时钟寄存器。这就牵扯出了一个问题,LM3S的库函数是给整个系列通用的,如果我们在没有RCC2寄存器的芯片上设置RCC2会有什么反应呢?这个函数的第一句就给了我们答案。
if(DEVICE_IS_SANDSTORM && (ulConfig & SYSCTL_RCC2_USERCC2))
{
return;
}
DEVICE_IS_SANDSTORM是一个宏,定义在库文件Hw_sysctl.h中。它的作用就是检测当前芯片有没有RCC2寄存器,从这个宏中我推测LM3S芯片至少分为两个版本,Ver0版和Ver1版,其中Ver1版又分为两个系列,Sandstorm系列和Fury系列。其中只有Ver1版的Fury系列有寄存器RCC2。这个宏会根据DID0寄存器来判断当前芯片是否是Fury系列芯片,如果不是则返回true。
ulConfig & SYSCTL_RCC2_USERCC2则是判断当前设置中有没有设计RCC2寄存器。ulConfig是函数SysCtlClockSet的参数,其最高位决定是否使用RCC2,1表示使用RCC2、0表示不使用RCC2。SYSCTL_RCC2_USERCC2的值为0x80000000,最高位是1两者按位与之后,如果ulConfig最高位为1,则结果不为0,与DEVICE_IS_SANDSTORM逻辑与的结果为真,就会执行return语句结束函数。这样,当我们在没有RCC2寄存器的器件上设置RCC2 的时候,函数不会执行任何操作,避免出现错误。
由于时钟对于处理器的重要性,使得时钟设置不能像其它寄存器那样直接将新的值写入即可,这中间涉及到时钟切换等操作,要不我们一下子把所有时钟都给关闭了,处理器也就不能执行后面的语句了,新的时钟也就出不来了。因此在设置时钟之前有必要先了解一下当前时钟状况。如下:
ulRCC = HWREG(SYSCTL_RCC);
ulRCC2 = HWREG(SYSCTL_RCC2);
读出当前系统中RCC和RCC2寄存器的值在变量ulRCC和ulRCC2中暂存,前面我们说过了,有些器件吗没有RCC2寄存器,对于没有RCC2寄存器的器件,在RCC2位置读出的数据将为0,而写该操作将被忽略,不会对系统产生影响,因为其RCC2所在地址(偏移量0x70)没有被使用。
锁相环输出依赖其它时钟源,因此,不论怎样更改时钟设置,锁相环都会有一段时间停止工作,为了抱着处理器的时钟连续,在配置时钟之前需要先把系统时钟切换到不依赖锁相环的时钟源,外部时钟或者内部时钟。
ulRCC |= SYSCTL_RCC_BYPASS;
ulRCC &= ~(SYSCTL_RCC_USESYSDIV);
ulRCC2 |= SYSCTL_RCC2_BYPASS2;
HWREG(SYSCTL_RCC) = ulRCC;
HWREG(SYSCTL_RCC2) = ulRCC2;
LM3S有众多的时钟源,要知道,每一个时钟源都要耗电,为了实现低功耗,允许用户将不用的时钟源关闭,但这又会带来一个问题,如果我们把时钟切换到了一个时钟源,但是我一不小心刚刚把这个时钟源给关掉了,这该怎么办呢?所以,在切换时钟之前先要看看我们使用的时钟源是否正在运行,方法是检测ulRCC和ulConfig中相应位是否一致。这里的SYSCTL_RCC_IOSCDIS和SYSCTL_RCC_MOSCDIS起到的其实是一个屏蔽字的作用,屏蔽掉其他的位,留下相应的位来运算。检测及设置时钟源代码如下:
if(((ulRCC & SYSCTL_RCC_IOSCDIS) && !(ulConfig & SYSCTL_RCC_IOSCDIS)) ||
((ulRCC & SYSCTL_RCC_MOSCDIS) && !(ulConfig & SYSCTL_RCC_MOSCDIS)))
{
ulRCC &= (~(SYSCTL_RCC_IOSCDIS | SYSCTL_RCC_MOSCDIS) |
(ulConfig & (SYSCTL_RCC_IOSCDIS | SYSCTL_RCC_MOSCDIS)));
//如果新的时钟配置字中关于时钟源使能的部分与原来的不一致,则先将原来的清零,然后再将新的写入
HWREG(SYSCTL_RCC) = ulRCC;
if((ulRCC2 & SYSCTL_RCC2_USERCC2) &&
(((ulRCC2 & SYSCTL_RCC2_OSCSRC2_M) == SYSCTL_RCC2_OSCSRC2_30) ||
((ulRCC2 & SYSCTL_RCC2_OSCSRC2_M) == SYSCTL_RCC2_OSCSRC2_32)))
{
//如果使用了RCC2并且系统采用的是低速时钟,则等待4096个时钟周期,反之则等待524288个时钟周期。
SysCtlDelay(4096);
}
else
{
SysCtlDelay(524288);
}
}
好了,解决了时钟连续的问题了,下面该大刀阔斧的干了,将所设置的时钟标志字写入RCC,如下:
ulRCC &= ~(SYSCTL_RCC_XTAL_M | SYSCTL_RCC_OSCSRC_M |
SYSCTL_RCC_PWRDN | SYSCTL_RCC_OEN);
清除原来寄存器里的相关位,包括用于指定晶振频率的9:6位,用于指定振荡源的5:4位,用于控制锁相环供电的PWRDN位与锁相环使能的OEN位,不管原来是什么值,统统清除设置新的值。这里需要提及一下的就是这个OEN位,在Datasheet上没有提及这一位,其所在的位置(12位)被标为保留位,应该是Datasheet的一处错误!
设置新的值:
ulRCC |= ulConfig & (SYSCTL_RCC_XTAL_M | SYSCTL_RCC_OSCSRC_M |
SYSCTL_RCC_PWRDN | SYSCTL_RCC_OEN);
跟前面一样,这里的SYSCTL_RCC_XTAL_M等是作为屏蔽字的。
对RCC2如法炮制:
ulRCC2 &= ~(SYSCTL_RCC2_USERCC2 | SYSCTL_RCC2_OSCSRC2_M | SYSCTL_RCC2_PWRDN2);
ulRCC2 |= ulConfig & (SYSCTL_RCC2_USERCC2 | SYSCTL_RCC_OSCSRC_M |
SYSCTL_RCC2_PWRDN2);
后面还有一个赋值语句:
ulRCC2 |= (ulConfig & 0x00000008) << 3;
这个是干什么的呢?
这是用来设置使用低频振荡器的。在RCC2中,设置振荡源的位比RCC中多出一位,同时RCC中有一个保留字11,用此保留字再加上前面多出的一位就可以用来指定两个振荡源了,分别是内部30KHz和外部32KHz。由于与RCC相同的两位已经在前面设置了,这里只需要设置最高位就可以了。
在ulConfig取值中,代表30KHz的是0x80000030,代表32KHz的是0x80000038。与屏蔽字0x00000008运算后左移三位,就可以将决定位移动到所需要的位置第“6”位(从0开始)。
那么从刚才这个运算来看,起作用的其实只有第四位一位,我们把30KHz和32KHz代表的值取0x80000000和0x80000008就可以了,前面的那个3根本没参加运算,如果设置成0x00000040的话连移位都不需要了,事实是这样的吗?我们前面说了,RCC2中与RCC相同位置的位已经在前面设置了,同时,要想使用低频晶振,RCC2中的5:4位必须设置成11,但是11在RCC是保留字,所以,不可以通过可以与RCC共享的值来设置,必须单独指定。我们再来看一下这两个值的二进制代码(最后8位):
30KHz:00110000
32KHz:00111000
如果把32KHz中位置3处的1移动到位置6,大家会发现什么,这不就是30KHz和32KHz在RCC2中编码的原码吗?
所以设置是这样的:5:4中的两位因为作用一样,只是在RCC中没意义,但是RCC2起作用的时候RCC中相应位不起作用,所以,完全可以由这两位来覆盖,但是第6位就不一样了,在RCC寄存器中有别的用处,RCC2中的值不能覆盖,所以将这一位挪到了另外两位的后面,形成了这样一种编码,再赋值的时候先赋第5:4位(可以同时赋给RCC和RCC2,然后屏蔽掉这两位,再左移三位,将最后一位移动到需要的位置,再单独赋给RCC2。
接下来清除锁相环锁定中断,因为待会等待锁相环锁定时还需要判断这一中断。
HWREG(SYSCTL_MISC) = SYSCTL_INT_PLL_LOCK;
把变量中暂存的值写入实际寄存器:
if(ulRCC2 & SYSCTL_RCC2_USERCC2)
{
HWREG(SYSCTL_RCC2) = ulRCC2;
HWREG(SYSCTL_RCC) = ulRCC;
}
else
{
HWREG(SYSCTL_RCC) = ulRCC;
HWREG(SYSCTL_RCC2) = ulRCC2;
}
SysCtlDelay(16);
这里有个顺序,如果使用了RCC2,则需要先写RCC2再写RCC,否则先写RCC再写RCC2,我想估计是RCC2中的USERRCC2位需要抢先控制系统吧,目前还不很确定为什么这么做。
写入寄存器之后需要等待一段时间等待新的设定生效。
最后是有关锁相环分频系数和振荡源使能及其它一些的设置,由于前面新的时钟源已经运行起来了,所以这里不需要再注意顺序了,全部设置完成后一次性写入。
下面是关于锁相环分频系数及振荡源使能的设置,目前操作是在临时变量中进行的。
ulRCC &= ~(SYSCTL_RCC_SYSDIV_M | SYSCTL_RCC_USESYSDIV |
SYSCTL_RCC_IOSCDIS | SYSCTL_RCC_MOSCDIS);
ulRCC |= ulConfig & (SYSCTL_RCC_SYSDIV_M | SYSCTL_RCC_USESYSDIV |
SYSCTL_RCC_IOSCDIS | SYSCTL_RCC_MOSCDIS);
ulRCC2 &= ~(SYSCTL_RCC2_SYSDIV2_M);
ulRCC2 |= ulConfig & SYSCTL_RCC2_SYSDIV2_M;
如果使用了锁相环,则需要等待锁相环锁定后才能进行后面的操作:
if(!(ulConfig & SYSCTL_RCC_BYPASS))
{ for(ulDelay = 32768; ulDelay > 0; ulDelay--)
{
if(HWREG(SYSCTL_RIS) & SYSCTL_INT_PLL_LOCK)
{
break;//等待锁相环锁定
}
}
// 如果使用了锁相环,则需要将锁相环旁路位清零
ulRCC &= ~(SYSCTL_RCC_BYPASS);
ulRCC2 &= ~(SYSCTL_RCC2_BYPASS2);
}
HWREG(SYSCTL_RCC) = ulRCC;
HWREG(SYSCTL_RCC2) = ulRCC2;
SysCtlDelay(16);
最后将暂存变量中的值写入寄存器,短暂延时等待新设置生效,时钟设置就完成了!
注:程序中用到的宏DEVICE_IS_SANDSTORM及HWREG(x)在hw_types.h中定义。函数SysCtlDelay(x)就在文件sysctl.c中定义。
|
|