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

Функция формирования задержки

Доброго всем времени суток!

Работаю на stm32f103ve в keil'е. Хочу сформировать функцию задержки. Особая точность не нужна - в пределах 1мкс вполне достаточно. Делаю так:

Настраиваю таймер:

    TIM5->ARR = 0xFFFF;            
    TIM5->PSC = 71;                //72МГцчастота шины/72 = 1Мгц тик таймера
    TIM5->CR1 = 0x0001;            //Запускаю таймер

В функции задаю значение задержки в ARR и опрашиваю флаг:

void Delay(u16 time)            
{
    TIM5->CNT = 0x0000;
    TIM5->ARR = time-1;
    while(!(TIM5->SR & 0x0001))
    {
        __nop();
    }    
    TIM5->SR&= ~0x0001;
}

 

Вобщем этот код работает только когда я вставляю его в основной цикл. Проверяю так:

GPIOE->BSRR|= GPIO_Pin_2; //Установить ногу
Delay(10);                             //Задержка 10мкс.
GPIOE->BRR|= GPIO_Pin_2;   //Сбросить ногу

 

Если я использую этот код в обработчике нажатия кнопки, то пауза куда-то исчезает и вместо положенных 10 мкс на осцилографе наблюдается 1,2мкс.

Есть подозрение, что компилятор каким-то образом оптимизирует код, исключая цикл - другого объяснения у меня нет. Уровень оптимизации - O0 (пробовал O1, O2-результат тот же). Пробовал также объявлять параметр фукции time как volatile - не помогло. Уже ветает шальная мысль написать функцию задержки на asm :( Есть ли у кого какие соображения? Может есть директивы компилятора типа:

отключить оптимизацию
...
код
...
включить оптимизацию

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Пробовал также объявлять параметр фукции time как volatile - не помогло. Уже ветает шальная мысль написать функцию задержки на asm :( Есть ли у кого какие соображения? Может есть директивы компилятора типа:
Не нужно гадать. Если вы чувствуете в себе силы написать эту функцию на асме - для вас не составит огромного труда посмотреть листинг этой функции и убедиться, что компилятор тут не при чем. Убедиться, что и volatile в параметре функции не нужен, и __nop() лишний, и компилить без оптимизации - прятать голову в песок.

 

Есть большое подозрение, что для борьбы с "чудесами" достаточно поставить сброс флага переполнения непосредственно перед циклом. А после цикла можно убрать.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Есть большое подозрение, что для борьбы с "чудесами" достаточно поставить сброс флага переполнения непосредственно перед циклом. А после цикла можно убрать.

;) Вы правы - это все невнимательность. Тема закрыта.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Есть большое подозрение, что для борьбы с "чудесами" достаточно поставить сброс флага переполнения непосредственно перед циклом. А после цикла можно убрать.

Есть нюанс - если сброс флага будет перед циклом проверки, то если вдруг после загрузки счетчика произошло прерывание на длительное время, и счетчик уже успел отсчитать до выхода из прерывания, а после этого мы сбрасываем флаг переполнения и ждем... следующего переполнения. У меня используется однократный режим, вообще не дождаться. :)

 

  TIM6->PSC = (72000000 / 10000 - 1);
  TIM6->CR1 = TIM_CR1_OPM | TIM_CR1_URS;

void Timer_delay(uint32_t time) {
  TIM6->ARR = time;            // Autoreload
  TIM6->CR1 |= 0x0001;        // Enable
  while (!TIM6->SR);            // Wait Update Interrupt Flag in Status
  TIM6->SR = 0;                // Status reset
}

 

upd. А еще у меня таймер считает в 2 раза быстрее! По осциллографу вижу. Сижу, разбираюсь, горюю.

upd2. Код исправил. Спасибо, АНТОХА!

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

    inline void _delay_loops(U32 loops) {
        asm volatile (
            "1: SUBS %[loops], %[loops], #1 \n"
            "   BNE 1b \n"
            : [loops] "+r"(loops)
        );
    }

    #define delay_us( US ) _delay_loops( (U32)((double)US * F_CPU / 3000000.0) )
    #define delay_ms( MS ) _delay_loops( (U32)((double)MS * F_CPU / 3000.0) )
    #define delay_s( S )   _delay_loops( (U32)((double)S  * F_CPU / 3.0) )

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для ukpyr

А вас прерывания и наличие буфера команд не смущают? Цифры проверяли на железе?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А еще у меня таймер считает в 2 раза быстрее! По осциллографу вижу. Сижу, разбираюсь, горюю.

Оно:

post-29684-1329730507_thumb.png

?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Оно: ?

Нет, похоже, прескалер для APB1 не задан /2. Библиотечная функция... туды ее в качель. Шукаю...

 

Да, вы правы! Оно! Не сообразил, что 36 MHz - это уже поделенное прескалером на 2. А потом умноженное.

Подправлю код, что выдал раньше.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Подправил код, перенес сброс запроса перед запуском таймера (и на две части разбил, между которыми можно не просто ждать, а сделать что-нибудь полезное).

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

На реальном железе работает, как положено.

  TIM6->PSC = (72000000 / 10000 - 1);    // Prescaler 10 kHz, 0.1 ms
  TIM6->CR1 = TIM_CR1_OPM | TIM_CR1_URS;

void Timer_start(uint32_t time)
{
  TIM6->SR = 0;                // Status reset
  TIM6->ARR = time;            // Autoreload (one pulse)
  TIM6->CR1 |= 0x0001;        // Enable
}

void Timer_wait()
{
  while (!TIM6->SR);            // Wait Update Interrupt Flag in Status
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

В очередной раз порекламирую другой подход к использованию таймера:

 

typedef        uint32_t                TTimer;
#define        TIMER_COUNTER            LPC_TMR32B0->TC
#define        START_TIMER(tmr)        tmr    = TIMER_COUNTER
#define        TIMER_VALUE(tmr)        (TIMER_COUNTER - tmr)
#define        WAIT_TIMEOUT(tmr,val)    while (!(val <= TIMER_VALUE (tmr)))

__inline void delay_us (TTimer val)
{
    START_TIMER  (TTimer Tmr);    
    WAIT_TIMEOUT (Tmr, val);
}
__inline void delay_ms (TTimer val)
{
    delay_us (val * 1000);
}

 

Используется 32-битный таймер в LPC. Таймер настраивается на частоту 1 МГц и ни разу не сбрасывается.

Никто не мешает при этом на тот же таймер вешать какие-то другие прерывания.

 

Для таймеров STM надо поменять typedef TTimer на 16-битный, чтобы эта переменная переполнялась вместе с таймером.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А вас прерывания и наличие буфера команд не смущают? Цифры проверяли на железе?
перед _delay_loops прерывания нужно запрещать, если они возможны. Цифры проверены неоднократно на реальных проектах - тело цикла выполняется за 3 такта. Проверялось также на проекте с несколькими шинами 1-wire под Protothreads
Изменено пользователем ukpyr

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

typedef uint32_t TTimer;

#define TIMER_COUNTER LPC_TMR32B0->TC

#define START_TIMER(tmr) tmr = TIMER_COUNTER

#define TIMER_VALUE(tmr) (TIMER_COUNTER - tmr)

#define WAIT_TIMEOUT(tmr,val) while (!(val <= TIMER_VALUE (tmr)))

__inline void delay_us (TTimer val)

{

START_TIMER (TTimer Tmr);

WAIT_TIMEOUT (Tmr, val);

}

Допустим, TC вот-вот переполнится. Мы запоминаем его значение в переменной tmr, и потом ждем на время, пока (TC - tmr) < val. Когда TC переходит в 0, (TC - tmr) превращается в большое число, и ваша функция рапортует о таймауте, которого на самом деле еще нет.

А для STM32 с 16-битовыми счетчиками это еще более вероятно.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Допустим, TC вот-вот переполнится. Мы запоминаем его значение в переменной tmr, и потом ждем на время, пока (TC - tmr) < val. Когда TC переходит в 0, (TC - tmr) превращается в большое число, и ваша функция рапортует о таймауте, которого на самом деле еще нет.

А для STM32 с 16-битовыми счетчиками это еще более вероятно.

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

Нормально там всё.

Допустим, мы хотим проспать 20 тактов (val).

1. Засекаем время начала ожидания: tmr = TMR;

2. Потом начинаем в цикле (или изредка, как угодно) вычислять беззнаковую разность TMR-tmr, которая будет монотонно расти от 0 до 0xFFFF (в случае 16-битного таймера), и сравнивать её с val.

3. Как только значение разности станет больше чем val - готово.

Главное - не прозевать 0xFFFF тиков таймера:)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Нормально там всё.

Да, получается.

Я пытался сделать иначе, чтобы разность все время не вычислять. Допустим, нужна задержка на 0x14 тактов. Читаем начальное значение таймера, к примеру, 0xfff0. Прибавляем к нему нашу задержку, получаем 0x0004. А теперь ждем, когда таймер проскочит этот порог. И... не можем определить. :( Пока таймер досчитает до этого порога, он и очень большие значения будет иметь, пока до переполнения дойдет, и малые.

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

Почти парадокс. :laughing:

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Проверил в кейловском симуляторе. Всё нормально работает даже при переходе через 0xffffffff. Как переделать на 16 бит, подумаю.

 

Дизассемблер "проекта" прикладываю. Исходный код (все 10 строчек) легко восстановить.

 

; generated by ARM C/C++ Compiler with , RVCT4.0 [build 524] for uVision
; commandline ArmCC [--debug -c --asm --interleave -omain.o --depend=main.d --device=DARMP1 --apcs=interwork -O3 -IC:\Keil\ARM\INC\NXP --omf_browse=main.crf main.c]
                         THUMB

                         AREA ||.text||, CODE, READONLY, ALIGN=2

                 main PROC
;;;31     
;;;32     int main (void)
000000  b530              PUSH     {r4,r5,lr}
;;;33     {
;;;34     
;;;35     	TIM0->CTCR = 0x00;
000002  2000              MOVS     r0,#0
000004  f04f2240          MOV      r2,#0x40004000
000008  6710              STR      r0,[r2,#0x70]
;;;36     	TIM0->TCR = 0x02;
00000a  2102              MOVS     r1,#2
00000c  6051              STR      r1,[r2,#4]
;;;37     	TIM0->PR = 0;
00000e  60d0              STR      r0,[r2,#0xc]
;;;38     	TIM0->TCR = 0x01;
000010  2001              MOVS     r0,#1
000012  6050              STR      r0,[r2,#4]
;;;39     
;;;40     
;;;41     	GPIO0->FIODIR ^= 0x01;
000014  4c08              LDR      r4,|L1.56|
000016  6820              LDR      r0,[r4,#0]
000018  f0800001          EOR      r0,r0,#1
00001c  6020              STR      r0,[r4,#0]
00001e  f2427510          MOV      r5,#0x2710
                 |L1.34|
000022  6891              LDR      r1,[r2,#8]
000024  462b              MOV      r3,r5
                 |L1.38|
000026  6890              LDR      r0,[r2,#8]
000028  1a40              SUBS     r0,r0,r1
00002a  4298              CMP      r0,r3
00002c  d3fb              BCC      |L1.38|
;;;42     
;;;43     	while (1)
;;;44     	{
;;;45     		delay_ms (10);
;;;46     
;;;47     		GPIO0->FIOPIN ^= 0x01;
00002e  6960              LDR      r0,[r4,#0x14]
000030  f0800001          EOR      r0,r0,#1
000034  6160              STR      r0,[r4,#0x14]
000036  e7f4              B        |L1.34|
;;;48     	}
;;;49     
;;;50     		;
;;;51     }
                         ENDP

                 |L1.56|
                         DCD      0x2009c000

                 __ARM_use_no_argv EQU 0

 

таймер настраивался в режиме "лишь бы тикал"

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

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

×   Вставлено с форматированием.   Вставить как обычный текст

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

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

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

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

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