项目采用51单片机,型号为STC89C52RC,主要为了熟练应用51单片机的功能。
一、项目描述
项目主要是模拟不用手动打开垃圾桶盖,而进行自动操作。自动打开的条件如下:
- 1、垃圾桶检测到有人靠时自动打开桶盖
- 2、发生震动时会自动打开
- 3、可以手动按下按钮打开
二、项目用到的模块如下所示:
- HC-SR04(超声波模块)
- SG90(舵机模块)
- MH-sensor-series(振动传感器)
- 蜂鸣器,两个指示灯
三、项目源码
#include "reg52.h"
#include <intrins.h>
sbit pulse = P1^1; // sg90 signal
sbit trig = P1^5; // 超声波trig引脚
sbit echo = P1^6; // 超声波echo引脚
sbit beep = P2^0; // 蜂鸣器
sbit key = P2^1; // 按钮
sbit vibrateData = P3^2; // 振动do引脚
sbit led2 = P3^6;
sbit led1 = P3^7;
int cnt = 0;
int phase = 1;
int vibrate;
void Delay2000ms() //@11.0592MHz
{
unsigned char i, j, k;
i = 15;
j = 2;
k = 235;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay200ms() //@11.0592MHz
{
unsigned char i, j, k;
_nop_();
i = 2;
j = 103;
k = 147;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay150ms() //@11.0592MHz
{
unsigned char i, j, k;
i = 2;
j = 13;
k = 237;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay300ms() //@11.0592MHz
{
unsigned char i, j, k;
i = 3;
j = 26;
k = 223;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void Delay10us() //@11.0592MHz
{
unsigned char i;
i = 2;
while (--i);
}
¶æ»ú/
void sg90Init() {
Delay300ms();
TMOD &= 0XF0;
TMOD |= 0X01;
TL0 = 0X33;
TH0 = 0XFE;
TR0 = 1;
ET0 = 1;
EA = 1;
}
/Õñ¶¯/
void initExInt0() {
IT0 = 0; // µÍµçƽ´¥·¢
EX0 = 1; // ¿ªÆôÍⲿÖжÏ0
}
/·äÃùÆ÷/
void beep300ms() {
int n;
beep = 0;
for(n=0;n<2;n++)
Delay150ms();
beep = 1;
}
/led/
void ledStatusWhenOpend() {
led1 = 1;
led2 = 0;
}
void ledStatusWhenClosed() {
led1 = 0;
led2 = 1;
}
/³¬Éù²¨/
// Éù²¨Ê¹ÓÃtimer 1
void initTimer1() {
TMOD &= 0X0F;
TMOD |= 0X10;
TL1 = 0;
TH1 = 0;
}
void uart_init() {
TMOD &= 0X0F;
TMOD |= 0X20;
SCON = 0X40;
TH1 = 0XFD;
TL1 = 0XFD;
TR1 = 1;
}
// ±£³Ö10us¸ßµçƽ
void trigWave() {
trig = 0;
trig = 1;
Delay10us();
trig = 0;
}
void startUltrasonicWave() {
trigWave();
while(echo == 0);
TR1 = 1;
while(echo == 1);
TR1= 0;
}
double getDistance() {
return (TH1 * 256 + TL1) * 1.085 * 0.017;
}
void main() {
double distance;
double lastPhase;
initExInt0();
sg90Init();
initTimer1();
while(1) {
Delay200ms();
startUltrasonicWave();
distance = getDistance();
if (distance < 10 || !key || vibrate) {
ledStatusWhenOpend();
phase = 3;
if(lastPhase != phase) {
cnt = 0;
beep300ms();
Delay2000ms();
}
vibrate = 0;
} else {
ledStatusWhenClosed();
phase = 1;
cnt = 0;
Delay150ms();
}
lastPhase = phase;
TL1 = 0;
TH1 = 0;
}
}
void timer0_pwm() interrupt 1 {
cnt ++;
TL0 = 0X33;
TH0 = 0XFE;
if (cnt < phase) {
pulse = 1;
} else {
pulse = 0;
}
if (cnt == 40) {
pulse = 1;
cnt = 0;
}
}
void exteral0() interrupt 0 {
vibrate = vibrateData;
}
2.1、sg90舵机
由于51没有引脚输出PWM,故只能使用软件定时器进行模拟。PWM有一个占位比的概念,所谓占空比就是一个脉冲周期内高电平持续的比例。
sg90 旋转角度如下所示:
由上可以看出每个角度的高电平持续的时间都是0.5ms的倍数,所以可以用定时器定时0.5ms,那么在一个脉冲周期内需要定时器跑了20 / 0.5 = 40次,当舵机需要转0度时,高电平需要跑0.5 / 0.5 = 1次,同理旋转45度时,高电平需要跑1 / 0.5 = 2次等等。
项目使用51定时器0的模式1,即16位定时器/计数器,TL1、TH1全用。
定时器的原理是在一个机器周期内进行加1的操作。一个机器周期又等于n个时钟周期。
该51单片机使用的晶振频率为11.0592MHz,所以时钟周期为1/11.0592, 51单片机采用的是12T模式,所以单片机的机器周期=12 倍时钟周期,即机器周期为12 / 11.0592≈1.085us。如何定时0.5ms呢?
16位定时器最大计数2^16=65536, 定时0.5ms 需要计数为 500 / 1.085 = 461, 那么需要预装 65536 - 461 = 0xFE33,所以TH1 = 0XFE ,TL1 = 0X33;
2.2、超声波
这个没有什么好说的,按照时序图触发波和接收波就行了。
2.3、按钮
按钮的监测采用轮询,可能会碰到轻按一下没有反应的状况,是因为按下是没有被检测到。
2.4、振动传感器
振动传感器利用了定时器1中断,如果采用轮询的方式,灵敏度不如按键,所以采用定时器1中断检测
四、项目总结及遇到的问题
4.1、移位问题
double getDistance() {
return (TH1 * 256 + TL1) * 1.085 * 0.017;
}
刚开始乘以 256 是利用的左移8位,即<<8, 但是计算的结果一直不正常,排查原因是TH1 是8位的寄存器,所以<<8变成0了,获取的值不正确。这个通过串口打印证实。
4.2、超声波不正常工作
按时序写完之后发现代码不正常运行,最终在main()函数内在启动超声波检测之前延时200ms,得以解决,这个可能和硬件有关,毕竟每个开发板都不可能完成相同。
4.3、物体靠近垃圾桶不动,导致舵机有细微抖动
这个算软件bug,通过判断舵机当前的和上一次的状态解决
4.4、物体靠近垃圾桶不动,这时触发振动传感器,也会导致舵机抖动
当然只是一个简单的小项目,肯定在功能和体验上还有不完善的地方。
项目演示视频:
垃圾桶