Systick
Системный таймер SysTick состоит из четырёх регистров:
Все регистры 32-разрядные. Однако в регистре управления используется всего пять бит, а в регистре состояния -- так вообще один бит.
Базовый адрес регистров Системного таймера 0xE000F000. Регистры располагаются относительно базового адреса со смещением, которое приведено в таблице. Таким образом, узнать текущее состояние счетчика можно обратившись по адресу 0xE000F008.
uint32_t value;
...
value = *((uint32_t *) 0xE000F008); // Получить текущее значение счётчика
Но лучше пользоваться файлом core_riscv.h, в котором уже есть определения регистров таймера. Заметьте, что поскольку Системный таймер относится не к периферийным устройствам микроконтроллера, а принадлежит ядру, то определение его регистров находится в указанном файле, а не в файле ch32v00x.h. Кроме того, искать описание системного таймера также нужно не в толстой книжке ("CH32V003 Reference Manual"), а в тоненькой — "KingKeV2 Microprocessor Manual" (смотрите главу 3, страница 16).
Входная частота у Системного таймера либо HCLK, либо HCLK/8 задаётся установкой бита STCLK в регистре CTRL. Поэтому он позволяет охватить весьма внушительный диапазон времён -- практически от одно такта ядра (да-да, это какое-то безумие!) и почти до 12 минут при максимальной тактовой частоте ядра 48 МГц. А если тактовая частота ядра будет меньше, то и временнОй интервал, формируемый чисто аппаратно, будет ещё длиннее. Куда уж ещё больше-то! И в прям какое-то безумие!
Таймер считает до значения, заданного в регистре CMP. При равенстве счетчика таймера (регистр CNT) и регистра сравнения (CMP) взводится флаг CNTIF (регистр состояния SR) и, если установлен бит STIE в регистре управления (CTRL), то возникает прерывание.
Кроме того, если в регистре управления CTRL установлен бит STRE, счетчик таймера сбросится и начнётся отсчет нового периода.
Вот пример кода, в котором используется системный таймер для генерации односекундных промежутков времени.
static volatile bool _fSystick; // Флаг сработки Системного таймера
// Обработчик прерывания от системного таймера
__attribute__((interrupt("WCH-Interrupt-fast")))
void Systick_Handler(void)
{
_fSystick = true; // Поднимаю програмный флаг
SysTick->SR = 0; // Очищаю аппаратный флаг
}
// Настраиваю Ситемный таймер на генерацию односекундных интервалов
void sysytem_initSystick(void)
{
_fSystick = false;
NVIC_Enable(Systick_IRQn); // Включаю прерывание от Системного таймера
Systick->SR = 0 ;
Systick->CMP = (24000000 - 1); // Тактовая частота ядра: 24 МГц
Systick->CNT = 0;
Systick->CTRL = 0x0000000F; // Запускаю Системный таймер
}
// Определяет сработал и таймер
bool sysytem_isTimeout(void)
{
bool tmp;
__disable_irq();
{ // Защищённая секция
tmp = _fSystick; // делаю временную копию флага
_fSystick = false; // Принудительно сбрасываю флаг
} // Защищённая секция
__enable_irq();
return tmp;
}
int main(void)
{
...
__enable_irq(); // Разрешаю прерывания
while (true) // Главный цикл программы
{
...
if (system_ifTimeout())
{
// Сделать что-то полезное по прошествии заданного промежутка времени
...
}
}
}
Наверно нужно пояснить, зачем нужно было писать такой странный код с использование программного флага.
Вообще, если полезная работа, которая должна выполняться по сигналу таймера, небольшая и не содержит вызовов функций, то надобности в программном флаге нет никакой. Нужно тупо написать код в обработчик прерывания и более ни о чём не беспокоится. Пример такой лёгкой работы -- управление светодиодом.
Если же в предстоит по таймеру отрабатывать много кода, который будет работать много времени, то в этом случае лучше не "грузить" прерывание от таймера, а вынести обработку тяжёлого кода куда-нибудь в главный регистр. Собственно, это продемонстрировано в примере кода, приведённого выше.
Ещё один скользкий момент -- почему использование вызовов функций из прерывания дурной опасный код.
Дело в том, что многие библиотеки (и вполне может так случится, что и код ваших функций) написаны без оглядки на использование их во многопоточных программах. Если это так, то программа неминуемо потерпит крах. Вы можете удивится -- откуда в вашей программе несколько потоков? Это элементарно! Если вы используете прерывания, то как минимум их будет два или больше. Один поток исполнения -- это главный поток, это главный цикл в вашей программе. Второй поток -- это любое прерывание. В случае примера, приведённого выше -- прерывание от системного таймера.
А теперь представьте, что в главном цикле программы совершается какая-то обработка данных какой-то, допустим проверенной-перепроверенной библиотечной функцией, которая не рассчитана на многопоточность. В какой-то момент времени, когда выполняется эта функция, возникает прерывание. Внутренне состояние функции остаётся, как говорят, в неконсистентном состоянии. Ладно, если из прерывания такая функция не будет вызываться, то ничего страшного не произойдёт. А сели вы через какое-то время решите доработать свой проект, позабыв, что есть нюансы, и в каком-либо прерывании вызовите свою безобидную функцию, в которой (ах, склероз!) затесалась та самая библиотечная функция... Что произойдёт? А произойдёт следующее.
Обработчик прерывания отработает без вопросов. Опасная функция будет вызвана и она тоже отработает без вопросов -- сделает всё, как положено. Но возвращаясь из прерывания нам нужно будет продолжить выполнение прерванной функции. По факту окажется, что её данные испорчены вызовом её же копии из прерывания. Функция потерпит крах.
Конечно, если вы чётко понимаете, что делает ваша программа в каждый момент времени, то большой беды нет. Опасность есть, но она купирована вашим пониманием. Поэтому, если нет опыта в написании крутых программ, то лучше всю обработку данных совершать в главном цикле, а извещать его о произошедших в системе тех или иных событиях, лучше примерно таким способом, который приведён в примере.
Edited by zhevak
добавлены фотки
2 Comments
Recommended Comments
Join the conversation
You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.