打印
[MCU]

这些单片机最基础的原理,你确定你已经吃透了?

[复制链接]
13511|64
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 青蓝pisces 于 2017-2-6 22:07 编辑

前言:

计算机原理是单片机/嵌入式开发中的重要知识。
通过了解CPU的运行、单片机(SOC)内部结构、外设寄存器和总线的相关原理等相关知识,可以达到知其然知其所以然的效果。
在嵌入式系统的规划、选型、驱动开发中做到胸有成竹、得心应手,快速适应日新月异的技术和复杂多变的需求。
本系列计算机原理技术分享**定位在干货的层次,
旨在从发展、原理、细节、实践等方面深入探讨嵌入式开发中涉及到的计算机原理。
同时,为了保证这些干货不会枯燥,每一个环节都会配合若干示例工程。
希望大家多多把玩这些工程,也欢迎联系我交流讨论,我会积极回复的。


本人QQ:877293640
QQ群:604532799





第一章  总线与外设

Lesson1:用古董机演示总线与外设寄存器
提到计算机原理(微机原理)就想到了大学课本里的8086处理器
8086用来学习计算机原理确实比较合适,因为它的总线结构是最“古老”、最简单直接的,
同时也是经典的总线,学原理当然要从简单直接的开始。

考虑到8086实体芯片已经成为收藏品,所以这里采用大家熟知的8051进行演示
8051的总线更加简单直接,除了为了节省引脚而采取的复用措施外,8051的总线几乎是一个最简单的并行总线。

那么什么是总线呢?从字面上来说,总线就是“一组线”。
程序编写者的角度来说,
狭义的总线(广义的总线后面继续讨论)就是CPU访问地址空间上任一存储单元所用的功能模块(希望你已经熟悉了指针或一种汇编语言)

CPU这个将军眼中,除了它的智囊团(CPU内部寄存器,直接通过指令访问,或硬件自动管理)外,
它还可以指挥一只军队,为了方便管理,它给每个小兵(存储单元)都编了个号(地址)
它可以通过总线这个传令兵了解士兵的情况(读数据),也可以给某个或某些士兵下命令(写数据)
传统的外设结构是基于外设寄存器的,设计者们很聪明地给它设计了总线接口
这样对于程序开发者来说主要关心外设寄存器的功能即可,至少通信问题简单多了。

总结起来,对于CPU来说,驱动外设的过程就是按特定方式访问总线地址空间上的某些外设寄存器
对于程序编写者来说,需要了解的内容就是外设的寄存器地址都是多少,分别代表什么意思,怎么访问可以达到效果
而这些大都被写在了芯片的手册里。
也就是说,了解了总线与外设的原理,就算是征服了各种外设芯片的手册的数字部分

对于这些寄存器,各大厂商的单片机使用了不同的技术进行抽象
最后我们使用到的是抽象后的成果,一般是固件库或者C语言衍生版本

为了兼容标准C语言,有些标准使用结构体指针常量对外设寄存器进行封装(ARM)
而很多厂商则使用编译器进行绑定,如C51是衍生版本的C语言,有标准C之外的关键字,
TICCS中对固件库中的代码进行地址绑定,从而让代表寄存器的结构体可以直接用“.”运算符进行访问。

接下来,我们在proteus中使用51单片机的总线来演示外设及外设寄存器的运行原理(希望你懂一些基本的数字电路原理)。
74HC573作为外设寄存器,配合其他逻辑芯片实现一个8位并行IO的外设。
最后写一个固件库作为编程接口,并使用固件库编写简单的回环测试程序。

对于这个8位并行IO口外设,有如下规划:
偏移地址0x0000    数据寄存器 Data     可读写    内容为8位数据
偏移地址0x0001    配置寄存器 Mode   可读写    其中最高位为1代表输入,最高位为0代表输出,其他7位保留


附件中为lesson1的工程,包括proteus和keil两个工程,欢迎大家下载把玩!
lesson1.rar (115.35 KB)


相关帖子

来自 2楼
青蓝pisces|  楼主 | 2017-2-4 17:16 | 只看该作者
本帖最后由 青蓝pisces 于 2017-2-6 22:08 编辑

首先简单介绍一下51单片机的总线:
51单片机的总线为了节省引脚,P0端口采用复用结构,复用了地址线的低8位和8位数据线。
解复用的电路这里暂时不讲述,如果大家有兴趣可以回帖提出,我会写个附录说一下。
使用74HC573配合51单片机的ALE(Address Latch Enable 地址锁存使能)信号,
从宏观上来看这个总线就变成了一个经典的并行总线,有16根地址线,8根三态的数据线,一个WR(Write)信号一个RD(Read)信号。

WR信号有效期间数据线上的信号被存进对应地址的存储单元,
RD信号有效期间对应地址单元的数据被送上总线,随后被CPU接收。

是不是很简单粗暴呢?

首先我们要给这两个寄存器分配地址,这里我以0x8000为基地址,向后偏移,即我们的两个寄存器所在地址空间为0x8000-0x8001
想明白了这些,就开始设计外设寄存器的电路。
74HC573对输入输出各有一个信号来控制,即LE(Latch Enable 锁存使能)OE(Output Enable 输出使能)
LE有效时输出等于输入,当LE无效时输出保持不变,与输入无关。
OE无效时输出为高阻状态,有效时输出为正常高低电平。
那么我们只要设计好这两个信号的来源就算是完成了工作。

首先我们知道只有在地址满足要求并且写信号有效时总线的数据才能被写入寄存器
所以LE连接一个与门,与门的两个输入信号:
一个是总线的WR信号(由于是低电平有效还需要一个非门进行反相)
一个暂时叫作地址有效信号
图 - LE信号的组成


那么这个地址有效信号又由基地址有效偏移地址有效两个信号相与组成,所以地址有效信号处也要作为一个与门的输出。
而这个与门的两个输入信号分别为基地址有效信号和偏移地址有效信号。
图 - 地址有效信号的组成


这里我们的基地址是0x8000,所以基地址有效信号可以简单的通过多输入与门实现(这里我们用了两个8输入与门和一个2输入与门)
由于我们用地址线的4做了偏移地址(74HC154译码为16个信号低电平有效信号)
所以将高12位相与(由于最高位是1,所以最高位要取反),这样就得到了基地址有效信号。
而偏移地址有效信号则可以直接从74HC154译码器中接出对应0的这根线,并进行取反。
图 - 地址译码器


这样我们就完成了一个寄存器的写操作电路,接下来实现读操作电路。
其实它们在总线端的逻辑是相同的,只是接到了不同的位置。
由于这里的数据需要连接到外部使用,所以不能直接接到数据总线,
而是需要通过一组三态门进行控制,当它需要被送上总线时打开三态门的使能,其他时候关闭三态门保持高阻状态,释放总线

为了方便起见,我们使用特殊接法的74HC573来代替三态门(也可以用74HC244)
74HC573LE固定接高电平,它就退化为了一个三态门,OE信号即是三态门的输出控制信号。
将前面的写有效信号中的总线WR信号换为总线RD信号,就成为了读有效信号,
将这个信号接到三态门的OE引脚,这个外设寄存器就算完成了。

图 - 方向控制

图 - 两个寄存器之间的关系

接下来我们将这个外设寄存器电路复制3份,并排放置,就形成了4个外设寄存器。
将它们的偏移地址有效信号分别替换为123三根线,则CPU可以以0x8000-0x80034个地址访问到这4个寄存器。
接下来我们以2个寄存器为一组,来实现8位并行IO口外设。
按照我们的规划,可以将0x80000x8002两个寄存器作为两个IO端口(我们这里命名为PortAPortB)数据寄存器,将0x80010x8003作为它们的控制寄存器
那么两个数据寄存器其实已经达到了要求,直接将寄存器的输出线引出即可。
而两个控制寄存器也只需要将输出线的最高位连接到数据寄存器的OE端即可完成控制方向的功能。

图 - 最终效果

这样我们的硬件就完成了!
为了演示这一过程,我提供了完整的仿真工程软件工程,欢迎大家下载把玩。
lesson1.rar (115.35 KB)

接下来我将讲解固件库的代码。

使用特权

评论回复
来自 3楼
青蓝pisces|  楼主 | 2017-2-7 11:26 | 只看该作者
本帖最后由 21ic小喇叭 于 2017-2-7 13:32 编辑

固件库
相信来看本文的各位对单片机都有或多或少的了解,那么固件库一词应该是十分熟悉的了。
秉承序章中客观分析的原则,我们来总结一下当前对于固件库这个词的各种理解,同时提出几个较为准确的用词。

从固件库的英文名称Firmware Library可以看出,它代表的是“固化”的代码.
那么从广义上来说,由提供者写好并保证正确性的前提下,被用户使用的代码都可以叫作固件库。
keil环境为例,我们的启动代码、C语言标准库、寄存器定义都可以算是固件库。

而目前最流行的对于固件库的解释来自于STM32的固件库。
ST官方在寄存器定义的基础上进行了进一步封装,提供了用于初始化及操作寄存器的函数。
开发人员可以快速上手这款芯片,把大部分精力用于驱动开发和业务逻辑,
可以说这是STM32如此知名的重要原因。
在前面的内容中,我们已经实现了一个8位并行IO口外设,并将它复制两份接入了51单片机的总线中。

通过0x8000-8x8001两个寄存器可以控制PortA,通过0x8002-0x8003两个寄存器可以控制PortB
那么我们作为“硬件厂商”要为广大开发者提供便利,自然需要对这些内容进行一定的抽象
使得C语言代码中访问寄存器的方法便于理解,契合芯片手册的内容。

在这里我们使用标准C语言配合C51的存储关键字对两套IO口的外设寄存器进行封装
并基于此运行一个简单的回环测试程序(不算循环和初始化只有一行代码)
希望大家可以通过这个简单的例子感受到驱动开发的思路
在后续的例子中,随着应用系统的扩大,
我们还会增加各种层级的(类似STM32的固件库、HAL、驱动框架、UI框架),
这些库的设计思想是嵌入式一个重要的门槛,将它们融入你的设计,你的开发速度会大大提高。
希望大家可以通过这些内容全方面的了解嵌入式开发中的底层内容,做到胸有成竹。

代码讲解:
从手册(其实就是硬件那一节中写的说明)中可以看出,这个外设的寄存器由28位寄存器组成,
偏移量为0x0000的寄存器为数据寄存器,偏移量为0x8001的寄存器为控制寄存器,其中最高位控制IO口方向,其他7位预留。
那么对于它的操作有读数据、写数据、设置方向等。

由这些分析我们可以发现,用(class)来描述这个外设是很恰当的。
在这种简单的应用中,面向对象思想可以说是最适合组织代码的,
当然复杂场景中也一样,但是需要库的设计者懂得更多的设计模式,投入更多精力。

那么我们如何用C语言来实现面向对象呢?由于C语言没有从语法层面上支持访问控制(public private)
也就是说我们不能强制用户(库调用者)使用接口函数来访问外设
那么我们只能退而求其次,为用户提供接口函数,同时不强制使用。
这样用户在访问寄存器时既可以使用接口函数,也可以手动访问寄存器实现
当然也可以混着来(前提是知道自己在干什么)

当然,如果有了操作系统,我们可以通过系统线程实现一个真正意义上的访问控制,
可以参考linux的设备驱动思路(POSIX接口),或者windows句柄系统
当然后续的内容中也会实现这样的框架,有兴趣的朋友可以在顶楼找到联系方式联系我。
这里由于功能简单,我们只实现数据结构部分(结构体的定义,外设常量的定义)
对于该外设的封装,以及接口函数的实现留给大家作为练手项目。


那么请看代码

<div>#include <reg52.h>
#include <absacc.h>             // For Reference

*----------------------------------------------------------------------------*/
/* Global Data Type */

typedef unsigned char uint8_s;

/*----------------------------------------------------------------------------*/


/*----------------------------------------------------------------------------*/
/* Firmware Library  */

typedef struct io_port_reg_mode{
       uint8_s Reserve:7;
       uint8_s InOut:1;
}IO_Reg_Mode;

#define IO_MODE_OUT 0
#define IO_MODE_IN  1

typedef struct io_port_reg{
       uint8_s        Data;
       IO_Reg_Mode Mode;
}IO_Reg;

IO_Reg volatile xdata * const IOA_Reg = (IO_Reg volatile xdata *)0x8000;
IO_Reg volatile xdata * const IOB_Reg = (IO_Reg volatile xdata *)0x8002;

/*----------------------------------------------------------------------------*/

int main(void)
{

       /* Initial */
       IOA_Reg->Mode.InOut = IO_MODE_OUT;
       IOB_Reg->Mode.InOut = IO_MODE_IN;
      
       /* Main Loop */
       for(;;)
       {
              IOA_Reg->Data = IOB_Reg->Data;   
       }
      
}</div><div>
</div>
这里使用了位域对控制寄存器进行封装,同时将数据寄存器和控制寄存器定义到一个结构体中,作为该外设的寄存器结构。

对于PortAPortB两个端口的寄存器指针常量定义,xdataC51中的扩展关键字,对应我们硬件中所连接的总线。
volatileC语言中代表“不优化”的关键字,用这个关键字修饰的变量不会被编译器以“没意义、没作用”为由进行优化,
而是在任何时候都与C语言代码保持一致,对于外设寄存器的定义使用这个关键字是一个很好的习惯。

关于代码相信大家在学习过程中已经见了很多了,所以在这里只讲解上面两点,
如果大家有什么意见建议,回帖见哦~我将会进行修改补充,感谢大家的支持!

使用特权

评论回复
地板
renxiaolin| | 2017-2-4 17:44 | 只看该作者
很古老的话题

使用特权

评论回复
5
icecut| | 2017-2-6 09:06 | 只看该作者
顶起

使用特权

评论回复
6
海迹天涯| | 2017-2-6 10:18 | 只看该作者
关注

使用特权

评论回复
7
笑鸟007| | 2017-2-6 10:26 | 只看该作者
关注!

使用特权

评论回复
8
山狼啸月| | 2017-2-6 13:54 | 只看该作者
期待固件库的推出!

使用特权

评论回复
9
青蓝pisces|  楼主 | 2017-2-6 16:01 | 只看该作者
山狼啸月 发表于 2017-2-6 13:54
期待固件库的推出!

固件库在附件压缩包里,有C语言基础可以直接下载阅读代码

使用特权

评论回复
10
清水在流| | 2017-2-6 16:40 | 只看该作者
找来小板凳,瓜子

使用特权

评论回复
11
749120145| | 2017-2-6 17:47 | 只看该作者
关注

使用特权

评论回复
12
dogglove| | 2017-2-7 08:27 | 只看该作者
很好,从根上描述单片机的理论基础值得赞啊

使用特权

评论回复
13
springvirus| | 2017-2-7 08:31 | 只看该作者
顶楼主

使用特权

评论回复
14
simonliu009| | 2017-2-7 10:26 | 只看该作者
支持基础教程,对于我这种半路出家的,恶补基础知识很有必要。

使用特权

评论回复
15
longquanshuang| | 2017-2-7 10:59 | 只看该作者
大部分菜鸟只会成为2b

使用特权

评论回复
16
NE5532| | 2017-2-7 14:35 | 只看该作者
关注中。

使用特权

评论回复
17
乐思善行者| | 2017-2-7 15:06 | 只看该作者
顶起!

使用特权

评论回复
18
pang7| | 2017-2-7 15:48 | 只看该作者
c51最好不用位域位域实际上是编译成字节逻辑操作,是两条指令 如果两条指令中间有中断,中断操作了其它位 再回来时候会导致中断操作无效。

使用特权

评论回复
19
yan心跳| | 2017-2-7 16:31 | 只看该作者
关注

使用特权

评论回复
20
528618581| | 2017-2-7 17:29 | 只看该作者
关注下,期待后续

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

8

主题

31

帖子

11

粉丝