2013年10月17日 星期四

Bluetooth遙控車透過Mini2440開發版實作

底下是bluetooth遙控車的架構圖:


準備了一顆行動電源與一台遙控車,遙控器用不到了,可以丟了。


將遙控車拆開,把內建接收與控制的電路板拆掉,只剩馬達與車身。


接下來,拆開行動電源,外殼可以丟了。


我把行動電源的電路板移植到遙控車底部,然後電池固定在車身上面,下圖白色部分就是電池,被白色膠帶包起來。圖片上可以看到有一顆L293D IC焊在綠色電路板上,透過mini2440的GPIOs控制此IC達到DC馬達正反轉,前面馬達控制左右轉,後面馬達則是前進與後退。如果要參考L293D的datasheet,請按這裡。如果要參考L293D馬達控制電路,請按這裡。要注意此IC的工作電壓要大於4.5V。mini2440開發版可以供5V給IC,所以依datasheet資料可以得知或三用電錶量測IC的output電壓是正負3V(正反轉)提供給馬達。


下圖黃色框框部分有兩個USB孔,分別是5V/1A與5V/2.1A,我是接5V/1A孔供電給mini2440。


車身左側有拉出一條USB可以幫電池充電,充電的時候還會閃藍燈,酷@@。


我切了一塊透明的壓克力板,然後用六角銅柱固定在車身上面,再把mini2440開發板固定在壓克力板上面。


接下將車身與車殼結合,下圖紅色框框是透過USB接頭供電給mini2440,而綠色框框是從mini2440接出GPIOs的排線連接到IC,排線負責供電與發出控制訊號給IC。


硬體部分已經施工完成,欣賞一下不同角度。


接下來要幫它注入靈魂。

  1. 首先你要能讓mini2440開發板開能動,可以參考我之寫的文章『Mini2440我命令你復活吧!』。
  2. 然後你要將BlueZ移植到mini2440,可以參考我之寫的文章『Cross compile bluez for ARM』,建議使用BlueZ 2.25,BlueZ工具可以用來設定bluetooth。
  3. 接下來撰寫一隻控制馬達的driver與一隻user space背景執行的daemon,此daemon扮演bluetooth server角色,當server收到手機bluetooth client發出的commands後,會發出system call通知driver,driver再去控制GPIOs來控制IC,讓馬達前進,後退,左右動作。
底下是控制馬達driver的header:
#include <linux/ioctl.h>
#define IOC_MAGIC 'k'
#define IOCTL_UP_GO _IO(IOC_MAGIC,1)
#define IOCTL_UP_STOP _IO(IOC_MAGIC,2)
#define IOCTL_DOWN_GO _IO(IOC_MAGIC,3)
#define IOCTL_DOWN_STOP _IO(IOC_MAGIC,4)
#define IOCTL_LEFT_GO _IO(IOC_MAGIC,5)
#define IOCTL_LEFT_STOP _IO(IOC_MAGIC,6)
#define IOCTL_RIGHT_GO _IO(IOC_MAGIC,7)
#define IOCTL_RIGHT_STOP _IO(IOC_MAGIC,8)
底下是控制馬達的driver:
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/types.h>
#include <mach/regs-gpio.h>

#include "car.h"

#define DEVICE_NAME  "car"
#define EINT0 0
#define EINT1 1
#define EINT2 2
#define EINT3 3
#define UP_GO    1
#define UP_STOP   2
#define DOWN_GO   3
#define DOWN_STOP   4
#define LEFT_GO   5
#define LEFT_STOP   6
#define RIGHT_GO   7
#define RIGHT_STOP   8

static dev_t car_dev_number;
struct class *car_class;

int gpios[] = {
 S3C2410_GPF(0),
 S3C2410_GPF(1),
 S3C2410_GPF(2),
 S3C2410_GPF(3),
};

struct car_dev {
    struct cdev cdev;
    char name[10];
} *car_devp;

static int car_ioctl(struct inode *inode, struct file *file, 
    unsigned int cmd, unsigned long arg)
{
    switch (cmd) {
    case IOCTL_UP_GO:
        s3c2410_gpio_setpin(gpios[EINT0], S3C2410_GPIO_OUTPUT);
        printk("Control Mode: UP\n");
        break;
  
    case IOCTL_UP_STOP:
        s3c2410_gpio_setpin(gpios[EINT0], S3C2410_GPIO_INPUT);
        printk("Control Mode: Stop UP\n");
        break;
  
    case IOCTL_DOWN_GO:
        s3c2410_gpio_setpin(gpios[EINT1], S3C2410_GPIO_OUTPUT);
        printk("Control Mode: DOWN\n");
        break;
  
    case IOCTL_DOWN_STOP:
        s3c2410_gpio_setpin(gpios[EINT1], S3C2410_GPIO_INPUT);
        printk("Control Mode: Stop DOWN\n");
        break;
  
    case IOCTL_LEFT_GO:
        s3c2410_gpio_setpin(gpios[EINT2], S3C2410_GPIO_OUTPUT);
        printk("Control Mode: LEFT\n");
        break;
  
    case IOCTL_LEFT_STOP:
        s3c2410_gpio_setpin(gpios[EINT2], S3C2410_GPIO_INPUT);
        printk("Control Mode: Stop LEFT\n");
        break;
  
 case IOCTL_RIGHT_GO:
        s3c2410_gpio_setpin(gpios[EINT3], S3C2410_GPIO_OUTPUT);
        printk("Control Mode: RIGHT\n");
        break;
  
    case IOCTL_RIGHT_STOP:
        s3c2410_gpio_setpin(gpios[EINT3], S3C2410_GPIO_INPUT);
        printk("Control Mode: Stop RIGHT\n");
        break;
  
    default:
        return -EIO;
    }

    return 0;
}

static struct file_operations car_fops = {
    .owner    =   THIS_MODULE,
    .ioctl    =   car_ioctl,
};

static int __init toy_car_controller_module_init(void)
{
    int i;
 
    if (alloc_chrdev_region(&car_dev_number, 0, 1, DEVICE_NAME) < 0) {
        printk(KERN_DEBUG "Can't register device\n");
        return -1;
    }

    car_class = class_create(THIS_MODULE, DEVICE_NAME);

    car_devp = kmalloc(sizeof(struct car_dev), GFP_KERNEL);

    if (!car_devp) {
        printk("Bad Kmalloc\n");
        return -1;
    }
    sprintf(car_devp->name, "car%d", 1);

    cdev_init(&car_devp->cdev, &car_fops);
    car_devp->cdev.owner = THIS_MODULE;

    if (cdev_add(&car_devp->cdev, car_dev_number, 1)) {
        printk("Bad cdev\n");
        return -1;
    }

    device_create(car_class, NULL, car_dev_number, NULL, "car%d", 1);

    for (i = 0; i < sizeof(gpios)/sizeof(gpios[0]); i++) {
        s3c2410_gpio_setpin(gpios[i], S3C2410_GPIO_INPUT);
    }

    printk("Toy Car Controller Driver Initialized.\n");
    return 0;
}

static void __exit toy_car_controller_module_exit(void)
{
    int i;
 
    cdev_del(&car_devp->cdev);
    unregister_chrdev_region(car_dev_number, 1);
    device_destroy(car_class, car_dev_number);
    class_destroy(car_class);

    for (i = 0; i < sizeof(gpios)/sizeof(gpios[0]); i++) {
        s3c2410_gpio_setpin(gpios[i], S3C2410_GPIO_INPUT);
    }
}
module_init(toy_car_controller_module_init);
module_exit(toy_car_controller_module_exit);

MODULE_DESCRIPTION("Control a toy car via bluetooth in mini2440 board");
MODULE_LICENSE("GPL");
MODULE_ALIAS("toy car controller module");
MODULE_AUTHOR("Renee's Blog");
底下是bluetooth server的code:
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <syslog.h>
#include <unistd.h>

#include "car.h"

#define CAR_DEVICE "/dev/car1"
#define CMD_PREFIX "AT+MODE="

int main(int argc, char **argv)
{
 pid_t pid, sid;
 FILE *fp= NULL;
 struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 };
 char buf[1024] = { 0 };
 int s, client, bytes_read;
 socklen_t opt = sizeof(rem_addr);
 struct pollfd pfd;
 int car_fd;

 pid = fork();
 if (pid < 0) {
  exit(EXIT_FAILURE);
 }

 if (pid > 0) {
  exit(EXIT_SUCCESS);
 }

 umask(0);

 sid = setsid();
 if (sid < 0) {
  printf("Failed to create a new SID for the child process\n");
  exit(EXIT_FAILURE);
 }

 if ((chdir("/")) < 0) {
  printf("Failed to change the current working directory\n");
  exit(EXIT_FAILURE);
 }

 s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
 if (s < 0) {
  printf("Can't create RFCOMM socket\n");
  exit(EXIT_FAILURE);
 }
 
 loc_addr.rc_family = AF_BLUETOOTH;
 loc_addr.rc_bdaddr = *BDADDR_ANY;
 loc_addr.rc_channel = (uint8_t) 1;
 if (bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr)) < 0) {
  printf("Can't bind RFCOMM socket\n");
  close(s);
  exit(EXIT_FAILURE);
 }
 
 listen(s, 1);
 
 while (1) {
  printf("Waiting for connection on channel %d\n", loc_addr.rc_channel);
  client = accept(s, (struct sockaddr *)&rem_addr, &opt);
  
  ba2str( &rem_addr.rc_bdaddr, buf );
  printf("Accepted connection from %s\n", buf);
  memset(buf, 0, sizeof(buf));
  
  pfd.fd = client;
  pfd.events = POLLERR | POLLIN;
  for (;;) {
   memset(buf, 0, sizeof(buf));
   poll(&pfd, 1, -1);
   if (pfd.revents & POLLIN) {
    bytes_read = read(client, buf, sizeof(buf));
    if( bytes_read > 0 ) {
     printf("received %s", buf);
     if (strncmp(buf, CMD_PREFIX, 8) == 0) {

      car_fd = open(CAR_DEVICE, O_RDONLY);
      if(car_fd == -1) {
       printf("Failed to open %s file\n", CAR_DEVICE);
      }

      char *pos = strstr(buf, "=");
      int cmd = atoi(pos+1);
      printf("cmd: %d\n", cmd);

      switch(cmd) {
       case 1:
        ioctl(car_fd, IOCTL_UP_GO);
        break;
       case 2:
        ioctl(car_fd, IOCTL_UP_STOP);
        break;
       case 3:
        ioctl(car_fd, IOCTL_DOWN_GO);
        break;
       case 4:
        ioctl(car_fd, IOCTL_DOWN_STOP);
        break;
       case 5:
        ioctl(car_fd, IOCTL_LEFT_GO);
        break;
       case 6:
        ioctl(car_fd, IOCTL_LEFT_STOP);
        break;
       case 7:
        ioctl(car_fd, IOCTL_RIGHT_GO);
        break;
       case 8:
        ioctl(car_fd, IOCTL_RIGHT_STOP);
        break;
      }
     }
    }
   } else if(pfd.revents & POLLERR) {
    printf("Disconnected\n");
    break;
   }
  }
  close(client);
  close(car_fd);
    }
    close(s);
    exit(EXIT_SUCCESS);
}
bluetooth client是由Android APK實現,底下我只貼出核心的code,UI方面就自己設計:
package com.example.bt_remote_controller;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {
 
 final Context mContext = this;
 
 private static final boolean D = true;
 
 private static final String TAG = "BTCarMain";
 private static final int REQUEST_ENABLE_BT = 1;
 public static final String ACTION_SEND_MSG = 
  "COM.EXAMPLE.BT_REMOTE_CONTROLLER.SEND_MSG";
 public static final String ACTION_CLOSE_SOCKET = 
  "COM.EXAMPLE.BT_REMOTE_CONTROLLER.CLOSE_SOCKET";
 public static final String ACTION_CLOSE_PROGRESS_DIALOG = 
  "COM.EXAMPLE.BT_REMOTE_CONTROLLER.PROGRESS_BAR";
 public static final String ACTION_SHOW_DIALOG = 
  "COM.EXAMPLE.BT_REMOTE_CONTROLLER.SHOW_DIALOG";
 
 private ListView mListDevicesFound = null;
 private ArrayAdapter<String> mBtArrayAdapter = null;
 private BluetoothAdapter mBluetoothAdapter = null;
 private BluetoothSocket mBluetoothSocket = null;
 private OutputStream mOutStream = null;
 private boolean hasBluetooth = false;
 private String mAddress = null;
 private ProgressDialog mProgressDialog = null;
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  
  if (D) Log.d(TAG, "ON CREATE");

  setContentView(R.layout.activity_main);
  
  mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
  hasBluetooth = (mBluetoothAdapter != null);
  
  if (hasBluetooth && !mBluetoothAdapter.isEnabled()) {
   Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
   startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
  }
  
  TextView textView = new TextView(this);
  textView.setText("Devices found:");
  
  mBtArrayAdapter = new ArrayAdapter<String>(MainActivity.this, 
   android.R.layout.simple_list_item_1);
  
  mListDevicesFound = (ListView) findViewById(R.id.devicesfound);
  mListDevicesFound.addHeaderView(textView);
  mListDevicesFound.setAdapter(mBtArrayAdapter);
  mListDevicesFound.setOnItemClickListener(new OnItemClickListener() {
   public void onItemClick(AdapterView<?> parent, 
    View view, int position, long id) {
    Object o = mListDevicesFound.getItemAtPosition(position);
    String str = (String) o;
    String[] names = str.split("\n");
    mAddress = names[1];
    
    mProgressDialog = new ProgressDialog(mContext);
    mProgressDialog.setTitle("Progress");
    mProgressDialog.setMessage("Connecting to remote toy car...");
    mProgressDialog.setCancelable(false);
    mProgressDialog.show();
             
    Thread mThread = new Thread(new Runnable() {  
                    public void run() {  
                     connectToServer(mAddress);
                    }
    });
    mThread.start();
   }
     });
  
  IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
  filter.addAction(ACTION_SEND_MSG);
  filter.addAction(ACTION_CLOSE_SOCKET);
  filter.addAction(ACTION_CLOSE_PROGRESS_DIALOG);
  filter.addAction(ACTION_SHOW_DIALOG);
  registerReceiver(BtActionFoundReceiver, filter);
  
 }
 
 @Override
 public void onResume() {
  super.onResume();
  if (D) {
   Log.d(TAG, "ON RESUME");
  }
  if (hasBluetooth && mBluetoothAdapter.isEnabled()) {
   mBtArrayAdapter.clear();
   mBluetoothAdapter.startDiscovery();
  }
 }

 @Override
 public void onPause() {
  super.onPause();
  if (D) Log.d(TAG, "ON PAUSE");
 }

 @Override
 public void onStop() {
  super.onStop();
  if (D) Log.d(TAG, "ON STOP");
 }

 @Override
 public void onDestroy() {
  super.onDestroy();
  unregisterReceiver(BtActionFoundReceiver);
  mBluetoothAdapter.cancelDiscovery();
  if (D) Log.d(TAG, "ON DESTROY");
 }
 
 private void connectToServer(String mac) {
  BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(mac);
  boolean success = false;
  
  for (int i = 1; i <= 30; i++) {
   try {
    Method m = device.getClass().getMethod("createInsecureRfcommSocket", 
     new Class[]{int.class});
    mBluetoothSocket = (BluetoothSocket)m.invoke(device, Integer.valueOf(i));
   } catch (NoSuchMethodException e) {
    Log.e(TAG, "No such method.", e);
   } catch (IllegalArgumentException e) {
    e.printStackTrace();
   } catch (IllegalAccessException e) {
    e.printStackTrace();
   } catch (InvocationTargetException e) {
    e.printStackTrace();
   }
   
   try {
    mBluetoothSocket.connect();
    Log.d(TAG, "BT connection established, data transfer link open.");
    success = true;
    break;
   } catch (IOException e) {
    try {
     mBluetoothSocket.close();
     success = false;
    } catch (IOException e1) {
     Log.e(TAG, "Unable to close socket during connection failure", e1);
    }
   }
  }
  
  if (success) {
   mBluetoothAdapter.cancelDiscovery();
   try {
    mOutStream = mBluetoothSocket.getOutputStream();
   } catch (IOException e) {
    Log.e(TAG, "Output stream creation failed.", e);
   }
   Intent intent = new Intent(mContext, ControlPanelActivity.class);
   startActivity(intent);
  } else {
   Intent intent = new Intent(MainActivity.ACTION_SHOW_DIALOG);
   mContext.sendBroadcast(intent);
  }
  
  Intent intent = new Intent(MainActivity.ACTION_CLOSE_PROGRESS_DIALOG);
  mContext.sendBroadcast(intent);
 }
 
 private final BroadcastReceiver BtActionFoundReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {
   String action = intent.getAction();
   if(BluetoothDevice.ACTION_FOUND.equals(action)) {
    BluetoothDevice device = 
     intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
    mBtArrayAdapter.add(device.getName() + "\n" + device.getAddress());
    mBtArrayAdapter.notifyDataSetChanged();
   } else if (ACTION_SEND_MSG.equals(action)) {
    String cmd = intent.getStringExtra("command");
    byte[] cmdBuffer = cmd.getBytes();
       try {
        mOutStream.write(cmdBuffer);
       } catch (IOException e) {
        Log.e(TAG, "ON CREATE: Exception during write.", e);
       }
   } else if (ACTION_CLOSE_SOCKET.equals(action)) {
    if (mOutStream != null) {
     try {
      mOutStream.flush();
     } catch (IOException e) {
      Log.e(TAG, "ON PAUSE: Couldn't flush output stream.", e);
     }
    }
    try {
     if (mBluetoothSocket != null)
      mBluetoothSocket.close();
    } catch (IOException e) {
     Log.e(TAG, "ON PAUSE: Unable to close socket.", e);
    }
   } else if (ACTION_CLOSE_PROGRESS_DIALOG.equals(action)) {
    mProgressDialog.dismiss();
   } else if (ACTION_SHOW_DIALOG.equals(action)) {
    AlertDialog.Builder ad = new AlertDialog.Builder(mContext);
    ad.setTitle("Error");
    ad.setMessage(
     "Failed to connect to remote toy car. Please try it again!");
    ad.setNeutralButton("OK", null);
    ad.show();
   }
  }
 };
}
下圖是scan bluetooth的UI:


遙控車的控制UI:


完工啦,準備開機來玩一玩。


Demo影片:

沒有留言:

張貼留言