準備了一顆行動電源與一台遙控車,遙控器用不到了,可以丟了。
將遙控車拆開,把內建接收與控制的電路板拆掉,只剩馬達與車身。
接下來,拆開行動電源,外殼可以丟了。
我把行動電源的電路板移植到遙控車底部,然後電池固定在車身上面,下圖白色部分就是電池,被白色膠帶包起來。圖片上可以看到有一顆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影片:
沒有留言:
張貼留言