Idler (29.04.2017 22:58 - 23:31, просмотров: 370) ответил Ксения на Мне не срочно после заполнения буфера надо начинать повторное его заполнение. Периодичность между обновлениями содержимого буфера около 1 сек, что соответствует времени обновления цифири на дисплее. Т.е. за это время я даже FFT успела бы на этом
Вот как то так (поправил и прокомментировал) (еще 2 раза поправил):
DMA.CTRL = 0;
DMA.CTRL = DMA_RESET_bm;
while ((DMA.CTRL & DMA_RESET_bm) != 0)
;
// configure DMA controller
// DMA.CTRL = DMA_CH_ENABLE_bm | DMA_DBUFMODE_CH01_gc; // double buffered with channels 0 and 1
// В данном случае "double buffered" не нужно, проще будет,
// и не DMA_CH_ENABLE_bm, а DMA_ENABLE_bm. Хотя они "чисто случайно" совпадают.
// Если другие каналы ДМА тоже используются то нужно подумать о приоритетах. К сожалению,
// Атмеловский ДМА имеет приоритет меньше, чем процессор. При вызове любой функции или прерывания
// идет запушивание кучи используемых регистров, и все это время ДМА не работает.
// При выходе - аналогично. Если используются и другие каналы ДМА, то можно задать им не равные
// приоритеты (карусель), а иерархию, и раздать каналы ДМА в соответствии со срочностью.
// АЦП даже на макс. скорости выдает данные раз в ~160тактов, что хватает и на пуши/попы
// и на другие каналы. А вот если есть что-то более скоростное, то можно сделать, например,
// DMA.CTRL = DMA_ENABLE_bm | DMA_PRIMODE_CH0123_gc и использовать для АЦП не CH0 а другой, а CH0
// оставить для быстрой передачи. У меня, например, АДС сидит на CH1, а через CH0 сделан видеовывод.
DMA.CTRL = DMA_ENABLE_bm;
// channel 0
// DMA.CH0.REPCNT = 0;
// DMA.CH0.CTRLA = DMA_CH_REPEAT_bm | DMA_CH_BURSTLEN_2BYTE_gc; // ADC result is 2 byte 12 bit word
// Когда канал ADC выдаст данные, нам нужно запустить передачу только очередного бурста (2байта),
// а не всего буфера, поэтому добавляем DMA_CH_SINGLE_bm.
// Если DMA.CH0.REPCNT = 0 (бесконечно) и DMA_CH_REPEAT_bm = 1, то после заполнения буфера
// (передачи блока) выдастся прерывание, но не остановится, а сразу продолжится перезапись буфера
// с начала. Нам тут это не нужно. Поэтому или DMA.CH0.REPCNT = 1 (тогда DMA_CH_REPEAT_bm не влияет),
// или DMA_CH_REPEAT_bm = 0 (DMA.CH0.REPCNT не влияет) или и то и другое. В этом случае ДМА
// заполнит буфер и заткнется, даже если АЦП продолжает молотить.
// Получили прерывание, обработали буфер, и когда надо, опять разрешили канал через
// DMA.CH0.CTRLA |= DMA_CH_ENABLE_bm;
DMA.CH0.REPCNT = 1; // передаем 1 блок (буфер) и затыкаемся
DMA.CH0.CTRLA = DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_2BYTE_gc; // по каждой отработке канала АЦП забираем 2-байтовый бурст
// DMA.CH0.ADDRCTRL = DMA_CH_SRCRELOAD_BURST_gc | DMA_CH_SRCDIR_INC_gc | DMA_CH_DESTRELOAD_TRANSACTION_gc | DMA_CH_DESTDIR_INC_gc;
// буфер - это у нас BLOCK, а не TRANSACTION, соответственно
DMA.CH0.ADDRCTRL = DMA_CH_SRCRELOAD_BURST_gc | DMA_CH_SRCDIR_INC_gc | DMA_CH_DESTRELOAD_BLOCK_gc | DMA_CH_DESTDIR_INC_gc;
// reload source after every burst, reload dest after every block
// Если DMA_CH_REPEAT_bm =0, то TRANSACTION = BLOCK и предыдущий вариант тоже сработает, но это грязно.
DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_ADCA_CH0_gc;
// DMA.CH0.TRFCNT = 2048;
// Тут сколько надо - если 1000 измерений по 2 байта, значит 2000. И не забыть буфер такой же выделить.
DMA.CH0.TRFCNT = 2000;
DMA.CH0.DESTADDR0 = (( (uint16_t) buffer_a) >> 0) & 0xFF;
DMA.CH0.DESTADDR1 = (( (uint16_t) buffer_a) >> 8) & 0xFF;
DMA.CH0.DESTADDR2 = 0;
DMA.CH0.SRCADDR0 = (( (uint16_t) &ADCA.CH0.RES) >> 0) & 0xFF;
DMA.CH0.SRCADDR1 = (( (uint16_t) &ADCA.CH0.RES) >> 8) & 0xFF;
DMA.CH0.SRCADDR2 = 0;
// У нас не "double buffered" и CH1 не нужен, все что про него - удаляем.
// Если после заполнения буфера нужно получить прерывание, то здесь, до старта, нужно его разрешить,
// причем тут (в отличие от CTRLA при запуске) можно именно через RMW - если там стояли флаги,
// они прочитаются и при записи заодно сбросятся.
DMA.CH0.CTRLB |= DMA_CH_TRNINTLVL_LO_gc;
// И, конечно, не забыть организовать сам вектор прерывания (т.е. в шапке обработчика
// сказать что это соответствующее прерывание).
// DMA.CH0.CTRLA |= DMA_CH_ENABLE_bm;
// Тут лучше писать напрямую, без чтения:
DMA.CH0.CTRLA = DMA_CH_ENABLE_bm | DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_2BYTE_gc;
// Здесь разрешаем АЦП нужным вам способом - запуском таймера, как тут, или собственно
// разрешением АЦП, или разрешением ивента и т.д.
// В принципе, можно это и заранее сделать, ну будет АЦП впустую молотить и аллах с ними обоими...
TCC1.CTRLA = TC_CLKSEL_DIV1_gc; // start timer, and in turn ADC
// DMA.CH0.CTRLB |= DMA_CH_TRNIF_bm;
// while ((DMA.CH0.CTRLB & (DMA_CH_CHBUSY_bm | DMA_CH_CHPEND_bm)) == 0)
// while ((DMA.CH0.CTRLB & (DMA_CH_CHBUSY_bm | DMA_CH_CHPEND_bm)) != 0)
// Это все нам не нужно
// В обработчике прерывания нужно сбросить флаг этого прерывания (сам он не сбрасывается!)
DMA.INTFLAGS = DMA_CH0TRNIF_bm
// Как-нибудь (раз в секунду) нужно опять разрешить ДМА:
DMA.CH0.CTRLA = DMA_CH_ENABLE_bm | DMA_CH_SINGLE_bm | DMA_CH_BURSTLEN_2BYTE_gc;
// Еще проще, прерывания ДМА вообще не разрешать (DMA.CH0.CTRLB |= DMA_CH_TRNINTLVL_OFF_gc;),
// а просто раз в секунду обрабатывать буфер и опять запускать ДМА (первый раз нужно
// запустить при инициализации). И обработчик будет просто подпрограммой, не прерыванием.
-
- Спасибо за труды! У меня получилось вот что (кое-где отступила от рекомендаций): Ксения(2539 знак., 01.05.2017 00:03 - 01:31)
- Сразу включать DMA_CH_ENABLE_bm нельзя! Idler(2243 знак., 01.05.2017 01:21 - 01:23)
- Несомненно! Ксения(2802 знак., 01.05.2017 01:49 - 01:56)
- Согласен с тем что правильнее Apтём(1045 знак., 01.05.2017 01:36)
- Но в даташите написано: "The ADC has 12-bit resolution and is capable of converting up to 2 million samples per second". Ксения(750 знак., 01.05.2017 02:24 - 02:35)
- В старой ХМеге я получал 6 бит на 300kSps и 10 бит на 40kSps (медленнее не пробовал). На новой я имею полное соответствие DS - 12бит на 300kSps. Idler(258 знак., 01.05.2017 02:33)
- О кстати, точно! АЦП то у нас конвейерное, сразу 4 канала мерять может. Apтём(751 знак., 01.05.2017 02:40 - 02:45)
- На 300kSps 12бит - шум около 2-3 единиц, но он даже полезен, программный фильтр добавляет еще пару бит. Idler(577 знак., 01.05.2017 03:27)
- Про вход с ОУ интересуюсь. Как сделать (но, чтобы по-проще), чтобы на АЦП можно было подавать синусоиду, центрированную относительно нуля (т.е. иногда имеющую напряжение, отрицательное относительно земли). Причем речь даже не об усилении, а об Ксения(610 знак., 02.05.2017 14:46 - 15:33)
- У меня тоже опора 2.5в, но вход без ОУ, питание грязное, разводка плохая, ИксМега старая :) - см. картинку. Ксения(709 знак., 01.05.2017 13:37)
- Вроде всё верно. Сойдет вообщем. Apтём(3167 знак., 01.05.2017 00:49 - 00:58)
- Почти всё верно, или может всё верно. Apтём(948 знак., 29.04.2017 23:51)