Embedded HandbookПриложения
Все разделы › III. Периферия
STM32F407, STM32F103xC · уровень средний · 11 мин чтения
Открыть в интерактивном справочнике

15. Цифро-аналоговый преобразователь (ЦАП)

Обратная задача к предыдущему разделу: получить из числа напряжение. Нужно для генерации сигналов, задания опорных уровней, управления исполнительными устройствами с аналоговым входом и синтеза звука.

Как это называется в документации

Оригинал Перевод
output buffer выходной буфер
data holding register регистр хранения данных
left / right aligned выравнивание влево / вправо
trigger source источник запуска
noise / triangle wave generation генерация шума / треугольника
dual mode двухканальный режим
settling time время установления
electronic load электронная нагрузка
current sink управляемый приёмник тока

15.1 ⚠️ Сначала проверьте, есть ли он вообще

Единственный раздел, который начинается с предупреждения о наличии блока:

Микроконтроллер ЦАП
STM32F103C8 (BluePill) нет
STM32F103xC / xD / xE 2 канала
STM32F407 и большинство F4 2 канала (PA4, PA5)
STM32F401, F411 нет

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

Чем заменить, если ЦАП нет: ШИМ с фильтром. Раздел 13 даёт прямоугольник с регулируемой скважностью, а RC‑цепочка сглаживает его до постоянного напряжения, пропорционального скважности. Для медленных сигналов — управление яркостью, задание уставки — этого достаточно. Для звука и быстрых сигналов фильтр придётся делать сложнее, и проще взять кристалл с ЦАП.


15.2 Как им пользоваться

Блок предельно простой: записали число — получили напряжение.

напряжение = код × Vref / 4095
void dac_ch1_init(void)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    RCC->APB1ENR |= RCC_APB1ENR_DACEN;

    /* PA4 — аналоговый режим: цифровая обвязка должна быть отключена. */
    GPIOA->MODER |= GPIO_MODER_MODER4;        /* 11 -> аналоговый */
    GPIOA->PUPDR &= ~GPIO_PUPDR_PUPD4;        /* подтяжки убрать */

    DAC->CR |= DAC_CR_EN1;
}

void dac_ch1_set_mv(uint32_t millivolts)
{
    uint32_t code = millivolts * 4095u / VREF_MV;
    if (code > 4095u) {
        code = 4095u;
    }
    DAC->DHR12R1 = code;                      /* выравнивание вправо, 12 бит */
}

Три регистра на один канал — это не опечатка в документации: DHR12R1 для 12 бит с выравниванием вправо, DHR12L1 — влево, DHR8R1 — для 8‑битного режима. Выбирайте тот, что удобнее вашим данным: выравнивание влево полезно, когда результат приходит из 16‑битного источника и не хочется его сдвигать.


15.3 Выходной буфер

Единственная настройка, требующая размышления.

Буфер включён (по умолчанию): внутреннее сопротивление низкое, но нагрузку он держит скромную — по таблице ЦАП в документации F407 не ниже 5 кОм и не больше 50 пФ, при 3,3 В это 0,66 мА. И диапазон сужается — до самых краёв шкалы напряжение не доходит, примерно 0,2 В теряется с каждой стороны.

Буфер выключен: полный размах от нуля до опоры, но выходное сопротивление — до 15 кОм, и для точности 1 % нагрузка должна быть не ниже 1,5 МОм: только высокоомный вход или внешний операционный усилитель.

Практическое правило: сигнал идёт на вход усилителя или АЦП — буфер можно отключать; нагрузка от 5 кОм — оставляйте включённым и мирьтесь с потерей краёв; всё, что берёт больше долей миллиампера, — только через внешний ОУ.


15.4 🔧 Генератор сигнала на таблице

Самое интересное применение: связка «таблица в памяти → DMA → ЦАП» с запуском от таймера. Процессор не участвует вообще, а на выходе идёт непрерывный сигнал любой формы.

Логика простая. В памяти лежит один период сигнала, разбитый на точки. Таймер тикает с заданной частотой, каждый тик заставляет DMA переложить очередную точку в регистр ЦАП. Дошли до конца таблицы — начали сначала.

частота сигнала = частота запуска / число точек в таблице
/* Таблица синуса, 12 бит, смещение в середину шкалы.
   const — значит лежит во Flash и не тратит ОЗУ. */
static const uint16_t sine_table[64] = { 2048, 2248, 2447, /* ... */ };

void dac_sine_init(void)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_DMA1EN;
    RCC->APB1ENR |= RCC_APB1ENR_DACEN | RCC_APB1ENR_TIM6EN;

    GPIOA->MODER |= GPIO_MODER_MODER4;

    /* Таймер 6 задаёт частоту выдачи точек. */
    TIM6->PSC = 84u - 1u;                     /* тик 1 мкс */
    TIM6->ARR = 10u - 1u;                     /* запуск каждые 10 мкс */
    TIM6->CR2 &= ~TIM_CR2_MMS;
    TIM6->CR2 |= TIM_CR2_MMS_1;               /* 010: событие обновления наружу */
    TIM6->CR1 |= TIM_CR1_CEN;

    /* DMA по кругу гоняет таблицу в регистр ЦАП. */
    DMA1_Stream5->PAR  = (uint32_t)&DAC->DHR12R1;
    DMA1_Stream5->M0AR = (uint32_t)sine_table;
    DMA1_Stream5->NDTR = 64;
    DMA1_Stream5->CR = (7u << DMA_SxCR_CHSEL_Pos)
                     | DMA_SxCR_DIR_0                 /* память -> периферия */
                     | DMA_SxCR_MINC
                     | DMA_SxCR_CIRC                  /* по кругу, вечно */
                     | DMA_SxCR_PSIZE_0               /* полуслово */
                     | DMA_SxCR_MSIZE_0
                     | DMA_SxCR_PL_1;
    DMA1_Stream5->CR |= DMA_SxCR_EN;

    /* Канал ЦАП: запуск от таймера, выдача в DMA. */
    DAC->CR &= ~DAC_CR_TSEL1;                 /* 000 -> TIM6 TRGO */
    DAC->CR |= DAC_CR_TEN1 | DAC_CR_DMAEN1 | DAC_CR_EN1;
}

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

При запуске каждые 10 мкс и таблице из 64 точек получается синус частотой около 1,56 кГц. Чтобы изменить частоту — меняете ARR таймера, таблицу трогать не нужно.

На осциллографе это ступенчатый синус — 64 ступеньки на период:

Выход ЦАП на осциллографе: синус из таблицы 64 точек, ступенька каждые 10 мкс

Сколько точек брать. Компромисс: больше точек — глаже сигнал, но ниже предельная частота и больше памяти. Для звука обычно берут 256 точек, для служебных сигналов хватает 32–64. Таблицу считают заранее и кладут в const, чтобы она осталась во Flash.


15.5 Встроенные генераторы

Помимо таблицы у блока есть два собственных режима: треугольник и псевдослучайный шум. Амплитуда задаётся, форма считается аппаратно, таблица и DMA не нужны вовсе. Шаг генератор делает только по запуску (TEN1 = 1, источник — TSEL1, обычно таймер): без запуска треугольника не будет. Треугольник с амплитудой MAMP1 = 2ⁿ − 1 поднимается и опускается по единице, так что период — около 2 · 2ⁿ запусков: при запуске раз в 10 мкс и амплитуде 4095 — 8190 запусков, 12 Гц. Значение DHR12R1 прибавляется как смещение.

Треугольник удобен для развёртки и проверки линейности схемы, шум — для тестирования фильтров и подмешивания дизеринга.


15.6 🔧 Электронная нагрузка: ЦАП задаёт ток

Прибор для проверки источников питания и батарей: он забирает ровно заданный ток, каким бы ни было напряжение. Собирается из ЦАП, операционного усилителя, MOSFET и шунта:

ЦАП (PA4)       → вход «+» ОУ
выход ОУ        → затвор MOSFET
исток MOSFET    → шунт 1 Ом → земля
верх шунта      → вход «−» ОУ
сток MOSFET     → проверяемый источник

Усилитель (раздел 56) подстраивает затвор так, чтобы напряжение на шунте сравнялось с напряжением ЦАП. Поэтому ток через транзистор и шунт равен U_ЦАП / R, и от напряжения источника он не зависит. MOSFET (раздел 52) работает здесь не ключом, а регулирующим элементом — в линейной области.

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

#define VREF_MV    3300u
#define R_SHUNT_MO 1000u                    /* шунт 1 Ом, в миллиомах */
#define I_MAX_MA   1000u                    /* программный предел нагрузки */

/* код ЦАП для тока в миллиамперах: U = I·R, код = U·4095 / Vref, с округлением */
uint32_t load_code(uint32_t ma)
{
    if (ma > I_MAX_MA)
        ma = I_MAX_MA;
    return (uint32_t)(((uint64_t)ma * R_SHUNT_MO * 4095u + VREF_MV * 500u) / (VREF_MV * 1000u));
}

void load_dac_init(void)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    RCC->APB1ENR |= RCC_APB1ENR_DACEN;
    GPIOA->MODER |= GPIO_MODER_MODER4;      /* PA4 — аналоговый */
    DAC->DHR12R1 = 0;                       /* старт с нулевого тока */
    DAC->CR = DAC_CR_BOFF1 | DAC_CR_EN1;    /* буфер выключен: вход ОУ высокоомный, шкала до нуля */
}

void load_set_ma(uint32_t ma)
{
    DAC->DHR12R1 = load_code(ma);
}

Что даёт шкала. При опоре 3,3 В и шунте 1 Ом один код — 0,806 мА. Программный предел 1 А — это код 1241; всё, что выше, урезается. Пересчёт в целых с округлением ошибается не больше чем на полшага — 0,403 мА по всей шкале 0–1000 мА.

Смещение ОУ съедает низ шкалы. На шунте 1 Ом каждый милливольт смещения — миллиампер ошибки, а шаг ЦАП всего 0,806 мА. Смещение обычного усилителя — около 3 мВ (раздел 56): в модели с +3 мВ нагрузка на коде 0 тянет 3,05 мА, а с −3 мВ коды 0–3 не дают тока вовсе, и он появляется только с четвёртого. Поэтому ОУ берут с входом до земли и малым смещением, а ноль калибруют.

Петля может звенеть. Затвор для ОУ — ёмкостная нагрузка (раздел 163), а обратная связь здесь идёт не с выхода усилителя, а с шунта, за транзистором. Развязывающий резистор из 163.3 в одиночку тут вредит: в модели с усилителем на 1 МГц ступенька тока на 10 % даёт выброс от 1 до 24 % без резистора и от 8 до 45 % с резистором 470 Ом в затворе. Работает пара: резистор 220 Ом в затвор и местная обратная связь — шунт соединяют со входом «−» не проводом, а через 10 кОм, и с выхода ОУ на тот же вход ставят 1 нФ. Выброса нет ни на 20 мА, ни на 0,5 А, ни с транзистором втрое большей ёмкости; цена — установление 7–8 мкс на 0,5 А и около 70 мкс на 20 мА.

Куда уходит мощность. В шунте при 1 А выделяется 1 Вт (I²R), так что резистор нужен с запасом. Но основное греет транзистор: он гасит всё, что источник даёт сверх падения на шунте. При 12 В и 1 А это (12 − 1) × 1 = 11 Вт, при 5 В — 4 Вт. Без радиатора транзистор такую мощность не рассеет.

Транзистор выбирают по области безопасной работы. Он работает у самого порога: в модели раздела 171 ток 1 А при 11 В на стоке — это 1,87 В на затворе, и участок кристалла, горячее на 60 °C, при том же затворе пропустил бы 2,5 А (171.5). Горячее место забирает ток и греется дальше — кристалл выгорает пятном раньше, чем обещает тепловое сопротивление. Поэтому смотрят график области безопасной работы (SOA) — линию постоянного тока (DC) при нужном напряжении, — а не сопротивление открытого канала. Нет на графике линии DC — транзистор для такой работы не рассчитан.

Электронная нагрузка на 1 А: шунт — 1 Вт, остальное греет транзистор


15.7 Грабли

ЦАП нет в вашем кристалле. Проверять по datasheet до разводки платы.

Вывод не в аналоговом режиме. Цифровой буфер остаётся подключённым и мешает.

Не доходит до краёв шкалы. Включён выходной буфер — это его нормальное поведение, а не неисправность.

Выход «проседает» под нагрузкой. Буфер выключен, а нагрузка низкоомная. ЦАП не источник питания: для реальной нагрузки нужен усилитель.

Сигнал ступенчатый. Мало точек в таблице или нет фильтра на выходе. ЦАП выдаёт именно ступеньки, сглаживание — задача внешней RC‑цепочки.

Частота сигнала не та, что рассчитывали. Забыли, что таймер на APB1 получает удвоенную частоту шины (раздел 13).

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

Электронная нагрузка не опускается до нуля. Включён выходной буфер ЦАП: нижние коды не доходят до нуля (15.3). Вход усилителя в буфере не нуждается — бит BOFF1 (15.6).


15.8 Итоги

Дальше: раздел 15а «АЦП нового образца на G4» — дифференциальный вход, передискретизация до 16 бит и две калибровки.

Открыть в интерактивном справочнике