说到MCU的复位肯定是不陌生了,但究竟其怎么工作的,设计其目的和作用是什么呢?其实我们程序最初的加载就与复位有关,比如一上电,MCU就自动执行我们设计的程序,复位有很多种,比如异常复位(程序跑飞阿,电源不稳定阿,看门狗喂狗超时阿),但不管哪种复位,其做的大多工作基本类似,大多包括以下几点:1.将所有寄存器恢复成默认值 -> 2.确认MCU工作模式 ->3. 关全局中断 ->4. 关闭外设 ->5. 将IO置为高阻输入状态 ->6. 等待时钟震荡趋于稳定 ->7. 从固定地址取复位向量的第一条指令开始执行。这前面几步大致就是使整个CPU恢复成出厂设置把(暂时简单可以这么理解)。最后一步可关键了,它是加载我们用户程序的关键一步呢,前面讲到了中断向量表,里面第二个表项对应的就是复位向量,既然复位时执行的是我们用户程序,很容易就想到这里,应该存的是我们用户程序的main函数的第一条指令的地址。事实上,它没这么干,它指向的是由开发工具提供的帮助生成的startup.s汇编启动代码(这部分启动代码有些开发工具会帮助生成,例如Keil,但有些开发工具不会帮助生成,这时需要确保工程里有这个启动代码存在)的第一条指令,在这个启动代码的最后写了一个jump到main函数,这里多提一下,为何需要启动代码以及为何启动代码又用汇编来写呢,网上给的说法是由于CPU上电时候需要准备好硬件执行环境和软件环境,硬件环境就是中断,寄存器,堆栈什么的,甚至有些包括用到的外部RAM还是内部RAM也还没确定,软件环境就是C语言的执行环境,所以需要用启动代码(Bootloader)来完成初始化,而且必须用汇编来写,C程序的环境那时还没准备好,堆栈指针和PC指针还没有找到正确值,启动代码相当于给后面C程序的执行铺平了道路。再回来看一下前面说的复位,,复位用到了中断向量表项的复位向量,在Cortex内核的48个中断向量表里,前16个都是异常向量都与复位有关(例如看门狗阿,电源阿等等)。刚说到了复位向量(即中断向量表的第二个),复位时候,由电路将该表项里为2的对应的指令地址值加载到PC寄存器里,这样程序就可以开始跑了,但还没完,要知道程序执行还需用到堆栈的,那堆栈寄存器SP里的初始值怎么知道呢,这也是在复位时就确定下的,一复位,SP和PC里就有东西了,一个确定主程序在哪一个,一个确定可以往哪里堆。
都知道了MCU里的时钟就像心脏,不过这个心脏频率就值得探讨下,MCU由于考虑到CPU的不同外设和用户需要,就将时钟设计成了时钟树,这很有意思,可总的来源只能有一个(要么片内,要么片外,片内的稳定程度不够,如果可靠的话还是片外ok,这样确定好了来源,具体分配下来就要看各自需要了,就好像树枝一样,不断发叉,就开出不同的大小花(不同频率))
前面讲的这些还都是CPU里面的东西,真正开始MCU的,还得是存储器的集成,CPU里面也是有存储的东西的,寄存器就是的,每个寄存器都需要特定指令操作,我们写的程序无非也就是往不同寄存器里写东西来操作CPU帮我们实现想要的功能,但当程序一旦复杂,比如涉及到运行的中间数据存储或程序执行时程序放在哪呢,这就需要更大容量存储东西,这时RAM和ROM(FLASH)就出现了,RAM用来放堆栈和运行的程序和数据,ROM(FLASH)用来存放用户已编译的code,但是怎样CPU才能完成对存储器的访问的,前面也差不多了解了大小存储器就好像一段段小格子一样,不过每个都是编了号的,从CPU角度向他看,那就是通过地址的形式去访问,存储器上也规定哪段地址用来放向量表,哪段用来作实际的RAM.
再将整个CPU,存储器,时钟向外延申下,这时就会发现世界更大,各种千奇百怪的外设开始出现了,要想去接触这些东西并干掉它,就得用总线去降伏他们(有数据总线,地址总线,控制总线这个篇章已经讲解了)
MCU的总体结构
前面介绍了MCU的种种,现在就大致形成了下面这一幅方框图
CPU以外都相当于CPU的外设,在CPU的内部其是通过指令操作寄存器来实现,对于CPU以外的外设,其是通过操作存储器上的地址空间来完成控制的,SRAM,ROM,FLASH,这很明显是操作其地址的,这点前面已提过,在这就不赘述了,但对于其他的外设,例如AD接口,或者其他LCD什么的,它们大多也有寄存器,对这些寄存器里进行0或1的读写就能实现对这些外设的控制。但这里需要注意一点,外设里的寄存器和CPU内部的寄存器,尽管名称一样,但对CPU来说是有巨大区别的,首先一个在里,一个在外,另外操作方式也有很大区别,对于CPU内部的寄存器,往往写的指令(往寄存器写0,1字符串),这些字符串代表了CPU内部的某些结构的开或关(或者一些相关操作),而对于外设中的寄存器,这部分也是写指令,不过这些指令表示对特定的引脚写高低电平(高为1,低为0),而且也不是直接操作的,都是间接的去更改其外设的那个寄存器对应地址空间的值,也就是间接的去修改外部寄存器的值以达到控制外设的目的,这些也都是靠我们用户去加以编程实现的。
下面放上一张ARM Cortex内核里的地址划分,由于其是32位的,其地址线就为32位,从0x0000_0000一直到0xFFFF_FFFF
左边这张图是ARM设计公司设计好的其32位Cortex内核的地址划分,规定了在哪些地址空间里可以干嘛,规定的地址空间基本给的很足,对于一般厂家或生产一般的MCU并不需要这么大的空间,所以其地址有些厂家可根据需要是否使用,正如右边那幅图那样,实际使用的可能只是其中一部分,Cortex划分给了RAM为512M,实际我们使用就用了几K,厂家在设计时也是这样的,例如,MKL25Z128里就只设置了16K的RAM,那么我们的编程最终也是来操控这些对应的地址(或者说是存储器),所以说,单片机的编程精髓也在于对存储器的使用,具体怎么使用存储器,就是来操作地址,地址里有程序,有堆栈,有外设寄存器对应的内容等等,更高级的还有地址映射的概念如将实际的物理地址映射到一个虚拟的地址。
那么既然编程是对这些存储器进行操作,那不妨来探讨一个真正的程序从上电开始是如何运行的,这里就拿MKL25Z128作简要分析,在上电之前,我们的中断向量表和程序是存储在非易失存储器里的(例如FLASH),当系统上电后,CPU将中断向量表里的第一项的值赋值给堆栈指针作为堆栈起始地址,这时SP指向了那个12K的RAM部分,复位向量将其程序函数入口地址赋给PC指针,程序开始一条条往下运行,程序在运行过程中的一些变量(局部或全局)放在堆里(比如4k区域),栈放在12K的RAM里,当程序里跑到对外设的一段程序时,程序会转到外设寄存器所对应的那个地址里,通过往该地址进行读或写来达到对特定外设的控制,当程序执行完子程序,又经过出栈,返回到原来地址位置一条条的向下执行,讲到这里,归总一下,不管是哪家的MCU,只要内核一致(指令集+寄存器),再了解一下对应的存储空间,任何MCU基本可以手到擒来。