Время выполнения отдельных команд Arduino

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

При программировании тех или иных критичных ко времени выполнения фрагментов кода нужно уметь оценить время выполнения отдельных команд точнее, чем "арифметические операции над числами с плавающей запятой выполняются существенно медленнее, чем над целыми." ( http://arduino.ru/Reference/Float ).

Ниже приведены результаты замеров времени выполнения некоторых конструкций и команд:

запись байта в память - 2 такта
запись 2 байтов       - 4 такта
запись 4 байтов       - 8 тактов
цикл int (long)       - 6 тактов
int ADD, SUB, MUL     - 6-7 тактов
int DIV               - 235-245 тактов
long ADD, SUB, MUL    - 15-17 тактов (22-23)
long DIV              - 670-680 тактов
int => float          - 70-72 тактов
float ADD, SUB, MUL   - 200-220 тактов
float DIV             - 550-560 тактов
sin(float)            - 2000 тактов / 124 мкс
millis                - 2 мкс
micros                - 4 мкс
digitalWrite          - 6-8 мкс, Mega - 8-9 mcs
digitalRead           - 4-6 мкс, Mega - 7 mcs
pinMode               - 5 мкс
analogRead            - 111-112 мкс
shiftOut              - 180-185 мкс, Mega - 210 

Измерения проводились для двух версий Arduino Uno 328, различающихся конструктивом чипа, Nano 328, а также Mega 2560. В целом результаты совпали, но для Меги отдельные команды (в основном - работы с портами) оказались чуть медленнее.

Интересный результат - независимость времени цикла от типа переменной цикла. Однако, подозреваю, что при константных пределах цикла оптимизатор мог попросту заменить один тип на другой, тем более, что, как показали измерения, время выполнения арифметических операций с long примерно в 2.5-3 раза больше времени выполнения аналогичных операций с int.

float, конечно, в среденм обрабатывается заметно дольше, чем целочисленные типы, но за одним исключением: деление long выполняется дольше. А т.к. деление вообще выполняется намного дольше других операций, иногда может иметь смысл производить вычисления не в long, а в float.

Из тригонометрии исследовался только синус, но и этого результата, мне кажется, достаточно для оценки прогизводительности тригонометрических функций.

Люблпытно, что команда запроса времени micros() выполняется вдвое дольше, чем millis(), хотя, по логике следовало бы сделать это наоборот.

Кстати, кто не знает, micros() округляет показания до ближайшей "четверки", т.е. результат ее выполнения всегда делится нацело на 4. Это для тактовой частоты 16 МГц. Для 8 МГц - на 8, т.е. результатом можкт быть 8, 16, 32, 48, 64, 80, 96..., но никак не 28 или 60.

Работа с портами происходит достаточно быстро, если не считать АЦП (analogRead()) и shiftOut(). В общем, ничего удивительного: обе команды имеют внутри цикл.

 

Но, вообще-то, самой медленной командой, пожалуй, следует считать работу с COM-портом, т.е. Serial.print(). В данном случае я ее не анализировал чтобы не засорять вывод результатов в этот самый порт, но примерно можно оценить так, что при скорости 9600 выврд одного символа занимает порядка 1000 мкс. Это оценка снизу.

Измерения выполнялись при помощи этого скетча.

// Time measurement programm
#include <avr/pgmspace.h> 

#define ml8 1320 // buffer size in bytes (byte, char)
#define ml16 ml8/2 // buffer size in 2-bytes words (int)
#define ml32 ml8/4 // buffer size in 4-bytes words (long, float)

union buff_union {
  byte buff_b[ml8];
  int buff_i[ml16];
  long buff_l[ml32];
  float buff_f[ml32];
};

buff_union bu;

void setup() {
  Serial.begin(115200);
  pinMode(3, OUTPUT);
  pinMode(11, OUTPUT);
}

void PrintRes(float a, int i, const __FlashStringHelper* str) {
  Serial.print(str);
  Serial.print(a);
  Serial.print(F(" mcs\t"));
  Serial.print(a*16);
  Serial.print(F(" cycles\t"));
  Serial.println(i);
}

void loop() {
  Serial.print(F("   Time Measurements, byte size:\t"));
  Serial.print(ml8);
  Serial.print(F(",\tint size:\t"));
  Serial.print(ml16);
  Serial.print(F(",\tfloat size:\t"));
  Serial.println(ml32);
  int i;

// (01) fill bytes
  long t0 = micros();
  for(i = 0; i < ml8; i++) {
    bu.buff_b[i] = 3;
  }
  long t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("Fill bytes:\t"));

// (02) fill bytes
  long j;
  t0 = micros();
  for(j = 0; j < ml8; j++) {
    bu.buff_b[j] = 3;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/j, (int)j, F("Fill bytes(L):\t"));

// (03) fill intrgers
  t0 = micros();
  for(i = 0; i < ml16; i++) {
    bu.buff_i[i] = 312;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("Fill ints:\t"));

// (04) fill long
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_l[i] = 123312;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("Fill longs:\t"));

// (05) fill float
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_f[i] = 1.234567;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("Fill floats:\t"));

// (06) run millis (byte)
  t0 = micros();
  for(i = 0; i < ml8; i++) {
    bu.buff_b[i] = (byte)millis();
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("millis(b):\t"));

// (07) run millis (int)
  t0 = micros();
  for(i = 0; i < ml16; i++) {
    bu.buff_i[i] = (int)millis();
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("millis(i):\t"));

// (08) run millis (long)
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_l[i] = millis();
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("millis(L):\t"));

// (09) run micros (long)
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_l[i] = micros();
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("micros(L):\t"));

// (10) int mul
  t0 = micros();
  for(i = 0; i < ml16; i++) {
    bu.buff_i[i] = i*37;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("int MUL:\t"));

// (11) int div
  t0 = micros();
  for(i = 0; i < ml16; i++) {
    bu.buff_i[i] = 32719/(i+1);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("int DIV:\t"));

// (12) int add
  t0 = micros();
  for(i = 0; i < ml16; i++) {
    bu.buff_i[i] = 21719 + i;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("int ADD:\t"));

// (13) int sub
  t0 = micros();
  for(i = 0; i < ml16; i++) {
    bu.buff_i[i] = 32719 - i;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("int DIV:\t"));

// (14) long mul
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_l[i] = i*37831;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("long MUL:\t"));

// (15) long div
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_l[i] = 2123456789/(i+1);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("long DIV:\t"));

// (16) long add
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_l[i] = 2123456789 + i;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("long ADD:\t"));

// (17) long sub
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_l[i] = 2123456789 - i;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("long SUB:\t"));

// (18) int => float
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_f[i] = i;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("int => float:\t"));

// (19) float mul
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_f[i] = i*378.31;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("float MUL:\t"));

// (20) float div
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_f[i] = 212345.6789/(i+1);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("float DIV:\t"));

// (21) float add
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_f[i] = 212345.6789 + i;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("float AD:\t"));

// (22) float sub
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_f[i] = 2123.456789 - i;
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("float SUB:\t"));

// (23) float sin
  t0 = micros();
  for(i = 0; i < ml32; i++) {
    bu.buff_f[i] = sin(0.123*i);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("sin(float):\t"));

// (24) digitalWrite
  t0 = micros();
  for(i = 0; i < ml8; i++) {
    digitalWrite(2, bu.buff_b[i] && 1);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("digitalWrite:\t"));

// (25) digitalWrite
  t0 = micros();
  for(i = 0; i < ml8; i++) {
    digitalWrite(11, bu.buff_b[i] && 1);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("digitalWrite:\t"));

// (26) digitalRead
  t0 = micros();
  for(i = 0; i < ml8; i++) {
    bu.buff_b[i] = digitalRead(2);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("digitalRead:\t"));

// (27) digitalRead
  t0 = micros();
  for(i = 0; i < ml8; i++) {
    bu.buff_b[i] = digitalRead(10);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("digitalRead:\t"));

//  (28) analogRead
  t0 = micros();
  for(i = 0; i < ml16; i++) {
    bu.buff_i[i] = analogRead(A0);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("analogRead:\t"));

// (29) pinMode
  t0 = micros();
  for(i = 0; i < ml16; i++) {
    pinMode(3, i && 1);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("pinMode:\t"));
  
// (30) shiftOut
  t0 = micros();
  for(i = 0; i < ml16; i++) {
    shiftOut(3, 11, LSBFIRST, i && 0xff);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("shiftOut:\t"));
  
// (31) delayMicroseconds
  t0 = micros();
  for(i = 0; i < ml8; i++) {
    delayMicroseconds(1);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("delayMs(1):\t"));
  
// (32) delayMicroseconds
  t0 = micros();
  for(i = 0; i < ml8; i++) {
    delayMicroseconds(10);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("delayMs(10):\t"));
  
// (33) delayMicroseconds
  t0 = micros();
  for(i = 0; i < ml8; i++) {
    delayMicroseconds(100);
  }
  t1 = micros();
  PrintRes((float)(t1-t0)/i, i, F("delayMs(100):\t"));

 
  delay(15000);
}

При написании аналогичных программ следует учитывать, что Arduino IDE использует оптимизирующий компилятор, который "вырезает" все "ненужные" вычисления, т.е. вычисления, результаты которых не используются в программе.

MagicianT
Offline
Зарегистрирован: 03.10.2015
Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Длительность команд ЦПУ хорошо описана в соответствующих книжках по AVR. Не помню откуда выкачивал avr-sys.pdf "Система команд 8-и разрядных RISC контроллеров семейства AVR" - даны потактовки для каждой команды для всех режимов.

Чтение/запись в память идет за 2 такта согласно скорости работы SRAM, и на Мега2560 может идти и 3 такта (до 6), если к ней подключено расширение SRAM внешней память (до 64 килобайт напрямую) через имеющийся у неё x-bus интерфейс (занимает 19 ног), И если обращение идет к адресам, превышаюшим 8200 байт SRAM (для внутренних адресов всегда 2 такта). Настраивается при работе с x-bus. Соответственно, нет никакой нужды измерять команды ЦПУ.

Кстати, x-bus позволяет и легко расширить память меги. Компилятору достаточно только указать верхний допустимый предел в 64К или сколько поставили.. в Сети есть схемы, легко расширяющие до 512к, себе рисовал до 2М на 3 корпусах 74 серии и плюс 2 ноги меги.. но делать пока не стал, ибо микросхема памяти, из-за которой и спроектировал - оказалась банально битой.

Ну а по скорости выполнения функций Wiring ... что есть, то есть. Каждый вполне способен сам свести digitalWrite() или analogWrite() до 2-х тактов (0.13мксек). Скорость analogRead() в 8-16мкс, тоже является "штатной" без учета времени разогрева ADC, указанного в даташите как 125кгц скорость аналогового чтения, а при снижении разрядности может достигать более 1 Мгц (1мксек) - регулируется настройками ADC.

micros() и все остальные типовые решения подсчета времени на базе 0-го таймера - да, считают микросекунды по 4шт. Но, это не обязательно так. У меня есть micros() с точностью до 0.5мксек и менее - счел нецелесообразным из-за пожирания процессорного времени процедурой обработки прерываний от таймера. Но .. можно настроить (зачем только?).

Ну и советую не забывать, что штатная функция от Wiring() - millis() считает время "скачками" по 42 миллисекунды и оно плавает на плюс-минус 1 миллисекунду с этим периодом. Из-за этого, не бывает 42, 84 .. и т.д. "миллисекунд". После 41 всегда идет 43. :)

А в целом, спасибо за оценку времени исполнения функций Wiring...

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

2 такта на память, как понимаю из-за того, что команды чтения/записи содержат в себе адрес, то есть занимают 2 слова, а не одно. или при косвенном обращении надо выложить на шину содержимое регистра дополнительным тактом.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Из моих расчетов по даташитам:

Время выполнения без учета вызова функций:

1. Процедура обработки прерывания Т0: у Wiring = 3.5мксек, можно свести к 2.5мксек, за счет отказа от ихней millis()

2. micros() = 2.625 мксек, и "лучше" не получается.

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Arhat109-2 пишет:

Длительность команд ЦПУ хорошо описана в соответствующих книжках по AVR.

...

Соответственно, нет никакой нужды измерять команды ЦПУ.

Я, кажется, нигде не писал об AVR и не ставил такой цели. Я писал об Arduino, который может быть на различных платформах. Да, в проведенных мной тестах принимали участие только AVR, но никто не мешает запустить этот код, скажем, на Duo (изменив строчку с include) и поучить актуальные результаты для другой архитектуры.

Цитата:

У меня есть micros() с точностью до 0.5мксек и менее - счел нецелесообразным из-за пожирания процессорного времени процедурой обработки прерываний от таймера. Но .. можно настроить (зачем только?).

Ну и советую не забывать, что штатная функция от Wiring() - millis() считает время "скачками" по 42 миллисекунды и оно плавает на плюс-минус 1 миллисекунду с этим периодом. Из-за этого, не бывает 42, 84 .. и т.д. "миллисекунд". После 41 всегда идет 43. :)

Мне вообще не нравится, как сделан учет времени в Arduino. Я бы редпочел структуру длиной не менее 6 байт с "единицей" в районе 10-20 мкс и возможностью получать 4-байтовое число тремя функциями: байты с 0 по 3, с 1 по 4 и со 2 по 5. Т.е. одно в единицах пусть 16 мкс, второе - 4 мс и третье - 1 с (это примерно, все - степени 2).

До исследования свойств millis() и micros() я пока не добрался, но сильно подозреваю, что они друг с другом не связаны, т.е. нельзя запросить micros() и получить дробную часть millis(). НО как-нибудь до этого доберусь. Для этого, минимум, надо продключить внешние RTC. Тем более, Вы подсказали, в каком направлении искать.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Да, в Wiring они никак по сути не связаны. micros() получает значение микросекунд непосредственно из структур данных учета времени ovf_count и регистра таймера Т0 с учетом его возможного переполнения, но ещё не обработанном прерывании. Как оказалось это важно. У меня сделано точно также, и по сути получилось "байт в байт".

millis() там работает практически независимо, исключительно на данных от процедуры обработки прерываний. Зато быстро. Но, из-за обеспечения корректной работы millis(), процедура обработчика перегружена: она считает "накопление" ошибки времени для ускорения millis(). Потому как счетчик переполнений ovf_count переполняется каждую 1024 микросекунду, а не каждую 1000. Вот эти 24 микросекунды и приходится накапливать и потом изменять время millis() скачками.

Поэтому у себя отказался от фракционного накопления, а все измерения делаю прямым консистентным чтением счетчика переполнений есть макрос getOvfCount(). Быстро, дешево и практично. Неудобство только в том, что время бежит по 1024 микросекунды, и константы задержек приходится пересчитывать вручную домножая миллисекунды на 0.976 на куркуляторе. Сделал себе millis() тоже, но на целочисленном делении, поэтому медленно и практически оказалась не нужна. Счетчика переполнений хватает "за глаза".

Да, ещё есть замечательные функции _delay_loop_1() и _delay_loop2() в самой библиотеке AVR. Они считают микросекундные задержки с высокой точностью ассемблерными вставками. Первая принимает байтовое значение по 3 микросекунды, а вторая 16бит по 4 микросекунды.

nkk
nkk аватар
Offline
Зарегистрирован: 18.03.2016

Немного не понятно, проясните, пожалуйста, какое отличие в тактах у операций, к примеру:

x * 4000

и

x << 12

?

MagicianT
Offline
Зарегистрирован: 03.10.2015

x << 12= x * 4096

 

nkk
nkk аватар
Offline
Зарегистрирован: 18.03.2016

Очень приятно общаться с образованными людьми, но Вы не поняли.

Я хочу узнать, чтоит ли оптимизировать программу под замену умножения сдвигами и что я на этом выиграю.

Например, в этой теме - http://arduino.ru/forum/programmirovanie/zapusk-funktsii-s-zadannoi-chastotoi#comment-186323 вместо задердки в 4 секунды, я стремился выполнять пересчет частоты оборотов раз в 3.750 секунды только для того, чтобы при расчете нужно было количество опознаных оборотов умножать на 16, а не на 14.65

MagicianT
Offline
Зарегистрирован: 03.10.2015
Зависит от размера операндов. 
Например если 8-бит(макс. значение 256 - без знака) умножать на 8-бит то умножение займет 2 такта (125 наносек), а сдвиг тратит 1 такт на каждый сдвиг на одну позицию, т.е. если надо сдвинуть на 8 влево (типа умножаем на 256) то потратите 8 тактов, и оптимизация в проигрыше.
С 16-битными операндами, скорее всего оптимизация сдвигом окажется быстрее, ардуино не умеет умножать 16х16 а делает 32х32, что занимает уйму времени 5 microsec. на 8-битном МК. Особенно, если сдвигать немного, компиллёр кстати этим успешно пользуется, и может сам без вашего ведома позаменять умножение на степени 2-ки сдвигом.
Если вопрос оптимизации остро стоит, то надо смотреть ассемблерный код, который IDE генерирует перед тем как залить в ардуино, и там считать такты по ассемблерным инструкциям.
Можно проще, если быстро оценить результат оптимизации кода, делаете FOR цикл и крутите с умножением, время засекаете через micros() до и после. То же самое и со сдвигом. Переменные надо volatile, а то оптимизатор выкинет всё.
Joiner
Offline
Зарегистрирован: 04.09.2014

Загрузил скетч из стартового сообщения на две ардуинки. Вот что получилось

В общем совершенно одинаково для двух ардуинок.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

nkk пишет:

Я хочу узнать, чтоит ли оптимизировать программу под замену умножения сдвигами и что я на этом выиграю.

Лет десять назад - стоило. Сейчас - однозначно нет: современные компиляторы достаточно умны, чтобы в подавляющем большинстве случаев сгенерировать оптимальный код. И скорее всего, все ваши попытки оптимизировать окажутся ничуть не лучше встроенных в компилятор возможностей, в итоге - только время потратите.

Безусловно, это лишь общие слова, конкретика бывает разной, но в общем, опять же случае - premature optimization is the root of all evil (c) Дональд Кнут, емнип.

 

Jeka_M
Jeka_M аватар
Offline
Зарегистрирован: 06.07.2014

Joiner пишет:

В общем совершенно одинаково для двух ардуинок.

А что могло быть не одинаково на двух одинаковых микроконтроллерах, работающих на одинаковой тактовой частоте?

Joiner
Offline
Зарегистрирован: 04.09.2014

Один МК в DIP корпусе, другой в другом, маленьком (не знаю как называется), и второй МК работает на частоте 16000000 на напряжении 3.3 вольта. У первого МК нет А6,А7, а у второго есть. Поэтому я подумал, что может быть  есть какая-нибудь разница.

Jeka_M
Jeka_M аватар
Offline
Зарегистрирован: 06.07.2014

Нет никакой разницы, у них одинаковые ядро и переферия. Тип корпуса и количество выводов на производительность не влияют, они не участвуют в каких-либо расчётах. Напряжение 3,3 вольта тоже не влияет, если МК стабильно работает на той же тактовой частоте (16МГц), что и при 5 вольтах.

Joiner
Offline
Зарегистрирован: 04.09.2014

Jeka_M пишет:

Нет никакой разницы, ........ 3,3 вольта тоже не влияет, если МК стабильно работает на той же тактовой частоте (16МГц), что и при 5 вольтах.

Кстати...

Меня терзает вопрос, но мои познания не находят на него ответ.

У меня есть несколько таких ардуинок про мини

Все время использовал их как пятивольтовые, но на днях решил переключить их на 3.3 вольта. Перепаял перемычку и стал питать напряжением 3.3 вольта. Но так и не понял, что на плате меняют перемычки, которые на фотке обозначены Output Voltage Set, и надо ли ее питать напряжением 3.3 в? И что обозначают надписи внизу на правой картинке?

nkk
nkk аватар
Offline
Зарегистрирован: 18.03.2016

Эта перемычка позволяет подстраивать выходное напряжение линейного стабилизттора и никак не влияет на то, что вы подаете на VCC - скорее, на то, что получите с VCC если на вход RAW подадите питание.

На надписи внизу на "заводе" должны были нанести пометки и обозначить, на какие напряжение и частоту настроена ардуинка. Судя по большому диапазону частот, по пометкам на этих надписях можно было бы судить о том, какой внешний керамический резонатор припаян к плате.

http://microsin.net/programming/avr/beginning-embedded-electronics-part3.html

Послепрочтения этой статьи, я, чё-то, стал думать, сто эта маленьая хренька как раз резонатор, а не кварц.

Joiner
Offline
Зарегистрирован: 04.09.2014

nkk пишет:

Эта перемычка ......

Понял...примерно.

Надо просто поподавать, поизмерять.

P.S. Очень порадовал переходничек USB-TTL (не уверен, что правильно назвал...ну которым ардуинку питаю и заливаю скетчи). Так вот....подключил, стал измерять напряжение на VCC. Ардуинка моргнула и перезагрузилась. Измерил еще раз....такая же история. Посмотрел на тестер, а он включен на ток 20А. Короче пару раз сделал коротыш, но все осталось живое....ура!

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Joiner пишет:

..... Перепаял перемычку и стал питать напряжением 3.3 вольта. Но так и не понял, что на плате меняют перемычки, которые на фотке обозначены Output Voltage Set, и надо ли ее питать напряжением 3.3 в? И что обозначают надписи внизу на правой картинке?

http://arduino.stackexchange.com/questions/3728/can-i-change-vcc-pin-to-3-3v-on-a-5v-arduino-pro-micro

Перемычка стоит- замкнуты вход и выход стабилизатора.
Перемычка не стоит - стабилизатор работает, а его выходное  напряжение почему-то  3.3 Вольта.
Так совпало.

Это обычно так.
Но у вас похоже идёт замыкание части делителя напряжения.
То есть регулировка напряжения стабилизатора.
Где-то так : http://lib.qrz.ru/node/3886

Joiner
Offline
Зарегистрирован: 04.09.2014

trembo пишет:

Joiner пишет:

..... Перепаял перемычку и стал питать напряжением 3.3 вольта. Но так и не понял, что на плате меняют перемычки, которые на фотке обозначены Output Voltage Set, и надо ли ее питать напряжением 3.3 в? И что обозначают надписи внизу на правой картинке?

http://arduino.stackexchange.com/questions/3728/can-i-change-vcc-pin-to-3-3v-on-a-5v-arduino-pro-micro

Перемычка .................................
То есть регулировка напряжения стабилизатора.
.................................

Спасибо, проверю. На плате стоит какая-то пятиногая штучка, предположительно как по первой ссыле. Только там не одна перемычка, как на схеме а три контактика...и замыкается средний или с левым, или с правым.

Joiner
Offline
Зарегистрирован: 04.09.2014

trembo пишет:

.........................................
Но у вас похоже идёт замыкание части делителя напряжения.
То есть регулировка напряжения стабилизатора.
............................................

trembo, спасибо. Проверил, все так как Вы сказали. Если на вход RAW подать напряжение 7-12в, то при левом положении перемычки на входе  VCC - 5в, при правом положении перемычки VCC - 3.3в. Т.Е. это имеет значение, если питать нестабилизированным напряжением. А если стабилизированным 3.3в, то будет ардуинка на 3.3в, если стабилизированным 5.0в, то будет ардуинка на 5.0в.

Попробовал свою ардуинку питать напряжением 3.3в, и хоть кварц на ней 16 мегагерц, она работает прекрасно. Но, сразу снимается столько геммороя с подключением дисплеев и модулей, которые любят 3.3в.

Все......перехожу на 3.3в!!!!!

vde69
Offline
Зарегистрирован: 10.01.2016

в кто сравнивал по скорости операции с EEROM и SD ???

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

nkk пишет:

Очень приятно общаться с образованными людьми, но Вы не поняли.

Я хочу узнать, чтоит ли оптимизировать программу под замену умножения сдвигами и что я на этом выиграю.

Например, в этой теме - http://arduino.ru/forum/programmirovanie/zapusk-funktsii-s-zadannoi-chastotoi#comment-186323 вместо задердки в 4 секунды, я стремился выполнять пересчет частоты оборотов раз в 3.750 секунды только для того, чтобы при расчете нужно было количество опознаных оборотов умножать на 16, а не на 14.65

1. На AVR умножение выполняется аппаратно, так что замиенять на сдвиг в целях оптимизации не стоит.

2. Все рассуждения про уминожения и сдвиги справедливы для целых чисел. Но приведенное Вами 14.65 -  число вещественное. Для вещественных чисел ни о какой замене умногжения сдвигами и речи быть не может. Ну и операции над ними выполняются во многол аз дольше, чем над целыми (кроме деления). Так что Вы снсчала определитесь, с какими числеми работаете.

Joiner
Offline
Зарегистрирован: 04.09.2014

Вот ролик про скорости. Ни чего не понял по английски, но интересно https://www.youtube.com/watch?v=rkIfsYRshRQ

Logik
Offline
Зарегистрирован: 05.08.2014

Наверно пора обновить тему. Актуализируем информацию для новых устройств.

OrangPi PC. Код для измерения длительности выполнения операции.

  pinMode (16, OUTPUT) ;

  for(int n=0;n<10;n++)
  {
   int m=millis();
   for(int i=1000000;i;i--)
   {
    digitalWrite(16, HIGH);
    digitalWrite(16, LOW);
   }
   printf("T=%u\n",millis()-m);
  }

 Вывод на экран.

 
T=770
T=704
T=709
T=705
T=709
T=705
T=705
T=710
T=704
T=704
 
Получаем миллион циклов за 700мсек, 0,7мксек на цикл или 0,35мксек на выполнение digitalWrite().
А что на выходе? А неизвестно, нечем глянуть. Но можна померить вольтметром постоянного тока. При прохождении теста показывает 1,5-1,55В. Т.е. похоже на выходе таки меандр.
 
В следующий раз потещу прямую работу с портом.
Arhat109-2
Offline
Зарегистрирован: 24.09.2015

То есть у Вас получилось что 4-х ядерный 1600мгц АРМ всего лишь в 20 раз быстрее дергает ногодрыги? Круть .. :)

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Logik, Вам полтора мегагерца "нечем глянуть"?

a5021
Offline
Зарегистрирован: 07.07.2013

Logik пишет:
В следующий раз потещу прямую работу с портом.

Протестил на двадцатипятирублевом STM32F030F4P6. Результат -- МК трясет ногой с чаcтотой 8мгц, при работе ядра на 64мгц. Новый проект было лень создавать, врезал тест прямо в свои текущий. Код:

  turnPLLOn(RCC_CFGR_PLLMUL16);  // Set CPU Freq (F_CPU = 4Mhz x 16 = 64Mhz)
  initTIM2();                    // Run TIM2 to measure timings
  
  TIM2->EGR = TIM_EGR_CC1G;      // Capture counter before cycle starts
  
  for(uint32_t i = 1000000; i; i--) { // Do 1M pin switchings
    GPIOA->BSRR = (uint16_t)(1 << 2); // Set pin PA2 HIGH
    GPIOA->BRR =  (uint16_t)(1 << 2); // Set pin PA2 LOW
  }
  
  TIM2->EGR = TIM_EGR_CC2G;      // Capture counter after cycle ends
 
  uint32_t t = TIM2->CCR1;       // Get time of the start
  t = (TIM2->CCR2 - t) / 64;     // Find test duration in us
  __IO uint8_t f  = 1000000 / t; // Find pin swithing speed in Mhz

  if (f != 0) { // just for inspecting the var
    turnPLLOff();       // resed CPU Freq
    while(1);           // stop here
  }

Пруф:

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

То есть у Вас получилось что 4-х ядерный 1600мгц АРМ всего лишь в 20 раз быстрее дергает ногодрыги? Круть .. :)

Это не у меня "так получилось" это у авторов WiringPi так получилось. Но все достаточно легко обяснимо.

Четырехядерность - хороше, но мое приложение разумеется работает в один поток, остальные 3 ОС использует на системные нужды, т.е. только  не мешает приложению. На этом их роль ограничивается. Проц работает не на 1600 а на 1400, что не особо важно, но считаем исходя из этого. Проц оранжа выполняет такты быстрей в 1400/16=87 раз. Код выполняется в 7мс/0,35=20 раз быстрей. Следовательно на один вызов digitalWrite у оранжа уходит в 87/20=4,35 раза больше тактов чем а 328-го. Далее по работе например STM32 известно что 8-битный проц эффективней использует частоту чем 32-битный раза в 2-3. Для определенностипусть 2,5. Остается принять что код digitalWrite у оранжа раза в полтора-два длиней, просто функционала больше,  приймем 1,75. А авторы его явно не заморачивались производительностю, в digitalWrite все начинается с проверки нахождения номера пина в допустимом диапазоне, проверки режима настройки и т.д. 

Проверяем 20*2,5*1.75=87. Ясно что очень грубая прикидка, но показывает что ничего невероятного нет.

Logik
Offline
Зарегистрирован: 05.08.2014

andriano пишет:

Logik, Вам полтора мегагерца "нечем глянуть"?

Так я прогер, не электронщик (далекое прошлое не в счет), мне серезный осцил дома - моветон ;) То что есть, на STM дискретизирует на 2МГц. Не тянет. Но так даже лучше я подумал. Найти для полутора МГц осцил можна, но при прямой работе с портом есть риск получить 200-400  мегагерц, такой осцил я не найду, пусть уж методика будет одинакова на оба опыта.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Понятно что ничего невероятного нет. Вы просто наглядно подтвердили мой тезис годовалой давности за 4.35 раза, который я тогда сделал "из общих соображений". Из которого, кстати, вытекает что никакой STM32F103 не "порвет" мегу .. даже просто мегу, не говоря уже за иксы.. Спасибки. :)

Logik
Offline
Зарегистрирован: 05.08.2014

Ну да. Тогда ж так и порешили, только из общих соображений было 2-3, если правильно помню. Тут еще разрабы добавили сложностю кода до 4 с хвостиком.

a5021
Offline
Зарегистрирован: 07.07.2013

Arhat109-2 пишет:
Из которого, кстати, вытекает что никакой STM32F103 не "порвет" мегу .. даже просто мегу, не говоря уже за иксы..

Чет я не понял, а как ее рвать надо, эту мегу? На меге три инструкции SBI, CBI и RJMP выполняются 6 тактов, что дает максимальную скорость ногодрыга 2.667мгц. Это несколько меньше восьми мегагерц на стм32, полученных мной в простом эксперименте. Причем, данную скорость выдает сишный код, который хоть и хорошо оптимизируется компилятором, но все равно не идеален. Если зарубиться, то можно еще чуток подкрутить.

Logik
Offline
Зарегистрирован: 05.08.2014

Прямая работа с портами оранжа. Код

#include <sys/mman.h>
#define	BLOCK_SIZE		(4*1024)
#define GPIO_BASE_BP		(0x01C20000)

void BlinkWithoutDelay ()
{
  int fd;
  volatile uint32_t *gpio ;

  if ((fd = open ("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC) ) < 0)
    return printf ("nOK\n") ;

  gpio = (uint32_t *)mmap(0, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, GPIO_BASE_BP);

  pinMode(16, OUTPUT);

  for(int n=0;n<200;n++)
  {
    int m=millis();
    for(int i=1000000;i;i--)
    {
      *(gpio+570)=0;
      *(gpio+570)=0x00000080;
    }
    printf("T=%u\n",millis()-m);
  }
  close(fd);

}

Выводит:

T=101
T=105
T=100
T=100
T=106
T=100
T=100
T=105
T=100
 
Т.е. в 7 раз быстрей чем через digitalWrite(), соответственно частота 10МГц, а вывод на пин (точней сразу на порт) 0,05мксек, получается порядка 70 тактов на вывод при 1400МГц. Весьма таки странная цифра, ни туда-ни сюда. Остается предположить что 10МГц - все что может переферия, и ядро тормозится под неё.
Выход контролировал также как в первом опыте, сигнал есть.
По ходу дела заметил мелочи, gpio+570 в цикле, вынес наружу, удленял цикл в 10 раз - ничего не поменялось.

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Для справки, исключительно, напомню: примитивный цикл ногодрыга на Мегах - 3 такта. То есть 5.33Мгц. для 16Мгц и 6.67Мгц для 20Мгц, при разгоне до 24Мгц (некооторые гонят и успешно) можно получить 8Мгц. :)

С учетом того, что надо декрементировать счетчик и делать условное ветвление, ещё + 3-4 такта и все становится в 2 раза хуже для такого теста.

Да, делается на обычном Си с применением макросов из io.h.

a5021
Offline
Зарегистрирован: 07.07.2013

Arhat109-2 пишет:
Для справки, исключительно, напомню: примитивный цикл ногодрыга на Мегах - 3 такта.

Я пока себе плохо представляю, как с помощью инструкций, выполнение которых бы занимало на меге три такта, можно организовать цикл. Если речь идет о последовательном выполнении команд  OUT, OUT и команды перехода, то это будет четыре такта, т.к. у меги нет однотактовых команд перехода. Итого, для МК, работающего на  чаcтотах 16, 20 и 24мгц, максимальной частотой ногодрыга будут 4, 5 и 6мгц.

Но даже эти дивные мегагерцы оказываются с подвохом, т.к. команды OUT не могут адресовать все пространство и дальше порта E (пишу по памяти, могу чуть напутать с буквой, но общий смысл такой) они не достают. Да и придумать сколь-нибудь осмысленное применение "короткозамкнутого" цикла в реальных приложениях довольно проблематично. Таким образов выходит, что если на специально составленной "синтетике" мега еще может куда-то дернуться (да и то не очень убедительно), то для какого-то более широкого использования все это не очень подходит.

В прямом противпоставлении у меги и вовсе шансов никаких. Реализация того же цикла с миллионом интераций (как в примере для пи и стм32) на меге повлечет драмматическое снижение быстродействия просто из за увеличившегося объема арифметических операций. Мега упадет ниже мегагерца в ногодрыге. Ну и кого тут "рвать" ?

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

:)

Logik
Offline
Зарегистрирован: 05.08.2014

Arhat109-2 пишет:

С учетом того, что надо декрементировать счетчик и делать условное ветвление, ещё + 3-4 такта и все становится в 2 раза хуже для такого теста.

Если уж пошло такое дело, что нада выжать максимум частоты, то можна и вечный цикл делать, а выход по прерыванию, например таймера, с подменой адреса возврата в стеке. Экстремальные результаты требуют нетривиальных методов.

Logik
Offline
Зарегистрирован: 05.08.2014

Решил еще продолжить, проверить реально ли "10МГц - все что может переферия, и ядро тормозится под неё."

Догрузил ядро командами до  70 тактов на вывод. Теперь код внутри цикла такой.

{
      *gpio=0;

asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");
asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");

asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");
asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");

asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");
asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");

asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");
asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");

asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");
asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");

asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");
asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");

asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");asm volatile("nop");
asm volatile("nop");asm volatile("nop");

//asm volatile("nop");asm volatile("nop");asm volatile("nop");

      *gpio=0x00000080;
    }

Имеем 70 нопов, из которых 3 закоментированы. Скорость работы кода не изменилась.

Если раскоментируем 3 последних нопа - скорость станет немного, менее процента, ниже. Вылавливать при скольких именно 2,3 или 4 закоментированых нопах проходит граница я не стал, изменение слабое, надо много возится.

Но похоже что работает так:

1. Непосредственно обращение к порту на запись по указателю занимает порядка 3-х тактов процессора т.е. 0,002мкс

2. Перефирия выполняет вывод за 0,05мкс.

3. При записи в порт до того как он выполнит вывод от предыдущей команды происходит блокировка вызова на время завершения работы периферии.

Итого имеем. Максимальная частота на выходе 10МГц, время вывода константы в порт - около 0,002мкс (здесь может быть заметная погрешность). В пределах цикла вывода частоты остаются около 67+67=134 тактов, т.е. около  0,096мкс, на выполнение других действий, организацию цикла и т.д.

Пожалуй это все что меня интересовало про ногодрыг в оранже.

a5021
Offline
Зарегистрирован: 07.07.2013

На 2-ой малинке ногодрыгом до ~42мгц выходит.

Любопытства ради, глянул, какой объем двоичного кода получается для ногодрыга  мегой и стм.

  ;;    ATMEGA2561

  ;;  for (uint8_t i = 255; i; i--) {
  ;;    PORTB = 1;
  ;;    PORTB = 0;
  ;;  }

282:  11 e0       	ldi	 r17, 0x01	; 1
284:  8f ef       	ldi	 r24, 0xFF	; 255

286:  15 b9       	out	 0x05, r17	; 5
288:  15 b8       	out	 0x05, r1	; 5
28a:  81 50       	subi	 r24, 0x01	; 1
28c:  e1 f7       	brne	 .-8      	; 0x286 <main+0xe8>


  ;;    STM32F030F4P6

  ;;  for (uint32_t i = 1000000; i; i--) { 
  ;;    GPIOA->BSRR = (uint16_t)(1 << 2);  
  ;;    GPIOA->BRR =  (uint16_t)(1 << 2);  
  ;;  }

0016  0x4846           LDR      R0,??main_0+0x4  ;; 0xf4240
0018  0x4946           LDR      R1,??main_0+0x8  ;; 0x48000018
001A  0x2204           MOVS     R2,#+4
             main_1:
001C  0x600A           STR      R2,[R1, #+0]
001E  0x610A           STR      R2,[R1, #+16]
0020  0x1E40           SUBS     R0,R0,#+1
0022  0xD1FB           BNE      main_1

Фактически, объем одинаков. Разница в два байта в пользу меги возникла в результате того, что регистр r1 пришел в нуле с какой-то из предыдущих операций и компилятор не стал его повторно инициализировать тем же самым значением. Сам же код, дрыгающий ногами, по объему одинаков, не смотря на то, что в одном случае операции восьмибитные, а в другом тридцатидвухбитные.

ssss
Offline
Зарегистрирован: 01.07.2016

a5021 пишет:

Протестил на двадцатипятирублевом STM32F030F4P6. Результат -- МК трясет ногой с чаcтотой 8мгц, при работе ядра на 64мгц.

<del>

Написано в даташите на СТМ32Ф0хх 2 такта, значит два такта. Всё остальное - твои голимые косяки и лишние дыры в голове с утечкой остатка мозга. 2+2=4. 64/4=16МГц. Это максимальная долговременная частота махания ногами для СТМ32Ф0хх. <del>

Для СТМ32Ф2 и Ф4 - 1 такт. <del>.

ssss
Offline
Зарегистрирован: 01.07.2016

Logik пишет:

Далее по работе например STM32 известно что 8-битный проц эффективней использует частоту чем 32-битный раза в 2-3.

<del>

ssss
Offline
Зарегистрирован: 01.07.2016

Arhat109-2 пишет:

вытекает что никакой STM32F103 не "порвет" мегу .. даже просто мегу, не говоря уже за иксы.. Спасибки. :)

Конечно он её не порвёт, он её просто в ноль помножит. )))))))))))))))))))))

Клапауций 234
Offline
Зарегистрирован: 24.10.2016

ssss пишет:

Самая могучая шутка этого форума.

<del>, ты на этом форуме хоть одну строку кода опубликовал?

a5021
Offline
Зарегистрирован: 07.07.2013

<del>

Logik
Offline
Зарегистрирован: 05.08.2014

<del> неактуально

ssss
Offline
Зарегистрирован: 01.07.2016

<del>

Клапауций 234
Offline
Зарегистрирован: 24.10.2016

<del>

 

Logik
Offline
Зарегистрирован: 05.08.2014

Вау! Активность модератора детектед 8) Спасибо, так бы почаще.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

a5021, Цена этой оптимизации - куча адрсного пространства занятая "битовой адресацией" портов ввода/вывода, что существенно ограничивает возможности по использованию адресного пространства в перспективе. Как-бы "микроконтроллерам" стока не надо .. но, я хорошо помню времена, когда 16 метров оперативы х286 считалось "стока не бывает".. не так уж и давно оно було. :) То есть, в перспективе такие (ARM) процессоры - кандидаты на уход в прошлое аналогично х286, и только. Очередной "тупик" в который всех активно загоняют продаваны.. :)