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

Почему после Servo или tone() перестал работать analogWrite?

Обновлено Редакция GAW

На ATmega328P всего три таймера, и каждый обслуживает два вывода ШИМ. Ядро Arduino при старте настраивает все три под analogWrite(). Библиотека, которой нужен точный отсчёт времени, перенастраивает таймер под себя, и ШИМ на его выводах пропадает — без сообщения об ошибке.

Кто какой таймер занимает на Uno

ТаймерВыводы ШИМКто ещё его используетЧто ломается
Timer05, 6millis(), micros(), delay()менять делитель нельзя — поедет время
Timer19, 10библиотека ServoanalogWrite() на 9 и 10 после attach()
Timer23, 11tone()analogWrite() на 3 и 11 во время звука

Servo. В ServoTimers.h версии 1.2.2 для ATmega328P определён один таймер — Timer1. При первом attach() библиотека пишет TCCR1A = 0 (обычный режим счёта) и делитель 8, а импульсы на выводах серво формирует в прерывании по совпадению. Режим ШИМ Timer1 выключен, поэтому analogWrite() на 9 и 10 больше не выдаёт ШИМ, даже если серво висит на другом выводе.

tone(). Для ATmega328P в Tone.cpp указан Timer2. На время звука он работает в режиме CTC, и ШИМ на выводах 3 и 11 пропадает. noTone() возвращает Timer2 в режим ШИМ ядра, но выходы на выводах 3 и 11 остаются отключёнными до следующего analogWrite().

Как обойти

  1. Разнести функции по таймерам: Servo плюс ШИМ на выводах 3, 5, 6, 11; звук плюс ШИМ на 5, 6, 9, 10.
  2. Если выводов ШИМ не хватает, вынести ШИМ на отдельную микросхему — например, на PCA9685 по I²C.
  3. Для одного-двух серво можно обойтись без библиотеки: импульсы сформирует сам Timer1 в режиме быстрого ШИМ с TOP в ICR1 на выводах 9 и 10, без прерываний, а Timer2 останется свободным для tone().

Как ядро настраивает таймеры для analogWrite(), разобрано в уроке «analogWrite изнутри», режимы таймеров ATmega — на странице «Таймеры AVR». Как устроен сервопривод и какой импульс ему нужен — в статье «Сервоприводы».

Похожие вопросы

Вернётся ли ШИМ на выводах 3 и 11 после noTone()?

Не сам по себе: noTone() на Uno возвращает Timer2 в режим ШИМ с правильной фазой и делителем 64, как его настраивает ядро. Но TCCR2A при этом записывается целиком, только с битом WGM20, — выходы сравнения обоих выводов отключаются, а OCR2A обнуляется. Поэтому analogWrite() нужно вызвать заново и на выводе 3, и на выводе 11.

Можно ли тогда управлять сервоприводом от вывода 9?

Можно: библиотека Servo формирует импульсы программно, по прерыванию таймера, и сигнал подаётся на любой цифровой вывод. Пропадает только аппаратный ШИМ analogWrite на 9 и 10 — даже если серво подключён к другому выводу.

Какие ещё библиотеки занимают таймеры?

Обычно те, что отмеряют точные интервалы, — для ИК-пультов, звука, программных UART и ШИМ. Какой таймер занят, надёжнее всего узнать в исходнике библиотеки — поиском TCCR1, TCCR2 или TIMER1_COMPA_vect.

Источники

  1. Arduino. Servo library 1.2.2: src/avr/ServoTimers.h, src/avr/Servo.cpp — ServoTimers.h: для микроконтроллеров, не перечисленных отдельно (в том числе ATmega328P), определён только _useTimer1; Servo.cpp, initISR: TCCR1A = 0 — обычный режим счёта, TCCR1B = _BV(CS11) — делитель 8, прерывание по совпадению OCR1A
  2. Arduino. ArduinoCore-avr 1.8.8: cores/arduino/Tone.cpp, variants/standard/pins_arduino.h — Tone.cpp: для ATmega328P AVAILABLE_TONE_PINS = 1, USE_TIMER2 — tone() использует Timer2 (TCCR2A = 0, TCCR2B = 0, затем CTC); disableTimer(2) в noTone() записывает TCCR2A = (1 << WGM20) — биты COM2A и COM2B сброшены, — делитель 64 (CS22) и OCR2A = 0, а для других таймеров, по комментарию в коде, ШИМ не восстанавливает; pins_arduino.h: таймеры выводов Uno — 3: TIMER2B, 5: TIMER0B, 6: TIMER0A, 9: TIMER1A, 10: TIMER1B, 11: TIMER2A

Нашли ошибку?