Общий принцип работы ацп. AVR


Отличительные особенности:

  • Рассмотрены характеристики аналогово-цифровых преобразователей
  • Измерение описанных характеристик АЦП
  • Влияние температуры, частоты и напряжения питания на результат преобразования
  • Компенсация погрешностей смещения и коэффициента передачи

Введение

В данных "Рекомендациях…" объясняется снятие характеристик различных АЦП, приведенных в документации, и как они влияют на результат измерений АЦП. Также описывается, как определить данные параметры в процессе тестирования приложения на стадии производства и как выполнить реально-временную компенсацию некоторых измеренных отклонений.

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

1. Сведения из теории

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

1.1. Основные характеристики АЦП

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

Рисунок 1. Передаточная функция идеального АЦП

Чтобы дать определение совершенного АЦП необходимо предварительно рассмотреть понятие квантование. В связи с тем, что АЦП имеет цифровую основу, то генерация им непрерывных значений невозможна. Выходной диапазон может быть представлен в виде множества интервалов, каждому из которых соответствует собственное цифровое значение. Это означает, что одно выходное значение соответствует не конкретному уровню входного напряжения, а небольшому диапазону входных значений. Передаточная функция такого преобразования имеет лестничную форму. Например, АЦП с 8 интервалами имеет разрешающую способность 8 уровней или иными словами 3 разряда. На рисунке 2 представлен пример передаточной функции 3-хразрядного совершенного АЦП вместе с передаточной функций идеального АЦП. Как следует из рисунка совершенный АЦП эквивалентен идеальному точно посредине каждого интервала квантования. Это означает, что совершенный АЦП по существу округляет входные значения к ближайшему выходному значению.


Рисунок 2. Передаточная функция 3-разрядного совершенного АЦП

Максимальная погрешность совершенного АЦП составляет ±1/2 интервала дискретизации. Иными словами, максимальная погрешность квантования всегда ±1/2 мл.разр., где мл. разр. - приращение входного напряжения, при котором изменяется значение младшего разряда выходного кода. Реальный АЦП характеризуется другими источниками погрешностей, которые будут рассмотрены далее.

1.2. Диапазоны преобразования

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

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


Рисунок 3. Упрощенная схема входного каскада АЦП

Для задания диапазона преобразования в схеме необходим источник опорного напряжения (Vион), который задает, какому уровню входного напряжения соответствует выходное значение. В соответствии с документацией напряжение Vион должно быть не менее 2,0В для стандартных микроконтроллеров и не менее 1,0В для микроконтроллеров с напряжением питания от 1,8В. Данное распространяется на оба режима преобразования: несимметричный и дифференциальный. Подробности необходимо выяснить в документации.

1.2.1. Несимметричный диапазон преобразования

В несимметричном режиме входной сигнал поступает непосредственно к схеме преобразования (см. рисунок 3а). 10-разрядный АЦП микроконтроллера AVR, таким образом, преобразовывает непрерывные входные напряжения в диапазоне от GND до Vион в дискретные выходные значения от 0 до 1023, соответственно.

1.2.2. Дифференциальный диапазон преобразования

В дифференциальном режиме преобразования два входных канала подключаются к дифференциальному усилителю с опциональным усилительным каскадом. Затем напряжение с выхода усилителя поступает к логике преобразования, как показано на рисунке 3б. В этом случае разности напряжений в диапазоне от -Vион до +Vион соответствуют выходные значения в диапазоне от -512 до +511. Выходное значение представляется в формате двоичного дополнения. Несмотря на возможность образования отрицательного напряжения на выходе дифференциального усилителя входные напряжения должны быть в диапазоне GND…AVCC.

Обратите внимание, что некоторые микроконтроллеры не могут измерить отрицательного приращения, как, например, ATtiny26.

1.3. Необходимость калибровки

Общая погрешность реального АЦП складывается не только из погрешности квантования. В данном документе рассматриваются погрешности смещения и коэффициента передачи и методы их компенсации. Кроме того, рассматривается измерение двух нелинейностей, а именно дифференциальной и интегральной нелинейности.

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

Однако, при использовании дифференциального преобразования ситуация меняется, особенно при использовании внутреннего усилительного каскада с большим усилением. Незначительные отклонения, вызванные особенностями производства микроконтроллеров, умножаются усилительным каскадом и, поэтому, у разных микроконтроллеров могут наблюдаться существенные отличия в результате измерения при прочих равных условиях. Некомпенсированная погрешность может достигать 20 мл. разр. и выше. Данные отклонения могут быть определены для каждого микроконтроллера, а затем компенсированы программно.

Значение 20 мл. разр. на первый взгляд может показаться большим значением, но это не означает, что дифференциальный режим непрактичен в использовании. С помощью простого калибровочного алгоритма возможно достичь точность 1-2 мл.разр.

1.4. Абсолютная погрешность

Абсолютная погрешность - максимальное отклонение между идеальной прямолинейной и реальной передаточными функциями, в т.ч. внутри интервалов квантования. Минимальная абсолютная погрешность, таким образом, равна погрешности квантования 1/2 мл. разр.

Абсолютная погрешность или абсолютная точность - общая некомпенсированная погрешность, которая включает погрешность квантования, погрешность смещения, погрешность коэффициента передачи и нелинейность. Смещение, коэффициент передачи и нелинейность будут описаны далее.

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

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

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

1.5. Погрешность смещения

Погрешность смещения - отклонение фактической передаточной функции АЦП от прямолинейной передаточной функции идеального АЦП при нулевом входном напряжении.

Когда выходное значение изменяется от 0 к 1, но при этом входное напряжение не достигло уровня 1/2 мл.разр., то говорят, что имеет место погрешность смещения. Если ошибка смещения положительная, то выходное значение будет больше 0, когда входное напряжение приближается к 1/2 мл.разр. снизу. Если ошибка смещения отрицательная, то входное значение будет больше 1/2 мл.разр. при первом изменении выходного кода. Другими словами, если фактическая передаточная функция становится ниже идеальной линии, то погрешность смещения отрицательная и наоборот. Отрицательные и положительные смещения показаны на рисунке 4.


Рисунок 4. Примеры положительного (а) и отрицательного (б) смещений

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

1.5.1. Погрешность смещения в несимметричных каналах

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

На рисунке 5а первый переход возникает при уровне 1 мл.разр. При изменении выходного кода с 2 к 3 у совершенного АЦП эквивалентное входное напряжение будет равно 2 1/2 мл. разр. Разница равна +1 1/2 мл. разр. и является погрешностью смещения. Данная разница показана на рисунке размерной линией. Такие же рассуждения применимы и к рисунку 5б. Первое изменение возникает при 2 мл.разр. У совершенного АЦП переход от 0 к 1 возникает при входном напряжении 1/2 мл.разр. Таким образом, погрешность смещения равна разнице: - 1 1/2 мл. разр.


Рисунок 5. Положительная (а) и отрицательная (б) погрешности смещения в режиме несимметричного преобразования

Процедура измерения может быть формализована в виде блок-схемы (см. рисунок 6).


Рисунок 6. Блок-схема измерения несимметричных погрешностей смещения

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

1.5.2. Погрешность смещения в дифференциальных каналах

Погрешность смещения в дифференциальных каналах вычисляется более просто, т.к. в этом случае не требуется регулировка входного напряжения. Два дифференциальных входа необходимо подключить к одному и тому же напряжению, а результирующее выходное значение и будет погрешностью смещения. Поскольку при данном способе не дается точная информация при каком именно уровне возник первый переход, то его погрешность равно от 1/2 до 1 мл.разр. в худшем случае.

Для компенсации погрешностей смещения при использовании дифференциальных каналов необходимо из каждого измеренного значения вычесть погрешность смещения.

1.6. Передаточная погрешность

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

При реально-временной компенсации часто используется целочисленная арифметика, т.к. вычисления с плавающей точкой выполняются гораздо дольше. Таким образом, для достижения наилучшей точности измерения отклонения наклона оно должно быть выполнено как можно далее от нулевого значения. Чем выше значения, тем лучше точность измерения. Это более подробно описано далее. Пример передаточной функции 3-разрядного АЦП с передаточной погрешностью показан на рисунке 7. Приведенное далее описание распространяется на оба режима преобразования: несимметричный и дифференциальный.


Рисунок 7. Примеры положительной (а) и отрицательной (б) передаточных погрешностей

Для измерения передаточной погрешности необходимо увеличивать входное напряжение от 0 до достижения последнего интервала преобразования. Масштабирующий коэффициент для компенсации передаточной погрешности равен отношению идеального выходного значения посредине последнего интервала дискретизации и фактического значения в этой же точке.

На рисунке 7а выходное значение достигло предела еще до достижения максимума входным напряжением. Вертикальная размерная линия показывает середину последнего выходного интервала дискретизации. Идеальное выходное значение для данного входного напряжения равно 5,5, таким образом, масштабирующий коэффициент равен 5,5/7. На рисунке 7б выходное значение достигло только 6 при достижении входным напряжением максимума. В итоге присутствует отрицательное отклонение от фактической передаточной функции. Для этого случая идеальное выходное значение посередине последнего интервала преобразования равно 7,5, а масштабирующий коэффициент 7,5/6. Процедура измерения представлена на рисунке 8.


Рисунок 8. Блок-схема измерения передаточных погрешностей

1.7. Нелинейность

После компенсации погрешности смещения и передаточной погрешности фактическая передаточная функция должна совпадать с передаточной функцией совершенного АЦП. Однако ввиду нелинейности АЦП фактическая кривая может слегка отклоняться от совершенной кривой, даже если обе кривые совпадают в районе 0 и в точке измерения передаточной погрешности. Имеется два способа измерения нелинейности; оба метода описаны ниже. На рисунке 9 показаны примеры для обоих методов измерения.


Рисунок 9. Пример нелинейной кривой преобразования АЦП

1.7.1. Дифференциальная нелинейность

Дифференциальная нелинейность (ДНЛ) - максимальное и минимальное отклонения фактической ширины интервала от ширины интервала совершенного АЦП (1 мл. разр.) для всех интервалов дискретизации. Нелинейность приводит к варьированию размеров интервалов дискретизации. Все интервалы должны иметь ширину 1 мл. разр., но некоторые уже или шире.

Для измерения ДНЛ на вход подается пилообразное напряжение и записываются все изменения выходных значений. Ширина интервала определяется как расстояние между двумя переходами и большинство отрицательных и положительных отклонений от 1 мл.разр. используются для определения максимальной и минимальной ДНЛ.

Интегральная нелинейность

Интегральная нелинейность (ИНЛ) - максимальное отклонение по вертикали между фактической и совершенной кривыми преобразования АЦП.

ИНЛ можно интерпретировать как сумму ДНЛ. Например, несколько последовательных отрицательных ДНЛ поднимают фактическую кривую над совершенной, как показано на рисунке 9а. Отрицательные ИНЛ сигнализируют о снижении фактической кривой ниже совершенной. Максимальная и минимальная ИНЛ измеряются с помощью того же пилообразного входного напряжения, что и при измерении ДНЛ. Для этого записываются отклонения посередине каждого интервала преобразования, а затем определяются максимальное и минимальное значения, соответствующие максимальной и минимальной ИНЛ.

Измерения и компенсация

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

Нелинейность не может быть компенсирована с помощью простых вычислений. Для этого необходима либо полиноминальная аппроксимация, либо таблицы преобразования. Однако типичные значения ИНЛ и ДНЛ для 10-разрядных АЦП микроконтроллеров AVR составляют 1/2 мл. разр. и редко влияют на жизнеспособность приложений.

1.8. Влияние температуры, частоты и напряжения питания

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

Точность работы АЦП также связана с его синхронизацией. Рекомендованная максимальная частота синхронизации АЦП ограничивается характеристиками внутреннего ЦАП в схеме преобразования. Для достижения оптимальных характеристик частота синхронизации АЦП не должна превышать 200 кГц. Однако частоты до 1 МГц не приводят к существенному ухудшению разрешающей способности.

Характеристики работы АЦП с частотами синхронизации выше 1МГц не определялись.

1.9. Частотный диапазон и входное сопротивление

В несимметричном режиме работы АЦП частотный диапазон ограничивается частотой синхронизации АЦП. Одно преобразование длиться 13 тактов, поэтому, при максимальной тактовой частоте 1 МГц достигается частота преобразования 77 тысяч преобразований в секунду. Таким образом, в соответствии с теоремой Котельникова частотный диапазон для несимметричного режима преобразования ограничивается частотой 38,5 кГц.

В дифференциальном режиме частотный диапазон ограничивается частотой 4 кГц за счет дифференциального усилителя. Частотные составляющие выше частоты 4 кГц должны быть удалены с помощью внешнего аналогового фильтра, что позволить избежать нелинейностей.

Входное сопротивление по отношению к VCC и GND составляет 100 МОм (типичное значение). Совместно с внутренним сопротивлением источника сигнала образуется делитель напряжения. Таким образом, для получения корректного результата преобразования необходимо, чтобы внутреннее сопротивление источника сигнала было намного меньше входного сопротивления АЦП.

2. Реализация

На рисунке 10 показан пример установки для выполнения калибровки.


Рисунок 10. Установка для калибровки в производственных условиях

На этапе тестирования выполняется определение характеристик АЦП каждого микроконтроллера с помощью подобной приведенной испытательной установки. После подключения тестового блока к калибруемому микроконтроллеру AVR его тестовые сигналы выполняют самокалибровку автоматически. В состав тестового блока входит высокоточный ЦАП (например, с 16-разрядным разрешением) для генерации входных напряжений в соответствии с калибровочным алгоритмом. По завершении калибровки определенные значения погрешности смещения и передаточной погрешности записываются в ЭСППЗУ для дальнейшего использования, а затем AVR сигнализирует о готовности к следующей фазе тестирования.

Обратите внимание, что в данном случае требуется, чтобы бит EESAVE был запрограммирован. В этом случае выполнение операции стирания всей памяти, которая предшествует программированию флэш-памяти, не затрагивает содержимое ЭСППЗУ. В противном случае, параметры АЦП должны быть временно запомнены программатором перед стиранием памяти микроконтроллера.

2.1. Арифметика с фиксированной точкой для коррекции погрешности смещения и передаточной погрешности

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

Поскольку коэффициент компенсации передаточной погрешности никогда не превысит значения 2, то можно отмасштабировать с коэффициентом 2 14 , чтобы точно вписаться в 16-разрядное слово. Иными словами, масштабирующий коэффициент может быть представлен двумя байтами как число с фиксированной точкой и знаком 1:14.

Ниже приведено выражение для одновременной компенсации передаточной погрешности и погрешности смещения.

Фактическое_значение = (Код_АЦП - Смещение) · Км, (1)

где Км- масштабирующий коэффициент передаточной погрешности.

При преобразовании результата вычисления к целочисленной форме он всегда округляется к наибольшему целочисленному значению, которое меньше или равно результату. Чтобы добиться корректного округления к ближайшему целому перед преобразованием необходимо добавить 0.5. Прибавление 0.5, масштабирование на 214 и смещение представим в виде выражения (2).

2 14 · Фактическое_значение = 2 14 · Код_АЦП · Км + 2 14 · 0,5 - 2 14 · Смещение · Км (2)

Поскольку значения коэффициента масштабирования передаточной погрешности и смещения являются константами, то вычисления можно оптимизировать. Кроме того, если результат отмасштабировать на 2 2 , т.е. достигая общего масштабирования 2 16 , то старших два байта результата будут равны преобразованному целому, исключая необходимость выполнения 16 сдвигов вправо.

Введя две отмасштабированные константы factor и correction, которые используются в программе, получаем итоговые выражения:

factor = 2 14 · Км,

correction = 2 14 · (0,5 - Смещение · Км), (3)

2 16 · Фактическое_значение = 2 2 · (Код_АЦП · factor + correction).

С помощью данного метода калибровочная программа вычисляет константы factor и correction, а затем сохраняет их в ЭСППЗУ. Время выполнения программы компенсации составляют одно целочисленное умножение, одно сложение и два сдвига влево. При использовании компилятора Си компании IAR C с максимальной оптимизацией быстродействия на эти действия потребуется 42 такта ЦПУ.

2.1.1. Калибровка

Разработка тестового блока не рассматривается в рамках данных "Рекомендаций…". Однако блок-схема калибровки с помощью микроконтроллера AVR приведена. В ней подразумевается использование в тестовом блоке внешнего ЦАП и работа по собственному калибровочному алгоритму.

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

Программа должна быть реализована, как показано на рисунке 11.

Рисунок 11. Блок-схема калибровочной программы

Данная часть программного обеспечения записывается в AVR до начала калибровки, а по ее завершении заменяется программным кодом фактического приложения. Еще раз необходимо обратить внимание, что программирование конфигурационного бита EESAVE позволит заблокировать действие команды стирания всей памяти относительно ЭСППЗУ во время перепрограммирования флэш-памяти и, таким образом, калибровочные данные будут незатронутыми.

2.1.2. Компенсация

Программный код реально-временной компенсации реализован как небольшая функция. Каждый результат измерения АЦП пропускается через эту функцию, в который используются константы factor и correction .

Рисунок 12. Блок-схема программы компенсации погрешности смещения и передаточной погрешности

Вычисления на рисунке 12 могут быть реализованы с помощью следующей Си-функции или альтернативно с помощью макроса:

Signed int adc_compensate(signed int adcvalue, signed int factor, signed long correction) { return (((((signed long)adcvalue*factor)+correction)<<2)>>16); }

Константы хранятся в ЭСППЗУ и перед началом работы должны быть скопированы в ОЗУ для ускорения доступа к ним.

Использованная литература:

  1. Robert Gordon - A Calculated Look at Fixed-Point Arithmetic (Прагматичный взгляд на арифметику с фиксированной точкой)
    http://www.embedded.com/98/9804fe2.htm
  2. Рекомендации по применению AVR210: Использование аппаратного умножающего устройства микроконтроллеров AVR

АЦП – аналогово-цифровой преобразователь (ADC- Analog-to-Digital Converter). Преобразует некий аналоговый сигнал в цифровой. Битность АЦП определяет точность преобразования сигнала. Время преобразования – соответственно скорость работы АЦП. АЦП встроен во многих микроконтроллерах семейства AVR и упрощает использование микроконтроллера во всяких схемах регулирования, где требуется оцифровывать некий аналоговый сигнал.

Рассмотрим принцип работы АЦП . Для преобразования нужен источник опорного напряжения и собственно напряжение, которое мы хотим оцифровать (напряжение, которое преобразуется должно быть меньше опорного). Также нужен регистр, где будет храниться преобразованное значение, назовем его Z . Входное напряжение = Опорное напряжение*Z/2^N, где N – битность АЦП . Условимся, что этот регистр, как у ATmega8, 10-ти битный. Преобразование в нашем случае проходит в 10 стадий. Старший бит Z9 выставляется в единицу. Далее генерируется напряжение (Опорное напряжение*Z/1024) , это напряжение, с помощью аналогового компаратора сравнивается с входным, если оно больше входного, бит Z9 становиться равным нулю, а если меньше – остается единицей. Далее переходим к биту Z8 и вышеописанным способом получаем его значения. После того, как вычисление регистра Z окончено, выставляется некий флаг, который сигнализирует, что преобразование закончено и можно считывать полученное значение. На точность преобразования могут очень сильно влиять наводки и помехи, а также скорость преобразования. Чем медленнее происходит преобразования – тем оно точней. С наводками и помехами следует бороться с помощью индуктивности и емкости, как советует производитель в даташите:

В микроконтроллерах AVR как источник опорного напряжения может использоваться вывод AREF , или внутренние источники 2,56В или 1,23В. Также источником опорного напряжения может быть напряжение питания. В некоторых корпусах и моделях микроконтроллеров есть отдельные выводы для питания АЦП: AVCC и AGND . Выводы ADCn – каналы АЦП . С какого канала будет оцифровываться сигнал можно выбрать с помощью мультиплексора.
Теперь продемонстрируем примером сказанное выше. Соорудим макет, который будет работать как вольтметр с цифровой шкалой. Условимся, что максимальное измеряемое напряжение будет 10В. Также пусть наш макет выводит на ЖКИ содержимое регистра ADC .

Для увеличения кликните на схему.

Обвязка микроконтроллера и ЖКИ WH1602A стандартна. X1 – кварцевый резонатор на 4 Мгц, конденсаторы С1,С2 – 18-20 пФ. R1-C7 цепочка на выводе reset по 10 кОм и 0,1 мкФ соответственно. Сигнальный светодиод D1 и ограничивающий резистор R2 200 Ом и R3 – 20 Ом. Регулировка контраста ЖКИ – VR1 на 10 кОм. Источник опорного напряжения мы будем использовать встроенный на 2,56В. С помощью делителя R4-R5 мы добьемся максимального напряжения 2,5В на входе PC0 , при напряжении на щупе 10В. R4 – 3 кОм, R5 – 1 кОм, в их номиналу нужно отнестись тщательно, но если не возможности подобрать точно такие, можно сделать любой резистивный делитель 1:4 и программно подкорректировать показания, если это потребуется. Дроссель на 10мкГн и конденсатор на 0,1 мкФ для устранения шумов и наводок на АЦП на схеме не показан. Их наличие подразумевается само собой, если используется АЦП . Теперь дело за программой:

{codecitation style="brush: xml;"} #include

#define RS 2 //RS=PD2
#define E 3 //E=PD3

#define TIME 10 //Константа временной задержки для ЖКИ
//Частота тактирование МК - 4Мгц

#define R_division 3.837524 //=R4/R5 константа

Unsigned int u=0; //Глобальная переменная с содержимым преобразования

Void pause (unsigned int a)
{
unsigned int i;
for (i=a;i>0;i--);
}

Void lcd_com (unsigned char lcd) //Передача команды ЖКИ
{
unsigned char temp;

Temp=(lcd&~(1< PORTD=temp; //Выводим на portD старшую тетраду команды, сигналы RS, E
PORTD=temp&~(1<
temp=((lcd*16)&~(1< PORTD=temp; //Выводим на portD младшую тетраду команды, сигналы RS, E
asm("nop"); //Небольшая задержка в 1 такт МК, для стабилизации
PORTD=temp&~(1<
pause(10*TIME); //Пауза для выполнения команды
}

Void lcd_dat (unsigned char lcd) //Запись данных в ЖКИ
{
unsigned char temp;

Temp=(lcd|(1< PORTD=temp; //Выводим на portD старшую тетраду данных, сигналы RS, E
asm("nop"); //Небольшая задержка в 1 такт МК, для стабилизации
PORTD=temp&~(1<
temp=((lcd*16)|(1< PORTD=temp; //Выводим на portD младшую тетраду данных, сигналы RS, E
asm("nop"); //Небольшая задержка в 1 такт МК, для стабилизации
PORTD=temp&~(1<
pause(TIME); //Пауза для вывода данных
}

Void lcd_init (void) //Иниализация ЖКИ
{
lcd_com(0x2c); //4-проводный интерфейс, 5x8 размер символа
pause(100*TIME);
lcd_com(0x0c); //Показать изображение, курсор не показывать
pause(100*TIME);
lcd_com(0x01); //Очистить DDRAM и установить курсор на 0x00
pause (100*TIME);
}

Unsigned int getADC(void) //Считывание АЦП
{ unsigned int v;

ADCSRA|=(1<
while ((ADCSRA&_BV(ADIF))==0x00) //Дождатся окончания преобразования
;

V=(ADCL|ADCH<<8); br=""> return v;
}

Void write_data (unsigned int u)
{ unsigned char i;
double voltage=0;

Lcd_com(0x84); //Вывод регистра ADC на ЖКИ
for (i=0;i<10;i++) br=""> if ((u&_BV(9-i))==0x00) lcd_dat (0x30);
else lcd_dat (0x31);

Lcd_com(0xc2);
voltage= R_division*2.56*u*1.024; //Расчет напряжения

I=voltage/10000; //Выведение напряжения на ЖКИ
voltage=voltage-i*10000;
if (i!=0) lcd_dat(0x30+i);

I=voltage/1000;
voltage=voltage-i*1000;
lcd_dat(0x30+i);

I=voltage/100;
voltage=voltage-i*100;
lcd_dat(0x30+i);

I=voltage/10;
voltage=voltage-i*10;
lcd_dat(0x30+i);

Lcd_dat("v");
}

Int main(void)
{
DDRD=0xfc;

Pause(3000); //Задержка для включения ЖКИ
lcd_init(); //Инициализация ЖКИ

Lcd_dat("A"); //Пишем "ADC=" и "U=" на ЖКИ
lcd_dat("D");
lcd_dat("C");
lcd_dat("=");
lcd_com(0xc0);
lcd_dat("U");
lcd_dat("=");

ADCSRA=(1< //Включаем АЦП, тактовая частота бреобразователя =/8 от тактовой микроконтроллера
ADMUX=(1< //Внутренний источник опорного напряжения Vref=2,56, входом АЦП является PC0

While(1)
{
u=getADC(); //Считываем данные
write_data(u); //Выводим их на ЖКИ
pause(30000);
}

Return 1;
}

Программа проста. В начале мы инициализируем порты ввода/вывода. Для того, чтобы служить входом АЦП , пин PC0 должен работать на вход. Далее проводим инициализацию ЖКИ и АЦП . Инициализация АЦП заключается в его включении битом ADEN в регистре ADCSRA . И выбора частоты преобразования битами ADPS2, ADPS1, ADPS0 в том же регистре. Также выбираем источник опорного напряжения, биты REFS1 REFS0 в регистре ADMUX и вход АЦП : биты MUX0,MUX1,MUX2, MUX3 (в нашем случаем входом АЦП является PC0 , поэтому MUX0.3=0 ). Далее, в вечном цикле, начинаем преобразования установкой бита ADSC в регистре ADCSRA . Дожидаемся окончания преобразования (бит ADIF в ADCSRA становиться равным 1). Далее вынимаем данные из регистра ADC и выводим их на ЖКИ . Вынимать данные из ADC нужно в такой последовательности: v=(ADCL+ADCH*256); если использовать v=(ADCH*256+ADCL); - в упор не работает. Также есть хитрость, чтобы не работать с дробными числами. Когда производиться вычисления входного напряжения в вольтах. Мы просто будем хранить наше напряжения в милливольтах. Например, значение переменной voltage 4234 означает, что мы имеем 4,234 вольта. Вообще операции с дробными числами кушают очень много памяти микроконтроллера (наша прошивка вольтметра весит чуть больше 4 килобай, это половина памяти программ ATmega8 !), их рекомендуется использовать только при особой необходимости. Вычисления входного напряжения в милливольтах просто: voltage=R_division*2.56*u*1.024;
Здесь R_division – коефициент резистивного делителя R4-R5 . Так, как реальный коефициент делителя может отличаться от расчетного, то наш вольтметр будет врать. Но подкорректировать это просто. С помощью тестера меряем некое напряжение, получаем X вольт, а наш вольтметр пускай показывает Y вольт. Тогда R_division = 4*X/Y , если Y больше X и 4*Y/X если X больше Y . На этом настройка вольтметра завершена, и им можно пользоваться.
Скачать прошивку в виде проекта под AVR Studio 4.
Как работает вольтметр можно ознакомиться на видео:

Также можно доработать свой блок питания. Вставив в него цифровой вольтметр-амперметр на ЖКИ и защиту от перегрузки (для измерения тока нам понадобиться мощный шунт сопротивлением порядка 1 Ом).

В свой блок питания я встроил еще защиту от перегрузки, когда ток превышает 2А, то пьезо пищалка начинает усердно пищать, сигнализируя о перегрузке:

Урок 22

Часть 2

Изучаем АЦП

Сегодня мы продолжаем изучать очень интересную технологию, а для микроконтроллера — периферию — аналго-цифровой преобразователь или как его называют АЦП . В нашего занятия мы познакомились, что такое вообще АЦП, также познакомились, как он организован в контроллере AVR, а также создали новый проект и настроили его.

Дальнейшая задача — реализация АЦП в нашем проекта.

Ну и чтобы нам данную задачу выполнить, нам нужны будут определённые функции для обращения к АЦП контроллера.

Для этого зайдём в файл adc.c и создадим функцию инициализацию нашего АЦП

#include "adc.h"

//—————————————-

void ADC_Init ( void )

{

}

Также создадим на данную функцию прототип в хедер-файле adc.h для видимости её из внешних модулей, а также заодно и посмотрим всё содержимое данного файла

#ifndef ADC_H_

#define ADC_H_

#include "main.h"

void ADC_Init ( void );

#endif /* ADC_H_ */

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

Начнем с управляющего регистра

void ADC_Init ( void )

ADCSRA |= (1<< ADEN )

|(1<< ADPS2 )|(1<< ADPS1 )|(1<< ADPS0 ); //Делитель 128 = 64 кГц

Это не две строки, а одна, так писать в студии можно и даже нужно, так как код становится понятнее. А одна, потому что нет символа конца строки — точки с запятой.

Здесь мы включили бит ADEN , тем самым включили вообще модуль АЦП, а также установили делитель на 128, тем самым, помня то, что частота тактирования у нас 8 МГц и разделив её значение на 128, мы получили работу АЦП на частоте 64 кГц, что вполне нормально и надёжно, до 200 граничных далеко. Как видим, ничего сложного в инициализации регистра нет.

Также ещё в данной функции нам необходимо выбрать канал, к которому мы будем подключать измеряемое напряжение. У нас судя по схеме канал 0, поэтому соответствующий MUX мы и включим. А соответствующий MUX — это все нули в данных битах, поэтому ничего-то и включать не надо. Но мы ещё помним, что в регистре ADMUX у нас помимо всего прочего есть и управляющие биты, а именно биты REFS1 и REFS0, с помощью которых мы установим в качестве источника опорного напряжения внутренний источник на 2,56 вольта, а ADLAR мы не используем

ADCSRA |= (1<< ADEN ) // Разрешение использования АЦП

|(1<< ADPS2 )|(1<< ADPS1 )|(1<< ADPS0 ); //Делитель 128 = 64 кГц

ADMUX |= (1<< REFS1 )|(1<< REFS0 ); //Внутренний Источник ОН 2,56в, вход ADC0

Ну вот, в принципе, и вся инициализация.

Вызовем эту функцию в главном модуле программы в функции main() где-нибудь вот тут

LCD_ini (); //Инициализируем дисплей

ADC_Init (); //Инициализируем АЦП

clearlcd (); //Очистим дисплей

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

unsigned int ADC_convert ( void )

{

}

Само собой нужен будет в хедер-файле прототип на неё

void ADC_Init ( void );

unsigned int ADC_convert ( void );

Данная функция нам вернёт значение из регистровой пары ADC , которая и будет содержать величину нашего электрического сигнала в единицах, выражающих отношение измеряемого сигнала к опорному и умноженных на количество возможных отрезков, которых у нас 1023, ну или 1024. Насчёт этого ходят много слухов, но в технической документации на контроллер в расчетной формуле содержится именно 1024. Но это нам не так важно.

Включим преобразование с помощью бита ADSC

unsigned int ADC_convert ( void )

ADCSRA |= (1<< ADSC ); //Начинаем преобразование

Теперь нам надо как-то отследить тот момент, когда данное преобразование закончится. А делается это достаточно легко с помощью мониторинга того же бита ADSC, который по окончании процесса преобразования сам сбрасывается в 0 (When the conversion is complete, it returns to zero). Отслеживается данный бит с помощью условного цикла

ADCSRA |= (1<< ADSC ); //Начинаем преобразование

while (( ADCSRA & (1<< ADSC )));

Ну и по окончании вернём результат в виде беззнаковой величины

while (( ADCSRA & (1<< ADSC ))); //проверим закончилось ли аналого-цифровое преобразование

return ( unsigned int ) ADC ;

Вернёмся теперь в нашу главную функцию main() и создадим там локальную переменную для хранения результата преобразования для дальнейшей с ним работы

int main ( void )

unsigned int adc_value ;

Вызовем функцию преобразования, которая нам положит в нашу переменную результат преобразования

while (1)

adc_value = ADC_convert (); //Вызовем преобразование

Setpos (0,0);

Давайте сначала отобразим данную сырую величину, хотя бы посмотрим, что в ней есть. За основу мы пока возьмём код из наших часов, функция sprintf на помощь придёт в более поздних занятиях, время её пока не пришло и нам надо вообще понять, как преобразовываются символы. Это нам ой как пригодится в программировании светодиодных индикаторов

Setpos (0,0);

sendcharlcd ( adc_value /1000+0x30);

sendcharlcd (( adc_value %1000)/100+0x30); //Преобразуем число в код числа

sendcharlcd (( adc_value %100)/10+0x30); //Преобразуем число в код числа

sendcharlcd ( adc_value %10+0x30); //Преобразуем число в код числа

Delay_ms (500);

Здесь мы разбиваем по цифрам четырёхзначную величину.

Теперь мы соберём код, прошьём контроллер и посмотрим наши результаты, покрутив резистор на 10 килоом

Вот так вот оно всё и работает.

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

unsigned int adc_value ;

float n ;

Также забудем про существование функции sprintf и попробуем получить плавающий тип на дисплее программным путём. Для этого сначала преобразуем наш сырой результат в плавающий тип явным образом, то есть та же цифра будет, но только тип другой, не забыв, конечно, перед этим поставить курсор в нужное место на дисплее. Для этого существует понятие в языке СИ явного преобразования типов и разделим преобразованный результат на 400

sendcharlcd ( adc_value %10+0x30); //Преобразуем число в код числа

setpos (8,0);

n = ( float ) adc_value / 400;

Тут, конечно, возникает вопрос, а почему мы делим именно на 400. А вот почему.

Это ничто иное как 1024, разделённое на 2,56, то есть на наше опорное напряжение. Видимо, не зря разработчики контроллера выбрали именно такую величину опроного напряжения, чтобы всё делилось без остатка. Почему мы именно такое деление применяем. А потому что у нас есть формула в технической документации

Вот поэтому и мы и вычислили её самую последнюю часть. Осталось теперь лишь только перевернуть ещё наоборот, выразив отсюда входное напряжение, так как неизвестное у нас именно оно. И мы получим, что оно будет у нас равно ADC, делённому на 400, что мы, собственно и сделали выше в коде. Я думаю, всё предельно стало теперь всем понятно.

Осталось самое интересное — отобразить всё это на экран, зная, то.что мы не можем работать с дисплеем с плавающим типом. А оказывается всё просто. Всё решается вот таким кусочком кода

N = ( float ) adc_value / 400;

sendcharlcd (( unsigned char ) n +0x30); //Преобразуем число в код числа

sendcharlcd ("."); //Преобразуем число в код числа

sendcharlcd ((( unsigned char ) ( n *10))%10 +0x30); //Преобразуем число в код числа

sendcharlcd ((( unsigned char ) ( n *100))%10 +0x30); //Преобразуем число в код числа

Delay_ms (500);

Не пугайтесь, сейчас мы всё тут разрулим.

Сначала мы обратным преобразованием типов отсекаем вооще всю дробь и, зная, что дальше 9 мы не уйдём и у нас будет только одна цифра, да мы даже и дальше 2 тут не уйдём, у нас максимум 2,56, мы просто отображаем данную цифру.

Потом мы умножаем наш результат, преобразованный к плавающему типу на 10, тем самым, передвигаем запятую на один разряд в нём вправо и, преобразовав результат вычисления обратно в целочисленный тип, берём из него известным образом младшую цифру и отображаем её на дисплее после запятой.

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

Вот и всё!

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

Post Views: 6 917


Описание работы Аналого-цифрового преобразователя.
Прерывания от АЦП

ATMega16 содержит в себе 10-битовый АЦП, вход которого может быть соединён с одним из восьми выводов Port A. АЦП Mega16, как и любому другому АЦП, нужно опорное напряжение для целей сравнения со входным (если измеряемое равно опорному, то получаем максимальный код в двоичном виде). Опорное напряжение подаётся на вывод ADRef или может использоваться внутренний генератор с фиксированным напряжением 2,65 В. Полученный результат можно представить в таком виде:

АЦП включается установкой бита ADEN в регистре ADCSRA. После преобразования, 10-битный результат оказывается в 8-битных регистрах ADCL и ADCH. По умолчанию, младший бит результата находится справа (то есть в bit 0 регистра ADCL, так называемое правое ориентирование). Но порядок следования битов на левое ориентирование можно сменить установив бит ADLAR в регистре ADMUX. Это удобно, если требуется получить 8-битовый результат. В таком случае требуется прочитать только регистр ADCH. В противном случае, Вы должны сначала прочитать регистр ADCL первым, а ADCH вторым, чтобы быть уверенным в том, что чтение этих двух регистров относится к результату одного преобразования.

Одиночное преобразование может быть вызвано записью бита ADSC в регистр ADCSRA. Этот бит остаётся установленным всё время, занимаемое преобразованием. Когда преобразование закончено, бит автоматически устанавливается в 0. Можно также начинать преобразования по событиям из разных источников. Модуль АЦП также может работать в режиме "свободного полёта". В таком случае АЦП постоянно производит преобразование и обновляет регистры ADCH и ADCL новыми значениями.

Для выполнения преобразования модулю АЦП необходима тактовая частота. Чем выше эта частота, тем быстрее будет происходить преобразование (оно, обычно, занимает 13 тактов, первое преобразование занимает 25 тактов). Но чем выше частота (и выше скорость преобразования), тем менее точным получается результат. Для получения максимально точного результата, модуль АЦП должен тактироваться частотой в пределах от 50 до 200 КГц. Если необходим результат с точностью менее 10 бит, то можно использовать частоту больше 200 КГц. Модуль АЦП содержит делитель частоты, чтобы получать нужную тактовую частоту для преобразования из частоты процессора.

Регистр ADMUX задаёт входной контакт порта A для подключения АЦП, ориентирование результата и выбор опорной частоты. Если установлен бит ADLAR, то результат лево-ориентирован. Опорная частота от внутреннего генератора задаётся выставленными в 1 битами REFS1 и REFS0. Если оба бита сброшены, то опорная частота берётся от контакта AREF. В случае, если REFS1=0 а REFS0=1, опорная частота берётся от AVCC с внешним конденсатором, подключенным к AREF. Выбор контакта ввода выполняется следующим образом:

Регистр контроля и статуса АЦП ADCSRA :

Бит ADEN=1 включает модуль АЦП.
Запись единицы в ADSC запускает цикл преобразования. В режиме "свободного полёта" запись единицы запускает первое преобразование, последующие запускаются автоматически.
ADIF - флаг прерывания АЦП. Этот бит устанавливается в 1 когда АЦП завершено преобразование и в регистрах ADCL и ADCH находятся актуальные данные. Этот флаг устанавливается даже в том случае, если прерывания запрещены. Это необходимо для случая программного опроса АЦП. Если используются прерывания, то флаг сбрасывается автоматически. Если используется программный опрос, то флаг может быть сброшен записью лог.1 в этот бит.
ADIE - Если в этом бите установлена единица, и прерывания разрешены глобально, то при окончании преобразования будет выполнен переход по вектору прерывания от АЦП.
Биты ADPS2..0 задают коэффициенты предделителя частоты:


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

Первое о чем нужно сказать — АЦП микроконтроллера умеет измерять только напряжение. Чтобы произвести измерение других физических величин, их нужно вначале преобразовать в напряжение. Сигнал всегда измеряется относительно точки называемой опорное напряжение, эта же точка является максимумом который можно измерить. В качестве источника опорного напряжения (ИОН), рекомендуется выбирать высокостабильный источник напряжения, иначе все измерения будут плясать вместе с опорным.

Одной из важнейших характеристик является разрешающая способность, которая влияет на точность измерения. Весь диапазон измерения разбивается на части. Минимум ноль, максимум напряжение ИОН. Для 8 битного АЦП это 2^8=256 значений, для 10 битного 2^10=1024 значения. Таким образом, чем выше разрядность тем точнее можно измерять сигнал.

Допустим вы измеряете сигнал от 0 до 10В. Микроконтроллер используем Atmega8, с 10 битным АЦП. Это значит что диапазон 10В будет разделен на 1024 значений. 10В/1024=0,0097В — с таким шагом мы сможем измерять напряжение. Но учтите, что микроконтроллер будет считать, величину 0.0097, 0.0098, 0.0099… одинаковыми.

Тем не менее шаг в 0,01 это достаточно неплохо. Однако, есть несколько рекомендаций, без которых эта точность не будет соблюдена, например для измерения с точностью 10бит, частота на которой работает АЦП должна быть 50-200 кГц. Первое преобразование занимает 25 циклов и 13 циклов далее. Таким образом, при частоте 200кГц мы сможем максимум выжать
200 000/13 = 15 384 измерений.

В качестве источника опорного напряжения можно использовать внутренний источник и внешний. Напряжение внутреннего источника (2,3-2,7В) не рекомендуется использовать, по причине низкой стабильности. Внешний источник подключается к ножке AVCC или Aref, в зависимости от настроек программы.

При использовании АЦП ножка AVCC должна быть подключена. Напряжение AVCC не должно отличаться от напряжения питания микроконтроллера более чем на 0,3В. Как было сказано, максимальное измеряемое напряжение равно опорному напряжению(Vref), находится оно в диапазоне 2В-AVCC. Таким образом, микроконтроллер не может измерить более 5В.

Чтобы расширить диапазон измерения, нужно измерять сигнал через делитель напряжения. Например, максимальное измеряемое напряжение 10В, опорное напряжение 5В. Чтобы расширить диапазон измерения, нужно уменьшить измеряемый сигнал в 2 раза.

Формула для расчета делителя выглядит так:

U вых = U вх R 2 /(R 1 + R 2)

Подставим наши значения в формулу:

5 = 10*R2/(R1+R2)

т.е. можно взять любые два одинаковых резистора и подключить их по схеме

Следовательно, когда мы измеряем напряжение через делитель, нужно полученное значение АЦП умножить на коэффициент=Uвых/Uвх.

Полная формула вычисления измеряемого напряжения будет выглядеть так:
U=(опорное напряжение*значение АЦП*коэффициент делителя)/число разрядов АЦП

Пример: опорное 5В, измеренное значение АЦП = 512, коэффициент делителя =2, АЦП 10разрядный.

(5*512*2)/1024=5В — реальное измеренное значение напряжения.

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

Перейдем к программной реализации. Создаем проект с указанными параметрами. Также подключим дисплей на порт D для отображения информации.

Измерение будет производиться в автоматическом режиме, обработка кода в прерывании, опорное напряжение подключаем к ножке AVCC. По сути нам нужно только обрабатывать получаемые данные. Измеренные данные хранятся в переменной adc_data. Если нужно опрашивать несколько каналов, то выбираем какие каналы сканировать, а данные будут для ножки 0 в adc_data, для ножки 1 в adc_data и т.д.

В основном цикле добавим строки:

result=((5.00*adc_data)/1024.00); //пересчитываем значение АЦП в вольты
sprintf(lcd_buffer,»U=%.2fV»,result); //помещаем во временную переменную результат
lcd_puts(lcd_buffer); //выводим на экран

Небольшое замечание, чтобы использовать числа с плавающей точкой, нужно в настройках проекта изменить (s)printf Features: int, width на float, width, precision. Если этого не сделать десятые и сотые мы не увидим.

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

Резистором можно менять напряжение, измеряемое напряжение выведено на дисплей. При сборке на реальном железе к ножке Aref нужно подключить конденсатор на 0,1мкФ. Урок получился немного сложным, но думаю он вам понравится.

Файл протеуса и прошивка:

Update:
Измерение тока:







2024 © sdelano-krasnodar.ru.