使用QT实现二进制相移键控(BPSK)
一、本文目的&知识
BPSK是二进制相移键控,是一种通信的调制信号的方式,网络上大多都使用MATLAB来实现仿真的。本篇文章将介绍使用QT实现BPSK,目的是理解BPSK数字调制解调的原理,以及QT的基本使用。
二、要实现的功能
1.该程序分为发射机部分和接收机部分,也就是说要两个UI
2.发射机部分:
需要打开一个文本文件,文本文件里是101010(字符串),来模拟基带信号
打开文件的同时,显示基带信号的波形
点击发射按钮,对信号进行调制,同时接收机解调信号
3.接收机部分
接收发送机的信号,并且显示解调出来波形
可以将波形保存成文件,文件格式与读取的文件格式相同
总体框架图
三、BPSK简介
BPSK调制数学表达式 ,其中s(t)是已调信号,m(t)是基带信号,f是载波频率
s
(
t
)
=
A
c
o
s
(
2
π
f
t
+
π
m
(
t
)
)
s(t) = Acos(2\pi ft+\pi m(t))
s(t)=Acos(2πft+πm(t))
相干解调:
C
o
r
r
(
r
(
t
)
,
s
(
t
)
)
=
∫
∞
∞
r
(
τ
)
s
(
τ
−
t
)
d
τ
Corr(r(t),s(t))=\int^\infty_\infty r(\tau)s(\tau-t)d\tau
Corr(r(t),s(t))=∫∞∞r(τ)s(τ−t)dτ
低通滤波,在本设计中,低通滤波采用的是差分方程的方式实现
y
[
n
]
=
(
1
−
α
)
y
[
n
−
1
]
+
α
x
[
n
]
y[n]=(1-\alpha)y[n-1]+\alpha x[n]
y[n]=(1−α)y[n−1]+αx[n]
其中
α
\alpha
α是截至频率系数,
f
c
f_c
fc是截至频率,
T
s
T_s
Ts是抽样时间。
α
=
2
π
f
c
T
s
2
π
f
c
T
s
+
1
\alpha=\dfrac{2\pi f_cT_s}{2\pi f_c T_s +1}
α=2πfcTs+12πfcTs
四、发射机
4.1读取文件部分
读取文件部分实现比较简单,就是一个按钮按下信号,然后再到槽函数进行处理,以下是槽函数处理部分代码:
void SendWave::on_pushButton_OpenFile_clicked()
{
QString bitFileStr=QFileDialog::getOpenFileName(this,
"选择一个文件",
"./"
"*.txt");
if(bitFileStr.isEmpty()){
QMessageBox::information(this,"警告","请打开一个文件");
}else{
QFile bitFile(bitFileStr);
bitFile.open(QIODevice::ReadOnly);
QByteArray ba=bitFile.readAll();
//装载读取的波形到发射buffer里面
byteR.clear();
for(int i=0;i<ba.length();i++){
double temp=(double)ba.data()[i];
byteR.push_back(temp);
}
bitString=QString(ba);
bitFile.close();
int len=bitString.length();
axisX_r->setMax(len);
axisX_r->setTickCount(len+1);
axisX_m->setMax(len);
axisX_m->setTickCount(len+1);
DrawLineSeries_R(bitString);
//direct block drawing
DrawModulSignal();
}
}
其中,弹出窗口部分代码依赖QMessageBox这个类
4.2显示波形部分
由于读取文件涉及到了显示信号波形,因此,显示信号波形函数如下:
//original signal
void SendWave::DrawLineSeries_R(QString str){
QByteArray byte;
qDebug()<<str;
byte=str.toLatin1();
lineSeries_r->clear();
for(int i=0;i<str.length()*2;i++){
lineSeries_r->append(QPointF(i, (int)(byte.data()[i]-'0')));
lineSeries_r->append(QPointF(i+0.99, (int)(byte.data()[i]-'0')));
qDebug()<<byte.data()[i];
}
}
//modul signal
void SendWave::DrawModulSignal(){
QByteArray byte=bitString.toLatin1();
double inc=(double)(bitString.length())/(double)4097;
moudulSInc=inc;
byteM.clear();
lineSeries_m->clear();
for(int i=0;i<bitString.length();i++){
for(double j=(double)i;j<(double)(i+1.0);j+=inc){
double ph=(byte.data()[i]-'0')*PI;
double temp=(double)std::sin(j*2*signal_frequency*PI+ph);
lineSeries_m->append(QPointF(j, temp));
byteM.push_back((double)temp);
}
}
}
在此之前,控制波形的这些对象都需要初始化,其中初始化的操作如下:
axisX_r = new QValueAxis();
axisY_r = new QValueAxis();
axisX_c = new QValueAxis();
axisY_c = new QValueAxis();
axisX_m = new QValueAxis();
axisY_m = new QValueAxis();
lineSeries_r=new QLineSeries();
lineSeries_c=new QLineSeries();
lineSeries_m=new QLineSeries();
chart_r=new QChart();
chart_carry=new QChart();
chart_modul=new QChart();
axisX_r->setTitleText("Time (s)");
axisY_r->setTitleText("Y");
axisX_r->setMin(0);
axisY_r->setMin(0);
axisY_r->setMax(1);
axisX_r->setTickCount(10+1); //x 轴刻度是10
axisX_c->setTitleText("Time (s)");
axisY_c->setTitleText("Y");
axisX_c->setMin(0);
axisX_c->setMax(1);
axisY_c->setMin(-1);
axisY_c->setMax(1);
axisX_c->setTickCount(10+1); //x 轴刻度是10
axisX_m->setTitleText("Time (s)");
axisY_m->setTitleText("Y");
axisX_m->setMin(0);
axisX_m->setMax(1);
axisY_m->setMin(-1);
axisY_m->setMax(1);
axisX_m->setTickCount(5+1); //x 轴刻度是10
lineSeries_r->setPointLabelsVisible(false);
lineSeries_r->setName("base signal");
lineSeries_c->setPointLabelsVisible(false);
lineSeries_c->setName("carry signal");
lineSeries_m->setPointLabelsVisible(false);
lineSeries_m->setName("modul signal");
chart_r->addAxis(axisX_r,Qt::AlignBottom);
chart_r->addAxis(axisY_r,Qt::AlignLeft);
chart_r->addSeries(lineSeries_r);
chart_carry->addAxis(axisX_c,Qt::AlignBottom);
chart_carry->addAxis(axisY_c,Qt::AlignLeft);
chart_carry->addSeries(lineSeries_c);
chart_modul->addAxis(axisX_m,Qt::AlignBottom);
chart_modul->addAxis(axisY_m,Qt::AlignLeft);
chart_modul->addSeries(lineSeries_m);
lineSeries_r->attachAxis(axisX_r);
lineSeries_r->attachAxis(axisY_r);
lineSeries_c->attachAxis(axisX_c);
lineSeries_c->attachAxis(axisY_c);
lineSeries_m->attachAxis(axisX_m);
lineSeries_m->attachAxis(axisY_m);
lineSeries_m->setUseOpenGL(true);
ui->graphicsView->setChart(chart_r);
ui->graphicsView->setRenderHint(QPainter::Antialiasing);
ui->graphicsView_c->setChart(chart_carry);
ui->graphicsView_c->setRenderHint(QPainter::Antialiasing);
ui->graphicsView_m->setChart(chart_modul);
ui->graphicsView_m->setRenderHint(QPainter::Antialiasing);
显示波形这部分代码还是很复杂的,除了c++代码操作之外,还有就是工程的操作,大体如下:
要素1:修改.pro文件,就是:QT += core gui charts,因为需要用到这个资源
要素2:在UI文件里要添加Graphics View这个组件,并且将其提升为QChartView
要素3:需要包含QChartView QValueAxis QLineSeries这几个类
发送信号部分
波形显示出来之后,实际上是存到一个数据结构里面了,需要将它发给接收机部分,也是通过一个按钮来实现的
void SendWave::on_pushButton_Send_clicked()
{
emit sendM_Signal(byteM);
}
4.3信号与槽部分
发送的信号和接收的信号是自定义信号与槽实现的,需要在对应的头文件自定义信号与槽函数,发射机的头文件:
signals:
void sendM_Signal(QVector<double> dat);
对应的接收机的头文件:
private slots:
void receiveM_Signal(QVector<double> dat);
五、接收机
5.1接收机接收信号
接收信号不仅仅需要的是信号本身,还有一个重要的参数是载波主频,这个载波主频我是通过FFT来计算出来的,代码太长就不予展示了,具体在这个链接:浅谈FFT以及FFT算法的基本实现
void Receive::receiveM_Signal(QVector<double> dat){
Rfrequency=(double)mainfre;
qDebug()<<"read main frequency:"<<Rfrequency;
//vector全部转到数组
RecvDatLen=dat.length();
double temp_array[dat.length()]={0};
MainWindow::vector2array(temp_array,dat,(int)dat.size());
qDebug()<<incr;
bitInc=incr;
lineSeries_squ->clear();
RecvTime=0.0;
for(int i=0;i<dat.length();i++){
lineSeries_squ->append(RecvTime,temp_array[i]*temp_array[i]);
RecvTime+=incr;
}
axisX_squ->setMax(RecvTime);
DrawCorSignal(dat,temp_array);
DrawSamSignal(corSignal,temp_array);
qDebug()<<"Recv time:"<<RecvTime;
}
5.2 相干解调+画这个波形
void Receive::DrawCorSignal(QVector<double> dat,double datArray[]){
double x=0.0;
int prej=0;
int nowj=0;
corSignal.clear();
lineSeries_cor->clear();
for(int i=0;i<dat.length();i++){
double temp=std::sin(2*PI*Rfrequency*x)*datArray[i];
lineSeries_cor->append(x,temp);
corSignal.push_back(temp);
x+=bitInc;
nowj=x;
if(prej!=nowj)x=(double)nowj;
prej=x;
}
axisX_cor->setMax(x);
}
5.3 低通滤波+采样+画波形
void Receive::DrawSamSignal(QVector<double> dat,double datArray[]){
double x=0.0;
double outData[dat.length()]={0};
int prej=0;
int nowj=0;
LPF_Process(dat,outData,dat.length());
lineSeries_sam->clear();
outSignal.clear();
for(int i=0;i<dat.length();i++){
lineSeries_sam->append(x,outData[i]);
outSignal.push_back(QPair<double,double>(x,outData[i]));
x+=bitInc;
nowj=x;
if(prej!=nowj)x=(double)nowj;
prej=x;
}
axisX_sam->setMax(x);
}
低通滤波函数:
double Receive::LPF_Init(double fc,double Ts){
double b=2.0*PI*fc*Ts;
return b/(b+1);
}
void Receive::LPF_Process(QVector<double> inData,double outData[],int len){
double out_pre=inData.at(0);
double alpha=LPF_Init(1.8,bitInc);
for(int i=0;i<len;i++){
double out=out_pre+alpha*(inData.at(i)-out_pre);
outData[i]=out;
out_pre=out;
}
}
六、复频域分析
实现界面如下,用来估计载波中心频率
七、误码率分析(略)
八、总结
本次设计介绍了基于 C++和 QT 框架设计的 BPSK(Binary Phase Shift Keying,二进制相移键控)通信模拟程序,还对其中涉及的理论知识和技术进行了分析和解释。这项工作旨在设计了一个直观的工具,用于展示了 C++和 QT 在通信系统仿真方面的可行性。