OS自作入門 -Advent04-

NO IMAGE

Step2 シリアル通信 前半

前回までで一通りプログラムの基幹となる部分は作ったような気がする。ref: commit。今回はシリアル通信の話になるらしい。確かにこの辺りもあんまりよくわかっていないところではある。

メモリマップドI/O

データの送受信に関しては電圧やら転送速度などが決められている。それをインターフェースと呼ぶ。また、その時の例えばエラーだったら再送する、受信側が準備ができたら送信するなど通信の約束事をプロトコルと呼ぶ。今やっているシリアル通信は9600bpsで通信をする。なのでCPU(プログラム)で9600回/秒でON/OFFの切り替えをして通信することも可能だが、それを書かなくてもいいようにICチップに実装がされている。これをコントローラと呼ぶ。

プログラム => CPU => ICチップ(コントローラ)

こんな流れか。

コントローラの制御はレジスタを経由して行う。コントローラの特定のレジスタに1byte書き込むとコントローラがよろしく1byteをシリアル送信してくれるという感じらしい。

で、このレジスタとはなんなんだろうかという話。32bit CPUならレジスタのサイズは32bit。データ転送のデータ・バスとアドレス指定に使うアドレス・バスどちらも32bitで作られているのが普通。アドレス・バスはある特定の周辺機器のメモリのどこにI/Oをしたいのかを指定できる経路のこと。かな。(あんまり分かっていない)

32bitで表現可能な範囲が、0x00000000 - 0xffffffffなのでこの範囲のアドレス・バスを使ってアドレスマッピングをしないといけない。(だから32bitマシンだとメモリは4GBが限界ということなのか)例えば、0x00000000:0x000fffffと0x00100000:0x001fffffの範囲に同じ機器が接続されていると衝突しちゃうから上位のbit(ここだと0x000とか0x001とかになっている部分)を使って周辺機器をコントロールするために周辺機器:アドレスををメモリ内でマッピングしましょう。この場合だと0x001は周辺機器Xで0x002は周辺機器Yでそれぞれ決まった下位のバイトを指定するとそれに従って制御ができますよ。

という話か。

C言語からの操作

ということでシリアルコントローラに対してのマッピングが行われていて、アドレスマッピングされた状態で特定のメモリ空間上をread/writeすることにより制御が可能になる。

例)

char c;
volatile char *reg1 = (char *)0x10000000;

c = *reg1;
*reg1 = 0x01;

かなりシンプルな例ではあるが、0x10000000-0x1000000fにシリアルコントローラがマッピングされていて、そこに対して0x01をwriteしている。
(0x01をwriteすることによって何が起きるかは知らないが、何かを制御したり送信したりすることができるということを示している)

内臓シリアル・コントローラ

組み込みの場合はある程度内部に周辺コントローラを内臓しているのが普通(CPUの外部にシリアル・コントローラを接続するのとは少し違う)。もともとハードウェア制御に利用される目的のCPUであるためマイクロ・コントローラと呼ばれる場合もあるらしい。(だからマイコンなのか)。H8の場合はSCIと呼ばれるシリアル・コントローラを内臓している。

H8の場合はSCI0,SCI1,SCI2とあるが、SCI1が繋がっているとのことで、

#define H8_3069F_SCI1 ((volatile struct h8_3069f_sci *)0xffffb8)
struct h8_3069f_sci {
  volatile uint8 smr;
  volatile uint8 brr;
  volatile uint8 scr;
  volatile uint8 tdr;
  volatile uint8 ssr;
  volatile uint8 rdr;
  volatile uint8 scmr;
}

という感じの構造体になっている。

各それぞれのレジスタは1byteのサイズになっていてそのアドレスに書き込まれた値によって動きを変えることができる。

smr = SerialModeRegisterはシリアル入出力のパラメータ設定を行う。クロックの制御やらパリティの種類などなど。
scr = SerialControllRegisterはシリアル入出力の制御を行う。送受信有効/無効の切替とか割込有効/無効の切替など。
bbr = BitRateRegisterはシリアル通信の速度(ボーレート)の設定を行う。ボーレートとは?初耳だ。9600bpsが一般的だが115200bpsもあるらしい。これはCPUのクロック数によるものらしいがH8はそこまで速くないので。詳細な設定とかBBRの計算式とかはドキュメントに載っている模様。

文字出力に関して
tdr = TransmitDataRegister
ssr = SerialStatusRegister
を使う。

SSRはシリアル通信の状態を見たりするために使うらしい。SSRの状態を確認しながら(ループで待ちながら)TDRに対して送信した文字列を書き込んだりする。多くのコントローラには、設定・制御・状態取得という3つのレジスタがあることが多いらしい。んーいろいろ知らない事情が多いなぁ。

面白いことが書いてあるな。抜粋すると、

C言語では改行コードは'\n'と表記されますが、UNIXの世界では0x0aでになります。しかしシリアル通信では歴史的経緯により、改行コードは'\r'(0x0d)が利用されます。さらにPC側からシリアル出力する場合には、改行コードは'\n'から'\r\n'のように変換することになっています。

なるほど。なんかline breakとcarriage returnの違いかなぁくらいだったけど、このレベルに出てくるのか。c言語での改行コードが'\n'なのは知ってる。シリアル通信だと'\r'になっているのはなんでなんだろう。さらにわざわざ改行コードを'\r\n'に変えるはwindowsが出てきたからだったりするのかな。

シリアル: 顛末変換を行わない生の入出力
コンソール: 端末変換を行なっての入出力
端末変換: 上記のようなコード変換を指す

送信の大きな流れとしては

何かの文字 => cで書かれたputc => 端末変換 => シリアル入力 => serial_send_byte => ssrの送信完了ビットを見て送信可能になるまで無限ループで待つ => 書き込み可能になったらtdrにbyteを書き込む => ssrの送信完了をfalseにする => 送信完了するとコントローラがssrの送信完了ビットを立てる

こんな流れでシリアル通信(送信)が行われているようだ。

メモ

  • 情報の出力のために真っ先にドライバが実装されるI/O
  • ペリフェラル、周辺インターフェース、周辺I/Oとか呼ばれるらしい
  • データ転送の目的という観点だとシリアル通信もLANも変わらない。
  • 変わるのは規格や電圧、クロックなど。
  • volatile: C言語コンパイル時の最適化が抑制される予約語
    例えば、コントローラの制御をする際にはある特定のレジスタに値を書き込んだ後に同じレジスタに別な値を書き込む。とかがある。

      serreg->reg1 = 0x01;
      serreg->reg1 = 0x02;

    これにvolatileをつけないと一行目がなかったことにされる(最適化されて1行目の評価は不要であると判断されるらしい)
    しかし、コンパイラからすればserregがメモリ上の値なのかコントローラのレジスタの値なのかとか知りようがないため必要。