Led Sign with MMC Memory Card

 

History

At the beginning this project was to buy a led sign to interface with my home automation network. This automation network display information like temperature, wind speed, humidity, etc.  I had bought on EBay a LED Sign but when I received it I got a surprise! There was no serial port to program messages... After a couple of days, I look inside to see how it was built.  The principle of operation is simple you have just 160 bits shift register with drivers for LEDs. I removed the old CPU from the LED sign and connected a couple of wires to the shift register (column driver) and the row driver to an ATMega128. The main reason why I chose to use an ATMega128 was the need of a large amount of RAM.  I decided to use a MMC memory card to store all the messages for 3 reasons: low cost, SPI interface and a lot of space for messages.

 

Features

puce 3 Colors messages
puce 5 Scrolling speed
puce Serial port for uploading messages

 

Pictures

Click to enlarge

Surgery

New CPU Board

What it's look like

 

Sources codes & Schematics

-CPU Shematic in PDF format

-LED Board Shematic in PDF format


//**************************************
// Moving Sign V:1.0
// Version 1.0 Dec 2003
// Sylvain Bissonnette
//
// Clock : 16Mhz
// Stack : 32
//**************************************

//**************************************
//            I N C L U D E
//**************************************
#include <macros.h>
#include <stdlib.h>
#include <iom128v.h>
#include <STRING.H>

//**************************************
//            D E F I N E
//**************************************
#define VERSION     10
#define TRUE        0x01
#define FALSE       0x00

#define SERIALPORT  PORTA
#define SERIALDDR   DDRA
#define SERIALBIT   0x01
#define SERIALCLK   0x02

#define ROWPORT     PORTC
#define ROWDDR      DDRC

#define SPIDDR      DDRB
#define SPIPORT     PORTB
#define SPIPIN      PINB

#define SCLK        0x02
#define MOSI        0x04
#define MISO        0x08
#define CS          0x01
#define MMCPOWER    0x10

#define RED         0
#define GREEN       1
#define AMBER       2

/*--------------------------------------------------------------------------------------------------
                                     Character generator

             This table defines the standard ASCII characters in a 5x7 dot format.
--------------------------------------------------------------------------------------------------*/
static const char FontLookup [][5] =
{
    { 0x00, 0x00, 0x00, 0x00, 0x00 },  // sp
    { 0x00, 0x00, 0x2f, 0x00, 0x00 },   // !
    { 0x00, 0x07, 0x00, 0x07, 0x00 },   // "
    { 0x14, 0x7f, 0x14, 0x7f, 0x14 },   // #
    { 0x24, 0x2a, 0x7f, 0x2a, 0x12 },   // $
    { 0xc4, 0xc8, 0x10, 0x26, 0x46 },   // %
    { 0x36, 0x49, 0x55, 0x22, 0x50 },   // &
    { 0x00, 0x05, 0x03, 0x00, 0x00 },   // '
    { 0x00, 0x1c, 0x22, 0x41, 0x00 },   // (
    { 0x00, 0x41, 0x22, 0x1c, 0x00 },   // )
    { 0x14, 0x08, 0x3E, 0x08, 0x14 },   // *
    { 0x08, 0x08, 0x3E, 0x08, 0x08 },   // +
    { 0x00, 0x00, 0x50, 0x30, 0x00 },   // ,
    { 0x10, 0x10, 0x10, 0x10, 0x10 },   // -
    { 0x00, 0x60, 0x60, 0x00, 0x00 },   // .
    { 0x20, 0x10, 0x08, 0x04, 0x02 },   // /
    { 0x3E, 0x51, 0x49, 0x45, 0x3E },   // 0
    { 0x00, 0x42, 0x7F, 0x40, 0x00 },   // 1
    { 0x42, 0x61, 0x51, 0x49, 0x46 },   // 2
    { 0x21, 0x41, 0x45, 0x4B, 0x31 },   // 3
    { 0x18, 0x14, 0x12, 0x7F, 0x10 },   // 4
    { 0x27, 0x45, 0x45, 0x45, 0x39 },   // 5
    { 0x3C, 0x4A, 0x49, 0x49, 0x30 },   // 6
    { 0x01, 0x71, 0x09, 0x05, 0x03 },   // 7
    { 0x36, 0x49, 0x49, 0x49, 0x36 },   // 8
    { 0x06, 0x49, 0x49, 0x29, 0x1E },   // 9
    { 0x00, 0x36, 0x36, 0x00, 0x00 },   // :
    { 0x00, 0x56, 0x36, 0x00, 0x00 },   // ;
    { 0x08, 0x14, 0x22, 0x41, 0x00 },   // <
    { 0x14, 0x14, 0x14, 0x14, 0x14 },   // =
    { 0x00, 0x41, 0x22, 0x14, 0x08 },   // >
    { 0x02, 0x01, 0x51, 0x09, 0x06 },   // ?
    { 0x32, 0x49, 0x59, 0x51, 0x3E },   // @
    { 0x7E, 0x11, 0x11, 0x11, 0x7E },   // A
    { 0x7F, 0x49, 0x49, 0x49, 0x36 },   // B
    { 0x3E, 0x41, 0x41, 0x41, 0x22 },   // C
    { 0x7F, 0x41, 0x41, 0x22, 0x1C },   // D
    { 0x7F, 0x49, 0x49, 0x49, 0x41 },   // E
    { 0x7F, 0x09, 0x09, 0x09, 0x01 },   // F
    { 0x3E, 0x41, 0x49, 0x49, 0x7A },   // G
    { 0x7F, 0x08, 0x08, 0x08, 0x7F },   // H
    { 0x00, 0x41, 0x7F, 0x41, 0x00 },   // I
    { 0x20, 0x40, 0x41, 0x3F, 0x01 },   // J
    { 0x7F, 0x08, 0x14, 0x22, 0x41 },   // K
    { 0x7F, 0x40, 0x40, 0x40, 0x40 },   // L
    { 0x7F, 0x02, 0x0C, 0x02, 0x7F },   // M
    { 0x7F, 0x04, 0x08, 0x10, 0x7F },   // N
    { 0x3E, 0x41, 0x41, 0x41, 0x3E },   // O
    { 0x7F, 0x09, 0x09, 0x09, 0x06 },   // P
    { 0x3E, 0x41, 0x51, 0x21, 0x5E },   // Q
    { 0x7F, 0x09, 0x19, 0x29, 0x46 },   // R
    { 0x46, 0x49, 0x49, 0x49, 0x31 },   // S
    { 0x01, 0x01, 0x7F, 0x01, 0x01 },   // T
    { 0x3F, 0x40, 0x40, 0x40, 0x3F },   // U
    { 0x1F, 0x20, 0x40, 0x20, 0x1F },   // V
    { 0x3F, 0x40, 0x38, 0x40, 0x3F },   // W
    { 0x63, 0x14, 0x08, 0x14, 0x63 },   // X
    { 0x07, 0x08, 0x70, 0x08, 0x07 },   // Y
    { 0x61, 0x51, 0x49, 0x45, 0x43 },   // Z
    { 0x00, 0x7F, 0x41, 0x41, 0x00 },   // [
    { 0x02, 0x04, 0x08, 0x10, 0x20 },   // back slash
    { 0x00, 0x41, 0x41, 0x7f, 0x00 },   // ]
    { 0x04, 0x02, 0x01, 0x02, 0x04 },   // ^
    { 0x40, 0x40, 0x40, 0x40, 0x40 },   // _
    { 0x00, 0x01, 0x02, 0x04, 0x00 },   // '
    { 0x20, 0x54, 0x54, 0x54, 0x78 },   // a
    { 0x7F, 0x48, 0x44, 0x44, 0x38 },   // b
    { 0x38, 0x44, 0x44, 0x44, 0x20 },   // c
    { 0x38, 0x44, 0x44, 0x48, 0x7F },   // d
    { 0x38, 0x54, 0x54, 0x54, 0x18 },   // e
    { 0x08, 0x7E, 0x09, 0x01, 0x02 },   // f
    { 0x0C, 0x52, 0x52, 0x52, 0x3E },   // g
    { 0x7F, 0x08, 0x04, 0x04, 0x78 },   // h
    { 0x00, 0x44, 0x7D, 0x40, 0x00 },   // i
    { 0x20, 0x40, 0x44, 0x3D, 0x00 },   // j
    { 0x7F, 0x10, 0x28, 0x44, 0x00 },   // k
    { 0x00, 0x41, 0x7F, 0x40, 0x00 },   // l
    { 0x7C, 0x04, 0x18, 0x04, 0x78 },   // m
    { 0x7C, 0x08, 0x04, 0x04, 0x78 },   // n
    { 0x38, 0x44, 0x44, 0x44, 0x38 },   // o
    { 0x7C, 0x14, 0x14, 0x14, 0x08 },   // p
    { 0x08, 0x14, 0x14, 0x18, 0x7C },   // q
    { 0x7C, 0x08, 0x04, 0x04, 0x08 },   // r
    { 0x48, 0x54, 0x54, 0x54, 0x20 },   // s
    { 0x04, 0x3F, 0x44, 0x40, 0x20 },   // t
    { 0x3C, 0x40, 0x40, 0x20, 0x7C },   // u
    { 0x1C, 0x20, 0x40, 0x20, 0x1C },   // v
    { 0x3C, 0x40, 0x30, 0x40, 0x3C },   // w
    { 0x44, 0x28, 0x10, 0x28, 0x44 },   // x
    { 0x0C, 0x50, 0x50, 0x50, 0x3C },   // y
    { 0x44, 0x64, 0x54, 0x4C, 0x44 },   // z
    { 0x00, 0x08, 0x36, 0x41, 0x00 },   // {
    { 0x00, 0x00, 0x7f, 0x00, 0x00 },   // |
    { 0x00, 0x41, 0x36, 0x08, 0x00 },   // }
    { 0x04, 0x02, 0x04, 0x08, 0x04 },   // ~
    { 0x00, 0x00, 0x36, 0x00, 0x00 },   // ¦
    { 0x0e, 0x51, 0x31, 0x11, 0x08 },   // Ç
    { 0x3c, 0x41, 0x40, 0x21, 0x7c },   // ü 
    { 0x38, 0x54, 0x56, 0x55, 0x18 },   // é 
    { 0x20, 0x56, 0x55, 0x56, 0x78 },   // â 
    { 0x20, 0x55, 0x54, 0x55, 0x78 },   // ä 
    { 0x20, 0x55, 0x56, 0x54, 0x78 },   // à 
    { 0x08, 0x08, 0x2a, 0x1c, 0x08 },   // Right Arrow  (chr 134)
    { 0x0e, 0x51, 0x31, 0x11, 0x08 },   // ç 
    { 0x38, 0x56, 0x55, 0x56, 0x18 },   // ê 
    { 0x38, 0x55, 0x54, 0x55, 0x18 },   // ë 
    { 0x38, 0x55, 0x56, 0x54, 0x18 },   // è 
    { 0x00, 0x45, 0x7c, 0x41, 0x00 },   // ï 
    { 0x00, 0x46, 0x7d, 0x42, 0x00 },   // î 
    { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f },   // free (chr 141)   
    { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f },   // free (chr 142)      
    { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f },   // free (chr 143)   
    { 0x7c, 0x54, 0x56, 0x55, 0x44 },   // É 
    { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f },   // free (chr 145)         
    { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f },   // free (chr 146)         
    { 0x38, 0x46, 0x45, 0x46, 0x38 },   // ô 
    { 0x7f, 0x7f, 0x7f, 0x7f, 0x7f },   // free (chr 148)      
    { 0x38, 0x45, 0x46, 0x44, 0x38 },   // ò 
    { 0x3c, 0x42, 0x41, 0x22, 0x7c },   // û
    { 0x3c, 0x41, 0x42, 0x20, 0x7c }    // ù    
};

//**************************************
//          P R O T O T Y P E
//**************************************
void main(void);
void GetNextPage(unsigned char First);
void uart1_rx_isr(void);
void Analyse(void);
void TxString(const char *ch);
void TxChar(char ch);
void ClrSCR(void);
void WriteString(int x,const unsigned char *ptr,unsigned char color);
void WriteChar(int ptr,unsigned char ch,unsigned char color);
int DoDisplay(void);
void SendOnBit(void);
void SendOffBit(void);
void Delay_1ms(int Del);

int MMCInit(void);
void MMCInfo(void);
int MMCReadSector(unsigned long lba, unsigned char * s);
int MMCWriteSector(unsigned long lba, unsigned char *s);
unsigned char MMCGet(void);
unsigned char MMCDataToken(void);
void MMCCommand(unsigned char command, unsigned int px, unsigned int py);

unsigned char SpiByte(unsigned char byte);

//******************************************************************
//*   Global Variable
//******************************************************************
char Red[512];
char Green[512];
char GreenT[512];
char RedT[512];
int MsgLen;
unsigned char Speed = 3;
unsigned char MaxPage = 0;

unsigned char NewData = FALSE;

char RxBuffer[100];

//**************************************
//            M A I N
//**************************************
void main(void)
{
WDR();
WDTCR = 0x0f; // Watch Dog enable

ROWDDR = 0xff;
SERIALDDR |= SERIALBIT + SERIALCLK;

UCSR1B = 0x00; //disable while setting baud rate
UCSR1A = 0x00;
UCSR1C = 0x06;
UBRR1L = 0x33; //set baud rate lo for 19200 at 16Mhz
UBRR1H = 0x00; //set baud rate hi
UCSR1B = 0x98;

SEI();

ClrSCR();
while (MMCInit() != 0);
cstrcpy(&RxBuffer[0],"<99MMC: ~GInit OK  ~AVer:1.0>\0");
Analyse();

MMCReadSector(0,&GreenT[0]);
MaxPage = GreenT[0];

GetNextPage(TRUE);
while(1)
       {
       if (DoDisplay()) GetNextPage(FALSE);
       if (NewData == TRUE)
         {
         Analyse();
         NewData = FALSE;
         }
       WDR();
       }
}

//**************************************
// void uart1_rx_isr(void)
//**************************************
#pragma interrupt_handler uart1_rx_isr:31
void uart1_rx_isr(void)
{
static char *Ptr = &RxBuffer[0];
unsigned char ch;

ch = UDR1;
if (Ptr > &RxBuffer[100]) Ptr = &RxBuffer[0];

if (ch == '<') Ptr = &RxBuffer[0];
*Ptr++ = ch;
if (ch == '>') NewData = TRUE;
}

//**************************************
// void Analyse(void)
//**************************************
void Analyse(void)
{
char *Ptr = &RxBuffer[0];
unsigned char Page;
int x = 0;
unsigned char Color = AMBER;
unsigned char Spd = 3;
unsigned char Row;

Row = ROWPORT;
ROWPORT = 0x00;

ClrSCR();
Ptr++;
Page = (((*Ptr)-0x30) * 10) + ((*(Ptr+1)-0x30));
Ptr++;
Ptr++;

if ((Page == 99) && (*Ptr == 'D'))
   {
   for (x=0;x<512;x++) GreenT[x] = 0x00;
   MMCWriteSector(0,&GreenT[0]);
   MaxPage = 0;
   TxString("Delete ALL\n\r\0");
   ROWPORT = Row;
   return;
   }

if ((Page > 99) || (Page < 0))
   {
   ROWPORT = Row;
   return;
   }

if (Page > (MaxPage+1))
   {
   ROWPORT = Row;
   return;
   }

while (*Ptr != '>')
     {
     if (*Ptr == '~')
       {
       Ptr++;
       if (*Ptr == 'R') Color = RED;
       if (*Ptr == 'G') Color = GREEN;
       if (*Ptr == 'A') Color = AMBER;
       if (*Ptr == '1') Spd = 1;
       if (*Ptr == '2') Spd = 2;
       if (*Ptr == '3') Spd = 3;
       if (*Ptr == '4') Spd = 4;
       if (*Ptr == '5') Spd = 5;
       Ptr++;
       }
     else WriteChar(x++,*Ptr++,Color);
     }

GreenT[510] = Spd;
RedT[510] = (x*6)>>8;
RedT[511] = (x*6);

MMCWriteSector((Page*2)+1, &RedT[0]);
MMCWriteSector((Page*2)+2, &GreenT[0]);
if ((Page > MaxPage) && (Page != 99))
   {
   for (x=0;x<512;x++) GreenT[x] = 0x00;
   GreenT[0] = Page;
   MMCWriteSector(0,&GreenT[0]);
   MaxPage = Page;
   }
ROWPORT = Row;
}

//**************************************
// void TxString(unsigned char *ch)
//**************************************
void TxString(const char *ch)
{
while(*ch != 0x00) TxChar(*ch++);
}

//**************************************
// void TxChar(unsigned char ch)
//**************************************
void TxChar(unsigned char ch)
{
while (!(UCSR1A & 0x20)) WDR(); // Wait for empty transmit buffer
UDR1 = ch;                     // Write char
}

//**************************************
// void GetNextPage(void)
//**************************************
void GetNextPage(unsigned char First)
{
static int Page = 0;

WDR();
if (First == TRUE) // Retrive the First time init message of status
   {
   MMCReadSector((99*2)+1, &Red[0]);
   MMCReadSector((99*2)+2, &Green[0]);
   Speed = Green[510];
   MsgLen = (Red[510]<<8) + Red[511];
   }
else
   {
   MMCReadSector((Page*2)+1, &Red[0]);
   MMCReadSector((Page*2)+2, &Green[0]);
   Speed = Green[510];
   if (Speed > 5) Speed = 1;
   MsgLen = (Red[510]<<8) + Red[511];

   Page++;
   if (Page > MaxPage) Page = 0;
   }
}

//**************************************
// void ClrSCR(void)
//**************************************
void ClrSCR(void)
{
int i;

for (i=0;i<510;i++)
   {
   WDR();
   RedT[i] = 0x00;
   GreenT[i] = 0x00;
   }
}

//**************************************
// void WriteString(unsigned char ptr,unsigned char color)
//**************************************
void WriteString(int x,const unsigned char *ptr,unsigned char color)
{
while(*ptr != 0x00) WriteChar(x++,*ptr++,color);
}

//**************************************
// void WriteChar(unsigned char x,unsigned char ch,unsigned char color)
//**************************************
void WriteChar(int x,unsigned char ch,unsigned char color)
{
unsigned char i;

x *= 6;

if (x > 509) return; // Max of 85 char per page 85*6 = 510

for (i=0;i<5;i++)
    {
   WDR();
    if (color == RED) RedT[x++] = FontLookup[ch - 32][i];
   if (color == GREEN) GreenT[x++] = FontLookup[ch - 32][i];
   if (color == AMBER)
      {
      RedT[x] = FontLookup[ch - 32][i];
      GreenT[x++] = FontLookup[ch - 32][i];
      }
    }
RedT[x] = 0x00;
GreenT[x++] = 0x00;
}

//**************************************
// void DoDisplay(void)
//**************************************
int DoDisplay(void)
{
unsigned char Row;
unsigned char Col;
unsigned char RowMask;
int i,j;
static int Pos = 80;
char GreenT[80];
char RedT[80];
char SHIFT[160];

WDR();
// Clear Buffer's
for (i=0;i<80;i++)
   {
   GreenT[i] = 0x00;
   RedT[i] = 0x00;
   }

// Do Scrolling Right to Left
if (Pos >= 0)
   {
   j = 0;
   for (i=Pos;i<80;i++)
         {
      GreenT[i] = Green[j];
      RedT[i] = Red[j];
      j++;
      }
   }
else
   {
   j = 0;
   for (i=abs(Pos);i<(80+abs(Pos));i++)
         {
      GreenT[j] = Green[i];
      RedT[j] = Red[i];
      j++;
      }
   }

Pos--;
if (Pos < 0-MsgLen)
   {
   Pos = 80;
   return 1;
   }

// Append Red & Green to the Real shifting buffer

j = 0;
for (i=0;i<80;i++)
   {
   if ((!(i % 8)) && (i != 0)) j +=8;
   SHIFT[j] = GreenT[i];
   SHIFT[j+8] = RedT[i];
   j++;
   }

// Do the Row/Column display

for (i=0;i<Speed;i++)
   {
   RowMask = 0x40;
   for (Row=0;Row<7;Row++)
      {
      WDR();
      for (Col=0;Col<160;Col++)
         {
         if ((SHIFT[Col] & RowMask) == RowMask) SendOnBit();
         else SendOffBit();
         }
      ROWPORT = RowMask;
      Delay_1ms(1);
      ROWPORT = 0x00;
      RowMask = RowMask >> 1;
      }
   }
return 0;
}

//**************************************
// void SendOnBit(void)
//**************************************
void SendOnBit(void)
{
SERIALPORT |= SERIALBIT;
SERIALPORT |= SERIALCLK;
SERIALPORT &= ~SERIALCLK;
}

//**************************************
// void SendOffBit(void)
//**************************************
void SendOffBit(void)
{
SERIALPORT &= ~SERIALBIT;
SERIALPORT |= SERIALCLK;
SERIALPORT &= ~SERIALCLK;
}

//**************************************
// void Delay_1ms(void)
//**************************************
void Delay_1ms(int Del)
{
int i;

while (Del--)
     {
     for (i=0;i<2000;i++) WDR();
     }
}

/*************************************************************
 * MMC Init function
 *
 * - flushes card receive buffer
 * - selects card
 * - sends the reset command
 * - sends the initialization command, waits for card ready
 *************************************************************/
int MMCInit(void)
{
unsigned int i;
unsigned char Byte;

SPIDDR = SCLK + MOSI + CS + MMCPOWER;
SPIPORT = 0x00;
Delay_1ms(500);
SPIPORT |= MMCPOWER;
SPIPORT |= CS;
SPCR = (1<<SPE) | (1<<MSTR) | (1<<SPR1) | (1<<SPR0);    /* enable SPI as master, set clk divider */
Delay_1ms(250);

/* start off with 80 bits of high data with card deselected */
for(i=0;i<10;i++)
SpiByte(0xff);
SPIPORT &= ~CS;        /* select card */

/* now send CMD0 - go to idle state */
MMCCommand(0,0,0);
if (MMCGet() != 1)
   {
   SPIPORT |= CS;
   return -1;  // MMC Not detected
   }

/* send CMD1 until we get a 0 back, indicating card is done initializing */
i = 0xffff;
while ((SpiByte(0xff) != 0) && (--i))
     {
     MMCCommand(1,0,0);
     WDR();
     }
if (i == 0)
   {
   SPIPORT |= CS;
   return -2;  // Init Fail
   }

SPIPORT |= CS;
return 0;
}

/************************************************************
 * void MMCInfo(void)
 *
 * - gets and prints formatted CID and CSD info from card
 ************************************************************/
void MMCInfo(void)
{
int i;

MMCCommand(10,0,0);
if (MMCDataToken() != 0xfe) TxString("MMC: error during CID read\n\r\0");
else TxString("MMC: CID read\n\r\0");

// Skip 3 byte Manufacturer ID
SpiByte(0xff);
SpiByte(0xff);
SpiByte(0xff);

TxString("MMC: Product Name : ");
for (i=0;i<7;i++) TxChar(SpiByte(0xff));
TxString("\n\r\0");

for (i=0;i<9;i++) SpiByte(0xff); // Read 9 left byte

SPIPORT |= CS;
}

/************************************************************
 * int MMCReadSector(unsigned long lba, unsigned char * s)
 *
 * - reads a sector from the card (512 bytes)
 * - takes sector # as param
 ************************************************************/
int MMCReadSector(unsigned long lba, char *s)
{
unsigned int i;

MMCCommand(17,(lba>>7) & 0xffff, (lba<<9) & 0xffff);
if (MMCDataToken() != 0xfe)
   {
   SEI();
   return -1;
   }

for (i=0;i<512;i++)     /* read the sector */
   {
    *s++ = SpiByte(0xff);
   }
SpiByte(0xff);          /* checksum -> don't care about it for now */
SpiByte(0xff);       /* checksum -> don't care about it for now */
SPIPORT |= CS;
return 0;
}

/************************************************************
 * int MMCWriteSector(unsigned long lba, unsigned char * s)
 *
 * - reads a sector from the card (512 bytes)
 * - takes sector # as param
 ************************************************************/
int MMCWriteSector(unsigned long lba, char *s)
{
unsigned int i;

MMCCommand(24, (lba>>7)& 0xffff, (lba<<9)& 0xffff);
if (MMCGet() == 0xff) return -1;

SpiByte(0xfe);  // Send Start Byte

for (i=0;i<512;i++)       /* read the sector */
   {
    SpiByte(*s++);
   }
SpiByte(0xff);          /* checksum -> don't care about it for now */
SpiByte(0xff);       /* checksum -> don't care about it for now */
SpiByte(0xff);       /* Read "data response byte"                 */

i = 0xffff;
while ((SpiByte(0xff) == 0x00) && (--i)); /* wait for write finish */
if (i == 0) return -1; // Error

SPIPORT |= CS;
return 0;
}

/************************************************************
 * unsigned char MMCGet(void)
 *
 * - pings the card until it gets a non-0xff value
 * - returns one byte of read info
 ************************************************************/
unsigned char MMCGet(void)
{
unsigned int i = 0xffff;
unsigned char Byte = 0xff;

while((Byte == 0xff) && (--i)) Byte = SpiByte(0xff);
return Byte;
}

/************************************************************
 * int MMCDataToken(void)
 *
 * - pings the card until it gets data token
 * - returns one byte of read info (data token)
 ************************************************************/
unsigned char MMCDataToken(void)
{
unsigned int i = 0xffff;
unsigned char Byte = 0xff;

while((Byte != 0xfe) && (--i)) Byte = SpiByte(0xff);
return Byte;
}

/************************************************************
 * void MMCCommand(unsigned char command, unsigned int px, unsigned int py)
 *
 * - send one byte of 0xff, then issue command + params + (fake) crc
 * - eat up the one command of nothing after the CRC
 ************************************************************/
void MMCCommand(unsigned char command, unsigned int px, unsigned int py)
{
SPIPORT &= ~CS;
SpiByte(0xff);
SpiByte(command | 0x40);
SpiByte((unsigned char)((px >> 8)&0x0ff)); /* high byte of param y */
SpiByte((unsigned char)(px & 0x00ff));     /* low byte of param y */
SpiByte((unsigned char)((py >> 8)&0x0ff)); /* high byte of param x */
SpiByte((unsigned char)(py & 0x00ff));     /* low byte of param x */
SpiByte(0x95);            /* correct CRC for first command in SPI          */
                          /* after that CRC is ignored, so no problem with */
                          /* always sending 0x95                           */
SpiByte(0xff);
}

/*****************************************************
 * Main SPI routine
 *  - transmits a byte and receives a byte simultaneously
 *  - received byte is returned
 *  - if you only want to read a byte, put a dummy 
 *    (say 0xff) in the transmit slot
 ****************************************************/
unsigned char SpiByte(unsigned char byte)
{
WDR();
SPDR = byte;               /* put byte to send in SPDR, which initiates xmit  */
while(!(SPSR & (1<<SPIF)));/* wait for completion */
return SPDR;               /* return with byte shifted in from slave */
}