STM Урок 27. HAL. DAC

 

 

 

 

Урок 27

 

HAL. DAC

 

 

Сегодня мы начнём изучать интересную технологию — цифро-аналоговый преобразователь (ЦАП) или по-английски Digital-to-analog converter (DAC).

Как видно из названия данного преобразователя, он занимается преобразованием цифрового кода или величины в напряжение определённой величины, каким-то образом зависящим от данного цифрового кода. И также из названия видно, что цифро-аналговый преобразователь занимается задачей, обратной той, которой занимается аналого-цифровой преобразователь (АЦП), который мы уже изучили, начиная с урока 16.

Вообщем, слишком подробно мы не будем изучать принцип работы цифро-аналогового преобразователя. Нам важно научиться с ним работать с использованием контроллера STM32.

Во-первых, откроем Reference Manual и посмотрим, какой шиной управляется данная периферия

 

image02

 

Как мы видим — это шина APB1.

Также в этой же документации посмотрим краткие характеристики нашего ЦАП

 

image03

 

ЦАП наш является 12-битным.

Мы видим, что существует два таких ЦАПа, но также здесь находится ремарка, что не очень радостно, что на каждый такой конвертер находится только одна ножка контроллера и переопределить её невозможно.

Также можно выровнять данные по правому или левому краю двухбайтного регистра DHRx. То есть в зависимости от определённых настроек мы назначаем, каким образом мы будем располагать данные в этом регистре для последующего преобразования в электрический сигнал.

Также существует возможность синхронного обновления, генерации шума, треугольных импульсов, раздельного преобразования по каналам, а также совместного их использования, использования DMA для каждого канала, обнаружения ошибок опустошения DMA, использования внешнего управления преобразованием с помощью триггеров и использования опорного напряжения.

Существует также структурная схема ЦАП

 

image04

 

На данной схеме видно, что существует ряд таймеров, которыми мы можем воспользоваться в качестве триггеров. Есть у нас управляющий регистр, биты которого служат для определённых настроек, регистр данных DHRx, ну, верней их два — DHR1 и DHR2, которые мы используем для хранения значения, которое будет преобразовано в электрический сигнал, а номера соответствуют номерам ЦАПов. Ну, и непросредственно, сам преобразователь.

А вот здесь указано, какие существуют режимы для преобразования

 

image05

 

Мы видим, что существует также 8-битный режим и два варианта 12-битного режима в зависимости от выравнивания преобразуемой величины. Данные регистры мы в коде непосредственно использовать не будем, мы разве что к ним обратимся в процессе отладки, если у нас что не заработает.

Ну давайте наконец займёмся кодом.

Проект создаём из проекта TEST001, т.к. там ничего лишнего не подключено. Назовем его DAC. Запустим проект в Cube, включим там DAC OUT1

 

image06

 

В Clock Configuration внесем следующие настройки

 

image00_0500

 

В самом DAC настройки следующие

 

image01

 

Больше ничего не включаем.

Генерируем и запускаем проект. Соберем код, настроим программатор на авторезет и начнем писать.

 

 

Запустим ЦАП, найдя функцию в документации HAL User Manual на странице 215

 

  /* USER CODE BEGIN 2 */

        HAL_DAC_Start(&hdac,DAC_CHANNEL_1);

  /* USER CODE END 2 */

 

Попробуем затем занести в ЦАП какое-нибудь значение.

Значение, которое неоходимо занести в периферию ЦАП, рассчитывается по следующей формуле

 

image07

 

Ну вернее эту формулу необходимо перевернуть, выразив из неё 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);

 

Прежде чем прошивать контроллера, посмотрим, как мы всё подключили

 

image08

 

С ножки PA4, соответствующей выходу нашего преобразователя, я подтянул провод на макетную плату, на которой собрал делитель напряжения, для того, чтобы послушать преобразованный сигнал в активных колонках. А пока мы подключим вольтметр к ножке PA4 и общему проводу.

Соберем, прошьем и померяем напряжение на ножке PA4

 

image09

 

Напряжение у нас вполне соответствует заявленному с очень малой погрешностью.

Напишем следующий код в бесконечный цикл, который будем постепенно то наращивать напряжение на выходе

 

  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);

 

Для того, чтобы подключить активные колонки, я спаял такой вот незамысловатый разъём-переходник

 

image10

 

Подключим в него провод для колонок

 

image11

 

На общий провод колонок мы подключим общий провод с макетной платы, а на активный, например, правый канал усилителя активных колонок, мы подадим напряжение с делителя. Делитель собран с расчётом 1 к 10, то есть максимальное напряжение с него будет сниматься максимально до 0,3 вольт, что вполне нам хватит, чтобы услышать звук в громкоговорителе канала.

 

image12

 

 

Собираем код, прошиваем конторллер, слушаем звук в колонках.

Чтобы нам ещё увеличить частоту, нам будет недостаточно функции задержки в милисекундах. Придется написать функцию задержки в микросекундах ну или проще взять её из файла 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

 

image13

 

На следующем занятии мы попробуем с помощью ЦАП сформировать треугольные импульсы аппаратным образом.

 

Предыдущий урок Программирование МК STM32 Следующий урок

 

Исходный код

 

 

Купить отладочную плату можно здесь STM32F4-DISCOVERY

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

STM HAL. DAC

6 комментариев на “STM Урок 27. HAL. DAC
  1. Максим:

    Почему делим на 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.

  2. Аня:

    Привет, ребят!

    Работаю с STM32F746 и возникла очень некомфортная ситуация — элементарно не могу подсоединиться к ножкам ЦАПа (РА4,РА5). Ссылаясь на мануал, могу сказать, что  PA4 сидит на DCMI (DCMI_HSYNC), а РА5 — USB_OTG_HS_CK . И как это следует понимать? Как осуществлять, так выражусь, хардварный доступ к  ЦАП?

    В МК новичок, ровно как и в электронике, уж не ругайтесь особо:)

    • Да нормально осуществлять. Если у Вас не подключена камера и Вы не включили DCMI в Cube MX, то смело пользуйтесь ножкой. А вот если нет её на разъёме, то это хуже. Тогда значит не предусмотрено. Discovery вообще созданы не для того, чтобы к ней что-то подключать, а в основном для того, чтобы пользоваться уже распаянными на ней компонентами либо специфическими интерфейсами. А для внешних целей больше подходит линейка Nucleo

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*