• ·简介:由于业务关系,需要开发一个向单片机发送数据的应用,不过由于需求很简单,只  需要发送数据即可,所以该dome的功能只有发送数据功能,并没有对输入的数据  做进一步的处理 这里有蓝牙开发的基本...
    ·简介:由于业务关系,需要开发一个向单片机发送数据的应用,不过由于需求很简单,只  需要发送数据即可,所以该dome的功能只有发送数据功能,并没有对输入的数据   做进一步的处理

    这里有蓝牙开发的基本资料,建议蓝牙基础欠缺的可以先悉知一下:http://pan.baidu.com/s/1ntrHntB

    这里是我的dome工程目录





    布局文件:
    <RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
    
    
    
        <TextView
            android:id="@+id/textView1"
            android:text="@string/hello_world"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <TextView
            android:id="@+id/textView2"
            android:layout_below="@id/textView1"
            android:text="@string/hello_world"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
        <TextView
            android:id="@+id/textView3"
            android:layout_below="@id/textView2"
            android:text="@string/hello_world"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    
    
        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/seekBar1"
            android:layout_below="@+id/textView2"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_marginTop="108dp" />
        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/seekBar2"
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"
            android:layout_alignParentEnd="true" />
    
    </RelativeLayout>

    总的来说,通讯过程是:
    1、得到本地蓝牙设备,
     void initOpenBluetooth() {
            //取得本地蓝牙设备
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    
            if (mBluetoothAdapter == null) {
                Toast.makeText(this, "蓝牙不可用", Toast.LENGTH_SHORT).show();
                finish();
                return;
            }
    
            //如果蓝牙没打开,提醒用户打开蓝牙
            if (!mBluetoothAdapter.isEnabled()) {
                Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(intent, REQUEST_ENABLE_BT);
            }else {
                if(mBluetoothAdapter.isEnabled()){
                    initGetDevice();
                }
            }
    
        }

    2、得到远程设备,
    private void initGetDevice(){
            //获取远程BlueToothDevice
            device = mBluetoothAdapter.getRemoteDevice(address);
            textView3.setText(device.getAddress() + ":" + device.getName());
    
            if (mChatService == null) {
                mChatService = new BluetoothChatService(this);
                if (D) Log.v(TAG, "当前蓝牙状态:" + mChatService.getState());
    
                mChatService.connect(device);//启动了蓝牙连接线程,
                if (D) Log.v(TAG, "当前蓝牙状态:" + mChatService.getState());
            }
        }

    3、找到已配对的设备,连接,连接需要唯一的通讯信道UUID
     /*********************    连接线程   ****************/
        private class ConnectThread extends Thread {
            private final BluetoothSocket mmSocket;
            private final BluetoothDevice mmDevice;
    
            public ConnectThread(BluetoothDevice device) {
                mmDevice = device;
                BluetoothSocket tmp = null;
    
                // Get a BluetoothSocket for a connection with the
                // given BluetoothDevice
                try {
                    tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
                } catch (IOException e) {
                    Log.e(TAG, "create() failed", e);
                }
                mmSocket = tmp;
            }
    
            public void run() {
                Log.i(TAG, "BEGIN mConnectThread");
                setName("ConnectThread");
    
                // Always cancel discovery because it will slow down a connection
                mAdapter.cancelDiscovery();
    
                // Make a connection to the BluetoothSocket
                try {
                    // This is a blocking call and will only return on a
                    // successful connection or an exception
                    mmSocket.connect();
                } catch (IOException e) {
                    connectionFailed();
                    // Close the socket
                    try {
                        mmSocket.close();
                    } catch (IOException e2) {
                        Log.e(TAG, "unable to close() socket during connection failure", e2);
                    }
                    /*// Start the service over to restart listening mode
                    BluetoothChatService.this.start();*/
                    return;
                }
    
                // Reset the ConnectThread because we're done
                synchronized (BluetoothChatService.this) {
                    mConnectThread = null;
                }
    
                // Start the connected thread
                connected(mmSocket, mmDevice);
            }
    
            public void cancel() {
                try {
                    mmSocket.close();
                } catch (IOException e) {
                    Log.e(TAG, "close() of connect socket failed", e);
                }
            }
        }
    

    3、连接后的连接管理,通讯
     /*********************   连接管理,通讯线程   **********************/
        private class ConnectedThread extends Thread {
            private final BluetoothSocket mmSocket;
            private final InputStream mmInStream;
            private final OutputStream mmOutStream;
    
            public ConnectedThread(BluetoothSocket socket) {
                Log.d(TAG, "create ConnectedThread");
                mmSocket = socket;
                InputStream tmpIn = null;
                OutputStream tmpOut = null;
    
                // Get the BluetoothSocket input and output streams
                try {
                    tmpIn = socket.getInputStream();
                    tmpOut = socket.getOutputStream();
                } catch (IOException e) {
                    Log.e(TAG, "temp sockets not created", e);
                }
    
                mmInStream = tmpIn;
                mmOutStream = tmpOut;
            }
    
            public void run() {
                Log.i(TAG, "BEGIN mConnectedThread");
                byte[] buffer = new byte[1024];
                int bytes;
    
                // Keep listening to the InputStream while connected
                while (true) {
                    try {
                        // Read from the InputStream
                        bytes = mmInStream.read(buffer);
    
                    } catch (IOException e) {
                        Log.e(TAG, "disconnected", e);
                        connectionLost();
                        break;
                    }
                }
            }
    
            /**
             * Write to the connected OutStream.
             * @param buffer  The bytes to write
             */
            public void write(byte[] buffer) {
                try {
                    mmOutStream.write(buffer);
    
                } catch (IOException e) {
                    Log.e(TAG, "Exception during write", e);
                }
            }
    
            public void cancel() {
                try {
                    mmSocket.close();
                } catch (IOException e) {
                    Log.e(TAG, "close() of connect socket failed", e);
                }
            }
        }

    4、最后一步,修复一些小Bug,然后可以根据自己的业务需求来更改代码。
         本来还有一个等待连接线程的,但该次业务无此需求,故不列出来了。

    5:这里是整个dome的代码:
    MainActivity:
    public class MainActivity extends ActionBarActivity implements SeekBar.OnSeekBarChangeListener {
    
        // Debugging
        private static final String TAG = "MainActivity";
        private static final boolean D = true;
    
        //定义打开蓝牙的请求码
        private static final int REQUEST_ENABLE_BT = 2;
    
        // 本地蓝牙设备
        private BluetoothAdapter mBluetoothAdapter = null;
        // Member object for the chat services
        private BluetoothChatService mChatService = null;
    
        BluetoothDevice device;
        private static String address = "30:14:11:12:38:01";
    
        SeekBar seekBar1,seekBar2;
        TextView textView1,textView2,textView3;
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            if(D) Log.v(TAG, "+++ ON CREATE +++");
            System.out.println("+++ ON CREATE +++");
    
            seekBar1 = (SeekBar) findViewById(R.id.seekBar1);
            seekBar2 = (SeekBar) findViewById(R.id.seekBar2);
            textView1 = (TextView) findViewById(R.id.textView1);
            textView2 = (TextView) findViewById(R.id.textView2);
            textView3 = (TextView) findViewById(R.id.textView3);
    
            //两种方式监听
            seekBar1.setOnSeekBarChangeListener(this);
            seekBar2.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                @Override
                public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                    textView2.setText("当前值:" + progress);
    
                    //  发送数据
                    String msg = String.valueOf(progress);
    
                    if (mChatService.getState() == BluetoothChatService.STATE_CONNECTED) {
    
                        mChatService.write(getHexBytes(msg));
    
                    }else {
                        System.out.println("没有连接好");
                    }
                }
    
                @Override
                public void onStartTrackingTouch(SeekBar seekBar) {
    
                }
    
                @Override
                public void onStopTrackingTouch(SeekBar seekBar) {
    
                }
            });
    
            initOpenBluetooth();
    
        }
    
        /***************  再按一次退出程序  ****************/
        private long _doubleClickedTime = 0;
        @Override
        public boolean onKeyDown(int keyCode, KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                long clickedTime = System.currentTimeMillis();
                if (clickedTime - _doubleClickedTime <= 2000) {
                    // 两次点击退出
                    finish();
                } else {
                    Toast.makeText(getApplicationContext(), "再按一次退出程序", Toast.LENGTH_SHORT).show();
                    _doubleClickedTime = clickedTime;
                }
                return true;
            }
            return super.onKeyDown(keyCode, event);
        }
    
    
        /***********  退出后,把蓝牙的连接注销   ***********/
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mChatService.stop();
            finish();
        }
    
    
        /*************  当“打开蓝牙的dialog结束后,要回调的函数”    ************/
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
    
            if(requestCode == REQUEST_ENABLE_BT){
    
                initGetDevice();
    
            }
        }
    
    
        /*******************  initOpenBluetooth    *********************/
        void initOpenBluetooth() {
            //取得本地蓝牙设备
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    
            if (mBluetoothAdapter == null) {
                Toast.makeText(this, "蓝牙不可用", Toast.LENGTH_SHORT).show();
                finish();
                return;
            }
    
            //如果蓝牙没打开,提醒用户打开蓝牙
            if (!mBluetoothAdapter.isEnabled()) {
                Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
                startActivityForResult(intent, REQUEST_ENABLE_BT);
            }else {
                if(mBluetoothAdapter.isEnabled()){
                    initGetDevice();
                }
            }
    
        }
    
    
        /********************  intDetDevice  **********************/
        private void initGetDevice(){
            //获取远程BlueToothDevice
            device = mBluetoothAdapter.getRemoteDevice(address);
            textView3.setText(device.getAddress() + ":" + device.getName());
    
            if (mChatService == null) {
                mChatService = new BluetoothChatService(this);
                if (D) Log.v(TAG, "当前蓝牙状态:" + mChatService.getState());
    
                mChatService.connect(device);//启动了蓝牙连接线程,
                if (D) Log.v(TAG, "当前蓝牙状态:" + mChatService.getState());
            }
        }
    
    
        /******************** 字符串转换为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;
        }
    
    
        /********************************  拖动发送数据   ********************************/
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    
            textView1.setText("当前值:" + progress);
            //  发送数据
            String msg = String.valueOf(progress);
    
            if (mChatService.getState() == BluetoothChatService.STATE_CONNECTED) {
    
                mChatService.write(getHexBytes(msg));
    
            }else {
                System.out.println("没有连接好");
            }
        }
        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
    
        }
        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
    
        }
    
    /******************************   标题栏  ************************************/
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.menu_main, menu);
            return true;
        }
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle action bar item clicks here. The action bar will
            // automatically handle clicks on the Home/Up button, so long
            // as you specify a parent activity in AndroidManifest.xml.
            int id = item.getItemId();
    
            //noinspection SimplifiableIfStatement
            if (id == R.id.action_settings) {
                return true;
            }
    
            return super.onOptionsItemSelected(item);
        }
    }

    工具类:BluetoothChatService:
    public class BluetoothChatService {
        // Debugging
        private static final String TAG = "BluetoothChatService";
        private static final boolean D = true;
    
        // Name for the SDP record when creating server socket
        private static final String NAME = "BluetoothChat";
    
        // Unique UUID for this application
    
        private static final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");
    
        // Member fields
        private final BluetoothAdapter mAdapter;
    
        private AcceptThread mAcceptThread;
        private ConnectThread mConnectThread;
        private ConnectedThread mConnectedThread;
        private int mState;
    
        // Constants that indicate the current connection state
        public static final int STATE_NONE = 0;       // we're doing nothing
        public static final int STATE_LISTEN = 1;     // now listening for incoming connections
        public static final int STATE_CONNECTING = 2; // now initiating an outgoing connection
        public static final int STATE_CONNECTED = 3;  // now connected to a remote device
    
    
        public BluetoothChatService(Context context) {
            mAdapter = BluetoothAdapter.getDefaultAdapter();
            mState = STATE_NONE;
        }
    
    
        private synchronized void setState(int state) {
            if (D) Log.d(TAG, "setState() " + mState + " -> " + state);
            mState = state;
    
    
        }
    
        public synchronized int getState() {
            return mState;
        }
    
        /***************  本来还有个监听线程入口 和 监听线程。此应用没用到  ***********/
    
        /**********************   启动连接线程  ************************/
        public synchronized void connect(BluetoothDevice device) {
            if (D) Log.d(TAG, "connect to: " + device);
    
            // Cancel any thread attempting to make a connection
          /*  if (mState == STATE_CONNECTING) {
                if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
            }
    */
            if (mState == STATE_NONE) {
                if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
            }
    
            // Cancel any thread currently running a connection
            if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
    
            // Start the thread to connect with the given device
            mConnectThread = new ConnectThread(device);
            mConnectThread.start();
            setState(STATE_CONNECTING);
        }
    
    
        /*******************  启动连接管理,通讯线程的入口 **************************/
        public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
            if (D) Log.d(TAG, "connected");
    
            // Cancel the thread that completed the connection
            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
    
            // Cancel any thread currently running a connection
            if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
    
            // Cancel the accept thread because we only want to connect to one device
            if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
    
            // Start the thread to manage the connection and perform transmissions
            mConnectedThread = new ConnectedThread(socket);
            mConnectedThread.start();
            setState(STATE_CONNECTED);
        }
    
        /******************   注销掉所有  *****************/
        public synchronized void stop() {
            if (D) Log.d(TAG, "stop");
            if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
            if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;}
            if (mAcceptThread != null) {mAcceptThread.cancel(); mAcceptThread = null;}
            setState(STATE_NONE);
        }
    
        /***************  写出给通许线程,由线程来发送数据  ***********/
        public void write(byte[] out) {
            // Create temporary object
            ConnectedThread r;
            // Synchronize a copy of the ConnectedThread
            synchronized (this) {
                if (mState != STATE_CONNECTED) return;
                r = mConnectedThread;
            }
            // Perform the write unsynchronized
            r.write(out);
        }
    
    
        private void connectionFailed() {
            setState(STATE_LISTEN);
        }
    
        private void connectionLost() {
            setState(STATE_LISTEN);
        }
    
    
       /*********************  等待监听线程,此应用没用到  ****************/
        private class AcceptThread extends Thread {
            // The local server socket
            private final BluetoothServerSocket mmServerSocket;
    
            public AcceptThread() {
                BluetoothServerSocket tmp = null;
    
                // Create a new listening server socket
                try {
                    tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
                } catch (IOException e) {
                    Log.e(TAG, "listen() failed", e);
                }
                mmServerSocket = tmp;
            }
    
            public void run() {
                if (D) Log.d(TAG, "BEGIN mAcceptThread" + this);
                setName("AcceptThread");
                BluetoothSocket socket = null;
    
                // Listen to the server socket if we're not connected
                while (mState != STATE_CONNECTED) {
                    try {
                        // This is a blocking call and will only return on a
                        // successful connection or an exception
                        socket = mmServerSocket.accept();
                    } catch (IOException e) {
                        Log.e(TAG, "accept() failed", e);
                        break;
                    }
    
                    // If a connection was accepted
                    if (socket != null) {
                        synchronized (BluetoothChatService.this) {
                            switch (mState) {
                                case STATE_LISTEN:
                                case STATE_CONNECTING:
                                    // Situation normal. Start the connected thread.
                                    connected(socket, socket.getRemoteDevice());
                                    break;
                                case STATE_NONE:
                                case STATE_CONNECTED:
                                    // Either not ready or already connected. Terminate new socket.
                                    try {
                                        socket.close();
                                    } catch (IOException e) {
                                        Log.e(TAG, "Could not close unwanted socket", e);
                                    }
                                    break;
                            }
                        }
                    }
                }
                if (D) Log.i(TAG, "END mAcceptThread");
            }
    
            public void cancel() {
                if (D) Log.d(TAG, "cancel " + this);
                try {
                    mmServerSocket.close();
                } catch (IOException e) {
                    Log.e(TAG, "close() of server failed", e);
                }
            }
        }
    
    
       /*********************    连接线程   ****************/
        private class ConnectThread extends Thread {
            private final BluetoothSocket mmSocket;
            private final BluetoothDevice mmDevice;
    
            public ConnectThread(BluetoothDevice device) {
                mmDevice = device;
                BluetoothSocket tmp = null;
    
                // Get a BluetoothSocket for a connection with the
                // given BluetoothDevice
                try {
                    tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
                } catch (IOException e) {
                    Log.e(TAG, "create() failed", e);
                }
                mmSocket = tmp;
            }
    
            public void run() {
                Log.i(TAG, "BEGIN mConnectThread");
                setName("ConnectThread");
    
                // Always cancel discovery because it will slow down a connection
                mAdapter.cancelDiscovery();
    
                // Make a connection to the BluetoothSocket
                try {
                    // This is a blocking call and will only return on a
                    // successful connection or an exception
                    mmSocket.connect();
                } catch (IOException e) {
                    connectionFailed();
                    // Close the socket
                    try {
                        mmSocket.close();
                    } catch (IOException e2) {
                        Log.e(TAG, "unable to close() socket during connection failure", e2);
                    }
                    /*// Start the service over to restart listening mode
                    BluetoothChatService.this.start();*/
                    return;
                }
    
                // Reset the ConnectThread because we're done
                synchronized (BluetoothChatService.this) {
                    mConnectThread = null;
                }
    
                // Start the connected thread
                connected(mmSocket, mmDevice);
            }
    
            public void cancel() {
                try {
                    mmSocket.close();
                } catch (IOException e) {
                    Log.e(TAG, "close() of connect socket failed", e);
                }
            }
        }
    
      /*********************   连接管理,通讯线程   **********************/
        private class ConnectedThread extends Thread {
            private final BluetoothSocket mmSocket;
            private final InputStream mmInStream;
            private final OutputStream mmOutStream;
    
            public ConnectedThread(BluetoothSocket socket) {
                Log.d(TAG, "create ConnectedThread");
                mmSocket = socket;
                InputStream tmpIn = null;
                OutputStream tmpOut = null;
    
                // Get the BluetoothSocket input and output streams
                try {
                    tmpIn = socket.getInputStream();
                    tmpOut = socket.getOutputStream();
                } catch (IOException e) {
                    Log.e(TAG, "temp sockets not created", e);
                }
    
                mmInStream = tmpIn;
                mmOutStream = tmpOut;
            }
    
            public void run() {
                Log.i(TAG, "BEGIN mConnectedThread");
                byte[] buffer = new byte[1024];
                int bytes;
    
                // Keep listening to the InputStream while connected
                while (true) {
                    try {
                        // Read from the InputStream
                        bytes = mmInStream.read(buffer);
    
                    } catch (IOException e) {
                        Log.e(TAG, "disconnected", e);
                        connectionLost();
                        break;
                    }
                }
            }
    
            /**
             * Write to the connected OutStream.
             * @param buffer  The bytes to write
             */
            public void write(byte[] buffer) {
                try {
                    mmOutStream.write(buffer);
    
                } catch (IOException e) {
                    Log.e(TAG, "Exception during write", e);
                }
            }
    
            public void cancel() {
                try {
                    mmSocket.close();
                } catch (IOException e) {
                    Log.e(TAG, "close() of connect socket failed", e);
                }
            }
        }
    }
    


    希望这边文章可以帮到阅读者

    顺便一提:参考的出处的时候太久远了,忘了出处,在这里给他(他)说声:不好意思。。。


    展开全文
  • 使用蓝牙模块也有段时间了,更新…… 现在市面上用的蓝牙芯片大部分是ble的了,也就是低功耗透传模式。最近用到蓝牙SOC(片上系统),和大家分享下。 我们平时用蓝牙,一般是单片机的串口与蓝牙的串口连接,实现...

    技术小白,感谢大家阅读和点赞!使用蓝牙模块也有段时间了,更新……

     

    现在市面上用的蓝牙芯片大部分是ble的了,也就是低功耗透传模式。最近用到蓝牙SOC(片上系统),和大家分享下。

    我们平时用蓝牙,一般是单片机的串口与蓝牙的串口连接,实现数据传输,同时,也会用到mcu的其他一些功能,比如IIC,比如定时器,ADC等。但对于一些功能相对较少的产品,或者要求小体积的产品,这时候可以考虑把mcu省略掉,通过蓝牙直接实现需求,这就是soc。

    目前市面上用的比较多的soc包括TI的CC2640, Nordic的nRF52832和nRF52840,高通的CSR102x,各个品牌的功能和价格都大同小异,开发环境稍有不同,一般能满足可穿戴、物联产品的需求,博主最近在使用CSR102X,  使用后分享经验哈!

     

    以下帖子为原内容

    第一次用蓝牙通讯,现在市场上很多蓝牙模块功能都很强大,如果只是使用,不需要过多了解内部结构原理,只需要设置一些自己用到的参数就行了。

    蓝牙分为传统蓝牙和ble蓝牙,现在大部分用到的都是ble低功耗蓝牙。蓝牙分主从模式,主模式是主动连接其它蓝牙设备,作为主模式可同时连接7个从设备,作为从设备只能被一个主设备连接。

    使用蓝牙模块时,看模块支持哪种电平,有的可以直接接单片机的串口(TTL电平),有的需要经过232芯片连接。选定串口,设置好波特率,写好通讯协议,就可以通讯了。

    以上都是很简单的内容,这里需要跟大家分享的惨痛的教训是关于串口和单片机的隔离。

    我采购的SKY369可以直接连在单片机串口上,而且也可以3.3v供电。所以在设计电路时,我直接把蓝牙模块与单片机的某个串口接在一起,同时从模块引出四个排针,分别是vcc、gnd、rx、tx,注意,此时蓝牙模块已经焊在板子上了,按道理,在电路板不供电的情况下,我用usb转ttl线接四个排针,是可以进行AT指令设置的。但是,此处却出现了很多问题,很多问题!

    问题如下图,我用的是友善串口助手,串口设置好了(可以在电脑计算机右键——设备管理——端口处查询自己用的串口号),蓝牙模块都有初始波特率,可以参考蓝牙模块的手册,数据位校验位停止位也是参考手册。发送和接收都是ASCII,然后点击发送,没反应,再点击,还是没反应。

    排查串口波特率、排查串口线电压、排查tx、rx接没接反,最后发现都没问题,总不可能是蓝牙坏了吧,事实证明现在的模块都很稳定,一般不会出现质量问题,芯片坏了的情况基本不要考虑。

    而且,这个现象并不是每次都出现,而且蓝牙的通讯功能完好,只是设置出问题。同时,这个问题不是每次都会出现,有时候发送AT指令,有的可以实现,有的就会出现00 00 00 00……

    这个现象据蓝牙模块厂家说,是供电问题,换了好几个串口工具和线,确认不是此处的原因。

    经历了蓝牙模块返厂等一系列,发现,单片机和蓝牙模块之间在设置模式下,最好不要直接连接,拿一块板子做测试,把单片机与模块间的线割掉,就再也没出现发送数据没反应的现象了。

    串口连接蓝牙模块时,同时也给单片机供电了,发送给串口的数据同时也发给单片机了,这时就混乱了,处理方法是单片机与蓝牙模块之间加跳线帽,设置时拔开,通讯时插上。

     

    经验教训:外接设备与单片机连接时,最好做好隔离,包括编码器、蓝牙模块、wifi模块等。

    另外,做通讯协议时,单片机通过蓝牙收到数据,处理返回数据时,返回处理函数最好清晰,此串口用作接受处理函数后,就不要再定时器里写其他的定时返回函数,否则会很混乱!收发乱七八糟……这也是血的教训。

    例如,用单片机usart5做蓝牙通讯,蓝牙收到00,返回01,同时,蓝牙还要每隔0.01秒返回02,这样就会乱,导致蓝牙发送接收丢数据。

    同时,还要强调,一定要检查自己的电脑com口有没有问题,博主用自己的台式机一直失败,换了个笔记本莫名其妙好了!

    蓝牙篇就到这。

    展开全文
  • (2)安卓手机与蓝牙模块联合调试(二)—— 单片机蓝牙控制LED灯亮灭(上) (3)安卓手机与蓝牙模块联合调试(三)—— 单片机蓝牙控制LED灯亮灭(下) 本教程的项目地址:1989Jiangtao/BluetoothSCM: 安卓手机...

    系列博文:

    (1)安卓手机与蓝牙模块联合调试(一)——蓝牙模块的串口通讯 

    (2)安卓手机与蓝牙模块联合调试(二)—— 单片机蓝牙控制LED灯亮灭(上)

    (3)安卓手机与蓝牙模块联合调试(三)—— 单片机蓝牙控制LED灯亮灭(下)

    本教程的项目地址:1989Jiangtao/BluetoothSCM: 安卓手机通过蓝牙与单片机通信-发送指令/接收数据​​​​​​​


    忙里偷闲,承接上一篇文章继续

    安卓手机与蓝牙模块联合调试(一)——蓝牙模块的串口通讯 - CSDN博客

    本篇将实现两个实例,手头正好有8位的单片机,索性就用来练手了。将会提供两个例子,一个是基于STM8的库函数实例,一个是基于STC89C52的实例。

    1.首先了解下单片机串口通讯线的接法。这个比较重要,建议参考文章。

    (1)HC-05初探 - 骑着蜗牛逛世界 - 博客园      https://www.cnblogs.com/LittleHann/p/5621937.html

    (2)HC-05 蓝牙模块的调试与使用 - CSDN博客      https://blog.csdn.net/txf1984/article/details/46553715

     

    2. 准备工作和单片机原理图。

           首先看下我用到的硬件支持,一块STM8S103F2P6最小系统板,一个ST-Link下载器,一个HC-05的模块(暂时先调试HC-5的模块,CC2541单片机端的代码是一样的)

    最小系统的原理图如下,大家按照上面两个博文中的接线方法接好单片机,特别注意蓝牙模块的TX/RX与单片机的TX/RX要交叉相接

    3. 单片机的代码,使用STM8库函数编写。

    首先看下目录结构,本项目使用的是IAR编译器

    下面贴出来main函数,注释在代码中已经描述的很清楚了,大家可以自行参考。

    /*************
    ** Author   : 江涛
    ** Date     :  2018/08/28
    ** Describe : 蓝牙控制单片机开关灯
    **************/
    /* Includes -----------*/
    #include "stm8s.h"
    #include <string.h>
    
    /********************************************
    **  TestLed        PB5  测试LED
    **********************************************/
    
    /**串口1接收数据标志位**/
    unsigned char Usart1_bufStart_or_bufSotp = 0 ;
    /**串口1数据,这里10个字节够放命令了,大家可以根据实际需要调整数组长度**/
    char Usart1BufData[10];
    /**串口数据数目自增量,用来统计数目是否达到数组最大长度**/
    char Usart1BufConst;
    
    /****以下是定义的命令*****/
    char LED_ON[10] = "ON\n";  
    char LED_OFF[10] = "OFF\n";
    
    void Delay(uint16_t nCount)
    {
      /* Decrement nCount value */
      while (nCount != 0)
      {   
        nCount--;
      }
    }
    
    /*************
    * 初始化串口UART1
    **************/
    void initUart1()
    {
      // 串口设置
      UART1_DeInit();
      // 9600波特率,8位数据位,一位停止位,
      UART1_Init((u32)9600, UART1_WORDLENGTH_8D, UART1_STOPBITS_1, UART1_PARITY_NO, UART1_SYNCMODE_CLOCK_DISABLE, UART1_MODE_TXRX_ENABLE);//UART1_MODE_TX_ENABLE);
      // 使能串口接收中断
      UART1_ITConfig(UART1_IT_RXNE_OR, ENABLE);
    }
    
    /*********
    * 端口配置
    **************/
    void initGPIO(){
      // LED灯的GPIO口初始化
      GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_FAST);
      GPIO_WriteHigh(GPIOB , GPIO_PIN_5); // 关测试灯
      
    }
    
    /*****************
    *  程序入口
    *******************/
    void main(void)
    {
    
      // 初始化系统时钟,
      CLK_HSICmd(ENABLE);
      CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1);
      //内部时钟16M,8分频
      CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);    
      
      initGPIO();
       
      initUart1();
      
      enableInterrupts(); // 使能中断
           
      while (1)
      {    
        if(Usart1_bufStart_or_bufSotp)
        {
           Usart1_bufStart_or_bufSotp = 0 ; 
                  
           if(strcmp(Usart1BufData,LED_ON)==0)
           {
              GPIO_WriteLow(GPIOB , GPIO_PIN_5);         
           }
           else if(strcmp(Usart1BufData,LED_OFF)==0)
           {
              GPIO_WriteHigh(GPIOB , GPIO_PIN_5);
           }
                  
           Usart1BufConst = 0 ;
           memset(Usart1BufData,0,10);//清0数组   
        }    
      }
    }
    
    
    #ifdef USE_FULL_ASSERT
    
    /**
      * @brief  Reports the name of the source file and the source line number
      *   where the assert_param error has occurred.
      * @param file: pointer to the source file name
      * @param line: assert_param error line source number
      * @retval : None
      */
    void assert_failed(u8* file, u32 line)
    { 
      /* User can add his own implementation to report the file name and line number,
         ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
    
      /* Infinite loop */
      while (1)
      {
      }
    }
    #endif

     项目中还有个it.c文件,内容如下:

    /* Includes ------------------------------------------------------------------*/
    #include "stm8s.h"
    
    /* Private typedef -----------------------------------------------------------*/
    
    extern unsigned char Usart1_bufStart_or_bufSotp;
    extern char Usart1BufData[10];
    extern char Usart1BufConst;
    
    /* Private define ------------------------------------------------------------*/
    /* Private macro -------------------------------------------------------------*/
    /* Private variables ---------------------------------------------------------*/
    /* Private function prototypes -----------------------------------------------*/
    /* Private functions ---------------------------------------------------------*/
    /* Public functions ----------------------------------------------------------*/
    
    
    #if defined (STM8S208) || defined(STM8S207) || defined(STM8S007) || defined(STM8S103) || \
        defined(STM8S003) ||  defined (STM8AF62Ax) || defined (STM8AF52Ax) || defined (STM8S903)
    /**
      * @brief UART1 TX Interrupt routine.
      * @param  None
      * @retval None
      */
     INTERRUPT_HANDLER(UART1_TX_IRQHandler, 17)
     {
        /* In order to detect unexpected events during development,
           it is recommended to set a breakpoint on the following instruction.
        */
     }
    
    
    /**
      * @brief UART1 RX Interrupt routine.
      * @param  None
      * @retval None
      */
     INTERRUPT_HANDLER(UART1_RX_IRQHandler, 18)
     {
        /* In order to detect unexpected events during development,
           it is recommended to set a breakpoint on the following instruction.
        */  
         
         Usart1BufData[Usart1BufConst]=UART1_ReceiveData8();
         UART1_SendData8(UART1_ReceiveData8()); 
         // 收到了结束符号
         if(Usart1BufData[Usart1BufConst]==0x0A)
         {
           Usart1_bufStart_or_bufSotp=1;    
         }
         else
           Usart1BufConst++;
       
          UART1_ClearITPendingBit(UART1_IT_RXNE); // 清除标志位
          
     }
    #endif /*STM8S208 or STM8S207 or STM8S103 or STM8S903 or STM8AF62Ax or STM8AF52Ax */
    
    /******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

    主要代码就是这些了,注释中已经写的很清楚了。

    4.接好线看下运行的效果 。

    过后放上一个视频连接,实在因为gif的效果不明显。 

     

    好了,STM8蓝牙控制LED亮灭的功能基本实现了,下一遍将实现蓝牙和STC89单片机的联调。

    当这个系列结束之后我会将代码一并上传到Git。博文更新的话,可能因为工作原因会断断续续,希望大家见谅。如果觉得有帮助或者有设么其他好的建议可以在博文下留言,赞赏码没有别的意思,这是个知识付费的时代,可能付费了才会更珍惜学习的不易,当然也是作者创作的动力所在,哈哈。


     

     

     

     

    展开全文
  • (1)安卓手机与蓝牙模块联合调试(一)—— 蓝牙模块的串口通讯  (2)安卓手机与蓝牙模块联合调试(二)—— 单片机蓝牙控制LED灯亮灭(上) (3)安卓手机与蓝牙模块联合调试(三)—— 单片机蓝牙控制LED灯亮...

    (1)安卓手机与蓝牙模块联合调试(一)—— 蓝牙模块的串口通讯 

    (2)安卓手机与蓝牙模块联合调试(二)—— 单片机蓝牙控制LED灯亮灭(上)

    (3)安卓手机与蓝牙模块联合调试(三)—— 单片机蓝牙控制LED灯亮灭(下)

    (4)安卓手机与蓝牙模块联合调试(四)—— 单片机数据上传至蓝牙(STC89C52 + DS18b20)

    本教程的项目地址:1989Jiangtao/BluetoothSCM: 安卓手机通过蓝牙与单片机通信-发送指令/接收数据​​​​​​​


    Github参考:  dingjikerbo/BluetoothKit: Android BLE蓝牙通信库 

                          zagum/Android-SwitchIcon: Google launcher-style implementation of switch (enable/disable) icon

                          huangyanbin/CalendarView: 日历 仪表盘 圆盘,提供全新RecyclerView日历,功能更加强大。

                          Android BLE4.0 常用的一些Service和Characteristic的UUID - CSDN博客
     

    1.AS添加依赖的注意事项。

    由于许多前辈的开源精神和无私奉献,才使得现如今你的IT事业日新月异,也让我们后来人免去了很多重复造轮子的繁琐工作,因此在这里感谢无私的开源奉献者。

    下面说下依赖项目引入时候的注意事项,其实如果自己去git上看也能够自己搞懂。switch-icon的依赖请务必在项目project/build.gradle中添加如下两行,原因是因为该项目放在了jitpack上,使用maven方式。

     

    2.看下演示的效果。

     

     

     

    3.上代码,本篇就提交部分主要代码,后续会更新到GitHub。

    先看下依赖引入,使用了第三方的蓝牙扫描连接和读写库,按钮切换库,自定义的表盘,build.gradle文件如下

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 27
        defaultConfig {
            applicationId "com.cjt.bluetoothscm"
            minSdkVersion 15
            targetSdkVersion 27
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            /*由于使用了Vector Asset,所以必须添加支持*/
            vectorDrawables.useSupportLibrary = true
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:27.1.1'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        implementation 'com.android.support:design:27.1.1'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
        /*第三方蓝牙操作库*/
        implementation 'com.inuker.bluetooth:library:1.4.0'
        /*第三方SwitchIcon,图标切换库*/
        implementation 'com.github.zagum:Android-SwitchIcon:1.3.7'
    }
    

    由于我在项目中大量使用了Vector Asset图标,所以依赖中要添加对VectorDrawable的支持。还有一个表盘的自定义View,我没有引入依赖,直接拷贝了别人的java代码和资源文件到项目中使用的,稍后也会贴出来部分。

    布局全部使用的约束布局,感觉比RelativeLayout还好用,如果有不熟悉的同学,建议可以找相关的教程学习下,刚开始可能不习惯,后面用熟练了会感觉很爽,以前我都是手写布局xml文件,现在有了这个东西,反而喜欢直接拖拽,简单快捷。下面的是activity_main.xml布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <TextView
            android:id="@+id/main_title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:textSize="18sp"
            android:text="@string/scan_hint"
            android:textColor="@color/colorAccent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <View
            android:id="@+id/top_div"
            android:layout_width="wrap_content"
            android:layout_height="1dp"
            android:layout_marginEnd="5dp"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_marginStart="5dp"
            android:layout_marginTop="10dp"
            android:background="@color/arc1"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/main_title" />
    
        <com.cjt.bluetoothscm.DashboardView
            android:id="@+id/temp_view"
            android:layout_width="280dp"
            android:layout_height="280dp"
            android:layout_marginTop="15dp"
            app:angleTextSize="16sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/top_div" />
    
        <View
            android:id="@+id/top_div2"
            android:layout_width="wrap_content"
            android:layout_height="1dp"
            android:layout_marginEnd="5dp"
            android:layout_marginLeft="5dp"
            android:layout_marginRight="5dp"
            android:layout_marginStart="5dp"
            android:layout_marginTop="20dp"
            android:background="@color/arc1"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/temp_view" />
    
        <com.github.zagum.switchicon.SwitchIconView
            android:id="@+id/sw_lamp_01"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginLeft="20dp"
            android:layout_marginStart="20dp"
            android:layout_marginTop="20dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/top_div2"
            app:si_animation_duration="200"
            app:si_disabled_alpha=".5"
            app:si_disabled_color="@color/colorOff"
            app:si_enabled="false"
            app:si_no_dash="true"
            app:si_tint_color="@color/colorOn"
            app:srcCompat="@drawable/ic_lamp" />
    
        <com.github.zagum.switchicon.SwitchIconView
            android:id="@+id/sw_lamp_02"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginLeft="30dp"
            android:layout_marginStart="30dp"
            android:layout_marginTop="20dp"
            app:layout_constraintStart_toEndOf="@+id/sw_lamp_01"
            app:layout_constraintTop_toBottomOf="@+id/top_div2"
            app:si_animation_duration="200"
            app:si_disabled_alpha=".5"
            app:si_disabled_color="@color/colorOff"
            app:si_enabled="false"
            app:si_no_dash="true"
            app:si_tint_color="@color/colorOn"
            app:srcCompat="@drawable/ic_lamp" />
    
        <com.github.zagum.switchicon.SwitchIconView
            android:id="@+id/sw_power"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginLeft="30dp"
            android:layout_marginStart="30dp"
            android:layout_marginTop="20dp"
            app:layout_constraintStart_toEndOf="@+id/sw_lamp_02"
            app:layout_constraintTop_toBottomOf="@+id/top_div2"
            app:si_animation_duration="200"
            app:si_disabled_alpha=".5"
            app:si_disabled_color="@color/colorOff"
            app:si_enabled="false"
            app:si_no_dash="true"
            app:si_tint_color="@color/colorOn"
            app:srcCompat="@drawable/ic_switch" />
    
        <com.github.zagum.switchicon.SwitchIconView
            android:id="@+id/sw_fan"
            android:layout_width="60dp"
            android:layout_height="60dp"
            android:layout_marginLeft="30dp"
            android:layout_marginStart="30dp"
            android:layout_marginTop="20dp"
            app:layout_constraintStart_toEndOf="@+id/sw_power"
            app:layout_constraintTop_toBottomOf="@+id/top_div2"
            app:si_animation_duration="200"
            app:si_disabled_alpha=".5"
            app:si_disabled_color="@color/colorOff"
            app:si_enabled="false"
            app:si_no_dash="true"
            app:si_tint_color="@color/colorOn"
            app:srcCompat="@drawable/ic_fan" />
    
        <TextView
            android:id="@+id/lamp_01_name"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:text="@string/lamp_01"
            app:layout_constraintEnd_toEndOf="@+id/sw_lamp_01"
            app:layout_constraintStart_toStartOf="@+id/sw_lamp_01"
            app:layout_constraintTop_toBottomOf="@+id/sw_lamp_01"
            tools:ignore="HardcodedText" />
    
        <TextView
            android:id="@+id/lamp_02_name"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:text="@string/lamp_02"
            app:layout_constraintEnd_toEndOf="@+id/sw_lamp_02"
            app:layout_constraintStart_toStartOf="@+id/sw_lamp_02"
            app:layout_constraintTop_toBottomOf="@+id/sw_lamp_02" />
    
        <TextView
            android:id="@+id/power_name"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:text="@string/power_sw"
            app:layout_constraintEnd_toEndOf="@+id/sw_power"
            app:layout_constraintStart_toStartOf="@+id/sw_power"
            app:layout_constraintTop_toBottomOf="@+id/sw_power" />
    
        <TextView
            android:id="@+id/fan_name"
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:text="@string/fan_sw"
            app:layout_constraintEnd_toEndOf="@+id/sw_fan"
            app:layout_constraintStart_toStartOf="@+id/sw_fan"
            app:layout_constraintTop_toBottomOf="@+id/sw_fan" />
    
    </android.support.constraint.ConstraintLayout>

    接着贴出来MainActivity.java文件

    package com.cjt.bluetoothscm;
    
    import android.app.Activity;
    import android.bluetooth.BluetoothDevice;
    import android.content.Intent;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.text.TextUtils;
    import android.util.Log;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import com.github.zagum.switchicon.SwitchIconView;
    import com.inuker.bluetooth.library.Constants;
    import com.inuker.bluetooth.library.connect.options.BleConnectOptions;
    import com.inuker.bluetooth.library.connect.response.BleConnectResponse;
    import com.inuker.bluetooth.library.connect.response.BleNotifyResponse;
    import com.inuker.bluetooth.library.connect.response.BleWriteResponse;
    import com.inuker.bluetooth.library.model.BleGattProfile;
    
    import java.util.UUID;
    
    import static com.inuker.bluetooth.library.Constants.REQUEST_SUCCESS;
    
    /*****************
     * 包名:com.cjt.bluetoothscm
     * 类名:MainActivity.java
     * 时间:2018/9/11  23:28
     * 作者:Cao Jiangtao
     * 首页:https://1989jiangtao.github.io/index.html
     ******************/
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        private static final int REQUEST_CONNECT_DEVICE = 0x100;
    
        TextView mainTitle ;
    
        // 温度显示仪表盘
        DashboardView tempView ;
        private final static int invs[] = {35, 18, 35};
        private final static int[] colorRes = {R.color.arc1, R.color.arc2, R.color.arc3};
    
        // 灯组01 ,灯组02 , 电源开关, 风扇开关
        SwitchIconView lamp01 , lamp02 , powerSw , fanSw ;
        TextView lamp01Name , lamp02Name ,powerName , fanName;
    
        // 蓝牙通信的地址和两个UUID
        String MAC = "" ;
        UUID serviceUuid , characterUuid ;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            initView();
    
            // 运行的时候检查是否打开蓝牙,没有打开就开启蓝牙
            if(!MyApp.getBluetoothClient().isBluetoothOpened())
                MyApp.getBluetoothClient().openBluetooth();
    
        }
    
        private void initView() {
    
            mainTitle = findViewById(R.id.main_title);
            tempView = findViewById(R.id.temp_view);
            lamp01 = findViewById(R.id.sw_lamp_01);
            lamp02 = findViewById(R.id.sw_lamp_02);
            powerSw = findViewById(R.id.sw_power);
            fanSw = findViewById(R.id.sw_fan);
            lamp01Name = findViewById(R.id.lamp_01_name);
            lamp02Name = findViewById(R.id.lamp_02_name);
            powerName = findViewById(R.id.power_name);
            fanName = findViewById(R.id.fan_name);
    
            // 为按钮设置点击事件
            lamp01.setOnClickListener(this);
            lamp02.setOnClickListener(this);
            powerSw.setOnClickListener(this);
            fanSw.setOnClickListener(this);
    
            // 初始化温度表盘
            String[] str = getResources().getStringArray(R.array.mult_temp_dash);
            tempView.initDash(-20, invs, str, "℃", colorRes);
    
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.menu_main , menu); // 加载菜单页面
            return super.onCreateOptionsMenu(menu);
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            if(item.getItemId() == R.id.action_scan){
                Intent intent = new Intent(MainActivity.this , ScanResultActivity.class);
                startActivityForResult(intent , REQUEST_CONNECT_DEVICE);
                return true;
            }
            return super.onOptionsItemSelected(item);
        }
    
        @Override
        protected void onDestroy() {
            // 关闭蓝牙
            MyApp.getBluetoothClient().closeBluetooth();
            super.onDestroy();
        }
    
        @Override
        public void onClick(View v) {
    
            // 当蓝牙有连接,并且MAC地址存在,两个UUID都不为空的情况下,点击按钮才有效
            // 以下只要有一个条件不满足,就不让点击按钮发送数据
            if(!MyApp.getBluetoothClient().isBleSupported()
                    || TextUtils.isEmpty(MAC)
                    || TextUtils.isEmpty(serviceUuid.toString())
                    || TextUtils.isEmpty(characterUuid.toString())){
                Toast.makeText(MainActivity.this , "请先检查蓝牙设备与手机是否连接正常",Toast.LENGTH_SHORT).show();
                return;
            }
    
            switch (v.getId()){
                case R.id.sw_lamp_01: // 灯组01
                    lamp01.switchState();
                    lamp01Name.setText(lamp01.isIconEnabled() ? "灯组1开" : "灯组1关");
                    writeCmd(MAC , serviceUuid , characterUuid , "001-on\r\n");
                    break;
                case R.id.sw_lamp_02: // 灯组02
                    lamp02.switchState();
                    lamp02Name.setText(lamp02.isIconEnabled() ? "灯组1开" : "灯组1关");
                    break;
                case R.id.sw_power: // 电源
                    powerSw.switchState();
                    powerName.setText(powerSw.isIconEnabled() ? "电源开" : "电源关");
                    writeCmd(MAC , serviceUuid , characterUuid , "power-on\r\n");
                    break;
                case R.id.sw_fan: // 风扇
                    fanSw.switchState();
                    fanName.setText(fanSw.isIconEnabled() ? "风扇开" : "风扇关");
                    writeCmd(MAC , serviceUuid , characterUuid , "fan-sw\r\n");
                    break;
            }
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            super.onActivityResult(requestCode, resultCode, data);
            Log.d("CJT" , "requestCode = "+ requestCode +" ,  resultCode = "+ resultCode + " , data ="+data);
            if(requestCode == REQUEST_CONNECT_DEVICE) {
                // 响应结果
                switch (resultCode) {
    
                    case Activity.RESULT_CANCELED:
                        Toast.makeText(this , "取消了扫描!",Toast.LENGTH_SHORT).show();
                        break;
                    case Activity.RESULT_OK:
                        // 选择连接的设备
                        final BluetoothDevice device = data.getParcelableExtra(RecycleAdapter.EXTRA_DEVICE);
                        // 得到选择后传过来的MAC地址
                        MAC = device.getAddress();
                        Log.d("CJT" , "address ===================== " +MAC);
    
                        // 设置BLE设备的连接参数
                        BleConnectOptions options = new BleConnectOptions.Builder()
                                .setConnectRetry(3)   // 连接如果失败重试3次
                                .setConnectTimeout(30000)   // 连接超时30s
                                .setServiceDiscoverRetry(3)  // 发现服务如果失败重试3次
                                .setServiceDiscoverTimeout(20000)  // 发现服务超时20s
                                .build();
    
                        // 开始连接操作
                        MyApp.getBluetoothClient().connect(MAC, options, new BleConnectResponse() {
                            @Override
                            public void onResponse(int code, BleGattProfile data) {
                                Log.d("CJT" , "getBluetoothClient().connect  --- code ----- " + code);
    
                                // 表示连接成功
                                if(code == REQUEST_SUCCESS){
    
                                    mainTitle.setText("当前连接设备 :"+device.getName());
    //                                for(BleGattService sls : data.getServices()){
    //                                    Log.d("CJT" , "onActivityResult -------1111111111-------  : "+sls.getUUID());
    //                                    for(BleGattCharacter gls : sls.getCharacters()){
    //                                        Log.d("CJT" , "onActivityResult *******22222222222*****  : "+gls.getUuid());
    //                                    }
    //                                }
                                    serviceUuid = data.getServices().get(3).getUUID();
                                    Log.d("CJT" , "getBluetoothClient().connect  --- serviceUuid  : "+serviceUuid);
                                    characterUuid = data.getService(serviceUuid).getCharacters().get(0).getUuid();
                                    Log.d("CJT" , "getBluetoothClient().connect  --- characterUuid : "+characterUuid);
    
                                    // 获取温度值
                                    getTemperature(MAC , serviceUuid , characterUuid);
    
                                    // 下发数据
                                    writeCmd(MAC , serviceUuid , characterUuid , "finish\r\n");
    
                                }else{
                                    mainTitle.setText("当前暂无蓝牙设备连接");
                                    Toast.makeText(MainActivity.this , "蓝牙连接不成功!",Toast.LENGTH_SHORT).show();
                                }
                            }
                        });
    
                        break;
                }
    
            }
        }
    
        /***
         * 获取温度值并显示到界面上
         * @param address           设备地址
         * @param serviceUuid       服务UUID
         * @param characterUuid     特征UUID
         */
        private void getTemperature(String address , UUID serviceUuid , UUID characterUuid ){
            MyApp.getBluetoothClient().notify(address, serviceUuid, characterUuid, new BleNotifyResponse() {
                @Override
                public void onNotify(UUID service, UUID character, byte[] value) {
                        String hexStr = bytesToHexString(value);
                        int beginIndex = hexStr.indexOf("2b") + 2; // 加号开始截取,并且跳过加号
                        int endIndex = hexStr.indexOf("2e") + 2 ;  // 小数点开始截取
                        String validTemp = hexStr.substring(beginIndex , endIndex );
                        Log.d("CJT" , "valid temp = "+validTemp+", hex2Str = "+ new String(hexStringToBytes(validTemp)));
    
                        // 设置温度值
                        tempView.setAngleWithAnim(Double.valueOf(new String(hexStringToBytes(validTemp))));
                }
    
                @Override
                public void onResponse(int code) {
    
                }
            });
        }
    
        /***
         * 向设备下发指令
         * @param address           设备MAC地址
         * @param serviceUuid       服务UUID
         * @param characterUuid     特征UUID
         * @param cmd               待下发的命令
         */
        private void writeCmd(String address , UUID serviceUuid , UUID characterUuid , String cmd){
            MyApp.getBluetoothClient().write(address, serviceUuid, characterUuid, cmd.getBytes(), new BleWriteResponse() {
                @Override
                public void onResponse(int code) {
                    if(code == Constants.REQUEST_SUCCESS){
    
                    }
                }
            });
        }
    
        /**
         * Convert byte[] to hex string.这里我们可以将byte转换成int,然后利用Integer.toHexString(int)来转换成16进制字符串。
         * @param src byte[] data
         * @return hex string
         */
        public static String bytesToHexString(byte[] src){
            StringBuilder stringBuilder = new StringBuilder("");
            if (src == null || src.length <= 0) {
                return null;
            }
            for (int i = 0; i < src.length; i++) {
                int v = src[i] & 0xFF;
                String hv = Integer.toHexString(v);
                if (hv.length() < 2) {
                    stringBuilder.append(0);
                }
                stringBuilder.append(hv);
            }
            return stringBuilder.toString();
        }
    
        /**
         * Convert hex string to byte[]
         * @param hexString the hex string
         * @return byte[]
         */
        public static byte[] hexStringToBytes(String hexString) {
            if (hexString == null || hexString.equals("")) {
                return null;
            }
            hexString = hexString.toUpperCase();
            int length = hexString.length() / 2;
            char[] hexChars = hexString.toCharArray();
            byte[] d = new byte[length];
            for (int i = 0; i < length; i++) {
                int pos = i * 2;
                d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
            }
            return d;
        }
        /**
         * Convert char to byte
         * @param c char
         * @return byte
         */
        private static byte charToByte(char c) {
            return (byte) "0123456789ABCDEF".indexOf(c);
        }
    
    }
    

    代码可能暂时比较乱,我就挑主要的给大家梳理下逻辑,注释中有的已经写的很清楚了。

    (1)先在界面上Toolbar上添加扫描的按钮,点击扫描按钮会跳转到扫描界面。

    贴出来main.xml的菜单文件

    <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        tools:context="com.cjt.bluetoothscm.MainActivity">
        <item
            android:id="@+id/action_scan"
            android:orderInCategory="100"
            android:icon="@drawable/ic_scan"
            android:title="@string/action_settings"
            tools:ignore="AppCompatResource"
            app:showAsAction="always" />
    </menu>

    其中有个icon,引用的是drawable下的一个VectorDrawable资源文件,ic_scan.xml

    <vector android:height="42dp" android:viewportHeight="1024"
        android:viewportWidth="1024" android:width="42dp" xmlns:android="http://schemas.android.com/apk/res/android">
        <path android:fillColor="#ffffff" android:pathData="M512,960c-249.6,0 -448,-198.4 -448,-448s198.4,-448 448,-448 448,198.4 448,448 -198.4,448 -448,448zM512,883.2c204.8,0 371.2,-166.4 371.2,-371.2S716.8,140.8 512,140.8 140.8,307.2 140.8,512s166.4,371.2 371.2,371.2z"/>
        <path android:fillColor="#3cbc74" android:pathData="M512,787.2c-153.6,0 -275.2,-121.6 -275.2,-275.2S358.4,236.8 512,236.8s275.2,121.6 275.2,275.2 -121.6,275.2 -275.2,275.2zM512,748.8c128,0 236.8,-108.8 236.8,-236.8S640,275.2 512,275.2 275.2,384 275.2,512 384,748.8 512,748.8z"/>
        <path android:fillColor="#ffffff" android:pathData="M512,512m-38.4,0a38.4,38.4 0,1 0,76.8 0,38.4 38.4,0 1,0 -76.8,0Z"/>
        <path android:fillColor="#ffffff" android:pathData="M524.8,531.2l345.6,-217.6 -19.2,-32 -352,211.2zM492.8,25.6L492.8,320c0,12.8 6.4,19.2 19.2,19.2s19.2,-6.4 19.2,-19.2L531.2,25.6c0,-12.8 -6.4,-19.2 -19.2,-19.2s-19.2,6.4 -19.2,19.2zM998.4,492.8L704,492.8c-12.8,0 -19.2,6.4 -19.2,19.2s6.4,19.2 19.2,19.2h294.4c12.8,0 19.2,-6.4 19.2,-19.2s-6.4,-19.2 -19.2,-19.2zM531.2,998.4L531.2,704c0,-12.8 -6.4,-19.2 -19.2,-19.2s-19.2,6.4 -19.2,19.2v294.4c0,12.8 6.4,19.2 19.2,19.2s19.2,-6.4 19.2,-19.2zM25.6,531.2L320,531.2c12.8,0 19.2,-6.4 19.2,-19.2s-6.4,-19.2 -19.2,-19.2L25.6,492.8c-12.8,0 -19.2,6.4 -19.2,19.2s6.4,19.2 19.2,19.2z"/>
    </vector>

    使用这个的好处就是,改变大小的时候不会失真,因为使用的矢量作图。 

    (2)蓝牙连接的处理,这一块大家要恶补下关于蓝牙UUID的知识,由于我也研究的不深入,所以就不误导大家了,梳理下onActivityResult回调方法

    (3)我把client写到了全局Application中,如下

    package com.cjt.bluetoothscm;
    
    import android.app.Application;
    
    import com.inuker.bluetooth.library.BluetoothClient;
    
    /*****************
     * 包名:com.cjt.bluetoothscm
     * 类名:MyApp.java
     * 时间:2018/9/17  23:46
     * 作者:Cao Jiangtao
     * 首页:https://1989jiangtao.github.io/index.html
     ******************/
    
    public class MyApp extends Application {
    
        private static MyApp instance ;
    
        private static BluetoothClient bluetoothClient ;
    
        public static MyApp getInstance() {
            return instance;
        }
    
        public static BluetoothClient getBluetoothClient() {
            return bluetoothClient;
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            instance = this ;
    
            // 新建全局的蓝牙客户端实例
            bluetoothClient = new BluetoothClient(this);
    
        }
    
    }

     (4)看下扫描界面的界面布局文件和java代码

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ScanResultActivity">
    
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/result_recycle_list"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginBottom="8dp"
            android:layout_marginTop="8dp"
            android:paddingLeft="8dp"
            android:paddingRight="8dp"
            app:layout_constraintBottom_toTopOf="@+id/btn_exit"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <ProgressBar
            android:id="@+id/progressBar"
            style="?android:attr/progressBarStyle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginLeft="8dp"
            android:layout_marginRight="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    
        <Button
            android:id="@+id/btn_exit"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="16dp"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:layout_marginStart="16dp"
            android:textSize="20sp"
            android:textStyle="bold"
            android:background="@color/colorRed"
            android:textColor="@color/colorWhite"
            android:text="@string/btn_exit"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />
    
    </android.support.constraint.ConstraintLayout>
    package com.cjt.bluetoothscm;
    
    import android.bluetooth.BluetoothDevice;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;
    import android.text.TextUtils;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.ProgressBar;
    
    import com.inuker.bluetooth.library.search.SearchRequest;
    import com.inuker.bluetooth.library.search.SearchResult;
    import com.inuker.bluetooth.library.search.response.SearchResponse;
    
    import java.util.ArrayList;
    import java.util.HashSet;
    import java.util.List;
    /*****************
     * 包名:com.cjt.bluetoothscm
     * 类名:ScanResultActivity.java
     * 时间:2018/9/17  23:49
     * 作者:Cao Jiangtao
     * 首页:https://1989jiangtao.github.io/index.html
     ******************/
    public class ScanResultActivity extends AppCompatActivity implements View.OnClickListener {
    
        RecyclerView recyclerView ; // 列表展示扫描的结果
    
        ProgressBar progressBar ; // 页面上的进度条
    
        Button exitBtn ; // 退出按钮
    
        RecycleAdapter adapter ; // 列表适配器
    
        List<BluetoothDevice> bluetoothDeviceList = new ArrayList<>();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_scan_result);
    
            initView(); // 界面初始化
    
            startScan(); // 开启扫描
    
        }
    
        @Override
        protected void onDestroy() {
            // 停止扫描
            MyApp.getBluetoothClient().stopSearch();
            super.onDestroy();
        }
    
        /**开启蓝牙扫描**/
        private void startScan() {
            // 新建一个扫描结果集
            SearchRequest request = new SearchRequest.Builder()
                    .searchBluetoothLeDevice(3000, 3)   // 先扫BLE设备3次,每次3s
                    .searchBluetoothClassicDevice(5000) // 再扫经典蓝牙5s
                    .searchBluetoothLeDevice(2000)      // 再扫BLE设备2s
                    .build();
    
            // 开始扫描
            MyApp.getBluetoothClient().search(request, new SearchResponse() {
                @Override
                public void onSearchStarted() {
                    progressBar.setVisibility(View.VISIBLE);
                    Log.d("CJT" , "onSearchStarted ***** ");
                }
    
                @Override
                public void onDeviceFounded(SearchResult device) {
                    Log.d("CJT" , "onDeviceFounded ==   name :"+ device.device.getName()+", address : " + device.device.getAddress());
                    if(!TextUtils.isEmpty(device.device.getName())){
                        bluetoothDeviceList.add(device.device);
                    }
                }
    
                @Override
                public void onSearchStopped() {
                    progressBar.setVisibility(View.GONE);
                    // 实时更新列表适配器
                    adapter.notifyData(removeDuplicate(bluetoothDeviceList));
                    Log.d("CJT" , "onSearchStopped ######  设备数量 :"+removeDuplicate(bluetoothDeviceList).size());
                }
    
                @Override
                public void onSearchCanceled() {
                    progressBar.setVisibility(View.GONE);
                }
            });
    
        }
    
        /***
         * 列表去重复
         * @param list 待去除重复数据的列表
         * @return      返回去重后的列表
         */
        public static List removeDuplicate(List list) {
            HashSet h = new HashSet(list);
            list.clear();
            list.addAll(h);
            return list;
        }
    
        /**初始化页面和控件**/
        private void initView(){
            // 初始化页面控件
            recyclerView = findViewById(R.id.result_recycle_list);
            progressBar = findViewById(R.id.progressBar);
            exitBtn = findViewById(R.id.btn_exit);
    
            exitBtn.setOnClickListener(this);
    
            // 设置recycleView显示方式
            recyclerView.setLayoutManager(new LinearLayoutManager(this , LinearLayoutManager.VERTICAL , false));
    
            adapter = new RecycleAdapter();
            // 设置适配器
            recyclerView.setAdapter(adapter);
    
        }
    
        @Override
        public void onClick(View v) {
            finish();
        }
    }
    

    代码中有一个去重复的方法,避免最后的列表中很多重复的设备,因为蓝牙在后台不停的扫描,会有很多重复的设备。

    (5)看下列表适配器的写法,我用了RecycleView来列表展示已扫描到的设备,同时添加了点击事件,当选择和点击的时候就携带相关的设备信息跳转到MainActivity,进行读写操作。

    package com.cjt.bluetoothscm;
    
    import android.app.Activity;
    import android.bluetooth.BluetoothDevice;
    import android.content.Intent;
    import android.support.annotation.NonNull;
    import android.support.constraint.ConstraintLayout;
    import android.support.v7.widget.RecyclerView;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.TextView;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /*****************
     * 包名:com.cjt.bluetoothscm
     * 类名:RecycleAdapter.java
     * 时间:2018/9/14  10:14
     * 作者:Cao Jiangtao
     * 首页:https://1989jiangtao.github.io/index.html
     ******************/
    public class RecycleAdapter extends RecyclerView.Adapter<RecycleAdapter.MyViewHolder> {
    
        public static final String EXTRA_DEVICE = "extra_device";
    
        List<BluetoothDevice> deviceList = new ArrayList<>();
    
        @NonNull
        @Override
        public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            Log.d("CJT"," RecycleAdapter --- onCreateViewHolder --  ");
            return new RecycleAdapter.MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_device_layout,parent,false));
        }
    
        @Override
        public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
            holder.deviceName.setText(getItem(position).getName());
            holder.deviceAddress.setText(getItem(position).getAddress());
            // 为列表添加点击事件,传入的参数是当前项的蓝牙设备BluetoothDevice
            holder.deviceItem.setOnClickListener(new itemClick(getItem(position)));
        }
    
        @Override
        public int getItemCount() {
            return deviceList.size();
        }
    
        /***
         * 获取指定位置的元素
         * @param position
         * @return
         */
        private BluetoothDevice getItem(int position){
            return deviceList.get(position);
        }
    
        /***
         * 数据集合改变的方法
         * @param deviceList
         */
        public void notifyData(List<BluetoothDevice> deviceList){
            Log.d("CJT" , "notifyData == %%%%%%%%%%%%%% ");
            this.deviceList = deviceList ;
            this.notifyDataSetChanged();
        }
    
    
        class MyViewHolder extends RecyclerView.ViewHolder{
    
            ConstraintLayout deviceItem ; // item的单个布局
            TextView deviceName ; // 设备名称
            TextView deviceAddress; // 设备地址
    
            public MyViewHolder(View itemView) {
                super(itemView);
                deviceItem = itemView.findViewById(R.id.item_layout);
                deviceName = itemView.findViewById(R.id.item_device_name);
                deviceAddress = itemView.findViewById(R.id.item_device_address);
            }
        }
    
        /**点击事件内部类**/
        private class itemClick implements View.OnClickListener {
    
            private BluetoothDevice device ;
    
            public itemClick(BluetoothDevice item) {
                this.device = item ;
            }
    
            @Override
            public void onClick(View v) {
                // 设置返回数据
                Intent intent = new Intent();
                intent.putExtra(EXTRA_DEVICE, device);
                Log.d("CJT" , "device.getAddress() == "+device.getAddress());
                // 设置返回值并结束程序
                ((Activity)v.getContext()).setResult(Activity.RESULT_OK, intent);
                ((Activity)v.getContext()).finish();
            }
        }
    }
    

     (6)还有一个表盘布局的文件,关于自定义View,有不明白的同学可以自行去学习下,我这里就不深入讲解了。

    4.小结。

         主要的代码就是上面给大家展示的部分,由于使用的第三方的蓝牙库,这个蓝牙库呢还有一个比较有缺陷的地方就是可以扫描到经典蓝牙的设备,却没有相应的connect方法,更不能去读写了,所以后面我会再去完善连接经典蓝牙模块的部分,本次蓝牙模块我使用的是蓝牙BLE模块cc2541。

         好了,本篇就到此为止了,后面代码上传了GitHub我会跟进更新地址,另外下一篇我会主要讲解和演示单片机接收数据的部分。
     

    展开全文
  • 手机打开蓝牙可以搜索到蓝牙芯片并可以连接,连接码为1234。系统开启后数码管全0,继电器低电平,LED灯不亮。 手机连接到系统后,扫描二维码,得到一串数字后,输入该串密码,得以解锁继电器。输入密码,支持断点续...

    一.实验结果

    手机安装HC-PDA-ANDROID.apk软件后,开启系统。手机打开蓝牙可以搜索到蓝牙芯片并可以连接,连接码为1234。系统开启后数码管全0,继电器低电平,LED灯不亮。

    手机连接到系统后,扫描二维码,得到一串数字后,输入该串密码,得以解锁继电器。输入密码,支持断点续传密码,如本次输入“123”,再次输入“456”,即可完成输入“123456”。输入错误可以选择按键S5清空输入后重新输入。单片机暂时内部设置密码为“12345678”。输入密码的过程中,流水灯会展示输入密码的最后一个字符的ASCII码,用于指示传输过程中字符是否正确被接收。如果输入了错误的密码,则会返回一串错误提示消息,并将数码管清空。输入错误的字符后,流水灯全亮。

    输入正确的密码后,继电器高电平,LED构成回路后点亮,返回给手机端计费信息和成功解锁提示。数码管开始计时,每10毫秒变动一次,8个数码管两个一组,分别显示小时、分钟、秒、十毫秒。当处于计费状态时,手机端发送数据,返回无法发送的字样。按下按键S4后,停止计费,返回到手机端计时时长和本次费用,数码管清零,等待下次解锁。

    二.单片机工作原理

    51单片机有P0、P1、P2、P3四个端口。本次实验将其中P0用于数码管的显示数字,P1用于流水灯的显示,P2端口的第1位(P2^1)接在继电器的DIO端,在单片机内控制高低电平。P3端口的第0位连接蓝牙的TX端,是单片机串口的接收端,P3端口的第1位连接蓝牙的RX端,是单片机串口的发送端。按键S4在内部连接P3端口的第2位,为外部中断0。按键S5在内部连接P3端口的第3位,为外部中断1。开启定时器0,设置好计数器的初始值;外部中断0,1打开;串口中断打开,设置好波特率9600,0,0与蓝牙模块一致即可。

    串口的中断触发后,需要软件清除RI(接收中断)与TI(发送中断)值为0,SBUF存放串口接收数据或发送数据,在C语言代码中赋值相同寄存器,在物理上分为发送和接收,每次1字节缓冲。

    定时器0设置工作方式0,13位计数器,计数到8192。晶振为11.0592MHz,每个机器周期需要12个时钟周期,计数5000次,所以每次进入中断的时间为5000*12/11.0592M=0.00543s,所以每次进入中断时间为0.005秒,数码管需要每10ms进入一次,所以每次加到2的时候,数码管变动。计数器初值为(8192-5000),分别存放入TH0与TL0,高低位。

    外部中断直接设置触发方式ITx=0/1低电平触发或下降沿触发后,开启外部中断,EXx=1后,编写相应中断函数即可。

    蓝牙模块首先按住复位键上电,即进入AT指令模式,对它输入AT指令进行设置名字、串口波特率、主从回环等后,连接到单片机上即可使用。

    三.模块工作原理

    二维码扫描APP:

    通过谷歌开源ZXing库开发了一款安卓APP,并安装到手机,扫描出数据后通过蓝牙模块输入到单片机中。

    扫描后为“12345678”。


    蓝牙模块HC-05:

    TX连接单片机P3.0口,RX连接单片机P3.1口。

    在蓝牙模块连接到单片机上前,首先通过USB-TTL转接器,连接到电脑上后,通过串口调试助手调试。首先进入命令调试模式,输入AT指令,设置模块的参数。

    设置蓝牙的名称,用指令

    AT+name=”LiMou”\r\n          设置蓝牙模块名字为LiMou,方便后续查找。

    设置自动连接模式的串口波特率为9600,用指令

    AT+uart=9600,0,0\r\n             设置波特率为9600,停止位1位,无校验位。

    用于手机与单片机之间的通信者,发送密码到密码上,在接入计费系统时,向手机端发送解锁成功标志,并提供计费标准。解锁失败发送解锁失败指示。

    测试问题:

    如果串口收不到数据,换一个模块。

    如果单片机收到数据错误,调整波特率,通过串口助手调节。

    继电器模块:

           继电器电源连接VCC,GND接地,DIO连接单片机P2.1端口,继电器的模块的开闭表示单车的开闭锁。在继电器下接入一个LED的小灯,用它的亮灭来表示继电器是否上电,是否已经开锁。

           LED:

    连接通过继电器构成回路,长脚(正极)接电源,短脚(负极)接继电器常闭端,继电器公共触点引到地,构成回路,点亮小灯(在继电器DIO高电平时)。


    数码管:

           给P1口送编码即可,0-F的编码分别为0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71。

    其中P2^6为段选通,P2^7为位选通。

    用于计时模块和指示通过蓝牙传入的密码数据。

    按键:

           根据不同按键连接的端口触发不同的中断,编写对应的中断函数。在本系统中,外部中断0通过独立键盘模块按键S4实现,低电平触发,在计费状态下,停止计费,并向手机发送计费结果。

           外部中断1通过独立键盘模块按键S5实现,下降沿触发,当在非计费状态下,清空数码管显示为全0。

          

    流水灯:

    流水灯连接到单片机的P1端口,直接对P1口赋值即可,高电平为灭,低电平亮。用于指示接收数据的ASCII码和错误输入提示,全亮。


    51单片机代码:

    #include "reg51.h"
    #include <intrins.h>
    sbit lock = P2 ^ 1;
    sbit dula = P2 ^ 6;
    sbit wela = P2 ^ 7;
    
    unsigned char digit_led_pointer = 0;
    unsigned char count = 0;
    unsigned char opened = 0;
    
    unsigned char code table[] = { 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d, 0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71 };	// 数码管0-f表
    
    unsigned char led[] = { 0, 1, 2, 3, 4, 0, 0, 0 };	//存放8个数码管显示的数字
    
    
    void delay(unsigned int i)		//延迟函数
    {
    	while (--i);
    }
    
    void clear_digit(void)			//清空数码管,全变成0
    {
    	unsigned char i = 0;
    	for (; i < 8; ++i)
    		led[i] = 0;
    	digit_led_pointer = 0;
    }
    
    
    void send(unsigned char dat)	//通过串口发送到蓝牙模块上,蓝牙模块发送出去一个字节的字符
    {
    	ES = 0;
    	SBUF = dat;
    	while (!TI);
    	TI = 0;
    	ES = 1;
    }
    
    void send_str(unsigned char * str)	//通过蓝牙模块发送一个字符串
    {
    	while (*str != '\0')
    		send(*str++);
    }
    
    
    void display(unsigned char dig)		//输入密码阶段的显示,在已经输入的密码后续进行输入
    {
    	led[digit_led_pointer++] = dig;
    	if (digit_led_pointer == 8)
    	{
    		//判断如果输入密码为12345678的话,返回解锁成功
    		if (led[7] == 8 && led[6] == 7 && led[5] == 6 && led[4] == 5 && led[3] == 4 && led[2] == 3 && led[1] == 2 && led[0] == 1 && (opened == 0))
    		{
    			//开锁状态置位
    			opened = 1;
    			lock = 1;	//开锁继电器,高电平,点亮LED
    			clear_digit();
    			send_str("price: 1Yuan/min\n");
    			send_str("lock successfully!\n");
    		}
    		else
    		{
    			delay(10000000);	//输入8位密码后,延迟显示错误密码
    			clear_digit();
    			//解锁失败
    			send_str("lock fail!\n");	//向手机发送提示信息
    		}
    		digit_led_pointer = 0;
    	}
    }
    
    
    //计时模块,每次进入该函数,为10毫秒
    void time_add_10ms()
    {
    	led[7]++;
    	//100进位,每次10毫秒
    	if (led[7] == 10)
    	{
    		led[7] = 0; led[6]++;
    	}
    
    	if (led[6] == 10)
    	{
    		led[6] = 0; led[5]++;
    	}
    
    	//60进位,为秒数
    	if (led[5] == 10)
    	{
    		led[5] = 0; led[4]++;
    	}
    
    	if (led[4] == 6)
    	{
    		led[4] = 0; led[3]++;
    	}
    	//分钟数
    	if (led[3] == 10)
    	{
    		led[3] = 0; led[2]++;
    	}
    
    	if (led[2] == 10)
    	{
    		led[2] = 0; led[1]++;
    	}
    	//小时数,如果100个小时清零
    	if (led[1] == 10)
    	{
    		led[1] = 0; led[0]++;
    	}
    
    	if (led[0] == 10)
    		clear_digit();
    }
    
    //定时器0,为1号中断
    void timer0_ISR(void) interrupt 1
    {
    	if (opened == 1)
    	{
    		//晶振为11.0592MHz,每个机器周期需要12个时钟周期,计数5000次,所以每次进入中断的时间为
    		//	5000*12/11.0592M=0.00543s,每次进入add_10ms函数需要计数2次,所以每次count==2的时候,数码管显示+1
    		TL0 = (8192 - 5000) % 32;
    		TH0 = (8192 - 5000) / 32;
    		count++;
    		if (count == 2)
    		{
    			//达到10ms了
    			time_add_10ms();
    			count = 0;
    		}
    	}
    }
    
    
    
    void uart() interrupt 4
    {
    	unsigned char ky;
    
    	if (RI)
    	{
    		RI = 0;		//需要软件清除标志位
    		ky = SBUF;	//接收到的数据保存到
    		if (opened == 1)
    		{
    			send_str("can't stop, please press the button S4!\n");	//计费状态不接受输入,并返回提示信息。
    			return;
    		}
    		ES = 0;
    		P1 = ky;	//data send to Port 1,通过流水灯显示接收到的数据的ASCII码
    
    		//根据收到的数据进行异常处理或数码管输入
    		if (ky >= '0'&&ky <= '9')
    		{
    			display(ky - '0');
    		}
    		else if (ky >= 'a' && ky < 'f')
    		{
    			display((unsigned char)((ky - 'a') + 10));
    		}
    		else
    		{
    			//错误数据,流水灯全亮。
    			P1 = 0x00;
    		}
    		ES = 1;
    	}
    }
    
    void init_uart()
    {
    	TMOD = 0x25;            //定时器1工作方式2,计数器0工作方式1
    	SCON = 0x50;            //串口工作方式1
    	EA = 1;             //开总中断
    	ES = 1;             //开串口中断
    	TH1 = 0xfd;            //串口波特率9600
    	TL1 = 0xfd;
    	TR1 = 1;             //定时器1工作	
    }
    
    void timer0_init(void)
    {
    	TMOD &= 0xF0;	//计数器0方式0
    	TL0 = (8192 - 5000) % 32;	//计数5000次
    	TH0 = (8192 - 5000) / 32;
    	count = 0;
    	ET0 = 1;		//开启计时器和中断
    	TR0 = 1;		
    }
    
    void init_EX(void)
    {
    	EX0 = 1;
    	IT0 = 0;
    	EX1 = 1;		//开启外部中断 0
    	IT1 = 1;          //设置成低电平触发,1为下降沿触发
    }
    
    //把计时费用转换成字符串发送给手机端
    void num2str(int cost)
    {
    	while (1)
    	{
    		if (cost / 10 != 0)
    			send(cost / 10 + '0');
    		else
    		{
    			send(cost + '0');
    			break;
    		}
    		cost %= 10;
    	}
    }
    
    //外部中断0,S4按下停止计费
    void Ex0_IRQ(void) interrupt 0
    {
    	//不在计费状态下没反应
    	if (opened == 1)
    	{
    
    		int time;
    		opened = 0;
    		send_str("duration:");
    		send(led[0] + '0'); send(led[1] + '0'); send(':');
    		send(led[2] + '0'); send(led[3] + '0'); send(':');
    		send(led[4] + '0'); send(led[5] + '0');
    		send('\n');
    		time = ((led[0] + led[1]) * 60 + (led[2] * 10 + led[3]));
    		send_str("cost: RMB "); num2str(time + 1); send('\n');
    		lock = 0;
    		clear_digit();
    	}
    }
    
    //外部中断1,2号中断,S5按下后清空数码管
    void Ex1_IRQ(void) interrupt 2
    {
    	//不在计费状态下才有反应
    	if (opened == 0)
    	{
    		digit_led_pointer = 0;
    		clear_digit();
    	}
    }
    
    void main()
    {
    	unsigned char num = 0;
    	//初始化
    	init_uart();
    	timer0_init();
    	clear_digit();
    	init_EX();
    	lock = 0;
    	while (1)
    	{
    		//显示数码管
    		for (num = 0; num < 8; ++num)
    		{
    			wela = 1;
    			P0 = _crol_(0xfe, num);	//选中数码管,循环左移
    			wela = 0;
    			dula = 1;
    			P0 = table[led[num]];
    			delay(100);
    			dula = 0;
    		}
    	}
    }


    展开全文
  • **首先声明我用的是52单片机,板子型号为HC6800-ES V2.0;如果 非同类型,实现不了,概不认账。** 要实现这个玩法,首先得掌握串口通信的知识,不需要太多,只要知道如何接收/发送数据就行。 串行通信: 按照...
  • 本人以前常用的HC-06蓝牙模块可以通过串口将数据发送至电脑,但需要两个蓝牙模块,如下图: 由于经常带产品出差测试,得一直把上图右边那个带在身上,容易弄丢。 我就想既然笔记本上带有蓝牙,为何不直接使用...
  • 单片机学习-HC-05蓝牙模块-LCD12864显示 简介 1.硬件部分 HC-05蓝牙模块 STC12C5A60S2(51系列单片机) LCD12864 2.软件部分 Keil软件 串口调试软件 硬件部分 蓝牙模块 蓝牙HC05是主从一体的蓝牙串口模块,简单的说,...
  • 最近的项目上有需要无线模块连接手机APP,实现一些配置功能,蓝牙模块和wifi模块都有用到,蓝牙模块用的是YHD-BT421,是基于tlsr8266蓝牙芯片实现的,WIFI模块用的是ESP8266。蓝牙模块 蓝牙传输距离大约为10米,...
  • 一 简介 该文基于某款蓝牙模块的硬件设计经验总结,仅作硬件设计参考之用。 二 天线设计 2.1PIFA 天线设计 2.1.1尺寸要求 该天线是...
  • 关于HC-05蓝牙模块 蓝牙模块BT-HC05模块是一款高性能的蓝牙串口模块。 可用于各种带蓝牙功能的电脑、蓝牙主机、手机、PDA、PSP等智能终端配对。 宽波特率范围4800~1382400,并且模块 兼容单片机系统。 当主从模式...
  •    我会通过本系列文章,详细介绍如何从零开始用51单片机去实现智能小车的控制,在本系列的上一篇文章中介绍了如何让小车实现自动避障,本文作为本系列的第四篇文章,主要介绍蓝牙模块的使用,如何通过蓝牙进行...
  • 接着来介绍一下设置中某个模块的源码,本文依旧是基于Android4.42源码进行分析,分析一下蓝牙模块的实现。建议大致看一下关于Settings的剖析。 ZERO,蓝牙模块的fragment及其配置  1>,首先由Settings_headers.xml...
  • 蓝牙模块按照标准分有1.2,2.0,3.0,4.0,4.1,4.2,5.0;通常后者兼容前者产品;蓝牙模块根据应用和支持协议划分主要分为经典蓝牙模块(BT)和低功耗蓝牙模块(BLE);经典蓝牙模块(BT):泛指支持蓝牙协议在4.0...
  • 文章目录一 、模块简介二、开发工具三、蓝牙模块初始化四、单片机串口程序串口初始化主函数五、手机端操作 一 、模块简介 嵌入式蓝牙串口通讯模块(简称蓝牙模块)具有两种工作模式: 命令响应工作模式和自动连接...
  • 这是2016年底两周时间做的一个蓝牙小车,它分为上下两篇,本文是上篇。原本是发在了http://bbs.elecfans.com/ 的,不过由于我的博客都在CSDN上,因此我就把它们重新复制到这里来了。 原文地址:...
  • 51单片机蓝牙小车

    2019-12-26 22:08:00
    51单片机蓝牙小车(是我大二做的一个课程设计,小菜鸟一个,欢迎大家指正和参考。) 摘要 本次设计选择基于蓝牙遥控的多功能智能小车为对象。选用STC98C52RC单片机作为主控芯片,电机驱动采用L293N ,...
  • ****这是我写的第一篇博客,如果有问题欢迎指正。***** 之前早就做好了,但一直没有时间发表,现在就把我做的过程和...蓝牙模块主要要掌握其基本参数以及工作原理即可,附下图。 L298模块 l298内部包含4...
  • ,其操作流程和使用方法简单,一种深受广大编程小镇的喜爱今天为大家介绍一种利用它开发手机APP的实战经历 ——手机APP开发之MIT Appinventor详细实战教程(一),利用通过蓝牙控制单片机,以及实现单片机与android...
  • Android系统 蓝牙模块

    2017-03-16 16:41:04
    接着来介绍一下设置中某个模块的源码,本文依旧是基于Android4.42源码进行分析,分析一下蓝牙模块的实现。建议大致看一下关于Settings的剖析。 ZERO,蓝牙模块的fragment及其配置  1>,首先由...
1 2 3 4 5 ... 20
收藏数 847
精华内容 338
关键字:

蓝牙模块内部单片机