STM32 μC: Задержка SysTick не работает внутри обработчика прерываний
Поэтому я написал программу для STM32F103C8T6 на C, используя RTC (часы реального времени) и модуль отображения.
RTC и дисплей работают нормально, но когда я пытаюсь обновить дисплей внутри обработчика прерываний RTC, он не работает.
Когда я пишу что-то на дисплей из main() , он работает отлично.
Обработчик прерываний работает также, поэтому я решил, что проблема связана с функцией, которая записывается на дисплей.
Эта функция использует небольшие задержки для битового обмена данными с контроллером дисплея.
Ранее я использовал SysTick для создания таких задержек:
Но так или иначе, внутри обработчика прерываний RTC это не работает. Поэтому я заменил функцию задержки на эту, не используя SysTick:
и теперь все работает отлично.
Я пытаюсь понять, почему SysTick видимому, не работает внутри обработчика прерываний RTC.
Я понял, что, возможно, это вызвано приоритетами прерывания, но, согласно данным таблицы, по умолчанию приоритет SysTick Interrupt выше, чем приоритет прерывания RTC.
Может быть, кто-то может объяснить, почему это происходит?
EDIT1: Хорошо, поэтому я прочитал немного больше о приоритетах прерывания, и мне кажется, что мне нужно правильно настроить NVIC_IRQChannelPreemptionPriority. Я попробую это, как только смогу.
Что касается задержки внутри прерывания, я знаю, что это не правильный способ сделать это, но я все еще хотел понять поведение программы
EDIT2: Я просто попытался изменить приоритеты прерываний следующим образом:
и теперь задержка SysTick работает в обработчике прерываний RTC.
Способ реализации задержки с использованием SysTick совпадает с HAL_Delay в библиотеке HAL, исходящей от ST. Способ его работы заключается в том, что SysTick постоянно увеличивает (обычно с интервалом 1 мс) переменную — systick_count в вашем случае — которая сравнивается с локальной переменной внутри функции задержки — start в вашем случае.
Здесь есть две вещи:
systick_count должен быть systick_count чтобы принудительно перечитать его значение перед каждым сравнением. Он модифицируется прерыванием, а не внутри этой функции. Ваш компилятор может выбрать оптимизацию этой части кода.
Прерывание SysTick должно быть «запущено» при вызове функции задержки. Если это не так, потому что у вас прерывание с более высоким приоритетом (числовое значение ниже), вы оказываетесь в тупике, и функция задержки никогда не закончится, потому что счетчик никогда не будет увеличиваться с помощью SysTick, который никогда не произойдет. Что касается этого момента — не принимайте вещи и полагайтесь только на таблицу данных. Идите вперед и убедитесь, что он работает во время задержки. Либо проверьте значения регистра NVIC в момент вызова функции задержки, либо просто установите контрольную точку в SysTick, когда вы находитесь в прерывании RTC, — это должно ударить эту точку останова, когда вы находитесь в функции задержки. Если это не t-, вам, вероятно, необходимо настроить приоритеты прерываний.
Что касается всей идеи отложить задержку в ваших прерываниях — это обычно считается плохой практикой. Предполагается, что прерывания очень быстро выполняются очень небольшими частями работы, например, установкой флага, что что-то нужно делать в главном программном коде или помещать полученный байт в очередь для последующей обработки. Хотя это может показаться не очевидным в простой программе, где у вас есть только одно прерывание (два, если вы считаете Systick), это очень быстро станет проблематичным, если вы добавите больше прерываний и столкнетесь с такими проблемами, как потеря информации, поскольку ваши прерывания блокируют каждый другие, и их не называют достаточно быстро.
Источник
Электроника для всех
Блог о электронике
ARM. Учебный Курс. SysTick — Системный таймер
Продолжаем потрошить кортексы М3. Есть у них у всех, вне зависимости от производителя, такая штука как системный таймер — SysTick. Это часть ядра. Тупейший и примитивный таймер. Он ничего не умеет кроме как генерировать прерывание в заданном промежутке времени. Используется обычно во всяких RTOS для проворачивания диспетчера. К тому же его прерывание имеет высокий приоритет.
▌Краткое описание
Сам таймер 24 разрядный. И тикает вниз от предзагруженного значения до нуля, после чего перезагружается вновь и генерирует прерывание. Управляется он четырьмя регистрами:
SYST_CSR — SysTick Control and Status Register
Регистр управления и статуса. Несмотря на то, что он 32 битный заняты там всего 4 бита 🙂
- Бит 0 — ENABLE — бит разрешающий таймеру считать. 1 можно, 0 — нельзя. Когда мы ставим ENABLE в 1 то таймер автоматически загружает свои счетные регистры значением из регистра SYST_RVR и начинает тикать вниз.
- Бит 1 — TICKINT — разрешение прерывания. Когда этот бит 1, то на обнулении будет прерывание таймера.
- Бит 2 — CLKSOURCE — откуда брать тики. Варианта два 0 — внешнее тактирование эталонного генератора, 1 — с частоты процессора.
- Бит 16 — COUNTFLAG — флаг отсчета. Первый раз ставится в 1 после первого обнуления. При чтении, как я понял, автоматом обнуляется. Возвращает 1 если с последнего его чтения таймер перешел через ноль. Позволяет отследить были ли переполнения. Удобно.
SYST_RVR — SysTick Reload Value Register
Регистр предзагрузки. Именно отсюда берет таймер свое значение для перезагрузки при обнулении. Фактически вот сюда и нужно грузить требуемое число задержки. Регистр 32 разрядный, но используются только первые 24 бита.
Класть туда можно любое число в диапазоне 0x00000001-0x00FFFFFF. Можно и 0 положить, но ничего не будет. Т.к. у таймера весь экшн происходит с перехода из 0x00000001 в 0x00000000. А на ноль и получается ноль. Разве что COUNTFLAG вскочит. Только и всего. Поэтому значение SYST_RVR должно быть N-1 от желаемого количества тактов. Т.е. если надо получить прерывание на каждые 10000 тактов, то кладем 9999.
SYST_CVR — SysTick Current Value Register
Это, собственно, и есть счетный регистр. Тут оно тикает! В первых трех байтах. Есть у этого регистра особенность одна забавная. Из него можно только читать. А вот запись любого значения сразу его обнуляет. С обнулением флага COUNTFLAG.
Т.е. если надо занулить таймер — пиши сюда 🙂
SYST_CALIB — SysTick Calibration Value Register
Это, как ясно из названия, регистр калибровки. Его можно только читать, и возвращает он следующее:
- SKEW — Показывает что записано в TENMS 0 — там реальыне калибровочные константы. 1 — какой то мусор который не имеет значения. Ну или ноль.
- NOREF — показывает есть ли у девайса эталонная тактовая частота. 0 — есть, 1 — нет. Это зависит от производителя, позаботился ли он о такой штуке 🙂 Если эталонных часов нет, то бит CLKSOURCE из регистра SYST_CSR читается как 1 и его нельзя изменить.
- TENMS — калибровочная константа. Содержит значение для 10мс задержки. Как я понял, для эталонного генератора. Которого может и не быть.
▌Адреса и имена
Осталось залезть в CMSIS и поискать там описание и дефайны SysTick. Находятся быстро в core_cm3.h:
typedef struct < __IO uint32_t CTRL; /*! LOAD=TimerTick; // Загрузка значения SysTick->VAL=TimerTick; // Обнуляем таймеры и флаги. Записью, помните? SysTick->CTRL= SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;
▌Обработчик прерывания SysTick
Где взять имя обработчика? Да из таблицы прерываний. Поглядеть, если кто не помнит, можно в STM32F10x.s
; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler DCD SysTick_Handler ; SysTick Handler
Имя есть. Осталось организовать прерывание:
//SysTick Interrupt void SysTick_Handler(void) < // Тут будем делать нечто полезное. Например, ставить бит B.05 GPIOB->BSRR = GPIO_BSRR_BS5; // Set bit >
А сбрасывать его будет в главном цикле. В результате мы получим иголки с частотой 1Кгц которые хорошо видно осциллографом 🙂
Вот так выглядит полный код примера:
#include «stm32f10x.h» #define F_CPU 72000000UL #define TimerTick F_CPU/1000-1 void Delay(uint32_t Val); //SysTick Interrupt void SysTick_Handler(void) < GPIOB->BSRR = GPIO_BSRR_BS5; // Set bit > int main(void) < SystemInit(); RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // Config CRL GPIOB->CRL &=
GPIO_CRL_CNF5; // Clear CNF bit 5. Mode 00 — Push-Pull GPIOB->CRL |= GPIO_CRL_MODE5_0; // Set bit MODE0 for pin 5. Mode 01 = Max Speed 10MHz SysTick->LOAD=TimerTick; SysTick->VAL=TimerTick; SysTick->CTRL= SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;; while(1) < GPIOB->BSRR = GPIO_BSRR_BR5; // Clear bit > >
Собрал пример на PB II — ARM. Накинул джампер на B05. Зацепил щуп осциллографа, прошил контроллер…
И осциллограф радостно показал игольчатую картину на экране:
▌Функции CMSIS
У Таймера, раз он часть ядра, есть функция конфигурации в CMSIS. Описана она в core_cm3.h и выглядит так:
static __INLINE uint32_t SysTick_Config(uint32_t ticks) < if (ticks >SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */ SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) — 1; /* set reload register */ NVIC_SetPriority (SysTick_IRQn, (1 VAL = 0; /* Load the SysTick Counter Value */ SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */ return (0); /* Function successful */ >
То же самое, что и мы сделали вручную. Только есть проверка на корректность значения и обрезка лишних битов. Возвращает 1 если данные некорректные. Ну и еще там приоритет ставится если ядро М0 (это кстати хз зачем? У M0 же должна быть своя версия CMSIS?)
Так что таймер можно загрузить и следующей фигней:
Для LPC все совершенно аналогично. До последней буковки можно сказать. Т.к. это фича не STM32, а ядра и описана в CMSIS. Т.е. едина для всех.
Спасибо. Вы потрясающие! Всего за месяц мы собрали нужную сумму в 500000 на хоккейную коробку для детского дома Аистенок. Из которых 125000+ было от вас, читателей EasyElectronics. Были даже переводы на 25000+ и просто поток платежей на 251 рубль. Это невероятно круто. Сейчас идет заключение договора и подготовка к строительству!
А я встрял на три года, как минимум, ежемесячной пахоты над статьями :)))))))))))) Спасибо вам за такой мощный пинок.
14 thoughts on “ARM. Учебный Курс. SysTick — Системный таймер”
Неужели вернулся тот DI HALT, который пишет учебные курсы 🙂
Я ждал и верил!
Источник
STM32 µC: задержка SysTick не работает внутри обработчика прерываний
Поэтому я написал программу для STM32F103C8T6 микроконтроллер на С, использующий RTC (часы реального времени) и дисплейный модуль.
RTC и дисплей работают нормально, но когда я пытаюсь обновить дисплей из обработчика прерываний RTC, он не работает.
Когда я пишу что-то на дисплей изнутри main() , это работает просто отлично.
Обработчик прерываний также работает, поэтому я решил, что проблема заключается в функции записи на дисплей.
Эта функция использует небольшие задержки для передачи бит-связи с контроллером дисплея.
Я ранее использовал SysTick чтобы сгенерировать такие задержки:
Но почему-то внутри обработчика прерываний RTC это не работает. Поэтому я заменил свою функцию задержки на эту, не используя SysTick:
и теперь все работает просто отлично.
Я пытаюсь понять, почему SysTick очевидно, не работает внутри обработчика прерываний RTC.
Я подумал, что, возможно, это вызвано приоритетами прерываний, но в соответствии с таблицей, по умолчанию SysTick Interrupt приоритет выше, чем RTC прерывает приоритет.
Может быть, кто-то может объяснить, почему это происходит?
EDIT1: Итак, я прочитал немного больше о приоритетах прерывания, и мне кажется, что мне нужно правильно настроить NVIC_IRQChannelPreemptionPriority. Я попробую это, как только смогу.
Что касается задержки внутри прерывания, я знаю, что это неправильный способ сделать это, но я все же хотел понять поведение программы
РЕДАКТИРОВАТЬ 2: Я просто попытался изменить приоритеты прерываний следующим образом:
и теперь задержка SysTick работает в обработчике прерывания RTC.
1 ответ
Ваш способ реализации задержки с помощью SysTick такой же, как HAL_Delay в библиотеке HAL из ST. Это работает так, что SysTick постоянно увеличивает (обычно с интервалом в 1 мс) переменную — systick_count в вашем случае — это сравнивается с локальной переменной внутри функции задержки — start в твоем случае.
Здесь нужно отметить две вещи:
systick_count необходимо сделать изменчивым, чтобы заставить перечитывать его значение перед каждым сравнением. Это модифицируется прерыванием, а не внутри этой функции. Ваш компилятор может решить оптимизировать эту часть кода.
Прерывание SysTick должно быть «запущено» при вызове функции задержки. Если это не так, например, потому что вы находитесь в прерывании с более высоким приоритетом (численно более низким), то вы находитесь в тупике, и функция задержки никогда не завершится, потому что SysTick никогда не будет увеличивать счетчик, что никогда не происходит. Что касается этого пункта — не принимайте вещи и полагайтесь только на таблицу. Идите и на самом деле убедитесь, что он работает во время задержки. Либо проверяйте значения регистра NVIC в момент вызова функции задержки, либо просто устанавливайте точку останова в SysTick, когда вы находитесь в прерывании RTC — он должен достичь этой точки останова, пока вы находитесь в функции задержки. Если это не так — вам, вероятно, нужно настроить приоритеты прерываний.
Что касается самой идеи задержки ваших прерываний — это обычно считается плохой практикой. Предполагается, что прерывания используются для выполнения очень небольших частей работы очень быстро, например, для установки флага, что что-то должно быть сделано в основном программном коде, или для помещения полученного байта в очередь для последующей обработки. Хотя это может показаться неочевидным в простой программе, в которой у вас есть только одно прерывание (два, если вы учитываете Systick), оно очень быстро станет проблематичным, когда вы добавите больше прерываний, и столкнется с такими проблемами, как потеря информации, поскольку каждое прерывание блокирует каждое другие, и они не звонят достаточно быстро.
Источник