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
#!/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;
}
#--------------------------------------------------
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/10/27 00:56,
216.73.216.180,10-8-63-169: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? <A HREF="http://massmind.org/Techref/mem/flash/mmc/PCppDelphi.htm"> MMC,parallel port,Delphi Source code</A> |
| Did you find what you needed? |
Welcome to massmind.org! |
Welcome to massmind.org! |
.