注解目录
第一章《振南当年入门 C 语言和单片机的那些事儿》
1、注定堕入单片机
1.1 懵懂好奇的我
(小时候好奇的性格经常让我屁股开花。初中开始对计算机产生兴趣,并一发不可收拾。)
1.2 我的 C 语言学习经历
(上大学后自学 C 语言。遇到“能人”加入 ACM 竞赛。感觉 C 语言乐趣多多,程序如人生。)
1.3 C 语言的顶级赛事
(ACM 国际程序设计竞赛在东北被我们发扬光大。ACM 竞赛浙大的一段传奇佳话。振南在关注的 IOCCC 国际混乱 C 代码大赛。网吧包宿学 C 语言惊呆室友。)
1.4 岔路口上选择单片机
(搞纯软件还是搞单片机,这是一个抉择。鬼才杜撰拉我进入单片机快车道。)
1.5 窗户纸破了
(入门阶段的困惑,看破 C 语言与单片机之间的鸿沟。)
2 、看穿单片机
2.1 CPU 模型
(CISC 与 RISC 指令集。CPU 如何执行指令。汇编不是第一代编程语言,打孔纸带才是。)
2.2 存储器模型
(存储器就是一个指令和数据的容器。)
2.3 总线模型
(地址、数据和控制三大总线。贯穿整个单片机芯片的通路。)
2.4 外设模型
3、单片机跑起来
3.1 时钟系统
(时钟是单片机激励和血液。时钟频率不能无限提高。)
3.2 二进制
(为什么单片机采用二进制?振南告诉你如果单片机使用十进制会怎样?)
3.3 中断机制
(中断不是在给 CPU 捣乱。中断对于单片机为什么如何重要?)
看穿单片机
2.2 存储器模型
存储器对于整个计算机系统来说是至关重要的;供CPU执行的程序指令、程序运行过程中的变量和数据·.....,它们都要以存储器作为载体。所以在实际的应用和开发中,人们总是希望单片机芯片的RAM和ROM 容量能尽量大一些。这样就可以存储更多的代码指令,运行规模更大更为复杂的程序。另外,存储器本身的读写速度也就成为了CPU性能的最大瓶颈之一。更为形象的描述如图 1.8 所示。
ROM,即只读存储器,也就是说在它上面存储的内容是无法被CPU直接修改的。(通常只能使用专用的烧录器来修改其中的数据,不过现在一些新型的单片机芯片已经可以在CPU运行过程中去修改ROM数据了,这种技术被称为“IAP”)所以,ROM通常被用来固化存储程序指令代码和一些无需修改的数据,比如字模字库、常数等等。RAM与ROM不同,它是可读可写的,因此被称为随机读写存储器。CPU在运行过程中,可以对RAM中的任何数据进行读写修改操作。这就是C语言中赋值语句在底层得以实现的物理基础,比如“int a;a=0”,就是将 RAM 中的某一个存储单元写人了一个数值 0。但
图1.9 CPU从存储器取指令以及进行变量、数据的存储
如果是“code int a;a=0”的话,编译的时候就一定会报错。(code关键字在51单片机C语言中是用来说明“变量”的位置在 ROM 中,同样的定义在ARM上使用const)而且,RAM比ROM在读写速度上要快得多,所以CPU在运行程序的时候,通常都会把一些代码指令拷贝到RAM中来,尤其是那些会被频繁执行的部分(这就是C语言中的.text 段,即代码段)。但是RAM通常比ROM要昂贵的多(关于这一点大家应该有宏观的感知,一个 U盘16G才10块钱,但是电脑内存条却要好几百),这也就是生产厂商为什么在单片机芯片中对于RAM的配比显得很吝音,而对于 ROM 则略显慷慨的原因。
为了更好地讲解后面的内容,对于存储器大家必须明确一点,它也算是一个常识;它是由很多的地址连续的存储单元构成的。如图1.9所示
图1.10 存储器是由地址连续的存储单元构成
总体来说,存储器就是一个指令和数据的容器,它与CPU相互依存,这才使得整个计算机系统得以正常运作。此时一个极其重要的问题便应运而生:CPU是如何精准地从存储器中取出指令和数据的,又是如何将数据写入到存储器中的?这一问题说起来简单,但它却引申出了一个关键的技术一“总线”!
2.3 总线模型
如果把CPU看作“帝都”,存储器看作是“卫城”,它们之间要互通往来,就必然要修建道路,而这条道路又可以不断延伸分支,将很多城市串连起来。这样,城市两两之间便均可通行。这条“道路”就是总线!如图 1.10 所示。(这些被串连起来的“城市”就犹如振南后面要讲到的“CPU外设”)。
好,现在 CPU 与存储器之间的这条通路有了。此时,CPU 如果要读取存储器中地址为
图1.11 总线的结构模型
addr位置上的一个字节,该如何作呢? 这个过程主要分三步:(是不是想起了“把大象装冰箱总共分几步?”)
(1)CPU首先告诉存储器要读取的地址;
(2)等待储存器将相应地址上的数据取出来;
(3)CPU将数据取走。
更为形象的说明如图1.11 所示。
仔细想一下,这个过程的实现其实涉及几个问题:CPU如何将地址给存储器?CPU如何知道存储器已将数据准备好?CPU又如何将数据取走?
……总结起来,主要是地址和数据的传输,以及它们之间的协调与控制。为了解决这一问题,我们提出了这一模型,请看图 1.12。
图中所看到的连线就是实实在在的用于传输二进制信号(0 或 1)的导线。CPU首先将地址输出到地址总线上(很显然地址线的数量决定了 CPU可以寻址的空间范围),然后再将RD信号置为 0(RD平时为1),告诉存储器地址已经给出,请准备好数据并将其输出到数据总线上(数据线的数量决定了CPU的数据吞吐量,这也是衡量 CPU 位数的标准,51 单片机是 8 位
图1.12 CPU访问存储器的主要过程
单片机,则它每次只能读到一个字节的数据,ARM是32位的,所以它可以一次性读取一个字)。CPU对数据总线进行读取,再将RD信号置 1,整个过程便完成了。那CPU如何向存储器写人数据呢?其实道理是一样的,如图 1.13 所示。
图1.13 CPU与存储器之间的总线模型(读数据)
仍然是由 CPU 先给出地址,再向数据总线给出要写入的数据,然后将WR信号置0,告诉存储器地址与数据已经就绪,请予以处理。最后将 WR信号置1即可。综上所述,CPU中有三大总线:地址总线、数据总线与控制总线。这一模型最终如图 1.14 所示。
图1.14 CPU与存储器之间的总线模型(写数据)
如果我们把CPU访存过程中,各总线信号上的电平随时间变化的示意图画出来的话,它将是这样的,如图 1.15 所示。
图1.15 CPU总线操作的时序图
上图就是 CPU 总线操作的时序图(Timing Digram)。它是描述接口时序与信号协议最为直观的形式。看懂时序图是我们学习电子和单片机技术,使用 C 语言正确编写底层驱动程序的根本基础。
2.4 外设模型
我们已经知道了CPU如何通过总线进行存储器的读写,也知道地址总线的宽度决定了CPU的寻址空间,数据总线的宽度则决定了CPU的位数(单次能够读写的数据量),而控制总线在一定程度上影响了访存的速度(WR与RD为0的时间越短,访存速度越快,当然也要存储器速度跟得上才行)。有了CPU和存储器,以及连接它们的总线,这就足以构成一个完整的、可正常运行的计算机系统。我们可以把一些算法放在其中来运行,但是单片机(嵌入式处理器)并不仅仅只是用来作计算的,它更大的作用在于控制(所以单片机的英文缩写是MCU,即Micro Controller Unit.微控制单元)。IO是最直接、最常用的控制接口、我们可以将它置1或清0来输出高电平或低电平,从而实现对外部电路或机构的控制。
对前面振南的困惑从更基础的层面进行解释:C语言是如何实现对物理世界产生影响的?在图1.5中,以CPU为核心,周边除了存储器(RAM与 ROM)之外,还有很多控制器,比如IO控制器、串口控制器等等,这些就是所谓的“CPU 外设”。外设其实就是一些电路,它用于实现某一特定的功能。这些电路肯定要受CPU的控制,因此在电路设计上留出了专门的接口(寄存器)。这个接口的读写是符合CPU总线时序的,所以它可以直接挂接在CPU总线上,与存储器、其他外设并存(但是分属于不同的地址区间,CPU向这些地址读写数据将对应于外设电路的不同功能)。更形象的说明请看图 1.16。
很明显,CPU的整个寻址空间(它的大小由地址总线宽度决定)并不都是由存储器独占的。存储器只是占用其中的某一段而已,其他的地址空间一部分分配给各个外设,而更多的可能只是空闲保留。有人可能会问:“既然这样,那我们完全可以把自己作的电路接到CPU的总线上,为CPU 扩展外设。”没错,只要CPU芯片把总线通过外部的引脚开放出来,我们就可
图1.17 CPU外设的结构模型
以挂接自己的电路,让CPU直接去访问控制,比如挂接一个8080接口的液晶屏等。(51的xdata,STM32的FMC都是CPU内核将总线对外开放的实例,在后面大家会看到一些单片机外部总线巧妙的应用实例)。