Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Функция формирования задержки
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > ARM, 32bit
Sprite
Доброго всем времени суток!
Работаю на 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 sad.gif Есть ли у кого какие соображения? Может есть директивы компилятора типа:
Код
отключить оптимизацию
...
код
...
включить оптимизацию
Сергей Борщ
QUOTE (Sprite @ Feb 15 2012, 17:35) *
Пробовал также объявлять параметр фукции time как volatile - не помогло. Уже ветает шальная мысль написать функцию задержки на asm sad.gif Есть ли у кого какие соображения? Может есть директивы компилятора типа:
Не нужно гадать. Если вы чувствуете в себе силы написать эту функцию на асме - для вас не составит огромного труда посмотреть листинг этой функции и убедиться, что компилятор тут не при чем. Убедиться, что и volatile в параметре функции не нужен, и __nop() лишний, и компилить без оптимизации - прятать голову в песок.

Есть большое подозрение, что для борьбы с "чудесами" достаточно поставить сброс флага переполнения непосредственно перед циклом. А после цикла можно убрать.
Sprite
Цитата(Сергей Борщ @ Feb 16 2012, 00:46) *
Есть большое подозрение, что для борьбы с "чудесами" достаточно поставить сброс флага переполнения непосредственно перед циклом. А после цикла можно убрать.

wink.gif Вы правы - это все невнимательность. Тема закрыта.
ViKo
Цитата(Сергей Борщ @ Feb 15 2012, 20:46) *
Есть большое подозрение, что для борьбы с "чудесами" достаточно поставить сброс флага переполнения непосредственно перед циклом. А после цикла можно убрать.

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

Код
  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. Код исправил. Спасибо, АНТОХА!
ukpyr
Код
    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) )
ViKo
Для ukpyr
А вас прерывания и наличие буфера команд не смущают? Цифры проверяли на железе?
AHTOXA
Цитата(ViKo @ Feb 20 2012, 14:38) *
А еще у меня таймер считает в 2 раза быстрее! По осциллографу вижу. Сижу, разбираюсь, горюю.

Оно:
Нажмите для просмотра прикрепленного файла
?
ViKo
Цитата(AHTOXA @ Feb 20 2012, 12:36) *
Оно: ?

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

Да, вы правы! Оно! Не сообразил, что 36 MHz - это уже поделенное прескалером на 2. А потом умноженное.
Подправлю код, что выдал раньше.
ViKo
Подправил код, перенес сброс запроса перед запуском таймера (и на две части разбил, между которыми можно не просто ждать, а сделать что-нибудь полезное).
Раньше в отладчике подобная конструкция показывала неправильную работу. Но дело было в отладчике. Просто в нем при пошаговом выполнении таймер свою работу не прекращал. Только запустил - глядь, на следующем шаге уже все готово. sm.gif
На реальном железе работает, как положено.
Код
  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
}
esaulenka
В очередной раз порекламирую другой подход к использованию таймера:

Код
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-битный, чтобы эта переменная переполнялась вместе с таймером.
ukpyr
Цитата
А вас прерывания и наличие буфера команд не смущают? Цифры проверяли на железе?
перед _delay_loops прерывания нужно запрещать, если они возможны. Цифры проверены неоднократно на реальных проектах - тело цикла выполняется за 3 такта. Проверялось также на проекте с несколькими шинами 1-wire под Protothreads
ViKo
Цитата(esaulenka @ Feb 22 2012, 13:00) *
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-битовыми счетчиками это еще более вероятно.
Нужно проверять на равенство, а не на превышение. Но в этом случае нельзя проскочить это равенство, значит, нельзя проверять изредка. То есть, не получится, к примеру, задать несколько задержек на одном таймере, и проверять их по мере необходимости.
AHTOXA
Цитата(ViKo @ Feb 29 2012, 18:54) *
Допустим, 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 тиков таймераsm.gif
ViKo
Цитата(AHTOXA @ Feb 29 2012, 17:50) *
Нормально там всё.

Да, получается.
Я пытался сделать иначе, чтобы разность все время не вычислять. Допустим, нужна задержка на 0x14 тактов. Читаем начальное значение таймера, к примеру, 0xfff0. Прибавляем к нему нашу задержку, получаем 0x0004. А теперь ждем, когда таймер проскочит этот порог. И... не можем определить. sad.gif Пока таймер досчитает до этого порога, он и очень большие значения будет иметь, пока до переполнения дойдет, и малые.
А если постоянно вычислять разность между текущим значением таймера и начальным, то она будет расти монотонно.
Почти парадокс. laughing.gif
esaulenka
Проверил в кейловском симуляторе. Всё нормально работает даже при переходе через 0xffffffff. Как переделать на 16 бит, подумаю.

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

CODE

; 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



таймер настраивался в режиме "лишь бы тикал"
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.