(一)单片机编程与下载软件
- Keil5 STC-ISP
- Keil5 C51和Keil5 MDK的区别
两者都是Keil系列软件,但前者是用来开发51单片机的,后者是用来开发ARM系列,比如STM32的。
(二)单片机介绍
- 单片机,英文Micro Controller Unit,简称MCU
- 单片机内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能
- 单片机的任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(例如电机,LED等)的控制
- 单片机跟计算机相比,单片机算是一个袖珍版计算机,一个芯片就能构成完整的计算机系统。但在性能上,与计算机相差甚远,但单片机成本低、体积小、结构简单,数量远超计算机,在生活和工业控制领域大有所用
- 同时,学习使用单片机是了解计算机原理与结构的最佳选择
- STC89C52单片机
- 所属系列:51(五妖)单片机系列
- 51单片机是指80年代intel开发的8051单片机内核的统称
- 公司:STC公司
- 位数:8位
- RAM:512字节(random access mermory)(掉电消失)(工厂)
- ROM:8K(Flash) (read only memory)(长期存储)(仓库)
- 工作频率:11.0952MHz(本开发板使用)
Vss是电源正极
Gnd是电源负极
XTA时钟(外接晶振)
RxD复位
P0p1p2都是八个管脚因为位数是八位
右上角电源正极与负极之间有滤波器
(四)51单片机的各个模块
单片机:单片机在安上时方向不能安反,不然电源会接反,用单片机的缺口确定方向(p0→p20)
八位LED模块:操作I/O口控制LED亮灭
4x4矩阵键盘:
18B20
红外接收:接受遥控器信号
2.4G无线接口:调用无线遥控模块操控
4位独立按键:
供电下载通讯接口:下载程序
CH340
电源开关
AD/DA:模数转换器。单片机本质是数字处理器,AD可通过热敏电阻、光敏电阻、外接模块传输模拟信号后转化为数字信号交给单片机处理;DA单片机将数字信号转化为模拟信号给外部使用。
蜂鸣器:输出声音
74H245D:驱动数码管
步进电机模块:步进电机可精确控制角度(一圈半圈)不会受电压影响
八位数码管:
电位器:调节液晶屏对比度
138译码机:扩张I/O口;驱动数码管
24c02:实际上是rom掉电不丢失
DS18B20:测温度模块
8x8LED点阵:LED阵列
595芯片
12M晶振:时钟
(复位按键)
二、LED灯
1.中文名:发光二极管
2.外文名:Light Emitting Diode
3.简称:LED
4.用途:照明、广告灯、指引灯、屏幕
三、LED模块
首先电流从VCC它出来之后通过一个电阻,电阻其实是4个一体。在板上的这2个位置这2个小黑子一块儿总共有8个引脚在里面,每个小黑子就是4个小电阻。这个电阻是1000欧的,那4个电阻上面写的有数字是102读出来是1k。102就是10它类似于类似于科学计数法但是又不太一样就10然后呢后面再加2个零是倍率,前面两位是有效数字然后第3位呢是倍率。比如说473 是47后面再加3个零那么它就是是47000
1.单片机p2的引脚连接到LED上
2.RP9和RP10是限流电阻的作用
3.控制LED亮灭的问题可以转化为正极电压不变,控制负极电压从0v到5v变化(也叫TTL变频)。
4.CPU通过配置的寄存器来控制的硬件电路,硬件电路执行我们想要完成的功能
5.在keil中新建工程,在型号中Atmel中的AT89C52不需要复制启动文件到工程文件中(相当于桌子)
6.添加工程文件(相当于桌子上的纸)(我感觉很像是VS里面创建空文件跟添加模块)
编译点击三个按钮中间那个
此时会弹出warning:cannot determine root segment(缺少主函数)
(一)点亮一个灯泡
十进制 | 二进制 | 十六进制 | 十进制 | 二进制 | 十六进制 |
0 | 0000 | 1 | 8 | 1000 | 9 |
1 | 0001 | 2 | 9 | 1001 | 10 |
2 | 0010 | 3 | 10 | 1010 | A |
3 | 0011 | 4 | 11 | 1011 | B |
4 | 0100 | 5 | 12 | 1100 | C |
5 | 0101 | 6 | 13 | 1101 | D |
6 | 0110 | 7 | 14 | 1110 | E |
7 | 0111 | 8 | 15 | 1111 | F |
写入主函数,加入头文件;
While是一个绕圈函数
注意P2的大小写
然后点击这个按钮
弹出这个界面点击OK创建文案
然后在STC-ISP中打开文件,下载到单片机里冷启动
这样单片机的一个LED就亮了
(二)点亮一个闪烁的灯泡
在STC-ISP中的软件计时器调一下系统频率和定时长度和8051指令集:因为我的这块单片机晶振上的频率是11.0592Mhz,系统频率如是;定时长度写500ms写在P2=0xFE和P2=0xFF后边就可以按一秒亮灭;STC-Y1可以适用于stc89c5rc单片机。
#include <REGX52.H>
#include<INTRINS.H>
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();
}
}
下载到单片机即可得到一个闪烁的灯泡
(三)LED流水灯
#include <REGX52.H>
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;//1111 1110
Delay500ms();
P2=0xFD;//1111 1101
Delay500ms();
P2=0xFB;//1111 1011
Delay500ms();
P2=0xF7;//1111 0111
Delay500ms();
P2=0xEF;//1110 1111
Delay500ms();
P2=0xDF;//1101 1111
Delay500ms();
P2=0xBF;//1011 1111
Delay500ms();
P2=0x7F;//0111 1111
Delay500ms();
}
}
(四)独立按键控制LED亮灭
轻触按键:相当于是一种电子开关,按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开.
底座+金属弹片
按下开关就是连接两个引脚(I/O口变为低电频,此时寄存器0),松开就是断开引脚(I/O口保持高电频,此时寄存器是1)
不过我的这块单片机第一个开关接在P3.1(因为第一个独立按键的管脚接到P31上了)
C51数据运算
类别 | 运算符 | 意义 | 类别 | 运算符 | 意义 |
算术 | + | 加 | 逻辑 | && | 逻辑与 |
- | 减 | || | 逻辑或 | ||
* | 乘 | ! | 逻辑非 | ||
/ | 除 | 位运算 | << | 按位左移 | |
% | 求余 | >> | 按位右移 | ||
= | 赋值 | & | 按位与 | ||
判断 | > | 大于 | | | 按位或 | |
>= | 大于等于 | ^ | 按位异或 | ||
< | 小于 | ~ | 按位取反 | ||
<= | 小于等于 | ||||
== | 等于 | ||||
!= | 不等于 |
<<n、>>n:指n位按位左移是指最高n位出去,最低n位补0,其他位数向左进n位
Eg:0011 1100<<1→0111 1000
Eg:0011 1100>>2→0000 1111
&:与
0001 1000&0010 1010→0000 1000
(把位拆开,一位一位进行比较0&0→0、1&1→1、0&1→0)
|:或
0001 1000|0010 1010→0011 1010
(把位拆开,一位一位进行比较0&0→0、1&1→1、0&1→1)
^:按位异或
0001 1000^0010 1010→0010 1010
(把位拆开,相同则为0,不同为1)
~:取反
~0001 1000→1110 1000
C51基本语句
对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动。肉眼无法观测,而单片机可以检测的到,所以可能会产生一些误差。
硬件方面使用电路操作进行过滤:
软件处理:delay二十毫秒再继续操作
(五)独立控制LED的亮灭
按键消抖,检测松手
#include <REGX51.H>
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;//将LED状态取反(如果原先是接通现在则为断开,反之亦然)
}
}
}
(六)独立按键控制LED显示二进制
这段代码仍是以独立按键控制LED亮灭为基础
#include <REGX51.H>
void Delay(unsigned int xms ) //@11.0592MHz
{
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);
}
}
}
(1)将代码写到按键消抖,检测松手那里后如果在加入一行P2++;会出现取反的情况。因为p2在最开始的时候是1111 1111的状态(即高电压的状态),再加一的话会直接变成0000 0000的状态(即全亮的状态)。
(2)将代码写到按键消抖,检测松手那里后如果在加入一行P2++;和一行P2=~P2;会一直处于全亮状态。在P2++执行过后(1111 1111变为0000 0000),在执行P2=~P2;(0000 0000又变回1111 1111)
(3)解决方法是重新定义一个unsigned char LEDNum=0;再在按键消抖,检测松手后加入LEDNum++;和P2=~LEDNum;
#include <REGX51.H>
void Delay(unsigned int xms ) //@11.0592MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
unsigned char LEDNum=0;
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);
LEDNum++;
P2=~LEDNum;
}
}
}
(七)独立按键控制LED位移
#include <REGX51.H>
void Delay(unsigned int xms )
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
P2=~0x01;
unsigned char LEDNum=0;
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);
LEDNum++;
if(LEDNum>=8)LEDNum=0;
P2=~(0x01<<LEDNum);
}
}
}
向前的LED
#include <REGX51.H>
unsigned char LEDNum=0;
void Delay(unsigned int xms )
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{
P2=~0x01;
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);
}
}
}
}
四、数码管模块
LED数码管:数码管是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成“8”字型的器件
共阴极连接
共阳极连接
(一)数码管的静态显示
1.单个数码管的操作
(1)以共阴极连接为例,管线3、8接地,则其他引脚为位选端,需要点亮的引脚为“1”不亮的为“0”,生成LED的段码的数据。
(2)以阳阴极连接为例,管线3、8接VCC,所以直接按共阴极连接取反即可。
2.四位一体的数码管
1.COM是公共端的意思(而这个数码管是共阴极的)所以选中的话是标“0”,不选中的话是 “1”。与此同时,阳码给“1”是亮,给“0”是灭。
2. 74HC245是一个双向数据缓冲器(数据缓冲/提高驱动能力)OE跟使能端差不多(接地就工作VCC不工作)DIR是diretion
3.江科大自化协给74HC245供电的VCC上有个滤波器,但我这个好像没有
4.74HC245和数码管之间还有排组,四个一组,一组100欧。(这两个写着101的小黑子)
5.OE与这个跳线器的OE相联,他的OE与VCC相联,哪边的电势高就把数据从哪边送到另一边
6.以共阴极数码管为例,可以将四位共阴极的一端状态写成“1”或“0”,然后其他引脚为位选端,需要点亮的引脚为“1”不亮的为“0”。这样写的话就会暴露出四位数码管共用管脚的弊端,就是一次只能显示一个数字,即使是四位都在显示数字也显示的是同一个数字。共阳极位选端取反就行。
8个LED灯接的是在138译码器的输出端
而后就可以以三个端口(A、B、C)作为输入端控制八个输出端(Y0-Y7)(G1、G2A、G2B为使能端,电频有效的话就可以工作,无效就不工作G1直接接到VCC,G2A、G2B接地,上电就工作)。三个输入端相当于输入三个数据,其中C是最高位,A是最低位(输入的数是二进制,后以十进制输出)。138译码器也叫38线译码器。
C | B | A | Y |
0 | 0 | 0 | Y0(LED1) |
0 | 0 | 1 | Y1(LED2) |
1 | 0 | 1 | Y5(LED6) |
给点亮一个LED
(输出的数字“0”为有效)
步骤(是某一位显示一个数字):
1.控制138译码器的三个输入口使八个输出口中的某个为0输出选中(共阴极一端为0)
2.再给段码p0口数据(注意此时数据也是跟输入口一样从高位读到低位)
数组和子函数的内容不赘述了
数组:把相同类型的一系列数据统一编制到某一个组别中,可以通过数组名+索引号简单快捷的操作大量数据
int x[3]; //定义一组变量(3个)
int x[]={1,2,3}; //定义一组变量并初始化
x[0] //引用数组的第0个变量
x[1] //引用数组的第1个变量
x[2] //引用数组的第2个变量
引用x[3]时,数组越界,读出的数值不确定,应避免这种操作
子函数:将完成某一种功能的程序代码单独抽取出来形成一个模块,在其它函数中可随时调用此模块,以达到代码的复用和优化程序结构的目的
void Function(unsigned char x, y)
{
}
返回值 函数名(形参)
{
函数体
}
代码如下:
#include <REGX52.H>
void main()
{
while(1)
{
P2_4=1;
P2_3=0;//LED与输入端引脚冲突也可能会亮
P2_2=1;//给输入端传入数据
P0=0x7D;//给段码传入数据
}
}
使用子函数
#include <REGX52.H>
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Nixie (unsigned char Location,Number)
{
swich(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=0;P2_3=1;P2_2=1;break;
case 5:P2_4=0;P2_3=1;P2_2=0;break;
case 6:P2_4=0;P2_3=0;P2_2=1;break;
case 7:P2_4=0;P2_3=0;P2_2=0;break;
case 8:P2_4=1;P2_3=1;P2_2=1;break;
}
P0= NixieTable[Number];
}
void main()
{
while(1)
{
Nixie(7,2)
}
(一)数码管的动态显示
数码管扫描
消隐
位选 段选 位选 段选 位选 段选
位选与段选交替进行,挨在一起的数据会篡位
所以若要消影可以在段选和位选加入清零
单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间
专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可
#include <REGX52.H>
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Delay(unsigned int xms )
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}}
void Nixie(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=0;P2_3=1;P2_2=1;break;
case 5:P2_4=0;P2_3=1;P2_2=0;break;
case 6:P2_4=0;P2_3=0;P2_2=1;break;
case 7:P2_4=0;P2_3=0;P2_2=0;break;
case 8:P2_4=1;P2_3=1;P2_2=1;break;
}
P0= NixieTable[Number];
Delay(1);
P0=0x00;
}
void main()
{
while(1)
{
Nixie(1,1);
Nixie(2,2);
Nixie(3,3);
}
}
关于第二次任务
代码如下
#include <REGX52.H>
int a=250,i;
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
void Delay(unsigned int xms) //延时函数
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
void Nixie(unsigned char Location,Number)//NixieTube让数码管显示相应数字
{
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= NixieTable[Number];
Delay(1);//消隐
P0=0x00;
}
unsigned char state = 0;//定义初始state
unsigned char num = 1;
unsigned char num2 = 1;
void anjiansaomiao()//按键扫描
{
if(P3_1==0) state = 1;
if(P3_0==0) state = 2;
if(P3_2==0) state = 3;
if(P3_3==0) state = 4;
}
void smg_01()
{
P2_4=1;P2_3=1;P2_2=1;
P0=0x3E;
Delay(1);
P0=0x00;
Nixie(2,1);
Nixie(7,0);
Nixie(8,1);
}
void smg_02()
{
for(i=1;1<2;i++)
{
a=230;//晶振的频率为11.0592m,后分为12分,在经过4毫秒延时,
//所以循环230次就可以有1秒钟的延时
while(a--)
{
P2_4=1;P2_3=1;P2_2=1;
P0=0x3E;
Delay(1);
P0=0x00;
Nixie(2,2);
Nixie(7,num2/10);
Nixie(8,num2%10);
}
num2++;
if(num2==11)
{
num2 = 1;
}
if(P3_1==0)break;
else if(P3_2==0)break;
else if(P3_3==0)break;
}
}
void smg_03()
{
for(i=1;i<=1000;i++)
{
a=83;
while(a--)
{
P2_4=1;P2_3=1;P2_2=1;
P0=0x3E;
Delay(1);
P0=0x00;
Nixie(2,3);
Nixie(7,0);
Nixie(8,3);
P2_4=0;P2_3=1;P2_2=1;
P0=0x40;
Delay(1);
P0=0x00;
P2_4=0;P2_3=1;P2_2=0;
P0=0x40;
Delay(1);
P0=0x00;
}
if(P3_0==0)break;
else if(P3_1==0)break;
else if(P3_2==0)break;
a=83;//扫描时间一样而延时时间却短了2ms所以它就比较亮咯
while(a--)
{
P2_4=1;P2_3=1;P2_2=1;
P0=0x3E;
Delay(1);
P0=0x00;
Nixie(2,3);
Nixie(7,0);
Nixie(8,3);
}
if(P3_0==0)break;
else if(P3_1==0)break;
else if(P3_3==0)break;
}
break;
}
void smg_04()
{
P2_4=1;P2_3=1;P2_2=1;
P0=0x3E;
Delay(1);
P0=0x00;
Nixie(2,4);
Nixie(7,num/10);
Nixie(8,num%10);
if(P3_3==0)
{
while(P3_3==0)
{
P2_4=1;P2_3=1;P2_2=1;
P0=0x3E;
Delay(1);
P0=0x00;
Nixie(2,4);
Nixie(7,num/10);
Nixie(8,num%10);
}
num++;
}
if(num==99)
{
num = 1;
}
}
void main()
{
while(1)
{
anjiansaomiao();
if(state == 1) smg_01();
if(state == 2) smg_02();
if(state == 3) smg_03();
if(state == 4) smg_04();
}
}
我所遇到的奇怪的状况
每到数字刷新他就只闪一下,然后就不动了。当时我以为delay()出现在有系统的程序里面,当执行到这条代码时,单片机出去想执行别的函数,但是此时states为2,然后又进入函数,逻辑闭环。但实际上单片机确实是去执行了其他函数,但是是主函数外的其他函数,这导致单片机每次扫描了一次就没事干了,就暗下来了。