Bug测试

微型计算机和单片机简介

微型计算机的组成及其应用形态

  • 微处理器(中央处理单元,简称CPU):将运算器控制器一些寄存器,集成在一个芯片上

  • 存储器

  • I/O接口

应用形态:多板机,单板机,单片机

单片机的发展过程

  • 单片机的组成:将微处理器,一定容量的RAM和ROM以及I/O接口,定时器/计数器、通信接口,中断系统等电路集成在一块芯片上,构成单片微型计算机
  • 单片机的外形封装:分为双列直插(DIP)和方形封装(PLCC或TQFP封装)

    双列直插有40个引脚,方形封装有44个引脚。

  • 系统机和单片机比较

    • 系统机(桌面应用):主要用于数据处理,办公自动化及辅助设计
    • 单片机(嵌入式应用):主要用于智能仪表及传感器,智能家电,智能办公设备,汽车及军事电子设备等系统。通常又把单片机称为微控制器、嵌入式控制器(EMCU)
  • 单片机的发展过程

    • Intel推出的MCS-48系列单片机,组成:8位CPU,1K字节ROM、64字节RAM、27根I/O线和18位定时/计数器

      特点:存储器容量小,寻址范围小,不大于4K,五串行接口,指令系统功能不强。

    • Intel推出MCS-51系列单片机:8位CPU,4K字节ROM、128字节RAM、4个8位并口、1个全双工串行口、2个16位定时/计数器,寻址范围64K,并有控制功能较强的布尔处理器

      特点:结构体系完善,性能已大大提高。

    • ATMEL推出的AT89S52:8位CPU,片内有8KB的Flash程序存储器ROM256字节RAM、316位定时/计数器(比8051多出一个定时器,且具有捕捉功能)

单片机的特点

  • 体积小、价格低,可靠性高
  • 结构上突出控制功能:可直接操作I/O端口,位操作能力突出,CPU、存储器及I/O接口集成在同一芯片内,数据在传送时受干扰的影响小,且不易受环境条件的影响。

单片机的发展趋势

向高性能化,存储大容量,外围电路内装化等方面发展

  • 存储器发展:片内存储器普遍采用Flash存储器,容量可达数十KB
  • 片内I/O口的改进:增加了并行口驱动能力,设置了特殊的串行I/O功能
  • 低功耗:CMOS工艺,功耗小,消耗电流仅在微安或纳安量级,便携
  • 外围电路内装化:将外围电路装入片内,一片芯片就是一个测控系统

AT89S52单片机的片内硬件结构

熟悉AT89S52芯片内部结构

AT89S52单片机的硬件组成

  • 单片机由总线连接,总线指的是能为多个部件服务的信息传送线,由导线组成

  • CPU:8位,起到运算和控制(位处理和位控)功能

    CPU通过地址、数据和控制总线连接ROM和RAM

    • 数据总线:传输发送接收数据

    • 地址总线:传递一些地址信息,CPU将数据指令在存储器中的地址放到地址总线。

    • 控制总线:传送一些控制信号和时序信号,CPU在控制总线上发送一个读或写的信号

      • 读操作:从存储器指定位置读出数据并放在数据总线上
      • 写操作:CPU将数据输出到数据总线上
  • 内部RAM(随机存取存储器):256个RAM单元(每个单元8位)。用户使用前128个单元,用于存放可读写数据,后128个单元被专用寄存器占用。

    • 用以存放可以读/写的数据,如运算的中间结果,最终结果以及欲显示的数据等
  • 片内程序存储器ROM:8KB闪存

    • 用以存放数据、一些原始数据和表格。但有些单片机内部不带ROM/EPROM,如8031、8032、80C31等
  • 定时计数器:3个16位的定时计数器,

    • 用来计数,也可以设置成定时方式,并可以根据计数或定时的结果实现计算机控制。
  • 并行I/O口:4个8位的I/O口(输入/输出),P0,P1,P2,P3,共有32位接口。

    • P0口既可以作为数据的输入输出口,也可以作为1-8位地址的输出口,p0口也叫做数据和地址的分时复用口,在CPU访问片外存储器时,P0口为分时复用的低8位地址总线和8位数据总线。

    • P2口:数据I/O口,也是高八位地址的复用口。

    • P3口:有第二功能

  • 全双工URAT串行I/O口

    • 全双工:可以同时(瞬时)进行信号的双向传输(A到B且B到A),如手机,电脑

    • 半双工:也是双向,即(A到B且B到A)但不能够同时传输,如对讲机

    • 单工:(A到B但B不能到A),如电视

  • 中断控制系统:实时控制

    • 6个中断源

      • 外中断(信号来自单片机芯片的外部):2个
      • 内部中断(信号来自单片机CPU的内部):定时计数中断3个,串行中断1个
    • 有2级中断优先权(就是谁的中断优先级高,就先处理谁的中断请求)

  • 时钟电路:分为时序逻辑电路(特点:工作过程必须要有时钟脉冲)和组合逻辑电路,可产生时钟脉冲序列,允许晶振频率6MHZ和12MHZ.

  • 片内振荡器:需要外接晶振和电容

  • 特殊功能寄存器(SFR):有32个特殊功能寄存器

    • 用于CPU对片内各外设部件进行管理、控制和监视
    • 掌握AT89S52的存储器配置及特点
  • 一个看门狗定时器(WDT):当单片机由于干扰而使程序陷入死循环或跑飞状态时,可引起单片机复位,是程序恢复正常运行。

AT89S52与AT89C51单片机结构对比

- [x] CPU同

  • 内部ROM4kb掩膜ROM,存放程序,原始数据和表格。(固定不变的)

  • 定时计数器两个16位的定时计数器,实现定时或计数功能

  • 中断控制系统:实时控制5个中断源(外中断2个,定时计数中断2个,串行终断1)

AT89S52单片机的引脚功能

  • 双列直插式(DIP)

    • 电源线:VCC(+5V)、VSS(接地线)

    • 逆时针规律排列,有40个引脚

    • 振荡电路:XTAL1(反相放大器输出)、XTAL2(反相放大器输入),通过电容接在18、19引脚

      • 通常外接一个晶振和两个电容

    • 复位引脚:RST(高电平有效),接在9角,使单片机处于初始的复位状态。

    • 并行I/O口:P0,P1,P2,P3

      • 准双向:当I/O口作为输入时,应先向此口锁存器写入全1,此时该口引脚浮空,可作高阻抗输入。
    • EA(非):内部ROM选择端,访问程序存储控制信号,因为程序存储器可能在单片机内部,也可能在单片机外部,这时候就由这个管脚的高低电平来选取内部或者外部的指令。

      • EA(非):保持高电平时,先访问内ROM,但当PC(程序计数器)值超过8kb(1FFFH)时,将自动转向执行外ROM中的程序。
      • EA(非):保持低电平时,则只访问外ROM,不管芯片内是否有内ROM。
      • Vpp功能:片内有EPROM的芯片,在EPROM变成期间,施加编程电源Vpp(12-21V)
    • PSEN(非):外部ROM读选通信号,当单片机中的CPU需要从外部程序存储器中选取指令时,可以通过这个管脚做一个选通的控制信号

      • 读控制端(OE非)低电平有效
    • ALE(PROG非):地址锁存控制信号和片内EPROM编程脉冲(片内有EPROM的芯片,在EPROM编程期间,在此引脚输入编程脉冲。

      • 所存P0口送出的低8位地址

第二功能

  • P3口具有第二功能

  • P3 0:RXD,P3 1:TXD:既可以作为一般的输入输出的I/O口的控制端子,同时可以作为串行通信的两条线,P3 0是串行口输入,P3 1是串行口输出

    • P3 2:INT0非,P3 3:INT1非:既可以作为I/O口的控制端子,还可以作为外部中断的请求输入信号

      • INT0:外部中断0请求输入端
      • INT1:外部中断1请求输入端
    • P3 4:T0,P3 5:T1:既可以作为I/O口的控制端子,也可以作为定时计数器对外部世界进行计数的输入端子,P3 4 是定时器0的外部输入,P3 5是定时器1的外部输入

    • P3 6:WR非,P3 7:RD非:P3 6,P3 7既可以作为I/O口的控制端子,也可以作*单片机与外部的数据存储器进行数据传输时的选通控制信号

      • WR非:片外数据存储器写选通,低电平有效(写选通控制输出)
      • RD非:片外数据存储器读选通,低电平有效(读选通控制输出)

  • 方形封装式(PLCC)

  • 控制线:EA,ALE,PSFN,RST

AT89S52单片机的CPU

  • 控制器:用于指挥和控制计算机工作的部件

    • 分为程序计数器PC,数据指针DPTR

      • 单片机有一个与16位地址线对应的16位寄存器,存放程序存储器的地址,成为程序计数器PC,具有自动加1的功能。

        • PC中存放的是下一条将要执行的指令的地址

        • AT89S52单片机共有16根地址线,有低八位地址总线(P0)和高八位地址总线(P2)。故最大空间为2^16=64kb

      • 数据指针DPTR:指针指某存储单元或变量的地址,DPTR主要用于存放片外数据存储器地址

        • 数据指针一边可以访问外部数据存储器的地址寄存器,存放数据地址,也可以访问外部程序存储器的基址寄存器,存放程序基地址。也可以做普通的16位寄存器使用,也可以做两个8位寄存器使用,高8位为DPH,低八位为DPL
      • PC访问程序存储器,DPTR访问数据存储器。

      • PC和DPTR都是16位寄存器,都与地址有关,只是PC只能存放程序存储器地址,而DPTR能存放程序,数据存储器地址。

      • DPTR和PC的寻址方式可以是变址寻址(机制寄存器+变址寄存器间接寻址)

        • 变址寻址:以某个寄存器的内容为基地址,在这个基地址的基础上加上地址偏移量形成真正的操作数地址。

        • 单片机中采用DPTR或PC为变址寄存器,A(累加器)的内容为地址偏移量。

        • 变址寻址只能访问程序存储器,访问范围是64KB.

      如题所示,move移动累加器和数据指针到这个累加器,DPTR和A相加就是程序存储器地址。本题0302H,再找到0302H对应的数据就是1EH,再把1EH送到累加器中去,最终的累加器的数据就是1EH,不是0302H,0302H是一个地址,不是数据

    • 指令寄存器IR,指令译码器、控制逻辑

      • 一条指令都会有对应的操作码和操作数,而在AT89S52单片机中共有三种指令长度。

      • 指令寄存器IR:专门存放指令操作码的寄存器

      • 指令译码器分析操作码功能的寄存器

      • 控制逻辑:根据译码结果发出一系列控制信号,控制单片机各个功能部件执行相应操作。

  • 运算器

    • 用于对数据进行算数运算和逻辑操作的执行部件。

    • 分为累加器A,寄存器B,程序状态字PEW,算术逻辑运算单元ALU

      • 累加器A,是最常用的8位寄存器,用于数据传送、运算、控制转移各类指令中。存放操作数和运算的最终结果。

      • 寄存器B,可以进行乘法运算,除法运算和暂存器(作为数据的暂存)。

      • 程序状态字PEW,8位寄存器,反映当前CPU工作状态和运算结果的状态

        • CY表示进位状态,进位或借位标志位。可进行加减法运算,如FFH+01H=100H,00H-01H=1FFH(注:某些控制转移类指令影响CY,如CJNE指令。)

        • AC表示半进位状态,辅助进位或借位标志位加减法运算,如:00001111+00000001=00010000

        • OV表示溢出标志位:加减法运算(用于判断有符号数运算结果是否发生溢出),乘法运算:乘积>255,OV=1,除法运算:除数=0,OV=1

        • P表示奇偶校验位:判断累加器A内容的奇偶性,奇数个1则P=1

  • 一些寄存器

掌握AT89S52的存储器配置及特点

  • 存储器组织:分为两种结构,冯诺依曼结构和哈佛结构

    • 冯诺依曼结构:特点:程序、数据、外设寄存器位于统一的存储空间,地址无重叠,结构简单

    • 哈佛结构:特点:1.程序、数据存储器独立分开编址,地址可能重叠,结构复杂。2.地址,数据总线独立,速度快

      注:AT89S52单片机不用哈佛结构

  • 存储器结构,存储器组织有七种类型,分为程序存储器(CODE),外部数据存储器(XDATA/PDATA),内部存储区(DATA/IDATA/SFR/BIT)

    • 存储器都是由0000到FFFF,最大64K。

AT89S52寄存器

分为工作寄存器特殊功能寄存器系统寄存器

作用:暂存数据、配合累加器A进行数据运算及存放结果。

注:任意时刻只能使用一组寄存器

单片机的并行端口及复位

了解并行I/O口端口内部结构

4个I/O口介绍

  • AT89S52有4组I/O口(P0,P1,P2,P3),每组包括8根并行线

  • P0:8位开漏双向I/O口,使用片外部存储器,可作为低8位地址与数据复用总线,能驱动8个TTL(TTL:Time To Live的缩写,生存时间值,该字段指定IP包被路由器丢弃之前允许通过的最大网段数量)

  • P1:8位准双向I/O口,能驱动4个TTL

  • P2:8位准双向I/O,使用片外存储器时,可作为高8位地址与数据复用总线,能驱动4个TTL

  • P3:8位准双向I/O口,能驱动4个TTL

并行I/O口特点

  1. 这四个均是8位并行I/O口
  2. 每个端口都是8位准双向口,共占32根引脚
  3. 每条I/O线都能独立地用作输入或输出
  4. 每个端口都包括一个锁存器(即特殊功能寄存器SFR P0-P3),一个输出驱动器输入缓存器作输出时数据可以锁存,作输入时数据可以缓冲

P0口

  • P0口某位的结构由一个输出锁存器、两个三态输入锁存器和输出驱动电路及控制电路组成。

  • 当(控制端)C=0(即输入低电平)时,开关MUX(自动转向开关)接D锁存器,P0口为通用I/O口(就是一般的输入输出口

  • 当C=1时(即输入高电平),开关拨向反相器3的输出端,P0口分时作为地址/数据总线使用。

  • 1和2是输入缓冲器,1是读锁存器,2是读引脚,同时还包含了P0口的输出驱动电路和控制电路。

  • 4是与门

P0口作通用I/O口时

  • P0口作通用I/O口时,C=0分两种情况,做输入口和输出口

    • 做输入口

      • 自动转向开关接D锁存器的Q非端,在此时读引脚时,需要事先将输出锁存器写入1,(场效应管)T1和T2均截止,引脚处于悬浮状态,可作高阻抗输入,否则有可能被拉低,读入错误信息。

        • T2为截止原因:MUX接D锁存器,导通,T2端是低电平,从而T2截止。
        • T1为截止原因:P0引脚做通用I/O口,控制信号端和地址数据的复用端口,而此时控制端是0,4是与门,根据与门特性,有一端输入为0则与门输出为0,即T1为0,截止
        • T1、T2均截止时,P0端处于悬浮状态,则外部数据从2读取数据送到内部CPU
        • 开始时内部总线写入1原因:若刚开始接入0,即D=0,那么根据D锁存器原理可得到Q非是1,根据先前分析,T2会导通,T2导通那么外部数据从这里进来,输出会全为低电平,因为T2端接地,得到数据会不准确,所以提前将输出锁存器写入1,才能保证T1,T2都是低电平,输出有效数据。

        作输入口提前写入1,两边都截止,读入信息

    • 做输出口

      • 当P0口做输出口时,因输出级处于开漏状态,必须外接上拉电阻,当“写锁存器”信号加在锁存器的时钟端CLK上,此时D触发器将“内部总线”上的信号反相后输出到Q非端,若D端信号为0,Q非为1,v2导通,P0,x引脚输出“0”;若D端信号为1,Q非为0,v2截止,虽然v1截止,因P0,x引脚已外接上拉电阻,P0,x引脚输出“1”

        • 刚开始写入1,则V1V2均为0,此时又因为接了电源VCC,通过外接上拉电阻,把引脚处拉成了高电平输出,故P0口仍然有高电平输出
        • 刚开始写入0,V2导通,数据进入被接地拉为低电平,即有低电平输出。

        做输入口,刚开始写1输出1,写0输出0

  • 读引脚和读锁存器的区别

    • 读引脚:即T1T2均截止,输入数据从输入缓冲器2读进内部CPU,读的是引脚带的信息
    • 读锁存器:读的是D锁存器输出的状态,读的是Q,如上图,Q非是0那么Q为1,此时输入缓冲器中就会输入进1,即读入。

P0口作为地址/数据复用口

当P0口作为地址/数据复用口时,控制端为1

  • 输出地址/数据=0时

    此时开关MUX接反相器3,因为输出地址数据为0,经过反相器所以下面是1,即V2导通,而4与门的两个端口一个为0一个为1,根据与门性质输出为0即V1截止。两个场效应管下方导通上方截止,下方导通接地,那么输出为0

  • 输出地址/数据=1时

    同理,此时V2截止,V1导通,V1导通接高电平VCC,引脚输出被拉为高电平即输出为1

输出地址/数据信号与引脚信号一起变,均同

  • 作数据输入:与P0用作通用I/O口时输入情况相同,也要先写入1,此时控制端信号C=0,V1V2均截止,从引脚上输入的外部数据经缓冲器U2进入内部数据总线。

  • CPU自动将0ffH写入锁存器

P1口

  • P1口仅有一个场效应管
  • P1口一般只作为通用I/O口,不会作为地址/数据分时复用口。作为通用I/O时与P0口C=0作用时完全一样的。

P2口

  • P2口同样可作地址数据的复用端口,与P0类似,但不同的是P0输出低8位地址,P2口输出高8位地址

P3口

CPU的时序及单片机的工作过程

时钟电路与时序

  • 单片机的时序就是指CPU在执行指令时各个控制信号间的时间顺序关系
  1. CPU通过地址总线发出地址信号到程序存储器
  2. CPU通过控制总线发出正确的地址锁存控制信号
  3. CPU通过控制总线发出正确的读控制信号
  4. CPU通过数据总线读取数据
  5. 在CPU内部控制信号作用下完成程序执行。

不允许调换上述顺序,任何一个信号的时间顺序出错,都可能导致程序运行出错。

单片机内部的电路需在唯一的时钟信号控制下严格按照时序工作

  • 计算机的工作原理就是在时钟节拍的作用下,将预先编好的程序一步一步的执行下去,其中时钟信号就来源于晶体振荡器(简称晶振)

晶体振荡器

  • 材料:石英晶体(理由:石英晶体具有压电效应,石英晶体的选频特性非常好,它有一个极其稳定的串联谐振频率fs,只有频率为fs的信号最容易通过,而其他频率的信号均会被晶体衰减。石英晶体的谐振频率fs与晶片的切割方式,几何形状,尺寸等有关,因此可以制成各种频率的石英晶体谐振器。)

单片机的引脚(晶振端)

  • 接18(XTAL1)、19脚(XTAL2),分别为晶体振荡电路反向输入端和输出端。接法:
  • 两端跨接石英晶体及两个电容就可以构成稳定的自激振荡器。
  • 电容器C1和C2通常取30pF左右,可稳定频率并对振荡频率有微调作用
  • 振荡脉冲频率范围为fosc=0~24MHz。

单片机的时钟周期

为便于分析指令的执行,定义了一些能度量各种时序信号出现时的尺度,分别为时钟周期、机器周期、指令周期

  • 时钟周期:即振荡周期,大小为1/(11.0592Hz)=90.4ns,一个时钟周期定义为一个节拍,2个节拍定义为1个状态周期。
  • 机器周期=12个时钟周期,对于11.0592MHz晶振,机器周期=12×90.4ns=1.085us
  • 指令周期:执行一条指令所需的时间,单字节执行指令时间短一些(单周期),双字节执行指令时间长一些(双机器周期),三字节执行指令时间也会长一些(双机器周期),若是乘除、转移之类的指令需要多个机器周期(四机器周期)
  • 4个时序单位从小到大依次是节拍,状态周期,机器周期和指令周期

AT89S52的复位电路及复位功能

复位操作的主要功能

  • 使CPU及系统各部件处于确定的初始状态,并从初始状态开始工作。
  • 把PC初始化为000H,使单片机从000H单元开始执行程序。

下面使各特殊功能寄存器的复位值

PC:程序指针

ACC:累加器

PCW:程序状态器

SP:堆栈指针

DPTR:数据指针

P0-P3:I/O口,必须是FFH才是复位因为作为通用I/O口,必须提前写入一个1才能保证输入数据准确,而8位1就是11111111转化为16进制就是FFH。

复位信号

RST引脚是复位信号输入端,当RST引脚为高电平,且有效时间持续24个振荡周期(两个机器周期)以上,才能复位。

复位电路

  • 上电自动复位:通过外部复位电路的电容充电实现。

首先VCC给电容充电,电容充电的同时给RST一个高电平信号,这样持续两个机器周期以上就可以复位了。

  • 按键手动复位

当RESET按键按下,即R1R2路导通,VCC分压,给RST一个高电平信号即可以复位了。

单片机低功耗的工作方式

两种节电工作方式:空闲(待机)方式掉电(停机)方式,如图为空闲和掉电方式控制电路。

  • 空闲方式和掉电方式是通过对SFR中的PCON(地址87H)相应位置1而启动的。下图即为电源控制寄存器(PCON)的各位分布情况。HMOS期间的PCON只包括一个SMOD位(最高位),其他四位是CHMOS器件独有的,三个保留位用户不得使用。
  • PCON内部结构

    • SMOD:波特率倍频位,若此位为1,则串行口方式1,方式2和方式3的波特率加倍。
    • GF1和GF0:通用标志位
    • PD:掉电方式位,此位写1则启用掉电方式
    • IDL:空闲方式位,此位写1则启用空闲方式,这是CPU因无时钟控制而停止运作。如果同时向PD和IDL两位写1,则PD优先
  • 空闲(等待、待机)工作方式

    • CPU执行完置IDL=1(PCON.1)的指令后,系统进入空闲工作方式。
    • 有两种退出空闲方式的方法:
      • 任何的中断请求被响应都可以由硬件将PCON.0(IDL)清0而终止空闲工作方式
      • 硬件复位
  • 掉电(停机)工作方式

    • 当CPU执行一条置PCON.1位(PD)=1的指令后,系统进入掉电工作方式。
    • 推出掉电方式的唯一方法就是硬件复位,复位后将所有特殊功能寄存器的内容初始化,但不改变片内RAM区的数据
    • 在掉电工作方式下,VCC可以降到2V,但在进入掉电方式之前,VCC不能降低,而在准备退出掉电方式之前,VCC必须恢复正常的工作电压值,并维持一段时间(约10ms),使振荡器重新启动并稳定后方可退出掉电方式。

    RAM位寻址区位地址表

    高于7F的就是字节寻址,比如位地址是88H,那么他的字节地址也是88H,位地址是28H,则他的字节地址就是25H。

    片内字节地址为2AH,那么对应最低位位地址就是50H,而片内字节地址为A8H,那么对应最低位的位地址还是A8H,变为字节寻址。超出7F.

C51语言编程基础

C51语言介绍

  • C51语言能直接对8051单片机硬件进行操作

  • Keil C51,在标准C语言的基础上发展的。

使用C51语言软件开发优点

  • 可读性好
  • 模块化开发与资源共享,可以不需要修改直接拿去别的地方使用,先建立好库函数直接调用即可。
  • 可移植性好,比如对51单片机开发的C语言,只需要把51单片机编写的程序改头文件就可以用到其他型号单片机上。
  • 生成的代码效率高

C51与标准C语言的比较

C51,标准C语言有许多相同的地方,但也有自身特点。对于51单片机,目前广泛使用的是Keil C51语言,简称C51语言。下面是二者的主要区别。

  1. 库函数的不同

例如库函数printf与scanf,在标准C中,这两个函数通常用于屏幕打印和接受字符,而在c51中,主要用于串行口数据的收发

  1. 数据类型有一定的区别

c51中增加了几种针对51单片机特有的数据类型,在标准c的基础上有扩展了4种位操作功能

  • c语言中,数据类型有整型,字符型,浮点型等。
  • 51单片机包含位操作空间,和丰富的位操作指令,因此,C51语言与标准C相比就要增加位类型
  1. c51的变量存储模式与标准c中的变量存储模式数据不一样
  • 对于c语言是针对通用计算机来设计的,计算机中只有一个程序和数据统一寻址的内存空间,只有ROM和RAM内存。
  • c51语言针对单片机系统,单片机系统有很多存储器,根据数据处于不同的区来选择。
  1. 数据存储类型的不同
  • 51单片机存储区可分为内部数据存储区,外部数据存储区,以及程序存储区。
    • 内部数据存储区:分为3个不同的c51存储类型:data,idata,bdata
    • 外部数据存储区:分为2个不同的c51存储类型:xdata,pdata
    • 程序存储区:只能读不能写,位于51单片机内部或者外部。c51提供了code存储类型来访问程序存储区。
  1. 标准c语言中没有处理单片机中断的定义

c51中有专门的中断函数。有专门的引脚去接外部中断。

  1. c51语言与标准c语言的输入输出处理不一样

c51语言中的输入输出是通过51单片机的串行口来完成的,输入输出指令执行前必须对串行口进行初始化。

  1. 头文件的不同

51单片机内部的外设硬件资源如定时器、中断、I/O口等所相应的功能寄存器,需要写入头文件内。指令执行前必须对串行口进行初始化。

  1. 程序结构的差异

51单片机硬件资源有限,它的编译系统不允许太多的程序嵌套。其次,标准c所具备的递归特性不被c51语言支持。

C51语言程序设计基础

C51的数据类型与存储类型

  • 数据是计算机操作的对象,包含常量和变量

  • 数据类型:就是数据的格式,它决定数据的值域范围、占用存储单元的个数及能参与哪种运算

    • 编写C51程序,先要确定数据类型、储存分区
    • 应尽可能采用无符号格式(unsigned)
    • C51数据类型表
    • 红色的是c51增加的4个数据类型。16位的SFR有PC和DPTR

C51的扩展数据类型

  1. 位变量bit

bit定义位变量名字,编译器在位寻址区自动分配该位变量,变量名称叫flag1。

bit flag1 =0;

  1. 特殊功能位sbit

片内特殊功能寄存器的可寻址位。

下面两串代码就是先定义PSW的值,用sfr,再用sbit定义程序状态字的第二位。sbit定义寄存器的第几位

sfr PSW=0×d0;/*定义PSW寄存器地址为0×d0*/

sbit PSW ^2=0×d2;/*定义OV位为PSW.2*/

  1. 特殊功能寄存器sfr

地址单元80H~FFH之间,“sfr”数据类型占用一个内存单元,利用它可以访问AT89S51单片机内部的所有特殊功能寄存器。sfr定义这个寄存器。

sfr P1 = 0×90;/*定义了P1端口在片内的寄存器*/

P1=0×ff;/*对P1进行赋值ff(必须ff,使P1处于高电平,正确读入外部信息)*/

  1. 特殊功能寄存器sfr16(PC和DPTR)

“sfr16”数据类型占两个内存单元,它用于操作占两个字节的特殊功能寄存器。

sfr16 DPTR =0×82;/*定义了片内16位数据指针寄存器DPTR,其低八位字节地址为82H,高8位字节地址为83H*/

C51数据的存储类型

C51语言存储类型与8051存储空间的对应关系
存储区 存储类型 与存储空间的对应关系
DATA data 片内RAM直接寻址区,位于片内RAM的低128位字节
BDATA bdata 片内RAM位寻址区,位于20H~2FH空间
IDATA idata 片内RAM的256字节,必须间接寻址的存储区
XDATA xdata 片外64KB的RAM空间,使用@DPTR间接寻址
PDATA pdata 片外RAM的256字节,使用@Ri间接寻址
CODE code 程序存储区,使用DPTR寻址
  • 经常使用的变量应该置于片内RAM中,要用bdata、data、idata定义
  • 不经常使用的变量或规模较大的变量应该置于片外RAM中,要用pdata、xdata来定义。

bit bdata flags;/*位变量flags定位在片内位寻址区*/

char data var;/*字符变量var定位在片内RAM区*/

floar idata x,y;/*实型变量x,y定位在片内间接寻址RAM区*/

unsigned char pdata z;/*无符号字符变量z定位在片外分页间址RAM区*/

C51的数据存储模式

默认的变量存储分区

编译模式 默认存储分区 特点
SMALL data 小模式,变量默认在片内RAM,空间小,速度快。
COMPACT pdata 紧凑模式,变量默认在片外的RAM的页(256字节,页号由P2口决定)
LARGE xdata 大模式,变量默认在片外RAM的64KB范围,空间大,速度慢。
  • 如果未对变量存储分区定义时,C51编译器采用默认存储分区。

例如:char,var;//SMALL模式时,var定位于data存储区。

​ //COMPACT模式时,var定位于pdata存储区。

​ //LARGE模式时,var定位于xdata存储区。

C51语言的SFR及位变量定义

  1. 特殊功能寄存器的C51定义
  • 使用关键字定义sfr

    • 语法如下:sfr 特殊功能寄存器名字=特殊功能寄存器地址;
    • 例如:sfr IE=0×48;/*中断允许寄存器地址A8H*/
    • 例如:sfr TCON=0×88;/*定时器/技术去控制寄存器地址88H*/
    • 例如:sfr SCON=0×98H;/*串行口控制寄存器地址98H*/
    • 例如:sfr16 DPTR=0×82;/*数据指针DPTR的低8位地址为82H,高8位地址为83H*/(注意:16位SFR的低字节地址必须作为“SFR16”的定义地址
  • 通过头文件访问SFR

    • C51语言把51单片机(或52单片机)的常用的特殊功能寄存器和其中的可寻址位进行了定义,放在一个reg51.h(或reg52.h)的头文件中。在使用的时候需要调用头文件。

    • 使用预处理命令#include<reg51.h>

    • 头文件引用举例如下:

      #include<reg51.h> 	/*头文件为51型单片机的头文件*/
      void main(void)
      {
      TLO=0×F0;		/*给定时器T0低字节TL0设置时间常数,已在reg51.h中定义*/
      TH0=0×3F;			/*给T0高字节TH0设时间常数*/
      TR0=1;				/*启动定时器0*/
      ……
      }
      
  • 特殊功能寄存器中的位定义

    • sbit 位名=特殊功能寄存器^位置;

      sfr		PSW=0×D0;
      sbit	CY=PSW^7;
      sbit	OV=PSW^2;
      
    • sbit 位名=字节地址^位置

      sbit CY=0×D0^7;
      sbit OV=0×D0^2;
      
    • sbit 位名=位地址

    • 这种方法将位的绝对地址赋给变量。位地址必须在0×80~0×FF之间。

      sbit CY=0×D7;
      sbit OV=0×D2;
      
    • 例:片内P1口

    sfr		P1=0×90;
    sbit P1_7=P1^7;
    sbit P1_6=P1^6;
    sbit P1_5=P1^5;
    sbit P1_4=P1^4;
    sbit P1_3=P1^3;
    sbit P1_2=P1^2;
    sbit P1_1=P1^1;
    sbit P1_0=P1^0;
    
  1. 位变量的C51定义
  • 位变量的C51定义

    • C51采用关键字“bit”来定义位变量
    • bit bit_name

    如:

bit ov_flag;	/*将ov_flag定义为位变量*/
bit lock_pointer;	/*将lock_pointer定义为位变量*/
  • 函数可以包含类型为bit的参数,也可将其作为返回值。

​ 例如:

bit func(bit b0,bit b1);	/*位变量b0与b1作为函数func的参数*/
{
……
return(b1);		/*位变量b1作为函数的返回值*/
}
  • 位变量定义的限制

    • 位变量不能用来定义指针和数组

      bit *ptr; /*错误,不能用位变量来定义指针*/

      bit array[] /*错误,不能用位变量来定义数组array[]*/

C51语言的绝对地址访问

  1. 绝对宏

    C51编译器提供了一组宏定义来对code、data、pdata和xdata空间进行绝对寻址。

    在程序中,用#include<absacc.h>来对absacc.h中声明的宏来访问绝对地址,包括“CBYTE,CWORDE,DBYTE,DWORD,XBYTE,XWORD,PBYTE,PWORD”,具体使用方法参考absacc.h头文件。

    • CBYTE以字节形式对code区寻址;
    • CWORD以字形式对code区寻址;
    • DBYTE以字节形式对data区寻址;
    • DWORD以字形式对data区寻址;
    • XBYTE以字节形式对xdata区寻址;
    • XWORD以字形式对xdata区寻址;
    • PBYTE以字节形式对pdata区寻址;
    • PWORD以字形式对pdata区寻址;
    #include<absacc.h>
    #define PORTA XBYTE[0×FFC0]
    /*将PORTA定义为外部I/O口,地址为0×FFC0,长度16位*/
    #define NRAM DBYTE[0×50]
    /*将NRAM定义为片内RAM,地址为0×50,长度8位*/
    
    #include<absacc.h>
    #define PORTA XBYTE[0×FFC0]
    #define NRAM DBYTE[0×40]
    
    main()
    {
        PORTA=0×3D;/*将数据3D写入地址FFC0的外部I/O端口*/
        NRAM=0×01;/*将数据01H写入片内RAM的40H单元*/
    }
    
  2. _at_关键字

使用关键字_at_可对指定的存储器空间的绝对地址进行访问,格式如下:

[存储器类型] 数据类型说明符 变量名_at_地址常数

void main(void)
{
    data unsigned char y1_at_0×50;/*在data区定义字节变量y1,它的地址为50H*/
    xdata unsigned int y1_at_0×4000;/*在xdata区定义字节变量y2,它的地址为4000H*/
    y1=0×ff;
    y1=0×1234;
        ……
    while(1);
}

例:将片外RAM 2000H开始的连续20个字节单元清0.

程序如下:

xdata unsigned char
buffer[20]_at_0×2000;/*buffer表示数组*/
void main(void)
{
    unsigned char i;
    for(i=0;i<20;i++){
        buffer[i]=0
    }
}

例:如果把片内RAM 40H单元开始的8个单元内容清0,则程序如下:

data unsigned char
buffer[8]_at_0×40;
void main(void)
{
	unsigned char j;
	for(j=0;j<8;j++){
	buffer[j]=0
	}
}

一些问题

  1. 当A中的内容为63H,那么P标志位的值为多少?

答:P=0,P为奇偶校验标志位,由硬件址位或清零,根据累加器A中“1”的个数的奇偶性,即1的个数为偶数时,P=1,当A中的内容为63H时,A中“1”的个数为4,故P=0.

C51的基本运算

  • C51语言的基本运算与标准C类似,主要包括算术运算、关系运算、逻辑运算、位运算和赋值运算及其表达式等。

    算术运算符

    算术运算的算术运算符及其说明如下

    符号 说明 举例(设x=10,y=3)
    + 加法运算 z=x+y; //z=13
    - 减法运算 z=x-y; //z=7
    * 乘法运算 z=x*y; //z=30
    / 除法运算 z=x/y; //z=3
    % 取余数运算 z=x%y; //z=1
    ++ 自增1
    -- 自减1

    自增运算符与自减运算符

    运算符 说明 举例(设x初值为4)
    x++ 先用x的值,再让x加1 y=x++; //y为4,x为5
    ++x 先让x+1,再用x的值 y=++x; //y为5,x为5
    x-- 先用x的值,再让x-1 y=x--; //y为4,x为3
    --x 先让x-1,再用x的值 y=--x; //y为3,x为3

    逻辑运算符

    逻辑运算符及其说明

    运算符 说明 举例(设a=2,b=3)
    && 逻辑与 a&&b; //返回值为0
    || 逻辑或 a||b; //返回值为1
    逻辑非 !a; //返回值为0

    例如:条件“10>20”为假,“2<6”为真,则逻辑与运算为:

    (10>20)&&(2<6)=0&&1=0

    关系运算符

    关系运算符及其说明

    符号 说明 举例(设a=2,b=3)
    > 大于 a>b; //返回值为0
    < 小于 a<b; //返回值为1
    >= 大于等于 a>=b; // 返回值为0
    <= 小于等于 a<=b; //返回值为1
    == 等于 a==b; //返回值为0
    != 不等于 a!=0; //返回值为1

    位运算

    位运算及其说明

    符号 说明 举例
    & 按位逻辑与 0x19&0x4d=0x09
    | 按位逻辑或 0x19|0x4d=0x5d
    ^ 按位异或 0x19^0x4d=0x54
    ~ 按位取反 x=0x0f,则~x=0xf0
    << 按位左移(高位丢弃,低位补0) y=0x3a,若y<<2,则y=0xe8
    >> 按位右移(高位补0,低位丢弃) w=0x0f,若w>>2,则w=0x03
    • 0x19=19H=00011001,0x4d=01001101

    ​ 00011001

    相与 01001101 (上下相同就是该数,不同都是0)

    ​ = 00001001=09

    ​ 00011001

    相或 01001101 (上下相同是该数,不同为1)

    ​ = 01011101=5d

    ​ 00011001

    异或 01001101 (上下相同为0,不同为1)

    ​ = 01010100=54

    x=0x0f,则~x=0xf0 按位取反

    按位左移 00111010 (按位左移或右移后,空余的位补0)

    左移两位 11101000

    按位右移 00001111

    右移两位 00000011

    • 在实际应用中,常想改变I/O口某一位的值,而不影响其他位,如果I/O口可位寻址的,这个问题就很简单,但有时外扩的I/O口只能进行字节操作,要想实现单独位控,就要采用位操作

    例:编写程序将扩展的某I/O口PORTA(只能字节操作)的PORTA.5清0,PORTA.1置为1.

/*要想使其第五位清0而其他位不动,那么使用与逻辑运算,使其与11011111相与*/
/*要想使其第一位置1而其他位不变,那么使用或逻辑运算,使其与00000001相或*/
#define<absacc.h>
#define PORTA XBYTE[0xffc0]/*以字节形式去访问片外的地址0ffc0*/
void main(void)
{
    ……
    PORTA=(PORTA&0xdf)|0X02
    ……
}

指针和取地址运算符

  • 指针变量用于存储某个变量的地址

  • C51用''和'&'运算符来提取变量内容和变量地址*

赋值、指针和取值运算及其说明
符号 说明
* 提取变量的内容
& 提取变量的地址
  • 提取变量的内容和变量的地址的一般的形式分别为:

*目标变量=指针变量 //将指针变量所指的存储单元内容赋值给目标变量。

指针变量=&目标变量 //将目标变量的地址赋值给指针变量。

注意:指针变量中只能存放地址(也就是指针型数据),一般情况下不要将非指针类型的数据赋值给一个指针变量。

例如: a=&b; //定义指向b的地址

			   c=*b;             //定义指向b地址的内容
int i;         //定义整型变量i
int*b;         //定义指向整数的指针变量b
b=&i;          //定义指向i的地址
错误写法:b=i,整型变量的值不可以等于指针变量
    

C51的分支与循环程序结构

  • 在C51的程序结构上可以把程序分为三类,即顺序、分支和循环结构

分支控制语句

  • 分为if语句switch语句

    • if语句:if语句是用来判定所给定的条件是否满足,根据判定结果执行两种操作之一。if语句的基本结构if(表达式){语句}
    1. 形式1:if(表达式){语句}

    if(x>y){max=x;min=y};/*若表达式为真就执行后面的语句*/

    1. 形式2:if(表达式){语句1;} else{语句2;}
    if(x>y)/*if后表达式为真执行语句1,否则执行语句2*/
    {max=x;}
    else{min=y;}
    
    1. if语句的嵌套格式if,else if
    if(表达式1){语句1;}/*依次向下判定,是否符合表达式1,执行或往下递推,直到是否符合表达式n-1,符合就是语句n-1,不符合就是语句n*/
    else if(表达式2){语句2;}
    else if(表达式3){语句3;}
    ......
        else{语句n;}
    

    例如:

    if(x>100){y=1;}
    else if{x>50}{y=2;}
    else if(x>30){y=3;}
    else if(x>20){y=4;}
    else {y=5;}
    
    • switch语句:switch语句是多分支选择语句,一般形式如下
    switch(表达式1)/*当表达式1和常量表达式1相同时,就执行后面的语句1,之后遇到break就退出switch语句,以此类推,当所有常量表达式没有与表达式相对应的值,那么就执行default后面的语句n+1*/
    {
        case 常量表达式1:{语句1;}break;
        case 常量表达式2:{语句2;}break;
            ……
        case 常量表达式n:{语句n;}break;
        default:{语句n+1;}
    /*每一case常量表达式需互不相同,否则将混乱*/
    /*各个case和default出现次序,不影响程序执行的结果*/
    /*如果在case中遗忘了break语句,则程序执行了本行之后,不会按规定退出switch语句,而是将执行后续的case语句。在执行一个case分支后,使流程跳出switch结构,即终止switch语句的执行,可以用一条break语句完成*/
    }
    

    例如:在单片机程序设计中,常用switch语句作为键盘中按键按下的判别,并根据按下键的键号跳向各自的分支处理程序。

    input:  keynum=keyscan()/*keyscan是键盘扫描函数*/
    switch(keynum)
    {
          case 1:key1();break;//如果按下键为1键,则执行函数key1()
          case 2:key2();break;//如果按下键为2键,则执行函数key2()
          case 3:key3();break;//如果按下键为3键,则执行函数key3()
          case 4:key4();break;//如果按下键为4键,则执行函数key4()
      ……
          default:goto input
             
    }
    

循环结构流程控制语句

  • 实现循环结构的语句有以下三种:while语句、do-while语句和for语句。

    • while语句:语法形式如下
    while(表达式)
    {
        循环体语句;
    }
    /*while是先判断表达式真假,若为假程序不会执行,为真才执行*/
    

    例如:

    while((P1&0x80))= =0)
    {}
    

    while中的条件语句对AT89S52单片机的P1口第七位进行测试,如果P1 7为低(0),则由循环体无实际操作语句,故继续测试下去(等待),一旦P1 7变为高电平(1),则循环终止

    • do while语句:语法形式如下
    do
    {
        循环体语句;
    }
    while(表达式);
    /*do while 先执行循环体语句,再判断表达式真假,若为假则停止程序,但前提数据已经录入,即程序已经运行了。*/
    

    例:实型数组SAMPLE存有10个采样值,编写程序段,要求返回其平均值(平均值滤波)

    float avg(float *sample)
    {
        float sum=0;
        char n=0;
        do
        {
            sum+=sample[n];/*sum+=sample与sum=sum+sample等价*/
            n++;
        }while(n<10);
        return(sum/10);
    }
    
    • 基于for语句的循环:for循环的一般格式如下:
    for(表达式1;表达式2;表达式3)
    {
        循环体语句;
    }
    

    例:编写一个延时1ms程序

    void delayms(unsigned char int j)/*delayms延时函数*/
    {
        unsigned char i;
        while{j- -}
        {
            for(i=0;i<125;i++)/*执行一个for语句大概要延时8us,要执行125次,共延时1ms,利用for语句来延时*/
            {;}/*等待*/
        }
    }
    

    例:求1+2+3+...+100的累加和

    #include<reg51.h>
    #include<stdio.h>
    main()
    {
    	int nvar1,nsum;
    	for(nvarl=0,nsum=1;nsum<=100;nsum++)
    	nvar1+=nsum;  //累加求和
    	while(1);
    }
    
    • break语句

    例:

    void main(void)
    {
        int i,sum;
        sum=0;
        for(i=1;i<=10;i++)
        {
            sum=sum+i;
            if(sum>5)break;
            print("sum=%d\n",sum);/*通过串口向计算机屏幕输出显示sum值*/
        }
    }
    
    • continue语句

    例:输出整数1~100的累加值,但要求跳过所有个位为3的数。

    void main(void)
    {
        int i,sum=0;
        for(i=1;i<=100;i++)
        {
            if(i%10= =3)
            continue;
            sum=sum+i;
        }    
        print("sum=%d\n",sum);/*在计算机屏幕显示sum值,了解本语句的功能即可*/
    }
    
    
    • goto语句:基本格式如下:goto 标号

    例:计算整数1~100的累加值,存放到sum中。

    void main(void)
    {
        unsigned char i
        int sum;
         sumadd;
        sum=sum+i;
        i++;
        if(i<101)
        {
            goto sumadd/*无条件跳转到sumadd*/
        }
    }