简单介绍单片机原理

2019/04/10 10:10
阅读数 44

全文原创,转载请标明出处

如果您发现我写错了、不明白我写的内容或者能提出建设性意见,那么恳请您在评论区发表高见

本文的定位只是让具备基本电学、数制知识的读者明白裸机工作的大致流程,并不针对某款特定的芯片,不讲编程,全程幼儿园化

 

(近期准备施工加上一些内容:哈佛结构、冯·诺伊曼结构、RISC、CISC、多核、再来大家相对而言比较熟悉的Intel、ARM的几个例子)

本文大致结构如下:

  • 一些基本、必要的数字电子技术知识,用逻辑门引出二进制
  • 引导读者尝试打草稿"设计"一款单片机,在这个过程中讲解:
    • 微机原理的重要概念比如内核、寄存器、中断、总线等
    • 单片机的重要概念比如调试、外设等
    • 汇编语言和C语言出现的原因、优势、工具链工作细节
  • 高端单片机可以用到的技术
    • 用硬件实现功能
    • 库函数
    • 操作系统(非常粗略地介绍)

 


 

 

在引入单片机之前,先来了解一下简单、必要的电子技术知识吧,这些知识虽然和单片机原理没关系,但是能让你知道确实能用一些电子元件搭建出单片机

放心,这部分内容很简单、很短,我会尽量让你觉得不枯燥、不困难的

否则你看到后面的时候,好奇的你可能很想追问有关深入单片机内部的问题、但是你却不知道该如何向搜索引擎表达你的好奇之心

 

1.芯片、模拟、数字

电信号是目前最容易处理的信号,我们能用模拟电路、数字电路来处理电信号

话筒产生幅度正比于接收到的声音的响度的电压,这个电压是模拟(analog)信号

把模拟信号每隔一段时间后的新值进行量化、编码,就得到了数字(digital)信号

模拟电路能处理模拟信号,数字电路能处理数字信号,模数转换器ADC、数模转换器DAC架起了模拟和数字沟通的桥梁

 

单片机内核、电脑处理器内核都是数字电路

电路能被集成在一块芯片里成为集成电路(IC,integrated circuit),模拟电路、数字电路都能被集成

所以如果你认为芯片就是电脑CPU、显卡、内存条等等,那就错了,你忽略了广大的模拟集成电路,还有架起模拟电路和数字电路的桥梁的模数转换器(ADC)、数模转换器(DAC)

 

 

 

2.晶体管

模拟电路、数字电路里都有大量的晶体管(transistor)。晶体管是大名鼎鼎的贝尔实验室的几位工程师发明的,晶体管是20世纪最伟大的发明之一,它奠定了信息时代的硬件基础,奠定信息时代的软件基础的是香农(Claude Elwood Shannon)的信息论。发明晶体管的几位工程师都获得了诺贝尔物理学奖,发明集成电路的基尔比(Jack Kilby)也获得了诺贝尔物理学奖

晶体管在模拟电路里放大信号,在数字电路里当开关

晶体管学起来还是非常复杂的,学过模拟电子技术的同学肯定会赞同我,这里就简短地介绍一下晶体管,不讲深奥了:

上面是常见的晶体管的电路符号

晶体管可以被看作受控电源,从上面的几种晶体管里抽出一个,它的各个引脚的名称如下图所示

对于上图这种晶体管,它的Gate的电压高于Source的电压VGS能控制Drain到Source的电流IDS

举个例子:下图电路(在下一节)中只有2条电流通路,分别被用橙色、蓝色箭头标出。对于上图中的这种晶体管,电流无法从Gate流到Drain、无法从Source流到Drain,VGS能控制Drain和Source之间的导电通道的"宽度",越"宽"的话则允许流过的最大电流越大

在模拟电路里,"宽度"可能的取值非常多,多得让你对学习丧失信心

在数字电路里,"宽度"的取值范围只限于2个值:0和最大

 

 

 

3.逻辑门、电平

下图电路中电池负极接着地(ground,简写为GND),当然这个"地"不一定是地球表面,GND是用来定义电路中电压为0V的点的

高中物理学过"定义无穷远处电势为0V"或"定义大地电势为0V",下图电路也类似地定义电池负极电压为0V

如果说某个点的电压是多少伏,那就是在说那个点的电压相对于0V点的电压;

如果说某个元件比如下图中电阻的电压,那就是在说这个电阻两端各自对0V点的电压的差值

 

  • 让上图电路中的信号源输出电压够小比如0V,那么"宽度"就能取到0,蓝箭头指示的电流就不存在了,那么Drain的电压等于电池的正极电压3.3V
  • 让信号源输出电压够大比如3.3V,那么"宽度"就能取到最大,蓝箭头指示的电流也能达到最大,那么这时Drain的电压就接近电池负极的电压0V

这样就实现了通过信号源的电压来控制蓝色箭头指示的电流通路的通断,晶体管的作用是不是很像开关呢

这个电路实现了将输入取反的功能,这样的数字电路叫非门,上图的非门存在一些问题比如输出0V时有电流流过电阻,那么电阻会消耗电能

以后就用下面这个符号来表示非门,左边输入,右边输出

在上面的非门的例子中也可以把0V称作低电平或者用0表示,把3.3V称作高电平或者用1表示

实际上"不会导致数字电路错误工作"的低电平、高电平的电压值并不固定,而是在一定范围内,比如上面的非门的低电平范围可能是-0.3V~+0.5V、高电平范围可能是2.8V~3.5V

 

如果把晶体管换成耐压值更高的、电池换成5V的,那么低电平、高电平的范围也可能变化,可能高电平的范围变成4.5V~5.2V

低电平是0、高电平是1的数字逻辑称作正逻辑,否则称作反逻辑,大多数情况下选用正逻辑,如果用反逻辑,得特别说明

电平的电压范围是这种电平规则的特征,比如高电平是3.3V、低电平是0V的电平规则叫CMOS电平,高电平是5V、低电平是0V的电平规则叫TTL电平

如果要实现不同电平的互联,可能需要进行电平转换,否则可能烧毁晶体管或者晶体管不认前级发来的高电平

 

还能用晶体管搭建出与门、或门、异或门等等,这些电路叫逻辑门电路。这里没必要画出其它逻辑门的电路图了吧,就算画了,你也不一定看,总之你知道确实有这些逻辑门就行,你设计不出来不代表那些天才工程师、科学家们设计不出来

与门:可以多输入,所有输入都为1才输出1

或门:可以多输入,只要有输入为1就输出1

异或门:只能2输入,2个输入不同则输出1,否则输出0

这些简单的逻辑门电路能构成功能更复杂的电路,比如加法电路

 

这个加法电路可能有2组输入、1组输出,每组输入可能有8位,输出可能也是8位,输入2个二进制数,自动输出相加结果

你可能会问如果8位不够表示相加结果怎么办?怎么算减法?怎么计算负数的运算?这些问题并不影响你理解本文后面的部分,你要是有兴趣的话,那就稍后去自行搜索这些问题吧

 

你知道能用逻辑门搭出加法器就行,你是否能搭建出加法器并不影响你认识单片机

类似地,用逻辑门还能设计出数据选择器、编码器、译码器等等逻辑电路

 

这个加法器既然是用逻辑门构成的,那么也可以用0、1来表示它的输入、输出,这种情况下用二进制表示数具备天然的优势

 

 

 

4.数制、存储空间单位

刚才提到了二进制(binary),二进制是满二进一的计数方式,我们最常用的数制是十进制,可能是因为人类有10支手指吧,我在幼儿园就学过用手指算100以内的加减法,用的数制是十进制

二进制的10读作"一零"而不是"十",也不是"二",它等于十进制的2

为了区分二进制的10和十进制的10,在二进制的10末尾加上字母"B"(就是binary的第一个字母),以后看到10B就知道是二进制数一零了

 

一个不算太大的十进制数转成的二进制数可能很长,于是有了十六进制(hexdecimal),十六进制比十进制更容易转换成二进制,你手动计算的时候就能体会到了

十六进制的10得与十进制的10有所区别,人们在十六进制数的开头加上"0x"或者在末尾加上字母"H"(就是hexdecimal的首字母)

 

十六进制要满十六进一,十六进制的0~9用数字0~9表示,10~15用字母A~F表示(不区分大小写)

15=1111B=0xF=FH,这4个数分别是十进制、二进制、十六进制、十六进制,它们都等于人类一只手的手指的数量的3倍

 

取二进制数的英文单词binary digit的首尾组成一个新词bit,用bit表示二进制数的长度,比如1001B占4bit,4bit简称4b(注意是小写b)

8bit=1Byte,1Byte简称1B(注意是大写B,但这样就和二进制数1B产生歧义了,但是只要Byte数大于1就没有歧义了,比如4B,二进制数中怎么可能出现4呢)

1024Byte=1KibiByte=8Kibibit,KibiByte简称KiB。好像和很多同学印象中Windows操作系统用的KB不一样啊?你可以去查ISO(国际标准组织)的文件,Ki表示210,K表示1000,Windows很久以前用错了单位,于是将错就错这么多年了

从Ki开始,每乘1024就依次得到新单位Mi、Gi、Ti、Pi、Ei、Zi、Yi、Bi

 

 

 

5.组合逻辑电路、时序逻辑电路

上面提到的数字电路的输出都只与输入有关,这样的数字电路叫组合逻辑电路

还有一类数字电路,它下一时刻的输出不仅与当前时刻的输入有关,还与当前时刻的输出有关。举个例子比如秒表,它在计时的时候:

  • 这个时刻显示的秒数是3,那么1秒后它显示的秒数得是4,再下1秒后显示的秒数得是5,这是下一时刻的输出与这个时刻的输出有关;
  • 如果你按下了清零键,那么下一时刻它显示的秒数就是0了,这是下一时刻的输出与这个时刻的输入有关

秒表电路需要知道到底有没有"过了1秒",可以每隔1秒发一个高脉冲或低脉冲或上升沿或下降沿让电路知道过了1秒,这个周期为1秒的信号就是这个时序逻辑电路的时钟信号

这种下一时刻的输出与这一时刻的输入输出都有关的逻辑电路叫时序逻辑电路

时钟信号由振荡电路产生,时序逻辑电路的内部构造比组合逻辑电路的更复杂,设计振荡电路也有一定难度。这里就不打击大家的阅读兴趣了

 

现在你具备了一定的知识储备,那么来尝试设计你的单片机。会设计单片机的话,也能大致知道设计电脑处理器的流程

 

 

 

6.最小系统

设计单片机

  • 肯定会需要用到时钟信号,因为你的单片机的输出肯定不能只与输入信号有关,否则功能就太简单了
  • 肯定具备复位功能,比如上电复位,否则每次上电后你无法知道单片机里的这堆数字电路到底处在什么状态、这个状态是否能正确执行指令
  • 肯定有电源

上面提到的时钟信号、复位信号、电源,再加上单片机,就能组成单片机的最小系统

可以这么直观地理解最小系统:用你设计的单片机制作产品,无论产品是什么,时钟信号、复位信号、电源、单片机是肯定会被包含在产品里的,这4个都具备时产品才可能正常工作,只要缺少一个,那么产品总有个时候会不能正常工作;任何产品都能被看作是在最小系统上继续搭建而成的

 

 

 

7.指令集

你得让单片机获取你下达的指令并执行:这个"指令"是什么?以什么形式存在?怎么向单片机下达"指令"?

你的单片机的输出是由单片机内部的数字电路处理得到的,单片机里可以有很多具备不同功能的数字电路比如加法器、定时器等等,完成一件工作比如计算一个算式1+2×3+2×5肯定会用到一些步骤,计算这个算式的步骤肯定有先后顺序,单片机可能会这么做:

  • 把3送入加法器的输入1
  • →把3送入加法器的输入2
  • →暂存加法器的输出,记为数A
  • →把5送入加法器的输入1
  • →把5送入加法器的输入2
  • →暂存加法器的输出,记为数B
  • →把数A送入加法器的输入1
  • →把数B送入加法器的输入2
  • →暂存加法器的输出,记为数C
  • →把数C送入加法器的输入1
  • →把1送入加法器的输入2
  • →加法器的输出即为计算结果

你得设计一个"听话"的电路来完成上面的指令,还得设计一些存储器来暂存中间结果

 

你向单片机传达指令肯定是用二进制串表示的高低电平,因为数字电路只认高低电平,假设你设定0000表示算加法,那么你希望这个"听话"的电路:

  • 收到0000,知道要把接下来的2个输入都扔给加法器了
  • →时钟信号来了个上升沿,于是把这次收到的二进制串扔给加法器的输入1
  • →时钟信号又来了个上升沿,于是把这次收到的二进制串扔给加法器的输入2
  • →时钟信号又来了个上升沿,于是把加法器的输出取出来放到某个存储器里

 

是不是觉得这个"听话"的电路难以设计?没事,你不会设计不代表天才的工程师们、科学家们不会设计,假设你请来一位大神同学Q,向Ta讲述你的要求:

  • 收到0000之后把再收到的2个二进制串扔给加法器的2个输入,然后把加法器的输出存在一个地方
  • 收到0001之后,再收到第1个二进制数假设是A,再收到第2个二进制数假设是B,把A连加B次,算完后把结果存在一个地方
  • 收到0010之后把再收到的第1个二进制数假设是A扔给加法器的输入1,把再收到的第2个二进制数假设是B进行某种操作后扔给加法器的输入2,加法器的输出必须是A-B,把结果存到一个地方
  • 收到0011之后……
  • ……

 

看你刚才提的要求:收到0000、0001等之后就做某某事,这就是你设计的一套指令集架构(Instruction Set Architecture,简称ISA)

 

 

 

8.内核、微架构

你如果请另一个大神设同学W也设计一个"听话"的电路来实现你的指令集,那么同学W设计的电路不一定和同学Q设计的完全相同,甚至W设计的电路可能效率更高、有些指令执行所需的时钟周期比Q设计的电路需要的更少

 

Q和W萌生了独自创业的想法,他们都认为用这个电路继续搭建出完整的单片机还得烧更多钱,不如以后只设计这种电路算了,把设计好的方案卖给设计芯片的公司,这样大家都能专注于各自的领域,最终产品的性能可以更优越,大家也都能节省精力

为了方便芯片公司,Q和W把他们设计的电路继续完善,比如集成加法器等等

Q和W完善之后的东西叫内核(Core),就是单片机内核、电脑处理器的内核,他们实现内核的方式叫微架构(Microarchitecture),他们的内核都能实现你设计的指令集,他们给自己设计的内核设定独一无二的名字

 

现实世界中大名鼎鼎的ARM公司就像大神Q、W,ARM公司专注于设计内核,把内核设计方案卖给大名鼎鼎的ST、ADI、TI、NXP、华为海思、高通、苹果等等芯片设计公司

 

 

上图中的Cortex A53、M3是ARM公司设计的2款内核。发现上图中有很多东西是你不认识的?没关系,你总会认识的

 

ARM公司现在设计的内核的名字都是Cortex ??,第1个问号是"A"、"R"或"M",第2个问号是十进制数,可能是1位也可能是2位十进制数,以后可能会是更长的十进制数。例如上图中的Cortex A53、Cortex M3

高通买来Cortex A53的授权,开发出骁龙625;ST买来Cortex M3的授权,开发出STM32F1系列

骁龙625常被称为片上系统(System on Chip,简称SoC)或者微处理器(Micro Processor),常被用于手机等等;STM32F1系列常被成为单片机(Siingle Chip),被广泛应用于家电、玩具等等

由于A53和M3的性能、功耗、成本的差异,人们对它们构成的芯片的称呼不同,芯片的用途也不同

 

也有公司从内核到芯片全都自行设计,比如大名鼎鼎的Intel、AMD、Nvidia

 

绝大多数同学学习开发单片机程序是从51单片机开始的,51单片机的内核的名称是8051,由Intel公司开发

8051内核的性能比Cortex各系列的性能都低很多,但用8051内核开发的单片机更便宜,用起来更简单

 

回来继续设计你的单片机:

 

 

 

9.存储器

你使用内核时,要是得全程手动地

  • 把内核的各个输入拨到合适的电平
  • →按一下时钟信号按钮,告诉指令译码器你传完指令了
  • →再把内核的各个输入拨到合适的电平
  • →再按一下时钟信号按钮,告诉内核你传完第1个操作数了
  • →再把内核的输入拨到合适的电平
  • →……

这不得烦死你,这样算算式比你手算还慢

 

于是你想把指令存起来,让时钟信号能自己不断地跳变,你只需把指令编好存起来就行

你需要开发存储器,你再找来一位大神E,向Ta提要求:

  • 存储器能存储海量的0、1状态
  • 有1个读写控制引脚,比如高电平表示写数据,低电平表示读数据
  • 每8bit为一个存储单元,给存储单元们编号
  • 有7根地址线,给它们输入7个bit就能选中这7个bit组成的二进制数编号的存储单元
  • 有8根数据线,读数据时8根数据线输出选通的存储单元的8个bit,写数据时存储器把8根数据线上的8个bit写入存储单元
  • 输入存储单元的编号到地址线,给读写控制信号输入读,那么存储器的数据线输出指定存储单元里的内容
  • 输入存储单元的编号到地址线,给读写控制信号输入写,输入你想写入的二进制串到数据线,那么存储器的指定存储单元的内容就更新成你写入的二进制串
  • 有1个高电平使能引脚(使能(enable),可以理解为使……能……,比如高电平使能,那么使能脚为高电平时则存储器能工作,否则不工作,高电平就叫使能脚的有效电平)

  • 使能脚电平无效时存储器的数据线呈高阻态(高阻态也称Hi-Z,如果你认识"高"的英文单词、知道电学用Z表示阻抗,就不难理解Hi-Z就是高阻态。稍后让你直观理解高阻态)

 

可以这么理解高阻态:

上图电路中如果电阻阻值越大,那么测试点的电压就越接近信号源的输出电压,当阻值无穷大比如开路时,测试点的电压就是信号源的输出电压

 

存储器不工作时数据线呈高阻态的好处:

假设一个电路中存储器A、B都有8根数据线D0~D7,把它们各自的Dx(x是0~7的整数)接在一起,当只有一块存储器假设是存储器A工作时,那么Dx相连的点的电压就是存储器A的数据线的输出电压

至于为什么要这么连接存储器A、B,这是大神Q、W给你建议的,他们把内核和存储器联调成功后,你就知道为什么要这么接存储器A、B了

 

大神E经过不断研究发现(现实世界中也是如此):

掉电后数据会丢失的存储器的读写速度非常快而且造价高昂,可以每次只读写1个存储单元,想读写哪个单元就读写哪个单元。这样的存储器被命名为随机访问存储器(Random Access Memory,简称RAM)。大神E的初代产品叫静态随机访问存储器(SRAM),是个组合逻辑电路;后来大神E又研发出容量更大、速度更快的SDRAM、DDR等,都是时序逻辑电路

掉电后数据不丢失的存储器有的只能写一次,有个可以用紫外线照很久后擦掉内容后再写,有的可以用电飞快地擦除后再写;

大神E对掉电后不丢失数据、可以用电擦除内容的存储器的研究成果如下:

命名 特性

EEPROM

(Electrical Erasable Programable Read Only Memory)

可读写任一存储单元

集成度很低

速度慢

NOR Flash

可只读任一存储单元

存储区分块,比如每4096个存储单元为一个块,各块内部连续、外部相邻

如果要写入的存储单元的内容是0xFF就能直接写,否则要把存储单元所在的块的所有存储单元写为0xFF再继续操作

集成度高些

成本低些

NAND Flash

不能读写任一存储单元,每次得至少读或写一定数量个存储单元

集成度非常高

成本非常低

你可能会问:EEPROM是可写的,为什么叫ROM?等下你会分析该如何把掉电不丢失数据的存储器用在你的单片机里,分析的时候你就知道了

这么一看,NOR Flash、NAND Flash也属于EEPROM的行列,但是现在EEPROM一般指上表中能随意读写任一存储单元的低集成度的存储器

 

你肯定需要用到掉电不丢失内容的存储器,但那些只能写1次、需要紫外线擦除好久的肯定不用

如果采用EEPROM,单片机可能无法小巧玲珑

如果采用NAND Flash来存储你的指令、数据,那么会增加内核的复杂度、严重浪费这样的存储器的存储空间、严重拉低内核的执行效率

那就用NOR Flash吧,能做到读任一存储单元,虽然做不到写任一存储单元,但你可以让单片机在运行时尽量只读取NOR Flash的内容而不写入NOR Flash(现在知道为什么叫ROM了吧),让需要被不断读写的数据躺在RAM里。SRAM控制简单,就用它了

 

把这些存储器塞到单片机里面,这样才"单片"嘛

 

 

 

10.总线、寻址

现在Q、W修改他们设计的内核,让内核能自动地取你存储的指令和数据、执行完后就继续取下一条指令及其数据

Q、W对内核做出如下修改:

  • 伸出8根线用于选择存储器里的存储单元,把这8根线命名为地址总线
  • 伸出8根线用于读写存储器里的存储单元,把这8根线命名为数据总线

假设你选用的NOR Flash、SRAM的数据线都有8根、地址线都有7根,还各有1个名为EN的使能引脚(假设高电平有效),像下图这样连接内核和存储器

上图中内核最多能访问到28=256个不同的地址,可以这么说:这个内核的寻址范围是0x00~0xFF(把0x0非得写成0x00,目的是体现出地址总线是8位的)

如果用二进制数表示内核的地址总线上的值,高位(D7最高,D0最低)在前低位在后,那么显然内核访问地址0000 0000B~0111 1111B就能访问到SRAM里的内容、访问地址1000 0000B~1111 1111B就能访问到NOR Flash里的内容

这样做的好处显而易见:内核用8根数据线就能既读写NOR Flash又能读写SRAM,而不是用16根数据线分别接到NOR Flash和SRAM各自的8根数据线

现在你知道为什么要让存储器在使能电平无效时让数据线呈高阻态了吧

 

Q、W修改复位功能,让内核在单片机掉电再上电后从地址0x80开始取指令、取数据、执行、取指令、取数据、执行……

可以这么实现:

  • 用8bit的存储器(假设命名为IP(Instruction Point),指令指针)记录内核正在执行的二进制所在的地址
  • →内核读出IP存储器记录的地址里的二进制串并执行,读出的二进制串可能是指令,也可能是指令需要的数据或数据的地址
  • →每来一个时钟周期就让IP存储器器记录的地址+1,但是内核正在执行指令时不能让IP的内容变化,否则内核会漏执行、错执行指令

 

前面列举算加法的步骤时用了几个存储器来暂存加法的计算结果,这里又用几个存储器来指定内核执行的指令的地址,这些存储器起到了反映、控制电路的运行状态的作用。把这样的存储器称为寄存器

 

你以后把编写好的程序从NOR Flash的首地址开始存储,内核就能按照你的意愿工作了

 

内核要在复位后从0x80处开始执行,NOR Flash的读写速率比SRAM的更慢,假设你写了一份占用0x40个存储空间的代码,你想让程序运行得更快些,实现的方法之一是让内核从读写更快的SRAM取指令、取数据,那么你可以:

  • 把你写的占用0x40个存储空间的代码放在地址0xA0~0xDF(在NOR Flash)
  • →在0x80(在NOR Flash)处放上实现这个功能的指令:把地址0xA0~0xDF处的内容复制到地址0x00~0x3F(在SRAM)、以后从0x00开始不断取指令、取数据、执行

 

 

 

11.外设

如果想让单片机能具备更多功能呢?那就塞入更多电路到单片机里

  • 塞入IO口控制器,这样可以控制单片机的任一引脚的功能,比如用来输出高低电平来控制LED的亮灭
  • 塞入模数转换器ADC,这样单片机就能感知外面的物理世界的变化
  • 塞入存储控制器,这样就能非常方便地使用外接的更大的存储器
  • ……

这些被塞入的电路模块叫外设,由于它们都在单片机内部、在内核外部,所以也叫片内核外外设

有的外设比如LED,它并不在单片机内部,所以叫片外外设或者板载外设

有的外设在内核里,比如加速内核执行指令用的一些外设,这样的外设叫核内外设

 

 

 

12.寄存器

内核怎么控制众多的外设呢?给它们每个分配一个内核?这样太复杂了,外设需要实现的功能一般很单一,那就给外设们分配一些寄存器吧

  • 让内核的地址总线能定位到片内外设们的寄存器
  • →把"用于控制片内外设的寄存器"的值作为“控制片内外设的逻辑电路”的输入
  • →片内外设也能把它的计算结果、工作状态写入为它分配的数据寄存器、状态寄存器
  • →内核读取片内外设的数据寄存器、状态寄存器就能知道片内外设的工作结果和状态
  • 有的板载外设也有寄存器,但单片机内核可能无法直接用地址总线找到板载外设的寄存器,可以和板载外设做个约定:
    • →单片机以某种规则把板载外设的寄存器的地址(是板载外设用它自己的寻址方式寻到的地址)、单片机想读还是写板载外设的寄存器的信息发给板载外设
    • →板载外设知道单片机想访问的寄存器地址
    • →单片机通过这种方式访问板载外设的寄存器

至此你再次知道了那个非常重要的思想:寄存器能反映、控制单片机的运行状态

这里的片内外设寄存器挂在内核的地址总线上,但是寄存器并不总是需要挂在地址总线上,比如我们用的电脑的处理器Intel、AMD公司的CPU,它的内核的很多寄存器就没挂在内核的地址总线上,它的一些指令能读写这样的寄存器

 

拿大名鼎鼎的STM32单片机来举例:

取自STM32F103x8、F103xB的数据手册Rev. 17第34页的内存映射图(memory map)

STM32的地址线有32根,这32根地址线能访问到232个不同的地址,上图指出了每个地址范围对应哪个外设的寄存器、哪个存储器的内容,比如0x20000000对应单片机内部的NOR Flash的首地址,0x40012400~0x40012800对应ADC1的寄存器

232个地址并非每个都会被用到,那些没被用到的地址被标记为Reserved(保留)

如果要了解某个外设的寄存器的每一bit的意义、名称,那就去查参考手册上描述那个外设的寄存器的每一bit的功能的部分

 取自STM32F101、2、3、5、7  参考手册Rev. 20第237页

  • 上图中的寄存器有32bit,STM32的32bit的寄存器会占用32÷8=4个地址(即STM32把每8bit作为一个存储空间来编址)
  • 这32bit组成的寄存器的名字是ADC_SR
  • bit31~bit5是Reserved的,不使用
  • bit4~bit0都有各自的名称和用途
  • 图的上部标注了Reset value(复位值),即单片机复位后这32bit寄存器的值
  • 图的上部有个Address offset(地址偏移)

 

可以这么理解Address offset:

  • 假设芯片设计者把地址范围0x10~0x1F总计16个地址分配给某个外设的寄存器,那么0x10就是这个外设的寄存器地址基址
  • →这16个地址中的第3、4个地址分别是0x12、0x13,假设这2个地址上的寄存器被命名为REG
  • →那么名为REG的寄存器的Address offset就是0x12-0x10=0x02

编写程序读写外设的寄存器,就能控制外设

 

 

 

13.中断

假设ADC等一众外设工作得远比内核慢,那么内核想要实时了解各外设的工作进度和结果,该怎么操作呢?不断地读外设们的寄存器?这样确实可以,但是太浪费时间了,内核宝贵的性能都被浪费在不断查询外设们的寄存器

能不能让外设在完成工作后主动告诉内核并让内核迅速处理相关事情?当然可以,这种技术叫中断

 

你自己也具备中断功能,举个例子,假设你爸妈不让你玩电脑游戏,但是最近你又必须得上网课,你准备在上网课时偷偷摸摸地玩游戏

  • 匹配玩家的过程中,你去看剧了,但是如果听到耳机里游戏开始的声音,你立马暂停视频→去打游戏→游戏结束后再次匹配玩家的同时去看剧
  • 如果听到你爸妈走向你的房间的脚步声,那么不管你在打游戏还是看剧,都得立马恢复到上网课的界面,等爸妈走了再继续看剧或者玩游戏

(要认真学习,劳逸结合,上网课就认真上网课哈)

 

上例中,游戏开始的声音、爸妈走近你的脚步声都是中断信号,它们都能打断你当前正在做的事让你去做其它事,等做好其它事了,你再回到刚才被打断的地方继续

而游戏开始的声音让你转而去做的事(玩游戏,是游戏开始的声音这个中断信号的中断服务函数)能被爸妈走近你的脚步声打断,然后你得立即去做听到爸妈的脚步声之后该做的事(看网课,也是中断服务函数),这里爸妈的脚步声这个中断信号具备更高的优先级,本例中的2个中断信号实现了中断嵌套

 

内核可以这么实现中断:

  • 外设们伸一根中断信号线到内核
  • →外设遇到需要内核来处理的事情比如ADC发现根本没有需要转换的模拟信号输入给它时就在它伸向内核的中断信号线上产生有效的信号比如上升沿
  • →于是内核迅速记录手头的工作所在的地址并停下手头的工作
  • →内核去处理ADC那边的事(事的内容、地址由编程人员指定)
  • →处理完ADC的事之后接着做刚才被打断的工作

Q和W也觉得中断技术非常重要,于是把中断控制器集成到了他们设计的内核中

 

 

 

14.烧录/下载

假设你使用电脑为你的单片机开发程序,那么你可以设计一个专门用来连接电脑和单片机的NOR Flash的外设放到单片机里,通过这个外设,电脑把程序发给单片机,内核把外设收到的二进制串写到单片机内的Flash

比如很多同学学习单片机知识用的第1块单片机STC89C51就在单片机里集成了名为通用异步收发器(Universal Asynchronous Receiver Transmitter,简称UART)的外设,它负责单片机与单片机之间、单片机与电脑之间以符合UART的规则的方式通信。STC公司开发了运行在Windows的专门用来向STC单片机写入程序的软件STC-ISP,STC单片机的UART外设收到某串二进制后就产生一个中断告诉内核把稍后UART接到的二进制串写入NOR Flash

这个过程叫烧录或者下载

 

在很久以前,给单片机的存储器写入程序需要高压,一不小心就烧毁了单片机,所以有了"烧录"这个名称

单片机从电脑获取指令,像不像你通过因特网从服务器下载游戏?所以给单片机的存储器写入程序也可以叫"下载"

 

 

 

15.汇编语言

有没有觉得总是用二进制串给单片机下达指令非常麻烦?不如用助记符代替这些二进制指令吧,于是汇编语言产生了

内核肯定不认识你创造的助记符,得用软件把助记符转成内核能直接执行的二进制,实现这个功能的软件叫汇编器(assembler)

 

汇编语言有个很大的缺陷:过于依赖内核的工作流程、存储器的布局

你用汇编语言写程序的话,得先打草稿来划分存储空间,比如地址0x12~0x21用来暂存计算的中间结果,地址0x80~0x8F用来存实现某个功能的指令,你希望掉电后依然能存储用户设定的一些变量,那么得在Flash里找一块空间用来存这些变量……

把用汇编语言写的程序移植到其它内核、其它存储器布局上可是个天大的工程,不如创造一种接近人类的语言、不依赖于内核和存储器的编程语言吧,C语言就是这种语言的代表之一

 

 

 

16.C语言

内核更不可能认识C语言,所以需要用软件把C语言代码转成内核能直接执行的二进制,需要多个软件来实现:

  • 编译器(compiler)
    • 把C语言代码转成单片机能理解的二进制,还没完:都说了C语言可以不依赖存储器布局、你可以在C语言代码里不写任何一个存储器地址,编译器得到的结果并不包含任何一个存储器地址,所以单片机肯定不能执行编译器的产物
  • 连接器(linker)
    • 告诉连接器哪个地址范围用来存指令及其数据,哪个地址范围用来存变量,连接器来完成地址分配的工作。你也可以在C语言代码中告诉连接器把某些数据或指令放到哪个或哪段地址上
    • C语言代码里可能有一些指令并不会被执行,可能有一些数据并不会被用到,连接器可以只挑出会被执行的指令、会被用到的数据来连接,这样最终生成的一份二进制的体积就能尽量小

如果你在Windows操作系统上用Visual Studio、Code::Blocks、Dev C++等IDE写C语言程序,那么连接器得连接处能让Windows操作系统调用的可执行(executable,Windows下这种文件的扩展名是.exe)文件,这个步骤与连接处单片机能直接运行的一份二进制有所不同,你去了解一下Windows操作系统执行EXE文件的步骤就能大致猜出到底哪里"有所不同"了

 

实际上C语言的编译器可能会在编译过程中"夹带私货",例如有个外设会把它的最新运行结果存在某个地址的寄存器里,你用C代码不断读出那个寄存器的值,如果发现这个值具备一定特征就做某事

编译器发现你根本没写修改那个地址下的内容的代码,编译器可能认为那个地址下的内容全程都不会变化,于是把你用于"不断读出"的代码改成只读出一次再不断判断这个"只读出一次"的值是否符合特征

这就是C语言编译器的优化功能,很多时候这样的优化确实很有价值,但也有时候这种优化反而会把事情搞砸,你可以更改编译器的优化等级设定,也可以在代码里告诉编译器不许优化某些步骤

 

所以还是看看内核实际执行的二进制串对应的汇编指令更能帮助你准确判断内核的行为,这个过程叫分析反汇编内容,下一节会提到反汇编

有了C语言,开发、调试单片机程序就轻松多了

 

 

 

17.调试

假设写了个非常长的程序,虽然单片机能执行,但是执行的最终结果却是错的。编程人员特别想让单片机慢点执行,以便观察到单片机执行完关键步骤后的结果是否正确,这些"结果"可能在寄存器,可能在SRAM,也可能是某个板载外设的工作状态

上述的过程叫调试(debug)

Q和W也认为调试功能非常重要,于是把调试系统集成到他们设计的内核里

 

再拿大名鼎鼎的STM32单片机来举例:

有一种叫硬件调试器的电子系统,支持STM32的硬件调试器有很多种,比如ST-Link、J-LInk、CMSIS DAP-Link等等

硬件调试器能对接单片机和电脑,通过硬件调试器,能下载程序到单片机,电脑也能控制单片机控制单片机

  • 一步步地执行指令(即单步执行)
  • 执行到某条指令前停住(即设定断点)
  • 实时查看和修改某些寄存器、变量的值
  • 查看C代码被编译成的二进制代码及其汇编形式的代码
  • ……

上图是用Keil μVision MDK-ARM(这是个集成开发环境Integrated Development Environment,简称IDE,包含代码编辑器、调试器、编译器、连接器等等)+ST-Link硬件调试STM32F407ZG的界面,上半部分是反汇编(Disassembly),下半部分是源代码

  • 看上图中更靠上的青色、绿色、红色方框:
    • 地址0x0800 7318和0x0800 7319处的2个字节0xB530被内核当作一条指令,这条二进制指令对应的助记符是PUSH (r4-r5,lr)
  • 再看图中的紫色方框:
    • 图中下半部分对应一个C源文件,其第135行的内容出现在了紫色方框中,
  • 再看上图中更靠下的青色、绿色、红色方框:
    • 刚才说的C代码对应的二进制指令被存储在地址0x0800 7310~0x0800 7320+(4-1)即0x0800 7323

Keil μVision MDK-ARM的调试器还有很多很方便的工具,比如能实时查看某个变量的值的Watch、能查看正在执行的指令是怎么被一路调用来的调用栈Call Stack等等

 

 

 

现在你的单片机已经很完善了,成功面世

但是有客户在用你的单片机设计产品时遇到了一些问题:

  • 单片机做某些事情非常慢
  • 你的单片机的寄存器太多,如果要用到很多片内外设,那么在初始化外设时得写非常多的寄存器,容易写错
  • 单片机同时处理多件事时不够流畅

以下3节分别解决上述3个问题:

 

 

 

18.软件实现、硬件实现、通用计算、ASIC、超频

那些让单片机工作得很慢的代码可能频繁用到某些操作,单片机可能得执行很多条指令才能完成这样的1次操作,比如没有内置乘法器电路的单片机算整数的乘法得用连加

连加就是对乘法的软件实现,用乘法器电路算乘法就是硬件实现

 

再比如某个板载外设需要以某种规定与单片机通信,编程人员可以分析那个板载外设的通信规则之后,编写程序让单片机的某些引脚在适当的时刻产生适当的电平来与板载外设进行通信,编程人员得非常仔细地分析这种通信规则,一个细节都不能出错

如果这种通信规则非常常用,那么单片机厂商可以用电路来实现这种通信规则。把这个电路做成一个片内外设,单片机内核给这个片内外设的控制寄存器写入控制字,之后内核只需把要发送的内容传给这个外设,这个外设就能在不需要内核干预的情况下正确地将信息发送出去

事实上有很多通信规则已经被硬件化,比如SPI、SAI、I2C、I2S、USART、USB、HDMI、PCIe、SATA等等

用硬件电路实现通信协议的优势有:

  • 最大通信速率更快
  • 编程人员的编程、调试代价更小,如果编程人员水平不足,那么这条可能不成立

用软件实现的优势当然是金钱成本更低

但是硬件实现并非总是优于软件实现,例如机械按键的延时消抖,学过单片机编程的入门例程的同学有感触,没学过的话以后就去学吧

 

调试也能不用到硬件调试器,而纯软件实现,这样的调试叫模拟器调试

 

如果你对电脑有点研究,而且还必须要玩高画质、高帧率的游戏,那么你买打游戏用的电脑时肯定会选带独立显卡的

打游戏时,独立显卡专注于计算游戏里炫酷的画面,CPU专注于其它事情

 

举个例子:

  • 假设把大型3D游戏的画质调到最低的等级,和敌人激战时独立显卡和CPU的负载率都很低,都不到10%
  • 如果禁用独立显卡,那么这个游戏的画质、帧率严重下降,玩游戏时CPU的占用率也极高

在这个例子中,CPU拼死也干不好独立显卡轻轻松松能干好的事

 

独立显卡设计者画大力气用硬件电路实现计算显示画面的算法,可能独立显卡一条指令能做好的事CPU需要成千上万条指令才能做好

其实计算显示画面的过程并没有很多复杂的逻辑运算(就是判断对错、如果怎样就做什么等等),然而简单粗暴的数值运算却非常多

在上面的例子中,有4个核心的CPU就像是4个博士毕业生,有640个(真的可以有这么多)核心的独立显卡就像640个小学生

  • 如果他们比赛做640道10以内的加减法,那么640个小学生确实可以秒杀4个博士毕业生
  • 如果他们比做博士毕业生的专业课试题,那么1个博士毕业生就能秒杀640个小学生

 

电脑CPU的定位是做通用计算,通过支持复杂的指令集,让编程人员编写复杂的程序能实现复杂的功能

独立显卡是计算显示画面(其实不限于计算显示画面)的专用集成电路(Application Specific Integrated Circuit,简称ASIC),通过硬件电路实现了可以非常高效地实现某些功能,而在其它功能的实现上并不一定强于CPU

 

刚才的例子也能说明有的工作通过并行执行能极大地提升效率,比如算10以内的加减法

但有的工作却不能,例如假设你和你的一个同学都喜欢玩第一人称射击游戏,但你们的技术都不行,于是你们合作:一个人控制鼠标,一个人控制键盘,你们"合体"的效果可能并没多少提升甚至更差

在这个例子中提升玩第一人称射击游戏水平的方法应该是一个人控制一台电脑不断练习枪法、走位、意识、队友配合、技能Combo等等,类比到单片机和电脑处理器就是提升执行速度,最常见的方法之一就是超频

 

内核是被时钟信号指挥的,如果时钟信号跳变得更快,那么内核就会运行得更快。内核收到的时钟信号的频率叫内核的主频,把主频提升到高于厂家建议的最高主频即超频,但是不能无限制提超频,超频会让内核运行得更不稳定甚至烧毁内核

现在的电脑CPU已经很智能了,比如Intel的酷睿CPU能睿频,即任务繁重时提升主频来提升性能,任务少时降低主频来降低功耗

 

 

 

19.库函数

通过写单片机众多的寄存器来设置片内外设确实容易出错还费时费力,可以把配置寄存器这种繁琐的工作用C语言函数来实现

例如ST公司为STM32系列单片机编写了丰富的库函数,用户可以用C语言调用库函数来配置STM32的外设寄存器

库函数的优势就是优秀的C语言代码的优势:可以用英语把代码读出来,听你这样读代码的人(如果Ta了解STM32的功能、听得懂英语)能轻易听懂你想如何设置STM32的核内外设和片内核外外设

劣势就是执行效率更低,你试试把用库函数初始化单片机的C代码反汇编就知道了:如果写寄存器只需要几行汇编指令,那么用库函数,得先用好多好多个RAM存储单元来暂存外设的某些功能的标志位,最终再把这些标志位做运算后赋值给寄存器

STM32的性能足够,所以牺牲这一丁点运行效率来极大地提升开发、维护效率是值得的

 

 

 

20.操作系统——存储管理、并发……

刚才说过,用汇编语言写代码需要打草稿划分存储空间,用C语言写直接运行在单片机内核上的代码虽不用打刚才的草稿,但连接器需要打这个草稿,这个草稿的存在使得最终得到的二进制代码不一定能正常运行在存储器地址分布与你的单片机的不同的单片机上

刚才还说过,内核的工作就是不断地顺序地取指令、取数据、执行,还能响应中断,如果有多件事需要内核同时做,那么内核只得不断地交替做这些事情,只要交替得够快,人类就以为内核在同时做多件事(这种调度不同任务的方法叫时间片轮转),但是这样可能有些很闲的任务浪费内核的大量资源,而有些很忙的任务无法被内核及时响应。编程人员需要设计复杂的方法来决定在不同情况下各个任务应该获得多长的时间片、一个任务的时间片结束后应该切换到哪个任务

(实际上内核在同一时刻能做多少件事由核心数决定,如果只有1个核心,那么在某个具体的时刻,内核确实只能做1间事。多核心说的就是你买电脑的时候商家告诉你的双核、四核、八核等等)

 

操作系统就能很好地解决上面的2个问题

 

没有操作系统的单片机、电脑叫裸机

优秀的操作系统能充分利用机器性能,包括但不限于

  • 动态地合理管理存储器
  • 合理地分配内核资源到不同任务、不同外设
  • 帮用户处理好稍有不慎就会导致内核异常的操作

 

对操作系统的详细描述已超出本文讨论的范围,你知道操作系统的优势就行

 

 


 

 

完结撒花,做个重点总结:

  • 晶体管能构成逻辑门,进而构成功能复杂的组合逻辑电路和时序逻辑电路
  • 内核能从存储器的某个地址开始不断地取指令、取数据、执行
  • 指令集规定某个二进制串让内核实现什么功能
  • 不同架构的内核能实现相同的指令集
  • 内核内部、内核外部单片机内部、单片机外部都有外设
  • 内核可以通过总线访问、控制存储器、外设等等
  • 寄存器能反映、控制电路的运行状态
  • 外设可以通过中断来打断内核的工作、让内核去执行其它工作、做完其它工作后返回执行刚才被中断的工作。中断技术提升了对内核性能的利用率
  • 汇编语言、C语言极大地方便了开发单片机程序
  • 调试功能能帮助你检查程序存在的逻辑错误
  • 功能可以被硬件实现或软件实现,各有优劣
  • 通用计算和专用计算各有优劣
  • 实际上单片机、处理器的总线位数、存储器地址线数据线位数等等并不一定都与本文中我让你用的方案一致,本文中的方案只是示例,以你实际使用的芯片的手册为准

原文出处:https://www.cnblogs.com/Humorize/p/12373543.html

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部