STM32F103C8T6用の高速USBライブラリの負荷試験を実施

では 前の記事、私は標準のミドルウェアライブラリとSTM32F103マイクロコントローラ用のUSBバスの最高速度を示しました。コメントでは、USB FS からすべてのジュースを絞り出す 2 つの自家製ライブラリを同時に表示されました。しかし、ライブラリの 1 つの作成者は、それらは迅速に動作し、信頼性がどの程度かは不明であるという考えを表明しました。彼は、有用なデータを使用して負荷テストを行うことが役立つと考えました。失われたり歪められたりしない限り、図書館には生存権があると言えます。







言うまでもなく、私は週末にチェックをするのが待ちきれませんでした。テスト結果を見てみましょう。さらに興味深いものにするために、プロセッサ コアを停止せずに変数を「オンザフライ」で表示するテクノロジについて検討します。そうですね、バッチ コンパイラによって収集された elf ファイルのビジュアル デバッグの技術です。



どのような試験を実施するか



テストの本質は、前の記事へのコメントで議論されました。オリジナルの PC プログラムがデータを送信します。これは重要ではありません。データ、それだけです。コントローラーはこのデータを受け入れ、安全に無視します。問題は通信速度だったからです。新しいライブラリは、理論上、一部のデータをスキップするか、バッファのペアを混同することができます。したがって、流れは時間変化する必要があります。このような場合、疑似ランダム シーケンスまたはインクリメンタル データが送信されます。



ソースと宛先 (アルゴリズムは同じでなければなりません) の疑似ランダム シーケンスの生成に多くの時間を費やしたくないので、増分 32 ビット ワードに制限しました。このアプローチの主な欠点は、4 バイトのうち最大 3 バイトが隣接パケットで一致する可能性があることです。しかし、最初のものは異なります。したがって、今日のテストでは、それは許容できるように思えます。



実際、USB プロトコルはパケットです。そして、パケット自体はハードウェア レベルでキャッチされます。ブロック内でバイトが破損し、有用なデータが再度送信されるという状況があってはなりません。少なくとも、現在ライブラリでキャッチしたい問題のために、これは発生しません。データが破損する場合は、グローバルに。古いデータが新しいデータで上書きされる場合、最初のバイトが最初に上書きされ、パケットごとに異なります。



原則として、誰もが私のコードを別のバージョンのテスト データに書き直すことができます... 今日、私は単純にインクリメントする 32 ビット ワードを送信し、それを受信したら、シーケンスを壊すことなくインクリメントが行われることを確認します。



結果をどのように追跡するか



すべてが機能することをどのように確認しますか? LEDが1つじゃ足りない。 UART を追加するには、他の人のコードをハードコーディングする必要があります。間違いを犯すことがあります。昔から知っていた機能を使ってみましょうが、それは常に Keil 開発環境でしか使用していません。今日は、Eclipse での使用方法を紹介します。コメントから前回の記事まで、誰もがこのテクノロジーについて知っているわけではないことに気づきました。



JTAG デバッグ ポートは、プロセッサ コアが停止している場合にのみ作業を許可します。これは USB では受け入れられません。そこでは、通常の操作中に、停止はタイムアウトに満ちており、私たちの場合、タイムアウトをキャッチしなくても、速度が過小評価される可能性があります。幸いなことに、SWD デバッグ ポートを使用すると、オンザフライでメモリを監視できます。 2016 年に、パルス幅で同期を設定できるオシロスコープを使用して確認しました。SWD によるメモリへのアクセスは、実質的にプロセッサ コアの速度を低下させません。しかし、それをどのように使用しますか?



今日最初に頼るのは、CubeIDE (ドープされた Eclipse である) が変数をその場で表示する機能です。プログラムが多くの有用な情報を表示する変数のグループを作成し、画面上でそれらの追跡を開始します。多くの人がこれについて知っていますが、今のところ全員がそうではありません。今すぐみんなに知らせましょう。



そして2つ目は、私と男たちが最近見つけたものです。私たちのオフィスの誰もこれを知りませんでした。バッチ コンパイラでプロジェクトをビルドし、Dwarf-2 デバッグ情報を埋め込むと、この elf ファイルを Eclipse で開いてデバッグでき、ソースへの完全なリンクを取得できます。 同時に、ソース自体をプロジェクトに接続する必要はありません。デバッガーは、デバッグ情報からそれらへのパスを自動的にプルします。今はいつもそうしています。GCC または CLang プロジェクトが構築されたら、elf を Eclipse に接続してトレースするだけです。プロジェクト自体をこの Eclipse にアタッチするのに時間を無駄にすることはありません。ときには、私のマシンにないツールチェーンによって収集された elf ファイルを送信することさえあります。Linux でコンパイルすることもできます (私は Windows で作業しています)。この方法は、プロジェクトが完全なセット (elf とそのソース) で送信されている限り、このような場合でも機能します。今日、これは、著者のプロジェクトを構造の観点から完成させないようにするのに役立ちます。「ネイティブ」のメイクファイルに基づいてすべてをビルドし、デバッガーで elf ファイルに接続します。



つながるようにトレーニングします



CubeIDE で最初に行う必要があるのは、STM32F103 用のプロジェクトです。 「ちょっと待って!」と、熱心な読者が叫びます... 「作者は、元のプロジェクトで何もする必要がないことを約束しただけです!!!」大丈夫です。これが CubeIDE の癖です。 STM32F103 のプロジェクトが必要です。どれか。主なことは、それがSTM32F103の下にあるということです。私たちはそれを作成し、収集し、忘れます。その中に何が入っているかは重要ではありません。開発環境に存在するという事実そのものが重要です。



CubeIDE では、デバッガーの設定に移動します。たとえば、次のように:







GDB Hardware Debugging 項目を選択した場合、左側のプロジェクトの作成を邪魔する必要はありません。いつものEclipseで選びます。私はここでそれを選択しようとしました:







ああ。左のプロジェクトは不要になりますが、変数をリアルタイムで表示する機能は利用できないと言われています。したがって、ああ、ああ。 STM32 Cortex-M C / C ++ アプリケーションを選択します。そこにはすでに2つの構成があります。だまされていないことを確認するために、3 つ目のものを作成します。これを行うには、ここをダブルクリックします:







構成に名前を付けます 記事:







elf ファイルへのパスを選択する必要があります:







このパスを選択しました (パスにロシア語の文字はありません):







ここでエラーが点滅します.これが赤です。







これを削除するには、STM32F103 に関連付けられているプロジェクトを選択する必要があります。ここで、Browse: にアクセスし







、以前に作成した左側のプロジェクトを選択します。







赤 (エラーのサイン) が消えました:







おっと! この図では、プロジェクトを選択すると、elf ファイルの名前が飛び跳ねていることがわかります。このプロジェクトのエルフが登録されました。プロジェクトを再度指定した後、必要なものを選択する必要がありました。私がすべてのポイントを実行したのも不思議ではありません。



ここでは収集するものがないため (すべてをバッチで収集します)、システムが無駄な作業を行わないようにボックスを







オンにする必要があり ます。このタブでは、すべてが実行されます。[デバッガー] タブに移動します。確かに、ここでは何も変更する必要はありません。少なくとも、すべてが同じように設定されている場合:







実際、他の場所を変更する必要はありません。では、デバッグを始めましょうか。







技術的には、はい。組織的に - まず、実行するコードを準備する必要があります。



最初のライブラリをチェックする



したがって、マスター eddyem / stm32samples GitHub、プロジェクトstm32samples / F1-nolib / CDC_ACM をダウンロードします。 EddyEm...



デバッグ情報 dwarf-2 の形成を makefile に追加することを忘れないでください:





テキストと同じ:

CFLAGS	+= -O2 -g -gdwarf-2 -D__thumb2__=1 -MD

      
      





コードの編集を開始します。



main()関数に無限ループがあります。私は彼から角、はい、足だけを残します。

    while (1){
        IWDG->KR = IWDG_REFRESH; // refresh watchdog
        usb_proc();
        get_USB();
    }

      
      





次のように動作する get_USB () 関数を作成します。

uint32_t loop = 0;
uint32_t errors = 0;
uint32_t errState = 0;
int32_t lastData = 0;
int32_t show = 0;
int32_t pkt = 0;

#define USBBUF 63
char tmpbuf[USBBUF+1];
int32_t* pData = (int32_t*) tmpbuf;
// usb getline
char *get_USB()
{
    int x = USB_receive((uint8_t*)tmpbuf) / sizeof(uint32_t);
    int i;

    show += 1;

    if(!x) return NULL;

    pkt += 1;
    //     -    
    //   !
    if (pData [0] == 0)
    {
         lastData = 0;
         errState = 0;
         loop += 1;
    }
    //    
    if (errState)
    {
         return NULL;
    }
    //   
    for (i=0;i<x;i++)
    {
          // !
          if (pData[i]!=lastData++)
          {
             //  !
             errState = 1;
             //   
             errors += 1;
             //     
             return NULL;
          }
    }
    //        
    return  NULL;
}

      
      





それらをリアルタイムで監視するために、一連のグローバル変数が作成されます。ローカルはすべての起動時に失われます。グローバルなものは永遠に見えます。 show



変数 は、デバッガーが実際にすべてを表示していることを示します。データの有無にかかわらず、関数に入るたびにインクリメントされます。そして、常に無限ループで関数を呼び出します。 変数pktは、データが実際に送信されていることを示します (最初は私から送信されたものではありません)。 USBから何も出なかったために終了しなかった場合にのみ増加します。 最後のデータ







テストですでに数えた数が表示されます。これにより、大きなデータ ブロックを実際に処理していることを確認できます。テスト終了時のこの変数の値は、ブロック サイズをダブル ワードで示します。経過したバイト数を理解するには、値に 4 を掛ける必要があり ます。データ ブロックが到着すると、ゼロから開始して



ループが増加します。大雑把に言えば、これが試験番号です。まあ、または実行番号。プロットのために統計を収集すると、これらの実行がかなりの数になります。要求されたブロックのさまざまなサイズに繰り返しを掛けて結果を平均化します。



エラー状態- エラーの雪だるまの出現を防ぐ補助変数。最初のエラーで、1 まで飛んで、新しいテストを開始する前にデータの分析を停止します。



errors - 一度発生したエラーのカウンター。最初は私自身がロジックを間違えて、このカウンターはどんどん増えていました。しかし、すべてが良い場合、それは増加するべきではありません。



忘れそうだった。また、USB_receive 関数のフラグのチェックをコメントアウトしました:





テキストと同じ:

uint8_t USB_receive(uint8_t *buf){
    if(/*!usbON ||*/ !rxNE) return 0;
...

      
      







このフラグは、端末が仮想 COM ポートの速度を設定するときに設定されます。一方、私のテスト プログラムは、WinUSB ドライバーを介してデバイスを直接開き、CDC 機能をカスタマイズすることは何もしません。最も簡単な方法は、このフラグを無視することです。



上手。バッチ コンパイラでプロジェクトをビルドし、CubeIDE デバッガで実行します。私が言われたように、すべての読者がアニメーションの絵を好むわけではありません。一部の人にとって、それらはテキストを読むことを妨げます。でも、これは本当にうれしいです。







それはカチカチカチカチカチカチ音!それはカチカチカチカチカチカチ音!それはカチカチカチカチカチカチ音!



上手。配列を埋めるをテスト プログラムに追加します。

    QByteArray data;
    data.resize(totalSize);

    uint32_t* dwPtr = (uint32_t*) data.constData();
    for (uint32_t i = 0;i<totalSize/4;i++)
    {
        dwPtr[i] = i;
    }

      
      





そして、テストを実行します。この種の美しさは変数 (ゼロエラー) で得られます:







そして、ここに速度のグラフがあります:







値は完全にアイドル状態の操作よりもわずかに少ないですが、それでも快適です。一般に、ST-LINK がシステムに追加されており、USB を介して実行されるビット数は、ポンピングされるデータに依存します (同期ビットが挿入される場合があります)。



球形の馬は真空で動作しますが、本物の馬はどうですか?



このシステム全体に 1 つの潜在的な問題があります。これで、データを受信する関数が無限ループで常に呼び出されます。現在、特に有用な仕事はありません。もしそうなら?次に、この関数の呼び出しは、パケットの到着直後ではなく、遅延して発生する可能性があります。



さまざまなオプションをテストする予定ですか?心に - それは必要ですが、時間がありません。私は現在、まったく別のコントローラーでの作業で忙しくしています。そして、週末を楽しみました。したがって、私たちは別の方法で行きます。データの受信を到着の事実に接着します。



これらは割り込みで行われます。しかし、前回の記事では、USB 割り込みハンドラーを拡張すると、ハードウェアが NAK の送信を開始し、問題のライブラリのすべての魅力が無意味になると述べました。どうすれば割り込みを取得できますが、割り込みにとどまりませんか?



さて、その方法はここで知られています。 USB 割り込みハンドラーでは、終了直後に割り込みもトリガーされるようにする必要がありますが、他の割り込みもトリガーされます。そこで、保証された低レイテンシーで、ハードウェア バッファーから内部バッファーにデータをすばやく取り込みます。座る中断は?起動コードを調べています。つまり、割り込みハンドラー。私たちの仕事は、未使用のものを見つけることです。



ここに私たちの関心のあるファイルがあります

\ stm32samples-master \ F1-nolib \ inc \ startup \ vector.c



3 番目の UART からの割り込みを生意気に借りさせてください。実際、最初のものも使用しません。でも、もしかしたらそのうちかもしれません。そして、私は私の人生で3番目のものを使用したことがありません.したがって、個人的には、私はこの特定のハンドラーに生意気で座ります。 :これは、それが記述されている方法です



\、[NVIC_USART3_IRQ] = usart3_isr



名前を知って、main.cのファイル内の関数を作成します。

void usart3_isr()
{
    NVIC_ClearPendingIRQ(USART3_IRQn);
    get_USB();
}

      
      





これは一種のコールバック関数になります。そして、それはすでに私たちが最近書いたコードを呼び出します。そして、無限ループでのget_USB() の呼び出しを コメントアウトしましょう



ここで、誰にも干渉しないように、この割り込みを低い優先度に設定する必要があります。実際の生活では、優先順位を選択する際に創造性が必要になる場合があります。しかし、今日は15番目だけを取り上げます。main() 関数の初期化部分に次のコードを追加します。

    NVIC_SetPriority(USART3_IRQn, 15);
    NVIC_EnableIRQ(USART3_IRQn);

      
      





さて、楽しみな部分が来ました。USB 割り込みハンドラーで、エンドポイントへの呼び出しがあった場合、USART3 割り込みをトリガーする挑発を追加します。





同じテキスト。
#include "stm32f10x.h"
void usb_lp_can_rx0_isr(){
   LED_off(LED0);
    if(USB->ISTR & USB_ISTR_RESET){
    }
    if(USB->ISTR & USB_ISTR_CTR){
        // EP number
        uint8_t n = USB->ISTR & USB_ISTR_EPID;

        if (n == 1)
        {
             NVIC_SetPendingIRQ(USART3_IRQn);
        }
        // copy status register
        uint16_t epstatus = USB->EPnR[n];
        // copy received bytes amount

      
      









優先度が低いため、USB 割り込みが終了するまで何も起こりません。しかし、それが終わるとすぐに、彼らはすぐに私たちに電話します。まだ他の割り込みがないためです。15番目の優先順位でも、私たちはVIPになります。



立ち上げます。最初はショー変数が増えていないのが怖いです。でも普通です。これで、関数は無条件に呼び出されるのではなく、実際の割り込み後にのみ呼び出されます。そのため、テストを開始する必要があります。



テストのプロセスを永久に見ることができます。







そして、ここに速度の指標があります:







2 番目のライブラリを確認する



今、私たちはチェック メインCOKPOWEHEU / USBのGitHubでUSB / 5.CDC_F1ライブラリにより、 コッポウェフ... このライブラリの説明は 、レジスタ上の USB: STM32L1 / STM32F1 / にあります。これは、エンドポイント アクティビティを処理するためのコールバック関数が提供される場所です。ここで修正します。show変数 は不要になりました。私たちは常にデータの到着時に呼び出されます。それ以外の場合は、実質的に同じコードが得られます。

uint32_t loop = 0;
uint32_t errors = 0;
uint32_t errState = 0;
int32_t lastData = 0;
int32_t pkt = 0;

void data_out_callback(uint8_t epnum){
  int i;
uint8_t buf[ ENDP_DATA_SIZE ];
int32_t* pData = (int32_t*) buf;

  int len = usb_ep_read_double( ENDP_DATA_OUT, buf) / sizeof (uint32_t);
  if(len == 0)return;

    pkt += 1;
    //     -    
    //   !
    if (pData [0] == 0)
    {
         lastData = 0;
         errState = 0;
         loop += 1;
    }
    //    
    if (errState)
    {
         return NULL;
    }
    //   
    for (i=0;i<len;i++)
    {
          // !
          if (pData[i]!=lastData++)
          {
             //  !
             errState = 1;
             //   
             errors += 1;
             //     
             return NULL;
          }
    }


}

      
      





私に確認すると、なぜかCubeIDEが開始アドレスを間違って決定していました。おそらく、同じ「左」プロジェクトに何らかの非互換性があるのでしょう。これは別の研究のために延期しましょう。分かり始めるまでは、最初はPCレジスタの正しい値を入力していました。コードが実行され、機能し始めました。テストを実行します。エラーの数もゼロです:







速度もそこそこです:







結論



ロシアの両方の USB ライブラリは、大まかな負荷テストに対応しました。彼らの誰もレースを離れませんでした。確かに、テストはエラーがないことを証明するのではなく、エラーの存在を明らかにすることを直接知っています。しかし、具体的に引用されたテストは何も明らかにしませんでした。これは、これらのライブラリのいずれかを使用できるという希望を与えます。



その過程で、SWD ポートを介して多くの変数をリアルタイムで監視することにより、デバッグ出力の置き換えをマスターしました。ラフでは、Eclipse でバッチビルドされたアプリケーションのデバッグもマスターしましたが、途中で 2 つのプロジェクトが混在していたため、PC レジスタを直接修正することで克服しなければならないいくつかの困難に直面しました。しかし、通常の Eclipse では、この種の混合は必要ありません。そして、最終的に、鎌、ハンマー、そしてある種の母親の助けを借りても、最終的な目標は達成されました。デバッグが行われました。同時に、Syakh のソース コードは引き続き Eclipse に表示されました。



あとがき



記事がすでに書かれていて、まだHabrにアップロードしている途中だったとき、そのよう な素晴らしい資料が著者のために現れました DSarovsky... そこでも USB へのアクセスが実装されていますが、これは、私のお気に入りのスタイルである Konstantin Chizhov のスタイルで作成されたライブラリを介して行われます。



このような美しいバージョンで作成されたライブラリの存在に注意する必要があります。現時点で、作者とパフォーマンスをチェックしたところ、これまでのところ、その速度は最大ではなく典型的なものであることがわかりました。ただし、これらの行を読んだときには、すでにオーバークロックされている可能性があります。したがって、私は他のものの間でそれへのリンクを残します。彼女は単に離陸する必要があります !このスタイルの図書館は離陸せずにはいられません!



All Articles