1、 引言
在LM3S1138的使用过程中,如果要使用外设,如本文所述的GPIO_A端口时,就得先使能此外设在RCGCx寄存器中的对应位。至于为什么使用外设时要打开其相应的RCGCx寄存器中的对应位,此处先不讲,我也先不懂。
LM3S系列芯片因为自带了丰富的驱动库程序,所以编程变得方便了很多。但对于我一个入门级选手来说,我得先懂得其驱动库程序的组织结构,尔后才能把Luminary的驱动库为我所用。我有一个简单的愿望就是,我使用Luminary的驱动库的水平,能达到这个库仿佛是我写的一样。本文正是在此愿望水平还很强烈时草草拟出的。
本文内容,很单一,只是说明一个我在使用LM3S1138芯片时,为了把PA1引脚设置为通用的IO引脚,且能对其进行软件上的置位与复位所作的前期准备工作中的一部分。这一部分工作的核心就是把RCGC2寄存器中的GPIOA位置1,这个核心也就是本文的全部内容了。
2、 正文
我们先给出LM3S1138中的RCGC2寄存器结构,如图1所示。
![](image/zoom.gif)
![点击图片,在新窗口显示原始尺寸](https://i-blog.csdnimg.cn/blog_migrate/0c7d931adeddca2b4972b256b863be4f.jpeg)
图1 RCGC2寄存器的结构图
看到这个图之后,我们知道自己所做的工作即是把RCGC2中的0位GPIOA位置1。事实是,我们不管用什么程序结构,都是为了达到这个目的。最直接的,最熟练的方式是采用C语言的赋值语句:
RCGC2 |= 0x00000001;
接下来,我们顺着Luminary的驱动库程序的流程,来看一下,上述目的是怎么个实现过程。首现我们先将Luminary驱动库程序将RCGC2中的GPIOA位置1的程序流程图罗列出来,如图2所示。
图2 RCGC2中GPIOA位置1的程序结构图
图2所示的程序流程图中的函数原型:
extern void SysCtlPeripheralEnable(unsigned long ulPeripheral);
在Sysctl.h中声明,在Sysctl.c中定义,其作用是置位对应外设在RCGC2中的控制位,使能此外设。
程序流程图是简单的,程序的执行过程是复杂的,当然复杂是因为我的初学,不懂的太多。接下来,我们要探讨的是SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA)这个函数的执行细节。为了够细节,我们直接将Luminary的源码罗列在下面,以达到给篇幅注水的目的。
//*****************************************************************************
//
//! Enables a peripheral.
//!
//! /param ulPeripheral is the peripheral to enable.
//!
//! Peripherals are enabled with this function. At power-up, all peripherals
//! are disabled; they must be enabled in order to operate or respond to
//! register reads/writes.
//!
//! The /e ulPeripheral parameter must be only one of the following values:
//! /b SYSCTL_PERIPH_ADC, /b SYSCTL_PERIPH_CAN0, /b SYSCTL_PERIPH_CAN1,
//! /b SYSCTL_PERIPH_CAN2, /b SYSCTL_PERIPH_COMP0, /b SYSCTL_PERIPH_COMP1,
//! /b SYSCTL_PERIPH_COMP2, /b SYSCTL_PERIPH_ETH, /b SYSCTL_PERIPH_GPIOA,
//! /b SYSCTL_PERIPH_GPIOB, /b SYSCTL_PERIPH_GPIOC, /b SYSCTL_PERIPH_GPIOD,
//! /b SYSCTL_PERIPH_GPIOE, /b SYSCTL_PERIPH_GPIOF, /b SYSCTL_PERIPH_GPIOG,
//! /bSYSCTL_PERIPH_GPIOH, /bSYSCTL_PERIPH_HIBERNATE, /b SYSCTL_PERIPH_I2C0,
//! /b SYSCTL_PERIPH_I2C1, /b SYSCTL_PERIPH_PWM, /b SYSCTL_PERIPH_QEI0,
//! /b SYSCTL_PERIPH_QEI1, /b SYSCTL_PERIPH_SSI0, /b SYSCTL_PERIPH_SSI1,
//! /b SYSCTL_PERIPH_TIMER0, /b SYSCTL_PERIPH_TIMER1, /b SYSCTL_PERIPH_TIMER2,
//! /b SYSCTL_PERIPH_TIMER3, /b SYSCTL_PERIPH_UART0, /b SYSCTL_PERIPH_UART1,
//! /b SYSCTL_PERIPH_UART2, /b SYSCTL_PERIPH_UDMA, /b SYSCTL_PERIPH_USB0, or
//! /b SYSCTL_PERIPH_WDOG.
//!
//! /note It takes five clock cycles after the write to enable a peripheral
//! before the the peripheral is actually enabled. During this time, attempts
//! to access the peripheral will result in a bus fault. Care should be taken
//! to ensure that the peripheral is not accessed during this brief time
//! period.
//!
//! /return None.
//
//*****************************************************************************
void
SysCtlPeripheralEnable(unsigned long ulPeripheral)
{
//
// Check the arguments.
//
ASSERT(SysCtlPeripheralValid(ulPeripheral));
//
// Enable this peripheral.
//
HWREG(g_pulRCGCRegs[SYSCTL_PERIPH_INDEX(ulPeripheral)]) |=
SYSCTL_PERIPH_MASK(ulPeripheral);
}
这个函数基本上就做了两件事情,一件是采用断言:
ASSERT(SysCtlPeripheralValid(ulPeripheral));
检查形参的合法性,若形参不合法,ASSERT(条件)里面的逻辑值为假。程序在编译阶段是要报错的。断言的使用,目前不是很熟悉,不多讲了。
断言对形参进行判断之后,参数合法,接着,就指着这个参数来进行一系列的寄存器操作了。其操作语句为:
//
// Enable this peripheral.
//
HWREG(g_pulRCGCRegs[SYSCTL_PERIPH_INDEX(ulPeripheral)]) |=
SYSCTL_PERIPH_MASK(ulPeripheral);
关于这条语句的注释是,这条语句用专业的驱动库把一条简单的
RCGC2 |= 0x00000001;
赋值语句进行了一点点小小的复杂化。下面,我们就把这个语句,给拆明白了,如果我能把这条语给讲明白了,那真得觉得算是我的一点点小小的造化。首先,我们就
HWREG(g_pulRCGCRegs[SYSCTL_PERIPH_INDEX(ulPeripheral)]) |=
SYSCTL_PERIPH_MASK(ulPeripheral);
这条赋值语句的左边是如何解析出RCCG2来进行说明,然后,我们就这条赋值语句的右边是如何解析出0x00000001来再进行说明。这条赋值语句的左边是:
HWREG(g_pulRCGCRegs[SYSCTL_PERIPH_INDEX(ulPeripheral)])
HWREG它是一个带参数的宏,它的参数是一个数组名为g_pulRCGCRegs的元素,这个元素在数组中的序号是SYSCTL_PERIPH_INDEX(ulPeripheral),我查过,SYSCTL_SYSCTL_PERIPH_INDEX在Sysctl.c中有定义,是一个带参数的宏。完整的定义是:
//*****************************************************************************
//
// This macro extracts the array index out of the peripheral number.
//
//*****************************************************************************
#define SYSCTL_PERIPH_INDEX(a) (((a) >> 28) & 0xf)
HWREG(g_pulRCGCRegs[SYSCTL_PERIPH_INDEX(ulPeripheral)]),
其中ulPeripheral 这个形参所对应的实参是:SYSCTL_PERIPH_GPIOA。对上述左边的表达式,比较看好的执行结果是:
RCGC2
我们顺着HWREG(g_pulRCGCRegs[SYSCTL_PERIPH_INDEX(ulPeripheral)])的执行过程进行解析。
1) 给出ulPeripheral所对应的实参为:
SYSCTL_PERIPH_GPIOA,
这个实参是个代表32位二进制数的宏,在Sysctl.h定义,
#define SYSCTL_PERIPH_GPIOA 0x20000001 // GPIO A
2) 执行SYSCTL_PERIPH_INDEX(ulPeripheral)
也就是执行
(((a) >> 28) & 0xf)
代入实参SYSCTL_PERIPH_GPIOA(0x20000001)之后的情况是:
(((SYSCTL_PERIPH_GPIOA) >> 28) & 0xf)
(((0x20000001) >> 28) & 0xf)
这个值我们心算一下,可以得出,等于十进制数2。
3) 执行g_pulRCGCRegs[SYSCTL_PERIPH_INDEX(ulPeripheral)],也就是执行
g_pulRCGCRegs[2]
g_pulRCGCRegs[]是一个数组,在Sysctl.c中定义,其具体的定义形式为:
//*****************************************************************************
//
// An array that maps the "peripheral set" number (which is stored in the upper
// nibble of the SYSCTL_PERIPH_* defines) to the SYSCTL_RCGC? register that
// controls the run-mode enable for that peripheral.
//
//*****************************************************************************
static const unsigned long g_pulRCGCRegs[] =
{
SYSCTL_RCGC0,
SYSCTL_RCGC1,
SYSCTL_RCGC2
};
可以看到g_pulRCGCRegs[2]对应的元素即是:
SYSCTL_RCGC2
这也是个宏,在Hw_sysctl.h中定义,其具体的定义形式为:
#define SYSCTL_SCGC2 0x400FE118 // Sleep-mode clock gating reg 2
这对应的数值0x400FE118,即对应着RCGC2对应的地址,如图1所示的寄存器结构的左上脚的说明部分,如图3所示。
图3 RCGC2的地址说明
4) 执行HWREG(0x400FE118),这一句语翻译成标准的C语言之后,应该是:
*((volatile unsigned long *)0x400FE108),
HWREG()这个宏在Hw_types.h文件中有定义,具本定义为:
#define HWREG(x) (*((volatile unsigned long *)(x)))
在TI网站上,你可以下载一个spmu019c.pdf,在第7页与第9页,也会告诉你,在Luminary完整的驱动库文件(名为:SW-LM3S-5961.exe,这个是今年5月份的时候的叫法)中,你解压完之后的/inc目录下,你可以找到lm3s1138.h这个头文件,这头文件是为直接寄存器访问的编程而制做的头文件,在这个头文件中,你也可以找到如下的宏定义。
#define SYSCTL_RCGC2_R (*((volatile unsigned long *)0x400FE108))
表达式*((volatile unsigned long *)0x400FE108)的作用是:
先用(volatile unsigned long *)0x400FE108强制转换,将0x400FE108变成一个地址,然后再用*((volatile unsigned long *)0x400FE108)将这个地址,变成实实在在的一个没有名称的变量,你可以往里赋值了。
写到这里,请大家清醒的意识到,我们只是干完了
HWREG(g_pulRCGCRegs[SYSCTL_PERIPH_INDEX(ulPeripheral)]) |=
SYSCTL_PERIPH_MASK(ulPeripheral);
这条语句的左半部分,这条赋值语句的右边是:
SYSCTL_PERIPH_MASK(ulPeripheral)
它是一个带参数的宏,它的参数是ulPeripheral,对应的实参是SYSCTL_PERIPH_GPIOA,
这个带参数的宏SYSCTL_PERIPH_MASK()在Sysctl.c中有定义,完整的定义是:
//*****************************************************************************
//
// This macro constructs the peripheral bit mask from the peripheral number.
//
//*****************************************************************************
#define SYSCTL_PERIPH_MASK(a) (((a) & 0xffff) << (((a) & 0x001f0000) >> 16))
实参ulPeripheral,前面已经讲过是个代表32位二进制数的宏,在Sysctl.h定义,
#define SYSCTL_PERIPH_GPIOA 0x20000001 // GPIO A
SYSCTL_PERIPH_MASK(ulPeripheral)
中的ulPeripheral 这个形参所对应的实参是:SYSCTL_PERIPH_GPIOA,这条语句比较看好的执行结果是:
0x00000001
我们顺着SYSCTL_PERIPH_MASK(ulPeripheral)的执行过程进行解析。
1) 给出ulPeripheral所对应的实参为:
SYSCTL_PERIPH_GPIOA,
这个实参是个代表32位二进制数的宏,在Sysctl.h定义,
#define SYSCTL_PERIPH_GPIOA 0x20000001 // GPIO A
2) SYSCTL_PERIPH_MASK (ulPeripheral),也就是执行
(((a) & 0xffff) << (((a) & 0x001f0000) >> 16))
代入实参SYSCTL_PERIPH_GPIOA(0x20000001)之后的情况是:
(((SYSCTL_PERIPH_GPIOA) & 0xffff) << (((SYSCTL_PERIPH_GPIOA) & 0x001f0000) >> 16))
(((0x20000001) & 0xffff) << (((0x20000001) & 0x001f0000) >> 16))
0x00000001 << (0>> 16)
0x00000001 << 0
0x00000001
好了,你可以把这个1赋给RCGC2了,结合式子的左右部分得出的完整的语句是:
*((volatile unsigned long *)0x400FE108) |= 0x00000001;
图1所示的寄存器RCGC2的0位GPIOA,被成功的置1了。
3、 总结
夏天很热,上述文字写得也不冷静,许多暖昧不清的地方可能还没被我意识到,许多应该加以说明的地方,我可能草草了事。像每个引用的文件,其作用,没有被说明;像优秀的变量命名方式没有被表扬;究其原因,我觉得是我入门的太浅,不能对文中所述的内容,做以全局的把握和说明。