基于教科书C语言的FM收音机-Taskbus Stdio封装器在SDR课程中的应用

基于教科书C语言的FM收音机-Taskbus Stdio封装器在SDR课程中的应用,第1张

自从学校引入SDR教学以来,总觉得学生的实验课上的很吃力——还不如学习Matlab仿真算了。原因是编程基础不是很扎实的学生,可能只会使用C语言课上介绍的最基础的知识,这些知识往往无法支持其完成完整的SDR收音机接收实验。

之前为了避免学生学习太多的周边知识,对其造成困扰,我和几个老师共同建立了TaskBus工程。工程的初衷很好,旨在提供一种基于stdio(就是进程默认的键盘和回显句柄)的数据吞吐策略,把SDR的发射、接收、处理、播放串起来。但这个框架还是没有顺利推进,主要原因:

  1. stdio需要封包、拆包,封包本身显著提高了学习曲线。即使用工具函数可以完成,但跟踪到函数内部,一些复杂性让学生望而却步。(实际不是很复杂)
  2. 在构造工程时,引入不少的 if else 判断,使得代码变长。
  3. 一些模块必须使用多线程。多线程是学生不希望接触的。

因此,能够使用C语言为TaskBus开发模块的同学屈指可数。这个项目苟延残喘到本学期,把新来的实验老师弄毛了。我们提出了一个想法——能不能进一步简化,就在C语言教科书上的例子里,直接解决问题呢?折腾了几个礼拜,还真的实现了。

1 学生开发的FM最简收音机

学生根据课本内容,实现FM广播单声道解调。输入的是调制信号,采样率200KHz,基带复数形式。大部分学生都能够通过测量前后两个样点的相位差,来估算频率。频率与相位是积分关系,对相位求微分,获得频率。求atan后,得到声音。

这个程序没有用到任何难以接受的知识点。唯一的美中不足,是在windows下要开启stdio句柄的二进制模式,方式Windows下的回车+换行代换破坏数据。这点在树莓派和Linux下不需要。但让我想不到的是,耽误时间最多的,是atan(y/x)的0除和象限问题。这个问题困扰不少学生很久。

#include 
#include 
#include 
#include 
#define Pi 3.14159265354
//仿matlab Angle
double angle(int x, int y)
{
	double angle = 0;
	//计算角度
	if (x==0)
		angle = (y>0)? Pi/2:-Pi/2;
	else if (x >0 )
		angle = atan(y*1.0/x);
	else if (y >=0)
		angle = atan(y*1.0/x)+Pi;
	else
		angle = atan(y*1.0/x)-Pi;
	return angle;
}
int main()
{
	short buf[128][2];
	int nPts = 0;
	int last_i = 0, last_q = 0;
	//老师加的两行。用于在windows下吞吐二进制数据。
	setmode(fileno(stdout), O_BINARY);
	setmode(fileno(stdin), O_BINARY);
	//算法
	while ((nPts = fread(buf,4,128,stdin)))
	{
		short out[128];
		for (int j=0;j<nPts;++j)
		{
			//d(phase)/d(t)  = freq
			int curr_i = buf[j][0];
			int curr_q = buf[j][1];
			int diff_i = last_i * curr_i + last_q * curr_q;
			int diff_q = last_q * curr_i - last_i * curr_q;			
			last_i = curr_i;
			last_q = curr_q;
			out[j] = angle(diff_i,diff_q)/Pi*4096;
		}
		//输出到stdout
		fwrite(out,2,nPts,stdout);
		fflush(stdout);
	}
	return 0;
}

这个程序可以用离线的文件来测试,并把结果导入AudaCity里播放。学生写好了这个程序,直接放在taskBus里,就能使用。可以看到,与不使用封装器的 TaskBus FM解调模块代码相比,上面的代码更为聚焦算法,大大降低了篇幅。

2. 用Qt建立stdio封装器

Stdio封装器要把上面这个普通的使用标准输入输出的可执行文件封装起来,完成stdio输入输出的格式化组包。说到进程的封装,Qt的Process很棒。只要给定可执行文件的名字,就能作为子进程启动,并用QIODevie接口吞吐数据。

QProcess * m_process = new QProcess(this);
connect(m_process,&QProcess::readyReadStandardOutput,this,&DlgWrpStdio::slot_readyReadStandardOutput,Qt::QueuedConnection);
connect(m_process,&QProcess::readyReadStandardError,this,&DlgWrpStdio::slot_readyReadStandardError,Qt::QueuedConnection);
//...
const QString exe = ui->lineEdit_prgPath->text();
const QString args= ui->lineEdit_parasec->text();
m_process->startCommand(exe  + " " + args);

一旦进程启动,在 slot_readyReadStandardOutput 和 slot_readyReadStandardError里,就能顺序读取进程的输出了:

void DlgWrpStdio::slot_readyReadStandardOutput()
{
	QByteArray arred = m_process->readAllStandardOutput();
	//...
}
void DlgWrpStdio::slot_readyReadStandardError()
{
	QByteArray arred =m_process->readAllStandardError();
	//...
}

同时,想发往子进程数据,直接调用write即可:

QByteArray arr(somedata,length);
m_process->write(arr);
//立刻送到 
m_process->flush();

这个封装器作为Taskbus的新插件,在这里可以找到源码。其界面如下:

3.引入Taskbus

在Taskbus里,拖入封装器模块,把路径设置到学生自己的exe上,并连接管脚:这样,学生的模块就嵌入到FM解调工程中去了。FM的接收与解调,主要分为:

  1. 设置USRP B210工作在电台频率。
  2. 接收500KHz或1MHz等采样率的信号。
  3. 滤波与下采样到200KHz
  4. 解调。(这一步学生做)
  5. 声波下采样到44100Hz
  6. 声卡播放。
  7. 同时,分一路信号去fftw和进行实时谱分析显示。

学生能够直观感觉到自己的程序竟可以参与到实时的接收处理中,成就感很高!并且,通过开发类似的模块,可以逐步取代老师们开发的范例模块,并对比其性能。比如老师通过^V Github写的仿Matlab 分数倍采样率变换,使用了浮点,速率不高。如果能变成定点,性能肯定更好。

4.测试结果

通过测试,这个小模块可以很好的播放本地广播电台的音乐。
有学生非常兴奋,有事没事都开着。

5 衍生应用

封装EXE的标准输入输出,并不要求EXE一定是C语言写的。使用python、node.js甚至标准的命令程序,如grep,都可以引进来处理。

有了这种技术,即使是没有多线程、数据库、动态链接库、COM、消息队列等知识的新手,也能够根据教科书上的内容迅速尝试向实时处理系统中输出自己的知识。


Taskbus目前支持鼠标的动态连线了,进一步方便了学生的 *** 作。加入的USRP UHD 定时突发发射模块仍在测试中。相关程序的运行、软件使用文档尚不完善。20220419 DEMO发行版见这里。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/707222.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-24
下一篇 2022-04-24

发表评论

登录后才能评论

评论列表(0条)

保存