声明:该笔记大多整理自b站视频https://www.bilibili.com/video/BV1Mb411e7re?p=1
文章有点长,目录已经不够显示了
单片机是一种可通过编程控制的微型处理器。
单片机芯片本身不能单独运用于某种工程或产品上,他必须要依靠外围数字期间或模拟期间的协调才可发挥其自身的强大功能,所以我们在学习单片机知识的同时不能仅仅学习单片机的一种芯片,还要循序渐进地学习它外围的数字及模拟单片机知识,还要学习常用到的外围电路的设计与调试方法等。
关于51单片机:
下面是51单片机的原理图
CPU:(中央处理单元)负责处理和执行指令 CPU的核心是从程序或应用程序获取指令并执行计算。该过程分为三个关键阶段:获取,解码和执行。CPU从RAM中提取指令,对指令的实际含义进行解码,然后使用CPU的相关部分执行指令。
RAM: RAM是随机存取存储器(random access memory),是计算机内部存储器中的一种,也是其中最重要的,计算机和手机中一般把其叫做(运行)内存,它的速度要比硬盘快得多,所以用运行程序在RAM中,而存放运行时不用的数据则在硬盘中,什么时候需要数据,便把数据从硬盘中拿到内存,但同时RAM断电会丢失数据,所以我们电脑如果断电了就会丢失原来正在运行的数据。
ROM:ROM是只读存储器(Read-Only Memory),它具有断电不丢失数据的特性。
这是普中51单片机开发板,在中间的矩形黑色部分才是单片机原型。
以单片机表示上的标识STC89C51RC-DIP为例(不是上图的标识)
STC-前缀,表示芯片为STC公司生产的产品。其它前缀还有如AT,i,Winbond,SST等。
8-表示该芯片为8051内核芯片。
9-表示内部含 Flash E²PROM 存储器。还有如80C51中0表示内部含 Mask ROM(掩模ROM)内存器;如87C51中7表示内部含 EPROM 存储器(紫外线可擦除 ROM)。
C-表示该器件为CMOS产品。还有如89LV52和89LE58中的LV和LE都表示该芯片为低电压产品(通常为3.3V电压供电);而89S52中的S表示该芯片含有可串行下载功能的Flash储存器,即具有ISP可在线编程功能。
5-固定不变。
1-表示该芯片内部程序储存空间的大小,1为4KB,2为8KB以此类推,即该数乘上4KB就是该芯片内部的程序存储空间大小。程序空间大小决定了一个芯片所能装入执行代码的多少。一般来说,程序储存空间越大,芯片价格也就越高,只要程序能装得下,同类芯片的不同型号不会影响其功能。
RC-STC单片机的内部RAM为512B。还有如RD+表示内部RAM为1280B。
……
具体如下:
这是采用PDIP封装的单片机引脚分布图。
51单片机不一定是40个引脚的,也有20,28,32,44等不同引脚的51单片机。
在观察芯片时,大多可以在它的表面找到一个凹下去的小坑,或是用标识标记的小图形,,这个坑或者标记所对应的引脚就是这个芯片的第一个引脚,然后逆时针方向数下去,由1到最后一个引脚。
按功能类别
电源和时钟引脚:Vcc、GND、XTAL1、XTAL2等
Vcc(40脚)、GND(20脚)——电源引脚,常压+5V,低压+3.3V
XTAL1(19脚)、XTAL2(18脚)——外接时钟引脚,分别为片内振荡电路的输入、输出端
编程控制引脚:RST、PSEN▔、ALE/PROG▔、EA▔/Vpp等
I/O口引脚:P0、P1、P2、P3,4组8位I/O口(每个口可独立控制)
P0口(39脚-32脚)——双向8位三态I/O口,无上拉电阻,为高阻状态,一般外接10KΩ的上拉电阻
P1口(1脚-8脚)——准双向8位I/O口,内带上拉电阻,无高阻状态,输入不能锁存,该口在作为输入使 用前,要先向该口进行写1操作,然后单片机内部才可正确读出外部信号
P2口(21脚-28脚)——与P1口相似
P3口(10脚-17脚)——准双向8位I/O口,内带上拉电阻,第一功能与P1口相似,第二功能各引脚定义 如下表,P3口的每一个引脚均可独立定义为第一功能的输入/输出或第二功能
单片机是一种数字集成芯片,走的是数字电路。而数字电路中,只存在高低两种电平。一般定义单片机的输出和输入为TTL电平,高电平为+5V,低电平为0V。而计算机的串口为RS-232C电平,高电平为+12V,低电平为-12V。
所谓电平,是指两功率或电压之比的对数,有时也可用来表示两电流之比的对数。电平的单位分贝用dB表示。常用的电平有功率电平和电压电平两类,它们各自又可分为绝对电平和相对电平两种。
电平是一种反映能量变化的物理量,它是根据电路中功率、电压、电流的相互关系来确定的。它是某被测点功率P,与某一基准功率P之比的常用对数,其表达式为:
电平=101g(p2/p1)dB
在数字电路中,只存在两种电平,即为高电平和低电平。深入认识电平特性,在今后的开发工作中有着很重要的意义。
单片机的输入输出电平跟电脑的输入输出电平不是不一样吗?那么它们怎么互相通信呢?很简单,只要在单片机和电脑之间增加一个电平转换芯片就可以了。
电平不但是只有TTL以及RS-232C电平,此外还有CMOS、LVTIL、ECL、PECL等逻辑电平,而这些逻辑电平还有着5V系列、3.3V系列等的分类。
TTL电平信号使用的最多,这是因为数据的表示都是采用二进制的,+5V等价于逻辑1,0V等价于逻辑0。
CMOS集成电路电源电压可以在较大范围内变化,所以它对电源的要求并没有那么严格。
注意,TTL和CMOS电路之间是有逻辑电平关系的,而且他们之间能够互相转换。
与
必须都有,否则就没有
C语言中运算符为&
运算规则
0&0=0
0&1=1&0=0
1&1=1
运算符号
C语言中&表示“按位与”,变量之间按二进制位数对应关系一一进行与运算
或
只要其中之一有就有
C语言中运算符为|
运算规则
0|0=0
0|1=1|0=1
1|1=1
运算符号
C语言中|表示“按位或”,变量之间按二进制位数对应关系一一进行或运算
非
求反
C语言中运算符为!
运算规则
!0=1
!1=0
运算符号
C语言中表示按位取反,如~01010101=(10101010);
运算符号
!表示对单一位进行运算
同或
必须相同,否则就没有
逻辑运算符为⊙
异或
必须不同,否则就没有
逻辑运算符为⊕
C语言中有“按位异或”运算”^“
打开开发软件,找到下图选项
选择new project
最好选择放在新建一个文件夹,因为每个新建的project文件包含多个文件
在新弹出的窗口输入以上类型,选择,点击OK。
这个窗口可以点击任意一个都可以,但是建议选择否。
接下来点击target,然后右键Source Group 1,点击Add New item to……。
选择c file ,Name 输入main,点击Add就可以了。
就是说P2口能控制led的亮灭,总共八个口,但是序号是P2_0到P2_7,并且这个P一定是大写。
两位数的十六进制的数恰好能覆盖八个led灯的状态,对于led来说,0是低电平,能使得它亮起来,而默认是八个1,所以八个灯默认是灭的,此时我们想要它亮起来,就要使得它对应的电平发生变化,我们的代码烧录进单片机就有这样的功能。
输入代码
#include
void main(){
while (1){
P2=0xfe;
}
}
先点击红色箭头,再点击蓝色箭头,再点绿色箭头,最后点Ok,这样的创建出来的文件才能烧录进单片机
创建文件
点击红色箭头位置,之后下方提示没有错误和警告即可。
烧录
对于烧录软件单片机型号的选择和串口号的选择,建议观看推荐的b站视频。
打开烧录软件
打开程序文件
打开文件,后缀必须为hex,否则回到创建文件处,或者再上一级,重新操作一遍
之后选择下载/编程,重启单片机即可。
此时单片机的第一个led灯会亮起来。
后面创建文件的操作都是相同的,但是注意要将文件储存在一个文件夹中,容易整理。
对于led灯的闪烁,主要其实就是利用停顿,调出一个函数执行一段时间,不输出内容,不返回值,消耗时间,对于调出函数,主要依靠烧录软件生成的函数。
如下图
红色箭头找到软件延时计算器,系统频率在单片机身上有写,定时长度可以自己定,间隔可以自己控制,最后能看出闪烁就可以,指令集选择Y1。此时就可以生成代码,然后复制代码。进行使用,当然结合一点c语言的知识,可以自己修改相关代码延长时间。
下面是我自己写的代码,可以实现闪烁效果。
注意:#include
#include
#include
void Delay500ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main(){
while(1){
P2=0xFE;
Delay500ms();
P2=0xFF;
Delay500ms();
}
}
//如果把_nop_()去掉的话,就不用第二个头文件了。
流水灯其实就是灯向流水一般流动显示,就是结合上述的内容,添加单个led的控制,对于单个的led灯,我们其实可以进行单个赋值修改电平,例如:P2_0=0;就是将这个对应的灯点亮,要实现流水的效果的话,就让其熄灭又亮起嘛。
#include
#include
void Delay1ms(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms){
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main(){
unsigned int i=10;
while(1){
P2=0xFE;
Delay1ms(i);
P2=0xFD;
Delay1ms(i);
P2=0xFB;
Delay1ms(i);
P2=0xF7;
Delay1ms(i);
P2=0xEF;
Delay1ms(i);
P2=0xDF;
Delay1ms(i);
P2=0xBF;
Delay1ms(i);
P2=0x7F;
Delay1ms(i);
}
}
//以上采用十六进制的代码其实比较麻烦,可以采用单个赋值的方式改变电平。
注意:这里的按键对应P3_0是第二个独立按键,独立按键就是黑色一排的四个按键。
通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。
抖动时间的长短由按键的机械特性决定,一般为5ms~10ms。这是一个很重要的时间参数,在很多场合都要用到。按键稳定闭合时间的长短则是由操作人员的按键动作决定的,一般为零点几秒至数秒。为确保CPU对键的一次闭合仅作一次处理,必须去除键抖动。在键闭合稳定时读取键的状态,并且必须判别到键释放稳定后再作处理。
抖动原理图:
消去抖动,我们一般采用延时的方式。
消抖代码我们就让按下时延时20毫秒,执行相应代码,再松开时延时20毫秒
#include
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main(){
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
P2_0=~P2_0;
}
}
}
烧录之后,按下按键就可以发光了。再按一下就会关闭。
这里控制其移动需要对移动的逻辑符号很熟悉。
这里我们需要做的就是让灯的亮灭和二进制一样,也就是逢二进一,比较重要的就是运算符++,就是让这个数加一,这样加下去逢二进一,在单片机的灯上体现出来。
#include
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main(){
unsigned char LED=0;
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
LED++;
P2=~LED;
}
}
}
这里值得注意的是,这里的P2取的是LED的反,因为我们需要让LED为低电平才能亮(赋值为零)。
这里说的移位就是灯的亮灭,一个紧挨着的灯亮起来,其它都灭。
我们要做的就是用两个按键控制它的左右位移,但是要注意在边缘位置的特殊情况。
#include
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main(){
unsigned char LED;
while(1){
if(P3_0==0){
Delay(20);
while(P3_0==0);
Delay(20);
if(LED>=8){
LED=0;
}
P2=~(0x01<<LED);
LED++;
}
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
if(LED==0){
LED=7;
}else{
LED--;
}
P2=~(0x01<<LED);
}
}
}
这里代码的冗长源于消抖的需要(doge)。
<<表示左移。
原理图这里讲不清楚,详情请参见b站推荐视频。
但主要我们要做的就是选择数码管,再对那个数码管编程,输入相应的十六进制数可以让对应的数码管显示相应的数字,这里我们先从单个的数码管显示进行演示,下面是原理图:
#include
unsigned char NT[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void N(unsigned char location,Number){
switch(location){
case 1:P2_4=1;P2_3=1;P2_2=1;break;
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=NT[Number];
}
void main(){
N(6,2);
while(1){
}
}
这里的P2_4,3,2 的1和0,结合起来表示的就是选择数码管的对象,而后面的Number就是你需要让它显示的数字,这里我们先定义了一个数组,可以调用其对应十六进制的数字。
这里的动态指的就是不断扫描静态显示的数码管,通过余晖效应和视觉暂留现象产生的一直亮的错觉,实际上的它在不断的闪烁中。
#include
unsigned char NT[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms--){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
void N(unsigned char location,Number){
switch(location){
case 1:P2_4=1;P2_3=1;P2_2=1;break;
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=NT[Number];
Delay(1);
P0=0x00;
}
void main(){
while(1){
N(1,1);
// Delay(200);
N(2,2);
// Delay(200):
N(3,3);
// Delay(200);
}
}
这里我通过不断扫描,将数码管前三位一次显示位1,2,3
需要对逻辑运算进行熟练,对基本的16进制的转化需要更多的训练,最重要的就是对各个端口的熟悉,对于难以理解的小伙伴,建议结合b站视频。