SSブログ
English Version

CH32V203で遊ぶ(その6)クロックアップ [CH32V]

 前回の記事で CH32V203 に TFT 液晶 MSP2807(320 x 240 dot)を接続した環境でのライン描画の高速化から端を発して新たな高速ライン描画法を提案しました。
 今回はレジスタ直書込みによる高速化と CH32V203 のクロックアップによる変貌ぶりについて記録して置きたいと思います。


1.はじめに
 以前から認識はしていたのですが、CH32V203 は今回使用している開発環境(MounRiver Studio)のディフォルト設定では外部クロックを使用する設定になっていて、今回のように外部クロックを接続していない状態では内部クロックを使って 8MHz で動作します。
 言わばこの大リングボール養成ギブスを付けた状態で今までグラフィック描画の高速化を試みてきました。今回はレジスタの直たたきによる高速化を行った後に、クロックアップにより羊の皮を脱ぎ捨てた CH32C203 の真の実力について記載します。


2.レジスタ直接制御による高速化
 サンプルプログラムの記述に従ってC言語で開発するとペリフェラルのステータス確認の度にサブルーチンコールし、サブルーチンの中では対応ビットをANDチェック後の状態(ゼロか否か)により、リターン値を設定するようなまどろっこしい処理になってしまいます。特にライン描画のような繰り返し回数が多い処理ではなるべくステップ数の少ない実装にしたいものです。
 そこでステータスの確認とデータの書き込みをレジスタ直アクセスにしてみました。レジスタのアドレスやステータスのビットの定義についてはディバッガでトレースすれば容易に確認でき、これらは ch32v20x.h や ch32v20x_spi.h のファイル内で定義されています。
 参考として SPI1 関連のレジスタとステータスレジスタのビット構成を下表に示します。

SPI1 関連レジスタ

SPI1 ステータス


 下図がレジスタ直制御対応前と対応後のロジアナ波形です。SPI のデータ出力の間隔が短くなり、高速化できていることが判ります。末尾の「4.まとめ」に記載しているようにレジスタを直接制御することで前回の記事に掲載した 512 個の線分を描画するテストプログラムの実行時間は従来の処理から 16% 程速くなりました。

レジスタ直制御対応前のSPI波形

レジスタ直制御対応後のSPI波形



3.クロックアップ
 下図は CH32V203 のクロック系の構成図で、これを見ると HSI(内部クロック)の 8MHz を PLL で18倍まで逓倍したものを SYSCLK にすることができます。

CH32V203 のクロック構成


 今まではディフォルト設定で SYSCLK は 8MHz で、言わばこの大リーグボール養成ギブスを付けた状態で色々高速化を試みてきました。高速化の対処も一通りやった感があるので、この辺で CH32V203 の羊の皮を取り去り本来の実力を見てみたいと思います。
 クロックの設定は system_ch32v20x.c の下記の部分の define 設定を変更することで簡単にできました。

system_ch32v20x.c
/* 
* Uncomment the line corresponding to the desired System clock (SYSCLK) frequency (after 
* reset the HSI is used as SYSCLK source).
* If none of the define below is enabled, the HSI is used as System clock source. 
*/
//#define SYSCLK_FREQ_HSE    HSE_VALUE
//#define SYSCLK_FREQ_48MHz_HSE  48000000
//#define SYSCLK_FREQ_56MHz_HSE  56000000
//#define SYSCLK_FREQ_72MHz_HSE  72000000
/////#define SYSCLK_FREQ_96MHz_HSE  96000000
//#define SYSCLK_FREQ_120MHz_HSE  120000000
//#define SYSCLK_FREQ_144MHz_HSE  144000000
//#define SYSCLK_FREQ_HSI    HSI_VALUE
//#define SYSCLK_FREQ_48MHz_HSI  48000000
//#define SYSCLK_FREQ_56MHz_HSI  56000000
//#define SYSCLK_FREQ_72MHz_HSI  72000000
//#define SYSCLK_FREQ_96MHz_HSI  96000000
//#define SYSCLK_FREQ_120MHz_HSI  120000000
#define SYSCLK_FREQ_144MHz_HSI  144000000


 sysclk が従来の 8MHz から一挙に 144MHz にアップし、SPI のプリスケーラーは2に設定しているので SPI のクロックも 4MHz から 72MHz に変わることになります。流石に SPI のプリスケーラーの設定は変更が必要と思っていましたが、なんとそのままで動作しました。
 こうなると私の安いロジアナ(サンプリングの上限が100MSa/s)では波形を確認できません。最高サンプリング 1GSa/s のオシロで観測した SPI の CLK(水色)とデータ(黄色)が下図です。プローブが安物のせいか波形が綺麗には見えませんが、カーソルで示しているように確かに 72MHz 程度のクロックが出ているようです。ブレッドボード環境でこの周波数で動作していることもすごいですね。

SPI CLK(水色) と MOSI(黄色)



4.まとめ
 数回に渡り、高速化検討をしてきましたが、高速化を一通りやった感があるので今回はクロックを 8MHZ から一挙に上限の 144MHz にアップし、CH32V203 の羊の皮を脱がせて真の実力を見てみたところグラフィック描画はとんでもない速さになりました^^
 120円の CPU でこの速さはとてつもないと思います。接続した TFT 液晶 MSP2807 もすごいですね。

 最後に今回の高速化対応での線分描画時間の測定結果を下表に示します。

Nomethodtime[s]speed ratio
1Before13.1091.00
2Register direct11.3041.16
3Clockup(144MHz)0.62620.94


 X(旧Twitter)に投稿したメッセージに添付した動画を貼っておきます。




[TOP] [ 前へ ] 連載記事一覧 [ 次へ ]
nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー

CH32V203で遊ぶ(その5)高速なライン描画法 [CH32V]

 前回の記事では DMA での高速転送機能によって TFT液晶 MSP2807(320 x 240 dot)での塗りつぶし処理を高速化したことについて書きました。この時のデモ表示を見ているとラインの描画が目で見えるほど遅いので何とか高速化できないものか考えてみました。
 ディバッガで追ってみると速度のネックはライン描画アルゴリズムよりもドット描画自体にあり、SPI や PIO での I/O 制御部がC言語での標準な制御方法ではまどろっこしい程の処理なのでレジスタ直書きやアセンブラ化すればかなり速くなりそうです。
 今回はライン描画処理の高速化を検討する中で、上記のような I/O 制御自体の高速化では無く、ライン描画アルゴリズム自体の高速化を思い付いた(以降、傾き予測法(Slope prediction method)と記す)ので記録しておきたいと思います。
 尚、後で気が付きましたが「ポケコン(G850)高速ライン描画」の記事でも引用していた「ラインルーチンの高速化の手法」のページにライン描画の高速化について判り易くまとめられていて、ここにダブルステップの手法が書かれていますが評価結果として高速化はできなかったとの結論でした。今回提案する傾き予測法は本ページの末尾にまとめたように実際に高速化への効果が確かめられた描画法です。


1.今回のお品書き
 ライン描画手法について下記の内容を記載します。
  1. 標準的なライン描画処理
  2. 両端からの同時描画
  3. 今回提案の傾き予測法
最後にまとめとして上記手法の評価結果を記載します。


2.高速ライン描画手法
 以降にライン描画の各手法について記載します。


 2-1.標準的なライン描画処理
 高速化のために整数だけの処理でラインを描画する手法でブレゼンハムのアルゴリズムとして広く知られています。
 下記のソースはネット上のソースを参照せずに独自に起こしたものですが、ブレゼンハムのアルゴリズムと同等の処理になっています。
 高速化の観点からループ内での条件分岐を最小限にするために傾きの大きさにより場合分けし、二つのループ処理に分割しています。PSET は1ピクセルを描画する外部処理です。

標準的なライン描画処理(C言語)
void LineStd( int16_t xst, int16_t yst, int16_t xen, int16_t yen ) { int16_t x,y,difx,dify; int16_t err, xtail, ytail; LcdXPos = xen; LcdYPos = yen; difx = abs( xen - xst ) << 1; dify = abs( yen - yst ) << 1; if( ( (difx >= dify) && (xen > xst) ) || ( (difx < dify) && (yen > yst) ) ) { x = xst; y = yst; xtail = xen; ytail = yen; } else { x = xen; y = yen; xtail = xst; ytail = yst; } if( difx >= dify ) { int16_t dy; dy = ytail > y ? 1 : -1; err = difx >> 1; for( ; x <= xtail; x++ ) { PSET( x, y ); err += dify; if( err >= difx ) { err -= difx; y += dy; } } } else { int16_t dx; dx = xtail > x ? 1 : -1; err = dify >> 1; for( ; y <= ytail; y++ ) { PSET( x, y ); err += difx; if( err >= dify ) { err -= dify; x += dx; } } } }
・2024/08/16 中心部のポイントが乱れる場合がある問題に対処



 2-2.両端からの同時描画
 上記の改良版でラインが中心点から見て点対称なので始点側と同時に終点側も描画することでループ回数を1/2にして高速化しています。ループ終了後の PSET は始点~終点間の距離が偶数の場合、中心のポイントの描画が欠落してしまうことに対する対処です。

両端からの同時描画(C言語)
void LineBoth( int16_t xst, int16_t yst, int16_t xen, int16_t yen ) { int16_t x,y,difx,dify; int16_t err, xtail, ytail; LcdXPos = xen; LcdYPos = yen; difx = abs( xen - xst ) << 1; dify = abs( yen - yst ) << 1; if( ( (difx >= dify) && (xen > xst) ) || ( (difx < dify) && (yen > yst) ) ) { x = xst; y = yst; xtail = xen; ytail = yen; } else { x = xen; y = yen; xtail = xst; ytail = yst; } if( difx >= dify ) { int16_t dy; dy = ytail > y ? 1 : -1; err = difx >> 1; for( ; x < xtail; x++, xtail-- ) { PSET( x, y ); PSET( xtail, ytail ); err += dify; if( err >= difx ) { err -= difx; y += dy; ytail -= dy; } } if( x == xtail ) { PSET( x, y ); } } else { int16_t dx; dx = xtail > x ? 1 : -1; err = dify >> 1; for( ; y < ytail; y++, ytail-- ) { PSET( x, y ); PSET( xtail, ytail ); err += difx; if( err >= dify ) { err -= dify; x += dx; xtail -= dx; } } if( y == ytail ) { PSET( x, y ); } } }
・2024/08/16 中心部のポイントが乱れる場合がある問題に対処



 2-3.今回提案の傾き予測法
 上記の描画では傾きが1(45°)以下の場合と1より大きい場合の二通りに場合分けして処理しましたが、傾き予測法では傾きの大きさによりループ処理を更に2分割します。
 下図に示すように傾きの大きさと移動方向の条件により、次の移動方向を決定できるケースがあり、これを利用して高速化しています。前述の両端からの描画手法も併用します。

  • 傾きが 0 ~ 0.5 の場合
     縦移動/横移動 は1/2以下なので下図の上側の緑色の■のように縦移動直後は必ず横移動になります。

  • 傾きが 0.5 ~ 1.0 の場合
     縦移動/横移動 は1/2以上なので下図の下側の緑色の■のように横移動直後は必ず縦移動になります。

傾き予測法での予測方法


 例えば、上記の「標準的なライン描画処理」において単純にループ内で2ドット分の進みを処理すれば、ループ回数が 1/2 になるので for ループのための条件ジャンプの比率が1/2になり効果は少ないですが若干高速化します(ループ処理の展開による高速化)。逆に言うと1回のループ処理で多くのドットを処理してもそれに比例して条件分岐が増えれば高速化できません(当然処理量が比例して多くなっても高速化できません)。従って高速化するためには上図のようにループ内の条件ジャンプの増加を抑えつつ、より多くのドットを処理するようにする必要があります。

傾き予測法によるライン描画処理(C言語)
// draw line Ver 0.02b void LcdLine( int16_t xst, int16_t yst, int16_t xen, int16_t yen ) { int16_t x,y,difx,dify,until; int16_t err, xtail, ytail; LcdXPos = xen; LcdYPos = yen; difx = abs( xen - xst ) << 1; dify = abs( yen - yst ) << 1; if( ( (difx >= dify) && (xen > xst) ) || ( (difx < dify) && (yen > yst) ) ) { x = xst; y = yst; xtail = xen; ytail = yen; } else { x = xen; y = yen; xtail = xst; ytail = yst; } if( difx >= dify ) { int16_t dy; dy = ytail > y ? 1 : -1; err = difx >> 1; until = x + (difx>>1) + ( (difx&1) ? 0 : -1); if( difx >= (dify<<1) ) { // slope:[0,0.5] for( ; x < until; x++, xtail-- ) { PSET( x, y ); PSET( xtail, ytail ); err += dify; if( err >= difx ) { err += dify - difx; x++; xtail--; y += dy; ytail -= dy; PSET( x, y ); PSET( xtail, ytail ); } } } else { // slope:(0.5,1] for( ; x < until; x++, xtail-- ) { PSET( x, y ); PSET( xtail, ytail ); err += dify; if( err >= difx ) { err -= difx; y += dy; ytail -= dy; } else { x++; xtail--; PSET( x, y ); PSET( xtail, ytail ); err += dify - difx; y += dy; ytail -= dy; } } } for( ; x <= xtail; x++ ) { PSET( x, y ); err += dify; if( err >= difx ) { err -= difx; y += dy; } } } else { int16_t dx; dx = xtail > x ? 1 : -1; err = dify >> 1; until = y + (dify>>1) + ( (dify&1) ? 0 : -1); if( dify >= (difx<<1) ) { for( ; y < until; y++, ytail-- ) { PSET( x, y ); PSET( xtail, ytail ); err += difx; if( err >= dify ) { err += difx - dify; x += dx; xtail -= dx; y++; ytail--; PSET( x, y ); PSET( xtail, ytail ); } } } else { for( ; y < until; y++, ytail-- ) { PSET( x, y ); PSET( xtail, ytail ); err += difx; if( err >= dify ) { err -= dify; x += dx; xtail -= dx; } else { y++; ytail--; PSET( x, y ); PSET( xtail, ytail ); err += difx - dify; x += dx; xtail -= dx; } } } for( ; y <= ytail; y++ ) { PSET( x, y ); err += difx; if( err >= dify ) { err -= dify; x += dx; } } } }
・Ver 0.02 2024/08/03 中心部のポイントが描画されない場合がある問題に対処し、下記の実行時間も再測定
・Ver 0.02a 2024/08/16 中心部のポイントが乱れる場合がある問題に対処
・Ver 0.02b 2024/09/04 傾きが0.5以上で線が太くなる問題に対処


★追記 2024/08/06 {
 上記は速度優先で同様な処理が2カ所にありましたが、速度低下を最小限にしてコードを短くしたバージョンを追記します。

傾き予測法によるライン描画処理[shorter code ver](C言語)
// draw line Ver 0.03b void LcdLine( int16_t xst, int16_t yst, int16_t xen, int16_t yen ) { int16_t x,y,difx,dify,until; int16_t err, xtail, ytail,dy; int16_t *px,*py,*pxtail, *pytail; LcdXPos = xen; LcdYPos = yen; difx = abs( xen - xst ) << 1; dify = abs( yen - yst ) << 1; if( ( (difx >= dify) && (xen > xst) ) || ( (difx < dify) && (yen > yst) ) ) { x = xst; y = yst; xtail = xen; ytail = yen; } else { x = xen; y = yen; xtail = xst; ytail = yst; } if( difx >= dify ) { px = &x; py = &y; pxtail = &xtail; pytail = &ytail; } else { int16_t tmp; px = &y; py = &x; pxtail = &ytail; pytail = &xtail; tmp = difx; difx = dify; dify = tmp; } dy = *pytail > *py ? 1 : -1; err = difx >> 1; until = *px + (difx>>1) + ( (difx&1) ? 0 : -1); if( difx >= (dify<<1) ) { // slope:[0,0.5] for( ; *px < until; (*px)++, (*pxtail)-- ) { PSET( x, y ); PSET( xtail, ytail ); err += dify; if( err >= difx ) { err += dify - difx; (*px)++; (*pxtail)--; *py += dy; *pytail -= dy; PSET( x, y ); PSET( xtail, ytail ); } } } else { // slope:(0.5,1] for( ; *px < until; (*px)++, (*pxtail)-- ) { PSET( x, y ); PSET( xtail, ytail ); err += dify; if( err >= difx ) { err -= difx; *py += dy; *pytail -= dy; } else { (*px)++; (*pxtail)--; PSET( x, y ); PSET( xtail, ytail ); err += dify - difx; *py += dy; *pytail -= dy; } } } for( ; *px <= *pxtail; (*px)++ ) { PSET( x, y ); err += dify; if( err >= difx ) { err -= difx; *py += dy; } } }
・Ver 0.03a 2024/08/16 中心部のポイントが乱れる場合がある問題に対処
・Ver 0.03b 2024/09/04 傾きが0.5以上で線が太くなる問題に対処
}


3.各ライン描画法の評価
 上述のライン描画法の評価環境として、前回の記事でも使用した CH32V203(8MHz動作)に TFT液晶 MSP2807(320 x 240 dot)を接続した環境を使いました。評価方法としては事前に乱数で生成した 512 本のラインデータに従ってライン描画処理を実行した際の時間を計測しました。時間計測は systick タイマの内部カウンタだけでも計測できそうでしたが、今回は systick を使った 1ms 毎の割込み処理でインクリメントするカウンタ値で時間を計測しています。コンパイル時のオプティマイズはディフォルトのままの -Os(サイズ優先)です。

 下図は評価時の LCD 表示です。

ライン描画法評価時の LCD 画面


 計測結果を下表に示します。

No.methodtime[s]
1標準的なライン描画13.185
2両端からの同時描画13.121
3今回提案の傾き予測法13.109


 上記の表では殆ど時間差が無いように見えますが、冒頭で書いたようにドット描画処理(PSET)が重いので PSET を何もしないダミー関数にし、ライン描画アルゴリズムの処理自体の時間を計測し直した結果が下表です。

No.methodtime[s]speed ratio
1標準的なライン描画0.2001.00
2両端からの同時描画0.1241.61
3今回提案の傾き予測法0.1121.79


 期待通り、今回提案の傾き予測法は標準的なライン描画より約 1.8 倍速いという結果になりました。


[TOP] [ 前へ ] 連載記事一覧 [ 次へ ]
nice!(0)  コメント(0) 
共通テーマ:趣味・カルチャー