超簡易ゲルマニウムラジオで戸田に行く Part2

Part1でダイオードとイヤホンだけでラジオ聞くことができた。
Part2では、アンテナにしてラジオが聞こえたものとラジオが聞こえた距離を紹介する。
聞こえやすさは個人の主観である、送信所からの距離が一定でない、車ががんがん通る道だったのであまりあてにならないかもしれません。

ta2mi.hatenablog.com

アンテナになったものたち

聞こえやすは最大5点

Part1でもでてきた電柱の金属のベルト

聞こえやすさ: 3点

フェンスの錆びている部分

聞こえやすさ: 2点

排水溝の蓋

聞こえやすさ: 3点

よくわからん3mぐらいの棒

聞こえやすさ: 5点

アースを地面にさしてみたら音声が歪むぐらい出力が大きくなった。
アースの大切さがわかった。

道路の看板

聞こえやすさ: 3点

電灯

聞こえやすさ: 4点

わりと距離が離れているところでもはっきり聞こえた。

人間?

聞こえやすさ: 2点

送信所の近くであれば手で握るだけで聞こえた!!
これが一番驚いた。

その他聞こえたもの

金属であればだいたい聞こえた。

  • ファミマの入り口にあった金属の柵みたいなもの
  • マンホール
  • 消火栓の標識

ラジオが聞こえる距離

戸田公園駅に帰りながらどのくらいの距離までラジオが聞こえるか測定してみた。
アンテナにするものがバラバラなので実験としてはよくない測定をしている。
一応電柱か電灯などのような高さのあるものをアンテナにするようにしていた。

結果はなんと駅まで聞こえた。 その距離1.77kmであった。
思っていたよりも遠くまで聞こえた。

ピンクのピンが立っているところが測定した地点である。

さいごに

最初はたったこれだけの部品でラジオが聞こえるなんて信じられなかったが、やってみてラジオが聞こえた時はかなりテンションが上がって楽しかった。 ただAMラジオは2028年をめどに終了する予定らしいのでこの遊びはできなくなる。
それまでには今回作成に失敗したアンテナコイルをリベンジしたい。
アンテナコイルもただペッドボトルやラップにコイルを巻くだけでなくいろんなやり方があるようなのでそれらも試してみたい。

超簡易ゲルマニウムラジオで戸田に行く Part1

ネットを彷徨っていたら、イヤホンとダイオードだけでラジオを聞くことができるという記事を見た。
面白そうだったのでやってみた。

ゲルマニウムラジオ

ゲルマニウムラジオは電源なしでラジオを聞くことができる装置である。
回路図はこんな感じでかなりシンプル

回路図

この回路図だけでAMラジオを聞くことができるらしい。(FMは聞けない)
仕組みとしては左側にあるとコイルとコンデンサが共振することでエネルギー(AM波)が大きくなる。 それをダイオードに通すことで音を取り出すことができる。
共振させる周波数はコンデンサの容量を変えることで可能であり、この操作でラジオを選曲することができる。

作ってみる

とりあえずAmazonでキットを購入する。
1000円ぐらいで買える。

深夜12時に作り始めようとしたらコイルを巻くための円筒のものが家にないことがわかった。
急いでコンビニ行ってジュースを買ってきた。
早く作りたいのにクエン酸多いの買ってしまい酸っぱくて飲むのが進まない。

炭酸飲料

そんなこんなでアンテナコイルを作成に取り掛かったが、銅線がからまって悲惨な状況になった。

1時間くらいはほどこうと頑張ったけど諦めた。
翌日秋葉原に行き銅線を買おう心に決め寝た。

翌日秋葉原に行って銅線を買ったが不器用すぎてまた失敗した。。。

ダイオードとイヤホンだけでラジオは聞ける

実はラジオの送信所の近くにいけば、エネルギーが大きいため共振回路の部分は不要なのである。
アンテナはその辺にある金属で代用できるため準備する必要があるものは、ゲルマニウムダイオードとセラミックイヤホンのみでよい。 アンテナコイルは保険として作りたかっただけである。
冒頭でおもしろかったといった記事もダイオードとイヤホンだけでラジオを聞いていた。

dailyportalz.jp

戸田でラジオを聞く

東京付近でAMラジオ送信している場所は以下である。

川崎が最寄りであるが、埼玉に行ったことないので戸田市にあるTBSラジオにした。

戸田送信所

送信所付近の金属にダイオードの先端つけて、ラジオ聞いてみようとするがなにも聞こえない。。。
いろいろ試行錯誤しているとラジオが聞こえた!!!
なにを喋っていたかは覚えていないが女性の人が喋っている人が聞こえた!
どうやらアースがきちんととれていないみたいであった。 それまではアスファルトや電柱のコンクリート部分を画像に写っていない方を線をくっつけていた。
それを手で握ると音が聞こえるようになった。

初めてラジオが聞こえた電柱

このあとはいろんな金属にくっつけてラジオが聞こえるか試してみた。
ボードレース帰りのおっちゃんに白い目をむけられていたが、テンション上がっていたのでそんなことは気にせずやっていた。

Part2ではどの物体で聞こえたか、あとはどのくらいの距離まで聞こえたかをまとめていく。

Go言語のReadでは1GB以上のサイズを読むことができない

Goでファイルを分割してアップロードするAPIのクラアントのコードを作成していたときにしったことです。

2GBのバッファを使ってReadする

Go で以下のようなコードで5GBファイルから1回のReadで2GB読み込もうとすると、bufferに1GB分のデータしか格納されませんでした。
エラー発生しない限りはbufferのサイズ分読み込んでもらえるものだと思っていたので、原因探って行きます。

package main

import (
    "flag"
    "log"
    "os"
)

func main() {
    fname := flag.String("f", "", "file name")
    flag.Parse()

    buf := make([]byte, 2*1024*1024*1024)
    f, err := os.Open(*fname)
    defer f.Close()
    if err != nil {
        log.Println("can't open file: ", err)
    }

    for {
        n, err := f.Read(buf)
        if n > 0 {
            log.Printf("read %d bytes", n)
        }
        if err != nil {
            break
        }
    }
}

5GBのファイルを読み込んだ時の出力結果です。
1 * 1024 * 1024 * 1024 = 1073741824 byte = 1 GiBなので1回のReadで1GBしか読み込めていません。

go run main.go -f 5G.bin
2023/11/23 15:46:45 read 1073741824 bytes
2023/11/23 15:46:45 read 1073741824 bytes
2023/11/23 15:46:45 read 1073741824 bytes
2023/11/23 15:46:45 read 1073741824 bytes
2023/11/23 15:46:46 read 1073741824 bytes

MAX1GBまでしか読み込めない制限がありそうです。

原因

標準パッケージの中身を追っていくとfd.Readで以下の制限を設けていました。 1 << 30でビットシフトしています。
2^30 = 1073741824です。

なんでこんなことやっているのかもコメントに書いてありました。

// Darwin and FreeBSD can't read or write 2GB+ files at a time,
// even on 64-bit systems.
// The same is true of socket implementations on many systems.
// See golang.org/issue/7812 and golang.org/issue/16266.
// Use 1GB instead of, say, 2GB-1, to keep subsequent reads aligned.
const maxRW = 1 << 30

ChatGPTさんにこのコメントを説明してもらった結果は以下です

このコメントは、Go言語の標準パッケージに含まれるファイルディスクリプタに関連するUnixシステム用のReadメソッドについてです。

内容を解説すると、まず、Darwin(MacOSのカーネル)やFreeBSDなどの一部のUnix系システムでは、64ビットシステムであっても2GB以上のファイルを一度に読み書きすることができないと述べています。

また、多くのシステムにおけるソケットの実装でも同様の制限があることを指摘しています。

この制限についての詳細は、golang.orgの問題トラッカーである"issue/7812"や"issue/16266"で報告されています。これらの問題は、大容量のファイルやソケットに対する読み書き操作において、特定のシステムで制限があることを示しています。

そして、このコメントは、2GBよりもやや小さいサイズの1GBを使用することを推奨しています。これは、例えば2GB-1といった上限値ではなく、1GBを使うことで、後続の読み込み操作が適切にアラインされることを意味しています。アラインメントを保つことで、効率的なファイルやソケットの読み書きを行うことができます。

システムコールのreadを実行してみる

システムコールを直接実行すれば1GB以上のReadできそうなので試してみます。
コメント通りに2GB-1が上限でした。2GB以上指定するとinvalid argumentになります。

package main

import (
    "flag"
    "log"
    "os"
    "syscall"
)

func main() {
    fname := flag.String("f", "", "file name")
    flag.Parse()

    buf := make([]byte, 2*1024*1024*1024-1)
    f, err := os.Open(*fname)
    if err != nil {
        log.Println("can't open file: ", err)
    }

    for {
        n, err := syscall.Read(int(f.Fd()), buf)
        if n > 0 {
            log.Printf("read %d bytes", n)
        } else {
            break
        }
        if err != nil {
            log.Println(err)
            break
        }
    }
}
go run main.go -f 5G.bin
2023/11/28 00:14:49 read 2147483647 bytes
2023/11/28 00:14:50 read 2147483647 bytes
2023/11/28 00:14:50 read 1073741826 bytes

2GB以上指定できない理由

2GB以上指定できない理由調べてみます。
MacOSreadのリファレンスを見ると
read(int fildes, void *buf, size_t nbyte); になっています。
MacOSsize_tはおそらく32bitで、符号あり32bit整数型の最大値である2147483647が上限値ぽいです

Linuxで試してみる

Linuxだと5GBを一度で読み込めそうなので試してみます。 GoのDockerの公式イメージdebianがベースみたいなのでそれでやってみます。

FROM golang:1.21

WORKDIR /usr/src/app

RUN dd if=/dev/zero of=5G.bin bs=1M count=5120
COPY main.go .
RUN go build -v -o /usr/local/bin/app ./main.go

CMD ["app", "-f", "5G.bin"]

ソースコードのバッファサイズを2GBに変更します。 そしてDocker buildしてrunした結果がこれです。

docker run test       
2023/11/27 16:00:02 read 2147479552 bytes
2023/11/27 16:00:05 read 2147479552 bytes
2023/11/27 16:00:07 read 1073750016 bytes

予想とは異なり、2147479552 byteMacよりも小さくなってます。 Macの方が4095 byte大きいです。

どうやらLinuxにも制限あるみたいでした。

stackoverflow.com

1GB以上Readする方法

io.ReadFullを使えばこの問題回避できます。
これを使えばバッファサイズ分きちんと読み込んでくれます。

読み込んだサイズよりもバッファの方が大きい場合はErrUnexpectedEOFが返ってきます。

コードはこんな感じになります。

package main

import (
    "errors"
    "flag"
    "io"
    "log"
    "os"
)

func main() {
    fname := flag.String("f", "", "file name")
    flag.Parse()

    buf := make([]byte, 2*1024*1024*1024)
    f, err := os.Open(*fname)
    if err != nil {
        log.Println("can't open file: ", err)
    }

    for {
        n, err := io.ReadFull(f, buf)
        if err != nil {
            if errors.Is(err, io.ErrUnexpectedEOF) {
                log.Printf("read %d bytes", n)
                break
            } else if errors.Is(err, io.EOF) {
                break
            } else {
                log.Fatalf("read error: %v", err)
            }
        }
        log.Printf("read %d bytes", n)
    }
}