5

Is it practical to record speech with the Leonardo?

I want to record speech via a MEMS microphone and stream it over a serial connection to a Raspberry Pi for speech-to-text processing.

This article states that the typical bitrate for recording voice podcasts is 64 kbps. The Arduino's analogRead() function has a maximum sample rate of typically 8,600 times/sec for a maximum bitrate of 8,600 samples/sec * 10 bits/sample = 86 kbps, so theoretically, it should be fast enough to record decent voice audio.

However, will the serial connection be fast enough to stream this much data? Theoretically, the Leonardo's serial connection can go up to 115,200 bps, but I've never reliably gotten it to go faster than 57,600 bps, which would mean the serial connection would a bottleneck that would prevent it from sending a continuous stream of 64 kbps, and therefore it would be impractical to record speech with the Arduino.

Am I wrong? Has anyone had success with voice recording while streaming it over serial?

  • @Greenonline, sorry, I've fixed the link. – Cerin Apr 17 '16 at 18:46
3

On my page about the ADC converter I attempted to record a 4 kHz sine wave on the ATmega328P with the following code:

const byte adcPin = 0;  // A0

const int MAX_RESULTS = 256;

volatile int results [MAX_RESULTS];
volatile int resultNumber;

// ADC complete ISR
ISR (ADC_vect)
  {
  if (resultNumber >= MAX_RESULTS)
    ADCSRA = 0;  // turn off ADC
  else
    results [resultNumber++] = ADC;
  }  // end of ADC_vect

EMPTY_INTERRUPT (TIMER1_COMPB_vect);

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();

  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  TCCR1B = bit (CS11) | bit (WGM12);  // CTC, prescaler of 8
  TIMSK1 = bit (OCIE1B);  // WTF?
  OCR1A = 39;    
  OCR1B = 39;   // 20 uS - sampling frequency 50 kHz

  ADCSRA =  bit (ADEN) | bit (ADIE) | bit (ADIF);   // turn ADC on, want interrupt on completion
  ADCSRA |= bit (ADPS2);  // Prescaler of 16
  ADMUX = bit (REFS0) | (adcPin & 7);
  ADCSRB = bit (ADTS0) | bit (ADTS2);  // Timer/Counter1 Compare Match B
  ADCSRA |= bit (ADATE);   // turn on automatic triggering

  // wait for buffer to fill
  while (resultNumber < MAX_RESULTS)
    { }

  for (int i = 0; i < MAX_RESULTS; i++)
    Serial.println (results [i]);

  }  // end of setup

void loop () { }

The code (rather inefficiently) sends the results in ASCII, like this:

378
710
899
992
966
827
609
367
159
38
35
147
350
593
817
965
1001
917
733
494
259
88
21
76
239
471
711
905
1001
977
839
620
376
164
39

This gave reasonable results:

Sine wave with ADC

Now the Leonardo is, of course, a different processor. However it is similar. The ADC on it should be capable of similar performance.


I should point out that this code records first, then sends the data. This is so the recording can keep up. A normal ADC conversion (if you don't change the ADC prescaler) takes 104 µs. To send 10 bits via serial at 115200 baud rate would take 173.6 µs because it would require two bytes per sample (unless you somehow compress each reading). If you could put up with 8-bit resolution (per sample) then you halve that, and can probably just stream the data out fast enough.

  • Do you really need to enable the TIMER1_COMPB interrupt? The doc of the ADC says the conversion is triggered by the rising edge of the interrupt flag, and the interrupt flag does not need the interrupt enable bit to work. Did I miss something? – Edgar Bonet Apr 18 '16 at 9:33
  • You could be right. Try it without the interrupt. However the EMPTY_INTERRUPT probably just generates an RTI instruction. – Nick Gammon Apr 18 '16 at 10:10
  • I tried it with the interrupt disabled: it doesn't work. Seems there is an error either in the silicon or in the datasheet: “Note that an Interrupt Flag will be set even if the specific interrupt is disabled or the Global Interrupt Enable bit in SREG is cleared. A conversion can thus be triggered without causing an interrupt.” You may have noticed the inconsistency if I understand the comment next to setting TIMSK1. The empty interrupt is indeed just a reti, but the whole interrupt processing (save context, load address of vector, execute vector (jmp), reti) takes 11 cycles. – Edgar Bonet Apr 18 '16 at 10:54
  • There's no traditional meaning to baud rate on a Leonardo. Transmitting extra data over USB, at least in binary, is probably faster than doing logarythmic encoding as traditionally used for 8-bit telephone links. – Chris Stratton Apr 18 '16 at 19:00
  • I did some extra tests: my previous comment is wrong. What I missed is the conversion being triggered by a rising edge of the interrupt flag used as a trigger source. In order to have more than one such edge, the flag has to be cleared after it is set. This can be done automatically by enabling the interrupt: “OCF1B is automatically cleared when the Output Compare Match B Interrupt Vector is executed.” The other option is to clear the flag manually, e.g. by doing TIFR1 = _BV(OCF1B); inside ISR(ADC_vect). – Edgar Bonet Jun 6 '16 at 15:50
1

You don't actually have to initialize your connection to 115,200 bps or 57,600 bps. You can use a custom baud rate of 65,000 bps.

One thing to keep in mind that, you can use any custom baud rate you want, as long as it's supported by the hardware. The Arduino IDE however only supports standard baud rates.

One more thing, as you are streaming data (kind of), you might also want to look into Arduino serial buffer sizes.

  • There's actually no (traditional) meaning to the baud rate setting on a Leonardo. – Chris Stratton Apr 18 '16 at 18:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.