从零学习51单片机和STM32单片机入门指南:适合零基础C语言学习者的完整教程
[id_[id_[id_58112289[id_925087132]]061267869]6526215[id_1555932089]5]

专栏适用的人群是:想要从零基础开始学习入门单片机的童鞋,并且这些童鞋要有一定的 C 语言基础。
专栏的目标是从零基础开始,能够进入 51 单片机和 STM32 单片机的领域。希望在玩好单片机的同时,还能够知晓一些计算机的基本概念,以及了解电路及其元器件的基本理论等。
专栏的主要内容为:主要去学习 51 单片机的功能;主要去学习 51 单片机的各个模块;主要去学习 51 单片机的外设;主要去学习 51 单片机的驱动等。最终的目的是要玩好单片机和单片机的外设,并且在整个过程中都要手敲代码,以实现我们所想要实现的功能。
专栏说明:若文章中的知识点存在错误之处,欢迎大家随时在文章下方进行评论,我会即刻进行改正。让我们共同学习,共同进步。
专栏主页:
本学习过程参考:
单片机安装软件、各种资料以及源码的路径:
提取码:gdzf
本节主要介绍与定时器相关的功能。其中包含定时器的简介,定时器的原理,计数器、时钟以及中断系统等基础知识。并且通过两个小实验来进行练习,一个实验是利用定时器来实现按键控制流水灯模式,另一个实验是利用定时器来实现定时器时钟。
[id_[id_1322479884]591101]
1.1.1 定时器的简介。
STC89C52 具有定时器资源,其具体情况如下图所示。如需参考相关内容,可参考手册:\51 单片机入门教程资料\课件及程序源码\相关资料\STC89C52.pdf 的第七章 定时器/计数器。
1.1.2 定时器原理
下面会学习什么是中断系统。
定时器的工作模式如下:
本文只学习模式 1,即 16 位定时器/计数器。这种模式是最常用的。而其他几个模式基本不常用。
模式1的框图如下图所示:
可将其分成三部分,分别是时钟部分、计数器部分以及中断部分,就如下面的图所展示的那样:
1.1.2.1 计数器
计数器是如何进行计数的呢?上图中我们能看到计数器部分包含 TL0 和 TH0。TL0 和 TH0 每个都能存放 8 字节数据。它们总共可以存储的数字范围是从 0 到 65535。左边的时钟给 TL0 和 TL1 提供脉冲。每提供一个脉冲,这 16 位计数器里面的值就会增加 1。当值加到最大值 65535 时,下一个脉冲到来,就会导致计数器溢出。计数器溢出时,会给 TF0 一个标志位。收到标志位后,TF0 会给中断系统一个中断信号申请中断。
图中的TR0是控制定时器是否启动;
1.1.2.2 时钟
从图中能够看出,时钟的来源存在两个。其中一个是 SYSclk,它指的是系统时钟;另一个是 T0Pin,T0Pin 属于单片机外部的接口。如下图所示。
在关键图中可以看出,14引脚的位置就是T0Pin;
所以时钟能够由系统时钟(SYSclk)提供,也能够通过外部引脚提供。当通过外部引脚提供时钟时,我们的定时器就成为一个计数器。因为外部来一个脉冲,计数器就加一,所以它就相当于一个脉冲计数器。计数器名字的由来也与此相关。
当然最常用的是由系统时钟来提供脉冲。系统时钟就是晶振周期。晶振在开发部的位置如下图呈现:
本开发板上的晶振频率是 12MHz。因此,该开发板上的系统时钟为 12MHz。
系统时钟输入为 12MHz;12T 模式会对输入的 12MHz 系统时钟进行 12 分频;分频后输出的频率是 1MHz;所以定时器的周期是 1 微秒;Hz 代表每秒周期震动 1 次;60Hz 代表每秒周期震动 60 次;1MHz 等于 1000000Hz;所以 1MHz 为一秒震动一百万次。在图中所画红色线路(C/T = 0)的情形下,定时器的计数单元会每隔一微秒进行一次计数。
我们本节选择定时器时钟的配置如下:先选择分频模式为 12 分频,接着选择 C/T = 0,也就是选择定时器功能(当 C/T = 1 时,是连接下面的计数器功能);所以整体的流程是:系统时钟(SYSclk)经过 12 分频(12T 模式)后进入定时器模式(C/T = 0),如下图所示:
1.1.2.3 中断系统
中断系统是 51 单片机的一个重要知识点。本节的定时器需要中断系统,后面要学习的串口也需要中断系统。i/o 口也需要中断系统,还有很多其他设备也需要中断系统。所以中断系统会与许多外设进行交互。
中断系统的介绍情况如下:可参考 51 单片机入门教程资料中的课件及程序源码相关资料 STC89C52.pdf 的第 6 章 中断系统,其具体介绍如下图所示。
总结几个重点:
1、中断是可以使CPU拥有对外界紧急事件的实时处理能力
2、高优先级的中断可以打断中优先级的中断
中断的处理流程
中断的处理流程如下图所示:
主程序在运行时,若突然有中断请求到来,CPU 会对中断请求作出响应,接着去处理中断处理程序,中断处理程序执行完毕后,会返回主程序继续执行主程序的任务。
中断资源
中断资源与单片机的型号有关联。不同种类的单片机,其中断资源可能不同。比如,中断源个数不同,优先级个数也不同等。
下图是STC89C5的中断资源:
1.1.2.4 定时器和中断系统
3. 此图即为具体关系的呈现形式(以传统 51 单片机为例)。
整体的流程呈现为红线部分的样子,起始于时钟,接着到定时器,最后到中断系统;对于整个流程的具体代码实现,接下来将会进行学习。
1.1.2.5 定时器相关寄存器
我们之前学过,单片机能够通过对寄存器进行配置,以此来控制内部线路的连接。并且,通过内部线路的不同连接方式,就可以实现不同的功能。
下图列出了和定时器有关的寄存器已经和中断有关的寄存器:
寄存器具体由什么作用呢?
前面讲到的具体模式的配置,即上图中的各个开关,具体要拨到哪个位置,是由寄存器来控制的。如下图所示:
请参考手册中的相关内容,即\51 单片机入门教程资料\课件及程序源码\相关资料\STC89C52.pdf 的第 7.1.1 节,以获取具体的介绍。
视频介绍请参考(的第48分钟后)
总结如下:
1、寄存器是连接软硬件的媒介
在单片机中,寄存器是一段特殊的 RAM 存储器。其一,它能够存储数据并进行读取操作;其二,每一个寄存器都与一根导线相连接,通过这根导线控制着电路的连接方式。
3、寄存器相当于一个复杂机器的“操作按钮
所以,我们在程序中把相应的寄存器配置好,那么单片机的电路就会按照我们审定的模式进行工作。
1.2 本节目标
目标1:按键控制LED流水灯模式
前面学习 LED 灯时已经演示过流水灯的效果。本节的任务是通过定时器,把按键与流水灯结合起来。其效果依然是流水灯的效果。
目标2:使用定时器实现时钟
二、按键控制LED流水灯模式
代码的路径为 51 单片机入门教程资料中的课件及程序源码部分,其中包含程序源码,具体在 KeilProject 文件夹下的 7 - 1 按键控制 LED 流水灯模式。
代码的编写过程,请参考
具体代码:
#include
#include "Timer0.h"
#include "Key.h"
#include
unsigned char KeyNum,LEDMode;
void main()
{
P2=0xFE;
Timer0Init();
while(1)
{
KeyNum=Key(); //获取独立按键键码
if(KeyNum) //如果按键按下
{
if(KeyNum==1) //如果K1按键按下
{
LEDMode++; //模式切换
if(LEDMode>=2)LEDMode=0;
}
}
}
}
//定时器中断函数
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++; [id_939195004]
if(T0Count>=500)//分频500次,500ms
{
T0Count=0;
if(LEDMode==0) //模式判断
P2=_crol_(P2,1); //LED输出
if(LEDMode==1)
P2=_cror_(P2,1);
}
}
其中,Timer0.c如下:
#include
/**
定时器 0 进行初始化操作,其定时时间为 1 毫秒,当前系统的时钟频率为 12.000MHz。
* @param 无
* @retval 无
*/
void Timer0Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
/*定时器中断函数模板
在中断 1 处执行 Timer0_Routine 函数的操作。 此函数在中断 1 的情况下被触发并执行相关的定时任务。 它是与 Timer0 相关的中断处理程序,用于在特定的中断事件发生时进行相应的处理。 当 Timer0 产生中断信号且中断号为 1 时,就会调用 Timer0_Routine 函数来执行相应的操作。 该函数在中断处理流程中起到关键作用,用于处理 Timer0 相关的定时事件和操作。 它会根据 Timer0 的定时设置和中断配置,在特定的时间点被触发并执行相应的代码逻辑。
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
}
}
*/
Key.c的内容如下:
#include
#include "Delay.h"
/**
* @brief 获取独立按键键码
* @param 无
按下按键后,会返回一个键码,其范围在 0 到 4 之间。当没有按键按下时,返回值为 0。
*/
unsigned char Key()
{
unsigned char KeyNumber=0;
if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
return KeyNumber;
}
代码解释,主要对 Timer0.c 中的 Timer0Init 函数进行解释。
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0Init(void)
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}
首先是配置定时器模式TMOD:
TMOD 进行按位与操作后结果为 0xF0,这意味着 TMOD 的低四位被清零了,而高四位依然保持原来的状态不变。
TMOD 进行按位或运算后等于 0x01,这意味着将 TMOD 的最低位设置为 1,而其他位保持不变;
这两步操作,其效果与 TMOD = 0x01 相同。好处在于,能防止在同时操作多个寄存器时出现串位的情况,并且在实现配置高四位的同时,不会对低四位造成影响。
TL0 的值为 0x18;TH0 的值为 0xFC;这意味着进行了定时初值的设置,其中 TL0 处于低位,TH0 处于高位。
TF0=0清除TF0标志;
TR0 等于 1。在如上图所示的情况中,当 TR0 为 1 时,与门的输出结果仅仅取决于 TR0 下面的那部分。
ET0=1,打开ET0的开关;
EA=1,打开EA的开关;
PT0=0,设置为低优先级。
其实定时器的设置并非一定要我们通过手敲代码来完成。有一种简单的方法,那就是利用 STC-ISP 软件里的功能来生成。具体步骤如下:
假设我们要设置一个1ms的定时器:
根据下图来进行配置,接着点击生成 C 代码,这样就能生成一个 1ms 的定时器代码。
AUXR 是相对于最新版本的,而我们的单片机不需要它,直接将其删掉就可以。可以看出,这与我们自己上面写的代码是一致的。不过其中少了中断的配置,我们把中断的配置加上就可以了。
配置中断:
ET0=1,打开ET0的开关;
EA=1,打开EA的开关;
PT0=0,设置为低优先级。
后面我们写定时器的代码时,可直接利用 STC - ISP 软件生成代码,接着在其后添加上述配置中断的代码就可以了。
前面讲了定时器的配置。定时器达到定时时间后,需要触发中断。然后转而去执行中断处理程序。中断处理程序如下所示:在函数名定义后面加上 interrupt 1,就表示这是中断处理程序。当定时器达到时间后,会自动触发中断去执行该中断处理程序。
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++; //T0Count计次,对中断频率进行分频
if(T0Count>=500)//分频500次,500ms
{
T0Count=0;
if(LEDMode==0) //模式判断
P2=_crol_(P2,1); //LED输出
if(LEDMode==1)
P2=_cror_(P2,1);
}
}
执行中断处理程序时,需要给 TL0 和 TH0 赋初值。具体要赋的值参考上面 STC-ISP 生成的代码里的值即可。
T0Count 的作用是控制时间。例如,我们期望达到 0.5 秒的间隔后再去处理某件事,然而定时器每次触发的频率是 1 毫秒。因此,我们定义了 T0Count 。每次触发中断处理函数时,T0Count 就会加 1,这意味着过去了 1 毫秒。当 T0Count 增加到 500 时,就表示过去了 500 毫秒,也就是 0.5 秒。
再后面的代码(中断控制函数里的代码)就是控制独立按键了。
_crol_函数在 INTRINS.H 函数库中,其含义是循环左移;_cror_函数也在 INTRINS.H 函数库中,其含义是循环右移。
执行完中断处理函数后,继续执行主程序的代码。
最终的效果就是目标1了,即用按键控制流水灯模式。
三、定时器时钟
代码的路径为 51 单片机入门教程资料中的课件及程序源码部分,其中包含程序源码,该程序源码位于 KeilProject 文件夹下,具体是 7 - 2 定时器时钟这个文件。
具体代码:
#include
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char Sec=55,Min=59,Hour=23;
void main()
{
LCD_Init();
Timer0Init();
LCD_ShowString(1,1,"Clock:"); //上电显示静态字符串
LCD_ShowString(2,1," : :");
while(1)
{
LCD_ShowNum(2,1,Hour,2); //显示时分秒
LCD_ShowNum(2,4,Min,2);
LCD_ShowNum(2,7,Sec,2);
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000) //定时器分频,1s
{
T0Count=0;
Sec++; //1秒到,Sec自增
if(Sec>=60)
{
Sec=0; //60秒到,Sec清0,Min自增
Min++;
if(Min>=60)
{
Min=0; //60分钟到,Min清0,Hour自增
Hour++;
if(Hour>=24)
{
Hour=0; //24小时到,Hour清0
}
}
}
}
}