当前位置:网站首页>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
边栏推荐
- 【.NET CORE】 请求长度过长报错解决方案
- d绑定函数
- Recommend easy-to-use backstage management scaffolding, everyone open source
- Distill knowledge from the interaction model! China University of science and Technology & meituan proposed virt, which combines the efficiency of the two tower model and the performance of the intera
- Manifest of SAP ui5 framework json
- Fleet tutorial 13 basic introduction to listview's most commonly used scroll controls (tutorial includes source code)
- High precision operation
- 【Swoole系列2.1】先把Swoole跑起来
- UDP协议:因性善而简单,难免碰到“城会玩”
- Compilation Principle -- C language implementation of prediction table
猜你喜欢
2019 Alibaba cluster dataset Usage Summary
78 year old professor Huake has been chasing dreams for 40 years, and the domestic database reaches dreams to sprint for IPO
C语言指针*p++、*(p++)、*++p、*(++p)、(*p)++、++(*p)对比实例
推荐好用的后台管理脚手架,人人开源
Manifest of SAP ui5 framework json
關於這次通信故障,我想多說幾句…
STM32 key state machine 2 - state simplification and long press function addition
关于这次通信故障,我想多说几句…
Today in history: the mother of Google was born; Two Turing Award pioneers born on the same day
传输层 拥塞控制-慢开始和拥塞避免 快重传 快恢复
随机推荐
2019 Alibaba cluster dataset Usage Summary
编译原理——预测表C语言实现
Compilation Principle -- C language implementation of prediction table
C语言高校实验室预约登记系统
Flet教程之 13 ListView最常用的滚动控件 基础入门(教程含源码)
Distinguish between basic disk and dynamic disk RAID disk redundant array
F200 - UAV equipped with domestic open source flight control system based on Model Design
Take you through ancient Rome, the meta universe bus is coming # Invisible Cities
Distill knowledge from the interaction model! China University of science and Technology & meituan proposed virt, which combines the efficiency of the two tower model and the performance of the intera
【Android】Kotlin代码编写规范化文档
Transfer data to event object in wechat applet
std::true_type和std::false_type
The difference between parallelism and concurrency
Pourquoi Li shufu a - t - il construit son téléphone portable?
阿里云国际版ECS云服务器无法登录宝塔面板控制台
TCP packet sticking problem
kivy教程之在 Kivy 中支持中文以构建跨平台应用程序(教程含源码)
开源与安全的“冰与火之歌”
Olivetin can safely run shell commands on Web pages (Part 1)
李书福为何要亲自挂帅造手机?