由于本组有三名同学是正在参加同一个比赛,经过我们小组讨论,我们选取这个比赛作为我们的报告分析对象。比赛的大致要求是制作乒乓球分拣小车,通过视觉识别目标颜色的乒乓球,使用机械手爪抓取,运送并投放到指定区域。针对比赛要求,难度在于如何实现视觉识别,就是选择何种摄像头并搭载在何种主控板上。前期我们经过几次的讨论后,我们了解到几个可以实现的方案,并立刻投入精力学习。
(价格是个大概数)
主控板 价格 摄像头 价格 备注
方案1
STM32F103
这是一款功能强大的开发板
78
OV7670
单纯的摄像头
60 STM32涉及嵌入式开发,是一款功能强大的微处理器
方案2 Arduino 35 Openmv 455
Arduino上手简单,实现功能稍逊STM32
Openmv是致力于视觉识别的单片机和摄像头,有很多参考例程可供我们使用,功能十分强大
我们首先选择了方案1:前期了解STM32功能十分强大,并且设计嵌入式开发。嵌入式开发是很热门的概念,这个行业很有就业前景,如果我们有过能掌握STM32,能给我们就业带来很大帮助,从经济价值判断,选择STM32能给我们未来增值。一个STM开发板也不贵,于是我们立刻购买并且学习相关资料,但是在进一步学习中,我们发现:基于我们的基础水平,若想达到掌握使用的程度,实现难度十分巨大,因为我们需要从单片机底层学起,比如寄存器、位操作等。OV7670也只是一个简单的摄像头模块,网上也没有具体的项目可供参考,这也要求我们从原始资料自学。组员内部也发生了分歧,各组员想法不一致,这就要求一个团队的协调合作,如何使得团队朝一个方向前进。我们认为道德价值体现在分工合作和决策过程中组员有没有为整体为他人考虑。我们从技术价值和道德价值出发,知难而退,协调合作,放弃了方案一。
第二步选择了方案二,这也是我们最终的方案:我们从技术价值出发,基于我们基础水平和可操作程度,我们选择了更为简单的Arduino和比较贵的Openmv,这时候,经济价值成为我们牺牲的方面。Openmv是致力于视觉识别的单片机和摄像头,有很多参考例程可供我们使用,功能十分强大,这给我们带来了很多方便。我们能够轻松学习并实现我们想要的功能。
我们在这个项目中领悟最深刻的道理是技术价值,道德价值和经济价值往往需要准求一方面牺牲其他方面,没有完美的状态。
#include <Servo.h>
Servo servo_chassis; //创建伺服对象来控制伺服,底盘
Servo servo_arm; //臂
Servo servo_elbow; //肘
Servo servo_hand; //手爪
#define PIN_SERVO_CHASSIS 5 //底盘
#define PIN_SERVO_ARM 6 //臂
#define PIN_SERVO_ELBOW 9 //肘
#define PIN_SERVO_HAND 3 //手爪
void chassis_Clockwise(); //底盘顺时针转动
void chassis_Anticlockwise(); //底盘逆时针转动
void arm_Up(); //臂上升
void arm_Down(); //臂下降
void elbow_Extend(); //肘展开
void elbow_Back(); //肘收回
void hand_Hold(); //手爪握紧
void hand_Release(); //手爪放松
void serial_control(); //由串口获取x_chassis,y_chassis值
void reset(); //整个机械爪复位
void kinemati_analy(); //底座运动学分析
void armelbow_analy(); //手臂肘运动学分析
/*serial_control()的变量*/
//char inByte = 0; //串口接收的数据
//int angle = 0; //角度值
//String temp = ""; //临时字符变量,又或者说是缓存用的吧
////////////////////////
float chassis_angle=0; //底盘舵机角度
float arm_angle=0; //臂舵机角度
float elbow_angle=0; //肘舵机角度
float hand_angle=0; //手爪舵机角度
float max_chassis_angle=180; //底盘舵机复位角度
float min_chassis_angle=0; //底盘舵机有效角度
float max_arm_angle=135; //臂舵机复位角度
float min_arm_angle=70; //臂舵机有效角度
float max_elbow_angle=159; //肘舵机复位角度
float min_elbow_angle=23; //肘舵机有效角度
float max_hand_angle=157; //手爪舵复位机角度
float min_hand_angle=100; //手爪舵机有效角度
float x_camera=0; //小球对于摄像头x坐标
float y_camera=0; //小球对于摄像头y坐标
float x_chassis=0; //小球对于底盘x坐标
float y_chassis=0; //小球对于底盘y坐标
float i=0;
float serial_control_factor=0;
float chassis_high=9; //底座高度mm
float arm_long=13; //手臂长度
float elbow_hand_long=16.5; //手爪到肘长
float chassis_ball; //底座到球距离
float radian_chassis; //底座转角角度
float chassis_angle_camera; //底座转角角度
float radian_arm=0; //臂转角角度
float radian_elbow=0; //肘转角角度
void setup()
{
servo_chassis.attach(PIN_SERVO_CHASSIS);
servo_arm.attach(PIN_SERVO_ARM);
servo_elbow.attach(PIN_SERVO_ELBOW);
servo_hand.attach(PIN_SERVO_HAND);
Serial.begin(9600); //设置波特率
Serial.println("输入x_chassis和y_chassis,并以逗号隔开,如12,23");
while(Serial.read()>= 0){} //清除缓存
i=1;
reset();
armelbow_analy(); //手臂肘运动学分析
}
/**********************************函数集合****************************************************
chassis_Clockwise(); //底盘顺时针转动
chassis_Anticlockwise(); //底盘逆时针转动
arm_Up(); //臂上升
arm_Down(); //臂下降
elbow_Extend(); //肘展开
elbow_Back(); //肘收回
hand_Hold(); //手爪握紧
hand_Release(); //手爪放松
serial_control(); //由串口获取x_chassis,y_chassis值
reset(); //整个机械爪复位
*********************************************************************************************/
/********************************主函数****************************************************/
void loop()
{
while(i==1) //先松后尽
// serial_control(); //由串口获取x_chassis,y_chassis值
{
i=1;
serial_control(); //由串口获取x_chassis,y_chassis值
//delay(5000);
// ;
kinemati_analy(); //运动学分析
i=1;
}
}
/************************************************************************************/
void armelbow_analy() //手臂肘运动学分析
{
//解方程:
//chassis_ball = arm_long * sin(radian_arm) + elbow_hand_long * cos(radian_elbow);
//chassis_high = arm_long* cos(radian_arm) + elbow_hand_long * sin(radian_elbow);
Serial.println("进入臂肘分析");
/******
float chassis_high=9; //底座高度mm
float arm_long=13; //手臂长度
float elbow_hand_long=16.5; //手爪到肘长
float chassis_ball; //底座到球距离
float radian_chassis; //底座转角角度
float chassis_angle_camera; //底座转角角度
float radian_arm=0; //臂转角角度
float radian_elbow=0; //肘转角角度************手臂和肘部βθ角计算换算********************/
float arm_angle_camera; //手臂和水平线角度
float arm_ball; //臂舵机到球距离
float elbow_angle_camera; //肘舵机于竖线的角度
x_chassis=0;
y_chassis=14;
Serial.print("x_chassis=");
Serial.println(x_chassis);
Serial.print("y_chassis=");
Serial.println(y_chassis);
chassis_ball=sqrt(x_chassis*x_chassis+y_chassis*y_chassis);//底座到球距离
Serial.print("底座到球距离chassis_ball=");
Serial.println(chassis_ball);
arm_angle_camera=atan(chassis_high/chassis_ball); //角度:底座——球 & ——水平线
arm_ball=sqrt(chassis_ball*chassis_ball+chassis_high*chassis_high); //臂舵机到球距离
//手臂和水平线角度余弦定理c*c = a*a + b*b - 2*a*b*cosC求反余弦减去臂舵机到球距离
arm_angle_camera= (acos((arm_long*arm_long + arm_ball*arm_ball - elbow_hand_long*elbow_hand_long)/(2*arm_long*arm_ball))-arm_angle_camera)*180/PI; //手臂和水平线角度
Serial.print("手臂和水平线角度arm_angle_camera=");
Serial.println(arm_angle_camera);
elbow_angle_camera = acos((elbow_hand_long*elbow_hand_long + arm_long*arm_long - arm_ball*arm_ball )/(2 * elbow_hand_long * arm_long))*180/PI;//臂肘之间的角度
elbow_angle_camera = elbow_angle_camera-90+arm_angle_camera; //肘舵机于竖线的角度
Serial.print("肘舵机于竖线的角度elbow_angle_camera=");
Serial.println(elbow_angle_camera);
arm_angle_camera=arm_angle_camera + 35; //35是臂舵机度数和实际的臂与水平线的度数差值
elbow_angle_camera=elbow_angle_camera+3; //3是肘舵机度数和实际的臂与竖直线的度数差值
if(arm_angle_camera>=min_arm_angle&&arm_angle_camera<=max_arm_angle)
{
if(elbow_angle_camera>=min_elbow_angle&& elbow_angle_camera<=max_elbow_angle)
{
Serial.print("手臂将转动到arm_angle_camera=");
Serial.println(arm_angle_camera);
Serial.print("肘将转动到elbow_angle_camera=");
Serial.println(elbow_angle_camera);
arm_angle=servo_arm.read();//读取度数,第一次是90
elbow_angle=servo_elbow.read();//读取度数,第一次是90
for(;arm_angle<arm_angle_camera;arm_angle++)
{
for(;elbow_angle<elbow_angle_camera;elbow_angle++)
{
servo_arm.write(arm_angle);
//delay(30);
servo_elbow.write(elbow_angle);
delay(30);
}
}
for(;arm_angle>arm_angle_camera;arm_angle--)
{
for(;elbow_angle>elbow_angle_camera;elbow_angle--)
{
servo_arm.write(arm_angle);
//delay(30);
servo_elbow.write(elbow_angle);
delay(30);
}
}
}
else{
Serial.println("出仓失败");
}
}
else{
Serial.println("手臂伸出失败");
}
}
void kinemati_analy() //运动学分析
{
Serial.println("进入运动学分析");
//while(i==1){
//大齿轮50个,小齿轮17个,齿轮比17比50,小齿轮转180度,大齿轮转n=17*180/50=61.2 //61.2/2=30.6, 90-0.6=59.4/// 17/50=0.34
//小齿轮角度== 50*radian_chassis/(17)+90;
// *50/(17*180)
//x_chassis=-10;
// y_chassis=50;
Serial.print("x_chassis=");
Serial.println(x_chassis);
Serial.print("y_chassis=");
Serial.println(y_chassis);
/********************底座α角计算换算**************************/
radian_chassis = atan(x_chassis/y_chassis)*180/3.14; //atan得到弧度,换算为角度
Serial.print("小球偏移中心线角度:radian_chassis=");
Serial.println(radian_chassis);
//chassis_angle_camera=90-radian_chassis*50/17; //可以是这个,继续转换成舵机的转动角度
chassis_angle_camera=90-radian_chassis/0.34; //99-的作用是转换坐标轴,当radian_chassis为30.6时,角度=90-90=0;当radian_chassis为0时,90-0=90,处于中间
Serial.print("舵机运行角度:chassis_angle_camera=");
Serial.println(chassis_angle_camera);
if( chassis_angle_camera>=0&&chassis_angle_camera<=180) //判断是否在舵机运行范围
{
chassis_angle=servo_chassis.read();//读取度数,第一次是90
Serial.print("在运行范围, 将转到小球偏移角度:");
Serial.println(radian_chassis);
for(;chassis_angle<chassis_angle_camera;chassis_angle++)//输入数和状态比较
{
servo_chassis.write(chassis_angle);
delay(30);
}
for(;chassis_angle>chassis_angle_camera;chassis_angle--)//输入数和状态比较
{
servo_chassis.write(chassis_angle);
delay(30);
}
}
else{
Serial.println("已超出范围");
}
}
void reset() //整个机械爪复位
{
servo_chassis.write(max_chassis_angle/2); //底盘
servo_arm.write(max_arm_angle); //臂
servo_elbow.write(min_elbow_angle); //肘
servo_hand.write(max_hand_angle); //手爪
Serial.println("已进行机械爪复位");
delay(2000);
}
void chassis_Clockwise() //底盘顺时针转动
{
while(i==1)
{
for(chassis_angle=min_chassis_angle;chassis_angle<max_chassis_angle;chassis_angle++)
{
servo_chassis.write(chassis_angle); //设定舵机旋转角度的语句,可设定的角度范围是0°到180°。
delay(30);
}
Serial.print("底盘顺时针转动,此时角度为");
Serial.println(chassis_angle);
i=0;
}
}
void chassis_Anticlockwise() //底盘逆时针转动
{
while(i==1)
{
for(chassis_angle=max_chassis_angle;chassis_angle>min_chassis_angle;chassis_angle--)
{
servo_chassis.write(chassis_angle); //设定舵机旋转角度的语句,可设定的角度范围是0°到180°。
delay(30);
}
Serial.print("底盘顺时针转动,此时角度为");
Serial.println(chassis_angle);
i=0;
}
}
void arm_Up() //臂上升
{
while(i==1)
{
for(arm_angle=min_arm_angle;arm_angle<max_arm_angle;arm_angle++)
{
servo_arm.write(arm_angle); //设定舵机旋转角度的语句,可设定的角度范围是0°到180°。
delay(40);
}
Serial.print("臂上升,此时角度为");
Serial.println(arm_angle);
i=0;
}
}
void arm_Down() //臂下降
{
while(i==1)
{
for(arm_angle=max_arm_angle;arm_angle>min_arm_angle;arm_angle--)
{
servo_arm.write(arm_angle); //设定舵机旋转角度的语句,可设定的角度范围是0°到180°。
delay(40);
}
Serial.print("臂上升,此时角度为");
Serial.println(arm_angle);
i=0;
}
}
void elbow_Extend() //肘展开
{
while(i==1)
{
for(elbow_angle=min_elbow_angle;elbow_angle<max_elbow_angle;elbow_angle++)
{
servo_elbow.write(elbow_angle); //设定舵机旋转角度的语句,可设定的角度范围是0°到180°。
delay(30);
}
Serial.print("肘展开,此时角度为");
Serial.println(elbow_angle);
i=0;
}
}
void elbow_Back() //肘收回
{
while(i==1)
{
for(elbow_angle=max_elbow_angle;elbow_angle>min_elbow_angle;elbow_angle--)
{
servo_elbow.write(elbow_angle); //设定舵机旋转角度的语句,可设定的角度范围是0°到180°。
delay(30);
}
Serial.print("肘展开,此时角度为");
Serial.println(elbow_angle);
i=0;
}
}
void hand_Hold() //手爪握紧
{
while(i==1)
{
for(hand_angle=min_hand_angle;hand_angle<max_hand_angle;hand_angle++)
{
servo_hand.write(hand_angle); //设定舵机旋转角度的语句,可设定的角度范围是0°到180°。
delay(30);
}
Serial.print("手爪握紧,此时角度为");
Serial.println(hand_angle);
i=0;
}
}
void hand_Release() //手爪放松
{
while(i==1)
{
for(hand_angle=max_hand_angle;hand_angle>min_hand_angle;hand_angle--)
{
servo_hand.write(hand_angle); //设定舵机旋转角度的语句,可设定的角度范围是0°到180°。
delay(30);
}
// hand_angle= myservo.read();
Serial.print("手爪放松,此时角度为");
Serial.println(hand_angle);
i=0;
}
}
void serial_control() //由串口获取x_chassis,y_chassis值
{
while(i==1)
{
//由串口获取x_chassis值
if(Serial.available()>0){
delay(100);
if(serial_control_factor==0){
x_chassis = Serial.parseInt();
Serial.print("x_chassis:");
Serial.println(x_chassis);
serial_control_factor=1;
}
//由串口获取x_chassis值
if(serial_control_factor==1){
y_chassis = Serial.parseInt();
Serial.print("y_chassis:");
Serial.println(y_chassis);
serial_control_factor=0;
}
// 清除缓存
while(Serial.read() >= 0){}
Serial.println("输入成功!");
i=0;
}
}
}