Проблема с измерением задержки прерывания MCU ARM

Введение

Я студент, который хочет грубо измерить задержку прерываний чипов серии ARM Cortex-M без использования осциллографа. Однако я столкнулся с очень необычной проблемой, которая меня крайне озадачила.

Во-первых, позвольте мне кратко обрисовать мой подход к измерениям. Я попытался примерно измерить задержку прерывания с помощью встроенного таймера микроконтроллера. Моя основная идея заключалась в следующем: перед входом в процедуру обслуживания прерываний (ISR) я сбрасывал таймер на ноль. Затем я генерировал запрос на прерывание и вводил ISR. Сразу при входе в ISR я читал значение таймера. В этот момент значение таймера может служить очень грубой оценкой задержки прерывания.

По определенным причинам мне нужно написать упомянутую функцию ISR на языке ассемблера. В основной функции я генерирую программное прерывание с помощью регистра SWIER, которое позволяет мне войти в написанный мною ISR.

Во время этого процесса я столкнулся со своеобразной проблемой. При написании ISR на языке ассемблера принято помещать в стек несколько регистров при входе в функцию, например от R4 до R11, и возвращать их обратно при выходе из функции. Следовательно, теоретически, если я не помещаю регистры в стек при входе в ISR (при условии, что я не использую эти регистры в ISR), грубое измерение задержки прерывания должно быть немного меньше по сравнению с тем, когда я действительно помещаю регистры в стек при входе в ISR. Однако в ходе своих экспериментов я обнаружил, что результаты были прямо противоположными. Если я помещаю регистры в стек при входе в ISR, измеренная задержка прерывания на самом деле будет немного меньше. Я проводил эксперименты с использованием разных IDE (соответствующих разным компиляторам), а также экспериментировал с разными продуктами, использующими один и тот же чип ARM (STM32103ZET6 и STM32F103C8T6), но результаты оставались неизменными. Это привело меня в полное недоумение.

Экспериментальная установка

элемент конфигурация Процессор интел i5 8400 Операционные системы Windows 10 IDE Keil uVision 5 V5.39.0.0 и IAR для версии ARM 8.32.1.18631 С/С++ C99&C++11, оптимизация: O0 STM32CUBEMX версия 6.10.0 РУКА ARM Кортекс М3 микроконтроллеры STM32F103ZET6 и STM32F103C8T6 Таймер Таймер2, Прескалер: 0, ARR: 65535, Режим счетчика: включен, предварительная загрузка автоматической перезагрузки: включена ЭКСИ EXIT0, PA0, Группа приоритетов: 4 бита, Приоритет вытеснения: 1, Дополнительный приоритет: 0

Экспериментальный результат

В таблице ниже столбец CLOCK представляет частоту SYSCLK (HCLK). R4-R11 указывает значение счетчика таймера, полученное в результате чтения таймера, когда регистры R4–R11 были помещены в стек при входе в ISR. Аналогично, R4 представляет значение счетчика таймера, полученное, когда только регистр R4 был помещен в стек при входе в ISR. «Нестекированный» указывает значение счетчика таймера, полученное, когда ни один регистр не был помещен в стек при входе в ISR. Значения счетчика таймера в таблице получены из нескольких экспериментов.

Из приведенных выше экспериментальных результатов можно заметить, что во время процесса измерения, если в стек помещается только регистр R4, полученное значение счетчика таймера меньше по сравнению с тем, когда в стек помещаются регистры от R4 до R11 (что согласуется с теорией). и интуиция). Однако когда мы решаем не помещать регистры в стек, происходит нечто странное: полученное значение счетчика таймера оказывается больше, что противоречит нашей интуиции.

опыт1

【MCU】: STM32F103C8T6

【IDE】: Keil uVision 5 V5.39.0.0

ЧАСЫ Р4~Р11 Р4 несложенный 72ХМЗ 40~42 34~36 78~80 40 МГц 38~40 34~36 70~72 20 МГц 38 32 62~64

эксп2

【MCU】: STM32F103ZET6

【IDE】: Keil uVision 5 V5.39.0.0

ЧАСЫ Р4~Р11 Р4 несложенный 72ХМЗ 40 34 78 40 МГц 38~40 32 68~70 20 МГц 38 30~32 62~64

эксп3

【MCU】: STM32F103ZET6

【IDE】: IAR для версии ARM 8.32.1.18631.

ЧАСЫ Р4~Р11 Р4 несложенный 72ХМЗ 40~42 34~36 78~80 40 МГц 38~40 32 66 20 МГц 38~40 32 66

Из приведенных выше экспериментальных результатов можно заметить, что во время процесса измерения, если в стек помещается только регистр R4, полученное значение счетчика таймера меньше по сравнению с тем, когда в стек помещаются регистры от R4 до R11 (что согласуется с теорией). и интуиция). Однако когда мы решаем не помещать регистры в стек, происходит нечто странное: полученное значение счетчика таймера оказывается больше, что противоречит нашей интуиции.

Экспериментальный код

Вот часть основного кода.

//main.c 
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        TIM2->CNT = 0;      //set timer2 count=0
        EXTI->SWIER |= 1; //call EXIT0 ISR
        for(;i!=0;i--){}    //delay
        printf("[%5d]:TIM time t is :%d\n",line,t);
        printf("***********[%5d]:*******\n",line);
        line++;
        i = COUNT_NUM;
        HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
;ISR.s
    AREA ISR,CODE,READONLY
    EXPORT EXTI0_IRQHandler
    ;EXTI0_IRQHandler is a global function name that is an ISR written in the Assembly language.
    ;To enable EXTI0_IRQHandler, the automatically generated EXTI0_IRQHandler function needs to be commented out in the stm32f1xx_it.c file.
    IMPORT t
    ;t is a global variable used to store the value of the timer read in the ISR.
    IMPORT exit_pr_addr
    ;exit_pr_addr is the global variable used to store the address of the EXTI_PR register. 
    ;This address is used in the ISR in order to clear the interrupt request flag
    IMPORT tim_cnt_addr
    ;tim_cnt_addr is a global variable used to store the address of the count register in the timer.
    ;This address is used in the ISR in order to read the count value of the timer

EXTI0_IRQHandler
    ;push {r4-r11}
    ;push {r4}
    ;;;t = TIM2->CNT
    ldr r1,=tim_cnt_addr;read global var addr
    ldr r2,[r1]         ;read tim_cnt_addr value
    ldr r2,[r2]         ;read cnt value
    ldr r0,=t           ;get global var t addr
    str r2,[r0]         ;set t = TIM2->CNT
    ;;;clear interupt request flg
    ldr r1,=exit_pr_addr ;read global var addr
    ldr r1,[r1]     ;read exit_pr_addr value
    mov r3,#(1<<0)  ;
    str r3,[r1];
    ;pop {r4}
    ;pop {r4-r11}
    bx lr
    END

Некоторые настройки для компиляции с помощью Keil uVision 5 V5.39.0.0.

Проблема с измерением задержки прерывания MCU ARM

Проблема с измерением задержки прерывания MCU ARM

Вложение

Я упаковал вышеупомянутый код проекта в STM32F103C8T6, который успешно компилируется под Keil uVision 5 V5.39.0.0. Под платформой IAR код проекта практически такой же, за исключением некоторых отличий в формате ISR, написанного на ассемблере, остальной код остаётся прежним. Эта часть кода приведена в файле ISR_FOR_IAR.s в моем вложении. Упомянутый выше код загружен на Google Диск как вложение .

Есть ли кто-нибудь, кто может объяснить эту странную проблему, которую я обнаружил?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
0
223
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

В случае без push/pop вы, вероятно, запускаете ISR дважды из-за позднего сброса флага, вызвавшего прерывание. Поскольку два ISR выполняются один за другим, main печатает значение, сохраненное во втором прогоне (таким образом, примерно всю продолжительность ISR плюс «половину» второго, минус поправка на хвостовую цепочку).

Вы можете подтвердить или отклонить это, переместив всплывающее окно перед очисткой флага прерывания, или добавив счетчик, считающий число, или проверив флаг прерывания при входе в ISR, или очистив TIMx_CNT в ISR, или установив для TIMx_CNT значение какое-то заметное значение в ISR, или... вероятно, есть много способов.

P.S. Осциллограф или хотя бы дешевый логический анализатор (ЛА) – незаменимый инструмент для программирования микроконтроллеров; Я рекомендую вам получить немного. Один из способов проверить задержку/длительность прерываний — переключить вывод и измерить его — вы сами об этом упомянули.

Большое спасибо за Ваш ответ. Однако я считаю маловероятным, что сценарий, о котором вы упомянули, с двойным входом в ISR, произойдет. Поскольку вся программа имеет только одно программное прерывание, я генерирую прерывание, манипулируя соответствующими управляющими битами регистров. После срабатывания программного прерывания я использую цикл for для имитации задержки. Я считаю, что пока эта задержка достаточно велика, при входе в ISR моя программа, скорее всего, все еще находится внутри цикла for, и после выхода из прерывания она также остается внутри цикла for.

MasterLu 20.02.2024 03:52

@MasterLu: Вы неправильно поняли этот ответ. Он говорит о том, что часть ;;;clear interupt request flg вашего кода происходит слишком поздно в прерывании, поэтому не вступает в силу отмена подтверждения прерывания до bx lr. После возврата прерывания сигнал запроса на прерывание все еще активен, поэтому происходит еще одно прерывание. То, что делал основной поток, не имеет отношения к этому эффекту. Вы можете заставить свой ISR увеличивать указатель на то, где хранить результат таймера, чтобы временная метка второго прерывания не перезаписывала первую, и вы могли обнаружить, что это вообще происходит.

Peter Cordes 20.02.2024 04:00

Другой способ обойти эту проблему — заставить ISR вращаться некоторое время после очистки флага запроса, чтобы чтение таймера происходило сразу, но мы откладываем выход из ISR до тех пор, пока очистка флага не вступит в силу. Но увеличение счетчика — хороший способ убедиться в наличии эффекта.

Peter Cordes 20.02.2024 04:06

@PeterCordes Понятно, большое спасибо за объяснение. Это означает, что он генерировал вложенные прерывания внутри прерываний. Я доработаю свою программу по вашим предложениям, большое спасибо за ответ!

MasterLu 20.02.2024 04:14

@MasterLu: Нет, не вложенно. Согласно этому ответу и странице, на которую он ссылается, второе прерывание происходит после bx lr в первом, но до того, как оборудование вне ЦП отреагирует на сохранение в своем управляющем регистре. Если бы еще одно прерывание могло произойти до того, как вернулось это, они были бы вложены сколь угодно глубоко, никогда не достигая кода, который очищает флаг запроса.

Peter Cordes 20.02.2024 04:25

@PeterCordes: Большое спасибо за вашу постоянную помощь и поддержку в ответах на мой вопрос. Ваша помощь была для меня невероятно ценна. Я переработаю свою программу. Еще раз спасибо.

MasterLu 20.02.2024 04:39

@MasterLu, я писал о методах подтверждения или отклонения этого сценария, какой из них вы пробовали и каковы были результаты?

wek 20.02.2024 12:15

@wek: Извините, я еще не проверял, так как мне нужно купить новый адаптер USB-TTL. Как только у меня появятся условия для эксперимента, я сделаю это и сообщу вам о результатах. Спасибо за вашу помощь.

MasterLu 21.02.2024 02:56

@wek: Как показали мои эксперименты, ваш ответ абсолютно правильный. Действительно большое спасибо!

MasterLu 22.02.2024 14:17

Отвечать

Основываясь на моей экспериментальной проверке, причина этой проблемы точно такая же, как описано в ответе @wek.

Мой экспериментальный подход заключается в следующем: я использую глобальную переменную cnt, которая инициализируется значением 0. Каждый раз при вводе ISR я ставлю cnt = cnt + 1. После выхода из ISR (в основной функции) я сделал сброс cnt = 0.

//main.c 
  /* USER CODE BEGIN WHILE */
  while (1)
  {
        TIM2->CNT = 0;      //set timer2 count=0
        EXTI->SWIER |= 1; //call EXIT0 ISR
        for(;i!=0;i--){}    //delay
        printf("[%5d]:TIM time t is :%d\n",line,t);
        printf("[%5d]: cnt is :%d\n",line,cnt);
        printf("***********[%5d]:*******\n",line);
        line++;
        cnt=0;            //cnt is global var
        i = COUNT_NUM;
        HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
;ISR.s
    AREA ISR,CODE,READONLY
    EXPORT EXTI0_IRQHandler
    IMPORT t
    IMPORT exit_pr_addr
    IMPORT tim_cnt_addr
    IMPORT cnt
    ;cnt is a global variable used to store the number of times the ISR is entered.

EXTI0_IRQHandler
    ;push {r4-r11}
    ;push {r4}

    ;;;t = TIM2->CNT
    ldr r1,=tim_cnt_addr;read global var addr
    ldr r2,[r1]         ;read tim_cnt_addr value
    ldr r2,[r2]         ;read cnt value
    ldr r0,=t           ;get global var t addr
    str r2,[r0]         ;set t = TIM2->CNT
    
    ;;;set  cnt = cnt + 1
    ldr r0, =cnt;
    ldr r1,[r0]
    add r1,r1,#1;
    str r1,[r0]
    
    ;;;clear interupt request flag
    ldr r1,=exit_pr_addr ;read global var addr
    ldr r1,[r1]     ;read exit_pr_addr value
    mov r3,#(1<<0)  ;
    str r3,[r1];
    
    ;pop {r4}
    ;pop {r4-r11}
    bx lr
    END

Согласно моим экспериментам, когда я решаю поместить регистры в стек, напечатанное значение cnt равно 1. Однако, когда я решаю не помещать регистры в стек, напечатанное значение cnt равно 2. Это означает, что если регистры не помещаются в стек в ISR, ISR повторно вводится при выходе, в результате чего в ISR в общей сложности появляются 2 записи.

Другие вопросы по теме