Перейти к содержанию

CH32V

  • записей
    8
  • комментариев
    12
  • просмотров
    3 086

Systick


zhevak

3 050 просмотров

Системный таймер SysTick состоит из четырёх регистров:

2134301885_.png.50e74918ce5cd29589961bd6bcb05403.png

Все регистры 32-разрядные. Однако в регистре управления используется всего пять бит, а в регистре состояния -- так вообще один бит.

photo_2023-09-26_18-38-37_0.jpg.034c0f6994fa1828bc5520e8196e8edd.jpg

2023-09-26_18-38-37_1.jpg.5c76e3d92e3a1a1e006bfb69ee548c6b.jpg

Базовый адрес регистров Системного таймера 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 МГц. А если тактовая частота ядра будет меньше, то и временнОй интервал, формируемый чисто аппаратно, будет ещё длиннее. Куда уж ещё больше-то! И в прям какое-то безумие!

2023-09-26_18-38-37_2.jpg.c6be960347de65ca3cf01c86d93fef82.jpg

Таймер считает до значения, заданного в регистре 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())
    {
      // Сделать что-то полезное по прошествии заданного промежутка времени
      ...
    }
  }
}

Наверно нужно пояснить, зачем нужно было писать такой странный код с использование программного флага.

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

Если же в предстоит по таймеру отрабатывать много кода, который будет работать много времени, то в этом случае лучше не "грузить" прерывание от таймера, а вынести обработку тяжёлого кода куда-нибудь в главный регистр. Собственно, это продемонстрировано в примере кода, приведённого выше.

Ещё один скользкий момент -- почему использование вызовов функций из прерывания дурной опасный код.

Дело в том, что многие библиотеки (и вполне может так случится, что и код ваших функций) написаны без оглядки на использование их во многопоточных программах. Если это так, то программа неминуемо потерпит крах. Вы можете удивится -- откуда в вашей программе несколько потоков? Это элементарно! Если вы используете прерывания, то как минимум их будет два или больше. Один поток исполнения -- это главный поток, это главный цикл в вашей программе. Второй поток -- это любое прерывание. В случае примера, приведённого выше -- прерывание от системного таймера.

А теперь представьте, что в главном цикле программы совершается какая-то обработка данных какой-то, допустим проверенной-перепроверенной библиотечной функцией, которая не рассчитана на многопоточность. В какой-то момент времени, когда выполняется эта функция, возникает прерывание. Внутренне состояние функции остаётся, как говорят, в неконсистентном состоянии. Ладно, если из прерывания такая функция не будет вызываться, то ничего страшного не произойдёт. А сели вы через какое-то время решите доработать свой проект, позабыв, что есть нюансы, и в каком-либо прерывании вызовите свою безобидную функцию, в которой (ах, склероз!) затесалась та самая библиотечная функция... Что произойдёт? А произойдёт следующее.

Обработчик прерывания отработает без вопросов. Опасная функция будет вызвана и она тоже отработает без вопросов -- сделает всё, как положено. Но возвращаясь из прерывания нам нужно будет продолжить выполнение прерванной функции. По факту окажется, что её данные испорчены вызовом её же копии из прерывания. Функция потерпит крах.

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

 

Изменено пользователем zhevak
добавлены фотки

2 Комментария


Рекомендуемые комментарии

Стоило указать, что systick это именно ядерный таймер, прописанный прямо в стандарте risc-v, а не периферия, специфичная для какого-то конкретного контроллера. Потому и адрес начала такой странный. Соответственно и возможностей у него куда меньше, чем у периферийных: всего один регистр сравнения, нет ШИМ и т.д. И область применение у него куда проще: это либо глобальный счет времени для задержек, либо таймер для операционной системы, точнее для ее переключалки задач. Разумеется, никто не запрещал объединять.

Впрочем, для счета времени в risc-v есть и другие способы. Например, CSR-регистр mcycle, который считает такты ядра и доступен только для чтения:

inline uint32_t read_mcycles(){
  uint32_t res;
  asm volatile("csrr %0, mcycle" : "=r"(res) );
  return res;
}
Цитата

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

Если это ОС, то, естественно, код будет прямо в обработчике. Если счет глобального времени - тоже. А если это юзерский код, то его лучше расположить в периферийном таймере. Или использовать тот самый глобальный счет времени:

void some_user_func(){
  const uint32_t t_delay = 100500;
  static uint32_t t_prev = 0;
  uint32_t t_cur = read_mcycles();
  if( (t_cur - t_prev) < t_delay )return;
  t_prev = t_cur;
  //some code
}
Цитата

KingKeV2

Опечатка: ядро называется QingKe RISC-V2A

Цитата

Все регистры 32-разрядные.

А вот этот момент хорошо бы уточнить. В другом risc-v контроллере gd32vf103, да и в ch32v307 пишут, что счетчик вообще 64-битный.  Хотя у меня чего-то отложилось, что у gd-шки всего 24 бита. Но, может, я чего-то неправильно запомнил. Если это правда, он и без делителя вообще любые интервалы считать может, хоть столетия.

Ссылка на комментарий

Присоединяйтесь к обсуждению

Вы публикуете как гость. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.

Гость
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Добавить комментарий...

×   Вставлено с форматированием.   Восстановить форматирование

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

Загрузка...

×
×
  • Создать...