Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: STM32F10x
Форум разработчиков электроники ELECTRONIX.ru > Микроконтроллеры (MCs) > ARM
pr0m
Полдня чесал репу и разлохматил бубен на казалоь бы ровном месте... А именно: имеем некий код в основном цикле, с временем исполнения, определяемым периодом таймера. Таймер запускается перед контролируемым кодом, тот в цикле делает свои дела, а выходит по флагу, к-й устанавливается в обработчике прерывания (Update Event) этого таймера. Таймер запускается перед этим блоком. По так и невыясненным причинам, всё работало после ресета до того момента, пока не происходило некое внешнее прерывание (с приоритетом ниже таймера), делающее быстро другие примитивные дела, не относящиеся к интересующему участку кода. После этого начинались чудеса - на очередном внешнем цикле после запуска таймера код лихо проскакивал проверку while(!bStopDSS), даже не заглянув внутрь, и происходило 2! прерывания от таймера, одно из которых видимо и устанавливало bStopDSS=1 до входа во внутренний цикл. Вылечил проверкой флага прерывания перед включением таймера. Перекорячивается конвейер команд? При включении таймера (строка TIM3->CR1 |= TIM_CR1_CEN) ещё не сброшен бит прерывания?
Код
volatile uint8_t bStopDSS;
while(1) // внешний цикл
{
bStopDSS = 0;
TIM3->CNT = 0;
TIM3->SR = ~TIM_IT_Update;
while(TIM3->SR & TIM_IT_Update); // без этой проверки - чудеса
TIM3->CR1 |= TIM_CR1_CEN;

while(bStopDSS == 0)
{
// Внутренний цикл, выходим по таймеру.    
}
//
...готовимся к следующему циклу
//
}

void TIM3_IRQHandler (void)
{
    // Сбрасываем флаг прерывания
    TIM3->SR = ~TIM_IT_Update;
    // Останавливаем таймер
       TIM3->CR1 &= ~TIM_CR1_CEN;
    // Флаг окончания внутреннего цикла
    bStopDSS = 1;
}


Такие дела. В голове смятение.
akimych
Интересно то, что после while(TIM3->SR & TIM_IT_Update); bStopDSS все равно не обнуляется и TIM_IT_Update тоже не сбрасывается.
А таймер как сконфигурирован?

В данном случае наверно имело бы смысл использовать One-pulse mode. Если по прерыванию только выставляется флаг bStopDSS, то можно будет вообще без него обойтись.
AHTOXA
Цитата(pr0m @ Feb 7 2011, 01:02) *
Такие дела. В голове смятение.

Попробуйте проверять источник прерывания перед сбросом:
Код
void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF)
    {
        TIM2->SR = ~TIM_SR_UIF;
        ...
    }
}
pr0m
Цитата(AHTOXA @ Feb 7 2011, 09:06) *
Попробуйте проверять источник прерывания перед сбросом:
Код
void TIM2_IRQHandler(void)
{
    if (TIM2->SR & TIM_SR_UIF)
    {
        TIM2->SR = ~TIM_SR_UIF;
        ...
    }
}

Флаг UEV установлен в обоих случаях, смотрел в железе. А как может быть иначе, если прерывание только по нему сконфигурено, поэтому даже не проверяю. Даташиты и примеры молчат по этому поводу.


Цитата(akimych @ Feb 7 2011, 00:29) *
Интересно то, что после while(TIM3->SR & TIM_IT_Update); bStopDSS все равно не обнуляется и TIM_IT_Update тоже не сбрасывается.

В смысле, Вы это сами проверяли? Если так, то значит мне это не приглючилось.. Именно так и происходило.
Цитата(akimych @ Feb 7 2011, 00:29) *
А таймер как сконфигурирован?

Просто Upcounting (инициализирую прескалер, ARR, обнуляю CNT, разрешаю прерывания по UEV и вперёд).

Цитата(akimych @ Feb 7 2011, 00:29) *
В данном случае наверно имело бы смысл использовать One-pulse mode. Если по прерыванию только выставляется флаг bStopDSS, то можно будет вообще без него обойтись.

Да, вчера название режима привлекло, но ещё не пробовал. Попробую.
По поводу использования bStopDSS отдельная песня - внутренний блок очень критичен по времени (чем быстрее, тем лучше), причём это время должно быть детерминировано. После выяснений-экспериментов сделал следующие выводы:
1) Для детерминированности времени исполнения код должен исполняться из ОЗУ, что и сделал. Хотя исполняется медленнее, чем из флэш - что подтверждает высказывания по поводу отсутствия преимуществ исполнения кода из ОЗУ в сравнении с флэш для STM32F10...
2) Проверка флага UEV как признака окончания внутреннего цикла (while(!(TIMx->CR1 & UEV))увеличивает общее время исполнения блока на 2 такта, в сравнении c флагом в ОЗУ (bStopDSS), что в принципе понятно - чтение регистра+наложение маски бита UEV-сравнение, вместо чтение-сравнение). Поэтому bStopDSS.
AHTOXA
Цитата(pr0m @ Feb 7 2011, 13:42) *
Флаг UEV установлен в обоих случаях, смотрел в железе. А как может быть иначе, если прерывание только по нему сконфигурено, поэтому даже не проверяю.

Однако же может :-)
pr0m
Цитата(AHTOXA @ Feb 7 2011, 12:16) *
Однако же может :-)

Мдя, спасибо за ссылку. Понял, как нужно, но не понял, зачем. Многовато туману. Где ещё ожидают грабли? А то у меня ещё один необъяснимый и сильно напрягающий случай имеется - тоже таймер, тоже прерывание (с частотой 100кГц), в котором данные с группы из 6 каналов АЦП (АЦП в непрерывном режиме, с использованием ДМА в CircularMode, собирает данные в фиксированное место 6*2 байт) аккуратненько складывает эти 6 отсчётов в большой накопительный буфер, с инкрементом указателя ессно. Если перевести в требуемую скорость по каналу передачи 100кГц*6каналов*16бит = 9600кБит/c. Как только заполнен буфер, нужно его отправлять. Отправляю по Ethernet-у при помощи lwIP udp_send(). Пробовал 2 варианта:
1) В основном цикле программы (от чего очень хотелось бы избавиться, т.к. там коду есть чем другим заняться). Помечаем буфер как готовый для отправки, в основном цикле флажок готовности проверяется, буфер через lwIP udp_send() отправляется. Скорость наблюдаю на приёмном конце - на компе - соответсвует расчётной.
п.с. Процесс отправки чуть сложнее описанного, но сути не меняет - используются очереди, процедуры push в обработчике и "пока pop != 0" в основном цикле.
2) Поручаем отправку программному прерыванию:
Код
void SamplesMain(void) // Вариант1 - процедура отправки в основном цикле
{
#ifndef INT_TXR
    QUEUE_ELEMENT* pbuf;
    while( (pbuf = QueuePopSafe(&readyBufsQueue)) != NULL )
    {
        p->payload = pbuf->pSamples;
        udp_send(upcb, p);
        QueuePushSafe(&freeBufsQueue,pbuf);
    }
#endif        
}

void TIM2_IRQHandler (void)
{
    TIM2->SR = ~TIM_IT_Update;

    *(((u32*)pNextSamplesSet)+0) = *(((u32*)&samples_acc)+0);
    *(((u32*)pNextSamplesSet)+1) = *(((u32*)&samples_acc)+1);
    *(((u32*)pNextSamplesSet)+2) = *(((u32*)&samples_acc)+2);
    if(++pNextSamplesSet == (pSamplesAccumulator->pSamples + SAMPLES_SETS_PER_BUF))
       {
        QueuePushUnsafe(&readyBufsQueue,pSamplesAccumulator);
#ifdef INT_TXR
        EXTI->SWIER |= 0x0001;
#endif
        pSamplesAccumulator = QueuePopUnsafe(&freeBufsQueue);
        if( pSamplesAccumulator == NULL )
        {
            while(1);// Отладка. Сюда вваливаемся навсегда, если буферы уходят медленнее, чем заполняются.
        }
        pNextSamplesSet = pSamplesAccumulator->pSamples;    
       }
}

void EXTI0_IRQHandler(void)// Вариант2 - процедура отправки в программном прерывании
{
    QUEUE_ELEMENT* pbuf;
    EXTI->PR = EXTI_Line0;

    while( (pbuf = QueuePopSafe(&readyBufsQueue)) != NULL )
    {
        p->payload = pbuf->pSamples;
        udp_send(upcb, p);
        QueuePushSafe(&freeBufsQueue,pbuf);
    }
    time_elapsed = LocalTime - time; //отладка
    ++bufs;//отладка
//    bufs_per_ms = bufs/LocalTime;
//    kBs = bufs_per_ms * 1200UL * 1000UL;
}


Так вот при отправке через программное прерывание скорость передачи падает! примерно до 9200кБит. Как такое может быть? Ведь скорость однозначно определяется периодом работы таймера. При этом переполнения/опустошения очередей буферов не происходит. Ощущение, что замедляется бег таймера, как ни идиотски это звучит. Если закомментировать строчку udp_send(upcb, p);, всё приходит в норму (ориентируюсь по кол-ву отправленных буферов bufs за период времени time_elapsed). Ещё не до конца расковырял udp_send, но в конечном итоге она через memcpy копирует данные в буфер для Ethernet-а и отмечает к отправке. Чем может отличаться исполнение кода в конексте прерывания от кода в контексте главного цикла, что может приводить к такому эфф(де)фекту?
pr0m
Победил. Не была задана группировка с вытеснением в NVIC, т.е. 0 preemption групп, 16 приоритетов. Задал приоритет 0 для таймера (типа выше всех остальных в системе), и думал ни одна собака его не прервёт, зато он у всех будет проц вырывать, как приспичит, и гарантированно не пропустит ни одного сэмпла. Ан нет - почитал про preemption, прозрел, разделил на группы, таймер определил в гордом одиночестве в 0-ю группу, 0-й приоритет - и теперь всё в норме.
Что взять с начинающего?
sonycman
Цитата(pr0m @ Feb 7 2011, 17:18) *
Задал приоритет 0 для таймера (типа выше всех остальных в системе), и думал ни одна собака его не прервёт, зато он у всех будет проц вырывать, как приспичит, и гарантированно не пропустит ни одного сэмпла.

Так и есть. Без групп, нулевой приоритет - высший.
Разве нет?
pr0m
Цитата(sonycman @ Feb 7 2011, 18:21) *
Так и есть. Без групп, нулевой приоритет - высший.
Разве нет?

Высший-то высший, но тут речь о возможности вытеснения текущего прерывания, и предаче управления другому. Например, если сейчас исполняется прерывание 0-й группы и, скажем, приоритет 2, то пришедший запрос на прерывание из той-же 0-й группы с более высоким приоритетом=1 НЕ прервёт исполнение текущего обработчика, т.к. они оба относятся к одной (0-й) группе, а станет в очередь (pending). А вот если мы разделим приоритеты по группам, подгруппам, обработчик прерывания 1-й группы, любой приоритет будет вытеснен запросом на прерывание из 0-й группы, любой приоритет. Такая вот иерархия. Описание NVIC контроллера и регистра SCB_AIRCR к прочтению.
В одной группе приоритетом определяется только очерёдность передачи управления тому или иному вектора, а если они ещё и с одинаковым приоритетом, то аппаратным порядковым номером вектора.
sonycman
Цитата(pr0m @ Feb 7 2011, 18:39) *
Высший-то высший, но тут речь о возможности вытеснения текущего прерывания, и предаче управления другому.

По умолчанию, после аппаратного сброса, NVIC в STM32 работает в режиме одной единственной группы, без подгрупп.
То есть любому прерыванию можно присвоить лишь простой приоритет 0, 1, 2 и т.д.

Ноль будет высшим и будет прерывать выполнение любого другого прерывания (кроме системных).
AHTOXA
Цитата(sonycman @ Feb 7 2011, 21:04) *
По умолчанию, после аппаратного сброса, NVIC в STM32 работает в режиме одной единственной группы, без подгрупп.
То есть любому прерыванию можно присвоить лишь простой приоритет 0, 1, 2 и т.д.

Ноль будет высшим и будет прерывать выполнение любого другого прерывания (кроме системных).

Ненене! Внутри одной группы прерывания друг друга не вытесняют:-)
sonycman
Цитата(AHTOXA @ Feb 7 2011, 19:17) *
Ненене! Внутри одной группы прерывания друг друга не вытесняют:-)

Так ведь группы-то разные!
Приоритет 0 - нулевая группа.
Приоритет 1 - первая группа и т.д.

Это если PRIGROUP = 0.

Или нет?
pr0m
Цитата(sonycman @ Feb 7 2011, 19:25) *
Так ведь группы-то разные!
Приоритет 0 - нулевая группа.
Приоритет 1 - первая группа и т.д.

Это если PRIGROUP = 0.

Или нет?


Нет, одна группа - 0-я, и в ней 15 ПОДприоритетов, к-е друг друга не вытесняют.
Нажмите для просмотра прикрепленного файла
Кстати, не совсем понял один момент: после сброса поле групп = 000, если верить описанию регистра SCB_AIRCR:
Нажмите для просмотра прикрепленного файла
Такого сочетания этих конфигурационных бит нет. И какая схема приоритетов имеет место после ресета?
AHTOXA
Цитата(sonycman @ Feb 7 2011, 21:04) *
По умолчанию, после аппаратного сброса, NVIC в STM32 работает в режиме одной единственной группы, без подгрупп.

Цитата(sonycman @ Feb 7 2011, 21:25) *
Так ведь группы-то разные!

Так одна-единственная, или разная для каждого приоритета? sm.gif
Я так понимаю, что при старте (PRIGROUP == 0) получается, что все биты приоритета определяют Preempt Priority (если нет нулевого бита у Priority Level). То есть, разные приоритеты находятся в разных группах, и могут вытеснять друг друга.
Но ведь они по умолчанию одинаковые, приоритеты-то?
sonycman
Цитата(pr0m @ Feb 7 2011, 19:57) *
Нет, одна группа - 0-я, и в ней 15 ПОДприоритетов, к-е друг друга не вытесняют.

Хм, я тогда приведу другую картинку:
Нажмите для просмотра прикрепленного файла
В случае с STM32 используются только 4 бита регистра, поэтому отбрасываем 4 младших бита.

Что получается?
Читаем верхнюю строчку: кол-во вытесняющих приоритетов - 128 (в нашем случае - 16), кол-во подприоритетов - 2 (у нас - 1).

Когда происходит вытеснение? Когда вытесняющий приоритет больше.

1. Записываем в регистр 0: вытесняющий приоритет = 0, максимальный, подприоритет = 0.
2. Записываем в регистр 1: вытесняющий приоритет = 1, следующий за максимальным, подприоритет = 0.

Неужели не правильно рассуждаю?
AHTOXA
Цитата(pr0m @ Feb 7 2011, 21:57) *
Нет, одна группа - 0-я, и в ней 15 ПОДприоритетов, к-е друг друга не вытесняют.

Всё же нет, 16 групп, в каждой по одному уровню приоритета.
Цитата(pr0m @ Feb 7 2011, 21:57) *
Кстати, не совсем понял один момент: после сброса поле групп = 000, если верить описанию регистра SCB_AIRCR:
Такого сочетания этих конфигурационных бит нет. И какая схема приоритетов имеет место после ресета?

0b011. Ибо все значения, меньше либо равные этому (вплоть до нуля) дают одинаковый результат при имеющихся битах приоритета.
pr0m
Цитата(sonycman @ Feb 7 2011, 20:11) *
Хм, я тогда приведу другую картинку:


Ваша картинка красивее sm.gif у меня про сочетание b000 умалчивается. Черпал знания из "STM32F10xxx Cortex-M3 programming manual.pdf" с сайта ST.
sonycman
Цитата(AHTOXA @ Feb 7 2011, 20:15) *
Всё же нет, 16 групп, в каждой по одному уровню приоритета.

Вот именно, подприоритет одинаковый, а вот вытесняющий приоритет (pre-emption priority) разный!
Поэтому получается 16 групп (с одной подгруппой внутри), каждая группа будет вытеснять более младшую.

То есть получается максимум 16 уровней вложенности.
pr0m
Цитата(AHTOXA @ Feb 7 2011, 20:15) *
Всё же нет, 16 групп, в каждой по одному уровню приоритета.

0b011. Ибо все значения, меньше либо равные этому (вплоть до нуля) дают одинаковый результат при имеющихся битах приоритета.


А моя практика подсказывает, что нифига. Почему? Пока не конфигурил группы, имел место, как я теперь уже понял, пропуск прерываний от таймера, т.к. он хоть и имел 0-й приоритет, но не мог когда нужно вытеснить программное прерывание, к-е копировало порядка 1200байт, один раз в
200 отсчётов (раз в 2мс). После явного конфигуривания групп всё стало на свои места. Каким документом по поводу работы NVIC руководствуетесь?

Вот состояния NVIC на реальном железе STM32F107 сразу после сброса, и после конфигурирования приоритетов. К сожалению, у кейла нет для 107-го в периферии регистров NVIC.
Нажмите для просмотра прикрепленного файла
Нажмите для просмотра прикрепленного файла
AHTOXA
Цитата(pr0m @ Feb 7 2011, 22:22) *
А моя практика подсказывает, что нифига. Почему? Пока не конфигурил группы, имел место, как я теперь уже понял, пропуск прерываний от таймера

Так если не конфигурили, то у всех прерываний был одинаковый приоритет, потому и не вытесняло.
Цитата(pr0m @ Feb 7 2011, 22:22) *
Каким документом по поводу работы NVIC руководствуетесь?

Главный документ касательно ядра Cortex-M3 -- Cortex-M3 Technical Reference Manual.
pr0m
Разобрался. sonycman прав. 15 подгрупп после ресета.
У меня в коде инициализации, т.к. делал не с нуля проект, а взял шаблон, было вот это:

Код
/* 0 bit for pre-emption priority, 4 bits for subpriority */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);

В хидере:
#define NVIC_PriorityGroup_0         ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
                                                            4 bits for subpriority */
#define NVIC_PriorityGroup_1         ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
                                                            3 bits for subpriority */
#define NVIC_PriorityGroup_2         ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
                                                            2 bits for subpriority */
#define NVIC_PriorityGroup_3         ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
                                                            1 bits for subpriority */
#define NVIC_PriorityGroup_4         ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
                                                            0 bits for subpriority */


Т.е. переключался на 0 групп.
Сейчас вот так:
Код
/* 2 bit for pre-emption priority, 2 bits for subpriority */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);



Цитата(AHTOXA @ Feb 7 2011, 20:32) *
Так если не конфигурили, то у всех прерываний был одинаковый приоритет, потому и не вытесняло.

Ненене (с) sm.gif ПОДприоритеты назначал разные. Группа была одна. Из шаблона проекта унаследовал - там переключался NVIC на 0 групп.
Цитата(AHTOXA @ Feb 7 2011, 20:32) *
Главный документ касательно ядра Cortex-M3 -- Cortex-M3 Technical Reference Manual.

Угумс, первоисточник-с.

И что ж это получается, господа - NVIC у STM32 - кастрированный??? sm.gif В армовской спецификации у него вон скока бит для приоритетов!
KnightIgor
Цитата(pr0m @ Feb 7 2011, 18:31) *
А моя практика подсказывает, что нифига. Почему? Пока не конфигурил группы, имел место, как я теперь уже понял, пропуск прерываний от таймера,


И моя практика такое же подсказала, когда я ту же шишку набил на заре использования Cortex. После этого любой мой проект начинается с вызова NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); в процессе инициализации системы. Тогда при инициализации тех или иных прерываний вытесняющий приоритет в диапазоне [0..15] можно задать в поле "NVIC_IRQChannelPreemptionPriority" записи "NVIC_InitTypeDef", то есть (пример для UART1):
Код
...
NVIC_InitTypeDef NVIC_InitStructure =
{
    USART1_IRQn,    
    USART_IT_Priority, // preemptive
    0,                // subpriority
    ENABLE
};
...
NVIC_Init(&NVIC_InitStructure);

Пока 16-ти вытесняющих приоритетов хватало. На 51-х процессорах и двух было достаточно rolleyes.gif
pr0m
Цитата(KnightIgor @ Feb 7 2011, 20:58) *
После этого любой мой проект начинается с вызова NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); На 51-х процессорах и двух было достаточно rolleyes.gif

Воистину, теперь и у меня эту будет самый главный после main(), вызов biggrin.gif
Да уж, на 8-,16-разрядных, с коими достаточно имел дело, понятие приоритета однозначно подразумевало вытеснение. Придётся ломать сознание.
sonycman
Цитата(pr0m @ Feb 7 2011, 20:53) *
И что ж это получается, господа - NVIC у STM32 - кастрированный??? sm.gif В армовской спецификации у него вон скока бит для приоритетов!

У LPC17xx 5 битов приоритета - не намного больше sm.gif

Цитата(pr0m @ Feb 7 2011, 21:05) *
Воистину, теперь и у меня эту будет самый главный после main(), вызов biggrin.gif

В этом нет необходимости, это является значением по умолчанию sm.gif
pr0m
Цитата(sonycman @ Feb 7 2011, 21:05) *
В этом нет необходимости, это является значением по умолчанию sm.gif

.....если в моей программе будет использоваться более одного прерывания sm.gif
akimych
Цитата
1) Для детерминированности времени исполнения код должен исполняться из ОЗУ, что и сделал. Хотя исполняется медленнее, чем из флэш - что подтверждает высказывания по поводу отсутствия преимуществ исполнения кода из ОЗУ в сравнении с флэш для STM32F10...
2) Проверка флага UEV как признака окончания внутреннего цикла (while(!(TIMx->CR1 & UEV))увеличивает общее время исполнения блока на 2 такта, в сравнении c флагом в ОЗУ (bStopDSS), что в принципе понятно - чтение регистра+наложение маски бита UEV-сравнение, вместо чтение-сравнение). Поэтому bStopDSS.

Странные выводы. Вызов прерывания + установка флага займет куда больше 2х тактов + вход в прерывание можеть занимать разное время.
На счет кода в озу тоже есть большие сомнения. Имхо, вы рискуете нарваться на новые грабли.

Приоритеты - это все хорошо, теперь таймер будет выключаться в прерывании стабильно (хотя он это и сам умеет делать, достаточно лишт включить OPM). Но в этом ли была проблема изначально? Ведь получается, что код не успевает обрабатывать данные на заданной скорости, во всяком случае при возникновении того самого прерывания, которое "быстро выполняет другие дела".
pr0m
Цитата(akimych @ Feb 7 2011, 22:06) *
Странные выводы. Вызов прерывания + установка флага займет куда больше 2х тактов + вход в прерывание можеть занимать разное время.
На счет кода в озу тоже есть большие сомнения. Имхо, вы рискуете нарваться на новые грабли.

Я видимо путано высказался. 2 варианта проверки окончания внутреннего цикла:
1) проверять флаг UEV. Прерывания от таймера в этом случае конечно отключены. Polling классический, та-скать.
2) проверять переменную в ОЗУ bStopDSS, к-я устанавливается в обработчике прерывания таймера.
Так вот, практика показала, что мой внутренний цикл DDS, в который ессно входит проверка:
Код
while(один из 2-х вариантов окончания){зело критичный по времени код DDS}

выполняется на 2 такта быстрее во 2-м случае. Время установки флага кем-то извне (в моём случае в обработчике таймера bStopDSS или сам битик UEV анализируем) значения не имеет.
Какие именно сомнения по поводу кода в ОЗУ? Располагаю конкретными цифрами кол-ва тактов для 1)флэш-кода и 2)ОЗУ-кода. 2-й - медленнее, но абсолютно детерминированное кол-во тактов. 1-й быстрее, но с этим конвейером, IBUS-DBUS-ами может исполняться то 14 тактов, то 18, на одной прошивке ессно. После 8-,16-разрядников для меня это был шок. Недавно поднимал вопрос в какой-то ветке пониже.

Цитата(akimych @ Feb 7 2011, 22:06) *
Приоритеты - это все хорошо, теперь таймер будет выключаться в прерывании стабильно (хотя он это и сам умеет делать, достаточно лишт включить OPM). Но в этом ли была проблема изначально? Ведь получается, что код не успевает обрабатывать данные на заданной скорости, во всяком случае при возникновении того самого прерывания, которое "быстро выполняет другие дела".

В этой ветке переплелись мои вопросы по двум разным проектам. В этом сообщении Вы их тоже переплели sm.gif Там где речь зашла о приоритетах - другая проблема. Была. Решена.
Кстати, включил OPM. Понравилось - изящнее. То что нужно для 1-го проекта.

Ещё кстати, "того самого прерывания, которое "быстро выполняет другие дела"" - эти дела заключаются в приёме конфигурации по I2C от внешнего контроллера, код DDS-цикла конечно останавливается (более того, я его явно останавливаю при начале обмена по I2C, установив флаг окончания цикла), чтобы затем войти в цикл вновь, обновившись из принятой конфигурации.
akimych
Цитата
В этой ветке переплелись мои вопросы по двум разным проектам.

Ах вот оно что, тогда понятно sm.gif

На счет проверки бита. Вообще-то там не должно быть "чтение-наложение маски-сравнение", наложение маски + сравнение делается хитрее, команд столько же, надо смотреть, что генерит компилятор. Разница в том, что для TIM3->SR может генериться немного другая инструкция чтения, которая длиннее. Можно предварительно положить &TIM3-SR в регистр (присвоить указателю).
Сомнения больше на счет детерминированного времени выполнения команд, если оно действительно нужно, то стоило ли выбирать стм32...
А что там такого хитрого в цикле, если не секрет?
pr0m
Цитата(akimych @ Feb 8 2011, 00:24) *
На счет проверки бита. Вообще-то там не должно быть "чтение-наложение маски-сравнение", наложение маски + сравнение делается хитрее, команд столько же, надо смотреть, что генерит компилятор. Разница в том, что для TIM3->SR может генериться немного другая инструкция чтения, которая длиннее. Можно предварительно положить &TIM3-SR в регистр (присвоить указателю).
Сомнения больше на счет детерминированного времени выполнения команд, если оно действительно нужно, то стоило ли выбирать стм32...
А что там такого хитрого в цикле, если не секрет?

Вы наверное имеете ввиду доступ к bit-banding региону, вместо чтения регистра целиком+маска нужного бита? За пару недель тяжело стать гуру в армах, и ассемблер ихний тяжеловат, ещё и компилятор просто так не позволяет __asm вставки делать для thumb-кода. Я в процессе sm.gif
В цикле - подобие вот этого: DDS на AVR. Там должно стать понятным, почему этот код внутри должен исполняться за детерминированное кол-во тактов - им определяется частота дискретизации, и как следствие, частота самого генерируемого сигнала. Основное отличие моего проекта - вместа резистивного делителя для формирования аналогового сигнала используются бортовые ЦАП-ы, причём оба, и формируют прямой и инверсный формы сигнала, чтобы затем завести их на входы компаратора и получить целевые прямоугольные импульсы с малым джиттером (и у программной, и у аппаратной реализаций DDS на предельных частотах проблема явственная - сильно дрожат фронта, если формировать напрямую прямоугольник). По сути, программная замена DDS от Analog devices, AD9854 можно глянуть для примера.
Выбран был скажем так, не STM32, а в целом АРМ - по критерию скорости, потому что в моём проекте максимальная генерируемая частота (синус) требуется выше, чем в ссылке (до 450...500кГц). Код DDS получился 20 тактов, при 72МГц тактвовой ядра Fd=72/20=3.6МГц - 5-6 отсчётов за период 500кГц - приемлемо, без усложнения аналогового фильтра на выходе ЦАП-ов. Я всё ещё не определился окончательно с MCU, потому что
к задаче подходят и довольно шустрые C8051F12x, к тому же имеющие нужные 2 ЦАП-а на борту.
akimych
Цитата
Вы наверное имеете ввиду доступ к bit-banding региону

Нет, bit-banding тут не при чем. Просто если посмотреть код, генерируемый компилятором, то видно, что маску он не делает. Делается либо сдвиг + проверка <0, либо используется команда TST, которая как раз и делает AND.

А что надо получить в конечном итоге, прямоугольник заданной частоты? Может быть как-то на таймерах это сделать, неужели синус + компаратор лучше (не спорю, просто не сталкивался).
На счет цапов я бы предложил попробовать реализовать используя возможности стм32, а именно дма + синхр. по таймеру.
ReAl
Цитата(pr0m @ Feb 8 2011, 09:03) *
Там должно стать понятным, почему этот код внутри должен исполняться за детерминированное кол-во тактов
+1 к предыдущему оратору.
Лучше пусть с детерминированным периодом DMA выбрасывает данные в DAC-и, а цикл с временем "лишь бы успевал" эти данные готовит.
pr0m
Цитата(akimych @ Feb 9 2011, 01:00) *
А что надо получить в конечном итоге, прямоугольник заданной частоты? Может быть как-то на таймерах это сделать, неужели синус + компаратор лучше (не спорю, просто не сталкивался).


Мне нужны разные виды модуляции, в том числе ЛЧМ.
Код
typedef struct
{
    u32 freq_start; //начальная частота, Гц
    u32 freq_stop; //конечная частота, Гц        
    u32 tau_f;       //время изменения от начальной до конечной, мкс        
    u32 tau_d;      //длительность радиоимпульса,мкс            
    u32 freq_carrier;//частота гетеродина, Гц    
}DDS_CONFIG;

Таймера в стм32, конечно, хорошие, учитывая 16-битные прескалеры и авторелоады (кстати, для гетеродина использую), позволяют с достаточной точностью получить требуемые частоты, но не годятся для получения ЛЧМ-сигнала: при частоте 400кГц и тактовой таймера 72МГц прескалер=0, ARR=180-1. Следующее возможное значение по частоте 72000/179=402,234кГц, ниже 397,790кГц, - порядок шага по частоте ясен. Сравним с разрешающей способностью DDS (аккумулятор 32-битный) dF=Fd/2^32=72000000/(кол-во тактов на выполнение кода DDS)/2^32=72000000/20/2^32=0,00083819 Гц.
Это почему не таймеры, с приращением ARR в обработчике.
А почему именно синусы (комплементарные), кажется пояснял выше, может нет, повторюсь: кажется очевидным простой ход - вместо таблицы синуса подсунуть DDS-у таблицу меандра (наполовину 0х000, наполовину 0хFFF), как вариант, можно выводить на пин старший бит из таблицы сигнала. На низких формируемых частотах можете не заметить, но на высоких выползает джиттер (дрожат фронты). Причина в большой величине приращения аккумулятора фазы на каждом шаге... вобщем, тут нужно прочувствовать и увидеть sm.gif
А побороть это можно сделав 2 синуса, отфильтровать и подать на компаратор - так сделано у Analog Devices в некоторых DDS-чипах.


Цитата(akimych @ Feb 9 2011, 01:00) *
На счет цапов я бы предложил попробовать реализовать используя возможности стм32, а именно дма + синхр. по таймеру.

Не знаю.... В итоге нужно в ЦАП-ы засовывать, посредством дма или софтом, значение из таблицы сигнала, причём не значения по порядку, а вычислять индекс на основании значения аккумулятора фазы, так что сдаётся мне, дма по таймеру здесь не помогут. Собсно код:
Код
....
register union
    {
        struct
        {
            uint32_t unused:24;
            uint32_t index:8;
        }field;
        uint32_t total;
    }acc;
......
while(bStopDSS == 0)
            {
                if( dp > freq_stop )
                {
//                    acc.total = 0;
                    dp = freq_start;
                    ddp = tau;
//                    break;
                }
            
                DAC->DHR12RD = _sinewave[acc.field.index];
                acc.total += dp;
                dp += ddp;    
            }



ReAl
Цитата(pr0m @ Feb 9 2011, 18:48) *
Не знаю.... В итоге нужно в ЦАП-ы засовывать, посредством дма или софтом, значение из таблицы сигнала, причём не значения по порядку, а вычислять индекс на основании значения аккумулятора фазы,
Значения из таблицы по индексу из аккумулятора фазы заталкивать в массив в памяти, из которого выбирать при помощи DMA с активацией по таймеру. Буфер FIFO, выравнивающий неодинаковость времени вычислений.
pr0m
Цитата(ReAl @ Feb 10 2011, 00:08) *
Значения из таблицы по индексу из аккумулятора фазы заталкивать в массив в памяти, из которого выбирать при помощи DMA с активацией по таймеру. Буфер FIFO, выравнивающий неодинаковость времени вычислений.

Спасибо, мысль понятна, только аккумулятор бегает по кругу, через переполнение (изменение аака от 0 до 2^32-1 соответсвует одному периоду сигнала - фаза от 0 до 2*PI), причём на бОльших частотах шаг приращения акка выше. Предвычисление индексов и массива некоторого конечного размера для ЦАП-а, натравливание на него дма, и так опять же по кругу? А в чём преимущество в сравнении с 3 строчками кода, делающими это в главном цикле? Алгоритм не должен никого ждать, кроме как в случае прерывания извне для приёма новой конфигурации - только в этом случае он приостанавливается.
vmp
Цитата(pr0m @ Feb 10 2011, 09:06) *
Спасибо, мысль понятна, только аккумулятор бегает по кругу, через переполнение (изменение аака от 0 до 2^32-1 соответсвует одному периоду сигнала - фаза от 0 до 2*PI), причём на бОльших частотах шаг приращения акка выше. Предвычисление индексов и массива некоторого конечного размера для ЦАП-а, натравливание на него дма, и так опять же по кругу? А в чём преимущество в сравнении с 3 строчками кода, делающими это в главном цикле? Алгоритм не должен никого ждать, кроме как в случае прерывания извне для приёма новой конфигурации - только в этом случае он приостанавливается.

Преимущество - в DMA контроллере STM32, который умеет работать в режиме кольцевого буфера. Поэтому достаточно один раз предвычислить буфер и запрограммировать DMA. Дальше процессор может вообще не отвлекаться.
Если речь идет о выдаче синусоиды, то можно например иметь одну фиксированную таблицу синуса и манипулировать только частотой таймера.
pr0m
Цитата(vmp @ Feb 10 2011, 10:39) *
Преимущество - в DMA контроллере STM32, который умеет работать в режиме кольцевого буфера. Поэтому достаточно один раз предвычислить буфер и запрограммировать DMA. Дальше процессор может вообще не отвлекаться.
Если речь идет о выдаче синусоиды, то можно например иметь одну фиксированную таблицу синуса и манипулировать только частотой таймера.

То ли я Вас не понимаю, то ли Вы не поняли принцип работы алгоритма DDS....... Знаю и использую дма, в том числе в circular mode в других задачах.

Попробуйте реализовать Вашим методом ЛЧМ сигнал (для начала - непрерывный, потом добавить длительность радиоимпульса) с параметрами: f1=394575Гц, f2=417876Гц, частота модулирующего сигнала 1кГц (время изменения от f1 до f2 - 1мс). Первая же сложность, с которой столкнётесь - невозможность с такой точностью задать частоты с использованием таймеров. Впрочем, частоту 400кГц табличным методом + тактируемая таймером дма, уже не сможите получить, если в таблице больше 5-10 значений.
Вот то, что сейчас может делать алгоритм.
Нажмите для просмотра прикрепленного файла
ReAl
Цитата(pr0m @ Feb 10 2011, 08:06) *
Спасибо, мысль понятна, только аккумулятор бегает по кругу, через переполнение (изменение аака от 0 до 2^32-1 соответсвует одному периоду сигнала - фаза от 0 до 2*PI), причём на бОльших частотах шаг приращения акка выше. Предвычисление индексов и массива некоторого конечного размера для ЦАП-а, натравливание на него дма, и так опять же по кругу?
Да знаю я, как там фаза бегает.

У Вас - бегает, берётся индекс, выбирается из массива, заносится в ЦАП.

Проблема - нестабильность времени цикла.

Делаем - бегает, берётся индекс, выбирается из массива, заносится в небольшой кольцевой буфер в памяти. Оттуда по таймеру через ПДП в ЦАП. Даже прерывания всякие, дополнительная логика - не снесут работу ЦАП, если в кольцевом буфере достаточный запас и вычисления работают в среднем хотя бы немного быстрее, чем выборка из буфера по таймеру.

pr0m
Цитата(ReAl @ Feb 10 2011, 17:07) *
Проблема - нестабильность времени цикла.

Делаем - бегает, берётся индекс, выбирается из массива, заносится в небольшой кольцевой буфер в памяти. Оттуда по таймеру через ПДП в ЦАП. Даже прерывания всякие, дополнительная логика - не снесут работу ЦАП, если в кольцевом буфере достаточный запас и вычисления работают в среднем хотя бы немного быстрее, чем выборка из буфера по таймеру.

Ага, прозрел, спасибо. Возможности стм32 ещё толком не изучил. Реализовал на 1-м таймере, но есть побочные эффекты, сформулирую - отпишусь. Опережая, скажу, что склоняюсь всё же к начальному варианту, без дма.
pr0m
Красиво, получается, но основной недостаток - чуть медленнее, даже несмотря на то, что теперь код исполняется из флэш.
Код
#define BUF_SIZE    256
........
register u8 iDMA,iBuff;
........
while(1)
    {
        iDMA = (u8)(BUF_SIZE - DMA1_Channel5->CNDTR);
        while(iBuff != iDMA)
        {            
            buff[iBuff++] = _sinewave[acc.field.index];
            acc.total += dp;
            dp += ddp;
            if( dp > freq_stop )
            {
                dp = freq_start;
                ddp = tau;
            }    
        }
        if(bStopDDS)
            break;            
    }

Потому что загрузка из одного массива в другой отъедает львиную долю тактов. Чтобы заполнение циклическго буфера успевало за ДМА, минимальный период таймера получился = 25тактов (против 20 при программной реализации, самостоятельно пишущей в ЦАПы).

И ещё - вчера долго пялился в эти несколько строчек, потому что не работало (полная пурга на выходе ЦАПов), пока опцию компилятора О3+Optimize for time не сменил на без оптимизации по времени. Ассемблер армовский толком ещё не изучил, так что разобраться, что там наворотил компилятор, не смог.
akimych
В принципе, запись в буфер и запись в регистр ЦАП-а - одна и так же команда. Разницы быть не должно. Хотя в целом да, пересылок данных становится больше, дма ведь тоже ресурсы жрет. Так что учитывая жесткие временные рамки, может и правда оказаться, что этот вариант хуже.

Цитата
А в чём преимущество в сравнении с 3 строчками кода, делающими это в главном цикле?

Стабильные тайминги + немножко больше свободы, например для оптимизации. Но если удалось получить стабильное время без дма, то может и нет этих преимуществ. С другой стороны получается, что толку от SMT32 никакого, только лишние сложности.
pr0m
Цитата(akimych @ Feb 13 2011, 05:17) *
Изменить тут можно кое-что, сделать меньше проверок на каждом витке цикла, заполнять буфер по неск. значений подряд.
Заполнять по половине (1/4 и т.п.), можно отслеживать флаги дма (для 1/2) или счетчик дма.

Борюсь за каждый такт, а так много проверок, каждая превращается в инструкцию типа branch - >=3 такта. Выбрал буфер 256 байт и счётчик 1байт по понятным причинам - чтобы избежать внутри цикла лишних движений:
Код
if(iBuff>=BUZ_SIZE)
   iBuff = 0;


Цитата(akimych @ Feb 13 2011, 05:17) *
А так в принципе, запись в буфер и запись в регистр ЦАП-а - одна и так же команда.

Ну не одно и то же, говорит нам дизассемблер ).

Цитата(akimych @ Feb 13 2011, 05:17) *
Стабильные тайминги + немножко больше свободы, например для оптимизации. Но если удалось получить стабильное время без дма, то может и нет особых преимуществ. С другой стороны получается, что толку от SMT32 никакого, только лишние сложности.

Да, на ДМА остановился. И дело не в сложностях (один раз решаются), а в производительности. Для максимальной требуемой частоты известные мне 8-,16- разрядники не годятся, а стм32 показался оптимумом для моей задачи по соотношениям скорость-цена-доставабельность и т.п., не сочтите за рекламу. Вполне устроил бы шустрый камешек с 8 ногами наружу sm.gif
Как дойдут руки и подучу ассемблер, вынесу в отдельный модуль и пооптимизирую цикл. Всем спасибо за советы, тему вроде исчерпал.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.