- Регистрация
- 23 Авг 2023
- Сообщения
- 3,969
- Реакции
- 0
- Баллы
- 36
Ofline
В этом тексте я покажу, что можно сделать, если у вас закончились все аппаратные таймеры в микроконтроллере. Также Вы узнаете как из 32-битного таймера сделать 64-битный таймер.
В разработке на микроконтроллерах upTime значение нужно по разным причинам. Для тайм штампов в записях лога. Для управления планировщиком, для измерения времени выполнения участка с кодом и прочего.
В ARM Cortex-M процессорах помимо SysTick есть еще один 32 битный таймер по имени DWT. Этот таймер увеличивается на +1 каждый тик ядра. Если вы работаете на частоте 144MHz, то инкрементация будет происходить каждые 6,94 ns. Как же воспользоваться этим таймером?
Физический адрес регистров таймера начинается с 0xE0001000. Первым делом надо проинициализировать DWT. Как видите, я специально сделал возможность определять несколько экземпляров таймера, так как существуют еще и трёх ядерные микроконтроллеры.
Как быть с тем, что DWT таймер переполняется? На частоте 168MHz переполнение будет происходить каждые 25.56 sec. Согласитесь мало пользы от таймера, который считает только до 25 с. При этом у нас нет прерывания, которое срабатывает при переполнении, как в случае с аппаратными таймерами или SysTick. Ответ прост. Придется вручную следить за переполнением программным образом. Придется периодически опрашивать DWT таймер с периодом меньшим, чем период переполнения, например каждые 5 сек. То есть вызывать функцию dwt_get_run_time_counter_u64.
Функция dwt_get_run_time_counter_u64 не просто читает значение счетчика из регистров, она выявляет факт переполнения этого счетчика, сравнивая показания с предыдущим значением. Если нарушена монотонность, значит случилось переполнение. В случае переполнения в переменную up_time надо прибавить константу 0xFFFFFFFF.
Теперь, благодаря работе с 64-битным счетчиком можно считать вплоть до 1,09e11 секунд. Это 3454 лет. Теперь можно вообще не беспокоиться за переполнение таймера.
Так как работа с типом uint64_t не является атомарной операцией, то придется на время склеивания универсального upTime значения временной войти в критическую секцию.
Осталось только преобразовать тики таймера в удобные человеко-читаемый параметр: секунды. Интерес представляют обычно миллисекунды и микросекунды. Предделители лучше заранее вычислить в функции init, так как функция get_time_stamp скорее всего будет вызыватья очень часто (hi-load процедура).
Отладка
Вот я собрал прошивку и запустил DWT таймер на частоте ядра 144 MHz. На момет 26 мин 30 сек с момента старта DWT насчитал как раз 1590490 ms = 26.5 min. Значит таймер работает.
В микроконтроллерах есть огромный калейдоскоп всяческих источников для чтения набежавшего времени. Это SysTick, дюжина аппаратных таймеров, каскадные таймеры, DWT. При корректной работе все они должны показывать приблизительно одинаковую циферку, как тут.
Достоинства DWT
++Переносимость кода. Тот же самый код драйвера DWT таймера вы сможете пере использовать на ARM Cortex-M процессоре любого вендора: STM32, Eliot, MDR32, NRF53, YunTu, Artery, CC26x6 и прочее.
++ Простота настройки. Надо сконфигурировать только один регистр. По факту, прописать требуется только один бит. После чего таймер побежит. Это намного проще, чем копаться в дюжине регистров аппаратных таймеров и вкуривать даташит.
++ Высочайшее разрешение. DWT можно смело называть бешеным таймером. На частоте ядра 168 MHz вы можете измерять временные интервалы вплоть до 5,9 ns. За это время свет проходит всего 1.7 метра. Порой невозможно тактировать аппаратные таймеры на такой высокой частоте, a DWT - можно.
Недостатки DWT
--Нет регистра пред делителя. Для конвертации в микросекунды каждый раз надо делать деление на константу. Часто просто настроить аппаратный таймер и можно атоматно считывать микросекунды.
--Низкая разрядность 32 бит. Всё таки хотелось бы получить 64 битный UpTime регистр, как в RISC-V процессорах.
Итог
Удалось научиться пользоваться DWT таймером. Удалось научиться измерять upTime с точностью до микросекунд, при этом не беспокоясь за переполнение.
Как видите, в ARM Cortex-M процессорах всегда можно воспользоваться отдельным независимым аппаратным счетчиком DWT.
Словарь
Ссылки
Вопросы
--Как из 32-битного таймера сделать 64-битный таймер?
--Что делать, если все аппаратные таймеры на микроконтроллере уже закончилсь?
В разработке на микроконтроллерах upTime значение нужно по разным причинам. Для тайм штампов в записях лога. Для управления планировщиком, для измерения времени выполнения участка с кодом и прочего.
В ARM Cortex-M процессорах помимо SysTick есть еще один 32 битный таймер по имени DWT. Этот таймер увеличивается на +1 каждый тик ядра. Если вы работаете на частоте 144MHz, то инкрементация будет происходить каждые 6,94 ns. Как же воспользоваться этим таймером?
Физический адрес регистров таймера начинается с 0xE0001000. Первым делом надо проинициализировать DWT. Как видите, я специально сделал возможность определять несколько экземпляров таймера, так как существуют еще и трёх ядерные микроконтроллеры.
Код:
/* ISO-26262 require verify configuration */
bool DwtIsValidConfig(const DwtConfig_t* const Config) {
bool res = false;
if(Config) {
res = true;
ifn(Config->DWTx) {
LOG_ERROR(LG_DWT, "DWT%u,DWTx,Err", Config->num);
res = false;
}
}
return res;
}
bool dwt_init_common(const DwtConfig_t* const Config, DwtHandle_t* const Node) {
bool res = false;
if(Config) {
if(Node) {
Node->name = Config->name;
Node->DWTx = Config->DWTx;
Node->num = Config->num;
Node->counter_freq = Config->counter_freq;
Node->valid = true;
res = true;
}
}
return res;
}
/* Table C1-24 DWT_CTRL (0xE0001000) */
typedef union{
uint32_t dword;
struct{
uint32_t CYCCNTENA:1; /*[0] Enable CYCCNT */
uint32_t POSTPRESET:4; /*[4:1] Preset (reload) value for POSTCNT */
uint32_t POSTCNT:4; /*[8:5] xxxxxxxxxxxxxx */
uint32_t CYCTAP:1; /*[9] Selects a tap on the DWT_CYCCNT register */
uint32_t SYNCTAP:2; /*[11:10] Selects a synchronization packet rate. */
uint32_t PCSAMPLENA:1; /*[12] See CYCEVTENA */
uint32_t RES1:3;// 13 14 15
uint32_t EXCTRCENA:1; /*[16] Enables exception trace. */
uint32_t CPIEVTENA:1; /* [17] Enables CPI count event. */
uint32_t EXCEVTENA:1; /*[18] Enables Exception Overhead event */
uint32_t SLEEPEVTENA:1; /*[19] Enables Sleep count event. */
uint32_t LSUEVTENA:1; /*[20] Enables LSU count event. */
uint32_t FOLDEVTENA:1; /*[21] Enables Folded-instruction count event */
uint32_t CYCEVTENA:1; /*[22] Used with PCSAMPLENA to control CYCCNT or PC sample event generation. */
uint32_t RES2:1; /*[23 ] xxxxxxxxxxxxxx */
uint32_t NOPRFCNT:1; /*[24] When set, DWT_FOLDCNT, DWT_LSUCNT, DWT_SLEEPCNT, DWT_EXCCNT, and DWT_CPICNT are not supported */
uint32_t NOCYCCNTd:1; /*[25] When set, DWT_CYCCNT is not supported */
uint32_t NOEXTTRIGc:1; /*[26] When set, no CMPMATCH[N] support */
uint32_t NOTRCPKTb:1; /*[27] When set, trace sampling and exception tracing are not supported */
uint32_t NUMCOMP:4; /*[31:28] Number of comparators available. */
};
}ARM_DWT_CTRL_t;
bool dwt_init_one(uint8_t num) {
bool res = false;
const DwtConfig_t* Config = DwtGetConfig(num);
if(Config) {
res = DwtIsValidConfig(Config);
if(res) {
LOG_WARNING(LG_DWT, "%s", DwtConfigToStr(Config));
DwtHandle_t* Node = DwtGetNode(num);
if(Node) {
res = dwt_init_common(Config, Node);
Node->DWTx->CYCCNT = 0;
Node->counter_freq = clock_core_freq_get();
Node->divider_1us = Node->counter_freq/1000000UL;
Node->divider_1ms = Node->counter_freq/1000UL;
ARM_DWT_CTRL_t DWT_CTRL;
DWT_CTRL.dword = Node->DWTx->CTRL;
DWT_CTRL.CYCCNTENA = 1; /* enable the counter */
Node->DWTx->CTRL = DWT_CTRL.dword; /* enable the counter */
Node->valid = true;
Node->init = true;
}
}
}
return res;
}
Как быть с тем, что DWT таймер переполняется? На частоте 168MHz переполнение будет происходить каждые 25.56 sec. Согласитесь мало пользы от таймера, который считает только до 25 с. При этом у нас нет прерывания, которое срабатывает при переполнении, как в случае с аппаратными таймерами или SysTick. Ответ прост. Придется вручную следить за переполнением программным образом. Придется периодически опрашивать DWT таймер с периодом меньшим, чем период переполнения, например каждые 5 сек. То есть вызывать функцию dwt_get_run_time_counter_u64.
Код:
bool dwt_proc_one(uint8_t num) {
bool res = false;
DwtHandle_t* Node = DwtGetNode(num);
if(Node) {
Node->spin++;
dwt_get_run_time_counter_u64(num);
LOG_PARN(LG_DWT, "DWT_%u,Spin:%u,Proc", num, Node->spin);
}
return res;
}
Функция dwt_get_run_time_counter_u64 не просто читает значение счетчика из регистров, она выявляет факт переполнения этого счетчика, сравнивая показания с предыдущим значением. Если нарушена монотонность, значит случилось переполнение. В случае переполнения в переменную up_time надо прибавить константу 0xFFFFFFFF.
Код:
uint64_t dwt_get_run_time_counter_u64(uint8_t num) {
uint64_t up_time_u64 = 0;
DwtHandle_t* Node = DwtGetNode(num);
if(Node) {
enter_critical();
Node->up_time_u32 = Node->DWTx->CYCCNT;
if(Node->up_time_u32 < Node->up_time_u32_prev) {
Node->wrap_counter += DWT_U32_OVERFLOW_VALUE;
}
Node->up_time_u32_prev = Node->up_time_u32;
exit_critical();
Node->up_time_u64 = Node->wrap_counter | Node->up_time_u32;
up_time_u64 = Node->up_time_u64;
}
return up_time_u64;
}
Теперь, благодаря работе с 64-битным счетчиком можно считать вплоть до 1,09e11 секунд. Это 3454 лет. Теперь можно вообще не беспокоиться за переполнение таймера.
Так как работа с типом uint64_t не является атомарной операцией, то придется на время склеивания универсального upTime значения временной войти в критическую секцию.
Код:
bool isFromInterrupt(void) {
bool res = false;
res = ((SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) != 0);
return res;
}
void enter_critical(void) {
if(!isFromInterrupt()) {
if(critical_nesting_level == 0) {
disable_interrupt();
}
critical_nesting_level++;
}
}
void exit_critical(void) {
if(!isFromInterrupt()) {
if(critical_nesting_level) {
critical_nesting_level--;
if(critical_nesting_level == 0) {
enable_interrupt();
}
}
}
}
Осталось только преобразовать тики таймера в удобные человеко-читаемый параметр: секунды. Интерес представляют обычно миллисекунды и микросекунды. Предделители лучше заранее вычислить в функции init, так как функция get_time_stamp скорее всего будет вызыватья очень часто (hi-load процедура).
Код:
uint32_t dwt_get_time_ms32(uint8_t num){
uint32_t time_ms32=0;
DwtHandle_t* Node = DwtGetNode(num);
if(Node) {
uint64_t counter_u64 = dwt_get_run_time_counter_u64(num);
time_ms32 =(uint32_t) (counter_u64 /Node->divider_1ms);
}
return time_ms32;
}
uint64_t dwt_get_time_us64(uint8_t num){
uint64_t time_us64=0;
DwtHandle_t* Node = DwtGetNode(num);
if(Node) {
uint64_t counter_u64 = dwt_get_run_time_counter_u64(num);
time_us64 = (counter_u64 /Node->divider_1us);
}
return time_us64;
}
Отладка
Вот я собрал прошивку и запустил DWT таймер на частоте ядра 144 MHz. На момет 26 мин 30 сек с момента старта DWT насчитал как раз 1590490 ms = 26.5 min. Значит таймер работает.
В микроконтроллерах есть огромный калейдоскоп всяческих источников для чтения набежавшего времени. Это SysTick, дюжина аппаратных таймеров, каскадные таймеры, DWT. При корректной работе все они должны показывать приблизительно одинаковую циферку, как тут.
Достоинства DWT
++Переносимость кода. Тот же самый код драйвера DWT таймера вы сможете пере использовать на ARM Cortex-M процессоре любого вендора: STM32, Eliot, MDR32, NRF53, YunTu, Artery, CC26x6 и прочее.
++ Простота настройки. Надо сконфигурировать только один регистр. По факту, прописать требуется только один бит. После чего таймер побежит. Это намного проще, чем копаться в дюжине регистров аппаратных таймеров и вкуривать даташит.
++ Высочайшее разрешение. DWT можно смело называть бешеным таймером. На частоте ядра 168 MHz вы можете измерять временные интервалы вплоть до 5,9 ns. За это время свет проходит всего 1.7 метра. Порой невозможно тактировать аппаратные таймеры на такой высокой частоте, a DWT - можно.
Недостатки DWT
--Нет регистра пред делителя. Для конвертации в микросекунды каждый раз надо делать деление на константу. Часто просто настроить аппаратный таймер и можно атоматно считывать микросекунды.
--Низкая разрядность 32 бит. Всё таки хотелось бы получить 64 битный UpTime регистр, как в RISC-V процессорах.
Итог
Удалось научиться пользоваться DWT таймером. Удалось научиться измерять upTime с точностью до микросекунд, при этом не беспокоясь за переполнение.
Как видите, в ARM Cortex-M процессорах всегда можно воспользоваться отдельным независимым аппаратным счетчиком DWT.
Словарь
Сокращение | Расшифровка |
DWT | Data Watchpoint and Trace unit |
ARM | Advanced RISC Machine |
RISC | reduced instruction set computer |
Ссылки
Название | URL |
Счётчик DWT | |
Cycle Counting on ARM Cortex-M with DWT |
Вопросы
--Как из 32-битного таймера сделать 64-битный таймер?
--Что делать, если все аппаратные таймеры на микроконтроллере уже закончилсь?