C51普中A6

一、前言

1.1 先认识一下这个块板子

在这里插入图片描述

1.2 板载资源

在这里插入图片描述

1.3 使用

1.3.1 CH340 驱动安装

1.3.2 程序的烧录

在下载程序前先确认下开发板上的 USB 转 TTL 串口模块上的 J39 和 J44 端子短接片是否短接好(即 P31T 与 URXD 连接,P30R 与 UTXD 连接)
在这里插入图片描述

1.4 51单片机介绍

STC89Cxx 芯片介绍我国芯片技术的快速发展,宏晶公司推出的增强型 51 单片机STC89Cxx/STC90Cxx 等系列更受大众喜爱,除内部资源及功能大大增强外,还有一个非常重要的是它支持 ISP(在系统可编程)/IAP(在应用可编程),无需专用编程器或专用仿真器。宏晶公司推出的 51 芯片种类非常多,我们只需选择一款经典的学习即可

  • STC--表示芯片为 STC 公司生产的产品,其他公司的也有 AT、i、SST 等
  • 8--表示该芯片为 8051 内核芯片
  • 9--表示内部含有 Flash EEPROM 存储器,还有如 80C51 中 0 表内部含有MaskROM(掩模 ROM)存储器;如 87C51 中 7 表示内部含有 EPROM(紫外线可擦除ROM)存储器。
  • C--表示该器件为 CMOS 产品。还有如 89LV52 和 89LE58 中的 LV 和 LE 都表示该芯片为低电压产品(通常为 3.3V 电压供电);而 89S52 中 S 表示该芯片含有可串行下载功能的 Flash 存储器,即具有 ISP 可在线编程功能。
  • 5--固定不变。
  • 2--表示该芯片内部程序存储(FLASH)空间大小,1 为 4KB,2 为 8KB,3 为
    12KB,即该数乘以 4KB 就是芯片内部的程序存储空间大小。程序空间大小决定了一个芯片所能装入执行代码的多少。一般来说,程序存储空间越大,芯片价格也越高,所以我们再选择芯片的时候要根据自己需求选择合适芯片。

二、数字电路与C语言基础

2.1 电平特性

单片机是一种数字集成芯片,数字电路中只有两种电平:高电平和低电平
我们暂时定义单片机输出与输入为 TTL 电平,其中高电平为+5V,低电平为 0V。计算机的串口为 RS232 电平,其中高电平为-12V,低电平为+12V。这里强调的是,RS232C 电平为负逻辑电平,大家不要认为上面是我写错了。因此当计算机与单片机之间要通信时, 需要依靠电平转换芯片,比如 MAX232 电平转换芯片。
TTL 电路和 CMOS 电路的逻辑电平关系如下:
①VOH:逻辑电平 1 的输出电压。
②VOL:逻辑电平 0 的输出电压。
③VIH:逻辑电平 1 的输入电压。
④VIL:逻辑电平 0 的输入电压。
TTL 电平临界值:
①VOHmin=2.4V,VOLmax=0.4V。
②VIHmin=2.0V,VILmax=0.8V。
CMOS 电平临界值(假设电源电压为+5V):
①VOHmin=4.99V,VOLmax=0.01V。
②VIHmin=3.5V,VILmax=1.5V。
TTL 和 CMOS 的逻辑电平转换:CMOS 电平能驱动 TTL 电平,但 TTL 电平不能驱动 CMOS 电平,需加上拉电阻。

2.2 二进制中运算

2.2.1 & 与运算

与”运算是实现“必须都有,否则就没有”这种逻辑关系的一种运算。C 语言中运算符为“&”,其运算规则如下:0&0=0,0&1=0(1&0=0),1&1=1。
C 语言中“&&”表示“按位与”运算,意思是变量之间按二进制位数对应关系一一进行“与”运算。如(0101 0101)&&(1010 1010)=0000 0000,而上面讲到的“&”运算只是对单一位进行运算

2.2.2 | 或运算

或”运算是实现“只要其中之一有就有”这种逻辑关系的一种运算。C 语言中运算符为“|”,其运算规则如下:0|0=0,0|1=1(1|0=1),1|1=1。
C 语言中“||”表示“按位或”运算,意思是变量之间按二进制位数对应关系一一进行“或”运算。如(0101 0101)||(1010 1010)=1111 1111,而上面讲到的“|”运算只是对单一位进行运算

2.2.3 非运算

非”运算是实现“求反”这种逻辑关系的一种运算。C 语言中运算符为“!”,其运算规则如下:!0=1,!1=0
C 语言中“”表示“按位取反”运算。如~0101 0101=1010 1010,而上面讲到的“!”运算只是对单一位进行运算。

2.3 C51中的基础知识

C 语言数据类型:
在这里插入图片描述
单片机的 C 语言中常用的基本数据类型如下
在这里插入图片描述
大家在 C 语言的书籍上还能看到有 short int,long int,signed short int 等数据类型,在单片机的 C 语言中我们默认的规则如下:short int 即为 int, long int 即为 long,前面若无 unsigned 符号则一律认为是 signed 型。
C51 扩充数据类型
在这里插入图片描述
在 C51 中,为了增加程序的可读性,允许用户为系统固有的数据类型说明符用 typedef 起别名,格式如下

typedef c51 固有数据类型说明符 别名

三、实验与学习阶段

3.1 点亮第一个LED

GPIO概念
GPIO(general purpose intput output)是通用输入输出端口的简称,可以通过软件来控制其输入和输出。51 单片机芯片的 GPIO 引脚与外部设备连接起来,从而实现与外部通讯、控制以及数据采集的功能。不过 GPIO 最简单的应用还属点亮 LED 灯了,只需通过软件控制 GPIO 输出高低电平即可。当然 GPIO 还可以作为输入控制,比如在引脚上接入一个按键,通过电平的高低判断按键是否按下。
在这里插入图片描述
Led简介
LED 即发光二极管。它具有单向导电性,通过 5mA 左右电流即可发光,电流越大,其亮度越强,但若电流过大,会烧毁二极管,一般我们控制在 3 mA-20mA 之间,通常我们会在 LED 管脚上串联一个电阻,目的就是为了限制通过发光二极管的电流不要太大,因此这些电阻又可以称为“限流电阻”。当发光二极管发光时,测量它两端电压约为 1.7V,这个电压又叫做发光二极管的“导通压降发光二极管正极又称阳极,负极又称阴极,电流只能从阳极流向阴极。直插式发光二极管长脚为阳极,短脚为阴极。仔细观察贴片式发光二极管正面的一端有彩色标记,通常有标记的一端为阴极。
查看开发板上的电路设计
在这里插入图片描述
分析
中 LED 采用共阳接法,即所有 LED 阳极管脚接电源 VCC,阴极管脚通过一个 470 欧的限流电阻接到J19以及J18端子上,要让 LED 发光即对应的阴极管脚应该为低电平,若为高电平则熄灭。
代码

#include "reg52.h"
sbit LED1 = P2^0; 
void main(){
	LED1 = 0;
}

接线
在这里插入图片描述
说明:
我们在代码中使用的是p20针脚,说以我们只能接p20针脚,然后我们的LED灯可以接J19j18任意的针脚,接哪哪亮,因为led都是共阳极嘛!
在这里插入图片描述
代码解析
简单不解析了

3.2 LED 闪烁实验

如果要实现 LED 闪烁,只需循环让 D1 指示灯先亮一会后熄灭。

#include "reg52.h"
// 对系统默认数据类型进行重命名 
typedef unsigned int u16;
sbit LED1 = P2^0; 
void delay(u16 ten){
	while(ten--);
}
void main(){
	while(1){
		LED1 = 0;
		delay(5000);
		LED1 = 1;
		delay(5000);
	}
}

接线和上一个一实验室一样的不需要变化,将程序烧录进去我们直接看效果:
请添加图片描述
代码解析
与上个实验相比,我们增加了一个延时函数,我们就说一下这个延时函数,这个函数无非就是进入了一个while循环,来占用cpu,就是让cpu一直在个while循环内一直循环ten次,这样就起到了延时的作用,还有一个问题就是我们看到这个ten是一个u16类型,u16是个什么类型呢?其实这个u16不是 C 语言数据类型关键字,这是我们重定义的数据类型使用关键字 typedef 对系统默认数据类型 unsigned int 和 unsigned char 重新命名,主要是方便我们代码的书写和变量类型的查看。u16 即代表该变量是16 位的无符号整型数据,u8 代表该变量是 8 位的无符号字符型数据。

3.2 LED 流水灯

如果要实现 LED 流水灯,只需循环让 D1-D8 指示灯逐个点亮。

#include "reg52.h"
// 使用宏定义P2端口
#define LED_PORT P2
// 对系统默认数据类型进行重命名 
typedef unsigned int u16;
typedef unsigned int u8;
void delay(u16 ten){
	while(ten--);
}
void main()
{
	u8 i = 0;
	while(1)
	{
		for(i =0;i<8;i++)
		{
			//将1右移i位然后取反的值付给LED_PORT
			LED_PORT = ~(0X01<<i);
			delay(50000);
		}
	}
}		  

接线:
看代码,我们这里使用的是P2这组端口,这一点我们一定要明确,好了我们开始接线,当然我们可以接8根杜邦线,p00->D1,P07->D8
这我觉得有点麻烦,我就接了四根线
在这里插入图片描述
就是这个样子一次接下去。
现象:
请添加图片描述
代码解析:
因为要一次点亮,所以我们就要让其0-》7脚一次低电平所以我们可以使用一个16进制表示然后我们如果想要让p0脚亮起来来也就11111110这样的一个二进制数,不太好操作所以我们就反着来就是让其中的一个不亮然后再取反不就让其亮了吗,然后再进行向左移以为就可以实现一次点亮了不是吗?
使用函数实现
左移函数是_crol_(),右移函数是_cror_(),要使用这两个函数在我们的程序中必须包含 intrins.h 头文件

3.3 蜂鸣器

蜂鸣器是一种一体化结构的电子讯响器,采用直流电压供电,广泛应用于计算机、 打印机、 复印机、 报警器、 电子玩具、 汽车电子设备、 电话机、 定时器等电子产品中作发声器件。蜂鸣器主要分为压电式蜂鸣器和电磁式蜂鸣器两种类型。
压电式蜂鸣器主要由多谐振荡器、压电蜂鸣片、阻抗匹配器及共鸣箱、外壳等组成。多谐振荡器由晶体管或集成电路构成,当接通电源后(1.5~15V 直流工作电压),多谐振荡器起振,输出 1.5~5kHZ 的音频信号,阻抗匹配器推动压电蜂鸣片发声电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,振荡器产生的音频信号电流通过电磁线圈,使电磁线圈产生磁场,振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声
其实一句话就可概括它们之间的区别,想要压电式蜂鸣器发声,需提供一定频率的脉冲信号;想要电磁式蜂鸣器发声,只需提供电源即可。
我们开发板上使用的蜂鸣器是无源蜂鸣器,属于压电式蜂鸣器类型。这里说的有源和无源,并不是指电源的意思,而是指蜂鸣器内部是否含有振荡电路,有源蜂鸣器内部自带振荡电路,只需提供电源即可发声,而无源蜂鸣器则需提供一定频率的脉冲信号才能发声,频率大小通常在 1.5-5KHz 之间。
看一下板载电路
image
使用三极管进行电流放大,从而驱动蜂鸣器,当 J7 输出高电平,三极管截止,蜂鸣器不得电;当 J7 输出低电平,三极管导通,蜂鸣器得电。

#include "reg52.h"
typedef unsigned int u16;
typedef unsigned int u8;
sbit BEEP = P2^5;
void delay(u16 ten){
	while(ten--);
}
void main()
{
	while(1)
	{
		BEEP=!BEEP;
		delay(1000);
	}
}		  

这个接线就贼拉简单了,我们只需要将代码中的脚与开发板上的J7接上就好了!
代码也比较简单,我们就是将脚上的高低电瓶按照一定的周期往复,这个蜂鸣器就响了。

3.4 静态数码管

数码管是一种半导体发光器件,其基本单元是发光二极管。 数码管也称 LED 数码管,数码管是一种半导体发光器件,其基本单元是发光二极管。 数码管也称 LED 数码管
共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极 COM 接到+5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮。
共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极 COM 接到+5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮。
image
如果使用共阴数码管,需要注意增加单片机 IO 口驱动电流,因为共阴数码
管是要靠单片机 IO 口输出电流来点亮的,但单片机 I/O 口难以输出稳定的、如此大的电流,所以数码管与单片机连接时需要加驱动电路可以用上拉电阻的方法或使用专门的数码管驱动芯片,比如 74HC573、74HC245 等,其输出电流较大, 电路接口简单。
看一下开发板上的板载电路:
image

#include "reg52.h"
typedef unsigned int u16;
typedef unsigned int u8;
#define SMG_P0 P0
u8 gsmg_code[17]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E,0xFF};
void delay(u16 ten){
	while(ten--);
}
void main()
{	
	u8 i= 0;
	while(1)
	{
		for(i=0;i<16;i++)
		{
			SMG_P0=gsmg_code[i];
			delay(90000);
			SMG_P0=gsmg_code[17];	
		}
	}
}		  

接线图:
image
代码解析:
首先我们看一下数组第一位0xC0换算层二进制就是1100000对应的针脚也就76543210这么个对应方式然后看一下我们的接线,我们接线也是0->A以及7->DP这样的方式。其他的数码管led一次对应。

3.5 动态数码管

开发板上板载 2 个四位一体的共阴数码管
image
image
从上图中可以看出,该电路是独立的,动态数码管的控制管脚并未直接连接到 51 单片机的 IO 上,而是段选端连接到 J6 端子上,位选端连接到 J1 端子上。段选端通过 74HC245 芯片驱动这两个共阴数码管的 a-dp 段
如果要想 51 单片机控制动态数码管,就必须将单片机管脚通过导线连接到J6 端子和 J1 端子上。因此需使用一根 8Pin 排线将单片机的管脚与 J6 端子顺序连接,使用一根 8Pin 排线将 38 译码器的输出 J10 端子与 J1 端子顺序连接,使用 3 根杜邦线将单片机的管脚与 J9 端子连接。
74HC138 芯片简介
74HC138D 是一种三通道输入、八通道输出译码器,主要应用于消费类电子产品;
image
image
image
看这个图的时候我们需要注意的就是在看管脚说明的时候,使能控制管脚上带一个横杠的说明是低电平使能,不带的话是高电平,一般来说是这个样子的。
然后我们要控制对应的段选是就要看那个真值表了。

#include "reg52.h"
typedef unsigned int u16;
typedef unsigned int u8;
// 定义数码管对段 码口
#define SMG_P0 P0
// 定义数码管位选信号控制脚
sbit LSA = P2^2;
sbit LSB = P2^3;
sbit LSC = P2^4;
// 共阴极 数码管显示0~f
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
void delay(u16 ten){
	while(ten--);
}
// 动态数码管显示函数
void smg_dispaly(void)
{
	u8 i = 0;
	for(i=0;i<8;i++)
	{
		switch(i)
		{
			case 0: LSC=1;LSB=1;LSA=1;break;
			case 1: LSC=1;LSB=1;LSA=0;break;
			case 2: LSC=1;LSB=0;LSA=1;break;
			case 3: LSC=1;LSB=0;LSA=0;break;
			case 4: LSC=0;LSB=1;LSA=1;break;
			case 5: LSC=0;LSB=1;LSA=0;break;
			case 6: LSC=0;LSB=0;LSA=1;break;
			case 7: LSC=0;LSB=0;LSA=0;break;
		}
		SMG_P0 =  gsmg_code[i];
		delay(100);
		// 消影
		SMG_P0 = 0x00;
	}	
}
void main()
{	
	while(1)
	{
	   smg_dispaly();
	}
}		  

image
代码足够简单我们就不解析了!

3.6 独立按键

由于机械点的弹性作用,按键开关在闭合时不会马上稳定的接通,在断开时也不会一下子断开,因而在闭合和断开的瞬间均伴随着一连串的抖动。抖动时间的长短由按键的机械特性决定的,一般为 5ms 到 10ms。
按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更加简单,通常采用软件消抖。我们开发板也是采用软件消抖,一般来说一个简单的按键消抖就是先读取按键的状态,如果得到按键按下之后,延时 10ms,再次读取按键的状态,如果按键还是按下状态,那么说明按键已经按下。其中延时10ms 就是软件消抖处理
image
接线图
image
其实这个接线图不是固定的,想用那个按键就用哪个?反正我们就是读取低电平!

#include "reg52.h"
typedef unsigned int u16;
typedef unsigned int u8;


sbit KEY1 = P2^2;
sbit LED1 = P2^0;
//使用宏定义独立按键按下的键值
#define KEY1_PRESS	1
#define KEY_NotPRESS	0
// 共阴极 数码管显示0~f
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

void delay(u16 ten){
	while(ten--);
}
u8 key_scan(u8 mode)
{
	static u8 key =1;
	if(mode)key =1;
	if(key==1&&KEY1==0)
	{
		delay(1000);
		key =0;
		if(KEY1==0)
		{
			return 	KEY1_PRESS ;
		}	
	}
	else if(KEY1==1)
	{
		key=1;
		return 	KEY_NotPRESS ;
	}
   
}
void main()
{	
	  	u8 key =0;
	while(1)
	{
		key = key_scan(0);
	   if(key==KEY1_PRESS){
	   	  LED1=!LED1;//LED1 状态翻转
	   }
	}
}		  

代码解析:
我们这里只是用了一个按键,其实多按键也是一样的我们只需要在判断是用或条件加进去就好了!
至于现象的话,我们就不上图了,就是LED的闪烁。

3.7 按键矩阵

独立按键与单片机连接时,每一个按键都需要单片机的一个 I/O 口,若某单片机系统需较多按键,如果用独立按键便会占用过多的 I/O 口资源。单片机系统中 I/O 口资源往往比较宝贵,当用到多个按键时为了减少 I/O 口引脚,引入了矩阵按键
独立按键与单片机连接时,每一个按键都需要单片机的一个 I/O 口,若某单片机系统需较多按键,如果用独立按键便会占用过多的 I/O 口资源。单片机系统中 I/O 口资源往往比较宝贵,当用到多个按键时为了减少 I/O 口引脚,引入了矩阵按键
行列扫描法检测时,先送一列为低电平,其余几列全为高电平(此时我们确定了列数),然后立即轮流检测一次各行是否有低电平,若检测到某一行为低电平(这时我们又确定了行数),则我们便可确认当前被按下的键是哪一行哪一列 的,用同样方法轮流送各列一次低电平,再轮流检测一次各行是否变为低电平, 这样即可检测完所有的按键,当有键被按下时便可判断出按下的键是哪一个键。当然我们也可以将行线置低电平,扫描列是否有低电平。从而达到整个键盘的检测。
线翻转法,就是使所有行线为低电平时,检测所有列线是否有低电平,如果有,就记录列线值;然后再翻转,使所有列线都为低电平,检测所有行线的值, 由于有按键按下,行线的值也会有变化,记录行线的值。从而就可以检测到全部按键。
image
从上图中可以看出,该电路是独立的,4*4 矩阵按键引出的 8 根控制管脚并未直接连接到 51 单片机的 IO 上,而是连接到 JP3 端子上。电路中的 ARRAY_H1 表示矩阵键盘第 1 行,ARRAY_L1 表示矩阵键盘第 1 列。

#include "reg52.h"
typedef unsigned int u16;
typedef unsigned int u8;

#define KEY_PORT P2
#define SMG_PORT P0

// 共阴极 数码管显示0~f
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07, 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

void delay(u16 ten){
	while(ten--);
}
u8 key_scan(void)
{
	u8 key_value =0;
	KEY_PORT =0xf7;
	// 说明有按键按下
   	if(KEY_PORT!=0xf7)
	{
	  delay(1000);
	  switch(KEY_PORT)
	  {
	  	case 0x77 : key_value = 1;break;
		case 0xb7 : key_value = 5;break;
		case 0xd7 : key_value = 9;break;
		case 0xe7 : key_value = 13;break;
	  }
	}
	// 等待按键松开
	while(KEY_PORT!=0xf7);
	KEY_PORT =0xfb;
	// 说明有按键按下
   	if(KEY_PORT!=0xfb)
	{
	  delay(1000);
	  switch(KEY_PORT)
	  {
	  	case 0x7b: key_value=2;break; 
		case 0xbb: key_value=6;break; 
		case 0xdb: key_value=10;break; 
		case 0xeb: key_value=14;break;
	  }
	}
	// 等待按键松开
	while(KEY_PORT!=0xfb);

   	KEY_PORT=0xfd;//给第三列赋值 0,其余全为 1
	if(KEY_PORT!=0xfd)//判断第三列按键是否按下
	{
		delay(1000);//消抖
		switch(KEY_PORT)//保存第三列按键按下后的键值
		{
			case 0x7d: key_value=3;break; 
			case 0xbd: key_value=7;break; 
			case 0xdd: key_value=11;break; 
			case 0xed: key_value=15;break;
		}
	}
	while(KEY_PORT!=0xfd);//等待按键松开
	KEY_PORT=0xfe;//给第四列赋值 0,其余全为 1
	if(KEY_PORT!=0xfe)//判断第四列按键是否按下
	{
		delay(1000);//消抖
		switch(KEY_PORT)//保存第四列按键按下后的键值
		{
			case 0x7e: key_value=4;break; 
			case 0xbe: key_value=8;break; 
			case 0xde: key_value=12;break; 
			case 0xee: key_value=16;break;
		}
	}
	while(KEY_PORT!=0xfe);//等待按键松开
  return key_value;
}
void main()
{	
	u8 key =0;
	while(1)
	{
		key = key_scan();
	   if(key!=0){
	   	  SMG_PORT=	~gsmg_code[key-1];
	   }
	}
}		  

接线图
image
代码解析
代码简单不多说了就,主要是最后那个为啥要按位取反就是因为我们那个数组值是因为我们是共阴的值,如果使用供阳的值,就不需要取反了。

3.8 IO扩展74HC595

开发板板载 2 个 74HC595 芯片,仅需单片机 3 个 IO 口即可扩展 16 个,如果需要还可以将 4 个 74HC595 级联扩展出 32 个 IO,这就实现用少数 IO 资源控制多个设备
image
74HC595芯片使用说明
DS:14脚,串行数据输入引脚,级联的话接上一级的Q7’。
OE:13脚,输出使能控制脚,它是低电才使能输出,所以接GND
ST_CP:12脚,存储寄存器时钟输入引脚。上升沿时,数据从移位寄存器转存带存储寄存器。
SH_CP:11脚,移位寄存器时钟引脚,上升沿时,移位寄存器中的数据整体后移,并接受新的数据(从DS输入)。
MR:10脚,低电平时,清空移位寄存器中已有的数据,一般不用,接高电平即可。
Q7’:9脚,串行数据输出引脚。当移位寄存器中的数据多于8位时,会把已有的位“挤出去”,就是从这里出去的。用于595的级联,将它接下一个74HC595的DS端。
Q1-Q7:1到7脚,并行输出引脚

#include "reg52.h"
// 对系统默认数据类型 进行重命名
typedef unsigned char u8;
typedef	 unsigned int u16;

//移位寄存器时钟输入
sbit SRCLK  = P3^6;
// 存储寄存器时钟输入
sbit rCLK=P3^5;
// 串行数据输入
sbit SER = P3^4;

u8 ghc595_buf[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

#define LEDDZ_COL_PORT P0

// 延时函数
void delay_10us(u16 ten_us){
	while(ten_us--);
}

void  hc595_write_data(u8 dat){
	u8 i=0;
	for(i=0;i<8;i++){
		// 先传输一个字节中的高位
	   SER = dat>>7; 
	   dat<<=1;
	   SRCLK =0 ;
	   delay_10us(1);
	   SRCLK =1 ;
	   // 移位寄存器时钟上升沿将端口数据送入寄存器中
	   delay_10us(1);
	}
	rCLK =0;
	delay_10us(1);
	// 	 存储寄存器时钟上升沿将前面写入到寄存器的数据输出
	rCLK =1;	

}
void main(){
	 u8 i =0;
	 LEDDZ_COL_PORT =0x00;
	 while(1){
	 for(i=0;i<8;i++){
	 	hc595_write_data(0x00);
		hc595_write_data(ghc595_buf[i]);
		delay_10us(50000);//延时 500ms
		delay_10us(50000);
		delay_10us(50000);
	 }
	}
}

接线图
image
接线图说明,这里的接线不是固定方式,当然我们使用p3(456)是固定的其他的是可以变化的,具体主要是看代码以及板子,说明一下,如果我们想让8*8点阵亮绿色可以接他下面的那一排针脚,还有啊那个aq1不一定非要接dp你也可以接a不过这样依次点亮的顺序就变了。各位随意。
代码解析
代码其实也不难,就是我们使用了一个芯片而已,我们按照芯片的说明接三个管脚,并且人家说明了要上升沿时才会传输数据,上沿可能刚开始不明白,其实也很简单,上沿就是又低电平变成高电平的一瞬间这个就是上沿,其他的我们就按照之间一个管脚接一个管脚编程就可以了,这里还有一个要说以下就是要说一下就是我们使用的>>位运算,也就是将我们的第一个数据的高位右移值最右侧,然后将其发出,然后因为我们已经取走了一位,所以我们紧接着要左移一位这样下次就又可以拿到最高为了,这里面涉及到了位运算,不懂的可以去下面看看!
效果
image

3.9 LED点阵实验(点亮其中一个led)

点亮左上角的led灯

#include "reg52.h"
// 对系统默认数据类型 进行重命名
typedef unsigned char u8;
typedef	 unsigned int u16;

//移位寄存器时钟输入
sbit SRCLK  = P3^6;
// 存储寄存器时钟输入
sbit rCLK=P3^5;
// 串行数据输入
sbit SER = P3^4;

u8 ghc595_buf[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

#define LEDDZ_COL_PORT P0

// 延时函数
void delay_10us(u16 ten_us){
	while(ten_us--);
}

void  hc595_write_data(u8 dat){
	u8 i=0;
	for(i=0;i<8;i++){
		// 先传输一个字节中的高位
	   SER = dat>>7; 
	   dat<<=1;
	   SRCLK =0 ;
	   delay_10us(1);
	   SRCLK =1 ;
	   // 移位寄存器时钟上升沿将端口数据送入寄存器中
	   delay_10us(1);
	}
	rCLK =0;
	delay_10us(1);
	// 	 存储寄存器时钟上升沿将前面写入到寄存器的数据输出
	rCLK =1;	

}
void main(){
	 u8 i =0;
	 LEDDZ_COL_PORT =0xfe;
	 while(1){
	 	 hc595_write_data(0x01);
	}
}

3.9 LED点阵实验(显示一个汉字)

image
image
image
image
image
image
然后我们就将这个图形的数据取到了。
下面我们看一下代码

#include "reg52.h"
// 对系统默认数据类型 进行重命名
typedef unsigned char u8;
typedef	 unsigned int u16;

//移位寄存器时钟输入
sbit SRCLK  = P3^6;
// 存储寄存器时钟输入
sbit rCLK=P3^5;
// 串行数据输入
sbit SER = P3^4;

u8 ls[8] = {0x00,0x3C,0x04,0x7C,0x04,0x3C,0x00,0x00};
u8 gled_col[8]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};

#define LEDDZ_COL_PORT P0

// 延时函数
void delay_10us(u16 ten_us){
	while(ten_us--);
}

void  hc595_write_data(u8 dat){
	u8 i=0;
	for(i=0;i<8;i++){
		// 先传输一个字节中的高位
	   SER = dat>>7; 
	   dat<<=1;
	   SRCLK =0 ;
	   delay_10us(1);
	   SRCLK =1 ;
	   // 移位寄存器时钟上升沿将端口数据送入寄存器中
	   delay_10us(1);
	}
	rCLK =0;
	delay_10us(1);
	// 	 存储寄存器时钟上升沿将前面写入到寄存器的数据输出
	rCLK =1;	

}
void main(){
	 u8 i =0;
	 
	 while(1){
	 	  for(i=0;i<8;i++){
		  LEDDZ_COL_PORT = gled_col[i];
		  hc595_write_data(ls[i]);
		  delay_10us(100);
		  hc595_write_data(0x00);
		  }
	}
}

接线图
image
代码解析
代码也是比较简单的对照前面实验的解析就好了
注意
这里吧有一个需要注意的事,就是有可能你的图像是180旋转的,也就是倒过来的,然后我们可能会觉得那我们吧数据的顺序调过来不就好了,是的是可以这样操作的,但不是仅仅将数组的数据倒叙一下就可以完成的,我们需要重新计算。所以大家还是按照解析图一样的接法接吧。
现象
image

3.10 ULN2003芯片

ULN2003 是一个单片高电压、高电流的达林顿晶体管阵列集成电路。它是由7 对 NPN 达林顿管组成的,它的高电压输出特性和阴极箝位二极管可以转换感应负载。单个达林顿对的集电极电流是 500mA。达林顿管并联可以承受更大的电流。此电路主要应用于继电器驱动器,字锤驱动器,灯驱动器,显示驱动器(LED 气体放电),线路驱动器和逻辑缓冲器。ULN2003 的每对达林顿管都有一个 2.7k 串联电阻,可以直接和 TTL 或 5V CMOS 装置
主要特点

  • 500mA额定集电极电流(单个输出)
  • 高电压说出 50V
  • 输入和各种逻辑类型兼容
  • 继电器驱动器
    image
    因为 ULN2003 的输出是集电极开路,ULN2003 要输出高电平,必须在输出口外接上拉电阻。这也就能解释在后面连接直流电机时为什么不能直接将 ULN2003 的 2 个输出口接电机线,而必须一根线接电源,另一个才接 ULN2003 输出口。
    image
#include "reg52.h"
// 对系统默认数据类型 进行重命名
typedef unsigned char u8;
typedef	 unsigned int u16;
// 定义直流电机控制管脚
sbit DC_Motor=P1^0;
// 定义直流电机运行事件 5000ms
#define DC_MOTOR_RUN_TIME 5000

// 延时函数
void delay_ms(u16 ms)
{
	u16 i,j; 
	for(i=ms;i>0;i--)
		for(j=110;j>0;j--);
}


void main(){
	// 开启电机
	DC_Motor=1;
	delay_ms(DC_MOTOR_RUN_TIME)	;
	//关闭电机
	DC_Motor=0;
	while(1){}
}

接线图
image

3.11 ULN2003芯片(步进电机)

是将电脉冲信号转变为角位移或线位移的开环控制元件。在非超载的情况下,电机的转速、停止的位置只取决于脉冲信号的频率和脉冲数,而不受负载变化的影响,即给电机加一个脉冲信号,电机则转过一个步距角。这一线性关系的存在,加上步进电机只有周期性的误差而无累计误差等特点。使得在速度、位置等控制领域用步进电机来控制变的非常的简单。虽然步进电机已被广泛的应用,但步进电机并不能像普通的直流电机,交流电机在常规下使用。它必须由双环形脉冲信号、功率驱动电路等组成控制系统方可使用。因此用好步进电机也并非易事,它涉及到机械、电机、电子及计算机等多专业知识。
通常步进电机的转子为永磁体,当电流流过定子绕组时,定子绕组产生一矢量磁场。磁场会带动转子旋转一定的角度,使得转子的一对磁场方向与定子的磁场方向一致。当定子的矢量磁场旋转一个角度。转子也随着该磁场转步距角。每输入一个电脉冲,电动机转动一个角度前进一步。它输出的角位移与输入的脉冲数成正比、转速与脉冲频率成正比。改变绕组通电的顺序,电机就会反转。所以可以控制脉冲数量、频率及电动机各相绕组的通电顺序来控制步进电机的转动。
image
image

#include "reg52.h"
// 对系统默认数据类型 进行重命名
typedef unsigned char u8;
typedef	 unsigned int u16;

// 定义控制管脚
sbit IN1_A=P1^0;
sbit IN2_B=P1^1;
sbit IN3_C=P1^2;
sbit IN4_D=P1^3;
// 定义按键控制管脚
sbit KEY1=P2^0;
sbit KEY2=P2^1;
sbit KEY3=P2^2;
sbit KEY4=P2^3;
// 使用宏定义 按键按下的值
#define KEY1_PRESS 1
#define KEY2_PRESS 2
#define KEY3_PRESS 3
#define KEY4_PRESS 4
#define KEY1_NOT_PRESS 0
// 定义步进电机速度 值越小越快  最小不能小于1
#define STEPMOTOR_MAXSPEED	1
#define STEPMOTOR_MINSPEED	5
// 延时函数
void delay_ms(u16 ms)
{
	u16 i,j; 
	for(i=ms;i>0;i--)
		for(j=110;j>0;j--);
}

// 步进电机控制函数

void step_motor_28jy48(u8 step,u8 dir)
{
 u8 temp = step	;
 if(dir==0)
 	temp=7-step	;
 switch(temp)
 {
 	case 0: IN1_A=1;IN2_B=0;IN3_C=0;IN4_D=0;break;
	case 1: IN1_A=1;IN2_B=1;IN3_C=0;IN4_D=0;break;
	case 2: IN1_A=0;IN2_B=1;IN3_C=0;IN4_D=0;break;
	case 3: IN1_A=0;IN2_B=1;IN3_C=1;IN4_D=0;break;
	case 4: IN1_A=0;IN2_B=0;IN3_C=1;IN4_D=0;break;
	case 5: IN1_A=0;IN2_B=0;IN3_C=1;IN4_D=1;break;
	case 6: IN1_A=0;IN2_B=0;IN3_C=0;IN4_D=1;break;
	case 7: IN1_A=1;IN2_B=0;IN3_C=0;IN4_D=1;break;
 }
}
u8 key_scan(u8 mode)
{
	static u8 key=1;
	if(mode)key=1;//连续扫描按键
	if(key==1&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))//任意按键按下
	{
		delay_ms(1000);//消抖key=0;
		if(KEY1==0)
		return KEY1_PRESS; 
		else if(KEY2==0)
		return KEY2_PRESS;
		else if(KEY3==0)
		return KEY3_PRESS;
		else if(KEY4==0)
		return KEY4_PRESS;
	}
	else if(KEY1==1&&KEY2==1&&KEY3==1&&KEY4==1)	//无按键按下
	{
		key=1;
	}
	return KEY1_NOT_PRESS;		
}
void main(){
	u8 key=0;
	u8 dir=0;//默认逆时针方向
	u8 speed=STEPMOTOR_MAXSPEED;//默认最大速度旋转
	u8 step=0;
	
	while(1)
	{
		key=key_scan(1); 
		if(key==KEY1_PRESS)//换向
	    {
		    dir=!dir;
	    }
		else if(key==KEY2_PRESS)//加速
		{
			if(speed>STEPMOTOR_MAXSPEED)
			speed-=1;
		}
		else if(key==KEY3_PRESS)//减速
		{
			if(speed<STEPMOTOR_MINSPEED)
			speed+=1;
		}
		step_motor_28jy48(step++,dir); if(step==8)step=0;
		delay_ms(speed);
	}
}

image
代码解析
按键的代码我们不在解释了,然后我们说一下驱动电机,我们上边也有一个表,我们按照这个表就可以驱动了。

3.12 中断系统

中断是为使单片机具有对外部或内部随机发生的事件实时处理而设置的, 中断功能的存在,很大程度上提高了单片机处理外部或内部事件的能力。它也是单片机最重要的功能之一,是我们学习单片机必须要掌握的
你的主任务是洗衣服, 水开报警这是一个中断请求,这一时刻相当于断点处,你响应中断去关火,然后将开水灌入暖水瓶中,这一动作实际上就是处理中断程序,灌完开水后再回去继续洗衣服,相当于处理完中断程序后再返回主程序继续执行主程序。这里需要注意的是,水开是随时都有可能的,但是无论什么时候开,只要一开你将立即去处理它,处理完后再回来继续接着洗刚才那件衣服。单片机在执行程序时,中断也随时有可能发生,但无论何时发生,只要一旦发生,单片机将立即暂停当前程序, 赶去处理中断程序, 处理完中断程序后再返回刚才暂停处接着执行原来的程序。
中断的开启与关闭、设置启用哪一个中断等都是由单片机内部的一些特殊功能寄存器来决定的,在前面章节的学习中我们仅对单片机 IO 口操作过(实际上操作 IO 口即操作 IO 口寄存器,只不过编译器已经帮我们把 IO 口寄存器封装好直接操作 IO 即可,这些可在 51 单片机头文件内查看),从本章开始就会介绍单片机内部更多的特殊功能寄存器以及如何配置它实现相应的功能。
image
图中 INT0 和 INT1 即为外部中断 0 和外部中断 1 输入口。①INT0 对应的是 P3.2 口的附加功能,可由 IT0(TCON.0)选择其为低电平有效还是下降沿有效。当 CPU 检测到 P3.2 引脚上出现有效的中断信号时,中断标志 IE0(TCON.1)置 1,向 CPU 申请中断。②INT1 对应的是 P3.3 口的附加功能,可由 IT1(TCON.2)选择其为低电平有效还是下降沿有效。当 CPU 检测到 P3.3 引脚上出现有效的中断信号时,中断标志 IE1(TCON.3)置 1,向 CPU 申请中断。
(1)中断允许控制
CPU 对中断系统所有中断以及某个中断源的开放和屏蔽是由中断允许寄存器IE 控制的。
image
EX0(IE.0),外部中断 0 允许位; ET0(IE.1),定时/计数器 T0 中断允许位; EX1(IE.2),外部中断 0 允许位; ET1(IE.3),定时/计数器 T1 中断允许位; ES(IE.4),串行口中断允许位;
EA (IE.7), CPU 中断允许(总允许)位。
中断请求标志 TCON
image
T0(TCON.0),外部中断 0 触发方式控制位。当 IT0=0 时,为电平触发方式。
当 IT0=1 时,为边沿触发方式(下降沿有效)。IE0(TCON.1),外部中断 0 中断请求标志位。
IT1(TCON.2),外部中断 1 触发方式控制位。
IE1(TCON.3),外部中断 1 中断请求标志位。TF0(TCON.5),定时/计数器 T0 溢出中断请求标志位。TF1(TCON.7),定时/计数器 T1 溢出中断请求标志位。

外部中断配置

我们知道要让 51 单片机发生中断必须要满足以下 3 个条件,这 3 个条件的顺序可以任意:

①中断源有中断请求;
②此中断源的中断允许位为 1;
③CPU 开中断(即 EA=1)。
# 比如我们配置外部中断 0,对应的配置程序如下:
EA=1;//打开总中断开关EX0=1;//开外部中断 0
IT0=0/1;//设置外部中断的触发方式
如果要配置的是外部中断 1,只需将 EX0 改为 EX1,IT0 改为 IT1

因为独立按键一端是共地的,当按下后对应单片机 IO 口被拉低,而默认单片机 IO 口是高电平,这样就有一个下降沿过程,所以通常使用外部中断都是配置为下降沿触发,即 IT0=1;

#include "reg52.h"
// 对系统默认数据类型 进行重命名
typedef unsigned char u8;
typedef	 unsigned int u16;

sbit KEY3 = P3^2;
sbit LED1 = P2^0;
void delay_ms(u16 ms)
{
	while(ms--);
}

// 外部中断初始化
void exit0_init(void)
{
	IT0=1; 
	EX0=1;
	EA=1;
}

// 中断函数
void exit0() interrupt 0
{
	  delay_ms(1000);
	  if(KEY3==0)
	  {
	  	LED1=!LED1;
	  }
}

void main(){
	 exit0_init();
	while(1)
	{

	}
}

接线图和现象我们就不写了了,我们来看一下代码。
其实代码也没什么特殊的,无非就是中断函数比较的特殊,也没啥的我们只需要知道这个一定要这样写就好了。在中断函数中 exti0 是函数名,可自定义,但必须符合 C 语言标识符定义规则,interrupt 是一个关键字,表示 51 单片机中断。后面的“0”是中断号,外部中断 0 中断号为 0,如果是外部中断 1,则中断号为 2,

定时器中断

STC89C5X 含有 3 个定时器: 定时器 0、定时器 1、定时器 2(注意:51 系列单片机一定有基本的 2 个定时器
(定时器 0 和定时器 1),但不全有 3 个中断,需要查看芯片手册,通常我们使
用的是基本的 2 个定时器:定时器 0/1)
51 单片机定时器/计数器内部结构如下所示。
image
一使用定时器做的实验让Led小灯一秒闪烁一下

#include "reg52.h"
// 对系统默认数据类型 进行重命名
typedef unsigned char u8;
typedef	 unsigned int u16;

sbit LED1 = P2^0;
void delay_ms(u16 ms)
{
	while(ms--);
}


// 定时器
void tiem0_init(void)
{
	TMOD|=0X01;
	TH0=0XFC;
	TL0=0X18;

	ET0=1;
	EA=1;
	TR0=1;
	 
}
void time0() interrupt 1
{
	static u16 i ;
	TH0=0XFC;
	TL0=0X18;
	i++;
	if(i==1000)//1s
	{
		i=0;
		LED1=!LED1;
	}

}

void main(){
	tiem0_init();
	while(1)
	{

	}
}

4、元器件知识

4.1 三极管

image
单个硅原子最外层带有4个电子两两会形成八电子结构,纯净的硅晶体会形成如上图的共价键,这种结构是稳定的,所以不会导电,因为没有自由电子的流动。
image
如上图我们在硅晶体中掺杂5价磷,这样就会多出一个自由电子(带负电)这个也就是N型掺杂!
image
同性相斥推动自由电子向前,这样就有了电流!
image
在纯硅中掺杂3价硼,然后两个两个形成共价键发现还少一个,这样就有了一个空穴(带正电),负极的电子被空穴吸引再被正极吸引形成回路这样就有电流了,这个也就是P型掺杂!
image
二极管单向导电性
image
image
三极管
image
image
image

五、C 语言

位运算

A = 0011 1100
A << 2 将得到 240,即为 1111 0000
A >> 2 将得到 15,即为 0000 1111
<< 左边的二进制位丢弃,右边补0
>> 正数左补0,负数左补1,右边丢弃
posted @ 2022-08-09 15:21  niandb  阅读(750)  评论(0)    收藏  举报