The ST Nucleo is an Arduino-like board with an STM32 as the microcontroller, and many Arduino shields can be mounted on it. I recently played with an Ethernet shield for the network connection, but since the shield also contains a microSD card slot, I wanted to access the SD card to read/write files. I’d like to access files as I would with a standard C program, so I put together a solution that does it using some existing libraries and some code that I wrote myself.
The Ethernet shield is a board with Arduino common connectors, an Ethernet port, a Wiznet chip (mine has W5100) and a microSD card slot. Both the Wiznet chip and the SD card are connected to the microcontroller with an SPI bus. The two targets have two different “chip select” signals connected to them, so that the microcontroller can choose which one to control. The SPI signals that go to the SD card are also reduced in voltage by resistive partitors, so that the 5V I/O pins of the Arduino Atmel chip can drive the 3.3V interface of SD cards. The STM32 microcontroller on the Nucleo board instead has 3.3V I/Os, so the same resistors will bring the voltage levels to around 2.2V; turns out this is enough to drive the SD card interface correctly. Note that for the Nucleo to adapt to the Ethernet shield I had to route three wires from the ICSP connector to the commmon Arduino connectors to bring the SPI signals to the correct places. This is explained in a previous blog post “Arduino Ethernet shield on STM32 Nucleo“, summarizing the wiring is:
- MISO: pin 1 of the ICSP to pin 12 of the Arduino connector,
- SCK: pin 3 of the ICSP to pin 13 of the Arduino connector,
- MOSI: pin 4 of the ICSP to pin 11 of the Arduino connector.
My goal is to use functions like
fscanf to access the SD card content, and the solution that I implemented is shown in the picture below. It’s a stack of libraries that talk to each other; some of them were already developed, some of them I made them myself (the colored boxes) to glue together the parts. I’m going to explain them from top to bottom.
Main program and C standard library
When anybody learns C, they make use of standard functions to access files. I wanted to write a solution that lets the same C program be compiled on something like a Linux system and also on a microcontroller such as STM32. The program should be compiled using a different toolchain, and should link different libraries. For the STM32, the toolchain that I used is GCC ARM Embedded for Cortex-M microprocessors. This toolchain provides the C standard library functions by the
newlib library. This library implements the functionalities that can be found in “
stdio.h” and other headers, and it is optimized for small systems. The low-level part of the
newlib implementation resides on Linux-like system calls such as
_write. For example, when
fopen is called by the
main program, ultimately
newlib calls the
System calls and FAT filesystem
In my case I don’t have an operating system running on STM32: the code runs on bare metal. For this reason, I had to develop these system calls myself. These system calls must provide ways to manage the file descriptors that are returned by
_open and passed to functions such as
_write to identify the file that should be accessed. So there are structure in this part of the stack that contain all the information needed to manage an open file, such as the access mode (read/write). I decided to write this part of the system following POSIX specifications, from the function prototype to the behavior and the error codes. In this way I didn’t have to invent anything and users might be more familiar with the way the code is working. With the same approach I also implemented some functions to manage directories, such as
When a file needs to be opened or its data need to be read or written, a filesystem is needed. The filesystem itself has the role of organizing the files on a disk. An SD card does not have the concept of files, it’s a contiguous memory space that can be read or written. It’s the filesystem that writes tables and pointers on this memory to implement the hierarchy of directories and their content. I selected FatFS, a library that manages FAT filesystems, because it works independently of the hardware or the operating system, and it’s exactly what I needed to transform the system calls to file system access. Specifically I am using version 0.11a at the time of my development. In fact, much of FatFS API is very similar to system calls, with functions like
f_open that accept the same parameters (to some extent) as
_open. My system calls in most cases just need to translate the input parameters, call the FatFS API and then translate the result and the return value. FAT is common for SD cards and it is supported by most operating systems such as Linux and Windows natively and for a long time. It has disadvantages such as the absence of links to files, but it is a trade-off for its simplicity. FatFS can be configured with a simple header, for example the filesystem can be configured as read-only to reduce the footprint of the library that won’t need the write functions anymore; as another example, the filenames can be restricted to be the classic 8 characters of file name and 3 characters of file extension.
Disk I/O and SPI communication
FatFS implements the FAT filesystem but needs an underlying layer to manage the actual hardware access as disk I/O. Each filesystem access is translated to an action that reads the disk or writes the disk, so there are low-level functions called
disk_write that must be provided. I had to write this code that specifically accesses SD cards through SPI. Note that down to this point the code was unaware that we were using SD cards or an SPI bus: it was hardware-independent C. In this layer of code I implemented the SD card protocol, described in some specifications such as the official SD standard Physical Layer Simplified Specification. These specifications contain both an SD bus mode and an SPI mode to access the card. While the SD bus mode is generally faster because it uses more I/Os, the SPI mode is enough to implement all the functionalities, and it’s what we have to use because of the hardware connection between the microcontroller and the card.
The SD card can be commanded using simple SPI transactions, so I needed to access the SPI peripheral of the STM32 to perform these transfers. The last library that I used is libopencm3, which provides low-level access to Cortex-M3 core and many peripherals. These SPI transactions pass through the wires that I connected on the boards and communicate the data back and forth between STM32 and memory card. The code has also to assert the chip select of the SD card during each transaction to indicate to the SD card that the data travelling through the SPI bus is directed to it.
All together, the collection of layers and libraries implement the file I/O that is expected to be available for common C programs. Each call to a C standard function will propagate downwards through all the library stack and perform accesses to the SD card, and the result of the operation will be propagated upwards to give to the main program the correct response.
The libraries and test code is currently available at my GitHub repository nucleo_tests.