对于μClinux来说,其设计是针对没有MMU(Memory Manage Unit)的处理器,即μClinux不能使用处理器的虚拟内存管理技术,μClinux采用存储器的分页管理,系统在启动时把实际存储器进行分页。在加载应用程序时程序分页加载。但由于没有MMU管理,实际上μClinux采用实存储器管理策略。
ΜClinux系统对于内存的访问是直接的,它对地址的访问不需要经过MMU,而是直接送到地址线上输出,所有程序中访问的地址都是实际的物理地址。操作系统对内存空间没有保护,各个进程实际上共享一个运行空间。
根据内存空间是否独立,可以将I/O空间的配置分为两种:一种是I/O空间与内存空间相互独立,这样I/O空间的访问需要使用专门的I/O函数如inb和outb等。Intel CPU就使用这种方法。另一种是将I/O寄存器作为内存的一部分,即I/O寄存器与内存统一编址,这样使用普通的内存访问语句即可读写I/O寄存器。Motorola 68K处理器就采用这种体系结构,处理器MCF5272也统一编址。即其RAM、FLASH和外设I/O均统一编址,没有地址变换和内存保护。
2 快速设备访问
在C语言中,用指针可以对内存地址单元进行直接访问,因此在设计中可以采用指针对外部设备进行快速操作。
2.1 地址映像
为了访问外部设备,首先应将外设的寄存器映像到MCF5272的内存,与内存统一编址。为此,需要修改相应代码。
用于COLDFIRE MCF5272的嵌入式μClinux启动代码由两部分组成:
μClinux/linux/arch/m68knommu/platform/5272/MOTOROLA/crt0_rom.S
μClinux/linux/arch/m68knommu/platform/5272/sysinit.c
其中crt0_rom.S由汇编写成,完成CPU的初始化设置,这是整个软件体系的最开始执行的代码入口,CPU一加电就跳到这里执行;sysinit.c为C语言代码,完成MCF5272的集成模块SIM(如串口、时钟、通用I/O等)、SDRAM、FLASH和其它外设接口、片选等的初始化设置。
MCF5272的片选CS0~CS7的寄存器CSBR0~CSBR7和CSOR0~CSOR7可将外设备寄存器的地址映像到内存储空间,这样可以采用对内存空间的访问来达到访问外部设备。其中寄存器CSBR指明了映像的内存起始地址、映像的内存容量、总线宽度等;寄存器CSOR用于配置访问控制。片选CS0用于启动存储器ROM(FLASH)。
在C语言文件sysinit.c中修改代码以实现外设的寄存器映像功能。应用片选CS2实现的代码如下:
MCF5272_WR_CS_CSBR2(imm,0xffa00001);//寄存器内存开始地址:0xffa00000
MCF5272_WR_CS_CSOR2(imm,0xfff00014);//片选2
其中imm为无符号字符指针,代表了MCF5272系统集成模块(SIM)中的寄存器地址。
2.2 实现访问函数
通过修改启动代码,将外部设备的寄存器单元映像到内存单元后,就可以使用访问内存的宏和指针快速访问外部设备的寄存器。有两类实现设备快速访问的函数。
2.2.1 使用宏定义
(1)对设备的该函数read_register()实现
#define read_register(IMM,OFFSET,SIZE)Mcf5272iord(IMMP,OFFSET,SIZE)
(2)对设备的写函数write_register()实现
#define write_register(IMM,OFFSET,SIZE,DAT)Mcf5272iowr(IMMP,OFFSET,SIZE,DATA)
其中Mcf5272_iord和Mcf5272_iowr为宏。在sysinit.h中有下列宏定义:
(a)用于计算地址的宏
#define Mcf5272_addr(IMM,OFFSET)((void *)&((unsigned char *)IMMP[OFFSET]))
表示基地址为IMM,偏移地址为OFFSET的内存地址。宏返回物理地址。
(b)访问内存的宏
#define Mcf5272_iord(IMMP,OFFSET,SIZE)
(*(volatile uint ## SIZE *)(Mcf5272_addr)(IMMP,OFFSET)))
#define Mcf5272_iowr(IMMP,OFFSET,SIZE,DATA)
(*(volatile uint ## SIZE *)(Mcf5272_addr(IMMP,OFFSET))=(DATA))
分别表示读内存地址单元内容、将数据DATA写入内存地址单元。地址单元的基地址为IMM,偏移地址为OFFSET。SIZE表示每次读写操作的数据度,取值可为8、16、32,分别表示每次操作8位、16位、32位的总线数据。

2.2.2 采用指针直接定义
(1)对设备的读函数inb()、inw()、inl()实现
#define inb(addr)(*(volatile unsigned chart*)(addr))
#define inw(addr)(*(volatile unsignedshort*)(addr))
#define inl(addr)(*(volatile unsigned long*)(addr))
分别是8位、16位、32位数据总线的读函数。
(2)对设备的写函数outb()、outw()、outl()实现
#define outb(data,addr)((*(volatile unsigned char*)(addr))=(data))
#define outw(data,addr)((*(volatile unsigned short*)(addr))=(data))
#define outl(data,addr)((*(volatile unsigned short*)(addr))=(data))
#define outl(data,addr)((*(volatile unsigned long*)(addr))=(data))
分别是8位、16位、32位数据总线的写函数。
3 应用实验
在笔者的传感器网络节点中(见图2),外部设备芯片采用W99200F,它包含100多个寄存器。在芯片上电复位后,芯片寄存器的复位初始值在手册中是已知的。根据访问方式,它包含三类寄存器:只读、只写、可读写。W99200F芯片部分寄存器偏移地址及其复位初始化值如表1所示。
表1 W99200F芯片中部分寄存器及其初始化值
| 寄存器名 |
偏移地址 |
访问方式 |
数据宽度 |
复位值 |
| Vint_source |
0x0d |
只读 |
8 |
0x40 |
| Vbv_initial |
0x18 |
读写 |
8 |
0x13 |
| Vquality |
0x19 |
读写 |
8 |
0x08 |
| Vin_cntl |
0x21 |
读写 |
8 |
0x0c |
| Vsize_h |
0x24 |
读写 |
8 |
0x2c |
修改启动代码和实现访问函数,其中寄存器CSBR2指明了映像的内存起始地址、映像的内存容量、总线宽度等。重新编译μClinux内核,并将生成的下载文件烧写到FLASH中,重新上电在内核运行起来后(或者mount上宿主机硬盘手动启动μClinux内核),通过编制一段C语言的测试程序,调用设备访问函数,即可对外部设备芯片的寄存器进行读写。下面是一段测试程序test.c。
#include
#include "io.h" //包含定义的设备访问函数
int main(void)
{
printf("Vint_source:0x%x",inb(0xffa00000+0x0d));
//读寄存器Vint_source初始值
printf("Vbv_initial:0x%x",inb(0xffa00000+0x18));
//向寄存器Vbv_initial写入值0x7f
outb(ox1f,0xffa00000+0x19));//向寄存器Vquality写入值0x1f
outb(0x7f,0xffa00000+0x21));//向寄存器Vin_cntl写入值0x7f
outb(0x2d,0xffa00000+0x24));//向寄存器Vsize_h写入值0x2d
printf(“Vbv_initial:0x%x”,inb(0xffa00000+0x18));
//读寄存器Vbv_initial的值
printf("Vin_cntl:0x%x"inb(0xffa00000+0x21));
//读寄存器Vin_cntl的值
printf("Vsize_h:0x%x",inb(0xffa00000+0x24));
//读寄存器Vsize_h的值
return;
}
该测试程序先读出外部设备上电的初始值;再对外芯片的可读写寄存器进行写操作,后读出写入的值。在宿主机Linux系统的minicom调试窗口中mount上宿主机硬盘,运行编译好的test程序,得到该测试程序的输出。读出的初始化值与外部设备手册上的值完全一样,并且写入外部设备寄存器的值与而后读出的值也完全相同。
通过测试检验说明设备访问函数能够按物理地址访问外部设备。比较设备驱动程序方法,该方法可以在较短时间正确访问外部设备,这样对硬件调试人员来说节约了时间,可以快速进行硬件的开发调试,而不是等待编写好设备驱动程序后才调试硬件,编写设备驱动程序可以单独进行。因此,在μClinux嵌入系统中采用本文介绍的方法调试外部设备,具有快速方便的特点,大大加快了在μClinux应用系统中的设备调试,节约了时间