Esp32 and esp-now for audio streaming (slow acknowledge from receiver)

Hello,
I am trying to program two esp32 for live audio streaming. I am trying to use esp-now for the communication between the transmitter and the receiver, but I am facing some problems.
To understand if what I want to achieve is at all feasible, I started by sending 250 bytes from transmitter to receiver, measuring the time between esp_now_send() and the acknowledgement from the receiver. This time varies from 600us to more than 20ms in a seemingly random fashion. Is this normal?
I then set up a more complex system to try and measure overall latency.
Transmitter:

  • The transmitter has a sine wave signal stored in 250 bytes in memory.
  • Using a timer interrupt (20kHz frequency), this sine wave is output on DAC1 pin in cycles.
  • Every time the 250 bytes have been written to the DAC, they get sent using esp-now to the receiver.
    Receiver:
  • The receiver has a timer interrupt with a frequency of 20kHz
  • Every time a new esp-now frame (250 bytes) has been received, it gets copied to a buffer
  • In the ISR of the timer, if new data is available, the content of the 250 byte buffer gets sent, one byte at a time to DAC1

Overall, probing the DAC1 pins of the 2 esp32, one would expect to see a first full sine wave (1 period) on the transitter, followed by both sine waves on the two DAC1 pins perfectly aligned (0 latency). In reality, due to latency, the sine wave on the receiver side will always be out-of-phase with respect to the transmitter one.
This is the code I am using on the transmiter:

#define CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED 0
#define CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED 0

#include <esp_now.h>
#include <esp_wifi.h>
#include <driver/adc.h>
#include <driver/dac.h>

uint8_t broadcastAddress[] = {0xC0, 0x49, 0xEF, 0xCA, 0x3B, 0x00}; //

uint8_t IN_BUFF[250]={128, 131, 134, 138, 141, 144, 147, 150, 153, 157, 160, 163, 166, 169, 172, 175, 178, 181, 184, 187, 189, 192, 195, 198, 200, 203, 205, 208, 210, 213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 234, 236, 237, 239, 240, 242, 243, 244, 246, 247, 248, 249, 250, 251, 252, 252, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 242, 241, 240, 238, 236, 235, 233, 231, 229, 227, 225, 223, 221, 219, 216, 214, 212, 209, 207, 204, 202, 199, 196, 194, 191, 188, 185, 182, 179, 176, 173, 170, 167, 164, 161, 158, 155, 152, 149, 146, 142, 139, 136, 133, 130, 126, 123, 120, 117, 114, 110, 107, 104, 101, 98, 95, 92, 89, 86, 83, 80, 77, 74, 71, 68, 65, 62, 60, 57, 54, 52, 49, 47, 44, 42, 40, 37, 35, 33, 31, 29, 27, 25, 23, 21, 20, 18, 16, 15, 14, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 3, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 16, 17, 19, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 41, 43, 46, 48, 51, 53, 56, 58, 61, 64, 67, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96, 99, 103, 106, 109, 112, 115, 118, 122, 125, 128};
uint8_t OUT_BUFF[250];

volatile uint8_t i=0;
volatile uint8_t sent=1;
volatile uint8_t ack=1;
volatile uint8_t dataReady=0;
volatile uint8_t pin_state=0;

hw_timer_t *My_timer = NULL;

esp_now_peer_info_t peerInfo;

// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  if(status){
    Serial.println("PACKET ERROR!");
  }
  else{
    ack=1;
  }
}

void IRAM_ATTR sampleADC(){
  //Send a new sine wave byte to DAC1
  dac_output_voltage(DAC_CHANNEL_1, IN_BUFF[i++]);
  if(i==250){
    //If all 250 bytes have been sent, reset counter and prepare esp-now send if previous esp-now frame has been already sent out.
    i=0;
    if(sent){
      memcpy(OUT_BUFF, IN_BUFF, 250);
      dataReady=1;
      sent=0;
    }
  }
}
 
void setup() {
  Serial.begin(115200);
  adc1_config_width(ADC_WIDTH_BIT_12);
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0);
  dac_output_enable(DAC_CHANNEL_1);

  // Set device as a Wi-Fi Station
  esp_netif_init();
  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  esp_wifi_init(&cfg);
  esp_wifi_set_mode(WIFI_MODE_STA);
  esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT40);
  esp_wifi_set_storage(WIFI_STORAGE_RAM);
  esp_wifi_set_ps(WIFI_PS_NONE);
  esp_wifi_start();

  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  esp_wifi_config_espnow_rate(WIFI_IF_STA, WIFI_PHY_RATE_54M);
  esp_now_register_send_cb(OnDataSent);
  
  // Register peer
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;  
  peerInfo.encrypt = false;
  
  // Add peer        
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }
  //Setting up a timer interrupt with freq. of 20kHz
  My_timer = timerBegin(0, 8, true);
  timerAttachInterrupt(My_timer, &sampleADC, true);
  timerAlarmWrite(My_timer, 500, true);

  uint8_t started=0;
  while(!started){
    if(Serial.available()>0){
      char c=Serial.read();
      if(c=='1'){
        started=1;
      }
    }
  }
  Serial.println("Entering loop...");
  timerAlarmEnable(My_timer);
}
 
void loop() {
  //If there is new data to send and previous frame was acknowledged by receiver
  if(dataReady && ack){
    esp_err_t result = esp_now_send(broadcastAddress, OUT_BUFF, sizeof(OUT_BUFF));
    sent=1;
    dataReady=0;
    ack=0;
  }
}

This is the code for the receiver:

#define CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED 0
#define CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED 0

#include <esp_now.h>
#include <esp_wifi.h>
#include <driver/dac.h>

// Structure example to receive data
// Must match the sender structure
uint8_t IN_BUFF[250];
uint8_t OUT_BUFF[250];

volatile uint8_t i=0;
volatile uint8_t data4dac=0;

hw_timer_t *My_timer = NULL;

// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  //Copying received bytes to buffer and setting flag for new data for the DAC
  memcpy(IN_BUFF, incomingData, sizeof(IN_BUFF));
  data4dac=1;
}

void IRAM_ATTR writeDAC(){
  //If there is new data for the DAC
  if(data4dac){
    if(i==0){
      //If we finished sending previous 250 bytes, copy new ones from IN_BUFF to OUT_BUFF
      memcpy(OUT_BUFF, IN_BUFF, sizeof(OUT_BUFF));
    }
    dac_output_voltage(DAC_CHANNEL_1, OUT_BUFF[i++]);
    if(i==250){
        //If we reached end of 250 bytes, reset counter and data4dac flag
        i=0;
        data4dac=0;
    }
  }
}
 
void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);
  dac_output_enable(DAC_CHANNEL_1);
  
  // Set device as a Wi-Fi Station
  esp_netif_init();
  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  esp_wifi_init(&cfg);
  esp_wifi_set_mode(WIFI_MODE_STA);
  esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT40);
  esp_wifi_set_storage(WIFI_STORAGE_RAM);
  esp_wifi_set_ps(WIFI_PS_NONE);
  esp_wifi_start();
  
  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  esp_wifi_config_espnow_rate(WIFI_IF_STA, WIFI_PHY_RATE_54M);

  //Setting up a timer interrupt with a freq. of 20kHz
  My_timer = timerBegin(0, 8, true);
  timerAttachInterrupt(My_timer, &writeDAC, true);
  timerAlarmWrite(My_timer, 500, true);
  timerAlarmEnable(My_timer);
  
  esp_now_register_recv_cb(OnDataRecv);
}
 
void loop() {
  
}

The two esp32 are communicating and probing the DAC1 pins with an oscilloscope shows the two sine waves, the issue is that there are a lot of "skipped" sine waves on the receiver end, during which the DAC signal doesn't change. Also, the latency is varying a lot, as mentioned in the beginning. The WIFI_PHY_RATE which sets the speed of the communication doesn't seem to have a real influence: I tried both slower and faster speeds. Am I missing something here? I have very little experience with the esp32, so I hope it's me and there is an easy fix for this.
Any suggestions?
Thanks a lot!

1 Like

make this change so the freeRTOS OS knows to drop everything else and do this routine

void IRAM_ATTR sampleADC(){
  BaseType_t xHigherPriorityTaskWoken; add this.
  //Send a new sine wave byte to DAC1
  dac_output_voltage(DAC_CHANNEL_1, IN_BUFF[i++]);
  if(i==250){
    //If all 250 bytes have been sent, reset counter and prepare esp-now send if previous esp-now frame has been already sent out.
    i=0;
    if(sent){
      memcpy(OUT_BUFF, IN_BUFF, 250);
      dataReady=1;
      sent=0;
    }
  }
}

The ESP32Servo library, for one, uses the timer library and allows the ESP32's OS to auto assign timers. Typically, auto assignment starts at timer 1 and works its way up to 4. I found its best to use timer 4 then 3 then 2 then 1 instead of 1,2,3,4.
Change My_timer = timerBegin(0, 8, true); to My_timer = timerBegin(3, 8, true); to be on the safe side.

I use event triggers to triggers my timer events. instead of processing any code in the ISR.

void IRAM_ATTR onTimer()
{
  BaseType_t xHigherPriorityTaskWoken;
  xEventGroupSetBitsFromISR(eg, OneMinuteGroup, &xHigherPriorityTaskWoken);
} // void IRAM_ATTR onTimer()

You could use the ESP32's built in OS, freeRTOS for a better performance.

Thanks for doing a good post code tags and a clear description of the issue.

Would you want to set ack to 0 on failure?

void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  if(status)
{
ack=0;??????????????????????????????
    Serial.println("PACKET ERROR!");
  }
  else{
    ack=1;
  }
}

You could add a timestamp printouts to see which part is taking too long.

Hi Idahowalker!
Thanks for the help!
I tried implementing your feedback on timer choice and added the xHigherPriorityTaskWoken to the ISRs, but noticed little improvement.
This is what I see with acquiring the two DAC1 pins.


I also tried measuring the time of esp_now_send() and the total time of sending and getting the acknowledge back from the receiver for 100 consecutive sine waves/frames sent. These are the results in microseconds:

     send  send+ack
 0: 188	749
 1: 36	415
 2: 37	332
 3: 36	330
 4: 48	386
 5: 35	1032
 6: 45	762
 7: 35	354
 8: 47	329
 9: 36	3229
10: 45	346
11: 35	343
12: 47	377
13: 35	332
14: 47	1191
15: 35	1200
16: 37	332
17: 35	369
18: 48	328
19: 36	372
20: 46	351
21: 36	332
22: 37	1167
23: 35	321
24: 47	328
25: 35	335
26: 47	342
27: 35	364
28: 45	341
29: 35	340
30: 37	350
31: 35	342
32: 45	385
33: 35	323
34: 49	353
35: 35	351
36: 37	340
37: 36	358
38: 49	335
39: 36	344
40: 45	360
41: 35	341
42: 49	374
43: 35	346
44: 37	798
45: 36	328
46: 44	324
47: 35	366
48: 37	357
49: 36	333
50: 47	358
51: 35	343
52: 47	385
53: 35	323
54: 46	343
55: 36	581
56: 37	3021
57: 35	1743
58: 47	324
59: 44	340
60: 45	369
61: 36	339
62: 48	356
63: 36	349
64: 45	337
65: 36	347
66: 47	340
67: 36	350
68: 38	331
69: 35	332
70: 45	352
71: 36	323
72: 47	379
73: 36	349
74: 47	333
75: 36	327
76: 47	326
77: 36	372
78: 49	357
79: 35	326
80: 37	355
81: 35	374
82: 38	376
83: 36	342
84: 45	357
85: 36	336
86: 49	343
87: 36	366
88: 38	329
89: 36	345
90: 37	338
91: 36	341
92: 47	1180
93: 36	353
94: 45	348
95: 35	477
96: 37	344
97: 44	377
98: 45	335
99: 36	355

I never got an error saying that the receiver hadn't acknowledged a frame, which should mean no frames are lost.
Notice that these results correspond to the png image. Considering the transmitter is sending a frame every 12.5ms (250 * 1/20000Hz) and all the measured dt are below that, the system should work, but the receiver is still messing up somehow.

That's a print and plot of the receiver? Iask because printing and plotting are slow.

I do not know. Did you ask them at ESP32.com why their stuff is so slow?

Hi,
The list comes from a Serial.print on the transmitter side and everything seems fine.
The plot is generated using an oscilloscope by reading the two DAC1 pins.
So, after some tweaking of the receiver code which seems to be the problem, I managed to improve performance (no more skipped sine waves). This is the new receiver code:

#define CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED 0
#define CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED 0

#include <esp_now.h>
#include <esp_wifi.h>
#include <driver/dac.h>

uint8_t IN_BUFF[250];
uint8_t OUT_BUFF[250];

volatile uint8_t i=0;
volatile uint8_t data4dac=0;
volatile uint8_t data_rec=0;

hw_timer_t *My_timer = NULL;

// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(IN_BUFF, incomingData, sizeof(IN_BUFF));
  data_rec=1;
}

void IRAM_ATTR writeDAC(){
  BaseType_t xHigherPriorityTaskWoken;
  if(i==0 && data_rec){ 
    //if new data has been received and OUT_BUFF is free to use, copy in the new data from IN_BUFF, set data4dac to 1 and reset data_rec
    memcpy(OUT_BUFF, IN_BUFF, sizeof(OUT_BUFF));
    data_rec=0;
    data4dac=1;
  }
  if(data4dac){
    dac_output_voltage(DAC_CHANNEL_1, OUT_BUFF[i++]);
    if(i==250){
        //if finished sending 250 bytes, reset counter and data4dac
        i=0;
        data4dac=0;
    }
  }
}
 
void setup() {
  // Initialize Serial Monitor
  Serial.begin(115200);
  dac_output_enable(DAC_CHANNEL_1);
  
  // Set device as a Wi-Fi Station
  esp_netif_init();
  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  esp_wifi_init(&cfg);
  esp_wifi_set_mode(WIFI_MODE_STA);
  esp_wifi_set_bandwidth(WIFI_IF_STA, WIFI_BW_HT40);
  esp_wifi_set_storage(WIFI_STORAGE_RAM);
  esp_wifi_set_ps(WIFI_PS_NONE);
  esp_wifi_start();
  
  // Init ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  esp_wifi_config_espnow_rate(WIFI_IF_STA, WIFI_PHY_RATE_54M);

  //Setting up a timer interrupt every 22.7us to acquire samples on R and L channels at 44kHz
  My_timer = timerBegin(3, 8, true); // timerBegin(timer_num, prescaler, count_up=true)
  timerAttachInterrupt(My_timer, &writeDAC, true); //regenerate=true
  timerAlarmWrite(My_timer, 500, true); //counter= (1/0.44MHz) * 80MHz/8 = 227
  timerAlarmEnable(My_timer);
  
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info
  esp_now_register_recv_cb(OnDataRecv);
  t0_rec=micros();
}
 
void loop() {

}

Basically, I noticed a mistake in the if statement in the timer ISR code. The data4dac was reset in the if(i==250) statement and this sometimes prevented the new frame from being processed correctly. Now I fully split the "new data received" (data_rec variable) from the "data available for the dac" (data4dac variable) and this solved the skipping of sine waves. This is a photo of the DAC1 pins now.


There is still quite a bit of latency which I would like to improve on.
To answer your question, yes, I asked on the esp32.com forum, but no one has replied.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.