In this video, we explore the disappointingly slow data writing speed of the ESP32 when reading and writing to an SD card in our TinyTV project. With 500 kilobytes/sec reading and a dismal 270 kilobytes/sec writing, we embark on an adventure to find a solution. After ditching the Arduino code in favor of IDF functions, we discover incredible improvements. Seeing potential risks, I propose a truly bonkers plan: using a IC to interface SD cards with USB with a USB multiplexer switch and another switch to alternate between ESP32 and the GL823. This could be a total disaster, but I'm game for the challenge. Stay tuned to see if it works out!
[0:38] Well, that’s a bit disappointing.
[0:40] Reading data is not going to set the world on fire, but it’s a respectable 500 kilobytes a second.
[0:46] Writing is absolutely terrible - 270 kilobytes per second.
[0:50] We’d be much better off just disconnecting the SD card from the ESP32 and plugging it in directly.
[0:56] Surely we can do better than this.
[0:58] We’re back looking at our TinyTV project.
[1:01] Thanks once again to the fantastic channel Patreons and thanks once again to PCBWay for
[1:05] manufacturing the PCB we’re using for this project and for supporting the channel - we’ll
[1:09] be getting some new boards from them shortly.
[1:12] So what’s going on?
[1:13] Why is this so slow?
[1:15] It’s certainly not a limitation of the SD card - if we plug it directly into my laptop
[1:19] we get 26 megabytes per second writing and 90 megabytes per second read-in.
[1:24] But we’re not plugging it directly into our computer - we’re running our ESP32 as a mass
[1:30] storage controller and connecting through that to the SD card.
[1:34] The ESP32-S3 only supports USB 1.1 full speed mode.
[1:39] This gives us a maximum transfer rate of 12 megabits per second over the USB connection.
[1:44] Theoretically this could give us 1.5 megabytes per second, but there are overheads in the
[1:49] USB protocol - it feels like 1 megabytes per second is probably much closer to what should be achievable.
[1:55] We’ve also got the limitations of our ESP32 connection to the SD card - we’re connecting
[2:00] to it using SPI and the actual clock speed this connects at will depend on several factors,
[2:06] but with the cards and the wiring I’m using I seem to get 20MHz reliably.
[2:11] With better wiring we might be able to get up to 40MHz, but for that we’ll need a proper
[2:15] PCB with an SD card slot built into it.
[2:18] Reading and writing raw sectors to the SD card, I get 1 megabytes per second write speed
[2:23] and 1.7 megabytes per second read speed, so it feels like 1 megabyte per second writing
[2:28] and 1 megabyte per second reading should be achievable.
[2:32] Now we can of course use SDIO in 4-bit mode to talk to our SD card - that involves some
[2:37] extra cheap I/O pins, but it does give us much better performance.
[2:41] If we connect up a card in this mode we get 2.34 megabytes per second for writing and
[2:45] an amazing 8.39 megabytes per second for reading - that’s not bad!
[2:51] So why is our speed so slow when we connect the card over USB through the ESP32?
[2:56] 270 kilobytes per second is almost a quarter of what we should be getting - it’s just not good enough.
[3:02] So to understand what’s going on we’re going to have to look at some code.
[3:06] The code is actually pretty simple.
[3:08] We use the USBMSC class - this just needs three callbacks - one for writing data, one
[3:14] for reading data and another that tells us when the card should be ejected.
[3:18] We initialise the USBMSC class by telling it how many sectors our card has and what size the sector is.
[3:24] When our callbacks are called we’re asked to either write or read data starting from a sector.
[3:29] The amount of data to read or write is always a multiple of the sector size.
[3:33] We write the data using the WriteRaw function and read the data using the ReadRaw functions on the Arduino SD object.
[3:40] These let you read and write a single sector.
[3:42] Unfortunately these methods don’t exist on the SDMMC class so we’re limited to SPI
[3:48] if we want to use the Arduino code.
[3:50] I logged out the actual size of the data we’re being asked to read and write and typically
[3:54] it’s 4096 bytes, which given our 512 byte sector size is 8 sectors.
[4:01] With the API we’ve got we have to call the ReadRaw and WriteRaw 8 times.
[4:05] But if we drill into the functions far enough we can see it’s actually possible to read
[4:10] and write multiple contiguous sectors at once.
[4:13] The code is all there for us, it’s just not exposed in a friendly way for us to use.
[4:18] Writing and reading sectors one at a time incurs a lot of unnecessary overhead.
[4:23] So I’ve thrown away the Arduino code and gone straight to the IDF functions.
[4:28] These are a couple of friendly functions, sdmmc_write_sectors and sdmmc_read_sectors.
[4:34] What’s more, these functions work in both SPI and SDIO mode.
[4:39] In SPI mode, using these functions to write and read the data all in one go, we improve the write performance by over 70%.
[4:45] It’s now writing at 480 kilobytes per second instead of 270 kilobytes per second.
[4:52] Our read speed has also improved by 32%.
[4:54] We’re getting 671 kilobytes per second instead of 507 kilobytes per second.
[5:01] If we use the SDIO 4-bit connection we get even better performance.
[5:05] We can now write at 0.66 megabytes per second and reading is an incredible 1 megabytes per second.
[5:11] That’s probably as fast as we can go over the USB 1.1 connection.
[5:14] It’s a pretty amazing improvement from some very simple code changes.
[5:19] Reading is now performing as far as I think is possible but it feels like writing could
[5:23] still be improved, so I thought I’d try something a bit dodgy.
[5:27] We don’t actually need to wait for the writing to the SD card to complete before we return.
[5:32] What if we ran the writing to the SD card in the background and returned straight away to the USB controller?
[5:37] We would lose any proper error handling and would need to be careful to wait for the writes
[5:41] to complete before allowing any reading, but we could get quite a performance improvement
[5:46] as the transfers to the SD card could be overlapped with the transfers coming from the USB connection.
[5:51] So I’ve given that a go, with the SPI connection we can now get almost 1 megabytes per second write performance.
[5:57] It’s actually 915 kilobytes per second which is 229% faster than our original 270 kilobytes
[6:04] per second and it’s 90% faster than our multi-sector writing method.
[6:09] With the SDIO 4-bit connection we get similar results, so I suspect we’ve definitely hit
[6:14] the limits of what’s possible with our USB connection.
[6:17] Obviously there’s no impact on the read speed, this stays pretty much exactly the
[6:21] same as our multi-sector read version.
[6:23] The big question we have to ask is, is this actually safe to use?
[6:27] And in answer, I really have no idea.
[6:30] We now have no error handling when writes fail so potentially we could get our card
[6:33] into quite a weird state, but lots of hard disk controllers do do buffered writing so maybe it’s fine.
[6:40] Is it good enough for our tiny TV project?
[6:43] Short videos, around 5 minutes or so, end up at 50 megabytes in size once they’ve
[6:47] been converted to AVI files.
[6:49] So to copy that to our SD card we’d need about 1 minute.
[6:53] That might be acceptable but to me it’s annoyingly slow, so we’re going to need some kind of alternative.
[6:59] The simple solution is just to let people take the SD card out and plug it directly into their PC.
[7:05] And that’s my fallback option, but I’ve got a slightly bonkers idea.
[7:08] There is a really cheap IC for interfacing SD cards with USB, the GL823.
[7:15] This will run over USB 2 at a whopping 480 megabits per second, which would give us much
[7:20] faster access to our SD card.
[7:22] I’ve also found a handy USB multiplexer switch which is also really cheap.
[7:27] And I found another switch IC that will let us switch the SD card between the ESP32 and the GL823.
[7:33] It’s got 6 lines so we could run the ESP32 in SDIO 4 bit mode if we wanted to.
[7:39] Now potentially this could completely fail, but I think it’s worth a try.
[7:44] The way I’m thinking this will work is that we’ll start off with the USB connection
[7:47] routed to the ESP32 and the GL823 powered down or held in reset mode.
[7:53] We’ll also have the SD card powered up and routed through to the ESP32 as well.
[7:57] If the user wants to connect to the SD card, we’ll shut down the ESP32’s USB peripheral
[8:02] and switch the USB connection over to the GL823.
[8:05] We’ll then turn the SD card off and connect it to the GL823 as well.
[8:10] We’ll then power up the GL823 and the SD card and in theory it should connect up to the computer.
[8:17] I can’t think of any way of switching off this mode, there doesn’t seem to be any
[8:20] signal from the GL823 to indicate that the device has been ejected, so we’ll have to
[8:24] rely on the user unplugging the USB.
[8:26] It’s a long shot, but I think this could work.
[8:30] The other option would be to transfer files using WiFi and write them directly to the
[8:33] SD card, but I really want to get the USB solution working now, so keep an eye out for a new video where I’ll build a new PCB.