ここ最近エフェクターの創作アイデアをゆきょんくん(@Yukyoooon)と一緒にやってるのでそれの紹介
僕が適当に構図指定するとそれを送ってくれるので非常に助かってます。
デザインは上から順に新しいのがのるはず
はやく形にしたいなぁ
毎度おなじみのARM共通の機能を使ってみようの記事です。
まあオシロスコープがある方はこういう計測方法をよくやると思います。
MCU_GPIO_WRITE(GPIO_PORT_A,GPIO_PIN_13,TRUE); /* 計測したい処理を記述 */ MCU_GPIO_WRITE(GPIO_PORT_A,GPIO_PIN_13,FALSE);
処理時間の幅をはかればある程度の処理時間を計測できます。
ですがこれだと毎回つなぐのめんどいしGPIOの立ち上がり時間が入っちゃう(特に問題にならない)
DWTははオプションのユニットで、次のようなデバッグ機能を実行します。
DWTユニットには4つのコンパレータが組み込まれており、ハードウェアウォッチポイント、ETMトリガ、PCサンプライベント トリガ、またはデータアドレスサンプラ イベントトリガとして構成することができます。最初のコンパレータDWT_COMP0は、クロックサイクル カウンタCYCCNTに対して比較することもできます。2番目のコンパレータDWT_COMP1は、データコンパレータとしても使用できます。DWTにコンパレータが1つだけ含まれるように構成し、そのコンパレータをウォッチポイントまたはトリガとして使用可能にすることもできます。コンパレータが1つしか存在しない場合、データの一致はサポートされません。
DWTには、次のものに対して使用するカウンタが組み込まれています。
- クロックサイクル(CYCCNT)
- フォールドされた命令
- ロード/ ストアユニット(LSU)操作
- スリープサイクル
- CPI(最初のサイクルを除くすべての命令サイクル)
- 割り込みオーバヘッド
※ARM社より引用
ちなみにこの機能オプションなのでメーカーによってはない場合もある。
STはデータシートの一番下のDEBUGに搭載の記述があるので使える。
今回はこれのCYCCLE(クロックサイクルカウンタ)を利用して処理時間計測を行います。
まずやらなければならないことはDWTのアクセスロックの解除です。
LARに対してアクセスキーを入力します。
まあサンプルが出ているのでそのまま使います。
#define DWT_LSR_SLK_Pos 1 #define DWT_LSR_SLK_Msk (1UL << DWT_LSR_SLK_Pos) // CoreSight Lock Status Register lock availability bit #define DWT_LSR_SLI_Pos 0 #define DWT_LSR_SLI_Msk (1UL << DWT_LSR_SLI_Pos) // CoreSight Lock Access key, common for all #define DWT_LAR_KEY 0xC5ACCE55 static inline void dwt_access_enable(int ena) { uint32_t lsr = DWT->LSR;//ロックステータスのチェック if ((lsr & DWT_LSR_SLI_Msk) != 0) { if (ena) { if ((lsr & DWT_LSR_SLK_Msk) != 0) //locked: access need unlock DWT->LAR = DWT_LAR_KEY;//ロック解除キーの入力 } else { if ((lsr & DWT_LSR_SLK_Msk) == 0) //unlocked DWT->LAR = 0; } } }
プログラムの最初にこいつを呼んでやります。
あとは以下のようにしてDWTいじってあげます。
void init_cpu_cycle(){ dwt_access_enable(1); CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; (*((volatile uint32_t *) 0xE0001004)) = 0;//DWT->CYCCLE = 0;でも可 } uint32_t get_cpu_cycle(){ uint32_t cycle = DWT->CYCCNT; return cycle; }
この二つでは主に何をやっているかというと
です。
あとはこれらで取得したサイクルを減算してクロックで除算を行う関数を定義してやると楽です。
僕の場合オーディオ信号処理なので48kHzで割り込みが起きるので処理時間最大値のうちどのぐらいの時間を使っているかで負荷計測をしています。
float get_process_time(uint32_t start,uint32_t stop){ float time = ((float)stop-(float)start)/(float)SystemCoreClock; return time*1000000;//マイクロで返したいから } float get_cpu_load(uint32_t start,uint32_t stop){ float load = (((float)stop-(float)start)/(float)SystemCoreClock) / (float)(1.0/SAMPLE_RATE); return load*100;//%で返したいので }
あとはこんな感じに埋め込む
biquadフィルタを計算させたときの動作はこんな感じ
5.6%とかなのでかなり余裕がある(ART+DSP関数を使っているので)
ARMの共通機能のDWTについて理解を深めた
時間があればほかの機能についても試していきたい
stm32 Advent Calendar 2017 16日目の記事です。
100MHz越えのマイコン使ってるけど思ったより計算速度が出ない・・・・っていう問題があって色々調べたらフラッシュの速度が原因だった。
そこでSTのARTアクセラレータの機能を使って信号処理の高速化をやってみたって話です。
あくまでSTがすごいだけで僕はその機能を使ったってだけなので悪しからずw
ART Acceleratorは図の赤の部分についてるTCMバス経由でのフラッシュの読み出しウェイトを事実上0にするキャッシュ群みたいなもの。
実際flashの速度って80MHz程度が限界なので高速動作する場合はRAMにコードを置いたりする。(LPCなんかがそう
そこで下の図のようにflashから大量に先読みしてバッファするっていう脳筋ハードウエアペリフェラル
俗にいうインストラクションプリフェッチとか言われている奴をメモリで殴った感じ
インストラクションプリフェッチにもいろいろ問題があって例えばに実行時にキャッシュ上に必要なデータがあってうまくヒットした場合はいいが何かのデータを当てに分岐命令を書いている場合必ずしもヒットしない。
この場合再リロードを行う必要がある。この際flashからの応答にコアはwaitで待つことになる。
そういうことも見越してART内にはブランチキャッシュというものを持っておりある程度の緩和を行っているものだと思う。
実際の流れでは以下のようになる。
おっとそうでした。
図に示す通りにFlashへのアクセスはTCMだけではなくAHBのバスマトリックスを経由したAXIMからでも行えます。
そもそもCortexM7にはI/Dキャッシュがあります。(0~64KBの間でベンダーが決定して搭載)
こっちを有効にすれば命令、データともにキャッシュが有効になります。
実質この機能だけでだいぶ高速になります。
このAXIMってバスはペリフェラルバスと同一なレーンにいるため渋滞を招くことも懸念されます。
そのためSTはTCIM経由でキャッシュできる構造を取ったのでしょう。
とりあえずここまででARTが何なのか?がわかったと思うので僕が感心しているDTCMメモリでも話そうと思います。
要するにCPUから最速アクセス可能な密結合メモリのことです。
場所としてはここ
クリティカルなコードやデータをここに配置することで高速化を図れる。
おき方としてはリンカーでアドレスくくって定義てそこに対して起動時にコピーすれば行ける。
その他DTCMにはGPDMAでも書けるのでCPUとペリフェラルで書き換えが頻繁なものはこのアドレスに対して書き込ませれば処理時間が少しは早くなるかもしれない。
またこの領域はキャッシュの影響を受けないのでその点でも結構使いやすい。
各種配置による速度差はIAR様のこの資料がわかりやすいです。
https://www.iar.com/globalassets/pdf/st-kits-kk/8_work_with_effective_software_development_for_cortex-m7_201504.pdf
やりかただけ書いておく。
CubeMXでCortexM7の欄を開く
このように設定する。
次にリンカスクリプトでROMの開始アドレスを0x00200000に変更する。
これで実行した時のプログラムカウンタがそれっぽい番地に来てればいいはず
ちなみに有効になってるか確認するにはFlashレジスタを確認するといいです。
ちなみにONとOFFでFIRフィルタを回した時はこんな感じだいぶ処理時間が変わってくる。
2018/11/24追記
いつの間にかCubeMXもVer5になっていますが・・・・
F4ではRCCのメニューで有効にできるみたいです。
APS様CortexM7解説
www.aps-web.jp
ねむいさんのブログ
ねむいさんのぶろぐ | STM32F7を使ってみる5 -AXIMとITCM-
CQ出版の解説ページ
www.kumikomi.net
マイナビニュース
news.mynavi.jp
ここ最近タイマーを結構使うようになってクロック源について悩んだのでメモ
僕が普段使っているSTM32F7のタイマーのクロック源としてはAPB1、APB2の二つがある。
んでCubeでクロック設定をしている僕は気にしていなかったのだけどたくさん使うようになってどれがどこからもらってきているかがわからなくなった。
そんなわけでメモを残す。
うだうだ言ったけど結構簡単でRCC関連のレジスタをみれば書いてある。
ここに書いてある通りに割り振られてるのでCubeでこの辺いじると各種タイマーがどうなるのかわかると思う。
あと注意するのがRCC_DCKCFGR1レジスタのタイマークロックプリスケーラ(タイマーのカウントプリスケーラとは別のやつ)のビットをいじった時はこのへん読む
前回まででUSBCDC(コミュニケーションデバイスクラス)でVCP(VirtualCOMPort)の動作は確認できたので今回はこれにMicroShellを使える状態していく作業になります。
CDCのやり方はこの記事みてください。
gsmcustomeffects.hatenablog.com
やることは
ここ最近ARM系のお勉強を頑張っています。
今日はWEAK修飾子についてです。
STを例に話すと割り込み後に呼ばれるコールバックってのがWEAKマクロを使って記述されてます。
定義先に行くと
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { /* Prevent unused argument(s) compilation warning */ UNUSED(htim); /* NOTE : This function Should not be modified, when the callback is needed, the __HAL_TIM_PeriodElapsedCallback could be implemented in the user file */ }
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { //i2c_lcd_writeCommand(ClearDisplay); i2c_lcd_writeCommand(ReturnHome); HAL_Delay(2); xprintf("Count:%d",count); count++; }
のように記述されている。
これをMainで呼び出すとこのように使う。
要は同じ関数が二個あったらWEAKがついてる側が譲るということである。
こうすることで関数の上書き的なことができる。これがないとmain文で定義しなければコンパイルエラーになってしまうのでこうやって回避しているわけである。
ちなみにI2C系のデバイスライブラリ作るときもI2Cの送信APIを上書きで定義できるようにすれば汎用性を持たせられるので便利
__weak void i2c_write(){ //This Section is User function }
void i2c_write(){ HAL_I2C_Master_Transmit(&hi2c1,addri2c,(uint8_t*)data,2,0xFF); }
これでi2c_lcdの制御部分を記述してます。
1文字送信コマンドが2byteなのでこういった回りくどいことしてます。