Как настроить цап на stm32
DAC в STM32
ЦАП (цифро-аналоговый преобразователь) нужен для преобразования числового кода в напряжение. Я опишу работу с ЦАП в stm32f100c4t6, там их целых 2 еще и 12-битных.
Расчет выходного напряжения ЦАП производиться по такой формуле:
DACout = Vref*DOR/4095
Где Vref – опорное напряжение, в нашем случае это Vdda, DOR – значение в выходном регистре.
Перед началом работы с ЦАП, как и со всей периферией в stm, на него надо подать тактовые импульсы. Делается это в регистре RCC->APB1ENR установкой бита RCC_APB1ENR_DACEN.
В документации говориться что вывод к которому подключен ЦАП должен быть настроен как Analog in, но оно работает и так (с любо конфигурацией), даже тактирование порта можно не включать. После включения ЦАП он сам перейдет в нужный режим. Собственно ЦАП включается так:
Соответственно для включения ЦАП2 надо установить бит DAC_CR_EN2 в том же регистре. Только включать его надо в последнюю очередь, поскольку многие конфигурационные биты не могут быть изменены с поднятым флагом ENx.
Сначала данные заносятся в регистр предварительно хранения (data holding register, DHRx), дальше по событию (если включено) переносятся в выходной регистр (DORx), это занимает 3 такта APB1 если событие аппаратное и 1 если программное. Если вывод данных по событию выключен (по умолчанию), то это делается автоматически за 1 такт.
Для переноса данных возможны такие события:
Эти биты устанавливаются в регистре DAC->CR, для удобства в файле stm32f10x.h созданы битовые поля:
Т.е. для вывода данных с ЦАП1 по внешнему событию надо написать
Для программного события достаточно
(Для ЦАП2 соответственно DAC_CR_TSEL2 и т. д.)
Программное событие генерируется установкой бита DAC_SWTRIGR_SWTRIG1 (для ЦАП2 DAC_SWTRIGR_SWTRIG2) в регистре DAC->SWTRIGR.
преобразования
Бит сбрасывается автоматически через 1 такт.
Данные в регистр предварительного хранения могут загружаться в нескольких форматах: 12 бит выравнивание по правому краю, 12 бит выравнивание по левому краю (для 16-битного числа), 8 бит. Для каждого способа существует свой регистр, что намного упрощает настройку.
Соответственно регистры называются:
DAC->DHR8Rx – для 8 битного доступа
DAC->DHR12Lx – выравнивание по левому краю
DAC->DHR12Rx – по правому
x – номер ЦАП (1 или 2)
Одновременный доступ к двум ЦАП осуществляется в таком формате
Соответственно название регистров:
DAC->DHR8RD
DAC->DHR12LD
DAC->DHR12RD
Старшая часть битов для ЦАП2, младшая для ЦАП1. т.е. в регистре DAC->DHR8RD биты [7:0] для ЦАП1, а [15:8] – ЦАП2.
Данные из каждого из этих регистров сразу загружаются в регистр DHRx, а после команды в DORx (см. структурную схему).
Также каждый ЦАП имеет выходной буфер для того чтобы обойтись без внешнего повторителя-усилителя. По умолчанию он включен, за него отвечает бит DAC_CR_BOFF1 в регистре DAC->CR (для ЦАП2 бит называется DAC_CR_BOFF2). Если бит установлен то буфер выключен.
С буфером динамические характеристики немного хуже. Замеры выходного сопротивления собственного ЦАП проведу позже, в апноуте написано что при нагрузке 5.1 кОм без буфера выходное напряжение из 3.3 В падает до 1.2 В.
Минимум для запуска ЦАП
Чтобы заставить работать ЦАП1 достаточно прописать (инициализация)
и в нужное время загружать в него данные такой командой
ЦАП, таймер и DMA
Для управления ЦАП создано 2 таймера: таймер 6 и таймер 7. Они очень просты и имеют минимум настроек. Конечно управлять можно и каким-то другим (7, 3, 2, 4, 5, 15), но эти специально для ЦАП, в документации так и написано.
Для начала настроим таймер. Он будет считать до переполнения и по переполнению будет запускаться передача по DMA.
Переполнение так редко чтобы можно было тестером измерять напряжение на выходе ЦАП, так же в прерывании таймера буду мигать светодиодом чтобы видеть когда же загружаются данные.
В инициализации все поля понятны кроме TIM6->CR2 |= TIM_CR2_MMS_1; это значит что внешнее событие TRGO будет генерироваться при обновлении таймера (или при переполнении, что почти то же само).
При внешнем событии ЦАП, данные переносятся из регистра DHRx в DORx, а DMA может поместить данные только в DHRx. При чем ЦАП инициирует перенос данных по внешнему событию (только по внешнему). Для чего ЦАПу может понадобиться инициировать передачу DMA я не придумал, т.к. если настроить чтобы он преобразовывал данные по событию таймера, то данные перейдут с регистра DHRx в DORx, а потом через ПДП загрузятся новые в DHRx, т.е. данные на выходе появятся только при следующем событии.
Разумный вариант когда ЦАП преобразовывает данные сразу, а таймер запускает передачу данных по DMA. В таком случае по событию таймера данные передадутся в регистр DHRx откуда через такт в DORx. В таком случае достаточно на ЦАП подать тактовые импульсы и включит его.
DMA1_Channel3->CNDTR = 1; — это количество данных которые будут переданы, если оставить 0, то ПДП ничего не передаст. В принципе можно ввести любое число, но от этого ничего не измениться, т.к. инкримента адресов не происходит, а передача циклическая (по окончании передачи кол-ва байт записанных в CNDTR счетчик обнулится и все пойдет по кругу).
Программный декодер MP3 на STM32F10x. Часть 2. Запуск ЦАП
Хотя второй вариант выглядит более логичным, реализуем первый (как показали дальнейшие исследования, оба варианта работают идентично). Номер таймера выбираем любой из доступных и подключенных ко входу DAC_trigger (TIM2, 4..7, 3/8).
Подробно рассматривать устройство периферии STM32 здесь не будем, при необходимости читаем популярные статьи на сайтах http://we.easyelectronics.ru/, http://mycontroller.ru/, http://easystm32.ru/, http://microtechnics.ru/ и т.д.
Инициализация модуля ЦАП
Все команды инициализации помещаем в одну функцию DAC_TaskInit, однократно вызываемую из модуля main.c.
Если мы не хотим, чтобы таймер считал при остановленном ядре контроллера, необходимо установить соответствующий бит:
DMA контроллера STM32 может генерировать прерывание по окончании всей передачи (Transfer complete) либо по передаче половины данных (Half-transfer). Задержки при формировании звука недопустимы, поэтому было бы логично зациклить передачу DMA (Circular mode) и обрабатывать оба прерывания и подгружать новыми данными освободившуюся половину буфера.
Однако возникает неудобство: нужно чётко знать, какая половина буфера свободна, соответственно нужно передавать эту информацию из модуля ЦАП декодеру. Нет, не спорю, алгоритм вполне реализуем. Но попробуем его всё же немного упростить. DMA будем запускать в однократном режиме, а настройку DMA выполнять в обработчике прерывания Transfer complete.
Инициализация 7 канала DMA, к которому подключен сигнал запроса DMA от таймера 4:
И наконец инициализация ЦАП
Загрузку DMA будем выполнять по прерыванию. Перед загрузкой регистров DMA не забываем его выключать.
Первый пробный запуск
Выведем 5 ступенек:
Получаем такую картинку:
Верхняя трасса отображает выполнение обработчика прерывания (TEST_PIN1). Интервал срабатывания прерывания 104,1 мкс, делим на 5 и получаем частоту дискретизации около 48 кГц. Таймер настроен верно.
Обратим внимание на срез сигнала: он получился задержанным примерно на 2 мкс. Кроме того, на первой ступеньке присутствует колебательный переходной процесс. Такие дефекты сигнала обусловлены влиянием буферного усилителя ЦАП. При отключении усилителя сигнал становится «правильным», но амплитуда уменьшается в 2 раза (масштаб по вертикали 1 В/дел):
Хм, странный результат. Выходное напряжение оказалось притянутым к питанию 3В. Внимательно изучаем даташит на контроллер и смотрим схему отладочной платы. Ну да, всё правильно. Порт PA4 оказался подтянутым к 3 В резистором 10 кОм. А даташит рекомендует нагрузку при отключенном буферном усилителе — не менее 1,5 МОм!
Длительность среза от максимума до минимума составляет 2 мкс (буферный усилитель выключен, выход ЦАП нагружен только на высокоомный щуп осциллографа). Включение усилителя ускоряет процесс на 0,6 мкс.
А задержка среза (см. первую осциллограмму) обусловлена насыщением буферного усилителя, при уменьшении диапазона перестройки ЦАП с 0xFFF/0x000 до рекомендованного в даташите значения 0xF1C/0x0E0 насыщение пропадает.
Смотрим далее. Длительность выполнения обработчика порядка 600 нс. Прерывание возникает после преобразования ЦАП предпоследнего сэмпла. Откуда такая задержка? Почему не после последнего? Вероятно, имеется двойная буферизация на пути передачи данных от DMA до ЦАП. Проверим, сколько времени в запасе у нас есть на следующую инициализацию DMA. Для этого в обработчик прерывания перед загрузкой DMA вставим тупой пустой цикл:
Опытным путём установлено, что максимальная задержка загрузки DMA может составлять 2 такта (40 мкс). Этого времени для наших целей более чем достаточно:
Алгоритм и диаграммы взаимодействия модуля ЦАП и декодера могут выглядеть например так::
Далее.
В следующей статье приступим к воспроизведению MP3-потока.
Начинаем работать в STM32CubeMX. Часть 2
В прошлый раз мы научились создавать в STM32CubeMX новый проект, настраивать тактовый генератор, таймер и порт ввода-вывода, и немного помигали светодиодом. Сегодня мы освоим цифро-аналоговый преобразователь и научимся работать с ним через DMA. В результате у нас должен получиться простой генератор прямого синтеза (Direct digital synthesizer, DDS).
Работа с DAC
Большая часть микроконтроллеров STM32 оснащена 12-bit DAC в количестве одного или двух штук. При этом архитектура DAC одинакова во всех кристаллах, неважно, какое ядро ARM Cortex M там используется. Таким образом, сегодняшний эксперимент можно выполнить на любом микроконтроллере STM32, имеющем хотя бы один DAC.
Какова частота преобразования DAC? Ответ на этот вопрос не совсем прост. Если вы хотите подробностей, рекомендую эти два документа: [1][2]
Если излагать суть кратко, то она заключается в следующем: сам по себе ЦАП может обновлять выход с частотой до 5 MSPS (мегасэмплов в секунду), но буферный операционный усилитель (ОУ) на выходе не обеспечит такой скорости, ограничивая её до 1 MSPS. Если мы хотим больше, нам нужен внешний ОУ, к которому предъявляются некоторые требования по частотным характеристикам, о которых будет сказано ниже. Без буферного ОУ ЦАП использовать нельзя, так как он имеет довольно большое выходное сопротивление (> 10 кОм).
Немного схемотехники
На частоте выше сотни килогерц встроенный буферный усилитель начинает вносить существенные искажения в сигнал, поэтому я сразу поставил на выход внешний буферный усилитель, удовлетворяющий рекомендациям ST.
Для достижения скорости преобразования 5MSPS, ST рекомендует использовать ОУ с частотой единичного усиления не менее 10 MHz, усилением при разомкнутой обратной связи не менее 60 дБ и скоростью нарастания выходного сигнала не менее 16,5 В/мкс. ST в качестве примера рекомендует ОУ LMH6645/6646/6647 производства Texas Instruments.
Я использовал ОУ AD845JN, который имеет частоту единичного усиления 16 МГц, типовое значение коэффициента усиления 500 В/мВ (около 114 дБ) и скорость нарастания 100 В/мкс. Питание ОУ производится от DC/DC преобразователя 5 В/±9 B. Можно питать буферный усилитель однополярным питанием, например, взяв 5 В прямо с платы, но тогда понадобится rail-to-rail усилитель. Схема подключения приведена на рис. 1.
Рис. 1. Схема выходного буферного усилителя
Специальную плату делать не стал, смонтировал проводом на макетной плате, которая вставлена в arduino-совместимое посадочное место на отладке.
Рис. 2. Вид платы буферного усилителя
Внутренний буферный усилитель микроконтроллера начинает вносить заметные нелинейные искажения уже начиная с частоты 100-150 кГц. Если вы не собираетесь использовать DAC для генерации сигналов выше этих частот, можно обойтись и без буфера.
Теперь переходим к программной части.
Конфигурация DAC
Будем считать, что мы уже умеем создавать проект в CubeMX, выбирать микроконтроллер и настраивать тактовый генератор, как в первой части. Можно просто взять проект из первой части и продолжить его.
На плате, которой я пользуюсь, выходы DAC выведены не слишком удобно, к сожалению, DAC_OUT1 (вывод N4) выведен на разъем DCMI, DAC_OUT2 (вывод P4) подключен к интерфейсу USB и вряд ли может быть использован в качестве выхода DAC. Поэтому остаётся только DAC_OUT1. Включаем его во вкладке Pinout:
Во вкладке «Configuration» у DAC есть только одна интересная нам настройка: Output Buffer. Если вы не используете внешний усилитель, он должен быть включен, если используете — выключен.
Можно управлять выходом DAC «вручную» из программы, можно задействовать DMA. Второй способ хорошо подходит для генерации периодического сигнала произвольной формы, и его мы рассмотрим ниже, а сейчас используем первый способ. Просто установить на выходе постоянное напряжение неинтересно, попробуем сгенерировать сигнал. Для этого нам понадобится таймер. Делаем всё как в первой части, только частоту таймера устанавливаем больше, например, 500 кГц. Для этого нужно установить значение Prescaler = 215, тогда мы получим 216 МГц/(215 + 1) = 1 МГц, и Counter Period = 1, что даст 1 МГц / (1 + 1) = 500 кГц. Напоминаю, что 216 МГц — частота тактирования периферии в нашей конфигурации системы тактирования.
Генерация сигнала из обработчика прерываний
Генерируем код, открываем проект и вписываем следующее:
Ещё раз напоминаю, что весь пользовательский код пишется между строками вида /* USER CODE BEGIN… */… /* USER CODE END… */
Первый участок кода — обработчик прерывания таймера, в котором в регистр DAC записывается попеременно 0 и 4095, т. е. минимальное и максимальное значение DAC. Второй участок кода включает таймер и DAC. Получаем прямоугольные колебания с частотой 250 кГц, но из-за вносимых буферным усилителем искажений они выглядят так:
Пришло время задействовать внешний буфер, отключив внутренний. Для этого в STM32CubeMX заходим на вкладку Configuration, нажимаем кнопку DAC, на вкладке Parameter Settings устанавливаем Output Buffer = Disable. Заново генерируем код и прошиваем в плату. Сейчас импульсы выглядят как меандр, пропущенный через ФНЧ (в силу того, что полоса пропускания системы всё же ограничена):
Можно даже приблизительно оценить частоту среза ФНЧ.
Способ понятен из рисунка: проводим касательную к экспоненте до пересечения с верхним уровнем сигнала. Расстояние по шкале времени от начала импульса до пересечения и будет постоянной времени фильтра τ = 400 нс, частота среза равна fср = 1/2πτ ≈ 0,4 МГц.
Попробуем увеличить частоту в два раза, уменьшив Prescaler до 107, но нас ждёт разочарование: выше 333 кГц частота не поднимается. Вероятно, нужна некоторая оптимизация кода.
В действительности, наибольшую задержку вносит огромный обработчик прерывания таймера в недрах HAL (функция HAL_TIM_IRQHandler). Его можно заменить своим. Для этого находим файл stm32f7xx_it, и в нём изменяем функцию TIM1_UP_TIM10_IRQHandler:
HAL_TIM_IRQHandler больше не вызывается. Теперь частоту таймера можно поднять до 2 МГц, а частоту меандра, соответственно, до 1 МГц. Для этого нужно в настройках таймера установить значение Prescaler = 53, и тогда мы получим такую картину:
Это, вероятно, максимальная частота, достижимая на данном микроконтроллере.
Библиотека HAL, конечно, удобная вещь, но внутри неё происходит много разных действий, которых можно избежать. Просто следует помнить, что преждевременная оптимизация — зло, и прибегать к ней только когда мы достигли ограничения, как в этот раз.
Ещё один нюанс. Мы можем заметить, что в сигнале иногда попадаются странные скачки, имеющие период 1мс.
Они получаются в результате того, что у нас в системе есть ещё одно прерывание, имеющее больший приоритет, чем наш таймер. Оно спрятано внутри HAL, и это системный таймер SysTick, имеющий наивысший (нулевой) приоритет прерываний. Для исправления ситуации заходим в STM32CubeMX->Configuration->NVIC->Time base: System Tick Timer->Preeption Priority = 1. Заново генерируем код, искажения сигнала исчезли.
Попробуем сгенерировать синусоидальный сигнал. Для этого нам нужен массив длиной N значений, заполненный значениями функции round(A * cos((pi / 2) * (n / N))), где A — амплитуда сигнала, N — количество точек в массиве, n — номер точки. При выводе будем сдвигать точки на shift = 2048, амплитуда пусть будет А = 2047, тогда значения DAC будут от 1 до 4095. Массив можно заполнить только на четверть периода, от 0 до pi/2, а недостающие значения получать из него путём очевидных арифметических действий. Почему используем функцию косинуса, а не синуса, я напишу дальше.
Как выбрать N? С одной стороны, чем больше N, тем лучше, значения будут более близкими к точным величинам, с другой стороны, ограниченная разрядность DAC делает такое увеличение бесполезным после величины [A * pi / 2] = 3215. В самом деле, при N = 3215 приращение угла составит pi / (2 * 3215) = 4.89e-4, а приращение амплитуды вблизи середины шкалы, где скорость нарастания максимальна, составит 4.89e-4 * 2047 = 1 дискрет DAC.
Мы можем сгенерировать массив заранее и разместить его во flash-памяти, можем сгенерировать его в run-time при инициализации. Первый способ предпочтительнее для практического применения, но мы воспользуемся вторым для большей наглядности:
Я хочу, чтобы массив и формирование сигнала происходили в main, поэтому изменяем код обработчика прерывания в файле stm32f7xx_it на следующий:
Сейчас «медленная» функция HAL_TIM_IRQHandler() не вызывается, а вызывается HAL_TIM_PeriodElapsedCallback в main. В main пишем следующее:
В STM32CubeMX устанавливаем частоту срабатываний таймера 500 кГц, например так: Prescaler = 107, Counter Period = 3. Получаем красивую синусоиду с частотой 500e3/(4 * 3216) = 38,868 Гц.
Максимальная частота при данных настройках составит 250 кГц. При этом сигнал превратится в меандр, их мы уже видели. Попробуем получить 10 кГц. Для этого мы должны выставить delta — (1e4/2e5) * 4 * 3216 = 257.28. Округляем до целого значения 257, получаем расчётное значение частоты (5e3 * 257) / (4 * 3216) = 9989 Гц. Получаем такую картинку:
Разница с частотой 10 кГц составляет около 0,1%. Можно ли выставлять частоту более точно? Можно, но для этого нужно считать фазу как float, но на данной частоте дискретизации (500 кГц) микроконтроллер не успевает считать фазу как float. Возможно снизить частоту тактирования таймера или попытаться вручную оптимизировать код, но это уже другая история. Пока достигнутая точность нас устраивает.
Работа с DMA
Встроенный в микроконтроллеры STM32 цифроаналоговый преобразователь (DAC) может работать по сигналам таймера и получать данные напрямую из массива памяти через DMA. Таким образом, можно сконфигурировать контроллер так, что DAC будет работать без участия программы, не затрачивая ресурсы процессора, за исключением инициализации системы.
Недостатком метода с DMA является то, что здесь невозможны трюки с записью в массив четверти периода и вычисление накопления фазы. Мы должны записать в память весь массив и указать нужный период. Преимуществом, как уже упоминалось, является то, что при генерации сигнала через DMA процессор свободен для другой работы.
Итак, откроем новый проект в STM32CubeMX, и проделаем уже знакомую нам процедуру конфигурации тактового генератора. Теперь настроим всё остальное.
Вызывать DMA способны два таймера: TIM6 и TIM7. Задействуем TIM6.
Далее устанавливаем следующие настройки:
Генерируем код, и вставляем в main следующее:
Конечно, мы можем сделать и массив другого размера. Размер массива должен передаваться в функцию HAL_DAC_Start_DMA четвёртым параметром, после адреса массива.
После запуска программы мы должны получить на выходе синусоиду, точно такую же, как приводилась выше, поэтому приводить скриншот я не буду.
Вот и всё, что я хотел написать про работу с DAC.
Что дальше?
В следующий раз мы кратко обсудим работу АЦП и интерфейс USB.
Переходим с STM32 на российский микроконтроллер К1986ВЕ92QI. Генерируем и воспроизводим звук. Часть первая
Вступление
Общее представление о простом методе генерирования звука.
Чтобы создать звук нам нужно заставить колебаться мембрану динамика с определенной частотой. Каждой ноте соответствует своя частота, например ноте До 1 октавы, соответствует частота 261Гц. Т.е. дрыгая ногой микроконтроллера, подключенной к динамику, со скоростью 261 раз в секунду мы будем слышать звучание этой ноты. Для тех кто не силен в музыкальной теории, звук ближе от 1кГц и выше будет более писклявый, ниже 300Гц будет басить.
На нашей плате установлен разъем Jack 3.5 и усилитель к нему. Рассмотрим принципиальную схему.
Как мы видим, усилитель подключен к контроллеру через пин PE0. После переключения джампера «DAC_OUT_SEL» на плате, мы можем генерировать звук, который можем услышать, подключив, к примеру, наушники к Jack-у.
Реализация простой пищалки
Вооружившись теорией, можно попробовать написать программу, которая будет пищать с частотой ноты до первой октавы.
Настроим наш пин. Сразу договоримся, что код пишется с поправкой на то, что на порту нет ничего, кроме нашего усилителя. В последствии сделать универсальную функцию не составит труда.
Теперь нужно решить, с какой частотой мы будем «пищать». В этом мне помогла эта таблица частот. Для наглядности представлена ниже.
Нота до ( C ) первой октавы имеет частоту 261.63 Герца. Это значит, что за одну секунду проходит 261.63 периода. В каждом из которых мы 2 раза меняем состояние бита. И того нам нужно менять состояние бита 523.26 раз в секунду. Поделив 1 секунду на 523.26 мы получим 0,0019110958223445, что примерно равняется 191*10^(-5) секунды. Это «пауза» между переключениями.
Теперь, когда мы знаем примерное значение задержки, мы можем настроить SysTick таймер и задержку к нему. Настроить нам его нужно на прерывания раз в 10^(-5) секунды. Для этого немного изменим нашу функцию из этой статьи. Получим следующее.
Ну и теперь, наконец-то, воспользуемся всем вышеописанным.
Все бы хорошо, но тут первые грабли. Как вы могли заметить, в основной функции нет ни строчки кода для настройки тактирования. Контроллер тактируется от HSI. В нашем примере это привело к тому, что вместо ноты до первой октавы, играла нота ля малой октавы (на 2 целых тона ниже). Чтобы исправить эту ошибку — добавим функцию переключения источника тактовой частоты с HSI на HSE (внешний кварцевый резонатор).
О системе тактирования было рассказано в этом уроке.
Чуть усложним задачу. Напишем программу, которая будет играть нам гамму из 12 полутонов (7 белых клавиш и 5 черных, если смотреть на фортепиано). Для этого создадим массив с длительностями всех задержек. Рассчитываем их так же, как и предыдущую: 100000/частота_ноты/2 = длительность_задержки. Делим 100000 потому, что у нас прерывание раз в 0.00001 секунды (10^(-5)).
Теперь немного изменим основную функцию.
Небольшое пояснение к коду. Так как длинна звучания сэмпла (одного периода звуковой волны) у каждой ноты разное, то для того, чтобы более-менее сделать звучание каждой ноты одинаковым, цикл ожидания составлялся следующим образом. Число самой длинной ноты (нота с большим числом задержки) звучало «задержку раз» самой короткой. Поясню. Нота до первой октавы (191) игралась 96 * 3 раз, а нота до второй октавы (96) играла 191 раз * 3. Тройка здесь коэффициент длительности. Далее будет рассмотрен более правильный способ измерения задержки.
Вот так выглядит наша волна.
Если присмотреться по ближе, то можно увидеть ее несовершенность. Она даже близко не похожа на прямоугольные импульсы.
Освоение ЦАП
Это стало для меня очень приятным сюрпризом. Никогда ранее я не видел ЦАП-а непосредственно в самом микроконтроллере. Но для начала нужно понять, можно ли подключить ЦАП к усилителю. Для этого открываем схему платы и видим следующее.
Вывод нашего усилителя подключается непосредственно к PE0, к которому подключен ЦАП. Отлично. Можно начать настройку. Но перед этим немного изучим ЦАП.
В микроконтроллере реализовано два ЦАП. Для включения ЦАП необходимо установить бит Cfg_ON_DACx в 1, используемые выводы ЦАП порта Е были сконфигурированы как аналоговые и были отключены какие-либо внутренние подтяжки. Оба ЦАП могут работать независимо или совместно. При независимой работе ЦАП (бит Cfg_SYNC_A=0) после записи данных в регистр данных DACx_DATA на выходе DACx_OUT формируется уровень напряжения, соответствующий записанному значению. При синхронной работе (бит Cfg_SYNC_A=1) данные обоих ЦАП могут быть обновлены одной записью в один из регистров DACx_DATA. ЦАП может работать от внутренней опоры Cfg_M_REFx=0, тогда ЦАП формирует выходной сигнал в диапазоне от 0 до напряжения питания AUCC. В режиме работы с внешней опорой Cfg_M_REFx=1 ЦАП формирует выходное напряжение в диапазоне от 0 до значения DACx_REF.
Тут, можно сказать, описана вся настройка. Взглянем на регистры.
Их здесь всего три регистра на два ЦАП. Из них два регистра для хранения значения на выходе у каждого из ЦАП. Рассмотрим регистр настройки.
Приступаем к настройке. Так же не забываем про тактирование DAC. Ну и для теста выставим на выход максимальное напряжение (0xFFF = 4095).
Далее не забудем и о выходе. В предыдущем уроке мы настраивали его как цифровой выход. Сейчас же, согласно рекомендации, нужно настроить как аналоговый.
Ну и добавим все это в основную функцию.
Сейчас, если замерить напряжение на пине, мы должны получить около трех вольт. НО. Этого не происходит. На пине у нас около 0.08 вольт. Что не есть хорошо. Идем разбираться. Прежде всего я проверил, затактирован ли ЦАП, Все было хорошо. Отладчик сообщает, что все регистры заполнены верно. Далее я решил взглянуть на таблицу пинов и обнаружил следующее.
Вот это новость. PE0 подключен не к DAC1, а к DAC2! Вот и еще одна ошибка… Меняем функцию ЦАП-а.
Пробуем запустить. Теперь все хорошо. На выходе 3.28 вольта. Теперь по примеру простой пищалки попробуем сгенерировать звук прямоугольными импульсами. Для этого чуть изменим код предыдущего проекта.
Чисто по ощущениям звук намного приятнее, чем в предыдущем примере. Да и звучит он куда громче. Проект и звуковой файл на github.
А вот, для сравнения, наша волна.
Отступление: усилитель на плате безумно сильно греется. Если оставить его в таком режиме минут на 10, то он превращается в печь… Поэтому я отключаю джампер после того, как послушал звук. Так он не нагревается.
Генерация синусоидальной волны.
Разобравшись с тем, как генерировать напряжение на выходе разного уровня я задумался, откуда брать значения этого напряжения? Буквально сразу же после начала поисков я наткнулся на эту статью. В ней я нашел самое главное. Код получения значений синуса волны. Немного переделав код я получил программу, которая запрашивая длину волны и частоту сэмпла генерирует массив значений напряжения для нашего кода. Вот код на Pascal ABC на github (Все таки нужно готовиться к ЕГЭ и временами писать и на паскале.).
Эксперименты с синусоидальной волной.
Получив синусоиду разбитую на 100 частей, вспоминаем, что эта синусоида должна быть проиграна с частотой 261,63 герца. Теперь рассчитаем интервал прерывания. Секунда/(100 частей * 261, 63) = 0.00003822191 секунды. Ну чтож. Скажу сразу. Я провел море эксперементов, чтобы получить звук. Вкратце расскажу о них. Так как частоты в 8 Мгц уже явно не хватало для такой скорости, то я решил себя побаловать и разогнал чип до 80 МГц, надеясь, что этого мне точно хватит. Но не тут то было. Настроив прерывания SysTick на 10000000 раз в секунду, контроллер даже не доходил до цикла, в котором выводились данные. После я решил, что куда проще будет выдавать данные сразу в прерывании. Получилось следующее.
Основная функция имела вид:
Звук получался таким:
При детальном рассмотрении видно следующее:
Вот приближенное возрастание «синусоиды»:
Видна огромная погрешность. А так же звук получился очень низким. Может быть ля малой октавы. Не выше. Что свидетельствует о том, что код в прерывании просто не успевает выполняться. Даже при частоте 80 Мгц. Поступим иначе. Немного снизим качество. Сделаем прерывание чуть реже. И округлим цикл ожидания в прерывании. Получаем следующее.
Теперь прерывание успевает обработаться. Мы получаем звук практически идентичный с нотой До. Но все же на слух (при сравнении с фортепиано) можно услышать неточность. Файлы проекта и звуковые файлы на github.
Наша звуковая волна имеет следующий вид:
Подъем «синусоиды» имеет следующий вид:
Как мы видим, толка в наших 100 частях нет. ЦАП просто не успевает менять напряжение. (Как мне показалось на момент изучения.) Изменим наш проект так, чтобы синусоида состояла из 20 частей. Получаем следующий массив.
Рассчитаем частоту прерывания теперь. Секунду/(20 частей * 261.63) = 0.00019110958 секунды
191*10^(-6). Это уже лучше, чем было раньше. Настраиваем прерывания и задержку. Получаем следующее.
Мы получили звук еще более приближенный к ноте До. Звуковой файл так же имеется в проекте по ссылке выше.
К моему удивлению передо мной снова практически прямоугольные импульсы! Хотя должна была быть синусоида. Где-то я ошибся… «А что, если снизить амплитуду колебания?» — подумал я. Изменил в программе на паскале параметр, показывающий «высоту волны» от «0» до «предела» с 2047 на 1500. Но это ни к чему не привело. И тут я взглянул на меню программы подробнее и увидел.
Вот удачный массив:
Теперь взглянем на наш сигнал (аудио запись так же доступна на github по ссылке выше). Наконец-то что-то похожее на синусоиду!
Теперь, когда я смог создать синусоиду из 20 частей, воспользуемся кодом, рассмотренным ранее, и попытаемся сделать синусоиду из 100 частей. Вот получившийся массив.
Заменяем массив используя старый код и получаем это:
Мы получили очень качественную синусоиду!
Код проекта и аудио на github.
Лирическое отступление
Во время записи звука с амплитудой >3 Вольт на колонках, подключенных к той же самой звуковой карте ноутбука, появлялся чуть измененный звук с устройства. По началу я думал, что это из-за включенной фоном программы. Но как только до меня дошло снизить амплитуду — понял, что нет. Так как звук пропал. Подозреваю, что еще чуть-чуть и я бы сжег звуковую карту.
Для записи и анализа звука использовал полностью бесплатную программу Audacity. Она позволяет записывать звук в сколь угодно высоком качестве без ограничений и позволяет сохранять его в любом формате (в том числе и FLAC, в котором я и приводил примеры).
Так же хочу добавить, что у меня нет осциллографа. Эта программа заменила мне его.
Вместо заключения.
В следующей статье будет разобран DMA модуль и его спряжение с DAC.