Arduino Uno имеет шесть контактов, предназначенных для широтно-импульсной модуляции (ШИМ или PWM). ШИМ отлично подходит для аналогового управления скоростью двигателей или зажиганием светодиодов. Но что, если вы хотите контролировать более 6 устройств? Или что, если вы используете линии ШИМ для управления сервомоторами, но все же хотите вывести светодиод на 7-й вывод?

Один из вариантов – изменение платы. Например, вы можете перейти на Arduino Mega 2560. Но это означает, что вы берете более крупную плату, что сулит больше затрат, как и при использовании дополнительных шилдов расширения. Тем не менее, выход есть. Используя функции millis() и micros(), можно полностью реализовать ШИМ программным способом. В этом материале приводится код, с помощью которого вы можете, используя функции micros() и millis(), получить больше линий ШИМ на Arduino Uno, Nano или Pro Mini. Вероятно, эта техника будет работать на других платах экосистемы Arduino.
Ниже приведен полный код для примера программного ШИМ. В этом примере мы делаем так, чтобы половина линий «затухала» с одной скоростью, а другую половина с другой.
// макросы для состояния светодиодов
#define ON true
#define OFF false
// переменные для синхронизации паттернов
unsigned long currentMillis = millis();
unsigned long previousMillis = 0;
unsigned long millisInterval = 100;
// переменные для программного ШИМ
unsigned long currentMicros = micros();
unsigned long previousMicros = 0;
// это частота ШИМ
// частота = 1 / (2 * microInterval)
unsigned long microInterval = 250;
const byte pwmMax = 100;
// затухание (для времени)
int fadeIncrement = 1;
// для свойств каждого программного ШИМ-вывода
typedef struct pwmPins {
int pin;
int pwmValue;
bool pinState;
int pwmTickCount;
} pwmPin;
// создание линий программного ШИМ
// это могут быть любые линии ввода/вывода
// которые могут быть установлены на выход
const int pinCount = 8;
const byte pins[pinCount] = {2,3,5,6,9,10,11,12};
pwmPin myPWMpins[pinCount];
// функция для настройки состояний программных ШИМ-линий
// можно изменить под свои потребности
// это создает чередующиеся паттерны затухания
void setupPWMpins() {
for (int index=0; index < pinCount; index++) {
myPWMpins[index].pin = pins[index];
// немного смешаем
// изменяет начальный pwmValue на нечетный и четный
if (index % 2)
myPWMpins[index].pwmValue = 25;
else
myPWMpins[index].pwmValue = 75;
myPWMpins[index].pinState = ON;
myPWMpins[index].pwmTickCount = 0;
// в отличие от analogWrite (), это необходимо
pinMode(pins[index], OUTPUT);
}
}
void pwmFadePattern() {
// проходим через каждую линию ШИМ и увеличиваем значение программного ШИМ
// это будет похоже на вызов analogWrite () на каждом выводе аппаратного ШИМ
for (int index=0; index < pinCount; index++) {
myPWMpins[index].pwmValue += fadeIncrement;
if (myPWMpins[index].pwmValue > 100)
myPWMpins[index].pwmValue = 0;
}
}
void handlePWM() {
currentMicros = micros();
// проверяем, нужно ли нам еще увеличивать счетчики ШИМ
if (currentMicros - previousMicros >= microInterval) {
// Инкрементирование счетчика каждой линии
for (int index=0; index < pinCount; index++) {
// каждая линия имеет свой tickCounter
myPWMpins[index].pwmTickCount++;
// определить, будем рассчитываем мы время включения on или время выключения off
if (myPWMpins[index].pinState == ON) {
if (myPWMpins[index].pwmTickCount >= myPWMpins[index].pwmValue) {
myPWMpins[index].pinState = OFF;
}
} else {
// если это не включение, а выключение
if (myPWMpins[index].pwmTickCount >= pwmMax) {
myPWMpins[index].pinState = ON;
myPWMpins[index].pwmTickCount = 0;
}
}
// вероятно, может использовать некоторую побитовую оптимизацию здесь
// digitalWrite () действительно замедляет работу после 10 линий.
digitalWrite(myPWMpins[index].pin, myPWMpins[index].pinState);
}
// сбросить счетчик micros()
digitalWrite(13, !digitalRead(13));
previousMicros = currentMicros;
}
}
void setup() {
setupPWMpins();
pinMode(13, OUTPUT);
}
void loop() {
handlePWM();
// проверить таймер для затухающего паттерна
// это было бы то же самое, если бы мы использовали analogWrite()
currentMillis = millis();
if (currentMillis - previousMillis >= millisInterval) {
pwmFadePattern();
// установка таймера для следующего отсчета
previousMillis = currentMillis;
}
}
© digitrode.ru