Writing to a microSD card

Most MCUs have very little onboard EEPROM (typically a few kBs). For a standalone device that requires more than this, such as for recording large amounts of data, external storage is required. A convenient solution for this is the use of microSD cards.

In this article, data was written by an ATmega328P to a microSD card. To do this, Roland Riegel's SD-reader library was used (see html). In fact, this library (and the lack of a similar library for PIC MCUs) was one of the main reasons AVR MCUs were originally pursued.

MCU selection

The MCU requirements were, at a miminum:

  1. 16K flash to accomodate the SD-reader library (6.5KB) and other code and
  2. preferably a USART module.

The ATtiny167 and ATmega168P/328P MCUs were considered. While the ATtiny167 MCU seemed better suited to collecting data, they were not suited to writing to SD cards. Ultimately, the ATmega328P was chosen because it was supported by the SD-reader library and cost less than the ATmega168P for the associated increase in flash, EEPROM and SRAM. While it may be possible to modify the SD-reader library to support the ATtiny167, the effort did not justify the cost saving (about USD4).

Parts list

For this project, the following parts were used:

  • an ATmega328P MCU
  • a development board: Boarduino (see html)
  • a programmer: USBasp clone, sold by Mengjin Su as a kit (see html)
  • a microSD breakout board (see html)
  • a full-size breadboard
  • a power supply (see power_supply_breakout)
  • 8GB microSD card
  • USB microSD card reader
  • FTDI USB-to-Serial cable

ATmega328p pin mapping

ATmega328p pin mapping

Figure 1: ATmega328p pin mapping

Note that the pins on the boarduino do not match up direclty to those on the MCU. Instead, use the silkscreened numbers to identify the register each pin corresponds to.

SPI communication

The SD-reader library uses SPI to communicate with the microSD card. This involves four pins:

bit pin label description
PB5 13 SCK SPI bus master clock input
PB4 12 MISO SPI bus master output/slave input
PB3 11 MOSI SPI bus master output/slave input
PB2 10 SS SPI bus master slave input

These pins were connected to CLK, DO, DI, CS pins on the microSD card breakout board respectively. The 5V and GND pins were also wired up (see Figure 2).

microSD card breakout wiring

Figure 2: MicroSD card breakout wiring

SPI is a type of serial communication. It provides high-speed synchronous data transfer between multiple devices. A clock signal (SCK) synchronises the data tx/rx and the SS pin is used to select the direction of the data. The data itself is transferred using the MISO and MOSI bits.

Init, send and receive functions for SPI communication can be found in the ATmega328p datasheet. These are implemented in the sd_raw.* files in Roland's library. Unfortunately, the SPI init functions are not separated out from the SD card initialisation. The code will need some work to be able to transmit data between multiple devices on the same bus.

Program description

The SD-reader library main routine produces a kind of shell that allows basic FAT file commands to be entered. It did not work using CuteCom on GNU/Linux, so a new main routine was written. The remainder of the library was not changed.

The new main routine was composed of C-style functions. These performed basic functions such as changing directories, listing a directory, creating a file, appending to a file, and printing a file. Each of these functions were tested and the output displayed on a terminal via the FTDI USB-to-Serial cable. Ultimately the program should write useful data, such as that from a sensor, to file.

Debugging

Some of the problems encountered while writing the new main routine are listed below:

  1. MMC/SD initialization failed
  2. Could not list directory
  3. Could not locate file
  4. Could not print file
  5. Could not append to file

Each of these were solved in turn. The first was simply that the SD card needed to be inserted after powering on the MCU. The second was more difficult, as the SD card was identified correctly. After a lot of effort it was found that the SD card needed to be reformatted with fat32. A 4GB partition was used, but this may not be required. The third was due to not seeking to the beginning of the directory after every listing. The fourth was due to not correctly opening and closing the file for each loop (possibly also related to 1). The fifth was due to correctly seeking within a file and passing the correct string length.

Traditionally in C, a function returns "1" on error. Roland does the opposite with the functions in this library, that is, they return "0" on error. This also led to some fun problems. The library was not modified such that future revisions could also be used.

Results

The after initialising the SD card and opening the partition and filesystem, the main routine loops over the following operations:

  1. list root directory
  2. print file position and data length of string to write to UART
  3. write "blah blah\n" to file "test.log"

The output using a custom Python serial terminal is shown below:

# Settings:
#   port:     '/dev/ttyUSB0'
#   baud:     9600
#   parity:   'N'
#   stopbits: 1
#   bytesize: 8
# Connection open: True
# -------------------------------
                                     0
test.log                             55
file position = 55, data_len = 10
                                     0
test.log                             65
file position = 65, data_len = 10
                                     0
test.log                             75
file position = 75, data_len = 10
                                     0
test.log                             85
file position = 85, data_len = 10
                                     0
test.log                             95
file position = 95, data_len = 10
                                     0
test.log                             105
file position = 105, data_len = 10
                                     0
test.log                             115
file position = 115, data_len = 10
^C
# Done.

As can be seen, the file "test.log" increases by 10 bytes after each write. The file was also checked on a PC using the microSD card reader.

Discussion

The data is in no way useful at the moment, but the ability to write data has been tested. At this point, useful data such as that measured in the article temperature_logging can be stored local to the sensor.

To be even more useful, this sort of data should be time-stamped. For this, a real-time clock is needed. A real-time clock in turn requires a separate crystal, battery, library and interrupt routine. This will be the subject of a future article.

Conclusion

Data was written by an ATmega328p to a microSD card using Roland Riegel's SD-reader library.

Update: Changing the library and increasing the bandwidth

The block size on a fat32 formatted SD card is typically 512 bytes. SD cards are not byte addressable, so for every partial sector access operation (one byte up to the sector size of 512 bytes), the entire sector needs to be accessed. For example, reading a sector byte-by-byte, the sector will be read 512 times. The minimum busy time achievable is by reading or writing 512 bytes at a time (the size of a sector), so that the sector is only accessed once. Therefore a data buffer of 512 bytes should be used.

While Roland Riegel's library worked, and has been a very useful learning tool, there are two more SD card libraries for AVR MCUs that are well maintained

  1. FatFS (html) and
  2. Petit FatFS (html).

The smaller of the two, Petit FatFS, will be pursued first.

cc:by-nc-sa