Перейти к содержанию
    

ADC на ATMega328P (Arduino Nano v3)

Уж извините, что снова прошу подсказки.

Безуспешно пытаюсь понять, почему вот этот код в Arduino IDE не работает (писал по даташит):

int main() {
  
  unsigned int result_l, result_h, result; 
  unsigned char pin=7;
  
 while (1) {

    ADCSRA |= (1 << ADEN);					// ADC Enable
    ADCSRA |= (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2);	// ADC Prescaler CLK/128
    ADMUX = pin;						// ADC Source
    ADCSRA |= (1 << ADSC);					// Start conversation
    while (bit_is_set(ADCSRA, ADSC));				// Wait for conversation end
    ADCSRA |= (0 << ADEN);					// ADC Disable

    result_l = ADCL;						// Get ADC LOW Byte
    result_h = ADCH;						// Get ADC HIGH Byte
    result = result_l + result_h * 256;		
	
    // Output
    Serial.begin(115200);
    Serial.print("Result = "); 
    Serial.println(result);
    Serial.end(); 
    
    for(volatile unsigned long int i=200000; i>0; i--);
    
  }
  
  return 0;
}

Ардуиновский отлично выводит на серийный порт данные с датчика влажности (с хорошей точностью: сухой - 1023, пальцами дотрагиваюсь - 1000-900, воду капаю - в зависимости от количества - 500-300):

void setup() {
 Serial.begin(115200);
}
void loop() {
  int result = analogRead(A7);
  Serial.print("Result = "); 
  Serial.println(result); 
  delay(1000);
}

а нужный - выводит практически всё время 1023, пару раз удалось добиться хоть каких-то изменений в показаниях, при залитом датчике, но даже и это теперь не получается...

Необходимо именно через регистры, мне в другую программу вставлять (а из main функция analogRead(); вообще нули выводит).

Пробовал менять значение делителя и порядок операторов. Чего тут ещё не хватает-то?

Изменено пользователем ILSF15

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 hours ago, ILSF15 said:

ADMUX = pin; // ADC Source

Судя по битам REFS1/REFS0, которые равны нулю, у вас опорное напряжение берётся с ножки AREF. Это правильно? У вас туда физически напряжение поступает?

 

3 hours ago, ILSF15 said:

ADCSRA |= (0 << ADEN); // ADC Disable

Эта строка бессмысленна, т.к. ничего не делает ровным счётом. Если хотите снять бит ADEN, то писать надо так

ADCSRA &= ~(1 << ADEN); // ADC Disable

 

Далее, вынесете всю инициализацию АЦП за цикл. Зачем вы каждый раз инициализируете его? Достаточно только запускать преобразование.

 

Остальное по AVR я уже подзабыл, т.к. давно с ними не работаю. Но, рекомендую следующее:

1. На вход АЦП вместо датчика подключите потенциометр. Поглядите, меняются ли коды АЦП от нуля до значения полной шкалы - 1.

2. Убедитесь, что функция Serial.println способна выводить числа от нуля до 65535 (16 бит). Это можно сделать просто несколькими строками с записью этих чисел.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

7 hours ago, haker_fox said:

Судя по битам REFS1/REFS0, которые равны нулю, у вас опорное напряжение берётся с ножки AREF. Это правильно? У вас туда физически напряжение поступает?

Я сам не очень это дело понимаю, но судя по тому, что я прочёл про работу функции analogRead(); - там используется такое же опорное напряжение, и подключение одно и то же.

7 hours ago, haker_fox said:

Если хотите снять бит ADEN, то писать надо так


ADCSRA &= ~(1 << ADEN); // ADC Disable

Спасибо за поправку.

7 hours ago, haker_fox said:

вынесете всю инициализацию АЦП за цикл

Там, где оно будет использоваться, оно будет включаться по требованию и выключаться для экономии. Написал так, потому что первое преобразование занимает больше циклов - для воспроизведения условий. Не принципиально. Вынес - результат тот же.

7 hours ago, haker_fox said:

Остальное по AVR я уже подзабыл, т.к. давно с ними не работаю. Но, рекомендую следующее:

1. На вход АЦП вместо датчика подключите потенциометр. Поглядите, меняются ли коды АЦП от нуля до значения полной шкалы - 1.

2. Убедитесь, что функция Serial.println способна выводить числа от нуля до 65535 (16 бит). Это можно сделать просто несколькими строками с записью этих чисел.

1. Нет потенциометра. Скорее всего, меняться не будут: что-то не так в коде, если с ардуиновским датчик работает исправно, а с этим - нет.

2. Способна. И в ардуиновском коде, и в моём выводит 1023 и меньшие, а большего быть не может, преобразователь десятибитный.

 

Видимо, чтобы понять, что не так, нужен кто-то, кто знает, что именно делает ардуиновский код, и чем это отличается от кода, который написал я (моих весьма скромных знаний не хватает, чтобы понять, что там в ардуиновских библиотеках написано, и воспроизвести это в С...)

Изменено пользователем ILSF15

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Пока что сумел заставить работать вот такого монстрика, но это же уродское уродство... Хорошо бы всё-таки найти адекватное решение...

unsigned char pin=20;
unsigned int acd_r;

int main() {
  
  ADCSRA |= (1 << ADEN);

  while (1) {
    
    setup();

    Serial.begin(115200);
    Serial.print("Result = "); 
    Serial.println(acd_r);
    Serial.end();

   }
  return 0;
}

void setup() {
  
 acd_r=analogRead(pin);
  
}

 

Изменено пользователем ILSF15

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 часа назад, ILSF15 сказал:

Я сам не очень это дело понимаю, но судя по тому, что я прочёл про работу функции analogRead(); - там используется такое же опорное напряжение, и подключение одно и то же.

Судя по схеме, на вход AREF никакое напряжение не подается, поэтому вам в биты REFS1, REFS0 надо записать 01 (если ваш входной сигнал не превышает 1.1 В и вы хотите испльзовать внутренний источник опрного напряжения) или 11 (если вы хотите использовать в качестве опорного напряжения напряжение питания AVcc). То есть ваша строка выбора канала должна выглядеть так:

ADMUX = (1 << REFS1) | (1 << REFS0) | pin;	// AREF = VREFin (1.1 v)
// или так:
ADMUX = (0 << REFS1) | (1 << REFS0) | pin;	// AREF = AVCC

 

13 часов назад, ILSF15 сказал:

 


    ADCSRA |= (1 << ADEN);					// ADC Enable
    ADCSRA |= (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2);	// ADC Prescaler CLK/128

 

У вас есть какая-то уверенность, что до выполнения этого кода в регистре не были выставлены единицы в каких-то ненужных вам битах? Тут не нужна операция |= типа "чтение-модификация-запись", в первой строке достаточно простой операции записи, которая точно выставит в нули все ненужные вам биты.

ADCSRA = (1 < ADEN);

Ну и напоследок можно смело объединить эти две строки в одну, уменьшив размер и время выполнения вашей программы (пусть и немного, но все же - копейка рубль бережет):

ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);

 

13 часов назад, ILSF15 сказал:

 


    result_l = ADCL;						// Get ADC LOW Byte
    result_h = ADCH;						// Get ADC HIGH Byte
    result = result_l + result_h * 256;		

 

Это избыточно. В заголовочном файле описан регистр 16-битный регистр ADC, составленный из этих двух регистров и комилятор сам знает правильный порядок чтения таких регистров, так что совершенно безопасно писать

result = ADC;

И напоследок: если вы будете использовать в качестве входов АЦП ноги PC0...PC5, то будет очень полезно отключить от них цифровые сигналы при помощи регистра DIDR0.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

43 minutes ago, Сергей Борщ said:

Судя по схеме, на вход ARef никакое напряжение не подается.

Подключение вот такое:

Spoiler

Rain-sensor_bb.thumb.jpg.c9afde6c83dea0b7b19e40fc1992fa09.jpg

(Правая сторона меня не касается, и контроллер не UNO, но не суть). К AREF ничего не подключено, подключено к 5V, GND и A7.

Функция analogRead(); использует, если верить источникам, значения REFS1, REFS0 по умолчанию (0). Как я понимаю, любые другие значения требуют дополнительного подключения внешнего конденсатора и подключения датчика на AREF вместо 5V. В библиотеке wiring_analog.c от Arduino IDE код выглядит

Spoiler

 


uint8_t analog_reference = DEFAULT; // DEFAULT = 0

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;
}

Судя по тому, что я понимаю оттуда - менять значения этих битов мне не следует (экспериментировать наугад точно не хочу - как бы чего не спалить).

43 minutes ago, Сергей Борщ said:

У вас есть какая-то уверенность, что до выполнения этого кода в регистре не были выставлены единицы в каких-то ненужных вам битах?

Надо признать, что я понятия не имею,что выставляет Arduino IDE, (видимо, нужные значения, что доказывается тем, что работает мой уродец с функцией setup), поэтому как раз и предпочитаю не трогать то, что трогать не надо. Делители перепробовал все - без результата.

43 minutes ago, Сергей Борщ said:

Это избыточно.

Опять же, опирался на datasheet и на описание функции analogRead, приведённое выше.

43 minutes ago, Сергей Борщ said:

использовать в качестве входов АЦП ноги PC0...PC5

Только А6 и А7, остальные заюзаны, как цифровые входы/выходы (разумеется, не здесь, а в целевой программе).

 

Возможно, стоит ещё раз подчеркнуть, что даже с тем уродцем, которого я написал через функцию setup(); (см. выше) датчик работает исправно

Изменено пользователем ILSF15

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Воткнул в виде уродца в основную программу - судя по всему, работает (там нет вывода на серийный порт - в зависимости от данных датчика должно срабатывать или не срабатывать реле, частота опроса тоже зависит значения от последнего замера). Но хотелось бы всё-таки понять, что именно делает с регистрами функция analogRead (и/или сама среда Arduino IDE), и воспроизвести это в "пристойном" варианте.

Изменено пользователем ILSF15

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 hours ago, ILSF15 said:

Подключение вот такое:

AREF-то здесь не виден. Вам нужно смотреть схему электрическую принципиальную, которую предоставил уважаемый @Сергей Борщ.

34 minutes ago, ILSF15 said:

Но хотелось бы всё-таки понять, что именно делает с регистрами функция analogRead

REFS1/0 точно равны нулю в момент преобразования АЦП?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Хм... очень странно... В первую попытку приложенный ниже код не сработал - а теперь да (соответственно, вместо уродца вызов стандартной функции языка Ардуино - гораздо пристойней, возможно, на этом можно и остановиться на первый случай):

int main() {

  while (1) {
    
    unsigned char pin=21;
    unsigned int acd_r;
    
    ADCSRA |= (1 << ADEN);
    acd_r=analogRead(pin);
    ADCSRA &= ~(1 << ADEN);

    Serial.begin(115200);
    Serial.print("Result = "); 
    Serial.println(acd_r);
    Serial.end();
 
    for (volatile unsigned int i=1000000; i>0; i--);
   }
  return 0;
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 часа назад, ILSF15 сказал:

Функция analogRead(); использует, если верить источникам, значения REFS1, REFS0 по умолчанию (0). [...] В библиотеке wiring_analog.c от Arduino IDE код выглядит

Приведенный код противоречит вашему утверждению из начала цитаты. В приведенном коде в эти биты заносится значение analog_reference.

16 минут назад, haker_fox сказал:

Вам нужно смотреть схему электрическую принципиальную,

Я тоже не обратил внимания, что AREF выведен на разъем. Но раз ILSF15 утверждает, что к этому контакту ничего не подключенно - значит, АЦП нужно настраивать на работу от внутренней опоры или от AVcc.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

41 minutes ago, haker_fox said:

AREF-то здесь не виден

Правый верхний угол контроллера, сразу под джамперами. Никуда не подключен. Если об этом речь. Если речь о внутренней логике контроллера - простите, ничего не могу сказать. Я уже писал, что не очень понимаю на таком уровне.

41 minutes ago, haker_fox said:

REFS1/0 точно равны нулю в момент преобразования АЦП?

Для интересу набросал программку с выводом значений регистров на серийный порт. До вызова analogRead:

ADCSRA = 0
ADMUX = 0

После (в бинарном представлении):

ADCSRA = 10010000
ADMUX = 1000111

Интересно, что в datashit комбинация REFS1/0 = 10 указана, как зарезервированная

 

25 minutes ago, Сергей Борщ said:

В приведенном коде в эти биты заносится значение analog_reference.

В приведённом коде analog_refrence = DEFAULT, а DEFAULT (тоже где-то в библиотеках ардуино накопал) определена как 0.

25 minutes ago, Сергей Борщ said:

АЦП нужно настраивать на работу от внутренней опоры или от AVcc

Untitled1.thumb.png.91e4de66143347c5cda801a771687c06.png

01 и 11 подразумевают наличие внешнего конденсатора на пине AREF, которым я вообще не пользуюсь. 10 зарезервирован (хотя, как я писал выше, ардуино выдаёт именно такое значение регистра). Что именно нужно?

Изменено пользователем ILSF15

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Стоп. Тьфу. Он же срезал передний 0 с ADMUX! (ворона я, не посчитал биты). То есть, да, выходит, что нужно использовать REFS1/0 = 01!

UPD: Да! Заработало, как надо (и 18 байт экономии после компиляции:mosking:). Всем спасибо за подсказки!

Изменено пользователем ILSF15

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 hours ago, ILSF15 said:

В первую попытку приложенный ниже код не сработал - а теперь да

Тоже, кстати, догадываюсь, почему (проверять не охота, но звучит достоверно): в первую попытку я писал analogRead(A7), а он, видимо, не смог преобразовать это в 21...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...