2013-08-04 17:32:52 czjsky818 阅读数 12000
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2556 人正在学习 去看看 朱有鹏

STC51单片机一般是通过串口线下载程序到MCU。但是,有时候单片机放在作品上,串口线不够长,不方便下载,或者频繁拔插单片机,也不方便。

本方法,通过笔记本蓝牙,蓝牙串口模块HC-05,即可实现STC51单片机无线下载程序到MCU。


需要以下准备:

  1. 有蓝牙模块的笔记本。

  2. 蓝牙串口模块HC-05

  3. STC51单片机

  4. STC-ISP V6.53(只要能任意选择COM口的都行。)

  5. USB转串口模块


第一步:

 打开笔记本蓝牙模块。首先你要确定你有蓝牙模块和装好相应的驱动,这里我就不详细展开了。不同笔记本打开方式不同。我的联想Y笔记本如图1:


第二步:

将USB转串口和HC-05的VCC,GND,相连,TXD接RXD,RXD接TXD。将USB转串口模块插入笔记本。此时HC-05指示灯应该是快速闪烁。

如上图,点击添加设备,搜索到HC-05后连接,输入设备的配对码 1234 ,如图2,


然后就连接成功,在 控制面板\硬件和声音\设备和打印机 里便可以看到HC-05了。在HC-05上右键,属性,硬件,便可以看到COM口号,COM28,如图3。



第三步:

将HC-05模块的PIO11置高(我买的模块有个按键),上电,模块便进入AT命令响应模式。此时HC-05指示灯应该是慢速地闪烁。

此时可以打开任意串口助手软件,如STC-ISP的串口助手,设置波特率38400,数据位8位,停止位1位,无校验位,无流控制。

串口发送字符: AT(后面要加个回车键,我之前不知道,以为没进入AT模式),成功则返回OK。

接下来设置HC-05工作模式:波特率9600,数据位8位,停止位1位,偶校验,无流控制。串口发送字符:AT+UART=9600,0,2

然后就可以关闭串口了。



第四步:

将HC-05插到单片机上,还是一样,VCC,GND,相连,TXD接RXD,RXD接TXD。

硬件部分就完成了。


第五步:

在单片机程序中加入ISP.c,ISP.h,在程序开关调用函数 UARTInit(); 实现STC51单片机的ISP。

/******************************************************************************
* 文    件: ISP.c
* 原作者: 李锋源
* 修  改: ZhnJa 
* 创建日期: 2011-7-15
* 修改日期: 2013-8-01
* 说 明: 原文件为阿土开发板的Driver.c,提取出来的ISP下载程序。
******************************************************************************/

#include "ISP.h"
#include "STC12C5A60S2.h"
#include <intrins.h> 

#ifdef	Self_Define_ISP_Download			//如果有自定义ISP下载功能
unsigned char bufptr;
code unsigned char passward[]={ICPCODE};
unsigned char buf[sizeof(passward)*2];		//静态串口缓冲区
#endif
 
/******************************************************************************
*                               UART初始化            
*描    述:串口初始化函数, 通常是在使用串口前调用本函数来进行初始化, 通常是在
*          main函数中调用。
*入口参数:无
*返    回:无
*注    意:串口使用的是中断模式
******************************************************************************/
void UARTInit(void)
{
	#define Fclk 11059200UL	//晶振 11.059M 不可以写成11059200
	#define BitRate 9600UL	//9600b/s
	unsigned char i;
	unsigned int j;
#ifdef	Self_Define_ISP_Download   //自定义下载使用到
	bufptr = 0;
	for( i=0; i<sizeof(buf); i++)
	{
		buf[i] = 0;
	}
#endif
	EA=0; 							//暂时关闭中断
	TMOD &= 0x0F;
	TMOD |=0x20;    				//定时器1工作在模式2,自动重装模式
	SCON=0x50;     					//串口工作在模式1
	TH1=256-(Fclk/(BitRate*12*16)); //计算定时器重装值
	TL1=256-(Fclk/(BitRate*12*16));
	PCON|=0x80;    					//串口波特率加倍
   // ES=1;         				//串行中断允许
	TR1=1;        					//启动定时器1
	REN=1;        					//允许接收 
	EA=1;         					//允许中断 

  	for(i=0;i<8;i++)			 	//短暂延时判断有无ISP下载命令
	{	
	 	for(j=0;j<40000;j++)
		{
		 	if(RI)
			{
				RI = 0;
				IAP_CONTR = 0x60;	 //复位到IAP
			}
		}
	}
	ES=1;         					//串行中断允许
}


/******************************************************************************
*                               延时1s函数            
*描    述:延时1s函数,在UartInit()调用供ISP延时。
*入口参数:无
*返    回:无
*注    意:
******************************************************************************/
void delay1s(void)
{
 	unsigned char i,j,k;
	for(i=0;i<200;i++)
	{
	 	for(j=0;j<200;j++)
		{
			k = 10;
			while(k--);
		}
	}
}
/******************************************************************************
*                           发送一个字符            
*描    述:向串口发送一个字符。
*入口参数:要发送的字符
*返    回:无
*注    意:
******************************************************************************/
void SendByte(unsigned char c)	 
{
    SBUF = c;
    while(!TI);
    TI = 0;
}
/******************************************************************************
*                          发送一个字符串            
*描    述:向串口发送一个字符串
*入口参数:*s要发送的字符串
*返    回:无
*注    意:
******************************************************************************/
void SendStr(char *s)			
{
	while(*s)
	{
		SendByte(*s++);
	}
}

/******************************************************************************
*                               串口0中断            
*描    述:串口0(UART0)中断。
*入口参数:无
*返    回:无
*注    意:
******************************************************************************/
void UartISR(void) interrupt 4
{
#ifdef Self_Define_ISP_Download
	
	unsigned char ptScr,ptDst;
	if(RI)
	{
		RI = 0;				//清标志位
		buf[bufptr] = SBUF;
		ptScr = bufptr;	
		if(bufptr==sizeof(buf))
		{
			bufptr = 0;
		}
		else
		{
			bufptr++;
		}
		while(buf[ptScr] == passward[ptDst])
		{
			if(ptScr == 0)
			{
				ptScr = sizeof(buf)-1;
			}
			else
			{
				ptScr--;
			}
			if(ptDst == 0)
			{
			//	reset();
			delay1s();            
			//复位前提示程序
			IAP_CONTR = 0x60;	 	//复位到IAP
			}
			else
			{
				ptDst--;
			}	
		}
		//用户程序开始
		
		
		//用户程序结束
	}//End if(RI) 
	if(TI)							//发送完成
	{		   	
	 	TI = 0;						//清标志位
	}
#else		   						//如果不使用自定义ISP下载程序
	if(RI)
	{
		RI = 0;
	}
	else
	{
	 	TI = 0;
	}
#endif
}

/*======================End Of File====================*/

/******************************************************************************
* 文    件: ISP.h
* 原作者: 李锋源
* 修  改: ZhnJa 
* 创建日期: 2011-7-15
* 修改日期: 2013-8-01
******************************************************************************/
#ifndef	__ISP_H__
#define __ISP_H__

#include "STC12C5A60S2.h"
//系统配置
#define Self_Define_ISP_Download			//ISP下载
#define ICPCODE		0x12,0x34,0x56,0x78,0x90,0xAB,0xCD,0xEF
#define FOSC	11059000UL
#define T100HZ  (FOSC/12/100)


//函数声明

//串口
void UARTInit(void);
void UARTInit(void);

void SendByte(unsigned char c);
void SendStr(char *s);

void delay1s(void);


#endif

第六步:

先用普通的正常下载方式将包含有ISP功能的程序下载到STC51单片机上。

打开STC-ISP(V6.53),设置如图,点击发送自定义下载命令即可实现远程下载。

补充:最高,最低波特率要设置为9600



说明1:
ISP,即In-System Programming,在线编程。
具有ISP功能的单片机芯片,可以通过简单的下载线直接在电路板上给芯片写入或者擦除程序,并且支持在线调试。
说明2:
须先下载一次有ISP功能的程序到单片机,之后才能实现冷启动下载程序,即ISP功能。

2018-08-28 18:51:06 u010898329 阅读数 10872
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2556 人正在学习 去看看 朱有鹏

系列博文:

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

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

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

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


忙里偷闲,承接上一篇文章继续

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

本篇将实现两个实例,手头正好有8位的单片机,索性就用来练手了。将会提供两个例子,一个是基于STM8的库函数实例,一个是基于STC89C52的实例。

1.首先了解下单片机串口通讯线的接法。这个比较重要,建议参考文章。

(1)HC-05初探 - 骑着蜗牛逛世界 - 博客园      https://www.cnblogs.com/LittleHann/p/5621937.html

(2)HC-05 蓝牙模块的调试与使用 - CSDN博客      https://blog.csdn.net/txf1984/article/details/46553715

 

2. 准备工作和单片机原理图。

       首先看下我用到的硬件支持,一块STM8S103F2P6最小系统板,一个ST-Link下载器,一个HC-05的模块(暂时先调试HC-5的模块,CC2541单片机端的代码是一样的)

最小系统的原理图如下,大家按照上面两个博文中的接线方法接好单片机,特别注意蓝牙模块的TX/RX与单片机的TX/RX要交叉相接

3. 单片机的代码,使用STM8库函数编写。

首先看下目录结构,本项目使用的是IAR编译器

下面贴出来main函数,注释在代码中已经描述的很清楚了,大家可以自行参考。

/*************
** Author   : 江涛
** Date     :  2018/08/28
** Describe : 蓝牙控制单片机开关灯
**************/
/* Includes -----------*/
#include "stm8s.h"
#include <string.h>

/********************************************
**  TestLed        PB5  测试LED
**********************************************/

/**串口1接收数据标志位**/
unsigned char Usart1_bufStart_or_bufSotp = 0 ;
/**串口1数据,这里10个字节够放命令了,大家可以根据实际需要调整数组长度**/
char Usart1BufData[10];
/**串口数据数目自增量,用来统计数目是否达到数组最大长度**/
char Usart1BufConst;

/****以下是定义的命令*****/
char LED_ON[10] = "ON\n";  
char LED_OFF[10] = "OFF\n";

void Delay(uint16_t nCount)
{
  /* Decrement nCount value */
  while (nCount != 0)
  {   
    nCount--;
  }
}

/*************
* 初始化串口UART1
**************/
void initUart1()
{
  // 串口设置
  UART1_DeInit();
  // 9600波特率,8位数据位,一位停止位,
  UART1_Init((u32)9600, UART1_WORDLENGTH_8D, UART1_STOPBITS_1, UART1_PARITY_NO, UART1_SYNCMODE_CLOCK_DISABLE, UART1_MODE_TXRX_ENABLE);//UART1_MODE_TX_ENABLE);
  // 使能串口接收中断
  UART1_ITConfig(UART1_IT_RXNE_OR, ENABLE);
}

/*********
* 端口配置
**************/
void initGPIO(){
  // LED灯的GPIO口初始化
  GPIO_Init(GPIOB, GPIO_PIN_5, GPIO_MODE_OUT_PP_LOW_FAST);
  GPIO_WriteHigh(GPIOB , GPIO_PIN_5); // 关测试灯
  
}

/*****************
*  程序入口
*******************/
void main(void)
{

  // 初始化系统时钟,
  CLK_HSICmd(ENABLE);
  CLK_SYSCLKConfig(CLK_PRESCALER_HSIDIV1);
  //内部时钟16M,8分频
  CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV8);    
  
  initGPIO();
   
  initUart1();
  
  enableInterrupts(); // 使能中断
       
  while (1)
  {    
    if(Usart1_bufStart_or_bufSotp)
    {
       Usart1_bufStart_or_bufSotp = 0 ; 
              
       if(strcmp(Usart1BufData,LED_ON)==0)
       {
          GPIO_WriteLow(GPIOB , GPIO_PIN_5);         
       }
       else if(strcmp(Usart1BufData,LED_OFF)==0)
       {
          GPIO_WriteHigh(GPIOB , GPIO_PIN_5);
       }
              
       Usart1BufConst = 0 ;
       memset(Usart1BufData,0,10);//清0数组   
    }    
  }
}


#ifdef USE_FULL_ASSERT

/**
  * @brief  Reports the name of the source file and the source line number
  *   where the assert_param error has occurred.
  * @param file: pointer to the source file name
  * @param line: assert_param error line source number
  * @retval : None
  */
void assert_failed(u8* file, u32 line)
{ 
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */

  /* Infinite loop */
  while (1)
  {
  }
}
#endif

 项目中还有个it.c文件,内容如下:

/* Includes ------------------------------------------------------------------*/
#include "stm8s.h"

/* Private typedef -----------------------------------------------------------*/

extern unsigned char Usart1_bufStart_or_bufSotp;
extern char Usart1BufData[10];
extern char Usart1BufConst;

/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/* Public functions ----------------------------------------------------------*/


#if defined (STM8S208) || defined(STM8S207) || defined(STM8S007) || defined(STM8S103) || \
    defined(STM8S003) ||  defined (STM8AF62Ax) || defined (STM8AF52Ax) || defined (STM8S903)
/**
  * @brief UART1 TX Interrupt routine.
  * @param  None
  * @retval None
  */
 INTERRUPT_HANDLER(UART1_TX_IRQHandler, 17)
 {
    /* In order to detect unexpected events during development,
       it is recommended to set a breakpoint on the following instruction.
    */
 }


/**
  * @brief UART1 RX Interrupt routine.
  * @param  None
  * @retval None
  */
 INTERRUPT_HANDLER(UART1_RX_IRQHandler, 18)
 {
    /* In order to detect unexpected events during development,
       it is recommended to set a breakpoint on the following instruction.
    */  
     
     Usart1BufData[Usart1BufConst]=UART1_ReceiveData8();
     UART1_SendData8(UART1_ReceiveData8()); 
     // 收到了结束符号
     if(Usart1BufData[Usart1BufConst]==0x0A)
     {
       Usart1_bufStart_or_bufSotp=1;    
     }
     else
       Usart1BufConst++;
   
      UART1_ClearITPendingBit(UART1_IT_RXNE); // 清除标志位
      
 }
#endif /*STM8S208 or STM8S207 or STM8S103 or STM8S903 or STM8AF62Ax or STM8AF52Ax */

/******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE****/

主要代码就是这些了,注释中已经写的很清楚了。

4.接好线看下运行的效果 。

过后放上一个视频连接,实在因为gif的效果不明显。 

 

好了,STM8蓝牙控制LED亮灭的功能基本实现了,下一遍将实现蓝牙和STC89单片机的联调。

当这个系列结束之后我会将代码一并上传到Git。博文更新的话,可能因为工作原因会断断续续,希望大家见谅。如果觉得有帮助或者有设么其他好的建议可以在博文下留言,赞赏码没有别的意思,这是个知识付费的时代,可能付费了才会更珍惜学习的不易,当然也是作者创作的动力所在,哈哈。


 

 

 

 

2019-12-07 09:21:31 qq_43421598 阅读数 92
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2556 人正在学习 去看看 朱有鹏

蓝牙模块

蓝牙转串口(TTL)模块,蓝牙转串口,不需要知道蓝牙协议,只需要知道串口协议就好。我所用的是BT06,只能做从机,蓝牙接收数据。

BT06蓝牙模块是专为智能无线数据传输而打造,遵循V3.0 蓝牙规范。
本模块支持UART接口,并支持SPP蓝牙串口协议,具有成本低、体积小、功耗低、收发灵敏性高等优点,只需配备少许的外围元件就能实现其强大功能。

接线图蓝牙正反面图片

可以USB-TTL模块直接与蓝牙模块相连,但要注意 5V-5V,GND-GND,RXD-TXD,TXD-RXD。
也可以直接用开发板进行蓝牙设置,但注意不要插单片机,否则数据会冲突,因为单片机进行烧录也是用的RX,TX。
开发板连接

状态指示LED:

LED显示 模块状态
长亮 建立连接
均匀慢速闪烁 等待配对

AT指令集

在进行蓝牙与电脑连接上之后,打开串口助手(或者STC下载器自带的)打开串口可以进行AT指令设置,设置它的密码,名称以及波特率等
用户可以通过串口和 BT06蓝牙进行通信,串口使用 Tx, Rx 两根信号线,波特 率 支 持
1200,2400,4800,9600,14400,19200,38400,57600,115200,230400,460800 和921600bps。串口缺省波特率为
9600bps。
(注:发 AT 指令时必须回车换行, AT 指令只能在模块未连接状态下才能生效,一旦蓝牙模块与设备连接上,蓝牙模块即进入数据透传模式。\r\n为直接按电脑回车键,如不能按回车键则加\r\n。AT指令不分大小写)
AT指令集
指令
指令
在这里插入图片描述更改密码
波特率

应用链接

一般默认名称是BT-04,默认密码为1234,手机打开蓝牙,在手机app端控制蓝牙发送数据。

单片机程序

使用蓝牙时接受的数据由串口进行通讯传给MCU,所以使用串口中断。
参考程序:

#include <reg52.h> 
#define uchar unsigned char 

void UART_INIT()
{
 SM0 = 0;
 SM1 = 1;//串口工作方式1
 REN = 1;//允许串口接收
 EA = 1;//开总中断
 ES = 1;//开串口中断
 TMOD = 0x20;//8位自动重装模式
 TH1 = 0xfd;
 TL1 = 0xfd;//9600波特率
 TR1 = 1;//启动定时器1
}
//串口中断
void UART_SER() interrupt 4
{
 if(RI)
 {
  RI = 0;//清除接收标志
  switch(SBUF)
  {
   case 0x01:/*执行*/ ; break;//蓝牙接收1事进行的操作
  }
 }
}

void main()
{
 UART_INIT();//串口初始化
 
 while(1)
 {
 }
}
2018-08-31 17:13:57 u010898329 阅读数 7258
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2556 人正在学习 去看看 朱有鹏

系列博文:

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

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

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

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


继续承接上两篇文章,这一篇会写下使用STC89单片机编程,蓝牙接收数据,控制LED灯的亮灭。

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

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

1.首先看下使用到的工具和软件。

(1)先看下硬件工具需要哪些。

一个USB转串口下载器(CH340),蓝牙模块(先用HC-05做本次调试,下次一并讲CC2541),一块含有STC89单片机的最小板(这里我用的是自己闲时画的一块小板,含红外和OLED显示屏,还有一颗RGB灯),来个全家福。

(2)看下要使用的软件。

此次开发编译工具使用KEIL,keil需要大家自行下载安装和破解。

程序下载使用STC官方的下载工具,附上下载链接:http://www.stcmcudata.com/STCISP/stc-isp-15xx-v6.86O.zip

 

2.编程后做些简单的测试。

       其实简单来讲,蓝牙传输数据依据的就是串口通讯,如果你搞明白了单片机的串口通讯,那么编程就变得相当简单了。下面带大家开始撸代码,这一段的话仅针对单片机初级选手而言,老鸟不喜勿喷。

(1)直接使用工具自带的程序来实现串口通讯。

       先教新手偷个懒,如何使用STC-ISP来直接下载串口代码。直接上图了。

       这样下载后代码是可以直接运行的,下面我来个截图,主要是我的板子上接的是P2口,8个LED灯,直接用指示灯来指示收到的数据。

因此我在拷贝下来的代码中又改了些地方,主要是用来做指示灯的指示用的,也给大家截个图。

这个是STC-ISP自带的程序,大家可以研究下,这里的SBUF表示的是收到的数据。我就偷个懒直接改了下,主要演示用。下面看下实际的效果,由于不能上传视频,就只能用图片来做说明了。还是通过蓝牙串口发送指令,这次是直接发送的16进制指令,STC单片机接收后,通过P2口指示灯来输出显示发送的指令值。

 

(2)自己撸代码实现串口通讯。

     上面我是直接偷懒了 ,下面带大家撸代码了,单片机串口的代码其实网上有很多可以参考的,下面上代码。

/****************************************
**       蓝牙串口接收数据
** 
**   作者:江涛
**   时间:2018/08/31
**   描述:串口发送数据兼用OLED显示
****************************************/
#include "STC89C5xRC_RDP.h"
#include "string.h"     // 要使用字符串对比函数,需要引入该头文件
#include "OLED.h"       // OLED显示屏头文件

// 定义系统时钟和串口波特率
#define FOSC 11059200L      // 系统时钟
#define BAUD 9600           // 串口波特率

/******变量声明*********/ 
char RECEIVED_CMD[10] ;       // 暂定为10字节的指令
char RECEIVED_INDEX ;         // 数组指示索引,当接收到一个数据之后,索引会跟随增加
unsigned char flag = 0 ;      // 数据接收的标志位

/******命令常量*******/
code const char* LED_ON = "ON\r\n" ;
code const char* LED_OFF = "OFF\r\n" ;

/*******函数声明*********/
void Init_UART(); // 初始化串口
void UART_SendData(char dat); // 串口发送数据
void UART_SendStr(char* str); // 串口发送字符串

/*******程序入口*********/
void main() 
{

	Init_UART();  // 串口初始化
	
//	LCD_Init();  // OLED 初始化
//	LCD_CLS();   // 清屏
//	
//	LCD_P8x16Str(0 , 0 , "CODE:"); 
	
	while(1)
	{
		if(flag) // 接收数据完毕一次,就会进入中断一次
		{
			flag = 0 ; // 将标志位还原,使得串口又可以重新接收数据
			
			
			UART_SendStr(RECEIVED_CMD); // 接收到的数据通过串口发送回去
			
//			LCD_P8x16Str(6*8 , 0 , RECEIVED_CMD); // 显示接收代码
			
//			P2 = ~P2 ; // 翻转P2口的灯,用来测试接收到了数据
			if(strcmp(RECEIVED_CMD , LED_ON) == 0)
			{
				P2 = 0xFF ; // P2口全亮
			}
			else if(strcmp(RECEIVED_CMD , LED_OFF) == 0)
			{
				P2 = 0x00 ; // P2口全灭
			}
			
			// 用完之后要记得数组清零处理
      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' ); // 一直到字符串结束
}

本来是有一个OLED显示的,为了方便大家理解,我直接把OLED部分去掉了,代码中定义了两个命令,LED_ON 和LED_OFF, 注意事项,如果你是直接拷贝的话应该是不能运行的。因为安卓的换行对应的只有“\n”,所以拷贝后应该把命令换成如下格式,安卓调试助手才能识别开关灯的命令。

/******命令常量*******/
code const char* LED_ON = "ON\n" ;
code const char* LED_OFF = "OFF\n" ;

 看下运行后的效果,

 

3.小结。

好了,至此第一阶段单片机和蓝牙的通讯部分已经基本搞定,其实主要就是单片机的串口通讯的应用,下面的文章将围绕开发一个自己的安卓蓝牙应用为主要内容展开。

 



 

2018-09-18 17:07:04 u010898329 阅读数 2723
  • 单片机有很多种-1.3.第1季第3部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第3个课程,主要讲了单片机的发展史,各种主流单片机的各自特点,STC51单片机的各系列的特点以及项目中如何选型主控单片机。

    2556 人正在学习 去看看 朱有鹏

(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和安卓编程才能进行,如果没有太多基础,大家可以找些入门教程看下。

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

 

 

没有更多推荐了,返回首页