Формирование интервалов времени
Это короткая заметка про то, как с помощью аппаратного таймера, который имеется в микроконтроллере CH32V003, сформировать пару сигналов.
Поставленная задача чем-то напоминает задачу ШИМ (широтно-импульсной модуляции). Но в отличие от классической ШИМ-задачи, в описываемой здесь задаче не используется модуляция (то есть ширина импульса не меняется) и не предполагается использование (аппаратного) сигнала, который обычно выводится на ножки МК и который подаётся на управляющие выводы силовых транзисторов, например, в блоке питания. Хотя, на самом деле в решении поставленной задачи, используются выводы портов и на них действительно выводится сигнал, но это не является целью программного кода, а сделано лишь для индикации того, что программный код работает правильно -- как задумано.
Вот временная диаграмма того, что нужно получить в программе:
Здесь LED1 и LED2 -- это состояние светодиодов, которые подключены на ножки МК. Ещё раз: светодиоды нужны -- это только для отладки, только для индикации правильности процессов.
Мне нужно сфомировать два промежутка времени. Один с короткой продолжительностью (75 мс), другой -- с длительной продолжительностью (125 мс). Оба промежутка времени должны начинаться в одно и тоже время. Период повторения промежутков времени -- 500 мс.
Я буду использовать таймер TIM2. Период повторения будет формироваться счётчиком таймера CNT, который будет тактироваться входным сигналом с частотой 1 кГц. Входную частоту счетчика я получу из тактовой частоты шины AHB1, на которой "висит" этот счётчик.
Тактовая частота ядра (SysClock) в проекте 24 МГц. Делитель частоты для шины AHB1 равен единице, то есть тактовая частота таймера -- тоже 24 МГц. У счётчика таймера имеется предделитель. Чтобы из входной частоты таймера (24 МГц) получить входную частоту для счётчика таймера (1 кГц) необходимо в предделитель загрузить значение, равное (24000 - 1). В результате, если запустить таймер, его счётчик начнет считать со скоростью 1 кГц.
Чтобы сформировать период, равный 500 мс, необходимо в регистр автозагрузки записать число 500. Счетчик увеличивает своё значение каждую миллисекунду (частота 1 кГц), поэтому ему нужно досчитать до 500. После этого счётчик сбросится в ноль, поднимет в регистре INTFR флаг прерывания UIF и начнёт следующий период.
Для формирования заданных промежутков времени я использую регистры захвата/сравнения CH3CVR и CH4CVR. В регистр CH3CVR я записываю число 75, а в регистр CH4CVR -- число 125. Поскольку счётчик инкрементируется со скоростью один раз в миллисекунду, то третьего канала (CH3) возникнет прерывание на 75 миллисекунде, а от четвёртого канала (CH4) -- на 125 миллисекунде.
Вот код инициализации таймера TIM2:
void init_tim2(void)
{
RCC->APB1PCENR |= RCC_TIM2EN; // Включаю тактирование модуля таймера
NVIC_EnableIRQ(TIM2_IRQn); // Разрешаю прерывания от таймера TIM2
TIM2->PSC = 24000 - 1; // Таймер будет считать со скоростью 1 кГц
TIM2->ATRLR = 500; // Период 500 мс
TIM2->CH3CVR = 25; // Короткий импульс 25 мс
TIM2->CH4CVR = 125; // Длинный импульс 125 мс
TIM2->INTFR = 0; // На всякий случай сбрасываю все флаги
// Разрешаю прерывания от 3-го и 4-го каналов, а также прерывание от счётчика
TIM2->DMAINTENR |= (TIM_CC4IE | TIM_CC3IE | TIM_UIE);
TIM2->CTLR1 = TIM_CEN; // И наконец запускаю таймер в работу
}
Формирование промежутков осуществляется в обработчике прерываний от таймера. Обработчик прерывания от таймера TIM2 может обслуживать несколько прерываний. В моём случае это три прерывания -- одно от счётчика и два от каналов захвата/сравнения.
Вот как это делается:
__attribute__((interrupt("WCH-Interrupt-fast")))
void TIM2_IRQHandler(void)
{
// Прерывание от счётчика. Отрабатываю период.
if ((TIM2->DMAINTENR & TIM_UIE) && (TIM2->INTFR & TIM_UIF))
{
TIM2->INTFR &= ~TIM_UIF; // Снимаю флаг прерывания
// Начало периода. Включаю оба светодиода.
led1_on(); // Светодиоды нужны только для отладки кода программы
led2_on();
... // Выполняю некоторую полезную работу
}
// Прерывание от канала CH3. Отрабатываю короткий промежуток времени.
if ((TIM2->DMAINTENR & TIM_CC3IE) && (TIM2->INTFR & TIM_CC3IF))
{
TIM2->INTFR &= ~TIM_CC3IF; // Снимаю флаг прерывания
led1_off(); // Выключаю светодиод LED1
... // Выполняю некоторую полезную работу
}
// Прерывание от канала CH4. Отрабатываю длинный промежуток времени.
if ((TIM2->DMAINTENR & TIM_CC4IE) && (TIM2->INTFR & TIM_CC4IF))
{
TIM2->INTFR &= ~TIM_CC4IF; // Снимаю флаг прерывания
led2_off(); // Выключаю светодиод LED2
... // Выполняю некоторую полезную работу
}
}
В начале периода обработчик прерывания зажигает оба светодиода. Каждый светодиод гасится своим каналом согласно заданному промежутку времени.
Не пытайтесь понять назначение этой задачи и не пытайтесь оптимизировать её решение. Задача несколько синтезированная. Реальная задача, из которой получилась эта статья, несколько иная. Если бы я описывал реальную задачу, то потерялась бы суть и простота описания работы с таймером. Поэтому я упростил задачу на столько, что она почти выродилась. Мне было важно показать, что работать с таймером не так уж тяжело.
В реальной задаче мне нужны времена не в миллисекундах, а в микросекундах. Но это сути не меняет. Да, я понимаю, что время входа в прерывание может составлять около микросекунды. Но в реальной задаче допускается джиттер до примерно 5 мкс. И, как я уже говорил, в реальной задаче не используется вывод сигналов на порты. Реальная задача работает не с внешними сигналами, а со значениями внутренних переменных.
На всякий случай сообщу, что на Telegram имеется канал по микроконтроллерам CH32V t.me/ch32v003 и есть группа https://t.me/ch32v, в которой можно пообщаться с коллегами.
Изменено пользователем zhevak
0 Комментариев
Рекомендуемые комментарии
Комментариев нет
Присоединяйтесь к обсуждению
Вы публикуете как гость. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.
Примечание: Ваш пост будет проверен модератором, прежде чем станет видимым.