hi - I'm running some C++ code I wrote for the Bela to send 8 of its ADC input readings over serial/usb to my Mac (Pure Data). I don't need super high sample rate so the code averages each of the 16 values (per buffer) and then transmits that value. The numbers come into Pd as ASCII numbers so I use ',' and '#' to separate voltage values from the ADC# that they're coming from. The values are coming into PD fine but I wanted to ask about the Bela CPU usage. The IDE reports that while running this task the Bela is at 80%-85% CPU usage. This seems high for a simple process like this? Because my C++ coding ability is very low, I'm wondering if I've somehow written this code poorly for the Bela to function optimally? Thanks in advance

#include <Bela.h>
#include <libraries/Pipe/Pipe.h>
#include <libraries/Serial/Serial.h>
#include <cmath>
#include <iostream>
#include <string>

Serial gSerial;
Pipe gPipe;
float input;
float array[8];
int writevalue = 0;
float analogsum = 0;
float analogavg = 0;
AuxiliaryTask serialCommsTask;

void serialIo(void* arg) {
	while(!Bela_stopRequested())
	{
    for(unsigned int ars = 0; ars < 8; ars++) {
	gSerial.write((std::to_string(array[ars]).c_str())); //send the cv
	gSerial.write(","); //44
    gSerial.write(std::to_string(ars).c_str()); //send the adc #
    gSerial.write("*"); //42
   }
}
}

bool setup(BelaContext *context, void *userData) {
	gSerial.setup ("/dev/ttyGS0", 115200);
	AuxiliaryTask serialCommsTask = Bela_createAuxiliaryTask(serialIo, 0, "serial-thread", NULL);
	Bela_scheduleAuxiliaryTask(serialCommsTask);

	gPipe.setup("serialpipe", 1024);

	return true;
}

void render(BelaContext *context, void *userData)
{
//READ FRAME BY FRAME OF THE BUFFER
	for(unsigned int ar = 0; ar < 8; ar++) {
	analogsum = 0;
	for(unsigned int n = 0; n < context->audioFrames; n++) {
    input = (analogRead(context, n, ar));
    analogsum = input + analogsum;
    }
    analogavg = analogsum/16;
    array[ar] = analogavg;
    }
    //Bela_scheduleAuxiliaryTask(serialCommsTask);
}

void cleanup(BelaContext *context, void *userData) {}

you need to sleep between iterations of the while loop in serialIo(). Otherwise you are by definition using up as much CPU as there is available. Add usleep(10000) at the end of the loop to sleep 10ms between the data you send.

Btw, you may want to the example Pd patch PureData/midi-cv-midi which does something similar but in a better way:

  • sending MIDI over the USB connection instead of serial
  • sending only on change instead of continuously
  • it's bidirectional so yo can send MIDI to Pd and get it translated to voltage outputs
  • it's written in Pd so you can more easily modify to meet your needs.

    very cool - thank you! will try this out

    24 days later

    giuliomoro So I was finally able to try this out (usleep). Thank you again.

    I don't think it's exactly what I was hoping for since it slows down the sample rate of the signal read from the ADC. Originally I was getting about 600 Hz for each of the 8 ADCs. I was hoping to maintain that rate (but posted here bc also had been concerned about the CPU usage).

    I checked out the midi-cv-midi patch. I don't think it's for me since I want to keep the bit resolution of the Bela ADC (rather than covert to MIDI 7 bit). I'm considering implementing the change object in my C++ code but, since I'm sampling physical data from an acoustic instrument, change will likely be the norm rather than the exception, so I might tread with caution so I don't inadvertently cause the CPU to do extra work.

    You said that the patch uses the "USB connection instead of serial." Could you explain? Is this more efficient? Is there something I can do to my C++ code to do something similar?

    Thank you!

      violapwr ou said that the patch uses the "USB connection instead of serial."

      I meant that over the same USB connection, it sends MIDI instead of serial. The advantage of that is that it can be more easily integrated with Pd instead of having to parse the data back from serial. It can also be more compact, reducing the overall bandwidth required, which in principle allows for higher sampling rate. FWIW, you could send binary data over the virtual network and use [netreceive -u -b] in Pd. That would probably be lighter on the CPU?

      #include <libraries/UdpClient/UdpClient.h>
      
      UdpClient udpClient(4567, "192.168.7.1");
      float array[8];
      
      void ioLoop(void* arg) {
      	while(!Bela_stopRequested())
      	{
      		udpClient.send(array, sizeof(array));
      		usleep(1000); // or whatever
      	}
      }
      ...

      The receiving Pd side would be:

      [netreceive -u -b 4567]
      |
      [print]
        5 days later

        giuliomoro Thanks again for this. I'm having trouble getting it working. To reconfirm, this should work to send data from the Bela to Pure Data on a Mac via USB connection?

        This is the code I've implemented based on your message:

        #include <Bela.h>
        #include <libraries/Pipe/Pipe.h>
        #include <libraries/Serial/Serial.h>
        #include <cmath>
        #include <iostream>
        #include <string>
        #include <libraries/UdpClient/UdpClient.h>
        
        
        float input; //from Bela->context
        int array[8]; //eight ADC averages
        float analogsum = 0;
        UdpClient udpClient(4567, "192.168.7.1");
        /*
        Serial gSerial;
        Pipe gPipe;
        AuxiliaryTask serialCommsTask;
        
        
        void serialIo(void* arg) {
        	while(!Bela_stopRequested())
        	{
            for(unsigned int ars = 0; ars < 8; ars++) {
        	gSerial.write((std::to_string(array[ars]).c_str())); //send the cv
        	gSerial.write(","); //44
            gSerial.write(std::to_string(ars).c_str()); //send the adc #
            gSerial.write("*"); //42
            usleep(5);
           }
        }
        }
        */
        
        
        void ioLoop(void* arg) {
        	while(!Bela_stopRequested())
        	{
        		udpClient.send(array, sizeof(array));
        		usleep(1000); // or whatever
        	}
        }
        
        bool setup(BelaContext *context, void *userData) {
        	/*
        	gSerial.setup ("/dev/ttyGS0", 230400);
        	AuxiliaryTask serialCommsTask = Bela_createAuxiliaryTask(serialIo, 0, "serial-thread", NULL);
        	Bela_scheduleAuxiliaryTask(serialCommsTask);
        	gPipe.setup("serialpipe", 1024);
        */
        	return true;
        }
        
        void render(BelaContext *context, void *userData)
        {
        //READ FRAME BY FRAME OF THE BUFFER
        	for(unsigned int ar = 0; ar < 8; ar++) {
        	analogsum = 0; //reset ADC adder
        	
        	for(unsigned int n = 0; n < context->audioFrames; n++) {
            input = (analogRead(context, n, ar)); //read from ADC/context
            analogsum = input + analogsum; //sum
            }
            
            array[ar] = trunc((analogsum*100000)/16); //convert to int + average of 16 samples
            }
        }
        
        void cleanup(BelaContext *context, void *userData) {}

        And I've attached the PD side....
        alt text

        Please let me know any thoughts. Thank you again!

        The ioLoop() function is not being called. You need to start it via the AuxiliaryTask API similarly to what you were doing earlier for serialIo

          giuliomoro Thank you for the clarification.

          It sent something, this is the Pd console over 10ms:

          print: 197 82 0 0 203 82 0 0 84 82 0 0 117 82 0 0 216 48 0 0 173 41 0 0 107 26 0 0 154 24 0 0
          print: 198 82 0 0 204 82 0 0 90 82 0 0 122 82 0 0 225 48 0 0 178 41 0 0 119 26 0 0 160 24 0 0
          print: 198 82 0 0 204 82 0 0 96 82 0 0 122 82 0 0 228 48 0 0 176 41 0 0 107 26 0 0 157 24 0 0
          print: 197 82 0 0 203 82 0 0 90 82 0 0 124 82 0 0 218 48 0 0 171 41 0 0 112 26 0 0 158 24 0 0
          print: 199 82 0 0 204 82 0 0 91 82 0 0 118 82 0 0 219 48 0 0 170 41 0 0 110 26 0 0 151 24 0 0
          print: 203 82 0 0 206 82 0 0 90 82 0 0 115 82 0 0 223 48 0 0 174 41 0 0 117 26 0 0 151 24 0 0
          print: 199 82 0 0 205 82 0 0 93 82 0 0 114 82 0 0 235 48 0 0 186 41 0 0 125 26 0 0 170 24 0 0
          print: 196 82 0 0 202 82 0 0 97 82 0 0 113 82 0 0 226 48 0 0 178 41 0 0 118 26 0 0 163 24 0 0
          print: 198 82 0 0 201 82 0 0 92 82 0 0 120 82 0 0 233 48 0 0 185 41 0 0 124 26 0 0 151 24 0 0

          I'm not quite sure what the values mean. But I can make out 8 'sections' of each print line which I imagine might correspond to the 8 values in the array? Would you recommend any specific documentation for udpClient.send?

          Thanks again!

          Yeah those are individual bytes that come in. They are the floating point representation of your values .... While you could parse those, maybe it's best to store the values as integers as you were doing before and those are somewhat easier to parse: multiply each byte in each group of four by 1 (20 ), 256 (28 ), 65536 (216 ), and 16777216 (224 ) and sum the results together. Or make it simpler, use a 16-bit integer in C++ (using the type uint16_t) and then it's only two bytes per value, to be multiplied by 1 and 256 and summed together. Or send them as a string, separating values with a space and appending a semicolon and then remove the -b from [netreceive] and in Pd they should come in as a list ready to use.

            giuliomoro wow thx! it works great! upwards of 18Khz per adc at a lower CPU usage 🙂

            Since I have a workable sample rate, I'm now trying to send each of the 16 samples of the buffer (rather than average them into 1 value as before). The issue is that every other sample in the buffer seems to be empty.

            This is my PD output:

            print: 20 165 0 0 246 164 0 0 11 165 0 0 250 164 0 0 5 165 0 0 19 165 0 0 3 165 0 0 16 165 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 209 164 0 0 212 164 0 0 198 164 0 0 217 164 0 0 221 164 0 0 232 164 0 0 205 164 0 0 223 164 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 213 163 0 0 197 163 0 0 197 163 0 0 206 163 0 0 221 163 0 0 201 163 0 0 219 163 0 0 227 163 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 119 164 0 0 92 164 0 0 53 164 0 0 67 164 0 0 92 164 0 0 99 164 0 0 122 164 0 0 147 164 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 194 36 0 0 233 36 0 0 17 37 0 0 67 37 0 0 44 37 0 0 122 37 0 0 217 37 0 0 31 38 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 28 0 0 148 28 0 0 206 28 0 0 8 29 0 0 40 29 0 0 54 29 0 0 144 29 0 0 187 29 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 96 9 0 0 174 9 0 0 213 9 0 0 47 10 0 0 133 10 0 0 191 10 0 0 142 10 0 0 239 10 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 67 4 0 0 189 4 0 0 164 4 0 0 45 5 0 0 126 5 0 0 181 5 0 0 207 5 0 0 221 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

            and this is my C++ code:

            #include <Bela.h>
            #include <libraries/Pipe/Pipe.h>
            #include <libraries/Serial/Serial.h>
            #include <cmath>
            #include <iostream>
            #include <string>
            #include <libraries/UdpClient/UdpClient.h>
            
            
            float input; //from Bela->context
            int array[8][16]; //eight ADC x 16 samples
            UdpClient udpClient(4567, "192.168.7.1");
            Serial gSerial;
            Pipe gPipe;
            AuxiliaryTask serialCommsTask;
            
            
            
            void ioLoop(void* arg) {
            	while(!Bela_stopRequested())
            	{
            		udpClient.send(array, sizeof(array));
            		//usleep(1000);
            	}
            }
            
            bool setup(BelaContext *context, void *userData) {
            	gSerial.setup ("/dev/ttyGS0", 230400);
            	AuxiliaryTask serialCommsTask = Bela_createAuxiliaryTask(ioLoop, 0, "serial-thread", NULL);
            	Bela_scheduleAuxiliaryTask(serialCommsTask);
            	gPipe.setup("serialpipe", 1024);
            	return true;
            }
            
            void render(BelaContext *context, void *userData)
            {
            //READ FRAME BY FRAME OF THE BUFFER
            	for(unsigned int n = 0; n < context->audioFrames; n++) {
            	for(unsigned int ar = 0; ar < 8; ar++) {
            	input = analogRead(context, n, ar); //read from ADC/context
            	array[ar][n] = trunc(input*100000); //convert to int
                }
                }
            }
            
            void cleanup(BelaContext *context, void *userData) {}

            any ideas? thanks again 🙂

            as you are still using int, which is a 4-byte integer, that would mean that the top two bytes of each element are set to 0, i.e.: your values are all below 65536. If you were to use uint16_t instead of int, you'd get 2 bytes per element, so half the bandwidth and no zeros.

              5 days later

              giuliomoro Just was able to try this - thank you!

              The uint16_t does reduce the length of the message which is very helpful. That said, it seems that samples # 8-15 for each of the analog input's blocks are zeros. I've included 1 message from the Pd console below as well as the code. Any ideas?

              Thanks again!

              186 107 191 107 163 107 171 107 167 107 151 107 165 107 176 107 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 106 108 92 108 84 108 94 108 74 108 94 108 102 108 93 108 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 172 107 149 107 173 107 178 107 197 107 195 107 190 107 193 107 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 40 108 20 108 240 107 245 107 0 108 233 107 224 107 246 107 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 120 0 113 0 128 0 125 0 130 0 117 0 121 0 120 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 152 0 172 0 162 0 170 0 150 0 181 0 161 0 162 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 162 0 145 0 143 0 150 0 158 0 147 0 149 0 142 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 143 0 166 0 148 0 166 0 140 0 146 0 157 0 158 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
              #include <Bela.h>
              #include <libraries/Pipe/Pipe.h>
              #include <libraries/Serial/Serial.h>
              #include <cmath>
              #include <iostream>
              #include <string>
              #include <libraries/UdpClient/UdpClient.h>
              
              
              float input; //from Bela->context
              uint16_t array[8][16]; //eight ADC x 16 samples
              UdpClient udpClient(4567, "192.168.7.1");
              Serial gSerial;
              Pipe gPipe;
              AuxiliaryTask serialCommsTask;
              
              
              
              void ioLoop(void* arg) {
              	while(!Bela_stopRequested())
              	{
              		udpClient.send(array, sizeof(array));
              	//	usleep(100);
              	}
              }
              
              bool setup(BelaContext *context, void *userData) {
              	gSerial.setup ("/dev/ttyGS0", 230400);
              	AuxiliaryTask serialCommsTask = Bela_createAuxiliaryTask(ioLoop, 0, "serial-thread", NULL);
              	Bela_scheduleAuxiliaryTask(serialCommsTask);
              	gPipe.setup("serialpipe", 1024);
              	return true;
              }
              
              void render(BelaContext *context, void *userData)
              {
              //READ FRAME BY FRAME OF THE BUFFER
              	for(unsigned int n = 0; n < context->audioFrames; n++) {
              	for(unsigned int ar = 0; ar < 8; ar++) {
              	input = analogRead(context, n, ar); //read from ADC/context
              	array[ar][n] = trunc(input*65535); //convert from float to int
                  }
                  }
              }
              
              void cleanup(BelaContext *context, void *userData) {}

                violapwr for(unsigned int n = 0; n < context->audioFrames;

                You are going through n audioFrames but then reading analog inputs. As you have 8 analog inputs, analogSamplingRate is half of audioSamplingRate and analogFrames are half of of the audioFrames. This means that - with the default blocksize of 16 audio frames per block - you only have 8 analog frames.

                Change the size of your array to [8][8] and use context->analogFrames instead of context->audioFrames.

                  giuliomoro ahh yes that definitely makes sense. this is working well now - thank you!

                  There's one last thing I'm seeking to do/fix: create a consistent transmission rate from the Bela to PD on the computer. I'm measuring a range of 15000-18000 blocks/second in Pd -- I imagine the theoretical rate should be 2756.25 blocks/second since we have 8 samples/block and 22050 samples/second. I'm guessing here, but it seems that even though render() is called at period intervals perhaps the void ioLoop is making additional cycles? Even when I add usleepthe transmission rate will slow down, but is still not consistent.

                  Any ideas? thank you again! 🙂

                  #include <Bela.h>
                  #include <libraries/Pipe/Pipe.h>
                  #include <libraries/Serial/Serial.h>
                  #include <cmath>
                  #include <iostream>
                  #include <string>
                  #include <libraries/UdpClient/UdpClient.h>
                  
                  
                  float input; //from Bela->context
                  uint16_t array[8][8]; //eight ADC x 8 samples
                  UdpClient udpClient(4567, "192.168.7.1");
                  Serial gSerial;
                  Pipe gPipe;
                  AuxiliaryTask serialCommsTask;
                  
                  
                  
                  void ioLoop(void* arg) {
                  	while(!Bela_stopRequested())
                  	{
                  		udpClient.send(array, sizeof(array));
                  		usleep (100);
                  	}
                  }
                  
                  bool setup(BelaContext *context, void *userData) {
                  	gSerial.setup ("/dev/ttyGS0", 230400);
                  	AuxiliaryTask serialCommsTask = Bela_createAuxiliaryTask(ioLoop, 0, "serial-thread", NULL);
                  	Bela_scheduleAuxiliaryTask(serialCommsTask);
                  	gPipe.setup("serialpipe", 1024);
                  	return true;
                  }
                  
                  void render(BelaContext *context, void *userData)
                  {
                  //READ FRAME BY FRAME OF THE BUFFER
                  	for(unsigned int n = 0; n < context->analogFrames; n++) {
                  	for(unsigned int ar = 0; ar < 8; ar++) {
                  	input = analogRead(context, n, ar); //read from ADC/context
                  	array[ar][n] = trunc(input*65535); //convert from float to int
                      }
                      }
                  }
                  
                  void cleanup(BelaContext *context, void *userData) {}

                    violapwr I'm measuring a range of 15000-18000 blocks/second in PD -- I imagine the theoretical rate should be 2756.25 blocks/second since we have 8 samples/block and 22050 samples/second.

                    The first question to ask is: in the Bela code, what is limiting the rate at which udpClient.send() is called? If this was the only thread running on the board, it would be limited by the sum of the usleep() time and the time it takes to actually execute send(). As you are running the audio thread and other processes on the board, that value becomes the maximum rate, but the actual rate will be lower depending on the audio thread's and other processes' CPU usage. also have no guarantee of the interval between executions of the udpClient.send(). A call to usleep() guarantees that the thread will sleep at least the specified number of seconds, but makes no guarantees about the maximum sleep amount. Also, send() could be preempted by other higher-priority threads once or more during its execution.

                    So the answer is: it depends, but there is no reason why it should be 2756.25 blocks/second : as you see, your code ioLoop() makes no attempt to synchronise with the execution of the audio thread. The loop may run 0, 1 or several times in between executions of render() and you have no guarantees of when or how frequently on average that will happen. You could use a circular buffer being written by render() read by ioLoop(). ioLoop() would sleep and periodically wake up to check if any data is available to read and if it is it would read it and send it over the network.

                      giuliomoro Yes that totally makes sense - thank you!

                      I think this most likely solves this feature update for now - thank you again for your thoughtful responses and help through this. Very excited to see how this fits into my overall design.

                      Here's what I ended up with in case helpful for others down the road:

                      #include <Bela.h>
                      #include <libraries/Pipe/Pipe.h>
                      #include <libraries/Serial/Serial.h>
                      #include <cmath>
                      #include <iostream>
                      #include <string>
                      #include <libraries/UdpClient/UdpClient.h>
                      
                      
                      float input; //from Bela->context
                      bool NewBlock;
                      uint16_t array[8][8]; //eight ADC x 8 samples
                      UdpClient udpClient(4567, "192.168.7.1");
                      Serial gSerial;
                      Pipe gPipe;
                      AuxiliaryTask serialCommsTask;
                      
                      
                      
                      void ioLoop(void* arg) {
                      	while(!Bela_stopRequested())
                      	{
                      		if (NewBlock == true) { 
                      		udpClient.send(array, sizeof(array));
                      		NewBlock = false;
                      		}
                      		
                      		usleep (80);
                      	}
                      }
                      
                      bool setup(BelaContext *context, void *userData) {
                      	gSerial.setup ("/dev/ttyGS0", 230400);
                      	AuxiliaryTask serialCommsTask = Bela_createAuxiliaryTask(ioLoop, 0, "serial-thread", NULL);
                      	Bela_scheduleAuxiliaryTask(serialCommsTask);
                      	gPipe.setup("serialpipe", 1024);
                      	return true;
                      }
                      
                      void render(BelaContext *context, void *userData)
                      {
                      //READ FRAME BY FRAME OF THE BUFFER
                      	for(unsigned int n = 0; n < context->analogFrames; n++) {
                      	for(unsigned int ar = 0; ar < 8; ar++) {
                      	input = analogRead(context, n, ar); //read from ADC/context
                      	array[ar][n] = trunc(input*65535); //convert from float to int
                          }
                          }
                      	NewBlock = true;
                      }
                      
                      void cleanup(BelaContext *context, void *userData) {}

                      I have to point out that your code is not thread safe and you could have some cases where the ioLoop thread doesn't run in between executions of render() (e.g.: because the system is busy doing something else and doesn't manage to schedule it on time), thus losing one block worth of readings. Having a circular buffer of adequate size would guarantee that no blocks are lost. Also, in principle there is no guarantee that the array variable's content will be fully written by the time the ioLoop thread sees that NewBlock is true, though for a single-core system like this, where ioLoop is guaranteed not to run alongside and/or preempt render(), then you'll probably be fine in practice.

                      Thanks for sharing your code. To make it more useful to the you probably want to remove stale references to serial and unused variables/unneeded includes.

                      What's all this for, btw?