1

Topic: Is RemoteXY touchy about it's refresh rate ?

Has anyone got any idea why this sketch only allows RemoteXY to communicate if void loop() is remarked out.

With it in the code I can't get RemoteXY to connect at all, I get Error Device not reply.

I can see that it is a timed execution loop, but I want RemoteXY to be able to connect to set filter parameters etc., I can disconnect to see the effect of the changes....

// Arduino Beat Detector By Damian Peckett 2015
// License: Public Domain.

//////////////////////////////////////////////
//        RemoteXY include library          //
//////////////////////////////////////////////

// RemoteXY select connection mode and include library 
#define REMOTEXY_MODE__HARDSERIAL

#include <RemoteXY.h>

// RemoteXY connection settings 
#define REMOTEXY_SERIAL Serial1
#define REMOTEXY_SERIAL_SPEED 9600


// RemoteXY configurate  
#pragma pack(push, 1)
uint8_t RemoteXY_CONF[] =
  { 255,3,0,11,0,74,1,8,24,0,
  130,1,24,1,53,7,29,4,160,33,
  13,60,5,118,26,4,160,33,33,60,
  5,1,26,4,160,33,23,60,5,204,
  26,129,0,26,2,50,5,14,68,105,
  103,105,116,97,108,32,66,101,97,116,
  32,68,101,116,101,99,116,111,114,0,
  129,0,3,24,15,3,49,66,97,115,
  115,70,105,108,116,101,114,0,129,0,
  30,24,4,3,31,50,46,48,0,129,
  0,61,24,4,3,31,50,46,53,0,
  129,0,91,24,4,3,31,51,46,48,
  0,129,0,3,34,21,3,49,66,101,
  97,116,84,104,114,101,115,104,111,108,
  100,0,129,0,30,34,4,3,31,48,
  46,48,0,129,0,91,34,6,3,31,
  50,48,46,48,0,129,0,60,34,6,
  3,31,49,48,46,48,0,129,0,3,
  14,15,3,49,65,68,67,99,101,110,
  116,114,101,86,111,108,116,115,0,129,
  0,30,14,2,3,31,49,46,53,0,
  129,0,61,14,4,3,31,50,46,53,
  0,129,0,91,14,4,3,31,51,46,
  53,0,129,0,3,47,7,3,3,66,
  80,77,0,67,6,13,46,12,4,204,
  16,4,66,129,35,48,56,4,36,6,
  129,0,32,53,5,3,16,49,48,48,
  0,129,0,88,53,5,3,16,49,56,
  48,0,129,0,60,53,5,3,16,49,
  52,48,0,129,0,46,53,5,3,16,
  49,50,48,0,129,0,74,53,5,3,
  16,49,54,48,0,129,0,3,55,7,
  3,3,66,101,97,116,115,0,67,6,
  13,54,12,4,204,16,6 };
  
// this structure defines all the variables of your control interface 
struct {

    // input variable
  int8_t ADC_Centre; // =-100..100 slider position 
  int8_t Beat_Threshold; // =-100..100 slider position 
  int8_t Bass_Filter; // =-100..100 slider position 

    // output variable
  char BPM_Text[4];  // string UTF8 end zero 
  int8_t BPM_Level; // =0..100 level position 
  char Beat_Count[6];  // string UTF8 end zero 

    // other variable
  uint8_t connect_flag;  // =1 if wire connected, else =0 

} RemoteXY;
#pragma pack(pop)

/////////////////////////////////////////////
//           END RemoteXY include          //
/////////////////////////////////////////////   

// Our Global Sample Rate, 5000hz
#define SAMPLEPERIODUS 200

// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

const byte LED = 13;

void setup() {

    RemoteXY_Init ();  
  
//    // Set ADC to 77khz, max for 10bit
//    sbi(ADCSRA,ADPS2);
//    cbi(ADCSRA,ADPS1);
//    cbi(ADCSRA,ADPS0);

    //The pin with the LED
    pinMode(LED, OUTPUT);
}

// 20 - 200hz Single Pole Bandpass IIR Filter
float bassFilter(float sample) {
    static float xv[3] = {0,0,0}, yv[3] = {0,0,0};
    xv[0] = xv[1]; xv[1] = xv[2]; 
    xv[2] = (sample) / 3.f; // change here to values close to 2, to adapt for stronger or weeker sources of line level audio  
    

    yv[0] = yv[1]; yv[1] = yv[2]; 
    yv[2] = (xv[2] - xv[0])
        + (-0.7960060012f * yv[0]) + (1.7903124146f * yv[1]);
    return yv[2];
}

// 10hz Single Pole Lowpass IIR Filter
float envelopeFilter(float sample) { //10hz low pass
    static float xv[2] = {0,0}, yv[2] = {0,0};
    xv[0] = xv[1]; 
    xv[1] = sample / 50.f;
    yv[0] = yv[1]; 
    yv[1] = (xv[0] + xv[1]) + (0.9875119299f * yv[0]);
    return yv[1];
}

// 1.7 - 3.0hz Single Pole Bandpass IIR Filter
float beatFilter(float sample) {
    static float xv[3] = {0,0,0}, yv[3] = {0,0,0};
    xv[0] = xv[1]; xv[1] = xv[2]; 
    xv[2] = sample / 2.7f;
    yv[0] = yv[1]; yv[1] = yv[2]; 
    yv[2] = (xv[2] - xv[0])
        + (-0.7169861741f * yv[0]) + (1.4453653501f * yv[1]);
    return yv[2];
}

void loop() {
    unsigned long time = micros(); // Used to track rate
    float sample, value, envelope, beat, thresh;
    unsigned char i;

    RemoteXY_Handler (); 

    for(i = 0;;++i){
        // Read ADC and center so +-512
        sample = (float)analogRead(0)-503.f;

        // Filter only bass component
        value = bassFilter(sample);

        // Take signal amplitude and filter
        if(value < 0)value=-value;
        envelope = envelopeFilter(value);

        // Every 200 samples (25hz) filter the envelope 
        if(i == 200) {
                // Filter out repeating bass sounds 100 - 180bpm
                beat = beatFilter(envelope);

                // Threshold it based on potentiometer on AN1
                thresh = 0.02f * (float)analogRead(1);

                // If we are above threshold, light up LED
                if(beat > thresh) digitalWrite(LED, HIGH);
                else digitalWrite(LED, LOW);

                //Reset sample counter
                i = 0;
        }

        // Consume excess clock cycles, to keep at 5000 hz
        for(unsigned long up = time+SAMPLEPERIODUS; time > 20 && time < up; time = micros());
    }  
}
2B, or not 2B, that is the pencil ...

2 (edited by Guillaume 2019-05-06 19:43:37)

Re: Is RemoteXY touchy about it's refresh rate ?

Hello Daba,

for(i = 0;;++i){

loop() is already an infinite loop, you don't need another one

3 (edited by Daba 2019-05-07 06:36:52)

Re: Is RemoteXY touchy about it's refresh rate ?

Guillaume wrote:

Hello Daba,

for(i = 0;;++i){

loop() is already an infinite loop, you don't need another one

He's using i to count the number of samples - near the end he updates the envelope and resets i. Then he "consumes excess clock cycles" to make the void loop() a fixed cycle time.  I cannot see why this would prevent RemoteXY from connecting.

EDIT : I have just spotted what is wrong - he NEVER comes out of that for() loop, EVER !, so your comment was spot-on, thanks Guillaume for the pointer....

// Every 200 samples (25hz) filter the envelope 
        if(i == 200) {
                // Filter out repeating bass sounds 100 - 180bpm
                beat = beatFilter(envelope);

                // Threshold it based on potentiometer on AN1
                thresh = 0.02f * (float)analogRead(1);

                // If we are above threshold, light up LED
                if(beat > thresh) digitalWrite(LED, HIGH);
                else digitalWrite(LED, LOW);

                //Reset sample counter
                i = 0;
        }

        // Consume excess clock cycles, to keep at 5000 hz
        for(unsigned long up = time+SAMPLEPERIODUS; time > 20 && time < up; time = micros());
2B, or not 2B, that is the pencil ...

4 (edited by Guillaume 2019-05-07 10:42:19)

Re: Is RemoteXY touchy about it's refresh rate ?

You could simplify it like this, it should produce the same result, except RemoteXY_Handler will now be called (but not sure if it will timeout or not).

void loop()
{
    unsigned long time = micros(); // Used to track rate
    float sample, value, envelope, beat, thresh;
    static unsigned char i;

    RemoteXY_Handler (); 

    // Read ADC and center so +-512
    sample = (float)analogRead(0)-503.f;

    // Filter only bass component
    value = bassFilter(sample);

    // Take signal amplitude and filter
    if(value < 0)value=-value;
    envelope = envelopeFilter(value);

    // Every 200 samples (25hz) filter the envelope 
    if(++i == 200) {
        // Filter out repeating bass sounds 100 - 180bpm
        beat = beatFilter(envelope);

        // Threshold it based on potentiometer on AN1
        thresh = 0.02f * (float)analogRead(1);

        // If we are above threshold, light up LED
        if(beat > thresh) digitalWrite(LED, HIGH);
        else digitalWrite(LED, LOW);

        //Reset sample counter
        i = 0;
    }

    // Consume excess clock cycles, to keep at 5000 hz
    for(unsigned long up = time+SAMPLEPERIODUS; time > 20 && time < up; time = micros());
}

This line is bad:

for(unsigned long up = time+SAMPLEPERIODUS; time > 20 && time < up; time = micros());

- Because it is like calling delay(), it will block the code, not much, but still, it's bad practice, unless your microcontroller have two or more cores
- And I suspect there will be a problem after about 71 minutes and 35 seconds, when micros() will overflow.

Instead of this, you should use a state machine. If you don't know what it is or how to make one, here is an example:

enum ntpState : uint8_t
{
    STOPPED,
    SEND,
    WAIT,
    PARSE,
    SYNC_RTC,
    DONE
};

ntpState ntpCurrentState = STOPPED;

// Call this function to start the ntp request and sync the RTC.
void ntpRequest()
{
    if ( ntpCurrentState == STOPPED && wifiConnected )
    {
        Serial.print( "[NTP] Starting request.\n" );
        ntpRequesting = true;
        ntpCurrentState = SEND;
    }
    else
    {
        Serial.print( "[NTP] Unable to start request.\n" );
    }
}

// Call this function constantly in loop()
void loop_ntp()
{
    if ( ntpCurrentState != STOPPED )
    {
        static uint8_t retryCounter = 0;
        static uint32_t startMillis = 0;
        static uint16_t syncDelay = 0;

        switch ( ntpCurrentState )
        {
            case SEND :
            {
                startMillis = currentMillis;
                memset( &packet, 0, sizeof( packet ) );
                packet.vn = 4;
                packet.mode = 3;
                WiFi.hostByName( ntpServerName, ntpServerIP );
                udp.flush();
                udp.beginPacket( ntpServerIP, 123 );
                udp.write( (uint8_t*)&packet, sizeof( packet ) );
                udp.endPacket();
                retryCounter = 0;
                #if defined(DEBUG_NTP)
                    Serial.print( "[NTP] Waiting server reply...\n" );
                #endif
                ntpCurrentState = WAIT;
                break;
            }

            case WAIT :
            {
                static uint32_t previousMillis = currentMillis;

                if ( currentMillis - previousMillis >= 20UL )
                {
                    if ( ++retryCounter < 250 )
                    {
                        previousMillis = currentMillis;
                        ntpCurrentState = PARSE;
                    }
                    else
                    {
                        Serial.print( "[NTP] Timeout.\n" );
                        retryCounter = 0;
                        ntpCurrentState = DONE;
                    }
                }
                break;
            }

            case PARSE :
            {
                switch ( udp.parsePacket() )
                {
                    case 0 :
                    {
                        ntpCurrentState = WAIT;
                        break;
                    }
                    case sizeof( packet ) :
                    {
                        udp.read( (uint8_t*)&packet, sizeof( packet ) );
                        packet.txTm_s = ntohl( packet.txTm_s );
                        packet.txTm_f = ntpFractionToMs( ntohl( packet.txTm_f ) );
                        syncDelay = 1000 - packet.txTm_f;
                        
                        #if defined(DEBUG_NTP)
                            Serial.printf( "[NTP] Received packet after %u ms. Parsing...\n", currentMillis - startMillis );
                            packet.refTm_s  = ntohl( packet.refTm_s );
                            packet.refTm_f  = ntpFractionToMs( ntohl( packet.refTm_f ) );
                            packet.origTm_s = ntohl( packet.origTm_s );
                            packet.origTm_f = ntpFractionToMs( ntohl( packet.origTm_f ) );
                            packet.rxTm_s   = ntohl( packet.rxTm_s );
                            packet.rxTm_f   = ntpFractionToMs( ntohl( packet.rxTm_f ) );
                            Serial.printf( "li             = %hu\n",    packet.li );
                            Serial.printf( "vn             = %hu\n",    packet.vn );
                            Serial.printf( "mode           = %hu\n",    packet.mode );
                            Serial.printf( "stratum        = %hu\n",    packet.stratum );
                            Serial.printf( "poll           = %hu\n",    packet.poll );
                            Serial.printf( "precision      = %hu\n",    packet.precision );
                            Serial.printf( "rootDelay      = %u\n",     packet.rootDelay );
                            Serial.printf( "rootDispersion = %u\n",     packet.rootDispersion );
                            Serial.printf( "refId          = 0x%08X\n", packet.refId );
                            Serial.printf( "refTm          = %u.%hu\n", packet.refTm_s,  packet.refTm_f  );
                            Serial.printf( "origTm         = %u.%hu\n", packet.origTm_s, packet.origTm_f );
                            Serial.printf( "rxTm           = %u.%hu\n", packet.rxTm_s,  packet.rxTm_f  );
                            Serial.printf( "txTm           = %u.%hu\n", packet.txTm_s, packet.txTm_f );
                            Serial.printf( "[NTP] Synchronizing RTC in %u ms...\n", syncDelay );
                        #endif

                        startMillis = currentMillis;
                        ntpCurrentState = SYNC_RTC;
                        break;
                    }
                    default :
                    {
                        Serial.print( "[NTP] Bad packet length.\n" );
                        udp.flush();
                        ntpCurrentState = DONE;
                        break;
                    }
                }
                break;
            }

            case SYNC_RTC :
            {
                if ( currentMillis - startMillis >= syncDelay )
                {
                    time_t unixTime = packet.txTm_s - 2208988800UL + 1;
                    rtc.set( unixTime );
                    portENTER_CRITICAL( &mux );
                    rtcInterrupted = true;
                    portEXIT_CRITICAL( &mux );
                    nowLocal = tz.toLocal( unixTime );
                    TimeElements tm;
                    breakTime( nowLocal, tm );
                    Serial.printf( "[NTP] RTC synchronized. Local time: %02d/%02d/%4d %02d:%02d:%02d\n",
                        tm.Day, tm.Month, tmYearToCalendar(tm.Year), tm.Hour, tm.Minute, tm.Second );
                    ntpCurrentState = DONE;
                }
                break;
            }

            case DONE :
            {
                ntpRequesting = false;
                ntpCurrentState = STOPPED;
                break;
            }

            default :
            {
                break;
            }
        }
    }
}

You can see at the end of the SEND state, I go to the WAIT state, where I check if some time has passed, before going to the next state, PARSE. In that next state I go back to the WAIT state if the ntp server didn't reply yet, and after a few seconds without reply, it will give up, or if reply is received, I go to the next state, SYNC, where I wait a certain amount of time, 1000 ms minus the fraction of second returned by the ntp server, before synchronizing my DS3231 rtc, so it is synchronized "exactly" at the next second.

I hope it helps..!

5

Re: Is RemoteXY touchy about it's refresh rate ?

I've got it working perfectly now...

The "consuming excess clock cycles" isn't interfering with RemoteXY handler at all.

I just put i++; instead of the for(i  loop.

void loop() {
    unsigned long time = micros(); // Used to track rate
    float sample, value, envelope, beat, thresh;

    RemoteXY_Handler ();

    Bass_Filter = map(RemoteXY.Bass_Filter, -100, 100, 2.0, 3.0); 
    ADC_Centre = map(RemoteXY.ADC_Centre, -100, 100, -300, 300);
    Beat_Threshold = map(RemoteXY.Beat_Threshold, -100, 100, 0.0, 1023.0);

    i++;
        // Read ADC and apply "centering"
        sample = (float)analogRead(0) + ADC_Centre;

        // Filter only bass component
        value = bassFilter(sample);

        // Take signal amplitude and filter
        if(value < 0)value=-value;
        envelope = envelopeFilter(value);

        // Every 200 samples (25hz) filter the envelope 
        if(i > 200) {
                // Filter out repeating bass sounds 100 - 180bpm
                beat = beatFilter(envelope);

                // Threshold comes from RemoteXY
                thresh = 0.4f * Beat_Threshold;

                // detect a beat
                if(beat > thresh) {
                  if(millis() > TriggerTime + 250) {
                    digitalWrite(LED, HIGH);
                    BeatCount++;

                    // Calculate the BPM
                    // shunt the array entries down in the array, losing the oldest
                    Beats[3] = Beats[2] ;
                    Beats[2] = Beats[1] ;
                    Beats[1] = Beats[0] ;
                    // add the newest beat time at the top
                    TriggerTime = millis();
                    Beats[0] = TriggerTime ;
                    BPM =  (60000 / ((Beats[0] - Beats[3]) / 3 ));
                    RemoteXY.BPM_Level = map(BPM, 60, 180, 0, 100);
                    AccentCount++;
                    // add code here to do half, third, and quarter beats
                    sendDMX();
                  } //if(millis() > TriggerTime
                } //if(beat > thresh)
                else digitalWrite(LED, LOW);


                //Reset sample counter
                i = 0;
        } // if(i>200)

        // Consume excess clock cycles, to keep at 5000 hz
        for(unsigned long up = time+SAMPLEPERIODUS; time > 20 && time < up; time = micros());
} // void loop()

One thing I always struggle with (my mind goes into overdrive), is the conversion of numeric variables to the RemoteXY char[] arrays. What's the simplest way to do this ?

2B, or not 2B, that is the pencil ...

6

Re: Is RemoteXY touchy about it's refresh rate ?

itoa, dtostrf, sprintf.. It depends what you want to do exactly

7

Re: Is RemoteXY touchy about it's refresh rate ?

Guillaume wrote:

itoa, dtostrf, sprintf.. It depends what you want to do exactly

I just want to display a numeric variable on a RemoteXY screen,

I've got it working...

In RemoteXY's header, the auto-generated declaration...

    // output variable
  char BPM_Text[4];  // string UTF8 end zero 

In my declarations...

String BPM_Text = "0";

In my loop() code...

BPM_Text = String(BPM);
BPM_Text.toCharArray(RemoteXY.BPM_Text, sizeof(RemoteXY.BPM_Text));

It all looks clunky to me, surely there's a better way ?

P.S. I don't have an issue with naming members of structure variables the same as unique variables, I'm used to it from the PLC world.  "BPM_Text" is distinct from "RemoteXY.BPM_Text", two different animals.

2B, or not 2B, that is the pencil ...

8 (edited by Guillaume 2019-05-09 16:47:15)

Re: Is RemoteXY touchy about it's refresh rate ?

itoa ( int value, char * str, int base );

value is your numerical value, str is a char array where to store the conversion, base is the numerical base of the conversion (2 for binary, 10 for decimal, 16 for hexadecimal...)

itoa( BPM, RemoteXY.BPM_Text, 10 );

Or using sprintf

sprintf( RemoteXY.BPM_Text, "%d", BPM );

sprintf is very powerful and so is very heavy, it will use several kb of memory, if you don't have much free memory and plan to use sprintf just once, it isn't worth, either use it a lot or don't use it at all. itoa is lightweight, but very limited.

9

Re: Is RemoteXY touchy about it's refresh rate ?

 itoa( BPM, RemoteXY.BPM_Text, sizeof(RemoteXY.BPM_Text));

... is what I ended up using.

itoa is much less confusing than toCharArray(), but why isn't it in the Arduino Reference documentation ?

Anyway, my project is complete, and works far better than I had hoped for. So I am glad I went down the digital route, and not persevered with analog low-pass filtering, envelope filtering, and beat detection.

The application is to replace the rather poor "music-to-light" chase stepper in my "cheap" far-eastern DMX stage lighting controller. The circuit they have on-board is just a dual op-amp, and there's no overall gain or sensitivity control. Using that feature, the chases step through far too quickly, not in time to the beat of the music, and gives a stroboscopic effect - very poor.

I've put a nasty video on dropbox showing my Teensy implementation, unfortunately there's no sound for some reason, bur you can see it is being driven by an online metronome. The pulses you see on the oscilloscope are the triggers to the DMX chip, replicating what the second op-amp did on the original circuit. My "BPM" comes out very close to the metronome's BPM, You can also see that I have added half-beat. every third, fourth etc., up to every 8th beat, something the original circuitry couldn't do.

Link to the video ...
https://www.dropbox.com/s/r3ot7lctm7wh5 … 6.mov?dl=0

2B, or not 2B, that is the pencil ...

10

Re: Is RemoteXY touchy about it's refresh rate ?

Guillaume, I tried your itoa suggestion but it di not work...

This works...

                // detect a beat
                if(beat > thresh) {
                  if(millis() > TriggerTime + 250) {
                    digitalWrite(LED, HIGH);
                    BeatCount++;

                    // Calculate the BPM
                    // shunt the array entries down in the array, losing the oldest
                    Beats[3] = Beats[2] ;
                    Beats[2] = Beats[1] ;
                    Beats[1] = Beats[0] ;
                    // add the newest beat time at the top
                    TriggerTime = millis();
                    Beats[0] = TriggerTime ;
                    BPM =  (60000 / ((Beats[0] - Beats[3]) / 3 ));
                    RemoteXY.BPM_Level = map(BPM, 60, 180, 0, 100);
//                    itoa( BPM, RemoteXY.BPM_Text, sizeof(RemoteXY.BPM_Text));
//                    itoa( BeatCount, RemoteXY.Beat_Count, sizeof(RemoteXY.Beat_Count));
                    BPM_Text = String(BPM);
                    BeatCountText = String(BeatCount);
                    BPM_Text.toCharArray(RemoteXY.BPM_Text, sizeof(RemoteXY.BPM_Text));
                    BeatCountText.toCharArray(RemoteXY.Beat_Count, sizeof(RemoteXY.Beat_Count));
                    sendDMX();
                  } //if(millis() > TriggerTime
                } //if(beat > thresh)
                else digitalWrite(LED, LOW);

This doesn't ...

                // detect a beat
                if(beat > thresh) {
                  if(millis() > TriggerTime + 250) {
                    digitalWrite(LED, HIGH);
                    BeatCount++;

                    // Calculate the BPM
                    // shunt the array entries down in the array, losing the oldest
                    Beats[3] = Beats[2] ;
                    Beats[2] = Beats[1] ;
                    Beats[1] = Beats[0] ;
                    // add the newest beat time at the top
                    TriggerTime = millis();
                    Beats[0] = TriggerTime ;
                    BPM =  (60000 / ((Beats[0] - Beats[3]) / 3 ));
                    RemoteXY.BPM_Level = map(BPM, 60, 180, 0, 100);
                    itoa( BPM, RemoteXY.BPM_Text, sizeof(RemoteXY.BPM_Text));
                    itoa( BeatCount, RemoteXY.Beat_Count, sizeof(RemoteXY.Beat_Count));
//                    BPM_Text = String(BPM);
//                    BeatCountText = String(BeatCount);
//                    BPM_Text.toCharArray(RemoteXY.BPM_Text, sizeof(RemoteXY.BPM_Text));
//                    BeatCountText.toCharArray(RemoteXY.Beat_Count, sizeof(RemoteXY.Beat_Count));
                    sendDMX();
                  } //if(millis() > TriggerTime
                } //if(beat > thresh)
                else digitalWrite(LED, LOW);

There's no reference on itoa() to help me ....

2B, or not 2B, that is the pencil ...

11

Re: Is RemoteXY touchy about it's refresh rate ?

Ok, I have found that itoa() goes not work with unsigned ints...

I will leave as is with toCharArray(), "if it ain't broke, don't fix it"

2B, or not 2B, that is the pencil ...

12 (edited by Guillaume 2019-05-10 08:48:10)

Re: Is RemoteXY touchy about it's refresh rate ?

itoa( BPM, RemoteXY.BPM_Text, sizeof(RemoteXY.BPM_Text));

Third parameter is not the size of the string, as I said in previous post this is the numerical base (or "radix") that will be represented when doing the conversion to a string. Use 10.


First result in google for "itoa" : http://www.cplusplus.com/reference/cstdlib/itoa/