用自己的手机控制自己的台灯,是不是想想就挺有趣的! 以后就可以在床上,用手机来控制台灯的开关,是不是很酷!博主以前也有过这种想法,然后就尝试做了一下。为了让有和我一样想法的同学少走点弯路,就写了这篇博客,与诸君共勉。
国际惯例先上效果图
先讲一下整体思路哈!手机肯定不能直接控制台灯的,需要一个中间物来协调,在这里我用的是51单片机(如果大家不知道也没关系,下面我还会说的)。接下来就是具体怎么控制的,其实原理挺简单的。1.手机通过蓝牙来与单片机通信,因而单片机需要外接一个蓝牙模块(我用的是hc-05 主从一体 蓝牙模块)。大家千万不要被外接给吓到了,外接模块一点都不难的,就只要去淘宝卖相应的模块然后用杜邦线(不知道的可以把它当做导线来理解)和51单片机连起来就好了。到这里手机已经可以和51单片机通信了,也就是说手机可以给单片机发送“开灯”和“关灯”的消息了。2.接下来就要解决当51单片机接收到”开灯“和”关灯“的消息后,该怎么控制台灯实际的开关?这个时候我们就需要一个继电器(也是单片机的外接模块),关于继电器我们可以看下面的图片。继电器一共有三个输出端口(常开端 公共端 常闭端)。事先申明,我们可以通过单片机控制继电器的公共端是和常开端
连通,还是和常闭端连通。现在我们只需要剪断台灯的一根电线,将电线的两头分别和继电器的常开端和公共端连接起来即可。通过单片机控制继电器的公共端和常开端连接时台灯打开,反之台灯关闭。大致原理就是这样,我们来梳理一下整个流程。首先手机通过蓝牙和单片机的蓝牙模块建立通信,当手机发送一个打开台灯信号时,单片机收到相应的信号并控制继电器的公共端指向常开端,台灯亮起。
next~就是具体实现了。一共分为两大部分,分别是Android端和单片机端,先从Android端开始说起。
Android端其实就是一个简单的蓝牙通信,Android端只需要通过蓝牙向单片机的蓝牙模块发送开关对应的消息即可(我是用0xff表示打开台灯,0x00关闭台灯)。先来看看工程的总体结构以及软件界面。
其中BluetoothTool类是一个蓝牙工具类,里面有关于蓝牙的连接以及发送接收数据功能。IUpdateUI是一个接口,用来在 BlurtoothTool中更新主界面的设备列表以及log。MainAty就是主界面的Activity。activity_main.xml和layout_lv_devices_item.xml不用多说了吧,就是一些界面有关的。关于Android端的解析以贴代码为主,因为代码中我都有详细的解释,比较重要的我会在博客中用文字再次解释的。
1.MainAty:
获取蓝牙适配器,可以通过蓝牙适配器获取蓝牙设备的信息。
[java] view plain copy
1. /**
2. * 获得默认的蓝牙适配器
3. */
4. private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
如果手机没打开蓝牙,则界面跳转到打开蓝牙界面。
[java] view plain copy
1. @Override
2. protected void onStart() {
3. super.onStart();
4.
5. /** 判断蓝牙是否可用,不可用时请求打开*/
6. if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
7. new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
8. startActivity(intent);
9. }
10. }
通过蓝牙适配器获取之前匹配过的蓝牙设备信息(如单片机的蓝牙设备),所以第一次使用的时候,先用手机自带的蓝牙匹配成功一次哈!蓝牙设备中一个比较重要的信息就是设备地址-如98:D3:33:80:83:05就是一个蓝牙设备地址,唯一标示。
[java] view plain copy
1. /** 获取以前匹配过的蓝牙设备*/
2. null;
3. if (mBluetoothAdapter != null)
4. devices = mBluetoothAdapter.getBondedDevices();
5. else
6. this, "该设备不支持蓝牙功能 ", Toast.LENGTH_SHORT).show();
7.
8. if (devices != null && devices.size() > 0) {
9. data.clear();
10. for (BluetoothDevice device : devices) {
11. new HashMap<>();
12. "lv_left_icon", R.drawable.lv_left_icon);
13. "lv_address", device.getAddress());
14. "lv_right_icon", R.drawable.lv_right_white);
15. data.add(map);
16. }
17. else {
18. new HashMap<>();
19. "lv_left_icon", R.drawable.lv_left_icon);
20. "lv_address", "没有已经匹配的设备");
21. "lv_right_icon", R.drawable.lv_right_white);
22. data.add(map);
23. "没有已经匹配的设备" + "\r\n");
24. }
25.
26. simpleAdapter.notifyDataSetChanged();
连接指定的蓝牙:通过调用BluetoothTool连接蓝牙,我们传入了设备的地址"(String) data.get(0).get("lv_address")"以及连接类型 BluetoothTool.ServiceOrClient.CLIENT(这里我们是以客户端的形式连接,也就是单片机的蓝牙当作客户端 )。之后设置了BluetoothTool的更新UI接口,并在MainAty中具体实现。
[java] view plain copy
1. builder.setPositiveButton("连接", new DialogInterface.OnClickListener() {
2. @Override
3. public void onClick(DialogInterface dialogInterface, int i) {
4. new BluetoothTool((String) data.get(0).get("lv_address"),
5. BluetoothTool.ServiceOrClient.CLIENT);
6. new IUpdateUI() {
7. @Override
8. public void updateListViewDevices() {
9. for (int i = 0; i < data.size(); i++) {
10. if (i == index) {
11. "lv_right_icon", R.drawable.checked);
12. continue;
13. }
14. "lv_right_icon", R.drawable.lv_right_white);
15. }
16.
17. simpleAdapter.notifyDataSetChanged();
18. }
19.
20. @Override
21. public void updateLog(String msg) {
22. "\r\n" + msg);
23. }
24. });
25.
26. }
27.
28.
29. });
可以看到打开台灯按钮的点击事件,只是调用了BluetoothTool的发送功能向单片机蓝牙发送了一个ff消息(具体发送时将ff转化成16进制0xff,因而在单片机端我们会收到一个0xff的数据)。关闭事件同上。
[java] view plain copy
1. @Override
2. public void onClick(View view) {
3. int id = view.getId();
4.
5. switch (id) {
6. case R.id.id_btn_open:
7. if (mBluetoothTool != null) {
8. "ff");
9. else
10. this, "蓝牙未连接...", Toast.LENGTH_SHORT).show();
11. break;
12. case R.id.id_btn_close:
13. if (mBluetoothTool != null) {
14. "00");
15. else
16. this, "蓝牙未连接...", Toast.LENGTH_SHORT).show();
17. break;
18. default:
19. break;
20. }
21. }
2.BluetoothTool
[java] view plain copy
1. /**
2. * 蓝牙设备地址
3. */
4. private String mBluetoothAddress = null;
通过传入的蓝牙地址获取相应的蓝牙设备。
[java] view plain copy
1. /**
2. * 蓝牙设备
3. */
4. private BluetoothDevice mDevice = null;
[java] view plain copy
1. mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mBluetoothAddress);
这里我们都是以客户端的形式连接的。(也就是单片机上的蓝牙是客户端)。
[java] view plain copy
1. /**
2. * 枚举 表示是客户端还是服务端
3. */
4. public static enum ServiceOrClient {
5. NONE, SERVICE, CLIENT
6. }
7. private ServiceOrClient mServiceOrClient = ServiceOrClient.NONE;
Handler 用来更新UI
[java] view plain copy
1. private Handler mHandler = new Handler() {
2. @Override
3. public void handleMessage(Message msg) {
4. switch (msg.what) {
5. case MSG_UPDATE_LISTVIEW:
6. if (iUpdateUI != null)
7. iUpdateUI.updateListViewDevices();
8. break;
9. case MSG_UPDATE_LOG:
10. if(iUpdateUI!=null)
11. "");
12. break;
13. }
14. }
15. };
单片机的蓝牙与手机端的蓝牙通信的socket,说明一下蓝牙通信其实也是基于socket通信的。
[java] view plain copy
1. /**
2. * 蓝牙客户端socket
3. */
4. private BluetoothSocket mClientSocket = null;
以客户端身份连接的线程,我们来看看具体实现。
[java] view plain copy
1. /**
2. * 客户端线程
3. */
4. private ClientThread mClientThread = null;
通过蓝牙设备获取相应的socket,之后单片机的蓝牙和手机的蓝牙通信都是通过这个socket。其中在蓝牙中,每个服务和服务属性都唯一地由"全球唯一标识符" (UUID)来校验。而且这个UUID的值必须是00001101-0000-1000-8000-00805F9B34FB,这个是android的API上面说明的,用于普通蓝牙适配器和android手机蓝牙模块连接的。获取之后通过socket的connect进行连接,连接成功之后开启读取数据的线程。
[java] view plain copy
1. /**
2. * 客户端线程
3. */
4. private class ClientThread extends Thread {
5. @Override
6. public void run() {
7. super.run();
8. try {
9. /** 客户端通过服务端的UUID与之连接*/
10. mClientSocket = mDevice.createRfcommSocketToServiceRecord(
11. "00001101-0000-1000-8000-00805F9B34FB"));
12.
13. null, MSG_UPDATE_LOG);
14. "正在连接。。。";
15. mHandler.sendMessage(msg);
16.
17. /** 连接*/
18. mClientSocket.connect();
19.
20.
21. null, MSG_UPDATE_LOG);
22. "连接成功";
23. mHandler.sendMessage(msg);
24.
25.
26. null, MSG_UPDATE_LISTVIEW);
27. mHandler.sendMessage(msg);
28.
29. /** 接收数据*/
30. new ReadThread();
31. mReadThread.start();
32.
33. catch (IOException e) {
34. e.printStackTrace();
35.
36. null, MSG_UPDATE_LOG);
37. "连接失败";
38. mHandler.sendMessage(msg);
39. }
40. }
41. }
可以看到线程一直在查看有没有数据,如果有的话就接受,并根据接收到的数据进行相应的显示。有一点要先说一下,就是如果手机成功发送了一个开灯命令给单片机,单片机收到之后成功控制继电器将台灯打开之后,单片机会回发一个消息0xff给手机。因此手机端只要收到0xff这个消息,就知道台灯打开成功了手机就可以显示台灯成功开启。
[java] view plain copy
1. /**
2. * 读取数据线程
3. */
4. private class ReadThread extends Thread {
5. @Override
6. public void run() {
7. super.run();
8.
9. byte[] buffer = new byte[1024];
10. int bytes;
11. null;
12.
13. try {
14. in = mClientSocket.getInputStream();
15.
16. while (true) {
17. if ((bytes = in.read(buffer)) > 0) {
18. byte[] buf_data = new byte[bytes];
19. for (int i = 0; i < bytes; i++) {
20. buf_data[i] = buffer[i];
21.
22.
23. int j = buffer[i];
24. 0xff;
25. String str = Integer.toHexString(j);
26. if ("ff".equals(str)) {
27. null, MSG_UPDATE_LOG);
28. "台灯打开";
29. mHandler.sendMessage(msg);
30. else if ("0".equals(str)) {//注意不能用00,因为0x00实际的值是0
31.
32. null, MSG_UPDATE_LOG);
33. "台灯关闭";
34. mHandler.sendMessage(msg);
35. else {
36. null, MSG_UPDATE_LOG);
37. "err...";
38. mHandler.sendMessage(msg);
39. }
40.
41. }
42. }
43. }
44.
45. catch (IOException e) {
46. e.printStackTrace();
47.
48. null, MSG_UPDATE_LOG);
49. "连接数据失败";
50. mHandler.sendMessage(msg);
51. finally {
52. if (in != null)
53. try {
54. in.close();
55. catch (IOException e) {
56. e.printStackTrace();
57. }
58. }
59.
60. }
61. }
向单片机发送数据,通过socket获取相应的输出流,然后将要发送的字符串转化成16进制发送。
[java] view plain copy
1. /**
2. * 发送数据
3. *
4. * @param str
5. */
6. public void sendData(String str) {
7. if (mClientSocket == null) {
8. "sendData: 没有连接。。。");
9.
10. null, MSG_UPDATE_LOG);
11. "没有连接";
12. mHandler.sendMessage(msg);
13. return;
14. }
15.
16. null;
17. try {
18. out = mClientSocket.getOutputStream();
19. out.write(getHexBytes(str));
20. catch (IOException e) {
21. e.printStackTrace();
22. null, MSG_UPDATE_LOG);
23. "发送失败";
24. mHandler.sendMessage(msg);
25.
26. }
27. }
连接蓝牙,就是启动客户端连接线程。
[java] view plain copy
1. /**
2. * 连接蓝牙
3. */
4. private void connect() {
5. if (mIsConnected == true) {
6. "connect: 已经连接");
7. null, MSG_UPDATE_LOG);
8. "已经连接";
9. mHandler.sendMessage(msg);
10. return;
11. }
12.
13. /** 客户端*/
14. if (mServiceOrClient == ServiceOrClient.CLIENT) {
15. if (!mBluetoothAddress.equals("null")) {
16.
17. mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mBluetoothAddress);
18.
19. new ClientThread();
20. mClientThread.start();
21. true;
22. else Log.d(TAG, "connect: address is null");
23. }
24.
25. }
Android端项目github地址:https://github.com/973927190/TableLamp
二.单片机端:
说具体实现之前,先要说一下用到的设备。
1.51单片机开发板
2.杜邦线(公对母,母对母,公对公)
3.继电器(1路继电器模块带光耦隔离 支持高低电平触发 5V 一路) 我的台灯是usb接口的电压挺小的,大家也用这种台灯做这个吧。
4.蓝牙模块(HC-05 主从机一体蓝牙模块 无线蓝牙串口透传模块 无线模块)
哈哈 先给大家看一看单片机方面的代码 大家看了之后肯定就会有动力写了!
[cpp] view plain copy
1. #include <reg52.h>
2. unsigned char flage,temp;
3. sbit p33=P3^3;//单片机P3.3口
4.
5. void main()
6. {
7. //设置定时器1为工作方式2
8. // 波特率=9600时定时初值为FDH
9. //装载TH1
10. //装载TL1
11. //启动T1(定时器1),开始计数
12. //允许串行接收
13. //SM0=0 SM1=1 为工作方式2
14. SM0=0;
15. SM1=1;
16. //开启总中断
17. //允许串口产生中断
18.
19. while(1)
20. {
21. if(flage==1)
22. {
23. //不允许串口产生中断 保证此次操作安全
24. flage=0;
25. //发送数据
26. while(!TI);
27. //取消此次中断申请
28. //允许串口产生中断
29. }
30. }
31. }
32.
33. void interrupt4() interrupt 4//4号中断 处理函数
34. {
35. //取消此次中断申请
36. if(SBUF==0xff)//开灯
37. //单片机P33口置为高电平
38. else if(SBUF==0x00)//关闭
39. //单片机P33口置为低电平
40. temp=SBUF;
41. flage=1;
42. }
是不是看起来感觉挺少挺简单的,没错这就是单片机方面的所有代码。个人感觉单片机方面代码其实问题不是很大的,主要是硬件方面的问题,所以代码先放一放,我们先来说说具体器件的问题。
1.51单片机
等价于逻辑“1”)也可以输出低电压(等价于逻辑“0”)。补充一下电平信号的概念,TTL电平信号被利用的最多是因为通常数据表示采用二进制规定,+5V等价于逻辑“1”,0V等价于逻辑“0”,这被称做TTL(晶体管-晶体管逻辑电平)信号系统,这是计算机处理器控制的设备内部各部分之间通信的标准技术。个人认为单片机最重要的一个特性就是我们可以往单片机中写入自己的程序(C++/C/汇编 这个过程也叫做烧写程序),通过自己的程序控制单片机各个引脚的输出电压,然后在通过引脚的输出电压来控制相应的外接模块。外接模块就是将单片机的引脚和模块的引脚连接起来,单片机引脚的输出电压当做模块引脚的输入电压。如果之前没有接触过单片机开发的,博主强烈建议去看看“郭天祥十天学会单片机的视频教程”。如果你去淘宝买了单片机开发板到时会有一张光盘里面有很多的资料 教学视频 开发工具 以及配套开发板的结构图,郭天祥十天学会单片机的视频教程光盘里也有的。博主我将单片机有关的资料全部打包上传了 http://pan.baidu.com/s/1hrYwv1U
图一 图二 图三
2.HC-05主从一体蓝牙模块
HC-05主从一体蓝牙模块一共有6个引脚,我们只需要关注VCC,GND,TXD,RXD 这四个引脚。VCC:电源端,接单片机的VCC(高电平)。GND:接地端,接接单片机的GND端(低电平)。TXD:发送端,一般表示为自己的发送端,正常通信必须接另一个设备的RXD(我的单片机P30口是RX端)。RXD:接收端,一般表示为自己的接收端,正常通信必须接另一个设备的TXD(我的单片机P31口是TX端)。led指示蓝牙连接状态,快闪表示没有蓝牙连接,慢闪表示进入AT模式,双闪表示蓝牙已连接并打开了端口。注意! 不要将电源接到信号脚上,会直接烧坏。有可能你们的单片机RXD端,TXD端和我的不一样,这个就要去看光盘里的开发板结构图了,里面有画出来的。这里我想提醒一下大家,单片机烧写程序也是通过RXD端和TXD端。所以在烧写的时候要确保RXD端和TXD端没有外接其他模块,如果你接了蓝牙就会烧写失败。
“郭天祥十天学会单片机的视频教程”里面有相应的教学。在这里我稍微解释一下波特率,波特率,可以通俗的理解为一个设备在一秒钟内发送(或接收)了多少码元的数据。为了在彼此之间通讯,蓝牙收发端必须使用相同的波特率进行操作。如果将接收方的波特率设置为高于发送方的波特率,则接收数据时会出现错误。
[cpp] view plain copy
1. TMOD=0x20;//设置定时器1为工作方式2
2. // 波特率=9600时定时初值为FDH
3. TH1=0xfd;//装载TH1
4. TL1=0xfd;//装载TL1
5. TR1=1;//启动T1(定时器1),开始计数
6. REN=1;//允许串行接收
7. //SM0=0 SM1=1 为工作方式2
8. SM0=0;
9. SM1=1;
10. EA=1;//开启总中断
11. ES=1;//允许串口产生中断
蓝牙模块收到数据会触发4号中断,程序进入4号中断处理函数。我们只要重写中断函数如下。51单片机有两个物理上独立的接收 发送缓冲器SBUF,它们占用同一地址99H。 在中断处理函数中通SBUF存储着蓝牙模块接收到的数据。将其和我们自己定义的‘开’和‘关’进行比较,如果接收到的数据是0xff就将pP33口置为高电平(打开台灯),反之接收到的数据是0x00就将pP33口置为低电平(关闭台灯)。单片机的P33口又和继电器的控制端口连接(这个我们之后会具体分析的)。
[cpp] view plain copy
1. void interrupt4() interrupt 4//4号中断 处理函数
2. {
3. //取消此次中断申请
4. if(SBUF==0xff)//开灯
5. //单片机P33口置为高电平
6. else if(SBUF==0x00)//关闭
7. //单片机P33口置为低电平
8. temp=SBUF;
9. flage=1;
10. }
在中断函数中,处理完所有操作之后都会将flage置为1。在main函数中有一个无限循环,其中如果flage=1的话 就通过SBUF发送数据给手机端的蓝牙。发送的temp就是收到之前接收到的数据,还记得在之前分析Android端BluetoothTool的接收函数的时候,有说到如果手机成功发送了一个开灯命令给单片机,单片机收到之后成功控制继电器将台灯打开之后,单片机会回发一个消息0xff给手机。因此手机端只要收到0xff这个消息,就知道台灯打开成功了。具体发送过程就是在下面的代码中完成的,至于代码中ES TI这些都是和串口编程有关的,个人建议去看看视频教学比较好。
[cpp] view plain copy
1. while(1)
2. {
3. if(flage==1)
4. {
5. //不允许串口产生中断 保证此次操作安全
6. flage=0;
7. //发送数据
8. while(!TI);
9. //取消此次中断申请
10. //允许串口产生中断
11. }
12. }
3.继电器(1路继电器模块带光耦隔离 支持高低电平触发 5V 一路)
继电器模块接口有1、DC+:接电源正极(电压按继电器要求,有5V.9V.12V和24V选择)2、DC-:接电源负极3、IN:可以高或低电平控制继电器吸合。 IN端是用来控制继电器公共端是和常闭端连接还是和常开端连接用的,如果IN端是高电平那么继电器的公共端就和常闭端连接,反之公共端和常开端连接。我是将IN端和单片机的P33口连接,通过改变单片机P33端上的电平,从而改变继电器IN端的电平,达到控制继电器开合的效果。这里非常有必要说明一点,就是继电器的IN端和单片机的哪个口连的问题。不是所有的端口都可以连得,要接有上拉电阻的端口才可以,因为如果没有接上拉电阻的端口就算在代码中将该端口置为1,实际中该端口电压达不到高电压,只有接有外接电阻端口的电压才能达到高电压。至于怎么判断单片机端口是不是有接外接电阻有两种方法,一看单片机(开发板)的结构图找到接有外接电阻的端口,二是自己一个一个试过去。如代码所示,打开台灯的时候将P33口置为1(1是高电平 0是低电平)。
继电器输出端:1、NO: 继电器常开接口,继电器吸合前悬空,吸合后与COM短接 2、COM:继电器公用接口 3、NC: 继电器常闭接口,继电器吸合前与COM短接,吸合后悬空
[cpp] view plain copy
1. if(SBUF==0xff)//开灯
2. //单片机P33口置为高电平
3. else if(SBUF==0x00)//关闭
4. //单片机P33口置为低电平
4.台灯
继电器常开接口) COM(继电器公用接口)连接就好。这里要注意,台灯要是那种USB接口的,USB接口的有降压器会将220V降下来的。千万不要用那种不是USB的,那种电压太高太危险了,而且继电器也承受不了这么大的电压,要买相应的继电器。 切记!
5.单片机代码
[cpp] view plain copy
1. #include <reg52.h>
2. unsigned char flage,temp;
3. sbit p33=P3^3;//单片机P3.3口
4.
5. void main()
6. {
7. //设置定时器1为工作方式2
8. // 波特率=9600时定时初值为FDH
9. //装载TH1
10. //装载TL1
11. //启动T1(定时器1),开始计数
12. //允许串行接收
13. //SM0=0 SM1=1 为工作方式2
14. SM0=0;
15. SM1=1;
16. //开启总中断
17. //允许串口产生中断
18.
19. while(1)
20. {
21. if(flage==1)
22. {
23. //不允许串口产生中断 保证此次操作安全
24. flage=0;
25. //发送数据
26. while(!TI);
27. //取消此次中断申请
28. //允许串口产生中断
29. }
30. }
31. }
32.
33. void interrupt4() interrupt 4//4号中断 处理函数
34. {
35. //取消此次中断申请
36. if(SBUF==0xff)//开灯
37. //单片机P33口置为高电平
38. else if(SBUF==0x00)//关闭
39. //单片机P33口置为低电平
40. temp=SBUF;
41. flage=1;
42. }
到这里手机控制台灯的开关有关内容都已经介绍完毕,大家不妨自己动手做一个,挺有趣的。
Android端项目github地址:https://github.com/973927190/TableLamp
单片机端项目:http://pan.baidu.com/s/1i4P87dj
如果手机控制台灯开关的做出来了,大家也可以做做手机控制台灯暗亮的,可以用电阻来做的哈,