Простой код слишком медленный :-(
- Войдите на сайт для отправки комментариев
Пнд, 23/02/2015 - 13:19
Приветствую!
Вожусь с вольтметром. Сейчас использую код с корректировкой внешнего опорного напряжения по внутреннему. Код в принципе уже классический, но почему-то безмерно тормозит. Один полный проход получается около секунды. Это переделанный фрагмент под отладочную консоль:
// резисторы делителя напряжения const float r1 = 99250; // 100K const float r2 = 9930; // 10K // эту константу (typVbg) необходимо откалибровать индивидуально float typVbg = 1.069; // 1.0 -- 1.2 float Vcc = 0.0; float MaxVoltage = 0.0; #define A_PIN 1 #define COUNT 5 int i; float curVoltage; void setup() { Serial.begin(9600); Serial.println("---"); delay(1000); // определение внутреннего опорного напряжения analogReference(DEFAULT); // DEFAULT INTERNAL использовать Vcc как AREF delay(100); Vcc = readVcc(); MaxVoltage = Vcc / (r2 / (r1 + r2)); analogWrite(A_PIN, 0); Serial.print("Vcc = "); Serial.println(Vcc); Serial.print("Max V. = "); Serial.println( MaxVoltage ); Serial.println("---"); } void loop() { Vcc = readVcc(); // считываем точное напряжение с A1, где находиться вольтметр с делителем напряжения curVoltage = 0.0; for (i = 0; i < COUNT; i++) { curVoltage = curVoltage + analogRead(A_PIN); delay(10); } curVoltage = curVoltage / COUNT; float v = (curVoltage * Vcc) / 1024.0; float v2 = v / (r2 / (r1 + r2)); Serial.print("V = "); Serial.println(v2); analogWrite(A_PIN, 0); delay(500); } /**************************************************************************** * Функции ****************************************************************************/ float readVcc() { byte i; float result = 0.0; float tmp = 0.0; for (i = 0; i < 5; i++) { // works on an Arduino 168 or 328 ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);//Регистр ADMUX (регистр управления мультиплексором) delay(3); // Wait for Vref to settle ADCSRA |= _BV(ADSC); // Запуск преобразования (в режиме однократного преобразования) //0 – преобразование завершено //1 – начать преобразование while (bit_is_set(ADCSRA,ADSC)); // ждем, пока не будет сброшен ADSC uint8_t low = ADCL; // сперва читаем ADCL, затем ADCH uint8_t high = ADCH; tmp = (high<<8) | low; tmp = (typVbg * 1023.0) / tmp; result = result + tmp; delay(5); } result = result / 5; return result; }
Где происходят эти дикие тормоза и как ускориться ?
arny, в принципе весь ваш скетч -полный хаос. Куча бесмысленных команд, начиная прямо со 2 строки. -не оптимальное использование типа данных, самый трэш это 47-48 строка. такие вещи в крайнем случае рассчитываются один раз в сетапе, а в идеале на калькуляторе, и оперируют в дальнейшем уже только одним множителем. Зачем то пять отсчётов беретё в loop, ещё 5 отчётов в readvcc. Смысл? А 53 строка что делает? Ну про пол-секунды в 54 строке и так понятно. И конечно никаких корретировок тут и в помине нет.
Т.е. вы не видите, где возникает задержка в 1 секунду?
Каким образом строку 47 можно рассчитать в setup'е, если измеряемое напряжение меняется?
Пол секунды к делу не относится, т.к. запускается всего один раз.
На счет вещественных переменных для резисторов это да, но думаю это не суть. Коэффициент делителя не прописан заранее, т.к. пока все в процессе. Прошу не судить строго, я новичек. Это мой 5-й скетч.
5 прогонов, т.к. один никогда не дает более-менее точного значения измеренного напряжения. В рабочем варианте вообще только усреднение 30 проходов дают достоверный результат.
Получаю усредненное значение фактического напряжения питания (опорное) и на его основе измеряю входное напряжение как среднее между несколькими измерениями.
Если у вас есть ссылка на рабочий хороший вольтмерт, буду только рад. В яндексе не забанен, поиском пользоваться умею. Практически все скетчи по вольтметрам примерно так же работают, разве что не используют корректировку опорного напряжения.
А если с помощью millis посмотреть время выполнения куска кода с 38 по 48 строку, сколько покажет?
Barny, сейчас к сожалению нет времени на подробности, обратите ж внимание, полсекунды ваши из 54 строки стоят в loop, это главный тормоз.
Вот это и было в точку :-).
Не помню зачем я их туда поставил. Когда попросили измерить время обработки, тогда заметил эти лишние пол секунды. Сейчас скетч работает быстро.
Указание коэффициента делителя вместо его рассчета вообще не повлияло на время выполнения.
Кстати, не могу понять как быть с такой сторокой
float v = (curVoltage * Vcc) / 1023.0;
АЦП в Atmega328 10-ти разрядный, т.е. от 0 до 1023, но это 1024 значения... Совсем запутался, на сколько же тогда надо делить - на 1024 или на 1023?
Программист я не очень, но: если попробовть вычислять
float v2 = (curVoltage * Vcc) *const
где const = (r1 + r2)/( 1024.0 * r2) и этот расчёт сделать один раз в setup?
Это уже перетянул в setup. До сих пор не понятно на что делить. На 1024 или на 1023. В сети оба варианта гуляют. Максимальное значение ЦПУ - 1023, а всего значений 1024....
Ну если аналогрид выдает при 5 вольтах (ну или при максимальном) значение 1023 получается обычная пропорция и соответственно делить надо на 1023 (как и в официальном скетче), а то что в инете много копи-паста это понятно.
Не могу объяснить почему, но думаю что в стоках 47 и 83 должно стоять одинаковое число (или 1024, или 1023). Ещё. Если в readVcc() взять i=8? Ведь деление на 8 - это сдвиг вправо на три разряда. Возможно компилятор это и будет использовать.
А как быть с float?
Если обьявить резисторы как int, ничего не работает. Надо как то временно их в float перегнать...
Уже поставил везде 1023.
В readVcc() i - это только счетчик проходов измерения внешнего опорного напряжения, а сдвиг используется для получения результата сравнения. На сколько я понял из документации, процессор 8 разрядный, а АЦП 10-ти, поэтому результат сранится сразу в 2-х регистрах. В одном целиком, а во втором только часть.
Как разберемся с преобразованием типов "на лету" покажу рабочий скетч
А чем флот не устраивает? тем что две переменных займут на 4 байта больше? И что значит не работает когда в int переделываете, прям совсем ничего не работает? или как?
Целочисленные почему-то отказываются делиться :-)
Пдключил экранчик. Получился вот такой рабочий скетч вольтметра с самокорректировкой уровня входного напряжения питания. Внутреннее опорное напряжение подбирал под результат. Подключил на вход блок питания от ноутбука (измеренное 19.35В) и перебирая значения опорного в диаппазоне от 1 до 1.2В подобрал такое, при котором результат получился 19.35В Эту процедуру нужно делать для каждого чипа один раз.
Ссылка на первоисточник.
Отказываются делиться? На пикет чтоли вышли с транспарантами и лозунгами"свободу float долой int" :)
Как минимум 99250 не влезет в инт только в лонг соответственно r1 делаете лонг а в коде пишите MaxVoltage = Vcc / ((float)r2/(r1+r2)); и всё будет делиться. Но сомневаюсь, что маломальски выгоду увидите
Спасибо за совет, значит оставлю как есть :-). Просто не очень люблю деление на float. Не всегда 6/3=2 во флоате. Последний раз серьезно кодил 12 лет назад, почти все выветрилось из головы.
А что за индикатор? Впрочем SSD1306.
Графический OLED дистлей 128х64, Китайский. Без reset на этой библиотеке не заработал. На Adafruit пошло нормально.
Barny, второй ваш скетч уже более-менее :) 5 и 6 строки всё же лучше поменять на #define r1 99250L // 100K
#define r2 9930 // 10K И 35-ю чуть подправить Dev = (float)r2 / (r1 + r2); А можно в ней и сразу умножить на 1023, что б не делать эту операцию в loop каждый раз. Dev = (float)(r2*1023) / (r1 + r2); Соотвесно в 49й строке сразу делить на Dev, float v = (curVoltage * Vcc) / Dev; а 50-ю ликвидировать
А корректировка множителя, рассчитанная через "секретный вольтметр" -вещь весьма и весьма неоднозначная. С одной стороны вы измеряете фактическое опорное напряжение, а с другой стороны суммируете погрешность измерений референса и погрешность основного измерения , что вызовет только ещё большую болтанку результата счёта АЦП. ЛУчше просто измерить мультиметром напряжение шины+5, и сделать его константой. -Это будет на порядок точнее измерения контроллером. А ещё лучше перейти на reference internal, и не иметь лишних проблем. Впрочем если у вас на индикаторе одна цифра после запятой вам вообще все эти нюансы будут безразличны)
http://arduino.ru/forum/obshchii/schitat-voltazh-s-analogovogo-sensora
про 1023, 1024, удавов и попугаев :)
http://arduino.ru/forum/apparatnye-voprosy/problemy-s-pitaniem?page=1
:)
Dev = (float)(r2*1023) / (r1 + r2); - вроде как тут ошибка :-)
лучшеееее давать весь код (свёрнутый ) со ссылкой на строку вас тревожащею :) так проще.....
Немного еще ускорился и подчистил код.
Рабочий вариант выглядит так (с учетом комментария #29):
а типерь вопроса нету..... код устраивает ? какие вопросы ? :)
улучшить ? нано брать не среднее из выборки, а среднеВзвешанное !
Ой... я совсем не хочу беспокоить именно вас :-)
прикольно..... ответили - уже спалились :)
"Сынок, письмо в котором ты просишь денег - мы не получали " - :)
...тут и покруче ваших "ммммм...." слышал моя, "чявоужь там" - говори или спроси у папы Путина..... оне в ардуинах похлестчее нашего....
Ой... я совсем не хочу беспокоить именно вас :-)
изменять сообщение с целью изменить мнение - плохой вариант, не "по чесноку" :)
.....забыли-проехали :)
Barny, вы всё время вставляете чужие строки в скетч не разобрав их полностью. Вот например сокральный смысл строк 3..9 и 48-53 -скинуть пару младших бит, всю эту красивую большую конструкцию можно заменить на одну команду : ADCSRA&=0xFC; Это вообще то прескалер (делитель) по-умолчанию тактовая частота проца делится на 128, а эта команда меняет делитель на 16. Только это актуально для непрерывной работы, когда АЦП-шка строчит автоматом (поднятый бит ADATE), если читать через analogRead -это мёртвому припарка, скорость всё равно будет меньше самой маленькой. Т.е можете выкинуть эти строки вообще. К тому же (ещё повнимательнее посмотрел) вы этот дефайн даже и не запускаете, для запуска нужно было в шапке написать #define FASTADC 1
15 строка -число , которое занимает два байта -вы суёте в 4 байта. Конечно не жалко)) , но смысл? Из той же области 43-44 строка -конечно не вредят, но пользы никакой, Вы записываете значение, которое уже стоит по умолчанию в регистре. 103-106 строки можно выкинуть. Формально всё правильно, но можно написать в конце 107 строки /ADC вместо /tmp; будет так же работать и ещё 2 байта экономии) И последнее -пауза в 109 строке -кого ждёт?)
Спасибо за развернутый комментарий. Убрал лишние ускорители, т.к. скорость и точность очень даже устраивает. Теперь 30% проекта сделано :-).
Код выше подправил. Может кому пригодится.
Осталось собрать по такому же принципу остальные датчики.
Такой вопрос - в в чем физическая разница в использовании вн. и внешн. опорных напряжений и какое больше подходит для своих ситуаций ?
На ум приходят различные внешние электропомехи...
Barny, разница в помехах ,в температурной зависимости. Использовать напряжение питания МК в качестве опорного -худший вариант для вольтметра, я вам об этом уже сказал. Т.к.оно гуляет в зависимости от нагрузки и трясётся с тактовой частотой контроллера. Внутреннее опорное -подойдёт почти для всех применений, опорное от внешнего источника -имеет смысл, если использовать прецизионный ион, с параметрами лучшеми чем у внутреннего, или преследовать иные цели. Вам есть смысл перейти на внутреннее опорное, а если нужно сохранить диапазон до 50 вольт -то потребуется поменять всёго лишь один резистор. Зато не нужно будет измерять реальное напряжение, т.к. оно будет одинаковым при любом напряжении питания.
Надо попробовать. Получается под 1.1В безопасно лучше использовать R2=2кОм верно ?
Величина фактического внутреннего опорного известна.
Barny, можно 2кОм, тогда при входном 55вольт на АЦП придёт 1,09 вольт. Можно ещё упростить расчёт итогового напряжения. Сделайте делитель , подайте на вход известное напряжение, (желательно от аккумулятора, что б не было помех) например взяли аккум, измерили напряжение мультиметром, пусть будет 12,78 вольт. Смотрите сколько выдаёт чистый analogRead, измеря это напряжение - пусть для примера будет 217 единиц. делите на калькуляторе 217/12,78 =16,979. Это и будет множитель (кол-во попугаев на 1 вольт) . Перед выводом на дисплей делаете v= результ_ацп / 16.979 и всё. Это решение в совокупности с переходом на внутреннний ИОН позволит вам существенно облегчить скетч :)
Настраиваю датчик тока и столкнулся с такой ситуацией - если использовать внешнее опорное напряжение, то все хорошо, но если внутреннее, то начинают прыгать показания амперметра. Получается, при измерении мощности нужно использовать одну микросхему ваттметра, либо все-же использовать внешнее общее опорное напряжение. Вывод верный ?
Barny, что-то вы неправильно включили. По идее должно быть с точностью до наоборот. Тестировать измерение тока желательно на малошумящем источнике тока, хотя бы батарейка с резистором.
Ок, может у меня где-то провал по пониманию процесса.
Использую датчик тока ACS712T. Все подключено к USB. "Ноль" на датчике - 2,46В. Ардуино соответственно 511-514 уровней АЦП. Подключаю к внешнему питанию, на датчике 2,62В. АЦП выдает 510 - 512. Если использовать внутренний опорный источник, то "ноль" сразу скаканет.
Barny, Думаю объяснение тут может быть таким: если вы питаете acs712 от того -же, от чего и мк, то при измерении с reference=default опорное напряжение АЦП колеблется в точном соответствии с выходным напряжением acs712, а следовательно относительное измерение производится точно. А когда ацп считает от опорного 1,1в, то ловит все броски напряжения, которые пролезают через питание acs712. Тут 2 варианта, -либо использовать reference=default, как естественную компенсацию помех, либо питать acs712 от отдельного малошумящего источника.
Вот именно по этому я и максимально возможно калибраю значение внешнего источника питания. К сожалению очень стабильным его сделать нельзя, т.к. это всего лишь DC"шка бортового питания электровелосипеда у которой несколько потребилелей. Надеюсь в рамках этой ветки написать действующий скеч амперметра, базирующийся на слежении опорного напряжения.
Не могу понять, почему в регистре ADC хранится значение, считываемое с входа датчика тока ?
Я думал, там дискретное значение опорного напряжения...
Как же мне узнать цифроую величину опорного напряжения, что бы вывести на "ноль" показания датчика тока ?
Тут я уже что-то перестаю понимать в чём проблема. Если референс=default, и acs712 питается от того же, что и ардуина -то середина напряжения будет 512 попугаев ацп. Только умножать нужно будет на статический множитель, а не на вычисляемый Vcc. На референс =internal в силу специфики датчика тока нет смысла переходить, иначе придётся усложнять схему.
Посмотрите функцию = readVcc(). На сколько я понял, там берется ненагруженный пин и с него считывается величина Vcc. Вот только не понятно, какой это пин и как его назначить по своему усмотрению.
Barny, это не совсем пин.. Вообще ацп в меге один. Что-бы обслуживать все свои входы имеется мультиплексор, он конфигурируется регистром ADMUX, его младшие пол-байта задают к чему собссно подключить ацп. Есть вариант подключиться к 8 реальным входам, к внутреннему ИОН, к датчику температуры или соединить с землёй.
Про один АЦП и и мультиплексор я в курсе. В данной функции входные пины ни как не влияют на его работу?
И как можно получить величину, равную половине этого Vcc в попугаях?
Barny, в момент подключения ацп к ион входные пины само-собой никак не влияют. По второму вопросу не понял... Половина вашего Vcc -это всегда 512 попугаев.
ОК, один вопрос остался.
Вот формулы для рассчета тока:
Это рассчет для 5 амперного датчика (чувствительность 185 мВ/А) с опорным 5В. У меня 30А датчик. Вроде как чувствительность 66 мВ/A. Очень смущает число 1024... Его точно не надо заменить на 2013 ?
Barny, 1023 или 1024 это вопрос философский, из разряда вопросов что первичнее курица или яйцо? Какой год считать началом 21 века - 2000-й или 2001-й :) Я лично предпочитаю 1023, некоторые наши уважаемые форумчане предпочитают 1024 , в #20 посте вам давали ссылку на бурные дебаты на эту тему двух наших уважаемых товарищей :)