0:09
Hello and welcome to Controllers Tech.
0:12
In the previous video, I covered how to
0:14
interface the SD card with STM32 using
0:17
the SPI peripheral and DMA,
0:20
demonstrating step by step how to set up
0:22
SPI, configure DMA transfers, and
0:25
communicate with the SD card directly
0:27
from the microcontroller. The library
0:30
supported all types of cards, standard
0:32
capacity, SDHC, SDXC, and everything
0:36
worked perfectly without compatibility
0:38
issues. We achieved a right speed of
0:41
around 194 kilobytes per second and a
0:45
read speed of 200 kilobytes per second
0:48
using DMA. Solid results for SPI
0:51
communication. Today we will see how to
0:54
use the SDIO peripheral to interface the
0:57
SD card offering a different often
0:59
faster method tailor for SD cards. Using
1:02
SDIO will improve the read and write
1:05
speeds drastically over SPI
1:07
communication because SDIO allows wider
1:10
data paths and higher clock rates. I
1:13
have already covered a video in the past
1:15
covering the SDIO peripheral, but a lot
1:18
of viewers complained that the 4-bit
1:20
mode was not working with it. So in
1:22
today's video we will fix that issue.
1:24
Today we will use the SDIO in 4bit mode
1:28
ensuring it initializes correctly and
1:30
runs reliably. We will test the SDIO
1:34
speed in one bit mode for bit mode and
1:37
also with the DMA enabled to compare
1:39
performance improvements step by step.
1:41
I'm going to use the STM32F446
1:45
custom development board from WAX Studio
1:47
for this project. It's a solid board
1:52
SDMMC hardware support. This board has a
1:55
slot for SD card at the back which
1:57
supports SDIO and SDMMC as well, meaning
2:01
it's wired correctly for all modes we
2:03
need. You can purchase this board in
2:05
India from the controllers tech store.
2:08
The link is in the video description.
2:10
Here is the schematic of the development
2:12
board so you can follow along with the
2:14
hardware reference. You can see the SDIO
2:17
pin connection at the bottom left corner
2:19
of the schematic. This shows exactly
2:22
which MCU pins wire to the slot. I will
2:25
configure the pins in CubeMX according
2:28
to this diagram to ensure electrical
2:30
connections match. Let's start the Cube
2:33
IDE and create a new project to get the
2:36
code framework set up. As I mentioned, I
2:39
am using an STM 32F446RE
2:43
microcontroller which has built-in SDIO
2:46
hardware. Let's give some name to the
2:48
project and click finish to generate the
2:51
files. We will start with the clock
2:53
configuration first because the SDIO
2:55
peripheral depends on proper system and
2:58
peripheral clock settings. I am enabling
3:01
the external crystal to provide the
3:03
clock reference. This board has 8 MHz
3:06
crystal on it and we will run the system
3:09
at maximum 180 MHz which is the highest
3:13
frequency for the MCU. Let's enable the
3:15
serial wired debug interface so we can
3:18
step through code during development and
3:20
debugging. Now go to the connectivity
3:22
section and enable the SDIO peripheral
3:25
in one bit mode to start with a simpler
3:28
configuration. Let's set it to one bit
3:30
for now. We'll switch to 4-bit mode
3:32
later in the video to compare timings.
3:35
This way, we will be able to test the
3:36
transfer speeds in both modes and
3:39
evaluate the performance difference.
3:42
Let's remap the SDIO clock pin to PC12.
3:46
That's exactly how it is wired in the
3:48
schematic. In one bit mode, only one
3:50
data pin is used, SDIOD0.
3:54
So, it simplifies initial testing. This
3:57
pin is connected to PC8 and it is
3:59
configured correctly by cube MX based on
4:02
a schematic. Other than this data zero
4:04
pin, there are also command and clock
4:07
pins required for SDIO to operate. There
4:10
is pin PA8 which is used to detect when
4:12
the SD card is inserted and we must set
4:15
this pin as a GPIO input so the MCU can
4:19
read its state. Let's configure the
4:21
peripheral parameters now to ensure
4:23
correct behavior. The clock transition
4:26
should be on the rising edge which is a
4:28
standard timing phase for SD card
4:30
communication. SDIO clock divider bypass
4:34
should be disabled so we can control
4:36
clock frequency precisely. Disable the
4:39
power saver feature for the SDIO clock
4:42
to maintain stable continuous operation.
4:45
Enable the hardware flow control to help
4:48
manage buffering and data flow under
4:50
heavy loads. The final parameter is the
4:52
clock divider factor. This determines
4:55
the actual SDIO bus speed. This is the
4:58
formula to calculate the SDIO clock
5:01
frequency based on the system clock and
5:03
divider value shown in cubamx. Let's set
5:06
the SDIO clock first to a safe test
5:09
level. The maximum clock allowed here is
5:12
48 MHz. Let me configure the divider
5:15
here to bring the SDIO clock within that
5:18
range. 36 MHz is fine for initial
5:22
testing. It's under 48 MHz and well
5:25
above low speed mode. Now the SDIO
5:28
peripheral clock is 36 MHz. If I use
5:31
clock divider zero, the actual SDIO bus
5:34
clock will be 18 MHz. The SDIO clock
5:38
should be set within 187 kHz and 24 MHz
5:43
during initialization phases. And we
5:46
have the clock within this specified
5:48
range. We will configure the DMA later
5:50
for testing the speed improvements it
5:53
provides over polling or interrupt
5:55
methods. Now we will configure the FAT
5:58
FS middleware to enable file system
6:00
access on the SD card. Enable the SD
6:04
card mode in FAT FS settings inside
6:07
CubMX. Do not check the userdefined
6:10
option since we will use the board
6:12
support package BSB drivers provided by
6:16
STM32 cube. I am leaving all other
6:19
options at their default values except
6:22
for enabling long file name usage. Let's
6:25
enable the usage of long file names with
6:27
a dynamic working buffer allocated on
6:29
the heap. This allows longer, more
6:32
flexible file names. The rest of the
6:35
configuration remains at default values
6:37
for simplicity. Now go to advanced
6:40
settings in the FAT FS section. Ensure
6:43
the SDIO instance is set to SDIO. We're
6:46
not using DMA yet and the BSP code is
6:50
set to generic. Now go to platform
6:52
settings and configure the SD card
6:54
detect pin to be PA8 matching the
6:57
hardware design. We configured pin PA8
7:01
earlier for SD detect. So set that here
7:04
so FAT FS knows how to check card
7:06
presence. That is all the configuration
7:08
we need. Click save to generate the
7:11
project and how driver code. Let's copy
7:14
the library files into the project from
7:16
the downloaded package. You can get
7:18
these files after downloading the
7:20
project from the link in the description
7:22
below this video. Place the files where
7:24
I am showing inside the src and ink
7:28
folders. The pre-generated code calls
7:30
the MX SDIO SD init function during
7:34
setup. Inside this function, it just
7:37
configures the SDIO peripheral. It does
7:40
not initialize the SD card itself. The
7:43
actual SDIO initialization happens in
7:47
the BSB driver SDC file. Here you can
7:50
see the function how SD innit is being
7:52
called inside the BSB SD innit routine.
7:56
The parameters for this SD
7:58
initialization are already configured in
8:01
the main file. So no additional manual
8:03
changes are needed. This BSB driver SDC
8:07
file also contains other functions to
8:09
drive the SD card like read blocks and
8:12
detect card presence. The SDSOC C file
8:16
contains the driver glue code for FAT FS
8:20
linking FAT FS API to the BSBSD drivers.
8:24
This file basically links the FAT FS
8:27
functions with the SD card BSB drivers.
8:30
So FC read and others work correctly.
8:33
Let's take a look at the library files
8:35
we copied. Now the SD functions C file
8:39
contains high-level functions to operate
8:41
on the SD card. It includes functions
8:44
like mount, unmount, write file, append
8:47
file, read file, read CSV, delete file,
8:52
and so on. We will test most of these
8:54
functions in today's tutorial to show
8:57
how they work in real projects. Note
8:59
that all these functions use the print f
9:02
function to print logs to the console.
9:05
So we need to enable ur to actually see
9:07
these printouts in the console. I am
9:10
using ur1 for this purpose as it's easy
9:13
to connect to serial via ft232.
9:20
is connected to the ft232's rx pin on
9:24
the board. This way the data transmitted
9:26
by UERT will be received by the FT232
9:31
and hence it will be shown on your
9:32
console window. Let's build the project
9:34
again now that UE is enabled. Now define
9:38
a custom write function like underscore
9:41
write to route the print f data via UR1.
9:45
Make sure to change the UR instance in
9:48
that function if you're using a
9:49
different UR port. Now we will test some
9:52
file operations on the SD card. Include
9:57
header in the main C file so we can call
10:00
those APIs. We will first mount the SD
10:02
card, then list the files on the SD
10:05
card, and finally unmount the card as a
10:07
basic test. Let's build and flash the
10:10
project to the board using STLink. Open
10:13
the serial console and make sure the
10:15
settings match the URT configuration in
10:18
cubmx baud rate bits par. Now insert the
10:22
SD card into the slot and reset the
10:25
board to start execution. You can see
10:27
the logs have been printed on the
10:28
console confirming each step. The SD
10:32
card is mounted successfully indicating
10:35
hardware and driver initialization work
10:38
well. The card size is shown as 16 GB in
10:42
this example. The card type is detected
10:44
as SDC standard capacity and the version
10:48
is V1 of the SD spec. The mounting
10:51
function terminates after printing the
10:54
card class information. Then the list of
10:56
files and folders present on the SD card
10:59
is shown. I have already created some
11:01
files and folders for test purposes and
11:04
they're being shown here in the output.
11:06
At last, the card was unmounted
11:08
successfully, completing the test cycle.
11:11
The mounting and unmounting works fine.
11:14
Now we will read a file from this SD
11:16
card. I will read file 5.txt
11:20
which is inside folder F1 F2. And that
11:23
folder itself is inside another folder
11:26
F1. Let's define a buffer to store 100
11:29
bytes of data and a variable to store
11:32
the number of bytes read from the file.
11:35
Now we will first mount the SD card
11:37
again. Then we will read data from file
11:39
5.txt and store it in the buffer we just
11:42
defined. The buffer is 100 bytes in size
11:46
and the number of bytes read will be
11:48
stored in the variable BR. Now print the
11:51
buffer contents to the console and then
11:54
unmount the SD card again. Let's build
11:56
and flash the project to the board for
11:59
this test. You can see on the console
12:01
that after mounting 30 bytes are read
12:03
from the file 5.txt file. Then it
12:06
printed the data from the file
12:08
correctly. And finally the SD card is
12:11
unmounted ending the read test. Let's
12:14
include the standard IO header file
12:16
styio.h. So the print f calls work
12:20
properly. Now we will write data to a
12:22
file on the SD card. First mount the SD
12:25
card. Then call the function sdrite file
12:29
to write data to file 8.txt. If this
12:32
file is not present on the SD card
12:35
already, the function will first create
12:37
it and then write the data to it. Then
12:39
we will read this file back and store
12:41
its data in the buffer we created
12:43
earlier. After reading it, print the
12:46
buffer to the console and unmount the
12:48
card. Let's build and test this project
12:51
again to verify the right process. As
12:54
you can see in the console, after
12:56
mounting the card, 52 bytes were written
13:01
the file is read back and it is then
13:03
printed on the console to confirm the
13:05
content. So, the right operation works
13:08
just fine. It's reliable and repeatable.
13:11
Let's test appending data to an existing
13:14
file. Next, call the function sd append
13:17
file to append additional data to file
13:22
We will again read the file back and
13:24
print the updated data on the console.
13:27
Let's build and flash the project to the
13:29
board for this test. You'll see here
13:31
that 31 bytes are appended to file
13:36
The file was originally 52 bytes in size
13:39
and now with appended data is 83 bytes
13:43
total. This is the data read from the
13:46
file containing both the original and
13:49
the appended content. So appending data
13:52
to the file works fine as well. We've
13:54
verified that functionality. Now we will
13:57
read a CSV file from the SD card. The SD
14:01
functions C file contains a separate
14:04
function called SD read CSV to parse CSV
14:08
data. Call the function SD read CSV to
14:11
read the CSV file. I have already stored
14:14
a CSV file named file forth. CSV inside
14:18
the F1 F2 folder on the SD card. Provide
14:21
the correct path to this file when
14:24
calling the function. There are some
14:26
additional parameters to this function
14:27
as well. You need to pass a CSV record
14:30
structure pointer, a max records limit,
14:33
and get back record count. Let's
14:35
understand these parameters first before
14:37
calling it. CSV records is a structure
14:40
that contains two fields to store data
14:42
from two columns in each row. If your
14:45
CSV file has more or fewer columns, you
14:48
can modify this structure accordingly.
14:50
The variable max records defines the
14:53
maximum number of rows the function will
14:55
read from the file. And record count
14:58
stores how many rows were actually read
15:01
during the operation. Let's define these
15:03
parameters in the main function first
15:05
before using the function. The CSV file
15:08
I copied on the SD card contains 16
15:11
rows. So I am defining max records
15:14
equals 20. Now define a CSV records
15:17
array to store the data read from the
15:19
file and a variable to hold the number
15:22
of rows read. Let's pass these variables
15:24
to the SD read CSV function for
15:27
processing. This function itself prints
15:29
the files data to the console. So we do
15:32
not need to manually print f each entry.
15:35
Let's go ahead and debug the project now
15:38
to see how the data is stored inside the
15:41
CSV record structure. Let me put a break
15:43
point inside the while loop of the CSV
15:46
reader function and run the debugger.
15:48
The breakpoint hits, but there was no
15:51
data on the console initially. The log
15:53
output shows that it failed to open the
15:55
file. Turns out I named incorrectly. All
15:58
right, let's fix that file name and
16:01
launch the debugger again. We have hit
16:03
the break point again. And this time you
16:05
can see the record count is 16 meaning
16:08
the function has read all 16 rows. Here
16:11
you can see the data from the CSV file
16:13
printed in the console output. There are
16:15
16 rows in total index from 0 to 15. The
16:20
lines 01 02 03 correspond to row numbers
16:26
and columns while 1 C 2 3 N are actual
16:31
cell data. Here is how the data is
16:33
stored inside the CSV file on the card.
16:36
We have a total of 16 rows with two
16:39
columns each. And this is exactly what
16:42
is printed to the console. Let's see how
16:44
the data is stored inside the CSV record
16:47
structure. Note that I am viewing the
16:49
data in the variables tab of the
16:51
debugger, not the live expressions.
16:54
These variables are defined locally, so
16:57
they appear automatically when the
16:58
debugger pauses in that scope. The field
17:01
one member contains data from the first
17:03
column and field two contains data from
17:06
the second column. The data is stored in
17:09
character format which makes it suitable
17:11
for display or transmission. So reading
17:14
the CSV file works correctly and
17:16
cleanly. We have now tested several of
17:19
the functions available in the sd_f
17:22
functions c file and you can test the
17:25
others as well for more functionality.
17:27
Let's test the transfer speed in one bit
17:29
mode now and later we will do the same
17:32
for 4bit mode with and without DMA. The
17:37
C file can be used to benchmark SD card
17:40
performance. You can adjust the file
17:42
size used in the test. I am configuring
17:45
it to 1 megabyte for accurate results.
17:48
So we will write a 1 megabyte binary
17:50
file to the card and then read it back
17:52
to measure transfer speed. The write and
17:55
read speeds will be calculated based on
17:57
this roundtrip transfer. Include the
18:02
header file inside main. C to access the
18:06
function. Now call the function
18:09
inside the main function. The mounting
18:12
and unmounting of the SD card will be
18:15
handled by the benchmark function
18:16
itself. So we don't need separate calls.
18:19
Let's build and flash the project to the
18:22
board for benchmarking. As you can see
18:24
on the console, we are able to achieve
18:26
around 590 kilobytes per second right
18:29
speed and 1.4 megabytes per second read
18:32
speed during the test. Let me test it a
18:35
few more times to ensure consistency in
18:38
results. We can conclude that the right
18:40
speed is around 600 kilobytes per second
18:43
and the read speed is around 1.4
18:46
megabytes per second in one bit SDIO
18:48
mode. This is way higher than what we
18:50
previously achieved using the SPI
18:53
interface. We will now enable the 4bit
18:56
mode for the SDIO peripheral and test
18:58
the speeds again to see the improvement.
19:01
Open Cube Max via Cube ID. Go to the
19:04
SDIO section and change the mode to 4bit
19:08
wide bus. This mode uses four data
19:11
lines. So we need to reassign pins
19:14
according to the hardware schematic. Let
19:16
me change the pins. Now, as per the
19:18
schematic, PC8, PC9, PC 10, and PC11 are
19:23
the four data pins. PC12 is the clock
19:26
and PD2 is the command line. We do not
19:29
need to modify other parameter
19:32
configuration. Click save to regenerate
19:34
the project again. Assume that we'll
19:36
test the SD card with this default
19:39
generated setup. Let's build and flash
19:41
the board, then test again. As you can
19:44
see on the console, the mount function
19:46
fails immediately in 4-bit mode by
19:48
default. This is because the SD card
19:51
isn't able to initialize directly in
19:54
4bit mode. It requires one bit
19:57
initialization first. Let's take a look
20:02
init function in code. Here the SDI
20:05
peripheral is configured for a 4bit wide
20:07
bus before any card initialization
20:10
happens causing the failure. As I
20:13
already mentioned, the actual
20:14
initialization of the SD card takes
20:17
place in the BSB driver SDC file. And if
20:20
you note inside that file, the driver
20:22
enables wide bus operation only after
20:25
the card has been initialized in one bit
20:27
mode. So our mounting fails at the SD
20:30
initialization function itself because
20:33
the SD card won't initialize in 4bit
20:36
mode directly. Instead, we need to first
20:40
initialize it in one bit wide mode and
20:43
then let the driver configure the bus to
20:45
4bit afterward. If you set it to one bit
20:47
mode here in cube max, it will get
20:50
overwritten when the project is
20:51
regenerated. It is better to enforce it
20:54
in the user section of the MX SDIO SD
20:58
init function so CubeMX doesn't
21:01
overwrite it. This is the only
21:02
modification we need to make the 4bit
21:05
mode work reliably. Let's build and
21:08
flash the project again to test this
21:10
fix. You can see the card has
21:12
initialized successfully this time and
21:14
we get higher read and write speeds. Let
21:17
me test it a few more times to confirm
21:20
stability. We can say that in 4-bit mode
21:23
we are able to achieve around 700
21:25
kilobytes per second right speed and 2
21:28
megabytes per second read speed.
21:31
Significantly improved compared to one
21:33
bit mode. To make 4-bit mode work, you
21:36
must initialize in one bit mode first.
21:39
Then let the function configure wide bus
21:41
operation be called afterward. Now for
21:44
the final test, we will also add DMA to
21:47
the 4bit mode configuration. Open CubMX
21:50
again. Go to the SDIO section and then
21:53
the DMA settings. You can enable DMA for
21:56
specific transmit or receive operations
22:00
or simply select SDIO to enable both
22:03
channels automatically. The mode should
22:05
be set to peripheral flow control, FIFO
22:08
threshold set to full, data width to
22:11
word, and bur size to four for optimal
22:14
speed. Now go to the MVC tab and make
22:17
sure to enable the SDIO global
22:19
interrupt. This is necessary or else
22:22
SDIOD DMA rights won't work properly.
22:25
Now go back to the FAT FS section. Open
22:28
the advanced settings tab and enable the
22:30
DMA template for FATF FS. That is all we
22:34
need to enable for the DMA setup. Click
22:36
save to regenerate the project once
22:39
more. Even after regenerating the
22:41
modified SDIO initialization remains
22:44
intact because we placed it in a user
22:47
section. That is why we define that
22:49
change manually so it won't be lost
22:52
during code regeneration. We do not need
22:54
to modify the test files or benchmark
22:56
code. Let's build and run the project
22:59
again. You can see the write and read
23:01
speeds have noticeably increased with
23:03
the use of DMA. Now let me test it again
23:06
to confirm the results. we can say that
23:09
we are able to achieve around 740
23:12
kilobytes per second right speed and
23:14
around 2.5 megabytes per second read
23:17
speed with DMA enabled in 4bit mode. So
23:21
in summary, we saw how to interface the
23:23
SD card using SDIO in one bit mode, then
23:27
in 4bit mode, and finally in 4bit mode
23:30
with DMA for best performance. The read
23:33
and write speed using SDIO is way higher
23:36
than what we achieved with SPI and it
23:38
improves further with 4bit mode and DMA
23:41
enabled. You can also test the rest of
23:43
the convenient functions present in SDOR
23:48
like file deletion, CSV parsing and
23:51
directory listing. You can download the
23:53
full project including all source files
23:56
from the link in the description below
23:58
this video. The library files are
24:00
located inside the src and inc folders
24:04
of the project directory. We will
24:06
continue this series in the next video
24:08
where we will use the sdmmc peripheral
24:10
to interface with the SD card and
24:13
further improve performance. That is it
24:15
for this video. Leave comments below if
24:17
you have any doubts or suggestions.
24:20
Thanks for watching. Keep learning and
24:22
have a nice day ahead.