MMC FLASH Cards

Accessing MMC from a PC parallel port with Delphi Source code

Accessing MMC from a PC parallel port with Delphi Source code

By David Beals [DBeals at transdyn.com]

A 16 MB MMC card is plugged into a simple logic level translator and into the parallel port of a PC.

A Delphi 2 program operates the card. The program initializes the card to SPI mode.

It can then read registers, like the CSD, "Card Specific Device", from which the card capacity can be derived.

The included file "readResults.txt" shows the results of reading the CID, CSD and sector at address 0 of a sample MMC.

(A Perl script "mmc.pl" is included that demonstrates decoding the card size from the CSD. That was not done in the Delphi code.)

After init, Delphi can read any individual data block and display the contents.

Or it can read consecutive data blocks continuously until an error is returned, hopefully signalling the end of the card's storage.

It can demo a simple block write, where a few hardcoded characters are written to a hardcoded data block.

The block write performs an implicit erase; the program does not attempt any explicit block or multiblock erases, although it should be able to do those.

Later I'll forward a schematic and photo of the MMC logic translator board. But basically, it consists of a string of diodes that provides about 3.3 volts from a 5 volt supply. Three PC parallel port outputs go to three Schmidt trigger inverters (a CMOS 74HCT14) which run three transistors running from the 3.3 volts, which drive three MMC input lines. One MMC output directly drives a Schmidt trigger which feeds an input bit of the parallel port.

Disclaimers - I'm a DBA, not an electrical engineer! The logic inverter seems to work fine but is a slapped-together design. Also, I would not call myself a software engineer; the Delphi and Perl seem to do what they are supposed to but were not designed to be any sort of finished product; they are slapped together, too! This was all just for fun! So do with it what you will!

David Beals
Dec. 3, 2004

MMC.PL

#!/usr/bin/perl -w
#------------------------------------------------------------------------
# mmc.pl
#
# Demo of decoding the memory size from the bit array from
# a CSD register of an MMC card.
#
# This follows the algorithm on page 3-15 of the PDF document
# "MultiMediaCard Product Manual" rev 5.1, SanDisk corp.
# Perl uses the same mnemonics like "MULT", ... as SanDisk,
# to help follow this convoluted decoding process.
#
# card specific device register: 16 bytes, CRC and trailing FFs:
# CSD: 48,0E,01,2A,0F,F9,81,EA,EC,B1,01,E1,8A,40,00,BB,82,9C,FF,FF
#
# Results of running this script for 16M card: 16089088 = 0xF58000 bytes.
#
# This agrees with experimental reads until an out-of-bounds error
# is returned - success in accessing from 00000000 to 00F57E00, then
# the read returns an error, as expected.
#
#------------------------------------------------------------------------
use strict;
my(@csd, $memcap);
my($MULT, $BLOCK_LEN, $READ_BL_LEN, $BLOCK_NR, $C_SIZE, $C_SIZE_MULT);
 @csd = (72,14,01,42,15,249,129,234,236,177,01,225,138,64,00,187);
 $C_SIZE_MULT = &bits(49, 47);
 $MULT = $C_SIZE_MULT << 3;
 $READ_BL_LEN = &bits(83, 80);
 $BLOCK_LEN = 1 << $READ_BL_LEN;
 $C_SIZE = &bits(73, 62);
 $BLOCK_NR = ($C_SIZE+1) * $MULT;
 $memcap = $BLOCK_NR * $BLOCK_LEN;
 print "Mem cap: $memcap\n";
#------- end of main ------------------------------
#
# Return the number consisting of the specified bits
sub bits{
 my($i, $start, $end, $result, $shift, $bit);
 $start = $_[0];
 $end = $_[1];
 $result = 0;
 $i = $start;
 $shift = $start - $end;
 while($i >= $end){
 $bit = &bit($i);
 $result = $result + ($bit << $shift);
 $i--;
 $shift--;
 }
 $result;
}
#--------------------------------------------------
#
# Return the bit (0 or 1) at the argument location from the CSD array
sub bit{
 my($bitofbyte, $bit, $byte, $bytenumber, $bitnumber);
 $bitnumber = $_[0];
 $bytenumber = 15 - (int($bitnumber/8));
 $byte = $csd[$bytenumber];
 $bitofbyte = 7 - ((127 - $bitnumber) - (8 * $bytenumber));
 $bit = ($byte >> $bitofbyte) & 1;
 if ($bit > 0){ $bit = 1; }
 $bit;
}
#--------------------------------------------------

MMC.PAS

unit mmc;
// access MMC adaptor plugged into parallel port
//
// First init the MMC, then do whatever you want - read and
// display the contents of any single sector, read sectors 
// until there aren't any more, write a sample sector, read 
// the contents of several registers. 
// You can re-init the MMC at any time, if you feel like it.
// I cannot guarantee that this is exactly correct in the way that
// it operates the MMC but it seems to work flawlessly at the moment...
interface
uses
 Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls;
type
 TForm1 = class(TForm)
 Button1: TButton;
 Edit2: TEdit;
 Button2: TButton;
 Memo1: TMemo;
 Button9: TButton;
 Button5: TButton;
 Button8: TButton;
 Button10: TButton;
 Button11: TButton;
 Edit1: TEdit;
 Edit3: TEdit;
 Button21: TButton;
 Edit4: TEdit;
 Button15: TButton;
 Button17: TButton;
 Edit5: TEdit;
 Button3: TButton;
 procedure FormCreate(Sender: TObject);
 procedure Button1Click(Sender: TObject);
 procedure Button2Click(Sender: TObject);
 procedure Button5Click(Sender: TObject);
 procedure Button8Click(Sender: TObject);
 procedure Button9Click(Sender: TObject);
 procedure Button10Click(Sender: TObject);
 procedure Button11Click(Sender: TObject);
 procedure Button21Click(Sender: TObject);
 procedure Button15Click(Sender: TObject);
 procedure Button17Click(Sender: TObject);
 procedure Button3Click(Sender: TObject);
 
 procedure CShi;
 procedure CSlo;
 procedure DThi;
 procedure DTlo;
 procedure CKhi;
 procedure CKlo;
 procedure readRegister(reg: byte; length: byte);
 procedure CloseCommand;
 procedure initMMC;
 procedure writeSector;
 procedure outb(Value: Byte);
 function inb: Byte;
 function dataResponse: byte;
 function sendgetByte(b: byte): byte;
 function Command(Cmd: byte; address: longint):byte;
 function readSector(address: longint): byte;
 private
 { Private declarations }
 public
 { Public declarations }
 end;
var
 Form1: TForm1;
 byte378: Word;
 Globaladdress: longint;
 Globalbuffer: array [1..640] of byte;
implementation
{$R *.DFM}
//------------------------------------------------
procedure TForm1.FormCreate(Sender: TObject);
begin
// Initialize the ports to the same value
// as the hardware boots, so the MMC interface control bits
// have no surprises when the program starts.
 byte378 := 0;
 outb(byte378);
end;
//-----------------------------------------------------
// this has to be the first thing to run on the MMC
procedure TForm1.initMMC;
var res, i: word;
begin
 CShi;
 for i := 1 to 10 do begin // send 80 clocks
 sendgetByte($FF);
 end;
 Command(0, 0); // Init into SPI mode
 sendgetByte($FF); // finish clocking this command
 CShi;
 i := 0;
 res := 1;
 while ((i < 100) and (res <> 0)) do begin
 Inc(i);
 res := Command(1,ドル 0);
 end;
 CShi;
 sendgetByte($FF);
end;
//------------------------------------------------------------
// read sector at address.
// return:
// 0 on success.
// 1 command error
// 2 data error
function TForm1.readSector(address: longint): byte;
var res: byte; i: word;
begin
 res := Command(17, address);
 if ((res <> $FE) and (res <> 0)) then begin
 Result := 1;
 exit;
 end;
 res := dataResponse;
 if (res = $FF) then begin
 Result := 2;
 exit;
 end;
 for i := 1 to 514 do globalBuffer[i] := sendgetByte($FF);
 CloseCommand;
 Result := 0;
end;
//-----------------------------------------------------
// Send command. Command ranges from 0 to like 30 or so.
// Command 0 returns no response, and requires genuine CRC (always 95ドル)
// Only the middle two bytes of the 32 bit address are non-zero.
function TForm1.Command(Cmd: byte; address: longint): byte;
var i, addr1, addr2, CRC: byte;
begin
 Result := 0;
 if (Cmd = 0) then CRC := 95ドル else CRC := $FF;
 addr1 := ((address and $FF00) shr 8);
 addr2 := ((address and $FF0000) shr 16);
 CSlo;
 sendgetByte(40ドル or Cmd);
 sendgetByte(00ドル);
 sendgetByte(addr2);
 sendgetByte(addr1);
 sendgetByte(00ドル);
 sendgetByte(CRC);
 if (Cmd = 0) then exit;
// after sending the command, get the response to this command.
 Result := $FF;
 i := 0;
 while ((i < 100) and (Result = $FF)) do begin
 Result := sendgetByte($FF);
 Inc(i);
 end;
end;
//------------------------------------------------------------
// Wait for the command to be processed. FF means busy.
// Processing complete is acknowledged with "FE".
function TForm1.dataResponse: byte;
var i: word;
begin
 Result := $FF;
 i := 0;
 while ((i < 1000) and (Result = $FF)) do begin
 Result := sendgetByte($FF);
 Inc(i);
 end;
// Did we get the expected FE?
 if (Result <> $FE) then begin
 Edit1.text := 'Received error: ' + IntToStr(Result);
 Memo1.Lines.Add( 'error received after ' + IntToStr(i) + ' rcv loops');
 end;
end;
//-----------------------------------------------------
function TForm1.sendGetByte(b: byte): byte;
var i, value: byte;
begin
 value := 0;
// the incoming bits arrive MSB first.
 for i := 0 to 7 do begin
// (pre-shift the incoming data so the final bit is not shifted)
 value := value shl 1;
// the MMC data bit connects to bit 4 (at 10ドル) of the parallel port
 
 if ((inb and 10ドル) = 10ドル) then value := value or 1;
// place the outgoing bit onto the data line
 if ((b and 80ドル) = 0) then DTlo else DThi;
 b := b shl 1;
 CKhi;
 CKlo;
 end;
 Result := value;
end;
//---------------------------------------------------------
// The low-level bit controls
procedure TForm1.outb(Value: Byte);
begin
 asm
 mov al, Value
 mov dx, 378ドル
 out dx, al
 end
end;
function TForm1.inb: Byte;
begin
 asm
 mov dx, 379ドル
 in al, dx
 mov @result, al
 end
end;
procedure TForm1.CSlo;
begin
 byte378 := byte378 and not 1; outb(byte378);
end;
procedure TForm1.CShi;
begin
 byte378 := byte378 or 1; outb(byte378);
end;
procedure TForm1.DTlo;
begin
 byte378 := byte378 and not 2; outb(byte378);
end;
procedure TForm1.DThi;
begin
 byte378 := byte378 or 2; outb(byte378);
end;
procedure TForm1.CKlo;
begin
 byte378 := byte378 and not 4; outb(byte378);
end;
procedure TForm1.CKhi;
begin
 byte378 := byte378 or 4; outb(byte378);
end;
//--------------------------------------------------
// various MMC register read commands
procedure TForm1.Button5Click(Sender: TObject); // CSD
begin readRegister(9, 16); end;
procedure TForm1.Button8Click(Sender: TObject); // CID
begin readRegister(10, 16); end;
procedure TForm1.Button11Click(Sender: TObject); // OCR
begin readRegister(58, 5); end;
procedure TForm1.Button3Click(Sender: TObject); // CMD_STATUS
begin readRegister(13, 2); end;
//--------------------------------------------------
procedure TForm1.readRegister(reg: byte; length: byte);
var val: array [1..64] of byte; res, i,j: word; s: string;
begin
 res := command(reg, 0);
 if ((res <> 254) and (res <> 0)) then begin
 Edit1.text := 'Received cmd error: ' + IntToStr(res);
 exit;
 end;
 res := dataResponse;
 if (res <> 254) then begin
 Edit1.text := 'Received data error: ' + IntToStr(res);
 exit;
 end;
// Registers vary from 4 to 16 bytes.
// Read more to read 2 byte CRC and two $FF to make sure we're at the end.
 i := 1;
 for j := 1 to length+4 do begin
 val[j] := sendgetByte($FF);
 i := j;
 end;
 CShi;
 sendgetByte($FF);
 Edit1.text := 'Break at index ' + IntToStr(i) + ' because result is ' + IntToStr(res);
// Done MMC process. Display the results
 Memo1.clear;
 for i := 1 to length+4 do begin
 s := 'index ' + IntToStr(i) + ' ' + IntToHex(val[i], 2);
 if ((val[i] > 20) and (val[i] < 128)) then
 s := s + ' ' + char(val[i]);
 Memo1.Lines.Add(s);
 end;
end;
//-----------------------------------------------------
// initialize the MMC to SPI mode and set GUI global address
procedure TForm1.Button2Click(Sender: TObject);
var addr1, addr2: word;
begin
 initMMC;
 Memo1.clear;
 Memo1.Lines.Add('MMC initialized');
 addr2 := StrToInt(Edit4.text);
 addr1 := StrToInt(Edit3.text);
 Globaladdress := addr2 shl 16 + addr1 shl 8;
end;
//------------------------------------------------------------
procedure TForm1.Button9Click(Sender: TObject); // read specific sector
var res: byte; s, msg: string; i, line, ptr: word;
 addr1, addr2: word;
begin
 Memo1.clear;
 addr2 := StrToInt(Edit4.text);
 addr1 := StrToInt(Edit3.text);
 Globaladdress := addr2 shl 16 + addr1 shl 8;
 msg := 'read sector ' + Edit4.text + Edit3.text + Edit2.text;
 msg := msg + ' address ' + IntToHex(Globaladdress, 4);
 Memo1.Lines.Add(msg);
 readSector(Globaladdress);
 ptr := 1;
 for i := 1 to 16 do begin
 s := '';
 for line := 1 to 32 do begin
 res := globalBuffer[ptr];
 Inc(ptr);
 if ((res > 20) and (res < 128)) then
 s := s + ' ' + char(res)
 else
 s := s + IntToHex(res, 2);
 end;
 Memo1.Lines.Add(s);
 end;
 Edit1.text := msg;
end;
//--------------------------------------------------------
procedure TForm1.Button10Click(Sender: TObject);
begin
 Memo1.clear;
end;
//--------------------------------------------------------
// read next sector
procedure TForm1.Button21Click(Sender: TObject);
var res: byte; i: word; addr: byte;
begin
 Inc(Globaladdress, 512);
 addr := ((Globaladdress and $FF00) shr 8);
 Edit3.text := '$' + IntToHex(addr, 2);
 addr := ((Globaladdress and $FF0000) shr 16);
 Edit4.text := '$' + IntToHex(addr, 2);
 Memo1.clear;
 Memo1.Lines.Add('Reading address ' + IntToHex(Globaladdress, 4));
 i := 0;
 res := 1;
 while ((i < 10) and (res <> 0)) do begin
 res := readSector(Globaladdress);
 Inc(i);
 end;
end;
//-------------------------------------------------------------
// write a test sector
// 16 meg card: nearly 32768 512-byte sectors (16,777,216 bytes)
// = 0x8000 sectors
// = a million bytes [ 10ドル 00 00 00 ]
procedure TForm1.Button15Click(Sender: TObject);
begin
 writeSector;
end;
//-------------------------------------------------------------
procedure TForm1.writeSector;
var res: byte; s, msg: string; i: word; status: byte;
 addr1, addr2: byte; address: longint;
begin
 Memo1.clear;
 addr2 := $E2;
 addr1 := 00ドル;
 address := addr2 shl 16 + addr1 shl 8;
 msg := 'write sector 00 E2 00 00';
 Memo1.Lines.Add(msg);
// This sends only the 6 byte command.
 res := Command(24, address);
 if (res > 0) then begin
 Edit1.text := 'Received error: ' + IntToStr(res);
 exit;
 end;
// Send the start token "$FE":
 sendgetByte($FE);
// 512 bytes of data follow, then 2 CRC bytes.
 for i := 1 to 128 do begin
 sendgetByte(73);
 sendgetByte(74);
 sendgetByte(75);
 sendgetByte(76);
 end;
// send dummy CRC
 sendgetByte($FF);
 sendgetByte($FF);
// get a response?
 res := $FF;
 i := 0;
 while ((i < 10000) and (res = $FF)) do begin
 res := sendgetByte($FF);
 Inc(i);
 end;
// check results:
 status := res and $F;
 s := 'Write delay of ' + IntToStr(i);
 if (status = 5) then
 s := s + ' Write success'
 else if (status = $B) then
 s := s + 'Rejected: CRC error'
 else if (status = $B) then
 s := s + 'Rejected: write error';
 Edit1.text := s;
 CloseCommand;
end;
//---------------------------------------------------
// read continuous
procedure TForm1.Button17Click(Sender: TObject);
var res: byte; i: word; done: byte; errorcounts: word;
begin
 done := 0;
 errorcounts := 0;
 Globaladdress := 00000ドル;
 while (done = 0) do begin
 Inc(Globaladdress, 512);
 i := 0;
 res := 1;
 while ((i < 2) and (res <> 0)) do begin
 res := readSector(Globaladdress);
 if (res > 0) then begin
 Memo1.Lines.Add('Read ' + IntToHex(Globaladdress, 4) + ' errs: ' + IntToStr(errorcounts));
 Inc(errorcounts);
 end;
 Inc(i);
 end;
 if (errorcounts > 3) then done := 1;
 end; // endless loop
end;
//---------------------------------------------------
procedure TForm1.Button1Click(Sender: TObject);
begin
 halt;
end;
//---------------------------------------------------
procedure TForm1.CloseCommand;
begin
 sendgetByte($FF);
 sendgetByte($FF);
 CShi;
 sendgetByte($FF);
 sendgetByte($FF);
end;
//-----------------------------------------------------
end.
//------------------------------------------------------------


file: /Techref/mem/flash/mmc/PCppDelphi.htm, 17KB, , updated: 2008年2月2日 09:30, local time: 2025年9月11日 22:29,
40.74.122.252:LOG IN

©2025 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?
Please DO link to this page! Digg it! / MAKE!

<A HREF="http://techref.massmind.org/techref/mem/flash/mmc/PCppDelphi.htm"> MMC,parallel port,Delphi Source code</A>

After you find an appropriate page, you are invited to your to this massmind site! (posts will be visible only to you before review) Just type a nice message (short messages are blocked as spam) in the box and press the Post button. (HTML welcomed, but not the <A tag: Instead, use the link box to link to another page. A tutorial is available Members can login to post directly, become page editors, and be credited for their posts.


Link? Put it here:
if you want a response, please enter your email address:
Attn spammers: All posts are reviewed before being made visible to anyone other than the poster.
Did you find what you needed?

Welcome to massmind.org!

Welcome to techref.massmind.org!

.

AltStyle によって変換されたページ (->オリジナル) /