上手

  • 硬件环境普中A2学习板,STC89C52单片机;
  • 单片机开发软件:keil5 uvision5(选C51),软件需要授权;
  • 单片机下载软件:STC-ISP,软件不需要安装,直接点击运行。但是运行前PC需要预装CH340串口驱动;

CH340目的是实现USB转串口(UART)

  • STC89C52介绍
    • 为什么叫51?指只要兼容8051内核的单片机都可以叫做51单片机。
    • 什么是8位单片机?指的是CPU中的ALU,算术和逻辑运算单元的位数。8位的代表就是51单片机,表示数据总线的宽度是8位,它一次可以计算两个8位数据二进制相加,如果数据是16位/32位的,就要分成多次来计算。但对于32位单片机而言,计算32位的数据相加,只需要一次计算,速度上就有提升。 — > 本质是指它的ALU一次性可以最多处理多少数据位。若理解为参与运算的寄存器的数据长度也行。这里提到的位也可以称作单片机的字长
    • RAM512B怎么算的?
      • 片内RAM:标准51(片内128B)的片内RAM地址从00H~7FH共128B(16#7FH转换为10#127 + 00H占1位= 128B),而高128B(16#80H~16#FFH )给到SFR,特殊功能寄存器,低128B可以用直接或者间接寻址方式访问,一般算作片内;SFR只能通过直接寻址方式访问,算作片外。对于C52(片内256B)单片机而言,低128B(00H~7FH )位依旧可以直接和间接寻址,高128B(16#80H~16#FFH )只能用作间接寻址。但是SFR的地址依旧为高128B(16#80H~16#FFH )和用户高128B地址重叠,但只能用直接寻址。区别方法是通过指令的寻址方法的不同来区分51单片机比较另类的地方:寻址内存的空间,不靠总线,靠指令的使用来区别。
      • 片外RAM:片外RAM是可拓展的,最大可以拓展到16#0000~16#FFFF共64KB(2^16-1=65535Byte,约等于64KB)。另外要注意的是片内RAM和片外RAM的地址不是连在一起的,如片内0x00和片外0x0000是不同的。片内和片外RAM的定义最早来自于早期51单片机,分别指芯片内部和芯片外部,但是现在几乎所有的51单片机都在芯片内部集成了片外RAM,而真正的芯片外拓展已经很少用到。所以虽然她叫片外RAM,但是它还在芯片内部,具体区分如下:
        • data:片内RAM,从0x00~0x7F;
        • idata:片内RAM,从0x00~0xFF ;(虽然data包含在idata内,但是所带来的访问速度是不同的。在keil的默认设置下,data是可以省略的,即什么都不加时变量自动定义到data区域。data区域使用直接寻址,访问速度时最快的;使用idata之后用的是通用寄存器间接寻址,速度会比data慢一些。而且大部分时候我们不太希望直接访问到16#80H~16#FFH,因为它们很多时候用来做中断和函数调用堆栈,所以大部分情况下,在使用的时候直接用data即可。)
        • pdata:片外RAM,从0x00~0xFF ;(用通用寄存器间接寻址。)
        • xdata:片外RAM,从0x0000~0xFFFF ;(寻址范围从0-64K,但是需要使用2个字节寄存器DPTRH和DPTRL来间接寻址才行,它的访问速度是最慢的。)
      • 当前使用的STC8952共有512B的RAM,其中片内256B和片外256B。
      • 为什么说外部拓展最高可以拓展到64KBYTE?因为DPTR寄存器是16位(2^16-1=65535Byte,约等于64KB)的。
    • 51单片机的地址总线是16位的:
      • P0(P0.0~P0.7)口:双向IO口,既可作为低8位的地址总线,又可作为8位双向数据总线,分时复用。当P0口作为通用IO口时,需要外接上拉电阻以增加驱动能力。
      • P2(P2.0~P2.7)口:准双向IO口,可作为高8位的地址总线。P2是动态的IO口,虽然输出能被所存,但是不能稳定的输出。
    • 单片机中RAM的发展:
      • 最开始,使用8位的地址总线,那么所能寻址的最大宽度就是256B(从0到2^8-1=255字节)。
      • 后来,地址总线拓展到16位,最大寻址宽度变成了2^16-1=65535Byte,约等于64KB。
      • 32位CPU的出现,不仅带来了32位的数据总线,也带来了32位的地址总线,此时的最大寻址宽度变成了2^32,4亿多字节,合下来4GB,在嵌入式领域基本用不完了。
      • 随着PC和手机的发展,64位处理器出现,相应的地址总线也增加了数倍,以满足更高要求的使用场景。
    • ROM:8K,flash。
    • 51的外部晶振选用11.0592MHZ或12MHZ:
      • 1个机器时间的周期=12次振荡/震荡频率=12/(12MHZ)=1us;如果用11.0592那么除不尽,所以做定时器延时的时候用12MHZ更加准确。
      • 11.0952MHZ=11059200=115200*96,可以直接分频到115200以及所有需要的波特率(9600/115200/19200/38400/57600bps)。所以11.0592MHZ可以满足常见的串行通信标准波特率。
      • 1个机器周期(=12个振荡周期)指令往下读一次。所以如果没有晶振电路,程序就执行不了。
    • 89C52的最小系统:

VCC系统电源用C6/C5电容的目的是滤波,把交流干扰导入GND;
RST电路上电瞬间C1电容充电,此时会把RST引脚接通,实现单片机上电复位;
晶振电路是必要的,没有晶振程序不能运行;

  • STC89C52 35I-DIP40命名规则:

    • STC89:STC公司 12T/6T 8051系列
    • C:表示工作电压5.5~3.8V
    • 52: 表示8K字节ROM和512字节RAM
    • 35: 工作频率可到35MHZ
    • I: 工业级,-40℃到85℃
    • DIP: 封装类型PDIP
    • 40: 40管脚数
  • 关于IO口:

    • 带锁存和不带锁存的IO口的区别:不管IO口带不带锁存,都可以实现数据的保持(意味着就算没有在while中反复循环置位。而只用触发一次,也能使信号被保持),但是区别在于带锁存器的IO口抗干扰能力强,输出稳定,不会因为外部环境的干扰导致而使得IO口的状态发生变化;而不带锁存器的IO口可能会由于外部干扰导致信号状态变化。对于不带锁存的IO口,可以选择利用while循环反复刷新以对抗外部干扰。另外,不带锁存器的IO口意味着结构简单,响应速度更块,适合一些高速数据传输场景。
    • 除了P0口没有内部集成上拉电阻以外(需要外接上拉电阻),其他三个口内部都有上拉电阻。

准双向口

  • 什么叫准双向IO口:51单片机的P0/P1/P2/P3都是准双向IO口。它即可作为输入,也可以作为输出。但其输出高电平时依赖上拉电阻,驱动能力很弱(弱上拉)。准双向口时51单片机的一大特点。

  • 准双向口特点:

    • 默认状态为高电平(弱上拉):
      • 如果没有外部施加信号,IO口会保持高电平。
      • 在复位后,准双向口默认配置为输入模式,并通过一个弱上拉电阻连接到高电平。
    • 输出时,主动驱动:
      • 当程序向准双向口写入0时,IO口会主动输出低电平(相当于导线接通,使得电流可以从外部流入IO口,从而驱动外部器件)。
      • 当程序向准双向口写入1时,IO口会释放驱动,回到高阻状态(依靠若上拉电阻保持高电平)。
    • 输入时,依赖外部信号:
      • 若需要配置输入模式,需要写入1(keil的头文件已经默认配置了,如果是汇编,那就需要人工置1),保证其已进入高阻态(或叫做禁止状态)
      • 高阻态下,外部信号可以驱动该引脚,单片机可以读取到外部信号的电平
    • 弱上拉电阻的作用:
      • 弱上拉电阻的存在使得准双向口在未被驱动时默认为高电平,避免引脚悬空。
  • 准双向口和真正的双向IO口的区别:

    • 准双向口:
      • 默认有上拉电阻。
      • 输出高电平是通过释放驱动(高组态)实现的,而不是主动驱动高电平。
      • 切换为输入模式需要通过写1来实现。
    • 真正的双向口:
      • 没有弱上拉电阻。
      • 输出高电平是主动驱动的。
      • 输入和输出模式的调整通常是通过专门的方向寄存器来配置。
  • 高电平和高阻态的区别,如配置P0.0=1;如何判断P0.0当前是高电平还是高阻态:

    • 高电平:
      • P0.0引脚的电压为高,接近VCC
      • 高电平可能是因为内部或者外部电路主动拉高的。
    • 高阻态:
      • P0.0引脚内部电路完全断开,单片机不主动输出任何电平。引脚状态完全由外部电路决定,可能是高电平(弱上拉)或者低电平(外部下拉)。
    • 判断方法:
      • 配置P0.0=1后,执行if(P0.0==1):若为真,则说明此时为高电平;若为假,则说明P0.0引脚被外部拉低,此时P0.0为高阻态,外部电路将引脚拉低。

理解地址和数据的关系

  • 通俗的解释地址和数据的关系(地址的宽度表达的是数据):把地址单元比作一个一个的机器。那么cpu 总共能标识的机器数量就是地址寄存器最大所能表达的数量(地址寄存器类型和芯片地址总线相关,不是随便定义的),再多就溢出了。每个机器都有唯一的编号(以数量依次排开),所有编号的格式(数据类型)是统一的。地址的宽度(若地址单元为1byte,那么int类型就是2个地址单元的宽度)用来表达数据(int最多就表达2^16 这么多内容)。数据就好比机器所能生产出来的产品类型(假如一个地址单元是1byte,那么这个地址单元就能生产出2^8 =256种产品)。另外来看,内存或者硬盘存数据,其实存入的数据的大小就是数据占用的实际地址空间。我们读取数据的时候,选择相应的地址片段的长度,就可以得到这个片段长度表达出来的内容,内容即数据,这样就得到想要的数据。

使用keil5

  • 新建Source Group: 找不到厂家STC的名字,可以找Atmel--AT89C52
  • 新建程序文件: 右键Source Group文件夹,若选择C语言,会自动建立c文件。
  • 选择生成HEX文件

勾选Create HEX File才能生成供STC-ISP下载的文件。

  • 编译文件。
  • 利用STC-ISP下载程序。
  • 点击下载后,冷启动单片机,下载才会成功:

驱动LED

  • 开发板原理图:

如原理图,P2控制LED
VCC供电,电流从VCC流向P2口,所以 =0,灯亮/ =1 灯灭

  • 用到P2口,程序编辑器界面按#,鼠标右键点击加入头文件;
  • 不识别2进制(P2=11111110),需要转换成16进制显示(P2=0xFE)
#include <REGX52.H> //头文件
void main()
{
while(1)
{
P2=0xFE; //11111110,亮点第一个
P2=0xFF; //全灭
P2=0x55; //01010101
}
}
#include <REGX52.H> //头文件
#include<INTRINS.H>
//实现延时
void Delay500ms() //@12.00MHZ
{
unsigned char i,j,k; //unsigned char: 0~255
_nop_(); //表示空语句,什么都不做,需要引用头文件 #include<INTRINS.H>
i = 4;
j = 205;
k = 187;
do
{
do
{
while(--k);
}while(--j);
}while(--i);
}
void main()
{
while(1)
{
P2=0xFE; //11111110
Delay500ms(); //调用延时方法
P2=0xFF;
Delay500ms();
P2=0x55; //01010101
Delay500ms();
//流水灯
P2=0xFE;
Delay500ms();
P2=0xFD;
Delay500ms();
P2=0xFB;
Delay500ms();
P2=0xF7;
Delay500ms();
P2=0xEF;
Delay500ms();
P2=0xDF;
Delay500ms();
P2=0xBF;
Delay500ms();
P2=0x7F;
Delay500ms();
}
}
  • 利用STC自动生成延时函数

  • 参数化延时函数

void Delay1ms(unsigned int xms) //@12.000MHZ
{
unsigned char i,j;
while(xms)
{
i=2; //i和的迭代次数用STC软件自动生成出来
j=239;
do
{
while(--j);
while(--i);
}
xms--;
}
}
  • REGX52.H头文件中有方法可以设置单个位

独立按键

  • 开发板原理图

如原理图,单片机上电,所有IO口默认是高电平;没按下为高电平,按下为低电平。

  • 通过按键控制LED:
#include <REGX52.H>
void main()
{
while(1)
{
if(P3_1==0)
{
P2_0=0;
}
else
{
P2_0=1;
}
}
}
  • 防抖;利用延时代码可以消除抖动(按下按键后5 ~ 10ms再数据采集,收到松开按键信号后5 ~ 10ms 再去判断按键是否松开。)
#include <REGX52.H>
//延时函数依旧可以用STC软件生成
void Delay(unsigned int xms)
{
unsigned char i,j;
while(xms--)
{
i=2;
j=239;
do
{
while(--j);
} while(--i);
}
}
void main()
{
while(1)
{
if(P3_1=0) //==0表示按钮按下
{
Delay(20); //按下时消抖
while(P3_1==0); //监控按下后是否松手,不松手则一直在当前while中循环不跳出去,直到松手程序才往下执行
Delay(20); //松开时消抖
P2_0=~P2_0; //取反操作,原来亮,现在就不亮。
}
}
}
  • 左移右移
#include <REGX52.H>
void Delay(unsigned int xms)
{
unsigned char i,j;
while(xms--)
{
i=2;
j=239;
do
{
while(--j);
} while(--i);
}
}
void main()
{
usigned char LEDNum=0;
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);
LEDNum++; //控制左移
P2=~LEDNumm; //P2默认值是FF,直接左移P2没有意义
}
}
}
#include <REGX52.H>
//延时函数
void Delay(unsigned int xms)
{
unsigned char i,j;
while(xms--)
{
i=2;
j=239;
do
{
while(--j);
} while(--i);
}
}
sunsigned char LEDNum; //声明全局变量
//主函数
void main()
{
P2=~0x01; //初值00000001取反11111110
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);
LEDNum++;
if(LEDNum>=8) //实现循环左移
LEDNum=0;
P2=~(0x01<<LEDNum); //左移
}
//********************************//
if(P3_0==0)
{
Delay(20);
while(P3_0==0);
Delay(20);
if(LEDNum==0)
LEDNum=7; //从最右开始
else
LEDNum--;
P2=~(0x01<<LEDNum); //右移
}
}
}
  • 注意:C语言的自定义函数可以定义在main()前面,也可以定义在main()后面;当定义在main()后面,需要在main()前面声明,如:void Delay(unsigned int xms);把它加在main()前面。

  • 演示:

数码管点亮了是因为138译码器上共用了P22/P23/P24

数码管

  • 原理图

74HC138: 用输入(二进制,2的3次方)的三个(A/B/C)引脚实现控制输出(十进制,1..8)(Y0/Y1/Y2/Y3/Y4/Y5/Y6/Y7)8个引脚。
74HC245: 若DIR接高电平,数据流向从A->B;反之DIR接低电平,数据流向从B->A
因为单片机的高电平驱动能力弱,低电平驱动能力强,所以有时单片机IO口的驱动用低电平驱动。
从原理图来看,开发板的数码管共阴极。

  • 74HC138真值表

若要输出LED5,则P2_4(A2)=1,P2_3(A1)=0,P2_2(A0)=1。
E3对应原理图G1/E2对应原理图G2A/E1对应原理图G2B。
换算-> | 000:0 | 001: 1 | 010: 2 | 100: 4 | 011: 3 | 101: 5 | 110: 6 | 111: 7 |

51单片机学习笔记
  • 用74HC138缺点:一次只能输出一个位选位(P2_2/3/4只能共同决定一个位)。实际上如果想要使数码管输出看起来像一次输出多个位选位,只需要把循环输出的延时调低,骗过眼睛(动态显示数目管方式)。

  • 数码管的共阴极/共阳极

上面是共阴极,下面是共阴极;判断标准:观察LED电流是从公共端流出还是流入公共端。
每个数码管的11/7/4/2/1/10/5/3一共8位被连接在了一起,控制12/9/8/6中的某个引脚接通,从而控制具体的显示内容。

  • 共阴极,那么我们公共端(12、9、8、6)接地给0才会亮,及可以控制第几个亮;显示的数字则通过下面(11、7、4、2、1、10、5、3即对应字母ABCDEFG)控制,给1才亮,给0不亮;

    • 段选(码):A/B/C/D/E/F/G
    • 位选:12/9/8/6
    • DP构成小数点
  • 静态数码管显示方式:

#include <REGX52.H>
main()
{
P2_4=1; //选中LED5-> "| 000:0 | 001: 1 | 010: 2 | 100: 4 | 011: 3 | 101: 5 | 110: 6 | 111: 7 | "
P2_3=0;
P2_2=1;
P0=0x7D; //显示6,需要2#01111101
while(1){}
}
#include <REGX52.H>
//延时
void Delay(unsigned int xms)
{
unsigned char i,j;
while(xms--)
{
i=2;
j=239;
do
{
while(--j);
} while(--i);
}
}
//共阴数码管段选
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
//共阳数码管段选
unsigned char NixieTable_backup[]={0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff};
void Nixie(unsigned char Location,Number)
{
switch(Location)
{
//共阴极
case 1:P2_4=1;P2_3=1;P2_2=1;break;//8
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
P0=NixieTable[Number];
}
void main()
{
while(1)
{
//循环显示1-2-3
Nixie(1,1);
Delay(200);
Nixie(2,2);
Delay(200);
Nixie(3,3);
Delay(200);
}
}
  • 动态显示数码管方式:
    • 动态显示数码管通常要消影,就是消除在其他位上的影子。因为数码管显示过程:位选 段选 位选 段选 。 在第一次段选完成之后和下一段选之后(下一位选还没选之前),下一位的段码是上一轮的段码 。通俗点语言讲就是,太快了,还忘不了前任。所以我们要做个上一轮的段码清零操作,清零之前记得加一个小延时让它稳定显示,立即清零会导致数码管比较暗。
#include <REGX52.H>
void Delay(unsigned int xms)
{
unsigned char i,j;
while(xms--)
{
i=2;
j=239;
do
{
while(--j);
} while(--i);
}
}
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Nixie(unsigned char Location,Number)
{
switch(Location)
{
//共阴极
case 1:P2_4=1;P2_3=1;P2_2=1;break;//8
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
P0=NixieTable[Number];
Delay(1); //消影
P0=0x00; //复位
}
void main()
{
while(1)
{
Nixie(1,1);
//Delay(200);
Nixie(2,2);
//Delay(200);
Nixie(3,3);
//Delay(200);
}
}

动态数码管显示如图

  • 本次实验中,74HC138+74HC245的方式耗费了大量CPU的IO口(3+8),是比较浪费的。实际应用中,用专用数码管驱动芯片TM1640,内部自带显存,扫描电路。单片机只需要告诉他显示什么即可。可以节省很多IO口。

Xiaocuncun OT技术类博客

__EOF__

  • 本文作者: 一位不愿透露姓名的小村村
  • 本文链接: https://www.cnblogs.com/xiacuncun/p/18772452
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。