单片机与android

2017-02-02 18:53:41 qq122627018 阅读数 6345

源码传送门

前言

本篇文章将围绕App与单片机的蓝牙通信来说说lz最近进行开发的一些方案与思考
此文分为三部分:

  • 单片机的PWM与串口通信
  • Android的蓝牙开发
  • 单片机与Android App的通信方案

预览

这里写图片描述
这里写图片描述

视频观看:

http://v.youku.com/v_show/id_XMjQ5MTgyMTAwMA==.html

环境

单片机

  • 单片机:STC89C52
  • c语言
  • 编写环境:Keil uVision4
  • 烧录:stc-isp-15xx-v6.82

Android

  • android sdk 16+
  • Android studio 1.0+

单片机的PWM与串口通信

PWM

我相信PWM的概念大家都应该,如果还很模糊也可以去查查,可以看看这篇文章

http://www.eepw.com.cn/article/275890.htm

那么我们这里要说的,就是怎么通过程序去模拟pwm信号(有些单片机自带pwm处理,就无需写程序),从程序的方面讲,我们要模拟PWM,就是让高电平持续一小段时间,然后再让低电平持续一段时间,也就是改变占空比。
那么再单片机中,这种关于频率的事情一般都是通过定时器来实现的,那么我的方案是这样的:
设置一个全局变量t,PWM_T,每当定时器中断的时候使t自增1,当t等于100的时候,使之高电平,并让t等于0,当t等于PWM_T的时候,使之低电平,这样,我们就可以通过改变PWM_T的值来改变占空比,从而实现通过目标的电压,使之达到调节的效果(例如调节led灯的亮度,调节电机的速度等)

/****************************************************
               定时器0中断模拟PWM
               调节led的亮度
****************************************************/
int t = 0;
int PWM_T = 0;   //占空比控制变
void main()
{
	TMOD = 0x22;   //定时器0,工作模式2,8位定时模式
	TH0=210;     //写入预置初值(取值1-255,数越大PWM频率越高)
	TL0=210;     //写入预置值 (取值1-255,数越大PWM频率越高)
	TR0=1;       //启动定时器
	ET0=1;       //允许定时器0中断
	EA=1;        //允许总中断
	P1=0xff; 	 //初始化P1,输出端口
	PWM_T=30;
	while(1)      
	{   	
		if(!up)   //当up按键按下的时候
		{
			if(PWM_T<100)
			{
				PWM_T+=1;
			}
			delay_1ms(20);
		}
		if(!down)  //当down按键按下的时候
		{
			if(PWM_T>0)
			{
				PWM_T-=1;
			}
			delay_1ms(20);
		}
	 }  
}

timer0() interrupt 1  
{ 
	t++;    //每次定时器溢出加1
	if(t==100)   //PWM周期 100个单位
	{
		t=0;  //使t=0,开始新的PWM周期
		P1=0x00;  //输出端口,使之低电平
	} 
	if(PWM_T==t)  //按照当前占空比切换输出为高电平
	{  
		P1=0xff;    //输出端口,使之高电平    
	}
}

串口通信

上面我们说了PWM调速,那么要达到app实时显示速度,就必须要单片机把速度传输给手机(在这里先用占空比模拟实时速度,道理是一样的,春节快递停了,测速模块还没到),那么我的首选方案肯定是单片机通过蓝牙串口发送给app,app接收并进行显示,这里我的蓝牙模块是hc-06。串口通信很容易,但在这个过程中我发现难的地方是数据格式的定义和数据的解析,也就是说要统一使用16进制,还是10进制,数据的头节点和尾节点的定义,或者说数据每一位所代表的参数,在这里先埋个伏笔,文章的后面会对我自己的方案进行介绍.

Android蓝牙开发

那么android为我们提供的关于蓝牙的api其实已经很强大了,通常的步骤为:

  1. 打开蓝牙
  2. 搜索蓝牙设备
  3. 进行配对
  4. 连接
  5. 数据的发送与接收

开启蓝牙

private BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
/**
* 打开蓝牙
*/
public static void openBluetooth(@NonNull Activity activity) {
	if (INSTANCE.bluetoothAdapter == null) {
            // 设备不支持蓝牙
            Toast.makeText(INSTANCE.context.getApplicationContext(), "您的设备似乎不支持蓝牙", Toast.LENGTH_SHORT).show();
            return;
	}
	Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
	activity.startActivityForResult(enableBtIntent, 6);
}

/**
* 关闭蓝牙
*/
public static void closeBluetooth() {
	if (INSTANCE.bluetoothAdapter != null) {
		INSTANCE.bluetoothAdapter.disable();
	}
}

/**
* 判断蓝牙是否打开
* @return boolean 蓝牙是否打开
*/
public static boolean isBluetoothOpen() {
	return INSTANCE.bluetoothAdapter != null && INSTANCE.bluetoothAdapter.isEnabled();
}

搜索附近的蓝牙设备

那么搜索蓝牙设备当然也是调用系统的api即可,然后系统通过广播接收者的方式告诉你,我找到设备了,下面po出代码

/**
* 搜索蓝牙设备
*/
public static void searchDevices() {
	INSTANCE.bluetoothDevices.clear();
	if (INSTANCE.bluetoothAdapter != null) {
		// 寻找蓝牙设备,android会将查找到的设备以广播形式发出去
		INSTANCE.bluetoothAdapter.startDiscovery();
	}
}

下面是所要接收的广播


    /**
     * 初始化过滤器
     */
    private void initIntentFilter() {
        // 设置广播信息过滤
        IntentFilter intentFilter = new IntentFilter();
        //搜索到设备
        intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
        intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
        intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        //蓝牙状态改变
        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        //绑定状态改变
        intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
        // 注册广播接收器,接收并处理搜索结果
        registerReceiver(receiver, intentFilter);
    }

那么当接收到广播的时候,只需调用BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)就可以取出对应的搜索的蓝牙设备

蓝牙配对

 /**
     * 绑定设备
     *
     * @param device BluetoothDevice对象
     * @return 是否绑定成功
     */
    public static boolean bondDevice(BluetoothDevice device) {
        if (INSTANCE.bluetoothAdapter == null) {
            return false;
        }
        // 取消蓝牙设备搜索
        INSTANCE.bluetoothAdapter.cancelDiscovery();
        try {
            if (device.getBondState() == BluetoothDevice.BOND_NONE) {
                // 设备未配对,进行配对操作
                Method method = BluetoothDevice.class.getMethod("createBond");
                method.invoke(device);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

蓝牙连接

	BluetoothSocket socket;
	//获取一个uuid值
	UUID uuid = uuidCandidates.get(candidate++);
	//根据android不同sdk调用不同的api
	if (secure) {
		socket= device.createRfcommSocketToServiceRecord(uuid);
	} else {
		socket= device.createInsecureRfcommSocketToServiceRecord(uuid);
	}

数据的发送与接收

参考了网上很多关于蓝牙数据通信的做法,好多都是每发送一次数据都关闭socket,但是那样我觉得并不好,因为socket的开启与关闭都是比较耗费资源的,那么我的方案是开启一个线程保持socket连接进行蓝牙数据的接收与发送。

public class TouchMsgThread extends Thread {
    private  BluetoothSocket socket;
    private  InputStream inputStream;
    private  OutputStream outputStream;
    private  Handler handler;
    public TouchMsgThread(BluetoothSocket socket, Handler handler) {
        this.socket = socket;
        InputStream input = null;
        OutputStream output = null;
        this.handler = handler;
        try {
            input = socket.getInputStream();
            output = socket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        this.inputStream = input;
        this.outputStream = output;
    }
    public void run() {
        while (true) {
            try {
                int count = 5;
                byte[] bytes = new byte[count];
                int readCount = 0; // 已经成功读取的字节的个数
	              while (readCount < count) {
                    readCount += inputStream.read(bytes, readCount, count - readCount);
               }
                int s = BinaryToHexString(bytes);
                Message message=handler.obtainMessage();
                message.what = 333;
                message.obj=s;
                handler.sendMessage(message);
            } catch (Exception e) {
                e.printStackTrace();
                break;
            }
        }
    }
    public void write(byte[] bytes) {
        try {
            byte[] b = {-1,1,2,3,-1};
            outputStream.write(b);
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void cancel() {
        try {
            if(outputStream!=null){
                outputStream.close();
                outputStream = null;
            }
            if(inputStream!=null){
                inputStream.close();
                inputStream = null;
            }
            //socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //数据的检验,这里暂时先忽略
    public static int BinaryToHexString(byte[] bytes) {
        int result = 0;
        String temp = "";
        for(int i=0;i<5;i++){
            byte b = bytes[i];
            if(i==2){
                temp = Integer.toHexString((b & 0xff));
            }
            if(i==3){
                String hex = Integer.toHexString((b & 0xff));
                result = Integer.parseInt(temp+hex, 16);
            }
        }
        return result;
    }
}

单片机与Android的通信方案

制定协议

那么上面我们已经讲了单片机与Android怎么样通过蓝牙进行信息交互了,但是在实际应用中,二者之间传递的信息类型太多了,比如实时速度,电量,还有车子灯光打开,或者修改车子密码等等信息,那么单片机或者app要怎么去判断传递过来的是哪种信息呢?那么我们就必须去制定一套数据协议,这里看看我的方案,协议规定:

包头 类型位 数据位 数据位 结束位
0xFF 0x** 0x** 0x** 0xFF

那么我们的数据位可以分别代表高二位和低二位,那么通常情况下这种方案就可以满足我们的需求了。举个例子:

类型位 数据位 数据位 功能
0X00 0X02 0X00 前进
0X00 0X01 0X00 后退
0X00 0X03 0X00 左转
0X00 0X04 0X00 右转
0X00 0X00 0X00 停止
0X02 0x00 0X01 车灯亮
0X02 0x00 0X02 车灯灭
0X03 雷达数据高位 雷达数据低位 发送雷达数据

协议的解析

未完待续

2013-10-06 17:21:17 qq5132834 阅读数 7611


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/下的AndroidAccessoryUSB_Host_Shield复制到Arduino IDElibraries目录下。这两个分别是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设备上,应该就能互相传数据了。

 

2016-07-04 12:01:58 qq_29918347 阅读数 4891

前段时间一直在加班,单身狗的周末也搭进去了…只是为了解决一个莫名其妙的bug,不过最终bug还是拜倒在了朕的强大气场下,哈哈哈,现在就在这里好好吐槽下软件遇上硬件的坑;

先简单说说情景:软件需要给硬件设备的单片机发送一串byte数组,用于打开软件与硬件设备之间的通讯通道,软件命令发送完,接着就打开一个线程用于接收从硬件设备回传的数据,然后开始做其他的一些操作。但是现在出现的问题是命令发送了,会出现偶尔接收不到数据,刚开始还以为是硬件部门的问题,请硬件部门的人过来协助,结果发现软件发送的指令单片机也是接收到了的,那么是我写的程序有问题?NO~

中间的各种脑残调试,这里就不细说了,说说最终得到的分析结果:从软件发送指令到单片机接收到指令,其实是需要时间的,至于这个时间大概在两百毫秒以内,而软件在发送了这段指令之后,就直接执行后面的接收操作,但是这个时候单片机有可能还没有接收到软件发送的指令,通道没有打开,而我的程序已经在开始读取数据了(读个鬼啊,什么都没有),这样就造成了我的操作界面一直处于等待的状态…那为什么有时候又可以读取到数据了?因为从发送数据到单片机接收数据,这个时间不可控的,为什么不可控,这里我猜测跟系统资源及单片机内的程序有关,这种情况就好像当电脑内存紧张时,软件运行速度会降低一样(当然这种情况要毕竟很少),而单片机程序部分如果接收到指令后还做了其他耗时操作这个也是有可能的…知道了原因,那么在程序发送命令后,就简单粗暴的强制线程睡眠一秒Thread.sleep(200),其实这种方法,依然存在问题,正确的最好的办法是让单片机在接收到我发出的命令后返回一个响应(但是要跟硬件部门沟通了~~~)

2019-06-15 00:55:31 a_zhon 阅读数 2224

一、缘起

工作中经常遇到需要Android程序与各式各样的板子、智能设备进行交互,通信的方式也基本上都是Ble或者Socket tcp/udp等等…其中最重要的一点就是通信的协议协议协议重要的是说三遍;通信协议就是用来定义与设备交互的方式和交互过程中数据包的格式 如:(包头—数据包长度—数据—校验位—包尾)

二、这里先来理理各种数据类型、所占的字节数等

1、我们写的代码最终都是转化成各种机器所能识别的二进制或者字节码,不同的编程语言具有不同的数据类型基本的也好不基本的也好、当然有不同的也就有相同的byte(字节)就是其中的一个;
2、日常开发中我们进行通信发送的内容最终都会以字节的形式进行发送,这里以JavaSocket为例,我们来看下源码

  • 创建一个socket连接,发送数据
Socket socket = new Socket(ip, port);
OutputStream outputStream = socket.getOutputStream();
//发送数据
outputStream.write("Hello World!".getBytes());
outputStream.flush();
//关闭连接
outputStream.close();
socket.close();
  • 我们来看下OutputStreamwirte(byte[] b)函数
  • OutputStream # write(byte[] b)
//1 接着又调用了write(byte b[], int off, int len)
public void write(byte b[]) throws IOException {
    write(b, 0, b.length);
}

//2 最后又调用了write(byte b)
public void write(byte b[], int off, int len) throws IOException {
    if (b == null) {
        throw new NullPointerException();
    } else if ((off < 0) || (off > b.length) || (len < 0) ||
               ((off + len) > b.length) || ((off + len) < 0)) {
        throw new IndexOutOfBoundsException();
    } else if (len == 0) {
        return;
    }
    //3 这里就是讲我们发送的一个bye[]进行for循环一个个写入了
    for (int i = 0 ; i < len ; i++) {
        write(b[off + i]);
    }
}

//4 end
public abstract void write(int b) throws IOException;

小结:无论定义的通讯格式是什么样的最终肯定要转成byte[](字节数组)进行发送,所以只要将数据转成字节数组即可,下面进入数据类型科普时间

三、Java中的数据类型所占的字节数和bit数

数据类型 所占字节数 所占bit数 取值范围
byte 1 8 -128 ~ 127
char 2 16 ‘\u0000’ ~ ‘\uFFFF’
short 2 16 -2^15 ~ 2^15 - 1
int 4 32 -2^31 ~ 2^31 - 1
float 4 32 2^-149 ~ 2^128 -1
long 8 64 -2^63 ~ 2^63 - 1
double 8 64 2^-1074 ~ 2^1024 - 1
boolean / 1 true or false
  • String在Java中不属于基本数据类型,一个汉字占2个字节,一个英文字母占1个字节
  • 小结:1 byte = 8 bit

3.1 什么是bit呢?什么又是高低位呢?在Java中又怎么写代码呢?

  • bit就是 也就是二进制数据,取值只有 0,1
  • 高位在左,低位在右
    • 这里以byte 123为例:
    • byte b =123 转为bit
    • 高位在0,低位在1 0111 1011
  • 在Java中获取byte的8个bit
/**
 * byte转8 bit
 *
 * @param b byte
 * @return 高位到低位顺序, 以byte123 为例: 0111 1011
 */
public static byte[] byte2Bit(byte b) {
    byte[] arr = new byte[8];
    for (int i = 7; i >= 0; i--) {
        arr[i] = (byte) (b & 1);
        b = (byte) (b >> 1);
    }
    return arr;
}
  • 既然把byte转为了8个bit位,那我们又怎么再把bit转回为byte呢?
/**
 * 8个bit位转为byte
 */
public static byte bit2Byte(byte[] bytes) {
    if (bytes.length != 8) return 0;
    String binary = "";
    byte result;
    for (byte b : bytes) {
        binary += b;
    }
    if (bytes[0] == 0) {
        // 正数
        result = (byte) Integer.parseInt(binary, 2);
    } else {
        // 负数
        result = (byte) (Integer.parseInt(binary, 2) - 256);
    }
    return result;
}

3.2 上面已经说了byte与bit的相互转化,现在就轮到int

  • 上面已经说了一个int4个字节32bit
  • Integer类已经为我们封装好了转bit的方法,如下:
String s = Integer.toBinaryString(35235);
//输出结果
1000100110100011
  • 可以看到没有32位,这是为什么呢?这是因为高位都是为0所以就直接省略了,当然我们也可以主动补齐32位只需要在高位补0即可。
  • bit再转回为int
int result = Integer.parseInt("1000100110100011", 2);
//输出结果
35235
  • 这里需要注意的是Integer.toBinaryString()可以将负数转化为二进制,但是Integer.parseInt("", 2)不能直接将负数的二进制转为int,如下:
String radix = Integer.toBinaryString(-35235);
System.out.println(radix);
int result = Integer.parseInt(radix, 2);
System.out.println(result);

程序执行会报一个java.lang.NumberFormatException: For input string:"11111111111111110111011001011101"异常,那我们怎么将负数的转回为int呢?当然是有方法的啦,如下:

//需要借助 BigInteger类
String radix = Integer.toBinaryString(-3535);
BigInteger integer = new BigInteger(radix, 2);
System.out.println(integer.intValue());
//输出结果
-3535

3.3当然我们可以通过电脑的计算器来计算二进制

3.4 上面我们说了一个int32个字节也就是4byte,那理所当然一个int可以转成2个byte或者4byte,如下:

/**
 * 一个int转2个字节的byte数组
 * 由低位到高位的转换
 *
 * @param value
 * @return
 */
public static byte[] intTo2Bytes(int value) {
    byte[] src = new byte[2];
    src[0] = (byte) (value & 0xFF);
    src[1] = (byte) ((value >> 8) & 0xFF);
    return src;
}

/**
 * 一个int转4个字节的byte数组
 * 由低位到高位的转换
 *
 * @param value
 * @return
 */
public static byte[] intTo4Bytes(int value) {
    byte[] src = new byte[4];
    src[0] = (byte) (value & 0xFF);
    src[1] = (byte) ((value >> 8) & 0xFF);
    src[2] = (byte) ((value >> 16) & 0xFF);
    src[3] = (byte) ((value >> 24) & 0xFF);
    return src;
}

这里需要注意的是int转byte[]的时候是高位数组的0下标 还是低位数组的0下标,上面的两个方法都是低位在数组的0下标

四、上面bb了一大堆,现在我们通过一个具体的协议来深入了解这些内容

4.1 协议如下:

在这里插入图片描述

这里需要解释下ucharuint是什么意思?uchar = unsigned char 、uint = unsigned int,也就是无符号的数据,也就是表示了这个数据是正数

  • 1、对协议进行分析可以得知:整个数据包是由两部分组成的包头+扩展数据包,其中包头占固定的32个字节
  • 2、首先我们得分析包头里面的每一个字段所占了多少个字节
    • uchar 占1个字节
    • uint 占4个字节
  • 2、那我们重点就是得来分析包头的数据需要怎么封装,通过协议我们可以看出包头内一共包含6个字段,分别表示如下:
    • 第一个为固定的"DH",总共占2个字节
    • 第二个为版本1.0 ,总共占2个字节
    • 第三个为扩展数据长度"extlen" ,总共占4个字节
    • 第四个为扩展数据类型取值0或1 ,总共占1个字节
    • 第五个为保留字段不使用就0补齐,总共占3个字节
    • 第六个为保留字段不使用就0补齐,总共占20个字节
  • 3、通过上面一顿分析,我们就轻松的理清了每个字段所占的字节了

4.2 Talk is cheap. Show me the code.

//magic
byte[] magicB = {'D', 'H'};
//协议版本1.0转成int也就是1
byte[] versionB = {0, 1};
//扩展数据长度,这里假定扩展数据的长度为67
byte[] extLenB = intTo4Bytes(67);
//扩展数据类型 0:JSON、1:二进制数据;这里使用JSON
byte[] extType = {0};
//两个保留字段,直接0补齐,上面已经分析了两个字段一共占23个字节
byte[] reserved = new byte[23];
//这里将上面的多个数据合并至一个byte[]
byte[] data = byteMergerAll(magicB, versionB, extLenB, extType, reserved);

到这里包头的数据就已经处理好了,还可以进一步对它进行封装

  • 这里提供一个多个数据数组合并的工具方法
/**
 * 多个数组合并一个
 *
 * @return
 */
public static byte[] byteMergerAll(byte[]... bytes) {
    int allLength = 0;
    for (byte[] b : bytes) {
        allLength += b.length;
    }
    byte[] allByte = new byte[allLength];
    int countLength = 0;
    for (byte[] b : bytes) {
        System.arraycopy(b, 0, allByte, countLength, b.length);
        countLength += b.length;
    }
    return allByte;
}

4.3 封装包头数据

/**
 * 封装包头数据
 * 固定32个字节,其余的0补齐
 *
 * @param extLen 扩展数据长度
 */
public static byte[] getPkgHead(int extLen) {
    //magic
    byte[] magicB = {'D', 'H'};
    //协议版本1.0转成int也就是1
    byte[] versionB = {0, 1};
    //扩展数据长度,这里假定扩展数据的长度为67
    byte[] extLenB = intTo4Bytes(extLen);
    //扩展数据类型 0:JSON、1:二进制数据;这里使用JSON
    byte[] extType = {0};
    //两个保留字段,直接0补齐,上面已经分析了两个字段一共占23个字节
    byte[] reserved = new byte[23];
    //这里将上面的多个数据合并至一个byte[]
    return byteMergerAll(magicB, versionB, extLenB, extType, reserved);
}

4.4上面已经把包头处理好了那现在就可以发送命令了

//扩展数据:这里就需要根据实际的文档来生成了,我这里就随便写一个了
String extData = "{\"id\":12,\"cmd\":\"open\"}";
 byte[] extDataB = extData.getBytes();
 //获取包头
 byte[] pkgHead = getPkgHead(extDataB.length);
 //一个完整的数据包
 byte[] sendData = byteMergerAll(pkgHead, extDataB);

到这里一个完整的数据包就愉快的结束了也就实现了与设备的通信了;重点:以后再拿到一个协议首先研究一下由多少个部分组成,每个组成的部分占多少个字节,是高位到低位还是低位到高位?

欢迎加入QQ群一起交流
在这里插入图片描述

2015-09-29 10:27:08 u013168302 阅读数 10913

刚好碰到这蓝牙通信方面的项目,上网也看过其他人在蓝牙这方面写的博客,但大多都不全,给一些初触蓝牙的开发者造成一定的麻烦,自己当初也费了不少劲。所以把自己参考网上的一些资料用Android studio写的代码完全放出来,分享一下。菜鸟初写博客,若有不恰之处,请指出,必改正。下面我会把自己的思路和代码一一呈现。(PS:由于后期做了些逻辑操作,代码可能有点臃肿,请勿怪。还好完整的代码是会有的,里面有多出来的一两个类没有用的,但懒得重新打包了)

第一篇博客写完,感觉好菜。资源下载:http://download.csdn.net/detail/u013168302/9146907

1.添加权限

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

2.成员变量跟onCreate()方法,为了方便阅读,把这些摆出来

public class PipelineActivity extends Activity {
    public static BluetoothSocket btSocket;
    private BluetoothAdapter bluetoothAdapter;
    private ArrayAdapter<String> deviceAdapter;
    private List<String> listDevices;
    private ListView listView;
    private LinearLayout btContent;
    private TextView btAllData;
    private Button openBT;
    private Button searchBT;
    final private static int MESSAGE_READ = 100;
    int i = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pipeline);

        listView = (ListView) this.findViewById(R.id.list);
        btContent = (LinearLayout) findViewById(R.id.bt_content_llt);
        btAllData = (TextView) findViewById(R.id.all_data);
        btAllData.setText(btAllData.getText(), TextView.BufferType.EDITABLE);//这行可实现TextView尾部追加http://blog.csdn.net/u013168302/article/details/48785927

        openBT = (Button) findViewById(R.id.open_btn);
        searchBT = (Button) findViewById(R.id.search_btn);

        listDevices = new ArrayList<String>();
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter.isEnabled()) {
            openBT.setText("关闭蓝牙");
        }
        deviceAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.list_item, listDevices);

        openBT.setOnClickListener(new BTListener());
        searchBT.setOnClickListener(new BTListener());

        listView.setAdapter(deviceAdapter);
        listView.setOnItemClickListener(new ItemClickListener());//添加监听
    }


3.注册广播(因为下面搜索时要用到,所以现在前面注册)

private BroadcastReceiver receiver = new BroadcastReceiver() {

        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            
            //下面几行是为了在logcat里面看到搜索到的设备细节,需要的话,可以将注释打开
//            Bundle b = intent.getExtras();
//            Object[] lstName = b.keySet().toArray();
//            // 显示所有收到的消息及其细节
//            for (int i = 0; i < lstName.length; i++) {
//                String keyName = lstName[i].toString();
//                Log.e("-----" + keyName, String.valueOf(b.get(keyName)));
//            }
            
            //搜索设备时,取得设备的MAC地址
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent
                        .getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                String str = device.getName() + "|" + device.getAddress();
                if (listDevices.indexOf(str) == -1)// 防止重复添加
                    listDevices.add(str); // 获取设备名称和mac地址
                if (deviceAdapter != null) {
                    deviceAdapter.notifyDataSetChanged();
                }
            }
        }
    };





4.点击开启蓝牙,搜索蓝牙设备。 将搜索到设备名称和mac地址通过BroadcastReceiver保存到list集合,再在listview中展示

/**
     * 蓝牙开启与搜索按钮点击监听
     */
    class BTListener implements View.OnClickListener {
        @Override
        public void onClick(View view) {
            if (view.getId() == R.id.open_btn) {
                if (!bluetoothAdapter.isEnabled()) {
                    bluetoothAdapter.enable();//开启蓝牙
                    Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
                    enable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); //300秒为蓝牙设备可见时间
                    startActivity(enable);
                    openBT.setText("关闭蓝牙");

                } else {
                    bluetoothAdapter.disable();//关闭蓝牙
                    openBT.setText("开启蓝牙");
                    if (btSocket != null) {
                        try {
                            btSocket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else if (view.getId() == R.id.search_btn) {
                if (!bluetoothAdapter.isEnabled()) {
                    Toast.makeText(getApplicationContext(), "请先开启蓝牙", Toast.LENGTH_SHORT).show();
                } else {
                    btContent.setVisibility(View.GONE);
                    listView.setVisibility(View.VISIBLE);
                    if (listDevices != null) {
                        listDevices.clear();
                        if (deviceAdapter != null) {
                            deviceAdapter.notifyDataSetChanged();
                        }
                    }
                    bluetoothAdapter.startDiscovery();
                    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
                    registerReceiver(receiver, filter);

                }
            }
        }
    }



5.点击listview的item,通过反射连接设备。连接设备之前需要UUID来配对。查看网上的例子和自己的一些实践发现,通过UUID会出现一些问题,当然也有可能只是我自己的代码有问题。所以在此采取反射来获取蓝牙socket对象。

/**
     * 蓝牙选项,listview列表点击监听
     */
    class ItemClickListener implements AdapterView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {

            if (!bluetoothAdapter.isEnabled()) {
                Toast.makeText(getApplicationContext(), "请先开启蓝牙", Toast.LENGTH_SHORT).show();
            } else {
                bluetoothAdapter.cancelDiscovery();//停止搜索
                String str = listDevices.get(position);
                String macAdress = str.split("\\|")[1];

                BluetoothDevice device = bluetoothAdapter.getRemoteDevice(macAdress);
                try {
                    Method clientMethod = device.getClass()
                            .getMethod("createRfcommSocket", new Class[]{int.class});
                    btSocket = (BluetoothSocket) clientMethod.invoke(device, 1);
                    connect(btSocket);//连接设备

                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }


6.连接蓝牙设备,并开辟子线程通信

在这里值得注意while里面的2行代码。这2行代码花了不少功夫才弄到的

(1)if(inputStream.available() >0==false)

(2)Thread.sleep(400);//等待0.4秒,作用:让数据接收完整
这2行用于处理inputStream读取数据不完整或混乱的问题,但感觉还是不够好。若谁有更好的方法,欢迎提出。
另外inputStream在这里是关不掉的,因为这是蓝牙通信,单片机每产生的数据都要传输到手机端。试过多次:一旦断开inputstream,while循环结束,单片机再次产生的数据无法传送。不知道我有没有弄错,欢迎指出。


 /**
     * 连接蓝牙及获取数据
     */
    public void connect(final BluetoothSocket btSocket) {
        try {
            btSocket.connect();//连接
            if (btSocket.isConnected()) {
                Log.e("----connect--- :", "连接成功");
                Toast.makeText(getApplicationContext(), "蓝牙连接成功", Toast.LENGTH_SHORT).show();
                listView.setVisibility(View.GONE);
                btContent.setVisibility(View.VISIBLE);
                new ConnetThread().start();//通信

            } else {
                Toast.makeText(getApplicationContext(), "蓝牙连接失败", Toast.LENGTH_SHORT).show();
                btSocket.close();
                listView.setVisibility(View.VISIBLE);
                btContent.setVisibility(View.GONE);
                Log.e("--------- :", "连接关闭");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 蓝牙通信管理
     */
    private class ConnetThread extends Thread {
        public void run() {
            try {
                InputStream inputStream = btSocket.getInputStream();
                byte[] data = new byte[1024];
                int len = 0;
                String result = "";

                while (len != -1) {
                    if (inputStream.available() > 0 == false) {//inputStream接收的数据是一段段的,如果不先
                        continue;
                    } else {
                        try {
                            Thread.sleep(500);//等待0.5秒,让数据接收完整
                            len = inputStream.read(data);
                            result = URLDecoder.decode(new String(data, "utf-8"));
//                          Log.e("----result:----- :", ">>>" + result);
                            Message msg = new Message();
                            msg.what = MESSAGE_READ;
                            msg.obj = result;
                            handler.sendMessage(msg);

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                inputStream.close();//关不了,也好像不能关
                Log.e("--------- :", "关闭inputStream");
                if (btSocket != null) {
                    btSocket.close();
                }
            } catch (IOException e) {
                Log.e("TAG", e.toString());
            }
        }

    }


7.用Handler处理Message

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_READ:
                    String result = (String) msg.obj;
                    String data = result.split("\\r\\n")[0];
                    Log.e("----data:----- :", ">>>" + data);
                    if (i < 6) {
                        Editable text = (Editable) btAllData.getText();
                        text.append(data);
                        btAllData.setText(text + "\r\n");
                        i++;
                    } else {
                        btAllData.setText(data + "\r\n");
                        i = 0;
                    }
                    break;
            }
        }
    };

    @Override
    protected void onDestroy() {
        unregisterReceiver(receiver);
        super.onDestroy();
    }




8.1布局文件(尚未优化)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#000000"
        android:gravity="center"
        android:paddingLeft="20dp"
        android:text="@string/pipeline"
        android:textColor="#ffffff"
        android:textSize="20sp" />

    <LinearLayout
        android:visibility="gone"
        android:id="@+id/bt_content_llt"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_weight="1"
        android:background="#ffffff"
        android:orientation="vertical">

        <LinearLayout style="@style/bt_llt_style"
            android:layout_weight="3">

            <TextView
                style="@style/btTV_style"
                android:gravity="left"
                android:layout_weight="1"
                android:paddingTop="20dp"
                android:text="@string/all_data" />

            <TextView
                android:id="@+id/all_data"
                style="@style/btTV_style"
                android:gravity="left"
                android:paddingLeft="2dp"
                android:paddingTop="20dp"
                android:textSize="16sp"
                android:layout_weight="2" />

        </LinearLayout>

    </LinearLayout>

    <ListView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_weight="1"
        android:background="#f0f0f0"
        android:divider="#c0c0c0"
        android:dividerHeight="1dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_gravity="center"
        android:layout_marginTop="10dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/open_btn"
            style="@style/bt_button_style"
            android:text="@string/open_bluetooth" />

        <Button
            android:id="@+id/search_btn"
            style="@style/bt_button_style"
            android:text="@string/search_bluetooth" />
    </LinearLayout>

</LinearLayout>


8.2 item布局

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/device_name_tv"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:paddingLeft="15dp"
    android:gravity="center|left"
    android:textColor="#000000"
    android:background="@drawable/list_item_selector"
    android:textSize="18sp">

</TextView>

8.3 资源文件strings.xml

<resources>
    <string name="app_name">bluetooth</string>

    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
  
    <string name="pipeline">管道检测仪</string>
    <string name="open_bluetooth">开启蓝牙</string>
    <string name="close_bluetooth">关闭蓝牙</string>
    <string name="search_bluetooth">搜索蓝牙</string>
    <string name="all_data">全部数据:</string>
    <color name="item_default">#F0F0F0</color>
    <color name="item_press">#A5D8F5</color>
</resources>

8.4 style

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>

    <style name="btTV_style">
        <item name="android:background">#ffffff</item>
        <item name="android:gravity">left|center</item>
        <item name="android:paddingLeft">10dp</item>
        <item name="android:paddingRight">10dp</item>
        <item name="android:textSize">20sp</item>
        <item name="android:layout_width">1dp</item>
        <item name="android:layout_height">match_parent</item>
    </style>

    <style name="bt_llt_style">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">1dp</item>
        <item name="android:layout_weight">1</item>
        <item name="android:orientation">horizontal</item>
    </style>

    <style name="bt_button_style">
        <item name="android:layout_width">1dp</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:layout_marginLeft">20dp</item>
        <item name="android:layout_marginRight">20dp</item>
        <item name="android:layout_weight">1</item>
        <item name="android:background">#5CB85C</item>
    </style>

</resources>


8.5  listview的item选择器

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 选中 -->
    <item android:state_pressed="true" android:drawable="@color/item_press" />
    <!-- 默认 -->
    <item android:drawable="@color/item_default"/>

</selector>