米NISTの原子時計のラボツアー
実験用のクロックだと思うけど、ナイトビジョンゴーグルが置いてあるのが面白いな(おそらくFLIR社のGen1)。機器では剥き出しの赤外線レーザーも使っているそうだから、センサーカードでトレースするよりNVGで見たほうが楽、ってことなんだろう。
フィクション世界でよくある赤外線レーザーをナイトビジョンゴーグルで見るやつ、現実世界でも実際にやってるわけだ。
コロラド州ボールダーってアメリカのかなり内陸の方にある。標準周波数・時刻を放送するにはそのほうが都合がいいんだろうけど、最近の一般相対論的効果が問題になるような(標準海面からの標高が必要になるような)時代になると大変そうだな。まあ、国土を網羅した水準点からちょっと伸ばしてラボに引き込めばそれで済むんだろうけど。
将来的に標高計としての精度が10cmとか1cmとかのオーダーで日常的に得られるような時代になると、あまり内陸すぎると標準海面との結合が面倒になって海岸近くのラボのほうが使いやすいって話になったりするんだろうか? あるいは日本だと周りを海に囲まれていて潮汐が綺麗に流れないからモデル化が難しい、みたいな課題が出てきたりするんだろうか? 潮流を遮ることのない孤島に基準点を置いてコモンビューで結合するとかが必要になるのかな。
単に海面と結合できればいい程度なら、東京大学は日本水準原点から近くて便利そうだ。NICTもそう遠い場所じゃないし。
秋月電子、最近全然使ってなかったけど、久しぶりにいろいろ探して見るとディスコンマークだらけだな。
秋月製のブレークアウトボードがだいぶ前からSparkfunみたいな設計思想でなんだかなーと思ってたけど、より一層電子工作初中級者向けに力を入れる一方で、かゆいところに手が届くような商品ラインナップを減らしているような雰囲気。
ストリナもストリナで変なフォームファクタのブレークアウトボードを量産していた時期があったけど、最近はあまり見かけない気がする。そもそも最近は新製品をあまり作っていないというのもあるけど。ストリナは最近もちらほらセンサ類を出してるけど、基本的には電源周り専門メーカーみたいな感じになってきたな。
最近は中国系の安いPCBメーカーが使いやすくなってきたから、変なSMDを使いたい人は自分で基板を起こしたり、部品もMouserとかで買ったりするようになって、小さい店だと中上級者向けの商品ラインナップは厳しいのかもしれないけど。個人向けにニッチな製品が買えるショップは大陸系のECストア(含不正規品流通ストア)がその役割を取って代わって、実店舗は入門・初中級が気軽に欲しい部品を買える方向に特化して、みたいな。時代の流れ的な。
Type-C PDのデュアルロールなモバイルバッテリーとかで、ロールを強制的に切り替えるボタンが欲しい。例えばノートPCとモバイルバッテリーを接続した場合に、ノートPCの電力でモバイルバッテリーを充電したり、あるいは逆にモバイルバッテリーからノートPCを充電したり、というのを、気軽に切り替えられるようにしてほしいわけだ。
CCのブリッジみたいなICで、特定のメッセージを捨てるとか、強制的にロールをスワップするみたいなICもありそうだけど、気軽に安心して信頼できる製品でそういう物があるかというと…… エレコムやサンワサプライあたりでそういう機能(ユーザーインターフェース)を内蔵したPD充電ケーブルとか売ってくれるとありがたいんだけど。あるいはスイッチ無しでも、ダイオードとして使えるPDケーブル(ケーブルの向きを変えたら給電方向を切り替えられる)とか。
興味本位で海外通貨の購入方法を調べてみたら、近所のローソンが出てきて、いやいや、んなわけwwと思ってたんだけど、そういえばこのあたりってなまじ観光地なので、訪日客向けに外貨両替の需要もあるのか。
「外貨から邦貨への両替のみとなります」だそう。そりゃそうだろうな。
Google Mapには近所にもこの機械が置いてあるらしいんだけど、公式サイトの一覧には書かれていない。謎い。
旭川空港にも外貨両替機は置いてあるけど、100ドルとか100ユーロとかそのあたりの1金額しか対応していないらしい。海外旅行に行くために大きな額を両替したいわけじゃないからなー。単に小額紙幣を何種類か見比べるために手元に欲しいだけであって。
https://www.amazon.co.jp/dp/B076H4R6J6
大量の毒ヘビが飛行機の中で暴れまわる、みたいな感じの作品。毒が出てくるので若干グロ表現ありのB級ホラーなので、他の人に勧めたいという気はさらさら起きない作品だけど。
大昔に読んだB級映画の紹介で面白そうだと思って頭の片隅に入れていた作品。「フライトシミュレーター」(プレステ2)で2000時間の飛行経験を持つデブが最後に着陸を決めてサミュエル・L・ジャクソンが「神様プレステ様!」と叫ぶというシーンがあるらしくて面白そうだなって思って。
/* Bloggerのテキストエディタで黒文字黒背景にすると若干見えちゃうんだな。黒文字は明示的な色指示が無いので、スタイルシートとかブラウザの設定で真っ黒ではなく、濃い灰色で表示されるっぽい。ちゃんと見えないようにしようとすると、スタイルのcolorとbackground-colorに同じ色を指定しないとだめっぽい */
***
間接的にニュートリノを見る生態系、という空想。
惑星が恒星に近く、ニュートリノ断面積の大きい物質が比較的リッチな環境で、化学的に光を遮る物質がすくなく、地理的に恒星光が届かないような環境で進化した生態系を想定。このような環境では環境がニュートリノチェレンコフ光で発光しているはず。端的に言えば、このチェレンコフ光を見ることができるように進化した生物群。
惑星の軌道が小さく常に暗黒の世界ということは、潮汐ロックした環境だろう。夜間側で生態系を維持できるということは、昼間側はもちろん、トワイライトゾーンも生息するには高温すぎる環境のはず。であるならば、恒星光を浴びたことがない生態系もあり得るはず。ただ、その場合は初期の低効率な光受容体を選択して残すことができないから、チェレンコフ光が見えるような高感度な目に進化する可能性がかなり低い。
昼間側あるいはトワイライトゾーンで発生・進化した生態系が目を十分に進化させたあとで、何らかの要因で惑星軌道が縮小して生物は夜側へ移動し、その過程で高感度な目に進化させた、みたいな可能性はあり得るけど、ちょうどいい時間スケールで軌道を変えるのはかなり難しそう。あまり早すぎると生息地を移動する前に絶滅するだろうし、天体物理的な尺度での短期間でも、生物が進化するには十分に短い期間になるだろうし。
あるいは、大きな軌道の時点では潮汐ロックはしておらず、惑星全体(少なくとも赤道全体)に生態系が分散していて、何らかのイベントで近日点が極端に下がって、かつ遠日点も下がって、一部の地域にいた生態系がたまたま夜側で生き残る、みたいなことは考えられるか。公転軌道を都合よく2段階に変えるようなイベントも思いつかないけど。1回だけならショルツ星みたいに恒星の接近で軌道がずれる可能性はなきにしもあらずだけど、もう1回となるとなぁ。内惑星とのスイングバイとかでなんとか…… それにしたってきれいな円軌道に持っていくのは難しいし。
もしもニュートリノチェレンコフ光を見ることができる生態系があったとすると、進化の過程でニュートリノ断面積の大きい物質を排除するような選択もあるんだろうか? 「目に見える」食べ物を体内に取り込むと早く捕食されるということにはなるだろうけど、だからといって「目に見えない」食べ物をどうやって探すかという問題が出てくる。電気信号を探すみたいな器官が出てくるとかかな。そもそも「目に見えない」食べ物はどうやってニュートリノ断面積を下げてるんだという問題になるし。
ニュートリノを「(間接的に)見る」というよりは、単純に光源として使っているだけではある。恒星に極めて近い惑星で、通常であれば温度が高すぎて生物が生存できない環境であっても、裏面なら適度に冷えるはずだし、裏面なら有害な放射線もほとんど完全に遮断できるが、惑星を貫通するニュートリノのチェレンコフ光を光源にして数百nm程度の電磁波を見ることができる生物が作れないか、みたいな考え方。
地球の海洋でもニュートリノチェレンコフ光はそれなりの頻度で発生しているだろうけど、とはいえそれを見られる生物がいるかというと…… 人間がニュートリノチェレンコフ光を探そうとするとアホみたいな大きさのPMTが必要になるからなぁ。よほど強い理由がないと光受講器官をそこまで発達させるのは大変そう。
***
EthernetってそのルーツはALOHAnetとかにもつながるわけだけど、これはUHF(つまりRF)でパケットをやり取りしていた。10BASEとかで明示的にベースバンド信号と書いてあるのは、それらと比較してという点もあったんだろうか?
W5500のSocket n TX Write Pointer Registerの使い方が結構謎い気がする。送信する場合、データを入れたりSENDコマンドを叩いたりはコントローラー側で管理できるから、わざわざリングバッファを使う必要はないはず。RX Bufferみたいに、バッファの中にデータ長を持っているならともかく、そういうわけでもないし。書き込みするときにポインタをキューに持ってとかをやれば送信メッセージをキューイングできるけど、バッファの空き容量も自分で管理しなきゃいけなくなるし。
W5500のSPIは基本的にCSをトグルして可変長(VDM; Variable Length Data Mode)で転送するけど、固定長(N=1,2,4、FDM; Fixed Length Data Mode)の場合はCSをトグルしなくても連続してデータを送ることができる。アドレスが離れた場所に8bit,16bit,32bitのデータを書いたり、同じアドレスに連続してデータを書いたりしたい場合は、FDMで転送するとCSトグル分の時間を省略できる。W5500のデータシートではCS Highの最小時間は30nsだから、無駄になるような時間でもないけど。
W5500の場合、異なる番地に連続して値を書き込みたいという用途はさほど多くない。例えば宛先のIPアドレスとポートは連続した場所に配置されているから、まとめて6バイト転送(VDM)を行えばいい。強いて言えば、バンクを超えた場所(別のソケット)に対してまとめて読み書きしたい場合、とか? そんな使い方が必要になる状況も思いつかないけど。
同じアドレスに書き込む用途は、例えばソケットにコマンドを送る場合が考えられる。例えばあらかじめ送るデータをTX Bufferに書いておいて、OPEN, SEND, CLOSEのコマンドを連続して送る、とか。
今回の場合、ソフトSPIなのでクロックレートが極端に遅いので、1バイト転送(ヘッダ含めて4バイト/コマンド)に1ms程度かかる。このくらいの速度なら、OPEN, SEND, CLOSEを並べても問題なく送信できるようだ。UDPで送ったデータも正しく受信側で受け取れている(今回は5バイト転送)。10Mbpsなら1msあれば1万bitを送れるから、数バイト程度なら余裕で送れる。
SPIクロックレートが20MHzとか50MHzとかで、コマンド間隔がマイクロ秒程度まで狭くなると厳しそうな気もするけど、とはいえSENDを打ってからすぐに転送が始まって、CLOSEを受けても先に送ってるパケットを出し切ってから閉じるだろうし。
あとは、書き込みコマンドと読み込みコマンドを連続して出せる場合がある、みたいな利点もあるか。何に使うかわからないけど。SENDコマンドを打ったあとでInterruptを読んで、最速で送信が終わればSEND_OKが得られる、とか?
試しにDHCP discoverを送信。offerが帰らないけど、Wiresharkで覗いたらちゃんと出てる。結論として、discoverを送ってからW5500をダンプするまでに0.1秒くらい待たせていたのを、もっと長く(2秒程度)待たせたらofferが見えるようになった。うちのルーター(DHCPサーバー)の特性なのか、DHCPとはこういうものなのか、応答が返るまでにちょっと時間がかかるっぽい。
DHCPは貸し出そうとしているIPアドレスを使っている端末がないかどうかを確認するらしいから、それの待ち時間の分かも。WiFiとか多少レイテンシのある経路を考えると、応答がないと判断するまでには多少待つ必要があるし、それに応じてDHCP offerも時間がかかる、ということらしい(Wiresharkでdhcp or arpでフィルタすると、discoverとofferの間にarpが挟まる事がある。オファーを受けずに頻繁に探索するとarpを省略することがある?)。
Wikipedia曰くDHCPクライアントもOfferを受けた段階でARPを出して、自分が見える範囲(DHCPサーバーが見えない可能性のある範囲)にも割当予定のIPアドレスを持った端末がいないかを確認する必要があるらしい。
ただ、W5500からARPを出す方法がわからない。ぐぐってみると、TCPやUDPを出す前にARPを出すから、UDPタイムアウトをARPタイムアウトとして代用できるよ、みたいな話が出てくるんだけど、その場合って対象のIPアドレスが存在した場合は不要なUDP/TCPパケットを送信することになるはずなのだが、それってどうすればいいんだろう? ポート9にUDPで適当な長さのパケットを送ってみる、あたりが落とし所?
Wiresharkは便利だけど、デスクトップPCはSDRのパケットが2系列流れているから、全部取るとあっという間にメモリ使用量が10GBを超える。/* デスクトップPC組んだときに後で増設しようと思って4スロットMBに2スロットしか積んでなくて、そのうち安くなるだろと思って放置してたらめちゃくちゃ高騰しやがって */
ディスプレイフィルタでは[dhcp or arp]みたいに設定すればいいけど、キャプチャフィルタはもう少し複雑な設定が必要そう(少なくともDHCPは直接指定できない)。例えば[arp or (udp port 67 or 68)]みたいな感じにすれば良さそう?
*
SIGLENTのオシロ、起動するたびにIPアドレスが変わるのが謎いので、せっかくだし、Wiresharkでキャプチャ(2回分)
DHCPはUnicastなのでDiscoverとRequestしか見えない。1回起動するたびにDiscoveryが3回出ているのが謎い。しかもトランザクションIDが2種類ある。
W5500からDHCPのDiscover→Offer→Request→Packでやり取りすると、同じMACアドレスに対しては同じIPアドレスを割り当てようとしている気がする。
SIGのオシロもMACアドレスは固定だから、同様に固定のIPアドレスが割り振られてもいいはずなんだけど、実際にはそうはならない。謎い。
ユニキャストで通信されると何やってるかわからんな。リピーターハブを買えばいいんだろうし、amazonのレビューを見てもそういう用途で買ってる人が多いようだけど、しかしリピーターハブってわりといい値段するのな。高性能なはずのスイッチングハブのほうが安いなんて、量産効果すげーや。
速度を固定してツイストペアを分岐してEthernetアダプタに突っ込んだりしたら、パケットをキャプチャできたりしないだろうか? 電力が半分になるけど、短距離の接続なら-6dB程度は問題ないだろうし。この方法だと片方向しか取れないけど、アダプタを2個使えばTXペアとRXペアを同時に監視できる。1000BASE-Tは双方向で使うからこの方法は使えないけど、10BASE-Tや100BASE-Tなら取れそう。この方法なら全二重のフルの帯域(20Mbps、200Mbps)も取れるはず。あるいは、半二重でいいなら、TX/RXペアを分岐したあとでアナログ的に加算してもいいはず。
***
ArduinoでPORTD |= bit1;とかPORTD &= ~bit2;は許されるのに、PORTD = PORTD & ~bit2 | bit1;とかは許されないの謎い。エラーメッセージがno match for 'operator&'とかだから、PORTDとかってメモリマップドで直接触るわけじゃなくて、クラスでラップしてるらしい。
PORTB&=1;とかやると読み出し、ビット操作、書き込みで遅そうだけど、パルス幅を測ってみるとちゃんと65nsあたりに見えるから、たぶんうまいことOUTSETとかOUTCLRに展開してるんだと思う。だからPORTB=PORTB&~1|2;みたいな操作ができない。
あと、GPIOから読みたい場合、例えばPINDで8bitを読める、という説明があるんだけど、少なくともうちの環境だとコンパイルエラーになる。Google AI曰くVPORTB.INで読めるよ、とのことだけど、これはATmega側のレジスタなので、ビットマスクに互換性がない。
ATmega側にはPORTA...Fがあるけど、PORTB...DはArduinoが上書きしているから、ATmega側のレジスタを直接触ることができない。
Arduino、公式ボードだけでも大量にあるし、互換ボードも含めればさらに増えるし、それぞれで低レベルな部分は互換性が無いし、ググって出てきた情報がどのボードのものかも分かりづらいし、細かいところを触ろうとするとやっぱり大変。
かといって、今更STM32もなぁ…… 比較的ガッツリSTM32に触っていた頃は手癖でプロジェクトを作ってmakefileを設定してビルドして、とかできてたけど、触らなくなってかなり経つからSTM32+STM32CubeMX+gccとかはちょっと面倒。
今の時代に小さいマイコンでちょっとした制御を素早くやりたいなら、RasPiPico系にC++開発環境を載せて、速度が不要ならMicroPythonを使って、みたいな感じが良いのかな?
***
一旦方針転換して、大昔に買ったストリナのFT232Hボードを叩いて遊ぶ。
ちょっと前にこのボードのVer.2が発売されて、ディスコン部品の変更のほか、オンボードでLDOが乗って、外に3.3Vを出せるようになった。手元にあるやつはVer.1なので、5V出力のみ。W5500みたいに3.3V電源が必要なやつを相手にしようとすると、やはりLDOが乗ってる方がありがたい。
FTDIのMPSSEを使うと、対応している変換チップならI2CやSPIのような同期シリアルバスやパラレルバスの変換チップとして使うことができる。昔は結構面倒そうな感じだった気がするけど、最近はSPIとかI2Cはラッパーが追加されて簡単に使えるようになったっぽい。
試しにVer_libMPSSEを叩いて、DLLバージョンが読めることを確認。libmpsseはダウンロードした1.0.8と一致するけど、libftd2xxは3.2.21で、これはよくわからない。少なくとも、デバイスマネージャーで見えるFT232Hのドライババージョンとは一致しない。ググってもこの数値は出てこない。頭にlibとついているから、SPI/I2Cラッパーの下、ドライバの上の部分なのかも。
続いてSpiGetNumberOfChannelsを叩いてみる。接続したFT232Hが認識されず、channelsが0固定で出てくる。……なんのことはない、FT232HをTeraTermで開いていたのが原因。閉じたら1が帰った。
続けてSPI_GetChannelInfoでデバイスの情報を取得。DescriptionはASCII文字列(64bytes)だけど、SerialNumber(16byte)はバイナリっぽい。なんの数値かよくわからん。シリアルというくらいだから特に意味はないんだろうけど。
SPI_OpenChannelとSPI_CloseChannelも、たぶん動作。
この手の処理(DLL周り)、いい感じに実装する方法がいまいちよくわからん。ハンドルはnintで持つけど、nint剥き出しで取り扱うのも面倒だからクラスでラップしたいし、usingで閉じれるように関数の戻り値でインスタンスを返したいけど、開けなかったときは例外を投げるかnullを返すか、とか、いろいろ。if using(var dev = new Device()) { 正常系 } else { 異常系 }みたいな構文が欲しい。devが非nullのときはifブロックに入って、ブロックから出るときはusingを呼んで、devがnullな場合はelseブロックを処理して、みたいな。結局using var dev = new Device(); if (dev is not null) { } else { }で書くのと同じだから1行減るかどうか程度の違いでしかないけど。あとは変数のスコープの違いもあるけど。
あとは、列挙型の持ち方も。readonly record structで定義しておくとToStringが自動的に実装されるからデバッグが楽だけど、当然書き換えができない。
ここまでは順調だったんだけど、SPI_InitChannelでハングアップして進まなくなってしまった。色々試して、LatencyTimerに値を設定すると動作することが判明。ヘッダの定義には「value in milliseconds, maximum value shuld be <= 255」としか書いてなくて、8bit型だから「そりゃそうだろ」というような説明なんだけど、原因がわかってからフォルダ内でLatencyTimerに関する説明を探してみると、release-notes.txtの一番上のLimitationsの中に「LatencyTimer shuld be grater than 0 for FT232H」とそのものズバリの指示が書いてあった(FT232Hか否かはGetChannelInfoで見れるから、必要に応じて最小値の分岐もできる)。
FT232HにはPORTDが8本あって、SPIはSCK, MOSI, MISOの3系統が必要なので、残り5本からCSが選べる。ここで選んだCSは明示的に出力に設定してやらないといけない(SCK, DO, DIはうまいことマスクしてくれる)。CSの論理も指定できるので、HでEnableになるシリアルデバイスを制御したりにも使える。
SPI_InitChannelで渡すパラメータを、SPI_ChangeCS関数で渡すこともできる。CSは少なくとも5本使えるから、5個程度のSPIデバイス(CS論理が反転しているものも含む)を接続することができる。
あとは、パラメータが問題ない場合でも、まれにInitでハングアップすることがある。そういう場合はデバイスの挿抜でハードウェアをリセットすれば解決する場合がある。
データの転送はSPI_Read, SPI_Write, SPI_ReadWriteがあって、おおむね期待通りの動作をする。これらの関数に渡すオプションでCSを操作できて、例えばbit1を立てれば開始時にCSをアサートするし、bit2を立てれば終了時にCSをネゲートする。それぞれ0なら何もしないから、10バイト書いたあとに10バイト読むがその間はCSをアサートし続ける必要があって、転送が終わったらネゲートしたあとにさらに2バイト送らなきゃいけない、みたいなシーケンスも、問題なく要求できる。
あとは、バイト数でなくビット数で転送数を指示するモードもあるらしい。オクテット単位でなく、中途半端なクロック数でも送れそう?(未確認)
ただ、SPI_ReadWriteがなんか変な気がする。送信バッファが、最初にゼロ埋めされた8bitが出て、続けて指定したバッファが(つまり1バイトずれて)出る。受信側はおそらく問題ない(ループバックさせれば正しく1バイトずれた結果が得られる)。同じような操作(GCHandle/Dllimport)でSPI_Writeを呼べば問題ないから、たぶんReadWriteのバグだと思う。SPIで全二重が必要な状況はさほど多くはないけど、皆無と言い切れるわけではないので、ちゃんと使おうとすると困りそう。あと、読み書きをWrite/Readで分割して半二重で頑張るにしても、コマンドを分割するとその分多少のレイテンシが増えるし。
面白い機能としては、SPI_IsBusyという関数があって、クロックの操作無しにMISOを読むことができる。例えばコマンドを送ると処理中はMISOがLowで、処理が終わったらMISOをHighに上げる、みたいなデバイスを監視することを想定している(もちろん逆も可)。ただ、SPI_IsBusyを呼んだ場合、CSのトグル操作(アサート&ネゲート)が挟まる。なので、一旦デアサートされるとMISOにビジー状態を出力しないようなデバイスを使うことは基本的にできない。
もしCSのトグル無しにビジーチェックを行いたい場合は、CSのネゲートは省略したWriteで処理を指示し、Write後に一旦CSをChangeCSで別のピンに変更したうえで、IsBusyでポーリングして、ビジーが終わったら再びCSを戻して、SPI_ToggleCSでデアサートする(あるいは0バイトの書き込みでデアサートだけさせる、あるいはIsBusyで解放する)、みたいな処理は可能。
ただし変更先のCSがジタバタするのと、それに接続されているデバイスがMISOへ出力して競合するので、この用途で使うCSはNCとしておく必要がある。MPSSEにはCSが5本あるから、こういう使い方をする場合はデバイスは4個までしか接続できない。
SPI用の端子とは別に、GPIOが8本あって、任意に入出力として使うことができる。デバイスの割り込みピンを読んだりに使える(あくまでもアプリケーション側からポーリングして8bitバスを読む必要があるが)。書き込み時はDirも渡さなきゃいけないので、このあたりは適当なラッパーを作ると良さそう。どうせハンドルを持つクラスで隠すだろうしな。
GPIOをCSとして使うことも可能ではあるけど、操作が煩雑になる欠点はある。あるいは、3to8デコーダとかを外付けして、デコーダのEnable端子にCSを接続して、みたいなことをすればバンクを切り替える感じでデバイスを大量にぶら下げることもできる。あまり多く接続するとSPIバスの負荷が大きくなるけど。
結局、SPIデバイスを4,5個つないで10MHzくらいで使うのが安心かな。
SPIとGPIOを操作するサンプル
CS5本に対して、1本目はWrite, Read, ReadWriteを8バイトずつ、2-5本はWriteを8バイトずつ、その後、GPIO8本に対して32回のトグル操作。
Writeの前とReadの後にちょっと時間がかかってるっぽい。その割にReadWriteの前はほとんど待ち時間がないのが謎い。それにReadWriteの後が長すぎるのも。
GPIOは32回のトグルで1.54ms程度なので、1.54ms/31=49.7us程度と、ほぼ50us毎に操作ができる(矩形波を出して10kHz)。ジッタが大きいのはWindowsだししょうがないと思う。むしろ非RTOSでマイクロ秒程度の操作ができることのほうが驚き。コンテキストスイッチがなければそれなりに早いだろうけど、とはいえドライバ周りとかもレイテンシあるだろうに。USB HSのポーリング間隔って8kHzのはずだけど、最短27us(逆数で37k)でトグルってどうやってんだ。
VCPで開いている場合はGetNumChannelsから見えなくなるけど、OpenChannelでMPSSEとして開いている場合はGetNumChannelsから見えたままになる。例えばGetNumChで1が得られて、OpenChannel(0)で開いた場合、再度(or別のアプリから?)GetNumChで取ると1が帰るが、index:0で開こうとすると、エラーが返る。
どうせOpenChannelで開いて正常orエラーで判断するしかないなら、GetNumChannelsを呼ぶ必要性はよくわからん。
しいて言えば、GetChannelInfoでデバイスのハンドルが得られるから、MPSSEとして開いているかは確認できるので、GetNumChannelsでデバイス数を得て、それぞれにGetChannelInfoで使用中かどうかを確認して、未使用なMPSSEを使う、みたいなことは考えられるか。あるいは、GetChannelInfoでシリアル番号を読んで特定のデバイスを選択するとか。
他のアプリが開いているデバイスでもハンドルが取れるのかは未確認。それが取れたら結構変な使い方ができるけど、リソースの競合とかも怖いしなぁ。
I2Cモードも試しに叩いてみたけど、結構クセが強そう。SPIモードのSCKがSCLになるのは期待通りだけど、SDAはMOSIで書いてMISOで読む感じになる。MOSIとMISOを短絡しなきゃいけないので、SPIとI2Cを切り替えながら使うような用途には不向き。そんな使い方したいかどうかは別として。あるいは、GPIOで制御信号を出してフォトカプラとかでつないでもいいのかもしれないけど。
クロックレートは任意の数値を設定できるが、ヘッダにはよく使う速度として100kbps、400kbps、1Mbps、3.4Mbpsが定義してある。
あと、Read/Writeのオプションが結構複雑で、うまいこと設定しないと正しく動作しないっぽい。なんとなく正しく動いていそうなパラメータはあるけど、本当にこれでいいのかはわからない。スタート/ストップコンディションの有無とかアドレスのスキップとか、色々オプションも設定できる。不要な場合を除いてStart/Stopは明示しておけばとりあえず大丈夫かな? あとはFAST_TRANSFER_BYTEもあるとよし。
細かいところだと、Readで得られる転送済みバイト数が、バイト数ではなくビット数で帰ってる気がする。
I2Cで1バイト書き込みの拡大
3.3V系で4.7kΩ、100kbps。初期化オプションに2を指定している(HiZ/Lowモード)。
I2CはSPIに比べてコマンドを出せる周期が低い感じ。レジスタを読む場合、書き込みと読み込みを個別にやらなきゃいけないので、その間少し時間がかかる。
試しにTSYS01(大昔に何かで使った予備)を試してたんだけど、なぜかリードでアドレスを指定したときにNACKが頻発することがあって、うまく通信できなかった。結局、Resetコマンドのあとに10ms程度の待ち時間を入れて解決(Windowsが管理しているから25ms程度待ってるけど)。データシートにはそれらしいことは書いていないけど、Reset後に立ち上がるまで少し待たないとだめらしい。うまく動いたときは、もしかしたらOSやFT232の遅延でいい感じに時間が経っていたのかも。
うまく使えれば妥当そうな値が読めるので、多少の事に使うなら可能ではありそう。
試しにI2C High-Speed mode対応のデバイスを接続
同じく3.3V系、4.7kΩ、100kbps/3Mbps。
先に100kbps/ODでデバイス04h Wを呼び出して対応デバイスをHsへ移行させて、その後にクロックを3Mbps/PPへ変更してからSrに続けてデバイスのアドレスを指示し、レジスタを指示、さらにSrとアドレス、2バイト読み出し、というシーケンス。/* Hs移行指示は04h Wを使っているけど、これは予約されているので、本来は04h Rや05h W等を使う必要がある */
明らかにドライブ能力が足りていないので、オシロでは閾値を任意に設定できるから正しい値が読めているけど、ロジアナやFT232Hでは正しい値が読めない。
ちなみに、Sm/FmからHsへの移行指示を省略していきなり3Mbpsでデバイスアドレスを送ると、アドレスNACKが帰るが、Hs移行指示を出してからデバイスアドレスを送れば正しくACKが出るから、デバイス側のHsへの移行は正しく行われているはず。
NXP UM10204を眺めてみた感じ、Hsでプルアップを強化するのはコントローラーの責任であって、通常時(Sm/Fm)は通常のプルアップ抵抗オンリーだが、Hsに切り替えるコマンドを出した時点でコントローラーは電流源をONにする必要があるらしい。ただ、この図ではあくまでもSCLに対して電流源を追加するというような書き方にしか見えず、SDA側は通常通りのような気がする。データラインはどうするのがいいんだろう?
おそらくFT232Hには電流源を追加するような機能はないので、適切なHsコントローラーとして使うことはできないと思う。どうしても必要なら、FT232HのGPIOに定電流ダイオードをつけておいて、Hsに切り替えた時点でGPIOもトグルして、SCLとSDAをそれで引くという手はあるけど。
あと、Sm/FsからHsへ切り替えるためにはいちいちI2C_Initを呼んでクロックを切り替える必要があるが、それに0.2秒弱かかる。よほど大量のデータをやり取りするのでもなければ、Fsで使ったほうが早いと思う。
SPIでもTSYS01に接続
ちゃんと読める。SPIの場合はビジーを取れるので、リセット後の2ms程度やADCの8ms程度も適切に待てる(A7:灰色のジタバタしているのがCS退避先)。
クロックは10MHzで使用(TSYSは20MHzまで)。ちゃんと速度は出るけど、その分スカスカに見える。
libmpsse、まだまだ開発中という感じが拭えない気はする。仕事で使えるかというと、ちょっと怪しそう。どうしてもPCとSPI/I2Cのブリッジが必要なら、FT232HをUARTで使ってその下に適当なマイコンを挟んだりして、テキストベースでコマンドを打って必要に応じてバイナリデータを交換して、みたいな感じのプロトコルを作ったほうが安心できそう。マイコンをHIDでPCに直結する手もあるけど、その場合はたいていFSまでだろうから、帯域幅が厳しい。手軽にやるならやはり232H経由のコマンドが楽そう。
***
乱数発生器を内蔵した小さなチップって無いものかな、と思って軽くググってみたけど、あんまりなさそう? 電源ONで常時1Mbps程度で乱数を作って、十分に長いサイクルで予測が難しく、起動時に確実に乱数が出るようなしくみを持っているもの。
LFSRの幅が100bitとかはちょっと計算が重そうではあるけど、数十Mspsとかであれば最近の半導体なら流してくれないかな。例えば100bitのLFSRを適当な頻度(数秒に1回程度?)でFeRAMとかに保存して、次回起動時はそこから再開して、工場出荷時に適当な初期値を書いておいて、みたいな感じで、乱数はSPIとかI2Cで好きに読むことができるような感じで。
ある程度強度のある乱数が欲しい用途って、暗号化とかセキュリティの分野だから、大抵はそういうモジュールにTRNGも入っている印象。外付けでそういうのが必要な用途ってあんまりなさそう。
マイコンにFeRAM的な長寿命のNVRが乗ってるなら、それで32bit程度のLFSRを作って必要なときだけ生成すれば、予測不能性が不要ならそれで十分だろうしな。FeRAMでなくても、32bit程度ならバッテリバックアップドメインの小さいRAMにも入るだろうし、ある程度綺麗な乱数が必要な用途ならRTC用の電池とかも積んでるだろうし。
***
いよいよRasPi Picoに手を出すかー、と思ってamazonでポチってみたら、明らかにRSコンポーネンツのパッケージに入った基板が届いた。RSで620円で売ってるやつがamazonで1400円かぁ…… 送料含めてもRSで買ったほうがだいぶ安いんだな。でもRSはクレカがないと注文できないので。社会的な信用がない人間はこういうところで損が目に見えるな。
このRSの袋が正規品なのであれば、中身も正規品と考えて妥当なはずだが……
SPIのブリッジにArduino Nanoを使うよりRasPi Picoを使うべきじゃねと思ってポチったけど、FT232HでPCにSPIが直結できちゃったので、SPIブリッジを自分で作る必要がなくなってしまった。。。
買ったまま開封すらせずに積むのもあれなので、とりあえずVS Codeの拡張で開発環境を入れて、blinkをビルド。USBで接続してストレージにblink.uf2をコピー。コピーすると勝手にリセットされて走るが、ユーザープログラムが走っている間はストレージとして認識できなくなるから、プログラムを書き込みたい場合はBOOTSELを押しながらRUNを地絡してストレージとして再認識させなきゃいけない。
書き込みの手順としては、リセットボタンを押してプログラムを止めてからBOOTSELを押してリセットボタンを離して、USBとして認識されたらコマンドプロンプト等でcopyコマンドで書き込む、みたいな感じで、STM32F4のUSB DFUと同じような手間で使える。ただしRaspberry Pi Picoはオンボードのリセットスイッチが無いので、どうにか頑張るしかない。
Picoを2枚買いして1枚をターゲットボード、1枚をデバッグアダプタとして使って、開発環境からデバッガで書き込んで走らせるほうが楽そう。この場合はデバッグ端子を接続する方法を考える必要があるけど。
STBee Miniってオンボードでリセットスイッチやブートスイッチがついてて、買ったらそのままLチカできるしボタン入力も試せるから便利なのよな。スイッチが小さくて押しづらいというのはあるけど。
BOOTSELはブート用のFlash ROM(QSPI)のSSにアクティブLowで接続されている。通常はプルアップされていて、RP2040が起動したときにSSがHighならFlash ROMから起動するが、SSがLowならRP内蔵ROMからUSB MSCを起動する、という感じになるらしい。
ユーザープログラムからBOOTSELを読むことも可能だが、このピンはQSPIのSSだから、BOOTSELを押している間はFlash ROMからのプログラムを実行することができないので、普段使いしていいものでもなさそう。
そのためか、BOOTSELはRasPiPicoの外部には引き出すことができない(クロックほどではないにしても、ある程度狭いパルスを通すから、変に引き出してトラブル起こされても困るからってのもあるだろうけど)。なので、外部にリセットスイッチとBOOTSELスイッチを追加して、ボタンを2個押してUSB MSCを起動する、みたいなことができない。手動でUSBを起動するなら外付けのリセットスイッチとオンボードのBOOTSELを組み合わせるしかなさそうだ。
BOOTSELを押せばFlashからのプログラムの読み出しが止まるから、基本的にユーザープログラムは停止するはず。というわけで、WDTを設定しておけば、BOOTSELを押せば勝手にリセットされて、USBが起動する。RAMから走らせるプログラムでWDTをリセットしながら無限ループさせたりしていると機能しないけど、そういう状況を除けば、おそらくRasPiPicoを単体で使うときに、一番楽にプログラムを書き込む方法はこれだと思う。
自分でcopy hoge.uf2 f:\とか叩く手間はあるけど、RasPiPicoのMSCは2E8A:0003として見えるから、これを検出したらファイルをコピーするスクリプトを書くとか、あるいは指定したパスで単に1秒毎にコピーを試みて、ドライブが認識されていなければ単にコピーに失敗するとか、色々工夫は考えられる。BOOTSELボタンを押したら勝手にプログラムが転送されてそれが走る、みたいなことはできるはず。
RPには命令キャッシュ的なXIPという機能があるらしくて、こいつには16kの領域があるらしいので、それを有効にした場合はBOOTSELでQSPIを止めても小さなプログラムなら走り続けそうだけども。ぐぐってみるとXIPはデフォルトでONらしい。じゃあなんでBOOTSELで止まるんだ……
とりあえず、Lチカは確認できたし、sleep_msの引数を変えれば周期も変わるから、開発環境も含めてそれなりに正しく動いているはず。
VS Codeに拡張1個(+自動で入る3個)を入れるだけで使える開発環境。めちゃくちゃ簡単だなー。
***
C#でbyte hoge=(int)1;みたいな定数のダウンキャストで、キャスト先の範囲に収まる場合はエラーにならないんだな。列挙型も定数なので溢れなければ暗黙的に変換できる。一旦int型に明示的にキャストする必要はあるけど、例えばenum Hoge{foo=0x0001,bar=0x0100}があってbyte value=(int)Hoge.foo;は許可されるけど、byte value=(int)Hoge.bar;はエラーになる。byte value=(byte)Hoge.bar;もエラーになる(無理やり通すならuncheckedで囲む必要がある)。