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

Tang Primer 開発環境をセッティング

FPGA開発をしたくなったので、以前買ってあった Tang Primer用の環境をセットアップします。

公式サイトはこちらのようです。

セッティングにあたっては以下のサイトを参考にさせていただきました。

http://galaxystar.image.coocan.jp/tangprimer.htm

IDEをインストールと起動

https://tang.sipeed.com/en/getting-started/requirements/ にリンクがあるので、そこから下の方にあるURLからIDEをダウンロードします。

今回は、TD1909_linux.rarをダウンロードしてみました。(もっと新しい日付のものはRHEL用っぽいので)

https://tang.sipeed.com/en/getting-started/installing-td-ide/linux/ のページの記述を参考にインストールしますが、いろいろ違います。

~/ダウンロード$ sudo unrar x TD1909_linux.rar -d /opt/
~/ダウンロード$ sudo ln -s /opt/TD_RELEASE_September2019_r4.6.2/bin/td /usr/bin/td
~/ダウンロード$ sudo chmod a+x /opt/TD_RELEASE_September2019_r4.6.2/bin/td
~/ダウンロード$ td -gui

実際に起動するにはFPGAのデバイス情報を /usr/arch で参照できないといけないようです。

$ sudo ln -s /opt/TD_RELEASE_September2019_r4.6.2/arch /usr/arch 

また、ライセンスファイルが必要なようで、 https://dl.sipeed.com/shareURL/TANG/Premier/IDE から最新のライセンスファイル(Anlogic_*.lic)をダウンロードしてきて、 /usr/license の下に Anlogic.lic のファイル名で配置します。

$ cd /usr
$ mkdir license
$ sudo mkdir license
$ cd license/
$ sudo cp ~/ダウンロード/Anlogic_20220130.lic Anlogic.lic

これでIDEが起動できました。

実際には、https://dl.sipeed.com/shareURL/TANG/Premier/IDE により新しいバージョンがあるようなので、そちらを試したほうがいいかもしれません。

USBドライバをインストール

https://tang.sipeed.com/en/getting-started/installing-usb-driver/linux/ のページに沿ってドライバをインストールします。

Tang Nano PrimerをPCに接続して、lsusbで VID:PID = 0547:1002 を認識しているか確認します。

$ lsusb
Bus 001 Device 003: ID 0547:1002 Anchor Chips, Inc. Python2 WDM Encoder

USB3.0のHUB経由では認識せず、PCのポート直結、もしくはUSB2.0のHUB経由でなければ認識されませんでした。(PCのUSB3.0のポート直結なら認識するのかは試していません)

記述の通りにudevルールを作成します。

$ ls -la /etc/udev/rules.d/91-anlogic-jtag.rules 
-rw-r--r-- 1 root root 108  6月 20 08:31 /etc/udev/rules.d/91-anlogic-jtag.rules
$ cat /etc/udev/rules.d/91-anlogic-jtag.rules 
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0547", ATTRS{idProduct}=="1002", \
  GROUP="plugdev", \
  MODE="0660"

$ sudo service udev restart

次に、正しく認識しているかを確認しますが、自分の環境では一度再起動しないとだめでした。(ログアウト→ログインでも大丈夫かは試していません)

$ td -gui

として、IDEを起動します。起動したら、赤で囲った Download ボタンをクリックすると、Downloadウインドウが開くことを確認します。

ドキュメントでは、「未知のバグによりJTAGは400kbpsかそれ以下でないと動作しない」とのことです。

サンプルの合成

https://tang.sipeed.com/en/getting-started/getting-to-blinky/ に沿ってサンプル blinky を動かしてみます。

gitをインストールした上で、githubからサンプルをダウンロードしてきて、IDEを起動します。

~$ mkdir TangPrimer
~$ cd TangPrimer/
~/TangPrimer$ sudo apt install git
~/TangPrimer$ git clone --recursive https://github.com/Lichee-Pi/Tang_FPGA_Examples
~/TangPrimer$ td -gui

Project -> Open Project で ~/TangPrimer/Tang_FPGA_Examples/0.LED/prj の下の led.al を開きます。Process -> Run またはメニューボタンの中の Run をクリックすると、論理合成とフィッティングが行われて led.bit が生成されます。

サンプルのテストと改造

Tool -> Download またはメニューボタンの中の Download をクリックして、Downloadウインドウを開きます。「Add」アイコンをクリックして、生成した led.bit を選択します。Mode SelectionがJTAGになっていると、直接FPGAに書き込みに行くので、電源再投入で消えるとのことです。(http://galaxystar.image.coocan.jp/tangprimer.htm の記載による)

Speedを3Mbpsにして、Runボタンをクリックすると書き込みが行われます。書き込みが終わると動作を開始しますが、ユーザー回路へのリセットがかからないので、「USER」ボタンを押してユーザー回路へのリセットをかけてやる必要があります。以下のように書き換えて動作させようとしたものの思ったとおりに動作せず、少々悩みました。

①led.v

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

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

	parameter time1 = 25'd12000000;//クロック周波数 24Mhz

	reg [2:0]rledout;
	reg [24:0] count;
	
	initial
	begin
		count=25'b0;
		rledout=3'b110;
	end

	always @(posedge CLK_IN)begin
		if(RST_N==0)begin
			count <= 25'b0;
			rledout <= 3'b110;
		end
	
		if(count == time1)
 		begin 
 			count<= 25'd0;
			rledout <= {rledout[1:0],rledout[2]};
 		end
 		else
 			count <= count + 1'b1;
	end
	
	assign RGB_LED = rledout;
endmodule

②制約ファイル io.adc

set_pin_assignment {CLK_IN} { LOCATION = K14; }		##24MHZ
set_pin_assignment {RST_N} { LOCATION = K16; }		##USER_KEY

## RGB LEDs, 3 pins
set_pin_assignment {RGB_LED[0]} { LOCATION = R3;  }	##LED_R, R3
set_pin_assignment {RGB_LED[1]} { LOCATION = J14;  }	##LED_G, J14
set_pin_assignment {RGB_LED[2]} { LOCATION = P13;  }	##LED_B, P13

空間放射線量

中国広東省の原子力発電所で放射能漏れがあった、という報道がありました。広東省は日本の西方にありますので、放射能漏れの場合には偏西風に乗って日本でも放射線が検出される可能性があります。

そこで、原子力規制委員会が公開しているここ1週間の放射線モニタリング情報をグラフ化してみました。全国のを・・・と思ったのですが、福島県はデータ提供されている地点が多すぎて、1週間分をまとめてダウンロードすることができないので福島県を除いてやってみました。

データは県ごとにまとめてダウンロードして、それをPythonで並べ替えたものをLibreOffice Calcで読み込ませてグラフを作成してみました。どのグラフがどの地域かは、グラフのキャプションに記載しています(文字色もグレーなので見にくいですが)。

結果を見ると、関東地方で6/8の夜に値の上昇が見られますが、このタイミングでは他の地域では値の上昇は見られません。また、甲信越、中部東海、関西、中国四国にかけて6/13の午前中と、6/14の早朝に値の上昇が見られるようです(関東地方でも上昇している地域はあるようです)。いずれも大きな上昇とは言い難いので、これが広東省の原発と関係があるかはなんともいえないところかと思います。

なお、一時的な値の上昇は、降雨と関係があるというご指摘をいただきました。(そういえば、そういうデータを何度も見たことがあったのですが、忘れていました)
ちょうど梅雨時ですので、そういう関係かもしれません。

東北北海道(福島県を除く)
関東地方
甲信越
中部東海
関西
中国四国
九州沖縄

ESP-Nowをためす

電池動作での長時間化を狙って、ESP8266/ESP32での直接通信モードのESP-Nowを試してみます。

ESP-NowはEspressifの説明によれば、「ESP-NOWは、Espressifによって開発されたさらに別のプロトコルであり、Wi-Fiを使用せずに複数のデバイスが相互に通信できるようにします。このプロトコルは、ワイヤレスマウスによく使用される低電力2.4GHzワイヤレス接続に似ています。したがって、デバイス間のペアリングは、通信の前に必要です。ペアリングが完了すると、接続は安全でピアツーピアになり、ハンドシェイクは必要ありません。」(Google翻訳による)ということで、Wi-Fiのような複雑なプロトコルではないので、通信にかかる時間が短く=消費電力が削減されることが期待できます。

ESP-Nowのドキュメントはこちらになるのですが、今回は簡単にArduino環境のライブラリSimpleEspNowConnection(ライブラリマネージャからインストールできる)を使って、送信側にESP8266、受信側にESP32で動作させました。ESP-Nowでespressifから提供されているAPIはESP8266とESP32で異なっているのですが、SimpleEspNowConnectionはその差分も吸収してくれます。

想定する使い方は、電池で動くセンサー側から、PCもしくはRaspberry Piに接続されたホスト側に測定データを送信する、という使い方です。本来、ESP-Nowは双方向で通信ができるのですが、今回はこの使い方に絞った形でテストしてみました。

まずは受信側のコードです。これは、ライブラリのサンプルプログラム(SimpleEspNowConnectionServer.ino)をさらに簡略化したもので、ESP32で動作させました。

#include "SimpleEspNowConnection.h"

SimpleEspNowConnection simpleEspConnection(SimpleEspNowRole::SERVER);

String clientAddress;

void OnMessage(uint8_t* ad, const uint8_t* message, size_t len)
{
  Serial.printf("MESSAGE:[%d]%s from %s\n", len, (char *)message, simpleEspConnection.macToStr(ad).c_str());
}

void setup() 
{
  Serial.begin(115200);
  Serial.println();

  simpleEspConnection.begin();
  simpleEspConnection.setPairingBlinkPort(2);
  simpleEspConnection.onMessage(&OnMessage);  

  Serial.println(WiFi.macAddress());    
}

void loop() 
{
  simpleEspConnection.loop();
}

次に送信側のコードです。これも、ライブラリのサンプルプログラム(SimpleEspNowConnectionClient.ino)をさらに簡略化したもので、テストではESP8266で動作させました。

#include "SimpleEspNowConnection.h"

SimpleEspNowConnection simpleEspConnection(SimpleEspNowRole::CLIENT);

String serverAddress;

void OnSendError(uint8_t* ad)
{
  Serial.println("SENDING TO '"+simpleEspConnection.macToStr(ad)+"' WAS NOT POSSIBLE!");
}

void OnSendDone(uint8_t* ad)
{
  Serial.println("SENDING TO '"+simpleEspConnection.macToStr(ad)+"' WAS DONE!");
}

void setup() 
{
  Serial.begin(115200);
  Serial.println();

  Serial.println("Setup...");

  simpleEspConnection.begin();
  simpleEspConnection.setPairingBlinkPort(2);  

  serverAddress = "012345ABCDEF"; // 受信側のMACアドレス
  simpleEspConnection.setServerMac(serverAddress);
  simpleEspConnection.onSendError(&OnSendError);  
  simpleEspConnection.onSendDone(&OnSendDone);
  
  Serial.println(WiFi.macAddress());  
}

void loop() 
{
  static int n=1;
  static char buf[32];
  simpleEspConnection.loop();

  sprintf(buf,"%d",n++);
  simpleEspConnection.sendMessage(buf);
  delay(1000);
}

これで実際に動作させることができました。

送信に成功した場合、失敗した場合でそれぞれコールバック関数が呼ばれるので、それに応じた処理を記述することができそうです。例えば、成功した場合には2分間のディープスリープ、失敗した場合には5秒間のディープスリープとして再トライさせるなどの使い方になると思います。