Мне нужно было медленно менять напряжение на входе самодельного блока питания — плавно от нуля до четырёх вольт, чтобы найти порог, где схема начинает глючить. Первой на ум пришла ШИМ: analogWrite с потенциометром, RC-фильтр из 10 кОм и 10 мкФ — и на выходе почти постоянка. Почти: мультиметр показывал 2,47 В, потом 2,49, потом снова 2,47 — последние цифры плясали, потому что за усреднённым напряжением прятались пульсации в десятки милливольт. Для светодиода это неважно, а для поиска порога срабатывания — издевательство: будешь гонять схему вверх-вниз и не поймёшь, где настоящий порог, а где рябь от фильтра. Я сперва решил дожать фильтр: удвоил конденсатор до 22 мкФ — рябь упала до полутора десятков мВ, зато реакция стала ленивой, и каждый шаг рампы по 0,4 В въезжал полсекунды.
Лекарство зовётся MCP4725: чип размером с кунжутное зерно на платке 15×12 мм, который по двум проводам I2C принимает число и держит на выходе честное постоянное напряжение, без всяких внешних фильтров. В этом уроке научимся говорить с ним на голом Wire, без библиотек: одна команда 0x40, два байта данных — и на выходе любое напряжение от нуля почти до питания с шагом 1,22 мВ. Симулятор уже показал шину в панели I2C — а я по ходу объясню, почему для плавного напряжения ЦАП бьёт ШИМ вчистую и где у этой пары граница применимости.
Почему ЦАП, а не ШИМ
ШИМ — это не напряжение, а быстро моргающий ключ: на Uno пины 3, 9, 10 и 11 моргают с частотой 490 Гц, пины 5 и 6 — 980 Гц. Усреднить такое можно фильтром, но фильтр — компромисс: резистор побольше и конденсатор пожирнее дают меньше пульсаций, зато вялую реакцию — выход ползёт к новому значению десятые доли секунды. Я на этом обжёгся дважды: сначала получил рябь на вольтметре, потом, нарастив конденсатор, — задержку, из-за которой вся плавность превратилась в ступени. Как устроен сам моргающий ключ, я разбирал в уроке про ШИМ и analogWrite — там же про частоты 490 и 980 Гц. Честности ради: для ночной подсветки ШИМ с фильтром до сих пор мой выбор — там нужен не уровень вольт, а средний свет, и точность не важна.
ЦАП устроен иначе: внутри чипа резисторная матрица и буферный усилитель, который держит на выходе нужный уровень сам, без внешних деталей. Формула одна: Vout = Vcc × код / 4096. Питаем модуль от 5,0 В — получаем шаг 5,0/4096 ≈ 1,22 мВ и потолок у самого питания; питаем от 3,3 В — потолок 3,3 В, шаг 0,81 мВ. Задача урока — вилка «ноль–четыре вольта»: при питании 5,0 В это код 3277, потому что 4/5 × 4096 = 3276,8. Дальше в скетчах я жму диапазон именно этой константой — и советую вам её посчитать под свою задачу, а не копировать. Моё правило простое: для индикации и подсветки хватает ШИМ, а для измерительных и регулирующих цепей, где вольт считается милливольтами, — только ЦАП.
| Что сравниваем | ШИМ напрямую | ШИМ + RC-фильтр | MCP4725 |
|---|---|---|---|
| Пульсации на выходе | половина питания | десятки мВ | единицы мВ и меньше |
| Реакция на новый код | мгновенно, но это рябь | 0,1–0,5 с | микросекунды |
| Занятые пины | 1 ШИМ-пин | 1 пин + детали | 2: A4 и A5, общая шина |
| Разрешение | 8 бит, 256 ступеней | те же 256 | 12 бит, 4096 ступеней |
Красота MCP4725 в том, что библиотека не нужна — достаточно голого Wire, который мы разбирали в уроке про устройство шины I2C. Посылка короткая, четыре байта: адрес 0x62, команда 0x40 («пиши в регистр ЦАП»), затем два байта данных, где спрятаны 12 значащих бит кода. Адрес 0x62 — заводской для чипа с ножкой A0 на земле; посадите A0 на питание — чип переедет на 0x63. Не верите паспортному адресу — прогоните сканер из урока про поиск устройств на шине: он за минуту покажет, кто реально откликается, и вы узнаете адрес до всякой прошивки.
Два байта данных упаковываются просто: старшие восемь бит кода идут первым байтом (code >> 4), младшие четыре — вторым, но сдвинутыми к старшему краю ((code & 15) << 4). Ошибиться тут легче всего: я однажды забыл сдвиг и получил напряжение, прыгающее ступенями по 19,5 мВ вместо плавного хода. Запомните симптом: лестница вместо рампы — это почти всегда битовая упаковка, а не сломанный чип.
Бонус для дотошных: чип умеет и отвечать. Запрос Wire.requestFrom(0x62, 5) возвращает пять байтов — там лежат текущий код рабочего регистра и код, сохранённый в EEPROM; на живой плате так удобно проверять, дошла ли посылка, когда шину тянут длинными проводами. Нам для уставок и рамп хватает записи, но помнить про чтение стоит: он и адрес подтвердит, и значение покажет.
#include <Wire.h> const int MAXCODE = 3277; // 3277 шагов по 1,22 мВ = 4,00 В при питании 5,0 В void dacSet(int code) { Wire.beginTransmission(0x62); // адрес ЦАП: ножка A0 на земле Wire.write(0x40); // команда: пишем в регистр ЦАП Wire.write(code >> 4); // старшие 8 бит кода Wire.write((code & 0xF) << 4); // младшие 4 бита — к старшему краю байта Wire.endTransmission();} void setup() { Wire.begin(); Serial.begin(9600); Serial.println("Рампа 0-4 В, шаг 1,22 мВ");} int code = 0; void loop() { dacSet(code); int u = code * 5000 / 4096; // милливольты при питании ровно 5,0 В Serial.print("code="); Serial.print(code); Serial.print(" u="); Serial.println(u); code = code + 328; // примерно 0,4 В за шаг if (code > MAXCODE) code = 0; delay(400);}Проверяем в симуляторе
Симулятор собрал шину за нас: SDA на A4, SCL на A5, как и положено на Uno. Честно предупреждаю: чипа MCP4725 в виртуальной схеме нет — заглушка шины лишь повторяет протокол, и аналогового напряжения на выходе вы не увидите. Зато трафик подлинный: панель I2C покажет на каждый шаг рампы ровно ту посылку, что ушла бы на живую плату. Код 1024 выглядит так: START на адрес 0x62, данные 64, 64, 0 — и STOP. Здесь 64 — это команда 0x40, второй 64 — старший байт кода (1024 >> 4), ноль — младший.
Тайминги тоже честные: четыре байта с подтверждениями — это 36 бит шины, при 100 кГц около 360 мкс, при 400 кГц — около 90. Для рампы с шагом 400 мс это неважно, а для генерации сигналов — уже нет: меняя код каждые 90 мкс при 400 кГц, синус из 200 точек уложится в период 18 мс — это около 55 Гц, и такой звук из ЦАП реально извлечь. Если хочется шагать по рампе не на delay(), а по-взрослому — рецепт многозадачного скетча лежит в уроке про millis() вместо delay().
Эксперименты для закрепления
Первое: сожмите диапазон до 0–2 В — подсказка: 2/5 × 4096 = 1638, поменяйте константу потолка и урежьте шаг вдвое. Второе: переверните рампу, чтобы напряжение плавно падало от 4 В к нулю, — код должен уменьшаться, и следите, чтобы он не уехал в минус: я однажды вылетел за границу int и ловил напряжения, которых не бывает. Третье: перешейте адрес на 0x63 (посадите A0 на питание) и запустите сканер шины — убедитесь, что 0x62 замолчал, а 0x63 откликнулся. На живой плате параллельно рампе повесьте мультиметр между Vout и GND: сравнение напечатанных u= с прибором — лучший тест, что битовая упаковка не поехала. У меня на одном модуле максимум вышел 4,98 В при питании 5,02: буфер не дотянул до рельса 40 мВ, и для реального железа это нормально.
Четвёртое, для тех, кому мало: задайте уставку один раз и запишите её в EEPROM чипа — вместо команды 0x40 отправьте 0x60 с теми же двумя байтами данных, а затем перезапустите плату и убедитесь, что модуль сам вышел на сохранённое напряжение. Про энергонезависимую память на самой плате я рассказывал в уроке про счётчик включений на EEPROM — у чипа ЦАП логика та же, только пишется туда напряжение. И напоследок прислушайтесь к честности симулятора: он показывает шину, но не показывает вольты, — привычка мерить реальное напряжение мультиметром должна остаться с вами и на живой макетке.
- MCP4725 — 12-битный ЦАП на шине I2C: 4096 ступеней, шаг 1,22 мВ при питании 5,0 В
- Посылка на голом Wire: beginTransmission(0x62) → write(0x40) → write(старший байт) → write(младший, сдвинут к старшему краю) → endTransmission()
- Потолок выходного напряжения равен питанию модуля: 5,0 В от 5 В, 3,3 В от 3,3 В; 4,00 В — это код 3277
- ШИМ + RC-фильтр оставляет десятки милливольт пульсаций и вялую реакцию; ЦАП держит постоянку сам и переключается за микросекунды
- EEPROM внутри чипа помнит уставку после выключения (команда 0x60), но запись длится 25–50 мс, ресурс около миллиона циклов — пишите только при изменении
- 1. Какой код нужно отправить, чтобы получить 4,00 В при питании модуля 5,0 В?
- 2. Первый байт данных после адреса в посылке к MCP4725 — это:
- 3. Зачем младшие 4 бита кода сдвигают влево перед отправкой второго байта?
- 4. Чем плоха запись кода в EEPROM чипа на каждом проходе loop()?
- 5. Питаете MCP4725 от 3,3 В. Какое максимальное напряжение выйдет на Vout?
Частые вопросы по теме урока
- Какой код нужно отправить, чтобы получить 4,00 В при питании модуля 5,0 В?
- 4/5 × 4096 = 3276,8, округляем до 3277. Код 4095 — это потолок почти 5 В, а не 4 В. 3277
- Первый байт данных после адреса в посылке к MCP4725 — это:
- 0x40 приказывает чипу записать следующие два байта в рабочий регистр ЦАП. команда 0x40 — «пиши в регистр ЦАП»
- Зачем младшие 4 бита кода сдвигают влево перед отправкой второго байта?
- 12 бит кода упаковывают в два байта: восемь плюс четыре; в младшем байте значащие биты лежат вверху. значащие биты занимают старшую половину байта, младшая половина — служебная
- Чем плоха запись кода в EEPROM чипа на каждом проходе loop()?
- Ресурс — около миллиона циклов; десять записей в секунду выжигают его меньше чем за двое суток. запись занимает 25–50 мс, и ресурс чипа выгорит за сутки с небольшим
- Питаете MCP4725 от 3,3 В. Какое максимальное напряжение выйдет на Vout?
- Потолок равен питанию модуля: при 3,3 В весь диапазон — от нуля до 3,3 В с шагом 0,81 мВ. 3,3 В