(1)安卓手机与蓝牙模块联合调试(一)—— 蓝牙模块的串口通讯
(2)安卓手机与蓝牙模块联合调试(二)—— 单片机蓝牙控制LED灯亮灭(上)
(3)安卓手机与蓝牙模块联合调试(三)—— 单片机蓝牙控制LED灯亮灭(下)
(4)安卓手机与蓝牙模块联合调试(四)—— 单片机数据上传至蓝牙(STC89C52 + DS18b20)
(5)安卓手机与蓝牙模块联合调试(五)-- 编写自己的蓝牙控制界面控制单片机(上篇,Android 代码实现)
本教程的项目地址:1989Jiangtao/BluetoothSCM: 安卓手机通过蓝牙与单片机通信-发送指令/接收数据
接着上篇继续,本篇主要是完善单片机端的代码部分。废话不多说,开始飙车了。
(1)单片机端的代码主要是在之前的代码基础上做了修改,多增加了几条指令。
/****************************************
** 蓝牙串口接收数据
**
** 作者:江涛
** 时间:2018/08/31
** 描述:串口发送数据兼用OLED显示
****************************************/
#include "STC89C5xRC_RDP.h"
#include "string.h" // 要使用字符串对比函数,需要引入该头文件
#include "OLED.h" // OLED显示屏头文件
#include "DS18b20.h"
// 定义系统时钟和串口波特率
#define FOSC 11059200L // 系统时钟
#define BAUD 9600 // 串口波特率
/******变量声明*********/
char RECEIVED_CMD[10] ; // 暂定为10字节的指令
char RECEIVED_INDEX ; // 数组指示索引,当接收到一个数据之后,索引会跟随增加
unsigned char flag = 0 ; // 数据接收的标志位
unsigned char power_flag = 0 ; // 电源开关的标志位
/******命令常量*******/
code const char* LED_ON = "ON\r\n" ; // 电源开指令
code const char* LED_OFF = "OFF\r\n" ; // 电源关指令
code const char* LED_01_ON = "L1ON\r\n" ; // 灯组01开指令
code const char* LED_01_OFF = "L1OFF\r\n" ; // 灯组01关指令
code const char* LED_02_ON = "L2ON\r\n" ; // 灯组02开指令
code const char* LED_02_OFF = "L2OFF\r\n" ; // 灯组02关指令
code const char* FAN_ON = "FANON\r\n" ; // 风扇开指令,使用RGB灯循环来模拟风扇工作
code const char* FAN_OFF = "FANOFF\r\n" ; // 风扇关指令
code const char* FAILD = "power_off\r\n" ; // 返回失败原因,电源关闭了
extern unsigned int tvalue; //温度值
extern unsigned char tflag; //温度正负标志
unsigned char disdata[7]; // 温度数据,使用8字节数组来存储
char color_table[8][3] = { // 颜色表
{0,0,0},{0,0,1},{0,1,0},{0,1,1},{1,0,0},{1,0,1},{1,1,0},{1,1,1}
};
/*******函数声明*********/
void Init_UART(); // 初始化串口
void UART_SendData(char dat); // 串口发送数据
void UART_SendStr(char* str); // 串口发送字符串
void RGB_Display(int index); // RGB灯显示
//void split(char str[],char delims[]); // 字符串截取函数
void ds1820disp(); // 温度显示
void test_Fan(char flag); // 模拟测试风扇运行
void Delay1ms(); //@11.0592MHz
/***********端口定义*************/
sbit LED_R = P0^0 ;
sbit LED_G = P0^1 ;
sbit LED_B = P0^2 ;
/*******程序入口*********/
void main()
{
unsigned int temperature , old ; // 保存温度数值
Init_UART(); // 串口初始化
LCD_Init(); // OLED 初始化
LCD_CLS(); // 清屏
LCD_P8x16Str(0 , 0 , "TEMP:"); // 温度开始位置
temperature = ReadTemperature();
old = temperature ;
ds1820disp(); // 显示温度
UART_SendStr(disdata); // 向串口发送数据
LCD_P8x16Str(5*8 , 0 , disdata); // 显示温度
while(1)
{
temperature=ReadTemperature(); // 读取一次新的温度
if (temperature != old )
{
old = temperature;
ds1820disp(); // 显示温度
UART_SendStr(disdata); // 向串口发送数据
LCD_P8x16Str(5*8 , 0 , disdata); // 显示温度
}
if(flag) // 接收数据完毕一次,就会进入中断一次
{
flag = 0 ; // 将标志位还原,使得串口又可以重新接收数据
if(strcmp(RECEIVED_CMD , LED_ON) == 0)
{
P2 = 0xFF ; // P2口全亮
power_flag = 1 ; // 标志电源打开
}
else if(strcmp(RECEIVED_CMD , LED_OFF) == 0)
{
P2 = 0x00 ; // P2口全灭
power_flag = 0 ;// 标志电源关闭
}
else if(strcmp(RECEIVED_CMD , LED_01_ON) == 0)
{
if(power_flag) // 如果电源开关是关闭的,就不执行以下操作
P2 = 0x55 ; // 01010101 为1位置的灯是亮着的
else
UART_SendStr(FAILD); // 向串口发送失败原因
}
else if(strcmp(RECEIVED_CMD , LED_01_OFF) == 0)
{
P2 = P2^0x55 ; // P2口01010101相应位置的灯要全灭,所以使用异或操作
}
else if(strcmp(RECEIVED_CMD , LED_02_ON) == 0)
{
if(power_flag) // 如果电源开关是关闭的,就不执行以下操作
P2 = 0xAA ; // 10101010 为1位置的灯是亮着的
else
UART_SendStr(FAILD); // 向串口发送失败原因
}
else if(strcmp(RECEIVED_CMD , LED_02_OFF) == 0)
{
P2 = P2^0xAA ; // P2口10101010相应位置的灯要全灭,所以使用异或操作
}
else if(strcmp(RECEIVED_CMD , FAN_ON) == 0)
{
test_Fan(1);
}
else if(strcmp(RECEIVED_CMD , FAN_OFF) == 0)
{
test_Fan(0);
}
// 用完之后要记得数组清零处理
RECEIVED_INDEX = 0 ; // 数组指引复位
memset(RECEIVED_CMD,0,10); // 清0数组
}
}
}
/******************
** 初始化串口
*******************/
void Init_UART()
{
SCON = 0x50; //设置8位数据位
TMOD = 0x20; //8位自动重载
TH1 = TL1 = -(FOSC/12/32/BAUD); //设置重载值
TR1 = 1; //使能时钟
ES = 1; //使能串口中断
EA = 1; //开中断开关
}
/********************
** 串口中断处理
*********************/
void UART_Isr() interrupt 4 using 1
{
// 串口接收中断处理
if(RI)
{
RI = 0 ; // 清除中断标志位
RECEIVED_CMD[RECEIVED_INDEX] = SBUF ; // 保存串口接收的数据
if(RECEIVED_CMD[RECEIVED_INDEX] == 0x0A ){ // 遇到了结束符号
flag = 1 ; // 接收结束,到循环中处理接收的数据
}else {
RECEIVED_INDEX ++ ; // 继续接收数据
}
}
// 串口发送中断处理
if(TI)
{
TI = 0 ; // 清发送中断标志位
}
}
/**************************
** 通过串口发送一位数据
***************************/
void UART_SendData(char dat)
{
ES = 0 ; // 串口工作的时候禁止中断
SBUF = dat ; // 待发送的数据放到SBUF中
while(!TI) ; // 等待发送完毕
TI = 0 ; // 清TI中断
ES = 1 ; // 打开中断
}
/*****************************
** 通过串口发送字符串
******************************/
void UART_SendStr(char *str)
{
do
{
UART_SendData(*str);
}while(*str ++ != '\0' ); // 一直到字符串结束
}
/****************************
** 显示RGB灯的颜色
*****************************/
void RGB_Display(int index)
{
LED_R = color_table[index%8][0];
LED_G = color_table[index%8][1];
LED_B = color_table[index%8][2];
}
void Delay1ms() //@11.0592MHz
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
/****************
** 模拟风扇运行
****************/
void test_Fan(char flag)
{
unsigned int t , count = 500 ;
if(!flag) return ; // 如果传入的是0,表示停止,不往下继续进行
for(t=0 ; t<8 ; t++)
{
RGB_Display(t); // 风扇
for( ; count > 0 ; count --)
Delay1ms();
}
}
///***********************************
//** 字符串截取函数
//***********************************/
//void split(char str[],char delims[])
//{
// char *result = NULL;
// result = strtok( str, delims );
// while( result != NULL ) {
// result = strtok( NULL, delims );
// }
//}
/***************************
** 温度值显示
***************************/
void ds1820disp()
{
unsigned char flagdat;
if(tflag==0)
// flagdat=0x20;//正温度不显示符号
flagdat=0x2b;//正温度显示符号
else
flagdat=0x2d;//负温度显示负号:-
// disdata[0] = flagdat; //符号位
disdata[1]=tvalue/1000+0x30;//百位数
disdata[2]=tvalue%1000/100+0x30;//十位数
disdata[3]=tvalue%100/10+0x30;//个位数
disdata[4]= 0x2E ;//小数点
disdata[5]=tvalue%10/1+0x30;//小数位
if(disdata[1]==0x30) // 如果百位为0
{
disdata[0]= 0x20; // 第一位不显示
disdata[1]= flagdat; // 百位显示符号
if(disdata[2]==0x30) //如果百位为0,十位为0
{
disdata[1]=0x20; // 百位不显示符号
disdata[2]=flagdat; // 10位显示符号
}
}
}
代码中有部分逻辑处理还需要完善,其中有个电源开关和两组灯控制部分的逻辑,理论上电源关闭的时候,操作两个灯是无效的,为了演示用我将灯关闭的消息发送给了安卓端,让安卓端进行提示,但是单片机端这部分的代码逻辑还有要完善的地方。
(2)安卓端修改的部分
@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 , lamp01.isIconEnabled() ? "L1ON\r\n" :"L1OFF\r\n");
break;
case R.id.sw_lamp_02: // 灯组02
lamp02.switchState();
lamp02Name.setText(lamp02.isIconEnabled() ? "灯组1开" : "灯组1关");
writeCmd(MAC , serviceUuid , characterUuid , lamp02.isIconEnabled() ? "L2ON\r\n" :"L2OFF\r\n");
break;
case R.id.sw_power: // 电源
powerSw.switchState();
powerName.setText(powerSw.isIconEnabled() ? "电源开" : "电源关");
writeCmd(MAC , serviceUuid , characterUuid , powerSw.isIconEnabled() ? "ON\r\n" :"OFF\r\n");
break;
case R.id.sw_fan: // 风扇
fanSw.switchState();
fanName.setText(fanSw.isIconEnabled() ? "风扇开" : "风扇关");
writeCmd(MAC , serviceUuid , characterUuid , fanSw.isIconEnabled() ? "FANON\r\n" :"FANOFF\r\n");
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) {
Log.d("CJT" , "getTemperature -- value2Str -- :" + new String(value));
if(new String(value).contains("power_off\r\n")){
Toast.makeText(MainActivity.this , "请打开电源开关",Toast.LENGTH_SHORT).show();
return;
}
String hexStr = bytesToHexString(value);
Log.d("CJT","getTemperature -- hexStr -- : "+hexStr);
if(!hexStr.contains("2e")) return ; // 不包含小数点
int beginIndex = hexStr.indexOf("2b") + 2; // 加号开始截取,并且跳过加号
int endIndex = hexStr.indexOf("2e") + 4 ; // 小数点开始截取
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){
}
}
});
}
注释代码中写得比较清楚了,这里当手机收到的指令中含有单片机发送过来的`"power-off"`指令的时候,表示电源开关关闭,需要提示先打开电源开关,这部分的逻辑需要大家去完善下,我这里就先留个BUG给大家。
最后,单片机通过蓝牙模块和手机通信其实就是通过蓝牙模块的串口通讯,如果熟悉单片机底层的串口通讯,那么底层蓝牙部分就很简单了,手机端的编程需要掌握java和安卓编程才能进行,如果没有太多基础,大家可以找些入门教程看下。
最后希望该系列的博客能给大家一些启发和建议,行文至此,也了了我(从前的电子工程师,如今的安卓工程师)一个心愿。程序学习的路很漫长,希望大家都能找到自己喜欢的并坚持下去。