Show Posts
|
|
Pages: 1 ... 4 5 [6]
|
|
77
|
Forum 2005-2010 (read only) / Interfacing / Re: Serial Receive data rate much lower than expected
|
on: August 23, 2010, 07:49:58 pm
|
|
I looked into that. I don't think it works that way. I think the 128 byte serial buffer is a hardware limiation. All the documentation I can find about it says that the buffer size can be reduced below 128, but that's the maximum.
You might be able to get a larger buffer on NewSoftSerial, but that only supports bauds up to 57,600 (7,200 bytes / sec) and I need slightly faster than that.
|
|
|
|
|
78
|
Forum 2005-2010 (read only) / Interfacing / Re: Serial Receive data rate much lower than expected
|
on: August 23, 2010, 07:06:55 pm
|
|
I'm using a circular buffer to eliminate the 2k ram issue. I just need more data buffered at a time than just 128 bytes. 1024 bytes would work much better. So, if I could copy the 128 byte buffer into a location of my 1024 byte circular buffer, this would increase performance for me.
There is minimal processing of the data going on, it's just critical that my circular buffer does not run out of data.
I'll check out modifying the core Serial ISR's to see if I can have them return me a pointer to the buffer and a buffer length or something like that. I just hate modifying the core files because it makes it so that the code can't be used easily by others.
|
|
|
|
|
79
|
Forum 2005-2010 (read only) / Interfacing / Re: Serial Receive data rate much lower than expected
|
on: August 23, 2010, 06:31:14 pm
|
|
I have revisited the original speed calculations. There was no flow control, so the PC was just spitting data at the Arduino as fast as it possibly could. This meant that the 128 byte buffer on the Arduino was being over-run constantly. However, this kept the buffer full all the time, which increased the bytes / sec.
So, if you only care about the most current data, and don't care if old data is overwritten, then you can sustanably reach 20,000 bytes / second.
However, this was not the desired outcome for my application. I implemented some flow control, but that drastically limits the data rate. There's a bit of lag time between telling the PC to send more data and when it actually arrives. In most cases the receive buffer completely empties before more data arrives.
Changing the latency on the FTDI COM connection seems to help a little, since the packets must be < 128 bytes in order not to overflow the buffer.
At lower baud rates, you get better performance by sending packets that are half of the buffer size (ie: 64 bytes). At higher baud rates (above 57600) it does not really matter if the receive buffer empties or not, because the data comes in fast enough that you can refill an entire buffer at once rather than requesting half-buffer packets.
The real limitation is how fast you can empty the receive buffer. Is there a way that I can copy the entire buffer as a byte array instead of having to call Serial.read() 128 times?
|
|
|
|
|
82
|
Forum 2005-2010 (read only) / Interfacing / Re: Serial Receive data rate much lower than expected
|
on: August 22, 2010, 07:20:22 pm
|
|
Thank you westfw. You were exactly right. There appears to be quite a bit of overhead for sending one byte at a time.
I increased the amount of data the PC was sending each time. Instead of sending just 1 byte, I increased it to send 256 bytes at a time. This apparently works much better, as now I am getting aprox 10,000 bytes / sec. That's much more like what I was expecting.
Now I will increase by baud rates and see if it increases beyond that.
However, I am still wondering if there is a better way to receive data on the Arduino. Can I copy the entire receive buffer to another location somehow?
|
|
|
|
|
83
|
Forum 2005-2010 (read only) / Interfacing / Serial Receive data rate much lower than expected
|
on: August 22, 2010, 06:50:41 pm
|
|
I tested data transfer rates between a PC and Arduino using a very simple sketch.
I am using a baud rate ot 115,200 bps. At 8 bits / byte, this should ideally be 14,400 bytes / sec. Assuming we have a start and stop bit, and it actually takes 10 bits to send 1 byte of useable data, this drops to 11,520 bytes / sec.
However, when I ran my test with actual data and timing the results, I found a large discrepancy. Arduino doesn't seem to have a problem sending the data at the correct data rate. When the Arduino is the receiver, the actual data rate is only 30% of what it should be!
Arduino --> PC = 8,400 bytes / sec. Ideally this would be 11,520 bytes / sec, but it's somewhat close. PC --> Arduino = 3,400 bytes / sec. This is nowhere near 11,520 that it should be.
Increasing the baud rate beyond 115200 does not seem to make any difference on the send or receive speed.
I realize this may be due to the 128-byte receive buffer limitation. (I tested with a larger buffer in HardwareSerial.h, with no improvement.) Is there a faster way to receive all the data in the receive buffer without calling Serial.read() 128 times? I would really like to move all the data in the receive buffer out at once, since I think that the Serial.read() function might be really slowing things down.
Any thoughts? Anyone test their actual send / receive data rates and could share?
I'm really hoping to achieve a data rate of 8,000 bytes / sec or higher if I can for the Arduino's receive.
Here's my test code. I have a program running on my PC which sends data to the Arduino as fast as it can and does the timing. Every 256 bytes received, the Arduino sends the number of BytesRead back to the PC. The PC can then calculate the data rate as: (BytesRead / (current time - start time)).
/* SerialSpeed2 21 August 2010
Test the sending speed for a serial connection. 9,600 baud = 960 Bytes/sec 115,200 baud = 11,520 Bytes/sec */
long BytesRead=0;
void setup() { Serial.begin(115200); establishContact(); }
void establishContact() { while (Serial.available() <= 0) { digitalWrite(13, HIGH); // set the LED on Serial.println("Waiting For Connection..."); // send an initial string delay(300); digitalWrite(13, LOW); // set the LED off delay(300); } //End While } //End Establish Contact
void loop() {
while (Serial.available() > 0) { Serial.read(); BytesRead++; if (BytesRead % 256 == 0) { digitalWrite(13,!digitalRead(13)); Serial.println(BytesRead); }//End if }//End While }// End Loop
|
|
|
|
|
84
|
Forum 2005-2010 (read only) / Exhibition / Re: Streaming Audio over Serial
|
on: August 24, 2010, 02:56:47 pm
|
This can be done straight from the Serial Receive buffer if it is increased in HardwareSerial.cpp to 1024. Here is the simplified code: /* Streaming Audio Programmed by Matthew Vaterlaus 23 August 2010 Adapted largely from Michael Smith's speaker_pcm. <michael@hurts.ca> Plays 8-bit PCM audio on pin 11 using pulse-width modulation. For Arduino with Atmega at 16 MHz. The audio data needs to be unsigned, 8-bit, 8000 Hz. Although Smith's speaker_pcm was very well programmed, it had two major limitations: 1. The size of program memory only allows ~5 seconds of audio at best. 2. To change the adudio, the microcontroller would need to be re-programmed with a new sounddata.h file. StreamingAudio overcomes these limitations by dynamically sending audio samples to the Arduino via Serial. It uses a 1k serial revceive buffer for the audio samples, since the ATMEGA328 only has 2k of RAM. For chips with less RAM, the Serial RX_BUFFER_SIZE variable can be reduced. The only limit on length is the number of samples must fit into an long integer. (ie: 4,294,967,295 samples). At 8000 samples / second, that allows 8,947 minutes of audio. Even this could be overcome if needed. (The only reason to have the number of samples is to know when to turn the speaker off.) Remote Host ----------- 1. Sends 10 bytes of data representing the number of samples. Each byte is 1 digit of an unsigned long. 2. Each time the host recieves a byte it sends the next xxx audio samples to fill the Arduino's receive buffer. */
#include <avr/interrupt.h>
#define SAMPLE_RATE 8000 #define BUFFER_SIZE 1024
unsigned long sounddata_length=0; unsigned long sample=0; unsigned long BytesReceived=0;
unsigned long Temp=0; unsigned long NewTemp=0;
int ledPin = 13; int speakerPin = 11; int Playing = 0;
//Interrupt Service Routine (ISR) // This is called at 8000 Hz to load the next sample. ISR(TIMER1_COMPA_vect) { //If not at the end of audio if (sample < sounddata_length) { //Set the PWM Freq.
OCR2A = Serial.read(); BytesReceived++; sample++;
//if the Serial port buffer has room if ((BytesReceived % BUFFER_SIZE) == 0) { //Tell the remote PC how much space you have. Serial.println(BUFFER_SIZE); }//End if
}//End if else //We are at the end of audio { //Stop playing. stopPlayback(); }//End Else
}//End Interrupt
void startPlayback() { //Set pin for OUTPUT mode. pinMode(speakerPin, OUTPUT);
//---------------TIMER 2------------------------------------- // Set up Timer 2 to do pulse width modulation on the speaker // pin. //This plays the music at the frequency of the audio sample.
// Use internal clock (datasheet p.160) //ASSR = Asynchronous Status Register ASSR &= ~(_BV(EXCLK) | _BV(AS2));
// Set fast PWM mode (p.157) //Timer/Counter Control Register A/B for Timer 2 TCCR2A |= _BV(WGM21) | _BV(WGM20); TCCR2B &= ~_BV(WGM22);
// Do non-inverting PWM on pin OC2A (p.155) // On the Arduino this is pin 11. TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0); TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
// No prescaler (p.158) TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
//16000000 cycles 1 increment 2000000 increments //-------- * ---- = ------- // 1 second 8 cycles 1 second
//Continued... //2000000 increments 1 overflow 7812 overflows //------- * --- = ----- // 1 second 256 increments 1 second
// Set PWM Freq to the sample at the end of the buffer. OCR2A = Serial.read(); BytesReceived++;
//--------TIMER 1---------------------------------- // Set up Timer 1 to send a sample every interrupt. // This will interrupt at the sample rate (8000 hz) //
cli();
// Set CTC mode (Clear Timer on Compare Match) (p.133) // Have to set OCR1A *after*, otherwise it gets reset to 0! TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12); TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
// No prescaler (p.134) TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set the compare register (OCR1A). // OCR1A is a 16-bit register, so we have to do this with // interrupts disabled to be safe. OCR1A = F_CPU / SAMPLE_RATE; // 16e6 / 8000 = 2000
//Timer/Counter Interrupt Mask Register // Enable interrupt when TCNT1 == OCR1A (p.136) TIMSK1 |= _BV(OCIE1A);
//Init Sample. Start from the beginning of audio. sample = 0; //Enable Interrupts sei(); }//End StartPlayback
void stopPlayback() { // Disable playback per-sample interrupt. TIMSK1 &= ~_BV(OCIE1A);
// Disable the per-sample timer completely. TCCR1B &= ~_BV(CS10);
// Disable the PWM timer. TCCR2B &= ~_BV(CS10);
digitalWrite(speakerPin, LOW); reset(); }//End StopPlayback
//Use the custom powlong() function because the standard //pow() function uses floats and has rounding errors. //This powlong() function does only integer powers. //Be careful not to use powers that are too large, otherwise //this function could take a really long time. long powlong(long x, long y) { //Base case for recursion if (y==0) { return(1); }//End if else { //Do recursive call. return(powlong(x,y-1)*x); }//End Else }
void reset() { //PC sends audio length as 10-digit ASCII //While audio length hasn't arrived yet while (Serial.available()<10) { //Blink the LED on pin 13. digitalWrite(ledPin,!digitalRead(ledPin)); delay(100); } //Init number of audio samples. sounddata_length=0; //Convert 10 ASCII digits to an unsigned long. for (int i=0;i<10;i++) { //Convert from ASCII to int Temp=Serial.read()-48; //Shift the digit the correct location. NewTemp = Temp * powlong(10,9-i); //Add the current digit to the total. sounddata_length = sounddata_length + NewTemp; }//End for
//Tell the remote PC/device that the Arduino is ready //to begin receiving samples. Serial.println(128); while (Serial.available() < 64) { //Blink the LED on pin 13. digitalWrite(ledPin,!digitalRead(ledPin)); delay(100); } Playing =0; BytesReceived=0; sample=0; }//End Reset
void setup() { //Set LED for OUTPUT mode pinMode(ledPin, OUTPUT); //Start Serial port. If your application can handle a //faster baud rate, that would increase your bandwidth //115200 only allows for 14,400 Bytes/sec. Audio will //require 8000 bytes / sec to play at the correct speed. //This only leaves 44% of the time free for processing //bytes. Serial.begin(115200); reset();
}//End Setup
void loop() {
//If audio not started yet... if (Playing == 0) { //There's data now, so start playing. Playing=1; startPlayback(); }//End if }//End Loop
|
|
|
|
|
85
|
Forum 2005-2010 (read only) / Exhibition / Streaming Audio over Serial
|
on: August 24, 2010, 11:16:54 am
|
I really enjoyed Michael Smith's "speaker_pcm" but thought I could improve it to allow it to play full-length songs. This only required minor changes to Michael's code to allow the frequency data to be read from a circular buffer in ram instead of from program memory. The program on the remote host which supplies the audio data is very simple to write and could easily be done in most any programming language. The trickiest part was getting through the Arduino's 128-byte buffer and still sustaining 8khz (8,000 audio samples per second) over the serial connection without losing data. /* Streaming Audio Programmed by Matthew Vaterlaus 23 August 2010 Adapted largely from Michael Smith's speaker_pcm. <michael@hurts.ca> Plays 8-bit PCM audio on pin 11 using pulse-width modulation. For Arduino with Atmega at 16 MHz. The audio data needs to be unsigned, 8-bit, 8000 Hz. Although Smith's speaker_pcm was very well programmed, it had two major limitations: 1. The size of program memory only allows ~5 seconds of audio at best. 2. To change the adudio, the microcontroller would need to be re-programmed with a new sounddata.h file. StreamingAudio overcomes these limitations by dynamically sending audio samples to the Arduino via Serial. It uses a 1k circular buffer for the audio samples, since the ATMEGA328 only has 2k of RAM. For chips with less RAM, the BUFFER_SIZE variable can be reduced. The only limit on length is the number of samples must fit into an long integer. (ie: 4,294,967,295 samples). At 8000 samples / second, that allows 8,947 minutes of audio. Even this could be overcome if needed. (The only reason to have the number of samples is to know when to turn the speaker off.) Remote Host ----------- 1. Sends 10 bytes of data representing the number of samples. Each byte is 1 digit of an unsigned long. 2. Each time the host recieves a byte it sends the next 128 audio samples to fill the Arduino's receive buffer. */
#include <stdint.h> #include <avr/interrupt.h> #include <avr/io.h> #include <avr/pgmspace.h>
#define SAMPLE_RATE 8000 #define BUFFER_SIZE 1024
unsigned long sounddata_length=0; unsigned char sounddata_data[BUFFER_SIZE]; int BufferHead=0; int BufferTail=0; unsigned long sample=0; unsigned long BytesReceived=0;
unsigned long Temp=0; unsigned long NewTemp=0;
int ledPin = 13; int speakerPin = 11; int Playing = 0;
//Interrupt Service Routine (ISR) // This is called at 8000 Hz to load the next sample. ISR(TIMER1_COMPA_vect) { //If not at the end of audio if (sample < sounddata_length) { //Set the PWM Freq. OCR2A = sounddata_data[BufferTail]; //If circular buffer is not empty if (BufferTail != BufferHead) { //Increment Buffer's tail index. BufferTail = ((BufferTail+1) % BUFFER_SIZE); //Increment sample number. sample++; }//End if }//End if else //We are at the end of audio { //Stop playing. stopPlayback(); }//End Else
}//End Interrupt
void startPlayback() { //Set pin for OUTPUT mode. pinMode(speakerPin, OUTPUT);
//---------------TIMER 2------------------------------------- // Set up Timer 2 to do pulse width modulation on the speaker // pin. //This plays the music at the frequency of the audio sample.
// Use internal clock (datasheet p.160) //ASSR = Asynchronous Status Register ASSR &= ~(_BV(EXCLK) | _BV(AS2));
// Set fast PWM mode (p.157) //Timer/Counter Control Register A/B for Timer 2 TCCR2A |= _BV(WGM21) | _BV(WGM20); TCCR2B &= ~_BV(WGM22);
// Do non-inverting PWM on pin OC2A (p.155) // On the Arduino this is pin 11. TCCR2A = (TCCR2A | _BV(COM2A1)) & ~_BV(COM2A0); TCCR2A &= ~(_BV(COM2B1) | _BV(COM2B0));
// No prescaler (p.158) TCCR2B = (TCCR2B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
//16000000 cycles 1 increment 2000000 increments //-------- * ---- = ------- // 1 second 8 cycles 1 second
//Continued... //2000000 increments 1 overflow 7812 overflows //------- * --- = ----- // 1 second 256 increments 1 second
// Set PWM Freq to the sample at the end of the buffer. OCR2A = sounddata_data[BufferTail];
//--------TIMER 1---------------------------------- // Set up Timer 1 to send a sample every interrupt. // This will interrupt at the sample rate (8000 hz) //
cli();
// Set CTC mode (Clear Timer on Compare Match) (p.133) // Have to set OCR1A *after*, otherwise it gets reset to 0! TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12); TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));
// No prescaler (p.134) TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);
// Set the compare register (OCR1A). // OCR1A is a 16-bit register, so we have to do this with // interrupts disabled to be safe. OCR1A = F_CPU / SAMPLE_RATE; // 16e6 / 8000 = 2000
//Timer/Counter Interrupt Mask Register // Enable interrupt when TCNT1 == OCR1A (p.136) TIMSK1 |= _BV(OCIE1A);
//Init Sample. Start from the beginning of audio. sample = 0; //Enable Interrupts sei(); }//End StartPlayback
void stopPlayback() { // Disable playback per-sample interrupt. TIMSK1 &= ~_BV(OCIE1A);
// Disable the per-sample timer completely. TCCR1B &= ~_BV(CS10);
// Disable the PWM timer. TCCR2B &= ~_BV(CS10);
digitalWrite(speakerPin, LOW); }//End StopPlayback
//Use the custom powlong() function because the standard //pow() function uses floats and has rounding errors. //This powlong() function does only integer powers. //Be careful not to use powers that are too large, otherwise //this function could take a really long time. long powlong(long x, long y) { //Base case for recursion if (y==0) { return(1); }//End if else { //Do recursive call. return(powlong(x,y-1)*x); }//End Else }
void setup() { //Set LED for OUTPUT mode pinMode(ledPin, OUTPUT); //Start Serial port. If your application can handle a //faster baud rate, that would increase your bandwidth //115200 only allows for 14,400 Bytes/sec. Audio will //require 8000 bytes / sec to play at the correct speed. //This only leaves 44% of the time free for processing //bytes. Serial.begin(115200); //PC sends audio length as 10-digit ASCII //While audio length hasn't arrived yet while (Serial.available()<10) { //Blink the LED on pin 13. digitalWrite(ledPin,!digitalRead(ledPin)); delay(100); } //Init number of audio samples. sounddata_length=0; //Convert 10 ASCII digits to an unsigned long. for (int i=0;i<10;i++) { //Convert from ASCII to int Temp=Serial.read()-48; //Shift the digit the correct location. NewTemp = Temp * powlong(10,9-i); //Add the current digit to the total. sounddata_length = sounddata_length + NewTemp; }//End for
//Tell the remote PC/device that the Arduino is ready //to begin receiving samples. Serial.println(128); //There's data now, so start playing. //startPlayback(); Playing =0; }//End Setup
void loop() { //If audio not started yet... if (Playing == 0) { //Check to see if the first 1000 bytes are buffered. if (BufferHead = 1023) { Playing=1; startPlayback(); }//End if }//End if //While the serial port buffer has data while (Serial.available()>0) { //If the sample buffer isn't full if (((BufferHead+1) % BUFFER_SIZE) != BufferTail) { //Increment the buffer's head index. BufferHead = (BufferHead+1) % BUFFER_SIZE; //Store the sample freq. sounddata_data[BufferHead] = Serial.read(); //Increment the bytes received BytesReceived++; }//End if //if the Serial port buffer has room if ((BytesReceived % 128) == 0) { //Tell the remote PC how much space you have. Serial.println(128); }//End if }//End While
}//End Loop
|
|
|
|
|