реклама на сайте
подробности

 
 
3 страниц V   1 2 3 >  
Reply to this topicStart new topic
> STM32. Функция задержки, программные задержки на таймере
Влад Р.
сообщение Jun 27 2012, 19:47
Сообщение #1


Частый гость
**

Группа: Свой
Сообщений: 87
Регистрация: 9-12-10
Пользователь №: 61 511



Написал функцию задержки на базовом таймере.
В одной программе работает отлично, но вызывается правда постоянно с одним и тем же значением. В другой то ли работает криво, то ли не работает вообще:
Код
#define APB1_FREQ 24000000

void delay_ms (uint16_t volatile ms) {
  TIM7->CR1 &= ~0xFF;                               // Сбрасываем регистр управления таймера TIM7
  if (ms != 1) {                                    // Если значение задержки НЕ равно 1,
  TIM7->PSC = APB1_FREQ/1000-1;                     // настраиваем таймер TIM7 на период 1 мс,
  TIM7->ARR = ms-1;                                 // установить конечное значение счёта с учетом импульса переполнения;
  } else {                                          // иначе,
      TIM7->PSC = 2*APB1_FREQ/1000-1;               // настраиваем таймер TIM7 на период в 2 раза меньше - 0,5 мс
      TIM7->ARR = 1; }                              // установить конечное значение счёта на 1 с учетом импульса переполнения
  TIM7->CR1 |= (TIM_CR1_OPM | TIM_CR1_CEN);         // Установить режим "одного импульса" и включить таймер TIM7
  while(!(TIM7->SR & TIM_SR_UIF));                  // Дождаться конца задержки
  TIM7->SR &= ~TIM_SR_UIF;                          // Сбросить флаг прерывания
};


Не пойму почему не выдерживается задержка? wacko.gif

ЗЫ. Таймер тактирован в основной программе:
Код
RCC->APB1ENR |= RCC_APB1ENR_TIM7EN;   // Тактируем базовый таймер TIM7
Go to the top of the page
 
+Quote Post
skripach
сообщение Jun 27 2012, 20:58
Сообщение #2


■ ■ ■ ■
*****

Группа: Свой
Сообщений: 1 100
Регистрация: 9-08-06
Пользователь №: 19 443



Ну сделайте вы по людски, что-нибуди вроде:

Код
volatile uint32 ms_timer;

void ms_interrupt(void)
{
  if(ms_timer)
    ms_timer--;
}

void delay_ms (uint32_t ms)
{
  ms_timer=ms;
  while(ms_timer);
}





--------------------
Делай что должен и будь что будет.
Go to the top of the page
 
+Quote Post
Влад Р.
сообщение Jun 27 2012, 21:01
Сообщение #3


Частый гость
**

Группа: Свой
Сообщений: 87
Регистрация: 9-12-10
Пользователь №: 61 511



Цитата(skripach @ Jun 27 2012, 23:58) *
Ну сделайте вы по людски


За идею спасибо, попробую. Но что "нелюдского" в моем коде?
Go to the top of the page
 
+Quote Post
Сергей Борщ
сообщение Jun 27 2012, 21:21
Сообщение #4


Гуру
******

Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095



QUOTE (Влад Р. @ Jun 28 2012, 00:01) *
что "нелюдского" в моем коде?
Вы сбрасываете флаг переполнения после окончания задержки. До следующего вызова функции таймер может дотикать до еще одного переполнения, флаг которого вы и обнаружите при следующем вызове. Сбрасывать флаг нужно пока таймер не тикает (или сразу после программирования периода, если таймер остановить нельзя).

Ну и очищать регистр, про который известно, что старший байт всегда читается как ноль наложением маски ~0xFF неколько нерационально. Почему сразу не записать в него ноль, сэкономив одно чтение этого регистра и промежуточные операции? Аналогично и запись в регистр, содержащий ноль методом наложения маски по "ИЛИ" тоже выглядит "неаккуратненько"


--------------------
На любой вопрос даю любой ответ
"Write code that is guaranteed to work, not code that doesn’t seem to break" (C++ FAQ)
Go to the top of the page
 
+Quote Post
Влад Р.
сообщение Jun 27 2012, 21:37
Сообщение #5


Частый гость
**

Группа: Свой
Сообщений: 87
Регистрация: 9-12-10
Пользователь №: 61 511



Цитата(Сергей Борщ @ Jun 28 2012, 00:21) *
Ну и очищать регистр, про который известно, что старший байт всегда читается как ноль наложением маски ~0xFF неколько нерационально. Почему сразу не записать в него ноль, сэкономив одно чтение этого регистра и промежуточные операции? Аналогично и запись в регистр, содержащий ноль методом наложения маски по "ИЛИ" тоже выглядит "неаккуратненько"

Виновен по обоим пунктам. Действительно не по-людски. Переделаю. Но погоды это делать не может.

Цитата(Сергей Борщ @ Jun 28 2012, 00:21) *
Вы сбрасываете флаг переполнения после окончания задержки. До следующего вызова функции таймер может дотикать до еще одного переполнения, флаг которого вы и обнаружите при следующем вызове. Сбрасывать флаг нужно пока таймер не тикает (или сразу после программирования периода, если таймер остановить нельзя).

Дотикать не должен, должен отключиться сразу как только досчитает. Таймер я запускаю с установкой флага TIM_CR1_OPM. "One-pulse mode - counter stops counting at the next update event (clearing CEN bit)." Это ведь так работает?
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Jun 27 2012, 21:57
Сообщение #6


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Попробуйте после записи значений ARR и PSC сделать
Код
TIM7->EGR |= TIM_EGR_UG;

Это сгенерирует "update event". По нему ARR и прескалер грузятся из предварительного в теневой регистр. То есть, без этого действуют предыдущие значения.

Добавка.
Цитата(Влад Р. @ Jun 28 2012, 01:47) *
Код
    TIM7->PSC = 2*APB1_FREQ/1000-1;               // настраиваем таймер TIM7 на период в 2 раза меньше - 0,5 мс

Здесь вы настроили на период в два раза больше.


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post
Влад Р.
сообщение Jun 27 2012, 22:20
Сообщение #7


Частый гость
**

Группа: Свой
Сообщений: 87
Регистрация: 9-12-10
Пользователь №: 61 511



Цитата(AHTOXA @ Jun 28 2012, 00:57) *
Попробуйте после записи значений ARR и PSC сделать
Код
TIM7->EGR |= TIM_EGR_UG;

Это сгенерирует "update event". По нему ARR и прескалер грузятся из предварительного в теневой регистр. То есть, без этого действуют предыдущие значения.


По-моему, это "побочное" действие данной операции и, если таймер не запущен то ARR заносится программой сразу. Могу ошибаться... Поправьте, если не прав.

Цитата(AHTOXA @ Jun 28 2012, 00:57) *
Здесь вы настроили на период в два раза больше.

Деллитель в 2 раза больше, значит частное (частота импульсов таймера) будет в 2 раза меньше. Ага, период в 2 раза больше. Исправим. Я попутал частоту и период...

Сообщение отредактировал Влад Р. - Jun 27 2012, 22:43
Go to the top of the page
 
+Quote Post
Влад Р.
сообщение Jun 27 2012, 23:45
Сообщение #8


Частый гость
**

Группа: Свой
Сообщений: 87
Регистрация: 9-12-10
Пользователь №: 61 511



Проблема решилась установкой бита ARPE в управляющем регистре сразу после его сброса (буферизирует новое значение ARR в теневом регистре до события UEV):
Код
TIM7->CR1 = TIM_CR1_ARPE | TIM_CR1_OPM;


Но в чем косяк был до этого все равно не пойму sad.gif Вроде должно было и без него работать. Почему этот бит так важен для этой функции, на чем функцию клинило без него? Возможно спецы подскажут?

Еще одно: правильно ли я понимаю принцип генерации события таймером. Допустим конечное значение счета ARR=3. Таймер будет считать от 0 до 3 и только на 4-м импульсе возникнет переполнение и будет сгенерировано событие? Или событие сгенерируется уже на 3-м?
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Jun 28 2012, 03:51
Сообщение #9


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Короче, так.
Регистр PSC обновляется только при update event.
Если ARPE = 1, то ARR тоже обновляется только при update event (иначе сразу).
Так что установка ARPE в 1 вам не поможет, потому что прескалер всё равно буферизован. Сделайте как я написал, и всё заработаетsm.gif
(Update event - происходит при переполнении или вручную, взведением бита UG в регистре EGR.)


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post
sgs
сообщение Jun 28 2012, 04:36
Сообщение #10


Участник
*

Группа: Участник
Сообщений: 54
Регистрация: 25-01-06
Из: Самара
Пользователь №: 13 578



АНТОХА написал все правильно. Есть только один тонкий момент: нельзя запускать таймер сразу после UG. Требуется несколько тактов процессора, чтобы произошла реальная загрузка счетчика. Только после этого заданный интервал отрабатывается правильно. Для этого вполне достаточно оформить загрузку в виде процедуры, а запуск делать вне этой процедуры.
Go to the top of the page
 
+Quote Post
scifi
сообщение Jun 28 2012, 05:39
Сообщение #11


Гуру
******

Группа: Свой
Сообщений: 3 020
Регистрация: 7-02-07
Пользователь №: 25 136



Странный выбор таймера для задержки, если только в образовательных целях.
Для этих целей лучше подходит SysTick, а ещё лучше - вот этот счётчик циклов процессора (он 32-разрядный, поэтому арифметика с ним простейшая).
Go to the top of the page
 
+Quote Post
Влад Р.
сообщение Jun 28 2012, 11:21
Сообщение #12


Частый гость
**

Группа: Свой
Сообщений: 87
Регистрация: 9-12-10
Пользователь №: 61 511



Цитата(AHTOXA @ Jun 28 2012, 06:51) *
Регистр PSC обновляется только при update event.
(Update event - происходит при переполнении или вручную, взведением бита UG в регистре EGR.)


Новый предделитель не будет установлен, пока не произойдет update event, даже если таймер выключен (CEN=0) ? Т.е. даже если таймер выключен, я все равно должен установить бит UG в регистре EGR, чтобы обновить значение предделителя, правильно?

С учетом вышесказанного код преобразился в следующий:
Код
void delay_ms (uint16_t ms) {
  TIM7->PSC = APB1_FREQ/(((ms == 1) ? 2 : 1)*1000)-1;       // Настраиваем таймер TIM7 на период 0.5 мс для задержки в 1 мс
                                                            // и на период 1 мс для всех остальных значений
  TIM7->EGR = TIM_EGR_UG;                                   // Сгенерировать событие для загрузки нового значения предделителя
  TIM7->SR &= ~TIM_SR_UIF;                                  // Сбросить флаг прерывания
  TIM7->ARR = (ms == 1) ? 1 : (ms-1);                       // Установить конечное значение счёта с учетом импульса переполнения
  TIM7->CR1 = TIM_CR1_OPM | TIM_CR1_CEN;                    // Установить режим "одного импульса" и включить таймер TIM7
  while(!(TIM7->SR & TIM_SR_UIF));                          // Дождаться конца задержки
  TIM7->SR &= ~TIM_SR_UIF;                                  // Сбросить флаг прерывания
};


Не реализовано только это:
Цитата(sgs @ Jun 28 2012, 07:36) *
Есть только один тонкий момент: нельзя запускать таймер сразу после UG. Требуется несколько тактов процессора, чтобы произошла реальная загрузка счетчика. Только после этого заданный интервал отрабатывается правильно. Для этого вполне достаточно оформить загрузку в виде процедуры, а запуск делать вне этой процедуры.

Не пойму что имеется в виду "оформить загрузку в виде процедуры". Сделать загрузку в виду отдельной функции?

Цитата(scifi @ Jun 28 2012, 08:39) *
Странный выбор таймера для задержки, если только в образовательных целях.
Для этих целей лучше подходит SysTick, а ещё лучше - вот этот счётчик циклов процессора (он 32-разрядный, поэтому арифметика с ним простейшая).

Почему странный выбор? Простой таймер - для простой задачи. Вот это действительно интересно, обязательно попробую с ним разобраться. Жаль есть он только в M3, да и SysTick в M0 может отсутствовать. А это "минус" для переносимости кода.

Кто с этим может подсказать, из ДШ это не совсем ясно:
Цитата(Влад Р. @ Jun 28 2012, 02:45) *
Еще одно: правильно ли я понимаю принцип генерации события таймером. Допустим конечное значение счета ARR=3. Таймер будет считать от 0 до 3 и только на 4-м импульсе возникнет переполнение и будет сгенерировано событие? Или событие сгенерируется уже на 3-м?

Иными словами, нужно ли в ARR заносить задержку на один импульс меньше желаемой или это лишнее?

Сообщение отредактировал Влад Р. - Jun 28 2012, 15:26
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Jun 28 2012, 16:52
Сообщение #13


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Цитата(Влад Р. @ Jun 28 2012, 17:21) *
Новый предделитель не будет установлен, пока не произойдет update event, даже если таймер выключен (CEN=0) ? Т.е. даже если таймер выключен, я все равно должен установить бит UG в регистре EGR, чтобы обновить значение предделителя, правильно?

Да.
Цитата(Влад Р. @ Jun 28 2012, 17:21) *
С учетом вышесказанного код преобразился в следующий:

Лучше писать в ARR до генерации события (чтобы не зависеть от значения ARPE):
Код
void delay_ms (uint16_t ms) {
  TIM7->PSC = APB1_FREQ/(((ms == 1) ? 2 : 1)*1000)-1;       // Настраиваем таймер TIM7 на период 0.5 мс для задержки в 1 мс
                                                            // и на период 1 мс для всех остальных значений
  TIM7->ARR = (ms == 1) ? 1 : (ms-1);                       // Установить конечное значение счёта с учетом импульса переполнения
  TIM7->EGR = TIM_EGR_UG;                                   // Сгенерировать событие для загрузки нового значения предделителя
  TIM7->SR &= ~TIM_SR_UIF;                                  // Сбросить флаг прерывания
  TIM7->CR1 = TIM_CR1_OPM | TIM_CR1_CEN;                    // Установить режим "одного импульса" и включить таймер TIM7
  while(!(TIM7->SR & TIM_SR_UIF));                          // Дождаться конца задержки
  TIM7->SR &= ~TIM_SR_UIF;                                  // Сбросить флаг прерывания
};

К тому же сброс флага прерывания после генерации события вполне сойдёт за паузу перез запуском таймера (хотя я лично не встречал упоминаний о её необходимости).
Цитата(Влад Р. @ Jun 28 2012, 17:21) *
Иными словами, нужно ли в ARR заносить задержку на один импульс меньше желаемой или это лишнее?

Да, на один меньше. Для задержки на один такт нужно занести значение 0.


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post
Влад Р.
сообщение Jun 28 2012, 17:51
Сообщение #14


Частый гость
**

Группа: Свой
Сообщений: 87
Регистрация: 9-12-10
Пользователь №: 61 511



Цитата(AHTOXA @ Jun 28 2012, 19:52) *
Для задержки на один такт нужно занести значение 0.


Да вроде как нельзя так делать, иначе бы я не городил вот эти конструкции:
Код
((ms == 1) ? 2 : 1)

Код
(ms == 1) ? 1 : (ms-1)


Вот фраза из описания регистра TIMx_ARR: "The counter is blocked while the auto-reload value is null"

ЗЫ AHTOXA, огромное спасибо за активную помощь!

Сообщение отредактировал Влад Р. - Jun 28 2012, 17:53
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Jun 28 2012, 18:03
Сообщение #15


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Да, это я лопухнулсяsm.gif Значит, не надо уменьшать на 1.


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post

3 страниц V   1 2 3 >
Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 21st July 2025 - 19:56
Рейтинг@Mail.ru


Страница сгенерированна за 0.01528 секунд с 7
ELECTRONIX ©2004-2016