..
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=66846&start=220

AVR Freaks

AVR Tutorials - [TUT] [C] Getting SD/MMC card working painlessly with FatFS

kubark42 - Jul 24, 2008 - 04:30 PM
Post subject: [TUT] [C] Getting SD/MMC card working painlessly with FatFS
FatFS is a great library for getting FAT12/FAT16/FAT32 running on pretty much any C platform http://elm-chan.org/fsw/ff/00index_e.html. The developer even took the time to provide example code for pretty much any application you could want. The problem that I ran into, however, is that the code isn't very easy to adapt, unless you spend a lot of timing pouring over it and troubleshooting. 

This tutorial is primarily aimed at people who have little idea about how to program AVRs and want to spend more time developing their ideas and less time trouble-shooting the back-end. I hope that this tutorial helps you cut to the chase and have a working MMC/SD card interface in just a few minutes. 

First off, let me point out that this code is running on an ATmega644p, developed on an STK500. I'm using a Kingston microSD card that I can plug into a miniSD adapter (that I soldered to wires so that I could plug them directly into the SPI pins) or into a normal adapter (that I can plug into a computer SD card reader). 

My pinout looks like this: 

DAT1-------NC 
DO---------MISO (PB6) 
Vss2-------GND 
CLK--------SCK (PB7) 
Vcc--------3.3V (PC0) 
Vss1-------GND 
DI---------MOSI (PB5) 
CS---------SS (PC1) 
DAT2-------NC 

with no extra components. (In retrospect, it's probably not a bad idea to put a capacitor at the 3.3V input to the SD card, in order to make sure voltage doesn't sag.) 

For this tutorial, you will need a uC with at least 32K of flash memory and 4K of internal SRAM. (Of course, by disabling many of the functions and using Tiny-FatFS, it can be made far smaller, but that is outside the scope of this tutorial.) 

The code is very comprehensive, but there are a few things that are overengineered, and were causing serious problems with my ATmega644p. 

For instance, the UART code is very dense, and makes use of interrupts, which somewhat complicates things (the code is no longer linear, as the interrupts can, well, interrupt) and makes it harder to understand for beginners. It also uses a FIFO buffer, which is again overkill for a program that patiently waits until the user inputs a two letter combination. 

Anyhoo... Don't get me wrong, it's a GREAT library! I just think that for a first time user it's a little harder than it should be. 

CAVEAT!!!:
Quote:
You MUST use 3.5V or less on both the SD card, and the SPI lines. If your chip is already running at <3.5V, great, otherwise you NEED level converters. Don't be mad at me if you blow up your SD card because you gave it too much juice. (They like 3.3V, but will tolerate 3.5V.) 

Now on to the good stuff. Start by downloading the ffsample.zip package from http://elm-chan.org/fsw/ff/00index_e.html. Unzip the avr directory-- it's the only one you will need. 

This next step is optional; it's only to help clean the directory. Delete/move the following files (They're unnecessary for SD card support, although they don't hurt anything if you leave them there.): 
    ata.c 
    cfc.c 
    cfmm.c 
    Makefile_ata 
    Makefile_ata2 
    Makefile_cfc 
    Makefile_cfc2 
    Makefile_cfmm

makefile 

Next, modify "MCU_TARGET = atmega64" (line 3 or so) in both Makefile_mmc and Makefile_mmc2 to match your uC. I'm using a ATmega644p so I replaced "atmega64" with "atmega644p". 

Next, make a copy of Makefile_mmc and rename it Makefile. (Makefile_mmc2 is for Tiny-FatFS, which we'll get to later. The modifications to one apply to the other, with the exception of a couple bugs that needed fixing.) 

Right now, if you were to compile it, there's a good chance it wouldn't even compile because of different register names between uC. This is certainly the case between the atmega64 and the atmega644p. So let's fix that. 


main.c 

If you read the warnings, you'll probably see that most of the errors are in main.c where the ATmega64 has a LOT more ports than your standard avr chip. So fire up your favorite text editor (I use Smultron), and take a look at the IoInit() function. 

First, find the function ISR(TIMER2_COMP_vect). You need to check that this is the correct vector name. It wasn't for me. Looking on the Interrupt Vector table in the ATmega644p's documentation, I saw I had to change it to:
Code:
TIMER2_COMPA_vect 
(Remember, "_vect" must be put at the end of the vector name in the table) 

Comment ALL the lines out from PORTA = ... to PORTG=... 
(See further down for how this looks). You'll eventually need to feed the SD card 3.3V, so later on you might reactivate ONE (1) pin, but you certainly don't need all of them. 

Next, a little bit further down in the same function (line 196 or so) you might need to change the Timer2 variables. Be careful here, as it's not as simple as just renaming the variables. The registers might have changed, too. By looking on the ATmega64 datasheet, you can see on page 160 that OCR2 is the Output Compare Register for Timer2, an 8-bit timer. Cross-referencing to the ATmega644p datasheet, you see that there are TWO (2) Output Compare Registers. I chose to replace OCR2 with OCR2A, but I expect it would have worked just fine with OCR2B, also. 

You'll also probably need to change TCRR2 and TIMSK, but be careful, as you might need to change the value written to the register, too. In my case, again comparing datasheets, on page 157 I found that originally, TCCR2 bits WGM21, CS22, and CS20 are written high. On the ATmega644p, page 153-156, I found that the TCCR2 register has been split into two parts, and so in order to enable all the original bits, I had to write to both TCCR2A and TCCR2B. Likewise for TIMSK, where on page 160 you see that only OCIE2 is high. On the ATmega644p, I had to set TIMSK2 (I don't know why there is a two here and not on the ATmega64) for OCIE2A. 

Lastly, two lines later comment out sei();. The final function now looks like this: 
Code:
//   PORTA = 0b11111111;   // Port A 

//   PORTB = 0b10110000; // Port B 
//   DDRB  = 0b11000000; 

//   PORTC = 0b11111111;   // Port C 

//   PORTD = 0b11111111; // Port D 

//   PORTE = 0b11110010; // Port E 
//   DDRE  = 0b10000010; 

//   PORTF = 0b11111111;   // Port F 

//   PORTG = 0b11111;    // Port G 
   OCR2A = 90-1;      // Timer2: 100Hz interval (OC2) 
   TCCR2A = 0b00000010; 
   TCCR2B = 0b00000101; 

   TIMSK2 = 0b00000010;   // Enable TC2.oc interrupt 

   rtc_init();         // Initialize RTC 

//   sei(); 


mmc.c 

Now it still won't compile for most of us, but we're almost there. We need to fix some things in mmc.c. We will make a lot of modifications, so you might want to back it up, too. 

Again, on my chip I don't have a PORTE, so of course this fails. Moreover, the pins for the SPI port on the ATmega64 and ATmega644p are different. 

Start by adding the following lines right after the #include section. 
Code:
/*SPI configuration*/ 
#define DD_MOSI   DDB5 
#define DD_SCK   DDB7 
#define DDR_SPI   DDRB 
#define DD_SS   4 

/* Defines for SD card SPI access */ 
#define SD_CS_PIN   1 
#define SD_CS_PORT   PORTC 
#define SD_PWR_PIN   0 
#define SD_PWR_PORT   PORTC 

These defines will all have to be modified to your particular ATmega and setup. The DD_* #defines can be found in the I/O Ports-->Alternate Port Functions section. 

The SD_* #defines are up to you. I chose to conenct the SD card's power supply to PIN0 (you did make sure you're not outputting more than 3.5V, right???) chip select to PIN1 of PORTC, but you could chose another if it were more convenient. Just avoid using one of the four pins associated with the SPI port. You can even comment out the SD_PWR_* if you have an alternate power supply (in this case, you have a 3.3V level converter on the SPI I/O pins, right???) 

Continuing on, modify the #define SELECT() and #define DESELECT() as so: 
Code:
#define SELECT()      SD_CS_PORT &= ~(1<<SD_CS_PIN)      /* MMC CS = L */ 
#define DESELECT()   SD_CS_PORT |=  (1<<SD_CS_PIN)      /* MMC CS = H */ 

Then delete these three lines: 

Code:
#define SOCKPORT   PINB         /* Socket contact port */ 
#define SOCKWP      0x20         /* Write protect switch (PB5) */ 
#define SOCKINS      0x10         /* Card detect switch (PB4) */ 

Next, go to the power_on(void) function and replace it with 
Code:
static 
void power_on (void) 

#if (defined SD_PWR_PIN | defined SD_PWR_PORT) 
   DDRC|=(1<<SD_PWR_PIN);          // Turns on PWR pin as output 
   SD_PWR_PORT|=(1<<SD_PWR_PIN);   // Drives PWR pin high 
#endif 

   DDRC|=(1<<SD_CS_PIN);          // Turns on CS pin as output 
   DDR_SPI = (1<<DD_MOSI)|(1<<DD_SCK)| (1<<DD_SS); 
   SPCR = (1<<SPE)|(1<<MSTR); /* Initialize SPI port (Mode 0) */ 


Change power_off(void) to: 
Code:
static 
void power_off (void) 

   SELECT();            /* Wait for card ready */ 
   wait_ready(); 
   release_spi(); 
   Stat |= STA_NOINIT;      /* Set STA_NOINIT */ 


and change chk_power(void) to 
Code:
static 
int chk_power(void)      /* Socket power state: 0=off, 1=on */ 

   return 1; 


Finally, at the very end, replace the function disk_timerproc(void) with: 
Code:
void disk_timerproc (void) 

   BYTE n; 

   n=Timer1;                  /* 100Hz decrement timer */ 
   if(n) 
      Timer1 = --n; 
   n=Timer2; 
   if(n) 
      Timer2 = --n; 


The code will probably now compile, but it almost certainly won't work. There are still some more modifications to make. 


uart.c 

First, let's get rid of the original uarts code and replace it with something a little easier to understand. Simplyrename the existing uart.c, for example to uart.c.old, and save this code as a new uarts.c: 
Code:
/*------------------------------------------------*/ 
/* UART functions                                 */ 

#include <avr/io.h> 
#include <avr/interrupt.h> 
#include <util/delay.h> 
#include "uart.h" 

#define   BAUD      9600 

void USART_Transmit( unsigned char txData ) 

   /* Wait for empty transmit buffer */ 
   while ( !( UCSR0A & (1<<UDRE0)) ); 
   /* Put data into buffer, sends the data */ 
   UDR0 = txData; 


void USART_set_baud_rate(double baudrate) 

   // calculate division factor for requested baud rate, and set it 
   int bauddiv = ((F_CPU+(baudrate*8L))/(baudrate*16L)-1); 
   UBRR0L= bauddiv; 
#ifdef UBRR0H 
   UBRR0H= (bauddiv>>8); 
#endif 


/* Initialize UART */ 

void uart_init() 

   UCSR0B = (1<<RXEN0)|(1<<TXEN0);      // Turn on U(S)ART port 
   UCSR0C = (1<<UCSZ01)|(1<<UCSZ00);   // Set frame format: 8 data bits, 1 stop bit, no parity 
   USART_set_baud_rate(BAUD); //Set baud rate 



/* Get a received character */ 
uint8_t uart_get () 

   unsigned char d; 
   while ((UCSR0A & (1 << RXC0)) == 0) {}; // Do nothing until data have been recieved and is ready to be read from UDR 
   d=UDR0; 
   return d; 


/* Transmit a character */ 
void uart_put(uint8_t d) 

    
   USART_Transmit( d ); 


/* Transmit a string */ 
void uart_puts(const char *s) 

   while (*s) 
      USART_Transmit( *s++ ); 



uart.h 

You'll also need to backup uart.h (uart.h.old, perhaps), and replace it with: 
Code:
void uart_init(void);         /* Initialize UART */ 
uint8_t uart_get (void);      /* Get a byte from UART Rx */ 
uint8_t uart_test(void);      /* Check number of data in UART Rx FIFO */ 
void uart_put(unsigned char);   /* Transmit a byte*/ 
void uart_puts(const char *s);   /* Transmit a string of bytes*/ 

If you have trouble understand what this does, check out abcminiuser's excellent tutorial on USART. 

Next, let's look at the rtc clock. While what was done in the example was very comprehensive, this is far too much for a simple data logging operation. Let's just trim this down to the bare minimum. I'll leave the function structure there because it could come in handy later on, especially if you want to run a clock off the internal oscillator-- even if it's inaccurate, it will still be accurate enough to let you know approximately when a file was made. 

Again, backup rtc.c-- to for instance rtc.c.old-- and save this code to a new rtc.c 
Code:
/*--------------------------------------------------------------------------*/ 
/*  RTC controls                                                            */ 

#include <avr/io.h> 
#include <string.h> 
#include "rtc.h" 

BOOL rtc_gettime (RTC *rtc) 


//   BYTE buf[8]; 

//   rtc_read() //This is where you would read the clock. 
    
//   rtc->sec = (buf[0] & 0x0F) + ((buf[0] >> 4) & 7) * 10; 
//   rtc->min = (buf[1] & 0x0F) + (buf[1] >> 4) * 10; 
//   rtc->hour = (buf[2] & 0x0F) + ((buf[2] >> 4) & 3) * 10; 
//   rtc->mday = (buf[4] & 0x0F) + ((buf[4] >> 4) & 3) * 10; 
//   rtc->month = (buf[5] & 0x0F) + ((buf[5] >> 4) & 1) * 10; 
//   rtc->year = 2000 + (buf[6] & 0x0F) + (buf[6] >> 4) * 10; 


   //This code is just to provide some kind of a valid response. 
   rtc->sec = 1; 
   rtc->min = 2; 
   rtc->hour = 3; 
   rtc->mday = 4; 
   rtc->month = 5; 
   rtc->year = 2006; 


   return TRUE; 


BOOL rtc_settime (const RTC *rtc) 

   BYTE buf[8]; 

   buf[0] = rtc->sec / 10 * 16 + rtc->sec % 10; 
   buf[1] = rtc->min / 10 * 16 + rtc->min % 10; 
   buf[2] = rtc->hour / 10 * 16 + rtc->hour % 10; 
   buf[3] = 0; 
   buf[4] = rtc->mday / 10 * 16 + rtc->mday % 10; 
   buf[5] = rtc->month / 10 * 16 + rtc->month % 10; 
   buf[6] = (rtc->year - 2000) / 10 * 16 + (rtc->year - 2000) % 10; 

/*This is where you would set the new time to the clock*/ 

   return TRUE; 


BOOL rtc_init (void) 

   BYTE buf[8];   /* RTC R/W buffer */ 
//   UINT n; 

   /* Read RTC */ 
//   rtc_read() //This is where you would read the clock the first time. 

   if (/*SOMETHING_IS_WRONG*/0) {   /* When RTC data has been broken, set default time */ 
      /* Reset time to Jan 1, '08 */ 
      memset(buf, 0, 8); 
      buf[4] = 1; buf[5] = 1; buf[6] = 8; 
//      rtc_write(buf); 
      /* Clear data memory */ 
      memset(buf, 0, 8); 
//      for (n = 8; n < 64; n += 8) 
//         rtc_write(buf); 
      return FALSE; 
   } 
   return TRUE; 



There you go, all done! Now just type make in the directory root and you should have a hex file ready to be uploaded to your uC. 

CAVEAT!!!:
Quote:
On many uC, the SPI port is the same as the ISP port, so you may have problems uploading the program if the uC is connected to the memory card. I do, so I have to remove it every time I want to upload, otherwise I get an error.


Once you upload the hex file, you need to interface with the program through a serial terminal. It's a very simple interface, although it's not well documented. Commands are given in two-letter combinations, followed by a parameter, if one is needed. If the command was correctly formatted, there is ALWAYS a response of the form:
Code:
rc=
To get started, you need to initialize the disk. Type: 
Code:
di 0
This is a Disk Initialize for disk #0. Afterward, assuming the sd card is already formatted, type:
Code:
fi 0
which means File-system Initialize for disk #0. Now you can access the disk structure by typing
Code:
fl

which means File-system List. 

If you get this far and it seems to respond, then everything is just fine. (If not, post here and hopefully someone will help you.) There are many more commands, but you can discover them for yourself by looking in the for(;;) loop in main.c 

Enjoy! 

--Kenn 



P.S. If you want to try Tiny-FatFS, just apply the same modifications to main2.c as above, copy Makefile_mmc2 toMakefile, and recompile with 
Code:
make clean 
make


Edit: Fixed typos, added suggestion for capacitor on 3.3V input.
clawson - Jul 24, 2008 - 05:49 PM
Post subject: RE: [TUT][C]Getting SD/MMC card workin painlessly with FatFS
Wow - thanks for this - I just bought one of these: 

http://www.futurlec.com/Mini_SC.shtml 

and was about to start "playing" !
davef - Jul 25, 2008 - 12:48 AM
Post subject: RE: [TUT][C]Getting SD/MMC card workin painlessly with FatFS
Yes, thanks a lot for writing this up. Currently, I am looking at interfacing a SD card to my ATmega32 and this TUT will be a great help. 

davef
davef - Jul 25, 2008 - 10:26 AM
Post subject: RE: [TUT][C]Getting SD/MMC card workin painlessly with FatFS
Thought I'd just try compiling it for the ATmega64, before making changes and the error file doesn't look too good. BTW, it generates the hex file. 

What compiler are you using? With WinAVR 20071221 I get the following: 
(I think I have seen this "signedness" issue moving from older versions of WinAVR) 


All times are GMT + 1 Hour
Powered by PNphpBB2 © 2003-2006 The PNphpBB Group
Credits 


posted by 털보네i
: