Crazy Code

Hi! I am a fairly experienced arduino user, and recently I found this guide for making an acoustic levitator. Well, he supplied this code to drive the system. The transducers are driven by a motor controller (Dual) annd the motor controller is driven by the arduino. Well this code is extremley confusing. The only thing I can glean from it is that it is way overcomplicated, to the point where he created his own loop function and clock. I mainly am curious how he creates the frequency output from a bunch if I2C adresses. I would like to be able to change the frequency in a more simple way than using tons of lines of seemingly random I2C adresses… Here’s the code:

#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}};


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(){}

What do you mean by I2C addresses? Maybe I am being dense but I cannot find any.

vaj4088: What do you mean by I2C addresses? Maybe I am being dense but I cannot find any.

The variable called "Animation" With 0x5, 0x6, etc.

Those aren't addresses. They're just numbers written in hexadecimal notation. You could replace all the ones that are 0x and a digit with just that digit and replace the 0xa with 10 and it would be the same thing.

Delta_G: Those aren't addresses. They're just numbers written in hexadecimal notation. You could replace all the ones that are 0x and a digit with just that digit and replace the 0xa with 10 and it would be the same thing.

Alright, that makes more sense... But still, how would I be able to get the frequency from this series of Hexadecimal numbers?

goto LOOP;

Well it's pretty obvious that the person who wrote this code isn't really someone you want to follow on with.

I don't know where this OUTPUT_WAVE function is defined but it's not in the code you posted. That's the bit that is using those numbers so if you want to know anything about what those numbers are doing look there.

OUTPUT_WAVE is a macro that is defined in the supplied code:

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

Unfortunately, I will have to read the article and decode the wiring to figure out what is connected to PORTC where. Not going to happen.

I am unclear on why the " + 0" is present. Anyone care to explain?

OK, not sure how I missed that MACRO definition. It looks like the numbers are representing bit patterns that will be written out to some output pins.

vaj4088: I am unclear on why the " + 0" is present. Anyone care to explain?

It's probably there for the same reason the looping is handled with a goto in setup.

Delta_G: OK, not sure how I missed that MACRO definition. It looks like the numbers are representing bit patterns that will be written out to some output pins.

It's probably there for the same reason the looping is handled with a goto in setup.

So thank you, while this is helping me understand this code better, I think I might be asking the wrong question. I just need a way to vary the output frequency, whether it is in the code or with a potentiometer...

This code was probably developed by looking at the compiler output and tweaking the source until the compiler did what the author wanted - it looks carefully optimized for speed and timing. I suspect it requires specific compiler optimization setting to generate the intended code with all the timings right to the exact clock cycle.

bob8898: So thank you, while this is helping me understand this code better, I think I might be asking the wrong question. I just need a way to vary the output frequency, whether it is in the code or with a potentiometer...

I don't think there is an easy way to adjust the frequency of the generated signal. It seems to be based off of a 40khz clock - that's easy to change - but the extensive use of NOPs to burn CPU cycles suggests that the author has spent some time fine tuning the code to generate the appropriate signals to drive the transducers. I reckon you would probably have to fine tune for any adjustment in base frequency.

And BTW, the reason the code uses its own loop instead of just relying on the loop() function is because at the end of the loop() function an implicit check for serial events is done. This could influence the timing and break something, so the author avoided it.

arduarn: I don't think there is an easy way to adjust the frequency of the generated signal. It seems to be based off of a 40khz clock - that's easy to change - but the extensive use of NOPs to burn CPU cycles suggests that the author has spent some time fine tuning the code to generate the appropriate signals to drive the transducers. I reckon you would probably have to fine tune for any adjustment in base frequency.

And BTW, the reason the code uses its own loop instead of just relying on the loop() function is because at the end of the loop() function an implicit check for serial events is done. This could influence the timing and break something, so the author avoided it.

Alright, thanks... Thats what I originally thought. I might have to end up contacting the author of the code to figure this whole situation out, considering my science fair project revolves around this acoustic levitator.

Alright, so i contacted the author of the code and he replied with this section of code, (he also showed me where the frequency can be changed in it) but it is not complete and I don't know what else is needed...

#include

#include

//byte pattern = 0b00000000; //all the ports will output the same signal

byte pattern = 0b10101010; //consecutive ports will have an opposite signal

void setup()

{

DDRC = 0b11111111; //set pins A0 to A7 as outputs

PORTC = 0b00000000; //output low signal in all of them

// initialize timer1

noInterrupts(); // disable all interrupts

TCCR1A = 0;

TCCR1B = 0;

TCNT1 = 0;

//320 for 25kHz, 199 for 40kHz

OCR1A = 199; // compare match register 16MHz / 200 = 80kHz -> 40kHz square

TCCR1B |= (1

So I now have the complete code, but how the frequency is calculated is slightly confusing. 199 is 40khz and 320 is 25khz. Looking at both codes, how would i go about calculating custom frequencies?

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

//byte pattern = 0b00000000; //all the ports will output the same signal
byte pattern = 0b10101010; //consecutive ports will have an opposite signal

void setup()
{

 DDRC = 0b11111111; //set pins A0 to A7 as outputs
 PORTC = 0b00000000; //output low signal in all of them
 
  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;

//320 for 25kHz, 199 for 40kHz

  OCR1A = 199;            // compare match register 16MHz / 200 = 80kHz -> 40kHz square
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS10);    // 1 prescaler, no prescaling
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts

  // disable ADC
  ADCSRA = 0;  
  
  // turn off everything we can
  power_adc_disable ();
  power_spi_disable();
  power_twi_disable();
  power_timer0_disable();
  power_usart0_disable();

  while(true); //avoid entering into the loop
}

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  PORTC = pattern; //output pattern
  pattern = ~pattern; //invert all the bits
}

void loop(){
  
}

bob8898:
So I now have the complete code, but how the frequency is calculated is slightly confusing. 199 is 40khz and 320 is 25khz. Looking at both codes, how would i go about calculating custom frequencies?

This new sample sketch the kind author has provided is slightly differently set up to the first sketch you posted.
The first one uses the counter in Fast PWM mode to flip the pin directly and no interrupt. The new one has the counter set up in CTC mode (Clear Timer on Compare Match) and uses an interrupt to flip the output pins.
The calculations are similar.

All the details on this stuff are in the ATmega328 datasheet.

In CTC mode, each time the counter reaches the value of the OCR1A output compare register, it triggers the interrupt which flips the pins. It will take two flips to make one cycle. You can find the frequency calculation on datasheet page 161 (20.12.2. Clear Timer on Compare Match (CTC) Mode).
The Nano runs at 16MHz and you want 40kHz output frequency and you are not using the prescaler:
OCR1A = 16000000 / 40000 / 2 / 1 - 1 = 199
And likewise, for 25kHz:
OCR1A = 16000000/ 25000 / 2 / 1 -1 = 319

In the PWM mode used in the first sketch, it uses both output compare registers OCR1A and OCR1B to generate a cycle. The OCR1A is being used as the TOP value as described in the datasheet, it represents the duration of a whole cycle.
The OCR1B is being used as the pulse trigger in the PWM waveform.
So, loosely speaking: TCNT1 starts counting from 0, when it reaches the value of OCR1B it flips the pin, when it reaches the value of OCR1A it flips the pin back again.
On datasheet page 164 (20.12.3. Fast PWM Mode) you can see the formula for the Fast PWM mode. It represents the line in the original sketch OCR1A = (F_CPU / 40000L) - 1;. The other line OCR1B = (F_CPU / 40000L) / 2; is basically half of OCR1A to generate a 50% duty cycle.
You should be able to calculate the frequencies yourself.

All information without guarantee. That is just my understanding of it.

Edit: Nick Gammon has a nice page on this stuff too, worth a read.

arduarn: This new sample sketch the kind author has provided is slightly differently set up to the first sketch you posted. The first one uses the counter in Fast PWM mode to flip the pin directly and no interrupt. The new one has the counter set up in CTC mode (Clear Timer on Compare Match) and uses an interrupt to flip the output pins. The calculations are similar.

All the details on this stuff are in the ATmega328 datasheet.

In CTC mode, each time the counter reaches the value of the OCR1A output compare register, it triggers the interrupt which flips the pins. It will take two flips to make one cycle. You can find the frequency calculation on datasheet page 161 (20.12.2. Clear Timer on Compare Match (CTC) Mode). The Nano runs at 16MHz and you want 40kHz output frequency and you are not using the prescaler: OCR1A = 16000000 / 40000 / 2 / 1 - 1 = 199 And likewise, for 25kHz: OCR1A = 16000000/ 25000 / 2 / 1 -1 = 319

In the PWM mode used in the first sketch, it uses both output compare registers OCR1A and OCR1B to generate a cycle. The OCR1A is being used as the TOP value as described in the datasheet, it represents the duration of a whole cycle. The OCR1B is being used as the pulse trigger in the PWM waveform. So, loosely speaking: TCNT1 starts counting from 0, when it reaches the value of OCR1B it flips the pin, when it reaches the value of OCR1A it flips the pin back again. On datasheet page 164 (20.12.3. Fast PWM Mode) you can see the formula for the Fast PWM mode. It represents the line in the original sketch OCR1A = (F_CPU / 40000L) - 1;. The other line OCR1B = (F_CPU / 40000L) / 2; is basically half of OCR1A to generate a 50% duty cycle. You should be able to calculate the frequencies yourself.

All information without guarantee. That is just my understanding of it.

Edit: Nick Gammon has a nice page on this stuff too, worth a read.

Thank you so much for your help!