Перейти к содержанию
    

Быстрый GPIO

Добрый день,

 

хочу с очень точной фазой формировать сигнал включить/выключить на 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 к сожалению, тоже не помогает.

 

Пожалуйста, посоветуйте, можно ли как-то сделать, чтобы за каждый такт посылать в этот порт нужные числа?

 

Спасибо!

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

 

Максимум, что вы можете сделать, завести массив констант и из него в порт последоаательно элементы писать. Тогда код оптисизируется до последовательности LDR, STR, LDR, STR. Ещё можно переслать с помощтю DMA, если контроллер умеет.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вот смотрите. 

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.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо большое за ответ!

 

Тот исходник, что я привел, мне уже сейчас гарантированно дает 2 такта на запись, то есть при оверклоке до 816МГц тактовой я более-менее гарантированно за каждые 2.5нс засылаю новое 32-битное число в порт и это очень хорошо видно осциллографом, я смотел на 1ГС/с двухканальном от NI: PCI-5154-3, тыкаясь в разные пары каналов.

 

Мне же хочется положить эти числа в регистры, чтобы команды "LDR.N R2,??DataTable2_10" не было. Как я понимаю, регистров у этого процессора достаточно, чтобы 14 чисел туда сохранить, но если 14 не влезет, буду менять последовательность, возможно и 10 чисел мне хватит.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ваш код действительно даст 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}  

Регистров таки не хватило

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

17 minutes ago, VladislavS said:

Ваш код действительно даст 2 такта на запись, но сколько будет стоит подготовка к этой записи? Сомневаюсь, что вам 256 раз надо одно и то же выводить.

да, как раз много (от 100 до 256 примерно) мне выводить и надо. Данные можно сохранить в разные функции (у меня ожидается примерно 2000 выриантов), каждая функция сейчас занимает около 128 байт, то есть 256КБ на все, благо флаша там 8МБайт. Функции в быструю память не класть, они короткие, и одновременно мне надо только 10-20 функций, после первого их дерганья оно все закешируется и будет быстро работать.

 

По современному таймингу получается, что если я вывожу 256 раз это все, то сверху еще мне надо только 4 такта (я в примере выше печатал номер процессорного такта до и после) и сделал такие выводы.

 

Спасибо большое, за пример с регистрами!!!

 

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

 

У меня основная проблема - как понятно это скормить компилятору - я понимаю, что наверное, смогу осилить модифицировать небольшой кусок кода на ассемблере, но вот написать всю функцию с нуля на ассемблере быстро не смогу, поэтому ищу какие-то ключи или диррективы у компилятора, чтобы он гарантированно положил несколько переменных в регистры и далее сам дооптимизировал код. Скажите, пожалуйста, могли бы Вы посоветовать как бы это сделать?

 

Спасибо!

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Сделать чтобы компилятор оптимизировал как-то гарантировано невозможно.  Нужен асм. Функцию на асме проще всего начать написав её на С, скомпилировать её и вытащить листинг. В этом листинге будет вся "обязательная обвязка". Внутренности правьте как надо.

И всё же, при наличии много памяти, я бы на DMA попробовал. Скорее всего получите ту же скорость, но сильно проще алгоритмически.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

И еще... Как я понимаю, этот процессор имеет суперскалярную архитектуру, то есть две или даже несколько команд, если они не зависимы по аргументам, могут выполняться полностью параллельно, если ресурсы процессора позволяют. То есть у меня есть подозрение, что если я как-то объясню провессору, что мне надо загружать из общей памяти в первый регистр, а сохранять в GPIO6_DR из второго, а потом чередовать, то я смогу за один такт делать то, что мне надо и число регистров тут не сильно будет важно, и последовательность у меня может быть супер длинная.

1 minute ago, VladislavS said:

И всё же, при наличии много памяти, я бы на DMA попробовал. Скорее всего получите ту же скорость, но сильно проще алгоритмически.

Спасибо большое за советы!!!

 

С DMA как я понимаю, не получится, я читал посты на форуме от этой борды (https://forum.pjrc.com/) - там народ утверждает, что DMA на 1/4 тактовой может работать, то есть уже в 2 раза медленнее, чем я делаю. Я сам DMA для этого проессора только дла SPI и ADC использовал, то есть GPIO еще не пробовал, но на форумах пишут, и примеров простых не было, то есть не факт, что даже если попробовать, то что-то будет хорошее. Мне самому ручной ногодрыг очень не нравится - процессор в этот момент совсем ничего больше делать не может, но, похоже, у меня выбора нет.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Это же Cortex-M7, если я правильно помню? Не думаю что у него такое возможно. Вы же видели, что ваш код, состоящий из последовательных STR разных регистров даёт 2 такта. Со STR выше не прыгнешь.

 

Еще у вашего процессора fpu есть, там тоже хранить что-то можно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Спасибо, большое, 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.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...