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

ADC3 в режиме сканирования и DMA2

Здравствуйте, коллеги.

 

Не побеждается ADC3.

 

Имеем:

- STM32F103VC на 24MHz (не 72 для уменьшения потребления, т.к. есть back up на аккумуляторе),

- ADC3 в режиме сканирования трех каналов (11, 12 и 13), ADC тактируется 12MHz.

- естественно DMA2, канал 5, который складывает оцифрованное в буфер U16 длиной 32 триплета - всего 96 слов; таких буферов, конечно, два: один заполняем, из другого читаем.

- таймер 8, который с частотой 100Hz (замеряно) пихает ADC3 своим событием обновления (TRGO).

- прерывание от завершения передач DMA (по флагу TC), которое устанавливает флаг готовности буфера, переключает буферы и перегружает DMA канал.

 

В принципе, вся кухня работает. Но.

После рестарта может случиться, что данные оказываются смещенными в буфере: например, на месте канала 11 будут данные из канала 12, на месте 12 - из 13, ну а на месте 13 - из 11. Может случиться и сдвиг более дальнего порядка - в 11 - из 13, ну и так далее, по кругу. Причем этот сдвиг случается один раз при старте системы, а в процессе работы все остается фиксированно, без перескоков.

 

Я тщательно проанализировал последовательность инициализации и попытался в прерывании DMA сбрасывать бит STRT в ADC3->SR и "прочищать" ADC3->DR (чтением) перед тем как перегрузить DMA. Тем не менее в прерывании я мог всегда(!) поймать ситуацию при самом _первом_ вхождении, когда после перегрузки DMA его счетчик тут же уменьшался на единицу, словно ADC3 припрятал запрос к DMA несмотря на "прочистку".

Последнее, что я сделал, - поднял приоритет DMA до "kill 'em all", а в прерывании - выключал бит DMA в ADC3->CR2, делал прочистку ADC и перегрузку DMA и включал ADC3->CR2 снова. На моей плате это вроде привело к успеху (счетчик DMA не уменьшался сразу), а вот у коллеги - нет.

 

Идеи?

Заранее благодарен.

Изменено пользователем KnightIgor

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


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

Еще бы таймер "прочистить", чтобы события лишнего не генерил.

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


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

Еще бы таймер "прочистить", чтобы события лишнего не генерил.

Ну... это вряд ли поможет. Таймер стучит каждые 10мс - вечность. Оцифровка всех трех каналов занимает около 3х 85 циклов = 255 или ~22мкс (при 12MHz ADC). Пусть это был последний триплет, и сработало прерывание DMA. Прерывание из трех строк не зависает на 10мс.

 

П.С. очистка таймера (каким образом?) наверняка приведет к изменению периода, то есть к шуму квантизации... Не кошерно как-то...

Изменено пользователем KnightIgor

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


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

В принципе, вся кухня работает. Но.

После рестарта может случиться, что данные оказываются смещенными в буфере: например, на месте канала 11 будут данные из канала 12, на месте 12 - из 13, ну а на месте 13 - из 11. Может случиться и сдвиг более дальнего порядка - в 11 - из 13, ну и так далее, по кругу. Причем этот сдвиг случается один раз при старте системы, а в процессе работы все остается фиксированно, без перескоков.

вот недавно сам столкнулся с таким же. Нашел решение на этом же форуме. А именно сбрасывать регулярные каналы. И все заработал

вот мой код

 
//=== Прерывания DMA ADC1 ===//
void DMA1_Channel1_IRQHandler(void)
{
    ADC1->CR2 &= ~ADC_CR2_DMA;// | ADC_CR2_ADON);        // выключаем DMA запросы АЦП
    ADC1->SR &= ~(ADC_SR_STRT);           // сброс флага Overrun и старта преобразования
    ADC1->SQR1 &= ~ADC_SQR1_L;            // сброс количества последовательных преобразований  [сброс указателя канала]
    DMA1_Channel1->CCR &= ~ (DMA_CCR_EN |DMA_CCR_TCIE);   //// деактивация 1го потока DMA1

    if(DMA1->ISR & DMA_ISR_TCIF1) EventFlags.Bit.ADCConvFinish = 1;    // проверяем прерывание по завершению передачи блока
    DMA1->IFCR=DMA_IFCR_CGIF1|DMA_IFCR_CTCIF1|DMA_IFCR_CHTIF1|DMA_IFCR_CTEIF1;  //снимаем биты в  DMA1->ISR (пока все)
}

//=== Прерывания TMR2 ===//
void TIM2_IRQHandler(void){   // Период - 160 мксек при частоте сети 50 Гц.
vu32 aa, cosa, cosb, cosc;
vu8 i;

  TIM2->SR &= ~TIM_SR_UIF;    // Снять флаг прерывания.

  DMA1_Channel1->CNDTR = 18;                  // указываем число пересылаемых данных 3 канала по 6-и перобразовании
  DMA1_Channel1->CCR |=  DMA_CCR_EN | DMA_CCR_TCIE;// запустили очередное преобразование ADC (должны успеть,ааа)

 

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


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

Ну... это вряд ли поможет. Таймер стучит каждые 10мс - вечность. Оцифровка всех трех каналов занимает около 3х 85 циклов = 255 или ~22мкс (при 12MHz ADC). Пусть это был последний триплет, и сработало прерывание DMA. Прерывание из трех строк не зависает на 10мс.

По таймеру все 3 канала преобразуются, значит? Группа.

8-битовые? А по 16 битов, может, без сбоев пойдет?

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


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

По таймеру все 3 канала преобразуются, значит? Группа.

Группа.

8-битовые? А по 16 битов, может, без сбоев пойдет?

Собственно, 12-битовые (разрядность ADC), выровненные влево до 16-ти бит (разрядность DR), DMA забирает как U16.

 

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


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

После рестарта может случиться, что данные оказываются смещенными в буфере: например, на месте канала 11 будут данные из канала 12, на месте 12 - из 13, ну а на месте 13 - из 11.
Значит на момент старта DMA АЦП уже успело сделать два измерения и результат первого для DMA оказался потерян. В какой последовательности вы запускаете всю эту кухню? По моим понятиям надо делать так:

1) таймер остановлен

2) Настраиваем АЦП, сбрасываем все готовности, которые могли остаться от предыдущей работы.

3) Настраиваем ПДП

4) запускаем таймер.

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


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

вот недавно сам столкнулся с таким же. Нашел решение на этом же форуме. А именно сбрасывать регулярные каналы. И все заработал

вот мой код

 
//=== Прерывания DMA ADC1 ===//
void DMA1_Channel1_IRQHandler(void)
{
    ADC1->CR2 &= ~ADC_CR2_DMA;// | ADC_CR2_ADON);        // выключаем DMA запросы АЦП
    ADC1->SR &= ~(ADC_SR_STRT);           // сброс флага Overrun и старта преобразования
    ADC1->SQR1 &= ~ADC_SQR1_L;            // сброс количества последовательных преобразований  [сброс указателя канала]
    DMA1_Channel1->CCR &= ~ (DMA_CCR_EN |DMA_CCR_TCIE);   //// деактивация 1го потока DMA1

    if(DMA1->ISR & DMA_ISR_TCIF1) EventFlags.Bit.ADCConvFinish = 1;    // проверяем прерывание по завершению передачи блока
    DMA1->IFCR=DMA_IFCR_CGIF1|DMA_IFCR_CTCIF1|DMA_IFCR_CHTIF1|DMA_IFCR_CTEIF1;  //снимаем биты в  DMA1->ISR (пока все)
}

//=== Прерывания TMR2 ===//
void TIM2_IRQHandler(void){   // Период - 160 мксек при частоте сети 50 Гц.
vu32 aa, cosa, cosb, cosc;
vu8 i;

  TIM2->SR &= ~TIM_SR_UIF;    // Снять флаг прерывания.

  DMA1_Channel1->CNDTR = 18;                  // указываем число пересылаемых данных 3 канала по 6-и перобразовании
  DMA1_Channel1->CCR |=  DMA_CCR_EN | DMA_CCR_TCIE;// запустили очередное преобразование ADC (должны успеть,ааа)

Я тоже так пробую делать (сброс бита DMA в CR2 и STRT в SR, чтение DR три раза) за исключением сброса количества преобразований в SQR1, правда. Сейчас попробую и это, хотя на мой взгляд - как из пушки по воробьям.

 

Кстати, в Вашем коде нет собственно "взвода" ADC на следующую операцию. Опущено здесь для иллюстрации? Еще для упрощения можно делать ADC1->SR = ~(ADC_SR_STRT), без &=, т.к. регистр SR есть rc/w0 - тип: пишешь прямо 0 в сбрасываемый бит.

 

 

Значит на момент старта DMA АЦП уже успело сделать два измерения и результат первого для DMA оказался потерян. В какой последовательности вы запускаете всю эту кухню? По моим понятиям надо делать так:

1) таймер остановлен

2) Настраиваем АЦП, сбрасываем все готовности, которые могли остаться от предыдущей работы.

3) Настраиваем ПДП

4) запускаем таймер.

Ладно, цитатнём код.

Единократная инициализация:

   // TIM8 config:
   DBGMCU_Config(DBGMCU_TIM8_STOP, ENABLE);
   MIC_TIM_INIT(TIMx);
   // At this point TIM8 is still stopped; see MIC_Control(true) below.

   // Init DMA2 Channel5:
   DMA_DeInit(ADCx_DMA);
   DMA_INIT(ADCx_DMA, DMA_InitStruct);
   IRQ_INIT(NVIC_DMAInitStructure);
   DMA_TC_ENABLE(ADCx_DMA);    // transmission complete interrupt enable
   // At this point DMA is still disabled; see MIC_Control(true) below.

   // Init ADC:
   ADCx_ClockConfig(ADCx, SystemCoreClock); 

   ADC_DeInit(ADCx);
   ADC_INIT(ADCx, ADC_InitStructure);
   ADC_ConfigChannels(ADCx, MIC_ChannelRanks, sizeof(MIC_ChannelRanks));

   ADC_ENABLE(ADCx);               // wake up
   ADC_Calibrate(ADCx); ADCx->DR;  // calibrate once, clear DR afterwards
   ADC_ExternalTrigConvCmd(ADCx, ENABLE);  // enable by TIM8 TRGO

   // Start MIC sampling
   MIC_Control(true);

Это объяснение для MIC_Control(true):

// -----------------------------------------------------------------------------
//
//  Clears the data register n times resetting the scan mode
//
void ADC_ClearRegular(ADC_TypeDef* adc, int cnt)
{
   adc->SR = ~ADC_SR_STRT;    // rc/w0 register
   while (cnt--) adc->DR;
}
// -----------------------------------------------------------------------------
//
//  Starts/stops the ADC processing
//
void MIC_Control(bool action)
{
   // Stop completely
   TIM_DISABLE_R(TIMx);
   ADC_DMACmd(ADCx, DISABLE);
   ADC_ClearRegular(ADCx, MIC_CHAN_COUNT);

   if (action)
   {
       memset(*ADC_MICx, MIC_INIT_VALUE, sizeof(*ADC_MICx));
       MIC_DMA_Restart(bufnum = 0);
       ADC_DMACmd(ADCx, ENABLE);       // use DMA

       // Restart completely
       MIC_Reload_Timer(MIC_SAMPLE_RATE);
       TIM_ENABLE_R(TIMx);         // start the sample rate timer
   }
}

И самое интересное в прерывании:

// -----------------------------------------------------------------------------
//
//  DMA2 Channel5 IRQ handler
//
void DMA2_Channel4_5_IRQHandler(void)
{
   ADC_DMACmd(ADCx, DISABLE);
   ADC_ClearRegular(ADCx, MIC_CHAN_COUNT);
   MIC_DMA_Restart(bufnum = 1 - bufnum);
   ADC_DMACmd(ADCx, ENABLE);      
   sample_ready.val = 1;
}

В коде много функций, которые на самом деле макросы, но из имен ясно, что они делают. Думаю, последовательность ясна.

Изменено пользователем KnightIgor

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


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

Кстати, в Вашем коде нет собственно "взвода" ADC на следующую операцию. Опущено здесь для иллюстрации?

 

в принципе дальше в коде стоят

  
ADC1->SQR1 |= (uint32_t)((6-1) << 20);
ADC1->CR2 |=  ADC_CR2_SWSTART | ADC_CR2_DMA;    //ADC_CR2_ADON| //первый старт, н нужен, потому что как только разрешил DMA оно сразу стартует

но вы не поверите. и без них работает, потому что ранее записано кол-во перобразований, осуществяемых через DMA и выставлено прерывание. А может быть это просто мистика, но оно работает :)

DMA1_Channel1->CNDTR = 18;
DMA1_Channel1->CCR |=  DMA_CCR_EN | DMA_CCR_TCIE

Изменено пользователем AlHakim

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


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

Ой, сложно как все...

 

Буферов два? Расположите их подряд. Пусть ПДП таскает в них данные в циклическом режиме. Без взяких перенастроек. "Пустил-забыл". ПДП дает два прерывания - "половину буфера заполнил" и "весь буфер запомнил". По первому флагу обрабатываем первый буфер, по второму - второй. В обработчике проверяем флаги ПДП, сбрасываем сработавший, запускаем обработку соответствующей половины "склеенного" буфера. Все. Больше ничего не трогаем.

 

Ваш ADC_ClearRegular() делает совсем не то, что вы хотели. Он может вычитать результат максимум только одного преобразования. А может и вообще ни одного не вычитать. Потому что DR хранит результат одного (последнего) преобразования, никакого буфера типа FIFO на несколько результатов там нет. Если бы мне понадобилась подобная функция, я бы в ней выбросил параметр cnt, проверял STRT и если он выставлен (преобразование началось) - дожидался EOC, после чего сбрасывал бы EOC чтением DR, а STRT - записью в него 0.

 

 

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


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

Ой, сложно как все...

 

Буферов два? Расположите их подряд. Пусть ПДП таскает в них данные в циклическом режиме. Без взяких перенастроек. "Пустил-забыл". ПДП дает два прерывания - "половину буфера заполнил" и "весь буфер запомнил". По первому флагу обрабатываем первый буфер, по второму - второй. В обработчике проверяем флаги ПДП, сбрасываем сработавший, запускаем обработку соответствующей половины "склеенного" буфера. Все. Больше ничего не трогаем.

 

Ваш ADC_ClearRegular() делает совсем не то, что вы хотели. Он может вычитать результат максимум только одного преобразования. А может и вообще ни одного не вычитать. Потому что DR хранит результат одного (последнего) преобразования, никакого буфера типа FIFO на несколько результатов там нет. Если бы мне понадобилась подобная функция, я бы в ней выбросил параметр cnt, проверял STRT и если он выставлен (преобразование началось) - дожидался EOC, после чего сбрасывал бы EOC чтением DR, а STRT - записью в него 0.

Хорошие идеи. Спасибо. И все же интересно, почему происходит сдвиг данных.

 

P.S.

Итак, я реализовал Вашу идею, все замечательно. Еще раз спасибо. И кажется, я обнаружил-таки, в чем была причина "лишних" неполных данных.

В доке написано, что повторное разрешение ADC (то есть, запись "1" на уже "1" в CR2.ADON) приводит к запуску преобразования:

 

Conversion starts when this bit holds a value of 1 and a 1 is written to it.

 

Тут же далее курсивом упомянуто, что если в этом регистре одновременно меняются и другие биты (например CR2.DMA), то запуск преобразования не происходит:

 

If any other bit in this register apart from ADON is changed at the same time, then

conversion is not triggered. This is to prevent triggering an erroneous conversion.

Так вот, враки это все! Этот "предохранитель" не работает, и преобразование запускается. Четко в отладчике видно. Тема для errata. Вот и получалось, что команда ADC_DMACmd(ADCx, DISABLE) запрещала передачу запросов в DMA, но запускала каждый раз преобразование. Оттуда и "лишние" данные и сдвиги.

Изменено пользователем KnightIgor

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


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

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

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

Гость
Ответить в этой теме...

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

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

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

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

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

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