STM32F303 MCUの割り込みレイテンシ


8

私は、外部割り込みに応答する必要があるSTM32 MCU(正確にはSTM32303C-EVALボード上)を含むプロジェクトに取り組んでいます。外部割り込みに対する反応をできるだけ速くしたいのですが。STのWebページから標準のペリフェラルライブラリの例を変更しました。現在のプログラムは、PE6の連続する立ち上がりエッジごとにLEDをトグルするだけです。

#include "stm32f30x.h"
#include "stm32303c_eval.h"

EXTI_InitTypeDef   EXTI_InitStructure;
GPIO_InitTypeDef   GPIO_InitStructure;
NVIC_InitTypeDef   NVIC_InitStructure;

static void EXTI9_5_Config(void);

int main(void)
{

  /* Initialize LEDs mounted on STM32303C-EVAL board */
  STM_EVAL_LEDInit(LED1);

  /* Configure PE6 in interrupt mode */
  EXTI9_5_Config();

  /* Infinite loop */
  while (1)
  {
  }
}

// Configure PE6 and PD5 in interrupt mode
static void EXTI9_5_Config(void)
{
  /* Enable clocks */
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOD | RCC_AHBPeriph_GPIOE, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);

  /* Configure input */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  GPIO_Init(GPIOD, &GPIO_InitStructure);

  /* Connect EXTI6 Line to PE6 pin */
  SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource6);

  /* Configure Button EXTI line */
  EXTI_InitStructure.EXTI_Line = EXTI_Line6;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;  
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  EXTI_Init(&EXTI_InitStructure);

  /* Enable and set interrupt to the highest priority */
  NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure); 
}

割り込みハンドラは次のようになります。

void EXTI9_5_IRQHandler(void)
{ 
  if((EXTI_GetITStatus(EXTI_Line6) != RESET))
  {
    /* Toggle LD1 */
    STM_EVAL_LEDToggle(LED1);

    /* Clear the EXTI line 6 pending bit */
    EXTI_ClearITPendingBit(EXTI_Line6);
  }
}

この特定のケースでは、割り込みは、100 Hzで動作する外部のプログラム可能な関数発生器によって作成されます。MCUの応答をオシロスコープで調べた後、MCUが割り込みの処理を開始するのに1.32マイクロ秒近くかかることにかなり驚きました。 ここに画像の説明を入力してください

MCUが72 MHzで動作している場合(MCOピンのSYSCLK出力を事前に確認しました)、これは約89クロックサイクルになります。割り込みに対するMCUの応答がはるかに速くなるべきではありませんか?

PSコードはIAR Embedded Workbenchでコンパイルされ、最高速度で最適化されました。


それが割り込みの処理を開始するための遅延であると確信していますか?if条件を削除してトグルするだけではどうなりますか?
BeB00 2017

@ BeB00 if{}ステートメントが必要なのは、割り込みルーチンが割り込みのソースが何であるかがわからないためです。
RohatKılıç17年5

私の記憶が正しければ、レイテンシは約10〜15サイクルになるはずです
BeB00

1
そうですが、実験でそれを削除するとどうなりますか?私はあなたがこれを絶えずトリガーする他の多くの割り込みを持っていないと想定しているので、実際の遅延をよりよく感じることができるはずです
BeB00

1
それは本当に謎ではありません。割り込み関数のコンパイル済みアセンブラコードを確認し、適切なARMリファレンスマニュアルを参照して、各命令のクロックサイクル数を
合計し

回答:


8

問題

さて、あなたはあなたが使用している関数を見なければなりません、あなたが見たことのないコードの速度を単に仮定することはできません:

これは、EXTI_GetITStatus関数です。

ITStatus EXTI_GetITStatus   (   uint32_t    EXTI_Line    )  
{
  ITStatus bitstatus = RESET;
  uint32_t enablestatus = 0;

  /* Check the parameters */
  assert_param(IS_GET_EXTI_LINE(EXTI_Line));

  enablestatus =  *(__IO uint32_t *) (((uint32_t) &(EXTI->IMR)) + ((EXTI_Line) >> 5 ) * 0x20) & (uint32_t)(1 << (EXTI_Line & 0x1F));

  if ( (((*(__IO uint32_t *) (((uint32_t) &(EXTI->PR)) + (((EXTI_Line) >> 5 ) * 0x20) )) & (uint32_t)(1 << (EXTI_Line & 0x1F))) != (uint32_t)RESET) && (enablestatus != (uint32_t)RESET))
  {
    bitstatus = SET;
  }
  else
  {
    bitstatus = RESET;
  }
  return bitstatus;

}

ご覧のように、これは1〜2サイクルだけを必要とする単純なものではありません。

次はあなたのLEDトグル機能です:

void STM_EVAL_LEDToggle (   Led_TypeDef     Led  )  
{
  GPIO_PORT[Led]->ODR ^= GPIO_PIN[Led];
}

したがって、ここでは、いくつかの配列のインデックス付けと、LEDを切り替えるための読み取り変更書き込みがあります。

HALは、誤った設定や関数の誤った使用法に対処する必要があるため、多くの場合、かなりのオーバーヘッドを生み出します。必要なパラメーターのチェックと、単純なパラメーターからレジスター内のビットへの変換には、かなりの量の計算が必要になります(少なくともタイムクリティカルな割り込みの場合はそうです)。

したがって、あなたのケースでは、割り込みベアメタルをレジスタに直接実装し、HALに依存しないでください。


ソリューションの例

たとえば次のようなもの:

if (EXTI->PR & EXTI_PR_PR6)
{
    GPIOE->BSRR = GPIO_BSRR_BS_8;
    EXTI->PR = EXTI_PR_PR6;
}

注:これはLEDを切り替えず、単に設定するだけです。STM GPIOで使用できるアトミックトグルはありません。私ifが使用した構成も好きではありませんが、私の好みよりも速いアセンブリを生成しますif (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6))

トグルバリアントは、次のようなものになります。

static bool LEDstate = false;
if (EXTI->PR & EXTI_PR_PR6)
{
    if (!LEDstate)
    {
        GPIOE->BSRR = GPIO_BSRR_BS_8;
        LEDstate = true;
    }
    else
    {
        GPIOE->BSRR = GPIO_BSRR_BR_8;
        LEDstate = false;
    }
    EXTI->PR = EXTI_PR_PR6;
}

ODR特に72 MHzを使用する場合は、レジスタを使用する代わりにRAMに常駐する変数を使用する方が高速です。これは、異なるクロックドメインと単純に低い周波数で実行されるペリフェラルクロック間の同期により、ペリフェラルへのアクセスが遅くなる可能性があるためです。もちろん、トグルが正しく機能するために、割り込みの外でLEDの状態を変更することはできません。または、変数はグローバルである必要があり(volatile宣言するときにキーワードを使用する必要があります)、それに応じてどこでも変更する必要があります。

また、私はC ++を使用しているため、フラグを実装するためのタイプや類似タイプboolではないことに注意してくださいuint8_t。ただし、速度が主な懸念事項である場合はuint32_t、フラグが常に正しく調整され、アクセス時に追加のコードが生成されないため、おそらくフラグを選択する必要があります。

単純化は可能です。うまくいけば、自分が何をしているのかを理解し、常にそのように保つことができます。本当にEXTI9_5ハンドラーに対して単一の割り込みを有効にしているだけであれば、保留中のレジスタチェックを完全に取り除くことができ、サイクル数をさらに削減できます。

これは、別の最適化の可能性につながります。EXTI1からEXTI4のいずれかのような単一の割り込みを持つEXTIラインを使用します。そこでは、正しいラインが割り込みをトリガーしたかどうかのチェックを実行する必要はありません。


1
Cコードからどれだけの命令が必要かを判断するのは困難です。私は実際の呼び出しを含まないいくつかの命令に最適化されたより大きな関数を見てきました。
Dmitry Grigoryev 2017

1
レジスタとしての@DmitryGrigoryev volatileは、コンパイラが上記の関数であまり最適化できないため宣言されています。関数がヘッダーにインラインで実装されていない場合、通常、呼び出しも最適化されません。
アーセナル

5

PeterJの提案に従い、SPLの使用を省略しました。私のコード全体は次のようになります:

#include "stm32f30x.h"

void EXTI0_IRQHandler(void)
{
    // I am simply toggling the pin within the interrupt, as I only want to check the response speed.
     GPIOE->BSRR |= GPIO_BSRR_BS_10;
     GPIOE->BRR |= GPIO_BRR_BR_10;
     EXTI->PR |= EXTI_PR_PR0;
}

int main()
{
    // Initialize the HSI:
    RCC->CR |= RCC_CR_HSION;
    while(!(RCC->CR&RCC_CR_HSIRDY));

    // PLL configuration:
    RCC->CFGR &= ~RCC_CFGR_PLLSRC;     // HSI / 2 selected as the PLL input clock.
    RCC->CFGR |= RCC_CFGR_PLLMULL16;   // HSI / 2 * 16 = 64 MHz
    RCC->CR |= RCC_CR_PLLON;          // Enable PLL
    while(!(RCC->CR&RCC_CR_PLLRDY));  // Wait until PLL is ready

    // Flash configuration:
    FLASH->ACR |= FLASH_ACR_PRFTBE;
    FLASH->ACR |= FLASH_ACR_LATENCY_1;

    // Main clock output (MCO):
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER |= GPIO_MODER_MODER8_1;
    GPIOA->OTYPER &= ~GPIO_OTYPER_OT_8;
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR8;
    GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR8;
    GPIOA->AFR[0] &= ~GPIO_AFRL_AFRL0;

    // Output on the MCO pin:
    RCC->CFGR |= RCC_CFGR_MCO_SYSCLK;

    // PLL as the system clock
    RCC->CFGR &= ~RCC_CFGR_SW;    // Clear the SW bits
    RCC->CFGR |= RCC_CFGR_SW_PLL; //Select PLL as the system clock
    while ((RCC->CFGR & RCC_CFGR_SWS_PLL) != RCC_CFGR_SWS_PLL); //Wait until PLL is used

    // LED output:
    RCC->AHBENR |= RCC_AHBENR_GPIOEEN;
    GPIOE->MODER |= GPIO_MODER_MODER10_0;
    GPIOE->OTYPER &= ~GPIO_OTYPER_OT_10;
    GPIOE->PUPDR &= ~GPIO_PUPDR_PUPDR10;
    GPIOE->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR10;

    // Interrupt on PA0:
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;
    GPIOA->MODER &= ~(GPIO_MODER_MODER0);
    GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR0);
    GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR0);
    SYSCFG->EXTICR[0] &= SYSCFG_EXTICR1_EXTI0_PA;
    EXTI->RTSR = EXTI_RTSR_TR0;
    EXTI->IMR = EXTI_IMR_MR0; 
    NVIC_SetPriority(EXTI0_IRQn, 1);
    NVIC_EnableIRQ(EXTI0_IRQn);

    while(1)
    {

    }
}

とアセンブリ命令は次のようになります:

EXTI0_IRQHandler:
        LDR.N    R0,??DataTable1  ;; 0x48001018
        LDR      R1,[R0, #+0]
        ORR      R1,R1,#0x400
        STR      R1,[R0, #+0]
        LDRH     R2,[R0, #+16]
        ORR      R2,R2,#0x400
        STRH     R2,[R0, #+16]
        LDR.N    R0,??DataTable1_1  ;; 0x40010414
        LDR      R1,[R0, #+0]
        ORR      R1,R1,#0x1
        STR      R1,[R0, #+0]
        BX       LR               ;; return

64 MHz(つまり28クロックサイクル)で約440 nsで応答を得ることができたので、これはかなり問題を改善します。


2
BRR |= and BSRR |= をjust BRR = とに変更しますBSRR = 。これらのレジスターは書き込み専用であり、コードはそれらを読み取りORR、値をingしてから書き込みます。それは単一のSTR命令に最適化できます。
Colin

EXTIハンドラーとベクターをCCMRAMに移動します
P__J__

3

答えは非常に簡単です。優れたHAL(またはSPL)ライブラリです。時間に敏感な何かをする場合は、代わりにベアペリフェラルレジスタを使用してください。次に、正しい待ち時間を取得します。このばかげたライブラリを使用してピンを切り替える意味がわかりません!! または彫像レジスタをチェックします。


3

コードにエラーがあります= BSRRレジスタは書き込み専用です。| =演算子は使用せず、単純に "="を使用してください。適切なピンを設定/リセットします。ゼロは無視されます。

それはあなたにいくつかの時計を節約します。別のヒント:ベクトルテーブルと割り込みルーチンをCCMRAMに移動します。あなたはいくつかの他のティック(フラッシュ待機状態など)を保存します

PS私は十分な評判がないのでコメントできません:)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.