MOSの覚書

趣味人の公開メモ帳

ArkEdge Space でのインターン経験を振り返る|第一年度

今回は,ArkEdge Space という,宇宙開発を行っている企業でインターンをした経験を,月ごとに振り返ってみようと思います.なお,事あるごとに書きためたものの総集編的なアレです

10 月

インターン生として 株式会社 ArkEdge Space への入社(?)が決定しました.面接の時にも言われたことですが,そこら辺のコンビニバイトよりは良い給料が出るそうです.技術力が必要となるからなんですかね.ワタシそんなに技術ないけど大丈夫かな

ちなみにエンジニア補としての採用になります.内容としては主にハードウェア寄りの開発かな.これにより,なんちゃってエンジニアからエンジニア補に昇格です.やったぜ
また,本社が東京で私は北陸に住んでいるので,リモートインターンという感じになりました.リモートインターンで物理開発って大分難しいとおもう.いらない迷惑かけてスマンというお気持ち

というか,労働条件通知書みて思ったことですが,学部一年のインターン生に出す給料じゃないでしょこれ・・・
普通この半分くらいでは? と思うんですが,どうなんですかね.まあ,給料出してくれるだけでもありがたいことですが

ps. 履歴書出すの遅れてスマン...

11 月

入社し,オンボーディングを受けました
そこで,社用 Google アカウントの払い出しや各種ツールのアカウントのセットアップ,諸々の説明などを受け,業務開始です
(実際に担当する業務が決まって仕事を始めたのは 7 日のこと)

流石だな,と思ったのはいろんな物事にオートメーションが仕込まれてること.GitHub Repo にプルリクを発行し,marge されたらそれを元に色々走るみたいな.いや~すごい
例えば社内の連絡に使われる Slack.各 ch に誰が参加してるか,各メンバがどの ch に参加してるか,どんな ch があるか,などのリストが毎日自動的に更新されたり,Slack の参加する ch を管理するファイルを編集して PR 発行,marge することで自動的に kick / invite されるなど.細かな定形作業はあらかた自動化されている雰囲気です.とても私にはできない芸当

ところで,私に最初に割り振られた仕事ですが,それは衛星との通信に使うモジュールの調査でした.主に LoRa と Wi-Fi HaLow (どちらも 920MHz 帯)対応のモジュールです
双方ともに LPWA なモジュールで,無線 LAN 等に比べれば低速ながら長距離通信ができるというものです

LoRa 自体は前から知ってはいましたが,Wi-Fi HaLow なんてものは初めて聞きました.IEEE 802.11ah で規格化されているそうです.基本的に Wi-Fi は高速・広帯域を目指している印象がありますが,そんな Wi-Fi のイチ規格とは思えない異色の存在です.km 飛ぶんだぞこれ.ほんまに Wi-Fi かよ

12 月

11 月から特に変わったことはなく,普通に任された仕事をしていきました.そういえば初の給料が振り込まれたのは今月でしたね.労働時間が 30 時間程度だったのもあり 1 ヶ月分の給料としてみれば少額の部類に入るとは思いますが学生にとってはとてもありがたいものです

そしてこの辺から「インターンと言いつつ普通に仕事では?」と思ってきました.そういうこともあり,より一層使命感というのが湧いてきた気がします.思ってたのより倍くらいの給料出してくれてるのもあり,これからも頑張らねばという気持ちになりました

そして忘年会が開催されました.この一年で弊社の規模がかなり大きくなったらしく,とても賑やかな会でした.まさか自分も参加させてもらえるとは思ってなかったので,ちょっとびっくりしましたね
というのも,本社は東京.自分は地方在住なので,参加できないものと思っていたんですよね.ですが,Zoom を繋いでくれたので,オンラインながら参加させて貰えました

1 月

あけましておめでとうございます.これが公開されている頃には既に年度が明けていることと思いますが一応

1 月は主に社内 Wiki の記事作成に注力しました.本邦における 920MHz 帯の使われ方をまとめたものです.小論文並の文字数になってしまったのもあり作成には結構時間がかかりましたが,それなりにちゃんとまとめれたように思います.社内 Wiki は内部情報なので詳細はここには書けませんが,いろんな方がいろんな記事を書かれていて,結構参考になるものや周知されるべきことをまとめた良記事など,色々あります.見てるだけでも面白い

2 月

そろそろ書くことがなくなってきました.というのも,やってることが基本的に同じで,良くも悪くも目新しさがないからです.ただ,近い内に社内で作ってる基板のデバッグを手伝わせてもらえることになるかもしれないという話になってるので,それが来たらだいぶ変わるとは思いますが

というわけで,この辺で社内を傍から見てて思ったこと・気付いたことなどをつらつらと書いてみようかと思います

まず,毎月のように(いや,真に毎月かもしれない)新入社員が入られてます.採用を結構頑張ってるらしく,毎度毎度すごい方々がいらっしゃるので内心「僕が居ていい会社じゃないのでは・・・」と思っています.それくらい ArkEdge は素晴らしい人材が集う会社になってるってことですね.まだ設立して年も浅いのに,そうとは思えない成長具合です(誰目線よ)

ほんとに僕が居ていいんですか??? 大丈夫かな

そして,先ほどもちらりと挙げた社内 Wiki,これが結構すごいんですよ(語彙力).頻繁にいろんな記事が更新されていき,重要な情報からニッチだけどちょっと役に立つ記事まで,常に最新の状態に保たれていますし,バグ(表現としては適切ではないけどこう表現してみる)を見つければだれでも更新できる.中身 Git なのでミスってももとに戻せる.社内 Wiki って大事ですね.例えば「〇〇(共有設備)をきれいに使う Tips が集まる記事」のようなのがありまして,こういうのがあると共有設備をきれいに保つことができていいですよね.

もう一つ挙げると,社内の雰囲気がとてもあたたかい.新入社員が入られれば歓迎し,退職される方がいらっしゃれば特に近かった方々中心に送別会を開き見送る.その他諸々の手続きをするために連絡が飛び交う ch ではみんな言葉遣いとかちゃんとしてて,当たり前だけど大事なことをちゃんと守れる人しかいないように感じました.ほんといい会社だよここ

3 月

先に触れた社内で作ってる基板が送られてきました.下旬頃ですが

無線モジュールを積んでいるのが 2 つ送られてきて,これで無線とか積んでるセンサとか使って色々遊んでみてとのこと.基本自由にして良いらしいので,サンプルコードも眺めながら色々遊んでみてます
あまり詳しいことは言えませんが,将来的にはこれを元にしたなにがしを社で開発することになろうと思われるので,それにも携われたら嬉しいですね

最後の方で,5 分程度の LT をするということで,遥々上京し,初の物理出社をしました.オフィス自体には夏頃に一度伺ったことがあるんですが,まぁちょっとだけだったし実質初めてみたいなもんでしょ 出張という扱いになったので,交通費と宿泊費(実際には知人宅に泊まったのでなし)を出してくれるということになりました.ありがたい LT の内容ですが,例の基板を使ってみた感触についてお話しました.実際に設計された方とも話せたのでいい経験になりました しかも,恐れ多くも重役でおられる CTO の方から話しかけていただいたり,部長含む同じ部署の方々と食事に行ったり,これまたいい経験でした

終わるのが遅くなることはわかっていたので一泊してから帰るということにはなっていましたが,当日や帰宅日にある程度まとまった時間が取れたので東京在住の知り合い(Twitter の FF ニキたち)と会ったり,初の秋月電子実店舗で買い物をしたりと,充実した2日間を過ごせました.秋月行ったらそりゃそうなる感はありますが何気に 8k 円くらい使っちゃいましたね.余分にお金持って来といて良かったァ

結び

とまぁこんな第一年度でした.この書き散らしは今後も続けていこうと思っていますので今後ともどうぞよろしくの程~

2024 年の目標をつらつらと

あけましておめでとうございます(遅い).MOS でございます

2024 年になったということで,今年の目標ややりたいことなどをまとめておこうと思います

目標

今年の目標.一言でいうと「誰かに興味を持ってもらえるようなものを作りたい」です.そう思った理由について簡単に

2023 年までは,自分が欲しいと思ったもの,作りたいと思ったものばかりを作っていました.まあそれはそれで楽しいんですが,将来性があまりないなと思いました.せっかくなら自分が作ったやつで誰かに楽しんで欲しいものです.そういうわけで,とりあえず誰かに興味を持ってもらえるようなものを作るということを目標にしてみました.ただ他人の興味というものを知るのはなかなか難しいと思うので,可能な限り,という感じになるかとは思っています

やりたいこと

まず現時点でやりたいと思っていることを箇条書きし,それぞれについて二言三言書き残す感じでいきますかね

  • 工具をもっと充実させる
  • NT {任意} に出展する
  • 製品を 3 つリリースする
  • YouTube に何本か動画を投稿する

工具をもっと充実させる

現時点で使ってる道具でもそれなりにできてはいるんですが,ハンダゴテはコテ先が固着してしまっていたり,ニッパは錆びてきていたり(使えないわけではないので手入れするだけ)と,不満がないわけでもないので,そこを改善したいと思っています
例えばコテは白光の FX-600 にしたいし,ペンチ,ニッパ,ピンセットの種類を充実させたい.電動ハンダ吸い取り器も欲しい.等.お金がかかりそうな予感がしますがこれは追い追い...

本当は作業専用の場所も欲しいんですが,エレクトーンをどかさないことには場所は作れないし,かといってエレクトーンをどかすわけにも行かないので引っ越すまでは諦めですかねぇ

NT {任意} に出展する

例年各地で開催される NT,それに出展したいと思っています
場所と次期的に金沢くらいしか出せないかなぁ

製品を 3 つリリースする

趣味で作っている基板を使った製品というか,いうなればキットみたいなものをいくつか出したいと思っています
今のところ考えているのは両電源出力の定電圧回路キット,Audio Visualizer,ポータブルゲームハードです.最後のに関しては実現可能性はだいぶ低いと思っていてください.自分もそう思っているので

YouTube に何本か動画を投稿する

YouTube チャンネル自体は持っていますが,ほぼライブ配信用にしか使っていないので,キットの組み立て動画とか,簡単な電子工作の動画とか,そういうのを出したいなと思っています
カメラ買ったら本格的に始めようかなぁ(やらんやつ)

締め

本年も私 MOS 及び Nch-Lab をよろしくお願い申し上げます mm

NFC カードのパスワードは破れるか

手元に 6Byte のパスワードが掛けられた NFC Type-A(Mifare Classic 1K)のカードがあります.これのパスワードを破ることはできるでしょうか?

結論を言うと無理です.解散!!!

...ではおもしろくないので,如何にしてパスワード突破を試み,無理だと悟ったかを紹介しようと思います

本題

ちなみに手元にあるカードっていうのは学生証です.中のデータ読んでみたくて NFC カードリーダモジュールを買ってみました.Amazon にて 2 個セット 700 円くらい.安いですね

https://www.amazon.co.jp/gp/product/B088FLF8MC/

こちらを購入し,Arduino と接続しました

接続方式は SPI です.本体の印字が一部おかしくてちょっと迷った

そして以下のコードを実行します.サンプルコードを丸々コピって改造し,多重 for ループで解読を試みました
パスワードはどうも 6Byte あるそうです

/*
 * ----------------------------------------------------------------------------
 * This is a MFRC522 library example; see https://github.com/miguelbalboa/rfid
 * for further details and other examples.
 * 
 * NOTE: The library file MFRC522.h has a lot of useful info. Please read it.
 * 
 * Released into the public domain.
 * ----------------------------------------------------------------------------
 * Example sketch/program which will try the most used default keys listed in 
 * https://code.google.com/p/mfcuk/wiki/MifareClassicDefaultKeys to dump the
 * block 0 of a MIFARE RFID card using a RFID-RC522 reader.
 * 
 * Typical pin layout used:
 * -----------------------------------------------------------------------------------------
 *             MFRC522      Arduino       Arduino   Arduino    Arduino          Arduino
 *             Reader/PCD   Uno/101       Mega      Nano v3    Leonardo/Micro   Pro Micro
 * Signal      Pin          Pin           Pin       Pin        Pin              Pin
 * -----------------------------------------------------------------------------------------
 * RST/Reset   RST          9             5         D9         RESET/ICSP-5     RST
 * SPI SS      SDA(SS)      10            53        D10        10               10
 * SPI MOSI    MOSI         11 / ICSP-4   51        D11        ICSP-4           16
 * SPI MISO    MISO         12 / ICSP-1   50        D12        ICSP-1           14
 * SPI SCK     SCK          13 / ICSP-3   52        D13        ICSP-3           15
 *
 * More pin layouts for other boards can be found here: https://github.com/miguelbalboa/rfid#pin-layout
 *
 */

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN         9           // Configurable, see typical pin layout above
#define SS_PIN          10          // Configurable, see typical pin layout above

MFRC522 mfrc522(SS_PIN, RST_PIN);   // Create MFRC522 instance.

// Number of known default keys (hard-coded)
// NOTE: Synchronize the NR_KNOWN_KEYS define with the defaultKeys[] array
#define NR_KNOWN_KEYS   256

/*
 * Initialize.
 */
void setup() {
    Serial.begin(115200);         // Initialize serial communications with the PC
    while (!Serial);            // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4)
    SPI.begin();                // Init SPI bus
    mfrc522.PCD_Init();         // Init MFRC522 card
    Serial.println(F("Try the all keys to print block 0 of a MIFARE PICC."));
}

/*
 * Helper routine to dump a byte array as hex values to Serial.
 */
void dump_byte_array(byte *buffer, byte bufferSize) {
    for (byte i = 0; i < bufferSize; i++) {
        Serial.print(buffer[i] < 0x10 ? " 0" : " ");
        Serial.print(buffer[i], HEX);
    }
}

/*
 * Try using the PICC (the tag/card) with the given key to access block 0.
 * On success, it will show the key details, and dump the block data on Serial.
 *
 * @return true when the given key worked, false otherwise.
 */
bool try_key(MFRC522::MIFARE_Key *key)
{
    bool result = false;
    byte buffer[18];
    byte block = 0;
    MFRC522::StatusCode status;

    // Serial.println(F("Authenticating using key A..."));
    status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block, key, &(mfrc522.uid));
    if (status != MFRC522::STATUS_OK) {
        // Serial.print(F("PCD_Authenticate() failed: "));
        // Serial.println(mfrc522.GetStatusCodeName(status));
        return false;
    }

    // Read block
    byte byteCount = sizeof(buffer);
    status = mfrc522.MIFARE_Read(block, buffer, &byteCount);
    if (status != MFRC522::STATUS_OK) {
        // Serial.print(F("MIFARE_Read() failed: "));
        // Serial.println(mfrc522.GetStatusCodeName(status));
    }
    else {
        // Successful read
        result = true;
        Serial.print(F("Success with key:"));
        dump_byte_array((*key).keyByte, MFRC522::MF_KEY_SIZE);
        Serial.println();
        // Dump block data
        Serial.print(F("Block ")); Serial.print(block); Serial.print(F(":"));
        dump_byte_array(buffer, 16);
        Serial.println();
    }
    Serial.println();

    mfrc522.PICC_HaltA();       // Halt PICC
    mfrc522.PCD_StopCrypto1();  // Stop encryption on PCD
    return result;
}

/*
 * Main loop.
 */
void loop() {
  // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle.
  if ( ! mfrc522.PICC_IsNewCardPresent())
    return;

  // Select one of the cards
  if ( ! mfrc522.PICC_ReadCardSerial())
    return;

  // Show some details of the PICC (that is: the tag/card)
  Serial.print(F("Card UID:"));
  dump_byte_array(mfrc522.uid.uidByte, mfrc522.uid.size);
  Serial.println();
  Serial.print(F("PICC type: "));
  MFRC522::PICC_Type piccType = mfrc522.PICC_GetType(mfrc522.uid.sak);
  Serial.println(mfrc522.PICC_GetTypeName(piccType));
  
  // Try the break key
  MFRC522::MIFARE_Key key;
  for(int16_t f = 0; f < NR_KNOWN_KEYS; f++) {
    for(int16_t e = 0; e < NR_KNOWN_KEYS; e++) {
      for(int16_t d = 0; d < NR_KNOWN_KEYS; d++) {
        for(int16_t c = 0; c < NR_KNOWN_KEYS; c++) {
          for(int16_t b = 0; b < NR_KNOWN_KEYS; b++) {
            char txt[64];
            sprintf(txt, "Test Key : 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", f, e, d, c, b, 0x00);
            Serial.print(txt);

            for(int16_t a = 0; a < NR_KNOWN_KEYS; a++) {
              byte TestKeys[6] = {f, e, d, c, b, a};

              for (byte i = 0; i < MFRC522::MF_KEY_SIZE; i++) {
                key.keyByte[i] = TestKeys[i];                
              }

              // try key
              if (try_key(&key)) {
                // Found and reported on the key and block,
                // no need to try other keys for this PICC

                sprintf(txt, "Success Key : 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", f, e, d, c, b, a);
                Serial.print(txt);

                break;
              }

              // http://arduino.stackexchange.com/a/14316
              if ( ! mfrc522.PICC_IsNewCardPresent()) {
                break;
              }

              if ( ! mfrc522.PICC_ReadCardSerial()) {
                break;
              }
            }
          }
        }
      }
    }
  }
}

実行している様子がこちら.ガチの自分の学生証なので正面が映らないように横から撮ってます

シリアルモニタの表示はこちら
UID は伏せてます

14:58:10.163 -> Try the all keys to print block 0 of a MIFARE PICC.
14:58:15.491 -> Card UID: ** ** ** **
14:58:15.491 -> PICC type: MIFARE 1KB
14:58:15.491 -> Test Key : 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
14:58:23.428 -> Test Key : 0x00, 0x00, 0x00, 0x00, 0x01, 0x00
14:58:31.337 -> Test Key : 0x00, 0x00, 0x00, 0x00, 0x02, 0x00

こんな具合に,256 ループごとに進捗を表示する感じで実行させていきつつ,どれくらい時間が掛かるかを計算してみました
シリアルモニタのタイムコードを見ると,大体 8 秒おきにきてるので 256 探索で 8 秒掛かっていそうですね

突然ですが,ここで皆さんに文章題を解いてもらいます.何年ぶりでしょうね?

問)探索すべきパターンは 48bit 分あります.このうち 256 パターンを探索するのに 8 秒かかりました.全探索を終えるにはどれくらいの時間が掛かるでしょうか?

一緒に解いていきましょう

まずは探索すべきパターン数について.これは簡単で 2 の 48 乗ですね
このうち 2 の 8 乗分の探索に 2 の 3 乗秒掛かったので,1 秒で 2 の 5 乗パターン探索できたことになります
ということは 2 の 48 乗割ることの 2 の 5 乗秒掛かるわけなので 2 の 43 乗秒掛かるということがわかりました.途方もない数字な予感がしますが続けましょう

2 の 43 乗が実際にどんな値なのかを知るために関数電卓を叩きました.すると 8.796093022 x1012 だそうです.指数表記になっちゃいましたね
仕方がないので Python で計算しました.すると以下の通り

C:\Users\user>python3
Python 3.12.1 (tags/v3.12.1:2305ca5, Dec  7 2023, 22:03:25) [MSC v.1937 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 2**43
8796093022208

8796093022208 秒だそうです.これを年日時分秒に変換したいのですが,いい方法が見当たらなかったので泥臭く計算します

まず 8796093022208 から秒を取り出します.単純に 60 で割ったあまりですね
これまた Python を使いましょう

>>> 2**43%60
8

秒は 8 と求まりました

こんな具合に残りも計算していきますと,こんな感じ

>>> 2**43/60%60
50.133331298828125
>>> 2**43/60/60%24
4.835555553436279
>>> 2**43/60/60/24%365.2429
287.2270814708236
>>> 2**43/60/60/24/365.2429
278736.7864001777

よって 278736 年 287 日 4 時間 50 分 8 秒と求まりました.無理!!!!

という訳です.たかだか 6Byte とあなどるなかれ.一回のアクセスに結構時間かかるのもあって全探索は到底やってられないですね.セキュリティは十分そう

余談

学生証のパスワード突破は無理と分かりました.ではせっかく買ったこれ,何に使いましょうか?

今のところ考えているのは,付属のカードを利用したセキュリティキーとか,専用カードでないと解錠できない金庫とか,面白そうだなって思ってます
もしやるとしたらまた何かしらの形で発信すると思います

それでは今日はこの辺で

インターンってやつにチャレンジすることになった話

前座

お久しぶりです.コミケ直前にハンドルネームを変更したことに伴いドメインも変えたい気持ちになってきた MOS です

なんか急に寒くなりましたね.相変わらず令和ちゃんは温度管理がヘタクソだ

ってわけで(どういう訳?),この度インターンってやつに応募してみたので,ちょっくら文章というやつを書いてみようと思います.

本題

学部 1 年生にも関わらずインターンとは大分気が早いなと思ったそこのアナタ.そう,アナタのことです.その通り.大分気が早いですね.お前前期の単位何個落としたよ

確かに大分気が早いのはそうなんですが,ちょっと事情みたいなものがありまして,こうなりました.キッカケは多分このツイート

ハードウェア開発は何をするにも金がかかるけど,最近のソフトウェアは大体が OSS で,個人利用はおろか商用利用ですら無料でできるものも少なくない印象があり,計算機一台とやる気,あと時間があればとりあえずやってみることができていいなぁと思ってツイートしたんですが,このツイートに @_meltingrabbit からリプが飛んできました

ちなみにこの方とは割と長い付き合いで,僕が中学 2 年生の時に知り合ったと思います.缶サット講座の講師として色々教えてくれました.そのことから,僕がハードウェア開発に気があることを知っていて,またそれなりに技術知識経験を積んでいっているということも多分知っています.故に「やっぱ」という prefix が付いてるんでしょう

その後の流れは Twitter に飛んで見ていただくとして,ある日,とある DM が届きました.それは, @_meltingrabbit も創業に携わった宇宙開発系の企業「ArkEdgeSpace」のインターン枠の応募ページでした.もちろん速攻応募したよね

応募からのあれこれ

まず応募して,ちょっと(ホンマか?)遅れて履歴書を送り,メールのやり取りをしつつ,一次・二次面接と続いていきました.応募して履歴書送ってからは結構スムーズに進みましたね.履歴書遅れてスマンという気持ちになっております

一次面接も二次面接も,面接ってこんなんだっけというような簡単なおはなしをしただけのものでしたが,まあいいでしょう

一次面接は @_meltingrabbit も向こう側の担当者として居たので大分受けやすかったし,二次面接は一次面接のときにいた方だったのでこれまた受けやすく,特に緊張することもなく受けることができてとてもよかったです(小学生並みの感想とはこのこと).オマケにメールで送られてくる連絡がけっこう早いので助かりました

合格メールが来た

二次面接が終わって数日,面接合格の報せがとんできました.やったぜ

これにて晴れてインターン生となることになったわけですが,その後どうなったのかはまた書こうと思います.書いたらどっか下の方にリンク貼っときますね

ちなみに業務内容はまだよく分かってません.そのうち分かると思いますが,まあ楽しみですね

余談

一応僕の専門はハードウェアということにはなっていますが,ソフトウェアに気がない訳でもありません
事実,一時期自宅鯖ってやつに興味を持って 2 台くらいのマシンでマイクラ鯖やってみたり,C で円周率を計算するソフトを書いてみたりしてました
今も若干興味は持ってて,いづれは自宅に複数鯖を置いて Kubernetes クラスタ組んだり,Grafana Dashboard と Prometheus Exporter で監視基盤を組んだりしてみたいという気持ちはあります.問題はその上で動かしたいサービスがないってこと.案募集してます

とはいえやはり実体を伴ったものづくりの方がやってて楽しいので,普段電子工作ばかりやってるという訳ですね.ものづくりはいいぞぉ~

あとがき

インターン生としてやってることとかの書き散らしはこちら

ArkEdge Space でのインターン経験を振り返る|第一年度 - MOSの覚書

Raspberry Pi Pico で Ethernet 接続|W5500

先日、Amazon の樹海をさまよっていたところ、WIZnet の W5500 チップを搭載した SPI 接続の Ethernet モジュールを見つけました。これを使って RasPico を Ethernet 接続できないかと思い立ち、試しに一個買ってみました

購入元リンクはこちら

https://www.amazon.co.jp/gp/product/B08C7DX4HN/

尚、本稿執筆時点では在庫なしになっています

この記事では、実際にこのモジュールを使用して RasPico を Ethernet 接続に対応させるまでを書き残そうと思います

使用する回路

回路図を起こすのは面倒なので実物の写真でご勘弁ください
MAX7216 搭載の 8 桁 7 セグメント LED モジュールも使えるようにしています
また、部品はすべてピンソケットを経由させています。テスト基板だからね

(※ RasPico が逆になっています)

本モジュールは SPI 通信を使用するため、SPI の信号線が伸びています。接続は以下の通り

W5500 RasPico
MI GP16
MO GP19
CS GP17
SCK GP18

CS はおそらく任意ですが、今回は近場の GP17 を使用しました

回路の確認をするため、サンプルプログラムを書き込んでみます
今回は [ファイル] > [スケッチ例] > [Ethernet] より [UdpNtpClient] を選択しました

環境によって Ethernet.init(); にわたす引数を変えてあげる必要があります。ここに入れる引数はどうも SPI の CS ピンに該当するようなので今回は Ethernet.init(17); を追記しました
また、アクセス先のタイムサーバを ntp.nict.jp に変更しています

時刻を取得することができました。ですが、これだと UTC のままなので JST で表示してくれるようにちょっと手を加えます。また、ついでに実装した 7 セグメント LED に取得した時刻を表示するようにもしてみました

以下使用コード

#include <Ethernet.h>
#include <EthernetUdp.h>
#include <SPI.h>

#define LED_BUILTIN 25

#define MAX7219_DIN 10
#define MAX7219_CS  11
#define MAX7219_CLK 12

#define MAX7219_REGISTER_NOP          0x0  // 未使用:
#define MAX7219_REGISTER_DECODE_MODE  0x9
#define MAX7219_REGISTER_INTENSITY    0xA
#define MAX7219_REGISTER_SCAN_LIMIT   0xB
#define MAX7219_REGISTER_SHUTDOWN     0xC
#define MAX7219_REGISTER_TEST         0xF

// 0 ~ F のインデックスでセグメントの情報を出す配列:
const uint8_t SEGMENT_HEX_DECODER[16] = {0b01111110, 0b00110000, 0b01101101, 0b01111001,   // 0, 1, 2, 3:
                                         0b00110011, 0b01011011, 0b01011111, 0b01110000,   // 4, 5, 6, 7:
                                         0b01111111, 0b01111011, 0b01110111, 0b00011111,   // 8, 9, A, b:
                                         0b00001101, 0b10111101, 0b01001111, 0b01000111};  // c, d, E, F:

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};  // MAC addr:

unsigned int localPort = 8888;       // local port to listen for UDP packets

const char timeServer[] = "ntp.nict.jp"; // ntp.nict.jp NTP server

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

int8_t GMT = 9;  // JST に変換するため:

byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

void setup() {
  LED_init();
  Ethernet.init(17);  // CS pin:

  // Open serial communications and wait for port to open:
  Serial.begin(115200);
  while (!Serial);

  // start Ethernet and UDP
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // Check for Ethernet hardware present
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    } else if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Ethernet cable is not connected.");
    }
    // no point in carrying on, so do nothing forevermore:
    while (true) {
      delay(1);
    }
  }
  Udp.begin(localPort);
}

void loop() {
  sendNTPpacket(timeServer); // send an NTP packet to a time server

  // wait to see if a reply is available
  delay(25);
  if (Udp.parsePacket()) {
    // We've received a packet, read the data from it
    Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;

    // now convert NTP time into everyday time:
    Serial.print("Unix time = ");
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;
    // print Unix time:
    Serial.println(epoch);
    
    epoch = epoch + GMT * 60 * 60;  // JST に変更:

        // print the hour, minute and second:
    Serial.print("The GMT ");
    if(GMT > 0) {
      Serial.print("+");
    }else if(GMT == 0) {
      Serial.print("±");
    }
    Serial.print(GMT);
    Serial.print(" time is ");       // UTC is the time at Greenwich Meridian (GMT)
    Serial.print((epoch  % 86400L) / 3600); // print the hour (86400 equals secs per day)
    Serial.print(':');
    if (((epoch % 3600) / 60) < 10) {
      // In the first 10 minutes of each hour, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.print((epoch  % 3600) / 60); // print the minute (3600 equals secs per minute)
    Serial.print(':');
    if ((epoch % 60) < 10) {
      // In the first 10 seconds of each minute, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.println(epoch % 60); // print the second
    Display_time(epoch % 86400);  // Display to LED:
  }
  Serial.println();
  // // wait ten seconds before asking for the time again
  delay(5000);
  Ethernet.maintain();
}

// send an NTP request to the time server at the given address
void sendNTPpacket(const char * address) {
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); // NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

void LED_init(void) {
  pinMode(MAX7219_CS , OUTPUT);
  pinMode(MAX7219_CLK, OUTPUT);
  pinMode(MAX7219_DIN, OUTPUT);
  
  digitalWrite(MAX7219_CS , HIGH);
  digitalWrite(MAX7219_CLK,  LOW);
  digitalWrite(MAX7219_DIN,  LOW);

  LED_OUT(MAX7219_REGISTER_DECODE_MODE, 0b00000000);    //7セグでデコードするビットに1を立てる:
  LED_OUT(MAX7219_REGISTER_INTENSITY  , 0b00011100);    //輝度を設定, 0-15:
  LED_OUT(MAX7219_REGISTER_SCAN_LIMIT , 0b00000111);    //使用する桁数を指定, 桁数-1:
  LED_OUT(MAX7219_REGISTER_SHUTDOWN   , 0b00000001);    //特にいじる必要なし:
  LED_OUT(MAX7219_REGISTER_TEST       , 0b00000000);    //同上:
}

void LED_OUT(uint8_t addr, uint8_t dat){
  digitalWrite(MAX7219_CS,  LOW);
  shiftOut(MAX7219_DIN, MAX7219_CLK, MSBFIRST, addr);
  shiftOut(MAX7219_DIN, MAX7219_CLK, MSBFIRST, dat);
  digitalWrite(MAX7219_CS, HIGH);
}

void Display_time(uint32_t times) {
  if(times > 86400) {
    times = times % 86400;
  }
  Serial.print("times = ");
  Serial.println(times);
  uint8_t S8 = times / (60 * 60 * 10);
  times = times % (60 * 60 * 10);
  uint8_t S7 = times / (60 * 60);
  times = times % (60 * 60);
  uint8_t S5 = times / (60 * 10);
  times = times % (60 * 10);
  uint8_t S4 = times /  60;
  times = times % (60);
  uint8_t S2 = times /  10;
  times = times % (10);
  uint8_t S1 = times;

  Display_null();
  LED_OUT(8, SEGMENT_HEX_DECODER[S8]);
  LED_OUT(7, SEGMENT_HEX_DECODER[S7]);
  LED_OUT(6, 0b00000001);  // Hyphen:
  LED_OUT(5, SEGMENT_HEX_DECODER[S5]);
  LED_OUT(4, SEGMENT_HEX_DECODER[S4]);
  LED_OUT(3, 0b00000001);  // Hyphen:
  LED_OUT(2, SEGMENT_HEX_DECODER[S2]);
  LED_OUT(1, SEGMENT_HEX_DECODER[S1]);

  Serial.print(S8);
  Serial.print(S7);
  Serial.print(":");
  Serial.print(S5);
  Serial.print(S4);
  Serial.print(":");
  Serial.print(S2);
  Serial.print(S1);
  Serial.print("\n");
}

void Display_null(void){
  LED_OUT(0x9, 0x00);
  LED_OUT(0x1, 0b00000000);
  LED_OUT(0x2, 0b00000000);
  LED_OUT(0x3, 0b00000000);
  LED_OUT(0x4, 0b00000000);
  LED_OUT(0x5, 0b00000000);
  LED_OUT(0x6, 0b00000000);
  LED_OUT(0x7, 0b00000000);
  LED_OUT(0x8, 0b00000000);
}

これを書き込んで実行すると、こうなります

無事 NTP から時刻を取得しディスプレイに表示させることができました。結構簡単でいいですね

π とかいう邪悪な存在

円周率  \pi 、数学や物理を学んでいると嫌というほど出てきますよね

普通であれば「あ、また出てきた」くらいのものだと思いますが、ちょっと考えてみたらこの「 \pi 」って結構邪悪な存在なんですよね。僕が思うには

なので、なぜ「邪悪な存在」なのかを僕なりに書き残そうとおもいます

理由①|定義の問題

 \pi の定義は、「円周/直径」。これは皆さんご存知だと思います

 \pi = \cfrac{l}{R} \fallingdotseq 3.1416

では、円の定義はというと、「平面上で、定点からの距離が同じ点の集まり」です
ここで、定点からの距離というのは半径です。つまり、円は半径によって定義されています

ならば、円周率も半径で定義するのが筋でしょう

ちなみにこの場合、 \pi の代わりに  \tau (読み:タウ)を使うことが多いそうです。名前も同じ円周率だとごちゃごちゃになるので、「円周比」という呼び方を提案したい

 \tau = \cfrac{l}{r} \fallingdotseq 6.2832

また、 \pi  \tau の関係性は以下の通り

 \tau = 2 \pi

定義の中で分母が半分になっているので、まあ当然

理由②|実用上の問題

突然ですが、以下の3つの不定積分の式を見て下さい(積分定数は省略しています)

 \int_{}^{} CV dV = \cfrac{1}{2} CV^{2}

 \int_{}^{} LI dI = \cfrac{1}{2} LI^{2}

 \int_{}^{} 2 \pi r dr = \pi r^{2}

これらを見て違和感を覚えませんか? 2  \pi お前何やねん、という風に思いません?

では、これを \tau を使って書いてみましょう

 \int_{}^{} \tau r dr = \cfrac{1}{2} \tau r^{2}

同じ形になりました。実にシンプルで素晴らしい

他にも  2 \pi が関係しているのがたくさんあるので、いくつか例示します。ついでに  \tau で置換したものも並べます

  • 単振り子の周期の公式
 T = 2 \pi \sqrt{\cfrac{g}{l}} = \tau \sqrt{\cfrac{g}{l}}
  • 単振動の周期の公式
 T = 2 \pi \sqrt{\cfrac{k}{m}} = \tau \sqrt{\cfrac{k}{m}}
  • 半径rの円の円周の長さの公式
 l = 2 \pi r = \tau r
  • 単位円の円周の長さの式
 l = 2 \pi = \tau
  • 半径rの円の面積の公式
 S = \pi r^{2} = \cfrac{1}{2} \tau r^2
  • 単位円の面積の式
 S = \pi = \cfrac{1}{2} \tau
  • 角周波数の公式
 \omega = 2 \pi f = \tau f
  • 並行電流が及ぼし合う力の式
 F = \cfrac{\mu I_{1} I_{2}}{2 \pi r} l = \cfrac{\mu I_{1} I_{2}}{\tau r} l

これらを見比べて、 \pi の利点はせいぜい単位円の面積が一文字ですむことくらいでしょう。その他はどれを取っても  \tau の方がスッキリしていますよね

「弧度法」という観点からも見てみましょう

弧度法では、角度を半径に対する弧の長さの比で表します。360°は  2 \pi ラジアンに相当します
ですが、一周しかしていないのにあたまに  2 が付いている。これは不自然極まりない

ここで円周率の代わりに円周比を使えば、一周すれば丁度  \tau となり、理解がしやすい

円周率が関係している数式や公式はたくさんありますが、 \pi を使っているせいで邪魔な係数  2 が付いてくるものが多かったり、よく見る  \int_{}{} ax dx = \cfrac{1}{2} ax^{2} の形にならなかったり、
これって割と問題だと思うんですよ。数学らしくないし美しくない

理由③|三角関数との親和性

僕は Arduino などで正弦波の配列を作ることが多いんですが、その際  \pi に苦しめられた経験があります。その時、#define Tau (2 * PI)float Tau = 2 * PIとして代わりに  \tau を使った結果割とすんなり書けました

これはなぜか、やはり係数の  2 があるからです

三角関数を使って、N分割の sin の配列を作るときは以下のようにすると思います

float SIN[N];

for(uint16_t i = 0; i < N; i++) {
  SIN[i] = sin((i / N) * 2 * PI);
}

出ましたね、招かれざる客 2 \pi

もう書きませんが、この 2 * PITau に置き換えるとわかりやすくなります。何故なら単位円の円周の長さが  \tau で、それをN分割し、i をインクリメントしていくことでN分割したピザみたいなやつの数を増やしていくという考え方です

また、複素数を使った1のn乗根の解を計算する場合でも同様に考えることができます

 Z = \cos \theta + i \sin \theta

 \theta = \cfrac{2 \pi k}{n} , k = 1, 2, ... , n

書き換えると、

 Z = \cos \theta + i \sin \theta

 \theta = \cfrac{\tau k}{n} , k = 1, 2, ... , n

やはりシンプルでわかりやすいですね。円周をn分割していることが直感的にわかります

また、世界一美しいとされるオイラーの公式でも  \tau を使うメリットが出てきます

 e^{i \theta} = \cos \theta + i \sin \theta

この式の  \theta  \pi を代入すると、このようになります

 e^{i \pi} = \cos \pi + i \sin \pi

 \cos \pi = -1

 \sin \pi = 0

 e^{i \pi} = -1

これって本当に美しいですかね? マイナスがついてるのは残念に思えます。では、 \theta = \tau で計算してみましょう

 e^{i \tau} = \cos \tau + i \sin \tau

 \cos \tau = 1

 \sin \tau = 0

 e^{i \tau} = 1

イコール1になりました。こっちの方が美しいですね

理由④|その他数学的な理由

 2 × 3 × 5 × 7 × 11 × 13 × 17 × ... のように素数を無限に掛けていく場合を考えてみましょう
普通に考えてみれば、有限積の範囲であればどんどんと値が増えていきます。ですが、素数自体は無限に存在すると証明されているため、無限積の範囲になり話が変わってきます

詳しいことはまだ良くわかっていないんですが、複素関数論という学問の中で出てくる解析接続という手法を取ると、この無限積の解は  4 \pi ^{2} となるそうです。言いたいことはもうわかってきたでしょう。 4 \pi ^ {2} = (2 \pi) ^{2} = \tau ^ {2} となりますよね。 \tau を用いた方がシンプルでかつ本質的じゃないですか?

どうしてこうなった?

「円周率を円周と直径の比で定義してしまったから」これに尽きると思います

僕としてはどう考えても円周率  \pi より円周比  \tau の方が定義的にも、実用面でも、数学的にもメリットが大きいとしか思えないので、自分のプログラム内で円周率を扱う事があれば #define Tau (2 * PI) あるいは const float Tau = 2 * PI; として  \tau を使うことにしています

実際コード書いてて  \tau を用いたほうが楽なので、ぜひ試してみて下さい

かなり長くなりましたが、今日はここまで

詰み部品の消化がてら RasPico を触る

Raspberry Pi Pico(通称RasPico)を発売日に買ったはいいが全然使ってないということに気づいてしまったので消化します

ここに写ってるのは 3 個ですが、実際はもうちょいあります。全部で 5 個かな

して、このうち一つを徴用、、、もとい活用して RasPico 遊びをしていきましょう

マルツに行ったら専用基板があったので使ってみました。いい感じ
外部の電源を使えるようにレギュレータをつけましたが、今のところ使ってません

で、マイコン触るとなると GPIO 使うことが多いですが、今回は ADC を使うことにします。デジタルはまた今度

そのために可変抵抗を追加しま...

した。言わずもがな赤色の線が VSYS、黒色が GND、青色が分圧出力です

早速 ADC を読んでシリアル出力するプログラムを走らせてみましょう

#define ADC0 26
#define ADC1 27

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

void loop() {
  uint16_t ADC_0 = analogRead(ADC0);
  uint16_t ADC_1 = analogRead(ADC1);

  char buff[32];
  sprintf(buff, "%4d, %5d", ADC_0, ADC_1);
  Serial.println(buff);

  delay(125);
}

結果:

ちゃんと読めてますね

RP2040 の ADC はデータシートを見る限り 12bit ですが互換性のためか 10bit になってました。そもそもそこまで精度ないと思うのでまあ良いでしょう

シリアルプロッタを開いて可変抵抗を動かさずにちょっと放置
ちょっとちらついてるので実使用では 2bit か 3bit 分くらい落とした方がいいかも? あとは(やり方は知らないけど)ADC の積算回数を弄ってみるか

お次は電圧計モード
とは言っても Vcc 電圧を掛けて最大値で割るだけですが

一応それなりに近い値は出てました。簡易的に電圧見る分には使えそう

Arduino IDE で開発する場合、analogRead(pin) に GPIO の番号を入れて実行するだけで ADC 読めるので Arduino と同じ感覚で使えそうですね
今後この子にはインバータの制御をしてもらおうかと考えてるので進捗があり次第また何か書くかも