定时器/计数器
定时器/计数器0/1
定时器/计数器0和1相关寄存器
定时器/计数器控制寄存器TCON
定时器/计数器工作模式寄存器TMOD
定时器
简介
C51中的定时器和计数器是同一个硬件电路支持的,通过寄存器配置不同,就可以将他当做定时器
或者计数器使用。
确切的说,定时器和计数器区别是致使他们背后的计数存储器加1的信号不同。当配置为定时器使
用时,每经过1个机器周期,计数存储器的值就加1。而当配置为计数器时,每来一个负跳变信号
(信号从P3.4 或者P3.5引脚输入),就加1,以此达到计数的目的。
标准C51有2个定时器/计数器:T0和T1。他们的使用方法一致。C52相比C51多了一个T2
概念解读
- 定时器和计数器,电路一样
- 定时或者计数的本质就是让单片机某个部件数数
- 当定时器用的时候,靠内部震荡电路数数
- 当计数器用的时候,数外面的信号,读取针脚的数据
定时器怎么定时
定时器的本质原理: 每经过一个机器周期,就加1 :寄存器
-
什么是晶振
晶振(晶体震荡器),又称数字电路的“心脏”,是各种电子产品里面必不可少的频率元器件。数字电
路的所有工作都离不开时钟,晶振的好坏、晶振电路设计的好坏,会影响到整个系统的稳定性。
-
什么是时钟周期
时钟周期也称为[振荡周期](振荡周期_百度百科 (baidu.com)),定义为[时钟频率](时钟频率_百度百科 (baidu.com))的倒数。时钟周期是计算机中最基本的、最小的时间单
位。在一个时钟周期内,CPU仅完成一个最基本的动作。时钟周期是一个时间的量。更小的时钟周
期就意味着更高的工作频率 。- 什么是时钟周期
时钟周期也称为[振荡周期](振荡周期_百度百科 (baidu.com)),定义为[时钟频率](时钟频率_百度百科 (baidu.com))的倒数。时钟周期是计算机中最基本的、最小的时间单
位。在一个时钟周期内,CPU仅完成一个最基本的动作。时钟周期是一个时间的量。更小的时钟周
期就意味着更高的工作频率 。
-
什么是机器周期
机器周期也称为CPU周期。在计算机中,为了便于管理,常把一条指令的执行过程划分为若干个阶
段(如取指、译码、执行等),每一阶段完成一个基本操作。完成一个基本操作所需要的时间称为
机器周期。一般情况下,一个机器周期由若干个时钟周期组成 。
- 加1经过了多长时间
当晶振频率是11.0592MHz的时候,等于11059.2KHz = 11059200Hz
机器周期 = 12 x 时钟周期 =12 x (1/时钟频率) 秒 = 12 / 时钟频率 秒 = 12 / 11059200 秒 = 12 000 000
/ 11059200 微秒 = 1.085 微秒
- 什么是时钟周期
定时器编程
设置相关寄存器:
-
选用定时器0进行定时,对寄存器进行相关初始化
-
采用16位定时器,TL0和TH0全用,最大计数216 = 65536,最多计时65536 x 1.085us = 71ms
-
如何算出20ms定时器初值
单片机数1下需要花费1.085us,20ms可以数20ms/1.085us = 18433下;
那就使得单片机从65536-18433 = 47103(十六进制B7FF,近似B8)下开始计数
因此 TL0 = 0x00;TH0 = 0xB8;可以使用STC—ISP验证一下
关于TCON
-
怎么知道计时溢出
TCON寄存器的bit5(TF0)能表示溢出:当溢出的时候,硬件会修改bit5(TF0)位上面的数据,改成
1(置1),如果不用中断,我们代码清零 -
怎么知道定时器计数爆表了
TCON寄存器的bit5(TF0)能表示爆表:当爆表的时候,硬件会将bit5(TF0)(置1),如果不用中断,我们代码清零
-
怎么开始计时
TCON寄存器的bit4(TR0),通过软件将TR0赋值成1,开始计时
-
设置定时器模式
将TMOD的低四位设置成0 1,16位定时器功能。
-
四个二进制数表示一位的16进制数
-
8421法进制的转换(方便人类来看,对计算机底层来说,不关心进制010101010)
-
配寄存器推荐用按位操作,清零的时候,对应的需要清零的位与上0,不需要清零的位与上1
-
置1的时候,需要置1的位置或1,不需要置一的位置或0
-
小试牛刀
/*通过定时器0,控制LED亮一秒,灭一秒,晶振11059200Hz*/
#include <reg52.h>
sbit LED1 = P2^0;
void Timer0Init()
{
TMOD &= 0xF0; //与 1111 0000 高四位不变,低四位清零
TMOD |= 0x01; //或 0000 1111 高位不动,低位全用
TL0 = 0x00;
TH0 = 0xB8; //设置定时器初值为20ms
TF0 = 0; //设置定时器溢出标志
TR0 = 1; //启动定时器0
}
void main()
{
char cnt = 0;
LED1 = 1; //上电以后,LED处于高电平灭
Timer0Init(); //定时器0初始化
if(TF0 == 1) //如果TF0等于1的话,表示已经计数溢出已满一次
{
TF0 = 0; //重新将TF0赋值成0,再次计数
cnt++; //每溢出一次,cnt++一次
TL0 = 0x00;
TH0 = 0xB8; //重新设定初值
if(cnt == 50) //如果cnt++到50次,表示已经过了一秒
{
cnt = 0; //每50次经过1s,给cnt重新赋值,计算下一次的1s
LED1 = !LED1; //LED1初始化高电平,取反低电平
}
}
}
中断系统
中断结构
中断寄存器
定时器中断方式控制
CPU能响应定时器0中断的条件:需要配置IE寄存器的bit1: ET0 bit7:EA
- ET0中断允许要置1 ET0 = 1
- EA总中断要置1 EA = 1
硬件内部设计逻辑如下图:
小试牛刀
/*定时器控制LED1每隔一秒亮一次灭一次,main函数里LED2每隔500ms亮灭一次*/
#include <reg52.h>
#include <intrins.h>
sbit LED1 = P2^0;
sbit LED2 = P2^1;
char cnt = 0; //定义全局变量
void Timer0Init()
{
TMOD &= 0xF0; //与 1111 0000 高四位不变,低四位清零
TMOD |= 0x01; //或 0000 1111 高位不动,低位全用
TL0 = 0x00;
TH0 = 0xB8; //设置定时器初值为20ms
EA = 1; //开启总中断
ET0 = 1; //开启定时器0中断
TF0 = 0; //设置定时器溢出标志
TR0 = 1; //启动定时器0
}
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()
{
LED1 = 1;
Timer0Init(); //定时器0相关初始化
while(1)
{
LED2 = 0;
Delay500ms(); //延时500ms
LED2 = 1;
Delay500ms();
}
}
void Timer0 () interrupt 1
{
cnt++; //定时器0初始化每调用一次,cnt++一次
TL0 = 0x00;
TH0 = 0xB8; //溢出一次,重新赋值
if(cnt == 50) //溢出50次,表示计数满1s,执行相关操作
{
cnt = 0;
LED1 = !LED1; //对LED1的状态进行取反
}
}
PWM开发SG90舵机
简介
PWM,英文名Pulse Width Modulation,是脉冲宽度调制缩写,它是通过对一系列脉冲的宽度进
行调制,等效出所需要的波形(包含形状以及幅值),对模拟信号电平进行数字编码,也就是说通
过调节占空比的变化来调节信号、能量等的变化,占空比就是指在一个周期内,信号处于高电平的
时间占据整个信号周期的百分比,例如方波的占空比就是50%.
- 脉冲宽度调制
- 通过占空比编码模拟信号
- 占空比 一个周期内,高电平占据时长的百分比
如何实现PWM信号输出
- 通过芯片内部模块输出,一般观察手册或者芯片IO口都会标明这个是否是PWM口
如下图增强51,STC15w的CPU
- 如果没有集成PWM功能,可以通过IO口软件模拟,相对硬件PWM来说精准度略差
控制舵机
什么是舵机
如下图所示,最便宜的舵机sg90,常用三根或者四根接线,黄色为PWM信号控制
用处:垃圾桶项目开盖用、智能小车的全比例转向、摄像头云台、机械臂等
常见的有0-90°、0-180°、0-360°
红色:VCC 灰色:GND 黄色:PWM信号
怎么控制舵机
向黄色信号线“灌入”PWM信号。
PWM波的频率不能太高,大约50HZ,即周期=1/频率=1/50=0.02s,20ms左右
数据:
0.5ms-------------0度; 2.5% 对应函数中占空比为250
1.0ms------------45度; 5.0% 对应函数中占空比为500
1.5ms------------90度; 7.5% 对应函数中占空比为750
2.0ms-----------135度; 10.0% 对应函数中占空比为1000
2.5ms-----------180度; 12.5% 对应函数中占空比为1250
定时器需要定时20ms, 关心的单位0.5ms, 40个的0.5ms,初值0.5m cnt++
1s = 10ms * 100
20ms = 0.5ms * 40
编程实现
#include <reg52.h>
#include <intrins.h>
sbit sg90 = P2^7; //定义舵机IO口为P2^7
char cnt = 0;
char angel; //定义角度
void Timer0Init()
{
TMOD &= 0xF0;
TMOD |= 0x01; //配置定时器0工作模式为16位计数
TL0 = 0x33;
TH0 = 0xFE; //定义0.5ms初值
TF0 = 0; //定时器0溢出标志
TR0 = 1; //开启定时器
EA = 1; //开启总中断
ET0 = 1; //开启定时器0中断
}
void Delay2000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 15;
j = 2;
k = 235;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay300ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 3;
j = 26;
k = 223;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Rotate_1() //定义舵机旋转0°
{
angel = 1;
cnt = 0;
}
void Rotate_4() //定义舵机旋转135°
{
angel = 4;
cnt = 0;
}
void SG90_Init() //舵机IO口,角度初始化
{
sg90 = 1;
angel = 1;
cnt = 0;
}
void main()
{
Delay300ms(); //模拟PWM脉冲,给300ms延时稳定硬件
SG90_Init();
Timer0Init();
while(1)
{
Rotate_1();
Delay2000ms();
Rotate_4();
Delay2000ms();
} //使舵机的角度一直在0°和135°之间切换
}
void Timer0 () interrupt 1
{
cnt++; //每执行一次中断函数,cnt++一次
TL0 = 0x33;
TH0 = 0xFE; //执行一次重新赋值
if(cnt < angel)
{
sg90 = 1;
}
else
{
sg90 = 0;
} //控制单片机给P2^7口发送高低电平模拟PWM脉冲的形成
if(cnt == 40) //当cnt=40时经过了20ms,cnt重新开始计数
{
cnt = 0;
sg90 = 1;
}
}
玩转SG90舵机
/*把之前学过433M无线模块,按键也加进去玩*/
#include <reg52.h>
#include <intrins.h>
sbit sg90 = P2^7;
sbit KEY1 = P2^1;
sbit KEY2 = P2^0;
sbit D0_A = P0^0;
sbit D1_B = P0^1;
sbit D2_C = P0^2;
sbit D3_D = P0^3;
char cnt = 0;
char angel;
void Timer0Init() //定时器0相关初始化
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x33;
TH0 = 0xFE;
TF0 = 0;
TR0 = 1;
EA = 1;
ET0 = 1;
}
void Timer1Init() //定时器1相关初始化
{
TMOD &= 0x0F;
TMOD |= 0x10; //设置定时器1的工作模式
ET1 = 1; //上边已经开启了总中断EA,所以这里只需开启定时器1的中断ET1 = 1
TF1 = 0; //定时器1溢出标志
TR1 = 1; //定时器1运行控制位
}
void SG90_Init() //舵机相关初始化
{
sg90 = 1; //上电舵机处于高电平
angel = 1; //角度是0°
cnt = 0; //cnt从0开始计数
}
void Delay50ms() //@11.0592MHz
{
unsigned char i, j;
i = 90;
j = 163;
do
{
while (--j);
} while (--i);
}
void Delay300ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 3;
j = 26;
k = 223;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Rotate_1() //旋转0°
{
angel = 1;
cnt = 0;
}
void Rotate_2() //旋转45°
{
angel = 2;
cnt = 0;
}
void Rotate_3() //旋转90°
{
angel = 3;
cnt = 0;
}
void Rotate_4() //旋转135°
{
angel = 4;
cnt = 0;
}
void Rotate_5() //旋转180°
{
angel = 5;
cnt = 0;
}
void KEY1_Rotate() //按键1相关设置
{
if(KEY1 == 0) //按键1按下,可能是误触,延时一段时间再进行判断
{
Delay50ms(); //软件消抖
if(KEY1 == 0)
{
Rotate_1(); //当检测到按键1确实是被按下后,旋转0°
}
}
}
void KEY2_Rotate() //按键2相关设置
{
if(KEY2 == 0) //操作同上
{
Delay50ms();
if(KEY2 == 0)
{
Rotate_4(); //当检测到按键2确实是被按下后,旋转135°
}
}
}
void main()
{
Delay300ms();
SG90_Init();
Timer0Init();
Timer1Init();
while(1)
{
KEY1_Rotate();
KEY2_Rotate();
}
}
void Timer0 () interrupt 1
{
cnt++; //每执行一次中断函数,cnt++一次
TL0 = 0x33;
TH0 = 0xFE; //执行一次重新赋值
if(cnt < angel)
{
sg90 = 1;
}
else
{
sg90 = 0;
} //控制单片机给P2^7口发送高低电平模拟PWM脉冲的形成
if(cnt == 40) //当cnt=40时经过了20ms,cnt重新开始计数
{
cnt = 0;
sg90 = 1;
}
}
void Timer1 () interrupt 3 //定时器1相关中断函数
{
if(D0_A == 1) //当遥控器的按键A被按下后,旋转45°
{
Rotate_2();
}
if(D1_B == 1) //当遥控器的按键B被按下后,旋转90°
{
Rotate_3();
}
if(D2_C == 1) //当遥控器的按键C被按下后,旋转135°
{
Rotate_4();
}
if(D3_D == 1) //当遥控器的按键D被按下后,旋转180°
{
Rotate_5();
}
} //当每次遥控器按键被按下后,可以按按键1回到初始0°
超声波测距
简介
型号:HC-SR04
引脚:VCC和GND接开发板的5V和GND,Trig作为发送波的引脚,Echo作为接收波的引脚。
超声波测距模块是用来测量距离的一种产品,通过发送和收超声波,利用时间差和声音传播速度,
计算出模块到前方障碍物的距离。
如何使用
- 怎么让它发送波
Trig ,给Trig端口至少10us的高电平 - 怎么知道它开始发了
Echo信号,由低电平跳转到高电平,表示开始发送波 - 怎么知道接收了返回波
Echo,由高电平跳转回低电平,表示波回来了 - 怎么算时间
Echo引脚维持高电平的时间!
波发出去的那一下,开始启动定时器
波回来的拿一下,我们开始停止定时器,计算出中间经过多少时间 - 怎么算距离
距离 = 速度 (340m/s)* 时间/2
超声波的时序图
编程实现
/*距离小于10cm,LED1亮,LED2灭,风扇开始转,反之相反现象,电机用L9110电机驱动模块*/
#include <reg52.h>
sbit LED1 = P3^7;
sbit LED2 = P3^6;
sbit Trig = P0^5;
sbit Echo = P0^4;
sbit Motor_A = P0^6;
sbit Motor_B = P0^7; //定义IO口
void Delay10us() //@11.0592MHz
{
unsigned char i;
i = 2;
while (--i);
}
void Timer1Init() //定时器1相关初始化
{
TMOD &= 0x0F;
TMOD |= 0x10; //配置定时器1为16位计数模式
TL1 = 0;
TH1 = 0; //从0开始计数
}
void MotorInit() //电机初始化
{
//电机定义两个IO口,当两个IO电平不相同时,电机就会转动,正转和反转与所给IO口的电平有关
Motor_A = 1;
Motor_B = 1; //上电电机不转动
}
void HC_START() //给Trig一个10us的高电平
{
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0; //先让Trig处于低电平,然后给一个高电平维持10us,再跳转回低电平
}
void Nearby_Handler() //当dis<10 的时候的操作
{
LED1 = 0;
LED2 = 1; //距离小于10,LED1亮,LED2灭
Motor_A = 0;
Motor_B = 1; //电机开始转动
}
void Far_Handler() //当dis>=10 的时候的操作
{
LED1 = 1;
LED2 = 0; //距离大于或等于10,LED1灭,LED2亮
Motor_A = 1;
Motor_B = 1;
}
double Get_distance() //测量距离
{
double time;
TL1 = 0; //每次调用都让定时器1从0开始计数
TH1 = 0;
HC_START(); //给Trig10us的高电平,开始发送波
while(Echo == 0);
TR1 = 1; //Echo由低电平跳转到高电平代表开始发送波,这时启动定时器1开始计数
while(Echo == 1);
TR1 = 0; //Echo由高电平跳转到低电平表示接收到波,这时定时器1停止计数
time = (TH1*256+TL1)*1.085; //总共计数高位加低位*1.085us/下 = 超声波在空气中经过的时间(来回)
/*
十进制2左移1位,变成20。相当于乘以10
二进制1左移1位,变成10(2)。相当于乘以2的1次方,左移8位,乘以2的8次方=256;
*/
return (time * 0.017); //距离 = 时间(来回)* 速度/2
}
void main()
{
double dis;
Timer1Init();
MotorInit();
while(1)
{
dis = Get_distance();
if(dis < 10)
{
Nearby_Handler();
}
else
{
Far_Handler();
}
}
}
感应开盖垃圾桶
项目概述
- 检测靠近时,垃圾桶自动开盖并伴随滴一声,2秒后关盖
- 发生震动时,垃圾桶自动开盖并伴随滴一声,2秒后关盖
- 按下按键时,垃圾桶自动开盖并伴随滴一声,2秒后关盖
- 硬件说明
SG90舵机,超声波模块,震动传感器,蜂鸣器 - 接线说明
舵机控制口 P2.7;超声波Trig接 P0.1 ,Echo接 P0.0 ;蜂鸣器接 P0.2 口; 震动传感器接 P3.2口(外部中断0)
编程实现
开发步骤:
- 舵机和超声波代码整合
舵机用定时器0
超声波用定时器1
实现物体靠近后,自动开盖,2秒后关盖 - 查询的方式添加按键控制
- 查询的方式添加震动控制
- 使用外部中断0配合震动控制
代码:
#include <reg52.h>
#include <intrins.h>
sbit LED1 = P3^7;
sbit LED2 = P3^6;
sbit sg90 = P2^7;
sbit Trig = P0^1;
sbit Echo = P0^0;
sbit KEY1 = P2^1;
sbit BEEP = P0^2;
sbit vibrate = P3^2; //定义IO口
char cnt = 0;
char angel;
char angel_back; //定义一个备份来保存角度值
char mark = 0; //定义震动传感器响应标志位
void Timer0Init() //定时器0用来控制舵机的相关初始化
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x33;
TH0 = 0xFE;
TF0 = 0;
TR0 = 1;
EA = 1;
ET0 = 1;
}
void Timer1Init() //定时器1用来控制超声波的相关初始化
{
TMOD &= 0x0F;
TMOD |= 0x10;
TL1 = 0;
TH1 = 0;
}
void INT0Init() //使用外部中断0来控制震动传感器
{
EX0 = 1; //允许外部中断0中断
IT0 = 0; //IT0 = 0表示低电平触发
}
void SG90_Init() //舵机相关初始化
{
sg90 = 1;
angel = 1;
cnt = 0;
}
void Delay10us() //@11.0592MHz
{
unsigned char i;
i = 2;
while (--i);
}
void Delay2000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 15;
j = 2;
k = 235;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay300ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 3;
j = 26;
k = 223;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay100ms() //@11.0592MHz
{
unsigned char i, j;
i = 180;
j = 73;
do
{
while (--j);
} while (--i);
}
void HC_START() //给Trig一个10us的高电平
{
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0; //先让Trig处于低电平,然后给一个高电平维持10us,再跳转回低电平
}
double Get_distance() //测量距离
{
double time;
TL1 = 0; //每次调用都让定时器1从0开始计数
TH1 = 0;
HC_START(); //给Trig10us的高电平,开始发送波
while(Echo == 0);
TR1 = 1; //Echo由低电平跳转到高电平代表开始发送波,这时启动定时器1开始计数
while(Echo == 1);
TR1 = 0; //Echo由高电平跳转到低电平表示接收到波,这时定时器1停止计数
time = (TH1*256+TL1)*1.085; //总共计数高位加低位*1.085us/下 = 超声波在空气中经过的时间(来回)
/*
十进制2左移1位,变成20。相当于乘以10
二进制1左移1位,变成10(2)。相当于乘以2的1次方,左移8位,乘以2的8次方=256;
*/
return (time * 0.017); //距离 = 时间(来回)* 速度/2
}
void OpenLight() //开盖时LED1亮,LED2灭
{
LED1 = 0;
LED2 = 1;
}
void CloseLight() //关盖时LED1灭,LED2亮
{
LED1 = 1;
LED2 = 0;
}
void OpenLid() //当角度旋转135°时开盖
{
/*
如果备份角度和角度不相同的话,那就执行括号里的,如果相同,跳出if,防止舵机一直判断cnt的值
导致舵机抽搐
*/
angel = 4;
if(angel_back != angel)
{
cnt = 0;
BEEP = 0;
Delay300ms();
BEEP = 1;
Delay2000ms();
}
angel_back = angel; //上电执行一次以后,将angel的值备份,如果一直满足舵机旋转135°的条件,就 不再判断cnt的值
}
void CloseLid()
{
angel = 1;
angel_back = angel; //将angel的值备份,防止因angel的值改变导致舵机抽搐
cnt = 0;
Delay100ms();
}
void main()
{
double dis;
Timer0Init();
Timer1Init();
INT0Init();
SG90_Init();
while(1)
{
dis = Get_distance();
if(dis < 10 || KEY1 == 0 || mark == 1)
{
//如果距离小于10或者按键1被按下或者mark的值等于1的时候,垃圾桶开盖,否则关盖
OpenLight();
OpenLid();
mark = 0; //将mark重新赋值
}
else
{
CloseLight();
CloseLid();
}
}
}
void Timer0 () interrupt 1
{
cnt++; //每执行一次中断函数,cnt++一次
TL0 = 0x33;
TH0 = 0xFE; //执行一次重新赋值
if(cnt < angel)
{
sg90 = 1;
}
else
{
sg90 = 0;
} //控制单片机给P2^7口发送高低电平模拟PWM脉冲的形成
if(cnt == 40) //当cnt=40时经过了20ms,cnt重新开始计数
{
cnt = 0;
sg90 = 1;
}
}
void INT0_Handler () interrupt 0
{
mark = 1; //使用外部中断0改变mark的值,使之成为垃圾桶开盖的条件
}