見出し画像

CH552の高速Lチカ

CH552の処理速度を上限を見積もるために、ハードウエアに近いところを直接いじる方法でLチカをやってみました。いわゆる「高速Lチカ」。ArduinoIDEを使う前提で。

まずは普通のLチカ

void setup() {
  pinMode(10, OUTPUT);
}

void loop() {
  digitalWrite(10, 1);
  digitalWrite(10, 0);
}

ArduinoIDEサンプル通りのなんのヒネリもないLチカです。(出力ピンはP1.0を使っています)

普通のLチカ(108.5kHz)

実は普通のArduinoもそうですが、digitalWrite()には汎用化のためにいろいろな処理が書いてあって、デジタル出力の核心のGPIOポートのレジスタ書き込み以外にいろいろな処理があります。(興味のある方はch55xduinoのソースのdigitalWrite()のところを読んでみてください)そのため、Lチカの周波数は108.5kHzと低くなってしまいます。CH552の動作クロックが24MHzですから、24MHz/108.5kHz=221と、1周期に221クロックもかかっていることになります。

GPIOレジスタを直接操作するLチカ

digitalWrite()の代わりに、使うピンを決め打ちで直接GPIOを制御するレジスタに値を書き込んでみます。これで、digitalWrite()の汎用化の部分を省略できてだいぶ高速化できるはずです。

void setup() {
  pinMode(10, OUTPUT);
}

void loop() {
  P1 = 0x01;
  P1 = 0x00;
}

CH552のデータシートから、GPIOポートの入出力はPxレジスタへの読み書きで行うことができることがわかります。この例では、P1(ポート1)の0ビット目(2^0=0x1)を制御対象として、P1=0x01で"1"に、P1=0x00で"0"にしています。(通常は他のビットの値を保持するために論理和・論理積を使いますが、ここでは省略しています)

GPIOレジスタ操作のLチカ(540kHz)

Lチカの周期は540kHzと、さきほどの1/5になりました。

"1"の幅は124ns

"1"のパルス幅を測ると124nsでした。これは動作クロック24MHz(41.6ns)の3クロック分ですので、レジスタ代入に3クロックかかることがわかります。

無限ループを高速化

先ほどの例で、"0"の時間がずいぶん長いことに気づきます。これはloop()の実装方法からくるものです。ch55xduinoのソースのloop()の部分は、以下のようになっています。

void main(void)
{
    init();
    setup();
    for (;;) {
        loop();
        if (1) {
#ifndef USER_USB_RAM
            USBSerial_flush();
#endif
            //serialEvent();
        }
    }
}

これがmain()関数の実体で、for(;;)の無限ループ内でloop()関数を呼び出していますので、loop()に書いた処理が繰り返し行われるわけです。この書き方を見ると、loop()関数を呼び出していますが、このような関数呼び出しは機械語ではサブルーチンコールで行われますので、スタック退避などの処理時間が追加でかかります。さらにif(1)という形式上のif文がありますから、コンパイラの最適化がされなければ、ここも分岐命令が追加されます。このように、loop()に書いた処理以外にも、いろいろな処理が追加されるため、さきほどのように"0"の時間が長くなるわけです。

そこで、無限ループもloop()に書かずに、setup()内に自分で書くことで高速化を図ってみます。

void setup() {
  pinMode(10, OUTPUT);
  while(1){
    P1 = 0x01;
    P1 = 0x00;
  }
}

void loop() {
}

これだと、無限ループを形成するための無条件分岐命令だけになりますから、最小オーバーヘッドで無限ループをつくれます。この場合はLチカの周波数は2.4MHzとなり、10クロック分となりました。GPIOレジスタ書き込みがそれぞれ3クロックでしたから、無条件分岐命令が4クロックということになります。

NOP命令で時間調整

時間を微調整するのに、NOP命令を使う方法があります。NOP命令は「No Operation」、つまり何も行わない命令で、処理にかかる時間を調整するのに使うことができます。ArduinoIDEでは"__asm__()"関数で直接アセンブリ命令を書くことができます。

void setup() {
  pinMode(10, OUTPUT);
  while(1){
    P1 = 0x01;
    __asm__("nop\n"); // 40ns(24MHz)
    P1 = 0x00;
  }
}

void loop() {
}

この例では、"1"のあとに1つのNOP命令を入れています。

"1"の時間が164nsになった

その結果、"1"の時間が先ほどより40nsほど長い164nsとなりました。つまり1クロック分長くなったことになり、これがNOP命令の実行時間ということになります。あとは適宜NOPを入れる数を調整して、Lチカの周期を微調整することができます。

タイマ割り込み

以上はすべて無限ループを使うLチカでした。この方法では、他の処理を行うことができず、そのような他の処理の時間を含めてNOP調整しても、その処理の内容によって処理時間が変わる場合には、かなり面倒です。そのため、一般にはこのような繰り返し処理はタイマ割り込みで行います。CH552には3つのタイマがありますので、これらを使えば一定周期のパルスを安定につくることができます。割り込み処理は関数呼び出しと同様に追加の処理時間がかかりますので、そこが繰り返し処理の周期の下限を決めることになります。


この記事が気に入ったらサポートをしてみませんか?