analogRead через прерывания?

adrusha
Offline
Зарегистрирован: 02.03.2015

Передо мной стоит задача чтения значения с порта, например A4. Сейчас это реализовано так:

int val = analogRead(A4);
Serial.println(val);

И все хорошо, и мы получаем нужное нам значение. Но в программе где идет это считывание это немного медленно, затем я изучил это: https://habr.com/post/141442/ — тоже хорошо, но там не раскрыт мой главный вопрос на сегодня.

Изучая регистры DDRD, PORTD, PIND мы можем установить тип input/output и установить состояние low/high на нужном нам пине, а так же можем считать эти состояния при установленном типе input, где если на пине будет 5V это будет HIGH. Но задача не считывать значения Digital пинов, а считать значение Analog пинов, как это делает функция analogRead.

Сейчас я указываю через регистр DDRС состояние input вот так: DDRC &= ~(1<<PC4)

Затем я пытаюсь считать с регистра PINС данные так: int8_t pin = (PINC & (1<<PC4))

И получаю либо 0 либо 1 в зависимости от напряжения на аналоговом пине, выше 2.5 или ниже. А как получить, например, тоже значение как получает analogRead 0-1023 ?

Поискав в сети, ничего не нашел и прошу помощи куда копать. Конкретно моя задача состоит в том, чтоб внедрить в CNC шилд датчик температуры, изначально в исходниках нет ни единого упоминания об этом по этому я принялся написать этот код. Сейчас думаю что это должно как-то решаться прерыванием, считываем биты в буфер, затем переводим в нужные значения.. но вот тут я и встал колом, как это сделать :-) А может и вообще не так надо делать, а как-то иначе...

  •  
inspiritus
Offline
Зарегистрирован: 17.12.2012

А что такое и для чего необходимо по-Вашему мнению прерывание?

xDriver
xDriver аватар
Offline
Зарегистрирован: 14.08.2015

так как я сегодня добрый, привожу код analogRead из файла 

с:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\wiring_analog.c

int analogRead(uint8_t pin)
{
	uint8_t low, high;

#if defined(analogPinToChannel)
#if defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#endif
	pin = analogPinToChannel(pin);
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
	if (pin >= 54) pin -= 54; // allow for channel or pin numbers
#elif defined(__AVR_ATmega32U4__)
	if (pin >= 18) pin -= 18; // allow for channel or pin numbers
#elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__)
	if (pin >= 24) pin -= 24; // allow for channel or pin numbers
#else
	if (pin >= 14) pin -= 14; // allow for channel or pin numbers
#endif

#if defined(ADCSRB) && defined(MUX5)
	// the MUX5 bit of ADCSRB selects whether we're reading from channels
	// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
	ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5);
#endif
  
	// set the analog reference (high two bits of ADMUX) and select the
	// channel (low 4 bits).  this also sets ADLAR (left-adjust result)
	// to 0 (the default).
#if defined(ADMUX)
#if defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
	ADMUX = (analog_reference << 4) | (pin & 0x07);
#else
	ADMUX = (analog_reference << 6) | (pin & 0x07);
#endif
#endif

	// without a delay, we seem to read from the wrong channel
	//delay(1);

#if defined(ADCSRA) && defined(ADCL)
	// start the conversion
	sbi(ADCSRA, ADSC);

	// ADSC is cleared when the conversion finishes
	while (bit_is_set(ADCSRA, ADSC));

	// we have to read ADCL first; doing so locks both ADCL
	// and ADCH until ADCH is read.  reading ADCL second would
	// cause the results of each conversion to be discarded,
	// as ADCL and ADCH would be locked when it completed.
	low  = ADCL;
	high = ADCH;
#else
	// we dont have an ADC, return 0
	low  = 0;
	high = 0;
#endif

	// combine the two bytes
	return (high << 8) | low;
} 

 

nik182
Offline
Зарегистрирован: 04.05.2015

Этож без прерывания.
Первая ссылка по запросу аvr adc interrupt дает полную программу ацп на прерываниях
http://www.avr-tutorials.com/adc/utilizing-avr-adc-interrupt-feature
А если добавить к запросу слово arduino....

xDriver
xDriver аватар
Offline
Зарегистрирован: 14.08.2015

nik182 пишет:
Этож без прерывания.

я ответил на четко сформулированный вопрос, не более и не мение

adrusha пишет:

И получаю либо 0 либо 1 в зависимости от напряжения на аналоговом пине, выше 2.5 или ниже. А как получить, например, тоже значение как получает analogRead 0-1023 ?

название ветки тоже читал и подозревал, что хочет автор.

но выложив полный расклад, я бы "нарушил уставы форума"...

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

adrusha пишет:
затем я изучил это: https://habr.com/post/141442/

Вы не первый. Там у автора хорошая травка была:

DigitalWrite() — 1800мкс
DigitalRead() — 1900мкс

adrusha пишет:
Поискав в сети, ничего не нашел и прошу помощи куда копать.

Нельзя ли поточнее пнять для чего Вам это надо? Через прерывания? Чтобы отсчёты делать со строго определённой частотой? Или по какому-то событию? Для чего?

Если Вы просто хотите ускорить analogRead, то по Вашей ссылке первый же пример кода это делает. Самый первый. Показывать правда будет "цену на овёс", но зато быстро. А ускорить analogRead без потери точности не получится. Она аппаратно такая.

adrusha
Offline
Зарегистрирован: 02.03.2015

В программе обрабатываюся Gcode данные отправляемые по serial порту и очень важно чтоб не было задержек в месте чтения analogRead. Мне понравился способ использующий константы тем, что они достаточно быстро меняют состояние пинов, раз константы быстрее digitalWrite, я предположил что если они быстрее и analogRead.

Можете подсказать, использование кода с хабр хорошее решение или плохое?

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

adrusha пишет:

Можете подсказать, использование кода с хабр хорошее решение или плохое?

Не бывает абсолютно хороших решений (абсолютно плохие - бывают). Любое решение чем-то хорошо, чем-то плохо.

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

b707
Offline
Зарегистрирован: 26.05.2017

adrusha пишет:

В программе обрабатываюся Gcode данные отправляемые по serial порту

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

adrusha пишет:

Можете подсказать, использование кода с хабр хорошее решение или плохое?

Хорошее или плохое для чего? Чтобы работало быстро и "что-то такое показывало" - используйте. Если же Вам не всё равно, что показывает, то там очень большая потеря точности. Ещё раз говорю, без потери точности Вы analogRead сколько-нибудь существенно не ускорите.

adrusha
Offline
Зарегистрирован: 02.03.2015

b707 пишет:

adrusha пишет:

В программе обрабатываюся Gcode данные отправляемые по serial порту

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

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

Вернемся к датчику, напряжение высокого сигнала там 2.5 т.е полагаю оно и должно быть опорным, а все что выше это 1023. Нужно считать данные с порта в диапозоне этих значений 0-2.5. И мне действительно нужна точность и скорость два в одном. Для точности можно использовать тот же буфер и после искать среднее значение за последние, скажем 10 итеракций чтения порта. Пример выше по ссылке дает уже достаточные представления о работе analogRead на прерываниях, как к этому добавить еще опорку 2.5 использовать analogReference() ?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

adrusha пишет:
Пример выше по ссылке дает уже достаточные представления о работе analogRead на прерываниях

Не знаю, что он Вам даёт, но 1) там нет никаких прерываний и 2) выше Вам привели просто текст функции analogRead - он не быстрее и не медленне - это сам analogRead и есть.

adrusha пишет:
как к этому добавить еще опорку 2.5 использовать analogReference() ?
Заведите Ваши 2,5В на пин aref и настройте референс от него.

 

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

adrusha пишет:
Пример выше по ссылке дает уже достаточные представления о работе analogRead на прерываниях

Не знаю, что он Вам даёт, но 1) там нет никаких прерываний и 2) выше Вам привели просто текст функции analogRead - он не быстрее и не медленне - это сам analogRead и есть.

adrusha пишет:
как к этому добавить еще опорку 2.5 использовать analogReference() ?
Заведите Ваши 2,5В на пин aref и настройте референс от него. Как это сделать вот здесь написано - http://arduino.ru/Reference/AnalogReference

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

analogReference(EXTERNAL), на вывод AREF подать внешнее опорное напряжение 2.5 вольт

Евгений опередил )))

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

adrusha пишет:

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

И на второй, и на тетий взгляд "кажется" то же самое.

Прием стоки из 10 символов на скорости 9600 занимает 12.500мс, а выполнение analogRead() - 0.112мс, т.е. в 100 с лишним раз меньше.

Цитата:

...И мне действительно нужна точность и скорость два в одном...

Из Ваших рассуждений это не следует. Но если действительно нужно, рекомендую пордумать о смене "железа": либо внешний АЦП (например, MCP3008), либо контроллер целиком (напрмер, stm32f103).

adrusha
Offline
Зарегистрирован: 02.03.2015

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

А работает все на скорости 115200.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

adrusha пишет:

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

Правильно подумал. Так как написано по Вашей ссылке точность падает с 2 LBS до 5.

adrusha
Offline
Зарегистрирован: 02.03.2015

Решил все же использовать функцию analogRead в своем приложении, но при компиляции есть ошибка.

...\libraries\grbl\extruder_control.c:60:24: error: 'A4' undeclared (first use in this function)

 int val = analogRead(A4);

Затем я заменил A4 на просто 4, что работает в обычном проекте (стандартном main(); loop();) компиляция прошла, но на выходе чтения я получаю 0, хотя там есть значения.

b707
Offline
Зарегистрирован: 26.05.2017

adrusha пишет:

Решил все же использовать функцию analogRead в своем приложении, но при компиляции есть ошибка.

...\libraries\grbl\extruder_control.c:60:24: error: 'A4' undeclared (first use in this function)

 int val = analogRead(A4);

попробуйте в начало файла .c добавить

#include <Arduino.h>

adrusha
Offline
Зарегистрирован: 02.03.2015

b707 пишет:

попробуйте в начало файла .c добавить

#include <Arduino.h>

А нет, ошибка присутствует. При использовании числа 4, вместо A4. На выходе функции по прежнему 0. Думаю где-то константы прописывают Input/Output на этот пин, буду читать код...

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Попытайтесь использовать номер 18.

adrusha
Offline
Зарегистрирован: 02.03.2015

andriano пишет:

Попытайтесь использовать номер 18.

Не работает.

Вот пробую такой скетч:

int read(uint8_t pin) {
  uint8_t low, high;
  ADMUX = (1 << 6) | (pin & 0x07);
  #if defined(ADCSRA) && defined(ADCL)
    ADCSRA |= (1<<ADSC);
    while (bit_is_set(ADCSRA, ADSC));
    low  = ADCL;
    high = ADCH;
  #else
    return -1; // for test, default: 0
  #endif
  return (high << 8) | low;
}

void setup() {
  Serial.begin(115200);
  Serial.println( read(4) );
  while(1);
}

Значения на выходе в районе 450, ну как и должно быть.

Вставляю тот же самый код в нужное место из исходников https://github.com/grbl/grbl то не работает, на выходе 0.

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

adrusha,

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

Сделайте маленький код, в котором видна проблема (в большом ковыряться охотников мало). Уберите нахрен работу с регистрами, которой Вы не понимаете. Сделайте со штатным analogRead - и выложите его целиком. И еще, что и как подключено к ардуине? Что-то ж она у Вас измеряет. Схему и фото в студию

Только так Вам что-то разумное скажут.

adrusha
Offline
Зарегистрирован: 02.03.2015

У меня есть вот такая плата GRBL контроллера на ардуино:

прошивку (программу) я взял с сайта https://github.com/grbl/grbl и закгрузил на плату, все прекрасно работает и Gcode команды выполняются. Но есть необходимость подключения термистора ntc на 100k. Я нашел свободные ноги: A4, A5 и хочу использовать их для получения данных температуры, накидал схему такую:

И загрузил следующий скетч для настройки и проверки работы термистора на нужном порту:

int read(uint8_t pin) {
  uint8_t low, high;
  ADMUX = (1 << 6) | (pin & 0x07);
  #if defined(ADCSRA) && defined(ADCL)
    ADCSRA |= (1<<ADSC);
    while (bit_is_set(ADCSRA, ADSC));
    low  = ADCL;
    high = ADCH;
  #else
    return -1; // for test, default: 0
  #endif
  return (high << 8) | low;
}

void setup() {
  Serial.begin(115200);
  Serial.println( read(4) );
  while(1);
}

Скетч исправно работает и данные получает, я доработал код и перевожу данные 0-1023 в температуру цельсия. После чего я доработал код таким образом чтоб на незанятом выходе D11 был шим сигнал в зависимости от температуры 0-255. После чего в исходники grbl в файл spindle_control.c вставил нужные параметры и проверил PWM сигнал оцилографом на выходе, там было то, что ожидалось. После чего я приступил к правке кода чтоб добавить туда код который прикрепил выше, с датчиком температуры, но данные с A4 не получает, приходит 0, хоть analogRead хоть чем. Но когда я подаю 5В на ногу, и смотрю в PINC то там 1 в случае высокого сигнала на ноге и 0 в случае низкого. Т.е пин работает как входящий, но почему-то не читает данные с него в виде 0-1023.

adrusha
Offline
Зарегистрирован: 02.03.2015

ЕвгенийП пишет:

Уберите нахрен работу с регистрами, которой Вы не понимаете.

Не могу, вся программа GRBL заточена под это и работает на них. Раньше я писал простые программы, но когда начал изучать код пришлось освоить понимание этих регистров и их масок и как они применяются &=~ и |=
 

adrusha
Offline
Зарегистрирован: 02.03.2015

Вопрос решен. Добавил это после int main(void)

  sbi(TCCR0A, WGM00);
  sbi(TCCR0B, CS00);
  cbi(ADCSRA, ADPS0);
  sbi(ADCSRA, ADPS2);
  sbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADEN);

Выяснил это так, я не нашел функции setup в исходниках grbl которая как позже стало известна вызывается из int main() в которой помимо прочего были и выше указанные константы, которые за что-то от вечают, возможно за какие-нибудь тайминги. В grbl они нигде не прописаны, а прописав их сразу функция analogRead стала получать свои значения. Позже изучу за что они все же отвечают. Вопрос считаю закрытым, догадался сам, всем спасибо за участие.

nik182
Offline
Зарегистрирован: 04.05.2015

Ну да. В ардуиновском main кроме setup ещё init вызывается. Из него можно взять правильную инициацию ацп. Догадался это круто. А можно было просто даташит почитать. Там всё расписано.

adrusha
Offline
Зарегистрирован: 02.03.2015

nik182 пишет:
Ну да. В ардуиновском main кроме setup ещё init вызывается. Из него можно взять правильную инициацию ацп. Догадался это круто. А можно было просто даташит почитать. Там всё расписано.

Я не силен в таком языке програмирования, для меня было новым все что поднималось в этой теме. Но в жизни пригодится все, буду знать. Я же ранее не имел представления как работает это все, думал что все мол просто, функция запрашивает данные с порта, тот ему отвечает, а нет, там еще инициализация ацп нужна, которой не было в grbl просто потому что не нужна. Но сейчас встали другие вопросы, связанные с PWM буду их решать, там частота слишком высокая, не стандартная... Но результат появился, правда скетч на 99% вышел )))

nik182
Offline
Зарегистрирован: 04.05.2015

Ещё раз. grbl написан не для среды ардуино. Все ваши попытки использовать функции ардуино будут наталкиваться на отсутствие правильной инициализации железа под функции ардуины. С АЦП вы уже наступили на эти грабли. Что бы добавить функции в grbl программу нужно использовать возможности програмирования микроконтролеров  AVR. Это немного шире чем ардуино. Нужно изучить существенно больше. 

adrusha
Offline
Зарегистрирован: 02.03.2015

nik182 пишет:

Ещё раз. grbl написан не для среды ардуино. Все ваши попытки использовать функции ардуино будут наталкиваться на отсутствие правильной инициализации железа под функции ардуины. С АЦП вы уже наступили на эти грабли. Что бы добавить функции в grbl программу нужно использовать возможности програмирования микроконтролеров  AVR. Это немного шире чем ардуино. Нужно изучить существенно больше. 

Странно, но на самом сайте написано что для Arduino.

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