I'm about to close a long outstanding task: Make SD Cards work on a Hercules LaunchPad.

I've done several half-baked attempts since November 2013. Thanks to persistence (not mine, martinvalencia's), it's finally working now.

 

 

 

Part 2 of this blog series shows how to turn TI's HDK example (expensive kit ) into a LaunchPad example (cheap kit ).

We'll adapt the TI port so that it can be used for any SPI module that's available on your controller, and in part 3 we'll enable the fatfs string write functions.

Our end goal is to have a working SD Card program that can write data, read files and list directories.

 

Porting the Port

 

The HDK for the Hercules family comes with an SD card slot, and with firmware to use it. The LaunchPads don't have an SD Card bay.

Still, we can leverage the HDK example code, and make a few changes.

  • in the HDK example, there's a source file that you have to edit depending on the SPI device that you use. We will make that a configurable item.
  • the example reads info from the card. In part 3, we'll be writing something to the card too, and read it back later.

 

If you're in a hurry, just create a new mmc-hercules.c and mmc-hercules.h file in the fatfs/port folder, copy the sources below into them, and skip to the next chapter.

 

mmc-hercules.c

/*-----------------------------------------------------------------------*/
/* MMC/SDC (in SPI mode) control module  (C)ChaN, 2007                  */
/*-----------------------------------------------------------------------*/
/* Only rcvr_spi(), xmit_spi(), disk_timerproc() and some macros        */
/* are platform dependent.                                              */
/*-----------------------------------------------------------------------*/


/*
* This file was modified from a sample available from the FatFs
* web site. It was modified to work with an HDK development
* board.
*
*
* jc 20151004 added option to change what SPI is used
*/
#include "spi.h"


#include <stdint.h>
#include <stdbool.h>
#include "sys_common.h"
#include "gio.h"
#include "spi.h"
#include "diskio.h"
#include "mmc-hercules.h"
#include <assert.h>


/* Definitions for MMC/SDC command */
#define CMD0    (0x40+0)    /* GO_IDLE_STATE */
#define CMD1    (0x40+1)    /* SEND_OP_COND */
#define CMD8    (0x40+8)    /* SEND_IF_COND */
#define CMD9    (0x40+9)    /* SEND_CSD */
#define CMD10    (0x40+10)    /* SEND_CID */
#define CMD12    (0x40+12)    /* STOP_TRANSMISSION */
#define CMD16    (0x40+16)    /* SET_BLOCKLEN */
#define CMD17    (0x40+17)    /* READ_SINGLE_BLOCK */
#define CMD18    (0x40+18)    /* READ_MULTIPLE_BLOCK */
#define CMD23    (0x40+23)    /* SET_BLOCK_COUNT */
#define CMD24    (0x40+24)    /* WRITE_BLOCK */
#define CMD25    (0x40+25)    /* WRITE_MULTIPLE_BLOCK */
#define CMD41    (0x40+41)    /* SEND_OP_COND (ACMD) */
#define CMD55    (0x40+55)    /* APP_CMD */
#define CMD58    (0x40+58)    /* READ_OCR */




gioPORT_t *_spiPORT = 0;
spiBASE_t *_spiREG = 0;


void mmcSelectSpi(gioPORT_t *port, spiBASE_t *reg) {
    _spiPORT = port;
    _spiREG = reg;
}






// asserts the CS pin to the card
static
void DESELECT (void)
{
    _spiPORT->DSET = 0x01;        // SCS[0] = high
}


// de-asserts the CS pin to the card
static
void SELECT (void)
{
    assert(_spiPORT); // call mmcSelectSpi(gioPORT_t *port, spiBASE_t *reg) first
    assert(_spiREG); // call mmcSelectSpi(gioPORT_t *port, spiBASE_t *reg) first
    _spiPORT-> DCLR = 0x01;        // SCS[0] = low
}




/*------------------------------------------------------------------------------
  Write and Read a byte on SPI interface
*------------------------------------------------------------------------------*/
unsigned char SPI_send (unsigned char outb) {


  while ((_spiREG->FLG & 0x0200) == 0); // Wait until TXINTFLG is set for previous transmission
  _spiREG->DAT1 = outb | 0x100D0000;    // transmit register address


  while ((_spiREG->FLG & 0x0100) == 0); // Wait until RXINTFLG is set when new value is received
  return((unsigned char)_spiREG->BUF);  // Return received value
}


/*--------------------------------------------------------------------------


  Module Private Functions


---------------------------------------------------------------------------*/


static volatile
DSTATUS Stat = STA_NOINIT;    /* Disk status */


static volatile
BYTE Timer1, Timer2;    /* 100Hz decrement timer */


static
BYTE CardType;            /* b0:MMC, b1:SDC, b2:Block addressing */


static
BYTE PowerFlag = 0;    /* indicates if "power" is on */


/*-----------------------------------------------------------------------*/
/* Transmit a byte to MMC via SPI  (Platform dependent)                  */
/*-----------------------------------------------------------------------*/


static
void xmit_spi(BYTE dat)
{
    unsigned int ui32RcvDat;


    while ((_spiREG->FLG & 0x0200) == 0); // Wait until TXINTFLG is set for previous transmission
    _spiREG->DAT1 = dat | 0x100D0000;    // transmit register address


    while ((_spiREG->FLG & 0x0100) == 0); // Wait until RXINTFLG is set when new value is received
    ui32RcvDat = _spiREG->BUF;  // to get received value
}




/*-----------------------------------------------------------------------*/
/* Receive a byte from MMC via SPI  (Platform dependent)                */
/*-----------------------------------------------------------------------*/


static
BYTE rcvr_spi (void)
{


    while ((_spiREG->FLG & 0x0200) == 0); // Wait until TXINTFLG is set for previous transmission
    _spiREG->DAT1 = 0xFF | 0x100D0000;    // transmit register address


    while ((_spiREG->FLG & 0x0100) == 0); // Wait until RXINTFLG is set when new value is received
    return((unsigned char)_spiREG->BUF);  // Return received value
}




static
void rcvr_spi_m (BYTE *dst)
{
    *dst = rcvr_spi();
}


/*-----------------------------------------------------------------------*/
/* Wait for card ready                                                  */
/*-----------------------------------------------------------------------*/


static
BYTE wait_ready (void)
{
    BYTE res;




    Timer2 = 50;    /* Wait for ready in timeout of 500ms */
    rcvr_spi();
    do
        res = rcvr_spi();
    while ((res != 0xFF) && Timer2);


    return res;
}


/*-----------------------------------------------------------------------*/
/* Send 80 or so clock transitions with CS and DI held high. This is    */
/* required after card power up to get it into SPI mode                  */
/*-----------------------------------------------------------------------*/
static
void send_initial_clock_train(void)
{
    unsigned int i;


    /* Ensure CS is held high. */
    DESELECT();


    /* Send 10 bytes over the SSI. This causes the clock to wiggle the */
    /* required number of times. */
    for(i = 0 ; i < 10 ; i++)
    {
        /* Write DUMMY data */
        /* FIFO. */
        SPI_send (0xFF);
    }
}


/*-----------------------------------------------------------------------*/
/* Power Control  (Platform dependent)                                  */
/*-----------------------------------------------------------------------*/
/* When the target system does not support socket power control, there  */
/* is nothing to do in these functions and chk_power always returns 1.  */


static
void power_on (void)
{
    /*
    * This doesn't really turn the power on, but initializes the
    * SPI port and pins needed to talk to the card.
    */
    spiInit();


    /* Set DI and CS high and apply more than 74 pulses to SCLK for the card */
    /* to be able to accept a native command. */
    send_initial_clock_train();


    PowerFlag = 1;
}


// set the SPI speed to the max setting
static
void set_max_speed(void)
{
    // todo jc 20151004 - check if this is portable between hercules controllers/clock speeds
      _spiREG->FMT0 &= 0xFFFF00FF;  // mask out baudrate prescaler
                                    // Max. 5 MBit used for Data Transfer.
      _spiREG->FMT0 |= 5 << 8;    // baudrate prescale 10MHz / (1+1) = 5MBit
}


static
void power_off (void)
{
    PowerFlag = 0;
}


static
int chk_power(void)        /* Socket power state: 0=off, 1=on */
{
    return PowerFlag;
}






/*-----------------------------------------------------------------------*/
/* Receive a data packet from MMC                                        */
/*-----------------------------------------------------------------------*/


static
bool rcvr_datablock (
    BYTE *buff,            /* Data buffer to store received data */
    UINT btr            /* Byte count (must be even number) */
)
{
    BYTE token;




    Timer1 = 100;
    do {                            /* Wait for data packet in timeout of 100ms */
        token = rcvr_spi();
    } while ((token == 0xFF) && Timer1);
    if(token != 0xFE) return FALSE;    /* If not valid data token, retutn with error */


    do {                            /* Receive the data block into buffer */
        rcvr_spi_m(buff++);
        rcvr_spi_m(buff++);
    } while (btr -= 2);
    rcvr_spi();                        /* Discard CRC */
    rcvr_spi();


    return TRUE;                    /* Return with success */
}






/*-----------------------------------------------------------------------*/
/* Send a data packet to MMC                                            */
/*-----------------------------------------------------------------------*/


#if _READONLY == 0
static
bool xmit_datablock (
    const BYTE *buff,    /* 512 byte data block to be transmitted */
    BYTE token            /* Data/Stop token */
)
{
    BYTE resp, wc;




    if (wait_ready() != 0xFF) return FALSE;


    xmit_spi(token);                    /* Xmit data token */
    if (token != 0xFD) {    /* Is data token */
        wc = 0;
        do {                            /* Xmit the 512 byte data block to MMC */
            xmit_spi(*buff++);
            xmit_spi(*buff++);
        } while (--wc);
        xmit_spi(0xFF);                    /* CRC (Dummy) */
        xmit_spi(0xFF);
        resp = rcvr_spi();                /* Reveive data response */
        if ((resp & 0x1F) != 0x05)        /* If not accepted, return with error */
            return FALSE;
    }


    return TRUE;
}
#endif /* _READONLY */






/*-----------------------------------------------------------------------*/
/* Send a command packet to MMC                                          */
/*-----------------------------------------------------------------------*/


static
BYTE send_cmd (
    BYTE cmd,        /* Command byte */
    DWORD arg        /* Argument */
)
{
    BYTE n, res;




    if (wait_ready() != 0xFF) return 0xFF;


    /* Send command packet */
    xmit_spi(cmd);                        /* Command */
    xmit_spi((BYTE)(arg >> 24));        /* Argument[31..24] */
    xmit_spi((BYTE)(arg >> 16));        /* Argument[23..16] */
    xmit_spi((BYTE)(arg >> 8));            /* Argument[15..8] */
    xmit_spi((BYTE)arg);                /* Argument[7..0] */
    n = 0xff;
    if (cmd == CMD0) n = 0x95;            /* CRC for CMD0(0) */
    if (cmd == CMD8) n = 0x87;            /* CRC for CMD8(0x1AA) */
    xmit_spi(n);


    /* Receive command response */
    if (cmd == CMD12) rcvr_spi();        /* Skip a stuff byte when stop reading */
    n = 10;                                /* Wait for a valid response in timeout of 10 attempts */
    do
        res = rcvr_spi();
    while ((res & 0x80) && --n);


    return res;            /* Return with the response value */
}


/*-----------------------------------------------------------------------*
* Send the special command used to terminate a multi-sector read.
*
* This is the only command which can be sent while the SDCard is sending
* data. The SDCard spec indicates that the data transfer will stop 2 bytes
* after the 6 byte CMD12 command is sent and that the card will then send
* 0xFF for between 2 and 6 more bytes before the R1 response byte.  This
* response will be followed by another 0xFF byte.  In testing, however, it
* seems that some cards don't send the 2 to 6 0xFF bytes between the end of
* data transmission and the response code.  This function, therefore, merely
* reads 10 bytes and, if the last one read is 0xFF, returns the value of the
* latest non-0xFF byte as the response code.
*
*-----------------------------------------------------------------------*/


static
BYTE send_cmd12 (void)
{
    BYTE n, res, val;


    /* For CMD12, we don't wait for the card to be idle before we send
    * the new command.
    */


    /* Send command packet - the argument for CMD12 is ignored. */
    xmit_spi(CMD12);
    xmit_spi(0);
    xmit_spi(0);
    xmit_spi(0);
    xmit_spi(0);
    xmit_spi(0);


    /* Read up to 10 bytes from the card, remembering the value read if it's
      not 0xFF */
    for(n = 0; n < 10; n++)
    {
        val = rcvr_spi();
        if(val != 0xFF)
        {
            res = val;
        }
    }


    return res;            /* Return with the response value */
}


/*--------------------------------------------------------------------------


  Public Functions


---------------------------------------------------------------------------*/




/*-----------------------------------------------------------------------*/
/* Initialize Disk Drive                                                */
/*-----------------------------------------------------------------------*/


DSTATUS disk_initialize (
    BYTE drv        /* Physical drive nmuber (0) */
)
{
    BYTE n, ty, ocr[4];




    if (drv) return STA_NOINIT;            /* Supports only single drive */
    if (Stat & STA_NODISK) return Stat;    /* No card in the socket */


    power_on();                            /* Force socket power on */
    send_initial_clock_train();            /* Ensure the card is in SPI mode */


    SELECT();                /* CS = L */
    ty = 0;
    if (send_cmd(CMD0, 0) == 1) {            /* Enter Idle state */
        Timer1 = 100;                        /* Initialization timeout of 1000 msec */
        if (send_cmd(CMD8, 0x1AA) == 1) {    /* SDC Ver2+ */
            for (n = 0; n < 4; n++) ocr[n] = rcvr_spi();
            if (ocr[2] == 0x01 && ocr[3] == 0xAA) {    /* The card can work at vdd range of 2.7-3.6V */
                do {
                    if (send_cmd(CMD55, 0) <= 1 && send_cmd(CMD41, 1UL << 30) == 0)    break;    /* ACMD41 with HCS bit */
                } while (Timer1);
                if (Timer1 && send_cmd(CMD58, 0) == 0) {    /* Check CCS bit */
                    for (n = 0; n < 4; n++) ocr[n] = rcvr_spi();
                    ty = (ocr[0] & 0x40) ? 6 : 2;
                }
            }
        } else {                            /* SDC Ver1 or MMC */
            ty = (send_cmd(CMD55, 0) <= 1 && send_cmd(CMD41, 0) <= 1) ? 2 : 1;    /* SDC : MMC */
            do {
                if (ty == 2) {
                    if (send_cmd(CMD55, 0) <= 1 && send_cmd(CMD41, 0) == 0) break;    /* ACMD41 */
                } else {
                    if (send_cmd(CMD1, 0) == 0) break;                                /* CMD1 */
                }
            } while (Timer1);
            if (!Timer1 || send_cmd(CMD16, 512) != 0)    /* Select R/W block length */
                ty = 0;
        }
    }
    CardType = ty;
    DESELECT();            /* CS = H */
    rcvr_spi();            /* Idle (Release DO) */


    if (ty) {            /* Initialization succeded */
        Stat &= ~STA_NOINIT;        /* Clear STA_NOINIT */
        set_max_speed();
    } else {            /* Initialization failed */
        power_off();
    }


    return Stat;
}






/*-----------------------------------------------------------------------*/
/* Get Disk Status                                                      */
/*-----------------------------------------------------------------------*/


DSTATUS disk_status (
    BYTE drv        /* Physical drive nmuber (0) */
)
{
    if (drv) return STA_NOINIT;        /* Supports only single drive */
    return Stat;
}






/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/


DRESULT disk_read (
    BYTE drv,            /* Physical drive nmuber (0) */
    BYTE *buff,            /* Pointer to the data buffer to store read data */
    DWORD sector,        /* Start sector number (LBA) */
    UINT count            /* Sector count (1..255) */
)
{
    if (drv || !count) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;


    if (!(CardType & 4)) sector *= 512;    /* Convert to byte address if needed */


    SELECT();            /* CS = L */


    if (count == 1) {    /* Single block read */
        if ((send_cmd(CMD17, sector) == 0)    /* READ_SINGLE_BLOCK */
            && rcvr_datablock(buff, 512))
            count = 0;
    }
    else {                /* Multiple block read */
        if (send_cmd(CMD18, sector) == 0) {    /* READ_MULTIPLE_BLOCK */
            do {
                if (!rcvr_datablock(buff, 512)) break;
                buff += 512;
            } while (--count);
            send_cmd12();                /* STOP_TRANSMISSION */
        }
    }


    DESELECT();            /* CS = H */
    rcvr_spi();            /* Idle (Release DO) */


    return count ? RES_ERROR : RES_OK;
}






/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                      */
/*-----------------------------------------------------------------------*/


#if _READONLY == 0
DRESULT disk_write (
    BYTE drv,            /* Physical drive nmuber (0) */
    const BYTE *buff,    /* Pointer to the data to be written */
    DWORD sector,        /* Start sector number (LBA) */
    UINT count            /* Sector count (1..255) */
)
{
    if (drv || !count) return RES_PARERR;
    if (Stat & STA_NOINIT) return RES_NOTRDY;
    if (Stat & STA_PROTECT) return RES_WRPRT;


    if (!(CardType & 4)) sector *= 512;    /* Convert to byte address if needed */


    SELECT();            /* CS = L */


    if (count == 1) {    /* Single block write */
        if ((send_cmd(CMD24, sector) == 0)    /* WRITE_BLOCK */
            && xmit_datablock(buff, 0xFE))
            count = 0;
    }
    else {                /* Multiple block write */
        if (CardType & 2) {
            send_cmd(CMD55, 0); send_cmd(CMD23, count);    /* ACMD23 */
        }
        if (send_cmd(CMD25, sector) == 0) {    /* WRITE_MULTIPLE_BLOCK */
            do {
                if (!xmit_datablock(buff, 0xFC)) break;
                buff += 512;
            } while (--count);
            if (!xmit_datablock(0, 0xFD))    /* STOP_TRAN token */
                count = 1;
        }
    }


    DESELECT();            /* CS = H */
    rcvr_spi();            /* Idle (Release DO) */


    return count ? RES_ERROR : RES_OK;
}
#endif /* _READONLY */






/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                              */
/*-----------------------------------------------------------------------*/


DRESULT disk_ioctl (
    BYTE drv,        /* Physical drive nmuber (0) */
    BYTE ctrl,        /* Control code */
    void *buff        /* Buffer to send/receive control data */
)
{
    DRESULT res;
    BYTE n, csd[16], *ptr = buff;
    WORD csize;




    if (drv) return RES_PARERR;


    res = RES_ERROR;


    if (ctrl == CTRL_POWER) {
        switch (*ptr) {
        case 0:        /* Sub control code == 0 (POWER_OFF) */
            if (chk_power())
                power_off();        /* Power off */
            res = RES_OK;
            break;
        case 1:        /* Sub control code == 1 (POWER_ON) */
            power_on();                /* Power on */
            res = RES_OK;
            break;
        case 2:        /* Sub control code == 2 (POWER_GET) */
            *(ptr+1) = (BYTE)chk_power();
            res = RES_OK;
            break;
        default :
            res = RES_PARERR;
        }
    }
    else {
        if (Stat & STA_NOINIT) return RES_NOTRDY;


        SELECT();        /* CS = L */


        switch (ctrl) {
        case GET_SECTOR_COUNT :    /* Get number of sectors on the disk (DWORD) */
            if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) {
                if ((csd[0] >> 6) == 1) {    /* SDC ver 2.00 */
                    csize = csd[9] + ((WORD)csd[8] << 8) + 1;
                    *(DWORD*)buff = (DWORD)csize << 10;
                } else {                    /* MMC or SDC ver 1.XX */
                    n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
                    csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
                    *(DWORD*)buff = (DWORD)csize << (n - 9);
                }
                res = RES_OK;
            }
            break;


        case GET_SECTOR_SIZE :    /* Get sectors on the disk (WORD) */
            *(WORD*)buff = 512;
            res = RES_OK;
            break;


        case CTRL_SYNC :    /* Make sure that data has been written */
            if (wait_ready() == 0xFF)
                res = RES_OK;
            break;


        case MMC_GET_CSD :    /* Receive CSD as a data block (16 bytes) */
            if (send_cmd(CMD9, 0) == 0        /* READ_CSD */
                && rcvr_datablock(ptr, 16))
                res = RES_OK;
            break;


        case MMC_GET_CID :    /* Receive CID as a data block (16 bytes) */
            if (send_cmd(CMD10, 0) == 0        /* READ_CID */
                && rcvr_datablock(ptr, 16))
                res = RES_OK;
            break;


        case MMC_GET_OCR :    /* Receive OCR as an R3 resp (4 bytes) */
            if (send_cmd(CMD58, 0) == 0) {    /* READ_OCR */
                for (n = 0; n < 4; n++)
                    *ptr++ = rcvr_spi();
                res = RES_OK;
            }


//        case MMC_GET_TYPE :    /* Get card type flags (1 byte) */
//            *ptr = CardType;
//            res = RES_OK;
//            break;


        default:
            res = RES_PARERR;
        }


        DESELECT();            /* CS = H */
        rcvr_spi();            /* Idle (Release DO) */
    }


    return res;
}






/*-----------------------------------------------------------------------*/
/* Device Timer Interrupt Procedure  (Platform dependent)                */
/*-----------------------------------------------------------------------*/
/* This function must be called in period of 10ms                        */


void disk_timerproc (void)
{
//    BYTE n, s;
    BYTE n;




    n = Timer1;                        /* 100Hz decrement timer */
    if (n) Timer1 = --n;
    n = Timer2;
    if (n) Timer2 = --n;


}


/*---------------------------------------------------------*/
/* User Provided Timer Function for FatFs module          */
/*---------------------------------------------------------*/
/* This is a real time clock service to be called from    */
/* FatFs module. Any valid time must be returned even if  */
/* the system does not support a real time clock.          */


DWORD get_fattime (void)
{


    return    ((2007UL-1980) << 25)    // Year = 2007
            | (6UL << 21)            // Month = June
            | (5UL << 16)            // Day = 5
            | (11U << 11)            // Hour = 11
            | (38U << 5)            // Min = 38
            | (0U >> 1)                // Sec = 0
            ;


}





















 

mmc-hercules.h

 

/*
* mmc-hercules.h
*
*  Created on: Oct 4, 2015
*      Author: Jan
*/


#ifndef FATFS_PORT_MMC_HERCULES_H_
#define FATFS_PORT_MMC_HERCULES_H_


void mmcSelectSpi(gioPORT_t *port, spiBASE_t *reg);


#endif /* FATFS_PORT_MMC_HERCULES_H_ */





















 

 

Making the example SPI port agnostic

 

The TI HDK code has two source files, fatfs/port/mmc-hdk-hercules1.c and mmc-hdk-hercules2.c, that we excluded from build in part 1.

You are supposed to use one of the two files depending on the SPI port you're using. I've adapted the file so that we can use a single source file independent of the SPI.

To do that, we've copied all the code from fatfs/port/mmc-hdk-hercules1.c.

We've replaced all fixed spiPORT1 and spiREG1 references with two variables, _spiPORT and _spiREG (just by using the replace tool of CCS).

(I've also replaced BOOL by bool).

 

I've added 1 function; mmcSelectSpi(gioPORT_t *port, spiBASE_t *reg). You give it the SPI port and register for the SPI number that you are using.

it will put the address of your SPI into the _spiPORT and _spiREG variables. The code will now talk to the correct SPI on your device.

 

If you're using SPI1, your code should look like this:

mmcSelectSpi(spiPORT1, spiREG1);  // SD card is on the SPI1




















 

Avoid surprises with an assert() statement

 

Because we're introducing a new function - one that has to be called to make our port work -  and we like to avoid surprises, we want to warn our users.

I've added a set of asserts into the library that will warn the user if he forgets to call our new function.

Asserts come for free. They are only compiled in debug mode, so they won't consume space or time in your firmware release. An ideal way to warn a developer that something isn't ok.

 

In our case, when the developer forgets to call mmcSelectSpi(), she'll get a documented crash as soon as the first contact with the SD Card is made.

 

#include <assert.h>

// ...

void SELECT (void)
{
    assert(_spiPORT); // call mmcSelectSpi(gioPORT_t *port, spiBASE_t *reg) first
    assert(_spiREG); // call mmcSelectSpi(gioPORT_t *port, spiBASE_t *reg) first
    _spiPORT-> DCLR = 0x01;        // SCS[0] = low
}




















 

If the developer would have called mmcSelectSpi(), both _spiPort and _spiREG would have been primed, and the asserts would pass silently.


Thank you for reading along. Now you too go to the top of this chapter and copy the sources into new mmc-hercules.c and mmc-hercules.h files in the fatfs/port folder



Our First Test


Wire up your SD Card to these connections:



Right Click on the project again, and select Build Project.

 

If you see this:

 

<Linking>

'Finished building target: RM46_sdcard_testbed.out'

' '

 

 

**** Build Finished ****

 

tap yourself on the shoulder - for the third time.


We'll now add the logic to our main() and test our work.


The includes:

/* USER CODE BEGIN (1) */
#include "sci.h"
#include "spi.h"
#include "rti.h"
#include "uartstdio.h"
#include "mmc-hercules.h"
/* USER CODE END */




















The meat:

    /** - Initialize LIN/SCI2 Routines to receive Command and transmit data */
    sciInit();


    // spiInit();  // this happens in the fatfs port


    UARTprintf("Hercules SDCARD Demo\r\n ");
    UARTprintf("Type \'help\' for help.\r\n");
    UARTprintf("Catalog MCU, qjwang@ti.com \r\n");


    /* Initialize RTI driver */
    rtiInit();
    /* Enable RTI Compare 3 interrupt notification */
    rtiEnableNotification(rtiNOTIFICATION_COMPARE3);
    /* Enable IRQ - Clear I flag in CPS register */
    _enable_IRQ();
    /* Start RTI Counter Block 1 */
    rtiStartCounter(rtiCOUNTER_BLOCK1);




    mmcSelectSpi(spiPORT1, spiREG1);  // SD card is on the SPI1
    SD_Test();


    while(1);




















The last thing to do is add the timer interrupt handler that will serve our library's 10 ms clacker.

The skeleton for the interrupt service handler is created for you by HALCoGen in the notification.c file.

Add the following logic to rtiNotification():


/* USER CODE BEGIN (9) */
    if (notification  == rtiNOTIFICATION_COMPARE3) {
        SysTickHandler();
    }
/* USER CODE END */




















Let's go. Compile the project and start a debug session.

Open your favorite Terminal program and connect to the user UART of your LaunchPad


Baud rate: 115200

Data bits 8

Stop bits: 2

Parity: none

Flow Control: none


Start the program. If you see this in your terminal, you're good:


Type 'help' for help.
Catalog MCU, qjwang@ti.com


/>




















Type ls and enter in your terminal. You should see a file listing.

You can show the content of a file by typing cat <filename>

You can change directory by entering cd <dir>




We're now in a good position to start writing data to the SD Card. Over to Part 3

 


Related Blog
TI Hercules LaunchPad: using an SD Card Part 1
TI Hercules LaunchPad: using an SD Card Part 2
TI Hercules LaunchPad: using an SD Card Part 3