蓝牙传感器和单片机连接

2016-12-26 09:57:29 c_ycy 阅读数 6260
蓝牙(Bluetooth):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是作为RS232数据线的替代方案。蓝牙可连接手机、平板、电脑等设备,与单片机连接搭建无线系统的分端在各领域都得到了广泛应用。
1、端口连接
与单片机串口连接时,两者之间相互可以读写。例如51给蓝牙传递数据,即51向蓝牙写数据,蓝牙从51读取数据,那么51单片机串口写端P3.1引脚(TXD)就与蓝牙读端(RXD)相连,同样蓝牙向51传递数据时,蓝牙写端(TXD)T与51串口的读端P3.0引脚(RXD)相连,这样51单片机就可以与蓝牙相互通信。
通常用以下连接方式即可实现数据传送(两者使用同一电源)。

实现蓝牙HC-05、06与单片机的连接及与手机通信注意:只有正确连接读写端才能正常通信。
2、电平选择
一般情况下,蓝牙不能正常工作的原因出在电源这的可能比较小,大多数蓝牙模块电压范围比较大,像HC-05蓝牙模块一般在3.3~6V,单片机电源都在这个范围内。不过不排除部分3.3V蓝牙,所以在连接电源前一定按照技术手册,连接正确电源,并保证正负极不能接反。
3、蓝牙配置
设置决定了蓝牙模块自动连接工作后的角色,主角色(Master)会自动搜索配对连接。从角色(Slave)只被动接受连接,不会主动搜索。回环角色(Loopback),属于被动连接,接收远程蓝牙主设备数据并将数据原样返回给远程蓝牙主设备。如果两个HC05模块要建立连接,其中一个必须设置为主角色,另外一个可以设置为从角色,如果一个HC05模块和电脑蓝牙或者手机蓝牙通信,一般电脑或手机可以主动建立连接,所以HC05使用从角色,出厂默认也是设置为从角色的。
4、AT指令
在控制电平信号下,可以对蓝牙的一些特性参数进行查询课更改。
AT+XXX?                   //查询参数XXX
AT+XXX=mmm        //设置参数XXX为mmm
例如: 命令: AT+NAME?\r\n                //查询蓝牙名称
         返回:+NAME:ChunyuY19           //蓝牙名称为:ChunyuY19
          命令:AT+NAME=Xidian\r\n      //设置蓝牙名称为:Xidian
          返回: OK                                //返回提示符:OK
          命令:AT+PSWD?\r\n               //查询蓝牙配对密码
         返回:+PSWD:1234                   //配对密码为:1234
          命令: AT+ROLE?\r\n               //查询蓝牙模式
          返回:+ROLE:0                        //0:从角色,1:主角色,2:回环角色
注意:每行命令必须以更多AT命令\r\n结尾,更多的AT指令一般技术手册都会给出,或在网上查询。
             一般作为从角色时,不用配置
5、实现基于STC51单片机的蓝牙与手机通信
首先,给单片机载入串口通信程序,注意!!一般下载程序时单片机与蓝牙断开,避免因蓝牙占用单片机串口导致程序无法烧写。载入程序后,按照上图给出的读写连接方式连接,并给给单片机及HC-05连接合适电源,一般都用单片机开发板上电源。手机端需先在浏览器上搜索并下载“蓝牙串口调试助手”。
上电后,蓝牙指示灯一般进入快闪状态,即等待蓝牙连接(从模式),用手机搜索并连接单片机上的蓝牙,配对密码默认为1234。配对成功就可以发送数据给蓝牙,如下图,至此基于STC51单片机的蓝牙与手机通信成功。
单片机端代码连接: STC51单片机端蓝牙串口程序

2018-08-18 21:50:42 tongxin082 阅读数 17328

今天给大家介绍的是一个温湿度检测设计,基于51单片机、蓝牙模块、温湿度传感器、Android APP完成。首先先展示一下设计好的实物,接下来将从系统方案、硬件设计、软件设计这三个方面来阐述。

1.系统方案

先来看一下整体的架构图:硬件部分由STC89C52单片机、DHT11温湿度传感器、BT08蓝牙串口模块和Android手机组成。传感器将采集到的温湿度数据传送给单片机,然后单片机通过蓝牙串口模块将数据发送到手机APP,从而将温湿度在APP显示出来。在APP上可以设置温湿度告警的阈值,超过阈值将显示“偏高”或者“偏低”的相关信息。

2.硬件设计

整个设计的原理图如下所示,由单片机最小系统、蓝牙串口模块、温湿度传感器组成。传感器的DATA管脚连接单片机的P2^0口,蓝牙串口模块的RXD、TXD分别连接单片机的TXD、RXD。

蓝牙串口模块的功能是串口协议和蓝牙协议之间的相互转换,在单片机上自己编写一套蓝牙驱动代码是非常复杂的,借助这个模块我们在编写单片机代码时只需要编写串口收发的代码即可,该模块得到串口数据后会转成蓝牙数据。对于APP它接收到的是蓝牙数据,开发APP时只需要编写蓝牙相关的代码,Android封装了蓝牙相关的API,所以开发起来简单。蓝牙串口模块的引脚图如下图所示,在这个设计中用到了四个引脚,VCC、GND接5V电源和地,蓝牙模块的TXD接单片机的RXD,RXD接TXD。

DHT11温湿度传感器负责采集环境中的温湿度数据,在单片机软件设计部分会详细的介绍该传感器的使用步骤。引脚说明:
VDD 供电3.3~5.5V DC
DATA 串行数据,单总线
NC 空脚
GND 接地,电源负极

 

3.单片机软件设计

单片机程序主要是两个点,一是读取DHT11传感器的温湿度数据,二是串口通信。DHT11的官方文档写的很规范,有关于读取数据的详细步骤,文档更新也比较及时,最新的更新日期是2017年3月31号,官网的下载地址:http://www.aosong.com/products-21.html

DHT11采用单总线通信,单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。

  • 传送数据位定义

DATA 管脚用于DHT11与单片机之间的通讯和同步,采用单总线数据格式,一次传送40 位数据,高位先出。
数据格式:
8bit 湿度整数数据+ 8bit 湿度小数数据+ 8bit 温度整数数据+ 8bit 温度小数数据+ 8bit 校验位。
注:其中湿度小数部分为0。

  • 校验位数据定义

8bit 湿度整数数据 +  8bit 湿度小数数据 +  8bit 温度整数数据 +  8bit 温度小数数据 = 8bit 校验位
如果以上等式成立,则本次传感器采集的数据有效,否则无效。

先看采集数据有效的示例,接收到的40 位数据为:
0011 0101     0000 0000     0001 1000     0000 0100      0101 0001
湿度高8 位     湿度低8 位     温度高8 位     温度低8 位    校验位
计算: 0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 = 0101 0001,接收数据正确。
湿度:0011 0101(整数)=35H=53%RH 0000 0000(小数)=00H=0.0%RH =>53%RH + 0.0%RH = 53.0%RH
温度:0001 1000(整数)=18H=24℃ 0000 0100(小数)=04H=0.4℃ =>24℃ + 0.4℃ = 24.4℃

采集数据无效的示例,接收到的40 位数据为:
0011 0101     0000 0000     0001 1000    0000 0100     0100 1001
湿度高8 位    湿度低8 位     温度高8 位    温度低8 位    校验位
计算: 0011 0101 + 0000 0000 + 0001 1000 + 0000 0100 不等于0100 1001,本次接收的数据不正确,放弃,重新接收数据。

通过以上两个示例可以清楚DHT11数据格式以及数据如何去校验有效性。

  • 数据时序图

用户主机(MCU)发送一次开始信号后,DHT11 从低功耗模式转换到高速模式,待主机开始信号结束后,DHT11 发送响应信号,送出40bit 的数据,并触发一次信采集。信号发送如图所示。这里的主机是指单片机,从机是指DHT11传感器。

下面这个图表罗列了时序图相关的参考时间,在读取数据的详细步骤中会用到这些数值。

根据时序图和表中的参考时间,我们可以得出读取传感器数据的步骤。

step1:单片机输出低电平保持20ms

step2:单片机拉高电平保持13us等待DHT11传感器的低电平响应信号

step3:判断DHT11是否给出低电平响应,如果有低电平响应则进入步骤4,否则等待下一轮的尝试。

step4:通过while语句等待83us的低电平响应时间结束

step5:通过while语句等待87us的高电平响应时间结束

step6:计算温湿度数据

step7:单片机输出高电平结束一次数据采集的读取

step8:校验数据

在时序图中可以看到,数据读取是每次一位进行的,数据0位和数据1位的低电平时间是相同的,即54us。数据0位的高电平时间是24us,而数据1为的高电平时间是71us,通过高电平时间的差异我们就可以判断出是数据0还是数据1。所以单独写了一个函数用来计算数据0位和1位,由于温湿度的整数和小数部分分别是由8位表示的,我们定义该函数得到8位数据之后给出返回值。步骤6对应的函数computeData() 用来完成上述工作。我们对步骤6进行详细的描述:

step 6.1:等待54us低电平结束

step 6.2:延时30us判断高电平是否结束,因为数据0位的电平最大时长是27us,如果超过27us之后高电平结束,则为数据0位,否则为数据1位。

step 6.3:通过while语句等待高电平结束

step 6.4:通过移位和或与的方式保存一个数据位

step 6.5:循环6.1到6.4步骤8次,得到一个字节的数据

//--------------------------------
//-----湿度读取子程序 ------------
//--------------------------------
//----以下变量均为全局变量--------
//----温度高8位== temperature_H------
//----温度低8位== temperature_L------
//----湿度高8位== humidity_H-----
//----湿度低8位== humidity_L-----
//----校验 8位 == checkdata-----
//--------------------------------
void readData()
{
    U8  humidity_H_temp,humidity_L_temp,temperature_H_temp,temperature_L_temp,checkdata_temp;
    //step1:单片机输出低电平保持20ms
    P2_0=0;
    delayms(20);
    //step2:单片机拉高电平保持13us等待DHT11传感器的低电平响应信号
    P2_0=1;
    delay13us();
    //step3:判断DHT11是否给出低电平响应,如果有低电平响应则进入步骤4,否则等待下一轮的尝试。
    if(P2_0==0)
    {
        //step4:通过while语句等待83us的低电平响应时间结束
        while(P2_0==0);	
        //step5:通过while语句等待87us的高电平响应时间结束			
        while(P2_0==1);				
        //step6:计算温湿度数据
        humidity_H_temp = computeData();
        humidity_L_temp = computeData();
        temperature_H_temp = computeData();
        temperature_L_temp = computeData();
        checkdata_temp = computeData();
        //step7:单片机输出高电平结束一次数据采集的读取
        P2_0 = 1;		
        //step8:校验数据
        if(checkdata_temp = humidity_H_temp + humidity_L_temp + temperature_H_temp + temperature_L_temp)
        {
            humidity_H = humidity_H_temp;
            humidity_L = humidity_L_temp;
            temperature_H = temperature_H_temp;
            temperature_L = temperature_L_temp;
            checkdata = checkdata_temp;
        }
    }

}

/**
*根据时序计算温湿度值
*/
U8 computeData()
{
    U8 i,U8comdata;
    for(i=0; i<8; i++)
    {
        //step 6.1:等待54us低电平结束
        while(P2_0==0);
        //step 6.2:延时30us判断高电平是否结束	
        Delay_10us();					
        Delay_10us();
        Delay_10us();
        U8temp=0;
        if(P2_0==1)						
        {											
            U8temp=1;
        }
        //step 6.3:通过while语句等待高电平结束
        while(P2_0==1);
        //step 6.4:通过移位和或与的方式保存一个数据位			
        U8comdata<<=1;
        U8comdata|=U8temp;
    }
    return U8comdata;
}

温湿度数据读取完毕,接下来就是通过串口发送出去,串口发送数据的代码相对简单了,我们在主函数中对串口通信进行初始化,然后在一个while语句中每隔2s读取数据然后发送。

//----------------------------------------------
//main()功能描述:  STC89C52RC  11.0592MHz   串口发送温湿度数据,波特率 9600
//----------------------------------------------
void main()
{
    U8  i;
    TMOD = 0x20;          //定时器T1使用工作方式2
    TH1 = 253;        // 设置初值
    TL1 = 253;
    TR1 = 1;          // 开始计时
    SCON = 0x50;          //工作方式1,波特率9600bps,允许接收
    ES = 1;
    EA = 1;           // 打开所以中断
    TI = 0;
    RI = 0;
    Delay(1);         //延时100US(12M晶振)
    while(1)
    {
        //调用温湿度读取子程序
        readData();
        str[0]=humidity_H;
        str[1]=humidity_L;
        str[2]=temperature_H;
        str[3]=temperature_L;
        str[4]=checkdata;
        //发送到串口
        for(i=0; i<5; i++)
        {
            sendOneChar(str[i]);
        }
        //读取模块数据周期不易小于 2S
        delayms(2000);
    }

}

至此,单片机端的主要代码就讲解完了,可以看到核心代码是如何读取DHT11的数据。

4.手机APP软件设计

APP是用Android Studio(AS)开发的,不建议初学者学习Eclipse结合ADT(Android Eclipse Tools)插件的方式开发Android APP,这种方式已经过时并且以后会被淘汰,Google在2016年底已经停止了对ADT的更新,我之前所在的公司已经将Eclispe的代码全部迁移到AS平台了,推荐使用Google自家的AS集成开发环境。AS有很多优点,但是在使用时也有问题,AS借助gradle进行项目构建,至于为什么Google利用gradle进行Android app项目构建,读者可以自行上网搜索。gradle插件版本要和AS版本相对应,不同的开发者的gradle版本可能不同,所以当你拿到另外一个开发者的代码在自己的AS运行时时有可能会构建失败。这个现象对于国外开发者而言不是一个问题,AS可以自动去下载所需要的gradle插件版本,但是在国内,由于众所周知的原因,如果不会科学上网那么AS直接尝试下载gradle插件时会失败,会令很多初学者不知所措。在以后有时间我会单独写一篇blog来讲解如何去解决这个问题。最近听到Google要重返中国市场,如果能回归成功,对于国内的很多开发者和学术研究者而言是个好消息。

言归正传,本设计APP的代码主要分成两个部分,一是蓝牙数据的接收,二是图表显示。

4.1 APP蓝牙软件设计

蓝牙通信的三个基本步骤:搜索、配对、连接。这之后就可以进行数据传输了。

  • 权限

在蓝牙通信中需要获取Android系统的以下三个权限,如果不能给APP授予相关的权限会影响蓝牙的正常使用:
BLUETOOTH:允许配对的设备进行连接
BLUETOOTH_ADMIN:允许搜索和配对设备
ACCESS_COARSE_LOCATION:广播接收器接收BluetoothDevice.ACTION_FOUND广播需要改权限

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

在下文中还会提到在Android6.0及以上的版本中关于ACCESS_COARSE_LOCATION权限的申请。

  • 开启蓝牙

建立蓝牙通信之前需要验证是否有蓝牙设备,以及蓝牙设备是否已经开启。对于一个Android系统而言只有一个蓝牙适配器,通过getDefaultAdapter()方法可以返回其一个实例,如果返回为null,则说明该设备不支持蓝牙。

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
        // device doesn't support Bluetooth
}

接下来是检查蓝牙设备是否已经开启,如果没有开启,可以调用startActivityForResult()方法来弹出对话框让用户选择开启,这种方式不会停止当前的应用。

if (!mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
  • 搜索设备

搜索设备可以分成两部分,一是查找已经与本机配对的设备,通过getBondedDevices()方法返回已经配对的设备信息:

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
if (pariedDevices.size > 0) {
        for (BluetoothDevice device: pairedDevices) {
                String deviceName = device.getName();
                String deviceMACAddress = device.getAddress();
        }
}

二是搜索周围可用的但是还未配对的设备。
系统在发现蓝牙设备会通过广播的形式通知app,所以在搜索设备之前需要注册广播接收器来接收发现蓝牙设备的消息,在销毁Activity时注销广播接收器。

private BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceiver(Context context, Intent intent) {
                String action = intent.getAction();
                if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                        BluetoothDevie device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                }
        }
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    // Register for broadcasts when a device is discovered.
    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
    registerReceiver(mReceiver, filter);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // Don't forget to unregister the ACTION_FOUND receiver.
    unregisterReceiver(mReceiver);
}

BluetoothDevice.ACTION_FOUND广播需要ACCESS_COARSE_LOCATION权限,该权限是个危险权限,在Android 6.0及以上,除了在manifest中声明还需要在java代码中申请。获取了该权限之后,在搜索蓝牙设备时才能收到系统发出的蓝牙设备发现的广播。搜索设备调用startDiscovery()方法,当周围有可用设备时,系统会通过广播的形式通知应用。

//检查ACCESS_COARSE_LOCATION权限
                if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_COARSE_LOCATION)
                        == PackageManager.PERMISSION_GRANTED){
                    Toast.makeText(MainActivity.this,"搜索回调权限已开启",Toast.LENGTH_SHORT).show();
                    if(mBluetoothAdapter.isDiscovering()){
                        mBluetoothAdapter.cancelDiscovery();
                    }
                    mBluetoothAdapter.startDiscovery();
                }else{
                    Toast.makeText(MainActivity.this,"搜索回调权限未开启",Toast.LENGTH_SHORT).show();
                    ActivityCompat.requestPermissions(MainActivity.this,
                            new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_ACCESS_COARSE_LOCATION);
                }

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(requestCode==REQUEST_ACCESS_COARSE_LOCATION){
            if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){
                mBluetoothAdapter.startDiscovery();
                if(mBluetoothAdapter.isDiscovering()){
                    mBluetoothAdapter.cancelDiscovery();
                }
                mBluetoothAdapter.startDiscovery();

            } else {
                Toast.makeText(MainActivity.this,"action found is not granted.",Toast.LENGTH_LONG).show();
            }

        }
    }
  • 建立连接

在建立连接时需要一个UUID,UUID是用来标识不同设备的ID,对于蓝牙串口设备而言其对应的UUID是“00001101-0000-1000-8000-00805F9B34FB”。

If you are connecting to a Bluetooth serial board then try using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB
https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice.html

手机端是作为客户端与蓝牙模块进行连接的。
在蓝牙socket进行connect之前,一定要调用BluetoothAdapter的cancelDiscovery()方法。连接的第一步是通过调用BluetoothDevice的createRfcommSocketToServiceRecord(UUID)获取BluetoothSocket.第二步是调用BluetoothSocket的connect()方法发起连接。由于connect()为阻塞调用,因此该连接过程应该在主线程之外的线程中执行。在调用connect()时,应始终确保设备未在执行设备发现。如果正在进行发现操作,则会大幅降低连接尝试的速度,并增加连接失败的可能性。

String macAddr = "20:15:05:25:02:43";
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(macAddr);
UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
try {
    mSocket = device.createRfcommSocketToServiceRecord(uuid);
} catch (IOException e) {
    e.printStackTrace();
}
new Thread(){
    @Override
    public void run() {
        mBluetoothAdapter.cancelDiscovery();
        try {
            mSocket.connect();
        } catch (IOException e) {
            try {
                mSocket.close();
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }
        super.run();
    }
}.start();

确保在建立连接之前始终调用cancelDiscovery(),而且调用时无需实际检查其是否正在运行,如果确实想要执行检查,请调用isDiscovering()。

  • 发送数据

try {
     OutputStream os = mSocket.getOutputStream();
     os.write("发送的数据".getBytes());
     } catch (IOException e) {
         e.printStackTrace();
     }

系统的整个设计过程如上所述,我已经把该设计的实物挂在了淘宝上,如果想买来玩一玩,欢迎大家点击以下链接:

https://item.taobao.com/item.htm?spm=a1z10.1-c.w4004-3312521594.2.21332cf8bVg4fB&id=575320673715

物联网开发技术讨论群:

2016-05-22 13:38:22 wuermohuang 阅读数 45230

蓝牙(Bluetooth):是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的UHF无线电波)。蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是作为RS232数据线的替代方案, 蓝牙可连接多个设备,在与单片机连接使用也得到了广泛应用。

1、端口连接
与单片机串口连接时,两者之间 相互可以读写。例如51给HC-05传递数据,即51向HC-05写数据,HC-05从51读取数据,那么串口连接处51的写端P3.1引脚(TXD)就与HC-05读端(RXD)相连,反之蓝牙向51传递数据时,HC-05写端(TXD)T与51的读端P3.0引脚(RXD)相连,所以通常为以下连接方式即可实现数据传送。
注意:只有正确连接读写端才能正常通信。
端口连接
2、电平选择
一般情况下,蓝牙不能正常工作的原因出在电源这得可能比较小,大多数蓝牙模块电压范围比较大,像HC-05蓝牙模块一般在3.3~6V,单片机电源都在这个范围内。不过不排除部分3.3V蓝牙,所以在连接电源前一定按照技术手册,连接正确电源,并保证正负极不能接反。
3、蓝牙配置
设置决定了蓝牙模块自动连接工作后的角色,主角色(Master)会自动搜索配对连接,从角色(Slave)只被动接受连接,不会主动搜索,回环角色(Loopback),属于被动连接,接收远程蓝牙主设备数据并将数据原样返回给远程蓝牙主设备。如果两个HC05模块要建立连接,其中一个必须设置为主角色,另外一个可以设置为从角色或回环角色,如果一个HC05模块和电脑蓝牙或者手机蓝牙通信,一般电脑或手机可以主动建立连接,所以HC05可以使用从角色,出厂默认也是设置为从角色的。
4、AT指令
在控制电平信号下,可以对蓝牙的一些特性参数进行查询课更改。
AT+XXX? //查询参数XXX
AT+XXX=mmm //设置参数XXX为mmm
例如: 命令: AT+NAME?\r\n //查询蓝牙名称
返回:+NAME:ChunyuY19 //蓝牙名称为:ChunyuY19
命令: AT+NAME=Xidian\r\n //设置蓝牙名称为:Xidian
返回: OK //返回提示符:OK
命令: AT+PSWD?\r\n //查询蓝牙配对密码
返回:+PSWD:1234 //配对密码为:1234
命令: AT+ROLE?\r\n //查询蓝牙模式
返回:+ROLE:0 //0:从角色,1:主角色,2:回环角色
注意!!每行命令必须以更多AT命令\r\n结尾,更多的AT指令一般技术手册都会给出,活在网上查询。
5、实现基于STC51单片机的蓝牙与手机通信
首先,给单片机载入串口通信程序,注意!!一般下载程序时单片机与蓝牙断开,避免因蓝牙占用单片机串口导致程序无法烧写。载入程序后,按照上图给出的读写连接方式连接,并给给单片机及HC-05连接合适电源,一般都用单片机板子上电源。手机端需先在浏览器上搜索并下载“蓝牙串口调试助手”。
上电后,蓝牙指示灯一般进入快闪状态,即等待蓝牙连接(从模式),用手机搜索并连接单片机上的蓝牙,配对密码默认为1234。配对成功就可以发送数据给蓝牙,如下图,至此基于STC51单片机的蓝牙与手机通信成功。

因为毕业设计需要用到无线传输,第一次接触蓝牙串口通信,芯片用的HC-05。调试了一天,复制了不少例程,一直无解认为是程序问题。直到看到这篇文章才发现自己引脚就接错了……
一定记住单片机TX接蓝牙RX,单片机RX接蓝牙TX。一定记住单片机TX接蓝牙RX,单片机RX接蓝牙TX。一定记住单片机TX接蓝牙RX,单片机RX接蓝牙TX。重要的事说三次。

附基于STM32的HC-05串口通信框架代码

#include "stm32f10x.h"    
#include "stm32f10x_rcc.h"    
#include "stm32f10x_gpio.h"    
#include "stm32f10x_usart.h"   
#include "stm32f10x_crc.h"  
#include "system_stm32f10x.h"   
#include "stdio.h"   
  
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)  
  
void RCC_Configuration(void);    
void GPIO_Configuration(void);   
void USART_Configuration(void);   
void delay_ms(u16 time);  
void UART_PutChar(USART_TypeDef* USARTx, uint8_t Data);  
void UART_PutStr (USART_TypeDef* USARTx, uint8_t *str);
int Putchar(int c);
  
int main()  
{  
    SystemInit();  
    RCC_Configuration();    
    GPIO_Configuration();     
    USART_Configuration();
		GPIO_SetBits(GPIOB,GPIO_Pin_5);		
    while(1)  
    {  
      UART_PutStr(USART1, "hello world!"); 
			delay_ms(1000);			
    }   
      
}  
void RCC_Configuration(void)      
{       
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO|RCC_APB2Periph_GPIOB,ENABLE);       
}     
    
void GPIO_Configuration(void)      
{      
  GPIO_InitTypeDef GPIO_InitStructure;      
      
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;                
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;           
  GPIO_Init(GPIOA, &GPIO_InitStructure);                
      
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;           
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;        
  GPIO_Init(GPIOA, &GPIO_InitStructure);                 
       
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    
  GPIO_Init(GPIOB, &GPIO_InitStructure);     
}  
void USART_Configuration(void)
{    
       
    USART_InitTypeDef USART_InitStructure;                   
  
    USART_InitStructure.USART_BaudRate = 9600;                    
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;   
    USART_InitStructure.USART_StopBits = USART_StopBits_1;       
    USART_InitStructure.USART_Parity = USART_Parity_No;        
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; 
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;   
    USART_Init(USART1,&USART_InitStructure);                    
    USART_Cmd(USART1,ENABLE);    
}  
void delay_ms(u16 time)       
{      
  u16 i=0;      
  while(time--)       
  {      
    i=12000;      
    while(i--);      
  }      
}   
int Putchar(int c)                                             
{    
    if (c == '\n'){putchar('\r');}                                
    USART_SendData(USART1,c);                                  
		while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET){};
    return c;                                                       
}   
void UART_PutChar(USART_TypeDef* USARTx, uint8_t Data)  
{  
    USART_SendData(USARTx, Data);  
    while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET){}  
}  
void UART_PutStr (USART_TypeDef* USARTx, uint8_t *str)    
{    
    while (0 != *str)    
    {    
        UART_PutChar(USARTx, *str);    
        str++; 		
    }    
}  

程序员开源交流QQ群 792272915

2017-06-07 14:09:11 Wind_white 阅读数 31135

一.实验结果

手机安装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;
		}
	}
}


2014-04-10 15:47:58 gaixm 阅读数 9562

        概述:

           手机端打开编写的蓝牙通信软件,与单片机端蓝牙串口模块连接,而后,手机通过蓝牙发送读数据命令到单片机,单片机开始读取传感器信息,

将采集到得传感器信息通过蓝牙发送到手机端,手机端软件接收到后,显示。

       整体图:

        

       

      焊接板图:

     



        本项目涉及四个部分。

   一、手机端软件

   二、单片机端编程

   三、外设电路设计

   四、手机与单片机通信

     下面对四个部分进一步叙述。

  1、手机端软件

      手机端软件为安卓软件,只需要编写一个普通的蓝牙串口调试软件即可。但在编写手机端按安卓软件时,我利用一年前做安卓手机通

过蓝牙远程控制指纹识别器的源码改进,但却始终不能接收到蓝牙串口模块发送的数据。也就是说,手机可以给下位机发送信息,下位机成功接收,

但是却接收不到下位机上传的信息。我很是疑惑,我以为是蓝牙串口模块的问题,于是换了一个,结果还是一样。我一直坚信去年的源码没有问题,

但是却查不出原因在哪里。于是,我在相应的安卓蓝牙编程的书本上,找到讲解蓝牙部分的章节,按照步骤重新编写带代码,结果双向通信可以调通。

先前的问题正在研究之中,下面是正常运行源码中的部分代码:

  (1)蓝牙连接代码

 //接收活动结果,响应startActivityForResult()
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    	switch(requestCode){
    	case REQUEST_CONNECT_DEVICE:     //连接结果,由DeviceListActivity设置返回
    		// 响应返回结果
            if (resultCode == Activity.RESULT_OK) {   //连接成功,由DeviceListActivity设置返回
                // MAC地址,由DeviceListActivity设置返回
                String address = data.getExtras()
                                     .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
                // 得到蓝牙设备句柄      
                _device = _bluetooth.getRemoteDevice(address);
 
                // 用服务号得到socket
                try{
                	_socket = _device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
                }catch(IOException e){
                	Toast.makeText(this, "连接失败!", Toast.LENGTH_SHORT).show();
                }
                //连接socket
            	Button btn = (Button) findViewById(R.id.Button03);
                try{
                	_socket.connect();
                	Toast.makeText(this, "连接"+_device.getName()+"成功!", Toast.LENGTH_SHORT).show();
                	btn.setText("断开");
                }catch(IOException e){
                	try{
                		Toast.makeText(this, "连接失败!", Toast.LENGTH_SHORT).show();
                		_socket.close();
                		_socket = null;
                	}catch(IOException ee){
                		Toast.makeText(this, "连接失败!", Toast.LENGTH_SHORT).show();
                	}
                	
                	return;
                }
                
                //打开接收线程
                try{
            		is = _socket.getInputStream();   //得到蓝牙数据输入流
            		}catch(IOException e){
            			Toast.makeText(this, "接收数据失败!", Toast.LENGTH_SHORT).show();
            			return;
            		}
            		if(bThread==false){
            			ReadThread.start();
            			bThread=true;
            		}else{
            			bRun = true;
            		}
            }
    		break;
    	default:break;
    	}
    }
    
 
(2)手机蓝牙发送数据代码
 //发送数据线程
    public class SendThread extends Thread{
    	public void run(){
    		int i=0;
        	int n=0;
        	try{
        		OutputStream os = _socket.getOutputStream();   //蓝牙连接输出流
        		byte[] bos = edit0.getText().toString().getBytes();
        		for(i=0;i

  (3)手机蓝牙接收数据代码
//接收数据线程
    Thread ReadThread=new Thread(){
    	
    	public void run(){
    		int num = 0;
    		byte[] buffer = new byte[1024];
    		byte[] buffer_new = new byte[1024];
    		int i = 0;
    		int n = 0;
    		bRun = true;
    		//接收线程
    		while(true){
    			try{
    				while(is.available()==0){
    					while(bRun == false){}
    				}
    				while(true){
    					num = is.read(buffer);         //读入数据
    					n=0;
    					
    					String s0 = new String(buffer,0,num);
    					fmsg+=s0;    //保存收到数据
    					for(i=0;i

   
  2、单片机端
       单片机采用Arduino开发板,因其简单易学。
       单片机端的代码比较简单,是一些对应的传感器采集数据代码和串口通讯代码。

  3、外设焊接。
    外设有两个传感器,一个蓝牙串口模块。
    蓝牙串口模块负责蓝牙通信,传感器负责采集信息。
   
  4、手机与单片机通信
    首先,约定一个命令 符,当单片机端接收到手机端发送的命令符时,即开始采集传感器信息,将采集到得信息进行加工,然后传给
安卓手机。安卓手机接收数据后,随即显示出来。

   做成此项目用了三天时间,其中两天时间纠结与蓝牙单向通信问题,一直没有眉目。
剩下一天用半天调通手机端蓝牙串口调试软件,半天焊接电路板,写Arduino端程序,连接布线等。