Урок применим к 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)
- Есть функции для перенастройки стандартного ШИМ, при котором не теряются ШИМ выходы
- Библиотека написана очень просто, можно брать из неё код кусками