АЦП напряжение -> код.

Обсуждаем контроллеры компании Atmel.
Ответить
Родился
Сообщения: 17
Зарегистрирован: Пн фев 09, 2015 21:19:07

Сообщение Ziper009 »

Друзья помогите пожалуйста.
Задача такая у меня. Я считываю значение напряжение из файла допустим это напряжение 12.5 в. потом я контроллером мерию напряжение на входе и когда они совпадают с считанным 12.5 делаю что мне надо. Хочу реализовать так. Считываю из файла напряжение 12.5 преобразую его в значение АЦП которое соответствует напряжению 12.5 и сравниваю это значение с там которое сейчас в АЦП и когда они совпадают делаю что мне надо. Но как преобразовать 12.5 в и узнать какое значение должно быть в АЦП при таком напряжении на входе??

вот http://avr-start.ru/урок-10-ацп-в-avr-м ... ment-25958 нашёл хорошею статью как выбрать делитель напряжения и рассчитать его.
Из той статьи.
Полная формула вычисления измеряемого напряжения будет выглядеть так:
U=(опорное напряжение*значение АЦП*коэффициент делителя)/число разрядов АЦП
Пример: опорное 5В, измеренное значение АЦП = 512, коэффициент делителя =2, АЦП 10разрядный.
(5*512*2)/1024=5В — реальное измеренное значение напряжения.
мне надо произвести обратное преобразование. То есть контроллер ко входу контроллера через резисторный делитель подаётся напряжение. Мне надо рассчитать с учётом коэффициента делителя какой код в АЦП будет находиться для определённого напряжения на входе делителя??
Допусти мы подаём на вход резисторного делителя напряжение 12.5 в. образцовое внутренние у контроллера 1.1в. по какой формуле можно рассчитать какой код будет в АЦП контролера?? Помогите очень прошу нигде не нашёл про это. :(
Реклама
Друг Кота
Аватара пользователя
Сообщения: 3872
Зарегистрирован: Пт янв 29, 2010 10:27:40
Откуда: Москва

Сообщение Engineer_Keen »

А вывести из формулы одну из переменных в школе еще не научили? :facepalm:
U = (Vr * X * d) / 2 ^ n, где U - напряжение на входе, Vr - опорное, X - код, d - делитель, n - разрядность
выносим X:
X = U * 2 ^ n / (Vr * d)

Например делитель у вас 20, на вход попадает - 12.5, тогда:

X = 12.5 * 1024 / (1.1 * 20) = 581

Только:
1) если речь идет о внутреннем опорном напряжении AVR, то это 1.1 ± 0.1
2) младшие 2-3 бита будут болтаться, нужно как минимум усреднение, да и вообще проще использовать бит ADLAR и смотреть старшие 8 бит.
Неправильно собранная из неисправных деталей схема нуждается в отладке и сразу не работает... (С)
Реклама
Опытный кот
Аватара пользователя
Сообщения: 781
Зарегистрирован: Сб июн 01, 2013 22:24:21
Откуда: ПФО

Сообщение alex_ »

Всё верно написал коллега Engineer_Keen. Вот только я бы порекомендовал использовать не внутренний опорник а внешний, вольт на 5 а ещё лучше на 1024*4=4096/1000~=4.1 вольта с помощью микросхемы TL431. Возникает вопрос что это даст?, да в целом не много но всё будет понятнне, да и математических операций почти нет, умножаем значение на 4 с помощью сдвига регистров и делим на 1000 и получаем готовое значение(если надо вообще делить на 1000, для вывода на ЖК дисплей эта манипуляция не нужна будет). Да и ещё по поводу делителя, я бы поставил делитель на 4(резисторный делитель из 2 резисторов, например 1К и 3К или 5К и 15К). Останется только в программе результат умножить на 4-> 12,5/4=3,125 вольта будет на входе АЦП 3,125/4=~781 значение которое выдаст АЦП. :)
Друг Кота
Аватара пользователя
Сообщения: 6337
Зарегистрирован: Вт апр 24, 2007 07:45:40
Откуда: Minsk

Сообщение Jack_A »

Если нужно точное равенство, то это дохлый номер, потому что 1 бит разницы уже даст несовпадение. Программисты знают, что для действительных чисел срвнение if ( a == b ) некорректно. Другое дело, если есть допуск на несовпадение или измеряемое напряжение монотонно возрастающее - убывающее. Тогда факт пересечения изменяющимся уровнем заданного значения будет иметь место.
Реклама
Эиком - электронные компоненты и радиодетали
Родился
Сообщения: 17
Зарегистрирован: Пн фев 09, 2015 21:19:07

Сообщение Ziper009 »

Ураа спасибо всем большое что помогли. учту все предложенные пояснения. :)
Реклама
Друг Кота
Аватара пользователя
Сообщения: 44130
Зарегистрирован: Пн ноя 30, 2009 03:00:01
Откуда: Нерезиновая

Сообщение АлександрЛ »

Engineer_Keen писал(а):1) если речь идет о внутреннем опорном напряжении AVR, то это 1.1 ± 0.1.
1,1 вольта "внутреннее опорное" - это, видимо, только для ATTiny (ATTiny13, например, у Tiny2313 вообще нат АЦП) , для ATMega я видел только 2,56 вольта.
Реклама
Друг Кота
Аватара пользователя
Сообщения: 3872
Зарегистрирован: Пт янв 29, 2010 10:27:40
Откуда: Москва

Сообщение Engineer_Keen »

АлександрЛ писал(а):1,1 вольта "внутреннее опорное" - это, видимо, только для ATTiny (ATTiny13, например, у Tiny2313 вообще нет АЦП) , для ATMega я видел только 2,56 вольта.
Да, просто нарисовал такое, какое в последнем открытом даташите было, но суть была не в этом, а в том, что хреновое оно...
Неправильно собранная из неисправных деталей схема нуждается в отладке и сразу не работает... (С)
c2n
Сверлит текстолит когтями
Аватара пользователя
Сообщения: 1193
Зарегистрирован: Ср июл 25, 2012 21:40:09
Откуда: Самара

Сообщение c2n »

так. А теперь принцип работы "синхрофазатрона"
АЦП вызывается, и длится некоторое время.
в результате преобразования заполняются два 8 битных регистра ADCH и ADCL значением в 10 бит.
в зависимости от настроек АЦПшки в программе по окончании преобразования будет примерно такой результат:

Этот результат интерпретируется так: GND = 0x00(0) VCC = 0x03FF(1023) если напряжение преобразования между, то и значение буде соответствовать этому "между", для 2,5В скорее всего будет 0xFF (255)

предположим мы оцифровываем значение 3,2В = 0x28F(655)[0b1010001111]
тогда в регистры запишется:

ADCH[------10]ADCL[10001111] //черточки обычно нули, но может быть и "мусор"
или
ADCH[10100011]ADCL[------11]// Тут читайте даташит на микроконтроллер, в нем подробно описано какими регистрами программируется такой "финт".

Соответственно во втором случае значением ADCL можно пренебречь, т.к. он отрабатывает "сотые" доли напряжения.

Теперь смело ADCH можете обрабатывать как "простое" число, не заморачиваясь на его "сборку" , и вуаля, мы можем обрабатывать значения от 0 до 5В с точностью 1/256В.

Соответственно делитель вам надо запроектировать таким образом, что бы после него, максимальное входное значение соответствовало VCC микроконтроллера.

Так же стоит все это дело пропустить через операционный усилитель, с понижающим коэффициентом (у комрада Di-Halt отлично описано!, погуглите:)))).
Если вы пропускаете сигнал на оцифровку через ОУ, то далее этот сигнал практически не искажается. Если вы пропускаете сигнал через резестивный делитель, то есть шанс, что после делителя сигнал будет уже совсем не таким.
Контактная информация:
Родился
Сообщения: 17
Зарегистрирован: Пн фев 09, 2015 21:19:07

Сообщение Ziper009 »

привет, опять я :)
сегодня весь день в инете лазил не могу решить новую проблему. :(
вы мне уже подсказали формулу для расчёта
А вывести из формулы одну из переменных в школе еще не научили? :facepalm:
U = (Vr * X * d) / 2 ^ n, где U - напряжение на входе, Vr - опорное, X - код, d - делитель, n - разрядность
выносим X:
X = U * 2 ^ n / (Vr * d)

Например делитель у вас 20, на вход попадает - 12.5, тогда:

X = 12.5 * 1024 / (1.1 * 20) = 581
спасибо то что надо.
Но возниrла новая напасть :( хочу создать функцию на основе этой формулы. При вызове функции в параметре передать ей double число (напряжение 12.5 в) а чтоб функция возвратила мне код АЦП для этого напряжения. возвратила код число 0-255.
Но возникла такая проблема как собрать число double для передачи его в функцию??? То есть как я уже писал выше это число я буду считывать из файла .txt который находится на sd карте.
Подробнее расскажу. Есть скажем число 12.5 оно записанно в .тхт файле это число будет всегда занимать 4 байта и я могу его прочитать хоть за раз в переменную типа uint32_t или могу по одному байту прочитать это не важно. Так вот как из переменной uint32_t получить число double для передачи его в функцию? Явное присвоение мне кажется работать не будет (double)(переменная uint32_t).
Склоняюсь к идее чтоб создать буфер типа uint8_t где в каждой ячейке будет один символ потом это всё как то надо конвертировать и double но как ??
типа этого что то:
имеем число в .txt файле 12.5 (размер числа всегда будет 4 байта)
uint8_t test[3]; // буфер для считанных побайтно байтов из файла
test[0] =1; // побайтно считываем из файла и заполняем буфер
test[1] =2;
test[2] =.;
test[3] =5;

в результате в буфере будут символы 12.5 и теперь надо этот буфер конвертировать в переменную типа double и передать её в функцию. но как конвертировать ???? :dont_know:
также у меня есть функция uint32_t Read32(offset); с помощь этой функции можно прочитать сразу 4 байта (32 бита)

то есть так получится:
uint32_t test = Read32(offset); и всё в переменной uint32_t test окажутся 4 байта ASCII считанных символов 12.5 1(0x31) 2(0x32) .(0x2e) 5(0x35)
и теперь её тоже надо конвертировать в тип double для передачи в функцию. но как ??
явное преобразование (double)test; работать мне кажется не будет, просто число передаться этих ASCII символов и всё а нам надо смысл что это именно переменная double передать.
Расскажите, научите как умные люди и коты поступают в данной ситуации? Быстродействие кода не критично так как будет всего один раз вычисляться при ВКЛ контроллера и всё. Может спринтф задействовать как нибуть?
работаю в Atmel studio 6
Последний раз редактировалось Ziper009 Вт фев 10, 2015 21:36:30, всего редактировалось 2 раза.
Опытный кот
Аватара пользователя
Сообщения: 781
Зарегистрирован: Сб июн 01, 2013 22:24:21
Откуда: ПФО

Сообщение alex_ »

От блин, ну что я тебе скажу: как мне кажется опытние люди стараются вообще избавиться от запятой это и обрабатывать проще и передавать, ну и ошибка меньше, короче можно твои 12.5 изобразить вот так 1250(запятая в уме), тут во первых точность у тебя будет до сотых(если хочешь можно и тысячные пририсовать, ты художник тебе решать :)) ) во вторых всё число влезет в интежер а именно всего 2 байта, да и контроллер быстрее ворочает инежер в отличае от double :))
c2n писал(а): Этот результат интерпретируется так: GND = 0x00(0) VCC = 0x03FF(1023) если напряжение преобразования между, то и значение буде соответствовать этому "между", для 2,5В скорее всего будет 0xFF (255)

предположим мы оцифровываем значение 3,2В = 0x28F(655)[0b1010001111]
Уважаемый это как: 2.5=255 а 3.2=655?, напряжение выросло на 30% а показания АЦП аж на 250%, надо учить начинающих а не в водить их в ступор со словами как же оно могло так получиться? :dont_know:
c2n писал(а): Соответственно во втором случае значением ADCL можно пренебречь, т.к. он отрабатывает "сотые" доли напряжения.
Ну тут каждый решает сам, я использовал все разряды до последнего так как мне нужна была точность :)
c2n писал(а): Так же стоит все это дело пропустить через операционный усилитель, с понижающим коэффициентом (у комрада Di-Halt отлично описано!, погуглите:)))).
Если вы пропускаете сигнал на оцифровку через ОУ, то далее этот сигнал практически не искажается. Если вы пропускаете сигнал через резестивный делитель, то есть шанс, что после делителя сигнал будет уже совсем не таким.
Схему товарища Di-Halt я не видел, но как я писал выше: я добился внушительных результатьв и без операционика почти на одном делителе(не одного разряда не откинул), добился я этого благодаря: интегрирующей цепочке после делителя(резистор конденсатор), и раздельному питанию контролера от АЦП и опорного напряжения, никогда их не садите на одну шину иначе полную хрень получите в результатах.
Родился
Сообщения: 17
Зарегистрирован: Пн фев 09, 2015 21:19:07

Сообщение Ziper009 »

короче можно твои 12.5 изобразить вот так 1250(запятая в уме
а как это сделать? :dont_know:
Я вот что надумал так сделать.
uint8_t test[4]; // буфер для считанных побайтно байтов из файла
test[0] =0х31-0x30; // побайтно считываем из файла ASCII символы цифр, вычитаем 0x30 чтоб остались
test[1] =0х32-0x30; // только цифры и заполняем буфер
test[2] =0x2E; // "."
test[3] =0х35-0x30;

фактически получится в буфере это:
test[0] =1;
test[1] =2;
test[2] ='.' // точка вообще не нужна так для порядка поставил её
test[3] =5;
как теперь сложить ячейки буфера test[0]. test[1]. test[3] чтоб получить в итоге переменную вида
uint8_t number = 125; (0x7D) ??? число 125 можно передать в функцию а уже в функции получить double таким образом 125/10=12.5 :) как раз то что нам нужно.
Таким образом можно будет любое число передать(12.5в ==125 это MAX больше не надо мне) и назад разобрать.
я так понимаю надо взять переменную типа uint8_t она способна передать число 255 ==25.5в мне это даже с перебором будет, и над этой переменной пошаманить побитовыми операциями. Чтоб каждый из ячеек test[0]. test[1]. test[3] пошаманил над переменной в отдельности и мы получили в ней число 125 я так думаю что побитывые операции на это способны только такая проблема что я сними особе дел то не имел :dont_know: и не знаю как с помощью них реализовать задуманное?? Или может другой способ какой нибуть есть??
Вымогатель припоя
Сообщения: 574
Зарегистрирован: Вт ноя 02, 2010 17:46:37

Сообщение pokk »

Смутно понял ваш вопрос но сдаётся вы об этом
100*1+2*10+1*5=125
c2n
Сверлит текстолит когтями
Аватара пользователя
Сообщения: 1193
Зарегистрирован: Ср июл 25, 2012 21:40:09
Откуда: Самара

Сообщение c2n »

c2n писал(а): для 2,5В скорее всего будет 0xFF (255)
конечно! 1023/2 = 511.5
упечатался... будет не 0xFF(255)а 0x200(512) ну или 0x1FF(511)
интегрирующей цепочке после делителя(резистор конденсатор), и раздельному питанию контролера от АЦП и опорного напряжения, никогда их не садите на одну шину иначе полную хрень получите в результатах.
ну или так :)))))

тут уже дело художника.

как теперь сложить ячейки буфера test[0]. test[1]. test[3]
ResultNum = (test[0]*100)+(test[1]*10)+(test[0]) // типа так????

Теперь дальше...
побитовый сдвиг (на один байт) либо делит число на 2, либо умножает на те же 2. тут думайте сами :)
подскажу, что сдвиг на 2 байта поделит или умножит уже на 4....
на 8, 16, 32, 64, 128, 256, 512, 1024 думаю расписывать не надо ;)

и еще. Можно извратиться... целую часть числа держать в одной переменной, дробную в другой. // вопсчем в памяти они так и хранятся и поэтому double жрет 4(и более) байта...
Контактная информация:
Друг Кота
Аватара пользователя
Сообщения: 6337
Зарегистрирован: Вт апр 24, 2007 07:45:40
Откуда: Minsk

Сообщение Jack_A »

c2n писал(а):
что сдвиг на 2 байта поделит или умножит уже на 4....
Однако ... Думаю, что "сдвиг на 2 байта" изменит результат ( * / ) в 65536 раз :)
целую часть числа держать в одной переменной, дробную в другой. // вопсчем в памяти они так и хранятся и поэтому double жрет 4(и более) байта..
В общем, числа с плавающей запятой хранятся совсем не так, а в виде [знак][порядок][мантисса], но это тема совсем для другой песни. https://ru.wikipedia.org/wiki/Число_с_плавающей_запятой
c2n
Сверлит текстолит когтями
Аватара пользователя
Сообщения: 1193
Зарегистрирован: Ср июл 25, 2012 21:40:09
Откуда: Самара

Сообщение c2n »

Jack_A писал(а): Однако ... Думаю, что "сдвиг на 2 байта" изменит результат ( * / ) в 65536 раз :)
Конечно БИТ я имел ввиду :facepalm: я чегой то рассеяный на этой неделе.

а теперь на пальцах покажу. // вдруг ТС интересно :)

исходное число 0b101 = 5
сдвигаем на 1 влево: 0b1010 = 10 (5*2)
сдвигаем на 2 влево: 0b10100 = 20 (5*4)

возьмем исходное число 0b110 = 6
влево на 1: 0b1100 = 12 (6*2)
влево на 3: 0b110000 = 48 (6*8)

продолжаем...
Исходное 0b10101 = 21.
вправо на 1: 0b1010 = 10 (21/2)
Вправо на 4:0b1 = 1 (21/16)

В С/С++ для этого есть опиратыр: >> и << стрелки указывают направление сдвига. Формат оператора такой: [ЗНАЧЕНИЕ] << [на сколько бит двинуть] при этом помним, что младший бит справа, старший слева. после сдвига в освободившемся месте - нули!.

ну про хранение чисел, тут да. Совсем другая песня :) но принцип такой, что знак, порядок и мантисса крутятся как минимум в 4х байтах. Если ТС интересно, думаю он разберется :)

так же я ХЗ владеет ли ТС побитовыми операциями "И, ИЛИ, НЕ"... Если интересно - могу показать :)
Контактная информация:
Родился
Сообщения: 17
Зарегистрирован: Пн фев 09, 2015 21:19:07

Сообщение Ziper009 »

pokk писал(а):Смутно понял ваш вопрос но сдаётся вы об этом
100*1+2*10+1*5=125
c2n писал(а):
как теперь сложить ячейки буфера test[0]. test[1]. test[3]
ResultNum = (test[0]*100)+(test[1]*10)+(test[0]) // типа так????

Теперь дальше...
побитовый сдвиг (на один байт) либо делит число на 2, либо умножает на те же 2. тут думайте сами :)
подскажу, что сдвиг на 2 байта поделит или умножит уже на 4....
на 8, 16, 32, 64, 128, 256, 512, 1024 думаю расписывать не надо ;)

и еще. Можно извратиться... целую часть числа держать в одной переменной, дробную в другой. // вобщем в памяти они так и хранятся и поэтому double жрет 4(и более) байта...
Спасибо. я сам вчера думал, думал, часов до двух ночи думал. потом решил что утро ночи мудренее, ВЫКЛ комп и пошёл спать собираться. :) Лёг на диванчик и стал смотреть телек с приглушённым светом в зале, я там всегда к морфею в гости собираюсь. Смотреть я стал разумеется не канал НТВ и ПЕРЕЦ так как убеждён что эти каналы созданы для определённой категории людей к которой а себя не отношу. :)) так что то смотрел не напрягающие и думал. И тут как раз пришла ко мне идея как реализовать задуманное :idea: . Я сразу на бумажке записал чтоб не забыть а когда проснулся и ВКЛ комп. увидел что, мысли наши совпадают в реализации. Распишу поподробней что придумал я, может пригодится кому нибуть.


uint8_t test[4]; // буфер для считанных побайтно байтов из файла (ASCII символы)
#define ASCII_OFFSET 0x30 // смещение ASCII символа от реального значения
test[0] = '1' - ASCII_OFFSET; // побайтно считываем из файла ASCII символы цифр, вычитаем 0x30
test[1] = '1' - ASCII_OFFSET;
test[2] = '.' ; // точку не обрабатываем так как она нам не нужна
test[3] = '5' - ASCII_OFFSET;

теперь в буфере будут реальные цифры а не ASCII коды:
test[0] == 1;
test[1] == 1;
test[2] == '.' ; // точку не обрабатываем так как она нам не нужна
test[3] == 5;

собираем нужные разряды в переменную типа uint8_t:
uint8_t ResultNum;
if(test[0]==1 || test[0]==2) ResultNum = test[0] * 100; // так как тип uint8_t мах 255
if(test[1]) ResultNum += test[1] * 10; // чтоб не умножать на ноль
if(test[3]) ResultNum += test[3] ; // чтоб не прибавлять ноль

теперь в переменной будет нужное нам число:
ResultNum == 115; // 115 это считанное и преобразованное из файла напряжение 11.5 в.

собственно сама функция преобразования напряжения в код АЦП:
uint8_t VoltageToADC(uint_8 voltage){
#define FACTOR_RESISTOR 11 // коэффициент делителя
#define REFERENCE_VOLTAGE 1.1 // образцовое напряжение
#define ADC_RESOLUTION 256 // разрешение ацп
double voltage_double = (double)((double)(voltage) / 10); // 115 преобразуем в 11.5

// расчёт кода АЦП по известному напряжению
return (uint8_t)((uint16_t)(voltage_double * ADC_RESOLUTION /(REFERENCE_VOLTAGE * FACTOR_RESISTOR)));
}

функция работает, проверял в протеусе в отладчике. :) Даже компилятор Atmel Studio 6 не ругался на точку (думал что тип double где нибуть в настройках надо подключать или в make)

uint8_t Result;
Result = VoltageToADC(115); // 11.5 в Result равен код АЦП 243
Result = VoltageToADC(120); // 12.0 в Result равен код АЦП 253

Всем большов, человеческое спасибо ещё раз. :)
Ответить

Вернуться в «AVR»