放課後の電子工作 HOME > 超小型MP3プレーヤー [ Timpy ]
> Rev5.0 【FMチューナ+ワイヤレスリモコン+USB】 > USB-MSD-RD KeilからSDCCへの移植

2007年6月9日 更新

超小型MP3プレーヤー [ Timpy ]
 
USB-MSD-RD KeilからSDCCへの移植
C8051F340-TB+AB5

【オリジナル版】
ターゲット・ボードC8051F340-TB(右)と
USB-MSD-RD用基板AB5(左)

C8051F340-TB+miniSD手配線基板

【移植版】
ターゲット・ボードC8051F340-TBと
手配線基板(miniSD)

USB-MSD-RDとは、シリコン・ラボラトリーズ社の8ビットマイコンC8051F340による、USBマス・ストレージのリファレンス・デザインです(右の写真上)。
C8051F340開発キットのターゲット・ボードに、USB-MSD-RD用の基板AB5を追加することで、SD/MMCまたはコンパクト・フラッシュがUSBマス・ストレージ・デバイスとしてアクセスできます。
もちろんWindows標準のデバイス・ドライバ経由です。
さらにUSBケーブルの代わりにRS232をつなげば、ハイパー・ターミナルなどを使ってDOS風の操作もできます。

ところで、このリファレンス・デザインのプログラム・ソースは、Keil社製のCコンパイラ、アセンブラ用に書かれています。
しかしシリコンラボの開発キットに付属している評価版コンパイラでは、機能制限(オブジェクト・サイズ)に引っかかってコンパイルできません。
そこでこれをフリーのSDCCに移植してみました。
このページは、その移植作業についてまとめた備忘録です。

右の写真下が今回の移植版です。C8051F340の開発キットは購入しました($99)が、リファレンス・デザインが開発キットと同じ$99だったので、手配線で作りました。AB5基板が大したことない割りに高価なのは、256MBのSDカードが添付されているからでしょう。ドキュメントやプログラム・ソースはシリコンラボのHPでダウンロードできますので、基板とカードさえ用意できれば問題ありません。
結局、秋月のユニバーサル基板にminiSDのコネクタがちょこんと付く形でできあがりです。

なお、SDCCを触るのは初めてなので、頓珍漢なことをしているかもしれません。誤りなどお気づきの点がありましたら、ぜひお知らせください。よろしくお願いします。

(注)写真のとおり、オリジナルではコンパクト・フラッシュもサポートされていますが、Timpyでは使用予定がないのでまじめに移植していません。単にコンパイラを通すための変更のみ加えてあります。

【移植元】

移植のターゲットは、シリコン・ラボラトリーズ社によるUSBマス・ストレージ・リファレンス・デザイン USB-MSD-RD (Release 1.1)です。
○シリコン・ラボラトリーズ社の該当ページ USB Mass Storage Reference Design
○リファレンス・デザインのユーザーズ・ガイド USB-MSD-RD.pdf
○リファレンス・デザインのプログラマーズ・ガイド AN282.pdf
○リファレンス・デザインのプログラム・ソース AN282SW.zip ソースだけでなく、回路図その他一式が入っています。
(注)各ファイルは勝手に再配布していますので最新でない場合があります。

【修正項目】

KeilからSDCCへの移植に際して、変更した主な点を以下に挙げます。
書き出すとかなり長くなりそうです。少しずつ追記していきますので、長い目で見てやってください。
また、ここに挙げたのはUSB-MSD-RDを移植する上で必要になったものです。一般に移植に必要な項目を網羅しているわけではありません。念のため。

--

》SDCCのバージョン

今回の移植には、安定版のRelease 2.5.0ではなく、最新(2006年6月28日版)のスナップショットを使いました。

Release 2.5.0にはバグがあるため、この移植には使えません。
具体的には、code記憶域に定義したstruct内のunionが正しく展開されない、というものです。

まず、型名WORDは、F34x_MSD_USB_Descriptor.hその他で、unsigned intとunsigned char配列のunionとして宣言されています。

typedef union {unsigned int i; unsigned char c[2];} WORD;

このWORDをメンバとして持つstructが、F34x_MSD_USB_Descriptor.hにて宣言されています。
同様のstructは複数ありますが、そのうちのひとつを例に挙げます。

typedef code struct
{
  BYTE bLength; // Size of this Descriptor in Bytes
  BYTE bDescriptorType; // Descriptor Type (=2)
  WORD wTotalLength; // Total Length of Data for this Conf
  BYTE bNumInterfaces; // No of Interfaces supported by this Conf
  BYTE bConfigurationValue; // Designator Value for *this* Configuration
  BYTE iConfiguration; // Index of String Desc for this Conf
  BYTE bmAttributes; // Configuration Characteristics (see below)
  BYTE bMaxPower; // Max. Power Consumption in this Conf (*2mA)
} configuration_descriptor; // End of Configuration Descriptor Type

このconfiguration_descriptor型の変数が、F34x_MSD_USB_Descriptor.cにて次のように宣言されています。

const configuration_descriptor Config_Desc =
{
  0x09, // Length
  0x02, // Type
  0x2000, // Totallength
  0x01, // NumInterfaces
  0x01, // bConfigurationValue
  0x00, // iConfiguration
  0x80, // bmAttributes
  0x0F // MaxPower
}; //end of Config_Desc

これをRelease 2.5.0でコンパイルすると、アセンブル出力は次のようになります。

G$Config_Desc$0$0 == .
_Config_Desc:
  .db #0x09
  .db #0x02
  .byte #0x20,#0x00
  .db 0x00  ←ゴミ
  .db 0x00  ←ゴミ
  .db #0x01
  .db #0x01
  .db #0x00
  .db #0x80
  .db #0x0F

このように、unionのバイト数分のゴミ(0x00)が出力されてしまっています。
同じソースをスナップショット版でコンパイルすると次のようになります。

G$Config_Desc$0$0 == .
_Config_Desc:
  .db #0x09
  .db #0x02
  .byte #0x20,#0x00
  .db #0x01
  .db #0x01
  .db #0x00
  .db #0x80
  .db #0x0F

このように、2.5.0でみられた「ゴミ」は生成されなくなっています。
めでたし、めでたし。

--

》文法的な違い(1) - sfr, sfr16, sbit

処理系による文法的な相違はある程度やむを得ません。マニュアルを突き合せて、ガシガシ書き換えます。
まずは内部レジスタの宣言から。
Keilでは、

sfr P0 = 0x80;

ですが、SDCCでは

__sfr __at (0x80) P0;

です。16ビットレジスタsfr16、ビットアドレスsbitも同様の表記となります。
スナップショット版をインストールすると、C:\Program Files\SDCC\include\mcs51\フォルダにc8051f340.hが用意されていますので、それを使えばOKです。

なおSDCCのマニュアルによると、
"sfr16で定義された2バイトレジスタのアクセス順は、基本的に下位バイト→上位バイトの順だが保障はされない。
 したがってアクセス順を守らなければならない16ビットレジスタはsfr16で宣言すべきでない"
とあります。C8051ではPCA0H、PCA0Lが該当しますね。PCA0Lを先に読む必要がありますので要注意です。

またKeilではsbitの表記として

sbit CF_RST = P1^0;

という"^"(キャラット)を使った記述ができますが、SDCCではできません。

--

》文法的な違い(2) - ポインタ宣言で記憶域を指定する際の語順

ジェネリックポインタよりも、記憶域を指定したほうが効率の良いコード生成につながります。
その記憶域を指定する際の語順が両者で微妙に違います。
例えば、
 "外部メモリxdataを指すcharポインタpを内部メモリdataに置く"
場合、Keilでは

char xdata * data p;

SDCCでは

__xdata char * __data p;

と記述します。

--

》文法的な違い(3) - 初期化つきunion

上記『SDCCのバージョン』でも出てきましたconfiguration_descriptor型には、unionで定義されたWORD型メンバが含まれています。
この変数を初期化つきで宣言する際、SDCCではそのメンバを{、}でくくる必要があります。Keilでは要りません。

const configuration_descriptor Config_Desc =
{
  0x09, // Length
  0x02, // Type
  {0x0020}, // Totallength ← これ
  0x01, // NumInterfaces
  0x01, // bConfigurationValue
  0x00, // iConfiguration
  0x80, // bmAttributes
  0x0F // MaxPower
}; //end of Config_Desc

--

》アセンブラ

USB-MSD-RDにはアセンブラで書かれたモジュールが2つ(F34x_MSD_MMC_Command.asm、F34x_MSD_USB_Procedure.asm)あります。
これらはどちらも大幅に書き換えました。変更に関係する主な点を挙げておきます。
○引数の渡し方
○擬似命令の違い
○ジェネリック・ポインタの最上位バイトに格納される定数の違い
○その他もろもろ・・・

--

》メモリ・モデル

Keilのメモリ・モデルには、スモール、コンパクト、ラージの3種類があります。
オリジナル版はラージ・モデルを使用しています。

一方のSDCCにはスモール、ミディアム、ラージの3種類があります。
移植版では、変数域の状況をそれぞれ比較した結果、スモール・モデル+stack-autoオプションを使うことにしました。

stack-autoオプションを使った場合、staticでないauto変数に記憶域指定子が付いているとエラーになります。
オリジナルのソースではauto変数にもことごとく記憶域が指定されているため、それらを片端からコメントアウトします。

--

》ウォッチドッグ・タイマの無効化

C8051シリーズはウォッチドッグ・タイマ有効がデフォルトです。
オリジナル版ではmainの先頭でウォッチドッグ・タイマを無効にしていますが、SDCCではスタートアップ・ルーチンの途中でリセットが発生してしまい、mainまでたどり着けませんでした。
そのため、マニュアルにしたがって_sdcc_external_startup関数をオーバライドし、その中でウォッチドッグ・タイマを禁止するようにしました。

--

》スタートアップ・ルーチンと_XPAGEディレクティブ

SDCCでは、スタートアップ・ルーチン内でXDATAメモリを初期化する際に、8ビット・メモリ・アドレスによる外部メモリ・アクセス命令"movx @Ri, A"を使用するようです。
この場合、メモリ・アドレスの上位バイトはポート2(P2)に出力しておくのがオリジナル8051の作法です。
しかし、C8051その他外部メモリを内蔵しているデバイスでは、メモリ・アクセスがデバイス内で完結しているため、P2は使えません。代わりに、デバイス内でアドレスの上位バイトを生成するためのSFRが用意されています。C8051ではEMI0CNレジスタがそれにあたります。

_XPAGEディレクティブはSDCCにそれを知らせるためのものです。
私はF34x_MSD_Definitions.hの中に次の1行を入れました。

sfr at 0xaa _XPAGE; //EMI0CN

これを定義しておかないと、デフォルトでP2が使われてしまいます。
最初これを知らなくて、ウォッチドッグ・タイマの関係でスタートアップ・ルーチンのディスアセンブル・リストを見ていて偶然気がつきました。
「なんだこりゃ」と思ってマニュアルを良くみると、ちゃんと書いてありました(4.1.1 pdata access by SFR)。撃沈。

--

》エンディアンの違い

Keil対SDCCで最大の相違点といってもいいでしょう。Keilはビッグ・エンディアンですが、SDCCはリトル・エンディアンです。
オリジナル版のソースはすでにこの点に配慮してあります。

#define LSB 1
#define MSB 0

// All words sent to and received from the host are
// little endian, this is switched by software when
// neccessary. These sections of code have been marked
// with "Compiler Specific" as above for easier modification

このLSB、MSBの"B"はビットではなくバイトを意味します。
まず、この定義がF34x_MSD_USB_Main.hとF34x_MSD_USB_Descriptor.hのそれぞれにありますので、両方とも0と1を反転します。
次に、"Compiler Specific"と書かれている6箇所の内、変更が必要な次の2箇所を書き換えます。

(1)F34x_MSD_USB_ISR.cの193行目から、以下の3行をコメントアウト

// Setup.wValue.i = Setup.wValue .c[MSB] + 256*Setup.wValue.c[LSB];
// Setup.wIndex.i = Setup.wIndex .c[MSB] + 256*Setup.wIndex.c[LSB];
// Setup.wLength.i = Setup.wLength.c[MSB] + 256*Setup.wLength.c[LSB];

(2)F34x_MSD_USB_Std_Req.cの344行目、

Data_Size = Config_Desc.wTotalLength.c[MSB] + 256*Config_Desc.wTotalLength.c[LSB];

を以下に修正。

Data_Size = Config_Desc.wTotalLength.i;

最後に、多バイト定数のエンディアンを逆にします。
修正箇所は多くありません。全部で5、6箇所だったと思います。

--

》コマンド・ライン・オプション

オリジナルのKeil版では以下のようになっていました。
○コンパイラ:PW(80) SB LC OT(9,Size) CD DB OE DF(__F340_VER__) Large
○アセンブラ:XR GEN DB EP NOMOD51
○リンカ:RS(256) PL(68) PW(78) IX

移植版では次のようにしました。
○コンパイラ:-c --debug --stack-auto --nooverlay --model-small --use-stdout -V -I"c:\program files\sdcc\include" -D__SDCC__ -D__F340_VER__ --iram-size 0x0100 --xram-size 0x0400 --code-size 0xfc00
○アセンブラ:-plosgff
○リンカ:--debug --use-stdout -V --stack-auto --model-small --xram-loc 0x0800
マニュアルには明確な記述がありませんが、リンカにも--model-smallとともに--stack-autoを渡す必要があるようです。

--

》intrinsライブラリ

Keilにはintrinsというライブラリが用意されています。
マニュアルによると_crol_、_cror_、_irol_、_iror_、_lrol_、_lror_、_nop_、_testbit_の8つが定義されているようです。
一般の関数は常にcall命令(acall、lcall)で呼び出されるのに対し、これらの関数は、呼び出した箇所にインライン展開されることが特徴です。

USB-MSD-RDではこのうち_nop_を使っていますが、SDCCには対応するライブラリがありません。
そこで、インラインでnopを生成する関数を作成します。引数や戻り値が無いので簡単です。

--

》getkey関数

KeilにはUARTの受信データを返すgetkey関数が用意されていますが、SDCCには無いようなので用意します。
処理は受信フラグのチェックとデータの読み出し、エラー時の処理だけですので数行です。


大きな変更点は取りあえず挙げたつもりです。
1週間くらいの試行錯誤でしたが、こうしてみると結構ありますね。
でもまだ十分でないので、もう少し書き足したいと思います。

本当はもっと網羅的にまとめられればいいのですが、そのためにはまず
両者の和文マニュアル抄を書く必要があるような気がします。


Rev5.0のページへ戻る

ページの先頭へ戻る

HOME