Микроконтроллер 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. Используя эти подходы, вы сможете легко интегрировать аналоговые датчики в свои проекты и обрабатывать полученные данные.