Урок 5. Работа с АЦП
На прошлом уроке мы собрали свой первый программатор и прошили с его помощью
МК. Ну что, поздравляю вас с первым реальным проектом. Мигание светодиодом это
конечно хорошо, но давайте займемся чем-нибудь по интереснее. Сегодня я расскажу
как работать с АЦП(аналогово-цифровой преобразователь). Ну поехали.
Перед тем как браться за программирование, давайте вернемся в школу. Да, да
именно в школу, на урок алгебры и вспомним что же такое график. Из своего детства
я помню, что при построении графика по той или иной функции многие из класса
выпадали в осадок. Да, да именно графики были не понятны как и векторы. Хотя что
там не понять. А может просто преподаватель плохо объяснял. Ну я попробую удалить
это белое пятно. Те кто могут с ходу нарисовать график Х = У могут не читать эту
часть текста. Для тех кто не понимает как это делать, лучше внимательно прочесть.
Давайте рассмотрим выражение Х = У . У нас есть неизвестная Х и неизвестная У .
Если мы возьмем любое число и подставим скажем под Х , то соответственно У
примет то же значение. Ну это само собой, ведь у нас знак равенства. А как теперь
по этому уравнению построить график. А очень просто. Первое что нужно понять, так
это то что любой график является прямой или кривой состоящие из множества точек.
Что это значит. А значит это то, что если вы возьмете фломастер и попытаетесь
нарисовать прямую, только не проведя по листу, а ставя точки одну за другой, то
вы получите некую прямую или кривую. Вот она больше всего подходит к понятию
график. Но просто нарисованная линия на листе это еще не все. Ведь нам надо
строить линию точно по уравнению. А как это сделать? Вот тут нам поможет
координатная ось. Что это такое. Вот что можно сделать при помощи простой
деревянной линейки? Правильно, измерить длину. А в чем? Конечно в мм или см.
Глупый вопрос скажите вы, но будете не правы. Та шкала на которую вы смотрите для
определения длины и есть координатная ось. А в каких единицах она, это все равно.
Ведь у англичан она в дюймах. Теперь давайте представим что наша линейка имеет
значения в Х-ах. И равна она от 0 до скажем 20. Это означает что на всю длину
линейки мы имеем 20 рисок от 0 до 20 соответственно. И назовем ее ось Х.
Так же возьмем еще одну линейку на 20 рисок и назовем ее ось У.
Теперь соединим эти линейки нулевыми рисками так чтобы линейка Х лежала
горизонтально, а У вертикально.
Рисунок 1.
И так мы получили координатную сетку. Теперь давайте попробуем с помощью
данной координатной сетки нарисовать наш график по уравнению Х = У.
Подставим под Х число 2. Теперь проведем от риски 2 оси Х вверх линию.
Так как у нас икс равен игрику, то проведем еще одну линию от риски 2, но уже
от оси У. Теперь посмотрев на эти две линии, найдем место их пересечения.
Таким образом мы нашли одну из множества точек нашего будущего графика.
Давайте найдем еще одну точку. Скажем что Х равен 4. Значит и У равен 4.
Чисто теоретически мы можем на этом закончить. Так как видно, то что, при
любом значении Х, У принимает то же значение, и график будет выглядеть простой
прямой. Но мы не верим. Давайте проверим. Давайте найдем еще три точки. К примеру
6, 8 и ну скажем 18. Найдя все точки давайте соединим их прямой. Вот что у меня
получилось.
Рисунок 2.
Как видите, прямая оказалось прямой. И если внимательно присмотреться, то можно
сделать вывод, что эта прямая наклонена от обеих осей на 45°. Это вытекает из
равенства обеих неизвестных. А давайте по химичим. Ну к примеру удвоим значение
X. Теперт наше равенство примет вид Y = 2X. Попробуем построить график.
Возьмем к примеру значения Х равным 2, 4 и 9. Вот что у меня получилось.
Рисунок 3.
Отсюда вытекает замечание, чем больше коэффициент переменной, тем больше он
удаляет от себе прямую. Ну это видно из построенного графика. Видите как
прямая прижалась к оси У. Ну с прямыми вроде как разобрались. Давайте теперь
построим что-нибудь кривое. Это не в смысле плохого здания. Берем классическое
квадратное уравнение. Y = X². (Квадратное, это не потому что выглядит как
квадрат, а потому что имеет одну из неизвестных во второй степени.) Теперь давайте
найдем четыре точки со значением Х равным 1, 2, 3 и 4. Не пугайтесь, график
получится большой не смотря на такие маленькие иксы. Ведь они у нас в квадрате.
Вот что у меня получилось.
Рисунок 4.
Ничего не напоминает? Конечно, ветвь параболы. Если мы возьмем еще иксы и с
отрицательным значением, то получим полноценную параболу. Теперь давайте построим
более сложную вещь. Синусойду. Уравнение имеет вид У = sinХ. Здесь
надо помнить лишь одно, что значение икса есть угол, который меняется от 0° до 360°.
То есть полный оборот. Значение может быть и больше, но это будет лишь следующий
оборот, а за ним следующий и так далее. Теперь давайте подставим под Х значения
0°, 90°, 180°, 270° и 360°. Да, да, это полный оборот. Вот что у меня получилось.
Рисунок 5.
Ну на этом с графиками мы закончим. Я надеюсь что все было понятно.
Что такое отцифровка сигнала? Сразу отмечу, мы не будем разбирать теорему
Котельникова, так как это займет очень много времени и появятся много вопросов.
Будем разбирать все на пальцах. И так, отцифровка - это разбитие некого сигнала
на множество маленьких прямых. Вспомним нашу параболу, она была построена из
четырех прямых. Это маловато, оно и видно. Ветвь какая-то ломаная. А вот если
взять не пять точек, а скажем сто, то понятно что ветвь будет выглядеть более
менее плавная. А если взять тысячу точек? Хорошо, ну разбили мы наш сигнал на
прямые и что? А тут нам понадобятся знания которые мы получили выше. Только
мы пойдем от обратного. Если мы имеем график и одно из неизвестных, то мы можем
смело найти второе неизвестное графическим путем. Для наглядности смотрим на
рисунок ниже.
Рисунок 6.
Что мы здесь видим. По оси Х отложено время в секундах, а по оси У напряжение в
вольтах. Нам известна форма сигнала и время. Теперь мы договариваемся что через каждую
секунду будем узнавать величину напряжения. Что собственно и видно из графика.
Теперь собрав в последовательность наши напряжения, а именно: 0.3, 0.9, 1.9, 4, 6,
7.9, 8.9, 9.5, 10, 9.9, 9.6, 9.1, 8.1, 6. Мы можем в принципе заявить, что мы
отцифровали сигнал. Ведь мы знаем несколько точек находящихся на кривой сигнала.
Но вот если мы обратно попробуем собрать наш сигнал, то вот что получим.
Рисунок 7.
Но как вы видите это не совсе то что мы хотели увидеть. А почему так получилось?
А потому что мы очень редко снимали значение напряжения. Чтобы график был более
плавным, нужно уменьшить период времени опроса. То есть смотреть не через 1 секунду,
а через 1 сотую секунды. И тогда у нас будет не 13 точек, а 1300. Есть разница?
Теперь давайте перейдем к аппаратной части. Как реализована данная схема в
железе. АЦП не может запоминать напряжение. А как же тогда, спросите вы. А я скажу
что достаточно просто. Вот смотрите. Берем конденсатор и подаем на него наш сигнал.
Пока конденсатор заряжается, мы отсчитываем время, ну например 1 миллисекунду.
По истечении времени прекращаем подавать сигнал. Теперь все наоборот, начинаем
разряжать наш конденсатор, а в это время считаем от нуля до конца разряда конденсатора.
Получившееся число и есть та точка которая попадает на график по истечении
1 миллисекунды. В МК эта процедура называется "выборка". Ну с теорией мы вроде
закончили, переходим к практике.
Нам понадобится МК Atmega8. Данный МК имеет 6 каналов 10-ти разрядных АЦП, два из
которых 8-ми разрядные. Для того чтобы начать работать с АЦП, нам понадобиться
лишь опорное напряжение. Для начала возьмем 5 вольт. Это означает что отцифровка
сигнала возможна от 0 до 5 вольт. Так как канал 10-ти разрядный то получается
5/210 = 0.0048 В на единицу. Значит если у нас АЦП выдаст при выборке
число 482, то напряжение будет равно 482*0.0048 = 2.3136 В. В принципе неплохая
точность. По заявленным параметрам, МК делает 15000 выборок в секунду. Отсюда
наш график можно разбить на 14999 прямых. Давайте для начала соберем схему чтоб
было немного по понятнее.
Рисунок 8.
Немного пояснений. На порт В МК подсоединен LCD 16х2. Сейчас я вдаваться в
подробности как он работает не буду, это не в этом уроке. Просто подсоедините как
на рисунке. Выводы AREF и AVCC присоединены к 5В. Это как раз есть то самое
апорное напряжение. На порт С к нулевому разряду подсоединен контакт с вольтметром
и переменным резистором для изменения входного напряжения. Наша задача такова.
Мы должны вывести на экран величину напряжения ту которую показывает вольтметр.
Приступаем к программированию.
Запускаем новый проект. В настройках Chip выбираем Atmega8, 4,00000000 MHz.
Во вкладке LCD выбираем PORTB. И сохраняем проект под названием, в моем случае,
ADC. Вы можете его назвать как вам угодно. Первым делом мы добавляем две директивы
препроцессора для работы с текстом и задержки. Для этого после директивы LCD добавим
эти две строки.
#include <delay.h>
#include <stdio.h>
Первая из них нужна для создания задержек, а вторая для работы с текстом. Далее
нам нужно создать массив для временного хранения форматированного текста. После
надписи "// Declare your global variables here" пишем char string[10];.
// Declare your global variables here
char string[10];
Теперь после открытия главной функции main, мы должны объявит две переменные.
Одна для хранения значения после выборки, а другая для хранения выводимого значения.
Для этого запишем так.
void main(void)
{
// Declare your local variables here
int data; // Переменная для хранения данных выборки. int так как регистр 10 разрядов.
float V; // Переменная для выводимого значения. float так как у нас точность до 2 знаков.
Теперь давайте займемся настройкой самого АЦП. Для этого после настройка компаратора
сразу запишем две строчки.
// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;
ADMUX=0; // Первая строка, № порта.
ADCSR=0x85; // Вторая строка настройка АЦП.
Ну с номером порта все ясно, какой номер прописан с тем и работаем, а вот с настройкой
тут давайте по подробнее. Для того чтоб начать работу с АЦП у МК есть такой регистр
который называется ADCSR. Что в нем находится.
0-й бит ADPS0 Выбор частоты преобразования
1-й бит ADPS1 Выбор частоты преобразования
2-й бит ADPS2 Выбор частоты преобразования
3-й бит ADIE Разрешение прерывания
4-й бит ADIF Флаг прерывания
5-й бит ADFR Выбор работы АЦП. 1-непрерывный 0-по запуску ADSC
6-й бит ADSC Запуск преобразование 1-старт. После преобразования
сбрасывается в ноль аппаратно.
7-й бит ADEN Разрешение работы АЦП 1-да 0-нет
Теперь давайте его настроим. Чтобы включить АЦП нам надо выставить в 1 7-й разряд.
Далее выставим 0 для старта преобразования. Мы его потом дергать будем. После
выставим в 0 5-й разряд. Будем сами запускать преобразование. 3-й и 4-й разряды
выставим в 0. Мы не будем работать с прерыванием. Теперь осталось подобрать частоту.
Если почитать мануал на МК, то там сказано что для более стабильной работы АЦП
его необходимо тактировать частотой в пределах 50 кГц - 200 кГц. Так как у нас
кварц на 4000 кГц, то нам его надо поделить. Вот для этого мы и воспользуемся
первыми тремя разрядами. Смотрим ниже на таблицу коэффициентов деления.
ADPS2 |
ADPS1 |
ADPS0 |
Делитель |
0 |
0 |
0 |
2 |
0 |
0 |
1 |
2 |
0 |
1 |
0 |
4 |
0 |
1 |
1 |
8 |
1 |
0 |
0 |
16 |
1 |
0 |
1 |
32 |
1 |
1 |
0 |
64 |
1 |
1 |
1 |
128 |
Что нам выбрать. Берем нашу частоту кварца и делим на коэффициент.
4000 кГц делим на 8, получаем 500 кГц. Много. Давайте теперь на 16, получим
250 кГц, тоже многовато. Делим на 32 и получаем 125 кГц. Во то что надо. Мы
уложились в заданные пределы. Смотрим в таблицу и видим что коэффициент 32
задается значением разрядов 101. Ну вроде все собрали. теперь давайте посмотрим на
наш регистр. Вот его значения. 10000101 здесь собраны все ранее рассмотренные
значения всех разрядов. Если перевести это число в HEX то получим 0х85.
Теперь вам понятно почему я записал в регистр
ADCSR значение 0х85.
С настройками АЦП закончено. Теперь давайте выведем в первой строке наши намерения.
Для этого после инициализации LCD запишем строчку. lcd_putsf("Work with ADC");
// LCD module initialization
lcd_init(16);
lcd_putsf("Work with ADC"); // Выводим запись
Теперь при старте программы мы в первой строке увидим нашу надпись. Далее в бесконечном
цикле пишем тело самой программы.
while (1)
{
delay_ms(20); // Задаем задержку в 20 миллисекунд
ADCSR |= 0x40; // Записываем 1 в ADSC
data = ADCW; // Вычитываем значение
V = (float) data*0.0048828; // Переводим в вольты
sprintf(string, "Data: %1.2f", V); // форматируем
lcd_gotoxy(0,1); // Выставляем курсор
lcd_puts(string); // Выводим значение
};
Как вы заметили вся программа состоит из семи строк. Давайте все по порядку.
delay_ms(20); Эта функция которая создаст задержку на 20 миллисекунд.
ADCSR |= 0x40; Здесь мы делаем по битное ИЛИ. Число 0х40 в бинаре выглядит так
0b01000000. Если мы проведем по битное ИЛИ с 0х85 (0b10000101), то у нас в 6-й
разряд запишется 1. Помните, что надо сделать чтобы началось преобразование. Да,
да, именно в 6-й разряд нужно записать 1. А после преобразование он сбросится в 0 аппаратно.
data = ADCW; После преобразование МК записывает полученное значение в регистр ADCW.
Вот мы от туда его и выдергиваем.
V = (float) data*0.0048828; Здесь мы преобразуем полученное число в вольты. Так
как у нас опорное напряжение 5В, а значение регистра 1024, то мы 5/1024=0.0048828
Это коэффициент напряжения. Ну или минимальная величина напряжения при минимальном
значении регистра ADCW. То есть если в регистре будет значение 1, то велечина напряжения
будет равна 0.0048828 В. Поэтому мы в строке, данные ADCW перемножаем с 0.0048828.
Слово float в скобке нужно для того чтобы преобразовать переменную data из целочисленной
в вещественную с плавающей точкой.
sprintf(string, "Data: %1.2f", V); Здесь мы заносим значение напряжения в массив
string с форматированием. Сначала мы впишем Data: . После ставится знак процента.
Он говорит о том сколько знаков будет выведено. 1.2f говорит о том что мы хотим
вывести один знак до запятой и 2 знака после, а буква f говорит что мы имеем дело
со значением вещественным с плавающей точкой.
lcd_gotoxy(0,1); Ну тут все ясно. Выставляем курсор в нулевую позицию во второй строке.
lcd_puts(string); Выводим значение на экран.
Перед тем как собрать проект нужно сделать небольшие настройки. Зайдите в настройки
проекта
"Project->Configure" и в открывшемся окне перейдите во вкладку
"C Compiler"
Далее в левом нижнем углу поменяйте значение
(s)printf Features:
с
int, width на
float, width, precision.
Зачем это нужно я расскажу в статье по работе с ЖК дисплеями, а сейчас просто поменяйте.
Вот и вся программа. Ниже на рисунке видно как это работает.
Рисунок 9.
Архив с проектоми для CodeVisionAVR и Proteus.
Распакуйте архив в корень диска С и проект можно сразу запускать.
<-Назад Вперед ->