Ganggu latensi pada MCU STM32F303

8

Saya sedang mengerjakan proyek yang melibatkan STM32 MCU (pada papan STM32303C-EVAL tepatnya) yang harus menanggapi gangguan eksternal. Saya ingin reaksi terhadap interupsi eksternal secepat mungkin. Saya telah memodifikasi contoh pustaka periferal standar dari halaman web ST dan program saat ini hanya menyalakan LED di setiap sisi kenaikan berturut-turut pada PE6:

#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); 
}

Penangan interrupt terlihat seperti ini:

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);
  }
}

Dalam kasus khusus ini, interupsi dibuat oleh generator fungsi eksternal yang dapat diprogram berjalan pada 100 Hz. Setelah memeriksa respons MCU pada osiloskop, saya agak terkejut bahwa dibutuhkan hampir 1,32 kita untuk MCU untuk mulai memproses interupsi: masukkan deskripsi gambar di sini

Dengan MCU berjalan pada 72 MHz (saya telah memeriksa output SYSCLK pada pin MCO sebelumnya) ini berjumlah hampir 89 siklus clock. Bukankah seharusnya respons MCU terhadap interupsi jauh lebih cepat?

PS Kode ini dikompilasi dengan IAR Embedded Workbench dan dioptimalkan untuk kecepatan tertinggi.

KR
sumber
Apakah Anda yakin itu penundaan untuk mulai memproses interupsi? Apa yang terjadi ketika Anda menghapus kondisi if dan hanya beralih?
BeB00
@ BeB00 if{}pernyataan diperlukan karena rutin interupsi tidak tahu apa sumber interupsi itu.
Rohat Kılıç
Jika saya ingat dengan benar, latensi harus sekitar 10-15 siklus
BeB00
1
Benar, tetapi apa yang terjadi ketika Anda menghapusnya dalam percobaan? Saya berasumsi Anda tidak memiliki banyak gangguan lain yang memicu hal ini terus-menerus, jadi Anda harus bisa merasakan lebih baik atas keterlambatan yang sebenarnya
BeB00
1
Itu seharusnya tidak menjadi misteri. Lihatlah kode assembler terkompilasi untuk fungsi interupsi Anda dan lihat manual ref ARM yang sesuai untuk menjumlahkan jumlah siklus clock untuk setiap instruksi ...
brhans

Jawaban:

8

Masalah

Nah Anda harus melihat fungsi yang Anda gunakan, Anda tidak bisa hanya membuat asumsi pada kecepatan kode yang belum Anda lihat:

Ini adalah fungsi 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;

}

Seperti yang Anda lihat, ini bukan hal sederhana yang hanya membutuhkan satu atau dua siklus.

Berikutnya adalah fungsi sakelar LED Anda:

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

Jadi di sini Anda memiliki beberapa pengindeksan array dan baca modifikasi tulis untuk beralih LED.

HAL sering kali menghasilkan jumlah overhead yang baik karena mereka harus mengurus pengaturan yang salah dan penggunaan fungsi yang salah. Memeriksa parameter yang diperlukan dan juga terjemahan dari parameter sederhana ke bit dalam register dapat mengambil jumlah komputasi yang serius (setidaknya untuk waktu kritis setidaknya mengganggu).

Jadi dalam kasus Anda, Anda harus menerapkan bare metal interrupt Anda secara langsung pada register dan tidak bergantung pada HAL.


Contoh solusi

Misalnya sesuatu seperti:

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

Catatan: ini tidak akan mengubah LED tetapi hanya mengaturnya. Tidak ada tombol atom yang tersedia di GPIO STM. Saya juga tidak suka ifkonstruksi yang saya gunakan, tetapi menghasilkan perakitan lebih cepat daripada pilihan saya if (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6)).

Varian toggle bisa berupa sesuatu di sepanjang baris ini:

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;
}

Menggunakan variabel yang berada dalam RAM daripada menggunakan ODRregister harus lebih cepat, terutama ketika Anda menggunakan 72 MHz, karena akses ke periferal dapat lebih lambat karena sinkronisasi antara domain jam yang berbeda dan jam periferal hanya berjalan pada frekuensi yang lebih rendah. Tentu saja, Anda tidak boleh mengubah status LED di luar interupsi agar toggle berfungsi dengan benar. Atau variabel harus bersifat global (maka Anda harus menggunakan volatilekata kunci saat mendeklarasikannya) dan Anda harus mengubahnya di mana saja.

Perhatikan juga, bahwa saya menggunakan C ++, karenanya booldan bukan uint8_ttipe atau serupa untuk mengimplementasikan flag. Meskipun jika kecepatan adalah perhatian utama Anda, Anda mungkin harus memilih untuk uint32_tbendera karena ini akan selalu disejajarkan dengan benar dan tidak menghasilkan kode tambahan saat mengakses.

Penyederhanaan dimungkinkan karena Anda mudah-mudahan tahu apa yang Anda lakukan dan selalu tetap seperti itu. Jika Anda benar-benar hanya memiliki satu interupsi yang diaktifkan untuk penangan EXTI9_5, Anda dapat menyingkirkan pemeriksaan register yang tertunda sama sekali, mengurangi jumlah siklus lebih jauh.

Ini mengarah ke potensi optimasi lain: gunakan jalur EXTI yang memiliki interupsi tunggal seperti salah satu dari EXTI1 ke EXTI4. Di sana Anda tidak perlu melakukan pemeriksaan apakah jalur yang benar telah memicu interupsi Anda.

Gudang senjata
sumber
1
Sulit untuk mengetahui dari kode C berapa banyak instruksi yang diperlukan. Saya telah melihat fungsi yang lebih besar dioptimalkan untuk beberapa instruksi yang bahkan tidak melibatkan panggilan yang sebenarnya.
Dmitry Grigoryev
1
@DmitryGrigoryev sebagai register dideklarasikan sebagai volatilekompiler tidak diperbolehkan untuk mengoptimalkan banyak fungsi di atas dan jika fungsi tidak diimplementasikan secara inline di header, panggilan biasanya tidak dioptimalkan juga.
Arsenal
5

Mengikuti saran PeterJ saya telah menghilangkan penggunaan SPL. Keseluruhan kode saya terlihat seperti ini:

#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)
    {

    }
}

dan instruksi perakitan terlihat seperti ini:

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

Ini sedikit meningkatkan masalah, karena saya telah berhasil mendapatkan respons di ~ 440 ns @ 64 MHz (yaitu, 28 siklus clock).

KR
sumber
2
Ubah Anda BRR |= dan BSRR |= menjadi adil BRR = dan BSRR = , register itu hanya menulis, kode Anda membacanya, ORRnilainya dan kemudian menulis. yang dapat dioptimalkan untuk satu STRinstruksi.
Colin
Pindahkan penangan & vektor EXTI Anda ke CCMRAM
P__J__
3

Jawabannya sangat mudah: pustaka HAL (atau SPL) yang hebat. Jika Anda melakukan sesuatu yang sensitif terhadap waktu, gunakan register periferal yang kosong. Maka Anda akan mendapatkan latensi yang benar. Saya tidak mengerti apa gunanya menggunakan perpustakaan konyol ini untuk beralih pin !! atau untuk memeriksa daftar patung.

P__J__
sumber
3

Ada beberapa kesalahan dalam kode Anda = Daftar BSRR hanya menulis. Jangan gunakan | = operator, cukup sederhana "=". Ini akan mengatur / mengatur ulang pin yang tepat. Nol diabaikan.

Ini akan menghemat beberapa jam. Petunjuk lain: pindahkan tabel vektor & interupsi rutin Anda ke CCMRAM. Anda akan menyimpan beberapa kutu lainnya (flash waitstates dll)

PS Saya tidak bisa berkomentar karena saya tidak memiliki reputasi yang cukup :)

P__J__
sumber