| 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 | 
                     Похожие запросы: 
  | 
    
         |