Аналоговые входы ATmega328: основы и примеры

Микроконтроллер ATmega328 оснащен шестью каналами аналогово-цифрового преобразователя (АЦП), которые позволяют считывать аналоговые сигналы и преобразовывать их в цифровые значения. Эти каналы обозначаются как ADC0–ADC5 и соответствуют физическим контактам на плате Arduino Uno (или аналогичных платах), например, A0–A5.

Основные аспекты работы с аналоговыми входами:

1. Настройка АЦП:

Для начала работы с аналоговыми входами необходимо настроить АЦП. Для этого используются следующие регистры:

  • ADMUX: Регистр выбора мультиплексора и опорного напряжения.
  • ADCSRA: Регистр управления и статуса АЦП.
  • ADCH и ADCL: Регистр данных АЦП (результаты измерений хранятся в этих регистрах).

2. Выбор канала:

Каналы выбираются путем установки соответствующих битов в регистре ADMUX. Канал 0 соответствует битам MUX0-MUX3 равным 0000, канал 1 — 0001 и так далее до канала 5 (00101).

3. Опорное напряжение:

Опорное напряжение определяет максимальное значение, которое можно измерить с помощью АЦП. По умолчанию оно равно напряжению питания (Vcc), но можно выбрать альтернативные источники опорного напряжения, такие как внутренний источник 1.1В или внешний источник.

4. Разрешение АЦП:

АЦП в ATmega328 имеет разрешение 10 бит, то есть результат измерения будет находиться в диапазоне от 0 до 1023.

5. Частота дискретизации:

Скорость работы АЦП определяется установкой предделителя в регистре ADCSRA. Возможны следующие коэффициенты деления: 2, 4, 8, 16, 32, 64, 128.


Теперь перейдём к примерам кода.

Пример 1: Простое чтение значения с одного канала
Этот пример показывает базовую настройку АЦП и считывание значения с канала 0.


#include <avr/io.h>
#include <util/delay.h>

// Функция инициализации АЦП
void adc_init() {
  // Настроим опорное напряжение на VCC
  ADMUX = (1 << REFS0);

  // Включим АЦП и установим коэффициент деления на 128
  ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
}

// Функция чтения значения с заданного канала
uint16_t read_adc(uint8_t channel) {
  // Выберем нужный канал
  ADMUX &= ~((1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (1 << MUX0)); // Очистим старые настройки
  ADMUX |= (channel & 0x07); // Установим новый канал

  // Начнем преобразование
  ADCSRA |= (1 << ADSC);

  // Подождём окончания преобразования
  while ((ADCSRA & (1 << ADIF)) == 0);

  // Прочитаем результат из обоих регистров
  uint16_t result = ADCL | (ADCH << 8);

  return result;
}

int main() {
  adc_init();

  while (1) {
    uint16_t value = read_adc(0); // Читаем значение с канала 0

    // Обработка полученного значения
    // Например, выводим его на последовательный порт
    // Serial.println(value);

    _delay_ms(100); // Пауза перед следующим чтением
  }
}


Пример 2: Измерение температуры с использованием термодатчика LM35
LM35  это популярный датчик температуры, который выдает напряжение пропорциональное 
температуре. Напряжение меняется линейно: каждый градус Цельсия добавляет 10 мВ к выходу 
датчика. В этом примере мы будем считать температуру с помощью АЦП и выводить её на 
последовательный порт.


#include <avr/io.h>
#include <util/delay.h>

#define F_CPU 16000000UL // Частота тактирования микроконтроллера
#include <util/delay.h> // Для функции задержки

void adc_init() {
  // Настроим опорное напряжение на VCC
  ADMUX = (1 << REFS0);

  // Включим АЦП и установим коэффициент деления на 128
  ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
}

float read_temperature() {
  uint16_t adc_value = read_adc(0); // Читаем значение с канала 0

  float voltage = adc_value * (5.0 / 1024.0); // Переводим в вольты
  float temperature = voltage * 100.0; // Переводим в градусы Цельсия

  return temperature;
}

uint16_t read_adc(uint8_t channel) {
  // Выберем нужный канал
  ADMUX &= ~((1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (1 << MUX0)); // Очистим старые настройки
  ADMUX |= (channel & 0x07); // Установим новый канал

  // Начнем преобразование
  ADCSRA |= (1 << ADSC);

  // Подождём окончания преобразования
  while ((ADCSRA & (1 << ADIF)) == 0);

  // Прочитаем результат из обоих регистров
  uint16_t result = ADCL | (ADCH << 8);

  return result;
}

int main() {
  adc_init();

  while (1) {
    float temp = read_temperature();

    // Выводим температуру на последовательный порт
    // Serial.print("Temperature: ");
    // Serial.print(temp);
    // Serial.println(" °C");

    _delay_ms(1000); // Пауза перед следующим измерением
  }
}

Пример 3: Управление светодиодом в зависимости от уровня освещенности
В этом примере мы используем фоторезистор для измерения уровня освещённости. 
Чем ярче свет, тем меньше сопротивление фоторезистора, и наоборот. Светодиод будет 
изменять свою яркость в зависимости от показаний фоторезистора.


#include <avr/io.h>
#include <util/delay.h>

#define LED_PIN PB5 // Пин для подключения светодиода

void adc_init() {
  // Настроим опорное напряжение на VCC
  ADMUX = (1 << REFS0);

  // Включим АЦП и установим коэффициент деления на 128
  ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
}

uint16_t read_light_level() {
  uint16_t adc_value = read_adc(0); // Читаем значение с канала 0

  return adc_value;
}

uint16_t read_adc(uint8_t channel) {
  // Выберем нужный канал
  ADMUX &= ~((1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (1 << MUX0)); // Очистим старые настройки
  ADMUX |= (channel & 0x07); // Установим новый канал

  // Начнем преобразование
  ADCSRA |= (1 << ADSC);

  // Подождём окончания преобразования
  while ((ADCSRA & (1 << ADIF)) == 0);

  // Прочитаем результат из обоих регистров
  uint16_t result = ADCL | (ADCH << 8);

  return result;
}

void set_led_brightness(uint8_t brightness) {
  OCR0A = brightness; // Устанавливаем ШИМ-сигнал для светодиода
}

int main() {
  adc_init();

  // Настройка выхода для светодиода
  DDRB |= (1 << LED_PIN); // Пин PB5 настроен на выход

  // Настройка таймера 0 для ШИМ
  TCCR0A = (1 << COM0A1) | (1 << WGM01) | (1 << WGM00); // Fast PWM mode
  TCCR0B = (1 << CS01); // Коэффициент деления на 8

  while (1) {
    uint16_t light_level = read_light_level(); // Читаем уровень освещенности

    // Преобразуем показания в яркость светодиода
    uint8_t brightness = map(light_level, 0, 1023, 255, 0); // Яркость уменьшается с увеличением света

    set_led_brightness(brightness); // Устанавливаем яркость светодиода

    _delay_ms(50); // Пауза перед следующим измерением
  }
}
Эти примеры показывают основные принципы работы с аналоговыми входами в микроконтроллере 
ATmega328. Используя эти подходы, вы сможете легко интегрировать аналоговые датчики в свои 
проекты и обрабатывать полученные данные.

Добавить комментарий

Войти с помощью: