STM32+HC05串口蓝牙设计简易的蓝牙音箱

举报
DS小龙哥 发表于 2022/07/06 09:37:40 2022/07/06
【摘要】 Android手机打开APP,设置好参数之后,选择音乐文件发送给蓝牙音箱设备端,HC05蓝牙收到数据之后,再传递给VS1053进行播放。程序里采用环形缓冲区,接收HC05蓝牙传递的数据,设置好传递的参数之后,基本播放音乐是很流畅的。

一、环境介绍

MCU: STM32F103C8T6

蓝牙模块: HC05 (串口蓝牙)

音频解码模块: VS1053B

OLED显示屏: 0.96寸SPI接口OLED

开发软件: Keil5

上位机: 使用QT设计Android端APP

二、功能介绍

Android手机打开APP,设置好参数之后,选择音乐文件发送给蓝牙音箱设备端,HC05蓝牙收到数据之后,再传递给VS1053进行播放。程序里采用环形缓冲区,接收HC05蓝牙传递的数据,设置好传递的参数之后,基本播放音乐是很流畅的。

三、硬件实物

image.png

VS1053可以接耳机或者接音箱设备即可听音乐。

四、设置HC05蓝牙波特率

image.png
image.png

HC05蓝牙串口默认波特率是38400,为了提高蓝牙传输速率,需要修改波特率为: 921600。

五、APP端界面

image.png
image.png
image.png

六、设备端:核心代码

6.1 vs1053.c

#include "vs1053b.h"	

/*
函数功能:移植接口--SPI时序读写一个字节
函数参数:data:要写入的数据
返 回 值:读到的数据
*/
u8 VS1053_SPI_ReadWriteByte(u8 tx_data)
{			  	 
	u8 rx_data=0;				 
  u8 i;
  for(i=0;i<8;i++)
	{
		VS1053_SCLK=0;  
		if(tx_data&0x80){VS1053_OUTPUT=1;}
		else {VS1053_OUTPUT=0;}
		tx_data<<=1;	
		VS1053_SCLK=1;
		rx_data<<=1;
		if(VS1053_INPUT)rx_data|=0x01;
	}
	return rx_data; 
}


/*
函数功能:初始化VS1053的IO口	 
*/
void VS1053_Init(void)
{
	 RCC->APB2ENR|=1<<0;
	 AFIO->MAPR&=~(0x7<<24);  //释放PA13/14/15
	 AFIO->MAPR|=0x4<<24;
	
	 RCC->APB2ENR|=1<<2;
	 RCC->APB2ENR|=1<<3;
	 
	 GPIOA->CRH&=0x00000FFF;
	 GPIOA->CRH|=0x33338000;
	  
	 GPIOB->CRL&=0xFFF00FFF;
	 GPIOB->CRL|=0x00083000;
	
	 VS1053_SCLK=1;
	 VS1053_XCS=1;
}	


/*
函数功能:软复位VS10XX
*/
void VS1053_SoftReset(void)
{	 
	u8 retry=0;  				   
	while(VS1053_DREQ==0); 							//等待软件复位结束	   
	VS1053_SPI_ReadWriteByte(0Xff);			//启动传输
	retry=0;
	while(VS1053_ReadReg(SPI_MODE)!=0x0800)	// 软件复位,新模式  
	{
		VS1053_WriteCmd(SPI_MODE,0x0804);		// 软件复位,新模式	    
		DelayMs(2);//等待至少1.35ms 
		if(retry++>100)break; 	  
	}	
	while(VS1053_DREQ==0);//等待软件复位结束	 
	retry=0;
	while(VS1053_ReadReg(SPI_CLOCKF)!=0X9800)//设置VS10XX的时钟,3倍频 ,1.5xADD 
	{
		VS1053_WriteCmd(SPI_CLOCKF,0X9800);	//设置VS10XX的时钟,3倍频 ,1.5xADD
		if(retry++>100)break; 	    
	}	 
	DelayMs(20);
} 


/*
函数 功 能:硬复位MP3
函数返回值:1:复位失败;0:复位成功	
*/
u8 VS1053_Reset(void)
{
	u8 retry=0;
	VS1053_RESET=0;
	DelayMs(20);
	VS1053_XDCS=1;//取消数据传输
	VS1053_XCS=1; //取消数据传输
	VS1053_RESET=1;	   
	while(VS1053_DREQ==0&&retry<200)//等待DREQ为高
	{
		retry++;
		DelayUs(50);
	};
	DelayMs(20);	
	if(retry>=200)return 1;
	else return 0;	    		 
}


/*
函数功能:向VS10XX写命令
函数参数:
				address:命令地址
				data   :命令数据
*/
void VS1053_WriteCmd(u8 address,u16 data)
{  
	while(VS1053_DREQ==0);	//等待空闲		   	   
	VS1053_XDCS=1; 	 
	VS1053_XCS=0; 	 
	VS1053_SPI_ReadWriteByte(VS_WRITE_COMMAND);//发送VS10XX的写命令
	VS1053_SPI_ReadWriteByte(address); 	//地址
	VS1053_SPI_ReadWriteByte(data>>8); 	//发送高八位
	VS1053_SPI_ReadWriteByte(data);	 		//第八位
	VS1053_XCS=1;            
} 


/*
函数参数:向VS1053写数据
函数参数:data:要写入的数据
*/
void VS1053_WriteData(u8 data)
{
	VS1053_XDCS=0;   
	VS1053_SPI_ReadWriteByte(data);
	VS1053_XDCS=1;      
}


/*
函数功能:读VS1053的寄存器 
函数参数:address:寄存器地址
返回值:读到的值
*/
u16 VS1053_ReadReg(u8 address)
{ 
	u16 temp=0;   	
  while(VS1053_DREQ==0);//非等待空闲状态   	
	VS1053_XDCS=1;       
	VS1053_XCS=0;        
	VS1053_SPI_ReadWriteByte(VS_READ_COMMAND);//发送VS10XX的读命令
	VS1053_SPI_ReadWriteByte(address);       	//地址
	temp=VS1053_SPI_ReadWriteByte(0xff); 		  //读取高字节
	temp=temp<<8;
	temp+=VS1053_SPI_ReadWriteByte(0xff); 		//读取低字节
	VS1053_XCS=1;      
   return temp; 
}  


/*
函数功能:读取VS1053的RAM
函数参数:addr:RAM地址
返 回 值:读到的值
*/
u16 VS1053_ReadRAM(u16 addr) 
{ 
	u16 res;			   	  
 	VS1053_WriteCmd(SPI_WRAMADDR, addr); 
	res=VS1053_ReadReg(SPI_WRAM);  
 	return res;
} 


/*
函数功能:写VS1053的RAM
函数参数:
				addr:RAM地址
				val:要写入的值 
*/
void VS1053_WriteRAM(u16 addr,u16 val) 
{  		   	  
 	VS1053_WriteCmd(SPI_WRAMADDR,addr);	//写RAM地址 
	while(VS1053_DREQ==0); 							//等待空闲	   
	VS1053_WriteCmd(SPI_WRAM,val); 			//写RAM值 
} 


/*
函数参数:发送一次音频数据,固定为32字节
返 回 值:0,发送成功
				  1,本次数据未成功发送   
*/ 
u8 VS1053_SendMusicData(u8* buf)
{
	u8 n;
	if(VS1053_DREQ!=0)  //送数据给VS10XX
	{			   	 
		VS1053_XDCS=0;  
    for(n=0;n<32;n++)
		{
			VS1053_SPI_ReadWriteByte(buf[n]);	 			
		}
		VS1053_XDCS=1;     				   
	}else return 1;
	return 0;//成功发送了
}


/*
函数参数:发送一次音频数据,固定为32字节
返 回 值:0,发送成功
				  1,本次数据未成功发送   
*/ 
void VS1053_SendMusicByte(u8 data)
{
	u8 n;
	while(VS1053_DREQ==0){}	   	 
	VS1053_XDCS=0;  
	VS1053_SPI_ReadWriteByte(data);	 			
	VS1053_XDCS=1;     				   
}


/*
函数功能:设定VS1053播放的音量
函数参数:volx:音量大小(0~254)
*/
void VS1053_SetVol(u8 volx)
{
    u16 volt=0; 			      //暂存音量值
    volt=254-volx;			    //取反一下,得到最大值,表示最大的表示 
	  volt<<=8;
    volt+=254-volx;					//得到音量设置后大小
    VS1053_WriteCmd(SPI_VOL,volt);//设音量 
}

七、Android手机APP核心源码

7.1 代码页面

image.png

7.2 mainwindow.cpp代码

#include "mainwindow.h"
#include "ui_mainwindow.h"


/*
 * 设置QT界面的样式
*/
void MainWindow::SetStyle(const QString &qssFile) {
    QFile file(qssFile);
    if (file.open(QFile::ReadOnly)) {
        QString qss = QLatin1String(file.readAll());
        qApp->setStyleSheet(qss);
        QString PaletteColor = qss.mid(20,7);
        qApp->setPalette(QPalette(QColor(PaletteColor)));
        file.close();
    }
    else
    {
        qApp->setStyleSheet("");
    }
}

static const QLatin1String serviceUuid("00001101-0000-1000-8000-00805F9B34FB");
//这个字符串里面的内容就是串口模式的Uuid

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    this->SetStyle(":/qss/blue.css");     //设置样式表
    this->setWindowTitle("HC05蓝牙音箱"); //设置标题
    this->setWindowIcon(QIcon(":/wbyq.ico")); //设置图标

    /*1. 实例化蓝牙相关的对象*/
    discoveryAgent = new QBluetoothDeviceDiscoveryAgent();
    localDevice = new QBluetoothLocalDevice();
    socket = new QBluetoothSocket(QBluetoothServiceInfo::RfcommProtocol);
    //RfcommProtocol表示该服务使用RFCOMM套接字协议。RfcommProtocol属于模拟RS232模式,就叫串口模式

    /*2. 关联蓝牙设备相关的信号*/
    /*2.1 关联发现设备的槽函数,当扫描发现周围的蓝牙设备时,会发出deviceDiscovered信号*/
    connect(discoveryAgent,
            SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)),
            this,
            SLOT(addBlueToothDevicesToList(QBluetoothDeviceInfo))
            );

    //蓝牙有数据可读
    connect(socket,
            SIGNAL(readyRead()),
            this,
            SLOT(readBluetoothDataEvent())
            );

    //蓝牙连接建立成功
    connect(socket,
            SIGNAL(connected()),
            this,
            SLOT(bluetoothConnectedEvent())
            );

    //蓝牙断开连接
    connect(socket,
            SIGNAL(disconnected()),
            this,
            SLOT(bluetoothDisconnectedEvent())
            );

    //蓝牙写成功的数据
    connect(socket,
            SIGNAL(bytesWritten(qint64)),
            this,
            SLOT(bluetoothbytesWritten(qint64))
            );


    /*3.2 设置标签显示本地蓝牙的名称*/
    QString name_info("本机蓝牙:");
    name_info+=localDevice->name();
    ui->label_BluetoothName->setText(name_info);

     ui->plainTextEdit->setEnabled(false); //不可编辑

     /*定时器用于定时发送文件*/
     timer = new QTimer(this); //创建定时器
     connect(timer, SIGNAL(timeout()), this, SLOT(update())); //关联槽函数

     ConnectStat=0;     //连接状态
     SendStat=0;        //文件打开状态
     FileSendTime=100;  //默认每次发送的时间 单位ms
     ui->lineEdit_Timer->setText(QString::number(FileSendTime));
     FileSendCnt=32;    //默认每包数据值32字节
     ui->lineEdit_SendFileCnt->setText(QString::number(FileSendCnt));


     //创建存放音乐文件的目录
     QDir dir;
     if(dir.mkpath("/sdcard/WBYQ_MP3"))
     {
          ui->plainTextEdit->insertPlainText("/WBYQ_MP3 目录创建成功!\n");
     }
     else
     {
          ui->plainTextEdit->insertPlainText("/WBYQ_MP3 目录创建失败!\n");
     }

      MusicFileDir="assets:/nansannan.mp3";  //目录的路径
      Def_MusicName="assets:/nansannan.mp3";
      file.setFileName(Def_MusicName); //设置文件名称
      ui->lineEdit_MusicName->setText(Def_MusicName); //默认文件名称
}


int count=0;
//更新
void MainWindow::update()
{
    if(SendStat)
    {
        int len;
        if(file.atEnd()==false)  //文件未结束
        {
            len=file.read(FileBuff,FileSendCnt);
            len=socket->write(FileBuff,len); //发送数据
        }
        else
        {
           file.close();
           timer->stop(); //停止定时器
           SendStat=0;
           ui->plainTextEdit->setPlainText("文件读取写入完毕!\n");
        }
    }
}

MainWindow::~MainWindow()
{
    delete ui;
    delete discoveryAgent;
    delete localDevice;
    delete socket;
    timer->stop();
    delete timer;
}

void MainWindow::on_pushButton_CloseBluetooth_clicked()
{
    /*关闭蓝牙设备*/
    localDevice->setHostMode(QBluetoothLocalDevice::HostPoweredOff);
}


void MainWindow::on_pushButton_BluetoothScan_clicked()
{
    /*3.1 检查蓝牙是否开启*/
    if(localDevice->hostMode() == QBluetoothLocalDevice::HostPoweredOff)
    {
        /*请求打开蓝牙设备*/
        localDevice->powerOn();
    }

     /*开始扫描周围的蓝牙设备*/
    discoveryAgent->start();
    ui->comboBox_BluetoothDevice->clear(); //清除条目
}

void MainWindow::on_pushButton_DeviceVisible_clicked()
{
    /*设置蓝牙可见,可以被周围的设备搜索到,在Android上,此模式最多只能运行5分钟。*/
    //  localDevice->setHostMode( QBluetoothLocalDevice::HostDiscoverable);
    static bool cnt=1;
    cnt=!cnt;
    if(cnt)
    {
        MusicFileDir="assets:/nansannan.mp3";  //目录的路径
        QMessageBox::information(this,"提示","路径切换为内部路径!\n请选择文件!",QMessageBox::Ok,QMessageBox::Ok);
    }
    else
    {
        MusicFileDir="/mnt/sdcard";  //目录的路径
        QMessageBox::information(this,"提示","路径切换为SD路径!\n请选择文件!",QMessageBox::Ok,QMessageBox::Ok);
    }
}


void MainWindow::on_pushButton_StopScan_clicked()
{
    /*停止扫描周围的蓝牙设备*/
    discoveryAgent->stop();
}


/*当扫描到周围的设备时会调用当前的槽函数*/
void MainWindow::addBlueToothDevicesToList(const QBluetoothDeviceInfo &info)
{
   // QString label = QString("%1 %2").arg(info.name()).arg(info.address().toString());
    QString label = QString("%1 %2").arg(info.address().toString()).arg(info.name());
    ui->comboBox_BluetoothDevice->addItem(label); //添加字符串到comboBox上
}


//有数据可读
void MainWindow::readBluetoothDataEvent()
{
    QByteArray all = socket->readAll();
    ui->plainTextEdit->insertPlainText(all);
}


//建立连接
void MainWindow::bluetoothConnectedEvent()
{
    QMessageBox::information(this,tr("连接提示"),"蓝牙连接成功!");
    ConnectStat=1;
    /*停止扫描周围的蓝牙设备*/
    discoveryAgent->stop();
}


//断开连接
void MainWindow::bluetoothDisconnectedEvent()
{
    ConnectStat=0;
    QMessageBox::information(this,tr("连接提示"),"蓝牙断开连接!");
}


/*
在说蓝牙设备连接之前,不得不提一个非常重要的概念,就是蓝牙的Uuid,引用一下百度的:
在蓝牙中,每个服务和服务属性都唯一地由"全球唯一标识符" (UUID)来校验。
正如它的名字所暗示的,每一个这样的标识符都要在时空上保证唯一。
UUID类可表现为短整形(16或32位)和长整形(128位)UUID。
他提供了分别利用String和16位或32位数值来创建类的构造函数,提供了一个可以比较两个UUID(如果两个都是128位)的方法,还有一个可以转换一个UUID为一个字符串的方法。
UUID实例是不可改变的(immutable),只有被UUID标示的服务可以被发现。
在Linux下你用一个命令uuidgen -t可以生成一个UUID值;
在Windows下则执行命令uuidgen 。UUID看起来就像如下的这个形式:2d266186-01fb-47c2-8d9f-10b8ec891363。当使用生成的UUID去创建一个UUID对象,你可以去掉连字符。
*/

//发送音乐文件
void MainWindow::on_pushButton_SendData_clicked()
{
    if(ConnectStat)
    {
        if(file.open(QIODevice::ReadOnly))
        {
            SendStat=1;
            count=0;
            ui->plainTextEdit->insertPlainText("系统提示: 开始发送文件!\n");
            ui->lineEdit_fileSizef->setText(QString::number(file.size()));
            ui->lineEdit_fileCount->setText("");
            ui->progressBar_SendCount->setMaximum(file.size()); //设置进度条显示最大值
            ui->progressBar_SendCount->setValue(0); //设置进度条的值
            timer->start(FileSendTime); //启动定时器
        }
    }
}


//连接蓝牙
void MainWindow::on_pushButton_ConnectDev_clicked()
{
    QString text = ui->comboBox_BluetoothDevice->currentText();
    int index = text.indexOf(' ');
    if(index == -1) return;

    QBluetoothAddress address(text.left(index));

    QString connect_device="开始连接蓝牙设备:\n";
    connect_device+=ui->comboBox_BluetoothDevice->currentText();
    QMessageBox::information(this,tr("连接提示"),connect_device);

    //开始连接蓝牙设备
    socket->connectToService(address, QBluetoothUuid(serviceUuid) ,QIODevice::ReadWrite);
}

//帮助提示
void MainWindow::on_pushButton_help_clicked()
{
    QMessageBox::information(this,tr("帮助提示"),"本软件用于HC-05/06系列串口蓝牙连接!\n"
                                             "暂时不支持BLE4.0版本蓝牙!\n"
                                             "用于发送音乐文件数据,每次发送32字节,默认100ms发送间隔时间\n"
                                             "软件作者:DS小龙哥\n"
                                             "BUG反馈:1126626497@qq.com");
}


//选择音乐文件
void MainWindow::on_pushButton_select_clicked()
{
    QString filename=QFileDialog::getOpenFileName(this,"选择发送的音乐文件",MusicFileDir,tr("*.mp3 *.MP3 *.WAV"));
    //filename==选择文件的绝对路径
    file.setFileName(filename);
    ui->lineEdit_MusicName->setText(filename);
    Def_MusicName=filename; //保存文件名称
}


//清除
void MainWindow::on_pushButton_clear_clicked()
{
     ui->plainTextEdit->setPlainText("");
}


void MainWindow::on_pushButton_StopSend_clicked()
{
       timer->stop(); //停止定时器
}


//设置发送间隔时间
void MainWindow::on_pushButton_SendTime_clicked()
{
    QString str=ui->lineEdit_Timer->text();
    int time=str.toInt();
    FileSendTime=time; //保存时间
    if(time<=0)
    {
        QMessageBox::warning(this,"警告提示","设置错误: 发送的间隔时间最小1ms\n",QMessageBox::Ok,QMessageBox::Ok);
    }
    else
    ui->plainTextEdit->insertPlainText(tr("设置发送间隔时间为%1ms\n").arg(time));
}


//修改每包发送的数量
void MainWindow::on_pushButton_SendFileCnt_clicked()
{
    QString str=ui->lineEdit_SendFileCnt->text();
    int cnt=str.toInt();
    if(cnt>4096 || cnt<=0)
    {
         QMessageBox::warning(this,"警告提示","设置错误: 每包发送的数量范围: 1~4096\n",QMessageBox::Ok,QMessageBox::Ok);
    }
    else
    {
        FileSendCnt=cnt;
        ui->plainTextEdit->insertPlainText(tr("发送数量设置为%1字节.\n").arg(cnt));
    }
}


//写成功的数量
void MainWindow::bluetoothbytesWritten(qint64 byte)
{
   count+=byte;
   ui->lineEdit_fileCount->setText(QString::number(count));
   ui->progressBar_SendCount->setValue(count); //设置进度条的值
}

如果需要完整的代码可以从这里下载:https://download.csdn.net/download/xiaolong1126626497/18621270
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。