ピンヘッダのレイアウトツールに 幅狭の DIP IC のラベルを作る機能を足した。印刷して貼るだけで配線ミスが減らせるので便利

特に DIP IC の場合、文字を入れるスペースが殆どないので、自分に必要な要素だけ厳選して作る必要があって、簡単に別バージョンを作れたほうがいいと思う。ブラウザ上でやって実寸 PDF すぐ印刷できるのはかなり便利だと思う。

  1. トップ
  2. tech
  3. IC のピン配置ラベルを作る

Path::Class と Path::Tiny はほぼ同じことができるモジュールで、どっちを使っても特に変わりはないんですが「やっぱこっち〜」って感じで変えるとハマるような差異があり、今回ハマりましたのでご紹介します。

Path::Class->tempdir, Path::Tiny->tempdir

いずれのモジュールにも、tempdir というメソッドがあり、これはいずれも全く同じ引数をとり、File::Temp を使ってテンポラリディレクトリを掘ってパスオブジェクトを返すだけのメソッドです。

しかし内部で使っている File::Temp のメソッドが少々違うため、CLEANUP まわりの挙動が以下のように異ります。

  • Path::Class は File::Temp::tempdir() を使っている
    • CLEANUP => 0 がデフォルト (デフォルトで削除されない)
    • CLEANUP => 1 を指定した場合 Perl プロセス終了時にファイルが削除される
  • Path::Tiny は File::Temp->newdir() を使っている
    • CLEANUP => 1 がデフォルト (デフォルトで削除される)
    • 返ってきたオブジェクトが DESTROY されるときにファイルが削除される

File::Temp の挙動

  • File::Temp::tempdir() は文字列を返す
  • File::Temp->newdir() はオブジェクトを返す
    • 内部的には File::Temp::tempdir() を呼んでる
    • DESTROY が設定されている

ハマる例

普通ハマらないんですが、アホなことしてるとハマります。

  • Path::Class->tempdir を使っていて、Path::Tiny に変えた場合、その後の処理でファイルが存在していない場合がある
  • Path::Tiny->tempdir を使っていて、Path::Class に変えた場合、ファイルが削除されない場合がある
  1. トップ
  2. tech
  3. File::Temp, Path::Class, Path::Tiny

印刷してピンヘッダにハメれば、どれがどのピンだかわかりやすくなるというツールを作った。PDF (A4) を出力するので、あとは印刷してカッターで切りとれば 100mil のピンヘッダにぴったりあう。

一行目の # からはじまる行がタイトルになり、あとは適当にそれっぽくピン番号と名前のペアを書いていく。名前の語尾に # をつけると反転する。

ほんとはフォントも変えれるようにしたかったんだけど、ブラウザ上で任意のフォントを確実に指定する方法がなくてあきらめた。

作るまで

いろいろな方法があるが、結局 PDF を JS で生成している。jsPDF というライブラリをつかっているので比較的楽に書けた。

他に検討した方法は以下の通り

HTML/CSS

できるならこれが一番良い方法だと思ったが、いまいちクロスブラウザでうまくいくか微妙なのでやめた。table を印刷しようとするとプリントするときだけ width/height が効かなくなったり、つらい。

Canvas

実寸で印刷しようと思うと結局めんどうなのでやめた。

SVG

ハマりそうなのでやめた。

  1. トップ
  2. tech
  3. ピンヘッダのレイアウトを実寸で印刷するツール

FT4232H

http://strawberry-linux.com/catalog/items?code=50029

FT2232H か FT4232H を使って JTAG インターフェイスにするのが高コストパフォーマンスっぽいので、FT4232H を買ってみた。FTDI の一部のシリアルポート変換チップは、MPSSE という方法で JTAG や I2C, SPI を喋ることができる。

なので、UART 接続しつつ JTAG しつつ、みたいなのが1個あればできて便利そう!って感じです。ただ、他プロトコル喋らせるのは、やはりちょっとハマりどころがあった……

JTAG 専用の USB インターフェイスの中には普通にこれを使っているのも多いみたい。専用のものだとここに書いてあるようなハマり所はあまりなさそう。

この製品のピン配置

OpenOCD

JTAG をハンドリングして使えるようにしてくれる OSS として Open OCD (オンチップデバッガー) というのがある。これは gdb のプロトコルを喋るサーバーになったりしてくれる。

FT4232H を生で使った場合、2つの方法がある。

libftd2xx を使う場合

FTDI が提供しているドライバを使う場合。ドライバ自体のソースは非公開のビルド済みのものを使う。

http://www.ftdichip.com/Drivers/D2XX.htm で libftd2xx をいれる

sudo cp /Volumes/release/D2XX/bin/10.5-10.7/libftd2xx.1.2.2.dylib /usr/local/lib/
sudo ln -sf /usr/local/lib/libftd2xx.1.2.2.dylib /usr/local/lib/libftd2xx.dylib
sudo cp /Volumes/release/D2XX/Samples/ftd2xx.h /usr/local/include/
sudo cp /Volumes/release/D2XX/Samples/WinTypes.h /usr/local/include/  # Win とかついてるが必要

で、入れたら、homebrew で以下の通りインストールする

sudo brew install openocd ----enable-ft2232_ftd2xx 

このようなファイルを generic-ft4232h.cfg でつくる。

# Adapter Configuration:
# http://openocd.sourceforge.net/doc/html/Debug-Adapter-Configuration.html

interface ft2232
ft2232_device_desc "Quad RS232-HS"
ft2232_layout usbjtag
ft2232_vid_pid 0x0403 0x6011 # vendor id, product id from usb

vid_pid は system_profiler SPUSBDataType を実行して出てくるやつを入れたらよい。

ft2232_layout は、この FT4232H を使う場合 usbjtag を指定すればよい。usbjtag を指定した場合

  • ADBUS0 -> TCK
  • ADBUS1 -> TDI
  • ADBUS2 -> TDO
  • ADBUS3 -> TMS
  • ADBUS4 -> nTRST
  • ADBUS5 -> WAIT or STOPCLK
  • ADBUS6 -> nRST
  • ADBUS7 -> RTCK

になる。

libftdi を使う場合

入れるのはより簡単

sudo brew install openocd --enable-ft2232_libftdi 

とするだけで依存まで入る。ちなみに OpenOCD が最新の libftdi には対応していないので、自分でビルドしようとするとだいぶハマる。homebrew 使いましょう。

設定ファイルは以下のようになる。コメント頑張って書いた。AN_135_MPSSE_Basics.pdf で出てくるドキュメントと FT4232H のデータシートを読むとだいたい必要な情報は理解できる……

# Adapter Configuration:
# http://openocd.sourceforge.net/doc/html/Debug-Adapter-Configuration.html

# Use MPSSE adapter
interface ftdi
# USB driver name (must be matched with your device)
ftdi_device_desc "Quad RS232-HS"
# USB vendor ID, product ID pairs
ftdi_vid_pid 0x0403 0x6011

# Channel for using JTAG
ftdi_channel 0

# Initial state and direction setting (ref. AN_135_MPSSE_Basics.pdf)
# ftdi_layout_init [data] [direction]

#  name   signal  state      direction  JTAG name
# *DBUS0  TCK/SK  (low =0)   (out=1)    TDI
# *DBUS1  TDI/DO  (low =0)   (out=1)    TDO
# *DBUS2  TDO/DI  (low =0)   (in =0)    TCK
# *DBUS3  TMS/CS  (high=1)   (out=1)    TMS
# *DBUS4  GPIOL0  (low =0)   (in =0)    (nTRST)
# *DBUS5  GPIOL1  (low =0)   (in =0)    WAIT | STOPCLK
# *DBUS6  GPIOL2  (low =0)   (in =0)    (nRST)
# *DBUS7  GPIOL3  (low =0)   (in =0)    RTCK
ftdi_layout_init 0x08 0x0b

# Use GPIOL0 as nTRST
ftdi_layout_signal nTRST -data 0x10
# Use GPIOL2 as nRST
ftdi_layout_signal nRST -oe 0x40

libftdi を使う場合エラーメッセージがろくに表示されないので罠い。例えば、ftdi_device_desc が間違っていると error code -100 みたいなエラーしかでないのでさっぱりわからない。

この設定ファイルの場合、ftd2xx の usbjtag と同じピン配置になる (はず…)。すなわち以下

  • ADBUS0 -> TCK
  • ADBUS1 -> TDI
  • ADBUS2 -> TDO
  • ADBUS3 -> TMS
  • ADBUS4 -> nTRST
  • ADBUS5 -> WAIT or STOPCLK
  • ADBUS6 -> nRST
  • ADBUS7 -> RTCK

Raspberry Pi との接続

Raspberry Pi 側の配置 (リビジョンによって違うが、最近のバージョン = rev2 で)

  • 7pin TDI
  • 13pin TMS
  • 15pin TRST
  • 18pin TDO
  • 22pin TCK
  • 25pin GND


Raspi <-> JTAG

  • 7pin (TDI) 4pin (TDI)
  • 13pin (TMS) 6pin (TMS)
  • 15pin (TRST) 7pin (TRST, GPIOL0)
  • 18pin (TDO) 5pin (TDO)
  • 22pin (TCK) 3pin (TCK)
  • 25pin GND

とする。あとは同じ名前のピン同士を接続する。

Bare metal Raspbery Pi 側で JTAG を有効にする

JTAG は GPIO ピンの ALT 機能なので (JTAG を使ってる場合これらのピンはGPIOとしては使えない)、JTAG を使うようにブートする必要がある。以下のイメージを kernel.img にして SD カードにコピー

https://github.com/dwelch67/raspberrypi/blob/master/armjtag/armjtag.bin

これは単に JTAG を利用するように設定したあと、LED をチカチカさせて接続を待つプログラムになっている。

接続する

raspi.cfg を以下の内容でつくる。

# Broadcom 2835 on Raspberry Pi

telnet_port 4444
gdb_port 3333

#jtag_khz 1000
adapter_khz 1000

#jtag_nsrst_delay 400
#jtag_ntrst_delay 400

if { [info exists CHIPNAME] } {
   set  _CHIPNAME $CHIPNAME
} else {
   set  _CHIPNAME raspi
}

reset_config none

if { [info exists CPU_TAPID ] } {
   set _CPU_TAPID $CPU_TAPID
} else {
   set _CPU_TAPID 0x07b7617F
}
jtag newtap $_CHIPNAME arm -irlen 5 -expected-id $_CPU_TAPID

set _TARGETNAME $_CHIPNAME.arm
target create $_TARGETNAME arm11 -chain-position $_TARGETNAME

でもって、

openocd -d -f generic-ft4232h.cfg -f raspi.cfg 

とする。-d はデバッグオプションなので、ちゃんと動けばつけなくていい。

シリアルポートドライバのアンロード

ちゃんとデバイス名とか設定してるのに! 動かない! ってときは

kextstat  | grep FTDI

してみると、シリアルポートドライバが (入れたなら) 入ってるので、それが動いている。そのポートに対して JTAG をしかけようとしても衝突するのでうまくいかない。

とりあえずアンロードして動くことを試す

sudo kextunload /System/Library/Extensions/FTDIUSBSerialDriver.kext
# ドライバを無効にしている状態で抜き挿しするとロードされてしまう? アップルがデフォルトで入れてるドライバも無効にする kextstat | grep FTDI ででてくる
sudo kextunload -b com.apple.driver.AppleUSBFTDI

ftd2xx の場合 FT_OpenEx あたりで、うまくいくはずなのに失敗している場合これが疑われる。libftdi の場合エラーがよくわからないので気付きにくい。


これで動くなら動くでいいんだけど、このままだと他のチャンネルをシリアルポートとして使えない。なので、一部のポートだけシリアルポート機能をやめたい。検索したところドライバの設定ファイルを変えればいいっぽい。以下の場所にある。

/System/Library/Extensions/FTDIUSBSerialDriver.kext/Contents/Info.plist

これに対して以下のように A ポートだけ無効にする。

--- Info.plist.orig	2014-03-02 14:58:05.000000000 +0900
+++ Info.plist	2014-03-02 14:58:35.000000000 +0900
@@ -2033,25 +2033,6 @@
 			<key>idVendor</key>
 			<integer>1027</integer>
 		</dict>
-		<key>FT4232H_A</key>
-		<dict>
-			<key>CFBundleIdentifier</key>
-			<string>com.FTDI.driver.FTDIUSBSerialDriver</string>
-			<key>IOClass</key>
-			<string>FTDIUSBSerialDriver</string>
-			<key>IOProviderClass</key>
-			<string>IOUSBInterface</string>
-			<key>bConfigurationValue</key>
-			<integer>1</integer>
-			<key>bInterfaceNumber</key>
-			<integer>0</integer>
-			<key>bcdDevice</key>
-			<integer>2048</integer>
-			<key>idProduct</key>
-			<integer>24593</integer>
-			<key>idVendor</key>
-			<integer>1027</integer>
-		</dict>
 		<key>FT4232H_B</key>
 		<dict>
 			<key>CFBundleIdentifier</key>

一応バックアップをとってから編集して、

sudo kextunload /System/Library/Extensions/FTDIUSBSerialDriver.kext
sudo kextload /System/Library/Extensions/FTDIUSBSerialDriver.kext

すると ls -l /dev/tty.usbserial-* の結果に A が出てこなくなり、JTAG もうまく動くようになる。

JTAG 経由で書きこんで実行してみる

$openocd -f generic-ft4232h.cfg -f raspi.cfg 
Open On-Chip Debugger 0.7.0 (2014-03-02-16:47)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.sourceforge.net/doc/doxygen/bugs.html
Info : only one transport option; autoselect 'jtag'
adapter speed: 1000 kHz
none separate
raspi.arm
Info : clock speed 1000 kHz
Info : JTAG tap: raspi.arm tap/device found: 0x07b7617f (mfg: 0x0bf, part: 0x7b76, ver: 0x0)
Info : found ARM1176
Info : raspi.arm: hardware has 6 breakpoints, 2 watchpoints

上記通りの設定をすると、コマンドでポート 4444 を listen するようになっている。この状態でイメージをアップロードしてみる。

$ telnet localhost 4444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Open On-Chip Debugger
> halt
target state: halted
target halted in ARM state due to debug-request, current mode: Supervisor
cpsr: 0x600001d3 pc: 0x000210d0
> load_image main.elf
330812 bytes written at address 0x00008000
downloaded 330812 bytes in 3.823093s (84.502 KiB/s)
> resume 0
>

でうまくいけばとりあえず JTAG 経由で何かしらする準備ができた感じ。

gdb

上記通りの設定をしていると、gdb プロトコル用のポートとして 3333 も listen している。これを使って gdb でデバッグをする。

まず先に telnet するなりなんなりして halt してないとだめっぽい?

$  arm-none-eabi-gdb main.elf
GNU gdb (32-bit ARM EABI Toolchain JBS-FLOAT_IO-SGXXLITE_ML-2013.05-23-v2013.05-20-g7e710b6) 7.4.50.20120716-cvs
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "--host=x86_64-apple-darwin13.0.2 --target=arm-none-eabi".
For bug reporting instructions, please see:
<https://github.com/jsnyder/arm-eabi-toolchain>...
Reading symbols from /Users/cho45/project/raspberrypi-mruby-bare-metal/main.elf...done.
(gdb)  target remote localhost:4445
Remote debugging using localhost:4445
0x00000000 in ?? ()
(gdb) load
Loading section .text, size 0x52c00 lma 0x8000
Load failed

しかし load がうまくいかず……

Error: 33 words out of 4060 not transferred

みたいなエラーがでる。データラインにノイズが乗ってる?

ちょっとすぐ解決できなそうなので今日はここまで……

  1. トップ
  2. tech
  3. FT4332H (FT2232) で Raspberry Pi (bare metal) を JTAG (Mac OS X)

ゾックス パソコンでワンセグテレビを楽しめるUSB接続ワンセグチューナー ブラック DS-DT305BK - オウルテック

オウルテック

5.0 / 5.0

ワンセグチューナーでソフトウェアラジオをやるのが流行ってるらしくて、やってみたかったので、DS-DT305BK というのを買ってやってみた。Realtek RTL2832U というチップを使っているやつならなんでもいいっぽいけど、日本で買えるのはあまりないっぽい。Mac でも簡単にいけるかな〜と思ったけど超簡単だった……

でもって以下のように rtl-sdr というのを入れる。OSS のデバイスドライバ

git clone git://git.osmocom.org/rtl-sdr.git
cd rtl-sdr/
mkdir build
cd build
cmake ../
make
make install

で /usr/local に入る。

rtl_test -s 3.2e6

とかやるとなんか動いてるふう。何の問題もなく動いてる感じ……

しかしこれだと音聞いたりがめんどうなので GUI も入れてみる。

http://gqrx.dk/ を入れると GUI でウォーターフォール画面とかが見れるようになる。これもビルド済みのがあるので入れるだけで良い。

  1. トップ
  2. tech
  3. 900円でソフトウェアラジオ入門
  1. トップ
  2. ham
  3. 900円でソフトウェアラジオ入門

去年歯医者で一通りの治療が終わったあと、フロスの必要性をかなりしつこく言われたので、最初は糸ようじを使い、途中から普通の糸フロスを使ってみたりしていた。

しかし普通の糸フロスはめちゃくちゃむずかしくて挫折、フロスホルダーという糸ようじみたいにフロスを固定するものを使いはじめたけど、これも糸をうまくピンと張れなかったり、なんか微妙に使いにくかったりして、だんだんフロスをするのがおろそかになっていった。

結果、半年後には虫歯が2つ発見される (1つは神経ギリギリ・もう1つは軽め) というハメになった。まさにこのザマである。

結局結論としては糸ようじが最強ということがわかった。糸1本1本が細いので歯並びが悪くてもだいたいの歯間に入れることができ、なおかつ複数本の糸の塊になっているので汚れがじゃんじゃかとれる感じがキモチいい。

糸ようじ 60本 - 糸ようじ

糸ようじ

5.0 / 5.0

さすがにもう歯を削ったりしたくないので、毎日やっていきたい。今のところ毎日できている。

[tech] 割込みと WFI 命令を使った sleep の実装 | Wed, Feb 26. 2014 - 氾濫原 で WFI 命令があるのでそれ使えばよさそうみたいなことを書いたけど、Which architectures support the WFI instruction? を読んでいたら、Raspberry Pi が WFI 命令をサポートしていないことに気付いてしまった……

Raspberry Pi は ARMv6K というアーキテクチャの ARM1176JZ-F というプロセッサらしい。無印の ARMv6 は WFI 命令をサポートしない。ただ、ARM1176JZ-F は別の方法で使うことができる。

いろいろ書いてあってややこしいが、重要なのはここ

ARMv6K and ARMv6T2 include the WFI instruction, meaning that these processors do not cause an undefined instruction exception when the WFI instruction is executed. However, the ARM1136J(F)-S rev 1 and ARM1176JZ(F)-S (architecture ARMv6K), as well as the ARM1156T2(F)-S (architecture ARMv6T2) treat the WFI as a NOP, and implement the CP15 method for entering "wait for interrupt" mode.

なので、WFI 命令は NOP として扱われてた…… 気を使って NOP でも普通に動くコードを書いた結果気付きにくいバグを作っていた。

mov r1, #0;
mcr p15, #0, r1, c7, c0, #4;

とすれば WFI 相当のことができるみたい。コプロセッサってなんだよって感じだけど、書いてある通り書いたらエラーはでなかった。

mcr 命令は ARM レジスタからコプロセッサへデータを転送する命令らしい。上の mcr 命令の場合

  • C15 (p15) コプロセッサの
  • レジスタ7 (c7) = レジスタ 7: キャッシュ管理機能 に対し
  • 割込み待ち (c0, #4 ) を転送する

転送する値はなんでもいい?のかな。r1 に転送する値を入れるけど 0 にしてる。mcr の第2引数もコプロセッサのオペコードっっぽいけどよくわからない。

  1. トップ
  2. tech
  3. 続・割込みと WFI 命令を使った sleep の実装

この GCC (ビルド済み) を使えば fpu がどうとかも何も問題なくうまくいく。

ただし、gdb の simulator がついてないのが不便…… だけど、むしろ gdb でやるより、qemu とかでエミュレーションしたほうがよさそう…… 環境つくるのめんどいけど

  1. トップ
  2. tech
  3. arm-none-eabi クロスコンパイル環境

Raspberry Pi で bare metal をやっているこのコードを読んで理解したいと思います。README に殆ど書いてありますが、ちょっとよくわからないところがあったのでさらに詳しくしてみます。

前提として、ld スクリプト の通り、このプログラムの冒頭は 0x8000 からはじまっています。

割込みハンドラの実行

まず _start の最初には割込みハンドラの定義が書いてあります。

_start:
    ldr pc,reset_handler
    ldr pc,undefined_handler
    ldr pc,swi_handler
    ldr pc,prefetch_handler
    ldr pc,data_handler
    ldr pc,unused_handler
    ldr pc,irq_handler
    ldr pc,fiq_handler

reset_handler:      .word reset
undefined_handler:  .word hang
swi_handler:        .word hang
prefetch_handler:   .word hang
data_handler:       .word hang
unused_handler:     .word hang
irq_handler:        .word irq
fiq_handler:        .word fiq

これは、README によるとちょっとしたハックになっていて、若干トリッキーな動きをします。

まず最初に _start が実行されはじめると、最初にあるのが ldr pc,reset_handler なので、すぐに reset: に実行が移り、後続のコードは実行されません。

そして reset: の冒頭では以下のようになっています (コメントはこちらでつけたものです)

reset:
    /* Set interrupt handler to radical address from 0x8000
     */
    mov r0,#0x8000
    mov r1,#0x0000
    /* copy machine code 32bytes bytes at once */
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9} /* load machine code from 0x8000 */
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9} /* store code to 0x0000 */
    /* more 32 bytes */
    ldmia r0!,{r2,r3,r4,r5,r6,r7,r8,r9}
    stmia r1!,{r2,r3,r4,r5,r6,r7,r8,r9}

割込みハンドラは本来、0x0000 から初まるアドレスに指定通り配置する必要があります (すなわり、割込みが入ると、決め打ちのアドレスが実行される)。これは ARM のアーキテクチャマニュアルに書いてあるマジックナンバーです。なので、0x0000 へ、割込みハンドラをコピーする必要があり、それがこの部分になっています。

ldmia は LDM 命令 + IA (インクリメントアフター) という命令で、指定されたメモリアドレス (ここでは r0!) から、指定したレジスタリストに値をロードします。r0 はロードされるごとにインクリメントされます。

そして stmia は STM 命令 + IA という命令で、指定したメモリアドレス (ここでは r1!) に、指定したレジスタリストを書きこみます。ここでは一括して1度にレジスタ8個に対し処理が行われているので、8 * 4bytes = 32bytes がコピーされます。1セット目の ldmia/stmia で割込みハンドラ8個分がコピーされ、2セット目でその後に続くハンドラのアドレスリストがコピーされています。

C的には memcpy ですかね。

なぜこのようなハックが必要か?というと、プログラムが必ず 0x8000 にロードされて、直接 0x0000 には書きこめないからみたいです。

スタックポインタの初期化

続いて以下のような似たような3つの塊がでてきます。

    /* IRQ Mode (0b11010010),   */
    mov r0,#0xD2
    msr CPSR_c,r0
    mov sp,#0x8000

    /* FIQ Mode (0b11010001) */
    mov r0,#0xD1
    msr CPSR_c,r0
    mov sp,#0x4000

    /* Supervisor Mode (0b11010011) */
    mov r0,#0xD3
    msr CPSR_c,r0
    mov sp,#0xF000000

msr 命令はプログラムステータスレジスタに値を書きこむ命令で、ここでは特にモードの切り替えを行っています。CPSR_c は カレントプログラムステータスレジスタの下位 8bit に書きこむという意味です。

カレントプログラムステータスレジスタの下位 8bit は以下のような構造になっています。

    /* 
     *  CPSR register lowest 8bit: (page A2-11)
     *      I    [7]   -> IRQ disabled (set 1 to disable)
     *      F    [6]   -> FIQ disabled (set 1 to disable)
     *      T    [5]   -> Always set 0 in ARM state
     *      MODE [4:0] -> Mode bit
     */

下位4bitでモードを指定するようになっており、それぞれコメントにあるようなモードに遷移します。

ARM では例外モードと呼ばれるモードそれぞれに対して sp レジスタは別々に存在しています。ここでは sp をそれぞれ別のアドレスを示すように初期化しているようです。

notmain の呼び出し

その後、スーパーバイザーモードのまま、C で定義された notmain 関数が呼ばれて終わりです。

本来、.bss の初期化などがあるはずですが、この例では使ってないので書いてないようです。

  1. トップ
  2. tech
  3. Raspberry Pi で bare metal している blinker05 の ARM ブートコードを読む

mruby で Raspberry Pi の GPIO をいじるというやつで、sleep をビジーループにしていたのがどうしてもひっかかっていた。

どうも ARM には割込みが起きるまで眠る命令があるみたいなので、それを使ってみることにした。コード全体

static mrb_value mrb_mruby_raspberrypi_gpio_gem_delay_us(mrb_state* mrb, mrb_value self) {
	mrb_int delay;
	mrb_get_args(mrb, "i", &delay);

	// Reset timer flags
	PUT32(ARM_TIMER_CONTROL, 0x3E0020);
	// Load count down timer value
	PUT32(ARM_TIMER_LOAD, delay-1);
	PUT32(ARM_TIMER_RELOAD, delay-1);
	// predevider = (apb_clk - freq) / freq
	PUT32(ARM_TIMER_PRE_DIVIDER, 250 - 1);
	PUT32(ARM_TIMER_IRQ_CLEAR_ACK, 0);
	PUT32(ARM_TIMER_CONTROL,
		(0x3E<<16) | // default free-running pre-scaler
		(1<<7)     | // timer enabled
		(1<<5)     | // timer interrupt enabled
		(1<<1)       // 23-bit counter
	);

	// Enable ARM Timer IRQ
	PUT32(ARM_INTERRUPT_ENABLE_BASIC_IRQS, 1);
	
	while ((GET32(ARM_INTERRUPT_IRQ_BASIC_PENDING) & 1) == 0) {
		// Waiting For Interrupt
		asm volatile ("wfi");
	}

	// Disable ARM Timer IRQ
	PUT32(ARM_INTERRUPT_DISABLE_BASIC_IRQS, 1);

	return mrb_nil_value();
}

コメントにある通りだけど、割込みを設定して、ARM_INTERRUPT_IRQ_BASIC_PENDING のフラグを見つつ、セットされるまでは wfi 命令で継続的に眠る、というようにしてみた。他に割込みを設定していないので、while は1回で抜けるつもり……

wfi 命令はオプショナルな命令らしく、ハードによっては nop として解釈されるらしい。なのでこのような実装の場合、フラグをポーリングするようなコードを併用したほうが安全そう。

というか、実際 wfi 命令がちゃんと動いているかを確かめる方法が面倒くさい。電流を測るしかなさそう。電流を今回測るところまでやってないので、ちゃんと動いてないのかもしれない。ただ、挙動として割込みをポーリングで待つ、というのはできいてるっぽい。

どうでもいいけど wfi で検索しても wifi 扱いされてだいぶウザい。

  1. トップ
  2. tech
  3. 割込みと WFI 命令を使った sleep の実装

//#!gcc -O0

#include <stdio.h>

int main (int argc, char *argv[]) {

        int a;
        int b;

        a = 0; b = 3;

        asm volatile (
                "mov r0, %[x];"
                "mov %[x], %[y];"
                "mov %[y], r0;"
                : [x] "+r" (a), [y] "+r" (b)
                :
                : "r0"
        );

        printf("a=%d, b=%d\n", a, b);

        return 0;
}

asm でやる必要は全然ないけど inline asm の文法が意味不明なのでちょっと書いてみた。

asm( code : output list : input list : clobber list)

という文法らしいけど、パっと見さっぱりわからない。上記の例(値の交換)だと

  • a と b どちらも読み書きが必要なので、output リストのほうで + (read/write) をつける。r はレジスタの意味らしい。
  • operand の指定は名前で行っている。角括弧を使うと名前で指定できるみたい
  • input list は空 (output list で read/write を指定しているので input として指定する必要はない)
  • clobber list というで、このコードによって上書きされるレジスタを指定する
    • これを指定しないと、意図せず他の変数のレジスタを破壊してしまったりする (GCC はどのレジスタが破壊されるかがわからないから、破壊されるレジスタに変数を割り当ててしまう)
    • 指定することで、GCC はそのレジスタを一旦退避させたりできる

このページが比較的わかりやすい。

  1. トップ
  2. tech
  3. ARM inline asm で値の交換を書いてみる

x86 で asm 書く機会ってほんとなくて、むしろ ARM の命令セットを学んだほうがいろいろモチベーションがでてきそう、ということで、とりあえず ARM Linux 上で簡単なのを書いてみます。

ARM

ARM は伝統的に OABI ("arm"), EABI ("armeb")という2つのインターフェイスがあるらしいです。OABI が古いほうで汎用 (Old)。EABI は組込み (Embeded) 用。ABI の違いということはシステムコールの呼びかたとか、サブルーチンの呼びかたが違いということです…… すなわちアセンブリで書くときはどっちを書いてるか明確にする必要があります。

ここでは EABI だけでやってみます。EABI の場合システムコールは、r7 にシステムコール番号を指定して、SVC 命令を発行 (オペランドは 0) すれば良いようです。

環境は GCC でやります。

その前に

アセンブリ書くとき、そもそも命令セット (instruction set) がわからないとどうにもならない。リストはARM のサイトから探す。

x86 とかも、とにかく命令セットがわからないと何もしようがない。x86 instruction set とかでググったらよさそう。x86 は命令いっぱいありすぎて pdf が4つぐらいにわかれてる。

ただ、GCC で書く場合 gas (gnu as) のフォーマットで書かないといけないので、データシートと形式がちがうのが罠い。

GCC as (gas) での書きかたは、公式のドキュメントを参照する必要がある。x86 と arm でも書きかたが違う。

exit するだけのコード

まず exit するだけのコードを書いてみます。

/*#!as -o sketch.o sketch.s && ld -o sketch -e _start sketch.o && ./sketch
 */

.global _start
_start:
        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */
$ as -o sketch.o sketch.s
$ ld -o sketch -e _start sketch.o
$ ./a.out

簡単!!!

r7 にシステムコール番号を入れ、r0 には exit の唯一の引数である終了ステータスを入れ、svc 命令でシステムコールが実行されます。当然 exit なので返ってきません。

obj-dump するとそのままのコードが出てきます

Disassembly of section .text:

00008054 <_start>:
    8054:       e3a00000        mov     r0, #0
    8058:       e3a07001        mov     r7, #1
    805c:       ef000000        svc     0x00000000

Hello, World

こっちは微妙に面倒です。普通に同じようにシステムコールを呼ぶだけですが、文字列をどこに置くか、どうやってアドレスを参照するかがいろいろあります。

.text に一緒に置く

たぶん一番シンプルな例

/* text section */

.section .text
.global _start

_start:
        mov r0, $0x01 /* set file descripter to 1 (stdout) */
        adr r1, msg   /* adr is GNU as's special instruction. This is translated to add */
        mov r2, $0x0d /* set msg length to 0x0d (13) */
        mov r7, $0x04 /* set system call number to 4 (write) */
        svc $0x00     /* supervisor call  */

        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */

msg:
        .asciz "Hello, World\n"
        .align 2

1行ごとにコメントつけてますが、この例では "Hello, World\n\0" という文字列が text セクションに置かれ、それを adr 命令で参照して r1 に書きこんでいます。adr 命令は GNU as が解釈して、指定したラベルを示すように add 命令に書きかえられます。試しにこれをアセンブルしたものを objdump すると

Disassembly of section .text:

00008054 <_start>:
    8054:       e3a00000        mov     r0, #0
    8058:       e28f1014        add     r1, pc, #20
    805c:       e3a0200d        mov     r2, #13
    8060:       e3a07004        mov     r7, #4
    8064:       ef000000        svc     0x00000000
    8068:       e3a00000        mov     r0, #0
    806c:       e3a07001        mov     r7, #1
    8070:       ef000000        svc     0x00000000

00008074 <msg>:
    8074:       6c6c6548        .word   0x6c6c6548
    8078:       57202c6f        .word   0x57202c6f
    807c:       646c726f        .word   0x646c726f
    8080:       000a            .short  0x000a
        ...

とかなってます。_start の中の adr があったところは add 命令に書きかわってます。.text はプログラム用のセクションですが、普通にデータも置けます。

.data セクションに置く

もう少しまともな例です。

/* text section */

.section .text
.global _start

_start:
        mov r0, $0x01   /* set file descripter to 1 (stdout) */
        ldr r1, msg     /* load memory address of msg to register  */
        ldr r2, msg_len /* load memory address of msg_len to register  */
        mov r7, $0x04   /* set system call number to 4 (write) */
        svc $0x00       /* supervisor call  */

        mov r0, $0x00 /* set exit status to 0 */
        mov r7, $0x01 /* set system call number to 1 (exit) */
        svc $0x00     /* supervisor call  */

msg:
        .word data_msg /* write data_msg address to here */
msg_len:
        .word data_msg_len

/* data section */
.section .data
data_msg:
        .asciz "Hello, World\n"
data_msg_len = . - data_msg
        .align 2

テキストデータ本体は data セクション (初期化される・書き換え可能・Cで書いた場合グローバル変数の領域) において、text セクションにはポインタのアドレスを置いています。ldr 命令でそのアドレスをレジスタの読みこみ、write を呼んでいます。

ついでに文字列の長さもアセンブラで計算させています。. は現在のアドレスを示していて、. から data_msg のアドレスを引くことで文字列の長さを求められます。

これを objdump -d -j .text -j .data sketch すると

Disassembly of section .text:

00008074 <_start>:
    8074:       e3a00000        mov     r0, #0
    8078:       e59f1014        ldr     r1, [pc, #20]   ; 8094 <msg>
    807c:       e59f2014        ldr     r2, [pc, #20]   ; 8098 <msg_len>
    8080:       e3a07004        mov     r7, #4
    8084:       ef000000        svc     0x00000000
    8088:       e3a00000        mov     r0, #0
    808c:       e3a07001        mov     r7, #1
    8090:       ef000000        svc     0x00000000

00008094 <msg>:
    8094:       0001009c        .word   0x0001009c

00008098 <msg_len>:
    8098:       0000000e        .word   0x0000000e

Disassembly of section .data:

0001009c <data_msg>:
   1009c:       6c6c6548        .word   0x6c6c6548
   100a0:       57202c6f        .word   0x57202c6f
   100a4:       646c726f        .word   0x646c726f
   100a8:       000a            .short  0x000a
        ...

みたいな感じになります。

ちなみに、ld に --verbose オプションをつけるとリンカスクリプトが表示されます。これはコードやデータを実際にメモリ上にどのように配置するかを定義したもので、ld はこの定義に従って、シンボルを解決 (アドレスを決めるということ) オブジェクトを配置します。

  1. トップ
  2. tech
  3. ARM アセンブリ Hello, World on Raspberry Pi

[tech] perl 5.19.9 の signatures 構文 (普通に引数を書ける構文) を試す | Mon, Feb 24. 2014 - 氾濫原 で「ただ引数の名前とかは外からとることができない。せっかく構文に組込まれたのなら、とれてもよさそうだなと思った」と書いたので、すこしだけ実装を書いてみた。

blead perl ecb4de39577c95734821743685366f6fe7f59a2d へのパッチです。

#!./perl -Ilib
use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";
use Test::More;
package signatures {
sub args ($sub) {
$signatures::subs{$sub+0};
}
sub arity ($sub) {
$signatures::arities{$sub+0};
}
}
subtest "signatures" => sub {
package _test1 {
sub foo ($xxx, $yyy) {
}
sub bar ($zzz, $aaa=1) {
}
sub baz ($zzz, @rest) {
}
}
is_deeply signatures::args(_test1->can('foo')), [qw(
$xxx
$yyy
)];
is signatures::arity(_test1->can('foo')), 2;
is_deeply signatures::args(_test1->can('bar')), [qw(
$zzz
$aaa
)];
is signatures::arity(_test1->can('bar')), 1;
is_deeply signatures::args(_test1->can('baz')), [qw(
$zzz
@rest
)];
is signatures::arity(_test1->can('baz')), -2;
};
subtest "no signature subs" => sub {
package _test2 {
sub foo {
}
}
is signatures::args(_test2->can('foo')), undef;
is signatures::arity(_test2->can('foo')), undef;
};
TODO: subtest 'lexical_subs' => sub {
local $TODO = "FAIL";
use feature 'lexical_subs';
no warnings "experimental::lexical_subs";
state sub foo ($foo) {
}
is_deeply signatures::args(\&foo), [qw(
$foo
)];
is signatures::arity(\&foo), 1;
};
done_testing;
view raw 01-signatures-args.t hosted with ❤ by GitHub
diff --git a/toke.c b/toke.c
index 88524b4..b517e37 100644
--- a/toke.c
+++ b/toke.c
@@ -12432,6 +12432,48 @@ S_parse_opt_lexvar(pTHX)
return var;
}
+void
+Perl_parse_subsignature_remember_sub_arg(pTHX_ OP* var)
+{
+ HV* signaturesubmap;
+ SV* subid;
+ SV* name;
+ HE* signatures;
+ AV* list;
+
+ signaturesubmap = get_hv("signatures::subs", 0);
+ if (!signaturesubmap) signaturesubmap = get_hv("signatures::subs", GV_ADD);
+
+ subid = newSViv((IV)(PL_compcv));
+ name = newSVsv(PAD_COMPNAME_SV(var->op_targ));
+ signatures = hv_fetch_ent(signaturesubmap, subid, 0, 0);
+ if (signatures) {
+ list = (AV*)SvRV(HeVAL(signatures));
+ } else {
+ list = newAV();
+ }
+ av_push(list, name);
+ hv_store_ent(signaturesubmap, subid, newRV_inc((SV*)list), 0);
+}
+
+void
+Perl_parse_subsignature_remember_sub_arity(pTHX_ int min_arity, int max_arity) {
+ HV* signaturearitymap;
+ SV* subid;
+ int arity;
+
+ signaturearitymap = get_hv("signatures::arities", 0);
+ if (!signaturearitymap) signaturearitymap = get_hv("signatures::arities", GV_ADD);
+
+ subid = newSViv((IV)(PL_compcv));
+ if (max_arity == -1) {
+ arity = -(min_arity + 1);
+ } else {
+ arity = min_arity;
+ }
+ hv_store_ent(signaturearitymap, subid, newSViv(arity), 0);
+}
+
OP *
Perl_parse_subsignature(pTHX)
{
@@ -12488,7 +12530,10 @@ Perl_parse_subsignature(pTHX)
prev_type = 0;
min_arity = pos + 1;
}
- if (var) expr = newASSIGNOP(OPf_STACKED, var, 0, expr);
+ if (var) {
+ expr = newASSIGNOP(OPf_STACKED, var, 0, expr);
+ Perl_parse_subsignature_remember_sub_arg(var);
+ }
if (expr)
initops = op_append_list(OP_LINESEQ, initops,
newSTATEOP(0, NULL, expr));
@@ -12539,6 +12584,7 @@ Perl_parse_subsignature(pTHX)
initops = op_append_list(OP_LINESEQ, initops,
newSTATEOP(0, NULL,
newASSIGNOP(OPf_STACKED, var, 0, slice)));
+ Perl_parse_subsignature_remember_sub_arg(var);
}
prev_type = 2;
max_arity = -1;
@@ -12590,6 +12636,7 @@ Perl_parse_subsignature(pTHX)
newSVpvs("Too many arguments for subroutine"))))),
initops);
}
+ Perl_parse_subsignature_remember_sub_arity(min_arity, max_arity);
return initops;
}

やってることは signatures::subs みたいな名前でハッシュをつくって放りこんでるだけです。perl のコード難しくてあってるかわからないのと、たぶん参照カウントがおかしかったりしてそうですが、とりあえず簡単には動く。

ただ、lexical_subs に対してはうまくとれない (とってるサブルーチンのアドレスがちがう) ので、そこが TODO かな、というのと、サブルーチンとか大量に定義されるので、結構メモリ食ったりしそうだな、ってのが怪しい感じです。

  1. トップ
  2. tech
  3. perl 5.19.9 の signatures 構文に引数リストをとれる機能を足す

Perl 5.19.9 で実装された signatures の構文をためしてみる - tokuhirom blog を見てそんなのできたのか〜と思ったので、いろいろ試してみた。なんとこれは、今までになく直感的に引数が書けてしまう革命的構文です。

use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";

use feature 'lexical_subs';
no warnings "experimental::lexical_subs";

# 引数の数チェックをしてくれる。便利
eval {
	say "check length of arguments";
	state sub hello ($foo) {
		say "hello $foo";
	}


	hello();
	#=> Too few arguments for subroutine at pl.pl line 9.
}; if ($@) { warn $@ }

# デフォルト値も普通に書けます
eval {
	say "default value";
	state sub hello ($foo="world") {
		say "hello $foo";
	}


	hello();
	#=> "hello world"

	hello("yunotti");
	#=> "hello yunotti"
}; if ($@) { warn $@ }

# デフォルト値に式も書けます。Ruby なんかでも書けますね。引数として指定されない場合だけ氷菓されます
eval {
	say "expression in default value (evaluated same as Ruby)";
	my $i = 0;
	state sub hello ($foo=$i++) {
		say "hello $foo";
	}


	hello();
	#=> hello 0

	hello();
	#=> hello 1

	hello("yunotti");
	#=> "hello yunotti"

	hello();
	#=> hello 2
}; if ($@) { warn $@ }

# @foo を指定したら可変引数にできます。%hash とかもできます。
eval {
	say "variable length arguments";
	state sub hello ($foo, @rest) {
		say "hello $foo and " . join(", ", @rest);
	}


	hello("yunotti", "miyachan", "sae", "hiro");
	#=> hello yunotti and miyachan, sae, hiro
}; if ($@) { warn $@ }

# @_ の挙動は今までどおり変数渡しです。
eval {
	say 'with @_: @_ is passed by variable (same as prev perls)';
	state sub hello ($foo, $bar) {
		$foo = 'xxx';
		$_[1] = 'xxx';
	}


	my $foo = 'foo';
	my $bar = 'bar';
	hello($foo, $bar);
	say "$foo, $bar"; #=> foo, xxx

}; if ($@) { warn $@ }

ただ引数の名前とかは外からとることができない。せっかく構文に組込まれたのなら、とれてもよさそうだなと思った。すこしコード追ってみたけど、基本、今までで同じようなコードが内部的に生成されるだけっぽい。サブルーチンリファレンスに附属する形でなんかメタデータを入れれたらいいんだけど、よくわからなかった。

あと、Smart::Args みたいなのは使えるかと思って試してみたけど、既存コードと同じ感じなので、当然普通に使える。ただ、signatures を活用した感じにはできなそう。うまいやりかたあるのかな。

use v5.19;
use strict;
use warnings;
use feature 'signatures';
no warnings "experimental::signatures";

use feature 'lexical_subs';
no warnings "experimental::lexical_subs";


{
	# Smart::Args は普通に使える
	use Smart::Args;
	{
		# % を書けば呼ばれる時点でハッシュかどうかのチェックは入る
		state sub hello (%) {
			args(my $foo);
			say "hello $foo";
		}

		hello(foo => "foobar");
		eval {
			hello(1);
		}; if ($@) { warn $@ }; #=> Odd name/value argument for subroutine at pl.pl line 20.
	};

	{
		# % を書かなければ今までどおり Smart::Args 側でエラる
		state sub hello {
			args(my $foo);
			say "hello $foo";
		}
		eval {
			hello(1);
		}; if ($@) { warn $@ }; #=> Odd number of elements in anonymous hash at lib/site_perl/5.19.9/Smart/Args.pm line 39.
	}
};

{
	# せっかくなら signatures を活用したい感あるけどいい感じにならない (引数名を2度書かないといけない)

	use PadWalker qw/var_name/;
	sub validate ($var, $rule) {
		my $name = var_name(1, \$_[0]) =~ s{^\$}{}r;
		require Smart::Args;
		$_[0] = Smart::Args::_validate_by_rule($_[0], 1, $name, $_[1]);
	}

	{
		state sub hello ($foo) {
			validate($foo, { isa => 'Int' });
			say "hello $foo";
		}
		eval {
			hello("xxx");
		}; if ($@) { warn $@ }; #=> Validation failed for 'Int' with value xxx
	}
};
  1. トップ
  2. tech
  3. perl 5.19.9 の signatures 構文 (普通に引数を書ける構文) を試す

bare metal という言葉を最近知って、おもしろそうだなあと思ったので Raspberry Pi 上で試してみた。bare metal とは OS レスで直接動かす (つまりリッチなマイコン的に使う) ことのようです。bare metal で動かすだけだと目標が低い感じなので、まず mruby を動かすところまでやろう、という感じです。

yamanekko さんというかたが既にやって日本語で書いているので、それを見てコピペしながらやってみつつという感じです。ちょいちょいうまくいかなかったのでかなりハマった……

ARM クロスコンパイル環境の構築

https://launchpad.net/gcc-arm-embedded にあるバイナリをインストールするのが一番良い。これを入れれば以下にあるような fpu 問題にも悩まない。

大変めんどう。ググるといろいろでてくるけど、今流行りの全部うまいことやってくれる感じのスクリプトがあったので試してみた。

https://github.com/jsnyder/arm-eabi-toolchain

git clone git@github.com:jsnyder/arm-eabi-toolchain.git
cd arm-eabi-toolchain
make install-cross

大変時間がかかる。Makefile を開いて、--enable-languages="c,c++" となってるところから c++ を消したほうがいいっぽい。使わないし…

入るライブラリ

  • arm-none-eabi-
    • gcc
    • binutils (objdump とか)
    • gdb
  • GMP
    • 数値演算ライブラリ
  • MPFR
    • 浮動小数点ライブラリ
  • MPC
    • MPFR ベースのさらに複雑な浮動小数点ライブラリ
  • newlib
    • 組み込み環境用のライブラリ。 libc とかを提供

少し古いバージョンが入る

FPU 問題

最初

arm-none-eabi-ld: error: main.elf uses VFP register arguments, /Users/cho45/arm-cs-tools/arm-none-eabi/lib//libc.a(lib_a-errno.o) does not

とか出て ld が通らなかった。

yamanekko さんのレポジトリにある Makefile だと fpu ライブラリへのパスが書いてあるけど、上のスクリプトでクロスコンパイル環境を構築してもでてこない……

gcc の configure オプションか何かなのかもしれないけど、時間がかかるしむずかしいので諦めた。

よくわからなすぎるのでググったらARM EABIでの浮動小数点演算というが参考になった。

結局 -mfloat-abi=soft にして完全にソフトウェア命令しか出さないようにしたら動くようになった。-mfloat-abi=soft=softfp だと浮動小数点演算が走ったタイミングで死んだりしてよくわからない。

オプションの種類は arm-none-eabi-gcc --target-help で見ることができる。-mfpu=vfp のところもいろいろ種類があるけどよくわからない。

未だ完全には解決してない。

リンカスクリプト

memmap というファイルになってる。面倒だった。間違えるといろいろよくわかないことになる。今回遭遇したのは

  • .ARM.exidx を定義してなかったせいでおこられる
    • C++ 用のセクションらしいけど 特定の関数を使おうとすると必要っぽい
    • 定義の順番がおかしいとできるバイナリがデカくなったり、動かなくなったりする
  • アラインメントがおかしかったせい?で newlib の mALLOc が無限ループする
    • 当然ヒープを実装せず使わないなら再現しないので簡単なLチカだと気付かない

newlib が要求してくるシステムコール類は syscalls.c でモック的に定義されている(コピペしたのものをクリーンアップした)。だいたいはどうでもいい関数だけど、_sbrk が大変重要で、これによってヒープが実現されている。

メモリ割当のマッピングとかはリンカスクリプトで定義したものを vectors.s (アセンブラ) で C 側から見れるようにして、それを使ってヒープとかの実装をしているみたい。

gdb でシミュレーションデバッグ

.gdbinit に書いてあるけど

file main.elf
target sim
load

とすることで、gdb 上でシミュレーションして動かすことができるので、周辺機器が絡まない、とりあえずのデバッグはこれでも十分できる。

ただ、一部の演算、確認したのは % (余剰)で変なエラーがでておかしい。

実際やった手順

mruby なしでLチカ

まず mruby なしでLチカができないとどうしようもないので、まずそこまでできるプロジェクトツリーを作って動くことを確認するまでやった。

https://github.com/cho45/raspberrypi-bare-metal

この過程だけでも上記の通りかなりハマった……

mruby ありでLチカ

次に mruby ありでLチカに挑戦した。yamanekko さんのmruby-rs-led を参考に、Linux の gpio 的な GPIO module gem を実装した。

GPIO のアドレスとかはデータシート記載のものを手動で変換せずにできるだけそのまま書いておきたいと思うので、そうしてる。バスアドレスというのと、物理アドレスというのがあって、データシートにはバスアドレスで書いてあって、しかし実際使うのは物理アドレスなので、変換 (固定値) が必要みたい。

これによって以下のように書けるように (完全にちゃんと動いているか微妙だけど) なった。Ruby にすると一気に簡単になった感じがして落差がウケます。ほぼ Linux 上で適当にラッパー書いて書くのと同じコードになった。うまくラップできれば主要コードは Ruby レベルでテストが書けてよさそう。

pin = 16

GPIO.direction(pin, :low)
bool = false
loop do
	bool = !bool
	GPIO.write(pin, bool)
	10000.times { }
end

しかしここまで動かすのも大変で、何時間もハマった。ここでハマったのは特にリンカスクリプトでアラインメントがおかしかったせいで、mrb_open() がそもそも返ってこないというのがつらかった。

gdb で追ってもなぜか newlib の中で無限ループにになるので意味不明だった。

そしてそれがようやくなおったと思っても mrb_open() の最中に浮動小数点演算もしていて、ここでハングするということが起きてつらかった。最終的には上記の通り、とりあえず常にソフトウェアにしてなんとか動かした。

sleep の実装を書いてないので Ruby 側でビジーループしてる。16ピンにしてるのは、Raspberry Pi の基板上に実装済みの LED だからで、デバッグ用に新規配線をしなくてすむからです。

sleep の実装

さすがにビジーループをRuby側でやるのはダサいので次に sleep を実装した。

system timer と arm timer というのがあるけど、今回は system timer を使った。arm timer はシステムの状態によってクロックが変わるみたい。system timer は独立したクロックを持ってるようだ。

ただ、肝心の system timer clock がよくわからなかった。試行錯誤したかんじだと、1Mhz ぐらいで動いてる。分周比設定とかがなくて謎。要追試

Cレベルではビジーループしてる。うまくCPUスリープさせたいけど、まだよくわかってない。

mruby でハマったところ

mruby ではあまりハマらなかった。mrbgem 作るのは大変簡単になっていておもてなし感がある。

ただ、build_conf.rb で gem を指定せずにビルドすると、あとから gem を指定しても、clean するまで反映されないのが罠ぽかった。深くおってないけど、DISABLE_GEMS が永続されてしまうぽい?

この件は再現させて1行パッチ送ったらマージされたのでたぶん大丈夫です。build_config.rb を書きかえたら .c ソースのリビルドが走るはず…

レポジトリ

これも習作だけど別レポジトリにおいてる

課題点

  • fpu の問題の解決
  • GPIO モジュールの拡張
    • TIMER や割込みなどを使えるように
    • ハードウェア I2C SPI の実装
    • bit banging での I2C 実装
  • JTAG を試す
  • ブートローダーを使うように
    • SDカードいちいち抜き差ししてるとつらいので

感想

mruby は夢が広がる。mruby で OS とか作ったらかっこよさそう。

  1. トップ
  2. tech
  3. mruby を Raspberry Pi 上で bare metal で動かすまで