安卓手机与蓝牙模块联合调试(六)-- 编写自己的蓝牙控制界面控制单片机(下篇,STC单片机代码实现)

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

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

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

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

(5)安卓手机与蓝牙模块联合调试(五)-- 编写自己的蓝牙控制界面控制单片机(上篇,Android 代码实现)

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


接着上篇继续,本篇主要是完善单片机端的代码部分。废话不多说,开始飙车了。 

1.看下初步的演示效果

 

2.主要代码部分,main.c

 (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给大家。

3.小结

        最后,单片机通过蓝牙模块和手机通信其实就是通过蓝牙模块的串口通讯,如果熟悉单片机底层的串口通讯,那么底层蓝牙部分就很简单了,手机端的编程需要掌握java和安卓编程才能进行,如果没有太多基础,大家可以找些入门教程看下。

        最后希望该系列的博客能给大家一些启发和建议,行文至此,也了了我(从前的电子工程师,如今的安卓工程师)一个心愿。程序学习的路很漫长,希望大家都能找到自己喜欢的并坚持下去。

 

 

你可能感兴趣的:(Android,--,单片机和蓝牙,安卓与蓝牙硬件)