01 |
Содержание:
|
|
02 |
Несмотря на кажущуюся схожесть с другой парой функций digitalRead() и digitalWrite(), которые работают по одному механизму, только в разных направлениях, функции analogRead() и analogWrite() имеют в корне разную природу, в первую очередь на аппаратном уровне. Поэтому и рассматривать их нужно в отрыве друг от друга — функционал analogRead() целиком и полностью реализован на «железном уровне» в аналого-цифровом преобразователе (АЦП), analogWrite() — в регистрах выходного сравнения OCR (ШИМ-генераторам) встроенных таймеров Arduino. |
|
03 |
Перед тем, как переходить к модификации кода функции analogWrite() в целях ускорения работы платы, автор рекомендует ознакомиться со статьей о ШИМ, таймерах Arduino, управляющих ими регистрах и работе таймеров с прерываниями. |
|
04 |
Ускоряем analogWrite() Проведем эксперимент — посмотрим на замеры времени исполнения функции analogWrite() на разных пинах (как известно у Arduino Uno их 6): |
|
05 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 unsigned long _timeStart, _timeEnd;
int pins[] = {6, 5, 9, 11, 3, 10};
int count = 10000;
void setup() {
Serial.begin(9600); // будем выводить информацию о тестировании
for (int i = 0; i < 6; i++) {
pinMode(pins[i], OUTPUT);
}
}
void loop() {
for (int i = 0; i < 6; i++) {
testAWrite(pins[i]);
}
delay(1000);
}
//=========================================================
void testAWrite(int pin) {
_timeStart = micros();
for (int i = 0; i < count; i++) {
analogWrite(pin, 100);
}
_timeEnd = micros();
String funcname = "analogWrite(" + (String)pin + ")";
showResult (funcname, count );
}
void showResult (String func, int iter) {
unsigned long timeTotal = _timeEnd - _timeStart;
unsigned long time1 = timeTotal / iter;
String strToOut = func + ". Total time: " + String(timeTotal) + " microsec, AVG time: " + String(time1) + " microseconds. (iterations " + String(iter) + ")";
Serial.println(strToOut);
} |
|
06 |
Результаты (порядок пинов подобран по убыванию скорости исполнения функции): |
|
07 |
1 2 3 4 5 6 analogWrite(6). Total time: 47164 microsec, AVG time: 4,72 microseconds. (iterations 10000)
analogWrite(5). Total time: 48152 microsec, AVG time: 4,81 microseconds. (iterations 10000)
analogWrite(9). Total time: 50056 microsec, AVG time: 5,00 microseconds. (iterations 10000)
analogWrite(11). Total time: 50056 microsec, AVG time: 5,00 microseconds. (iterations 10000)
analogWrite(3). Total time: 51320 microsec, AVG time: 5,13 microseconds. (iterations 10000)
analogWrite(10). Total time: 54488 microsec, AVG time: 5,45 microseconds. (iterations 10000) |
|
08 |
Разница исполнения функции analogWrite() на разных пинах достигает 0,7 мкс. |
|
09 |
Немного о таймерах Arduino ШИМ на Arduino целиком и полностью (не берем в расчет программную эмуляцию) реализован благодаря встроенным таймерам — и у Arduino Uno их 3 — два 8-битных (Timer0 и Timer2) и один 16-битный (Timer1). Каждый таймер управляется несколькими регистрами. Основные регистры управления — TCCRnA и TCCRnB. Ниже представлены биты, принадлежащие этим регистрам, которые нам понадобятся в рамках данной статьи:
|
Подробнее о таймерах Arduino, регистрах управления, ШИМ и прерываниях таймеров можно почитать в статье — Таймеры Arduino и прерывания |
10 |
|
|
11 |
Посмотрим на код функции analogWrite(): |
|
12 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 void analogWrite(uint8_t pin, int val)
{
pinMode(pin, OUTPUT);
if (val == 0)
{
digitalWrite(pin, LOW);
}
else if (val == 255)
{
digitalWrite(pin, HIGH);
}
else
{
switch(digitalPinToTimer(pin))
{
case TIMER0A:
// connect pwm to pin on timer 0, channel A
sbi(TCCR0A, COM0A1);
OCR0A = val; // устанавливаем уровень ШИМ
break;
// И так далее...
//case ... Много однотипных условий
//case ...
//case ...
//case ...
// ...
case NOT_ON_TIMER:
default:
if (val < 128) {
digitalWrite(pin, LOW);
} else {
digitalWrite(pin, HIGH);
}
}
}
} |
|
13 |
Именно все эти проверки switch-case и преобразования вносят свою, далеко не лучшую, лепту в скорость исполнения функции. Если присмотреться внимательнее, то дольше функция исполняется на тех пинах, которые имеют перед собой большее количество проверок switch-case — на их исполнение тоже уходит драгоценное время. |
|
14 |
Теперь, если знать на какой пин воздействовать, какой таймер за какой пин отвечает... |
|
15 |
|
|
16 |
...не составит труда сократить код функции до 2 строчек: |
|
17 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
void setup() {
// ...
pinMode(3, OUTPUT); // Не забываем предварительно выставить pinMode
}
void analogWritePin3(int val)
{
// Предполагается, что при val==0 или val==255 задается не ШИМ, а LOW или HIGH
sbi(TCCR2A, COM2B1);
OCR2B = val; // устанавливаем уровень ШИМ
} |
|
18 |
Посмотрим теперь на время исполнения новой функции: |
|
19 | Arduino (C++) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
unsigned long _timeStart, _timeEnd;
int count = 10000;
void setup() {
Serial.begin(9600); // будем выводить информацию о тестировании
pinMode(3, OUTPUT);
}
void loop() {
testAWrite();
delay(1000);
}
//=========================================================
void testAWrite() {
_timeStart = micros();
for (int i = 0; i < count; i++) {
analogWritePin3(100);
}
_timeEnd = micros();
showResult ("analogWritePin3()", count );
}
void showResult (String func, int iter) {
unsigned long timeTotal = _timeEnd - _timeStart;
unsigned long time1 = timeTotal / iter;
String strToOut = func + ". Total time: " + String(timeTotal) + " microsec, AVG time: " + String(time1) + " microseconds. (iterations " + String(iter) + ")";
Serial.println(strToOut);
}
void analogWritePin3(int val)
{
// Предполагается, что при val==0 или val==255 задается не ШИМ, а LOW или HIGH
sbi(TCCR2A, COM2B1);
OCR2B = val; // устанавливаем уровень ШИМ
} |
|
20 |
Результат: |
|
21 |
1 analogWritePin3(). Total time: 6920 microsec, AVG time: 0,69 microseconds. (iterations 10000) |
|
22 |
Впечатляет? Нам удалось сократить время исполнения примерно в 7 раз — до 0,69 мкс. Но как видно из примера такое достижение, как и в случае с digitalRead()/digitalWrite(), далось в ущерб гибкости, понятности и универсальности. Там где скорость исполнения не в приоритете, имеет смысл оставить штатную функцию analogWrite(). |
|
24 |
Похожие запросы:
|
|