2014-01-05 12:04:42 jason0539 阅读数 37706
  • 串口通信和RS485-第1季第13部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第13个课程,主要讲解了串行通信UART及其扩展RS485。本课程很重要,因为串口通信是我们接触的早也简单的通信方式,是后续继续学习SPI、I2C甚至USB、网络通信等的基础,大家务必认证对待完全掌握。

    6011 人正在学习 去看看 朱有鹏

之前两篇都是在说与手机的连接,连接方法,和主动配对连接,都是手机与手机的操作,做起来还是没问题的,但是最终的目的是与单片机的蓝牙模块的通信。

 

下面是到目前为止尝试的与单片机的通信方法,没有成功,但是从思路上来说没有问题,最大的问题是与单片机配对的时候,单片机的蓝牙模块的PIN配对码是写死的,固定为1234,

而手机这边连接配对都是自动生成的PIN配对码,这种方式在手机与手机配对的时候是极为方便的,但是在这里与单片机连接却成了最大的问题,因为手机自动生成而且每次都不一样,所以没法与单片机蓝牙模块的1234相同也就没法陪对了。下面只是介绍的到目前为止我们的大题思路,具体代码很多,而且涉及到项目也就没有贴。

如果关于上面的问题哪位同学有思路或者做过类似的项目还请指点。

 

首先,如何开启蓝牙设备和设置可见时间:

private void search() {
        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (!adapter.isEnabled()) {
            adapter.enable();
        }
        Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
        enable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3600); //3600为蓝牙设备可见时间
         startActivity(enable);
        Intent searchIntent = new Intent(this, ComminuteActivity.class);
        startActivity(searchIntent);
    }


正式开始与蓝牙模块进行通信

public class ComminuteActivity extends Activity {
    private BluetoothReceiver receiver;
    private BluetoothAdapter bluetoothAdapter;
    private List<String> devices;
    private List<BluetoothDevice> deviceList;
    private Bluetooth client;
    private final String lockName = "YESYOU";
    private String message = "000001";
    private ListView listView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.search_layout);

        listView = (ListView) this.findViewById(R.id.list);
        deviceList = new ArrayList<BluetoothDevice>();
        devices = new ArrayList<String>();
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        bluetoothAdapter.startDiscovery();
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        receiver = new BluetoothReceiver();
        registerReceiver(receiver, filter);

        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                setContentView(R.layout.connect_layout);
                BluetoothDevice device = deviceList.get(position);
                client = new Bluetooth(device, handler);
                try {
                    client.connect(message);
                } catch (Exception e) {
                    Log.e("TAG", e.toString());
                }
            }
        });
    }

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

    private final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case Bluetooth.CONNECT_FAILED:
                    Toast.makeText(ComminuteActivity.this, "连接失败", Toast.LENGTH_LONG).show();
                    try {
                        client.connect(message);
                    } catch (Exception e) {
                        Log.e("TAG", e.toString());
                    }
                    break;
                case Bluetooth.CONNECT_SUCCESS:
                    Toast.makeText(ComminuteActivity.this, "连接成功", Toast.LENGTH_LONG).show();
                    break;
                case Bluetooth.READ_FAILED:
                    Toast.makeText(ComminuteActivity.this, "读取失败", Toast.LENGTH_LONG).show();
                    break;
                case Bluetooth.WRITE_FAILED:
                    Toast.makeText(ComminuteActivity.this, "写入失败", Toast.LENGTH_LONG).show();
                    break;
                case Bluetooth.DATA:
                    Toast.makeText(ComminuteActivity.this, msg.arg1 + "", Toast.LENGTH_LONG).show();
                    break;
            }
        }
    };

    private class BluetoothReceiver extends 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);
                if (isLock(device)) {
                    devices.add(device.getName());
                }
                deviceList.add(device);
            }
            showDevices();
        }
    }

    private boolean isLock(BluetoothDevice device) {
        boolean isLockName = (device.getName()).equals(lockName);
        boolean isSingleDevice = devices.indexOf(device.getName()) == -1;
        return isLockName && isSingleDevice;
    }

    private void showDevices() {
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
                devices);
        listView.setAdapter(adapter);
    }
}

这里需要提一下的是,startDiscovery()这个方法和它的返回值,它是一个异步方法,会对其他蓝牙设备进行搜索,持续时间为12秒。

搜索过程其实是在System Service中进行,我们可以通过cancelDiscovery()方法来停止这个搜索。在系统搜索蓝牙设备的过程中,系统可能会发送以下三个广播:ACTION_DISCOVERY_START(开始搜索),

ACTION_DISCOVERY_FINISHED(搜索结束)

和ACTION_FOUND(找到设备)。

ACTION_FOUND这个才是我们想要的,这个Intent中包含两个extra fields:    EXTRA_DEVICE和EXTRA_CLASS,

包含的分别是BluetoothDevice和BluetoothClass

EXTRA_DEVICE中的BluetoothDevice就是我们搜索到的设备对象,从中获得设备的名称和地址。

EXTRA_CLASS中的BluetoothClass是搜索到的设备的类型,比如搜索到的是手机还是耳机或者其他,之后我会写一篇关于它的介绍

在这个上面我现在在想,是否通过判断搜索到的设备类型来识别单片机蓝牙模块与手机蓝牙的不同,采取不一样的配对方式,从而不自动生成配对码。不知是否可行,一会尝试。

 

 搜索到该设备后,我们就要对该设备进行连接和通信。

public void connect(final String message) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                BluetoothSocket tmp = null;
                Method method;
                try {
                    method = device.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
                    tmp = (BluetoothSocket) method.invoke(device, 1);
                } catch (Exception e) {
                    setState(CONNECT_FAILED);
                    Log.e("TAG", e.toString());
                }
                socket = tmp;
                try {
                    socket.connect();
                    isConnect = true;
                } catch (Exception e) {
                    setState(CONNECT_FAILED);
                    Log.e("TAG", e.toString());
                }
	       if (isConnect) {
                    try {
                        OutputStream outStream = socket.getOutputStream();
                        outStream.write(getHexBytes(message));
                    } catch (IOException e) {
                        setState(WRITE_FAILED);
                        Log.e("TAG", e.toString());
                    }
                    try {
                        InputStream inputStream = socket.getInputStream();
                        int data;
                        while (true) {
                            try {
                                data = inputStream.read();
                                Message msg = handler.obtainMessage();
                                msg.what = DATA;
                                msg.arg1 = data;
                                handler.sendMessage(msg);
                            } catch (IOException e) {
                                setState(READ_FAILED);
                                Log.e("TAG", e.toString());
                                break;
                            }
                        }
                    } catch (IOException e) {
                        setState(WRITE_FAILED);
                        Log.e("TAG", e.toString());
                    }
                }

                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        Log.e("TAG", e.toString());
                    }
               }
       }
}

 这里包括写入和读取,用法和基本的Socket是一样的,但是写入的时候,需要将字符串转化为16进制:

private byte[] getHexBytes(String message) {
        int len = message.length() / 2;
        char[] chars = message.toCharArray();
        String[] hexStr = new String[len];
        byte[] bytes = new byte[len];
        for (int i = 0, j = 0; j < len; i += 2, j++) {
            hexStr[j] = "" + chars[i] + chars[i + 1];
            bytes[j] = (byte) Integer.parseInt(hexStr[j], 16);
        }
        return bytes;
    }


 

连接设备之前需要UUID,所谓的UUID,就是用来进行配对的,全称是Universally Unique Identifier,是一个128位的字符串ID,用于进行唯一标识。网上的例子,包括谷歌的例子提供的uuid,通用的"00001101-0000-1000-8000-00805F9B34FB"也试过了,在配对的时候都是自动生成了配对码,也无法正常与单片机的蓝牙模块连接,所以,我就利用反射的原理,让设备自己提供UUID尝试。到这里其实我有点怀疑自己对于UUID的理解是否正确了。

            在谷歌提供的例子中,我们可以看到谷歌的程序员的程序水平很高,一些好的编码习惯我们可以学习一下,像是在try..catch中才定义的变量,我们应该在try...catch之前声明一个临时变量,然后再在try...catch后赋值给我们真正要使用的变量。这种做法的好处就是:如果我们直接就是使用真正的变量,当出现异常的时候,该变量的使用就会出现问题,而且很难进行排查,如果是临时变量,我么可以通过检查变量的值来确定是否是赋值时出错。

   

作者:jason0539

微博:http://weibo.com/2553717707

博客:http://blog.csdn.net/jason0539(转载请说明出处)

2015-06-02 14:26:10 renqian1991 阅读数 3469
  • 串口通信和RS485-第1季第13部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第13个课程,主要讲解了串行通信UART及其扩展RS485。本课程很重要,因为串口通信是我们接触的早也简单的通信方式,是后续继续学习SPI、I2C甚至USB、网络通信等的基础,大家务必认证对待完全掌握。

    6011 人正在学习 去看看 朱有鹏

                转载请注明出处。

                这次是一个课程设计,利用单片机开发一个物联网系统。我们利用了手机蓝牙与单片机板子上的蓝牙通信,通过 控制信号来控制单片机上led灯的亮灭和定时。

 网上有很多的搜索蓝牙的例程,大家可以自己去看,由于本次我们是与特定的设备连接,因此直接使用Mac地址连接,不在使用搜索功能,当然如果大家采用搜索到设备后在连接也可以。我们将蓝牙连接和数据收发放在一个service中,由于蓝牙socket读是阻塞的,因此我们新开一个线程专门用于接收板子的信号。

             在service的onCreate()方法中,我们连接指定的蓝牙,并获得其io流,在新开一个线程用于socket读。

            记得在mk文件中给该service添加intent-filter。

            

	/**
	 * 服务初始化
	 */
	@Override
	public void onCreate() {
		// TODO 自动生成的方法存根
		initBluetooth();
		super.onCreate();
	}

        /**
	 * 初始化蓝牙适配器
	 */
	public void initBluetooth(){
		bluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
		if(bluetoothAdapter==null){//等于null时通知主界面,设备不支持蓝牙
			
			Intent intent = new Intent();
			intent.setAction(Constants.ERROR);
			sendBroadcast(intent);
		}else {
			if(!bluetoothAdapter.isEnabled())//蓝牙未开启时,开启蓝牙
		{
			bluetoothAdapter.enable();
		}
		connectDevice();
	}
	}
	
	/**
	 * 链接设备
	 */
	private void connectDevice(){
		device = bluetoothAdapter.getRemoteDevice(Constants.ADDRESS);//输入要连接的蓝牙的Mac地址,在说明书上可以查到
		if(device==null){//为空,连接失败
			
			Intent intent = new Intent();
			intent.setAction(Constants.ERROR);
			sendBroadcast(intent);
			
		}else {
			
			
			try {
			
				socket = device.createRfcommSocketToServiceRecord(Constants.MY_UUID);//uuid,一般为00001101-0000-1000-8000-00805F9B34FB
				socket.connect();//获得socket接口
				inputStream = socket.getInputStream();//获得输入流,另起线程监听输入
				receiveThread = new ReceiveThread(inputStream);
                new Thread(receiveThread).start();
	            outputStream = socket.getOutputStream();
				Intent intent = new Intent();//发送广播,已连接
				intent.setAction(Constants.CONNECTED);
				sendBroadcast(intent);
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			
			}
		}
		
	}
	

	/**
	 * 接收反馈信号的线程
         * 判断反馈信号,更新主界面ui
	 * @author qian ren
	 *
	 */
	class ReceiveThread implements Runnable{
		
		InputStream in;
		int msg;
       public ReceiveThread(InputStream in){
    	   this.in=in;
       }
		@Override
		public void run() {
			// TODO 自动生成的方法存根
			while(true){
				try {
					msg=in.read();
					System.out.println("msg: "+msg);
					switch (msg) {
					case 1://发送广播,台灯打开
						Intent intentOn = new Intent();
						intentOn.setAction(Constants.ON);
						sendBroadcast(intentOn);
						break;
                    
					case  2://发送广播,台灯关闭
						Intent intentOff = new Intent();
						intentOff.setAction(Constants.OFF);
						sendBroadcast(intentOff);
						break;
						
					case 3://发送广播,定时完成
						Intent intentTimer = new Intent();
						intentTimer.setAction(Constants.TIMER);
						sendBroadcast(intentTimer);
						break;
						
					default:
						break;
					}
				} catch (IOException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			}
		}
		
	}

 初始化完成后,在onStartCommond()方法中发送控制信号给板子。


	public int onStartCommand(Intent intent, int flags, int startId) {
		// TODO 自动生成的方法存根
		
		String control;
	   control = intent.getStringExtra("control");
	   if(control.equals("on")){//打开台灯
		   byte [] buffer = new byte[]{1,25,1,2};
		   try {
			outputStream.write(buffer);
			outputStream.flush();
			System.out.println("on");
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	   }else if (control.equals("off")) {//关闭台灯
		   byte [] buffer = new byte[]{1,26,1,2};
		   try{
		   outputStream.write(buffer);
			outputStream.flush();
			System.out.println("off");
		   }catch(IOException e){
			   e.printStackTrace();
		   }
			
		}else if (control.equals("timer")) {//定时
			byte hour = (byte) intent.getIntExtra("hour",0);
			byte minute = (byte) intent.getIntExtra("minute",1);
			byte [] buffer =new byte[]{1,hour,minute,2};
			
			try{
		
			outputStream.write(buffer);
			outputStream.flush();
			System.out.println("timer");
		}catch(IOException e){
			e.printStackTrace();
		}
		}
	
	
		return super.onStartCommand(intent, flags, startId);
	}
  现在,service已经写完了,我们再来写主界面,主界面有一个Switcher来转换灯的开关状态,并有一个按钮可以跳转到另一个activity实现定时。还有一个imageview通过不断更换背景来展示灯的亮灭。如图所示




下面是关灯开灯的逻辑实现,通过启动service来实现信号的发送。

首先是连接设备:

/**
	 * 链接设备
	 */
	private void connectDevice() {
		// TODO 自动生成的方法存根
	Intent intent = new Intent();
	intent.setAction(Constants.LAMP_SERVICE);
	intent.putExtra("control", "connect");
	startService(intent);
	}


然后在switcher的状态改变回调方法中,实现开关控制

class SwitchChangedListener implements OnCheckedChangeListener{

		@Override
		public void onCheckedChanged(CompoundButton buttonView,
				boolean isChecked) {
			// TODO 自动生成的方法存根
			if(isChecked){
				
				Intent intent = new Intent();
				intent.setAction(Constants.LAMP_SERVICE);
				intent.putExtra("control", "on");
				startService(intent);
			}else {
				
				Intent intent = new Intent();
				intent.setAction(Constants.LAMP_SERVICE);
				intent.putExtra("control", "off");
				startService(intent);
			}
		}
		
	}


发送消息后必然会接受到反馈,我们用broadcastreceiver来接受service收到的反馈信号。记得在oncreate方法中绑定receiver。

/**
	 * 更新界面的broadcastreceiver
	 * @author qian ren
	 *
	 */
	class UpdateUiReceiver extends BroadcastReceiver{

		@Override
		public void onReceive(Context context, Intent intent) {
			// TODO 自动生成的方法存根
			String action = intent.getAction();
			if(action.equals(Constants.CONNECTED)){
				progressBar.setVisibility(View.INVISIBLE);
				ToastDisplay("台灯已经连接");
				System.out.println("device has been connected");
			}else if (action.equals(Constants.ERROR)) {
				progressBar.setVisibility(View.INVISIBLE);
				ToastDisplay("未连接到台灯,请检查设备");
				System.out.println("device can't connect");
			}else if (action.equals(Constants.ON)) {
				background.setImageResource(R.drawable.lamp_on);
				ToastDisplay("台灯已经打开");
				System.out.println("lamp on");
			}else if (action.equals(Constants.OFF)) {
				background.setImageResource(R.drawable.lamp_off);
				ToastDisplay("台灯已经关闭");
				System.out.println("lamp off");
			}else if (action.equals(Constants.TIMER)) {
				background.setImageResource(R.drawable.lamp_on);
				ToastDisplay("台灯将在"+hour+"小时"+minute+"分后自动关闭  ");
				System.out.println("timing has been finished");
			}
		}
		
	}
定时功能在另一个activity中实现,通过点击闹钟图标跳到另一个activity。


定时功能比较简单,使用timerpicker。




首先监听timechange的回调方法得到定时的时间。

public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
		// TODO 自动生成的方法存根
		this.hour = hourOfDay;
		this.minute = minute;
	}
然后在点击确定键时将信号发送给service。

public void onClick(View v) {
		// TODO 自动生成的方法存根
		if(hour==0){
			hour=countDown.getCurrentHour();
		}else if (minute==0) {
			minute=countDown.getCurrentMinute();
		}
		Intent intent = new Intent();
		intent.setAction(Constants.LAMP_SERVICE);
		intent.putExtra("control", "timer");
		intent.putExtra("hour", hour);
		intent.putExtra("minute", minute);
		startService(intent);
		
		Intent intentBack = new Intent();
		intentBack.putExtra("hour", hour);
		intentBack.putExtra("minute", minute);
		TimerActivity.this.setResult(Constants.RESPONSE,intentBack);
		this.finish();
	}
上面的setresult是为了在主界面得到设置的时间,进而刷新UI,因此主界面可以使用startactivityforresult来启动该定时界面。

	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		// TODO 自动生成的方法存根
		if(requestCode==Constants.REQUEST && resultCode ==Constants.RESPONSE){
			hour = data.getIntExtra("hour", 0);
			minute = data.getIntExtra("minute", 1);
		}
	}



2016-07-04 12:01:58 qq_29918347 阅读数 4708
  • 串口通信和RS485-第1季第13部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第13个课程,主要讲解了串行通信UART及其扩展RS485。本课程很重要,因为串口通信是我们接触的早也简单的通信方式,是后续继续学习SPI、I2C甚至USB、网络通信等的基础,大家务必认证对待完全掌握。

    6011 人正在学习 去看看 朱有鹏

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

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

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

2014-11-22 15:54:59 a353183177 阅读数 1788
  • 串口通信和RS485-第1季第13部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第13个课程,主要讲解了串行通信UART及其扩展RS485。本课程很重要,因为串口通信是我们接触的早也简单的通信方式,是后续继续学习SPI、I2C甚至USB、网络通信等的基础,大家务必认证对待完全掌握。

    6011 人正在学习 去看看 朱有鹏

前言:若你你一个电子爱好者,若你酷爱编程,那我分享一个Android BLE application的开发方法,让你可以使用手机和单片机结合,使用手机控制单片机,做一些更有趣的东西,下面开始讲解如何开发一个Android BLE application,参考谷歌官方教程https://developer.android.com/guide/topics/connectivity/bluetooth-le.html

本文为封装之后的教程,可以更方便的更快速的搭建你的Android BLE application.当然我会把我的源码上传分享给大家。也可以参考官方提供的例程sdk/samples/android-18/legacy/BluetoothLeGatt

我的源码下载:http://download.csdn.net/detail/a353183177/8187165

1. 新建安卓工程

    打开EclipseFile->New->Android Application Project,在Application Name编辑框内填入Application名字,如:BleExampleMinimum Required SDK选择API18Android 4.3Target SDK也选择API18:Android 4.3,因为buletooth 4.0必须要Android 4.3及以上版本才能使用,其他默认不变,一直点Next按钮,直到出现Finish按钮,然后点Finish按钮。

2. 添加权限和服务

    在manifest file文件中添加:

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

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

<service android:name="com.elecfreaks.ble.BluetoothLeService" android:enabled="true"/>

3. 新建listView item布局文件

用于显示ListView中每一项的内容,我们这里使用自定义,这样可以让ListView中一项显示更多内容,item_list.xml内容如下:

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

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical"    

    >

    <TextView android:id="@+id/textViewDevName"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:textSize="24dp"/>

    <TextView android:id="@+id/textViewDevAddress"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:textSize="12dp"/>

</LinearLayout>

Copy我提供的源码BleExamplcom.elecfreaks.ble包到你的工程src目录里面,然后打开有错误提示的文件,按下shift+ctrl+O键。

4. 修改activity_main.xml增加scanButton按钮和bleDeviceListView

增加内容如下:

<Button

        android:id="@+id/scanButton"

        android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        android:onClick="scanOnClick"

        android:text="scan" />

 

     <ListView

        android:id="@+id/bleDeviceListView"

        android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        android:layout_alignLeft="@+id/scanButton"

        android:layout_below="@+id/scanButton"

        android:layout_above="@+id/sendButton"

         >

    </ListView>

5. 在MainActivity.java中添加scanButton响应事件方法(onClick="scanOnClick")

public void scanOnClick(final View v){

 

}

6. 为MainActivity类添加成员

private Button scanButton;

private ListView bleDeviceListView;

private BLEDeviceListAdapter listViewAdapter;

private BluetoothHandler bluetoothHandler;

private boolean isConnected;

7. 在MainActivity.onCreate中设置成员的值

scanButton = (Button) findViewById(R.id.scanButton);

bleDeviceListView = (ListView)  findViewById(R.id.bleDeviceListView);

listViewAdapter = new BLEDeviceListAdapter(this);

bluetoothHandler = new BluetoothHandler(this);

bluetoothHandler.setOnConnectedListener(new  OnConnectedListener() {

@Override

public void onConnected(boolean isConnected) {

// TODO Auto-generated method stub

setConnectStatus(isConnected);

}

});

bluetoothHandler.setOnRecievedDataListener(new  OnRecievedDataListener() {

@Override

public void onRecievedData(byte[] bytes) {

// TODO Auto-generated method stub

System.out.printf("REC:");

for(byte b:bytes)

System.out.printf("%02X "b);

System.out.printf("\n");

}

});

8. 添加setConnectStatus方法

public void setConnectStatus(boolean isConnected){

this.isConnected = isConnected;

if(isConnected){

showMessage("Connection successful");

scanButton.setText("break");

}else{

bluetoothHandler.onPause();

     bluetoothHandler.onDestroy();

     scanButton.setText("scan");

}

}

private void showMessage(String str){

Toast.makeText(MainActivity.thisstr,  Toast.LENGTH_SHORT).show();

}

9. 在scanOnClick中添加内容

if(!isConnected){ bleDeviceListView.setAdapter(bluetoothHandler.getDeviceListAdapter());

bleDeviceListView.setOnItemClickListener(new OnItemClickListener() {

@Override

public void onItemClick(AdapterView<?> parent, View view,

int positionlong id) {

String buttonText = (String) ((Button)v).getText();

if(buttonText.equals("scanning")){

showMessage("scanning...");

return ;

}

BluetoothDevice device = bluetoothHandler.getDeviceListAdapter().getItem(position).device;

// connect

bluetoothHandler.connect(device.getAddress());

}

});

bluetoothHandler.setOnScanListener(new OnScanListener() {

@Override

public void onScanFinished() {

// TODO Auto-generated method stub

((Button)v).setText("scan");

((Button)v).setEnabled(true);

}

@Override

public void onScan(BluetoothDevice deviceint rssibyte[] scanRecord) {}

});

((Button)v).setText("scanning");

((Button)v).setEnabled(false);

bluetoothHandler.scanLeDevice(true);

}else{

setConnectStatus(false);

}

10. 发送数据

byte[] data = new byte[1];

data[0] = 0x02;

bluetoothHandler.sendData(data);

11. 接收数据

接收到数据时会调用bluetoothHandler.setOnRecievedDataListener()方法中设置的OnRecievedDataListener.onRecievedData(byte[] bytes)方法,其中bytes为接收到的数据

12. 通过协议发送数据给单片机

src目录新建Transmitter.java,添加带两个参数的构造函数,如下:

public Transmitter(Context context,  BluetoothHandler bluetoothHandler){

this.context = context;

this.mBluetoothHandler = bluetoothHandler;

}

添加sendData()方法:

private void sendData(byte[] bytes){

mBluetoothHandler.sendData(bytes);

}

添加sendPackage()方法:

public void sendPakege(byte[] databyte cmd){

byte[] bytes = new byte[data.length+6];

bytes[0] = (byte) 0xFA; // 数据头1

bytes[1] = (byte) 0xFB; // 数据头2

bytes[2] = (byte) 0xFC; // 数据头3

bytes[3] = (bytedata.length; // 数据长度

bytes[4] = cmd; // 指令

System.arraycopy(data, 0, bytes, 5, data.length);

for(int i=0; i<data.lengthi++){

bytes[data.length+5] ^= data[i]; // 校验和

}

sendData(bytes);

}

13. 通过协议接收单片机数据

src目录新建MyArray.java,用于连接两个数组,代码如下:

public class MyArray {

static public byte[] arrayCat(byte[] buf1,byte[] buf2){

    byte[] bufret=null;

    int len1 = 0;

    int len2 = 0;

    if(buf1 != null)

     len1 = buf1.length;

    if(buf2 != null)

     len2 = buf2.length;

    if(len1+len2 > 0)

     bufret = new byte[len1+len2];

    if(len1 > 0)

     System.arraycopy(buf1, 0, bufret, 0, len1);

    if(len2 > 0)

     System.arraycopy(buf2, 0, bufretlen1len2);

    return bufret;

    }

}

Copy我提供例程中的protocol.javasrc目录

添加成员private Protocol protocol;

在onCreate()中删除bluetoothHandler.setOnRecievedDataListener();

添加:

protocol = new Protocol(thisnew Transmitter(thisbluetoothHandler));

protocol.setOnReceivedDataListener(recListener);

在MainActivity中添加成员:

private static final boolean INPUT = false;

private static final boolean OUTPUT = true;

private static final boolean LOW = false;

private static final boolean HIGH = true;

private boolean digitalVal[];

private int analogVal[];

并且在onCreate中初始化:

digitalVal = new boolean[14];

analogVal = new int[14];

private OnReceivedRightDataListener recListener = new OnReceivedRightDataListener() {

@Override

public int onReceivedData(byte[] bytes) {

// TODO Auto-generated method stub

/*System.out.print("REC:");

    for(byte b:bytes)

    System.out.printf("%02X ", b);

    System.out.println("");*/

    

     byte pin;

     int pinValue;

    

     switch(bytes[4]){

     case Transmitter.READ_DATA:

if(bytes[Protocol.MODE_INDEX+5] == Protocol.DIGITAL){

     pin = bytes[Protocol.PIN_INDEX+5];

     pinValue = (short)  (bytes[Protocol.PINVALL_INDEX+5]);

     if(pinValue > 0){

     digitalVal[pin] = HIGH;

     }else{

     digitalVal[pin] = LOW;

     }

     }else if(bytes[Protocol.MODE_INDEX+5] ==  Protocol.ANALOG){

     pin = bytes[Protocol.PIN_INDEX+5];

     int high = bytes[Protocol.PINVALH_INDEX+5] & 0xff;

     int low = bytes[Protocol.PINVALL_INDEX+5] & 0xff;

     pinValue = high<<8 + low;

     System.out.println("low="+high+" high="+low+" val="+pinValue);

     analogVal[pin] = pinValue;

}

     break;

     case Transmitter.WRITE_DATA:

     break;

     default:break;

     }

    

return 0;

}

};

14. 使用协议发送数据

protocol.writeAnalogData(9, 20);

protocol.writeDigitalData(3, 1);

15. 使用协议接收数据

protocol.readAnalogDataCommand(9);

protocol.readDigitalDataCommand(3);

注意: 返回的数据由recListener 接收

16. 单片机端协议(arduino)

参考我提供的示例源码AndroidIOControl

2017-02-02 18:53:41 qq122627018 阅读数 5690
  • 串口通信和RS485-第1季第13部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第13个课程,主要讲解了串行通信UART及其扩展RS485。本课程很重要,因为串口通信是我们接触的早也简单的通信方式,是后续继续学习SPI、I2C甚至USB、网络通信等的基础,大家务必认证对待完全掌握。

    6011 人正在学习 去看看 朱有鹏

源码传送门

前言

本篇文章将围绕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 雷达数据高位 雷达数据低位 发送雷达数据

协议的解析

未完待续

没有更多推荐了,返回首页