День добрый. Для работы с портами использую библиотеку FastIO.h (https://github.com/ErikZalm/Marlin-non-gen6/blob/master/Marlin/fastio.h), так же есть не плохая библиотека CyberLib (http://www.cyber-place.ru/showthread.php?t=550), ее внимательно не изучал. Обе они построены на макроподстановках, первая еще на объедении двух лексем. К примеру данный код, ни когда не скомпилируется. Код (Text): void checkPressButtonInversePin(uint8_t* btnArray, uint8_t bitNumber, uint8_t pinNumber) { if (MC_BIT_IS_SET(*btnArray, bitNumber)) { MC_WRITE_PIN(pinNumber, !MC_READ_PIN(pinNumber)); } } Причина тут. Код (Text): #define MC_READ_PIN(IO) ((bool)(DIO ## IO ## _RPORT & MC_MASK_PIN(DIO ## IO ## _PIN))) Как в прочем и в MC_WRITE_PIN, там тоже используется объедении двух лексем (##). А хочется простой аналог digitalWrite, digitalRead с передачей простого int - номера пина ардуины. Это дает возможность, как передавать в функцию, так и работать в циклах. Первое что пришло мне в голову сделать функцию, с простым switch. Код (Text): void readPin(uint8_t pinNumber) { switch(pinNumber) { case 0: MC_READ_PIN(0); break; // И т.д. } } Минус, придется в 2 двух местах, 1 библиотеки контролировать сами пины. В switch и в списке #define со списком пинов. Или другие варианты со switch, переписав сами функции. На мой взгляд (всего лишь любитель), таким образом обеспечивается максимальная скорость и минимальный размер. А доработав их, используя те же макроподстановки можно добиться переносимости между любыми контролерами, заставляя их генерировать только нужный код, для соответствующего контролера. Минус, нет ни какого контроля как и с другими макроподстановками. Unixon не согласился с мои взглядом и вероятно он прав учитывая его опыт (да и тогда, просто не понял о чем он говорит, а может и сейчас не понимаю). Привожу цитату из темы - http://forum.amperka.ru/threads/тест-скорости-high-low.5431/#post-44397 Сама библиотека avrpins.h - https://github.com/felis/USB_Host_Shield_2.0/blob/master/avrpins.h. Сделано конечно красиво (С++, ООП в хорошем примере), но большое количество классов, по сути каждый пин, это класс. Почему он будет быстрее макроподстановок? Да и размер скетча определенно будет больше. Много написал (увы не талант), как более правильно, интересует скорость и размер скетча, сделать функции работы с портами, по аналогии с digitalWrite, digitalRead с передачей простого int? Не интересует код, интересует принцип. Классов не боюсь как и ООП (хоть и минимальный опыт работы с ними).
Не быстрее, но и не обязательно медленнее. И больше, этого не отнять. Задача ООП в получении не быстрого кода, а легко сопровождаемого. В этом отношении при грамотном подходе программа, написанная с использованием ООП получается самодокументированной за счет наименования классов, их полей и методов. В любом случае приходится приходить к некоторому компромиссу между читаемостью и сопровождаемостью кода с одной стороны и его эффективностью с другой. Причем скорость и размер кода часто требуют противоречащих друг другу оптимизаций. Скорость требует отказа от функций (вызов функции - это дополнтельные накладные расходы). А размер, наоборот, требует вынесения всего достаточно большого повторяющегося в отдельные функции. Если исходить из критериев скорости и размера, то, наверное, придется нарисовать что-то с директивами условной компиляции, в котором для каждого варианта платы предусмотрена своя ветка, обрамленная директивами #if,,, и #endif Категории плат можно с некоторой точностью различать по наличию определений SERIAL_PORT_USBVIRTUAL (для леонардоподобных), SERIAL_PORT_HARDWARE3 (для меги). Те, у которых таких определений нет - считать уноподобными. Внутри категории платы можно попробовать различать по количеству пинов (NUM_DIGITAL_PINS и NUM_ANALOG_INPUTS)
Большое спасибо за консультацию. Примерно, так себе и представлял. Вы подтвердили мои догадки и исправили ошибки в моем понимании. Мне больше нравится подход С++ (есть небольшой опыт с C#), но есть страх, что я не влезу ни по памяти, ни по размеру скетчей. Поэтому пока останусь на простом СИ, а уже после того, как закончу проект попробую переписать некоторые методы с помощью С++. Мне больше понравился, такой вариант, основанный на определении конкретного чипа Код (Text): //*********************** Определяем тип ардуины **************************// #if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega2560__) // Частота процессора. #define F_CPU 16000000UL // Определяем тип ардуины. #if defined(__AVR_ATmega328P__) // Если ArduinoProMini. #define ARDUINO_PRO_MINI #endif #if defined(__AVR_ATmega32U4__) // Если ArduinoProMicro. #define ARDUINO_PRO_MICRO #endif #if defined(__AVR_ATmega2560__) // Если ArduinoMega. #define ARDUINO_MEGA_2560 #endif #else #error This board not support! #endif //*********************** /Определяем тип ардуины *************************// Как я понял можно еще определять и типы платформ Код (Text): ifdef __AVR__ Код (Text): ifdef __arm__ UPD. Тут пока не уверен, есть же inline, но он увеличивает размер кода. Придется выбирать.
ООП, макросы ни причём, если мы говорим о быстродействии. Нужно понимать какие конструкции в какой машинный код превращаются. Вот смотрите, вы хотите использовать FastIO, чтобы быстро обращаться к портам. И тут же предлагаете использовать адовый switch, который не то, чтобы убъёт всё быстродействие, а сделает ваш подход много медленнее штатного digitalRead. Откуда быстродействие FastIO? От того, что вы в каждом конкретном месте использования точно знаете, к какому регистру обращаетесь. Без всяких параметров, а следовательно, без всяких конвертаций. Уверен, вы знаете, что на микроконтроллерах AVR состояние регистров — это просто один из байтов в памяти. К нему можно обратиться по имени PINA, PINB, PINC, etc, в зависимости от того какой из регистров вы имеете в виду. Хотите считать быстро? Код (Text): uint8_t myBlazingFastData = PINA; Всё, быстрее этого нельзя. Даже на ассемблере. FastIO — это просто фантик, который сводится к этой конструкции. Теперь мы вспоминаем, что есть Arduino с её собственной нумерацией пинов. Это уровень абстрации. Его нужно привести обратно к AVR’ному виду. Можно это развернуть в compile-time (что и делает FastIO), можно в runtime, если ваши входные параметры — переменные, о значениях которых, компилятор может только догадываться. Последний случай — это штатный digitalRead и то, что вы пытаетесь добиться. Давайте глянем на штатный digitalRead? Код (Text): int digitalRead(uint8_t pin) { uint8_t timer = digitalPinToTimer(pin); uint8_t bit = digitalPinToBitMask(pin); uint8_t port = digitalPinToPort(pin); if (port == NOT_A_PIN) return LOW; // If the pin that support PWM output, we need to turn it off // before getting a digital reading. if (timer != NOT_ON_TIMER) turnOffPWM(timer); if (*portInputRegister(port) & bit) return HIGH; return LOW; } Ага, тут какие-то digitalPinTo… Смотрим что это Код (Text): #define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) ) #define digitalPinToBitMask(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM + (P) ) ) #define digitalPinToTimer(P) ( pgm_read_byte( digital_pin_to_timer_PGM + (P) ) ) Макросы. Если развернёте дальше, то увидите, что это обычный lookup, который ведёт к исконным PINA, PINB и еже. В общем, необходимый уровень зла, связанный с тем, что распиновка Arduino ≠ распиновке микроконтроллера. Но это самый эффективный lookup, который может быть. Быстрее не сделаете. Но да, здесь что-то про таймеры, что-то про NOT_A_PIN, что-то про ветвление. Допустим, вам всё это не нужно: вас не интересует состояние PWM, вы уверены, что не промажете индексом, вам не нужен результат канонически приведённый к bool. Хорошо, отбрасываем ненужное: Код (Text): uint8_t digitalReadFast(uint8_t pin) { uint8_t bit = digitalPinToBitMask(pin); uint8_t port = digitalPinToPort(pin); return *portInputRegister(port) & bit; } Ну и если вы настаиваете на том, что это должно работать без пенальти на вызов функции (4 такта навскидку), в ущерб размеру скетча, заявите об этом компилятору: Код (Text): uint8_t digitalReadFast(uint8_t pin) __attribute__((always_inline)) { uint8_t bit = digitalPinToBitMask(pin); uint8_t port = digitalPinToPort(pin); return *portInputRegister(port) & bit; } Вот мы и получили самый скоростной способ прочитать порт Arduino. С записью всё то же самое.
Нет ни малейшего представления. Предполагаю, что для этого требуется знания ассемблера и понимание архитектуры конкретного чипа, увы ни того не другого нет в наличии. Не подумал, просто решал задачу в лоб. Да я представляю работу FastIO. Спасибо за подробное разъяснение, я как и любой любитель захотел все и сразу. Пока останусь на простой обертке FastIO. У меня к Вам пару вопросов, буду благодарен за ответы. 1. Как понимать? Предполагаю, что для этого требуется знания ассемблера и понимание архитектуры конкретного чипа, но так ли это. 2. __attribute__((always_inline)), как я понимаю это команда компилятора, где можно ознакомится со всеми командами компилятора? UPD. Ступил с вопросом 2. Сейчас использую Arduino IDE для компиляции, там используется avr-gcc. О нем, без труда можно найти всю информацию - https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html.
Хорошая статья про работу с пинами: http://easyelectronics.ru/rabota-s-portami-vvoda-vyvoda-mikrokontrollerov-na-si.html
На самом деле можно выжать еще немного ускорения. В частности, можно избавиться от pgm_read_byte, перенеся массивы с номерами портов и номеров бит там в ОЗУ и исключив обращение к флэшу (progmem). Для этого нужно убрать соответствующую спецификацию в объявлении массива. Но, естественно, за это придется заплатить небольшой потерей доступного ОЗУ (несколько десятков байт). Кстати, преобразование пинов в пары порт-бит можно сделать на уровне макроопределений. что-то вроде #define PIN1 PORTD,2 И скармливать это в виде единственного параметра для макросов FastIO.
В целом, да. Прямо на 5+ ассемблер знать не нужно, нужно общее понимание: какие бывают инструкции, как работает ветвление, прыжки, регистры, как происходит вызов функций. Ассемблер из всех языков ближе всего к машинному коду, именно поэтому его понимание приводит к пониманию работы процессора. Есть книга Hacker's Delight, она в своё время мне просто вывернула мозг, показав какие трюки можно творить на уровне битов-байтов. Очень полезно. Погуглите, весьма может быть, что найдёте её в PDF на первой же странице выдачи. Ну и далее, ATmega datasheet. Посмотреть какой ассемблер получается в итоге из вашего скетча на C++ можно с помощью утилиты arv-objdump: Код (Text): avr-objdump -S /path/to/your/build/directory/firmware.elf > asm_of_my_sketch.S
Извините за паузу, работа и разбирал ответы, не все было сразу понятно. Спасибо, ссылка действительно полезная. Там человек серьезно подошёл к данному вопросу, стараясь попробовать различные варианты. Конечное решение на С++ очень похоже на библиотеку avrpins.h. Лично мне, будет очень любопытно разобраться с его решением. Тоже усомнился, что с pgm будет быстро, видимо между скоростью и размером, они выбрали размер. Спасибо, за идею. Не знал о таком способе, даже не понимал, как это работает, на запятые в СИ не похоже. Через 30 минут нашел ответ. Код (Text): #define RECEIVE(first, second) (first*4+second*5) #define RECEIVE2(x) RECEIVE(x) #define A 3,4 RECEIVE2(A); Взято от сюда - http://www.cyberforum.ru/cpp-beginners/thread42490.html. Спасибо за ответ. Вы подтвердили мои опасения, ассемблер побаиваюсь, это инструмент профессионала. Да и нет на него времени, хотя бы СИ и С++, выучить более фундаментально, а не фрагментами. Как я понял, для меня самый разумный - выбирать контролер с запасом, а не гонятся за скоростью, которую толком измерить не могу (кроме как подсчет с помощью различных таймеров).