Страница 1 из 1

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

Добавлено: Пн фев 09, 2015 21:45:36
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в. по какой формуле можно рассчитать какой код будет в АЦП контролера?? Помогите очень прошу нигде не нашёл про это. :(

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

Добавлено: Вт фев 10, 2015 07:49:26
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 бит.

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

Добавлено: Вт фев 10, 2015 09:40:37
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 значение которое выдаст АЦП. :)

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

Добавлено: Вт фев 10, 2015 09:54:57
Jack_A
Если нужно точное равенство, то это дохлый номер, потому что 1 бит разницы уже даст несовпадение. Программисты знают, что для действительных чисел срвнение if ( a == b ) некорректно. Другое дело, если есть допуск на несовпадение или измеряемое напряжение монотонно возрастающее - убывающее. Тогда факт пересечения изменяющимся уровнем заданного значения будет иметь место.

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

Добавлено: Вт фев 10, 2015 13:52:06
Ziper009
Ураа спасибо всем большое что помогли. учту все предложенные пояснения. :)

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

Добавлено: Вт фев 10, 2015 14:24:57
АлександрЛ
Engineer_Keen писал(а):1) если речь идет о внутреннем опорном напряжении AVR, то это 1.1 ± 0.1.
1,1 вольта "внутреннее опорное" - это, видимо, только для ATTiny (ATTiny13, например, у Tiny2313 вообще нат АЦП) , для ATMega я видел только 2,56 вольта.

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

Добавлено: Вт фев 10, 2015 14:30:11
Engineer_Keen
АлександрЛ писал(а):1,1 вольта "внутреннее опорное" - это, видимо, только для ATTiny (ATTiny13, например, у Tiny2313 вообще нет АЦП) , для ATMega я видел только 2,56 вольта.
Да, просто нарисовал такое, какое в последнем открытом даташите было, но суть была не в этом, а в том, что хреновое оно...

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

Добавлено: Вт фев 10, 2015 16:06:07
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 отлично описано!, погуглите:)))).
Если вы пропускаете сигнал на оцифровку через ОУ, то далее этот сигнал практически не искажается. Если вы пропускаете сигнал через резестивный делитель, то есть шанс, что после делителя сигнал будет уже совсем не таким.

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

Добавлено: Вт фев 10, 2015 19:06:45
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

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

Добавлено: Вт фев 10, 2015 19:55:36
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 я не видел, но как я писал выше: я добился внушительных результатьв и без операционика почти на одном делителе(не одного разряда не откинул), добился я этого благодаря: интегрирующей цепочке после делителя(резистор конденсатор), и раздельному питанию контролера от АЦП и опорного напряжения, никогда их не садите на одну шину иначе полную хрень получите в результатах.

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

Добавлено: Вт фев 10, 2015 23:34:45
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: и не знаю как с помощью них реализовать задуманное?? Или может другой способ какой нибуть есть??

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

Добавлено: Ср фев 11, 2015 07:04:44
pokk
Смутно понял ваш вопрос но сдаётся вы об этом
100*1+2*10+1*5=125

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

Добавлено: Ср фев 11, 2015 07:42:39
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(и более) байта...

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

Добавлено: Ср фев 11, 2015 11:20:32
Jack_A
c2n писал(а):
что сдвиг на 2 байта поделит или умножит уже на 4....
Однако ... Думаю, что "сдвиг на 2 байта" изменит результат ( * / ) в 65536 раз :)
целую часть числа держать в одной переменной, дробную в другой. // вопсчем в памяти они так и хранятся и поэтому double жрет 4(и более) байта..
В общем, числа с плавающей запятой хранятся совсем не так, а в виде [знак][порядок][мантисса], но это тема совсем для другой песни. https://ru.wikipedia.org/wiki/Число_с_плавающей_запятой

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

Добавлено: Ср фев 11, 2015 13:21:51
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х байтах. Если ТС интересно, думаю он разберется :)

так же я ХЗ владеет ли ТС побитовыми операциями "И, ИЛИ, НЕ"... Если интересно - могу показать :)

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

Добавлено: Ср фев 11, 2015 13:44:31
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

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