Цифро-аналоговое преобразование с периферийным контроллером DMA SAM4S
Часть третья в этой серии из трех статей показывает, как создавать значения для дискретной синусоиды и непрерывно преобразовывать эти данные в аналоговый сигнал, не перегружая процессор.
Вспомогательная информация
- Введение в разработку проекта с помощью Atmel SAM4S Xplained Pro
- Введение в цифро-аналоговое преобразование
- Волновые формы переменного тока
Требуемое оборудование / программное обеспечение
- Комплект оценки SAM4S Xplained Pro
- Студия Atmel
Предыдущие статьи
- DMA цифро-аналоговое преобразование с микроконтроллером SAM4S: таймер / счетчик
- Понимание и использование цифрового преобразователя SAM4S
Не напрягайте процессор
В предыдущей статье мы завершили несколько сложный процесс настройки цифрового аналогового преобразователя (DACC), а затем мы использовали бесконечный цикл while, чтобы непрерывно генерировать треугольную волну. Это ни в коем случае не является незначительным достижением, но в то же время было бы нецелесообразно обременять ЦП всеми инструкциями, необходимыми для продолжения этой деятельности ЦАП.
Предположим, мы используем частоту дискретизации ЦАП 1 МГц; если микроконтроллер выполняет другие важные задачи, было бы крайне неудобно, чтобы процессор прерывался один миллион раз в секунду и принудительно выполнял различные инструкции, необходимые для доступа к массиву, увеличивал счетчик, загружал данные в конверсию DACC, регистрации данных или выполнять любые другие задачи, которые могут потребоваться в конкретном приложении.
Конечно, разработчики чипов давно признали эту проблему: зачем забивать сложный микропроцессор с чем-то простым, как перемещение данных из одного места памяти в другой «процессор», который знает немного больше, чем перемещать данные, и пусть этот подпроцессор осветляет Загрузка процессора? Ну, оказывается, что это действительно очень хорошая идея, и эти подсистемы перемещения данных, называемые контроллерами прямого доступа к памяти (DMA), были включены в многочисленные микроконтроллеры и цифровые сигнальные процессоры.
На этом этапе проекта мы добавим функциональность DMA в наше цифро-аналоговое преобразование с использованием периферийного контроллера DMA SAM4S (PDC). С помощью этого модуля мы можем перемещать значения синусоидальной волны из ОЗУ в регистр данных преобразования DACC с минимальным вмешательством ЦП. В следующей таблице на стр. 491 таблицы SAM4S (PDF) перечислены периферийные устройства, поддерживаемые PDC:

«Номера каналов» справа указывают приоритеты, которые использует PDC при обработке нескольких запросов на передачу, причем канал 0 является наивысшим приоритетом. Нам не нужно беспокоиться о каком-либо приоритетном бизнесе в этом проекте, потому что мы используем только одно периферийное устройство PDC. Также обратите внимание, что некоторые каналы поддерживают передачу и прием, что означает, что вы можете использовать PDC, например, для перемещения данных в UART для передачи и хранения данных, полученных UART.
Во-первых, некоторая тригонометрия
Прежде чем мы будем использовать PDC для перемещения синусоидных данных в DACC, нам нужны синусоидальные данные. Итак, давайте посмотрим, как генерировать значения синусоидальной волны с дискретным временем. Сначала нам нужно включить заголовочный файл, который предоставит нам математическую функциональность. Мы будем использовать файл arm_math.h, который дает нам функцию sin () и определяет PI как 3.14159265358979. Теперь давайте вспомним некоторые фундаментальные характеристики синусоидальной волны:

Когда мы используем функцию sin (), значения, соответствующие одному полному циклу, требуют аргументов в диапазоне от 0 до 2π. Функция sin () возвращает ноль, когда аргумент равен 0, π или 2π; он возвращает 1 (максимальное значение), когда аргумент находится на полпути между 0 и π (т. е. π / 2) и возвращает -1 (минимальное значение), когда аргумент находится на полпути между π и 2π (т. е. 3π / 2). Чтобы создать синусоиду с максимальными и минимальными значениями, отличными от 1 и -1, мы умножаем возвращаемые значения sin () на константу, называемую амплитудой. Мы также можем дать синусоиде смещение, например, поднять его выше оси x, так что ни одна часть волны не станет ниже нуля, добавив постоянную ко всем точкам данных, которые возникают в результате умножения возвращаемых значений на амплитуду.
Поэтому, если мы хотим генерировать один синусоидальный цикл, первый аргумент sin () должен быть 0, а последний должен быть 2π. Но что мы делаем со всеми аргументами между ними? Ну, в этот момент нам нужно решить, сколько точек данных, ака выборки, мы хотим за цикл синусоидальной волны. Затем мы делим 2π на количество выборок за цикл, чтобы определить, насколько увеличивается аргумент от одной точки данных до следующей.
Следующий код должен помочь вам понять весь процесс генерации дискретного синуса:
//variables for the discrete-time sine wave uint16_t SineWave_12bit(SAMPLES_PER_CYCLE); float DiscreteSineArgument, DiscreteSineArgumentStep; DiscreteSineArgument = 0; DiscreteSineArgumentStep = (2*PI)/SAMPLES_PER_CYCLE; for (n = 0; n < SAMPLES_PER_CYCLE; n+) { SineWave_12bit(n) = (uint16_t)((SINE_AMPLITUDE * sin(DiscreteSineArgument)) + SINE_MIDRANGE); DiscreteSineArgument = DiscreteSineArgument + DiscreteSineArgumentStep; }
Мне нравится хорошая гладкая синусоидальная волна, поэтому в этом проекте мы будем использовать 100 отсчетов за цикл. SINE_AMPLITUDE установлен в 2047, 5, то есть в половине 12-разрядного диапазона ЦАП. Это означает, что максимальное значение будет составлять 2047, 5 отсчетов выше среднего значения, а минимальное значение будет 2047, 5 отсчетов ниже среднего значения, и, таким образом, синусоидальная волна будет охватывать весь диапазон выходного сигнала 4095 ЦАП.
SINE_MIDRANGE также установлен в 2047.5, потому что мы хотим, чтобы минимальное значение было равным 0. (Обычно 0 отсчетов будет соответствовать аналоговым выходным напряжением 0 В, а в случае SAM4S ЦАП, 0 отсчетов соответствует одной шестой части аналогового опорного напряжения. Этот вопрос кратко обсуждается в разделе «Результаты и выводы» раздел предыдущей статьи.)
Назад к PDC
Ниже приведены шаги, связанные с запуском канала PDC:
Добавьте модуль ASF PDC (я не знаю, почему он называется «Пример периферийного DMA-контроллера»):

- Определите структуру типа pdc_packet_t, используемую для конфигурации канала PDC, и указательную переменную типа PDC, используемую для указания на базовый адрес регистра PDC периферии.
- Загрузите соответствующий адрес в указатель базового адреса, используя функцию, соответствующую используемому периферийному устройству; для этого проекта функция dacc_get_pdc_base ().
- Используйте структуру pdc_packet_t для хранения начального адреса и длины для передачи DMA. Длина относится к байтам, если вы перемещаете 8-битные данные, пол-слова, если вы перемещаете 16-битные данные, и слова, если вы перемещаете 32-битные данные.
- Настройте канал DMA для передачи или приема с помощью pdc_tx_init () или pdc_rx_init () в сочетании с структурой pdc_packet_t.
- Инициируйте передачу PDC с помощью pdc_enable_transfer ().
PDC автоматически перемещает данные в надлежащее время, поскольку его работа управляется сигналами состояния передачи и / или приема, управляемыми периферийным устройством. Например, с DACC PDC передает новые данные только тогда, когда сигнал готовности к передаче DACC указывает, что оборудование ЦАП готово к новому преобразованию.
Вышеупомянутый процесс можно преобразовать в код следующим образом:
//PDC variables pdc_packet_t DACC_PDC_Config; Pdc *PDC_ptr_to_DACC; PDC_ptr_to_DACC = dacc_get_pdc_base(DACC); DACC_PDC_Config.ul_addr = (uint32_t) SineWave_12bit; DACC_PDC_Config.ul_size = SAMPLES_PER_CYCLE; pdc_tx_init(PDC_ptr_to_DACC, &DACC_PDC_Config, &DACC_PDC_Config); pdc_enable_transfer(PDC_ptr_to_DACC, PERIPH_PTCR_TXTEN);
Нам еще нужен процессор
Когда PDC завершит перемещение всех данных из буфера в DACC, прерывание используется для того, чтобы спровоцировать вмешательство ЦП; процессор должен знать, что передача PDC завершена, чтобы он мог перенастроить канал PDC и тем самым обеспечить, чтобы синусоидальные данные продолжали перемещаться в ЦАП. Таким образом, последний шаг в реализации PDC - это правильное прерывание и настройка процедуры обслуживания прерываний (ISR), которая будет выполняться, когда PDC завершит передачу.
Внутри ISR мы просто перенастраиваем канал PDC с тем же буфером и длиной передачи. Благодаря интегрированному FIFO DACC (обсуждаемому в разделе «Основы» в предыдущей статье), процессор не должен испытывать трудности при завершении этой переконфигурации до того, как в DACC закончится преобразование данных.
Вот код, связанный с прерыванием:
int main (void) {… //enable the DACC's "end of transmit buffer" interrupt dacc_enable_interrupt(DACC, DACC_IMR_ENDTX); //enable DACC interrupts in the Nested Vectored Interrupt Controller NVIC_EnableIRQ(DACC_IRQn);… } void DACC_Handler(void) { //confirm that the "end of transmit buffer interrupt" fired if (dacc_get_interrupt_status(DACC) & DACC_ISR_ENDTX) { //re-configure the PDC so that sine-wave generation continues pdc_tx_init(PDC_ptr_to_DACC, &DACC_PDC_Config, &DACC_PDC_Config); } }
Возможно, вам интересно, как Atmel Studio знает, что DACC_Handler () - это DACC ISR - нет ничего, что явно идентифицирует его как таковое. Ну, это на самом деле приятно: имя функции «DACC_Handler» является официальным идентификатором ISR DACC, поэтому все, что вам нужно сделать, это назвать функцию DACC_Handler (), и она будет автоматически использоваться как ISR для любых прерываний DACC. Эта же схема применяется к другим периферийным устройствам: ADC_Handler (), UART0_Handler () и т. Д.
Один буфер или два?
Когда вы настраиваете канал PDC с помощью pdc_tx_init () или pdc_rx_init (), вы должны указать начальный адрес и длину передачи для «текущей» передачи. Однако у вас есть возможность указать начальный адрес и длину передачи для «следующей» передачи. В приведенном выше коде я настроил канал для «текущих» и «последующих» передач. Чтобы настроить канал только для одной передачи, вы используете NULL для третьего аргумента, как показано ниже:
pdc_tx_init (PDC_ptr_to_DACC, & DACC_PDC_Config, NULL);
Преимущество использования обеих передач заключается в том, что вмешательство ЦП уменьшается; CPU повторно настраивает канал PDC после того, как он перемещает два буфера вместо одного. Я предполагаю, что конфигурация с двумя передачами также будет полезна, если вы хотите переместить данные в один буфер или из одного буфера, а затем в другой буфер или из него: вы можете настроить оба буфера одним вызовом pdc_rx_init () или pdc_tx_init (), тем самым поддерживая правильная последовательность без использования ЦП для определения того, какой буфер только что перемещен.
Результаты
Вы можете использовать следующую ссылку для загрузки исходных файлов и файлов проекта:
Файлы источника и проекта
Вот захват области, показывающий синусоидальную волну, генерируемую ЦАП:

Обратите внимание, что частота составляет 10 кГц. Это то, что мы ожидаем: период выборки равен 1 / (1 МГц) = 1 мкс, и мы имеем 100 выборок за цикл. Таким образом, период синусоиды составляет 100 мкс, а 1 / (100 мкс) = 10 кГц. Мы можем обобщить это следующим образом:
(f_ {sine} = \ frac {sample \ rate} {samples \ per \ cycle} )
Если вы хотите получить более высокую частоту синусоидальной волны, вы можете либо уменьшить количество выборок за цикл (в результате получится более качественное изображение), либо увеличить частоту дискретизации (максимальное время установления DAC SAM4S составляет 0, 5 мкс, поэтому вам, вероятно, не следует нажать частота дискретизации выше 2 МГц).
Вывод
В этой серии проектов мы подробно рассмотрели модуль таймера / счетчика, контроллер цифрового аналогового преобразователя, периферийный контроллер DMA и некоторый код для генерации синусоидальной волны с дискретным временем. Эта функциональность может оказаться полезной для различных приложений. Одно из таких приложений, а именно, генерирование форм волны основной полосы для программно заданной радиопередачи будет предметом будущей статьи.