热度 1
LM3S9B96学习(二)-----blinky
打开blinky文件夹下的工程,整个工程包含两个最要的代码文件 : blinky.c 和 startup_rvmdk.S , startup_rvmdk.S 是系统的启动文件 ,里面使用汇编语言编写,因为这个例程不需要修改堆栈和使用到中断,所以在这里我们先不讨论 。 下面我们来看看 blinky.c , 整个 blinky.c 的内容如下所示 :
#include "inc/lm3s9b96.h"
int main(void)
{
volatile unsigned long ulLoop;
SYSCTL_RCGC2_R = SYSCTL_RCGC2_GPIOF;
ulLoop = SYSCTL_RCGC2_R;
GPIO_PORTF_DIR_R = 0x08;
GPIO_PORTF_DEN_R = 0x08;
while(1)
{
GPIO_PORTF_DATA_R |= 0x08;
for(ulLoop = 0; ulLoop < 200000; ulLoop++)
{
}
GPIO_PORTF_DATA_R &= ~(0x08);
for(ulLoop = 0; ulLoop < 200000; ulLoop++)
{
}
}
}
#include "inc/lm3s9b96.h" : 这句话的作用是包含一个头文件,该头文件的名称为 lm3s9b96.h,大概浏览一下这个文件全部是宏定义。该头文件所完成的功能是对 lm3s9b96芯片寄存器的定义,我们打开lm3s9b96.h这个文件
#ifndef __LM3S9B96_H__
#define __LM3S9B96_H__ // 这两句是告诉我们如果 __LM3S8962_H__ 没有被定义则定义 __LM3S8962_H__ , 它们作用是防止该头文件 lm3s8962.h 被重复定义,这样类是的宏定义在很多的.h文件中被使用。
接下来看看第一句代码:
#define WATCHDOG_LOAD_R (*((volatile unsigned long *)0x40000000))
这样的一个宏定义语句 ,首先( volatile unsigned long * )的意思是将后面的那个地址强制转换成 volatile unsigned long * , unsigned long * 是无符号长整形, volatile 是一个类型限定符(具体的可以去网上搜一下volatile的特点) , 当使用 volatile 时 , 表示这个变量的值每次都会改变,系统在使用它的时候每次都要从寄存器中读取。
(参考了网友的归纳)
1. volatile 变量可变 允许除了程序之外的比如硬件来修改他的内容
2. 访问该数据任何时候都会直接访问该地址处内容,即通过 cache 提高访
问速度的优化被取消 对于 (volatile unsigned long *)0x40000000 我们再分析一下,它是由两部分组成:
1 ) ( unsigned long *)0x40000000 , 0x40000000 只是个值,前面加( unsigned long *) 表示 0x40000000 是个地址,而且这个地址类型是 unsigned long ,意思是说读写这个地址时 , 要写进 unsigned long 类型的值 , 同样从该地址读出的也是 unsigned long 类型 。
2 ) volatile ,关键字 volatile 确保本条指令不会因 C 编译器的优化而被省略 , 且要求每次直接读值 。 例如用 while((unsigned long *)0x40000000) 时 , 有时系统可能不真正去读 0x40000000 的值 , 而是用第一次读出的值 , 如果这样 , 那这个循环可能是个死循环。用了 volatile 则要求每次都去读 0x40000000 的实际值。
总结一下,利用 (unsigned long *)0x40000000 我们可以定义一个指针,该指针的指向的地址为 0x40000000 , 这就相当于我们定义了一个指针 P , P 的值为 0x40000000 ,回想以前的内容,当我们需要取 P 中的内容的时候,我们会用 *P 来表示 ; 同样 , 如果我们在 (unsigned long *)0x40000000 前面加上 * 的话 , 就表示取这个地址中的内容了。
至于为什么0x40000000接下来会讲述。理解了这个宏定义,其他的宏都是相似的,下面我们来看看主程序,主程序从 main 开始:
首先程序定义了 volatile unsigned long ulLoop; 这里的 volatile 和前面是一个意思,表示的是每次用到该变量的时候都要重新读取。
第二句: SYSCTL_RCGC2_R = SYSCTL_RCGC2_GPIOF; 这句话是对 SYSCTL_RCGC2_R 寄存器进行赋值 SYSCTL_RCGC2_GPIOF , 在前面说道的头文件中我们可以找到它们的定义,
#define SYSCTL_RCGC2_R (*((volatile unsigned long *)0x400FE108))
#define SYSCTL_RCGC2_GPIOF 0x00000020 // Port F Clock Gating Control.
通过这些定义我们可以知道 : 把值 0x00000020 赋给 地址为 0x400FE108 的寄存器。那么现在就有了两个问题:
而为什么要将0x00000020 赋给 地址为 0x400FE108 的寄存器呢!现在就需要我们查看 LM3S9B96 的 DATASHEET (我参考的是北京锐鑫翻译的中文DATASHEET,如果E文比较好的话建议参考官方的DATASHEET)了,参看 294页, 0x400FE108 寄存器为运行模式时钟门控控制寄存器2(RCGC2)。第一次学习的同学要注意了,简单的说一下如何的看数据手册0x400FE108在DATASHEET并不是直接给出的,而是 基址+偏移量的。至于为什么会这样建议温习一下《微机原理》这门课程。有些人说在DATASHEET中找一个0x400FE108那不是大海捞针。如果是研究别人的代码时要找那一个寄存器的话,那就要利用好PDF阅读器左边的文档目录浏览功能了
找到寄存器的映射,就可以相应的找到基址和偏移地址了,
而且良好的代码风格很容易让我们找到对应的信息(如SYSCTL_RCGC2_R SYSCTL表示时钟 RCGC2_R表示RCGC2是个寄存器,且名为RCGC2 )。所以现在大家就知道一开始 WATCHDOG_LOAD_R 与0x40000000的关系了吧!
继续刚才所讲,RCGC2寄存器主要是用来控制运行模式时钟门控逻辑,换句话就是控制打开 9b96 端口的。那么为什么要把值 0x00000020 的值赋给它呢?我们把值 0x00000020 化为二进制 , 就可以得到值 0000 0000 0000 0000 0000 0000 0010 0000 ,可以看到除第五位为 1 外 , 其余都为 0 ( 从第 0 位开始计数 ) 。 再看DATASHEET 对RCGC2寄存器的描述 , 我们看到第五位是GPIOF位 , 当设置为 1 时表示打开 F 口的时钟 。 那么为什么要打开 F 口的时钟呢?这个问题可以参考板子的原理图 , 在原理图中可以看到板子上 的 LE D 是连接 到 GPIO F3口的 , 所以如果要使 LE D 可用的话必须使能F口 。
回来源代码: ulLoop = SYSCTL_RCGC2_R; 很明显,这句的作用是将 SYSCTL_RCGC2_R 的值赋到 ulLoop 变量中 ,这句话的主要是起一个延时的作用 。 因为上一条语句是使能端口的时钟,所以必须要有一个延时等待时钟稳定。 PS: 在这里放置一个 其它的延时代码的话也可以达到同样的效果(比如自定义一个delay函数)。接下两句代码:
GPIO_PORTF_DIR_R = 0x08;
GPIO_PORTF_DEN_R = 0x08;
这两句也是对某些寄存器赋值,同理,这两个寄存器及其功能定义也可以 在DATASHEET 上找到,参见420页,可以看到这两个寄存器主用是用来设置对应的 GPIO 是输入还是输出。最后 , 程序进入了一个死循环, 这个死循环非常重要 , 它保证了整个程序一直在运行而不退出 。 循环中有两条语句 , 一条是或的形式 , 一条是与的形式 , 它们对应的功能分别如下:
GPIO_PORTF_DATA_R |= 0x08; // 把值 0X08 与 GPIO_PORTF_DATA_R 原有的值进行 “ 或 ” 运算,然后把结果写回到 GPIO_PORTF_DATA_R 寄存器中,可以看到该语句的功能就是把 GPIO F3 引脚置为 1.
GPIO_PORTF_DATA_R &= ~(0x08); // 把值 0X01 按位进行 “ 非 ” 运算,然后再与 GPIO_PORTF_DATA_R 原有的值进行 “ 与 ” 运算 , 最后把结果写回到 GPIO_PORTF_DATA_R 寄存器中 , 可以看到该语句的功能就是把 GPIO F3 引脚置为 0 。而for(ulLoop = 0; ulLoop < 200000; ulLoop++);想必大家一看就知道,也是一个循环,为了让等暗灭交叉点亮,我们的肉眼能够看的见正常的现象。到此,整个程序剖析完成,在 while中, 9B96不停的调用 GPIO_PORTF_
DATA_R |= 0x08; 与 GPIO_PORTF_DATA_R &= ~(0x08); 语句,使 GPIO F3 引脚输出高电平与低电平, LED 就变亮或熄灭。
哎……搞了半天图片还是插不进来!:Cry: 所以把我的笔记WORD版传了上来,里面配有截图,需要的可以下载。额外的问一下,word里面的图片怎么才可以贴出来?