There were two objectives for this project; to learn how to write effective code for a micro possessor with the Microsoft compiler and to write a simple, effective, DOS for a micro possessor.
I have been working with the MSP430 micro computer and recently found out that SPI mode is supported on (most?) micro SD cards. This is encouraging. I have wanted to use inexpensive removable memory that could easily be utilized by anyone from an MSP430 project. That means using the file system of the card, the FAT system, so specialized software is not needed on a Windows platform. This code should work on any micro computer, I have kept all but the low level stuff as plain as possible.
I started by compiling FatFs directly to the micro. But it didn't work properly and occupied a large amount of ROM. Debugging it started to look like a can of worms.
So I figured that the next place to start would be on a Windows box. I used an MFC application to get going so I could see what my code was doing at run time. It was just nice to have a higher class of debugging tools than appear with a micro computer development system. Next I took a look at DOSFS Embedded FAT by Lewin A.R.W. Edwards in VS. Much better, it has reasonable documentation in the code. I got to the point where it hung in a loop not recognizing the FAT16 end of file table marker. It was looking for the FAT32 signature. But I could see how it was intended to work.
It was big and clunky and I wanted something small and simple. I didn't compile it with MSPGCC but it was advertised to make at about ~4k without the low level code. Not unreasonable for the likes of the MSP430F149. What I found was that whatever code I wrote in VS according to C rules could be ported to the micro with a copy and paste and it would work for the most part. So far I have found only one place where I had to cast with MSPGCC, where I didn't with the Microsoft compiler, so as not to loose the higher part of a DWORD. That was to force a
mult16_32, and I plan to write that out. Another thing I saw that was unnecessary in both examples above was a 'Pack Data'. The MSP430 is little endian, just like the x86, so can read and write the FAT data directly. The code I have here, with low level and the main, compiled to ~3k. From the dump, it looks like ~1.5k for the DOS code.
For now, I'm targeting the MSP430F1612. It has DMA and the code written by F. Foust, Dept. of Electrical and Computer Engineering, Michigan State University, compiled and ran perfectly.
Application Note - Secure Digital Card Interface for the MSP430
This is low level access to the SD card that
SetSectorData(...) wrap. A simpler version without DMA will be included in the future.
My hardware development platform:
Using the Code (MFC)
The extras in the MFC code is a call to enumerate drives on the box.
void EnumDrives( HEDriveInfoSet& inSet );
You will find the declarations that support this in "HE_Drives.h" The code is in "HE_Drives.cpp".
bool GetSectorData( pHEDriveInfo pDrvInfo, long sector, BYTE* buf );
bool SetSectorData( pHEDriveInfo pDrvInfo, long sector, BYTE* buf );
These are to read and write raw sectors on any mounted drive so be careful you don't smash your 'C' drive. I'm working on the SD card with a cheap USB adapter. The MFC gadget is a sector viewer, fun, but not that useful after all.
Using the Code (DOS)
There is test code in "mfc_diskioView.cpp" starting around line 200. Here I just started exercising the DOS code and making sure it reads and writes properly across clusters and appending a file.
Once you are into the micro code, the Low level access looks like:
bool GetSectorData( DWORD sector );
bool SetSectorData( DWORD sector );
The buffer RAM is shared and there is only one drive on the micro. The code that ports to the micro is in "HE_dos.cpp" and "HE_dos.h". But there is no C++ in any of the code that ports. MSPGCC does an excellent job with C but I haven't tried to beat it up with C++. At that, I think abstraction is great for PC software, but you will tend to be very aware of what happens at the machine level with a micro. C++ is probably overkill for a few K of code. Besides, OO is in the mind...
The function calls for the DOS interface are about as straight forward as it gets. There are only two structures, one for the drive volume information and one for an 'open' file. For the first version there is no 'Close File' as the last buffer will always get flushed no matter what files were opened on a 'Close Volume'. There is a flush because the low level does do caching. There are two reasons for caching. First is that the DOS code does not have to check for unnecessary reads and writes. Second is to save on battery life. Caching is pretty simple at the level of a single buffer and the PC example is in code starting near the top of "mfc_diskioView.cpp". Read sector zero, guaranteed not to be 'file' cached, and you have 'closed' all files even if all the files are still really open!
One feature of this code is that there is no mult or div math. (One exception noted and will be cleaned up.) All mults and divs are done with shifts. And the compiler is smart enough to translate a
val << 8 into dropping the low byte and carrying on. And a
val << 9 or >> 9 is just one shift.
The code is bare bones, just enough to get the job done. There is little or no bound checking as the calling code should know. Even if this is extended to handle FAT32, I would never look to support FAT12, I don't see the point.
All of the below return true if successful.
Call this once after a micro reset (RAM power up) to initialize the volume information.
bool HEDsk_LoadVolumeInfo( );
This is the 'Open' for a file. It initializes the file information for an existing file. The name is canonical, i.e., in the form of "TEST TXT". No flags, it is open for read and write and must exist. Just 11 BYTES for the name, a trailing null is not necessary. The file pointer is set to zero.
bool HEDsk_GetFileInfo( pHEDskFileInfo pInfo, char* lpsCanonicalName );
count' of BYTES into
buf from the file. Moves the file pointer up by
count. Reading past the end of file is undetermined.
bool HEDsk_ReadFile( pHEDskFileInfo pInfo, BYTE* buf, WORD count );
count' in BYTES from
buf to the file from the current file pointer. Will extend the size of the file if needed.
bool HEDsk_WriteFile( pHEDskFileInfo pInfo, BYTE* buf, WORD count );
Sets the file pointer to
pos. No check is made to make sure
pos < fileSize. The file size is available in
bool HEDsk_SeekFile( pHEDskFileInfo pInfo, DWORD pos );
Things that may happen in the future is to add optional code with
ifdefs so files can be created and zeroed if needed. There is no handling the data modified of the file. For a card that will be moved from the micro to a box, this is not a real issue. But while working on the box alone, the OS will not see that the file is modified and the cache may stay stale. I have had to pull the card and reinsert it to get a fresh copy of files. I tried one hack that didn't work, If you know one, let me know.
This code works on the MSP430:
if( HEDsk_GetFileInfo( &heFileInfo, "TEST TXT" ) )
for( j= 0; j < 100; ++j )
P1OUT ^= BIT4; HEDsk_WriteFile( &heFileInfo, "this is a test\r\n", 16 );
HEDsk_FlushFile( &heFileInfo );
There is a folder in the project called MspCode with all the files for the micro.
Points of Interest
- 28th October, 2009: Initial version