在学这章之前,笔者默认读者已经熟练掌握了51 单片机,如果读者没有掌握,麻烦您先去学习一下51的知识点,再来学习这部分知识,推荐一本51的书籍《STC15单片机实战指南》。 无论是51,还是 ARM处理器,实质都是操作内部的寄存器,给寄存器赋不同的值,处理器运行的结果就不同。SWM320这样的ARM核单片机同理,学习使用的过程,实质就是给内部寄存器赋值的过程,只是这些寄存器分布在不同的地址上,继而给这些寄存器赋值的过程就变成了给地址赋值,具体的地址和寄存器的对应关系,我们得参考官方的数据手册。接下来我们就以点亮一个小灯为例,看两者的赋值有何异同? 4.151单片机的点灯概述为了给我们学习和讲述SWM320做铺垫,先来看一个51的点灯例程,新建工程并命名为“我的第一个程序”,程序源码如下: 1. #include 2. ***it Led1 = P2^0; 3. void main(void) 4. { 5. Led1= 0; 6. while(1); 7. } 接着在Keil5界面右键点击“reg52.h”,打开之后能看到一句“sfr P2 = 0xA0;”这样代码,学过51的读者知道,“sfr”、“***it”都是51的关键词,前者是将地址0xA0指定为一个特殊功能寄存器,并命名为P2,后者是给P2的第0位取一个名称Led1,等价于给P2特殊功能寄存器最低位取一个名称Led1。这样上面程序第5行就是给特殊功能寄存器P2最低位赋值0,从而表现出P2.0端口就为低电平,这样就可以点亮LED了。由此可见,点亮灯的过程还是给寄存器赋值,只是51单片机中有特殊功能寄存器这一概念。 如果我们将上面的程序写修改如下,请读者思考能否点亮LED?如果不能点亮为什么?第三句是什么意思?vola tile什么意思什么用?指针的本质是什么? 1. void main(void) 2. { 3. *(volatileunsigned char *)0xA0 = 0xFE; 4. while(1); 5. } 4.2认识地址与寄存器的映射关系学习51的时候,我们并未如此的关注地址和寄存器的映射关系,因为51有“sfr”、“***it”两个关键词,而且51的部分特殊功能寄存器和通用寄存器共用一个地址,这就回答了上面提出的问题,为何不能点亮LED的原因。但是ARM核没有特殊功能寄存器一说,是不是ARM没有51功能强大了?当然是不是,ARM有自己的体系结构。接下来我们学习一下他们的地址和寄存器映射关系。 4.2.1SWM320的存储器映射打开华芯微特的数据手册地6章可知,控制器为32位通用控制器,提供了4G字节寻址空间,具体如表4-1所示,其中数据使用小端格式(Little-Endian),详细的请看官方规格书,这里只列举部分,以便讲解即可。 表4-1 存储器映射关系 起始地址 | 结束地址 | 名称描述 | 0x00000000 | - | Flash(256/512KB) | 0x20000000 | - | SRAM(64/128KB) | AHB总线外设 | 0x40000000 | 0x40000FFF | SYSCON | … | … | ... | APB总线外设 | 0x40010000 | 0x40010FFF | IO_CONFIG | … | … | … | 0x40018000 | 0x40018FFF | GPIOP |
读者需要注意的是,存储器本身不具有地址信息,他的地址是芯片厂或用户分配的,给存储器分配地址的过程称之为存储器映射,如果给存储器再分配一个地址,这个过程称为存储器重映射,说白了就是重复映射的过程。 由上表可知,片上系统有两大类总线,由于外设执行速度不同,因此挂载在不同的外设上,APB挂载低速外设(例如GPIO、UART、SPI、IIC等),AHB挂载高速外设(例如DMA、SDIO、NORFlash、SDRAM、LCD等)。相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。其中APB1总线的地址最低,片上外设从这里开始,也叫外设基地址。 4.2.2SWM320的寄存器映射接下来我们再来看看寄存器的映射关系,SWM320这样的控制器,有成百上千的寄存器,我们不可能一一讲述,这里随便列举一个系统管理所用到的部分寄存器,其对应关系如表4-2所示,光这个基地址(由表4-1可知地址为:0x40000000)上就分配这好多寄存器,这里列举部分,以做讲解。 表4-2 SYSCON寄存器映射表 名称 | 偏移量 | 类型 | 复位值 | 描述 | CLKSEL | 0x00 | R/W | 0 | 时钟选择控制寄存器 | CLKDIV | 0x04 | R/W | 0 | 时钟分频寄存器 | CLKEN | 0x08 | R/W | 0 | 时钟门控寄存器 | … | … | … | … | … |
走到这里,需要给读者再讲讲故事。上面说到,存储器本身没有地址,给存储器分配地址的过程叫存储器映射,那什么叫寄存器映射?寄存器到底是什么?在存储器这块区域,设计的是片上外设,它们以四个字节为一个单元,共32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。 学到这里,如果读者要访问CLKEN寄存器,请问访问地址是多少?带着这个问题,我们继续往下学习。 接下来,我们再看看CLKEN寄存器究竟控制那些东西?先看寄存器CLKEN的列表,如表4-3所示。同样,该寄存器有32位,我们不可能一一讲述,我们先列举几个,讲解即可。由上可知,CLKEN的寄存器映射地址为0x40000008,因为SYSCON的基地址是0x40000000,而CLKEN的偏移地址是0x08,两者相加即为0x40000008。 表4-3 CLKEN寄存器列表 位域 | 名称 | 类型 | 复位值 | 描述 | 31 | REVERSED | R | 0 | 保留位 | 30 | ADC1 | R/W | 0 | ADC1时钟使能 1:使能 0:不使能 | 29 | SDIO | R/W | 0 | SDIO时钟使能 1:使能 0:不使能 | 28 | RAMC | R/W | 0 | RAMC时钟使能 1:使能 0:不使能 | 27 | NORFL | R/W | 0 | NORFL时钟使能 1:使能 0:不使能 | 26 | SDRAM | R/W | 0 | SDRAM时钟使能 1:使能 0:不使能 | 25 | CAN | R/W | 0 | CAN时钟使能 1:使能 0:不使能 | 24 | RTCBKP | R/W | 0 | RTCBKP时钟使能 1:使能 0:不使能 | 23 | CRC | R/W | 0 | CRC时钟使能 1:使能 0:不使能 | 22 | ANAC | R/W | 0 | ANAC时钟使能 1:使能 0:不使能 | 21 | GPIOP | R/W | 0 | GPIOP时钟使能 1:使能 0:不使能 | … | … | … | … | … |
由此可见,这个寄存器是用来控制外设时钟的,因为SWM320为了降低功耗,每个外设的时钟是可控的,默认情况下,都是关闭的,要使用某个外设,必须先使能外设的时钟,只有这样,外设才可用。FSSW32核心板上的用户LED接在GPIOP.22口上,由此可见,要操作LED必须先使能GPIOP口的时钟,那如何使能?由表4-3可知,只需给CLKEN寄存器的第21位赋值1则可以使能,那如何给这个位赋值1?执行语句如下。 *(volatile unsigned long *)0x40000008 |= 1 << 21; 看到这里,请问读者能否彻底理解这句源码的意思?如果能熟练理解,说明C语言的基本功还是有的,如果读者不能理解,请马上先去补补C语言基础,特别是指针章节。既然讲解到这里,就为读者解释一下这句话的含义。 其中volatile(可变的)这个关键字说明这变量可能会被意想不到地改变,这样编译器就不会去假设这个变量的值了。这种“意想不到地改变”,不是由程序去改变,而是由硬件去改变——意想不到;unsigned long意思是无符号的长整型变量。这样我们可以将(volatile unsigned long *)0x40000008理解为:将其地址0x40000008强制转换成一个指针,并且该指针指向一个易变的无符号整数。那么“*(volatile unsigned long *)0x40000008”就是对这个内存地址进行操作(如读写操作)。“|”和“<<”是C语言最基本的操作符,如果连这个都不懂,还是赶紧放弃单片机的学习算了,哈哈。整个语句的意思就是:给CLKEN寄存器的第21位赋值“1”,也即使能GPIOP口的时钟。 4.3用寄存操作的方式点亮FSSW32的LED接下来我们重新建立工程,不用官方的库函数,用寄存器来点亮LED,建立好的工程如图4-1所示,工程建立参见第3章,读者需要注意的是,这里只需加入一个启动程序,也即“startup_SWM320.s”,剩下的一概不需要,连头文件都不需要。
图4-1 寄存器版的LED功能实例 如图4-1所示的第4行程序我们已经上面分析讲述了,接下来我们分析一下第6行。由表4-1可知,GPIOP口的基地址为0x40018000;由SWM320的规格书第6.7.3节可知,DIR(GPIO方向寄存器)的偏移量为0x04,可知DIR寄存器的地址就为:0x40018000+0x04 = 0x40018004,上面有说,我们的用户LED接在GPIOP.22口上,因此这里需要将GPIOP口DIR寄存器的第22位置“1”,这样该端口就为输出端口了。按道理,这两步设置好之后,应该给对应的端口赋值0,才为低电平点亮,可由规格书可知,数据寄存器的默认值为0,因此我们省略了这一步。到此,编译,下载到 开发板上,LED小灯就会点亮。 4.4寄存器方式的LED闪烁实验为了给读者再讲述一下,数据寄存器的作用,建立一个LED闪烁工程实例,编写如下的程序,其每一句的含义就留读者思考了。 1. void Delay(void) 2. { 3. for(unsignedint i = 0; i < 5000; i++) 4. { 5. for(unsignedint j = 0; j < 1000; j++); 6. } 7. } 8. 9. int main() 10. { 11. *(volatile unsigned long *)0x40000008 |=1 << 21; // 使能P口的时钟 12. 13. *(volatile unsigned long *)0x40018004 |=1 << 22; // P口的第22位为输出端口 14. 15. while(1) 16. { 17. *(volatile unsigned long*)0x40018000 = 1 << 22; // 熄灭LED 18. Delay(); 19. *(volatile unsigned long *)0x40018000= 0 << 22; // 点亮LED 20. Delay(); 21. } 22. } 待读者写完该实例之后,肯定会觉得原来ARM这么难学啊,点个LED小灯都需要查看这个多次数据手册,可51单片机我都学完了,还没看过数据手册长啥样,而且全是什么基地址、偏移量,那如果做个负责的项目,用这么地址,是不是得晕过去啊?难道没有什么好办法?带着这些问题,我们进入下一章的学习。
|