準備了一顆行動電源與一台遙控車,遙控器用不到了,可以丟了。
將遙控車拆開,把內建接收與控制的電路板拆掉,只剩馬達與車身。
接下來,拆開行動電源,外殼可以丟了。
我把行動電源的電路板移植到遙控車底部,然後電池固定在車身上面,下圖白色部分就是電池,被白色膠帶包起來。圖片上可以看到有一顆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。
硬體部分已經施工完成,欣賞一下不同角度。
接下來要幫它注入靈魂。
- 首先你要能讓mini2440開發板開能動,可以參考我之寫的文章『Mini2440我命令你復活吧!』。
- 然後你要將BlueZ移植到mini2440,可以參考我之寫的文章『Cross compile bluez for ARM』,建議使用BlueZ 2.25,BlueZ工具可以用來設定bluetooth。
- 接下來撰寫一隻控制馬達的driver與一隻user space背景執行的daemon,此daemon扮演bluetooth server角色,當server收到手機bluetooth client發出的commands後,會發出system call通知driver,driver再去控制GPIOs來控制IC,讓馬達前進,後退,左右動作。
#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影片:
沒有留言:
張貼留言