Официальный сайт компании Arduino по адресу arduino.cc
Что "стряслось" с компилятором?!?
- Войдите или зарегистрируйтесь, чтобы получить возможность отправлять комментарии
Втр, 03/05/2016 - 11:55
Вот текстовка хидера:
#include <inttypes.h> #define pin50 {PORTB,3} typedef struct{ uint16_t port; uint8_t mask; } Pin; extern "C" { void setPin(Pin p, uint8_t val){ if(val) p.port |= p.mask; else p.port &= ~p.mask; } }
Вот скетч:
#include "pins.h" Pin intPin = pin50; void setup() { } volatile uint8_t val = 0; void loop() { val = 1-val; intPin.port = PORTL; setPin(intPin, val); setPin(pin50, val); }
Как видим нет ничего "крамольного", один голимый wiring.. :) А это то, что получается с опцией -Wa,-a=test.asm
8 .text 14 setPin: 24 0000 0895 ret 47 loop: 56 0000 9091 0000 lds r25,val 57 0004 81E0 ldi r24,lo8(1) 58 0006 891B sub r24,r25 59 0008 8093 0000 sts val,r24 61 000c 8091 0B01 lds r24,267 62 0010 90E0 ldi r25,0 63 0012 9093 0000 sts intPin+1,r25 64 0016 8093 0000 sts intPin,r24 66 001a 8091 0000 lds r24,val 69 001e 8091 0000 lds r24,val 70 0022 85B1 in r24,0x5 72 0024 0895 ret 78 _GLOBAL__sub_I_setPin: 90 0000 85B1 in r24,0x5 91 0002 90E0 ldi r25,0 92 0004 9093 0000 sts intPin+1,r25 93 0008 8093 0000 sts intPin,r24 94 000c 0895 ret
Кто-то может объяснить "что тут произошло"?
Надеюсь идея понятна: вместо простого байта под номер пина отводим структуру из пары "порт,маска" и соответсвенно, получаем единую работу с пинами как в случае их константного определения, таки при хранении в переменной. Хотелось проверить как компилятор позволяет передавать структуры в качестве параметров..
А в чём проблема? Всё, вроде, правильно. Он функцию как inline развернул. Не нравится - запретите. Кстати, Вы уверены, что хотите передавать структуру именно по значению, а не по ссылке? Вроде ж Вы там за эффективность боретесь?
Проблема разворота макроса с переменными решается гораздо проще (могу показать), но если Вам так нравится, решайте так.
Нашел. Он Lvalue исключил "за ненадобностью". В setPin() пропустил *. Надо было так:
Не, это так .. в порядке "изысканий" компромисса между моим подходом в arhat.h, запрещающего хранить номера пинов в памяти и километровыми таблицами перекодировки в wiring, съедающими на нет всю прелесть ногодрыгов. :)
Просто появилась идейка как можно совместить "приятное с полезным"... проверяю.
Ну, в любом случае, переменные в номерах пинов можно "разрешить" гораздо проще.
Кстати, Вы какой режим выдачи предупреждений компилятора ставите в IDE? По умолчанию там none, чтобы чайники не пугались. В итоге можно соершенно "молча" переопределить задефайненную константу и потом голову сломать в поисках проблемы. У меня всегда стоит "all". Так вот, если влкючить уровень warning'ов "all", то вот такой скетч:
Выдаёт вот такую простыню:
Сами понимаете, сообщения типа "
comparison
is
always
true
" больше похожи на ошибки, чем на предупреждения.Это вам ещё мало высыпало .. в 1.6.8 ещё валит кучу варнгингов из-за переопределния препроцессорных констант. По умолчанию выставляю без предупреждений, только ошибки. Но иногда, в процессе отладок тоже любуюсь .. :)
Поделитесь? :)
Да, не вопрос, сейчас, минутку ...
Но там сделано не как универсальная библиотека, "на коленке", "для себя", "по быстрому". Поддерживаются только 328 и тини25/45/85 - другое не требовалось. Но принцип поймёте. Сейчас ...
Вот, смотрите:
Вот, смотрите, если Вы проследите всю цепочку от _digitalWrite() и выше, то увидите, что за выбор порта и бита отвечают макросы в строках 108-122.
А эти макросы написаны так, что если им подсунуть константу, они её вернут и получится digitalWrite одной машинной командой. А если им подсунуть переменную, то эти макросы развернутся в некий код по поределению порта и бита по значению этой переменной. От программиста ничего не требуется - само развернётся как надо в зависимости от обстоятельств.
Более того, замечен такой эфффект, если там переменная, которой была присвоена константа, компилятор сам оптимизирует, и эти макросы разворачиваются, как если бы там честная константа была - очень удобно.
Я надрал автору зад за то, что нет никакого контроля правильности пина (ну, там 101 подсунуть можно) и за отуствие контроля единости порта в групповых операциях, но он сказал, что тогда бы это была не работёнка на два часа, а серьёзный проект. Я согласился и отстал.
Спасибо. Этот подход понятен и давно известен.
Вот что-то похожее пробую упростить для второй части: когда номер пина в переменной. У такого решения есть существенный недостаток - "само развернется в некий код". И так "при каждом обращении". Мне (и не только) показалось что слишком накладно. Если у wiring огромные затраты на перекодирование по таблицам (на размер самих таблиц, для меги2560 в частности), то код растет не настолько шустро и не настолько теряет в скорости как этот подход. Это уже обсуждалось на Cyber-place в процессе развития Cyberlib.
Как пример, находил на просторах Сети, ещё с год назад digitalWriteFast.h:
Мне нравится такой подход. Переменные как таковые я использую редко, вот в этих редких случаях оно и подстрахует, чтоб не париться.
Вообще-то, для нормального выхода, здесь просто инструмент неправильный. Препроцессор в С убогий. Попробуйте представить себе, что в препроцессоре есть циклы, переменные и т.п. - насколько проще было бы делать код действительно универсальным и действительно оптимальным. На этап компиляции можно было бы разбираться с универсальностью и оставлять в коде только то, что реально нужно и ни байта больше.
В 80-ые, когда мы делали навигацию для спец. изделий, мы как раз и использовали версию языка ассемблера с очень развитым препроцессором (сами сделали препроцессор из исходников компилятора PL/1 - представляете мощь такого препроцессора?). Вот там действительно можно было нагенерить любой код при сохранении его универсальности. А универсальность была важна, т.к. обычно в одном изделии использовалось несколько разных процессоров (до трёх!). Мы это называли тогда не препроцессором, у "управляемым ассемблером".
Подумайте. может Вам по этому пути пойти, раз уж Вам так хочется сделать универсальную библиотеку? С IDE не знаю, а в АВР_Студию вставить дполонительный шаг обработки кода (пропускание через свой препроцессор) как два пальца. Нормально получилось бы, если по уму сделать.
Ну .. ежели помечтать, то мне было бы достаточно в макроорпеделениях условной обработки через defined(), чтобы забабахать универсальный подход без потери эффективности. Там жеж засада только в том, что какой-нибудь DREG##p разворачивается в отсутствующий макрос! Предварительно запросить IF(defined(DREG##p) и все дела. Только вот никак. :)
Есть одна мысля .. пока пробую. Мне кажется что засада в том, что типовые PORTB определены как буквенные константы .. вот ежели определить PORT(n) может оказаться проще.
Ну .. ежели помечтать, то мне было бы достаточно в макроорпеделениях условной обработки через defined(), чтобы забабахать универсальный подход без потери эффективности. Там жеж засада только в том, что какой-нибудь DREG##p разворачивается в отсутствующий макрос! Предварительно запросить IF(defined(DREG##p) и все дела.
Не сработает. Как только углубитесь, поймёте. Там никак нельзя по if выдать один текст на выход, а по else другой. Точно, не работает, поверьте.
Знаю. Я ж "помечтать".. :)
В общем, похоже интересен такой подход:
Итого:
вар.1 -- структура пина в виде прямого адреса порта и битовой маски. Константные номера = 20байт (6байт), в переменной = 36 байт (18байт).
вар.2 -- структура с индексом порта в массиве и битовой маски. Константно = 28 байт (14байт), с переменной = 46 байт (30). Плюсом массив в 12 слов х 3шт. для ввода/вывода и управления. 3*12*2 = 72 байта, можно в progmem.
вар.3 -- структура пина в виде битовых полей в 1 байте. Константный вариант как ни странно равен предыдущему из-за предвычисления сдвигов. А вот вариант с хранением в переменной практически не отличается от содержательной части digitalWrite() и занимает 78 байт. Плюсом сюда тоже 72 байта флеш.
Примечание: в случае заранее известной установки в 0 или 1, все значительно упрощается. В скобках даны цифирьки для такого упрощения.
P.S. я исходил из того, что номер пина, так или иначе, но где-то в программе задается константным способом. Соответственно, можно в таком месте использовать макропреобразователь числа в структуру пина.
Итого: 1-й способ в случае константных пинов дает код, практически неотличимый от ассемблерного, но требует для хранения каждого пина по 3 байта оперативы. Если оперативы не жалко, а размер кода "жмет" - то это самый компактный способ. И самый шустрый соответственно.
3-й Вариант с упаковкой в битовые поля не дает практически никаких преимуществ перед wiring.
2-й вариант - "золотая середина". Несущественно больше и тормозней, но зато позволяет реализовывать в т.ч. и групповые операции с пинами на одном порту. При этом отжирает по 2 байта на номер пина в памяти, что в общем-то "терпимо".
Вот .. так как-то. Универсального решения нет и в этом направлении. :)