控制电路板接受来自信号线的控制信号,控制电机转动,电机带动一系列齿轮组,减速后传动至输出舵盘。舵机的输出轴和位置反馈电位计是相连的,舵盘转动的同时,带动位置反馈电位计,电位计将输出一个电压信号到控制电路板,进行反馈,然后控制电路板根据所在位置决定电机转动的方向和速度,从而达到目标停止。舵机的控制信号周期为 20MS 的脉宽调制(PWM)信号,其中脉冲宽度从 0.5-2.5MS,相对应的舵盘位置为 0-180 度,呈线性变化。也就是说,给他提供一定的脉宽,它的输出轴就会保持一定对应角度上,无论外界转矩怎么改变,直到给它提供一个另外宽度的脉冲信号,它才会改变输出角度到新的对应位置上。
 
一、整体框架:
(1)设计功能:
 
①能完成多方向行走以及其他的自定义的动作。(前进,后撤,左右转,避障);
 
②可自动避障;
 
③通过手机蓝牙下令他的下一步动作。
 
(2)功能框架:
 
 
(3)使用器材:
 
STC89C52 单片机74LS04(反相器);
 
②蓝牙串口通信模块;
 
 
④9G 舵机 18 个;
 
⑤PVC 线槽若干(模具);
 
⑥PCB 转印板;
 
⑦螺丝螺母若干。
 
⑦keil3 软件
 
二、工作原理:
(1)蓝牙串口通讯模块:
 
蓝牙串口通讯模块接收手机蓝牙软件发送字符串信号,单片机通过串口通讯协议处理蓝牙模块接收到的信息,再根据信息的内容来判断机器人将进行的下一步行动。
 
(2)超声波测距模块:
 
超声波模块向某一方向发射超声波,在发射时刻的同时开始计时(传出低电平),超声波在空气中传播,途中碰到障碍物就立即返回来,超声波接收器收到反射波就立即停止计时(回到高电平),根据低电平的长短来计算测量距离。(超声波在空气中的传播速度为 340m/s,根据计时器记录的时间 t,就可以计算出发射点距障碍物的距离(s),即:s=340t/2)
 
(3)舵机控制:
 
控制电路板接受来自信号线的控制信号,控制电机转动,电机带动一系列齿轮组,减速后传动至输出舵盘。舵机的输出轴和位置反馈电位计是相连的,舵盘转动的同时,带动位置反馈电位计,电位计将输出一个电压信号到控制电路板,进行反馈,然后控制电路板根据所在位置决定电机转动的方向和速度,从而达到目标停止。舵机的控制信号周期为 20MS 的脉宽调制(PWM)信号,其中脉冲宽度从 0.5-2.5MS,相对应的舵盘位置为 0-180 度,呈线性变化。也就是说,给他提供一定的脉宽,它的输出轴就会保持一定对应角度上,无论外界转矩怎么改变,直到给它提供一个另外宽度的脉冲信号,它才会改变输出角度到新的对应位置上。
 
在我们的作品中,18 路舵机分成 2 组,分别用一个内部定时器来控制,产生对应舵机的 PWM 信号(首先定时器 1 生成第一个舵机的脉宽,再生成第二个舵机的,到第 9 个舵机为止,然后定时器 2 以同样方式生成剩余的 9 个舵机的 PWM 信号,以此往复)。
 
三、制作过程:
(1)仿真原理图:
 
 
(2)PCB 制作:
 
 
(3)硬件搭建:
 
《a》肢体制作:
 
材料:PVC 线槽,PVC 板
 
①模型制作:(纯手工割出来的)
 
 
②舵机改造:
 
 
③整体:
 

 

 
四、调试以及问题解决:
①结构问题:
 
我们认为,整体的外形结构是决定作品成败的关键。经过多种材料的试验,最终我们选择了容易裁剪、硬度基本满足的 PVC 线槽来改装拼接肢体,躯体使用更厚的塑料板。经历一周的纯手工加工改造后,完成了整个模型的制作。
 
②供电问题:
 
由于我们使用的是 9G 舵机,性能较差,扭力不够,无法支撑起我们设计的电源与稳压模块,最后放弃了内嵌的电源,使用实验室的可调电源箱通过电线来供电,无法独立开来也是我们的唯一遗憾。
 
③机器抖动问题
 
由于 89C52 只有 6 个内部中断,远远无法满足 18 个舵机的控制,并且其他功能模块也要使用到内部中断。所以我们将 18 路舵机分成了 2 组,初始时一个接一个舵机(每个舵机 20ms 周期)来发送 PWM,但这也产生了发送一次 18 路 PWM 的总周期长度太大(18*20=360ms),足以产生被人眼所察觉的抖动。经过反复研究,让当前舵机的 PWM 信号在上一个 PWM 信号的低电平处开始产生高电平(在上一个 PWM 的高电平结束后)如下图,大大缩短了 18 路舵机一次动作的总周期长度(经过 18 路后,总周期长度为一个 PWM 的周期长度约 20ms),使抖动无法被人眼所观察。
 
代码挺多,给出主要的舵机控制代码,代码看不懂没关系,后面有解释:
 
#include《reg52.h》
 
#include《intrins.h》
 
#include《dongzuo.h》
 
#define ucharunsigned char
 
#define uintunsigned int
 
//PWM
 
sbit PWM0 = P1^0;
 
sbit PWM1 = P1^1;
 
sbit PWM2 = P1^2;
 
sbit PWM3 = P1^3;
 
sbit PWM4 = P1^4;
 
sbit PWM5 = P1^5;
 
sbit PWM6 = P3^4;
 
sbit PWM7 = P3^5;
 
sbit PWM8 = P3^6;
 
sbit PWM9 = P3^7;
 
sbit PWM10 = P2^0;
 
sbit PWM11 = P2^1;
 
sbit PWM12 = P2^2;
 
sbit PWM13 = P2^3;
 
sbit PWM14 = P2^4;
 
sbit PWM15 = P2^5;
 
sbit PWM16 = P2^6;
 
sbit PWM17 = P2^7;
 
// 超声波测距
 
sfr T2MOD = 0XC9; // 定时器 2 模式控制寄存器地址
 
sbit Trig =P3^2;
 
sbit Echo =P3^3;
 
unsigned intdistance;
 
uchar DZCS =0x11; // 控制动作
 
uchar buf;
 
uchar sd=3;
 
bit flag=0; // 是否发送字符
 
bit CSB =0; // 超声波启动控制位
 
bit HZ=0; // 后退后左转控制位
 
uchar PWMscan =0;
 
uchar PWMscan1 =0;
 
uchar PWMval[]={// 初始姿态
 
0xf8,0x8f,0xf7,0x05,0xf9,0x8c,/*5*/ 0xfa,0x0d,0xf8,0x0b,0xf9,0x67,/*b*/ 0xfa,0xd4,0xf7,0x94,0xf9,0xcb,/*11*/
 
0xfa,0xad,0xfc,0xdd,0xfb,0x58,/*17*/ 0xfa,0xe9,0xfc,0xfc,0xfb,0x39,/*1d*/ 0xfc,0x18,0xfc,0xca,0xfb,0x00/*23*/
 
};
 
void delay(uint a)
 
{
 
uchar j;
 
for(a;a》0;a--)
 
for(j=0;j《112;j++)
 
;
 
}
 
void task00()
 
{
 
if(PWMscan==1) // 第 1 路 PWM。
 
{
 
PWM0=1;
 
TH0=PWMval[0];
 
TL0=PWMval[1];
 
}
 
else if(PWMscan==2) // 第 2 路 PWM。
 
{
 
PWM0=0;
 
PWM1=1;
 
TH0=PWMval[2];
 
TL0=PWMval[3];
 
}
 
else if(PWMscan==3) // 第 3 路 PWM。
 
{
 
PWM1=0;
 
PWM2=1;
 
TH0=PWMval[4];
 
TL0=PWMval[5];
 
}
 
else if(PWMscan==4) // 第 4 路 PWM。
 
{
 
PWM2=0;
 
PWM3=1;
 
TH0=PWMval[6];
 
TL0=PWMval[7];
 
}
 
else if(PWMscan==5) // 第 5 路 PWM。
 
{
 
PWM3=0;
 
PWM4=1;
 
TH0=PWMval[8];
 
TL0=PWMval[9];
 
}
 
else if(PWMscan==6) // 第 6 路 PWM。
 
{
 
PWM4=0;
 
PWM5=1;
 
TH0=PWMval[10];
 
TL0=PWMval[11];
 
}
 
else if(PWMscan==7) // 第 7 路 PWM。
 
{
 
PWM5=0;
 
PWM6=1;
 
TH0=PWMval[12];
 
TL0=PWMval[13];
 
}
 
else if(PWMscan==8) // 第 8 路 PWM。
 
{
 
PWM6=0;
 
PWM7=1;
 
TH0=PWMval[14];
 
TL0=PWMval[15];
 
}
 
else if(PWMscan==9) // 第 9 路 PWM。
 
{
 
PWM7=0;
 
PWM8=1;
 
TH0=PWMval[16];
 
TL0=PWMval[17];
 
}
 
else if(PWMscan==10) // 给一定低电平,将周期拉长
 
{
 
PWM8=0;
 
TH0=0xFF;
 
TL0=0xd2;
 
PWMscan=0;
 
TR0 = 0; // 关定时器 0,开定时器 1
 
TR1 = 1;
 
}
 
PWMscan++;
 
}
 
void task01()
 
{
 
if(PWMscan1==1) // 第 10 路 PWM。
 
{
 
PWM9=1;
 
TH1=PWMval[18];
 
TL1=PWMval[19];
 
}
 
else if(PWMscan1==2) // 第 11 路 PWM。
 
{
 
PWM9=0;
 
PWM10=1;
 
TH1=PWMval[20];
 
TL1=PWMval[21];
 
}
 
else if(PWMscan1==3) // 第 12 路 PWM。
 
{
 
PWM10=0;
 
PWM11=1;
 
TH1=PWMval[22];
 
TL1=PWMval[23];
 
}
 
else if(PWMscan1==4) // 第 13 路 PWM。
 
{
 
PWM11=0;
 
PWM12=1;
 
TH1=PWMval[24];
 
TL1=PWMval[25];
 
}
 
else if(PWMscan1==5) // 第 14 路 PWM。
 
{
 
PWM12=0;
 
PWM13=1;
 
TH1=PWMval[26];
 
TL1=PWMval[27];
 
}
 
else if(PWMscan1==6) // 第 15 路 PWM。
 
{
 
PWM13=0;
 
PWM14=1;
 
TH1=PWMval[28];
 
TL1=PWMval[29];
 
}
 
else if(PWMscan1==7) // 第 16 路 PWM。
 
{
 
PWM14=0;
 
PWM15=1;
 
TH1=PWMval[30];
 
TL1=PWMval[31];
 
}
 
else if(PWMscan1==8) // 第 17 路 PWM。
 
{
 
PWM15=0;
 
PWM16=1;
 
TH1=PWMval[32];
 
TL1=PWMval[33];
 
}
 
else if(PWMscan1==9) // 第 18 路 PWM。
 
{
 
PWM16=0;
 
PWM17=1;
 
TH1=PWMval[34];
 
TL1=PWMval[35];
 
}
 
else if(PWMscan1==10) // 给一定低电平,将周期拉长
 
{
 
PWM17=0;
 
TH1=0xFf;//b1 // 这是一个大概的值,由于每一组的 PWMval 的总和(PWMval 中定时器的间隔的总和就是一个周期)不一致,
 
// 所以会导致周期不一定是 20ms,但大概可以控制在 20ms 左右,也是因为周期的不固定,所以才需要
 
TL1=0xd2;//e0 // 调整每一个舵机的实际的占空比
 
PWMscan1=0;
 
TR0 = 1;// 开定时器 0
 
TR1 = 0;// 关定时器 1
 
}
 
PWMscan1++;
 
}
 
void TImer0()interrupt 1
 
{
 
task00();// 控制前 9 路 PWM
 
}
 
void TImer1()interrupt 3
 
{
 
task01();// 控制后 9 路 PWM
 
}

 

 
在实际过程中,或许是由于舵机的质量问题,又或者是其他问题,舵机的角度控制总是难以运用原理上的公式来控制角度,都是实际操作,手动调整高电平的宽度,当达到合适的值的时候,然后再把相应的代码记录下来。
 
单片机的高电平宽度是通过定时器的两个寄存器控制的,所以操作舵机的转动就变成操作定时器的寄存器,再具体一点就是要得到 TH、TL 两个值。(定时器高低位的差值对应高电平的宽度)
 
在代码上,在控制第几路舵机的时候,TH、TL 的值已经定死了为哪一个 PWMval[?],比如第 18 路:
 
TH1=PWMval[34];
 
TL1=PWMval[35];
 
这将决定此时第 18 路舵机的转动角度是多少,那么怎么控制下一次该舵机的转动角度呢?答案很简单,就是把 PWMval[34];PWMval[35];的值修改一下就可以了,其他的舵机同样是这个道理。所以,机器人的一个姿态就可以变为这样:机器人姿态→18 路舵机的角度→18 个 TH、TL 的值→一个 36 个元素的数组 PWMval 的值。
 
所以,一个动作姿态就可以用这样一个函数来确定:
 
void DZ(ucharPWM[])// 动作
 
{
 
uchar i;
 
for(i=0;i《36;i++)
 
PWMval[i]=PWM[i];
 
}
 
明白了这个之后,就是对每一个姿态收集数据了,在制作过程,我是把 TH 和 TL 的两个值显示在数码管上,然后记录下来的。
 
后面又加入了蓝牙控制模块,超声波测距,发现 51 单片机的定时器不太够用,改成了 52 系列的单片机,还一个定时器即用蓝牙模块,又用超声波测距,现在想来真佩服自己。给出控制代码,大家自行研究:
 
//***************************中断初始化**************************
 
void Init()
 
{
 
TMOD |= 0x11;// 定时器 0、1
 
ET0 = 1;// 使能定时器 0 中断
 
TR0 = 1;// 开启定时器 0,定时器 1 中断在定时器 0 开始后才打开
 
ET1 = 1;// 使能定时器 1 中断
 
IT1 = 0;// 外部中断 1,低电平触发 (边沿高变低)
 
EX1 = 1;// 开外部中断 1
 
// 定时器 2 用于波特率的产生
 
SCON=0x50;
 
PCON=0x00;
 
RCAP2H=0xFF;
 
RCAP2L=0xDC;// 设置波特率为 9600
 
T2CON=0x34;// 将定时器 2 设置为波特率发生器(接收和发送都用 TImer2) // 此处包括启动 T2
 
ES=1; // 串口中断
 
EA = 1;// 开总中断
 
}
 
void TImer0()interrupt 1
 
{
 
task00();// 控制前 9 路 PWM
 
}
 
void timer1()interrupt 3
 
{
 
task01();// 控制后 9 路 PWM
 
}
 
void serial() interrupt 4
 
{
 
EA=0; // 其余中断全停
 
if(RI)
 
{
 
RI=0; // 清除串行接受标志位
 
flag=1;
 
buf=SBUF; // 从串口缓冲区取得数据 (i-0x30)将 ASCLL 码转换成数字
 
switch(buf)
 
{
 
case 0x00: DZCS=0x00;break; // 向前走
 
case 0x01: DZCS=0x01;break; // 向后走
 
case 0x02: DZCS=0x02;break; // 左转
 
case 0x03: DZCS=0x03;break; // 右转
 
case 0x04: DZCS=0x04;break; // 横着左
 
case 0x05: DZCS=0x05;break; // 横着右
 
case 0x06: DZCS=0x06;break; // 挥爪子
 
case 0x07: sd++;break; // 减速,其实就是每个姿态中的延时不一样
 
case 0x08: sd--;break; // 加速
 
case 0xff: CSB=!CSB;break; // 启动关闭超声波壁障
 
default:
 
DZCS=0x11;break; //
 
}
 
}
 
EA = 1; // 打开总中断
 
}
 
void start()// 超声波测距启动函数
 
{
 
uchar i;
 
Trig=1;
 
for(i=0;i《20;i++)
 
{
 
_nop_();
 
}
 
Trig=0;
 
}
 
void count()// 超声波测距函数
 
{
 
unsigned int time,timeH,timeL;
 
timeH=TH1;
 
timeL=TL1;
 
time=timeH*256+timeL;
 
distance=time*1.7/100;
 
}
 
void Inter()interrupt 2// 外部中断 1 在次完成测距以及相应的后续操作
 
{
 
EA =0;
 
ET0=0; // 关定时器中断 0
 
TH1=0;
 
TL1=0;
 
TR1 =1; // 检测到距离开启定时器 1
 
while(!Echo); // 当 echo 为零时等待,中断 flag 跳出等待
 
TR1 =0; // 关闭定时器 1
 
count(); // 计算距离
 
if(((10《distance)&&(distance《30))||HZ) // 当距离小于 5cm 时,变换动作哦(在中断中变换平面感应
 
{
 
DZCS=0x02; // 向左
 
HZ=0;
 
}
 
if(distance《10) // 当距离小于 10cm 时,变换动作哦(在中断中变换曲面感应
 
{
 
DZCS=0x01; // 后退
 
HZ=1; // 后退后左转标志
 
}
 
if(distance》30) // 当距离小于 40cm 时,变换动作哦(在中断中变换
 
{
 
DZCS=0x00; // 向前
 
HZ=0;
 
}
 
TR1=1;
 
ET0=1;
 
EA = 1;
 
}
 
void main()
 
{
 
Init();
 
while(1)
 
{
 
uchar DZCST;//,i;
 
if(CSB)
 
start();
 
if(DZCST!=DZCS)// 动作发生改变,则回到平衡
 
DZ(PH1);
 
if(sd==0)
 
sd=1;
 
switch(DZCS)
 
{
 
case0x00:DZXQ(sd);break;
 
case0x01:DZXH(sd);break;
 
case0x02:DZXZ(sd);break;
 
case0x03:DZXY(sd);break;
 
case0x04:DZHZZ(sd);break;
 
case0x05:DZHZY(sd);break;
 
case0x06:DZZZ(sd);break;
 
default:
 
DZ(PH1);
 
}
 
DZCST=DZCS;
 
}
 
}