/* * Copyright (c) 2006-2012 by Roland Riegel * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include "fat.h" #include "fat_config.h" #include "partition.h" #include "sd_raw.h" #include "sd_raw_config.h" #include "uart.h" #define DEBUG 1 /** * \mainpage MMC/SD/SDHC card library * * This project provides a general purpose library which implements read and write * support for MMC, SD and SDHC memory cards. * * It includes * - low-level \link sd_raw MMC, SD and SDHC read/write routines \endlink * - \link partition partition table support \endlink * - a simple \link fat FAT16/FAT32 read/write implementation \endlink * * \section circuit The circuit * The circuit which was mainly used during development consists of an Atmel AVR * microcontroller with some passive components. It is quite simple and provides * an easy test environment. The circuit which can be downloaded on the * project homepage has been * improved with regard to operation stability. * * I used different microcontrollers during development, the ATmega8 with 8kBytes * of flash, and its pin-compatible alternative, the ATmega168 with 16kBytes flash. * The first one is the one I started with, but when I implemented FAT16 write * support, I ran out of flash space and switched to the ATmega168. For FAT32, an * ATmega328 is required. * * The circuit board is a self-made and self-soldered board consisting of a single * copper layer and standard DIL components, except of the MMC/SD card connector. * * The connector is soldered to the bottom side of the board. It has a simple * eject button which, when a card is inserted, needs some space beyond the connector * itself. As an additional feature the connector has two electrical switches * to detect wether a card is inserted and wether this card is write-protected. * * \section pictures Pictures * \image html pic01.jpg "The circuit board used to implement and test this application." * \image html pic02.jpg "The MMC/SD card connector on the soldering side of the circuit board." * * \section software The software * The software is written in C (ISO C99). It might not be the smallest or * the fastest one, but I think it is quite flexible. See the project's * benchmark page to get an * idea of the possible data rates. * * I implemented an example application providing a simple command prompt which is accessible * via the UART at 9600 Baud. With commands similiar to the Unix shell you can browse different * directories, read and write files, create new ones and delete them again. Not all commands are * available in all software configurations. * - cat \\n * Writes a hexdump of \ to the terminal. * - cd \\n * Changes current working directory to \. * - disk\n * Shows card manufacturer, status, filesystem capacity and free storage space. * - init\n * Reinitializes and reopens the memory card. * - ls\n * Shows the content of the current directory. * - mkdir \\n * Creates a directory called \. * - mv \ \\n * Renames \ to \. * - rm \\n * Deletes \. * - sync\n * Ensures all buffered data is written to the card. * - touch \\n * Creates \. * - write \ \\n * Writes text to \, starting from \. The text is read * from the UART, line by line. Finish with an empty line. * * \htmlonly *

* The following table shows some typical code sizes in bytes, using the 20090330 release with a * buffered read-write MMC/SD configuration, FAT16 and static memory allocation: *

* * * * * * * * * * * * * * * * * * * * * * *
layercode sizestatic RAM usage
MMC/SD2410518
Partition45617
FAT167928188
* *

* The static RAM is mostly used for buffering memory card access, which * improves performance and reduces implementation complexity. *

* *

* Please note that the numbers above do not include the C library functions * used, e.g. some string functions. These will raise the numbers somewhat * if they are not already used in other program parts. *

* *

* When opening a partition, filesystem, file or directory, a little amount * of RAM is used, as listed in the following table. Depending on the library * configuration, the memory is either allocated statically or dynamically. *

* * * * * * * * * * * * * * * * * * * * * * *
descriptordynamic/static RAM
partition17
filesystem26
file53
directory49
* * \endhtmlonly * * \section adaptation Adapting the software to your needs * The only hardware dependent part is the communication layer talking to the * memory card. The other parts like partition table and FAT support are * completely independent, you could use them even for managing Compact Flash * cards or standard ATAPI hard disks. * * By changing the MCU* variables in the Makefile, you can use other Atmel * microcontrollers or different clock speeds. You might also want to change * the configuration defines in the files fat_config.h, partition_config.h, * sd_raw_config.h and sd-reader_config.h. For example, you could disable * write support completely if you only need read support. * * For further information, visit the project's * FAQ page. * * \section bugs Bugs or comments? * If you have comments or found a bug in the software - there might be some * of them - you may contact me per mail at feedback@roland-riegel.de. * * \section acknowledgements Acknowledgements * Thanks go to Ulrich Radig, who explained on his homepage how to interface * MMC cards to the Atmel microcontroller (http://www.ulrichradig.de/). * I adapted his work for my circuit. * * \section copyright Copyright 2006-2012 by Roland Riegel * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2 as published by * the Free Software Foundation (http://www.gnu.org/copyleft/gpl.html). * At your option, you can alternatively redistribute and/or modify the following * files under the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation (http://www.gnu.org/copyleft/lgpl.html): * - byteordering.c * - byteordering.h * - fat.c * - fat.h * - fat_config.h * - partition.c * - partition.h * - partition_config.h * - sd_raw.c * - sd_raw.h * - sd_raw_config.h * - sd-reader_config.h */ static uint8_t read_line(char* buffer, uint8_t buffer_length); static uint32_t strtolong(const char* str); static uint8_t find_file_in_dir(struct fat_fs_struct* fs, struct fat_dir_struct* dd, const char* name, struct fat_dir_entry_struct* dir_entry); static struct fat_file_struct* open_file_in_dir(struct fat_fs_struct* fs, struct fat_dir_struct* dd, const char* name); static uint8_t print_disk_info(const struct fat_fs_struct* fs); int main() { /* we will just use ordinary idle mode */ set_sleep_mode(SLEEP_MODE_IDLE); /* setup uart */ uart_init(); while(1) { /* setup sd card slot */ if(!sd_raw_init()) { #if DEBUG uart_puts_p(PSTR("MMC/SD initialization failed\n")); #endif continue; } /* open first partition */ struct partition_struct* partition = partition_open(sd_raw_read, sd_raw_read_interval, #if SD_RAW_WRITE_SUPPORT sd_raw_write, sd_raw_write_interval, #else 0, 0, #endif 0 ); if(!partition) { /* If the partition did not open, assume the storage device * is a "superfloppy", i.e. has no MBR. */ partition = partition_open(sd_raw_read, sd_raw_read_interval, #if SD_RAW_WRITE_SUPPORT sd_raw_write, sd_raw_write_interval, #else 0, 0, #endif -1 ); if(!partition) { #if DEBUG uart_puts_p(PSTR("opening partition failed\n")); #endif continue; } } /* open file system */ struct fat_fs_struct* fs = fat_open(partition); if(!fs) { #if DEBUG uart_puts_p(PSTR("opening filesystem failed\n")); #endif continue; } /* open root directory */ struct fat_dir_entry_struct directory; fat_get_dir_entry_of_path(fs, "/", &directory); struct fat_dir_struct* dd = fat_open_dir(fs, &directory); if(!dd) { #if DEBUG uart_puts_p(PSTR("opening root directory failed\n")); #endif continue; } /* print some card information as a boot message */ print_disk_info(fs); /* provide a simple shell */ char buffer[24]; while(1) { /* print prompt */ uart_putc('>'); uart_putc(' '); /* read command */ char* command = buffer; if(read_line(command, sizeof(buffer)) < 1) continue; /* execute command */ if(strcmp_P(command, PSTR("init")) == 0) { break; } else if(strncmp_P(command, PSTR("cd "), 3) == 0) { command += 3; if(command[0] == '\0') continue; /* change directory */ struct fat_dir_entry_struct subdir_entry; if(find_file_in_dir(fs, dd, command, &subdir_entry)) { struct fat_dir_struct* dd_new = fat_open_dir(fs, &subdir_entry); if(dd_new) { fat_close_dir(dd); dd = dd_new; continue; } } uart_puts_p(PSTR("directory not found: ")); uart_puts(command); uart_putc('\n'); } else if(strcmp_P(command, PSTR("ls")) == 0) { /* print directory listing */ struct fat_dir_entry_struct dir_entry; while(fat_read_dir(dd, &dir_entry)) { uint8_t spaces = sizeof(dir_entry.long_name) - strlen(dir_entry.long_name) + 4; uart_puts(dir_entry.long_name); uart_putc(dir_entry.attributes & FAT_ATTRIB_DIR ? '/' : ' '); while(spaces--) uart_putc(' '); uart_putdw_dec(dir_entry.file_size); uart_putc('\n'); } } else if(strncmp_P(command, PSTR("cat "), 4) == 0) { command += 4; if(command[0] == '\0') continue; /* search file in current directory and open it */ struct fat_file_struct* fd = open_file_in_dir(fs, dd, command); if(!fd) { uart_puts_p(PSTR("error opening ")); uart_puts(command); uart_putc('\n'); continue; } /* print file contents */ uint8_t buffer[8]; uint32_t offset = 0; intptr_t count; while((count = fat_read_file(fd, buffer, sizeof(buffer))) > 0) { uart_putdw_hex(offset); uart_putc(':'); for(intptr_t i = 0; i < count; ++i) { uart_putc(' '); uart_putc_hex(buffer[i]); } uart_putc('\n'); offset += 8; } fat_close_file(fd); } else if(strcmp_P(command, PSTR("disk")) == 0) { if(!print_disk_info(fs)) uart_puts_p(PSTR("error reading disk info\n")); } #if FAT_WRITE_SUPPORT else if(strncmp_P(command, PSTR("rm "), 3) == 0) { command += 3; if(command[0] == '\0') continue; struct fat_dir_entry_struct file_entry; if(find_file_in_dir(fs, dd, command, &file_entry)) { if(fat_delete_file(fs, &file_entry)) continue; } uart_puts_p(PSTR("error deleting file: ")); uart_puts(command); uart_putc('\n'); } else if(strncmp_P(command, PSTR("touch "), 6) == 0) { command += 6; if(command[0] == '\0') continue; struct fat_dir_entry_struct file_entry; if(!fat_create_file(dd, command, &file_entry)) { uart_puts_p(PSTR("error creating file: ")); uart_puts(command); uart_putc('\n'); } } else if(strncmp_P(command, PSTR("mv "), 3) == 0) { command += 3; if(command[0] == '\0') continue; char* target = command; while(*target != ' ' && *target != '\0') ++target; if(*target == ' ') *target++ = '\0'; else continue; struct fat_dir_entry_struct file_entry; if(find_file_in_dir(fs, dd, command, &file_entry)) { if(fat_move_file(fs, &file_entry, dd, target)) continue; } uart_puts_p(PSTR("error moving file: ")); uart_puts(command); uart_putc('\n'); } else if(strncmp_P(command, PSTR("write "), 6) == 0) { command += 6; if(command[0] == '\0') continue; char* offset_value = command; while(*offset_value != ' ' && *offset_value != '\0') ++offset_value; if(*offset_value == ' ') *offset_value++ = '\0'; else continue; /* search file in current directory and open it */ struct fat_file_struct* fd = open_file_in_dir(fs, dd, command); if(!fd) { uart_puts_p(PSTR("error opening ")); uart_puts(command); uart_putc('\n'); continue; } int32_t offset = strtolong(offset_value); if(!fat_seek_file(fd, &offset, FAT_SEEK_SET)) { uart_puts_p(PSTR("error seeking on ")); uart_puts(command); uart_putc('\n'); fat_close_file(fd); continue; } /* read text from the shell and write it to the file */ uint8_t data_len; while(1) { /* give a different prompt */ uart_putc('<'); uart_putc(' '); /* read one line of text */ data_len = read_line(buffer, sizeof(buffer)); if(!data_len) break; /* write text to file */ if(fat_write_file(fd, (uint8_t*) buffer, data_len) != data_len) { uart_puts_p(PSTR("error writing to file\n")); break; } } fat_close_file(fd); } else if(strncmp_P(command, PSTR("mkdir "), 6) == 0) { command += 6; if(command[0] == '\0') continue; struct fat_dir_entry_struct dir_entry; if(!fat_create_dir(dd, command, &dir_entry)) { uart_puts_p(PSTR("error creating directory: ")); uart_puts(command); uart_putc('\n'); } } #endif #if SD_RAW_WRITE_BUFFERING else if(strcmp_P(command, PSTR("sync")) == 0) { if(!sd_raw_sync()) uart_puts_p(PSTR("error syncing disk\n")); } #endif else { uart_puts_p(PSTR("unknown command: ")); uart_puts(command); uart_putc('\n'); } } /* close directory */ fat_close_dir(dd); /* close file system */ fat_close(fs); /* close partition */ partition_close(partition); } return 0; } uint8_t read_line(char* buffer, uint8_t buffer_length) { memset(buffer, 0, buffer_length); uint8_t read_length = 0; while(read_length < buffer_length - 1) { uint8_t c = uart_getc(); if(c == 0x08 || c == 0x7f) { if(read_length < 1) continue; --read_length; buffer[read_length] = '\0'; uart_putc(0x08); uart_putc(' '); uart_putc(0x08); continue; } uart_putc(c); if(c == '\n') { buffer[read_length] = '\0'; break; } else { buffer[read_length] = c; ++read_length; } } return read_length; } uint32_t strtolong(const char* str) { uint32_t l = 0; while(*str >= '0' && *str <= '9') l = l * 10 + (*str++ - '0'); return l; } uint8_t find_file_in_dir(struct fat_fs_struct* fs, struct fat_dir_struct* dd, const char* name, struct fat_dir_entry_struct* dir_entry) { while(fat_read_dir(dd, dir_entry)) { if(strcmp(dir_entry->long_name, name) == 0) { fat_reset_dir(dd); return 1; } } return 0; } struct fat_file_struct* open_file_in_dir(struct fat_fs_struct* fs, struct fat_dir_struct* dd, const char* name) { struct fat_dir_entry_struct file_entry; if(!find_file_in_dir(fs, dd, name, &file_entry)) return 0; return fat_open_file(fs, &file_entry); } uint8_t print_disk_info(const struct fat_fs_struct* fs) { if(!fs) return 0; struct sd_raw_info disk_info; if(!sd_raw_get_info(&disk_info)) return 0; uart_puts_p(PSTR("manuf: 0x")); uart_putc_hex(disk_info.manufacturer); uart_putc('\n'); uart_puts_p(PSTR("oem: ")); uart_puts((char*) disk_info.oem); uart_putc('\n'); uart_puts_p(PSTR("prod: ")); uart_puts((char*) disk_info.product); uart_putc('\n'); uart_puts_p(PSTR("rev: ")); uart_putc_hex(disk_info.revision); uart_putc('\n'); uart_puts_p(PSTR("serial: 0x")); uart_putdw_hex(disk_info.serial); uart_putc('\n'); uart_puts_p(PSTR("date: ")); uart_putw_dec(disk_info.manufacturing_month); uart_putc('/'); uart_putw_dec(disk_info.manufacturing_year); uart_putc('\n'); uart_puts_p(PSTR("size: ")); uart_putdw_dec(disk_info.capacity / 1024 / 1024); uart_puts_p(PSTR("MB\n")); uart_puts_p(PSTR("copy: ")); uart_putw_dec(disk_info.flag_copy); uart_putc('\n'); uart_puts_p(PSTR("wr.pr.: ")); uart_putw_dec(disk_info.flag_write_protect_temp); uart_putc('/'); uart_putw_dec(disk_info.flag_write_protect); uart_putc('\n'); uart_puts_p(PSTR("format: ")); uart_putw_dec(disk_info.format); uart_putc('\n'); uart_puts_p(PSTR("free: ")); uart_putdw_dec(fat_get_fs_free(fs)); uart_putc('/'); uart_putdw_dec(fat_get_fs_size(fs)); uart_putc('\n'); return 1; } #if FAT_DATETIME_SUPPORT void get_datetime(uint16_t* year, uint8_t* month, uint8_t* day, uint8_t* hour, uint8_t* min, uint8_t* sec) { *year = 2007; *month = 1; *day = 1; *hour = 0; *min = 0; *sec = 0; } #endif