Writing and reading to SD card simultaneously

I am not the most experienced working with microcontrollers and Arduino libraries but I will do my best to describe what I want to achieve and what troubles I am encountering with my current way of solving the problem.

My goal is to try and upload an mp3 file from a Flask server to an SD card connected to an ESP32 C3. I want to play the first mp3 file that is already stored on the SD card while also uploading the second mp3 file to the SD card. I want to do this so that after the first mp3 has finished playing the second mp3 can immediately start playing once the first mp3 is finished playing to avoid a 3-5 second silence while the second mp3 is being uploaded.

At the moment I am trying to use freeRTOS to read and write at the same time but the problem is that when the audio plays, the mp3 file is only partially uploaded and only when the audio has finished playing does the mp3 get correctly uploaded. I have tested my testPlayAudio and testUploadFiletoSD functions and they work as expected without freeRTOS.

Not sure what is causing this issue but perhaps the SD card I am using only allows for half duplex SPI communication. I have thought about switching to an ESP32 as it has two cores but I am not sure if that will fix the issue. I am open to any suggestions. The SD card that I am using is SPI SMT 512M SD from Adafruit. In case it is needed I am using a I2S 3W Class D Amplifier Module DFRobot.

Here is the arduino code that I have been running. If any clarification is need please let me know.

#include <Arduino.h>

String _url = "http://";

void testUploadFileToSD(void);
void testPlayAudio(void* parameters);

TaskHandle_t xPlayAudioHandle = NULL;

void setup() {
    Serial.begin(115200);
    delay(3000);
    Serial.println("serial began...");
    //Code establishing wifi connection and server connection etc
}

void loop() {
    testUploadFileToSD();
}

void testPlayAudio(void* parameters) {
    for(;;) {
        Serial.println("About to initialise play audio");
        String filename = "/SD_welcome_files/welcome_file.mp3";
        AudioGeneratorMP3 *mp3 = new AudioGeneratorMP3();
        AudioFileSourceSD *file = new AudioFileSourceSD(filename.c_str());
        AudioOutputI2S *out = new AudioOutputI2S();
        Serial.println("MP3 initialised");
        Serial.println(filename.c_str());
        out->SetPinout(0, 1, 8);
        Serial.println("initialised audio i2s");

        mp3->begin(file, out);

        Serial.println("About to loop");
        bool looping = true;
        while (looping) {
            Serial.println("Looping");
            if (mp3->isRunning()) {
                if (!mp3->loop()) mp3->stop();
            } else {
                looping = false;
            }
            delay(20)    ;
        }
        Serial.println("Played");
        has_audio_finished_playing = true;
        vTaskDelete(NULL);
    }

void testUploadFileToSD(void) {
    Serial.println("Creating playing audio task");
    Serial.println();
    xTaskCreate(
    testPlayAudio,
    "Playing audio",
    4096,
    NULL,
    1,
    &xPlayAudioHandle);

    Serial.println("Uploading file to SD");
    uploadFileToSD("asdf.mp3", "/SD_welcome_files", "tmp");
    Serial.println("Waiting for audio to finish playing");
    playAudio("/SD_welcome_files/asdf.mp3");
    
    return;
}

void uploadFileToSD(String filename, String SD_path, String server_path) {
    String url = _url + "/api/get_file";
    String requestBody = "{\"filename\": \"" + filename + "\", \"server_path\": \"" + server_path + "\"}";
    Serial.println(requestBody);
    Serial.println("Uploading story file to the SD card");
    bool retryFlag = true;

    filename = SD_path + "/" + filename;
    Serial.print("SD path ");
    Serial.println(filename);

    while (retryFlag) {
        _http.begin(url.c_str());
        _http.setTimeout(10000);
        Serial.println(url);
        _http.setAuthorization(_api_email.c_str(), _api_key.c_str());
        
        _http.addHeader("Content-Type", "application/json");
        
        int uploadedBytes = 0;
        int httpResponseCode = _http.POST(requestBody);

        if (httpResponseCode > 0) {
            if (SD.exists(filename)) {
                SD.remove(filename);
            }
            File file = SD.open(filename, FILE_WRITE);
            
            int len = _http.getSize();
            Serial.print("Content size: ");
            Serial.println(len);

            uint8_t buff[2048] = { 0 };
            WiFiClient *stream = _http.getStreamPtr();

            while (_http.connected() && ((len > 0 || len == -1) && uploadedBytes != len)) {
                Serial.println("Uploading");
                while(!stream->available()) {
                    delay(1);
                }
                size_t size = stream->available();
                if (size) {
                    int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size));
                    size_t writtenBytes = 0;
                    writtenBytes = file.write(buff, c);

                    if (writtenBytes != c) {
                        Serial.print("File write error: ");
                        Serial.println(file.getWriteError());
                        Serial.println("Error writing to SD card");
                        break;
                    }
                    if (len > 0) {
                        len -= c;
                    }
                    uploadedBytes += c;
                }
            }

            if (uploadedBytes != _http.getSize()) {
                Serial.println("File not uploaded correctly, attempting to upload again");
                Serial.print(uploadedBytes);
                Serial.print(" ");
                Serial.println(len);
                retryFlag = true;  // Set the retry flag to attempt the request again
            } else {
                retryFlag = false; // Request succeeded, so no need to retry
            }

            file.close();
            Serial.println("Received all data");
        } 
        else {
            Serial.print("Error code: ");
            Serial.println(httpResponseCode);
        }
        _http.end();
    }
}


Welcome to the forum

Then I believe that you are going to be disappointed because you cannot open 2 files at the same time using the SD library

1 Like

I'm sure this isn't true because that code doesn't compile.

As you're doing that on an ESP32C3 you have enough memory to buffer both streams. So you open the existing file on the SD card, read 10kB in the memory, close it, open the newly created file for writing, write the amount of data already received and close it again. The bad news is that you might have to write lots of that software yourself because the libraries you used (we don't know which ones that are) might not have been developed with such a usage in mind.

Thanks so much. Here are the libraries that I used if its of any use.

#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <WiFi.h>
#include "AudioFileSourceSD.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2S.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <AsyncTCP.h>
#include "ESPAsyncWebServer.h"
#include <ESPmDNS.h>
#include <DNSServer.h>
#include "SPIFFS.h"
#include <Update.h>
#include <SPI.h>
#include <MFRC522.h>
#include <esp_task_wdt.h>

Perhaps there is a way to send the data faster over the server which would reduce the 3-5 delay? Here is the flask endpoint for that.

@app.route('/api/get_file', methods=["POST"])
@auth_required
def api_get_file():
    print("api_get_file")
    email = request.authorization.get('username')
    api_key = request.authorization.get('password')
    print(email + " " + api_key)
    data = request.json
    filename = data.get('filename')
    server_path = data.get('server_path')        
    print("Server path :" + str(server_path))
    print("File name in get story file" + str(filename))
    print(str(server_path) + "/" + str(filename))
    return_data = io.BytesIO()
    with open(f'{server_path}/{filename}', 'rb') as f:
        return_data.write(f.read())
    return_data.seek(0)
    return send_file(return_data, download_name=filename)```

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