这是我的第一篇小博客~本人正在参加电子设计竞赛的校赛,想起了多年前,啊呸,一年前学过的51单片机,自从学完后就放到了箱底被我冷落,今日重新拿起板子,既然学了,就要做点东西,学以致用嘛~(话说markdown写博客还真是爽~)
主要实现了以下的功能:
- 超声波HCSR-04实现避障功能
- 1602液晶显示与障碍物的距离
- 自动避障
- 寻迹(手机端控制是否进入寻迹模式)
- 调速(没用中断,自己写的PWM波形函数)
- 手机蓝牙控制(前进、后退、加速、减速等)
如果对本文有疑问或者想找男朋友,可以联系我,点击此处有我联系方式。
成果
电路图
成品
Oh, no, PCB有点粗暴,就不放了,我队友花了几个星期学的Altium Designer,有课的情况下学了大概三个星期,弄得我也手痒,有空学一下~
超声波避障
HC-SR04模块的具体使用就不说了,百度一大把,以及,卖模块的淘宝商家手里肯定也有相关的资料。外加本人主要负责写代码,直接结合代码说明,都有备注,以及能运行~
超声波是根据发射声波后,接受回声,根据回声持续的时间计算的距离。
1602 显示距离
1602的话,emmmmmm,学过51单片机的应该都比较熟了。
避障与距离部分程序
这里仅仅是超声波避免障碍和1602显示距离的程序哦~1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/****************1602的I/O口******************/
sbit rs = P1^1; //1602写命令模式
sbit lcden = P3^3; //1602使能端
sbit state = P0^7; //1说明液晶繁忙
/*********模式选择端口************/
sbit key = P2^4; //用来切换寻迹或者避障
/***************超声波**I/O口******************/
sbit trig = P1^0; //发射端与接收端,通过echo的
sbit echo = P3^2; //电平变化来计算距离,与外部中断同一个IO口
/***************测试灯**I/O口******************/
sbit led = P2^0; //写代码用的测试口,keil这玩意又不能像其他编译器,输出个东西看看程序执行的怎么样,只能通过灯的亮灭进行判断了。
/***************定义全局变量******************/
uchar table1[] = "Distance:"; //1602显示
uchar table2[] = "cm";
uchar table3[] = "Tracing"; //寻迹显示
uchar flag_ula; //超声波接收标志
float distance; //计算距离
/****************函数声明******************/
/***********************延时类函数***********************/
void Delayms(uint x) //延时一毫秒
{
uint a, b;
for(a = x; a > 0; a--)
for(b = 110; b > 0; b--);
}
void Delay10us(unsigned char i) //延时10微秒
{
unsigned char j;
do
{
j = 10;
do
{
_nop_();
}
while(--j);
}
while(--i);
}
/***************初始化中断************/
void init_T0()
{
TMOD = 0x01;
TH0 = 0;
TL0 = 0;
EA = 1;
IT0 = 1; //设置电平引发中断,与echo对应
}
/***************测量距离******************/
void meas_distance()
{
trig = 0;
Delay10us(2); //给至少20微秒的延时,说明书这么说的,进而检测echo的电平
trig = 1;
if (trig == 1) //确认一下,不然代码到处可能是bug
{
trig = 0;
if(trig == 0) //感觉多此一举了
{
TR0 = 1; //开启中断了
EX0 = 1; //中断中计算距离
Delayms(2);
if(flag_ula == 1) //测量成功的标志
{
flag_ula = 0;
}
}
}
}
/***************利用中断计算距离*****************/
void Time1() interrupt 0 //
{
TR0 = 0; //一旦受到下降沿,立马停止定时器计数
distance = (TH0 * 256 + TL0) * 1.09 / 58 - 8; //先取出定时器里的时间值,之后再将定时器置0
flag_ula = 1;//将标志位置0
TH0 = 0;
TL0 = 0;
}
//把距离写入液晶
/********************1602类**************************/
void wait()
{
P0 = 0xff; //判断液晶是否繁忙,繁忙不能写数据的
rs = 0;
if(state == 0)
{
lcden = 0;
}
}
void write_com(uchar com) //操作写命令的指针
{
wait();
rs = 0;
P0 = com;
lcden = 0;
Delayms(10);
lcden = 1;
Delayms(10);
lcden = 0;
}
void write_date(uchar date) //写入数据,数据送P0口
{
wait();
rs = 1;
P0 = date;
Delayms(10);
lcden = 1;
Delayms(10);
lcden = 0;
}
void init1602() //初始化1602
{
wait();
lcden = 0;
write_com(0x38); //这些命令就参考相关书籍了,我也背不过,都是抄的书
Delayms(10);
write_com(0x0c);
Delayms(10);
write_com(0x06);
Delayms(10);
}
void display(float dis) //1602写距离,还不显示
{
float dis1 = dis;
uchar bai, shi, ge, p1, p2;
bai = (dis1 / 100); //浮点不能取模运算,浮点模运算keil会报错
shi = (dis1 - bai * 100) / 10;
ge = dis1 - bai * 100-shi * 10;
p1 = (dis1 * 10) - bai * 1000 - shi * 100 - ge * 10;
p2 = (dis1 * 100) - bai * 10000 - shi * 1000 - ge * 100 - p1 * 10;
write_date(0x30 + bai);//将数字转换为字符,必须+0x30,
Delay10us(10);
write_date(0x30 + shi);//另外,1602液晶只能接收字符型的数据
Delay10us(10); //写入数据后稍微延时,等待液晶稳定
write_date(0x30 + ge);
Delay10us(10);
write_date('.');
Delay10us(10);
write_date(0x30 + p1);
Delay10us(10);
write_date(0x30 + p2);
Delay10us(10);
}
void show() //1602的最终显示
{
uchar a;
init1602();
write_com(0x80); //设置地址,在第一行第一列显示distance:,0x80是第一行的起始地址
Delayms(5);
for(a = 0; a < 9; a++) //写distance
{
write_date(table1[a]);
Delayms(5);
}
write_com(0x80 + 0x40 + 6);//设置地址,在第二行第7位显示cm,0x80+0x40是第二行的起始地址
for(a = 0; a < 2; a++) //写“cm”
{
write_date(table2[a]);
Delayms(5);
}
//之前这里有死循环,没错我故意的,防止不断的刷新,以及子函数计算距离进入死循环。后来把死循环放到了主函数没,之前遇到了这个bug。
init_T0(); //因此在显示子函数中测量距离,并显示,设置子函数中完成所有的死循环。
Delayms(1);
meas_distance();
write_com(0x80 + 0x40);
Delayms(1);
display(distance);
Delayms(20);
}
/******************主函数****************/
void main()
{
while(1)
{
init_T0(); //初始化后进入show函数的死循环,然后就可以了
show();
}
}