PWM控制SG90舵机
PWM可以对一系列脉冲的宽度进行调制,等效出所需要的波形
占空比为一个周期内,高电平占据时长的百分比。上图中一个周期为4ms,而高电平占据1ms因此占空比为25%
如何实现PWM信号输出
1.对于自带PWM口的芯片来说,可以直接通过芯片内部模块输出
2.如果没有集成PWM功能,可以通过IO口软件模拟相对硬件PWM来说精准度略低。即用软件代码结合定时器来实现波形的变换,来模拟PWM信号。
如何控制舵机
定时器需要定时20ms,最小使用单位为0.5ms,因此将初值设置为0.5ms,利用cnt记录爆表次数
编程实现
根据波形图调整角度变量jd即可
#include "reg52.h"
#include <intrins.h>
sbit sg90_con = P1^1;
int jd;
int cnt = 0;//用来记录爆表的次数
void Time0Init()
{
//1.配置定时器0工作模式为16位计时
//TMOD = 0x01;
TMOD &= 0xF0; //将低四位清零,且不改变高四位的值
TMOD |= 0x01; //将低四位修改,且不改变高四位的值
//2.给初值,定一个0.5ms出来
TL0 = 0x33;
TH0 = 0xFE;
//3.开始计时
TR0 = 1;
TF0 = 0;
//4.打开定时器0中断
ET0 = 1;
//5.打开总中断EA
EA = 1;
}
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 Delay2000ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 15;
j = 2;
k = 235;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
Delay300ms();//让硬件稳定一下
Time0Init();//初始化定时器
jd = 1;//初始化角度,0.5ms高电平
cnt = 0;
sg90_con = 1;//一开始从高电平开始
//4.爆表了,操作led吗,累计到1s再操作led,每次爆表变量+1,变量到100再操作led
while(1){
jd = 1;//0°根据波形图来看
cnt = 0;
Delay2000ms();
jd = 4;//135°
cnt = 0;
Delay2000ms();
}
}
void Time0Handler() interrupt 1 //爆表了就进入该中断服务程序
{
cnt++;
//重新给初值
TL0 = 0x33;
TH0 = 0xFE;
if(cnt < jd){//cnt=1的时候爆表1次,经过0.5ms
sg90_con = 1;
}else{
sg90_con = 0;
}
sg90_con = 1;
if(cnt == 40){//爆表了40次经过了20ms
cnt = 0;
sg90_con = 1;
}
}
超声波测距模块
型号:HC-SR04
超声波模块除了VCC和GND两个接口之外,还有TRIG和ECHO引脚,测距原理就是让波发出去接收回来,统计时间得出距离。
那么如何使波发出去呢?
给Trig口至少10微秒的高电平
怎么知道波开始发送了
Echo信号由低电平跳转到高电平,表示开始发送波
怎么知道波回来了
Echo引脚由高电平跳转回低电平 表示波回来了
怎么算时间
Echo引脚维持高电平的时间
波出去的那一刻,开始启动定时器
波回来的那一刻,停止定时器,计算中间的时间
怎么算距离
距离 = 速度(340m/s) * 时间 / 2
超声波时序图如下:
利用距离控制小灯和蜂鸣器代码如下:
#include "reg52.h"
#include <intrins.h>
sbit D5 = P3^7;
sbit D6 = P3^6;
sbit Trig = P1^5;
sbit Echo = P1^6;
sbit sounder = P1^0;
void Delay10us() //@11.0592MHz
{
unsigned char i;
i = 2;
while (--i);
}
void time0Init(){
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TH0 = 0;
TL0 = 0; //设置定时器0工作模式1.初值设定0开始数数,不着急启动定时器
}
void startHC(){
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0;
}
void main()
{
double time;
double dis;
time0Init();
while(1){
//1.给Trig一个10微秒的脉冲
startHC();
//2.Echo信号由低电平跳转到高电平,表示开始发送波
while(Echo == 0);
//3.波出去的那一刻,开始启动定时器
TR0 = 1;
//4.Echo引脚由高电平跳转回低电平 表示波回来了
while(Echo == 1);
//5.波回来的那一刻,停止定时器
TR0 = 0;
//6.计算中间的时间
time = (TH0 * 256 + TL0)*1.085; //以us为单位。将TL0和TH0拼接起来,即将TH0左移8位(即*256)
//7.计算距离
dis = 0.034 * time / 2;
if(dis < 10){
sounder = 0;
D5 = 0;
D6 = 1;
}else{
sounder = 1;
D5 = 1;
D6 = 0;
}
//定时器数据清零,以便下一次测距
TH0 = 0;
TL0 = 0;
}
}
感应开关盖垃圾桶
因为舵机和超声波模块会同时运行,我们可以分别用两个定时器来管理这两个硬件。
使舵机使用定时器0,超声波使用定时器2.
因此舵机的代码无需修改,只需要修改超声波的定时器初始化代码,定时器1采用高4位,因此TMOD要进行修改,同时修改TR0位TR1即可,代码如下:
#include "reg52.h"
#include <intrins.h>
sbit D5 = P3^7;
sbit D6 = P3^6;
sbit Trig = P1^5;
sbit Echo = P1^6;
sbit sounder = P1^0;
void Delay10us() //@11.0592MHz
{
unsigned char i;
i = 2;
while (--i);
}
void time0Init(){
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TH1 = 0;
TL1 = 0; //设置定时器0工作模式1.初值设定0开始数数,不着急启动定时器
}
void startHC(){
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0;
}
void main()
{
double time;
double dis;
time0Init();
while(1){
//1.给Trig一个10微秒的脉冲
startHC();
//2.Echo信号由低电平跳转到高电平,表示开始发送波
while(Echo == 0);
//3.波出去的那一刻,开始启动定时器
TR1 = 1;
//4.Echo引脚由高电平跳转回低电平 表示波回来了
while(Echo == 1);
//5.波回来的那一刻,停止定时器
TR1 = 0;
//6.计算中间的时间
time = (TH1 * 256 + TL1)*1.085; //以us为单位。将TL0和TH0拼接起来,即将TH0左移8位(即*256)
//7.计算距离
dis = 0.034 * time / 2;
if(dis < 10){
sounder = 0;
D5 = 0;
D6 = 1;
}else{
sounder = 1;
D5 = 1;
D6 = 0;
}
//定时器数据清零,以便下一次测距
TH1 = 0;
TL1 = 0;
}
}
另外可以将超声波模块进行封装使main函数更加简洁
double get_distance(){
double time;
//定时器数据清零,以便下一次测距
TH1 = 0;
TL1 = 0;
//1.给Trig一个10微秒的脉冲
startHC();
//2.Echo信号由低电平跳转到高电平,表示开始发送波
while(Echo == 0);
//3.波出去的那一刻,开始启动定时器
TR1 = 1;
//4.Echo引脚由高电平跳转回低电平 表示波回来了
while(Echo == 1);
//5.波回来的那一刻,停止定时器
TR1 = 0;
//6.计算中间的时间
time = (TH1 * 256 + TL1)*1.085; //以us为单位。将TL0和TH0拼接起来,即将TH0左移8位(即*256)
//7.计算距离
return (0.034 * time / 2);
}
将舵机和超声波模块合并,并对功能代码进行封装后函数如下:
#include "reg52.h"
#include <intrins.h>
sbit D5 = P3^7;
sbit D6 = P3^6;
sbit sounder = P1^0;
sbit sg90_con = P1^1;
sbit Trig = P1^5;
sbit Echo = P1^6;
int jd;
int cnt = 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 Delay10us() //@11.0592MHz
{
unsigned char i;
i = 2;
while (--i);
}
void time0Init()
{
//1.配置定时器0工作模式为16位计时
//TMOD = 0x01;
TMOD &= 0xF0; //将低四位清零,且不改变高四位的值
TMOD |= 0x01; //将低四位修改,且不改变高四位的值
//2.给初值,定一个0.5ms出来
TL0 = 0x33;
TH0 = 0xFE;
//3.开始计时
TR0 = 1;
TF0 = 0;
//4.打开定时器0中断
ET0 = 1;
//5.打开总中断EA
EA = 1;
}
void time1Init(){
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TH1 = 0;
TL1 = 0; //设置定时器0工作模式1.初值设定0开始数数,不着急启动定时器
}
void startHC(){
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0;
}
double get_distance(){
double time;
//定时器数据清零,以便下一次测距
TH1 = 0;
TL1 = 0;
//1.给Trig一个10微秒的脉冲
startHC();
//2.Echo信号由低电平跳转到高电平,表示开始发送波
while(Echo == 0);
//3.波出去的那一刻,开始启动定时器
TR1 = 1;
//4.Echo引脚由高电平跳转回低电平 表示波回来了
while(Echo == 1);
//5.波回来的那一刻,停止定时器
TR1 = 0;
//6.计算中间的时间
time = (TH1 * 256 + TL1)*1.085; //以us为单位。将TL0和TH0拼接起来,即将TH0左移8位(即*256)
//7.计算距离
return (0.034 * time / 2);
}
void openStatusLight(){
sounder = 0;
D5 = 0;
D6 = 1;
}
void closeStatusLight(){
sounder = 1;
D5 = 1;
D6 = 0;
}
void initSG90_0()
{
jd = 1;//初始化角度,0.5ms高电平
cnt = 0;
sg90_con = 1;//一开始从高电平开始
}
void openTrashbin()
{
//舵机开盖
jd = 3;//90°
cnt = 0;
Delay2000ms();
}
void cloaseTrashbin()
{
//舵机关盖
jd = 1;//0°
cnt = 0;
Delay2000ms();
}
void main()
{
double dis;
time0Init();
time1Init();
initSG90_0();
while(1){
//超声波测距
dis = get_distance();
if(dis < 10){
//灯和蜂鸣器的状态
openStatusLight();
openTrashbin();
}else{
closeStatusLight();
cloaseTrashbin();
}
}
}
void Time0Handler() interrupt 1 //爆表了就进入该中断服务程序
{
cnt++;
//重新给初值
TL0 = 0x33;
TH0 = 0xFE;
if(cnt < jd){//cnt=1的时候爆表1次,经过0.5ms
sg90_con = 1;
}else{
sg90_con = 0;
}
sg90_con = 1;
if(cnt == 40){//爆表了40次经过了20ms
cnt = 0;
sg90_con = 1;
}
}
添加按键开关盖功能
只需修改开盖条件即可if(dis < 10 || SW1 == 0)
添加震动控制
如果像添加按键一样修改开盖条件位if(dis < 10 || SW1 == 0 || vibrate == 0)会发现效果不好。原因是功能代码处于while循环之中,不断的循环,并且大多数情况是超声波测得距离大于10cm,会出发关盖操作中的delay函数导致延时,此时震动可能不会被响应,导致用户体验不好。因此我们可以添加外部中断来增加震动控制。
查表得外部中断0 对应中断号为interrupt 0;
另外外部中断0对应阵脚3.2口,因此我们将震动模块接入3.2口
使用外部中断时要将EX0和EA打开,EX0=1时表示允许外部中断,EX0=0禁止中断
查中断触发表得外部中断零,可以通过下降沿来触发,也可以通过低电平来触发。我们这里震动传感器检测到震动时是低电平因此可以低电平触发。设置IT0=0启动低电平触发条件。
整体代码:
#include "reg52.h"
#include <intrins.h>
sbit D5 = P3^7;
sbit D6 = P3^6;
sbit sounder = P1^0;
sbit sg90_con = P1^1;
sbit Trig = P1^5;
sbit Echo = P1^6;
sbit SW1 = P2^1;
sbit vibrate = P3^2;
int jd;
int cnt = 0;//用来记录爆表的次数
int mark_vibrate = 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 Delay10us() //@11.0592MHz
{
unsigned char i;
i = 2;
while (--i);
}
void time0Init()
{
//1.配置定时器0工作模式为16位计时
//TMOD = 0x01;
TMOD &= 0xF0; //将低四位清零,且不改变高四位的值
TMOD |= 0x01; //将低四位修改,且不改变高四位的值
//2.给初值,定一个0.5ms出来
TL0 = 0x33;
TH0 = 0xFE;
//3.开始计时
TR0 = 1;
TF0 = 0;
//4.打开定时器0中断
ET0 = 1;
//5.打开总中断EA
EA = 1;
}
void time1Init(){
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x10; //设置定时器模式
TH1 = 0;
TL1 = 0; //设置定时器0工作模式1.初值设定0开始数数,不着急启动定时器
}
void startHC(){
Trig = 0;
Trig = 1;
Delay10us();
Trig = 0;
}
double get_distance(){
double time;
//定时器数据清零,以便下一次测距
TH1 = 0;
TL1 = 0;
//1.给Trig一个10微秒的脉冲
startHC();
//2.Echo信号由低电平跳转到高电平,表示开始发送波
while(Echo == 0);
//3.波出去的那一刻,开始启动定时器
TR1 = 1;
//4.Echo引脚由高电平跳转回低电平 表示波回来了
while(Echo == 1);
//5.波回来的那一刻,停止定时器
TR1 = 0;
//6.计算中间的时间
time = (TH1 * 256 + TL1)*1.085; //以us为单位。将TL0和TH0拼接起来,即将TH0左移8位(即*256)
//7.计算距离
return (0.034 * time / 2);
}
void openStatusLight(){
sounder = 0;
D5 = 0;
D6 = 1;
}
void closeStatusLight(){
sounder = 1;
D5 = 1;
D6 = 0;
}
void initSG90_0()
{
jd = 1;//初始化角度,0.5ms高电平
cnt = 0;
sg90_con = 1;//一开始从高电平开始
}
void openTrashbin()
{
//舵机开盖
jd = 3;//90°
cnt = 0;
Delay2000ms();
}
void cloaseTrashbin()
{
//舵机关盖
jd = 1;//0°
cnt = 0;
Delay2000ms();
}
void EX0_Init()
{
//打开外部中断
EX0 = 1;
//查中断触发表,设置低电平触发
IT0 = 0;
}
void main()
{
double dis;
time0Init();
time1Init();
EX0_Init();
initSG90_0();
while(1){
//超声波测距
dis = get_distance();
if(dis < 10 || SW1 == 0 || mark_vibrate == 1){
//灯和蜂鸣器的状态
openStatusLight();
openTrashbin();
mark_vibrate = 0;
}else{
closeStatusLight();
cloaseTrashbin();
}
}
}
void Time0Handler() interrupt 1 //爆表了就进入该中断服务程序
{
cnt++;
//重新给初值
TL0 = 0x33;
TH0 = 0xFE;
if(cnt < jd){//cnt=1的时候爆表1次,经过0.5ms
sg90_con = 1;
}else{
sg90_con = 0;
}
sg90_con = 1;
if(cnt == 40){//爆表了40次经过了20ms
cnt = 0;
sg90_con = 1;
}
}
void EX0_Handler() interrupt0
{
mark_vibrate = 1;
}
至此51感应垃圾桶项目完结。