Producing multiple phase controlled square waves at 40 kHz

Hello everyone,

I am building an acoustic levitator, similar to the TinyLev made by Asier Marzo et al. In their supplementary material, they have code which controls the levitation, written for an Arduino nano. However, much of it seems to be in assembly which I don't really know how to even get started with.

I am using an Arduino Nano.

However, I want to modify mine so as to gain rotational control.
For this I will need to produce multiple signals at the same frequency, but whose phase can be controlled.

Here is what I have attempted so far:

bool toggle = 0;
byte frameA = 0 ;
byte frameB = 0;//defining where in the function to start

const int X = 8;
long countX = 0;

static byte waveAnd[X] = 
{0b11111010, 0b11111010, 0b11111010, 0b11111010, 0b11110101, 0b11110101, 0b11110101, 0b11110101};//the wavefunction is one set of pins high, the other low and then flips halfway along

static byte waveNot[X]=
{0b00001010, 0b00001010, 0b00001010, 0b00001010, 0b00000101, 0b00000101, 0b00000101, 0b00000101};

void setup() {

  Serial.begin(9600);


  toggle = 1;
  DDRC = 0b00001111;
  PORTC= 0b00000101;

  cli(); // stop interrupts



  //set timer0 interrupt at 40kHz
  TCCR0A = 0;// set entire TCCR0A register to 0
  TCCR0B = 0;// same for TCCR0B
  TCNT0  = 0;//initialize counter value to 0
  // set compare match register for 40*2*8khz increments
  OCR0A = 49;// 
  // turn on CTC mode
  TCCR0A |= (1 << WGM01);
  
  TCCR0B |= (1 << CS00);// | (1 << CS01);   No prescaler
  // enable timer compare interrupt
  TIMSK0 |= (1 << OCIE0A);

countX = ((OCR0A + 1) * X) - 1 ;//setting countEight as 8 times larger than the movement through the array as the array has 8 members to reset at correct time  
  //setting up timer 1 for 40kHz

  TCCR1A = 0;
  TCCR1B = 0; //clearing the registers, making sure they are all 0 before acting 
  TCNT1  = 0; // setting counter to 0

  //the number of counts required for 40kHz
  // this is doubled as the switching frequency has to be double the period frequency
  OCR1A = countX; // = 16MHz/80kHz - 1 8 times slower than timer 0
  //setting the timer to compare match (CTC) mode
  TCCR1B |= (1 << WGM12);
  //setting the prescaler to 1
  TCCR1B |= (1 << CS10);// | (1 << CS11); //64 scaler
  //enable the timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
sei(); //allow interrupts
}


ISR(TIMER0_COMPA_vect){
    
    PORTC = (PORTC & waveAnd[frameA]) | (waveNot[frameA]);
    frameA++;
    //frameB++;
    
    //frameA = 0;
    // if (frameA >= 0b1000){//if check seems too slow, limits the frequency to ~26.5kHz
    //   frameA = 0;
    // }
    
}

ISR(TIMER1_COMPA_vect){
    frameA = 0;    
}




void loop() {
  // put your main code here, to run repeatedly:
    
}

The reason I am using two timers is that an if loop to reset the counter within the timer1 was causing everything to slow down and limiting the frequency to 26.5kHz, although I always had 50% duty cycle.

However, the issue I am having is while everything works fine up to 20kHz, when I go for 40kHz, the on and off pulses are no longer of the same width (i.e. although the frequency is technically 40kHz, I no longer have the required 50% duty cycle). Additionally, doing it this way seems to be really pushing the processor and only gives me access to 8 different phase increments. The way that I have done it doesn't seem efficient - the timer essetnially has to be operating X times faster than the signal desired, but I am not sure how else to do it.

The idea for the phase control will be to have more lines in the array which will not necessarily toggle the two gourps of pin exactly out of sync like they are currently, for example something like this (for waveNot):

{0b00001010, 0b00001010, 0b00001111, 0b00001111, 0b00000101, 0b00000101,0b00000000,0b00000000}

Is this just a hard limit of the nano's 16MHz processor? Should I get a faster microprocessor (the Due or the Portenta maybe?).
Or perhaps there is a much more efficient way of doing this.
Sorry if anything seems obviosuly wrong or simplistic, I have only been working with c++/Arduinos for a couple of weeks now!

I think you are pushing a nano
using an Arduino Due I can generate 40KHz squarewave


// Arduino Due timer interrupt
//  generate frequency square wave generator on pin 13

#include <DueTimer.h>

// here is the ISR executed by the CPU when the corresponding interrupt occurs
void timerInterrupt()
{
 digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));   // invert the signal
} 

void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(115200);
  Serial.println("\n\nSquarewave on pin");
  Serial.println(LED_BUILTIN);
  Serial.setTimeout(60*60*1000ul);
  // generate 1KHz square wave
  Timer1.attachInterrupt( timerInterrupt).start(500); delay(50);
  readFrequency();
}

// read frequency to generate
void readFrequency(void)
{
  Serial.print("frequency Hz ? ");
  long frequency = Serial.parseInt();
  Serial.println(frequency);
  long period= 1000000/(frequency*2);
  Serial.print("\nperiod MicroSeconds = ");
  Serial.println(period);
  while(Serial.available())Serial.read();  // flush input buffer
  Timer1.start(period);                    // setup timer period
}

// if serial input read next frequency
void loop() {
  if(Serial.available())
    readFrequency();
}

serial monitor output

Squarewave on pin
13
frequency Hz ? 40000

period MicroSeconds = 12
frequency Hz ? 100000

period MicroSeconds = 5

40KHz square wave
image

100KHz square wave
image

1 Like

have a look at direct-digital-synthesis
years ago I used a pair of DDS modules to generate a pair of 1MHz sine waves with a preise phase difference for a medical experiemnt
have a look at Arduino-Controlled-AD9833-Function-Generator
a pair of AD9833 modules may do what you require

2 Likes

Thank you for both of your replies, they are very helpful. It looks like I may be needing to get some new hardware then (either function generators or a Due!).

I was able to get a 40kHz signal from the Nano, but my limit was 56 kHz (just switching between high and low, not including the array). It's very promising that you were able to achieve 100kHz from the Due.

I know from the paper referenced above that phase control is possible for two signals, but I would need to control 12 signal independently. This may be a bit beyond this scope, but do you think this would be possible? I might need to somehow link multiple Arduinos.

I'm just adding the code included in the open source paper for reference, all credit goes to Asier Marzo and their coworkers. I don't really understand it but it looks like they experimented for a long time to get the number of clock cycles to wait for each step in order to produce the various signals:

#include <avr/sleep.h>
#include <avr/power.h>

#define N_PORTS 1
#define N_DIVS 24

#define WAIT_LOT(a) \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop")
#define WAIT_MID(a) \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop")
#define WAIT_LIT(a) \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop"); \
  __asm__ __volatile__("nop")


#define OUTPUT_WAVE(pointer, d) PORTC = pointer[d * N_PORTS + 0]

#define N_BUTTONS 6
//half a second
#define STEP_SIZE 1
#define BUTTON_SENS 2500
#define N_FRAMES 24

static byte frame = 0;
static byte animation[N_FRAMES][N_DIVS] = { { 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa },
                                            { 0x9, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa },
                                            { 0x9, 0x9, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa },
                                            { 0x9, 0x9, 0x9, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa },
                                            { 0x9, 0x9, 0x9, 0x9, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa },
                                            { 0x9, 0x9, 0x9, 0x9, 0x9, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa },
                                            { 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa },
                                            { 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x5, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0xa, 0xa, 0xa, 0xa, 0xa },
                                            { 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x5, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0xa, 0xa, 0xa, 0xa },
                                            { 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x5, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0xa, 0xa, 0xa },
                                            { 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x5, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0xa, 0xa },
                                            { 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x5, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0xa },
                                            { 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0xa, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0xa, 0xa, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x5, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x5, 0x5, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xa, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x5, 0x5, 0x5, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xa, 0xa, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x9, 0x9, 0x9, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0x6, 0x6, 0x6, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x9, 0x9, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0x6, 0x6, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x9, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0x6, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x9, 0x9, 0x9, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0x6, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x9, 0x9, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0x6, 0x6 },
                                            { 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x9, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0xa, 0x6 } };
//0x5 = 0101,  0xa = 1010, 0x9 = 1001, 0x6 = 0110


void setup() {

  /*
 for (int i = 0; i < (N_PORTS*N_DIVS); ++i){
    animation[frame][i] =  0;
  }

  for (int i = 0; i < (N_PORTS*N_DIVS/2); ++i){
     animation[frame][i] = 0b11111111;
  }
  
  for(int i = 0; i < N_DIVS; ++i){
    if (i % 2 == 0){
      animation[frame][i * N_PORTS] |= 0b00000001;
    }else{
      animation[frame][i * N_PORTS] &= 0b11111110;
    }
  }
*/
  DDRC = 0b00001111;  //A0 to A3 are the signal outputs
  PORTC = 0b00000000;

  pinMode(10, OUTPUT);        //pin 10 (B2) will generate a 40kHz signal to sync
  pinMode(11, INPUT_PULLUP);  //pin 11 (B3) is the sync in
  //please connect pin 10 to pin 11

  for (int i = 2; i < 8; ++i) {  //pin 2 to 7 (D2 to D7) are inputs for the buttons
    pinMode(i, INPUT_PULLUP);
  }

  // generate a sync signal of 40khz in pin 10
  noInterrupts();                                  // disable all interrupts
  TCCR1A = bit(WGM10) | bit(WGM11) | bit(COM1B1);  // fast PWM, clear OC1B on compare
  TCCR1B = bit(WGM12) | bit(WGM13) | bit(CS10);    // fast PWM, no prescaler
  OCR1A = (F_CPU / 40000L) - 1;
  OCR1B = (F_CPU / 40000L) / 2;
  interrupts();  // enable all interrupts

  // disable everything that we do not need
  ADCSRA = 0;  // ADC
  power_adc_disable();
  power_spi_disable();
  power_twi_disable();
  power_timer0_disable();
  //power_usart0_disable();
  Serial.begin(115200);

  byte* emittingPointer = &animation[frame][0];
  byte buttonsPort = 0;

  bool anyButtonPressed;
  bool buttonPressed[N_BUTTONS];
  short buttonCounter = 0;

LOOP:
  while (PINB & 0b00001000)
    ;  //wait for pin 11 (B3) to go low

  OUTPUT_WAVE(emittingPointer, 0);
  buttonsPort = PIND;
  WAIT_LIT();
  OUTPUT_WAVE(emittingPointer, 1);
  anyButtonPressed = (buttonsPort & 0b11111100) != 0b11111100;
  WAIT_MID();
  OUTPUT_WAVE(emittingPointer, 2);
  buttonPressed[0] = buttonsPort & 0b00000100;
  WAIT_MID();
  OUTPUT_WAVE(emittingPointer, 3);
  buttonPressed[1] = buttonsPort & 0b00001000;
  WAIT_MID();
  OUTPUT_WAVE(emittingPointer, 4);
  buttonPressed[2] = buttonsPort & 0b00010000;
  WAIT_MID();
  OUTPUT_WAVE(emittingPointer, 5);
  buttonPressed[3] = buttonsPort & 0b00100000;
  WAIT_MID();
  OUTPUT_WAVE(emittingPointer, 6);
  buttonPressed[4] = buttonsPort & 0b01000000;
  WAIT_MID();
  OUTPUT_WAVE(emittingPointer, 7);
  buttonPressed[5] = buttonsPort & 0b10000000;
  WAIT_MID();
  OUTPUT_WAVE(emittingPointer, 8);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 9);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 10);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 11);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 12);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 13);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 14);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 15);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 16);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 17);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 18);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 19);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 20);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 21);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 22);
  WAIT_LOT();
  OUTPUT_WAVE(emittingPointer, 23);


  if (anyButtonPressed) {
    ++buttonCounter;
    if (buttonCounter > BUTTON_SENS) {
      buttonCounter = 0;

      if (!buttonPressed[0]) {
        if (frame < STEP_SIZE) {
          frame = N_FRAMES - 1;
        } else {
          frame -= STEP_SIZE;
        }
      } else if (!buttonPressed[1]) {
        if (frame >= N_FRAMES - STEP_SIZE) {
          frame = 0;
        } else {
          frame += STEP_SIZE;
        }
      } else if (!buttonPressed[2]) {
        frame = 0;
      }
      emittingPointer = &animation[frame][0];
    }
  } else {
    buttonCounter = 0;
  }

  goto LOOP;
}

void loop() {}

Thank you already for all your help!

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.