STM32 Nucleo: access SD cards with C standard library

Posted on 2016/07/03

1


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.

Hardware

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.
Nucleo board wired with Ethernet shield.

Nucleo board wired with Ethernet shield.

Software stack

My goal is to use functions like fopen, fprintf and 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.

stdio_fatfs

Software stack to access SD card filesystem with C standard I/O functions.

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 _open and _write. For example, when fopen is called by the main program, ultimately newlib calls the _open function.

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 mkdir and readdir.

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 fopen and _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_read and 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.

381px-SPI_single_slave.svg

“SPI single slave” by en:User:Cburnett – Own workThis vector image was created with Inkscape.. Licensed under CC BY-SA 3.0 via Wikimedia Commons – https://commons.wikimedia.org/wiki/File:SPI_single_slave.svg#/media/File:SPI_single_slave.svg

Conclusion

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.

Posted in: Embedded