View Categories

Разгон аппаратуры

Урок применим к ATMega328 и некоторым другим из этого поколения

GPIO #

Быстрые аналоги IO функций Arduino для ATMega328.

Для ускорения digital read/write используйте библиотеку GyverIO - она поддерживает почти все AVR Arduino + ESP8266 и ESP32

pinMode #

void pinModeFast(uint8_t pin, uint8_t mode) {
  switch (mode) {
    case INPUT:
      if (pin < 8) {
        bitClear(DDRD, pin);
        bitClear(PORTD, pin);
      } else if (pin < 14) {
        bitClear(DDRB, (pin - 8));
        bitClear(PORTB, (pin - 8));
      } else if (pin < 20) {
        bitClear(DDRC, (pin - 14));
        bitClear(PORTC, (pin - 14));
      }
      return;
    case OUTPUT:
      if (pin < 8) {
        bitSet(DDRD, pin);
        bitClear(PORTD, pin);
      } else if (pin < 14) {
        bitSet(DDRB, (pin - 8));
        bitClear(PORTB, (pin - 8));
      } else if (pin < 20) {
        bitSet(DDRC, (pin - 14));
        bitClear(PORTC, (pin - 14));
      }
      return;
    case INPUT_PULLUP:
      if (pin < 8) {
        bitClear(DDRD, pin);
        bitSet(PORTD, pin);
      } else if (pin < 14) {
        bitClear(DDRB, (pin - 8));
        bitSet(PORTB, (pin - 8));
      } else if (pin < 20) {
        bitClear(DDRC, (pin - 14));
        bitSet(PORTC, (pin - 14));
      }
      return;
  }
}

digitalWrite #

void digitalWriteFast(uint8_t pin, bool x) {
  // раскомментируй, чтобы отключать таймер
  /*switch (pin) {
    case 3: bitClear(TCCR2A, COM2B1);
      break;
    case 5: bitClear(TCCR0A, COM0B1);
      break;
    case 6: bitClear(TCCR0A, COM0A1);
      break;
    case 9: bitClear(TCCR1A, COM1A1);
      break;
    case 10: bitClear(TCCR1A, COM1B1);
      break;
    case 11: bitClear(TCCR2A, COM2A1);
      break;
  }*/

  if (pin < 8) {
    bitWrite(PORTD, pin, x);
  } else if (pin < 14) {
    bitWrite(PORTB, (pin - 8), x);
  } else if (pin < 20) {
    bitWrite(PORTC, (pin - 14), x);
  }
}

digitalToggle #

Быстро инвертирует состояние пина:

void digitalToggleFast(uint8_t pin) {
  if (pin < 8) {
    bitSet(PIND, pin);
  } else if (pin < 14) {
    bitSet(PINB, (pin - 8));
  } else if (pin < 20) {
    bitSet(PINC, (pin - 14));
  }
}

digitalRead #

bool digitalReadFast(uint8_t pin) {
  if (pin < 8) {
    return bitRead(PIND, pin);
  } else if (pin < 14) {
    return bitRead(PINB, pin - 8);
  } else if (pin < 20) {
    return bitRead(PINC, pin - 14);
  }
}

analogWrite #

void analogWriteFast(uint8_t pin, uint16_t duty) {
  if (!duty) {
    digitalWrite(pin, LOW);
    return;
  }

  switch (pin) {
  case 5:
    bitSet(TCCR0A, COM0B1);
    OCR0B = duty;
    return;
  case 6:
    bitSet(TCCR0A, COM0A1);
    OCR0A = duty;
    return;
  case 10:
    bitSet(TCCR1A, COM1B1);
    OCR1B = duty;
    return;
  case 9:
    bitSet(TCCR1A, COM1A1);
    OCR1A = duty;
    return;
  case 3:
    bitSet(TCCR2A, COM2B1);
    OCR2B = duty;
    return;
  case 11:
    bitSet(TCCR2A, COM2A1);
    OCR2A = duty;
    return;
  }
}

Прочее #

analogRead #

uint16_t analogReadFast(uint8_t pin, uint8_t reference = DEFAULT) {
  pin = ((pin < 8) ? pin : pin - 14);    // analogRead(2) = analogRead(A2)
  ADMUX = (reference << 6) | pin;
  bitSet(ADCSRA, ADSC);
  while (ADCSRA & (1 << ADSC));
  return ADC;
}

External interrupts #

// прицепляем аппаратные прерывания напрямую (пин, тип)
void attachInterruptFast(uint8_t num, uint8_t type) {
  switch (num) {
    case 0:
      EICRA = (EICRA & 0x0C) | type;
      bitSet(EIMSK, INT0);
      return;
    case 1:
      EICRA = (EICRA & 0x03) | (type << 2);
      bitSet(EIMSK, INT1);
      return;
  }
}

void detachInterruptFast(uint8_t num) {
  bitClear(EIMSK, num);
}

// векторы. В них будет прыгать прерывание
ISR(INT0_vect) {
}

ISR(INT1_vect) {
}

Частота и разрядность ШИМ #

Аппаратный ШИМ через analogWrite генерируется аппаратными таймерами, по умолчанию они сконфигурированы следующим образом:

Таймер Пины Частота Разрешение
Timer 0 D5 и D6 976 Гц 8 бит (0-255)
Timer 1 D9 и D10 488 Гц 8 бит (0-255)
Timer 2 D3 и D11 488 Гц 8 бит (0-255)

На самом деле все таймеры спокойно могут выдавать ШИМ сигнал с частотой до пары МГц, а таймер 1 - вообще 16 битный и на той частоте, которую ему дали Arduino, мог бы работать с разрешением 15 бит вместо 8, а это 32768 градаций вместо 256! Так к чему такая несправедливость? Таймер 0 занимается отсчётом времени и настроен так, чтобы миллисекунды тикали точно. А остальные таймеры видимо получили такие же настройки, чтобы всё работало одинаково.

Вот набор готовых команд для перенастройки таймеров. Нужно добавить код например в setup и последующий вызов analogWrite на соответствующих пинах будет работать на новой частоте:

Пины D5 и D6 (Timer 0) - 8 бит
// Пины D5 и D6 - 62.5 кГц
TCCR0B = 0b00000001;  // x1
TCCR0A = 0b00000011;  // fast pwm

// Пины D5 и D6 - 31.4 кГц
TCCR0B = 0b00000001;  // x1
TCCR0A = 0b00000001;  // phase correct

// Пины D5 и D6 - 7.8 кГц
TCCR0B = 0b00000010;  // x8
TCCR0A = 0b00000011;  // fast pwm

// Пины D5 и D6 - 4 кГц
TCCR0B = 0b00000010;  // x8
TCCR0A = 0b00000001;  // phase correct

// Пины D5 и D6 - 976 Гц - по умолчанию
TCCR0B = 0b00000011;  // x64
TCCR0A = 0b00000011;  // fast pwm

// Пины D5 и D6 - 490 Гц
TCCR0B = 0b00000011;  // x64
TCCR0A = 0b00000001;  // phase correct

// Пины D5 и D6 - 244 Гц
TCCR0B = 0b00000100;  // x256
TCCR0A = 0b00000011;  // fast pwm

// Пины D5 и D6 - 122 Гц
TCCR0B = 0b00000100;  // x256
TCCR0A = 0b00000001;  // phase correct

// Пины D5 и D6 - 61 Гц
TCCR0B = 0b00000101;  // x1024
TCCR0A = 0b00000011;  // fast pwm

// Пины D5 и D6 - 30 Гц
TCCR0B = 0b00000101;  // x1024
TCCR0A = 0b00000001;  // phase correct
Пины D9 и D10 (Timer 1) - 8 бит
// Пины D9 и D10 - 62.5 кГц
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00001001;  // x1 fast pwm

// Пины D9 и D10 - 31.4 кГц
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00000001;  // x1 phase correct

// Пины D9 и D10 - 7.8 кГц
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00001010;  // x8 fast pwm

// Пины D9 и D10 - 4 кГц
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00000010;  // x8 phase correct

// Пины D9 и D10 - 976 Гц
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00001011;  // x64 fast pwm

// Пины D9 и D10 - 490 Гц - по умолчанию
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00000011;  // x64 phase correct

// Пины D9 и D10 - 244 Гц
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00001100;  // x256 fast pwm

// Пины D9 и D10 - 122 Гц
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00000100;  // x256 phase correct

// Пины D9 и D10 - 61 Гц
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00001101;  // x1024 fast pwm

// Пины D9 и D10 - 30 Гц
TCCR1A = 0b00000001;  // 8bit
TCCR1B = 0b00000101;  // x1024 phase correct
Пины D9 и D10 (Timer 1) - 10 бит
// Пины D9 и D10 - 15.6 кГц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00001001;  // x1 fast pwm

// Пины D9 и D10 - 7.8 кГц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00000001;  // x1 phase correct

// Пины D9 и D10 - 2 кГц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00001010;  // x8 fast pwm

// Пины D9 и D10 - 977 Гц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00000010;  // x8 phase correct

// Пины D9 и D10 - 244 Гц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00001011;  // x64 fast pwm

// Пины D9 и D10 - 122 Гц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00000011;  // x64 phase correct

// Пины D9 и D10 - 61 Гц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00001100;  // x256 fast pwm

// Пины D9 и D10 - 30 Гц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00000100;  // x256 phase correct

// Пины D9 и D10 - 15 Гц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00001101;  // x1024 fast pwm

// Пины D9 и D10 - 7.5 Гц 10bit
TCCR1A = 0b00000011;  // 10bit
TCCR1B = 0b00000101;  // x1024 phase correct
Пины D3 и D11 (Timer 2) - 8 бит
// Пины D3 и D11 - 62.5 кГц
TCCR2B = 0b00000001;  // x1
TCCR2A = 0b00000011;  // fast pwm

// Пины D3 и D11 - 31.4 кГц
TCCR2B = 0b00000001;  // x1
TCCR2A = 0b00000001;  // phase correct

// Пины D3 и D11 - 8 кГц
TCCR2B = 0b00000010;  // x8
TCCR2A = 0b00000011;  // fast pwm

// Пины D3 и D11 - 4 кГц
TCCR2B = 0b00000010;  // x8
TCCR2A = 0b00000001;  // phase correct

// Пины D3 и D11 - 2 кГц
TCCR2B = 0b00000011;  // x32
TCCR2A = 0b00000011;  // fast pwm

// Пины D3 и D11 - 980 Гц
TCCR2B = 0b00000011;  // x32
TCCR2A = 0b00000001;  // phase correct

// Пины D3 и D11 - 980 Гц
TCCR2B = 0b00000100;  // x64
TCCR2A = 0b00000011;  // fast pwm

// Пины D3 и D11 - 490 Гц - по умолчанию
TCCR2B = 0b00000100;  // x64
TCCR2A = 0b00000001;  // phase correct

// Пины D3 и D11 - 490 Гц
TCCR2B = 0b00000101;  // x128
TCCR2A = 0b00000011;  // fast pwm

// Пины D3 и D11 - 245 Гц
TCCR2B = 0b00000101;  // x128
TCCR2A = 0b00000001;  // phase correct

// Пины D3 и D11 - 245 Гц
TCCR2B = 0b00000110;  // x256
TCCR2A = 0b00000011;  // fast pwm

// Пины D3 и D11 - 122 Гц
TCCR2B = 0b00000110;  // x256
TCCR2A = 0b00000001;  // phase correct

// Пины D3 и D11 - 60 Гц
TCCR2B = 0b00000111;  // x1024
TCCR2A = 0b00000011;  // fast pwm

// Пины D3 и D11 - 30 Гц
TCCR2B = 0b00000111;  // x1024
TCCR2A = 0b00000001;  // phase correct
Пример
void setup() {
    // Пины D5 и D6 - 7.8 кГц
    TCCR0B = 0b00000010;  // x8
    TCCR0A = 0b00000011;  // fast pwm

    // Пины D3 и D11 - 62.5 кГц
    TCCR2B = 0b00000001;  // x1
    TCCR2A = 0b00000011;  // fast pwm

    // Пины D9 и D10 - 7.8 кГц 10bit
    TCCR1A = 0b00000011;  // 10bit
    TCCR1B = 0b00000001;  // x1 phase correct

    analogWrite(3, 15);
    analogWrite(5, 167);
    analogWrite(6, 241);
    analogWrite(9, 745);    // диапазон 0-1023
    analogWrite(10, 345);   // диапазон 0-1023
    analogWrite(11, 78);
}

void loop() {
}

При изменении частоты на пинах D5 и D6 вы потеряете функции времени (millis(), delay(), pulseIn(), setTimeout() и прочие), они будут работать некорректно. Также перестанут работать библиотеки, которые их используют!

Библиотеки #

  • Библиотека PWM (GitHub) - мощная библиотека, позволяющая менять частоту ШИМ на микроконтроллерах ATmega48 / 88 / 168 / 328 / 640 / 1280 / 1281 / 2560 / 2561
    • Установить любую частоту ШИМ, предделитель, TOP
    • При работе с 8-битными таймерами доступен только один канал (например на ATmega328 останутся D3, D5, D9 и D10)
    • Позволяет работать с 16-битными таймерами на более высоком разрешении (16 бит вместо стандартных 8)
    • Библиотека написана очень сложно, по кускам её растащить не получится
  • Библиотека GyverPWM (GitHub) - позволяет очень гибко работать с ШИМ на микроконтроллере ATMega328p:
    • Установить любую частоту ШИМ в диапазоне 250 Гц - 200 кГц
    • Выбор разрядности: 4-8 бит для 8 бит таймеров, 4-16 бит для 16-бит таймеров (при разрядности 4 бита частота ШИМ составляет 1 МГЦ 😀 )
    • Выбор режима работы ШИМ: Fast PWM или Phase-correct PWM (благоприятен для электродвигателей)
    • Генерация меандра на пине D9 с частотой от 2 Гц до 8 МГц с максимальной точностью
    • При работе с 8-битными таймерами доступен только один канал (например на ATmega328 останутся D3, D5, D9 и D10)
    • Есть функции для перенастройки стандартного ШИМ, при котором не теряются ШИМ выходы
    • Библиотека написана очень просто, можно брать из неё код кусками
5 1 голос
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх