перлы оптимизации GCC и ногодрыги..
- Войдите на сайт для отправки комментариев
! ПЕРЕИМЕНОВАЛ ТЕМУ. Можно будет дополнять, если кто-то столкнется ещё с чем !
Гоняю плату дозиметра
Исходник скетча:
/**
* Тест накачки высокого обратноходовым импульсным преобразователем
* В этом варианте показывает до 679 попугаев или 1384в на ВВ-трансфоматоре
*/
extern "C" {
#define pinPWM 5 /* PORTD PORTD5*/
#define pinHV 0 /* analog pin A0! */
#define MAX_ADCS 4
#define MAX_SAMPLES 10
int adcs[MAX_ADCS*MAX_SAMPLES];
#define pwmON() (PORTD |= 0B00100000)
#define pwmOFF() (PORTD &= 0B11011111)
/**
* INLINE: 16-bit counter: up to 65535*4 F_CPU for 16Mhz:[0.25 .. 16383.75] mcsec.
* Короткие задержки по 4 цикла ЦПУ (кратно 250 нсек)
* !!! Работает неверно !!!
*/
#define delayMicro16(__count) \
__asm__ __volatile__ ( \
"1: sbiw %0,1 \n\t" \
"brne 1b\n\t" \
:: "w" (__count) \
)
/** вроде работает: проверить время! ТОЖЕ НЕ РАБОТАЕТ */
#define delayUS(t) \
{ \
do{;}while(--(t)>0); \
}
#define adcStart() (ADCSRA |= (1<<ADSC))
#define adcWait() while(ADCSRA&(1<<ADSC))
/**
* Однократное чтение АЦП 8-бит (младших)
* Если замер завершен, то сначала запуск следующего.
* Пока идет замер - работаем дальше
*/
#define adcReadFast(res) \
{ \
adcStart(); \
adcWait(); \
(res)=ADC; \
}
} // extern"C"
void setup()
{
pinMode(pinPWM, OUTPUT);
pwmOFF();
Serial.begin(115200);
ADCSRA = (1<<ADEN) | (2); // включаем АЦП и устанавливаем делитель 1(8), 2(4), 3(2), 4(1), 5(0.5), 6(0.25), 7(0.125)
ADMUX = (0<<REFS1)|(1<<REFS0)|(pinHV); // 01 - опорное 5в, рабочий вход - 7
ADCSRB = 0; // тут ничего не надо
DIDR0 = 255; // отключаем от OUTPUT все входы АЦП, дабы не разбираться
adcStart(); // запуск и сразу
adcWait(); // пропуск первого чтения, ибо мусор и долго
}
void loop()
{
int wait;
int n, i;
int *ptr;
for(n=0; n<MAX_SAMPLES; n++){
wait = 4;
ptr = adcs + MAX_ADCS*n;
cli();
pwmON();
delayMicro16(wait);
pwmOFF();
adcReadFast(*ptr++); // MAX_ADCS раз!
adcReadFast(*ptr++);
adcReadFast(*ptr++);
adcReadFast(*ptr++);
sei();
}
// вывод попыток накачки на плоттер
for(n=0; n<MAX_SAMPLES; n++){
for(i=0; i<5; i++){ Serial.println(n, DEC); }
for(i=0; i<MAX_ADCS; i++){
Serial.println( adcs[i+n*MAX_ADCS], DEC);
}
}
delay(2000); // пауза для просмотра результатов
}
и, непонимая что периодически происходит (код работает странно), полез в ассемблер через обждамп и обнаруживаю следующее:
Часть 1: Накачка и чтение данных:
00000596 <main>: // исполнение начинается отсюда!
void init() -- упс. inline вставка вместо вызова
{
596: 78 94 sei
// и далее стандартная настройка таймеров под Ардуино
// + теперь настройка АЦП на 125кГц тут
// + inline вставка pinMode(pinPWM, OUTPUT) ..
// + inline вставка setup() в который впихнут inline Serial.begin()
// + inline следом код из loop():
6e6: c4 e0 ldi r28, 0x04 ; 4 // (Y) wait=4;
6e8: d0 e0 ldi r29, 0x00 ; 0
// СЮДА приходит inline вставка вызова loop() в main()!!! отсюда повтор всего loop()
6ea: 9d e1 ldi r25, 0x1D ; 29 // Z = R12,13 = R24,25 = 0x11D (129)
6ec: c9 2e mov r12, r25
6ee: 91 e0 ldi r25, 0x01 ; 1
6f0: d9 2e mov r13, r25
6f2: f6 01 movw r30, r12
for(n=0; n<MAX_SAMPLES; n++){
wait = 4;
ptr = adcs + MAX_ADCS*n;
// сюда возвращаемся при повторе цикла:
6f4: f8 94 cli
6f6: 5d 9a sbi 0x0b, 5 ; 11 // pwmON();
6f8: 21 97 sbiw r28, 0x01 ; 1 // delayMicro16(wait);
6fa: f1 f7 brne .-4 ; 0x6f8 // цикл задержки
6fc: 5d 98 cbi 0x0b, 5 ; 11 // pwmOFF();
6fe: 80 91 7a 00 lds r24, 0x007A ; adcReadFast(*ptr++);
702: 80 64 ori r24, 0x40 ;
704: 80 93 7a 00 sts 0x007A, r24 ;
708: 80 91 7a 00 lds r24, 0x007A ;
70c: 86 fd sbrc r24, 6
70e: fc cf rjmp .-8 ;
710: 80 91 78 00 lds r24, 0x0078 ;
714: 90 91 79 00 lds r25, 0x0079 ;
718: 91 83 std Z+1, r25 ;
71a: 80 83 st Z, r24
71c: 80 91 7a 00 lds r24, 0x007A ; adcReadFast(*ptr++);
720: 80 64 ori r24, 0x40 ;
722: 80 93 7a 00 sts 0x007A, r24 ;
726: 80 91 7a 00 lds r24, 0x007A ;
72a: 86 fd sbrc r24, 6
72c: fc cf rjmp .-8 ;
72e: 80 91 78 00 lds r24, 0x0078 ;
732: 90 91 79 00 lds r25, 0x0079 ;
736: 93 83 std Z+3, r25 ;
738: 82 83 std Z+2, r24 ;
73a: 80 91 7a 00 lds r24, 0x007A ; adcReadFast(*ptr++);
73e: 80 64 ori r24, 0x40 ;
740: 80 93 7a 00 sts 0x007A, r24 ;
744: 80 91 7a 00 lds r24, 0x007A ;
748: 86 fd sbrc r24, 6
74a: fc cf rjmp .-8 ;
74c: 80 91 78 00 lds r24, 0x0078 ;
750: 90 91 79 00 lds r25, 0x0079 ;
754: 95 83 std Z+5, r25 ;
756: 84 83 std Z+4, r24 ;
758: 80 91 7a 00 lds r24, 0x007A ; adcReadFast(*ptr++);
75c: 80 64 ori r24, 0x40 ;
75e: 80 93 7a 00 sts 0x007A, r24 ;
762: 80 91 7a 00 lds r24, 0x007A ;
766: 86 fd sbrc r24, 6
768: fc cf rjmp .-8 ;
76a: 80 91 78 00 lds r24, 0x0078 ;
76e: 90 91 79 00 lds r25, 0x0079 ;
772: 97 83 std Z+7, r25 ;
774: 86 83 std Z+6, r24 ;
776: 78 94 sei ; sei();
778: 38 96 adiw r30, 0x08 ; 8 // оптимизация ptr++ прибавляем 1 раз сразу всё!
77a: 21 e0 ldi r18, 0x01 ; 1 оптимизация повтора цикла: контроль смещения ptr (Z)
77c: ed 36 cpi r30, 0x6D ; 109
77e: f2 07 cpc r31, r18
780: 09 f0 breq .+2 ; 0x784 выход
782: b8 cf rjmp .-144 ; 0x6f4 возврат на повтор цикла
Внезапно, установка wait=4 вынесено за тело цикла и вообще фактически за тело loop()! И это несмотря на то, что в макросе переменная ассемблерной вставки указана как ИЗМЕНЯЕМАЯ ..
Вопрос: КАК указать уомпилятору, что параметр этого макроса .. возвращаемый?
Макрос, который "не работает" на чистом "С" - тупо вообще выбрасывается компилятором как ненужный (это понятно почему).
Добавление volatile к переменной wait приводит к её выбросу в память и соответственно нарушает длительность задержки..
Есть какие-то решения?
Конечно, есть. Вот здесь в конце как раз про это (у меня всегда срабатывала передача переменных в asm по именам с явным указанием, что они out)
Так лучше,
/** * INLINE: 16-bit counter: up to 65535*4 F_CPU for 16Mhz:[0.25 .. 16383.75] mcsec. * Короткие задержки по 4 цикла ЦПУ (кратно 250 нсек) * !!! Работает неверно !!! */ #define delayMicro16(__count) \ __asm__ __volatile__ ( \ "1: sbiw %0,1 \n\t" \ "brne 1b\n\t" \ : "=w" (__count) \ : "0" (__count) \ )но тогда возникает 2 мелких засады:
1. Нельзя вызвать с константой: delayMicro16(4); -- получаем сообщение компилятора "Lvalue required", что совершенно правомерно.
и второе: код транслируется все равно так:
// вместо этого for(n=0; n<MAX_SAMPLES; n++){ wait=4; ptr = adcs + MAX_ADCS*n; cli(); pwmON(); _delay_loop_2(wait); pwmOFF(); adcReadFast(*ptr++); // MAX_ADCS раз! adcReadFast(*ptr++); adcReadFast(*ptr++); adcReadFast(*ptr++); sei(); } // обратно переводя на "С" получаем: for(n=0; n<MAX_SAMPLES; n++){ ptr = adcs + MAX_ADCS*n; cli(); pwmON(); wait=4; _delay_loop_2(wait); pwmOFF(); adcReadFast(*ptr++); // MAX_ADCS раз! adcReadFast(*ptr++); adcReadFast(*ptr++); adcReadFast(*ptr++); sei(); }То есть, вместо восстановления значения wait (все переменные регистровые) вначале цикла, имеем втягивание этой операции внутрь блока закрытых прерываний и удлинение времени между pwmOn() и pwmOff() гарантировано на 1 такт. Для этого места - очень много..
Тут, наверное, больше бы подошло какое-то указание что этот блок операторов не может быть переставлен компилятором .. есть такая возможность?
Т.е. Вы хотите одновременно иметь возможность и изменять параметр, и передавать константу? Ну, удачи. А чем Вас не устраивает delay_us стесняюсь спросить?
а чо отключение оптимизации говорит?
#pragma GCC optimize "O0"
Конечно! Это же только тестовый скетч, где тупо подбираю задержку и смотрю чего получается. Соответственно, нужна пиременная, дабы не перекомпилять скетч, а сделать подбор в цикле. А вот в рабочем скетче надо будет иметь константу. Переписывать? Лениво.
Не устраивает большой и грубой задержкой. Там нужна кратность от 1 такта ЦПУ (и даже это - "многовато", хочется аккуратней).
Отключение оптимизации и возврат к версии 1.6.4 - говорят что все компиляется практически как надо, по крайней мере этот критический кусок кода. LTO - конечно полезная весчь, но вот получить асм из под него низзя, а обратная декомпиляция объектника показывает вот такую фигню.
Ну и конечно хочется сделать так, чтобы можно было компилять без правки опций в ИДЕ.
P.S. Оставил последний вариант, который "лучше". В целом, за это время качество оптимизатора сильно выросло, что радует. Но .. при этом вопрос времени исполнения отдельных участков кода стал практически не прогнозируемым, что для управляющего компилятора - откровенно плохо: "привет всем любителям ногодрыгов".
Получается что, теперь ни в одном месте нельзя ожидать что код будет работать строго так, как написано.
P.P.S. В рабочем варианте буду уходить на прямое задание ШИМ таймером и работу через прерывания, что исключает замер высокого напряжения силами АЦП (работа по прерываниям значительно тормозней).
Получается что, теперь ни в одном месте нельзя ожидать что код будет работать строго так, как написано.
Это лжывый С++, брат. Когда встаешь с утра, никогда нельзя сказать точно, что сегодня будет значить оператор присваивания...
Получается что, теперь ни в одном месте нельзя ожидать что код будет работать строго так, как написано.
Отнюдь.
Полностью отключите оптимизацию, и "код будет работать строго так, как написано".
А при наличии оптимизации, по самой свое сути, оптимизатор будет пытаться сделать отклонения от того "как написано".
andriano, Вы странный оппонент: пишете ровно то же самое, но предваряете его "отнюдь". Какой в этом смысел .. даже не знаю как Вам ответить.. :)
1. "Полностью отключаете оптимизацию" - означает только то, что вопрос написания БИБЛИОТЕКИ для этого шилда (у меня дозиметр - это шилд к нашему "Ардуино как лего") - ИСКЛЮЧАЕТСЯ от слова "совсем".
2. Наличие оптимизации заложено в компиляцию скетчей "по умолчанию", что собственно и исключает первый пункт. И это утверждение выше жирным становится верным для любого программного ногодрыга, потому что нельзя гарантировать что между волатил "дрыгами" компилятор не насует или не уберет чего-то жизненно важного для тайминга "дрыга", что я и показал выше. Особенно это может быть прикольным из-за инлайн втягивания одноразовых функций, что может существенно повлиять на тайминг: время вызова и возврата - оно не такое и маленькое - около 10 тактов, даже без параметров и сохранения контекстов..
То есть, или в библиотеках любых ногодрыгов теперь надо отключать оптимизацию или переписывать все блоки ногодрыгов на ассемблерные вставки с опцией volatile. Кстати, вот опция для асм вставок - есть, а для блока кода, как понял нет.. а это может решить проблему.
Впрочем, вот тут и в смежных статьях поднимается ровно та же самая проблема и вопросы: http://microsin.net/programming/avr/avr-gcc-code-ordering-with-optimisat...
Как понимаю, проблема - давняя и нигде толком не решена. То есть, программные ногодрыги, так любимые многими, хорошо работают только тогда, когда их тайминг на порядок меньше чем времена исполнения инструкций. Ну или "а мне повезло - пашет". Все остальное - при нововведениях в оптимизатор может легко "пойти лесом" и не может быть применено в библиотеках "широкого пользования эмулирующего типа" (привет, wire.h, servo.h и т.д.).
Архат, Вы немного увлеклись и Вас понесло. Успокойтесь, может быть отложите это дело на день, а потом со спокойной головой проделйет следующее.
1. Оформите параметр не так, как Вы попытались, а так, как там написано - через параметры директивы asm. Она всё поймёт правильно. Параметры asm видны компилятору.
2. С передачей константы - ну подумайте сами, в этом ассемблерном куске вы изменяете параметр - какая нафиг константа? Вы константу менять собрались? Так что про константу забудьте или сделайте отдельную функцию с параметром-константой, которая скадыает константу во внутренний буффер, а потом вызывает эту функцию.
Полностью отключите оптимизацию, и "код будет работать строго так, как написано".
Сам не сталкивался, но в документации (я выше давал ссылку) прямо написано, что нифига. Даже с отключённой полностью оптимизацией может переставить код.
ЕвгенийП, да уже понятно что к чему .. поостыл. Библу буду делать на таймере, нафиг этот гемморой с ногодрыгами.
Удачи!
ЕвгенийП, да уже понятно что к чему .. поостыл. Библу буду делать на таймере, нафиг этот гемморой с ногодрыгами.
Здесь на таймере оч. правильно. Не забудте, что еще со "звоном" боротся, там тоже временной интервал, таймер нужен будет. А про оптимизацию - то что компилятор несколько операторов вида *ptr++ компилирует в типа std Z+n,.. , с разными n а потом "оптом" один раз прибавляет adiw r30,.. я и на младших версиях компилятора замечал, удивлялся, проверял, и таки да! так короче и ошибок в оптимизации не замечал. Что внутрь блока запрещенного прерывания может чего внести - так запроста. Даже вынести вроде может ))) Он помоему вобще в запретах/разрешениях не шарит, считая это обычными функциями и переставляя когда хочется. К счастью хочется ему не часто.
Так откуда ему знать чего ты там запретил, если ты ему сам про это не скажешь. Есть специальные средства, чтобы запретить переносы и синхронизовать доступ к памяти. Их надо просто знать и пользовать.
andriano, Вы странный оппонент: пишете ровно то же самое, но предваряете его "отнюдь". Какой в этом смысел .. даже не знаю как Вам ответить.. :)
1. "Полностью отключаете оптимизацию" - означает только то, что вопрос написания БИБЛИОТЕКИ для этого шилда (у меня дозиметр - это шилд к нашему "Ардуино как лего") - ИСКЛЮЧАЕТСЯ от слова "совсем".
Еще раз: "наличие оптимизации" и "выполнять, как написано" - вещи взаимоисключающие.
Совместить их принципиально невозможно. От слова "совсем".
Как понимаю, проблема - давняя и нигде толком не решена.
Это "проблема" принципиально неразрешима. Поэтому не только "толком" но и вообще никак решена быть не может.
Полностью отключите оптимизацию, и "код будет работать строго так, как написано".
Сам не сталкивался, но в документации (я выше давал ссылку) прямо написано, что нифига. Даже с отключённой полностью оптимизацией может переставить код.
Если может переставить, значит оптимизация полностью не отключается. Вне зависимости от того, что по этому поводу написано в документации.
Да, нет. Это ж не ассемблер, где транслируется "команда в команду". Задача компилятора сгенерировать код, адекватный исходному тексту. Он это делает. А что и где он размещает - его дело. Как и "что он называет оптимизацией, а что не называет".
Так откуда ему знать чего ты там запретил, если ты ему сам про это не скажешь. Есть специальные средства, чтобы запретить переносы и синхронизовать доступ к памяти. Их надо просто знать и пользовать.
Отсюда поподробнее плиз.. Про какие "специальные средства" - речь? Очень интересно .. если про асм "memory" то оно эту конкретно проблему имхо не решает ни разу. Или можете показать "как"? Буду - признателен, код выложен..
P.S. я пока что нашел только 1 выход: критические к оптимизации участки кода делать исключительно asm() вставками "целиком". Но это же .. "костыль"! Как быть с "переносимостью" на иную платформу?!?
Просто как итого, вывод банален до мычания: оптимизация устраняет писание на ЯВУ кода ногодрыга, критичного ко времни исполнения и/или порядка команд. Что для управляющего кода в общем-то "сплошь и рядом". Понятно что "в большинстве случаев" оно получается терпимо .. но как факт имеет место быть: "ногодрыг не гарантирован к исполнению".
Это "проблема" принципиально неразрешима. Поэтому не только "толком" но и вообще никак решена быть не может.
Если придираться к словам, то да Вы - правы, вещи взаимоисключающие. А если по смыслу сказанного, то Ваш ответ - демагогия, не требующая коммента.
А про твою проблему - я её не понимаю, извини. Ты хочешь передавать константу, и при этом изменять параметр? Эта проблема не решаема в принципе. Если не константу, а переменную, так тебе уже сказали как это делается, только ты упорно делаешь не так (судя по коду). В чём собственно проблема? В константе или в том, что ты по каким-то причинам не хочешь делать так, как написано в документации? Если хочешь обсудить свою проблему, сформулируй её как следует, пожалуйста
Понятно. Отстал. Проблема изложена достаточно подробно и уже понята остальными верно. Перечитайте. Вкратце: кусок кода внутри запрещенных прерываний имеет отличное содержимое от того, что написано в программе. В данном случае - существенно, в виде втягивания внутрь этого куска оператора присваниявания из начала цикла.
Так и решение изложено достаточно подробно. Потому мне и непонятно в чём оставшаяся проблема. В том, что ты не хочешь сделать как написано в документации? Или в том, что, что ты хочешь сохранить возможность изменения константы?
Чисто, чтобы не было недоразумений - то, что ты написал в посте №2 - это не так, как у микрочипа написано. А информации о том, что ты попробовал именно так, как там, в теме нет.
?!? Где "изложено решение" и тем более "достаточно подробно", не нашел .. вот правда. покажите пальцем.
что там в посте №2 "не так как у микрочипа" написано? Тоже поясните, мне опять непонятно что Вы хотели этим сказать.. там тупо выложен тестовый скетч .. что там "не так" и про какие рекомендации от микрочипа речь ..
про какие рекомендации от микрочипа речь ..
Опаньки! Ты, что, не читаешь, что тебе пишут? В посте №1 есть ссылка на микрочиповскую документацию, в конце страницы как раз описана ситуация - как заставить компилятор не выбрасывать то, что меняется в ассемблерной вставке. Ты этого не видел? Нужно просто тупо сделать как там (именно asm с параметрами) и всего делов. Я всегда так делаю и проблем не имел никогда.
Такое ощущение, что это как раз Вы не читали обсуждения. А между прочим, специально для Вас выложил ссылки на древнее состояние вопроса. И да, там ничего компилятором НЕ выброшено .. там вообще-то ДОБАВЛЕНО то, чего не должно быть в этом месте.
Понятно. Ну, я же говорил, что не понимаю проблемы. Удачи!
Ну и ещё:
/** * INLINE: 16-bit counter: up to 65535*4 F_CPU for 16Mhz:[0.25 .. 16383.75] mcsec. * Короткие задержки по 4 цикла ЦПУ (кратно 250 нсек) * !!! Работает неверно !!! */ #define delayMicro16(__count) \ __asm__ __volatile__ ( \ "1: sbiw %[__count],1 \n\t" \ "brne 1b\n\t" \ : "=w" (__count) \ : "0" (__count) \ )Приводит к сообщению об ошибке:
"exit status 1
undefined named operand '__count'"
что совершенно понятно, ибо это МАКРОС. Так что в моем ответе пост №2 - изменение макроса с указанием что параметр выходной - единственно верно, ибо он изменяется в нем. Но, при этом, присвоение переменной wait = 4 .. ВТЯГИВАЕТСЯ непосредственно к макросу, хотя в коде оно РАЗНЕСЕНО специально (вытащено из под запрета прерываний).
Ещё раз:
Вот это - изменение порядка следования операций "на ровном месте" (код инвариантен) и приводит к невозможности писать ногодрыги "в теории": точно также туда может быть развернуто, к примеру, инлайном вызов короткой процедуры, что может существенно (только вызов/возврат = 9 тактов) повлиять на время дерганья ногодрыгом пинов, что может оказаться существенным для тайминга. Или наоборот, туда может быть втянуто сколько угодно таких же "присваиваний" ..
и "отключение оптимизации" - не есть "решение", особенно для "библиотечного" кода. Потому что в таком разе к применению библиотеки требуется указывать "отключите оптимизацию".. что есть маразм.
В целом, что называется "привет любителям ногодрыгов". :)
Архат! Вот эта модификация компилируется нормально и дает именно тот код, который ты ожидаешь.
Дурная привычка ругать инструмент, если не умеешь им пользоваться.
/** * Тест накачки высокого обратноходовым импульсным преобразователем * В этом варианте показывает до 679 попугаев или 1384в на ВВ-трансфоматоре */ extern "C" { #define pinPWM 5 /* PORTD PORTD5*/ #define pinHV 0 /* analog pin A0! */ #define MAX_ADCS 4 #define MAX_SAMPLES 10 int adcs[MAX_ADCS*MAX_SAMPLES]; #define pwmON() (PORTD |= 0B00100000) #define pwmOFF() (PORTD &= 0B11011111) /** * INLINE: 16-bit counter: up to 65535*4 F_CPU for 16Mhz:[0.25 .. 16383.75] mcsec. * Короткие задержки по 4 цикла ЦПУ (кратно 250 нсек) * !!! Работает неверно !!! */ #define delayMicro16(__count) \ __asm__ __volatile__ ( \ "1: sbiw %0,1 \n\t" \ "brne 1b\n\t" \ :"=w" (__count): "0" (__count) \ ) /** вроде работает: проверить время! ТОЖЕ НЕ РАБОТАЕТ */ #define delayUS(t) \ { \ do{;}while(--(t)>0); \ } #define adcStart() (ADCSRA |= (1<<ADSC)) #define adcWait() while(ADCSRA&(1<<ADSC)) /** * Однократное чтение АЦП 8-бит (младших) * Если замер завершен, то сначала запуск следующего. * Пока идет замер - работаем дальше */ #define adcReadFast(res) \ { \ adcStart(); \ adcWait(); \ (res)=ADC; \ } } // extern"C" void setup() { pinMode(pinPWM, OUTPUT); pwmOFF(); Serial.begin(115200); ADCSRA = (1<<ADEN) | (2); // включаем АЦП и устанавливаем делитель 1(8), 2(4), 3(2), 4(1), 5(0.5), 6(0.25), 7(0.125) ADMUX = (0<<REFS1)|(1<<REFS0)|(pinHV); // 01 - опорное 5в, рабочий вход - 7 ADCSRB = 0; // тут ничего не надо DIDR0 = 255; // отключаем от OUTPUT все входы АЦП, дабы не разбираться adcStart(); // запуск и сразу adcWait(); // пропуск первого чтения, ибо мусор и долго } void loop () __attribute__ ((noinline,optimize ("-fno-reorder-blocks") )); void loop() { int wait; int n, i; int *ptr; for(n=0; n<MAX_SAMPLES; n++){ wait = 4; ptr = adcs + MAX_ADCS*n; cli(); pwmON(); delayMicro16(wait); pwmOFF(); adcReadFast(*ptr++); // MAX_ADCS раз! adcReadFast(*ptr++); adcReadFast(*ptr++); adcReadFast(*ptr++); sei(); } // вывод попыток накачки на плоттер for(n=0; n<MAX_SAMPLES; n++){ for(i=0; i<5; i++){ Serial.println(n, DEC); } for(i=0; i<MAX_ADCS; i++){ Serial.println( adcs[i+n*MAX_ADCS], DEC); } } delay(2000); // пауза для просмотра результатов }============================
кусок после компиляции
void loop () __attribute__ ((noinline,optimize ("-fno-reorder-blocks") )); void loop() { 474: 8f 92 push r8 476: 9f 92 push r9 478: af 92 push r10 47a: bf 92 push r11 47c: cf 92 push r12 47e: df 92 push r13 480: ef 92 push r14 482: ff 92 push r15 484: 0f 93 push r16 486: 1f 93 push r17 488: cf 93 push r28 48a: df 93 push r29 48c: 06 e1 ldi r16, 0x16 ; 22 48e: 11 e0 ldi r17, 0x01 ; 1 490: f8 01 movw r30, r16 wait = 4; ptr = adcs + MAX_ADCS*n; cli(); pwmON(); delayMicro16(wait); 492: 24 e0 ldi r18, 0x04 ; 4 494: 30 e0 ldi r19, 0x00 ; 0 for(n=0; n<MAX_SAMPLES; n++){ wait = 4; ptr = adcs + MAX_ADCS*n; cli(); 496: f8 94 cli pwmON(); 498: 5d 9a sbi 0x0b, 5 ; 11 delayMicro16(wait); 49a: c9 01 movw r24, r18 49c: 01 97 sbiw r24, 0x01 ; 1 49e: f1 f7 brne .-4 ; 0x49c <loop+0x28> pwmOFF(); 4a0: 5d 98 cbi 0x0b, 5 ; 11 adcReadFast(*ptr++); // MAX_ADCS раз! 4a2: 80 91 7a 00 lds r24, 0x007A 4a6: 80 64 ori r24, 0x40 ; 64 4a8: 80 93 7a 00 sts 0x007A, r24 4ac: 80 91 7a 00 lds r24, 0x007A 4b0: 86 fd sbrc r24, 6 4b2: fc cf rjmp .-8 ; 0x4ac <loop+0x38> 4b4: 80 91 78 00 lds r24, 0x0078 4b8: 90 91 79 00 lds r25, 0x0079 4bc: 91 83 std Z+1, r25 ; 0x01 4be: 80 83 st Z, r24 adcReadFast(*ptr++); 4c0: 80 91 7a 00 lds r24, 0x007A 4c4: 80 64 ori r24, 0x40 ; 64 4c6: 80 93 7a 00 sts 0x007A, r24 4ca: 80 91 7a 00 lds r24, 0x007A 4ce: 86 fd sbrc r24, 6 4d0: fc cf rjmp .-8 ; 0x4ca <loop+0x56> 4d2: 80 91 78 00 lds r24, 0x0078 4d6: 90 91 79 00 lds r25, 0x0079 4da: 93 83 std Z+3, r25 ; 0x03 4dc: 82 83 std Z+2, r24 ; 0x02 adcReadFast(*ptr++); 4de: 80 91 7a 00 lds r24, 0x007A 4e2: 80 64 ori r24, 0x40 ; 64 4e4: 80 93 7a 00 sts 0x007A, r24 4e8: 80 91 7a 00 lds r24, 0x007A 4ec: 86 fd sbrc r24, 6 4ee: fc cf rjmp .-8 ; 0x4e8 <loop+0x74> 4f0: 80 91 78 00 lds r24, 0x0078 4f4: 90 91 79 00 lds r25, 0x0079 4f8: 95 83 std Z+5, r25 ; 0x05 4fa: 84 83 std Z+4, r24 ; 0x04 adcReadFast(*ptr++); 4fc: 80 91 7a 00 lds r24, 0x007A 500: 80 64 ori r24, 0x40 ; 64 502: 80 93 7a 00 sts 0x007A, r24 506: 80 91 7a 00 lds r24, 0x007A 50a: 86 fd sbrc r24, 6 50c: fc cf rjmp .-8 ; 0x506 <loop+0x92> 50e: 80 91 78 00 lds r24, 0x0078 512: 90 91 79 00 lds r25, 0x0079 516: 97 83 std Z+7, r25 ; 0x07 518: 86 83 std Z+6, r24 ; 0x06 sei(); 51a: 78 94 seiАрхат! Вот эта модификация компилируется нормально и дает именно тот код, который ты ожидаешь.
Да, пасибки. Это и есть ответ на вопрос первого поста "есть решения?":
voidloop() __attribute__ ((noinline,optimize ("-fno-reorder-blocks") ));вопрос -закрыт, спасибо.
Ху! Меня забанили в гугле или загуглили в бане?
ты это к чему написал? Не компилируется? - Нет, компилируется.
Интересно откуда я это знаю? Скажу за 100 баксов. Кушать то надо что-то. ;) ;) ;) без обид.
Нужно хорошо знать GCC. Про него целые книги написаны. Я же их читал не бесплатно... ;)
ты это к чему написал? ....
Скажу за 100 баксов. Кушать то надо что-то. ;) ;) ;) без обид.
Написал я чтоб твое жлобство продемонстрироваит ;) Без обид разумеется )))
ПС. По этой фигне и остальным скрытым кормушкам сторонников открытого кода гуглить по "-fno-reorder-blocks". Так вот, в кавычечках.
А забавно. Полистал gcc.gnu.org .. нету там такой опции в документации на компилятор. Можно конечно и догадаться .. но, не факт что она поможет во всех случаях .. там ещё вагон + маленькая тележка опций, приводящая как к перестановке блоков кода, так и к свертке/развертке и даже копипасту.. так что,
получаем в очередной раз вариант "мне повезло, тут пашет так".
Не могу признать такой подход "решением в целом" .. частичное - да. И как вывод модифицируем: "Каждый ногодрыг, критичный к исполнению кода, должен проверяться на доп. указивки компилятору в каждом конктретном случае". :)
И написание критических участков кода (а это 80% управления) надо делать исключительно asm() вставками целиком. То есть "ну его нафиг этот си и тем более си++ для микроконтроллеров" :)
Нет, Архат, получается не так.
Может ли домохозяйка (с Ардуино напервес) написать корректный "ногодрыг" с таймингами порядка 1-2 мкс и меньше?
Нет не может. С дефолтными для ИДЕ настройками компилятора - не может совсем.
Например цикл с 4 NOP-ами оптимизатор просто развернет в 4 NOP-а и все твои расчеты времени пойдудт по 3.14зде.
Для этого есть опции настройки оптимизации, они ВСЕ - на самом деле - есть в полной документиции на GCC. У меня это файл gcc.pdf, аж на 1000 страниц. И да, его нужно знать, пусть не наизусть, но уметь пользоваться как справочником. Я, конечно, не помнил точно опцию, но помнил где ее искать, и да, можно было убрать оптимизацию, выключить только блок-реордер было чистым позерством, признаю.
Ты же, уважаемый, не смог написать нормально ассемблерную вставку, посмотри, я ее тоже правил. Это не упрек, ты научишся, но вместо признания собственных недостатков, например незнания, ты начинаешь ругать среду, стандарт, компилятор. А это уже система.
Где-то в детстве в тебе поселился комплекс, что "стыдно" не знать чего-либо. Так вот это полная херня! Никто не всеведущ, ну кроме сам понимаешь кого ;) ;) ;).
--------------
ЗЫ: gcc.pdf гуглится вот прямо так, по имени файла, на gnu.org ведет первая же ссылка в яндексе, и в этом файле есть все про эту опцию, в том числе.
Похоже и тебя и Логика и правда в гугле забанили. ;)
Пилять .. старею, недосмотрел, виноват .. :)
Твоя опция на самом деле ничего не изменила!
"код после компиляции":
wait = 4; ptr = adcs + MAX_ADCS*n; cli(); pwmON(); delayMicro16(wait); 492: 24 e0 ldi r18, 0x04 ; 4 494: 30 e0 ldi r19, 0x00 ; 0 for(n=0; n<MAX_SAMPLES; n++){ wait = 4; ptr = adcs + MAX_ADCS*n; cli(); 496: f8 94 cli pwmON(); 498: 5d 9a sbi 0x0b, 5 ; 11 delayMicro16(wait); 49a: c9 01 movw r24, r18 49c: 01 97 sbiw r24, 0x01 ; 1 49e: f1 f7 brne .-4 ; 0x49c <loop+0x28> pwmOFF(); 4a0: 5d 98 cbi 0x0b, 5 ; 11Строка 19, 49а: r24 = r18, а ведь wait=4 вынесено ДО cli() и pwmOn() .. промеж pwmOn() и pwmOff() не должно быть НИЧЕГО, кроме цикла паузы .. иначе она искажается на те самые 62.5нсек для 16Мгц.. как було так и осталось. :(
Ну и это твой макрос:
#define delayMicro16(__count) \ __asm__ __volatile__ ( \ "1: sbiw %0,1 \n\t" \ "brne 1b\n\t" \ :"=w" (__count): "0" (__count) \ )А это мой исправленный после коммента от Евгения:
#define delayMicro16(__count) \ __asm__ __volatile__ ( \ "1: sbiw %0,1 \n\t" \ "brne 1b\n\t" \ : "=w" (__count) \ : "0" (__count) \ )Что называется "найди 2 отличия" .. зато гонору .. "ты научишся" .. думаю ты тоже научишся хотя бы читать написанное .. (тоже не упрек, ежели вчё) :)
gcc.gnu.org - ваще-то веб-документ как понимаю и есть. Так вот в нем - данная опция - отсутствует. Присутствует обратная, по которой и можно догадаться. Может в печатном ПДФ есть и это .. не знаю, у меня его нет. Пользуюсь этим веб-вариантом, пока что.
Дурдом :)
Зато весело! Имеем недокументированую опцию к которой даже претензию что она неработает предявить нельзя. Ибо несчего взять что она должна делать именно то что нужно. И вобще несчего взять что она вобще чего делает. И даже нельзя твердо утверждать что она существует, а не ошибочное отсутствие сообщения о ошибке при данном сочитании букв в коде. Писец усиливает что, это не левое творение обезумевшего форумчанина, а сам gcc - мэйнстрим IT. Сегодня вечером забухаю с горя.
Зато весело!
Ты правда не нашел документацию? -f<any option> - описано, -fno<any option> отключение опции. Приятно дурака из себя строить? Не знал за тобой такого ранее... Умеешь удивить!
-freorder-blocks
Reorder basic blocks in the compiled function in order to reduce number of
taken branches and improve code locality.
Enabled at levels ‘-O’, ‘-O2’, ‘-O3’, ‘-Os’.
Пилять .. старею, недосмотрел, виноват .. :)
Извини, Архат. Планировал топать ногами, показывать на ошибки программирования... но подумал, что на твой поток сознания просто нет ответов. Это как баба в истерике: ей не нужно "по полочкам" раскладывать решение ситуации, её нужно просто обнять и пожалеть... сорри - считай, что я тебя обнял. ;) ;) ;)
Бабу тут ты из себя изображаешь, облажавшись.
Не надо меня жалеть, мне надо чтобы було вот так:
48c: 06 e1 ldi r16, 0x16 ; 22 // ptr = adcs + MAX_ADCS*n; 48e: 11 e0 ldi r17, 0x01 ; 1 // пофиг, пусть будет вынесено 490: f8 01 movw r30, r16 for(n=0; n<MAX_SAMPLES; n++){ 492: 24 e0 ldi r18, 0x04 ; 4 // wait = 4; 494: 30 e0 ldi r19, 0x00 ; 0 // пофиг, пусть даже через временный регистр 49a: c9 01 movw r24, r18 ; // НО ДО cli() и задержки!!! 496: f8 94 cli 498: 5d 9a sbi 0x0b, 5 ; 11 // pwmON(); 49c: 01 97 sbiw r24, 0x01 ; 1 // delayMicro16(wait); 49e: f1 f7 brne .-4 ; 0x49c <loop+0x28> 4a0: 5d 98 cbi 0x0b, 5 ; 11 // pwmOFF(); 4a2: 80 91 7a 00 lds r24, 0x007A ; // adcReadFast(*ptr++); // MAX_ADCS раз! 4a6: 80 64 ori r24, 0x40 ; 64 4a8: 80 93 7a 00 sts 0x007A, r24 4ac: 80 91 7a 00 lds r24, 0x007A 4b0: 86 fd sbrc r24, 6 4b2: fc cf rjmp .-8 ; 0x4ac <loop+0x38> 4b4: 80 91 78 00 lds r24, 0x0078 4b8: 90 91 79 00 lds r25, 0x0079 4bc: 91 83 std Z+1, r25 ; 0x01 4be: 80 83 st Z, r24 ... 51a: 78 94 seiстроки 496..4а0 содержат РОВНО 4 оператора: задрал ногу, сделал дело, опустил. Этот блок кода должен получиться именно таким, как он написан на Си, а не таким как его решил сделать оптимизатор!
Прикол в том, что преобразования оптимизатора - ИНВАРИАНТНЫ или УХУДШАЮТ качество этого участка кода, но никак не улучшают.. (на асме, я весь этот участок оптимизирую лучше).
P.S. Для тех, кто в Таньке, поясняю ещё раз: компилятору явно указано присвоение делать ДО закрытия прерываний и работы цикла. Втягивание присваивания (пусть даже во временный регистр) приводит к нарушению тайминга работы ногодрыга. В данном конкрретном случае - критическому. Минимальное исполнение 2-х команд = 4 такта или 250нсек. Внесение команды внутрь увеличивает длительность исполнения на 62.5нсек или на ЧЕТВЕРТЬ. Это - много.
А если макрос задержки (мне просто надо было кратно 250нсек) заменить на delay_loop1() где задержка .. байтовая, то искажение времени задержки составит уже ТРЕТЬ от заданной со всеми последствиями.
Есть решение, кроме рисования полной asm() вставки? Пока не вижу и это - принципиально: никакой ногодрыг НЕ гарантирует точное преобразование своего кода, а стало быть или должен писаться только асм вставками или ногодрыг не может быть писан критичным ко времени исполнения +- лапоть. Ибо сколько в него захочет втянуть оптимизатор - вилами по воде.
P.P.S. Редкий случай, когда Логик - прав. Ибо в документации есть прямые описания для "обратных опций" -fno-..., но .. КРОМЕ ЭТОЙ. То есть опция - недокументированная, но как и писал "догадаться можно". К сожалению не работает точно также.
Она может и работает. Там претензия не столько к перестановке блоков в реальности, сколько к тупости оптимизатора, который уже какой год всё лучшеет и лучшеет, но как не умел переназначать оптимально переменные на регистровое поле так и не умеет.
Все там понятно что происходит: сказали не баловаться с порядком, ну и ладушки. Завел ЕЩЁ ОДНУ (!) регистровую переменную, запихал в неё значение, а когда надо считать макрос задержки ВНЕЗАПНО использовал типовой регистровый блок ВРЕМЕННОГО назначения .. только он оказался не присвоен к моменту .. ну так не беда, ща переприсвоим.
.. и пофиг, что итоговая оптимизация ушла в откровенный минус .. сами же сказали "не балуй" ..
Аналогичное поведение можно увидеть и на присваивании значения указателю, что оказалось вынесенным (у меня) далеко за цикл, где он используется .. там временная перетасовка промеж регистров аж .. дважды. И пофиг, во многих иных местах, что такие перетасовки увеличивают код (и порой критично - см. обработчик таймера 0 - время ардуино). Ну не умеет оптимизатор помнить в какой регистр чего он нафигачил и что прямо оттуда и можно использовать. А ежели низзя (набор команд не позволяет), то можно фигачить сразу туда где оно будет использовано .. не, мы лучше ещё регистр-пару задействуем .. их многа.
Писал это уже им в службу поддержки .. в ответ "ага, мы понимаем, классно .. сделаем .. " 2 года - воз похоже и ныне там жеж.
Понеслась душа в рай :)))
Столько постов и только один попробовал помочь, да и то облажался.. по вопросу - добавить нечего? :)
Облажался ты, когда собрался константу изменять. А помочь тебе невозможно, т.к. ты сам не понимаешь чего хочешь.
Писец. Ты похоже вообще не читатель. Написано и ПОКАЗАНО в каких строчках результата проблема у оптимизатора. Когда в следующий раз будешь поражаться с какого фига твой ногодрыг пашет не так - перечитай топик.
Когда в следующий раз будешь поражаться с какого фига твой ногодрыг пашет не так
Мои ногодрыги пашут так! Наверное, потому, что я константы не модифицирую
Архат! Еще раз.
Вот код, он из твоего, комментарии и лишее выкинул для читаемости. Не сворачивал, так как маленький.
extern "C" { #define pinPWM 5 /* PORTD PORTD5*/ #define pinHV 0 /* analog pin A0! */ #define MAX_ADCS 4 #define MAX_SAMPLES 10 int adcs[MAX_ADCS*MAX_SAMPLES]; #define pwmON() (PORTD |= 0B00100000) #define pwmOFF() (PORTD &= 0B11011111) #define delayMicro16a(__count) \ __asm__ __volatile__ ( \ "1: sbiw %0,1 \n\t" \ "brne 1b \n\t" \ : : "w" (__count) \ ) inline volatile __attribute__ (( gnu_inline, optimize ("--param loop-unroll-jam-size=7") )) void delayMicro16c(uint8_t __count) {do {_NOP();} while (--__count);} } // extern"C" void setup() { pinMode(pinPWM, OUTPUT); pwmOFF(); } void loop () __attribute__ ((noinline,optimize ("-fno-reorder-blocks") )); void loop() { int wait; int n, i; int *ptr; for(n=0; n<MAX_SAMPLES; n++){ wait = 6; //ptr = adcs + MAX_ADCS*n; cli(); pwmON(); delayMicro16c(2); pwmOFF(); delayMicro16c(4); pwmON(); delayMicro16a(1); pwmOFF(); delayMicro16c(6); pwmON(); delayMicro16a(2); pwmOFF(); delayMicro16c(6); pwmON(); delayMicro16a(3); pwmOFF(); delayMicro16c(6); pwmON(); delayMicro16a(4); pwmOFF(); delayMicro16c(6); pwmON(); delayMicro16a(5); pwmOFF(); delayMicro16c(6); pwmON(); delayMicro16a(7); pwmOFF(); delayMicro16c(6); sei(); delay(1); } }Все видно: delayMicro16a и c - асм и С варианты, соответственно.
С вариант разворачивается в НОП без цикла, если их меньше 7 штук.
Этим и пользуемся.
С вариант, конечно с константой! Присвоение регистру, которое тебе не нравится не "глупость" оптимизатора, а следствие того, что в АСМ вставе есть и ВХОДНЫЕ и ВЫХОДНЫЕ регистры. Убери выход и все станет красиво. Запрет введения loop( ) инлайном, плюс запрет реордеринга - дают правильное расположение всех инициализаций. Как правильно тут заметели - компилятор НЕ МОЖЕТ знать, что для тебя важны cli/sti скобки.
Нужно разговаривать с компилятором на ЕГО языке, а не на "Ну ты чо, не понял?!"
я написал тебе пример использования ОБОИХ вариантов, с учетом особенностей АВР.
Вот картинка с логаналайзера:
И вот тайминги с него же. Третья колонка - это я для тебя в ЛибреКалке... ну для тебя понятнее - в Екселе - длительности посчитал.
Снимал от 575 до 590 мкс.
То есть видишь: два НОПа дают 250 нс импульс, потому что 125 и еще 125 на сами команды записи в порт.
4 Нопа (второй 0) - 250+125, как мы и ждали.
6 нопов - это все паузы - дают по 500нс.
далее в деле твоя ассемблерная задержка, Она дает от 375 и далее с шагом в 250, что мы от нее и ждали.
=========================
Ну так в чем сложность писать "ногодрыг"?
И да. Я понимаю, что у тебя в Новосибе вечер уже, так что ты, вернее всего, только завтра разберешься.
Но ЗАРАНЕЕ пишу, что если тебе нужна регулировка длительности импульса в рантайме, то писать нужно немного не так.
Тоже все спокойно делается с точностью до 62,5 нс и начиная от 125 нс, но писать сложнее, там выравнивать нужно будет по вариантам.
Код не будет сильно изящьным, но можно спокойно обойтись только С, без Асма.
Этот вариант написать? - Я понимаю, что ты из гордости откажешься, а зря. Я просто уже ходил по этим граблям и первый раз бился над неправильными таймингами долго. Я же и написал тебе, что правильный ногодрыг - это не просто и с наскока не решается. А ты готов всех "шашками порубать" и компилятор проклясть. Нормально?