RTL-SDRを動かしてみました

これ

でポチったRTL-SDRが届きました。買ったのは

です。以前、SDRにトライしたときのRTL2832Uのドングルもあるのですが、RTL-SDRはTCXOを搭載しているので周波数のズレが小さく変動も少ない状態で動作させることができるはずです。

とりあえず動かすのに、ソフトウェアはGqrxを使ってみます。

まず、/etc/udev/rules.d/20-rtlsdr.rules に以下のファイルを作成して一般ユーザーでもRTL-SDRにアクセスできるようにします。

SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2832", GROUP="adm", MODE="0666", SYMLINK+="rtl_sdr"
SUBSYSTEM=="usb", ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="2838", GROUP="adm", MODE="0666", SYMLINK+="rtl_sdr"

udevルールを再読込して有効化します。

$ sudo udevadm control --reload

RTL-SDRをテストするツールをインストールします

$ sudo apt install rtl-sdr

テストしてみます。

$ rtl_test
Found 1 device(s):
  0:  Realtek, RTL2838UHIDIR, SN: 00000001

Using device 0: Generic RTL2832U OEM
Detached kernel driver
Found Rafael Micro R820T tuner
Supported gain values (29): 0.0 0.9 1.4 2.7 3.7 7.7 8.7 12.5 14.4 15.7 16.6 19.7 20.7 22.9 25.4 28.0 29.7 32.8 33.8 36.4 37.2 38.6 40.2 42.1 43.4 43.9 44.5 48.0 49.6 
[R82XX] PLL not locked!
Sampling at 2048000 S/s.

Info: This tool will continuously read from the device, and report if
samples get lost. If you observe no further output, everything is fine.

Reading samples in async mode...
Allocating 15 zero-copy buffers
lost at least 4 bytes

とりあえずOKでしょうか。次に、Gqrx SDRのページに行くと、Experimentalですが、AppImageがありましたので、デスクトップ上にダウンロードして実行属性を付けてダブルクリックしてみました。

Deviceに Realtek RTL2838UHIDIR SN: 00000001 を選んで、周波数を80.000.000に設定して再生ボタンっぽいのを押すと、FM東京が再生されました。

RTL-SDRの資料

クイックスタートガイド(QUICK START GUIDE)

ユーザーガイド(RTL-SDR BLOG V.3. DONGLES USER GUIDE)

V.3固有の機能としてダイレクトサンプリングモードを使うと500kHz〜28.8MHzの短波帯を聞くことができる。SDR#の場合にはconfigureメニューでQ-branchを選択し、500kHz〜28.8MHzにチューニングする。
HDSDRやGQRXのようなその他のソフトウェアの場合には device string で値を2または3に設定する。GQRXの場合には device string は rtl=0,direct_samp=2 となる。インストールのしかたによっては rtl=0,direct_samp=3 になる。

SCD30のキャリブレーション

こちらのCO2センサの比較記事にSCD30のキャリブレーションはどうやるの?というご質問をいただきましたので、この記事を書いてみました。

まず、SCD30に関する情報源はSensirion社のサイトのダウンロードセンターになります。

そして、その下半分に各種資料が掲載されています。
キャリブレーションに関しては、Application Notesの中のField Calibration CO2 Sensor SCD30という資料になります。

これを見ると、冒頭のPrefaceの部分でキャリブレーションの方法としてASC (automatic self-calibration) と FRC(forced re-calibration) の2種類が存在し、この資料でそのアルゴリズムが解説されていることが説明されています。そして、これらはSCD30を組み込んだ製品の製造ラインでFRC(forced re-calibration)で初期キャリブレーションを行い、使用中のドリフト対策としてASC (automatic self-calibration)を使うことが想定されているようです。

FRC(forced re-calibration)について

forced re-calibrationは強制キャリブレーションで、既知の安定したCO2濃度の環境にしばらく置いてから、現在の(既知の)CO2濃度値をSCD30に書き込むというのが基本的な考え方です。

そして、FRC(forced re-calibration)を製造ラインで使う場合には密閉された400ppm〜2000ppmの安定したCO2濃度の環境に2分以上置いてから現在のCO2濃度値を書き込むことが推奨されているようです。

エンドユーザーがFRC(forced re-calibration)を使う場合には通常密閉されて安定したCO2濃度の環境は用意できないので、屋外に置いて屋外の環境を400ppmとみなしてCO2濃度値を書き込む方法が記載されています。

ASC (automatic self-calibration)について

対するautomatic self-calibrationは自動自己キャリブレーションで、過去の測定値からキャリブレーションを行う方法です。簡単に言えば、過去の測定値の最低値を400ppmとみなしてセンサのキャリブレーションを継続的に行うもの、と理解すればいいと思います。(この辺はMH-Z19Bのオートキャリブレーションも同じです)

したがって、ASC (automatic self-calibration)を使用する場合には、以下の条件が必要です。

  • 定期的にCO2濃度が400ppm程度まで下がること。つまり、外気との間で換気がよい人のいない環境が必要。
  • (これはSCD30ではなくMH-Z19Bの資料に記載があるのですが)CO2濃度が400ppm以下にならないこと。具体的には、森の中や温室の中など周囲に植物があり光合成が行われている環境では400ppmを下回るということです。

こういった条件があるため、SCD30ではデフォルトではASC (automatic self-calibration)はオフになっています。

実際のキャリブレーションについて

自分が使用したSparkfunのSCD30 Arduino Libraryでは、ソースコードを見ると初期化時にASC (automatic self-calibration)をオンにしています。ですので、キャリブレーションは就寝時には設置してある部屋の窓を開けて、窓際においた扇風機をONにして換気をよくする、というだけです。(センサ自体の工場出荷時にキャリブレーションされていると思うこと、濃度がはっきりわかる環境下で人が近くにいない状態でFRCを実施するのは難しいので、FRCは実施していません)

なお、SCD30ではなくMH-Z19BでオートキャリブレーションをOFFにして運用したことがありますが、やはりずれていってしまうようですので、自分の使い方の場合にはオートキャリブレーションをONにしたまま定期的に400ppm環境にさらして校正を行う(=夜間は窓を開けて換気する)必要があるのだと理解しています。

ですので、CO2モニタが設置された店舗等の場合、果たしてキャリブレーションが適正に行われているかはやや疑問です。ちなみに、横着して(冷房もありますので)1週間位締め切ったままにしていたら、

のようになりました。最後の部分は久しぶりに窓を開けて、換気をしたため急激に下がっていますが、2つのセンサの値のズレが大きくなりました。どちらが真の値かはわかりませんが、そこそこずれていきます。換気の目安としては大したズレではないですが、値の大きいところで数百ppm単位で一喜一憂することには意味がないことになります。

CircuitPythonでLPS331APを読み出す

CircuitPythonでLPS331APを読み出して気圧と温度をコンソール出力するようにしました。よく見かけるのはI2Cですが、今回はSPIで行います。

import board
import busio
import digitalio
from digitalio import DigitalInOut, Direction, Pull
import time

def rreg(add):
    result = bytearray(2)
    cs.value = False
    spi.write_readinto(bytes([add,0x00]),result)
    cs.value = True
    return result[1]    

def Pres():
    P = rreg(0xAA)              # P_H
    P = (P << 8) | rreg(0xA9)   # P_L
    P = (P << 8) | rreg(0xA8)   # P_LL
    P = P/4096.0
    return P

def Temp():
    T = rreg(0xAC)              # T_H
    T = (T << 8) | rreg(0xAB)   # T_L
    if T >= 0x8000 : T -= 0x10000
    T = 42.5 + T/480.0
    return T

# CS
cs=digitalio.DigitalInOut(board.D1)
cs.direction=digitalio.Direction.OUTPUT
cs.value=True

# SPI
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
while not spi.try_lock():
    pass
spi.configure(baudrate=10000000, phase=0, polarity=0)

# WHOAMI
print(hex(rreg(0x8F)))

# init
result = bytearray(2)
cs.value = False
spi.write_readinto(bytes([0x20,0x90]),result)   # CTRL1 REG
cs.value = True

# loop
while True:
    print(Pres(),Temp())
    time.sleep(1)

これをcode.pyとして保存すると以下のように動作します。

Code stopped by auto-reload.
ソフトリブート

オートリロードがオンです。ファイルをUSB経由で保存するだけで実行できます。REPLに入ると無効化します。

code.py 出力:
0xbb
1005.51 31.2667
1005.58 31.2833
1005.5 31.2771
1005.6 31.3104
1005.57 31.3042
1005.66 31.3083
1005.77 31.3458
1005.7 31.3438
1005.63 31.3063
1005.71 31.3417
1005.62 31.3125
1005.65 31.3146

Seeeduino XiaoでCircuitPython

せっかくなので、Seeeduino XiaoでCircuitPythonを動かしてみました。

基本的にはSeeedStudioの記事に従って操作するだけです。CircuitPythonは6.3.0の日本語版を使用しました。

記事のとおりにリセットを短く2回ショートさせて、USBドライブが見えるようになったら、ダウンロードしておいた拡張子uf2のファイルをドラッグ・アンド・ドロップで書き込みます。

その後、USBケーブルを一旦抜いて挿し直すとUSBドライブが見えるので、そこにあるcode.py(SeeedStudioの記事ではmain.pyと書いてあるがcode.pyが正しいっぽい)をエディタで開いて、

import time
import board
from digitalio import DigitalInOut, Direction

led = DigitalInOut(board.D13)
led.direction = Direction.OUTPUT

while True:
    led.value = True
    time.sleep(1)
    led.value = False
    time.sleep(1)

という内容で上書き保存すると、LEDが点滅し始めます。

このとき、

$ tail -f /dev/ttyACM0

などとしておくと、コンソール出力が観測できます。

$ screen /dev/ttyACM0

などとしておけば、対話的に操作が可能です。Ctrl+Cを押した後、リターンキーを叩くと対話モード(REPLモード)に入りますので、先のプログラムを1行ずつ打ち込めば、同様に実行できます。

Seeeduino XiaoでSPI通信

Seeeduino XiaoでSPI通信をさせてみたくて、手元に落ちていた秋月電子のLPS331APモジュール(AE-LPS331)を動かしてみました。このモジュールはすでに販売終了していて秋月電子のWebサイトには資料の掲載がなく、資料を探すのに少々苦労しました。

動作させるにあたって、LPS331APモジュール上の半田ジャンパ(I2Cのプルアップ用)を外して以下の接続を行いました。

信号名AE-LPS331ピン番号Seeeduino Xiao接続先備考
VDD1(1.71〜3.6V)3V3Xiao⇒LPS331で電源供給
SPC2SCKSPIクロック
SDI3MOSISPIデータ(Xiao⇒LPS331)
SDO4MISOSPIデータ(LPS331⇒Xiao)
CS5D1チップセレクト
GND8GNDGND

Seeeduino Xiaoの開発環境はArduino環境を使用しました。環境はSeeed Studioのページの書いてある通りにセットアップしました。(今考えれば CircuitPython で良かったのかもしれません)

LPS331APを動かすソースコードはGoogle検索で見つかった光永 法明先生のページ「ST micro のデジタル気圧センサ LPS331AP を使う(SPI接続)」という記事に掲載されているものをそのまま使わせて頂きました。(LPS331APを動かすのが目的ではなく、SPIの信号を出すのが目的なので・・・)

実際には、下記の通り、CS端子(SS端子)の設定の変更とLEDを点滅させるコードを追加しています。

#include <SPI.h>

const int LPS331AP_CS = 1;
int _SS = 1;      // ~CSをD1に接続

const byte LPS331AP_ADDR = B1011100;  // SA0 = GND
//const byte LPS331AP_ADDR = B1011101;// SA0 = VDD_IO

const byte LPS331AP_WHOAMI = 0x0f;
const byte LPS331AP_CTRL1 = 0x20;
const byte LPS331AP_CTRL2 = 0x21;
const byte LPS331AP_CTRL3 = 0x22;
const byte LPS331AP_P_LL = 0x28;
const byte LPS331AP_P_L  = 0x29;
const byte LPS331AP_P_H  = 0x2A;
const byte LPS331AP_T_L  = 0x2B;
const byte LPS331AP_T_H  = 0x2C;

const byte LPS331AP_RW = 0x80;
const byte LPS331AP_MS = 0x40;

void LPS331AP_write(byte reg, byte val)
{
  digitalWrite(LPS331AP_CS, LOW);
  SPI.transfer(reg);
  SPI.transfer(val);
  digitalWrite(LPS331AP_CS, HIGH);
}

byte LPS331AP_read(byte reg)
{
  byte ret = 0;

  digitalWrite(LPS331AP_CS, LOW);
  SPI.transfer(reg | LPS331AP_RW);
  ret = SPI.transfer(0);
  digitalWrite(LPS331AP_CS, HIGH);
  
  return ret;
}

void setup() {
  digitalWrite(_SS, HIGH);
  pinMode(_SS, OUTPUT);
  
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV8); // 8MHz/8 = 1MHz; (max 10MHz)

  Serial.begin(115200);
  while (!Serial) {}

  Serial.println(LPS331AP_read(LPS331AP_WHOAMI), HEX); // should show BB

  LPS331AP_write(LPS331AP_CTRL1, B10010000);
                             //   |||||||+ SPI Mode selection
                             //   ||||||+- DELTA_EN
                             //   |||||+-- BDU: block data update
                             //   ||||+--- DIFF_EN: interrupt circuit enable
                             //   |+++---- ODR2, ODR1, ODR0 (1Hz)
                             //   +------- PD: 0: power down, 1: active
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  long P;
  short T;
  float p, t;

  P = LPS331AP_read(LPS331AP_P_H);
  P = (P << 8) | LPS331AP_read(LPS331AP_P_L);
  P = (P << 8) | LPS331AP_read(LPS331AP_P_LL);

  T = LPS331AP_read(LPS331AP_T_H);
  T = (T << 8) | LPS331AP_read(LPS331AP_T_L);

  p = P;
  p = p/4096.0;
  
  t = T;
  t = 42.5 + t/480.0;
  
  Serial.print(P);    // pressure (reading)
  Serial.print(" ");
  Serial.print(T);    // temperature (reading)
  Serial.print(" ");
  Serial.print(p);  // pressure in [mbar]/[hPa]
  Serial.print(" ");
  Serial.println(t);  // temprerature in [`C]

  digitalWrite(LED_BUILTIN, HIGH); 
  delay(500); 
  digitalWrite(LED_BUILTIN, LOW); 
  delay(500);  
}

で、動かしてみたのですが、理由がよくわかりませんが、Arduino環境のシリアルモニタでは1秒程度で表示が止まってしまいます。不思議なことに、

$ tail -f /dev/ttyACM0 
4124716 -5507 1007.01 31.03
4124388 -5508 1006.93 31.02
4124483 -5508 1006.95 31.02
4124639 -5496 1006.99 31.05
4124199 -5532 1006.88 30.98
4124532 -5506 1006.97 31.03
4124496 -5508 1006.96 31.02
  :

とすると、問題なく動作し続けるのですが・・・。
これに嵌って、余計な時間を費やしてしまいました。

Tang Nanoで内部信号のモニタリング

GOWIN FPGA DesignerにはGOWIN Analyzer Oscilloscopeという機能があって、内部信号をモニタできます。Qiitaのこちらの記事で@hi631さんがまとめていただいているので、そちらを参考にLCD表示時の内部信号をモニタしてみました。

GAO Config Fileの作成と論理合成

新規ファイル作成でGAO Config Fileを選択する。

TypeはGowinSynthesisを使っている場合にはRTLを選択できます。Synplify Proの場合は合成後のネットリストに対してのみ可能なようです。今回はRTLに対して設定してみます。また、最初なのでトリガ条件の設定できないLiteの方でやってみます。

適当に設定ファイル名を付けます。

ファイルの一番下にGAOの設定ファイルが追加された。

これをダブルクリックして開く。

Addをクリックして観測対象の信号を選択する。Nameに信号名の一部を入力してSearchを押すと検索してくれます。

CTRLを押しながら選択することで対象信号を複数選べる。今回はLCDの外部信号を選定して、OKをクリック。

・・・と思ったが、LCD_CLKはLCDのクロック信号なので、この後除外しました。
Sample Clockの部分に同様の方法でLCD_CLKを選択。

こんな感じに設定できたところで、フロッピーのアイコンをクリックして保存。

あとは、普通にDesignペインのProcessタブで論理合成(Synthesize)と配置配線(Place & Route)をダブルクリックして配置配線まで実行。

Analyzer Oscilloscopeの実行

Tools⇒Gowin Analyzer OscilloscopeでAnalyzer Oscilloscopeを起動します。

Enable Programmerにチェックを入れます。

プログラマーの一部のような表示が出るので、Fs Fileを確認(別のプロジェクトになってることがあったような気がする)して、書き込みボタン(マウスカーソルがあるところ)をクリック。

プログレスバーが表示されて、書き込みが行われ、上の赤かった部分が緑で「Ready to acquire」に変わります。

その右側の再生ボタンをクリックしてキャプチャされるとロジアナ風の表示が出ます。

繰り返しボタンをクリックすると連続でキャプチャが行われます。Tang Nanoに載っているのは小さいデバイスなので苦しいですが、これ、簡単で便利ですね。

GowinSynthesisを使っている場合にはRTLに対して指定できるので、ソース修正⇒論理合成⇒配置配線までやったら、Analyzer OscilloscopeのTriggerタブをクリックして書き込みボタンをクリックするとダウンロードされて、再びキャプチャができるようになります。(合成後のネットリスト指定でも同様にできるのかは試していません)

Tang Nanoで液晶表示テスト

GitHubにTang Nanoのサンプルがありましたので試してみました。

このサンプルの中のexample_lcd/lcd_pjtディレクトリの下のlcd_pjt.gprjをGOWIN_FPGA Designerで開いて、論理合成と配置配線、ダウンロードを行うと、なんとなく動きますが、接続したLCDが480×272のものでしたのでちらつきます。

そこで、ソースコードを覗くとVGAod.vの中に、480×272液晶用のパラメータがコメントアウトされていました。こちらを有効化し、元の800×480のパラメータをコメントアウトしました。

これで合成、配置配線、ダウンロードすると、チラツキがなくなりましたが、800×480のつもりで表示しているので部分的にしか表示されません。そこで、このソースの最後の方に出力するラインによってRGBの出力を決めている箇所がありましたので、これを以下のように修正しました。

    assign  LCD_R   =   (PixelCount< 72)? 5'b11111 : 
                        (PixelCount< 96 ? 5'b00000 : 
                        (PixelCount<120 ? 5'b00001 :    
                        (PixelCount<144 ? 5'b00010 :    
                        (PixelCount<168 ? 5'b00100 :    
                        (PixelCount<192 ? 5'b01000 :    
                        (PixelCount<216 ? 5'b10000 :  5'b00000 ))))));

    assign  LCD_G   =   (PixelCount< 72)? 6'b111111 : 
                        (PixelCount<240 ? 6'b000000 : 
                        (PixelCount<264 ? 6'b000001 :    
                        (PixelCount<288 ? 6'b000010 :    
                        (PixelCount<312 ? 6'b000100 :    
                        (PixelCount<336 ? 6'b001000 :    
                        (PixelCount<360 ? 6'b010000 :  
                        (PixelCount<384 ? 6'b100000 : 6'b000000 )))))));

    assign  LCD_B   =   (PixelCount< 72)? 5'b11111 : 
                        (PixelCount<408 ? 5'b00000 : 
                        (PixelCount<432 ? 5'b00001 :    
                        (PixelCount<456 ? 5'b00010 :    
                        (PixelCount<480 ? 5'b00100 :    
                        (PixelCount<504 ? 5'b01000 :    
                        (PixelCount<528 ? 5'b10000 :  5'b00000 ))))));

これで合成&配置配線&ダウンロードすると以下のようにきれいに表示できました。

液晶モジュールの仕様書

Tang NanoでLチカ

開発環境をインストールしたので引き続きTang NanoでLチカにトライします。基本的には、論理合成にライセンスファイルの請求が必要な Synplify Pro ではなく、ライセンスファイルの請求なしに動作する GowinSysthesis を使っている以外は Qiita の @cinimal さんの「Sipeed Tang Nanoで遊んでみる (Linux版)」と同じ手順です。(感謝)

プロジェクトの作成

GOWIN FPGA DesignerのStart PageでNew Projectアイコンをクリックしてプロジェクトを作成します。

FPGA Desgin Projectを選んでOKをクリックします。

プロジェクト名をつけて、プロジェクトを保存するディレクトリをセットしてNextをクリックします。

GW1N-1のQFN48ピンの中から遅い方=GW1N-1LV1QN48C6/I5を選択します。

サマリを確認してFinishをクリックします。

ソースファイルの作成

Designペインで右クリックしてNew Fileを選択。

ファイルの種類の選択からVerilogファイルを選択し、作成するファイル名と拡張子を入力、現在のプロジェクトに追加(Add to current project)する。

Tang Primerのときに作成した led.v の内容をコピペする。(Add Filesで良かったかも)

module led
	(
		input wire CLK_IN,
		input wire RST_N,
		output wire [2:0]RGB_LED
	);

	parameter time1 =    10'd1000 - 1'd1;
//	parameter time2 =    10'd1000 - 1'd1;	// 1秒周期
	parameter time2 =    10'd500 - 1'd1;	// 0.5秒周期
	
	reg [2:0] rled;
	reg [2:0] rledout;
	reg [9:0] count1;   // PWM DUTY用(1ms周期)
	reg [9:0] count2;   // PWM レベル用(1s周期)
	reg [4:0] ckec;
	reg cke;			// 1us周期イネーブル

	reg [9:0] duty;
	
	initial
	begin
		count1=10'b0;
		count2=10'b0;
		rled=3'b110;
		cke = 1'b1;
		ckec <= 5'b0 ;
	end

	// 1MHz周期のイネーブル信号生成
	always @(posedge CLK_IN)begin
		if(RST_N==0)begin
			cke = 1'b1;
		end
		if(ckec == 5'd23)
		begin
			ckec <= 5'b0 ;
			cke <= 1'b1;
		end
		else begin
			ckec <= ckec + 1'b1;
			cke <= 1'b0;
		end
    end

	always @(posedge CLK_IN)begin
		if(cke == 1'b1 & RST_N==0)begin
			count1 <= 10'b0;
			count2 <= 10'b0;
			rled <= 3'b110;
		end

		// 1usクロックで0〜999(time1)までカウント
		if(cke == 1'b1)
		begin
			if(count1 == time1)
	 		begin 
	 			count1<= 10'd0;
	 		end
	 		else begin
	 			count1 <= count1 + 1'b1;
			end
	 	end

		// count1の1周期で0〜999(time2)までカウント
		if(cke == 1'b1 & count1 == time1)
			begin
				if(count2 == time2)
		 		begin 
		 			count2<= 10'd0;
					rled <= {rled[1:0],rled[2]};
		 		end
		 		else
		 			count2 <= count2 + 1'b1;
		 	end

		// LEDを駆動するPWM信号生成
		if(cke == 1'b1)
		begin
			if(count2 < time2/2)
				duty = count2 * 2;
			else 
				duty = (time2 - count2) * 2;
	
			if(count1 < duty)
				rledout = rled;
			else
				rledout = 3'b111;

		end
	end

	assign RGB_LED = rledout;

endmodule

合成と端子制約の追加

ProcessペインからSynthesizeをダブルクリックして、一旦合成を実行

保存されていなかったので、保存するファイルを確認してOKをクリック。

合成が実行される。

ProcessタブでFloorPlannerをダブルクリックする。

端子制約ファイル(*.cst)がないので作成するか聞いてくるのでOKをクリック。

チップ内のレイアウトが開くので、Package Viewタブをクリック、下のペインでI/O Constraintsをクリックする。

端子を割り当てる画面になります。

上記のTang Nanoの回路図はTang NanoのドキュメントのページDownload Stationの中のHDKディレクトリの中にあります。これを見ると、

  • クロックジェネレータからのクロックはIOR5A/GCLKT_2/RPLL_T_in(35pin)
  • LED_GはIOB7A(16pin)
  • LED_BはIOB10A/GCLKT_5(17pin)
  • LED_RはIOB10B/GCLKC_5(18pin)
  • ボタンAはIOB6B(15pin)
  • ボタンBはIOB3B(14pin)

につながっているようなので、PortのCLK_INをチップの絵の35ピンへドラッグアンドドロップします。
同様にLEDの3本を16〜18ピンへドロップ、RST_Nを15ピンへドロップします(下記では間違ってRST_Nを13ピンへつないでしまっています)。I/O電圧はすべて3.3Vのレギュレータへつながっているので、IO TypeをLVCMOS33にセット、LEDの3つの端子はアノードコモンで3.3Vに吊られているので気分でOpen Drainにセット、Drive能力は4mAあれば十分なので4にセット、プルアップはすべてNONEにセットしました。

フロッピーのアイコンで保存して、FloorPlannerを閉じます。

タイミング制約の追加

ProcessペインのTiming Constraint Editorをダブルクリックします。

Timing Constraint Editorが開きます。

右画面で右クリックしてCreate Clockを選択します。

Clock nameにCLK_IN、Frequencyに24[MHz]をセットします。

Searchを押して信号を検索、CLK_INを選択して「>」で対象にします。

OKを押します。

OKを押してクロックのリストに追加されていることを確認します。

フロッピーのアイコンをクリックして保存します。

配置配線を実行

ProcessペインでPlace&Routeをダブルクリックして実行します

実行すると、チェックマークが付きます。

ビットストリームの書き込み

一般ユーザーでもプログラマが使えるようにudevの設定がしてあれば、Tang Nanoを接続した後、

$ sudo modprobe -r ftdi_sio

としてから、Tools→Programmerでプログラマを起動します。modprobeでモジュールを外さないとデバイスが見つからないのでこの手順は重要です。

虫眼鏡のマークのScan Deviceでデバイスをサーチします。

GW1N-1を選択します

JTAGチェーンにGW1Nが追加されました。

デバイスをダブルクリックして、設定を行います。

OperationはSRAM Programを選択し、Programming Optionでビットストリームファイルを選択します。プロジェクトディレクトリの impl/pnr ディレクトリの下に拡張子 fs でできているはずです。

確認してSaveしてから、Program/Configureボタンをクリックします。

すると、プログレスバーが出てきて、書き込みが完了します。必要に応じて回路で設定したリセットボタンを押して動作させます。

ここで、Tang Primerでは

	assign RGB_LED = rledout;

という記述をしていた assign 文は Tang Nanoではだめでしたので、

	assign RGB_LED[0] = rledout[0];
	assign RGB_LED[1] = rledout[1];
	assign RGB_LED[2] = rledout[2];

と修正したら問題なく動作しました。(でもエラーは出ないので、悩みました)

実際の操作の際は、FloorPlannerやProgrammerは開いたままでソース修正⇒合成⇒書き込みができますので、まあまあ作業性は良さそうです。

Tang Nano 開発環境をインストール

Tang Primerに引き続き、さらに格安なFPGAボードTang Nanoの開発環境もインストールしてみました。ShigeZoneさんで800円で買えてしまうし、よく見るとDIP40ピンのソケットに挿せそうだし、ソフトウェアでできないことにトライするにはお手軽かなぁ、と思って挑戦してみました。

基本的には@cinimlさんの「Sipeed Tang Nanoで遊んで見る(Linux版)」に沿って始めてみました。しかし、現在ではSynplify Proのライセンスチェックが厳しくなっており、この手順のままではSynplify Proが動作しませんでした。(まあ、当たり前でしょうね・・・)

基本的な手順は変わっていませんが、GowinSynthesisであればライセンスの請求をすることなく動作させることができましたので、その手順をメモしておきます。環境はLinux Mint 20です。

GOWIN EDAのダウンロード

開発に使用するGOWIN EDAはこちらからダウンロードできました。ダウンロードにあたっては、ユーザー登録が必要で、登録にはメールアドレスと電話番号が必要でした。登録してログインすると、「Gowin EDA」のダウンロードのところからダウンロードできるようになっています。

自分は間違って一つ古い Gowin_V1.9.7.05Beta_GowinSynthesis-only_linux をダウンロードしてしまいましたが、最新版でも同じでしょう。GowinSynthesis-onlyのない版は Symplify Pro が使えるものですが、「Gowin EDA」のダウンロードの下にあるライセンスの申請からライセンスを申請する必要があるようです。(GowinSynthesis-onlyはライセンス申請の必要がない)

GOWIN EDAのインストール

ダウンロードしたファイルをディレクトリを作って展開します。

~$ mkdir gowin
~$ cd gowin/
~/gowin$ tar xvfz ~/ダウンロード/Gowin_V1.9.7.05Beta_GowinSynthesis-only_linux.tar.gz 

で、

~/gowin$ IDE/bin/gw_ide 

として起動するとライセンスエラーになりますので、ライセンスサーバーを設定して、Test ConnectionをクリックしてからSaveします。

その上で、再度

~/gowin$ IDE/bin/gw_ide 

として起動すると、

として起動することができました。Synplify Proが使えるバージョンは FloorPlannerの横にSynplify Proのアイコンがありますが、アイコンをクリックしてライセンス同意をしても、その後でライセンスエラーになります。

一般ユーザーでもProgrammerを使えるようにする

以下の手順を行って、一般ユーザーでもProgrammerを使えるようにしておきます。これは@cinimlさんの「Sipeed Tang Nanoで遊んで見る(Linux版)」の手順のままです。

~$ cd /etc/udev/rules.d/
/etc/udev/rules.d$ cat 50-tang-nano.rules 
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", ATTRS{product}=="Sipeed-Debug", GROUP="users", MODE="0666"
/etc/udev/rules.d$ sudo udevadm control --reload
/etc/udev/rules.d$ sudo modprobe -r ftdi_sio

ここでTang Nanoを挿入して、テストします。

$ cd ~/gowin/
~/gowin$ Programmer/bin/programmer_cli --channel 0 --device GW1N-1 --operation_index 0
 "Read Device Codes" starting on device-1...
 Unknown Cable
 ID code is: 0x0900281B
 User code is: 0x0000496F
 Status code is: 0x0001F020
 Cost 0.66 second(s)

Tang PrimerでPWMでLチカ

LEDのサンプルを改造して、PWMでホワッと点滅するLチカにしてみました。

秒を基本とした周期にするため、まず24MHz入力クロックを24分周して、1MHz周期のイネーブル信号ckeを生成します。

これで0〜999(time1)までカウントするcount1と、count1の桁上がりのタイミングで0〜499(time2)までカウントするcount2を作ります。

count2が前半の場合には2倍した値を、後半の場合にはtime2から引いた値を2倍した値をduty比較用の値として count1 と比較して、点灯期間の場合には指定のLEDを点灯、消灯期間の場合には全LEDを消灯させます。

/* デバイスは EG4::EG4S20BG256 */ 

module led
	(
		input wire CLK_IN,
		input wire RST_N,
		output wire [2:0]RGB_LED
	);

	parameter time1 =    10'd1000 - 1'd1;
//	parameter time2 =    10'd1000 - 1'd1;	// 1秒周期
	parameter time2 =    10'd500 - 1'd1;	// 0.5秒周期
	
	reg [2:0] rled;
	reg [2:0] rledout;
	reg [9:0] count1;   // PWM DUTY用(1ms周期)
	reg [9:0] count2;   // PWM レベル用(1s周期)
	reg [4:0] ckec;
	reg cke;			// 1us周期イネーブル

	reg [9:0] duty;
	
	initial
	begin
		count1=10'b0;
		count2=10'b0;
		rled=3'b110;
		cke = 1'b1;
		ckec <= 5'b0 ;
	end

	// 1MHz周期のイネーブル信号生成
	always @(posedge CLK_IN)begin
		if(RST_N==0)begin
			cke = 1'b1;
		end
		if(ckec == 5'd23)
		begin
			ckec <= 5'b0 ;
			cke <= 1'b1;
		end
		else begin
			ckec <= ckec + 1'b1;
			cke <= 1'b0;
		end
    end

	always @(posedge CLK_IN)begin
		if(cke == 1'b1 & RST_N==0)begin
			count1 <= 10'b0;
			count2 <= 10'b0;
			rled <= 3'b110;
		end

		// 1usクロックで0〜999(time1)までカウント
		if(cke == 1'b1)
		begin
			if(count1 == time1)
	 		begin 
	 			count1<= 10'd0;
	 		end
	 		else begin
	 			count1 <= count1 + 1'b1;
			end
	 	end

		// count1の1周期で0〜999(time2)までカウント
		if(cke == 1'b1 & count1 == time1)
			begin
				if(count2 == time2)
		 		begin 
		 			count2<= 10'd0;
					rled <= {rled[1:0],rled[2]};
		 		end
		 		else
		 			count2 <= count2 + 1'b1;
		 	end

		// LEDを駆動するPWM信号生成
		if(cke == 1'b1)
		begin
			if(count2 < time2/2)
				duty = count2 * 2;
			else 
				duty = (time2 - count2) * 2;
	
			if(count1 < duty)
				rledout = rled;
			else
				rledout = 3'b111;

		end
	end

	assign RGB_LED = rledout;

endmodule