Data Logger
This is going to be a data-logging sub-system for a UAV that I'm designing. The system has to be fast and accurate enough to give me data that is useful enough to build an autopilot program, yet simple and robust enough that there is no chance of it bogging down the other systems that will be running on the mbed.
Using an SD card is the obvious choice due to the reliability, support and portability. This is not to say there will be no challenges. I'm starting off with zero knowledge on these devices and plan to build the system on as low a level as I need to in order to meet my design criteria.
I have completed a lot of research and have obtained the hardware (a kingston low capacity card to get started, a patriot high capacity card for later, and a SD card socket break-out-board). I've stumbled upon Simon's SD card system, this might be helpful to see how other people (who probably know what they are doing) are going about this task. I'm going to go as far as I can to understand the system for myself in order to optimize it. He's using a FAT system, which would make thing very nice, I'll try doing something like that later. Just finished wiring up the board, the plan is to follow the documents literally and step by step.
The naieve code is working, so the basic operations make sense, however still having trouble with the high-capacity card. It is giving me illegal command responses to the startup command 41.
Both cards work after reordering the sequence; I've settled on cmd0, cmd8, cmd58, then the cmd55 + cmd41 sequence for the highest compatibility (probably wrong but it's the best I can figure). I'm now modularizing the program and adding layers of abstraction to make programming easier.
void SDCard::Command(unsigned char Index, unsigned int Argument, unsigned char* Response) { ChipSelect.write(0); DataLines.write(0x40 | Index); DataLines.write(((char*)&Argument)[3]); DataLines.write(((char*)&Argument)[2]); DataLines.write(((char*)&Argument)[1]); DataLines.write(((char*)&Argument)[0]); if (CRCMode) { DataLines.write(CommandCRC(&Index, &Argument)); } else { DataLines.write(0x00); } for (unsigned int j = 0; j < 8192; j++) { Response[0] = DataLines.write(0xFF); if (!(Response[0] & 0x80)) { break; } } if ((Index == 8) || (Index == 13) || (Index == 58)) { for (unsigned char j = 1; j < 5; j++) { Response[j] = DataLines.write(0xFF); } } ChipSelect.write(1); DataLines.write(0xFF); //clock the deselected card high to complete processing for some cards (credit to Simon) }
Now its time to add the extra features in. I have got the program to read the CSD and select the optimum frequency at which to operate the SPI lines. This will be part of initialization. Next moving on to get the mbed to perform cyclic redundancy checks.
if (((CSD[3] & 0x07) > 2) || ((CSD[3] & 0x7F) > 0x32)) { DataLines.frequency(25000000); } else { Workspace[0] = 1; for (unsigned char j = 0; j < (CSD[3] & 0x07); j++) { Workspace[0] *= 10; } switch (CSD[3] & 0x78) { case 0x08: DataLines.frequency(Workspace[0] * 100000); break; case 0x10: DataLines.frequency(Workspace[0] * 120000); break; case 0x18: DataLines.frequency(Workspace[0] * 140000); break; case 0x20: DataLines.frequency(Workspace[0] * 150000); break; case 0x28: DataLines.frequency(Workspace[0] * 200000); break; case 0x30: DataLines.frequency(Workspace[0] * 250000); break; case 0x38: DataLines.frequency(Workspace[0] * 300000); break; case 0x40: DataLines.frequency(Workspace[0] * 350000); break; case 0x48: DataLines.frequency(Workspace[0] * 400000); break; case 0x50: DataLines.frequency(Workspace[0] * 450000); break; case 0x58: DataLines.frequency(Workspace[0] * 500000); break; case 0x60: DataLines.frequency(Workspace[0] * 550000); break; case 0x68: DataLines.frequency(Workspace[0] * 600000); break; case 0x70: DataLines.frequency(Workspace[0] * 700000); break; case 0x78: DataLines.frequency(Workspace[0] * 800000); break; default: break; } }
CRC is not trivial! the command crc module is complete. A google will tell you how crc works, I'll explain how mine works. I'm using a crc lookup table stored in ram to maximize efficiency and minimize typing. the crc result of a byte is always the same, and in sequence it can be shown the total result is the crc of the first byte, XORing that with the next byte, finding the crc of that result and XORing that with the next byte and so on. If we simply calculate and store the crc of all 256 possible bytes, the code will not have to calculate it each time. To create the lookup table im having nested loops increment through all possible bytes, XORing the generator each time a 1 is added by either the increment or the generator. The results are written to a char array 256 chars long. When a command functon is called, the arguments and index are looked up and XORed in sequence. Now for the crc16 module
char SDCard::CommandCRC(unsigned char* IndexPtr, unsigned int* ArgumentPtr) { return CommandCRCTable[ CommandCRCTable[ CommandCRCTable[ CommandCRCTable[ CommandCRCTable[ *IndexPtr | 0x40 ] ^ ((char*)ArgumentPtr)[3] ] ^ ((char*)ArgumentPtr)[2] ] ^ ((char*)ArgumentPtr)[1] ] ^ ((char*)ArgumentPtr)[0] ] | 0x01; }
with a few modifications I'm able to generate a crc16 table with the same code used on the crc7 code. The sequence of look-up and final calculation is a bit more involved though. The table is twice as big to count for both resulting bytes of each input byte. the calculation is the same as before except the table lookups overlap and we need a variable to hold and carry the overlapping portion during each calculation. All crc modules now work. Final generation code (hope it makes sense, I'm not a programmer XD):
void SDCard::GenerateCRCTable(unsigned char Size, unsigned long long Generator, unsigned char* Table) { unsigned char Index[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0}; for (int i = 0; i < 64; i++) { if (((char*)&Generator)[7] & 0x80) { break; } Generator = Generator << 1; } for (unsigned char k = 0; k < Size; k++) { Table[k] = 0x00; } for (unsigned char i = 0; i < 8; i++) { if ((0x80 >> i) & ((unsigned char*)&Generator)[7]) { Index[Index[8]] = i; Index[8]++; } for (unsigned char j = 0; j < (0x01 << i); j++) { for (unsigned char k = 0; k < Size; k++) { Table[(Size * ((0x01 << i) + j)) + k] = Table[(Size * j) + k]; for (unsigned char l = 0; l < Index[8]; l++) { Table[(Size * ((0x01 << i) + j)) + k] ^= (((unsigned char*)&Generator)[7-k] << (i + 1 - Index[l])); Table[(Size * ((0x01 << i) + j)) + k] ^= (((unsigned char*)&Generator)[6-k] >> (7 - i + Index[l])); } } } } }
I got side-tracked after trying to double-check that the code still works with the low capacity card. There's a problem with the CRCs. Command 59 (used to turn on CRCs) is returnin illegal command responses. This should definitely not occurr, all specifications declare cmd59 shall be legal during initialization. Web searching turns up zero useful results; nobody is utilizing CRCs. I gave the code a (somewhat hackish) fix by just sending the crc on/off command after initialization is complete (because that's when the small card decided to work).
bool SDCard::Initialize() { for (unsigned char j = 0; j < 16; j++) { DataLines.write(0xFF); } for (unsigned int j = 0; j < 8192; j++) { Command(0, 0, Workspace); if (Workspace[0] == 0x01) { break; } if (j == 8191) { return 0; } } for (unsigned int j = 0; j < 8192; j++) { Command(59, 1, Workspace); if ((Workspace[0] == 0x01) || (Workspace[0] == 0x05)) { break; } if (j == 8191) { return 0; } } for (unsigned int j = 0; j < 8192; j++) { Command(8, 426, Workspace); if ((Workspace[0] == 0x05) || ((Workspace[0] == 0x01) && ((Workspace[3] & 0x0F) == 0x01) && (Workspace[4] == 0xAA))) { break; } if (j == 8191) { return 0; } } Version = Workspace[0] == 0x01; for (unsigned int j = 0; j < 8192; j++) { Command(58, 0, Workspace); if ((Workspace[0] == 0x01) && ((Workspace[2] & 0x20) || (Workspace[2] & 0x10))) { break; } if (j == 8191) { return 0; } } for (unsigned int j = 0; j < 8192; j++) { Command(55, 0, Workspace); Command(41, 1073741824, Workspace); if (Workspace[0] == 0x00) { break; } if (j == 8191) { return 0; } } for (unsigned int j = 0; j < 8192; j++) { Command(59, 1, Workspace); if (Workspace[0] == 0x00) { break; } if (j == 8191) { return 0; } } /////////////////////////////////////////////...
Now its time to try and squeeze as much as I can out of this card. There is a high-speed mode, however it looks like it might be a bit too power-hungry for the mbed. The exploratory commands tell me it draws, or can draw 200mA; well above the mbed's maximum 40mA. The answers I'm finding in my research are somewhat confusing to me; I can't tell if this is a worst-case number or a requirement.
This is a power reguirement for the rails, mbed only limits power from the usb link to prevent damage to a computer. This limit is 500mA. mbed draws 100, the card draws 200, so we are safe to operate in high-speed mode.
I just finished plugging in FATFs R0.08 into my code to give it the ability to produce files readable by a computer. I used cmd 25 and cmd 18 for reading writing operations to speed things up. I also implemented a lazy acmd23 to speed writing up even more. The FAT disk control functions were staight-forward to implement by reading the CSD and calculating and returning values. I adopted Simon's FATFileSystem files for interfacing mbed standard libraries with the FAT system and fixed them so that all of the functions worked correctly.
There is one final bug to fix:
The "rename" wrapper function in mbed's standard library seems to be broken. When I call it, it simply won't execute my block of code. I believe it is meant to be called like this: rename("/SDCard/testdir/NAME.txt", "/SDCard/testdir/OTHERNAME.txt"); just as one would call a standard c function like "fprintf". Mbed allows you to define what this does by writing a child class of "FileSystemLike" and writing a virtual function definition for "virtual int rename". This was done with FATFileSystem, however
no matter what I try, calling "rename" simply returns -1 without executing the code i defined in the virtual function. This leads me to believe the wrapper function is broken. I'm waiting to hear back from the folks at mbed to tell me if there is anything wrong.
mbed's FileSystemLike declaration:
public: virtual int rename(const char *oldname, const char *newname) { return -1; };
my FATFileSystem declaration:
public: virtual int rename(const char* oldname, const char* newname);
my FATFileSystem definition:
int FATFileSystem::rename(const char* oldname, const char* newname) { char OldName[64]; sprintf(OldName, "%d:/%s", Drive, oldname); if (f_rename((const TCHAR*)OldName, (const TCHAR*)newname)) { return -1; } else { return 0; } }
my instantiation in int main:
FATFileSystem("SDCard");
my function call in int main:
rename("/SDCard/testdir/TEST.txt", "/SDCard/testdir/TEST2.txt");
I did the same thing exactly with all of the other virtual functions in FileSystemLike and they all worked, so I can't figure out what is wrong.
0 comments
You need to log in to post a comment