Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: AT91SAM7xxxx, ADC, 2 канала и PDC
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > ARM, 32bit
hd44780
Привет всем.

AT91SAM7S256 цифрует стереозвук встроенным АЦП (ADC7-левый, ADC6-правый канал) и далее обрабатывает эти выборки. Выборки сейчас по 512 байт.

Сейчас сделано "как в AVR" - запуск АЦП по сигналу TIOA1 ~24kHz, съём данных в прерывании АЦП. Каналы АЦП переключаются в прерывании.

Код
// Прерывание АЦП - один из каналов завершил преобразование
void ADC_IrqHandler(void)
{
    dword status;
    word wValue;
    
    status = ADC_GetStatus(AT91C_BASE_ADC);
    
    if ( (status & (1<<ADC_CHANNEL_LEFT) ) !=0  )
    { // левый канал закончил преобразование
      wValue = AT91C_BASE_ADC->ADC_CDR7;

      // Включить правый канал
      ADC_EnableChannel ( AT91C_BASE_ADC, ADC_CHANNEL_RIGHT );
      ADC_EnableIt ( AT91C_BASE_ADC, 1 << ADC_CHANNEL_RIGHT );
      
      adcDataLeft [ dataPosLeft ] = wValue >> 2;
      dataPosLeft ++;
      if ( dataPosLeft == ADC_DATA_SIZE )
      {
        // останов преобразований
        StopConversion ( );
        conversionDone = 1;
      } // if
    } // if
  
    if ( (status & (1<<ADC_CHANNEL_RIGHT) ) !=0  )
    { // правый канал закончил преобразование
      // AT91C_BASE_ADC->ADC_CDR6 - регистр канала
      wValue = AT91C_BASE_ADC->ADC_CDR6;

      // Включить левый канал
      ADC_EnableChannel ( AT91C_BASE_ADC, ADC_CHANNEL_LEFT );
      ADC_EnableIt ( AT91C_BASE_ADC, 1 << ADC_CHANNEL_LEFT );
      
      adcDataRight [ dataPosRight ] = wValue >> 2;
      dataPosRight ++;
      if ( dataPosRight == ADC_DATA_SIZE )
      {
        // останов преобразований
        StopConversion ( );
        conversionDone = 1;
      } // if
    } // if
} // ADC_IrqHandler


Процесс начинается с левого канала:
ADC_EnableChannel ( AT91C_BASE_ADC, ADC_CHANNEL_LEFT );
ADC_EnableIt ( AT91C_BASE_ADC, 1 << ADC_CHANNEL_LEFT );

Это работает нормально. Результирующая частота дискретизации получается, конечно, в 2 раза меньше, но я сейчас не об этом.

Я где-то на форумах читал, что если написать
ADC_EnableChannel ( AT91C_BASE_ADC, ADC_CHANNEL_LEFT | ADC_CHANNEL_RIGHT );
ADC_EnableIt ( AT91C_BASE_ADC, ( 1 << ADC_CHANNEL_LEFT ) | ( 1 << ADC_CHANNEL_RIGHT ) );
то он будет сам щёлкать каналы по очереди. Попробовал - не работает. Точнее сказать - работает только левый канал - ADC7. Условие для правого канала вообще никогда не срабатывает. Проверял по DBGU.
Может я неправильно включаю этот режим... Или чего-то недопонимаю...

Вопрос - может ли он сам переключать каналы? Ведь зачем-то в регистрах сделано по битику на канал (а на как в AVR-ах ADMUX=7 и 6 туда уже одновременно не запишешь)?

Вопрос особенно актуален, если задействовать PDC. Получать 512 байт сперва с одного, потом 512 с другого, да ещё и с задержками на перепрограммирование ADC/PDC - как-то "некошерно", необходима как можно большая синхронность в оцифровке каналов. А это - только оцифровка каналов по очереди.
Не считая установки 2-х независимых АЦП

Спасибо.

PS.
Замечания типа "Омар фигня, лобстер круче", где омар==AT91SAM7, лобстер==STM32 просьба не писать. Я прекрасно знаю, что в STM32 есть 2-3 независимых АЦП и прочие плюшки, но пока интересует исключительно AT91SAM7.
RabidRabbit
Цитата(hd44780 @ Dec 10 2012, 17:17) *
Я где-то на форумах читал, что если написать
ADC_EnableChannel ( AT91C_BASE_ADC, ADC_CHANNEL_LEFT | ADC_CHANNEL_RIGHT );
ADC_EnableIt ( AT91C_BASE_ADC, ( 1 << ADC_CHANNEL_LEFT ) | ( 1 << ADC_CHANNEL_RIGHT ) );
то он будет сам щёлкать каналы по очереди. Попробовал - не работает. Точнее сказать - работает только левый канал - ADC7. Условие для правого канала вообще никогда не срабатывает. Проверял по DBGU.
Может я неправильно включаю этот режим... Или чего-то недопонимаю...


DS==========
Only one start command is necessary to initiate a conversion sequence on all the channels. The
ADC hardware logic automatically performs the conversions on the active channels, then waits
for a new request. The Channel Enable (ADC_CHER) and Channel Disable (ADC_CHDR) Registers
enable the analog channels to be enabled or disabled independently.
If the ADC is used with a PDC, only the transfers of converted data from enabled channels are
performed and the resulting data buffers should be interpreted accordingly.
DS==========

Попробуйте сделать, как в даташите - в регистр ADC_CHER записать два бита для соответствующих каналов, настроить PDC, в регистр ADC_IER записать бит ENDRX и запустить таймер. Руками по регистрам, кстати, гораздо понятнее получается, чем при использовании библиотечных функций (на мой взгляд, конечно).
hd44780
Цитата(RabidRabbit @ Dec 11 2012, 07:47) *
Попробуйте сделать, как в даташите - в регистр ADC_CHER записать два бита для соответствующих каналов, настроить PDC, в регистр ADC_IER записать бит ENDRX и запустить таймер. Руками по регистрам, кстати, гораздо понятнее получается, чем при использовании библиотечных функций (на мой взгляд, конечно).


Спасибо.
ADC_CHER я так и писал по задумке. Но недосмотрел реализацию той функции. В итоге бред получался rolleyes.gif .
Про END_RX понятно, сделаю. Но ещё надо курить, как с PDC работать. Я с ним дел ещё не имел ...
hd44780
AT91C_BASE_ADC->ADC_CHER и AT91C_BASE_ADC->ADC_IER установил руками - помогло. Оба канала живые. Даже DBGU не понадобился, и так видно sm.gif .

Приступаю вплотную к PDC sm.gif .
RabidRabbit
Я думаю, с PDC разберётесь с полпинка, там всё просто и логично. На всякий случай - для ADC счётчики в PDC считают не байты, а "отсчёты", если ADC в 10-битном режиме, то в счётчики надо заряжать количество 16-битных слов в буфере.
hd44780
Цитата(RabidRabbit @ Dec 12 2012, 08:25) *
Я думаю, с PDC разберётесь с полпинка, там всё просто и логично.

Надеюсь rolleyes.gif .

Цитата(RabidRabbit @ Dec 12 2012, 08:25) *
На всякий случай - для ADC счётчики в PDC считают не байты, а "отсчёты", если ADC в 10-битном режиме, то в счётчики надо заряжать количество 16-битных слов в буфере.


Да, спасибо. Я в курсе.
До этого у меня были 10-битовые отсчёты, но в прерывании я всё равно клал в буферы байты, сдвигая считанное 10-бит значение на 2 бита вправо.
Вчера перевёл АЦП в 8-битный режим, убрал эти сдвиги - работает. Пока без PDC.
Сергей Борщ
Когда будете работать с PDC обратите внимание на такой момент - у вас АЦП стартует от таймера и отсчеты выдает независимо от того - успели вы перезарядить PDC на новый буфер или нет. Так вот, если вы будете перезаряжать PDC слишком долго и АЦП успеет сделать преобразование, у вас один отсчет потеряется и все остальнвые будут лежать в буфере со сдвигом, т.е левый и правый каналы перепутаются. Отследить такую ситуацию можно по флагу RXBUFF. К сожалению ни циклических пересылок, ни цепочек буферов в PDC SAM7 не предусмотрено.
hd44780
Спасибо, на будущее учту.

Но пока преобразования разовые - выдал выборку, остановился. Повторный запуск - программный.
Обработка выборок достаточно длительная - FFT, отрисовка на дисплее. Вряд ли это всё успеет прокрутиться за время выборки даже 10-20 блоков..
Jack_of_Shadows
Цитата
Так вот, если вы будете перезаряжать PDC слишком долго и АЦП успеет сделать преобразование, у вас один отсчет потеряется и все остальнвые будут лежать в буфере со сдвигом, т.е левый и правый каналы перепутаются

хотелось бы уточнить: в похожей ситуации я настраиваю PDC на работу с несколькими каналами, подсовываю ему буфер и включаю прерывания End of Receive Buffer и Receive Buffer Full. Я правильно понимаю, что когда прерывание срабатывает, я должен успеть:
передать PDC новый буфер до того, как пройдет хотя бы одно преобразование (чтобы нумерация каналов не сбилась)
обработать данные из буфера до того, как АЦП успеет заполнить следующий (чтобы данные не затерлись)
?

upd:
понял что я не прав. PDC подсовывается адреса двух буферов, RPR - куда читать сейчас, RNPR - куда читать когда закончится первый буфер. По прерыванию End of Receive Buffer мне нужно снова указывать буфер RNPR? Немного запутался, можно ли поподробнее рассказать про случай, который я процитировал вначале. У меня имеется уже готовая чужая программа, в которой периодически проявляются проблемы перепутывания каналов АЦП, читаемых через PDC.

upd:
пока никто не отвечает, кажется у меня очередное просветление: если программа чтения АЦП укажет при запуске два буфера RPR/RNPR и по прерыванию ENDRX будет своевременно их "перезаряжать" - все будет отлично. Если же программа затупит и в какой-то момент ни одного буфера не будет задано - все что в это время оцифровывает АЦП потеряется. При использовании нескольких каналов соответственно может потеряться часть из них, и нумерация собьется. Верно?
Сергей Борщ
QUOTE (Jack_of_Shadows @ Dec 12 2012, 11:40) *
Если же программа затупит и в какой-то момент ни одного буфера не будет задано - все что в это время оцифровывает АЦП потеряется. При использовании нескольких каналов соответственно может потеряться часть из них, и нумерация собьется.
Совершенно верно.
RabidRabbit
Цитата(Сергей Борщ @ Dec 12 2012, 11:24) *
Когда будете работать с PDC обратите внимание на такой момент - у вас АЦП стартует от таймера и отсчеты выдает независимо от того - успели вы перезарядить PDC на новый буфер или нет. Так вот, если вы будете перезаряжать PDC слишком долго и АЦП успеет сделать преобразование, у вас один отсчет потеряется и все остальнвые будут лежать в буфере со сдвигом, т.е левый и правый каналы перепутаются. Отследить такую ситуацию можно по флагу RXBUFF. К сожалению ни циклических пересылок, ни цепочек буферов в PDC SAM7 не предусмотрено.

Как же так? sm.gif Я думал, что тяжело не успеть обновить два регистра PDC за время, пока отработает половина буфера (а это будет одно преобразование только в случае если размер буфера == 1, что странно), ну если только программа совсем повиснет...
Сергей Борщ
QUOTE (RabidRabbit @ Dec 12 2012, 17:51) *
Как же так? sm.gif Я думал, что тяжело не успеть обновить два регистра PDC за время, пока отработает половина буфера
Обработка бывает разной. Кроме обработки могут быть и другие задачи. Если размер буфера недостаточен - можно и не успеть. Я описал ситуацию чтобы автор знал, куда копать в случае, если вдруг обнаружит что каналы путаются.
Jack_of_Shadows
Во время стационарной работы конечно такое маловероятно. Я же кажется попался на достаточно глупую ошибку:
При запуске платы идет достаточно длительный процесс инициализации, на время которого прерывания глобально запрещаются, чтобы не мешали. Когда инициализируются ADC и PDC, сразу же начинается оцифровка и складывание значений в буфер, однако прерывание переполнения буфера не срабатывает - запрещено все таки. Если процесс инициализации в силу каких-то причин идет то дольше то меньше, сбой то случается то нет.
hd44780
Jack_of_Shadows, а Вы не запускайте шарманку во время инициализации. Зачем оно там? Тем более при сложной инициализации ...
Запускайте тогда, когда Ваша программа на 100% будет готова принимать эти потоки данных.

Я сделал у себя методы типа AdcStart, AdcStop - по сути старт/стоп таймера и вызываю их когда мне надо ..
У меня по выборке вообще считается длительный и довольно тяжёлый FFT, там никаких буфров может не хватить этому PDC. Поэтому его проще вообще вырубить на это время. Тем более, что у меня не осциллограф, где потеря каких-то 10 мс на расчёт/отрисовку смерти подобно.
Jack_of_Shadows
hd44780, сейчас то, когда я разобрался, именно так и сделал. Мне вообще потеря данных не важна, там всего лишь положение джойстика оцифровывается sm.gif
RabidRabbit
Цитата(Сергей Борщ @ Dec 13 2012, 11:09) *
Обработка бывает разной. Кроме обработки могут быть и другие задачи. Если размер буфера недостаточен - можно и не успеть.

Я с Вами всё же не согласен. Переключение буферов (АКА запись в регистры PDC) производится в прерывании по ENDRX. И помешать этому может только запрещение прерываний где-то ещё на всё время оцифровки буфера. Ну согласитесь, что это экстремальный вариант, который и рассматривать не стоит sm.gif
Сергей Борщ
QUOTE (RabidRabbit @ Dec 14 2012, 09:28) *
И помешать этому может только запрещение прерываний
В принципе - да. У меня же ситуация возникала элементарно - достаточно было остановить выполнение программы под отладчиком. И все - после продолжения выполнения программы каналы иногда путались. Я убил довольно много времени пока понял откуда растут ноги у этого явления.
hd44780
Народ, я не понял, зачем регистры RNPR (Receive Next Pointer Register) и RNCR (Receive Next Counter Register)?
Типа, если я не перепрограммирую RPR/RCR, он сам на те кинется?

Я их пока не программирую, т.к. по прерыванию ENDRX глушу таймер. Правильно ли это
Или их надо инициализировать тем же? В смысле ставить RNCR=RCR=count. И указатель аналогично.

Спасибо.
Сергей Борщ
QUOTE (hd44780 @ Dec 14 2012, 14:21) *
Народ, я не понял, зачем регистры RNPR (Receive Next Pointer Register) и RNCR (Receive Next Counter Register)?
Типа, если я не перепрограммирую RPR/RCR, он сам на те кинется?
Типа да. Точнее, в прерывании программировать надо именно RNPR/RNCR, а в RPR/RCR значения из RNPR/RNCR протолкнет DMA контроллер. RPR/RCR программируются только перед запуском процесса.
hd44780
Спасибо, примерно понял, но хочу уточнить.

Если я остановлю процесс выборки, а потом захочу запустить повторно, то перед стартом таймера мне надо будет заполнить только RNPR/RNCR, а RPR/RCR вообще не трогать?
Сергей Борщ
QUOTE (hd44780 @ Dec 14 2012, 15:28) *
мне надо будет заполнить только RNPR/RNCR, а RPR/RCR вообще не трогать?
Тогда он будет складывать туда, где остановился перед остановкой. Если вы хотите, чтобы он начал складывать с начала буфера, надо перегрузить RPR/RCR тоже.
hd44780
Сергей Борщ, спасибо. Понял.

Пошёл доделывать sm.gif
hd44780
Написал так:

1. Инициализация АЦП

Код
void ConfigureAdc(void)
{
  dword shtim, startup, prescal, trgSel;
    
  AT91C_BASE_PMC->PMC_PCER = (1UL << AT91C_ID_ADC);    // разрешить тактовую для ADC
  AT91C_BASE_ADC->ADC_CR = AT91C_ADC_SWRST;            // reset ADC

  shtim=8;
  startup=0x0F;
  prescal=0x02;
  trgSel=1;     // Запуск по TIOA1 (TC1)

  ADC_CfgModeReg (AT91C_BASE_ADC, (shtim << 24) | (startup << 16) | (prescal << 8) |
                  AT91C_ADC_SLEEP_NORMAL_MODE | AT91C_ADC_LOWRES_8_BIT |
                  (trgSel << 1) | AT91C_ADC_TRGEN_EN);

  // выкл все каналы
  AT91C_BASE_ADC->ADC_CHDR = 0xFF;
  
  // Включить канал 6, 7
  AT91C_BASE_ADC->ADC_CHER = (1 << ADC_CHANNEL_LEFT) | (1 << ADC_CHANNEL_RIGHT);
  
  // Разрешить прерывания каналов 6 и 7 в ADC
  AT91C_BASE_ADC->ADC_IER = ( 1 << ADC_CHANNEL_LEFT ) | ( 1 << ADC_CHANNEL_RIGHT );

  // Установка прерывания ADC
  IRQ_ConfigureIT ( AT91C_ID_ADC, 6, ADC_IrqHandler );
  
  // Set AIC_IECR
  IRQ_EnableIT(AT91C_ID_ADC);
  
  ////////////////////////////////////////////////////////////////////////////////  
  // PDC
  //  Буфер данных
  AT91C_BASE_ADC->ADC_RPR = (dword)ADCbuf;
  AT91C_BASE_ADC->ADC_RNPR = (dword)ADCbuf;
  
  //  Set receive counter register (number of transfers)
  AT91C_BASE_ADC->ADC_RCR = sizeof ( ADCbuf );    
  AT91C_BASE_ADC->ADC_RNCR = sizeof ( ADCbuf );    

  // ENDRX - End of receive buffer interrupt enable (когда регистр ADC_RCR досигает нуля)
  AT91C_BASE_ADC->ADC_IER = AT91C_ADC_ENDRX;

  // Конфигурирование  TC1 на переключение TIOA1  23.81kHz
  ConfigureTc1 ( );
} // ConfigureAdc


Всё, что до комментария PDC - рабочее на 100%. Без PDC работает нормально.

2. Запуск:

Код
void StartConversion ( void )
{
  //  Буфер данных
  AT91C_BASE_ADC->ADC_RCR = sizeof ( ADCbuf );    
  AT91C_BASE_ADC->ADC_RNCR = sizeof ( ADCbuf );    

  //  Set receive counter register (number of transfers)
  AT91C_BASE_ADC->ADC_RCR = sizeof ( ADCbuf );    
  AT91C_BASE_ADC->ADC_RNCR = sizeof ( ADCbuf );    

  //  Enable PDC receiver requests, disable transmitter requests
  AT91C_BASE_ADC->ADC_PTCR = AT91C_PDC_RXTEN | AT91C_PDC_TXTDIS;
  
  // запуск ТС1
  AT91C_BASE_TC1->TC_CCR = AT91C_TC_SWTRG | AT91C_TC_CLKEN;
} // StartConversion


До PDC здесь был только запуск TC1.
Инициализация проходит нормально, а после вызова StartConversion всё наглухо виснет. Даже прерываний от АЦП нету.
Проверялось через DBGU.

В чём я ошибся?
RabidRabbit
Я немного не понимаю, инициализация указателей для PDC происходит только в коде 1.? Зачем в коде 2. дважды инициализируются счётчики? Может, все исходники прицепите?

Вот это лишнее
AT91C_BASE_PMC->PMC_PCER = (1UL << AT91C_ID_ADC); // разрешить тактовую для ADC
ADC и так олвэйс клокед.
hd44780
Цитата(RabidRabbit @ Dec 15 2012, 09:35) *
Я немного не понимаю, инициализация указателей для PDC происходит только в коде 1.? Зачем в коде 2. дважды инициализируются счётчики?

Да то в полусне вчера писал biggrin.gif . Поправил - всё равно виснет sad.gif .

Цитата(RabidRabbit @ Dec 15 2012, 09:35) *
Может, все исходники прицепите?

Прицепил, посмотрите. Компилятор - IAR 6.40.

Цитата(RabidRabbit @ Dec 15 2012, 09:35) *
AT91C_BASE_PMC->PMC_PCER = (1UL << AT91C_ID_ADC); // разрешить тактовую для ADC
ADC и так олвэйс клокед.

У коллег списал sm.gif .
RabidRabbit
Я бы убрал вот это
// Разрешить прерывания каналов 6 и 7 в ADC
AT91C_BASE_ADC->ADC_IER = ( 1 << ADC_CHANNEL_LEFT ) | ( 1 << ADC_CHANNEL_RIGHT );

И выкинул бы нафик из обработчика прерывания от ADC вызовы DebugSendString() - у Вас один символ будет передаваться дольше, чем оцифровка одного отсчёта.
Ну и может в конце обработчика прерывания ADC поставить запись в регистр AIC_EOICR, на всякий случай... хотя мож яр сам ставит...

hd44780
Поправил. Прерывания каналов вырубил, добавил AT91C_BASE_AIC->AIC_EOICR = 0; в конец прерывания. Хотя раньше оно и без него работало ...
Даже сделал 2-й буфер, для ADC_RNPR ..

Ни фига, всё равно виснет sad.gif .

Вот нашёл осцилл самодельный на таком же проце - http://www.tomeko.net/miniscope_v2.php . Там ADC вроде через PDC тоже шурует. Только буферов там 3 или 4 штуки, в прерывании переключаются.
Вроде всё также ....
Jack_of_Shadows
hd44780, памяти для буфера ацп в куче точно хватает? У меня в похожей программе (только писалось под Crossworks) тоже проц вис когда запускал АЦП, пока не поставил heap побольше.
hd44780
Jack_of_Shadows, у меня кучи и динамического распределения нет, все буфера статические и фиксированные.

// Размер буфера одного канала
#define ADC_DATA_SIZE 512

// Буфер ADC PDC (размер под 2 канала)
static volatile byte ADCbuf [ADC_DATA_SIZE*2];
static volatile byte ADCbufNext [ADC_DATA_SIZE*2];

Регистры инициализирую так:

// Буфер данных
AT91C_BASE_ADC->ADC_RPR = (dword)ADCbuf;
AT91C_BASE_ADC->ADC_RNPR = (dword)ADCbufNext;

// Set receive counter register (number of transfers)
AT91C_BASE_ADC->ADC_RCR = sizeof ( ADCbuf );
AT91C_BASE_ADC->ADC_RNCR = sizeof ( ADCbuf );

byte == unsigned char (через typedef). Может ещё sizeof косячит - не проверял его ...
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.