"Вы читаете о роботах и программировании и думаете: «Было бы здорово сделать что-то подобное самому!» Теми, кем эта идея овладевает чуть больше просто мыслей смотрят кто и как делал своего робота. Читают статьи, смотрят видео. На картинках все понятно. В видеороликах тоже обычно показываются уже готовые продукты, а также сжато показываются технологии их изготовления. И вроде бы то же всё понятно: отпилил, прикрутил, припаял, соединил, запрограммировал вон на той программе вот этим кодом."

понедельник, 17 февраля 2020 г.

17.02.20 Продолжение. PWM Широтно-импульсная модуляция

Pulse-width modulation (PWM), или pulse-duration modulation (PDM) или Широтно-импульсная модуляция(ШИМ)
PWM сигнал является цифровой волной прямоугольной формы , где частота постоянна, но в части времени когда сигнал включен (рабочий цикл) его уровень может изменяться от 0 до100%.
 
100% рабочий цикл будет таким же, как установка напряжения 5 вольт (высокий). 0% рабочий цикл будет таким же, как заземление сигнала.
Можно управлять яркостью светодиода Предоставление аналогового выходного сигнала; только если фильтруется цифровой выход, это обеспечит аналоговое напряжение между 0% и 100%. Создание звуковых сигналов. Управление скоростью моторов. Генерации модулированного сигнала, например управление инфракрасным светодиодом для пульта дистанционного управления.

Простая широтно-импульсная модуляция с analogWrite

Язык программирования Arduino делает PWM простой в использовании; просто выполните analogWrite (контакт, рабочий цикл), где рабочий цикл может иметь значение от 0 до 255, а контакт является одним из выводов ШИМ (3, 5, 6, 9, 10 или 11).Функция analogWrite предоставляет простой интерфейс для аппаратного ШИМ, но не обеспечивает никакого контроля над частотой. (Отметьте, что, несмотря на имя функции analogWrite, производится вывод цифрового сигнала представляющего волны квадратной формы.)


Использование ATmega PWM регистр напрямую

Чип ATmega168P / 328P имеет три ШИМ таймера, управляющие 6 выходами ШИМ. Манипулируя таймером чипа напрямую, вы можете получить больше контроля, чем обеспечивает функция analogWrite. Техническое описание AVR ATmega328P дает подробное описание ШИМ таймера, но техническое описание может быть трудно понятым, из-за различных контрольных и выходных режимов таймеров.



настройка таймеров
cli(); //запрет прерываний

//режим вывода не инверсный
TCCR1A|=(1<<COM1A1);  //1
TCCR1A&=~(1<<COM1A0);  //0

//режим шим c точной фазой и частотой
TCCR1A&=~(1<<WGM10);  //0
TCCR1A&=~(1<<WGM11);  //0
TCCR1B&=~(1<<WGM12);  //0
TCCR1B|=(1<<WGM13);  //1

//предделитель 8
TCCR1B|=(1<<CS11);  //1
TCCR1B&=~((1<<CS10)|(1<<CS12));  //0 0

OCR1A =25;  // 50% длительность импульса
ICR1=50;  // 20кГц
sei(); //разрешение прерываний
Код нужно добавить в Setup. Настраивается таймер - счетчик 1, только канал А, соответственно нужный сигнал будет только на пине D9. Частота задается значением в регистре ICR1. Ниже представлены протестированные значения и соответствующая им частота.
ICR1=10; // 100 кГц
ICR1=20; // 50 кГц
ICR1=30; // 33 кГц
ICR1=40; // 25 кГц
ICR1=50; // 20 кГц
ICR1=60; // 16,6 кГц
ICR1=70; // 14,3 кГц
ICR1=80; // 12,5 кГц
ICR1=90; // 11,1 кГц
ICR1=100; // 10 кГц

Если изменить предделитель, то частоты будут другие, например при предделителе 64 и ICR1=50, частота будет 2,5 кГц.
Скважность регулируется значением в регистре OCR1A, она зависит от значения в ICR1, таким образом, что максимальное значение OCR1A равно значению в ICR1, если они равны получится постоянный сигнал. При значении OCR1A = ICR1 / 2 получится меандр (длительность импульса и длительность паузы между импульсами равны). Еще пример: при ICR1 = 50 (20кГц) и OCR1A = 10, длительность импульса будет 20%.

В loop можно менять значение OCR1A и соответственно будет меняться скважность.

К стати о setup(), loop() и main() .
Когда мы подключаем плату Arduino к питанию, то внутри него начинается весьма бурная деятельность встроенных микропрограмм. Микроконтроллер сконфигурирован так, что при запуске системы управление получает программа-загрузчик. Первое, что делает загрузчик – проверяет в течение 1-2 секунд, не начнется ли от пользователя отправка новой программы. Если процесс перепрограммирования начат, то скетч загружается в память и управление отдается ему. Если новых программ нет, то загрузчик выполняет ранее сохраненную программу.
Начав выполнение программы, Arduino выполняет ряд рутинных операций по инициализации и настройке среды окружения и только затем приступает к выполнению того самого кода, который  содержится в наших с вами скетчах. Таким образом, ардуино избавляет нас от необходимости помнить все детали архитектуры микропроцессора и сконцентрироваться на стоящей перед нами задаче (это не значит, что мы не должны понимать, что же происходит за кадром).
Для иллюстрации сказанного приведем фрагмент исходников Arduino, в которых и производится вызов наших функций (файл main.cpp):
  1. int main(void)
  2. {
  3. init();

  4. initVariant();

  5. #if defined(USBCON)
  6. USBDevice.attach();
  7. #endif
  8. setup();
  9. for (;;) {
  10. loop();
  11. if (serialEventRun) serialEventRun();
  12. }
  13. return 0;
  14. }
Функция main() – это настоящая точка входа в программу, именно она вызывается первой. Как мы видим, в ней вызываются методы инициализации параметров и среды окружения, а затем и наши void setup() и, уже в цикле – void loop();

Для чего нужна функция void setup()

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


Функция void loop()

Функция loop это то место, куда мы должны поместить команды, которые  будут выполняться все время, пока включена плата Arduino. Начав выполнение с первой команды, микроконтроллер дойдет до конца и сразу же перепрыгнет в начало, чтобы повторить ту же последовательность. И так бесконечное число раз (до тех пор, пока на плату будет подан электричество). Наиболее уместный перевод английского слова loop в даном случае – это цикл (петля). 





С помощью функций arduino void loop и void setup мы передаем микроконтроллеру  инструкции нашего скетча. Все то, что содержится внутри блока setup выполнится один раз. Содержимое блока loop будет выполняться в цикле до тех пор, пока останется включенным Arduino-контроллер.

Таким образом
 void analogWritePin3(int data) { 
TCCR2A |= 1 << COM2B1;
 OCR2B = data; // устанавливаем уровень ШИМ
}

 void analogWritePin5(int data) {  
TCCR0A |= 1 << COM0B1;
 OCR0B = data; // устанавливаем уровень ШИМ
}

 void analogWritePin6(int data) {  
TCCR0A |= 1 << COM0B1;
 OCR0A = data; // устанавливаем уровень ШИМ
}

 void analogWritePin9(int data) {  

TCCR1A |= 1 << COM1B1;
 OCR1A = data; // устанавливаем уровень ШИМ
}

 void analogWritePin10(int data) { 
TCCR1A |= 1 << COM1B1;
 OCR1B = data; // устанавливаем уровень ШИМ
}

 void analogWritePin11(int data) { 
TCCR2A |= 1 << COM2B1;
 OCR2A = data; // устанавливаем уровень ШИМ
}
Нам удалось сократить время исполнения примерно в 7 раз — до 0,69 мкс. Но как видно из примера такое достижение, как и в случае с digitalRead()/digitalWrite(), далось в ущерб гибкости, понятности и универсальности. Там где скорость исполнения не в приоритете, имеет смысл оставить штатную функцию analogWrite()