Introduction: Simple Mass Storage for Your Microcontroller Project

About: Dr James Moxham is a general medical practitioner in Blackwood, Australia. His interests include general family medicine, medical politics, microcontrollers and tending a rose garden. He lives on a property wi…

Do you want gigabytes of storage for your microcontroller? Would you like a simple way to tranfer files from your PC to your picaxe or arduino or other micro project?

This little projects demonstrates the uDrive that can be set up as an extra drive on a PC. Copy files to and from the SD card, then put it in the uDrive and talk to it with 5 simple commands:

Read - Read a file off the drive
Write - Write a file to the drive
Dir - get the directory listing of files on the drive
Erase - erase a file
Initialise - send 1 byte at your preferred baud rate

Step 1: Connecting to Your Microcontroller

Connection uses 5 wires: Gnd, Power, Tx, Rx and an optional reset line. The reset line can be left unconnected if no reset is needed.

The uDrive works from 3 to 5V so can interface to a wide range of controllers.

Step 2: Copying Files Across

Copying involves taking the micro SD card out of the uDrive and putting it into a micro-to-standard SD adaptor. This goes into a USB adaptor that plugs into the PC and then the SD card appears as another drive on the PC. The micro SD is the small purple part seen on the left of this picture. The USB to SD adaptor cost $5 including shipping on ebay. Most micro SD cards come with micro to standard adaptors and the total cost of the package is under $10 for gigabytes of storage.

Step 3: Accessing the Files From a Microcontroller

Microcontrollers all have different languages; assembly, C, Basic, Spin etc. You will have to write your own exact code, but below is an example using vb.net. This is still a bit complicated, but it is a lot easier than trying to talk to a SD card using bit-bang SPI code.

For the purposes of testing, we have built a little uDrive to RS232 converter. We are actually talking back to a PC via the PC serial port (or via a USB to serial adaptor), which seems a bit pointless, but it at least enables the device to be tested quickly and code debugged.

The Max232 converts the 0V/5V levels from the uDrive into valid RS232 voltage levels.

Step 4: Testing Using 4dsystems’ Software

The first test is to run some testing software from 4dsystems http://www.4dsystems.com.au/prod.php?id=22

This has pre-written test routines that not only check the device is working, but also indicate the bytes going back and forth. This makes it much easier to work out how to write your own code.

You can also talk to the card in raw mode, but using DOS mode that a PC can understand is a lot simpler. A file can be tiny - just 1 byte if you wish.

Step 5: Writing the Code

Writing code is a bit complicated, but let's start simple. Send the uDrive just 1 byte, a character U, at your nominated baud rate, and you will either get three things back. A 06, which means it worked, a 15 which means it got the byte but didn't understand it, or nothing, which means the drive is not connected or not wired up right.

Here is some vb.net code to do this:

SerialPort.Open() ' open the port
SerialPort.DiscardInBuffer() ' clear the input buffer
OutPacket(0) = Strings.Asc("U") ' send a U and this sets the baud rate
SerialPort.Write(OutPacket, 0, 1) ' send data in outpacket start 0, 1 byte
Sleep(100) ' wait for response
SerialPort.Read(InPacket, 0, 1) ' read 1 byte back, should be 06
SerialPort.Close()

From now on, all comms will be at this baud rate. Rates from 300 to 38400 work talking to a PC with a 3 metre cable. With a shorter cable it would easily go faster.

The code below is the complete source code in vb.net (vb.net is available for free). The main complications are the way the uDrive splits files up into pieces - you need to watch the arrays and the integer division and remainder division and write these routines in your preferred language for your micro.

Cost of the uDrive is $30US.

The uDrive can also work in Raw mode, where you read and write to individual sectors. This is useful where you want to interface to an existing operating system, such as CP/M. More brainstorming is happening at the Vintage Computer forum, the Picaxe forum and the N8VEM forum.

Stay tuned, as the next little project will be to get a $3 picaxe 08M to write a tiny file that can be read by a PC.

Have fun!

' uDrive example code for vb.net
' test setup is a uDrive talking to a PC's serial port via a max232
' James Moxham May 2009 moxhamj at internode.on.net

Imports System.IO ' needed to run various calls
Imports Strings = Microsoft.VisualBasic ' so can use things like left( and right( for strings
Public Class Main
Dim WithEvents SerialPort As New IO.Ports.SerialPort ' serial port declare
Public Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Integer) ' for sleep statements
Public InPacket(0 To 2000) As Byte ' bug with serial.write strings like chr(255) won't go out
Public OutPacket(0 To 50) As Byte ' usually only send out a few bytes though so could make this smaller
Private Sub Initialise_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Initialise.Click
' initialise
' send a U at the selected baud rate baud to initialise
Try
SerialPort.PortName = "COM1"
SerialPort.BaudRate = "38400" ' 38400
SerialPort.Parity = IO.Ports.Parity.None ' no parity
SerialPort.DataBits = 8 ' 8 bits
SerialPort.StopBits = IO.Ports.StopBits.One ' one stop bit
SerialPort.ReadTimeout = 1000 ' milliseconds so times out in 1 second if no response
SerialPort.Open() ' open the port
SerialPort.DiscardInBuffer() ' clear the input buffer
OutPacket(0) = Strings.Asc("U") ' send a U and this sets the baud rate
SerialPort.Write(OutPacket, 0, 1) ' send data in array outpacket start at 0 with 1 byte
Sleep(100) ' wait for response
SerialPort.Read(InPacket, 0, 1) ' read 1 byte back, should be 06
SerialPort.Close()
Catch ex As Exception
MsgBox("Error opening serial port - is another program using COM1? ")
SerialPort.Close()
End Try
If InPacket(0) = 6 Then
TextBox1.Text += "Initialised" + vbCrLf ' success
Else
TextBox1.Text += "Initialisation failed" + vbCrLf ' fail
End If
End Sub

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
' shutdown the program - make sure serial port is closed
SerialPort.Close()
End
End Sub

Private Sub ReadFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ReadFile.Click
' read back a file
' file name is in the text box in the format FILENAME.TXT where name is 1 to 8 chars, and extension is 3 chars
Dim Filename As String
Dim LengthFilename As Integer
Dim i As Integer
Dim j As Integer
Dim Counter As Integer
Dim BytesToRead As Integer
Dim Filesize As Long ' size of the file to read back
Dim PacketSize As Integer
Dim Packets As Long ' number of packets
Dim Remainder As Long ' remainder
Dim Readarray(50000) As Byte ' array to read data into
Dim Readcounter As Long ' counter for the readarray
Readcounter = 0 ' reset the counter for the readarray
PacketSize = 10 ' use 10 as convenient for picaxe
Filename = TextBox2.Text ' name of file to read
LengthFilename = Strings.Len(Filename) ' length of this filename
OutPacket(0) = Strings.Asc("@") ' command to read
OutPacket(1) = Strings.Asc("a")
OutPacket(2) = PacketSize ' get in blocks of n bytes
Counter = 3 ' increment counter
For i = 1 To LengthFilename ' send out filename
OutPacket(Counter) = Strings.Asc(Strings.Mid(Filename, i, 1))
Counter = Counter + 1
Next
OutPacket(Counter) = 0 ' null string to end
Counter = Counter + 1 ' one more byte
SerialPort.Open() ' open the serial port
SerialPort.Write(OutPacket, 0, Counter) ' send data in array outpacket start at 0
Sleep(100) ' wait for response
BytesToRead = SerialPort.BytesToRead ' how many bytes in the buffer? Should be 4
SerialPort.Read(InPacket, 0, BytesToRead) ' read them in
' get the file size in bytes, msb first, 4 bytes
Filesize = InPacket(0) * 16777216 + InPacket(1) * 65536 + InPacket(2) * 256 + InPacket(3)
Filesize = Filesize + 1 ' number of bytes plus there is an <ack> on the end
Counter = 0 ' reset the counter
Packets = Filesize \ PacketSize ' integer division
Remainder = Filesize Mod PacketSize ' and remainder left over
For i = 0 To Packets - 1 ' get the n blocks of data
OutPacket(0) = 6 ' ack
SerialPort.Write(OutPacket, 0, 1) ' request a block
Sleep(100) ' short delay
SerialPort.Read(InPacket, 0, PacketSize) ' read back a block
For j = 0 To PacketSize - 1 ' store in an array
Readarray(Readcounter) = InPacket(j)
Readcounter += 1 ' add 1 to readcounter
Next
Next
' now get the remainder
OutPacket(0) = 6 ' ack
SerialPort.Write(OutPacket, 0, 1) ' request a block
Sleep(100) ' wait for it to come back
SerialPort.Read(InPacket, 0, Remainder) ' read the remainder bytes, last should be 06
For j = 0 To Remainder - 1 ' store in an array
Readarray(Readcounter) = InPacket(j)
Readcounter += 1 ' add 1 to readcounter
Next
' print a message to say if it worked or not, ie is the last character =06
If Readarray(Readcounter - 1) = 6 Then
TextBox1.Text += vbCrLf + "Read success" + vbCrLf
Else
TextBox1.Text += vbCrLf + "Read fail" + vbCrLf
End If
' if the actual file is 4 bytes long then there is now an <ack> at the end
' = 5 bytes and the last readcounter+=1 makes readcounter=6
' so trim off and make the readcounter correct
Readcounter = Readcounter - 1 ' now will be = to 4
Readarray(Readcounter) = 0 'delete the ack (assume it came through, it should have)
SerialPort.Close() ' close the serial port
' vb.net test code - print the file in a text box
For i = 0 To Readcounter - 1
TextBox1.Text += Strings.Chr(Readarray(i))
Next
End Sub
Private Sub Dir_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Dir.Click
Dim DirBytes As Integer
Dim i As Integer
OutPacket(0) = Strings.Asc("@") ' command to read
OutPacket(1) = Strings.Asc("d") ' directory
OutPacket(2) = Strings.Asc("*") ' get all
OutPacket(3) = 0 ' zero to end
SerialPort.Open() ' open the serial port
SerialPort.Write(OutPacket, 0, 4) ' send data in array outpacket start at
Sleep(2000) ' wait a while, big dumps take a long time (or just poll the input)
DirBytes = SerialPort.BytesToRead ' number of bytes to read back
SerialPort.Read(InPacket, 0, DirBytes)
SerialPort.Close()
' display it
TextBox1.Text += vbCrLf
For i = 0 To DirBytes - 2 ' -1 and then 1 less for the ack at the end
If InPacket(i) <> 10 Then ' 10 is the delimiter in this list
TextBox1.Text += Strings.Chr(InPacket(i))
Else
' replace with a new line
TextBox1.Text += vbCrLf
End If
Next
End Sub
Private Sub Era_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Era.Click
Dim Filename As String
Dim LengthFilename As Integer
Dim i As Integer
Dim Counter As Integer
Filename = TextBox3.Text
LengthFilename = Strings.Len(Filename)
OutPacket(0) = Strings.Asc("@")
OutPacket(1) = Strings.Asc("e") ' erase
counter = 2
For i = 1 To LengthFilename ' send out filename
OutPacket(Counter) = Strings.Asc(Strings.Mid(Filename, i, 1))
Counter = Counter + 1
Next
OutPacket(Counter) = 0 ' null string to terminate
Counter += 1
SerialPort.Open() ' open the serial port
SerialPort.Write(OutPacket, 0, counter) ' send data in array outpacket start at 0
Sleep(100) ' wait for response
SerialPort.Read(InPacket, 0, 1) ' acknowledge
If InPacket(0) = 6 Then
TextBox1.Text += "Erase success" + vbCrLf ' success
Else
TextBox1.Text += "Erase failed" + vbCrLf ' fail
End If
SerialPort.Close()
End Sub

Private Sub Writefile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Writefile.Click
Dim Filestring As String ' convenient to store as a string for testing
Dim Filearray(1000) As Byte ' work with arrays for the file
Dim FileLength As Long
Dim PacketSize As Integer
Dim i As Integer
Dim j As Integer
Dim Filename As String
Dim Packets As Long
Dim PacketRemainder As Integer
Dim FileCounter As Long
FileCounter = 0
Filename = TextBox5.Text ' name of file to save
Filestring = TextBox4.Text ' this data would normally come from somewhere else
PacketSize = 10
FileLength = Strings.Len(Filestring)
' move the string into the array. (This string is just a convenience for testing)
For i = 1 To FileLength
Filearray(i - 1) = Strings.Asc(Strings.Mid(Filestring, i, 1)) ' convert to ascii value
Next
' file is ready to output
OutPacket(0) = Strings.Asc("@") ' command
OutPacket(1) = Strings.Asc("t") ' write
OutPacket(2) = PacketSize ' size of packets 0-50, new file (not append mode)
SerialPort.Open() ' open the serial port
SerialPort.Write(OutPacket, 0, 3) ' send data in array outpacket start at 0
For i = 1 To Strings.Len(Filename) ' send out the filename
OutPacket(0) = Strings.Asc(Strings.Mid(Filename, i, 1))
SerialPort.Write(OutPacket, 0, 1) ' send that byte
Next
OutPacket(0) = 0
SerialPort.Write(OutPacket, 0, 1) ' send out a null string to terminate
' now send out the 4 bytes for the file length
OutPacket(0) = FileLength \ 16777216 ' integer division
SerialPort.Write(OutPacket, 0, 1)
OutPacket(0) = FileLength \ 65536 ' integer division
SerialPort.Write(OutPacket, 0, 1)
OutPacket(0) = FileLength \ 256 ' integer division
SerialPort.Write(OutPacket, 0, 1)
OutPacket(0) = FileLength Mod 256 ' remainder
SerialPort.Write(OutPacket, 0, 1)
Sleep(100) ' wait for acknowledge
SerialPort.Read(InPacket, 0, 1) ' should be 06
If InPacket(0) <> 6 Then
TextBox1.Text += "Error with first read"
End If
' now send out the data in packetsize groups
Packets = FileLength \ PacketSize ' integer division
PacketRemainder = FileLength Mod PacketSize
For i = 1 To Packets
For j = 1 To PacketSize
OutPacket(0) = Filearray(FileCounter)
SerialPort.Write(OutPacket, 0, 1)
FileCounter = FileCounter + 1
Next
Sleep(100)
SerialPort.Read(InPacket, 0, 1)
If InPacket(0) <> 6 Then
TextBox1.Text += "Error with packet"
End If
Next
' now send out the remainder if it exists
If PacketRemainder <> 0 Then
For j = 1 To PacketRemainder
OutPacket(0) = Filearray(FileCounter)
SerialPort.Write(OutPacket, 0, 1)
FileCounter = FileCounter + 1

Next
Sleep(100)
SerialPort.Read(InPacket, 0, 1)
If InPacket(0) <> 6 Then
TextBox1.Text += "Write fail on last packet"
End If
End If
SerialPort.Close()
End Sub
End Class