[HC-05] How to squeeze max of it ?

Hi !
Im using two HC-05 modules, with my two UNO's with 14.7456Mhz crystals ( swapped and remade an bootloader ), with two 12bit DAC's and 12bit ADC's, and microphone+speaker for each of them. As you can see, Im trying to achieve to-way data transmission - voice, to be more specific. I've changed the crystal to achieve higher bauds ( 460800, 921600 etc ). I spent DAYS trying to get it working... and what I've achieved so far is very poor quality, one way communication. From what I've readen on forum, most "speed" issues comes from Serial.write() implementation and it's slowness. Well, the thing is, that when I connect those two UNO's at baud rate 460800 or higher, the audio quality is just enough for me. The problem came out after I tried a HC-05 connection. It's seems that HC-05 somehow slows down Serial communication! Obviously, I'm using Hardware Serial and its native pins. All I've achieved from it was very distorded low quality voice ( like it was losing half of packets, or slowed 4 times... ). Using the same program, connecting not via HC-05, but by cross RX->TX, TX->RX made perfect results. Is there any way, that HC-05 would slow down UNO's Serial ? I mean, those bluetooth modules are meant to achieve much higher bauds, and so far im only transmitting one-way. I also couldnt find any detailed HC-05 documentation. Most of it was about AT commands etc. Im aware that HC-05 are SPP, not Audio modules, but my target is 8bit samples and ~8000-10000Hz max :slight_smile: Here is my code :

As you can see, it includes functions for ADC and DAC reads ( it disables interrupts for a while! ). It's the transmitter program.

#include <SPI.h>

int readvalue;
const int dacChipSelectPin = 10;
const int adcChipSelectPin = 7;

void setup() {
  Serial.begin(460800);
  pinMode (dacChipSelectPin, OUTPUT);
  pinMode (adcChipSelectPin, OUTPUT);
  digitalWrite(dacChipSelectPin, HIGH);
  digitalWrite(adcChipSelectPin, HIGH);
  SPI.begin();
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  delay(5000);
}


// Function to set the DAC, Accepts the Value to be sent and the cannel of the DAC to be used.
void setDAC(int value, int channel) {
  byte dacRegister = 0b00110000;                        // Sets default DAC registers B00110000, 1st bit choses DAC, A=0 B=1, 2nd Bit bypasses input Buffer, 3rd bit sets output gain to 1x, 4th bit controls active low shutdown. LSB are insignifigant here.
  int dacSecondaryByteMask = 0b0000000011111111;        // Isolates the last 8 bits of the 12 bit value, B0000000011111111.
  byte dacPrimaryByte = (value >> 8) | dacRegister;     //Value is a maximum 12 Bit value, it is shifted to the right by 8 bytes to get the first 4 MSB out of the value for entry into th Primary Byte, then ORed with the dacRegister
  byte dacSecondaryByte = value & dacSecondaryByteMask; // compares the 12 bit value to isolate the 8 LSB and reduce it to a single byte.
  switch (channel) {
    case 0:
      dacPrimaryByte &= ~(1 << 7);
      break;
    case 1:
      dacPrimaryByte |= (1 << 7);
      break;
  }
  noInterrupts(); // disable interupts to prepare to send data to the DAC
  digitalWrite(dacChipSelectPin, LOW); // take the Chip Select pin low to select the DAC:
  SPI.transfer(dacPrimaryByte); //  send in the Primary Byte:
  SPI.transfer(dacSecondaryByte);// send in the Secondary Byte
  digitalWrite(dacChipSelectPin, HIGH); // take the Chip Select pin high to de-select the DAC:
  interrupts(); // Enable interupts
}

uint16_t readADC(int channel) {
  uint16_t output;
  byte channelBits = channel | 0x00;
  channelBits = channelBits << 5;
  noInterrupts(); // disable interupts to prepare to send data to the DAC
  digitalWrite(adcChipSelectPin, LOW); // take the Chip Select pin low to select the DAC:
  SPI.transfer(B00000110);
  byte msb = SPI.transfer(channelBits);
  byte lsb = SPI.transfer(0x00);
  digitalWrite(adcChipSelectPin, HIGH); // take the Chip Select pin high to de-select the DAC:
  interrupts(); // Enable interupts
  msb = msb & B00001111;
  output = msb << 8 | lsb;
  return output;
}

void loop() {
      readvalue = readADC(0);
      Serial.write((readvalue >> 4) & 0xff);
      delayMicroseconds(22);
}

Reciever program is just alittle bit different at the end :

void loop() {
      if(Serial.available()>0){
      readvalue = Serial.read();
      setDAC((readvalue << 4) & 0xff0, 0);}
}

I find it incredible that one would complain about speed while calling delay() in any variation.

PaulS:
I find it incredible that one would complain about speed while calling delay() in any variation.

Haha :slight_smile: You got the point. But obviously, im not using it without purpose. Im not sure why, but Serial.flush(); just doesnt make it's job :slight_smile: I've noticed that, there is no difference in using it or not. As you know, in such tight loops, Serial.write() wont finish it's job, and if readADC will be executed, it will both disable interrupts for a while, and change variable that Serial.write() didnt managed to send yet. It results in sending nothing at all :slight_smile:

PS : Even with this delay, straight connection RX->TX, TX->RX gives expected result, but just plugging it into HC-05 makes it stutter.

Gosucherry:
PS : Even with this delay, straight connection RX->TX, TX->RX gives expected result, but just plugging it into HC-05 makes it stutter.

I had to research what the HC-05 is. I see it is Bluetooth modules. The I had to try to find details on Bluetooth.

I see it is a packet system consisting of one master and up to 7 slave units. Looks to have a lot of overhead communications, but runs very fast. Are your units configured master and slave?

What you are doing is sending asynchronous data over a synchronous packet network. You are encountering the age old problem of latency. A synchronous packet system cannot send each of your asynchronous bytes without imbedding them in a packet. It has the option of sending one async byte per packet, or sending several. When sending a fixed number of async bytes in a packet, it has to wait to see if the required number of async bytes are ready to send. If they are not arriving, then padding must be inserted, or a short packet must be developed and sent.

If your async bytes are arriving faster than can be put in packets and sent, then they must be dropped. Are you able to look at the status of the HC-05 and determine if there are problems?

Your next research should be to determine if the HC-05 is sending one async byte per packet or just what number per packet. Also determine if you can look at a status word from the HC-05 and see if it is performing properly.

Try closing the gap, maybe it's the signal strength causing issues. You can solder a better antenna onto the unit. And maybe consider compression? You say the speed isn't too good.

Also consider some alternative serial libraries ... they may be faster...

Both closing the gap and soldering better antenna onto the unit wont work :slight_smile: Compression... ? Well, 8bitrate sample is already poor quality :> The speed is much more than enough, since with baud 460800 I should be able to send 46080 bytes/s, so I could double the quality of sample and keep perfect frequency :slight_smile: The problem is with the HC-05.

My units are properly configured and linked ( done this like hundreds of times already, on many different HC-05, so we could exclude this problem ). I've found detailed BC417 ( that's the HC-05 main chip ) documentation, but so far, no info about size of data packets. All my knowledge about status of those HC-05 is flashing diod, which flashes as it should ( after pairing etc. ). Since I dont have any measuring aparature, I will try to find out by testing speeds of incomming messages while changing buffer size :slight_smile: I will update this post soon.

[Update]
I've tested HC-05 using my >other< MCU board ( mbed platform ). It counted ( in microseconds ) time between two points in code. I have literally no idea why, but i've measured "read" time, and it result in very random times between 10us-250us. But here is something interesting - I've measured time of writes, and the first byte was written in 4us, the second in another 4us, but the third was 16us, and the fourth was 20us! Always the same pattern. It was pure "write" without anyone recieving message. Reading the data at the other end somehow "reset" the state of "writer" and first byte after reading takes again 4us, second 4us third 15-16us, fourth 20-21us, and any other next 20-21us. Im not sure if is it an influence of mbed libraries or something like that, but maybe that means our packet are size of 4 bytes? I couldnt find any information about size of the packets at any documentation i've found :slight_smile:

What you are doing is sending asynchronous data over a synchronous packet network.

No. Op is sending synchronous data over an asynchronous packet network. Bluetooth does not care that the packets get out of order.

Ok. I've done some test on ( different pair of ) HC-05 modules. Paired them, etc. Baud rate 460800. Two devices, two sketches, same way they "talk" to each other. So.
Transmitter :

    while(1) {
        read=readADC(0);             // 40 us
        bt.putc((read >> 4) & 0xff); //  5 us 
        wait_us(25);
    }

Reciever :

    while(1) {
        if(bt.readable()) {
            read=bt.getc();                  //  5 us
            // there coudl be wait_us(25);                     
            setDAC(((read << 4) & 0xff0),0); // 25 us 
            // there also could be wait_us(25);
        }
    }

In comments, there are measured times that those functions takes. In microseconds obviously. When I didnt use wait_us(25); ( it waits 25 us ), the communication was... not active at all ? I mean, with no delay, there was NO data recieved. The funny fact is, that delay have to be in at least one of the sketches. It doesnt matter in which one. And in which place. Devices talk at this moment, but the voice is very distorted, obviously there are issues with frequency of stream, clapping hands etc, are fine, whistle are fine, but the pitch in playback of my whistle is higher ( why ? is reciever playing data faster, than the transmitter ? ), voice is recognizable, but sound very bad. I've also tried adjusting different values of delays, in both ( to make both cycles ~equal ) but the result was pretty much the same. The data sent and recieved is in perfect condition.

The HC-05 takes in your serial data stream and sends it out via air in small packets. The actual speed within the packet is maybe a few MB/sec (always, independent of your serial baudrate).

As the connection via air is noisy and unreliable, it may be switching between channels (frequency hopping in 2.4GHz band, it continuously searching for free channels) during the transmission, and/or, it may repeat the sending of a packet many times until the packet comes over to the other side properly (there is a handshaking overhead in the air channel). Thus the "serial in/out" has nothing to do with "sending packets via air", those are two different communication layers. The HC-05 "guarantees" the serial data come over, the individual bytes with given baud rate, but it hardly can guarantee the bytes in the serial stream are flowing in "continuous" manner, without small delays between them.

To get a smooth serial data stream out of it, you must implement a sw FIFO BUFFER (the buffer receives the serial "sporadic data chunks" in, for example, at 50kB/sec peak, and it sends the data out at 5kB/sec continuous, so it has to be XX kB deep) at the receiver side, in order to compensate for the delays in the serial data.

It will most probably not fit into Uno, but with 1284p it may work.

pito:
The HC-05 takes in your serial data stream and sends it out via air in small packets. The actual speed within the packet is maybe a few MB/sec (always, independent of your serial baudrate).

As the connection via air is noisy and unreliable, it may be switching between channels (frequency hopping in 2.4GHz band, it continuously searching for free channels) during the transmission, and/or, it may repeat the sending of a packet many times until the packet comes over to the other side properly (there is a handshaking overhead in the air channel). Thus the "serial in/out" has nothing to do with "sending packets via air", those are two different communication layers. The HC-05 "guarantees" the serial data come over, the individual bytes with given baud rate, but it hardly can guarantee the bytes in the serial stream are flowing in "continuous" manner, without small delays between them.

To get a smooth serial data stream out of it, you must implement a sw FIFO BUFFER (the buffer receives the serial "sporadic data chunks" in, for example, at 50kB/sec peak, and it sends the data out at 5kB/sec continuous, so it has to be XX kB deep) at the receiver side, in order to compensate for the delays in the serial data.

It will most probably not fit into Uno, but with 1284p it may work.

I dont know how to thank you. This serial FIFO Buffer seems to do work ! And checking it was ultra easy, since I was doing test on my >other< boards ( mbed ) and they have a ready library for buffered serial, all I changed was one "#include" and it was working :slight_smile: So far, it just works without weird bugs, and without "wait_us" in code. This is huge ( since im trying to get it working for like... over a week now ). So the Fifo buffer was the thing. There is still alot of work to do, and I will edit this post, after doing some progress.

[Edit]
Well. It works... ALMOST like A2DP :slight_smile: But for people, who in future will face the same problem : I admit you to just buy proper modules. You guys need AGHFP - Audio Gateway bluetooth profile module, and some HFP :slight_smile: It's not worth wasting tons of hours on doing such poor quality transmission in SPP :slight_smile:

You said that you included a FIFO lybrary.
I would like to know which one you #included.
And more details perhaps. (I want a steady data flow to plot on regular time interval.)
Thanks in advance.