Используйте Arduino MEGA для генерации нескольких прямоугольных сигналов частотой 40 кГц с 10 фазами.

Я хочу использовать Arduino-mega для генерации нескольких прямоугольных сигналов с частотой 40 кГц, и его можно использовать для управления ультразвуковым преобразователем.

Итак, я использую стратегию из статьи "Ultraino: открытая система с фазированной решеткой для Узкополосная передача ультразвука по воздуху», которая применяет 10-битный сигнал с последовательностью «0» или «1» в PORTA, PORTC... и т. д. для получения прямоугольного сигнала, как на рис. 1.

Рис. 1 прямоугольный сигнал

Рис. 1 прямоугольный сигнал


Затем я меняю шаблон последовательности на сдвиг фазы, и всего 10 фаз, возникает какая-то проблема.

Мне любопытен принцип этого метода:

  1. Почему последовательность может генерировать сигнал с частотой 40 кГц? Означает ли это, что Arduino будет тратить несколько микросекунд на выполнение команды типа POTRA=0x1. Я пытаюсь изменить длину массива, он больше не будет хорошо генерировать прямоугольную волну.

  2. Когда я выполняю функцию сдвига shiftPhase, она имеет 10 фаз. В 5 фазах она будет перемещать прямоугольную волну, как на рис. 2, остальные 5 фаз будут как на рис. 3, обязанность прямоугольной волны изменяется, как и инвертированная. Итак, как это происходит, просто с изменением последовательности 0,1?

Рис. 2. Успех волны сдвига Рис. 2. Успех волны сдвига


Рис. 3 волна смещения не удалась Рис.3 волна сдвига не удалась


код, который я использую:

#include <avr/sleep.h>
#include <avr/power.h>
#define N_PATTERNS 1
#define N_PORTS 10
#define N_DIVS 10
#define COMMAND_SWITCH 0b00000000
#define COMMAND_DURATION 0b00110000
#define MASK_DURATION 0b00111111
#define COMMAND_COMMITDURATIONS 0b00010000

#define WAIT(a) __asm__ __volatile__ ("nop")
#define OUTPUT_WAVE(pointer, d)  PORTA = pointer[d*N_PORTS + 0]; PORTC = pointer[d*N_PORTS + 1]; PORTL = pointer[d*N_PORTS + 2]; PORTB = pointer[d*N_PORTS + 3]; PORTK = pointer[d*N_PORTS + 4]; PORTF = pointer[d*N_PORTS + 5]; PORTH = pointer[d*N_PORTS + 6];  PORTD = pointer[d*N_PORTS + 7]; PORTG = pointer[d*N_PORTS + 8]; PORTJ = pointer[d*N_PORTS + 9]
static byte bufferA[N_PATTERNS * N_DIVS * N_PORTS];
static byte bufferB[N_PATTERNS * N_DIVS * N_PORTS];
static byte animation[100] = {
                            0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
                            };

String str="";
void shiftPhase(byte* p,int len,int inter,int stepsize,byte id)
{
  byte mask=0xff-id;
  byte q[10]={0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0};
  for(int i=0;i<10;i++)
  {
    q[i]=p[i*inter];
  }
  for(int i=0;i<10;i++)
  {
    p[i*inter]=(q[(i+stepsize+10)%10]&id)|(mask&q[i]);
  }
}

void setup()
{
  //устанавливаем в качестве выходных портов ACLBKFHDGJ
  DDRA = DDRC = DDRL = DDRB = DDRK = DDRF = DDRH = DDRD = DDRG = DDRJ = 0xFF;
  //слабый сигнал на всех из них
  PORTA = PORTC = PORTL = PORTB = PORTK = PORTF = PORTH = PORTD = PORTG = PORTJ = 0x00;
  //очистить буферы
  for (int i = 0; i < (N_PATTERNS * N_DIVS * N_PORTS); ++i) {
    bufferA[i] = bufferB[i] = 0;
  }
   for (int i = 0; i < 100; ++i)
   {
      bufferA[i] =  animation[i];
   }

  // отключаем все что нам не нужно
  ADCSRA = 0;  // АЦП
  power_adc_disable ();
  power_spi_disable();
  power_twi_disable();
  power_timer0_disable();
  power_usart1_disable();
  power_usart2_disable();


  Serial.begin(9600);

  byte bReceived = 0;
  bool byteReady = false;
  bool emittingA = true;
  byte* emittingPointerH = & bufferA[0];
  byte* emittingPointerL = & bufferA[N_PORTS * N_DIVS / 2];
  
LOOP:
  OUTPUT_WAVE(emittingPointerH, 0);
  OUTPUT_WAVE(emittingPointerH, 1);
  OUTPUT_WAVE(emittingPointerH, 2);
  OUTPUT_WAVE(emittingPointerH, 3);
  OUTPUT_WAVE(emittingPointerH, 4);
  OUTPUT_WAVE(emittingPointerL, 0);
  OUTPUT_WAVE(emittingPointerL, 1);
  OUTPUT_WAVE(emittingPointerL, 2);
  OUTPUT_WAVE(emittingPointerL, 3);
  OUTPUT_WAVE(emittingPointerL, 4); 

  byteReady = Serial.available(); 
  if(byteReady!=0)
  {
      str="";
      str=char(Serial.read());
      Serial.println(str);
      if(str=="1")
      {
        shiftPhase(bufferA,10,10,1,0x1);
      }
      if(str=="2")
      {
        shiftPhase(bufferA,10,10,-1,0x1);
      }
    }
 while(Serial.read()>=0){}
  goto LOOP;

}

void loop() {}

Большое спасибо!

Код статьи можно увидеть по ссылке.

, 👍0

Обсуждение

Не по теме: `PORTA = PORTB = ... = PORTG = ... = 0xFF;' действительно плохая идея. PORTG имеет только 6 бит, и, согласно техническому описанию, неиспользуемые контакты читаются как 0. И этот шаблон присваивается самому правому порту, затем считывает значение и назначает его следующему., @KIIV


1 ответ


Лучший ответ:

1

Shiny100 спросил:

Означает ли это, что Arduino будет тратить несколько микросекунд на выполнение команды? например POTRA=0x1.

Я попытался скомпилировать POTRA=0x1 и получил следующее:

ldi r24, 0x01  ; load 0x1 into register r24
out 0x02, r24  ; output register r24 to PORTA (I/O address 0x02)

Эти инструкции выполняются за один цикл, поэтому последовательность занимает 2 цикла, т. е. 0,125 мкс при 16 МГц. Ваш код, однако, делает намного больше, чем это. Макрос OUTPUT_WAVE() включает чтение десять значений из ОЗУ и запись их на десять разных портов. Первое вызов этого макроса компилируется в это:

lds r24, 0x028D ; r24 = bufferA[0]
out 0x02, r24   ; PORTA = r24
lds r24, 0x028E ; r24 = bufferA[1]
out 0x08, r24   ; PORTC = r24
lds r24, 0x028F ; r24 = bufferA[2]
sts 0x010B, r24 ; PORTL = r24
lds r24, 0x0290 ; r24 = bufferA[3]
out 0x05, r24   ; PORTB = r24
lds r24, 0x0291 ; r24 = bufferA[4]
sts 0x0108, r24 ; PORTK = r24
lds r24, 0x0292 ; r24 = bufferA[5]
out 0x11, r24   ; PORTF = r24
lds r24, 0x0293 ; r24 = bufferA[6]
sts 0x0102, r24 ; PORTH = r24
lds r24, 0x0294 ; r24 = bufferA[7]
out 0x0b, r24   ; PORTD = r24
lds r24, 0x0295 ; r24 = bufferA[8]
out 0x14, r24   ; PORTG = r24
lds r24, 0x0296 ; r24 = bufferA[9]
sts 0x0105, r24 ; PORTJ = r24

Инструкция out по-прежнему однотактная, но lds (загрузка из память) и sts (сохранение в памяти) занимают два цикла каждый. Вы можете заметить что только порты от A до F доступны с помощью инструкции out. Это связано с тем, что они отображаются в адресном пространстве ввода-вывода микроконтроллер. Доступ к другим четырем портам осуществляется с использованием более медленного Инструкция sts для отображенных в памяти адресов.

Если вы подсчитаете свои циклы, вы должны получить 34 цикла для всего последовательность, которая составляет 2,125 мкс на частоте 16 МГц. В идеале вы хотели бы это займет 2,5 мкс (1/10 цикла сигнала 40 кГц). Ты Вы можете заметить, что код, из которого вы черпали вдохновение, имеет несколько инструкции между последовательными вызовами OUTPUT_WAVE(). мне кажется как будто они были тщательно рассчитаны, чтобы подобраться как можно ближе к 2,5 мкс на фазу. Такой подход явно хрупок: любой крошечный изменения в коде или даже обновление компилятора могут сбить вас с толку. тщательно рассчитанный по времени код.

Обновление: второй вопрос, если я правильно понял, касается изменение рабочего цикла. Сигналы имеют номинальный рабочий цикл 50%: сигнал НИЗКИЙ в течение 5 временных интервалов (5/10 цикла), затем HIGH для следующих 5 временных интервалов. Однако временные интервалы не имеют все одинаковую длину. Первые девять длятся по 2,125 мкс каждый. последний, однако, несколько длиннее, потому что занимает продолжительность код, обрабатывающий последовательный порт, и два цикла goto LOOP;. Если более длинный временной интервал происходит, когда выход ВЫСОКИЙ, вы получаете фактический рабочий цикл выше 50%. Если это происходит, когда оно ВЫСОКОЕ, вы получить более короткий рабочий цикл, как в вашем примере «сбой волны сдвига».

,

Спасибо, я думаю, что понял это более четко. Но мне интересно, как я могу получить информацию о компиляции, как вы, и могу ли я создать более 24 прямоугольных волн, как [это] (https://runtimemicro.com/Forums/RTM_TimerCalc /Примеры-и-Советы/Arduino-Timer-Phase-Shifted-Square-Waves) метод?, @Shiny100

@ Shiny100: Вы можете разобрать файл .elf с помощью команды avr-objdump -S. avr-objdump уже должен быть где-то в вашей установке Arduino. Метод, на который вы ссылаетесь, может управлять до трех сигналов на таймер. Используя функцию сброса предварительного делителя таймеров 0, 1, 3, 4 и 5, вы можете синхронизировать эти таймеры и когерентно управлять до 13 сигналов., @Edgar Bonet