Độ trễ gián đoạn trên MCU STM32F303


8

Tôi đang làm việc trong một dự án liên quan đến MCM STM32 (trên bảng STM32303C-EVAL chính xác) phải đáp ứng với ngắt ngoài. Tôi muốn phản ứng với ngắt bên ngoài càng nhanh càng tốt. Tôi đã sửa đổi một ví dụ thư viện ngoại vi tiêu chuẩn từ trang web ST và chương trình hiện tại chỉ đơn giản là bật đèn LED ở mỗi cạnh tăng liên tiếp trên 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); 
}

Trình xử lý ngắt trông như thế này:

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

Trong trường hợp cụ thể này, các ngắt được tạo bởi một bộ tạo chức năng lập trình bên ngoài chạy ở tần số 100 Hz. Sau khi kiểm tra phản hồi MCU trên máy hiện sóng, tôi khá ngạc nhiên khi phải mất gần 1,32 chúng tôi để MCU bắt đầu xử lý ngắt: nhập mô tả hình ảnh ở đây

Với MCU chạy ở 72 MHz (tôi đã kiểm tra đầu ra SYSCLK trên chân MCO trước đó), con số này lên tới gần 89 chu kỳ xung nhịp. Không nên phản ứng MCU với ngắt nhanh hơn nhiều?

PS Mã được biên dịch với IAR Embedded Workbench và được tối ưu hóa cho tốc độ cao nhất.


Bạn có chắc chắn đó là sự chậm trễ để bắt đầu xử lý ngắt? Điều gì xảy ra khi bạn loại bỏ điều kiện if và chỉ chuyển đổi?
BeB00

@ BeB00 if{}câu lệnh là cần thiết vì thói quen ngắt không biết nguồn ngắt là gì.
Rohat Kılıç

Nếu tôi nhớ chính xác, độ trễ sẽ vào khoảng 10-15 chu kỳ
BeB00

1
Phải, nhưng điều gì xảy ra khi bạn loại bỏ nó trong thử nghiệm của bạn? Tôi giả sử rằng bạn không có tải các ngắt khác kích hoạt điều này liên tục, vì vậy bạn sẽ có thể cảm nhận rõ hơn về độ trễ thực tế
BeB00

1
Nó thực sự không nên là một bí ẩn. Xem mã trình biên dịch được biên dịch cho chức năng ngắt của bạn và tham khảo hướng dẫn sử dụng ARM thích hợp để thêm số chu kỳ xung nhịp cho mỗi lệnh ...
brhans

Câu trả lời:


8

Vấn đề

Chà, bạn phải xem các chức năng bạn đang sử dụng, bạn không thể chỉ đưa ra các giả định về tốc độ mã mà bạn chưa xem:

Đây là hàm 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;

}

Như bạn có thể thấy, đây không phải là một điều đơn giản chỉ cần một hoặc hai chu kỳ.

Tiếp theo là chức năng chuyển đổi LED của bạn:

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

Vì vậy, ở đây bạn có một số chỉ mục mảng và đọc ghi sửa đổi để chuyển đổi đèn LED.

HAL thường kết thúc việc tạo ra một lượng chi phí tốt vì họ phải chăm sóc các cài đặt sai và sử dụng sai các chức năng. Việc kiểm tra tham số cần thiết và cả việc dịch từ một tham số đơn giản sang một bit trong thanh ghi có thể mất một lượng điện toán nghiêm trọng (ít nhất là trong một thời gian gián đoạn quan trọng).

Vì vậy, trong trường hợp của bạn, bạn nên thực hiện kim loại trần gián đoạn của mình trực tiếp trên các thanh ghi và không dựa vào bất kỳ HAL nào.


Giải pháp ví dụ

Ví dụ như một cái gì đó như:

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

Lưu ý: điều này sẽ không chuyển đổi đèn LED mà chỉ cần đặt nó. Không có chuyển đổi nguyên tử có sẵn trên các GPIO STM. Tôi cũng không thích ifcấu trúc tôi đã sử dụng, nhưng nó tạo ra lắp ráp nhanh hơn sau đó tôi thích if (EXTI_PR_PR6 == (EXTI->PR & EXTI_PR_PR6)).

Một biến thể chuyển đổi có thể là một cái gì đó dọc theo các dòng này:

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

Sử dụng một biến nằm trong RAM thay vì sử dụng thanh ODRghi sẽ nhanh hơn, đặc biệt là khi bạn sử dụng 72 MHz, vì truy cập vào các thiết bị ngoại vi có thể chậm hơn do đồng bộ hóa giữa các miền đồng hồ khác nhau và đồng hồ ngoại vi chỉ chạy ở tần số thấp hơn. Tất nhiên, bạn không thể thay đổi trạng thái của đèn LED bên ngoài ngắt để chuyển đổi hoạt động chính xác. Hoặc biến phải là toàn cục (sau đó bạn phải sử dụng volatiletừ khóa khi khai báo) và bạn phải thay đổi nó ở mọi nơi cho phù hợp.

Cũng lưu ý rằng tôi đang sử dụng C ++, do đó boolvà không phải uint8_tloại nào đó hoặc tương tự để thực hiện cờ. Mặc dù nếu tốc độ là mối quan tâm chính của bạn, có lẽ bạn nên chọn một uint32_tcờ vì điều này sẽ luôn được căn chỉnh chính xác và không tạo thêm mã khi truy cập.

Việc đơn giản hóa là có thể bởi vì bạn hy vọng biết những gì bạn đang làm và luôn giữ nó theo cách đó. Nếu bạn thực sự chỉ có một ngắt duy nhất được kích hoạt cho trình xử lý EXTI9_5, bạn có thể thoát khỏi kiểm tra đăng ký đang chờ xử lý hoàn toàn, giảm số lượng chu kỳ hơn nữa.

Điều này dẫn đến một tiềm năng tối ưu hóa khác: sử dụng một dòng EXTI có một ngắt đơn giống như một trong các EXTI1 đến EXTI4. Ở đó bạn không phải thực hiện kiểm tra xem dòng chính xác có kích hoạt ngắt của bạn hay không.


1
Thật khó để nói từ mã C bao nhiêu hướng dẫn. Tôi đã thấy các chức năng lớn hơn được tối ưu hóa theo một vài hướng dẫn thậm chí không liên quan đến một cuộc gọi thực tế.
Dmitry Grigoryev

1
@DmitryGrigoryev khi đăng ký được khai báo là volatiletrình biên dịch không được phép tối ưu hóa nhiều trong các chức năng trên và nếu các chức năng không được triển khai nội tuyến trong tiêu đề, cuộc gọi thường không được tối ưu hóa.
Arsenal

5

Theo đề nghị của PeterJ, tôi đã bỏ qua việc sử dụng SPL. Toàn bộ mã của tôi trông như thế này:

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

    }
}

và hướng dẫn lắp ráp trông như thế này:

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

Điều này cải thiện vấn đề khá nhiều, vì tôi đã quản lý để nhận được phản hồi trong ~ 440 ns @ 64 MHz (tức là 28 chu kỳ xung nhịp).


2
Thay đổi của bạn BRR |= BSRR |= chỉ BRR = BSRR = , những thanh ghi đó chỉ được viết, mã của bạn đang đọc chúng, ORRnhập giá trị và sau đó viết. có thể được tối ưu hóa thành một STRhướng dẫn duy nhất .
Colin

Di chuyển trình xử lý & vectơ EXTI của bạn sang CCMRAM
P__J__

3

Câu trả lời là cực kỳ dễ dàng: thư viện HAL (hoặc SPL) tuyệt vời. Thay vào đó, nếu bạn làm một cái gì đó nhạy cảm, hãy sử dụng các thanh ghi ngoại vi để thay thế. Sau đó, bạn sẽ nhận được độ trễ chính xác. Tôi không thể hiểu được đâu là điểm để sử dụng thư viện lố bịch này để chuyển mã pin !! hoặc để kiểm tra đăng ký tượng.


3

Có một số lỗi trong mã của bạn = thanh ghi BSRR chỉ được ghi. Không sử dụng toán tử | =, chỉ đơn giản "=". Nó sẽ thiết lập / thiết lập lại các chân thích hợp. Số không được bỏ qua.

Nó sẽ giúp bạn tiết kiệm vài đồng hồ. Một gợi ý khác: di chuyển bảng vectơ của bạn và các thói quen ngắt sang CCMRAM. Bạn sẽ lưu một số đánh dấu khác (flash Waitstates, v.v.)

PS tôi không thể bình luận vì tôi không có đủ danh tiếng :)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.