不管是什么样的嵌入式cpu,它必然有自己的访问地址空间。至于这个具体的访问空间是什么,那cpu就不知道了。它可以是ram,当然也可以是flash、uart、ide、i2c等。当然cpu不仅需要地址总线,它还需要数据总线和控制总线。这三个总线的目的都非常明确,控制总线主要是为了实现cpu对外设接口的控制,地址总线是为了实现地址的输出,数据总线则是为了实现数据内容的获取或者设置。所以,对于一般的嵌入式cpu来说,它的基本架构应该是这样的,
在x86的cpu上,很多对外设的操作是需要通过北桥或者通过南桥芯片完成的。而在嵌入式硬件中,我们就把经常使用到的接口芯片集成到了cpu里面。所以在嵌入式cpu功能上面,你除了看到cpu的字长、时钟、指令集、运算速率这些通常的数据之外,你还会看到很多的接口控制寄存器,比如说定时器寄存器,lcd寄存器,uart寄存器,i2c寄存器。这些都表明了此时的cpu完成的不仅仅是简单的计算功能,它还需要完成对外设接口的设置。通过对应的寄存器设置,我们就可以对外设的接口状态进行控制。
说了这么多,我们接下来要看看嵌入式系统在地址空间里面是怎么设计的啊?其实一个完整的嵌入式软件系统并不复杂。一般来说,一个完整的系统需要有boot、kernel、文件系统三部分。其中boot主要放在norflash里面,而kernel和文件系统是存放在nandflash里面。在系统上电之后,cpu会有一个初始地址,这个地址要么是0x00000001,或者是0xFFFF0000。通常这个地址会指向Norflash,下面开始执行的代码当然就是boot代码。因为Norflash的访问速度要比Ram速度慢很多,所以boot代码很快会把自己拷贝到Ram中,然后跳到Ram中继续运行。boot的功能比较简单,主要就是为了获取芯片参数,设置芯片属性,设置堆栈空间,加载操作系统内核等。在boot完成自己的功能之后,它会把系统内核加载到Ram中,然后jump到系统的运行首地址处运行。系统内核主要完成整个系统的初始化工作,比如说内存分配,信号量初始化,net初始化,驱动结构初始化等工作。在内核即将完成初始化的时候,它会进行最后一步操作,mount一个文件系统,加载文件系统的脚本数据,开启相关的系统进程,最后一步就是开启shell进程,接受用户的命令输入。至此,一个系统算是真正跑起来了。
前面,我们说过cpu需要数据总线、地址总线和控制总线才能与外设接口打交道。既然cpu是通过状态寄存器设置外设接口的状态的,那么cpu是如何通过地址总线与外界联系的呢?这里面就涉及到片选信号的问题。我们知道,一个32位的cpu有32条地址线和外界相连,那么也就是说如果没有其他的方法,它只能外设32个接口。那么有没有什么办法可以扩大外设接口呢?说到这里,你应该知道了,它就是解码器和片选信号了。比如说,现在有4个外设接口,我们可以怎么从地址总线中挑出两条线,00表示外设1,01表示外设2,10表示外设3,11表示外设4。这样有了解码器的帮助,我们就可以用两个地址线实现对4个外设接口的控制了。
有了cpu状态寄存器,我们可以设置当前外设接口的执行状态。如果是读命令,首先设置外设接口的状态模式为读状态,然后发送地址,此时片选信号选中的芯片就会处于使能状态,一会cpu就可以从数据总线上获得数据,存储在寄存器或者是内存当中;如果是写命令,那么cpu首先设置外设接口为写模式,然后在地址总线上输出地址,在收到芯片ready信号后,cpu再将数据从寄存器上传输到数据总线上,在等到外设芯片的ack信号后,整个数据的传输过程才算完成。我们看到,一个汇编指令的操作竟然涉及到这么多信号的操作,可见cpu的处理过程还是很复杂的。有的时候,中间还会涉及到信号完整性或者是时序的问题,那么这时候逻辑分析仪就可以派上用场了。