笔者将从芯片IC的系统设计的角度去诠释如何掌握体系编程和SOC编程。笔者有超过10年的嵌入式研发经验,作为架构师多次主导过基于ARM/MIPS/51核的多媒体SOC研发并成功量产案例,希望本系列文章能给嵌入式学习者和从业者有较深刻的指引。
一、体系编程的边界和范畴
嵌入式开发人员往往从最简单的51单片机编程开始,然后慢慢会接触到PIC、AVR、STM32等系列或者型号控制器,还会可能转向三星S5PV210、Exynos4412等高端处理器编程。
开发人员一般的开发过程都是理解完该型号控制器或者处理器对应的指令集和datasheet,然后通过简单的例程开始编程,而最普遍的模块是GPIO、中断和定时器timer。在接触到多种类型的处理器编程之后,我们可能会有归纳式的疑问:
在include一个代表该型号IC的register map的头文件后,某些系列芯片的控制编程可能是完全一样的,而不同系列芯片的控制编程也是类似,唯一的不同可能在于寄存器命名的不同。这是为什么?
要回答这些问题,我们必须要弄懂体系编程的范畴,明确体系编程的边界。要深刻地理解体系编程,我们需要从芯片的系统设计的角度去理解芯片的研发构成,可以参考笔者之前撰写过的《集成电路设计和分工》。
其中有一点非常重要,就是我们要理解CPU核心和SOC的关系。这个世界上有能力研发SOC(System on chip片上系统)的公司很多很多,大到大名鼎鼎的苹果和三星公司,小到深圳那些籍籍无名但又很赚钱的芯片设计公司都是属于这个行列。而CPU核呢,放眼世界,能设计CPU知识产权核的就那么几家,如Intel的X86,ARM公司的ARM核,MIPS公司的MIPS核,51内核也算一个。这里要说明一点,中国没有,国家虽然重点扶持集成电路,但高层也知道这个砸重金也没用,实实在在的先把集成电路封装、工艺等技术发展起来再说。现在的android智能机都是基于ARM核的,想知道ARM之父是谁吗?前不久大师steve furber在广东工业大学做了讲座:
SOC都是根据市场需求和功能定位,在某个类型的CPU核基础上集成各种通用的外围模块控制器,如前面说的GPIO、INT和TIMER,和一些专用的模块控制器,如TI公司的蓝牙单芯片CC2541集成的蓝牙基带和射频控制器。SOC和CPU核的关系如下图:
体系编程明显是针对体系结构的编程,而体系的载体就是CPU。因此,89C51、CC2541等都是51体系编程,而PIC32、ATJ213X都是基于MIPS体系编程,而S5PV210和S3C2440都是ARM体系编程。对同一家CPU核设计公司,它没有理由将自己的CPU核设计成风格迥异,同一家公司不同的CPU处理器系列应该遵循同一个体系结构。那么,体系结构的范畴是什么?是上图中红色的部分,其包括CPU核的设计和总线的时序和控制标准。而CPU核又细分CU(控制单元)和PU(运算单元),CU再分取指、译码、访问寄存器、回写存储单元等流水线操作,高级CPU一般还会集成协处理。因此体系架构编程一般包括以下内容:
1)指令集(寻址、运算)、汇编伪指令
2)流水线、指令预取和跳转规则。在流水线的工作模式下,指令预取当前执行指令的下面N条指令,因此PC和执行地址并不一致,因此假如发生中断、异常等情况下的返回地址要如何确定。
3)协处理(MMU虚拟内存、cache缓存)
4)寄存器使用和参数传递规范(ABI)。其要解决寄存器级如何实现C语言的参数传递。请参考笔者之前的博文《C/汇编混合编程接口--MIPS ABI》
5)异常中断处理(硬件中断、一般异常、指令陷入中断等),上图中的INT管理所有的硬件中断,如串口、定时器、外部等中断,并将所有的中断引脚进行或之后送入CPU的中断信号。因此,对于CPU来说,当发生中断时,它并不知道是哪个模块发生了中断,只有INT模块才知道。
6)调试规范
7)总线规范。对于编程人员来说,并不需要太关心总线规范。但它是SOC芯片设计的重要规范。它规定了总线的时序和仲裁的规范以及存储工作模式(冯诺依曼还是哈佛结构)。
对于一个CPU核设计公司来说,它会给SOC芯片设计公司提供两样东西,一是体系设计相关规范,如ARMV7指令集,描述了每一条指令执行的伪代码过程(这个伪代码针对的是硬件语言,如HDL);另一样东西是特定系列CPU的设计规范,以及在该规范下实现的CPU IP核(知识产权核),为RTL寄存器级电路。
那么,对于同一个体系下不同的系列,他们的差异在哪里?
例如,ARM公司现在的发展方向是Cortex A系列主要面向高端消费类电子,如手机平板,Cortex R主要面向军工、航空等实时要求高的场合,Cortex M系列则是抢占低端控制器市场,如STM32,STM8等。三者的体系都是ARM体系结构,但三者的工艺、功耗、性能并不一样,而且,CPU核集成的东西是不一样的,例如M系列并没有集成cache,相当A系列,M系列的MMU也是一个简化版。
体系编程是嵌入式架构师和操作系统开发人员需要精通的,对于一般的开发人员比较少接触到。以上的分析都是基于理论层面的分析。而SOC编程则是面向广大的普通开发者,将会从系统设计的角度指导大家如何进行SOC编程。
二、SOC编程的范畴和定义
SOC编程是针对片上集成模块进行寄存器控制编程。在开发实践中,我们往往把SOC编程和体系编程统称为体系编程了。没关系,只要我们理解清楚他们之间的关系就可以了。
那么SOC编程的范畴是什么?
1) 通用控制模块,如CLOCK、GPIO、INT、TIMER、UART等。
2) 专用控制模块,如LCD、蓝牙、编解码等。
有些模块不需要通过引脚连接外围设备就可以完成功能,如TIMER,大部分模块需要通过引脚连接外围设备来完成功能。因此芯片片上集成的是模块的控制部分,例如LCD控制器,该控制电路实现LCD驱动器(外围设备)所需要的数据传输时序、行信号、列信号和设置接口等。换一个角度说,如果没有LCD控制器,我们也一样能够通过GPIO来模拟出LCD驱动器的时序,但是这样做,对于普通的开发者来说太复杂,对CPU来说也是一个沉重的负担。所以系统设计人员会针对LCD驱动器来实现专有的控制电路。
那么对于LCD控制器来说,我们SOC编程来做什么呢?至少,我们需要给LCD控制器一个标识,让它启动或者关闭,如何给出这个信号呢?LCD控制器电路无非是一堆的时序电路和逻辑组合电路,启动和关闭可以用一个信号输入来表示,而该信号在CPU看来可以映射成一个寄存器的其中一个bit。因此,SOC编程即是针对寄存器编程。对于LCD控制编程,当然不只一个寄存器这么简单,因为对于一个SOC厂商来说,它希望能兼容市面越多的LCD驱动器,兼容不同的分辨率和图像深度,因此肯定会有不同的寄存器来进行设置。
从教学的角度来总结,体系编程和SOC编程分为五个层次:
1)体系指令集,如ARM指令集
2)特定系列的CPU规范,如基于ARM体系的CortexA8核
3)SOC级,在CPU核的bus总线基础上集成clock、timer、interrupt、GPIO等控制器,如三星的S5PV210和TI的OMAP3430都是基于Cortex A8核。
4)板级电路,如开发板、手机主板电路,其电路图将明确SOC的引脚和外围设备的连接关系。如GPIO A的第一个引脚接LED0,那要控制LED0就需要对GPIOA0进行控制编程。
5)外围设备规格,表示板级电路图上所有外围设备的规格,如具体LCD SPEC(尺寸、分辨率、延时参数等)。
笔者给SOC编程的过程定义是:基于1)和2)的指令集和CPU总线控制机制,根据具体外围设备5)的特性参数和4)具体的电路连接关系,对3)SOC对应控制模块的寄存器进行编程,达到控制、使用的目的。
三、SOC设计和编程
理解SOC系统设计能够极大地帮助编程,每款SOC的控制例程都是由SOC系统设计人员给出,因为他们也需要通过编程来对SOC芯片进行功能测试和验证。
另外,我们需要明白的一个观点是,对于不同的体系来说,同样的模块的设计思路是基本一致的,或者可以说,从编程的角度来说,片上集成的模块的设计控制过程无关于体系结构(两者有关更多的是在总线控制方面,而这个跟编程人员来说关系不大)。因此基于ARM体系的LCD控制器和基于MIPS体系的LCD控制器,其设计思路都是一致的,不同系统的GPIO控制也是一致的。尽管底层的寄存器电路可能不一样,但对于上层编程人员看来,两者是一致的,不同的可能只是寄存器名称。例如51单片机和arm处理器的GPIO都是输入寄存器、输出寄存器、方向寄存器、上下拉寄存器等等。
下面就以CLOCK和GPIO为例说明SOC编程,尽量提取通用的设计和控制过程。
1.最小系统
最小系统除了CPU和RAM之后,还有一个重要的组件就是晶振。SOC或者CPU说到底都是一堆逻辑组合电路和时序电路,时序电路需要时钟才能正常工作,而晶振就是提供时钟的,其在上电后不断地产生时钟。
2. CLOCK
一般的低端单片机并没有时钟控制模块,其直接利用晶振(如12M或者24M)工作,即CPU的工作频率就是12M或者24MHz。但是作为一个高级处理器来说,其支持的主频达到1G或者更高。而市场上并没有1G的晶振,因此SOC内部必然要实现调频,PLL锁相环倍频技术被广泛应用在处理器的CLOCK模块中,其是将晶振的频率进行倍频提升。CLOCK模块除了实现倍频的控制外,还需要考虑这样的需求:
1)SOC集成的模块很多,各种模块的工作频率并不尽相同,或者说是在不同的层级,例如CPU和GPU、RAM的工作频率在G级,LCD的工作频率则在100M左右,视频编解码的工作频率百M级别,而像GPIO、串口这些模块的工作频率可以在10M级别。因此CLOCK还需要进行分频。所以CLOCK模块一般的处理方法是将模块进行分类,并进行一级分频。例如,S5PV210的CLOCK会将频率分为三个范围,M域供给CPU、中断异常等,D域供给LCD、JPEG、HDMI等,P域供给GPIO、UART等外围设备。每个域都给定一个频率(可以通过控制器调节)。
2)对于在同一个域内的模块,其工作频率不尽相同,因此允许其内部进行二级分频。
因此,对于一个高级处理器集成的模块编程来说,设置好其CLOCK源频率的相关寄存器是第一步。一般一级分频由操作系统开发人员设定,二级分频设置由各模块的开发人员负责。
3.引脚复用
芯片封装是芯片成本的重要组成部分,而封装涉及到引脚复用,而且芯片封装也涉及到最终产品的BOM成本,因此在系统设计时的引脚复用是一项非常关键的技术,可以说SOC集成电路公司的绝密技术。尽可能高效地进行引脚复用非常关键。
对于编程人员说,我们要做什么呢?那就是记得每个引脚都可能是功能复用引脚,在使用这个引脚之前,必须设置该引脚的功能。引脚一般默认是GPIO功能,如果我们要使用该引脚进行串口或者LCD等控制线功能,就必须对该引脚对应的功能寄存器进行设置。这是SOC编程的第二步。
CLOCK和引脚功能设置对所有模块都是适用的。
4.GPIO
这里我们要谈通用的模块GPIO。对于GPIO编程来说,我们应该非常熟悉了,那就是它一定带有输入数据寄存器、输出数据寄存器、输入输出高阻态设置寄存器、上下拉设置寄存器。一般的过程是:
1)设置上下拉寄存器
2)设置引脚的方向
3)通过输入数据寄存器读入数据或者写数据到输出数据寄存器。
记得功能引脚设置也是需要的。有些高级SOC可能还会有驱动能力等级设置寄存器,控制引脚的驱动强度。
5.INT
请参考笔者的博文《软件和硬件都是对生活的高度抽象---论中断控制(ARM体系编程)》。