Микроконтроллеры: Arduino, порты, прерывания

Устройство микроконтроллера, работа с портами ввода-вывода, чтение аналоговых датчиков, ШИМ, прерывания и таймеры, опрос против прерываний, типовая схема стенда.

Курсовые по автоматизации и микроконтроллерам чаще всего делают на Arduino: дешёвая плата, простая среда и наглядный результат, который можно принести на защиту в собранном виде.

Что внутри микроконтроллера

БлокНазначениеПример ATmega328P
ЯдроВыполняет команды8-разрядное AVR, 16 МГц
FlashПрограмма32 КБ
SRAMПеременные во время работы2 КБ
EEPROMДанные, сохраняемые при отключении1 КБ
GPIOЦифровые входы-выходы23 линии
АЦПИзмерение напряжения6 каналов, 10 разрядов
ТаймерыОтсчёт времени, ШИМ3 таймера
ИнтерфейсыСвязь с внешними устройствамиUART, I²C, SPI
2 КБ оперативной памяти — жёсткое ограничение, о котором забывают. Строка на 100 символов съедает 5 % ОЗУ, а массив float на 500 элементов не поместится вовсе. Отсюда привычка выводить текст через F("..."), оставляя его во Flash.

Цифровые входы и выходы

const uint8_t LED_PIN = 13;
const uint8_t BTN_PIN = 2;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(BTN_PIN, INPUT_PULLUP);   // встроенная подтяжка к питанию
  Serial.begin(9600);
}

void loop() {
  // При INPUT_PULLUP кнопка, соединяющая вход с землёй, даёт LOW
  bool pressed = (digitalRead(BTN_PIN) == LOW);
  digitalWrite(LED_PIN, pressed ? HIGH : LOW);
}

Без подтягивающего резистора вход «висит в воздухе» и ловит наводки — показания случайны. Режим INPUT_PULLUP включает резистор внутри контроллера, поэтому внешний не нужен: кнопку достаточно соединить с землёй.

Дребезг контактов

unsigned long lastPress = 0;
const unsigned long DEBOUNCE_MS = 50;
bool lastState = HIGH;

void loop() {
  bool state = digitalRead(BTN_PIN);
  if (state != lastState && millis() - lastPress > DEBOUNCE_MS) {
    lastPress = millis();
    lastState = state;
    if (state == LOW) counter++;      // сработало один раз на нажатие
  }
}
Механический контакт при замыкании дребезжит несколько миллисекунд, и контроллер успевает насчитать десяток нажатий. Программное подавление через millis() — обязательный элемент любой работы с кнопками.

Аналоговые измерения

АЦП на 10 разрядов: 0…1023 отсчёта на диапазон 0…5 В

  Напряжение: U = отсчёт · 5,0 / 1023
  Разрешение: 5 В / 1024 ≈ 4,9 мВ на единицу

Пример с делителем напряжения для измерения до 15 В:
  R1 = 20 кОм, R2 = 10 кОм → коэффициент деления 3
  U_вх = (отсчёт · 5,0 / 1023) · 3
const uint8_t SENSOR = A0;

float readVoltage() {
  // Усреднение подавляет шум измерения
  uint32_t sum = 0;
  for (uint8_t i = 0; i < 16; i++) {
    sum += analogRead(SENSOR);
    delayMicroseconds(200);
  }
  float code = sum / 16.0;
  return code * 5.0 / 1023.0;
}

// Термистор или LM35: 10 мВ на градус
float readTemperature() {
  return readVoltage() * 100.0;
}

ШИМ

Широтно-импульсная модуляция: среднее напряжение задаётся
долей времени включённого состояния

  U_ср = U_пит · D,   где D = t_вкл / T — коэффициент заполнения

analogWrite(pin, 0…255):  128 соответствует D = 0,5, то есть 2,5 В при 5 В питания
Частота ШИМ на Arduino Uno ≈ 490 Гц (или 980 Гц на выводах 5 и 6)
const uint8_t MOTOR = 9;      // вывод с поддержкой ШИМ

void setMotorSpeed(uint8_t percent) {
  analogWrite(MOTOR, map(percent, 0, 100, 0, 255));
}

// Плавный разгон
for (uint8_t p = 0; p <= 100; p += 5) {
  setMotorSpeed(p);
  delay(100);
}

Управлять двигателем напрямую с вывода нельзя: линия отдаёт максимум 40 мА, а мотору нужны сотни. Между ними ставят транзистор или драйвер (L298N, IRF540) — это обязательный элемент схемы в курсовой.

Прерывания

volatile uint32_t pulseCount = 0;   // volatile обязательно!

void countPulse() {                 // обработчик прерывания
  pulseCount++;                     // только самое необходимое
}

void setup() {
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), countPulse, FALLING);
}

void loop() {
  noInterrupts();
  uint32_t count = pulseCount;      // атомарное чтение копии
  interrupts();
  Serial.println(count);
  delay(1000);
}

Опрос или прерывания

Опрос (polling)Прерывания
РеакцияЗависит от длительности циклаНемедленная
СложностьПростаяВыше, нужны volatile и защита
Пропуск событийВозможен при долгом циклеПрактически исключён
Где применятьКнопки, медленные датчикиЭнкодеры, счётчики импульсов, аварийные сигналы

Работа без delay

// delay() блокирует всё: за это время контроллер ничего не делает
unsigned long lastRead = 0, lastBlink = 0;
bool ledState = false;

void loop() {
  unsigned long now = millis();

  if (now - lastRead >= 500) {          // датчик — раз в 500 мс
    lastRead = now;
    Serial.println(readTemperature());
  }

  if (now - lastBlink >= 1000) {        // индикатор — раз в секунду
    lastBlink = now;
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState);
  }

  handleButton();                       // кнопка опрашивается постоянно
}
Сравнение через вычитание now − last корректно работает даже при переполнении millis() (примерно через 50 суток). Сравнение вида now >= last + interval в этот момент даёт сбой — распространённая скрытая ошибка.

Что включить в курсовую

  1. Схема электрическая принципиальная с указанием номиналов и типов элементов.
  2. Расчёт токоограничивающих резисторов и делителей напряжения.
  3. Алгоритм работы: блок-схема или диаграмма состояний.
  4. Листинг с комментариями, в приложение — полностью.
  5. Таблица испытаний: воздействие, ожидаемая реакция, фактическая.
  6. Фотографии собранного стенда и осциллограммы, если требуется.
  7. Оценка потребления и выбор источника питания.

Частые вопросы

Почему нужен резистор последовательно со светодиодом?

Светодиод почти не ограничивает ток сам: без резистора он мгновенно выйдет из строя вместе с выводом контроллера. Номинал считают как R = (U_пит − U_св)/I_св, обычно получается 220-330 Ом.

Arduino или чистый AVR для курсовой?

Arduino быстрее даёт результат, но некоторые кафедры требуют программирование регистров напрямую. Компромисс: сделать проект на Arduino, а один-два узла реализовать через регистры, показав, что понимаете, как работает библиотека.

Что делать, если не хватает памяти?

Убрать float там, где хватает целых чисел, вынести строки во Flash через F(), уменьшить буферы, отказаться от String в пользу массивов char. Строковый класс String к тому же фрагментирует память и приводит к зависаниям.

Читайте также

Сделаем работу по этой теме

Опишите задачу — ответим в течение 15 минут в личных сообщениях ВКонтакте, назовём срок и цену. Предоплаты за оценку нет.

  • Оценка заявки бесплатно
  • Правки по замечаниям преподавателя
  • Работы по всем техническим и IT-дисциплинам

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