Distortion in ESP32 I2S audio playback with external DAC for sample frequency higher than 20kSps - embedded

Hardware: ESP32 DevKitV1, PCM5102 breakout board, SD-card adapter.
Software: Arduino framework.
For some time I am struggling with audio playback using a I2S DAC external to ESP32.
The problem is I can only play without distortion for low sample frequencies, i.e. below 20kSps.
I have been studying the documentation, https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/i2s.html, and numerous other sources but sill haven't managed to fix this.
I2S configuration function:
esp_err_t I2Smixer::i2sConfig(int bclkPin, int lrckPin, int dinPin, int sample_rate)
{
// i2s configuration: Tx to ext DAC, 2's complement 16-bit PCM, mono,
const i2s_config_t i2s_config = {
.mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX | I2S_CHANNEL_MONO), // only tx, external DAC
.sample_rate = sample_rate,
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // single channel
// .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL3, // highest interrupt priority that can be handeled in c
.dma_buf_count = 128, //16,
.dma_buf_len = 128, // 64
.use_apll = false,
.tx_desc_auto_clear = true};
const i2s_pin_config_t pin_config = {
.bck_io_num = bclkPin, //this is BCK pin
.ws_io_num = lrckPin, // this is LRCK pin
.data_out_num = dinPin, // this is DATA output pin
.data_in_num = I2S_PIN_NO_CHANGE // Not used
};
esp_err_t ret1 = i2s_driver_install((i2s_port_t)i2s_num, &i2s_config, 0, NULL);
esp_err_t ret2 = i2s_set_pin((i2s_port_t)i2s_num, &pin_config);
esp_err_t ret3 = i2s_set_sample_rates((i2s_port_t)i2s_num, sample_rate);
// i2s_adc_disable((i2s_port_t)i2s_num);
// esp_err_t ret3 = rtc_clk_apll_enable(1, 15, 8, 5, 6);
return ret1 + ret2 + ret3;
}
A wave file, which was created in a 16 bit mono PCM, 44.1kHz format, is opened:
File sample_file = SD.open("/test.wav")
In the main loop, the samples are fed to the I2S driver.
esp_err_t I2Smixer::loop()
{
esp_err_t ret1 = ESP_OK, ret2 = ESP_OK;
int32_t output = 0;
if (sample_file.available())
{
if (sample_file.size() - sample_file.position() > 2) // bytes left
{
int16_t tmp; // 16 bits signed PCM assumed
sample_file.read((uint8_t *)&tmp, 2);
output =(int32_t)tmp;
}
else
{
sample_file.close();
}
}
size_t i2s_bytes_write;
int16_t int16_t_output = (int16_t)output;
ret1 = i2s_write((i2s_port_t)i2s_num, &int16_t_output, 2, &i2s_bytes_write, portMAX_DELAY);
if (i2s_bytes_write != 2)
ret2 = ESP_FAIL;
return ret1 + ret2;
}
This works fine for sample rates up to 20 kSps.
For a sample rate of 32k or 44.1k heavy distortion occurs. I suspect that this is caused by the I2S DMA Tx buffer.
If the number of DMA buffers (dma_buf_count) and the buffer length (dma_buf_len) is increased, then the sound is played fine at first. Subsequently, after a short time, the distortion kicks in again. I cannot measure this short time span, maybe around a second, but I did notice it depends on the dma_buf_count and dma_buf_len.
Next to this, I tried increasing the CPU frequency to 240MHz, no improvement.
Further I tried to play a file from SPIFSS, no improvement.
I am out of ideas right now, has anyone encountered this issue also?

Reading one sample at a time and pushing it to the I2S driver will not be the most efficient usage of the driver. You are using just 2 bytes in every 128 byte DMA buffer. That leaves just a single sample period to push the next sample before the DMA buffer is "starved".
Read the file in 128 byte (64 sample) chunks and write the whole chunk to the I2S in order to use the DMA effectively.
Depending on the file-system implementation it may be a little more efficient too to use larger chunks that are sympathetic to the file-system's media, sector size and DMA buffering.

Related

STM32 USB Tx Busy

I have an application running on STM32F429ZIT6 using USB stack to communicate with PC client.
MCU receives one type of message of 686 bytes every second and receives another type of message of 14 bytes afterwards with 0.5 seconds of delay between messages. The 14 bytes message is a heartbeat so it needs to replied by MCU.
It happens that after 5 to 10 minutes of continuous operation, MCU is not able to send data because
hcdc->TxState is always busy. Reception works fine.
During Rx interruption, application only adds data to ring buffer, so that this buffer is later serialized and processed by main function.
static int8_t CDC_Receive_HS(uint8_t* Buf, uint32_t *Len) {
/* USER CODE BEGIN 11 */
/* Message RX Completed, Send it to Ring Buffer to be processed at FMC_Run()*/
for(uint16_t i = 0; i < *Len; i++){
ring_push(RMP_RXRingBuffer, (uint8_t *) &Buf[i]);
}
USBD_CDC_SetRxBuffer(&hUsbDeviceHS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceHS);
return (USBD_OK);
/* USER CODE END 11 */ }
USB TX is also kept as simple as possible:
uint8_t CDC_Transmit_HS(uint8_t\* Buf, uint16_t Len) {
uint8_t result = USBD_OK;
/\* USER CODE BEGIN 12 */
USBD_CDC_HandleTypeDef hcdc = (USBD_CDC_HandleTypeDef*)hUsbDeviceHS.pClassData;
if (hcdc-\>TxState != 0)
{
ZF_LOGE("Tx failed, resource busy\\n\\r"); return USBD_BUSY;
}
USBD_CDC_SetTxBuffer(&hUsbDeviceHS, Buf, Len);
result = USBD_CDC_TransmitPacket(&hUsbDeviceHS);
ZF_LOGD("TX Message Result:%d\\n\\r", result);
/ USER CODE END 12 \*/
return result;
}
I'm using latest HAL Drivers and software from CubeIDE (1.27.1).
I have tried expanding heap min size from 0x200 to larger values but result is the same.
Also Line Coding is set according to what recommended values:
case CDC_SET_LINE_CODING:
LineCoding.bitrate = (uint32_t) (pbuf[0] | (pbuf[1] << 8) | (pbuf[2] << 16) | (pbuf[3] << 24));
LineCoding.format = pbuf[4];
LineCoding.paritytype = pbuf[5];
LineCoding.datatype = pbuf[6];
ZF_LOGD("Line Coding Set\n\r");
break;
case CDC_GET_LINE_CODING:
pbuf[0] = (uint8_t) (LineCoding.bitrate);
pbuf[1] = (uint8_t) (LineCoding.bitrate >> 8);
pbuf[2] = (uint8_t) (LineCoding.bitrate >> 16);
pbuf[3] = (uint8_t) (LineCoding.bitrate >> 24);
pbuf[4] = LineCoding.format;
pbuf[5] = LineCoding.paritytype;
pbuf[6] = LineCoding.datatype;
ZF_LOGD("Line Coding Get\n\r");
break;
Thanks in advance, any support is appreciated.
I don't know enough about the STM32 libraries to really check your code, but I suspect you are forgetting to read the bytes transmitted by the STM32 on PC side. Try opening a terminal program like PuTTY and connecting to the STM32's virtual serial port. Otherwise, the Windows USB-to-serial driver (usbser.sys) will eventually have its buffers filled with data from your device and it will stop requesting more, at which point the buffers on your device will fill up as well.

NAudio: Outputting an audio stream at high sample rate results in dropped and choppy sound

I am reading samples from a software defined radio device that delivers two channels of Int16 (shorts) that represent audio. I am trying to send this stream to an audio output device on a PC.
I already have this working with a different set of tools but would like to use NAudio in its place since there are other capabilities I could use.
The code starts with the software defined radio method placing an array of shorts (i.e. int16) arranged as left channel then right channel. The array size is 65536, the sample rate is 192,000.
When the packet is received it is place on a Blocking collection to be picked up by a thread that sends out the audio.
So for the purpose of this question, the thread starts by reading the blocking collection which returns 65536 shorts.
StartStream = true;
waveformat = new WaveFormat(192000, 16, 2);
bs = new BufferedWaveProvider(waveformat);
bs.BufferLength = 65536 * 4;
_waveOut.DeviceNumber = 4;
while (true)
{
dspPacket = dsp_Queue.Take(); //take int16[] of queue
int j = 0;
try
{
for (int i = 0; i < 32768; i += 2)
{
byte[] qDataByte = BitConverter.GetBytes(dspShort[i]);
dspBytes[j] = qDataByte[0];
dspBytes[j + 1] = qDataByte[1];
j += 2;
}
if (startStream)
{
bs.AddSamples(dspBytes, 0, 65536);
_waveOut.Init(bs);
_waveOut.Play();
startStream = false;
}
else
{
bs.AddSamples(dspBytes, 0, 65536);
}
}
catch (Exception ex)
{
}
// SendData(dspPacket); //if this is uncommented, everything works correctly with the old method.
}
Now I can feed the output of the waveout device to a virtual audio cable which then feeds it into some spectrum analyser software and then this is what you will see with here:
When I use the old routines, I get a solid line and the sound does not break up.
So, am I doing something wrong here?
Thanks, Tom

Reading 32-bit SPI data using 16-bit processor (dsPIC)

I am interfacing a M90E32AS energy meter IC with dsPIC33F series processor. I successfully read voltage and current values. I try to read power values also, as per the datasheet the power registers are 32-bits wide. I tried the following code to read the 32 bit value but I am unsuccessful. Help me to rectify the error.
int PmB_read()
{
CS=0;
SPI2BUF=SBUF=0x80B2;
while(SPI2STATbits.SPITBF==1){}
SPI2BUF=0x0;
while(SPI2STATbits.SPITBF==1){}
delay();
CS=1;
HiByte = SPI2BUF;
return HiByte;
}
int PmBLSB_read()
{
CS=0;
SPI2BUF=SBUF=0x80C2;
while(SPI2STATbits.SPITBF==1){}
SPI2BUF=0x0;
while(SPI2STATbits.SPITBF==1){}
delay();
CS=1;
LoByte = SPI2BUF;
TPmB = (HiByte << 16)| LoByte;
Total = TPmB * 0.00032;
return Total;
}
Here is the data sheet screen shot for power register

How Can I Establish UART Communication between 2 Stm32 and produce PWM signal

Edit: I solved UART communication problem but I have new problem getting pwm signal after receiving Transmit Data. I can blink led I can drive relay with transmitted data but I could not produce PWM signal.
maps(120, 1, 1, 250, RxData[4]);
ADC_Left = Yx; __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,ADC_Left);
I used __HAL_TIM_SET_COMPARE function but it doesnt work. I can observe ADC_Left’s value on Debug site but its not work.
I am trying to realize UART communication between 2 stm32. I know there are several topic related with but my question focused another one.
I am reading 2 adc value on stm32 which is only transmit these value and other one only receive these 2 adc value. To do this
MX_USART1_UART_Init();
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); // Interrupt Enable
__HAL_UART_ENABLE_IT(&huart1, UART_IT_TC);
char TxData1[10];
..............
TxData1[0] = 0xEA;
TxData1[1] = wData.Byte_1;
TxData1[2] = wData.Byte_2;
TxData1[3] = wData.Byte_3;
TxData1[4] = wData.Right_Adc_Val;
TxData1[5] = wData.Left_Adc_Val;
TxData1[6] = wData.Byte_6;
for(uint8_t i = 1 ; i < 7; i++)
{
wData.Checksum = wData.Checksum + TxData1[i];
}
wData.Checksum_H = (wData.Checksum >> 8)&0xFF;
wData.Checksum_L = (wData.Checksum)&0xFF;
TxData1[7] = wData.Checksum_H;
TxData1[8] = wData.Checksum_L;
TxData1[9] = 0xAE;
HAL_UART_Transmit_IT(&huart1,(uint8_t*) &TxData1,10);
............
This block sent them I can observate them on Debug screen and using TTL module's Tx Rx pins.
MX_USART1_UART_Init();
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE); // Interrupt Enable
__HAL_UART_ENABLE_IT(&huart1, UART_IT_TC);
char RxData[10];
while(1){
HAL_UART_Receive_IT(&huart1,(uint8_t*) &RxData,10);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
HAL_UART_Receive_IT(&huart1,(uint8_t*) &RxData,10);
}
There is no problem up to here but when i getting RxData 0. index , it gives EA . Of course it should be give EA. When the adc data change all the ranking is changing. RxData[0] gives meaningless data. adc value is jumping over the all RxData array.
data locations must always be in the same index. How Can I get these data in stability for ex.
RxData[0]=EA
.
.
RxData[4]= should give adc value. so on.
..
Edit: I tried other mode of UART, DMA (in circular mode) and direct mode were used. I cant receive even 1 byte with DMA .
In your example code, you have an extra & that needs to be removed from both the transmit and receive HAL method calls. Example:
HAL_UART_Transmit_IT(&huart1,(uint8_t*) &TxData1,10);
HAL_UART_Transmit_IT(&huart1,(uint8_t*) TxData1,10);
To avoid this type of error in the future, recommend not using the cast and try something like the following:
uint8_t TxData1[10];
...
HAL_UART_Transmit_IT(&huart1, TxData1, sizeof(TxData1);

Communication between Arduino and Raspberry Pi over USB

I have an Arduino connected over USB with a Raspberry Pi. A Python program is sending a value over USB, then the arduino receives it and turns the LED on. The Arduino is sending a analogue value from A0 to the Python program on the Raspberry. But Python receives sometimes 024, 24 or 4 instead of 1024. How can I fix this? Here is my code:
http://www.bitsharr.com/Hsoxw7nG
Arduino Code:
int led = 13;
char charIn;
int sensorPin = A0; // select the input pin for the potentiometer
int sensorValue = 0; // variable to store the value coming from the sensor
long previousMillis = 0;
long interval = 1000;
// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
Serial.begin(9600); //This initializes the USB as a serial port
}
void loop() {
if (Serial.available()) {
delay(5); // warten bis alle Daten da sind
while(Serial.available() > 0) {
charIn =(char) Serial.read();
if (charIn == '1') {
digitalWrite(led,HIGH);
delay(5000);
digitalWrite(led,LOW);
}
}
}
Python Code:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from serial import Serial
ser = Serial('/dev/ttyUSB0', 9600)
x=ser.readline()
ser.write('1')
print(x)
def cleanup( str ):
result = ""
for c in str:
if( (c >= "0") and (c <= "9") ):
result += c
return result
print( cleanup(x))
#ser.write("1")
sensorValue = analogRead(sensorPin); //Reads the voltage of the resistor.
Serial.println(sensorValue); //Writes the voltage on the Serial port.
}
For starters, you are using Arduino code in your Python code.
sensorValue = analogRead(sensorPin); //Reads the voltage of the resistor.
Serial.println(sensorValue); //Writes the voltage on the Serial port.
How would your computer know what values are on your Arduino? You need to move this into the Arduino code. I can't tell you where: you never specified exactly what you want to accomplish with this code.
Some more problems with your code:
#ser.write("1") should be ser.write("1")
These two lines aren't bad but they're a bad coding practice to include because you don't use them:
long previousMillis = 0;
long interval = 1000;
But python receives sometimes 024, 24 or 4 instead of 1024.
The Arduino never sent anything; there is no serial.print(); or serial.println();! I don't know how you are receiving data. If you make the edits to your code above and/or specify exactly what you want to do (i.e. Send a 1 to the Arduino, turn a LED on, get sensor data back every five seconds, if the data is above 50%, turn the light off) and still don't know what to do, feel free to comment for clarification.