iiv 25 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба Добрый день, хочу с очень точной фазой формировать сигнал включить/выключить на GPIO на IMXRT1062. Для этого мне надо максимально быстро писать 32 битное слово в GPIO6_DR порт. Я это делаю так (код для teensy 4.1) #pragma GCC push_options #pragma GCC optimize ("Ofast") #include <Arduino.h> void RunRealExcitationOne() { register unsigned int A0=0x22a20002; register unsigned int A1=0x22860002; register unsigned int A2=0x2a840002; register unsigned int A3=0x0a940002; register unsigned int A4=0x0a150002; register unsigned int A5=0x18150002; register unsigned int A6=0x18510002; register unsigned int A7=0x11510002; register unsigned int A8=0x11490002; register unsigned int A9=0x15480002; register unsigned int A10=0x05680002; register unsigned int A11=0x052a0002; register unsigned int A12=0x242a0002; register unsigned int A13=0x24a20002; unsigned char i=0; do { GPIO6_DR=A0; GPIO6_DR=A1; GPIO6_DR=A2; GPIO6_DR=A3; GPIO6_DR=A4; GPIO6_DR=A5; GPIO6_DR=A6; GPIO6_DR=A7; GPIO6_DR=A8; GPIO6_DR=A9; GPIO6_DR=A10; GPIO6_DR=A11; GPIO6_DR=A12; GPIO6_DR=A13; i--; } while(i); } #if 0 void RunRealExcitationTwo() { unsigned int A0=0x22860002; unsigned int A1=0x2a840002; unsigned int A2=0x0a940002; unsigned int A3=0x0a150002; unsigned int A4=0x18150002; unsigned int A5=0x18510002; unsigned int A6=0x11510002; unsigned int A7=0x11490002; unsigned int A8=0x15480002; unsigned int A9=0x05680002; unsigned int A10=0x052a0002; unsigned int A11=0x242a0002; unsigned int A12=0x24a20002; unsigned int A13=0x22a20002; unsigned char i=0; do { GPIO6_DR=A0; GPIO6_DR=A1; GPIO6_DR=A2; GPIO6_DR=A3; GPIO6_DR=A4; GPIO6_DR=A5; GPIO6_DR=A6; GPIO6_DR=A7; GPIO6_DR=A8; GPIO6_DR=A9; GPIO6_DR=A10; GPIO6_DR=A11; GPIO6_DR=A12; GPIO6_DR=A13; i--; } while(i); } #endif void setup() { while(!Serial); // wait for serial port to connect if (CrashReport) { Serial.print(CrashReport); delay(5000); } Serial.println("We are starting..."); pinMode(23, OUTPUT); pinMode(22, OUTPUT); pinMode(21, OUTPUT); pinMode(20, OUTPUT); pinMode(19, OUTPUT); pinMode(18, OUTPUT); pinMode(17, OUTPUT); pinMode(16, OUTPUT); pinMode(15, OUTPUT); pinMode(14, OUTPUT); pinMode(41, OUTPUT); pinMode(40, OUTPUT); pinMode(39, OUTPUT); pinMode(38, OUTPUT); } void loop() { unsigned long t1, t2; t1=ARM_DWT_CYCCNT; for(unsigned char i=0; i<128; i++) { RunRealExcitationOne(); // RunRealExcitationTwo(); } t2=ARM_DWT_CYCCNT; Serial.printf("Times: %ld %ld\n", t2-t1, (t2-t1)/255/128); delay(1000); } #pragma GCC pop_options я пробовал играться оптимизацией, но это не помогает. Как я понимаю, запись с регистра в GPIO6_DR стоит один такт, то есть теоретически я могу за каждый процессорный такт засылать в GPIO6_DR нужные числа. Слово register перез описанием этих A0...A13 к сожалению, тоже не помогает. Пожалуйста, посоветуйте, можно ли как-то сделать, чтобы за каждый такт посылать в этот порт нужные числа? Спасибо! Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
VladislavS 39 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба Надеюсь, вы понимаете, что запись числа в порт просходит через загрузку адреса порта в регистр процессора, загрузки числа в другой регистр процессора, пересылка данных в порт командой STR. И каждая из этих составляющих выпол6яется далеко не один такт. Максимум, что вы можете сделать, завести массив констант и из него в порт последоаательно элементы писать. Тогда код оптисизируется до последовательности LDR, STR, LDR, STR. Ещё можно переслать с помощтю DMA, если контроллер умеет. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
VladislavS 39 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба Вот смотрите. constexpr uint32_t buf[] = { 0x22a20002, 0x22860002, 0x2a840002, 0x0a940002, 0x0a150002, 0x18150002, 0x18510002, 0x11510002, 0x11490002, 0x15480002, 0x05680002, 0x052a0002, 0x242a0002, 0x24a20002 }; // У меня нет такого контроллера, адрес порта пальцем в небо volatile uint32_t *GPIO6 = (uint32_t *)0x1000'0000; void foo() { *GPIO6 = buf[0]; *GPIO6 = buf[1]; *GPIO6 = buf[2]; *GPIO6 = buf[3]; *GPIO6 = buf[4]; *GPIO6 = buf[5]; *GPIO6 = buf[6]; *GPIO6 = buf[7]; *GPIO6 = buf[8]; *GPIO6 = buf[9]; *GPIO6 = buf[10]; *GPIO6 = buf[11]; *GPIO6 = buf[12]; *GPIO6 = buf[13]; } И в лучшем случае, вы можете надеяться, что копилятор это оптимизирует вот так //foo(); LDR.N R1,??DataTable2 LDR R0,[R1, #+4] LDR.N R2,??DataTable2_1 STR R2,[R0, #+0] LDR.N R2,??DataTable2_2 STR R2,[R0, #+0] LDR.N R2,??DataTable2_3 STR R2,[R0, #+0] LDR.N R2,??DataTable2_4 STR R2,[R0, #+0] LDR.N R2,??DataTable2_5 STR R2,[R0, #+0] LDR.N R2,??DataTable2_6 STR R2,[R0, #+0] LDR.N R2,??DataTable2_7 STR R2,[R0, #+0] LDR.N R2,??DataTable2_8 STR R2,[R0, #+0] LDR.N R2,??DataTable2_9 STR R2,[R0, #+0] LDR.N R2,??DataTable2_10 STR R2,[R0, #+0] LDR.N R2,??DataTable2_11 STR R2,[R0, #+0] LDR.N R2,??DataTable2_12 STR R2,[R0, #+0] LDR.N R2,??DataTable2_13 STR R2,[R0, #+0] LDR.N R2,??DataTable2_14 STR R2,[R0, #+0] Понятно, что никакого одного такта на запись тут не может быть. Смотрите в сторону DMA. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
iiv 25 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба Спасибо большое за ответ! Тот исходник, что я привел, мне уже сейчас гарантированно дает 2 такта на запись, то есть при оверклоке до 816МГц тактовой я более-менее гарантированно за каждые 2.5нс засылаю новое 32-битное число в порт и это очень хорошо видно осциллографом, я смотел на 1ГС/с двухканальном от NI: PCI-5154-3, тыкаясь в разные пары каналов. Мне же хочется положить эти числа в регистры, чтобы команды "LDR.N R2,??DataTable2_10" не было. Как я понимаю, регистров у этого процессора достаточно, чтобы 14 чисел туда сохранить, но если 14 не влезет, буду менять последовательность, возможно и 10 чисел мне хватит. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
VladislavS 39 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба Ваш код действительно даст 2 такта на запись, но сколько будет стоит подготовка к этой записи? Сомневаюсь, что вам 256 раз надо одно и то же выводить. SECTION `.text`:CODE:NOROOT(2) THUMB // 7 void RunRealExcitationOne() // 8 { unsigned int A0=0x22a20002; _Z20RunRealExcitationOnev: PUSH {R4-R11,LR} // 9 unsigned int A1=0x22860002; // 10 unsigned int A2=0x2a840002; // 11 unsigned int A3=0x0a940002; // 12 unsigned int A4=0x0a150002; // 13 unsigned int A5=0x18150002; // 14 unsigned int A6=0x18510002; // 15 unsigned int A7=0x11510002; // 16 unsigned int A8=0x11490002; // 17 unsigned int A9=0x15480002; // 18 unsigned int A10=0x05680002; // 19 unsigned int A11=0x052a0002; // 20 unsigned int A12=0x242a0002; // 21 unsigned int A13=0x24a20002; // 22 unsigned char i=255; LDR.N R1,??DataTable3 LDR.N R2,??DataTable3_1 LDR R1,[R1, #+4] LDR.N R3,??DataTable3_2 LDR.N R4,??DataTable3_3 LDR.N R5,??DataTable3_4 LDR.N R6,??DataTable3_5 LDR.W R11,??DataTable3_6 LDR.W R12,??DataTable3_7 LDR.W LR,??DataTable3_8 LDR.W R8,??DataTable3_9 LDR.W R9,??DataTable3_10 LDR.W R10,??DataTable3_11 MOVS R0,#+255 // 23 do // 24 { *GPIO6=A0; ??RunRealExcitationOne_0: STR R10,[R1, #+0] // 25 *GPIO6=A1; STR R9,[R1, #+0] // 26 *GPIO6=A2; STR R8,[R1, #+0] // 27 *GPIO6=A3; STR LR,[R1, #+0] // 28 *GPIO6=A4; STR R12,[R1, #+0] // 29 *GPIO6=A5; STR R11,[R1, #+0] // 30 *GPIO6=A6; STR R6,[R1, #+0] // 31 *GPIO6=A7; STR R5,[R1, #+0] // 32 *GPIO6=A8; STR R4,[R1, #+0] // 33 *GPIO6=A9; STR R3,[R1, #+0] // 34 *GPIO6=A10; STR R2,[R1, #+0] // 35 *GPIO6=A11; // 36 *GPIO6=A12; // 37 *GPIO6=A13; // 38 i--; // 39 } while(i); SUBS R0,R0,#+1 LDR.N R7,??DataTable3_12 STR R7,[R1, #+0] LDR.N R7,??DataTable3_13 STR R7,[R1, #+0] LDR.N R7,??DataTable3_14 STR R7,[R1, #+0] BNE.N ??RunRealExcitationOne_0 // 40 } POP {R4-R11,PC} Регистров таки не хватило Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
iiv 25 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба 17 minutes ago, VladislavS said: Ваш код действительно даст 2 такта на запись, но сколько будет стоит подготовка к этой записи? Сомневаюсь, что вам 256 раз надо одно и то же выводить. да, как раз много (от 100 до 256 примерно) мне выводить и надо. Данные можно сохранить в разные функции (у меня ожидается примерно 2000 выриантов), каждая функция сейчас занимает около 128 байт, то есть 256КБ на все, благо флаша там 8МБайт. Функции в быструю память не класть, они короткие, и одновременно мне надо только 10-20 функций, после первого их дерганья оно все закешируется и будет быстро работать. По современному таймингу получается, что если я вывожу 256 раз это все, то сверху еще мне надо только 4 такта (я в примере выше печатал номер процессорного такта до и после) и сделал такие выводы. Спасибо большое, за пример с регистрами!!! С регистрами - да, есть подозрение, что на 14 чисел мне регистров не хватит, но меня устроит если их будет 7, я могу правильно все так сгруппировать, что одно и то же число будет выдаваться несколько раз. У меня основная проблема - как понятно это скормить компилятору - я понимаю, что наверное, смогу осилить модифицировать небольшой кусок кода на ассемблере, но вот написать всю функцию с нуля на ассемблере быстро не смогу, поэтому ищу какие-то ключи или диррективы у компилятора, чтобы он гарантированно положил несколько переменных в регистры и далее сам дооптимизировал код. Скажите, пожалуйста, могли бы Вы посоветовать как бы это сделать? Спасибо! Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
VladislavS 39 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба Сделать чтобы компилятор оптимизировал как-то гарантировано невозможно. Нужен асм. Функцию на асме проще всего начать написав её на С, скомпилировать её и вытащить листинг. В этом листинге будет вся "обязательная обвязка". Внутренности правьте как надо. И всё же, при наличии много памяти, я бы на DMA попробовал. Скорее всего получите ту же скорость, но сильно проще алгоритмически. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
iiv 25 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба И еще... Как я понимаю, этот процессор имеет суперскалярную архитектуру, то есть две или даже несколько команд, если они не зависимы по аргументам, могут выполняться полностью параллельно, если ресурсы процессора позволяют. То есть у меня есть подозрение, что если я как-то объясню провессору, что мне надо загружать из общей памяти в первый регистр, а сохранять в GPIO6_DR из второго, а потом чередовать, то я смогу за один такт делать то, что мне надо и число регистров тут не сильно будет важно, и последовательность у меня может быть супер длинная. 1 minute ago, VladislavS said: И всё же, при наличии много памяти, я бы на DMA попробовал. Скорее всего получите ту же скорость, но сильно проще алгоритмически. Спасибо большое за советы!!! С DMA как я понимаю, не получится, я читал посты на форуме от этой борды (https://forum.pjrc.com/) - там народ утверждает, что DMA на 1/4 тактовой может работать, то есть уже в 2 раза медленнее, чем я делаю. Я сам DMA для этого проессора только дла SPI и ADC использовал, то есть GPIO еще не пробовал, но на форумах пишут, и примеров простых не было, то есть не факт, что даже если попробовать, то что-то будет хорошее. Мне самому ручной ногодрыг очень не нравится - процессор в этот момент совсем ничего больше делать не может, но, похоже, у меня выбора нет. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
VladislavS 39 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба Это же Cortex-M7, если я правильно помню? Не думаю что у него такое возможно. Вы же видели, что ваш код, состоящий из последовательных STR разных регистров даёт 2 такта. Со STR выше не прыгнешь. Еще у вашего процессора fpu есть, там тоже хранить что-то можно. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
iiv 25 29 января, 2022 Опубликовано 29 января, 2022 · Жалоба Спасибо, большое, VladislavS за ответ! 57 minutes ago, VladislavS said: Это же Cortex-M7, если я правильно помню? Не думаю что у него такое возможно. Вы же видели, что ваш код, состоящий из последовательных STR разных регистров даёт 2 такта. Со STR выше не прыгнешь. у меня есть надежда что тут суперскалярность есть - о ней есть что-то в документации. Вот например, в моем примере цикл по инкременту ведь должен занимать хотя бы пару тактов, то есть полностью тело цикла должно длиться не 28 тактов, а хотя бы 30. А вот если измерить число тактов при вызове самой функции RunRealExcitationOne (причем я ее вызываю 128 раз) и посчитать сколько тактов потратил процессор, то оказывается, что за все это время лишних было только 820 тактов, (всего тактов 918324, а если только на эту часть считать, что мне надо 256*128*28 тактов, то разница только 820 такт), то есть по 6.4 такта на вызов RunRealExcitationOne и совсем ничего на условный переход и инкрементацию индекса. Причем сама функция не анроллится, если взять несколько таких функций и посчитать общий размер проекта для 10 и 9 такох функций, то разница составляет примерно 128 байт. 57 minutes ago, VladislavS said: Еще у вашего процессора fpu есть, там тоже хранить что-то можно. это тоже идейно, спасибо! У меня кстати, только первые два байта разные в этих A0...A13, но, понятно, что любые сдвиговые операции тоже требуют тактов, и я пока в этом направлении еще ничего хорошего не придумал. PS: слегка исправил исходник в первом сообщении, так как там еще старая версия была с циклом до 255, а не до 256. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться