1. 程式人生 > >安卓手機與藍芽模組聯合除錯(六)-- 編寫自己的藍芽控制介面控制微控制器(下篇,STC微控制器程式碼實現)

安卓手機與藍芽模組聯合除錯(六)-- 編寫自己的藍芽控制介面控制微控制器(下篇,STC微控制器程式碼實現)

接著上篇繼續,本篇主要是完善微控制器端的程式碼部分。廢話不多說,開始飆車了。 

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和安卓程式設計才能進行,如果沒有太多基礎,大家可以找些入門教程看下。

        最後希望該系列的部落格能給大家一些啟發和建議,行文至此,也了了我(從前的電子工程師,如今的安卓工程師)一個心願。程式學習的路很漫長,希望大家都能找到自己喜歡的並堅持下去。