这是第八篇教程。我们在第一篇就说过,我们会从SDR新手开始一步步教你,直到你学会调用API编程。前几篇说完后,现在是时候开始写程序了。如果你已经读完了前面的文章,会用GNU Octave,也熟悉Pothos和GNU Radio了,那么直接调用API只需要再前进一小步。
在Linux下开发(Ubuntu系统)
首先我们需要一个编译器,我们使用gcc,它是Linux的主要部件,所以你很可能已经有了。如果你没有,可以运行sudo apt-get install gcc来安装最新版本。
搞定后,我们来试试第一个Hello World程序。
创建一个hello_world.c,用你最喜欢的编辑器打开文件,加入下面的代码:
#include // so we can use printf
int main()
{
printf(“\nhello world\n”); // “\n” means new line
}
保存并关闭。接下来我们需要使用gcc编译它,我们运行这个命令: gcc -std=c99 hello_world.c -o helloworld.bin
这个命令会生成helloworld.bin,它是一个可执行程序,如果你没有写-o,那么会自动生成a.out。-std=c99表示使用的c语言的标准。我们接下去使用的soapy例子就是c99标准。
运行helloworld.bin会得到如下输出:
SoapySDR接口(API)
方便起见,我们可以把SopaySDR API看作一个黑盒。就好像printf一样,我们只需会用,当我们填入正确的数据后,它会按照我们的要求来工作。这个API有c++和python版本,你如果不熟悉c语言也可以使用另两个版本。
API文档在Device.h这个头文件里。
SoapySDR的网站上也有例子,C语言的例子在这里。
要使用这个API,必须安装SoapySDR。推荐你用Linux,但是Windows也是支持的,只不过设置编译器会更复杂。如果你之前也看了我的教程,那么你已经装过了,因为我们之前已经用了很多次SoapySDR了。如果没有安装,可以点这里。
你可以下载C语言的例子,并且保存为example.c文件。
然后就想编译helloworld.c一样,只不过这次gcc命令要这样写:
$ gcc -std=c99 example.c -lSoapySDR -lm -o example.bin
这次我们需要加入链接命令 -lm,这是因为要用到一些数学上的函数,我们最好每次都加上这个,这样不容易出错。
然后运行一下,会报错: SoapySDRDevice_make fail: RTL-SDR device not found.
但是这个例子并不要用RTL-SDR。
不要害怕,只需要做些修改,这个代码就适合LimeSDR了,把如下文字改一下:
“SoapySDRKwargs_set(&args,"driver", "rtlsdr");”
改为 :
SoapySDRKwargs_set(&args,"driver", “lime");
编译,再次运行,你就会看到如下显示:
很简单!
这样我们的例子就可以工作了,它读取了我们的LimeSDR的设置和序列号,另外还读了10组数据,每组1024个采样点。
API的文档说得很清楚了。比如如果我们想要更改增益,增益类型已经在代码里显示了,这是用如下语句实现的:
SoapySDRDevice_listGains(sdr,SOAPY_SDR_RX, 0, &length);
我们可以看一下API对这个函数的描述:
* List available amplification elements.
* Elements should be in order RF to baseband.
* \param device a pointer to a device instance
* \param direction the channel direction RX or TX
* \param channel an available channel
* \param [out] length the number of gain names
* \return a list of gain string names
*/
SOAPY_SDR_API char **SoapySDRDevice_listGains(const SoapySDRDevice *device, const int direction, const size_t channel, size_t *length);
要调整增益,我们只需要在这个文档里搜索关键字“_setGain”。我们把API中搜索到的内容贴在下面了,你可以看到它与listGain API很接近,那么仿照前面的代码来设置增益应该很简单。
* Set the overall amplification in a chain.
*The gain will be distributed automatically across available element.
*\param device a pointer to a device instance
*\param direction the channel direction RX or TX
*\param channel an available channel on the device
*\param value the new amplification value in dB
*\return an error code or 0 for success
*/
SOAPY_SDR_API int SoapySDRDevice_setGain(SoapySDRDevice *device, const int direction, const size_t channel, const double value);
通过上述信息,我们可以这样调用API来设置增益:
int success = SoapySDRDevice_setGain(sdr, SOAPY_SDR_RX, 0, 20)
这样接收通道就会有20dB的增益。就是这么简单,整个API的文档都是这么写的,所以要加入其它函数也是小事一桩。
先整理一下
我们现在要实现一个新的工具。我们要从小程序起步,比如实现一个频谱检测程序。我们可以对于每个频率读取1024个采样点,并计算RMS值。我们可以设置开始频率和结束频率,并且设置每次读取间隔多少Hz。
首先我们知道:程序代码对于硬件工程师来讲并不熟悉,我们无法阅读大量代码,所以最好把这个例子分解开来,这样我们的主循环可以小一点,维护代码也方便。我们需要用到函数和指针,所以你最好先学习一点C语言基础(我们自己也需要做点练习)。
我们会写一些函数(这些可以看作是原型):
struct SoapySDRDevice *Setup(void);
void DeviceInfo();
void Read_1024_samples(struct SoapySDRDevice *sdr, double freq, complex float *buffer);
void Close();
SoapySDRDevice
这个函数相对复杂,它会返回一个SoapySDRDevice结构体,并且把一个指针指向这个创建的SDR设备。这个指针是其它所有函数要用的,它们通过这种方式与LimeSDR交互。
这个函数可以像这样调用:
struct SoapySDRDevice *sdr = Setup();
struct SoapySDRDevice *Setup(void)
{
//enumerate devices
SoapySDRKwargs *results = SoapySDRDevice_enumerate(NULL, &length);
for (size_t i = 0; i < length; i++)
{
printf("Found device #%d: ", (int)i);
for (size_t j = 0; j < results[i].size; j++)
{
printf("%s=%s, ", results[i].keys[j], results[i].vals[j]);
}
printf("\n");
}
SoapySDRKwargsList_clear(results, length);
//create device instance
//args can be user defined or from the enumeration result
SoapySDRKwargs args = {};
SoapySDRKwargs_set(&args,"driver", "lime");
SoapySDRDevice *sdr = SoapySDRDevice_make(&args);
SoapySDRKwargs_clear(&args);
if(sdr == NULL)
{
printf("SoapySDRDevice_make fail: %s\n", SoapySDRDevice_lastError());
//return EXIT_FAILURE;
}
return sdr;
}
DeviceInfo
这是一个简单的函数,返回信息数据,在调用时需要传入SDR设备:
DeviceInfo(sdr);
void DeviceInfo(struct SoapySDRDevice *sdr)
{
//query device info
char** names = SoapySDRDevice_listAntennas(sdr, SOAPY_SDR_RX, 0, &length);
printf("Rx antennas: ");
for (size_t i = 0; i < length; i++) printf("%s, ", names[i]);
printf("\n");
SoapySDRStrings_clear(&names,length);
names = SoapySDRDevice_listGains(sdr, SOAPY_SDR_RX, 0, &length);
printf("Rx gains: ");
for (size_t i = 0; i < length; i++) printf("%s, ", names[i]);
printf("\n");
SoapySDRStrings_clear(&names, length);
SoapySDRRange *ranges = SoapySDRDevice_getFrequencyRange(sdr, SOAPY_SDR_RX, 0, &length);
printf("Rx freq ranges: ");
for(size_t i = 0; i < length; i++) printf("[%g Hz -> %g Hz], ", ranges[i].minimum, ranges[i].maximum);
printf("\n");
free(ranges);
}
Read_1024_samples
一开始这个函数看上去有点可怕。你可以像这样调用它:
Read_1024_samples(sdr, frequency, buffn);
sdr是Setup()函数创建的,frequency是要调谐到的频率值,最后的buffn是用来接收读取的采样点的buffer。这些值会存储到buffn变量的指针中。
我们还在这个函数中加入了一些错误检查机制。这样可以避免调谐到支持的范围外。除此之外,我们加入了增益,并且强制设置LNAL作为接收天线。
void Read_1024_samples(struct SoapySDRDevice *sdr, double freq, complex float *buffer)
{
// check freq is within range
if (freq <10e3)
{
freq = 10e3;
printf("Frequency out of range setting min: \n");
}
if (freq >3.8e9)
{
freq = 3.8e9;
printf("Frequency out of range setting max: \n");
}
//apply settings
if (SoapySDRDevice_setSampleRate(sdr, SOAPY_SDR_RX, 0, 1e6) != 0)
{
printf("setSampleRate fail: %s\n", SoapySDRDevice_lastError());
}
if (SoapySDRDevice_setAntenna(sdr, SOAPY_SDR_RX, 0, "LNAL") != 0)
{
printf("setAntenna fail: %s\n", SoapySDRDevice_lastError());
}
if (SoapySDRDevice_setGain(sdr, SOAPY_SDR_RX, 0, 20) != 0)
{
printf("setAntenna fail: %s\n", SoapySDRDevice_lastError());
}
if (SoapySDRDevice_setFrequency(sdr, SOAPY_SDR_RX, 0, freq, NULL) != 0)
{
printf("setFrequency fail: %s\n", SoapySDRDevice_lastError());
}
//setup a stream (complex floats)
SoapySDRStream *rxStream;
if (SoapySDRDevice_setupStream(sdr, &rxStream, SOAPY_SDR_RX, SOAPY_SDR_CF32, NULL, 0, NULL) != 0)
{
printf("setupStream fail: %s\n", SoapySDRDevice_lastError());
}
SoapySDRDevice_activateStream(sdr, rxStream, 0, 0, 0); //start streaming
void *buffs[] = {buffer}; //array of buffers
int flags; //flags set by receive operation
long long timeNs; //timestamp for receive buffer
int ret = SoapySDRDevice_readStream(sdr, rxStream, buffs, 1024, &flags, &timeNs, 100000);
//shutdown the stream
SoapySDRDevice_deactivateStream(sdr, rxStream, 0, 0); //stop streaming
SoapySDRDevice_closeStream(sdr, rxStream);
}
Close
最终我们需要关闭之前用Setup函数打开的SDR设备,我们只需要调用:
close(sdr)
void Close(struct SoapySDRDevice *sdr2)
{
//cleanup device handle
SoapySDRDevice_unmake(sdr2);
printf("Done\n");
//return EXIT_SUCCESS;
}
最小的程序
我们的程序大概已经写了90%了,我们现在写一个最小型的例子。我们需要include下面这些文件:
#include
#include
#include
#include
#include
这是唯一的全局变量
size_t length;
主函数只有这3行
struct SoapySDRDevice *sdr = Setup();
DeviceInfo(sdr);
Close(sdr);
一个频率监控工具
接下来只需要加入一小部分代码就可以开始创建很多应用程序了。这就是我们的频率监控例子:
我们在命令行传入3个参数: 开始频率、结束频率、间隔。这个代码就会设置好LimeSDR,计算需要做多少次采集。根据这个结果,程序会在每个频率上读取1024个采样点,然后计算RMS值。 RMS均方根,就是把所有值取平方,再求平均,再求平方根。这个结果会打印到终端里。注意sqrt命令要加入-lm才可以编译正常,可能你的电脑不一定要加。
int main(int argc, char *argv[])
{
int argvcount=0;
if(argc!=4)
{
printf("%d",argc);
printf("invalid input");
exit(EXIT_FAILURE);
}
struct SoapySDRDevice *sdr = Setup();
DeviceInfo(sdr);
atexit(Cleanup);
// HERE WE CAN DO THINGS
// Lets do a site survey measuring every xxx Hz and displaying the power
double StartFreq = strtod(argv[1],NULL);
double EndFreq = strtod(argv[2],NULL);
double Interval =strtod(argv[3],NULL);
double NumberOfScanPoints= (EndFreq-StartFreq)/Interval;
printf("Starting RMS scan, Start is: %f, End is: %f, Interval is: %f\n",StartFreq,EndFreq,Interval);
printf("This gives a total of: %f Sample points",NumberOfScanPoints);
while (NumberOfScanPoints !=0)
{
Get_Samples(sdr);
//create a re-usable buffer for rx samples
complex float buffn[1024];
double frequency = StartFreq + (Interval * ((EndFreq/Interval)-NumberOfScanPoints));
Read_1024_samples(sdr, frequency,buffn);
//printf("ret=%d, flags=%d, timeNs=%lld\n", ret, flags, timeNs);
float realavg = 0;
for (int x = 0; x < 1024; x++)
{
printf("%f + i%f\n", creal(buffn[x]), cimag(buffn[x]));
realavg= realavg+ (creal(buffn[x])*creal(buffn[x]));
}
realavg = sqrt(realavg/1024);
printf("Freq is: %f, RMS is: %f \n",frequency,realavg);
NumberOfScanPoints--;
}
Close(sdr);
}
编译完成并运行程序,输出如下:
在命令参数里加入LNA选择和增益设置应该也很简单。另外,把输出保存到文件中,在Linux下也很好实现,只需要这样运行:./soapy_api.bin 1e6 2e6 100e3 >output.txt
最后
C语言很有用,要实现更强大的程序也美问题。解码、编码、发射、接收都是可以实现的,就像上面这个例子一样简单。
我们希望从这个简单的SoapySDR API里会给予你一些启发,使用这个方式,我们就不受限于Pothos和GNU Radio提供的模块了,我们可以创建全新的程序,你也可以选用熟悉的语言(C、C++、Python都可以)。