はじめに
SDR(Software Defined Radio)によるNTSC方式アナログカラーテレビ放送をやってみました。
ハードウェアはPC,FPGA,DACです。
SDRって何
SDR(Software Defined Radio):ソフトウェアで定義された無線通信、ソフトウェア無線
旧来の無線通信では、アナログ回路で信号処理や変調を行っています。アナログ回路で組まれた無線機は、部品点数が多く複雑です。また、後から周波数帯や変調方式を変えることは不可能です。
これらの問題を解決するために、急速に進歩するコンピューター技術を活用しようという動きが生まれました。それがSDRです。
SDRでは、いままでアナログ回路で行っていた信号処理や変調をデジタルなソフトウェアで行います。(実際はFPGA/ASICといったデジタルなハードウェアで処理する場合もある)
SDRのハードウェアはシンプルです。基本となるのは、データを処理するコンピューターとADコンバーターまたはDAコンバーターの2個だけです。
SDR送信機では、コンピューターとDAコンバーターを使用します。コンピューターで送りたいデータを変調して、変調済みデータをDAコンバーターに渡せばDAコンバーターから無線信号が出てきます。DAC出力をパワーアンプで増幅し、アンテナにつないであげれば無線送信機の完成です。
ハードウェア構成
普通のPCでデータを処理し、変調を行います。プログラムはC#で書きました。1枚の画像を変調するのに1分、変調済みデータをFPGAに転送するのに3~5分くらいかかります。
FPGAボードは、PCから受け取ったデータをDAコンバーターにそのまま渡しているだけです。PCからデータを受け取り、それを1GBのDDR3 SDRAMにバッファしています。FPGAボードとDAコンバーターを組み合わせて、LAN経由で制御する任意波形発生装置として使っています。
DAコンバーター出力は、同軸ケーブルでオシロスコープと接続しました。アンテナとか接続するとたぶん電波法違反になると思います。テレビは、回路から1m離した位置からダイポールアンテナで受信します。送信アンテナを取り付けなくても回路からノイズとして放射される微弱信号を拾って受信できます。
参考にしたページ
RS-170A NTSCビデオ信号タイミング規格の概要
http://elm-chan.org/docs/rs170a/spec_j.html
2 章 変調方式と伝送 – 電子情報通信学会知識ベース
http://www.ieice-hbkb.org/files/05/05gun_08hen_02.pdf
様子
わざとノイズを加算してSN比を悪化させた信号を再現したり、マルチパスの信号を再現したりして遊んでました。
ソースコード
FPGA側のソースコードは非公開です。 FPGA側のソースコードが欲しい人は私に直接問い合わせてください。PC側の変調プログラムはご自由にお使いください。ライセンスはCC0です。(http://creativecommons.org/publicdomain/zero/1.0/deed.ja)
テキトーに作った。反省はしていない。使えるもんなら使ってみろ
[sourcecode]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
namespace ConsoleApp2
{
class Program
{
static double dacfreq;
static double dacinterval;
const double horizontalfreq = 15734.0;
const double horizontalinterval = 1.0/horizontalfreq;
static double modulatefreq = 603.25E6;
static double modulateinterval = 1.0/modulatefreq;
static ulong count = 0;
static Random r = new Random();
const double vsynclevel = -40;
const double vsynczerolevel = 0;
const double hsynclevel = -40;
const double hsynczerolevel = 0;
const double colorburstlevel = 20;
static void setdacfreq(double freq)
{
dacfreq = freq;
dacinterval = 1.0 / freq;
}
static void Main(string[] args)
{
var imgpath = @"D:\ntsc.jpg";
var img = new Bitmap(imgpath);
// DAC Sampling Rate : 2.5Gsps
setdacfreq(2.5E9);
ntsc(img);
}
static private void ntsc(Bitmap img)
{
var signal = new List<double>();
// Vsync
// Vsync Pulse
for (int i = 0; i < 3; i++)
{
addPulse(signal, vsynclevel, 2.3E-6);
addPulse(signal, vsynczerolevel, horizontalinterval / 2 - 2.3E-6);
addPulse(signal, vsynclevel, 2.3E-6);
addPulse(signal, vsynczerolevel, horizontalinterval / 2 - 2.3E-6);
}
for (int i = 0; i < 3; i++)
{
addPulse(signal, vsynclevel, horizontalinterval / 2 - 4.7E-6);
addPulse(signal, vsynczerolevel, 4.7E-6); ;
addPulse(signal, vsynclevel, horizontalinterval / 2 - 4.7E-6);
addPulse(signal, vsynczerolevel, 4.7E-6); ;
}
for (int i = 0; i < 3; i++)
{
addPulse(signal, vsynclevel, 2.3E-6);
addPulse(signal, vsynczerolevel, horizontalinterval / 2 - 2.3E-6);
addPulse(signal, vsynclevel, 2.3E-6);
addPulse(signal, vsynczerolevel, horizontalinterval / 2 - 2.3E-6);
}
Console.WriteLine("Vsync END");
for (int line = 0; line < 253; line++)
{
// Hsync
addPulse(signal, hsynczerolevel, 1.5E-6);
addPulse(signal, hsynclevel, 4.7E-6);
addPulse(signal, hsynczerolevel, 0.6E-6);
addColorBurst(signal);
addPulse(signal, hsynczerolevel, 1.58E-6);
for (int pixelcount = 0; pixelcount < 700; pixelcount++)
{
addPixel(signal,img.GetPixel(pixelcount, line * 2));
}
Console.WriteLine("LINE " + line.ToString());
}
addPulse(signal, vsynczerolevel, 1E-6);
// Baseband -> RF Modulation
for (int i = 0; i < signal.Count; i++)
{
signal[i] =
((-signal[i] + 100.0) / 140.0 + 0.125) / 1.125 * (Math.Sin(i * dacinterval / modulateinterval * 2 * Math.PI) + 1.0) / 2.0;
}
Console.WriteLine("Level MAX:" + signal.Max());
Console.WriteLine("Level MIN:" + signal.Min());
// Write File
System.IO.StreamWriter sw = new System.IO.StreamWriter(
@"D:\data.txt",
false
);
for (int i = 0; i < signal.Count; i++)
{
sw.Write(signal[i].ToString() + Environment.NewLine);
}
sw.Close();
}
static private void addPulse(List<double> signal,double level,double sec)
{
for (ulong i=0;i<(ulong)(sec/dacinterval);i++)
{
signal.Add(level);
}
}
static private void addColorBurst(List<double> signal)
{
double getcolorsignal(int count)
{
return Math.Sin(count * dacinterval * 3579545.0 * 2.0 * Math.PI);
}
for (int i = 0; i < (2.52E-6 / dacinterval); i++)
{
signal.Add(colorburstlevel * getcolorsignal(signal.Count())+hsynczerolevel);
}
}
static private void addPixel(List<double> signal,Color c)
{
var R = c.R / 256.0 * 100;
var G = c.G / 256.0 * 100;
var B = c.B / 256.0 * 100;
var Y = 0.299 * R + 0.587 * G + 0.114 * B;
var I = 0.5959 * R - 0.2746 * G - 0.3213 * B;
var Q = 0.2115 * R - 0.5227 * G + 0.3112 * B;
for (int i = 0; i < ((horizontalinterval - 10.9E-6) / 700.0 / dacinterval); i++)
{
signal.Add(
Y +
I * Math.Sin(signal.Count() * dacinterval * 3579545.0 * 2.0 * Math.PI - (57.0 * Math.PI / 180.0)) +
Q * Math.Sin(signal.Count() * dacinterval * 3579545.0 * 2.0 * Math.PI - (147.0 * Math.PI / 180.0))
);
}
}
}
}
[/sourcecode]

