В прошлый раз мы поговорили о том, как обрабатывать информацию, поступающую из внешнего мира. Теперь настало время подумать, как эту информацию получать. В этой статье мы рассмотрим простейшие аналого-цифровые преобразователи (а заодно и цифро-аналоговые), которые можно соорудить на основе наших контроллеров.
11 Встроенный операционный усилитель
Сегодня мы будем активно работать с аналоговыми сигналами, моделируя различные типы преобразователей аналоговых и цифровых сигналов друг в друга. Но поскольку это все не более чем демонстрационные модели, городить полноценные схемы с грамотным аналоговым питанием и дополнительными микросхемами смысла, все-таки, нет. Поэтому воспользуемся интересным аппаратным модулем, встроенным в CH32V303, операционным усилителем.
На самом деле, их у нас целых четыре штуки. У каждого из них, разумеется, есть инвертирующий вход, неинвертирующий вход и выход. И каждый из них можно коммутировать между двумя ножками, что описано, как обычно, в даташите. В случае моей отладочной платы, легкодоступным оказался только OPA2, у которого неинвертирующий вход соединен с PA7 (а по умолчанию — с PB14), инвертирующий с PA5 (по умолчанию — с PB10), а выход с PA2 (альтернативно — с PE14, но оно доступно только в стоногом корпусе).
Включаются операционные усилители в коде на удивление просто. Не надо настраивать ножки в альтернативную функцию, не надо даже разрешать тактирование. Достаточно записать четыре бита в OPA->CR
. OPA1 соответствуют биты 0–3, OPA2 биты 4–7, OPA3 биты 8–11 и OPA4 биты 12–15. К сожалению, описать их в заголовочном файле китайцы поленились, поэтому придется делать это вручную:
#define OPA_EN2 (1<<4) // OPA2 enable
#define OPA_MODE2 (1<<5) // OPA2 output: 0-default, 1-alternative
#define OPA_NSEL2 (1<<6) // OPA2 negative input: 0-default, 1-alternative
#define OPA_PSEL2 (1<<7) // OPA2 positive input: 0-default, 1-alternative
#define OPA_2_OUT0 (0*OPA_MODE2) //PA2
#define OPA_2_OUT1 (1*OPA_MODE2) //PE14
#define OPA_2_INP0 (0*OPA_PSEL2) //PB14
#define OPA_2_INP1 (1*OPA_PSEL2) //PA7
#define OPA_2_INN0 (0*OPA_NSEL2) //PB10
#define OPA_2_INN1 (1*OPA_NSEL2) //PA5
void opamp_init(){
OPA->CR = (OPA_EN2 | OPA_2_INP1 | OPA_2_INN1 | OPA_2_OUT0);
}
В принципе, из кода уже все видно: для обоих входов используется альтернативный канал (1-й), для выхода – дефолтный (0-й). И это все!
12 ЦАП
Начнем с того, как цифровые данные из недр контроллера преобразовать в аналоговые, пригодные для внешних схем. Такие преобразующие устройства называются цифро-аналоговыми преобразователями, ЦАП, они же digital to analog convertors, DAC. Выходное напряжение у них линейно зависит от цифрового значения и может меняться, как правило, от нуля до опорного напряжения. В контроллерах обычно в качестве опорного напряжения выступает питание аналоговой части AVCC, хотя иногда предусмотрен и специальный вывод Vref. Для 12-битного ЦАП зависимость выходного напряжения от кода такова:
U = U₀ * DAC/4096
12.1 ШИМ
С первым преобразователем цифрового кода в напряжение, ШИМ, мы уже знакомы. Во время разговора о таймерах, варьируя соотношение ширины импульса и паузы, мы выдавали на ножке нечто, похожее на аналоговый сигнал, которого было достаточно для изменения яркости светодиода или уровня звука на динамике. Но тут есть несколько небольших проблем. Во-первых, сигнал на самом деле все-таки цифровой и, если попытаться посмотреть на него при помощи осциллографа, вы получите сплошной забор передних и задних фронтов от нуля до питания. Это решается легко – добавлением интегрирующей цепочки. Может быть, RC, LC и т.д. в зависимости от того, насколько сильно несущая ШИМ нам мешает. Вторая проблема частично пересекается с первой: скорость изменения сигнала ограничена тактовой частотой таймера, да еще деленной на модуль счета, да еще уменьшенной постоянной времени интегрирующей цепочки. То есть довольно низкая. Зато такой способ управления напряжением, пожалуй, самый дешевый. Таймеров в контроллере много, выходов ШИМ еще больше. А что сигнал импульсный, а не аналоговый – так во многих применениях это только плюс. Как, например, для управления мощной нагрузкой вроде источников питания или контроллеров электродвигателей.
12.2 R-2R ЦАП
Но что, если выходной сигнал должен меняться быстро и плавно? Начнем с простого: получить однобитный «аналоговый» сигнал любой амплитуды мы можем, просто подав цифрвой сигнал с ножки контроллера на усилитель или делитель (чтобы привести его амплитуду к желаемому диапазону). Соберем, скажем, восемь таких делителей, соединим их выходы при помощи диодов (чтобы друг на друга не влияли) и, включая только одну ножку за раз, мы получим 8-уровневый ЦАП. Это будет работать, но тратить целых восемь ножек ради всего-навсего 3 битов как-то расточительно, поэтому упростим схему.
Для начала рассмотрим вот такой резистивный делитель, у которого на обоих входах разные напряжения, но плечи равны. Составим для него схему замещения. Путем несложных манипуляций с законами Ома и Кирхгофа, получаем, что такая схема эквивалентна источнику напряжения с э.д.с. (ε₁+ε₂)/2
и внутренним сопротивлением R/2
. Запись R/2 неудобна, поэтому пусть сопротивления обоих плечей будет 2R. Тогда результирующее внутреннее сопротивление окажется R
. А теперь используем уже полученный источник напряжения в одном из плечей. Поскольку его внутреннее сопротивление равно R, дополнительный резистор равен не 2R, а только R. А во втором плече все как положено, 2R. Схема замещения того, что получилось – источник напряжения с э.д.с. (ε₃ + (ε₂+ε₁)/2 )/2
или ε₃/2 + ε₂/4 + ε₁/4
.
А внутреннее сопротивление, что приятно, осталось равно R
. Продолжая масштабировать эту систему до 8 разрядов, получим ε₈/2 + ε₇/4 + ε₆/8 + ε₅/16 + ε₄/32 + ε₃/64 + ε₂/128 + ε₁/128
. Подозрительно похоже на позиционную запись дробного двоичного числа, особенно если вместо ε₁ подать напряжение нуля, а в качестве остальных использовать цифровые выводы контроллера. Такая реализация ЦАП называется R-2R цепочкой. Выпускаются даже специальные микросхемы, в которой такие резисторы строго согласованы, чтобы выходная зависимость оставалась как можно более линейной. Например, советские К572ПА1. Но мы здесь обсуждаем сами принципы, поэтому можно обойтись, скажем, 4-битным ЦАП на абы каких резисторах, выпаянных в прошлом тысячелетии из неопознанного устройства.
В отличие от ЦАПом из ШИМ, этот выдает уже чисто аналоговый сигнал, причем его быстродействие ограничено только скоростью ножек. Но зато требует точной подгонки резисторов, да и выходное сопротивление довольно значительно. То есть хорошо бы поставить выходной буфер. Который взамен снизит диапазон и скорость.
12.3 Встроенный ЦАП
Что в GD32VF103, что в CH32V303 есть и встроенный 12-битный ЦАП, которым точно так же можно воспользоваться. Сделать это довольно просто (на примере CH32V303):
RCC->APB1PCENR |= RCC_DACEN;
DAC->CTLR |= DAC_EN1;
...
DAC->R8BDHR1 = 128; // 128 / 256
DAC->R12BDHR1 = 2048; // 2048 / 4096
DAC->L12BDHR1 = 32768; // 32768 / 65536
Обратите внимание, что регистр для записи значения там не один, а целых три: R8BDHR1
, R12BDHR1
, L12BDHR1
. Скорее всего, физически регистр там один, а это – псевдонимы, позволяющие обращаться к страшим или младшим битам. Просто для удобства, чтобы иметь возможность писать туда 8-, 12- и 16-битные числа.
Ну и раз уж разработчики решили реализовать аппаратный модуль, было бы странно ограничиваться только цифро-аналоговым преобразованием. Здешний ЦАП может работать в связке с DMA, может самостоятельно и автономно генерировать шумовой сигнал (зачем он может быть нужен, рассмотрим ближе к концу), треугольник. Можно включить выходной буфер, который уменьшит как выходное сопротивление, так и диапазон ЦАП, и его скорость. Много чего можно реализовать.
Вот так выглядит схема, на которой PA0–PA3 отвечают за R-2R ЦАП, PA4 за встроенный ЦАП, и PA6 за ШИМ. Далее на все ЦАПы подавался линейно меняющийся сигнал. И в результате получается вот такая красивая осциллограмма.
Фиолетовая линия – встроенный ЦАП (ступеньки видны, потому что я сделал шаг по 4 единицы именно для того, чтобы их увидеть), голубая – ШИМ с RC-фильтром, зеленая – R-2R цепочка.
13 АЦП
А вот теперь перейдем к преобразованию аналоговых величин в цифровые. Как несложно догадаться, для этого используются аналого-цифровые преобразователи, АЦП, они же analog to digital convertors, ADC. Как и в случае ЦАП, выходной код зависит от входного напряжения, напряжения AVCC и разрядности преобразователя (допустим, он 12-битный):
ADC = U/U₀ * 4096
Но что делать, если в стабильности AVCC мы не уверены? Например, устройство питается от аккумулятора, и тот постепенно разряжается. В такой ситуации хотелось бы, во-первых, контролировать само AVCC чтобы знать, сколько заряда осталось, а во-вторых, как-то его учитывать при обычных измерениях напряжений. Для этого в большинстве контроллеров предусмотрен встроенный источник стабильного напряжения, который может быть подключен к одному из каналов АЦП и измерен относительно AVCC.
Допустим, измерение встроенного источника Uref дало значение ADCref, а измерение внешнего сигнала Ui дало ADCi. Тогда, используя описанную ранее пропорцию, можно найти
U₀ = Uref * 4096 / ADCref
Ui = U₀ * ADCi / 4096
Ui = Uref * ADCi / ADCref
Теперь перйдем к моделированию различных типов АЦП, какие нам позволит CH32V303.
13.1 АЦП прямого преобразования
Начнем с самого простого. Подадим входной сигнал на восемь компараторов, у каждого из которых второй вход будет соединен с опорным напряжением, пропущенным через делитель 1/8, 2/8, 3/8, 4/8, 5/8, 6/8, 7/8 и 8/8. Исходя из того, сколько компараторов переключилось в лог.1, можно сделать вывод о напряжении входного сигнала.
Правда, восемь компараторов обеспечивают всего лишь восемь градаций АЦП, то есть три бита. А чтобы получить хотя бы 8 бит, придется ставить аж 256 компараторов и аналогичное количество резисторов. Точно между собой согласованных!
Ради демонстрации напаивать восемь компараторов я не стал. Вместо этого давайте чуть изменим схему: делить будем не опорный сигнал, а входной, а в качестве порогов срабатывания воспользуемся порогами переключения ножек ввода – вывода. Благо они могут отличаться от партии к партии, но на одном кристалле будут достаточно близкими. Ну, достаточно для нашего показометра, во всяком случае. Некоторых сложностей доставил расчет резисторов делителя. Но, поскольку подобное извращение вряд ли кто-то будет повторять, не вижу смысла его выкладывать.
Здесь желтые квадратики отображают логические уровни на PA0 – PA7, а число справа – рассчитанное по ним напряжение.
Этот тип АЦП является, пожалуй, самым требовательным к элементной базе (количество и качество компонентов), зато обеспечивает огромную скорость. Она ограничена, по сути, только быстродействием компараторов и может достигать гигагерц. Еще бы не приходилось ставить такое количество прецезионных резисторов… Так что разрешение у него обычно небольшое, около 8 бит.
13.2 АЦП последовательного приближения
Хорошо, напрямую преобразовывать аналоговый сигнал в цифровой оказывается слишком затратно. Зато мы умеем преобразовывать цифровой сигнал в аналоговый. Давайте возьмем компаратор, и на один вход подадим измеряемый сигнал, на другой – с ЦАП и будем подбирать его значение, пока сигналы не сравняются. Такой подход называется АЦП последовательного приближения. В качестве ЦАП можно использовать, в общем-то, что угодно: R-2R цепочку, ШИМ. В нашем случае у контроллера есть встроенный ЦАП, им и воспользуемся. Благо его выход, PA5, совпадает с инвертирующим входом OPA2. Остается только на неинвертирующий вход (PA7) подать входной сигнал, а по логическому уровню на выходе (PA2) смотреть какое из напряжений больше.
В простейшем случае можно на каждой итерации проверять, превысило ли входное напряжение выставленное нами значение ЦАП. Если да, уменьшать ЦАП, если нет – увеличивать. Очевидным недостатком такого подхода является долгое измерение: нужно ведь пройти все шаги от предыдущего значения ЦАП до текущего. Зато оно нечувствительно к кратковременным выбросам. И разумеется, оно максимально простое.
В реальности же чаще встречаются более сложные алгоритмы. Например, можно выставить старший бит в 1 и проверить переключился ли выход. Если переключился, то входное напряжение не достигает половины питания, и старший бит нужно сбросить обратно в ноль. Потом переключаем следующий бит и так же проверяем. Таким образом можно оцифровать сигнал примерно за то количество шагов, какое количество бит нам требуется. То есть довольно быстро.
13.3 Аппаратный АЦП
Именно АЦП последовательного приближения чаще всего устанавливают в контроллеры в качестве аппаратного модуля. Что в GD32VF103, что в CH32V303 таких модулей аж две штуки, с разрешением по 12 бит и скоростью до 1 мегасемпла в секунду, то есть довольно быстрые. Вход кажодого АЦП может быть соединен с одной из ножек контроллера при помощи встроенного мультиплексора. Например, 0-й канал мультиплексора соединяет с PA0, 4-й с PA4, 9-й с PB1, а 16-й с встроенным датчиком температуры. Более подробно смотрите в документации.
Режимов работы у встроенного АЦП тоже немало. Самое, пожалуй, полезное – можно указать список каналов для измерения, настроить DMA-буфер и запустить непрерывное преобразование. И АЦП самостоятельно, без всякого участия ядра, измерит все указанные каналы. Причем один канал можно добавить в список несколько раз. То есть измерить, например, список из 6 каналов: ch1, ch2, ch1, ch1, ch3, vref. Обратите внимание на vref. Это внутренний канал АЦП, соединенный с источником опорного напряжения приблизительно 1.2 В. Есть и еще один внутренний канал – датчик температуры. Важно, что их точные значения не гарантируются: технологический разброс никто не отменял. Поэтому если хотите что-то из этого использовать для точных измерений – сначала откалибруйте.
За список каналов отвечают битовые поля SQ1 – SQ16, противоестественным образом разбросанные по регистрам RSQR3 – RSQR1. Мне в свое время пришлось написать специальный макрос, который бы вычислял к какому регистру принадлежит данный канал и по какому смещению расположен.
Как уже было сказано, vref можно использовать для косвенного измерения напряжения питания, поэтому его добавление в список каналов вполне оправдано. Но не забывайте, что его выходное сопротивление довольно велико, поэтому время выборки надо поставить побольше. Да, здешний АЦП позволяет назначить каждому каналу индивидуальное время выборки от 14 до 252 тактов модуля АЦП.
За время выборки отвечают битовые поля SMP0 – SMP17, не менее неудобно разбросанные по SAMPTR2 – SAMPTR1. Причем учтите, что время выборки задается именно для канала АЦП, грубо говоря, для конкретной ножки, а не для элемента списка измерения. Это логично: к каждому каналу подключена вполне конкретная периферия со вполне конкретным выходным сопротивлением, и именно им ограничивается время выборки. В частности, для датчика температуры не рекомендуется задавать время меньше 17.1 мкс.
Тактирование АЦП осуществляется через отдельный делитель и не должно превышать 14 МГц. Вот только коэффициентов деления там слишком мало, 2, 4, 6 и 8, то есть частота шины APB2 не должна превышать 112 МГц. Проще всего выставить для обеих шин APB1 и APB2 делитель 1:2.
А если используется I2C, то еще хуже, ему на вход нельзя подавать больше 60 МГц. Но при такой частоте уже не получить максимальной скорости от таймеров (а при 1:2 – еще получить, там для этого специальный делитель предусмотрен). В общем, снова придется искать компромисс.
Внимание, грабли: в CH32V103 (и, кажется, только в нем) включать vref одновременно с обычными каналами нельзя. Из-за какого-то бага в кристалле, при включенном vref во время измерения обычных каналов вместо правдоподобных значений считывается какая-то ерунда. И более того, при этом возникает огромная утечка с линии питания. То есть, если нет уверенности в стабильности AVCC, придется включить vref, измерить его значение, выключить vref, включить список нужных каналов, измерить их, выключить все каналы, снова включить vref и так далее. Знаю, что костыльно, но что поделать.
Еще одна интересная особенность встроенного АЦП: оно может начинать преобразование не только по программному сигналу и не только после завершения предыдущего, но и по событиям от другой периферии. В основном, конечно, от таймеров. Это может быть полезно, если измерения должны быть со строго заданными интервалами.
Также у CH32V303 (и некоторых других) есть возможность подключить ко входу встроенный усилитель от х1 до х64. Вероятно, разработчики посчитали, что использовать встроенные операционные усилители не всегда удобно. Внешние — тем более. А необходимость измерения слабых сигналов время от времени все же возникает. Ну что сказать, полезная фича. В принципе, я бы про нее не упоминал, но, как мы помним, значительная часть периферии CH32 скопирована с STM32, и наличие усилителя – исключение. Полагаясь на опыт работы с STM32, про него можно просто не знать.
13.4 АЦП однократного интегрирования
Описанные варианты АЦП хороши и обеспечивают хорошую точность, но требовательны к точности компонентов, да и их количеству. Но ведь нередки и задачи, когда ни к точности, ни к скорости особых требований не предъявляется и важна только простота. Скажем, оценить угол наклона джойстика при помощи контроллера, вообще не оборудованного АЦП (или хотя бы если заняты все ножки).
Как мы знаем, время заряда конденсатора до заданного напряжения зависит от его емкости, внутреннего сопротивления источника напряжения и его э.д.с. В данном случае нас интересует последнее. Возьмем резистор известного номинала, конденсатор известного номинала и будем заряжать их от измеряемого напряжения. А порогом срабатывания будет напряжение переключения ножки контроллера. По-хорошему, зависимость времени заряда не линейная, а экспоненциальная (то есть надо поставить интегратор на ОУ или пересчитывать вручную). Порог переключения ножки расположен вблизи половины питания, то есть малые напряжения так не измерить. Да и вообще, точность такого АЦП зависит от всего подряд: номинала конденсатора и резистора (с учетом дрейфа по времени и температуре!), от порога переключения выводов, от точности тактовой частоты (а она тоже дрейфует).
В общем, проще сказать, что точности мы не увидим. Поэтому давайте делать не АЦП «общего назначения», а специализированный «показометр» для определения положения ручки потенциометра.
И вот здесь такая схема вполне жизнеспособна. Порог переключения зависит от напряжения питания – скорость заряда тоже. Зависимость времени заряда от напряжения экспоненциальная – зато от сопротивления линейная. Разве что время заряда как зависело от точности тактирования, так и зависит. Но с этим придется смириться.
13.5 АЦП двойного интегрирования
Тем не менее, сам принцип преобразования входного напряжения во время оказался довольно удачным. Интервал времени измерить довольно просто. Осталось разобраться с недостатками простейшей схемы. Естественно, придется добавить операционные усилители, о которых говорили раньше. Во-первых, время заряда должно зависеть от входного напряжения линейно – придется собрать на одном из операционных усилителей интегратор. Правда, он получился инвертирующим и требующим наличия виртуальной земли. Но это не проблема: арифметические операции контроллер проводить умеет, и ЦАП в нем есть – выставим половину питания, 1.7 В, оно и будет виртуальной землей. Чуть хуже, что из-за наличия виртуальной земли придется сместить диапазон входного напряжения от 0...3.3 В до 1.7...3.3 В. Придется задействовать второй операционный усилитель в роли буфера и дополнительно поставить резистивный делитель. Подав на PA1 лог.1 как раз и получим подтяжку к питанию.
А вот теперь перейдем к алгоритму аналого-цифрового преобразования. На первом шаге мы, как и раньше, подаем на вход опорный сигнал. Но теперь зарядка идет фиксированное время. А потом входной сигнал отключаем, подключаем вместо него минус опорное напряжение и измеряем за какое время конденсатор зарядится обратно до начального уровня. И вот тут нам пригодится входной буфер, который изначально казался всего лишь усложнением схемы. Дело в том, что его можно отключить программно и использовать как обычную цифровую ножку. На PA2 и PA1 подаем лог.0, для интегратора это соответствует входному напряжению -1.7 В, и он начинает разряжать конденсатор. Только не забывайте, что интегратор у нас инвертирующий. То есть чем больше конденсатор заряжен, тем меньше напряжение на PA3 и наоборот.
Здесь на рисунке область Inp – время заряда конденсатора входным напряжением, Ref – разряд «опорным» напряжением. Можно увидеть, что наклон при заряде в левой и правой части различный, при том что время одинаковое. А при разряде необорот: наклон одинаковый, а время различное.
А теперь, внимание, магия. Время заряда конденсатора зависит от его емкости и сопротивления резистора. Но от них же зависит и время разряда, то есть необходимости их подбирать нет. Время заряда задается с точностью тактового генератора контроллера – но от него же измеряется и время разряда, то есть и тут особых требований к точности нет. Пороговое напряжение на конденсаторе может плавать от времени и температуры – но оно одно и то же что при заряде, что при разряде. В общем, схема АЦП двойного интегрирования позволяет обойти практически все неидеальности реальных компонентов!
13.6 Сигма-дельта АЦП
Рассмотрим еще одну интересную схему. Здесь входное напряжение складывается с напряжением на цифровом выходе (схематично обозначен ключом, коммутируемым на землю или питание) и подается на вход интегратора на ОУ. Неинвертирующий вход ОУ соединен с напряжением половины питания, назовем его опорным напряжением U₀.
Поскольку в операционном усилителе с правильно настроенной отрицательной обратной связью напряжения на обоих входах равны, несложно рассчитать все токи в схеме. Ток, текущий от входного напряжения, равен Iin = (Uin-U₀)/R = ΔUin/R
. Ток, текущий от цифрового входа, равен Id = (Ud-U₀)/R = ΔUd/R
, где Ud – напряжение на цифровом выходе, равное 0 или 2U₀. Соответственно, ΔUd равно ±U₀. Ток через конденсатор равен их сумме. За время интегрирования t этот ток зарядит конденсатор до ΔU = (Iin + Id)t/C = (ΔUin + ΔUd)t / RC
. Но поскольку напряжение на цифровом выходе может принимать только два значения, разделим общий интервал интегрирования на две части: t₁, когда на выходе лог.0, и t₂, когда лог.1. Тогда ΔU = (ΔUin * t / RC) - (U₀ * t₁ / RC) + (U₀ * t₂ / RC) = ΔUin(t₂+t₁)/RC + U₀(t₂-t₁)/RC
. Задача цифровой части АЦП заключается в том, чтобы поддерживать напряжение на выходе ОУ вблизи U₀. То есть ΔU = 0
. Поскольку нас интересует абсолютное напряжение на входе, выразим его через опорное напрежение и время:
ADC = (ΔUin + U₀)/2U₀ = t₁/t
И вот тут мы подходим к алгоритму работы сигма-дельта АЦП. Все время измерения t делится на некое количество тактов, допустим, N. На каждом такте контроллер проверяет, достигло значение на выходе ОУ половины питания, или нет, и в зависимости от этого подает на выход лог.0 или лог.1. И считает сколько раз пришлось подавать тот или иной уровень – допустим, насчиталось n раз, когда подавался лог.0. Это значит, что t₁ из предыдущей формулы равен t * n/N
. Соответственно, значение АЦП равно n/N
.
Код такого алгоритма может выглядеть примерно так:
#define OPAMP_IN A,2,1,GPIO_HIZ // Вход с интегратора
#define OPAMP_OUT A,6,1,GPIO_PP50 // Цифровой выход
#define ADC_t0_us 500 // время одного такта АЦП. Поскольку постоянная времени довольно велика, можно и время интегрирования сделать большим. Так проще строить осциллограмму.
uint16_t sd_adc_val = 0; // Результирующее значение
void sd_adc_poll(){
// Проверка что с прошлого выполнения прошло достаточно времени
static uint32_t t_av = 0;
uint32_t t_cur = systick_read32();
if( (t_cur - t_av) & (1<<31) )return;
t_av = t_cur + (ADC_t0_us * (144000000 / 1000000));
// Логика работы сигма-дельта АЦП
static uint16_t code = 0;
static uint16_t cnt = 0;
if( GPI_OFF( OPAMP_IN ) ){ // напряжение на выходе ОУ ниже порога -> подаем на цифровой выход лог.0
GPO_OFF(OPAMP_OUT);
code++;
}else{ //напряжение на выходе ОУ выше порога -> подаем на цифровой выход лог.1
GPO_ON(OPAMP_OUT);
}
// Счет количества прошедших тактов
cnt++;
if(cnt >= 256){
sd_adc_val = code;
code = 0;
cnt = 0;
}
}
Голубая пилообразная линия – напряжение на выходе интегратора, желтая – напряжение на цифровом выходе, фиолетовая – рассчитанное напряжение, зеленая – входное напряжение, измеренное встроенным АЦП.
Здесь, в отличие от АЦП с двойным интегрированием, время интегрирования не ограничено достижением какого-либо из питаний интегратором. Напряжение на его выходе всегда колеблется вблизи заданной точки. Это значит, что количество тактов интегрирования можно выбирать произвольно. Чем больше начситаем, тем больше точность, но тем ниже скорость. Абсолютная длительность одного такта также не играет никакой роли, главное чтобы не слишком дрейфовала за время измерения. Порог срабатывания компаратора также безразличен: нас ведь интересует всего лишь удержание напряжения на выходе интегратора на одном уровне, а не величина этого уровня. Таким образом, как и предыдущий тип АЦП, сигма-дельта не слишком требователен к подбору компонентов, но при этом позволяет получить весьма высокую точность.
13.7 Повышение точности АЦП: oversampling (передискретизация)
Давайте вспомним один из народных способов измерения диаметра обмоточного провода при помощи обычной линейки. Провод наматывается виток к витку на какой-нибудь карандаш, потом длина подобной катушки измеряется и делится на количество витков. Этот способ позволяет измерить диаметр довольно точно даже в отсутствие подходящего измерительного прибора – микрометра или хотя бы штангенциркуля.
Подобный подход применим и к АЦП. Можно провести, скажем, десять измерений и вычислить среднее. Или еще лучше: провести 16 измерений и сразу получить 16-битное число. Казалось бы, если входной сигнал не меняется, мы должны получить 16 абсолютно одинаковых значений, и от усреднения не будет никакого толка. Но в реальности к измеряемому сигналу всегда примешиваются разнообразные шумы. Как от находящейся поблизости электрики, так и внутренние шумы самого контроллера. А если вдруг таких шумов окажется недостаточно, у нас есть ЦАП, который тоже умеет их генерировать.
Если верить вот этой статье, добавление каждого бита точности преобразования требует увеличения числа выборок в четыре раза. А это, как несложно догадаться, снижает скорость АЦП. Иначе говоря, количество значащих битов растет с увеличением количества отсчетов всего лишь логарифмически.
Что забавно, такой подход можно применить даже к однобитному АЦП, основанному на напряжении переключения входа. Правда, придется здорово повозиться с источником шумового сигнала. Очевидно, его должно быть достаточно чтобы «дотянуть» входной сигнал до порога переключения, но при этом не вывести за пределы допустимого входного напряжения. Например, для 4-битного АЦП максимальная амплитуда шума должна быть от 1/16 до 1/2 от опорного напряжения. Для 12-битного, как в нашем контроллере, от 1/4096 до 1/2. А для однобитного – строго 1/2. Понятно, почему на практике такое практически не применяется.
Заключение
Разумеется, я рассказал сейчас только об отдельных примерах преобразователей между аналоговым и цифровым сигналами, не затронул тему фильтрации, ограничений и способов их обхода. Но, надеюсь, хотя бы познакомил с основами.
Glaid
Так и не понял, в чëм смысл генерирования шумов. В рандомных числах? Или я просто не уловил шутку последнего пункта.