組み込みでもC++と言う事で、最近では、RXマイコンでC++0Xを使ってソフト開発をしていますが・・
ルネサスなどのサンプルでは、I/Oデバイスの操作は、C言語で使えるビットフィールドを多用しています。
サンプルに付属のハードウェアーデバイスを定義してあるヘッダー「iodefine_renesas.h」がその典型例です、しかしながら、規約では、16ビットや32ビットの定義は処理系依存であったと思います。
又、エンディアンが変わった場合も少なくと gcc では問題あります。
それなのに、このやり方が使われているのは大きな問題だと感じています。
ルネサスが試用版としてダウンロード出来るコンパイラでは問題無いようですが、gcc では問題となります。
※やった事は無いのですが、ルネサスのCコンパイラで boost が正しくコンパイルできるか不明ですし、C++11を使いたいし、IDEは嫌いだしで、積極的に使いたいとは思えません。
※時間が出来たら、RX用のLLVMをポートしたいです。
特に gcc では、最適化が施された場合に、32ビットでしかアクセス出来ないI/O空間に、バイトでアクセスするようなコードが出てくる場合もあります。
※これはRX用 gcc の問題なのかもしれません。
又、C++の強みを生かしたコーディングをしたいのもあります、今回RXマイコン用のコードを作る過程で、C++でI/Oデバイスを操作するライブラリーを実験、考えてみました。
自分のC++スキル以内の事しか出来ませんが、とりあえず、我慢できる程度の物が出来たので紹介します。
まず、どんな感じで書きたいか考えます。
RXマイコンに備わっているI/Oポートを例に考えてみます。
device::PORT2::DDR.B3 = 1; // PORT23 output (/xCS) device::PORT2::DDR.B2 = 1; // PORT22 output (/xDCS/BSYNC) device::PORT4::DDR.B7 = 0; // PORT47 input (DREQ) device::PORT4::ICR.B7 = 1; device::PORT2::DR.B3 = 1; // xCS=H device::PORT2::DR.B2 = 1; // xDCS=H
とりあえず、こんな感じでしょうか?
※名前空間は「device」としています。
template <uint32_t base> struct portx_t { typedef io8<base + 0x00> ddr_io; struct ddr_t : public ddr_io { using ddr_io::operator =; using ddr_io::operator (); using ddr_io::operator |=; using ddr_io::operator &=; bits_t<ddr_io, 0, 1> B0; bits_t<ddr_io, 1, 1> B1; bits_t<ddr_io, 2, 1> B2; bits_t<ddr_io, 3, 1> B3; bits_t<ddr_io, 4, 1> B4; bits_t<ddr_io, 5, 1> B5; bits_t<ddr_io, 6, 1> B6; bits_t<ddr_io, 7, 1> B7; }; static ddr_t DDR; }; typedef portx_t<0x0008c000> PORT0; typedef portx_t<0x0008c001> PORT1;
オペレーターの定義として、「=」、「()」、「&=」、「|=」を定義してあります。
「=」オペレーターの戻り値をvoidとして値を返さないようにしてあります。
PORT0::DDR = 0xf0;
と代入は出来ますが・・・
uint8_t data = PORT0::DDR;
とは出来ません、代わりに、
uint8_t data = PORT0::DDR();
とする事で値を取得できます。
これには、幾つかの側面があって、I/Oに対する「読み出し」と「書き込み」は同じアドレスであっても全く別の操作だからです。
又、最適化によって起こる問題を回避できると考えています。
※又、「=」オペレーターを無効にする事により、リードオンリーのデバイスを扱えます。(書き込もうとするとコンパイルエラーになります)
さて、バイト操作が出来たので、ビット操作を考えてみます。
※ bits_t の定義がそれに辺り、アクセス開始ビットと、アクセスするビット幅を定義してあります。
template <class T, uint8_t pos, uint8_t len> struct bits_t { static typename T::value_type get() { return (T::read() >> pos) & ((1 << len) - 1); } static void set(typename T::value_type v) { typename T::value_type m = ((1 << static_cast(len)) - 1) << pos; T::write((T::read() & ~m) | (v << pos)); } void operator = (typename T::value_type v) { set(v); } typename T::value_type operator () () { return get(); } };
テンプレートに渡すT型のクラスは、8ビット、16ビット、32ビットのアクセス幅により決定します。
typedef io8<base + 0x00> ddr_io; bits_t<ddr_io, 0, 1> B0;
※上の例では、io8 クラスを使っています。
io8 はこんな感じです。
template <uint32_t adr> struct io8 { typedef uint8_t value_type; static void write(uint8_t data) { wr8_(adr, data); } static uint8_t read() { return rd8_(adr); }void operator = (value_type data) const { write(data); } value_type operator () () const { return read(); } void operator |= (value_type data) const { write(read() | data); } void operator &= (value_type data) const { write(read() & data); } };
実際にハードウェアー上のアドレスにアクセスするのは、以下です。
static inline void wr8_(uint32_t adr, uint8_t data) { *reinterpret_cast<volatile uint8_t*>(adr) = data; } static inline uint8_t rd8_(uint32_t adr) { return *reinterpret_cast<volatile uint8_t*>(adr); }
まだ冗長な部分がありますが、とりあえずやりたい事は出来ています。
※完全なソースコードは、AKI-RX62 でMP3再生(VS1063a)を参照して下さい。
※参考文献:
C++テンプレートテクニック
追記、修正: 2013,7,21
operator は継承出来ないので、冗長となっていた部分、using 文で、シンプルに書ける事が判った為修正。
※using ってこんな感じで使えるのか・・
※RX のソースアーカイブは、別の作業(RSPIの割り込み化)をしているので、それが終わってから更新します。
追記、修正: 2013,7,25
コンパイルした場合にどのようなアセンブリコードが出てくるか検証しました。
int main(int argc, char** argv) { // ICLK=96MHz, BCLK=48MHz, PCLK=48MHz SDCLK出力,BCLK出力 device::SYSTEM::SCKCR = 0x00010100; device::SYSTEM::MSTPCRA.MSTPA15 = 0; // B15 (CMT)のストップ状態解除 device::SYSTEM::MSTPCRB.MSTPB31 = 0; // B31 (SCI0)のストップ状態解除 device::PORT3::DDR.B0 = 1; // PORT3:B0 output device::PORT2::ICR.B1 = 1; // PORT2:B1 input (RXD0) }
-O2 で最適化した場合のアセンブリコードです。
fff80a64 <_main>: fff80a64: fb ee 20 00 08 mov.l #0x80020, r14 fff80a69: f8 ee 00 01 01 mov.l #0x10100, [r14] fff80a6e: fb 4e 10 00 08 mov.l #0x80010, r4 fff80a73: ec 4e mov.l [r4], r14 fff80a75: fb 3e 14 00 08 mov.l #0x80014, r3 fff80a7a: 77 2e ff 7f ff and #0xffff7fff, r14 fff80a7f: e3 4e mov.l r14, [r4] fff80a81: ec 3e mov.l [r3], r14 fff80a83: fb 4e 03 c0 08 mov.l #0x8c003, r4 fff80a88: 74 2e ff ff ff 7f and #0x7fffffff, r14 fff80a8e: e3 3e mov.l r14, [r3] fff80a90: cc 43 mov.b [r4], r3 fff80a92: fb ee 62 c0 08 mov.l #0x8c062, r14 fff80a97: 65 13 or #1, r3 fff80a99: c3 43 mov.b r3, [r4] fff80a9b: cc e4 mov.b [r14], r4 fff80a9d: 66 01 mov.l #0, r1 fff80a9f: 65 24 or #2, r4 fff80aa1: c3 e4 mov.b r4, [r14] fff80aa3: 02 rts
及第点でしょうか・・