当前位置:网站首页>STM32+HC05串口蓝牙设计简易的蓝牙音箱
STM32+HC05串口蓝牙设计简易的蓝牙音箱
2022-07-06 10:17:00 【华为云】
一、环境介绍
MCU: STM32F103C8T6
蓝牙模块: HC05 (串口蓝牙)
音频解码模块: VS1053B
OLED显示屏: 0.96寸SPI接口OLED
开发软件: Keil5
上位机: 使用QT设计Android端APP
二、功能介绍
Android手机打开APP,设置好参数之后,选择音乐文件发送给蓝牙音箱设备端,HC05蓝牙收到数据之后,再传递给VS1053进行播放。程序里采用环形缓冲区,接收HC05蓝牙传递的数据,设置好传递的参数之后,基本播放音乐是很流畅的。
三、硬件实物
VS1053可以接耳机或者接音箱设备即可听音乐。
四、设置HC05蓝牙波特率
HC05蓝牙串口默认波特率是38400,为了提高蓝牙传输速率,需要修改波特率为: 921600。
五、APP端界面
六、设备端:核心代码
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 代码页面
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");//这个字符串里面的内容就是串口模式的UuidMainWindow::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反馈:[email protected]");}//选择音乐文件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
边栏推荐
- J'aimerais dire quelques mots de plus sur ce problème de communication...
- Implementation of queue
- d绑定函数
- Maixll-Dock 摄像头使用
- Jerry is the custom background specified by the currently used dial enable [chapter]
- Five data structures of redis
- OliveTin能在网页上安全运行shell命令(上)
- Four processes of program operation
- Distiller les connaissances du modèle interactif! L'Université de technologie de Chine & meituan propose Virt, qui a à la fois l'efficacité du modèle à deux tours et la performance du modèle interacti
- Insert dial file of Jerry's watch [chapter]
猜你喜欢
传输层 拥塞控制-慢开始和拥塞避免 快重传 快恢复
Distinguish between basic disk and dynamic disk RAID disk redundant array
Is it meaningful for 8-bit MCU to run RTOS?
第三季百度网盘AI大赛盛夏来袭,寻找热爱AI的你!
递归的方式
Compilation Principle -- C language implementation of prediction table
30 minutes to understand PCA principal component analysis
The third season of Baidu online AI competition is coming in midsummer, looking for you who love AI!
Cool Lehman has a variety of AI digital human images to create a vr virtual exhibition hall with a sense of technology
Transport layer congestion control - slow start and congestion avoidance, fast retransmission, fast recovery
随机推荐
关于这次通信故障,我想多说几句…
阿里云国际版ECS云服务器无法登录宝塔面板控制台
Getting started with pytest ----- test case rules
Manifest of SAP ui5 framework json
Compilation Principle -- C language implementation of prediction table
Pytest learning ----- pytest operation mode and pre post packaging of interface automation testing
The integrated real-time HTAP database stonedb, how to replace MySQL and achieve nearly a hundredfold performance improvement
Scratch epidemic isolation and nucleic acid detection Analog Electronics Society graphical programming scratch grade examination level 3 true questions and answers analysis June 2022
推荐好用的后台管理脚手架,人人开源
8位MCU跑RTOS有没有意义?
带你穿越古罗马,元宇宙巴士来啦 #Invisible Cities
關於這次通信故障,我想多說幾句…
Jerry's watch deletes the existing dial file [chapter]
Five data structures of redis
The latest financial report release + tmall 618 double top, Nike energy leads the next 50 years
Windows connects redis installed on Linux
Jerry's setting currently uses the dial. Switch the dial through this function [chapter]
Olivetin can safely run shell commands on Web pages (Part 1)
Markdown grammar - better blogging
D binding function