Урок 27
HAL. DAC
Сегодня мы начнём изучать интересную технологию — цифро-аналоговый преобразователь (ЦАП) или по-английски Digital-to-analog converter (DAC).
Как видно из названия данного преобразователя, он занимается преобразованием цифрового кода или величины в напряжение определённой величины, каким-то образом зависящим от данного цифрового кода. И также из названия видно, что цифро-аналговый преобразователь занимается задачей, обратной той, которой занимается аналого-цифровой преобразователь (АЦП), который мы уже изучили, начиная с урока 16.
Вообщем, слишком подробно мы не будем изучать принцип работы цифро-аналогового преобразователя. Нам важно научиться с ним работать с использованием контроллера STM32.
Во-первых, откроем Reference Manual и посмотрим, какой шиной управляется данная периферия
Как мы видим — это шина APB1.
Также в этой же документации посмотрим краткие характеристики нашего ЦАП
ЦАП наш является 12-битным.
Мы видим, что существует два таких ЦАПа, но также здесь находится ремарка, что не очень радостно, что на каждый такой конвертер находится только одна ножка контроллера и переопределить её невозможно.
Также можно выровнять данные по правому или левому краю двухбайтного регистра DHRx. То есть в зависимости от определённых настроек мы назначаем, каким образом мы будем располагать данные в этом регистре для последующего преобразования в электрический сигнал.
Также существует возможность синхронного обновления, генерации шума, треугольных импульсов, раздельного преобразования по каналам, а также совместного их использования, использования DMA для каждого канала, обнаружения ошибок опустошения DMA, использования внешнего управления преобразованием с помощью триггеров и использования опорного напряжения.
Существует также структурная схема ЦАП
На данной схеме видно, что существует ряд таймеров, которыми мы можем воспользоваться в качестве триггеров. Есть у нас управляющий регистр, биты которого служат для определённых настроек, регистр данных DHRx, ну, верней их два — DHR1 и DHR2, которые мы используем для хранения значения, которое будет преобразовано в электрический сигнал, а номера соответствуют номерам ЦАПов. Ну, и непросредственно, сам преобразователь.
А вот здесь указано, какие существуют режимы для преобразования
Мы видим, что существует также 8-битный режим и два варианта 12-битного режима в зависимости от выравнивания преобразуемой величины. Данные регистры мы в коде непосредственно использовать не будем, мы разве что к ним обратимся в процессе отладки, если у нас что не заработает.
Ну давайте наконец займёмся кодом.
Проект создаём из проекта TEST001, т.к. там ничего лишнего не подключено. Назовем его DAC. Запустим проект в Cube, включим там DAC OUT1
В Clock Configuration внесем следующие настройки
В самом DAC настройки следующие
Больше ничего не включаем.
Генерируем и запускаем проект. Соберем код, настроим программатор на авторезет и начнем писать.
Запустим ЦАП, найдя функцию в документации HAL User Manual на странице 215
/* USER CODE BEGIN 2 */
HAL_DAC_Start(&hdac,DAC_CHANNEL_1);
/* USER CODE END 2 */
Попробуем затем занести в ЦАП какое-нибудь значение.
Значение, которое неоходимо занести в периферию ЦАП, рассчитывается по следующей формуле
Ну вернее эту формулу необходимо перевернуть, выразив из неё DOR. Это и будет значение, которое мы используем в функции.
Вообщем, например, нам необходимо сгенерировать напряжение на выходе ЦАП значением 1 вольт. Перевернув вышеуказанную формулу, мы должны 1 вольт разделить на опорное напряжение или на 3 и затем умножить на 4095. Получим мы 1365.
Давайте так и поступим.
HAL_DAC_Start(&hdac,DAC_CHANNEL_1);
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,1365);
Прежде чем прошивать контроллера, посмотрим, как мы всё подключили
С ножки PA4, соответствующей выходу нашего преобразователя, я подтянул провод на макетную плату, на которой собрал делитель напряжения, для того, чтобы послушать преобразованный сигнал в активных колонках. А пока мы подключим вольтметр к ножке PA4 и общему проводу.
Соберем, прошьем и померяем напряжение на ножке PA4
Напряжение у нас вполне соответствует заявленному с очень малой погрешностью.
Напишем следующий код в бесконечный цикл, который будем постепенно то наращивать напряжение на выходе
while (1)
{
/* USER CODE END WHILE */
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000000);
HAL_Delay(2);
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000280);
HAL_Delay(2);
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000500);
HAL_Delay(2);
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000780);
HAL_Delay(2);
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000999);
HAL_Delay(2);
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000780);
HAL_Delay(2);
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000500);
HAL_Delay(2);
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000280);
HAL_Delay(2);
Собираем, прошиваем, слушаем в колонках. Для того чтобы вам это услышать, посмотрите видеоверсию урока.
Попробуем удвоить частоту, уменьшив задержку до 1 милисекунды во всех 8 местах
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000280);
HAL_Delay(1);
Для того, чтобы подключить активные колонки, я спаял такой вот незамысловатый разъём-переходник
Подключим в него провод для колонок
На общий провод колонок мы подключим общий провод с макетной платы, а на активный, например, правый канал усилителя активных колонок, мы подадим напряжение с делителя. Делитель собран с расчётом 1 к 10, то есть максимальное напряжение с него будет сниматься максимально до 0,3 вольт, что вполне нам хватит, чтобы услышать звук в громкоговорителе канала.
Собираем код, прошиваем конторллер, слушаем звук в колонках.
Чтобы нам ещё увеличить частоту, нам будет недостаточно функции задержки в милисекундах. Придется написать функцию задержки в микросекундах ну или проще взять её из файла lcd.c проекта по переходнику с I2C на жидкокристаллический дисплей 20х4
/* USER CODE BEGIN 0 */
__STATIC_INLINE void DelayMicro(__IO uint32_t micros)
{
micros *=(SystemCoreClock / 1000000) / 5;
while (micros—);
}
/* USER CODE END 0 */
Сначала проверим данную функцию, введя вместо стандартных функций задержки HAL_Delay(1) во всех восьми местах DelayMicro(1000)
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000999);
DelayMicro(1000);
Прошьём и опять послушаем.
Теперь попробуем удвоить частоту, написав опять в 8 местах
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000999);
DelayMicro(500);
Соберем, прошьем и послушаем.
Увеличим впятеро частоту
HAL_DAC_SetValue(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,0x00000999);
DelayMicro(100);
Соберем, прошьем, послушаем и посмотрим на самодельном осциллографе организованном на плате 429
На следующем занятии мы попробуем с помощью ЦАП сформировать треугольные импульсы аппаратным образом.
Предыдущий урок Программирование МК STM32 Следующий урок
Купить отладочную плату можно здесь STM32F4-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
Почему делим на 5?
micros *=(SystemCoreClock / 1000000) / 5;
Как я понимаю твой SystemCoreClock = 168 Mhz. Делим на 1М получаем 168. Логичнее было бы поделить на 168, что бы получить 1 мили. Не понимаю откуда 5.
потому что столько элементарных циклов требуется для данной операции. У Вас что-то не сходится? Вы измеряли?
да, измеряю на осци. Использую DelayMicro(500). Вместо 500 получаю 600 с копейками. Пробовал погуглить, многие пишут 5, но не все. Пробовал варировать 5 с другими цифрами. Нет результата. Логического объяснения я так и не нашел.
SystemCoreClock — 8 МГЦ в моем случае. Делим на 1000000. Получаем 8. Логично было бы поделить на 8 что бы получить цикл в 1мс. Но кажется я чего то не так понимаю.
Я вообще думал, что делитель — это что-то связанное с количеством тактов на операцию NOP, но вроде должна работать за один такт. Кстати я логическим анализатором померил на 401 плате нуклео, тоже более менее правильные задержки получаются при делителе 8.
Привет, ребят!
Работаю с STM32F746 и возникла очень некомфортная ситуация — элементарно не могу подсоединиться к ножкам ЦАПа (РА4,РА5). Ссылаясь на мануал, могу сказать, что PA4 сидит на DCMI (DCMI_HSYNC), а РА5 — USB_OTG_HS_CK . И как это следует понимать? Как осуществлять, так выражусь, хардварный доступ к ЦАП?
В МК новичок, ровно как и в электронике, уж не ругайтесь особо:)
Да нормально осуществлять. Если у Вас не подключена камера и Вы не включили DCMI в Cube MX, то смело пользуйтесь ножкой. А вот если нет её на разъёме, то это хуже. Тогда значит не предусмотрено. Discovery вообще созданы не для того, чтобы к ней что-то подключать, а в основном для того, чтобы пользоваться уже распаянными на ней компонентами либо специфическими интерфейсами. А для внешних целей больше подходит линейка Nucleo