plutotx

plutotx è una semplice applicazione a linea di comando che pilota ADALM-PLUTO per generare un tono CW alla frequenza e intensità specificati dall’utente.

Spero che questo articolo e il codice sorgente in C di plutotx possano essere un piccolo aiuto per chi desidera iniziare un nuovo progetto SDR.

Il file compresso che troverai qui sotto contiene anche i binari x86 per Windows e Linux, per cui potrebbero essere utili anche per chi non è programmatore, ma è solo interessato a sperimentare con Pluto:

Latest Release:

plutotx v.1.1 (04 august 2022)
File size: 705,962  Bytes
MD5 bb0d3b1e42ff892f377fcf5e2cdbb7f3
SHA1 30ade07ad365b23c211f85632c6e9edcb1efdaa0
SHA256 607ae42da31bddaf1528a4dadac7b8ce711a4e2e9ee9210726c3fb2e1d6f673c

- F: 2,147GHz limit
- I: Output bursts issue
- I: Switching off any DDS second tone active
- A: Include, library and instruction to compile under Windows and Linux

Older versions:

plutotx (4 august 2022)
File size: 685,626 Bytes
MD5 e241666bd41a84e5eff1ec44dab2283f
SHA1 e775f8fce1af59c833a850635b6df135cda9ef1b
SHA256 bf1e782b704b5671c901bfb53280a7f754475eba3d0204dd5bf25f99326e393b

Il file ZIP contiene le librerie necessarie per l’esecuzione in ambiente Windows mentre per compilare ed eseguire Pluto sotto Linux occorre scaricare e installare la libreria libiio di ADI per il tuo specifico OS da qui: libiio.

plutotx richiede tre parametri: frequenza espressa in kHz, livello di uscita in dBm e opzionale, indirizzo URI del device da connettere.

Per esempio: plutotx 432410 -10

plutotx effettuerà la connessione all’indirizzo di default URI ip:192.168.2.1 se il terzo parametro non viene specificato.

Come funziona

Per migliorare la comprensione dei passaggi necessari per la generazione del tono CW, descriverò il codice in porzioni semplificate:

  • Connessione al device Pluto ed acquisizione della struttura context
  • Verifica se il modello del transceiver corrisponde a un AD9364 (richiesto per il completo range di frequenze)
  • Ricerca dei device physical transceiver e del DAC/TX output driver (FPGA)
  • Ricerca dei canali I, Q, catena TX e oscillatore locale TX
  • Applicazione di una configurazione di default del dispositivo
  • Impostazione dell’attenuatore TX
  • Impostazione della larghezza di banda TX
  • Impostazione dei parametri di scala, frequenza e phase dei canali I e Q
  • Impostazione della frequenza dell’oscillatore locale TX
  • Attivazione dei canali I e Q in modo raw per ottenere il segnale in uscita

Prima di tutto occorre effettuare la connessione al dispositivo ed acquisire la struttura context:

struct iio_context *ctx;
ctx = iio_create_context_from_uri("ip:192.168.2.1");

Verifica se il modello del transceiver corrisponde a un AD9364:

if((value=iio_context_get_attr_value(ctx, "ad9361-phy,model"))!=NULL)
  {
  if(strcmp(value,"ad9364"))
    stderrandexit("Pluto not expanded",0,__LINE__);
  }else
   stderrandexit("Error retrieving phy model",0,__LINE__);

Ricerca dei device physical transceiver e del DAC/TX output driver (FPGA):

phy = iio_context_find_device(ctx, "ad9361-phy");
dds_core_lpc = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc");

Ricerca dei canali I, Q, catena TX e oscillatore locale TX:

tx0_i = iio_device_find_channel(dds_core_lpc, "altvoltage0", true);
tx0_q = iio_device_find_channel(dds_core_lpc, "altvoltage2", true);
tx_chain=iio_device_find_channel(phy, "voltage0", true);
tx_lo=iio_device_find_channel(phy, "altvoltage1", true);

Applicazione di una configurazione di default del dispositivo. Forse questo passaggio non è necessario, ma è raccomandato nel caso di un utilizzo precedente di un altro programma SDR che potrebbe aver variato la configurazione di default del dispositivo.

//enable internal TX local oscillator
if((rc=iio_channel_attr_write_bool(tx_lo,"external",false))<0)
  stderrandexit(NULL,rc,__LINE__);

//disable fastlock feature of TX local oscillator
if((rc=iio_channel_attr_write_bool(tx_lo,"fastlock_store",false))<0)
  stderrandexit(NULL,rc,__LINE__);

//power on TX local oscillator
if((rc=iio_channel_attr_write_bool(tx_lo,"powerdown",false))<0)
  stderrandexit(NULL,rc,__LINE__);

//full duplex mode
if((rc=iio_device_attr_write(phy,"ensm_mode","fdd"))<0)
  stderrandexit(NULL,rc,__LINE__);

//calibration mode to manual
if((rc=iio_device_attr_write(phy,"calib_mode","manual"))<0)
  stderrandexit(NULL,rc,__LINE__);

La linea 18 imposta la calibrazione del TX in modo manuale, questo per evitare che l’automatismo di calibrazione generi dei picchi in uscita che potrebbero superare il livello di potenza specificato dall’utente.

Impostazione dell’attenuatore TX. Il valore dell’attenuatore si ottiene sottraendo al valore di intensità specificato dall’utente, il valore di potenza in uscita di Pluto (circa 10 dBm definito da REFTXPWR):

if((rc=iio_channel_attr_write_double(tx_chain,"hardwaregain",dBm-REFTXPWR))<0)
  stderrandexit(NULL,rc,__LINE__);

Impostazione della larghezza di banda TX:

if((rc=iio_channel_attr_write_longlong(tx_chain,"rf_bandwidth",FBANDWIDTH))<0)
  stderrandexit(NULL,rc,__LINE__);

Impostazione dei parametri di scala, frequenza e phase dei canali I e Q:

if((rc=iio_channel_attr_write_double(tx0_i,"scale",1))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_double(tx0_q,"scale",1))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx0_i,"frequency",FCW))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx0_q,"frequency",FCW))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx0_i,"phase",90000))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx0_q,"phase",0))<0)
  stderrandexit(NULL,rc,__LINE__);

Impostazione della frequenza dell’oscillatore locale TX. La frequenza dell’oscillatore locale TX è ottenuta sottraendo al valore richiesto dall’utente, la frequenza del tono CW definita da FCW:

if((rc=iio_channel_attr_write_longlong(tx_lo,"frequency",freq-FCW))<0)
  stderrandexit(NULL,rc,__LINE__);

Attivazione dei canali I e Q in modo raw per ottenere il segnale in uscita:

int rc;

if((rc=iio_channel_attr_write_bool(
        tx0_i,
        "raw",
        1))<0)
 stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_bool(
        tx0_q,
        "raw",
        1))<0)
 stderrandexit(NULL,rc,__LINE__);

Intero codice sorgente di plutotx:

/*
 Author: Alberto Ferraris IU1KVL - https://www.albfer.com

 This program is free software: you can redistribute it and/or modify
 it under the terms of the version 3 GNU General Public License as
 published by the Free Software Foundation.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "iio.h"

#define URIPLUTO "ip:192.168.2.1"
#define MINFREQ 50000000
#define MAXFREQ 6000000000
#define MINDBM -89
#define MAXDBM 10
#define REFTXPWR 10
#define FBANDWIDTH 4000000
#define FSAMPLING 4000000
#define FCW 1000000

struct iio_channel *tx0_i, *tx0_q;

void stderrandexit(const char *msg, int errcode, int line)
{
if(errcode<0)
  fprintf(stderr, "Error:%d, program terminated (line:%d)\n", errcode, line);
  else
  fprintf(stderr, "%s, program terminated (line:%d)\n",msg, line);
exit(-1);
}

void CWOnOff(int onoff)
{
int rc;

if((rc=iio_channel_attr_write_bool(
		tx0_i,
		"raw",
		onoff))<0)
 stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_bool(
		tx0_q,
		"raw",
		onoff))<0)
 stderrandexit(NULL,rc,__LINE__);
}

int main(int argc, char* argv[])
{
struct iio_context *ctx;
struct iio_device *phy;
struct iio_device *dds_core_lpc;
struct iio_channel *tx_chain;
struct iio_channel *tx_lo;
const char *value;
long long freq;
double dBm;
int rc;
int ch;

if(argc<3)
  {
  printf("Usage: plutotx kHz dBm [uri]\n");
  return  0;
  }

freq=atol(argv[1])*1000;

if(freq<MINFREQ || freq>MAXFREQ)
  stderrandexit("Frequency is not in range",0,__LINE__);

dBm=atof(argv[2]);

if(dBm<MINDBM || dBm>MAXDBM)
  stderrandexit("dBm is not in range",0,__LINE__);

if(argc>3)
  ctx = iio_create_context_from_uri(argv[3]);
  else
  ctx = iio_create_context_from_uri(URIPLUTO);

if(ctx==NULL)
  stderrandexit("Connection failed",0,__LINE__);

if((value=iio_context_get_attr_value(ctx, "ad9361-phy,model"))!=NULL)
  {
  if(strcmp(value,"ad9364"))
    stderrandexit("Pluto is not expanded",0,__LINE__);
  }else
   stderrandexit("Error retrieving phy model",0,__LINE__);

phy = iio_context_find_device(ctx, "ad9361-phy");
dds_core_lpc = iio_context_find_device(ctx, "cf-ad9361-dds-core-lpc");  
tx0_i = iio_device_find_channel(dds_core_lpc, "altvoltage0", true);
tx0_q = iio_device_find_channel(dds_core_lpc, "altvoltage2", true);
tx_chain=iio_device_find_channel(phy, "voltage0", true);
tx_lo=iio_device_find_channel(phy, "altvoltage1", true);

if(!phy || !dds_core_lpc || !tx0_i || !tx0_q || !tx_chain || !tx_lo)
  stderrandexit("Error finding device or channel",0,__LINE__);

//enable internal TX local oscillator
if((rc=iio_channel_attr_write_bool(tx_lo,"external",false))<0)
  stderrandexit(NULL,rc,__LINE__);

//disable fastlock feature of TX local oscillator
if((rc=iio_channel_attr_write_bool(tx_lo,"fastlock_store",false))<0)
  stderrandexit(NULL,rc,__LINE__);

//power on TX local oscillator
if((rc=iio_channel_attr_write_bool(tx_lo,"powerdown",false))<0)
  stderrandexit(NULL,rc,__LINE__);

//full duplex mode
if((rc=iio_device_attr_write(phy,"ensm_mode","fdd"))<0)
  stderrandexit(NULL,rc,__LINE__);

//calibration mode to manual
if((rc=iio_device_attr_write(phy,"calib_mode","manual"))<0)
  stderrandexit(NULL,rc,__LINE__);

CWOnOff(0);  

if((rc=iio_channel_attr_write_double(tx_chain,"hardwaregain",dBm-REFTXPWR))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx_chain,"rf_bandwidth",FBANDWIDTH))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx_chain,"sampling_frequency",FSAMPLING))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_double(tx0_i,"scale",1))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_double(tx0_q,"scale",1))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx0_i,"frequency",FCW))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx0_q,"frequency",FCW))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx0_i,"phase",90000))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx0_q,"phase",0))<0)
  stderrandexit(NULL,rc,__LINE__);

if((rc=iio_channel_attr_write_longlong(tx_lo,"frequency",freq-FCW))<0)
  stderrandexit(NULL,rc,__LINE__);

CWOnOff(1);

printf("TX ON! Q to exit or E to keep TX ON and exit\n");

while(1)
     {
     ch=getchar();
     if(ch=='q' || ch=='Q')
       {
       CWOnOff(0);
       break;
       }
     if(ch=='e' || ch=='E')
       break;
     };

iio_context_destroy(ctx);

return 0;
}

Trenta anni fa…

La Stampa, autunno 1990

Sulla scrivania due Olivetti M250E e due modem Motorola Codex 2264

Come unificare in una singola applicazione l’esigenza d’invio dei testi dai corrispondenti e dalle sedi distaccate verso la redazione centrale del giornale?

Come confluire tutti i dati in un formato compatibile verso il sistema editoriale formato da un network proprietario di dodici PDP11?

Questi erano i miei obiettivi…