STM32 DAC Примеры использования

Прежде чем приступить к использованию ЦАПа в реальных устройствах, давайте немного поиграемся с ним на отладочной плате. Для начала напомним некоторые сведения об этом модуле.

Микроконтроллер содержит двухканальный 12-разрядный ЦАП (он же DAC). Канал 1 подключен к выводу PA4, канал 2 – к выводу PA5.

Регистры DOR1 и DOR2 – это 16-разрядные регистры данных для первого и второго каналов. Если в регистре данных содержится 0, то на соответствующем выходе напряжение равно нулю. Если в регистре данных 4095, то на выходе максимальное напряжение, равное опорному. Напряжение на выходе рассчитывается по следующей формуле:

Vout = Vref  x  ( DOR / 4095 ), где Vref – опорное напряжение, DOR – значение в регистре данных.

Чтобы получить нужное напряжение на выходе, необходимо в регистр данных выбранного канала занести значение, рассчитанное по приведенной выше формуле. Но есть одна проблема – регистры данных доступны только для чтения. Для записи  имеются специальные промежуточные регистры – Data Holding Register.

Сразу пишем в промежуточный регистр, затем данные из промежуточного регистра переписываются в регистр данных, что приведет к появлению требуемого сигнала на выходе. Эта перезапись может происходить сама собой (через 1 такт) или под действием какого-то события, например от таймера.

Выбор способа перезаписи осуществляется разрядом TENх регистра управления CR. Если этот разряд сброшен, перезапись будет происходить автоматом, иначе потребуется внешнее событие. Источник события выбирается разрядами TSELх (х=1 или 2) регистра управления CR.

После сброса контроллера во всех разрядах нули, поэтому по умолчанию включен режим автоматической перезаписи.

Вернемся к промежуточным регистрам (по другому, регистры предварительной загрузки). Всего таких регистров в контроллере 9, по три регистра на каждый режим работы, которых тоже три. Рассмотрим эти три режима.

Восьмиразрядный  режим.

В этом режиме весь диапазон выходных напряжений разбит на 256 уровней (ЦАП из 12-разрядного превращается в  восьмиразрядный).

Для загрузки в первый канал используется регистр DHR8R1, во второй – DHR8R2. Эти регистры имеют 32 разряда (как впрочем все регистры предварительной загрузки), но используется только 8 младших разрядов.

Третий регистр – DHR8RD. Он используется для одновременной загрузки восьмиразрядных данных в оба канала. Структура регистра такая:

image

Как видно, младшие 8 разрядов используется для загрузки данных первого канала, следующие 8 разрядов – для второго канала. Это удобно для воспроизведения стереосигнала (по-моему, ЦАП в этих контроллерах оптимизирован для воспроизведения аудио сигнала). Формат стереосигнала обычно содержит чередующиеся выборки левого и правого канала, поэтому такая структура регистра очень удобна.

Попробуем используя приведенные выше регистры получить требуемый уровень сигнала на выходе ЦАПа (предполагаем, что данную настройку выполняем после ресета контроллера, когда во всех разрядах регистров нули):

1
2
3
4
5
6
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;     // тактирование линий GPIOA
RCC->APB1ENR |= RCC_APB1ENR_DACEN;      // включить тактирование ЦАПа
DAC->CR      |= DAC_CR_EN1;             // включить канал №1
DAC->CR      |= DAC_CR_EN2;             // включить канал №2
DAC->DHR8R1   = 0xff;                   // на выходе канала 1 максимальное напряжение
DAC->DHR8R2   = 0x7f;                   // на выходе канала 2 половина максимального напряжения

После этого можем измерить напряжение на выходе. На первом канале у меня получилось 2.9V, на втором примерно 1.5V

Того же  результата можно добиться  используя регистр DHR8RD. Для этого две последние строки нужно заменить одной:

1
DAC->DHR8RD = 0x7fff;                   //загрузить 0x7f во второй канал и 0xff в первый       /

12-разрядный режим с выравниванием вправо.

Задействованы все 12 разрядов ЦАПа. Регистр DHR12R1 обеспечивает доступ к первому каналу, регистр DHR12R2 – ко второму. Регистры 32-разрядные, но задействованы только 12 младших разрядов.

Для одновременной загрузки в оба канала используется регистр DHR12RD. В нем младшие 12 разрядов содержат значения для первого канала, а разряды 16..27 – данные второго канала.

image Получается все как в восьмиразрядном режиме, только точность выше.

12-разрядный режим с выравниванием влево.

Хитрый режим. Благодаря ему можно легко выводить звуковые данные, которые имеют разрядность 16 бит. Не будь этого режима, для вывода 16-разрядных данных приходилось бы сдвигать каждый отсчет (сэмпл)  на 4 разряда вправо, а это очень утомительно для контроллера.

Регистр DHR12L1 используется для первого канала, DHR12L2 – для второго. Используются разряды с 4 по 15, остальные игнорируются.

Для одновременной загрузки в оба канала используется регистр DHR12LD. Структура у него такая:

image

Итак, для загрузки данных имеется 9 регистров. На первый взгляд могло показаться, что это много, но при детальном рассмотрении понимаешь, что все очень хорошо продумано. Разработчики в ST не зря свой хлеб с маслом жуют.

Передача данных в ЦАП посредством ДМА

Это очень удобный режим, так как позволяет выводить данные из памяти в ЦАП (а значит и на выход ЦАПа) с минимальным участием процессора.

Предположим, что на выходе нашего дорогого ЦАПа необходимо сформировать сигнал определенной формы (это может быть синусоида, звуковой сигнал и др.).

Для простоты попробуем вывести периодический сигнал, изменяющийся по такому закону: 0, 1/4Vref, 1/2Vref, 3/4Vref, Vref, 3/4Vref, 1/2Vref, 1/4Vref.

Для этого понадобится всего 8 отсчетов. Их немного, но это позволит легко проверить сигнал на выходе обычным мультиметром.

Таблицу значений уровней выводимого сигнала занесем в память:

1
unsigned char tab[8] = {0,63,127,191,255,191,127,63};                                         /

Итак, у нас есть буфер в памяти, данные из которого нужно последовательно байт за байтом перемещать в регистр первого канала ЦАПа. Этим буде заниматься ДМА, но необходимо заставить его выдавать данные с заданной периодичностью (в нашем случае 2сек). Для этого нужен таймер. Я задействовал таймер TIM6, который управляет третьим каналом ДМА. Настройка ЦАПа, ДМА, и TIM6 будет выглядеть следующим образом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//Настраиваем первый канал ЦАПа для приема данных от ДМА
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;            //тактирование линий GPIOA
RCC->APB1ENR |= RCC_APB1ENR_DACEN;             //тактирование ЦАПа
DAC->CR      |= DAC_CR_DMAEN1;                 //разрешить прием данных канала №1 от ДМА
DAC->CR      |= DAC_CR_EN1;                    //включить канал 1
//Настроить третий канал ДМА для перемещения данных из буфера 
//в промежуточный регистр данных первого канала ЦАПа:
RCC->AHBENR         |= RCC_AHBENR_DMA1EN;      //тактирование DMA
DMA1_Channel3->CPAR  = (uint32_t)&DAC->DHR8R1; //указатель на регистр периферии
DMA1_Channel3->CMAR  = (uint32_t)&tab[0];      //указатель на начало буфера в памяти
DMA1_Channel3->CCR  |= DMA_CCR3_DIR;           //направление передачи - из памяти в периферию 
DMA1_Channel3->CNDTR = 8;                      //размер буфера  
DMA1_Channel3->CCR  &= ~DMA_CCR3_PINC;         //адрес периферии не инкрементируем
DMA1_Channel3->CCR  |= DMA_CCR3_MINC;          //адрес памяти инкрементируем
DMA1_Channel3->CCR  &= ~DMA_CCR3_PSIZE;        //размерность данных периферии - 8 бит.
DMA1_Channel3->CCR  &= ~DMA_CCR3_MSIZE;        //размерность данных памяти    - 8 бит
DMA1_Channel3->CCR  |= DMA_CCR3_CIRC;          //включить циклический режим передачи данных
DMA1_Channel3->CCR  |= DMA_CCR3_PL;            //приоритет
DMA1_Channel3->CCR  |= DMA_CCR3_EN;            //разрешаем работу третьего канала DMA
//Запуск передачи очередного значения через ДМА будет происходить от TIM6  
RCC->APB1ENR        |= RCC_APB1ENR_TIM6EN;     //подаем тактирование TIM6
TIM6->PSC            = 24000-1;                //на счетный регистр подаем импульсы 1мс
TIM6->ARR            = 2000-1;                 //1мс х 2000 = 2сек
TIM6->DIER          |= TIM_DIER_UDE;           //разрешить запрос DMA при обновлении 
                                               //счетного регистра
TIM6->CR1           |= TIM_CR1_CEN;            //запуск таймера

Небольшое пояснение к настройке таймера. Для обеспечения подачи миллисекундных импульсов в регистр CNT я занес в регистр PSC значение 24000-1, т.к. у меня тактовая частота 24МГц.

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

Хотя для работы через ДМА необходимо потрудиться, оно того стоит. Ведь в дальнейшем передача данных будет производиться автоматически.

Комментарии (9) на “STM32 DAC Примеры использования”

  • Open:

    На STM32F100 пришлось таки ногу проинициализировать:
    GPIOA->CRL &= ~GPIO_CRL_MODE4;
    GPIOA->CRL &= ~GPIO_CRL_CNF4;

  • Dmytro:

    Отличный цикл. Так держать!!!

  • Victor:

    Согласно референс мануалу для f101, f102, f103, f105 и f107, TIM6 и DAC_Channel1 закреплены за третьим каналом второго DMA, а в вашем примере указан первый DMA.

  • Victor:

    Этот пример для f100?

  • Антон:

    День добрый. А подскажите пожалуйста: каким образом связаны события таймера и ДМА?
    Пытаюсь освоить сей камень (stm32f100c4).
    Пользуюсь их (stm) библиотекой
    В отладчике вижу, что таймер считает, а ДМА стоит на месте, соответственно ДАК не работает.
    При этом аналогичным образом настроен ДМА и АЦП. Там всё работает.

    Что-то в голове не могу уложить схему, как работает этот зоопарк из таймера, ДМА, ДАКа.

  • Антон:

    Как оно работает я так и не понял, но проблема была в неправильном номере канала дма. Использую Т15 а для него надо канал 5

  • «зоопарк» из таймера, ПДП и ЦАП работает следующим образом — таймер по событию обновления запускает преобразование ЦАП, а ЦАП в свою очередь запрашивает данные у ПДП, вот и все. Таким образом получается минимальный джиттер и минимальное участие МК в передаче данных в ЦАП, особенно если используется циклический режим передачи данных, как в этом примере.

Оставить комментарий

Spam Protection by WP-SpamFree