DMA転送要因でMTUを指定すると、おかしな挙動をする・・
以前、DACストリームで、DMACの起動要因として、TPUを使っていた。
※分周器の設定が細かい方が良いので。
しかし、RX66T、RX72TにはTPUが無いので、どのデバイスでもあるMTUを使うように修正した。
※TMRを16ビットで使う事も考えたが、TMRは、DTCの起動には使えるが、DMACには使えない事が判り断念・・
※TMRを16ビットで使い、インターバルタイマーとして使う実装は行った。
まず、MTUで、インターバルタイマーだけ機能するような実装を行い、RX72Nで実験した。
ちゃんと動作する事を確認。
次に、PSG音楽再生を行おうと、RX64Mで実験・・、動作しない・・・
かなり時間を使い、原因が「power_mgr クラス」でMTUを定義していない事が原因だった・・・
そこで、power_mgr クラスを全デバイスで全て保守。
TPUを使うと動作する・・・
ここで、MTU関係クラスで、色々マズイ部分を修正。
色々修正したが、正常に動作しない・・・
良く調べると、DACストリームでは、MTUのインターバル設定を初期化時に、二度呼ぶような仕様だった。
RX64MのMTU割り込みは選択型Bとなっている、二度呼ぶと、選択型テーブルにMTUの割り込み要因が二か所設定されてしまうのが原因だった。
※TPUでは、これを避けていた。
しかし、良く精査すると、選択型割り込み設定に問題がある事が判り、修正。
選択型割り込み設定をテンプレート化する。
選択型割り込み設定は、RX24T以外、全てのデバイスに存在する。
同じコードをコピーするのも、保守の点で問題なので、テンプレート化して、共有出来るように実装した。
//-----------------------------------------------------------------//
/*!
@brief 選択型割り込みA設定テンプレート
@param[in] ICU ICU クラス
@param[in] VEC_TYPE 割り込み要因型
@param[in] TASK_TYPE タスクタイプ
@param[in] sel 割り込み要因
@param[in] task 割り込みタスク
@param[in] lvl 割り込みレベル
@return 成功なら「true」
*/
//-----------------------------------------------------------------//
template <class ICU, typename VEC_TYPE, typename TASK_TYPE, uint16_t org, uint16_t end>
static typename ICU::VECTOR set_interruptSELA(VEC_TYPE sel, TASK_TYPE task, uint8_t lvl) noexcept
{
// sel 要因があれば消す。
for(uint16_t i = org; i < (end + 1); ++i) {
auto idx = static_cast<typename ICU::VECTOR>(i);
if(ICU::SLIAR[idx] == sel) {
ICU::IER.enable(idx, 0);
set_interrupt_task(nullptr, i);
ICU::IPR[idx] = 0;
ICU::SLIAR[idx] = VEC_TYPE::NONE;
ICU::IR[idx] = 0;
}
}
if(lvl == 0 || task == nullptr) return ICU::VECTOR::NONE;
for(uint16_t i = org; i < (end + 1); ++i) {
auto idx = static_cast<typename ICU::VECTOR>(i);
if(ICU::SLIAR[idx] == VEC_TYPE::NONE) {
ICU::IER.enable(idx, 0);
set_interrupt_task(task, i);
ICU::IPR[idx] = lvl;
ICU::SLIAR[idx] = sel;
ICU::IR[idx] = 0;
ICU::IER.enable(idx, 1);
return idx;
}
}
return ICU::VECTOR::NONE;
}
呼び出し側:
//-----------------------------------------------------------------//
/*!
@brief 割り込み設定(選択Aベクター)
@param[in] sel 割り込み要因
@param[in] task 割り込みタスク
@param[in] lvl 割り込みレベル(0の場合、割り込み禁止)
@return 成功なら「true」
*/
//-----------------------------------------------------------------//
static ICU::VECTOR set_interrupt(ICU::VECTOR_SELA sel, utils::TASK task, uint8_t lvl) noexcept
{
return icu_utils::set_interruptSELA<ICU, ICU::VECTOR_SELA, utils::TASK, 208, 255>(sel, task, lvl);
}
※、テンプレートの実装は、イマイチの部分があるのだが、自分のスキルにとって何とか理解出来る範疇に収めてある。
それにしても、テンプレートは難しくて面白い、でも、余裕のある時にしか出来ないw
C言語を使い続ける理由はあるのか?
今回の件とは直接関係無いが、とある掲示板で、RXマイコンのUART(SCI)の設定に関しての質問があった・・
IDEの機能で、ソースコードを出力するもので、そのAPIを使う為の事柄のようだー
そこで、思ったのは、シリアル通信を行うだけなのに、多くの手順を行い、ドキュメントを詳細に読む必要があるのだった・・
プログラム生成で思い付く問題点:
- プログラムで生成する事で、自分が一部のコードを改修したら再度生成した時にマージが難しくなる。
- 後に保守する場合に備えて、生成したプログラムのバージョンや手順を全て記録しておく必要がある。
- シリアルのチャネルを変更したら、関数のプロトタイプが変わり、多くの変更を余儀なくされる。
- 生成プログラムのコードはブラックボックスで公開されていない為、何かの不具合に即座に対応出来ない。
- プログラム生成が初めての人に対して、簡単とは言えない。
自分の C++ フレームワークでは、シリアル通信の手順は、これ以上簡単に出来ないくらいにはしてある。
※プログラムで生成しないので、自由に変えられ、コンパイルをやり直すだけ。
※このような柔軟性は、C 言語で実装するのは難しいと思える。
- 利用するシリアルチャネルを決める。
- 利用するシリアルポートを決める。
- 受信、送信バッファのサイズを決める
- ボーレート、シリアル通信フォーマットを決める。
- 割り込みレベルを決定する。(ポーリングでも機能する)
typedef utils::fixed_fifo<char, 512> RXB; // RX (受信) バッファの定義
typedef utils::fixed_fifo<char, 256> TXB; // TX (送信) バッファの定義
typedef device::sci_io<SCI_CH, RXB, TXB> SCI;
// SCI ポートの第二候補を選択する場合
// typedef device::sci_io<SCI_CH, RXB, TXB, device::port_map::ORDER::SECOND> SCI;
SCI sci_;
- SCI_CH には利用するシリアルチャネルをtypedefする。(直で書いても構わない)
typedef device::SCI2 SCI_CH;
利用するポートは、port_map クラスにより決定され、候補を選択出来るようになっている。
たとえば、候補2を使う場合:
typedef device::sci_io<SCI_CH, RXB, TXB, device::port_map::ORDER::SECOND> SCI;
とする。
候補と、シリアルポートの関係は、port_map を観れば、判るようになっている。
あとは、開始するだけ:
{ // SCI の開始
uint8_t intr = 2; // 割り込みレベル(0を指定すると、ポーリング動作になる)
uint32_t baud = 115200; // ボーレート(任意の整数値を指定可能)
sci_.start(baud, intr); // 標準では、8ビット、1ストップビットを選択
// 通信プロトコルを設定する場合は、通信プロトコルのタイプを指定する事が出来る。
// sci_io.hpp PROTOCOL enum class のタイプを参照
// sci_.start(baud, intr, SCI::PROTOCOL::B8_E_1S);
}
これで、文字を、送ったり、受けたりが簡単に出来る。
A を送る:
sci_.putch('A');
文字を受け取る:
auto ch = sci_.getch();
printf で使いたければ、C の関数として、文字の入出力を extern 宣言する。
※この場合、「syscalls.c」をコンパイル、リンクする必要がある。
※ C++ では、printf を使う理由は無いので、utils::format を使う。
extern "C" {
// syscalls.c から呼ばれる、標準出力(stdout, stderr)
void sci_putch(char ch)
{
sci_.putch(ch);
}
void sci_puts(const char* str)
{
sci_.puts(str);
}
// syscalls.c から呼ばれる、標準入力(stdin)
char sci_getch(void)
{
return sci_.getch();
}
uint16_t sci_length()
{
return sci_.recv_length();
}
}
シリアルフォーマットを変更したい場合:
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
/*!
@brief SCI 通信プロトコル型
*/
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
enum class PROTOCOL {
B8_N_1S, ///< 8 ビット、No-Parity、 1 Stop Bit
B8_E_1S, ///< 8 ビット、Even(偶数)、1 Stop Bit
B8_O_1S, ///< 8 ビット、Odd (奇数)、1 Stop Bit
B8_N_2S, ///< 8 ビット、No-Parity、 2 Stop Bits
B8_E_2S, ///< 8 ビット、Even(偶数)、2 Stop Bits
B8_O_2S, ///< 8 ビット、Odd (奇数)、2 Stop Bits
};
※現在、以上のフォーマットをサポートしている(7ビットは使わないだろうから、サポートしていない)
8ビット、1ストップビット、偶数パリティの場合:
{
uint8_t intr = 2; // 割り込みレベル(0を指定すると、ポーリング動作になる)
uint32_t baud = 115200; // ボーレート(任意の整数値を指定可能)
sci_.start(baud, intr, SCI::PROTOCOL::B8_E_1S);
}
設定されたボーレートと、実際に設定されたボーレートの誤差を知りたい場合:
utils::format("SCI PCLK: %u\n") % SCI_CH::PCLK;
utils::format("SCI Baud rate (set): %u\n") % sci_.get_baud_rate();
float rate = 1.0f - static_cast<float>(sci_.get_baud_rate()) / sci_.get_baud_rate(true);
rate *= 100.0f;
utils::format("SCI Baud rate (real): %u (%3.2f [%%])\n") % sci_.get_baud_rate(true) % rate;
同じコードは、RX24T、RX64M、RX65N、RX66T、RX72T、RX72N などでおなじように使える。
途中で、SCI2 では、無く、SCI1 に変更したい場合は・・
typedef device::SCI1 SCI_CH;
とすれば良く、合わせて、ポートのマッピングを吟味する。
また、SCI1、SCI2、SCI3 などを同時に使う場合であっても、同じように定義とリソースを用意するだけとなっている。
さらに、C++ では、ソースコード(実装)とヘッダー(定義)を別ける必要性が無い為、「common/sci_io.hpp」をインクルードするだけで、
他の設定は必要無い。
※printf を使う為、syscalls.c をリンクする必要はあるが、これは、C に起因している為、省けない・・
質問者は、RX231 だったので、サポートしていないが、要望があれば、RX231 をサポートする準備はある。
※GitHub のスポンサーになる必要があるけど・・
※RX231 が載ったボードを用意して送ってもらう必要がある・・
良く言われる事が、C++ は難しいので、勧められないと言うのがある・・
しかし、多くの初心者が、Arduino を利用していると思う、自分のフレームワークでは、テンプレートを多く使っており、Arduino よりハードルは高いかもしれないが、サンプルも多くあり、真似るだけなら、そんなにハードルは高く無いと思うのだが・・・