51单片机学习笔记(新手向)

引言

本篇内容主要根据b站江科大单片机教学视频总结而来,并附上自己的一些感悟

https://www.bilibili.com/video/BV1Mb411e7re?p=1

老师讲的很详细,大家也可以跟着老师一步步学习

首先,我想从告诉大家如何看懂原理图开始,因为只会一种单片机不代表能够使用所有的单片机

真正广泛的原理图应该分成功能块。可能有一个部分用于电源输入和电压调节,或微控制器部分,或专门用于连接器的部分。尝试识别哪个部分是哪个部分,并遵循从输入到输出的电路流程。优秀的原理图工程师甚至可能像电子书一样放置电路,左侧输入,右侧输出。

 原理图在哪找?

Ⅰ、立创商城(推荐);Ⅱ、半岛小芯。
这里我们就以我们的51单片机为例

1.我们首先需要找到它的主控芯片

 

2.接着看它的引脚,进而浏览每一个模块

原理图上的器件类型繁多,但依旧遵循“二八原则”,80%都是常见的器件,因此我们只需熟悉常见器件即可。每一类器件,通常使用英文名称简写标记,比如电阻通常标记为Rn(n为数字),常见元件如下表

 有时,为了使原理图更清晰,我们将给网络命名并标记它,而不是在原理图上布线。假设没有连接它们的可见导线,假定连接具有相同名称的网络。名称可以直接写在网络的顶部,也可以是“标签”,悬挂在电线上。

我们以最简单的控制led灯点亮为例

MCU上 P1,P2,P3都为寄存器,是将数据存储下来并发送出去的模块

通过以上网络连接,我们就实现了通过开发MCU来对LED的操控

(网络通常被赋予一个名称,专门说明该线路上信号的用途。例如,电源网可以标记为“VCC”或“5V”,而串行通信网络可以标记为“RX”或“TX”。)

下面,我将从每一个实现的项目来讲起 

一、点亮第一个LED灯

在上面的图中,我们可以看到LED最终连上了单片机的管脚。而单片机需要通过CPU控制寄存器的值,进而通过驱动器加大控制力度,由控制电路输出高低电平(对应寄存器1/0)。因此,程序需要在对应的寄存器上写1或0,即可控制LED的亮灭

#include <STC89C5xRC.H>
 
void main()
{
	P2 = 0xFE; //1111 1110
}

注:单片机采用的16进制 1111 1110 到0xFE是二进制到16进制的计算

有时候无法亮灯可能是因为你使用的单片机型号是STC89C52RC而不是STC89C52,在烧录软件中更改型号即可,或者更换环境,重装驱动,万事开头难,不要放弃

这里再介绍一个如何使用vs code 搭建单片机开发环境,因为keil实在太落后了,开发起来对新手很不友好

主要参考另一篇csdn

使用vs code搭建单片机开发环境

按照上述方法做了后,若发现无法使用keil的Atmel库可以跟我做一遍

 

打开下载的EIDE,选择“项目属性”->选择“包含目录”->"+"->打开keil的文件夹->C51->inc->Atmel问题就解决了

二、实现LED的闪烁

这里需要用到delay函数,delay函数的原理是

利用CPU执行空循环所需的时间来引入延迟

那么我们如何准确写出让原件延时的delay呢?

-------》使用stc-isp来生成延时的函数

选择晶振的频率以及延时的时间拷贝即可

#include <STC89C5xRC.H>
#include <INTRINS.H>
 
void Delay500ms()		//@12.000MHz
{
	unsigned char i, j, k;
 
	_nop_();    //An empty function, from INTRINS.H
	i = 4;
	j = 205;
	k = 187;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
 
void main()
{
	P2=0xFE;
	Delay500ms();
	P2=0xFF;
	Delay500ms();
}

三、LED流水灯

很简单,掌握二进制转化为16进制很容易实现

#include <STC89C5xRC.H>
#include <INTRINS.H>
 
void Delay500ms()		//@12.000MHz
{
	unsigned char i, j, k;
 
	_nop_();
	i = 4;
	j = 205;
	k = 187;
	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灯亮灭

首先,我们需要了解独立按键

这几个引脚对应着MCU上的寄存器:

我们可以这样实现按下按键时亮灯,松开按键时熄灯

#include <STC89C5xRC.H>
 
void main()
{
	
	while(1)
	{
		if(P30==0)
		{
			P20=0;
		}
		else
		{
			P20=1;
		}
	}
}

五、通过独立按键来控制LED亮灭状态+消抖

我们为什么要消抖?

按键电路中消除因为接触不良或快速操作导致的误触发现象。当按键被按下或释放时,由于机械接触不良或电气噪声,可能会在短时间内产生多次电信号的快速变化,这会导致单片机错误地识别为多次按键操作

消抖的实现:

void main()
{
    while(1)
    {
        if(P3_1==0)
        {
            Delay(20);
            while(P3_1==0);
            Delay(20);
        }
        P2_0 = ~P2_0;
    }

 代码的实现

#include <STC89C5xRC.H>
 
void Delay(unsigned int xms)		//@12.000MHz
{
	unsigned char i, j;
	while(xms)
	{
	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
	xms--;
	}
}
 
 
void main()
{
	while(1)
	{
		if(P31==0)
		{
			Delay(20); 
			while(P31==0);
			Delay(20);  
			P20=~P20;
		}
	}
}

六、点亮晶体管

如图,我们需要先指定点亮哪一个晶体管

即指定最上面的那一排:LED8-1

再指定该晶体管需要点亮那些个区域

就比如我想点亮一个5,我就需要点亮a,b,d,e,g五个晶体管

attention:dp代表的是point,即晶体管右下角的小点

需要点亮的记上一再根据(需要从下往上读)二进制与16进制 的转换

我们就可以得到这样一个表:(共阴极类)

那么我们每想用晶体管显示一个数字就必须去进行这么复杂的计算吗?

这就引出了我们的模块化编程,通过引用已经编写好的库中的函数大大缩短编码的时间,且库的调用是非常灵活的,在任何一个电脑上都可以进行使用

模块化编程:

模块化编程 是指程序核心部分定义好功能的接口,而具体的实现留给各个模块去做。举个现实世界的例子:我们可以在电脑的PCI插槽上安装显卡、声卡或者网卡,原因就是这些硬件都按照PCI接口的规范来制造的,就像搭积木一样,建立一个你的武器库。

1)创建.c文件(xxx.c)
2)在.c文件内定义需要的函数
3)创建.h文件,文件名要与.c文件一致
4)在.h文件中声明在.c中定义的函数
5)在main.c文件中包含.h文件,在main函数直接调用自定义的函数

如上我们就可以写一个关于晶体管显示的.c.h文件

#ifndef __NIXIE_H__
#define __NIXIE_H__
 
void Nixie(unsigned char Location,Number);
	
#endif
#include <STC89C5xRC.H>
 
unsigned char NixieTable[]={
 
0x3f,0x06,0x5b,0x4f,
 
0x66,0x6d,0x7d,0x07,
 
0x7f,0x6f,0x77,0x7c,
 
0x39,0x5e,0x79,0x71, 0x00};
 
void Delay(unsigned int xms)		//@12.000MHz
{
	unsigned char i, j;
	while(xms)
	{
	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
	xms--;
	}
}
 
void Nixie(unsigned char Location,Number)
{
	switch(Location)
	{
		case 1:
			P24=1;P23=1;P22=1;break;
		case 2:
			P24=1;P23=1;P22=0;break;
		case 3:
			P24=1;P23=0;P22=1;break;
		case 4:
			P24=1;P23=0;P22=0;break;
		case 5:
			P24=0;P23=1;P22=1;break;
		case 6:
			P24=0;P23=1;P22=0;break;
		case 7:
			P24=0;P23=0;P22=1;break;
		case 8:
			P24=0;P23=0;P22=0;break;
	}
	P0=NixieTable[Number];
	Delay(1);  // Shadow elimination
	P0=0x00;  //  reset
}
 
void main()
{
	while(1)
	{
		Nixie(1,1);
		Nixie(2,2);
		Nixie(3,3);
	}
}

(很多人在开始时都会很抗拒直接搬别人的库包括我,但我们要知道,技术的本质就是解决问题,独立开发的都是些嵌入式开发大牛,关我们刚学单片机的小白什么事?独立开发可以等到后期有一定基础才开始进行)

七、LCD1602调试工具

依旧参照我们的模块化编程,调用老师的库函数

库函数https://d.nuelv.com/pc/xzji.com-ODE.rar
https://pan.xunlei.com/s/VO9bitWlVDwXq-oUvSfLHNfyA1?pwd=8nhu#

(包括定时库,延时库,矩阵键盘库)

下面是一段实例:

#include <STC89C5xRC.H>
#include "LCD1602.h"
 
void main()
{
	LCD_Init();//初始化
	LCD_ShowString(1,3,"Hello");//从第一行,第三列开始显示Hello
    LCD_ShowChar(1,1,'q');
	LCD_ShowNum(1,9,123,3);
	while(1)
	{
	
	}
}

八、矩阵键盘

如图所示此4*4的键盘即为矩阵键盘

我们来看看它的原理图

P1_3到P1_0是控制列数的 

P1_7到P1_4是控制行数

举个例子,如果我想根据第一行第一列来当作判断条件

P1_3=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}

这一块我推荐自己写头文件,比较简单同时也可以让自己更加熟悉自己的库,锻炼代码能力

九、定时器

传统51单片机有3个定时器(T0,T1,T2),51系列单片机的更新迭代都是向下兼容的。定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的。STC89C52自带计时模块

在之前的学习中,我们用到了delay()函数来让程序执行过程中出现中断,当计数完成也就代表延时结束,简单点说就是让cpu通过不停的计数来消耗时间,所以这种方式有个很大的弊端,就是当cpu “死跑” 延时的时候,是做不了其他事情的,这个时候就需要一个额外的工具来帮助cpu完成计时,这就是我们定时器存在的意义

 计数器由时钟提供脉冲信号,经过分频器,每来一次脉冲计数+1,我们可以先设置一个计数器的初值 x,当计次超过65535时,下一个脉冲就会产生溢出,触发溢出中断,程序可以捕获到这个中断,就可以得知此次经历了(65535-x+1)us,这样就完成了一次定时。

为什么65536就溢出了呢?

计数器里有两个时基单元,TL0(低8位)和 TH0(高8位)总共16位,所以叫16位定时/计数器,它最多可以计数 2^16=65535次。

那么我们应该怎么告诉单片机要使用定时器了呢?这就需要我们的报备过程:

(TMOD是工作方式寄存器,用于向MPU报备应该怎么计时)

C/T意为count/time,一个用于计数,一个用于计时)

若想要使用定时器1,则在7号gate给上 高电位,另外M1和M0是控制定时模式的,但我们经常使用的只有模式一,详细如下图:

 

 一般,定时器需要与中断系统搭配起来使用

  1. 中断请求:当一个中断事件发生时,相应的中断标志位被设置。
  2. 中断检查:CPU检查中断允许寄存器,确定是否允许当前的中断请求。
  3. 中断响应:如果中断被允许,CPU会暂停当前程序,保存当前状态(如程序计数器和某些寄存器),然后跳转到中断服务程序。
  4. 中断服务:执行中断服务程序,处理中断事件。
  5. 中断返回:中断服务程序执行完毕后,通过中断返回指令(RETI)返回到主程序,恢复之前保存的状态,继续执行。

中断与定时类似,都需要向MPU报备:

中断器中的寄存器ET0、EA、PT0

要初始化中断允许控制寄存器、中断优先级控制寄存器。

为了接收到定时器的中断请求,需要ET0=1EA=1
若选择低优先级,则需要PT0=0

初始化代码如下:

void Timer0_Init()
{
	TMOD = 0x01;	// 0000 0001
	TF0 = 0;
	TR0 = 1;
	TH0=64535/256;
	TL0=64535%256;
	ET0=1;
	EA=1;
	PT0=0;
}

 当然这只是初始化代码,要想真正发挥中断器的作用还需要使用中断服务程序

该如何编写一个中断服务程序呢?


void int0_work() interrupt 0//中断程序主体
{
	Delayms(5);
	if(KEY3==0)
	{
		LED1=!LED1;
	}
	while(!KEY3);

如果满足了中断条件,因为使用了中断程序,就会在当前执行语句保留一个断点(自行执行)进而执行终端服务程序直到走出循环。

(中断函数是特殊的函数,interrupt是<REG52.H>文件中的关键字,1是中断号,中断触发后会根据中断号跳转到相应的中断函数,注意:中断服务程序不能像常规函数一样被外部调用)

此外,中断还具有优先级:

  1. 中断优先级寄存器(IP):51单片机使用IP寄存器来设置中断的优先级。IP寄存器中的每一位对应一个中断源,用于设置该中断源的优先级。

  2. 高优先级中断:如果一个中断被设置为高优先级,那么在它发生时,即使当前正在执行低优先级的中断服务程序,它也会被立即处理。

  3. 低优先级中断:如果一个中断被设置为低优先级,那么在高优先级中断服务程序执行期间,它不会被处理,直到高优先级中断服务程序执行完毕。

  4. 中断嵌套:51单片机支持中断嵌套,即一个中断服务程序执行期间可以被另一个更高优先级的中断打断

基于此,我们可以实现一个定时器闹钟

代码实现如下: 

#include <STC89C5xRC.H>
#include "Timer0.h"
#include "Delay.h"
#include "LCD1602.h"
 
unsigned char Sec, Min=59, Hour=23;
 
void main()
{
	Timer0Init();
	LCD_Init();
	LCD_ShowString(1,1,"Clock:");
	while(1)
	{
		LCD_ShowNum(2,1,Hour,2);
		LCD_ShowString(2,3,":");
		LCD_ShowNum(2,4,Min,2);
		LCD_ShowString(2,6,":");
		LCD_ShowNum(2,7,Sec,2);
	}
}
 
void Timer0_Routine() interrupt 1  //中断函数标识,含优先级
{
	static unsigned int T0Count;  //静态变量,拥有局部作用域,全局生命周期
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;		//T0Count计次,对中断频率进行分频
	if(T0Count>=1000)//1000ms
	{
		T0Count=0;
		Sec++;
		if(Sec>=60)
		{
			Sec=0;
			Min++;
			if(Min>=60)
			{
				Min=0;
				Hour++;
				if(Hour>=24)
				{
					Hour=0;
				}
			}
		}
	}
}

 

 

 

相信很多爱好电子的朋友,对单片机这个词应该都不会陌生了吧。不过有些朋友可能只听说他叫单片机,他的全称是什么也许并不太清楚, 更不用说他的英文全称和简称了。单片机是一块在集成电路芯片上集成了一台有一定规模的微型计算机。简称为:单片微型计算机或单片机 (Single Chip Computer)单片机的应用到处可见,应用领域广泛,主要应用在智能仪表、实时控制、通信、家电等方面。不过这一切都没什么关系,因为我(当然也包括任何人)都是从不知道转变成知道的,再转变成精通的。现在我只想把我学习单片机的经历,详细地讲叙给大家听听,可能有些大虾会笑话我,想:那么简单的东西还在这里卖弄。但是你错了,我只是把我个人学习的经历讲述一遍而已,仅仅对那些想 学习单片机,但又找不到好方法或者途径的朋友,提供一个帮助,使他们在学习过程中,尽量少走些弯路而已! 首先,你必须有学习单片机的热情,不是说今天去图书馆看了一个下午关于单片机的书,而明天玩上半天,后天就不知道那个本书在讲什么东西了。还是先说说我吧,我从大二的第一个学期期末的时候才开始接触单片机,但在这之前,正如上面所说的:我知道有种芯片叫单片机,具体长成什么样子,却一点也不知道!看到这里很多朋友一定会忍不住发笑。嘿嘿,你可千万别笑,有些大四毕业的人也同样不知道单片机长成什么样子呢!而我对单片机的痴迷更是常人所不能想象的地步,大二的期末考试,我全放弃了复习,每当室友拿着书在埋头复习的时候,我却捧着自己从图书馆借的单片机书在那看,虽然有很多不懂,但是我还是坚持了下来,当时我就想过,为了单片机值不值得我这样去付出,或许这也是在一些三流学校的好处吧,考试挂科后,明年开学交上几十元一门的补考费,应该大部分都能过了。于是,我横下一条心,坚持看我的单片机书和资料。 当你明白了单片机是这么一回事的时候,显而易见的问题出来了:我要选择那种语言单片机编写程序呢?这个问题,困扰了我好久。具体选择C51还是A51呢?汇编在我们大二之前并没有开过课,虽然看着人家的讲解,很容易明白单片机的每一时刻的具体工作情况,但是一合上书或者资料,自己却什么也不知道了,根本不用说自己写程序了。于是,我最终还是决定学C51,毕竟C51和我们课上讲的C语言,有些类似,编程的思想可以说是相通的。而且C51还有更大的优点就是编写大程序时的优越性更不言而喻,当然在那时,我并没有想的那么深远,C51的特点,还是在后来的实践过程中,渐渐体会到的!朋友如果你选择了C51,那么请继续往下看,如果你选择了A51,那么你可以不要看了!因为下面讲的全是C方面的,完全在浪费你的时间! 呵呵 ^_^ 第二,既然你想学好单片机,你必须得舍得花钱,如果不买些芯片回来自己动手焊焊拆拆的(但是在后期会介绍给大家一个很好用的硬件仿真软件,并不需要你用实验板和仿真器了,直接在你的PC上完成,但是软件毕竟是软件,从某个特定的意义上来说是并不能代替硬件的),即使你每天捧着本书,把那本书翻烂,也永远学不会单片机的!刚接触单片机的朋友,看了资料,一定会对以下几个词见的比较多,但是具体的概念还是比较模糊,现作如下说明:
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值