天天看點

使用單片機Arduino(AVR)與Android裝置通訊

Android手機USB與arduino通信,啥也不說了,先上效果視訊:http://v.youku.com/v_show/id_XMTY3MjkxODQ5Ng==.html?from=y1.7-2

http://ghdawn.me/blog/2012/05/android-arduino/

簡述需求

現在的Android裝置,像手機,平闆等,有很多的資源,比如照相機,音箱等,同時CPU已經很好,運算能力很強。功能十分豐富,但是必須得人手操控才能使用。這麼豐富的資源,如果能自動做點事情,或者作為一個控制核心控制其它的東西就更好了,是以Google官方提供了一種方法,将Android裝置按附件模式與一個有USB Host的裝置相連,兩者通過USB接口相連傳輸資料,進而實作通過單片機操控手機。(USB是主從結構的總線,這裡要求Android裝置作為從機,單片機作為主機,而一般的開發闆附帶的usb口都是client,如果需要做這個實驗,則需要買有USB Host的開發闆,或者買專門的 USB host shield子產品放在開發闆上。)

在這裡,Google要求Android平台的版本至少為2.3.3,單片機要求實作了Android Accessory Protocol協定。其中Google官方支援了一個開源硬體平台Arduino。現階段,Android隻能支援一個USB裝置,不過能滿足大部分需求了。

Google官方提供了一個簡潔的教程(Arduino部分,與Android部分,以及一份代碼示例(包括Android與Arduino部分,在對應的網頁裡下)。但是教程過于簡潔而示例過于複雜:直接按照教程做,很多地方無從下手。按照代碼改,代碼結構又過于複雜,依然不好下手。于是我在這裡耽誤了一天多的時間。

Arduino部分

  1. 在Arduino下載下傳最新的IDE,它是用JAVA開發的,跨平台。同時幾乎所有底層的驅動全部寫好,開發的時候隻要調用即可,完全感覺不到是在開發單片機,實在很爽。
  2. 下載下傳Google 提供的協定實作代碼。解壓出來後,将firmware/arduino_libs/下的AndroidAccessory和USB_Host_Shield複制到Arduino IDE的libraries目錄下。這兩個分别是Android附件協定的實作和USB的驅動。
  3. 如果按照教程,現在隻需要打開firmware/demokit/demokit.pde并燒寫進開發闆,就可以和教程配套的Android程式進行通訊并控制電機之類的驅動了。
  4. 但是自己做開發的話就不要用上面的代碼了,太複雜太麻煩。在IDE裡建立一個檔案,包含USB驅動和AndroidAccessory的頭檔案,并建立一個AndroidAccessory對象,比如叫acc。在setup()函數中,調用acc.powerOn()方法,即可開始試探連結Android裝置。
  5. 在我的應用中,我需要做的是把Android裝置中計算的結果以序列槽的形式發給飛控子產品,是以我隻需要不斷的把Android裝置發送來的資料發給序列槽,再把序列槽接受到的資料發給Android裝置。于是,代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
      
#include <Usb.h>
#include <AndroidAccessory.h>



AndroidAccessory acc("BuaaITR",
		     "Demo",
		     "DemoKit Arduino Board",
		     "1.0",
		     "http://www.android.com",
		     "0000000012345678");


void setup()
{
	Serial.begin(115200);
	Serial.print("\r\nStart");

	acc.powerOn();
}

void loop()
{
	
	byte msg[1024];
	
	if (acc.isConnected())
        {
                while(Serial.available()>0)
                {
                  msg[0]=Serial.read();
                  acc.write(msg,1);
                }
                int len = acc.read(msg, sizeof(msg), 1);
                if (len > 0)
                {
                  Serial.write(msg,len);
                }
        }
	delay(200);
}

           
一些解釋

按照這樣的方法,單片機這部分就很容易能搞定了,隻要Android程式寫好了,兩個就能比對工作了。

  • Arduino簡化了開發流程,去掉了主函數,隻留下 setup()作為初始化,loop()不斷循環。是以把初始化的部分寫在setup()裡,工作的部分寫在loop()中。 
  • AndroidAccessory對象的構造函數有6個參數,分别為:裝置制造商,裝置模型,裝置描述,裝置版本,網址和序列号。其中制造商,模型和版本必須與Android裝置上的軟體比對。即開發Android裝置上運作的軟體時,也需要制定這三個參數,隻有這三個參數相同的裝置才能互相連接配接。 
  • 調用acc.powerOn();來使單片機開始工作 
  • 單片機與Android裝置不一定會比對,是以需要acc.isConnected()判斷是否已經成功的連接配接。 
  • 讀寫方法分别為

    acc.write(char* msg,int length)

    acc.read(char* msg,int length ,int nakLimit)

    。其中msg和length分别為存放資料的數組和期望讀寫的資料長度。讀取函數的第三個參數nakLimit,目前我在網上還沒找到有人知道是做什麼用的,反正設為1就能用。  

Android部分

首先聲明,這裡我是參考了Google的官方文檔,同時在Google給的示例代碼中改成的,代碼已經十分精簡,可以直接修改以完成所需的任務。如果有時間,完全可以讀Google的代碼,從那一大堆代碼裡修改。

操作USB的時候,SDK版本為2.3.3,即API 10時是一種操作,版本為那之上的是另一種操作。API 10需要裝add-on library,我用的是API 10。裝好之後,在項目屬性中,點選Android,把Build Target改為Google APIs。

要想使Accessory工作,需要在AndroidManifest.xml中聲明支援UsbManager.ACTION_USB_ACCESSORY_DETACHED,并添加一個過濾器,來過濾裝置。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
      
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="me.ghdawn"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-feature android:name="android.hardware.usb.accessory" />

    <uses-sdk android:minSdkVersion="10" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <uses-library android:name="com.android.future.usb.accessory" />

        <activity
            android:name=".UsbAccActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
            </intent-filter>

            <meta-data
                android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
                android:resource="@xml/accessory_filter" />
        </activity>
    </application>

</manifest>

           
一些說明
  • API 10使用的是Addon library,需要注明:
  • 要說明支援USB_ACCESSORY_ATTACHED模式,是以加上
    <intent-filter>
              <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
      </intent-filter>
               
  • 可能會有很多USB接口的裝置,是以我們還需要篩選一下此程式能接的單片機,是以增加一個accessory_filter.xml來篩選裝置。在res檔案夾下建立檔案夾xml,在其中建立檔案accessory_filter.xml,在這裡增加需要的單片機的條件。
    <meta-data
              android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
              android:resource="@xml/accessory_filter" />
               

上面這段代碼就是注冊這個篩選器的。下面這段就是篩選器的内容。還記得上面的Arduino部分中,建立的AndroidAccessory對象嗎?那裡的第1,2,4個參數正是這裡篩選的參數。隻有這幾個參數比對的裝置才能建立連接配接。當然,這裡篩選條件是可以選的,那幾個參數都可以作為篩選條件,隻要加在下面就可以。

1
2
3
4
5
      
<?xml version="1.0" encoding="utf-8"?>

<resources>
    <usb-accessory manufacturer="BuaaITR" model="Demo" version="1.0" />
</resources>

           

注:下文的所有裝置一詞,均指代Arduino裝置,程式則代表Android裝置。

這樣,就可以開始寫代碼了。首先需要一個UsbManager對象來管理USB裝置,需要一個廣播接收器,當系統有廣播時,來判斷是否為USB附件,并詢問是否提供權限。廣播的過濾器使用UsbManager.ACTION_USB_ACCESSORY_DETACHED作為action。當接受到一個滿足過濾條件的廣播時,并且獲得了通路的權限,就可以獲得該裝置的資訊,并進行讀寫了。

廣播接收器的代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
      
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver()
	{
		@Override
		public void onReceive(Context context, Intent intent)
		{
			String action = intent.getAction();
			if (ACTION_USB_PERMISSION.equals(action))
			{
				synchronized (this)
				{
					UsbAccessory accessory = UsbManager.getAccessory(intent);
					if (intent.getBooleanExtra(
					        UsbManager.EXTRA_PERMISSION_GRANTED, false))
					{
						openAccessory(accessory);
					}
					else
					{
						Log.d(TAG, "permission denied for accessory "
						        + accessory);
					}
					mPermissionRequestPending = false;
				}
			}
			else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action))
			{
				UsbAccessory accessory = UsbManager.getAccessory(intent);
				if (accessory != null && accessory.equals(mAccessory))
				{
					closeAccessory();
				}
			}
		}
	};

           

建立連接配接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
      
private void openAccessory(UsbAccessory accessory)
	{
		mFileDescriptor = mUsbManager.openAccessory(accessory);
		if (mFileDescriptor != null)
		{
			mAccessory = accessory;
			//獲得該裝置的輸入輸出流
			FileDescriptor fd = mFileDescriptor.getFileDescriptor();
			mInputStream = new FileInputStream(fd);
			mOutputStream = new FileOutputStream(fd);
			//是否能對裝置進行讀寫操作
			canIO = true;
			//定時查詢是否有資料可以接收
			timer.scheduleAtFixedRate(new TimerTask()
			{
				@Override
				public void run()
				{
					// TODO Auto-generated method stub
					int length = 0;
					byte[] buffer = new byte[maxBuffer];
					try
					{
						//如果有資料來,則接受資料。
						if(mInputStream.available()>0)
						{
							length=mInputStream.read(buffer);
							//處理接收到的資料,按需要自己改。
							usbuart.onReceive(buffer);
						}

					}
					catch (IOException e)
					{
						// tbhello.setText("IO error\n" + e.getMessage());

					}

				}
				
			}, 0, delaytime);
			Log.d(TAG, "accessory opened");

		}
		else
		{
			Log.d(TAG, "accessory open fail");
		}
	}

           

這樣,大部分功能就實作完了,現在需要注冊廣播接收器,并讓程式監視USB裝置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
      
@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		//使用add-on library時,必須這樣定義usbmanager對象
		mUsbManager = UsbManager.getInstance(this);
		mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(
		        ACTION_USB_PERMISSION), 0);
		//注冊接收器和過濾器
		IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
		filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
		registerReceiver(mUsbReceiver, filter);

		setContentView(R.layout.main);

	}

	@Override
	public void onResume()
	{
		super.onResume();

		Intent intent = getIntent();
		//如果已經打開了一個裝置,就不再查詢
		if (mInputStream != null && mOutputStream != null)
		{
			return;
		}
		//隻能支援一個裝置,如果發現了一個USB裝置并且有權限通路,就打開
		UsbAccessory[] accessories = mUsbManager.getAccessoryList();
		UsbAccessory accessory = (accessories == null ? null : accessories[0]);
		if (accessory != null)
		{
			if (mUsbManager.hasPermission(accessory))
			{
				openAccessory(accessory);
			}
			
		}
		else
		{
			Log.d(TAG, "mAccessory is null");
		}
	}

	@Override
	public void onPause()
	{
		super.onPause();
		closeAccessory();
	}

	@Override
	public void onDestroy()
	{
		unregisterReceiver(mUsbReceiver);
		super.onDestroy();
	}

           

如果需要發送資料,就這樣:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
      
public void send(byte[] data)
    {
	    if(canIO)
	    {
	    	try
            {
	            mOutputStream.write(data);
            }
            catch (IOException e)
            {	
	            // TODO Auto-generated catch block
	            e.printStackTrace();
            }
	    }
    }


           

用到的對象如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
      
private static final String TAG = "DemoKit";

	private static final String ACTION_USB_PERMISSION = "com.google.android.DemoKit.action.USB_PERMISSION";

	private UsbManager mUsbManager;
	private PendingIntent mPermissionIntent;


	private int maxBuffer=1024;
	private boolean canIO = false;
	UsbAccessory mAccessory;
	ParcelFileDescriptor mFileDescriptor;
	FileInputStream mInputStream;
	FileOutputStream mOutputStream;

	Timer timer = new Timer();

           

如果步驟沒出錯的話,至此,把Arduino開發闆插到Android裝置上,應該就能互相傳資料了。