二十四只氦气球,三个螺旋桨,像飞屋环游记里一样乘着气球飞翔!
下面描述技术细节:
技术细节
机械
从机械上来看,可以分为两部分:一部分是小巧的纤维板机架,上面有Arduino Nano单片机板、蓝牙模块、锂电池、电源模块、电机驱动模块、高速电机和螺旋桨;另一部分就是分多组挂在机架顶端的氦气球,共有24个。
氦气提供的升力大约是1.33千克/立方米。氦气球自身的升力与重力比并不高,所以气球充气尽量多可以增大比升力。为了让气球不至于过大,机架的重量就要控制在200g以内。氦气球的升力要接近但略小于机架的重力,这样可以在螺旋桨的升力作用下保持动态平衡,又能无动力自然降落。氦气球扎紧后,氦气会在两三天内逐渐泄漏。为防止泄漏,可以在充气口上涂抹胶水。为了让升力平衡,可以将氦气球分几组扎好,再挂到机架上。
机架是一个Y字形,左右两个角上各有一个朝向前的螺旋桨,底部有一个朝下的螺旋桨。左右两个螺旋桨控制前进、后退以及转向;底部螺旋桨负责垂直平衡与升降。
电路
电路主要有这么几个模块:Arduino Nano单片机板,蓝牙模块,锂电池,升压模块,电机驱动模块,高速电机。
Arduino Nano是Arduino的一个型号,特点是小巧,Mega328芯片。
升压模块是将3.7v锂电池升压到5v供给Arduino Nano用的。
锂电池是3.7V的,500mAh,仅有几十克重。使用过程中发现机器耗电量是十分省的。
蓝牙模块用于和手机通讯,通过手机控制飞行。
两个电机驱动模块驱动三个电机。
比较特别的一点是我们用了6路PWM来控制三个电机。这是因为电机驱动模块比较特别,没有方向控制。当A路为PWM,B路为低电平,则为正转;反过来就是反转了。
程序
程序分两部分,手机端和单片机端。
单片机端的程序是用Arduino的简化编程语言写的。
int motorPin[3][2] = {{ 3, 5 }, { 6, 9 }, { 10, 11 }};
int id;
char input;
String value;
String name;
const int END = 0;
const int NAME = 1;
const int VALUE = 2;
int readProcess = NAME;
void setup() {
Serial.begin(9600);
for(int i = 0; i < 3; i++){
for(int j = 0; j < 2; j++){
pinMode(motorPin[i][j], OUTPUT);
digitalWrite(motorPin[i][j], LOW);
}
}
}
void loop() {
if(Serial.available()){
input = Serial.read();
process();
}
}
void process() {
if(input==','){
readProcess = VALUE;
Serial.println(name);
}
else if(input==';'){
Serial.println(value);
run(name, value);
name = "";
value = "";
readProcess = NAME;
}
else if(readProcess==NAME){
name+=input;
}
else if(readProcess==VALUE){
value+=input;
}
}
void run(String name, String value){
if(name=="id"){
int num = value.toInt();
id = num;
}
else if(name=="speed"){
int num = value.toInt();
if(num>=0){
analogWrite(motorPin[id][1], num);
analogWrite(motorPin[id][0], 0);
}
else{
analogWrite(motorPin[id][1], 0);
analogWrite(motorPin[id][0], -num);
}
}
}
程序的作用很单纯,将“speed,128;”之类的指令字符串解释执行,控制电机速度。每条指令都是“指令类型,指令参数;”形式,能够方便的传输多种类型的指令,如电机ID,电机速度,同时易于扩展新的指令。
而手机上的程序就更加简单,通过控制界面发送指令字符串就好了。由于使用了原来已经设计好的库,这个程序本身只有几十行代码而已。
package org.faguzo.nabla;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.Switch;
import org.faguzo.nabla.element.Motor;
import org.faguzo.nabla.system.Hub;
public class MainActivity extends Activity {
Button upButton, downButton, foreButton, backButton, leftButton, rightButton;
SeekBar leftMotorBar, rightMotorBar, bottomMotorBar;
Switch globalSwitch;
Hub hub;
Motor leftMotor, rightMotor, bottomMotor;
int bottomSpeed = 0, leftSpeed = 0, rightSpeed = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
hub = new Hub("20:13:01:30:18:65");
leftMotor = new Motor(0, hub);
rightMotor = new Motor(1, hub);
bottomMotor = new Motor(2, hub);
upButton = (Button)findViewById(R.id.button_up);
downButton = (Button)findViewById(R.id.button_down);
leftButton = (Button)findViewById(R.id.button_left);
rightButton = (Button)findViewById(R.id.button_right);
foreButton = (Button)findViewById(R.id.button_fore);
backButton = (Button)findViewById(R.id.button_back);
leftMotorBar = (SeekBar)findViewById(R.id.seekBar_left);
rightMotorBar = (SeekBar)findViewById(R.id.seekBar_right);
bottomMotorBar = (SeekBar)findViewById(R.id.seekBar_bottom);
globalSwitch = (Switch)findViewById(R.id.switch_global);
upButton.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==MotionEvent.ACTION_DOWN){
bottomSpeed = bottomMotorBar.getProgress()-255;
bottomMotor.setSpeed(bottomSpeed + 128);
}
else if(event.getAction()==MotionEvent.ACTION_UP){
bottomMotor.setSpeed(bottomSpeed);
}
return false;
}
});
downButton.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==MotionEvent.ACTION_DOWN){
bottomSpeed = bottomMotorBar.getProgress()-255;
bottomMotor.setSpeed(bottomSpeed - 128);
}
else if(event.getAction()==MotionEvent.ACTION_UP){
bottomMotor.setSpeed(bottomSpeed);
}
return false;
}
});
leftButton.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==MotionEvent.ACTION_DOWN){
leftSpeed = leftMotorBar.getProgress()-255;
rightSpeed = rightMotorBar.getProgress()-255;
leftMotor.setSpeed(leftSpeed-128);
rightMotor.setSpeed(rightSpeed+128);
}
else if(event.getAction()==MotionEvent.ACTION_UP){
leftMotor.setSpeed(leftSpeed);
rightMotor.setSpeed(rightSpeed);
}
return false;
}
});
rightButton.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==MotionEvent.ACTION_DOWN){
leftSpeed = leftMotorBar.getProgress()-255;
rightSpeed = rightMotorBar.getProgress()-255;
leftMotor.setSpeed(leftSpeed+128);
rightMotor.setSpeed(rightSpeed-128);
}
else if(event.getAction()==MotionEvent.ACTION_UP){
leftMotor.setSpeed(leftSpeed);
rightMotor.setSpeed(rightSpeed);
}
return false;
}
});
foreButton.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==MotionEvent.ACTION_DOWN){
leftSpeed = leftMotorBar.getProgress()-255;
rightSpeed = rightMotorBar.getProgress()-255;
leftMotor.setSpeed(leftSpeed+128);
rightMotor.setSpeed(rightSpeed+128);
}
else if(event.getAction()==MotionEvent.ACTION_UP){
leftMotor.setSpeed(leftSpeed);
rightMotor.setSpeed(rightSpeed);
}
return false;
}
});
backButton.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
if(event.getAction()==MotionEvent.ACTION_DOWN){
leftSpeed = leftMotorBar.getProgress()-255;
rightSpeed = rightMotorBar.getProgress()-255;
leftMotor.setSpeed(leftSpeed-128);
rightMotor.setSpeed(rightSpeed-128);
}
else if(event.getAction()==MotionEvent.ACTION_UP){
leftMotor.setSpeed(leftSpeed);
rightMotor.setSpeed(rightSpeed);
}
return false;
}
});
globalSwitch.setOnTouchListener(new Button.OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
leftMotor.setSpeed(0);
rightMotor.setSpeed(0);
bottomMotor.setSpeed(0);
leftMotorBar.setProgress(255);
rightMotorBar.setProgress(255);
bottomMotorBar.setProgress(255);
return false;
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
成果展示
结语
因为飞艇可以控制升力平衡达到悬浮,控制就简单了很多。重要的是如何设计一个简洁稳定的结构,让重量足够轻,重心降低。