Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Simple SD-card driver for SPL?
#1
Hi,
I'm looking for a simple, low level SD card driver with SPL for STM32 devices without SDIO interface. Just simple initialize and block read/write/erase functions with SPI bus, no need of any filesystem, INT or DMA, etc. I found a very similar driver what I need, but in C++ with HAL:
https://ralimtek.com/Stm32_SPI_SD/
My C++ knowledge is very poor/old/dusty, so what do you think, how hard work would be to port this code to simple C with SPL?
Or can somebody suggest some similar SD card driver written for SPL?
Reply
#2
(18-12-2017, 02:35 PM)peterb Wrote: Hi,
I'm looking for a simple, low level SD card driver with SPL for STM32 devices without SDIO interface. Just simple initialize and block read/write/erase functions with SPI bus, no need of any filesystem, INT or DMA, etc. I found a very similar driver what I need, but in C++ with HAL:
https://ralimtek.com/Stm32_SPI_SD/
My C++ knowledge is very poor/old/dusty, so what do you think, how hard work would be to port this code to simple C with SPL?
Or can somebody suggest some similar SD card driver written for SPL?

Hi peterb,
here is a simple low-level SD/MMC driver, bit-banged.
Not a bugatti veyron, but works - on a discovery with F051 and a custom board with F030.
I am going to modify it to use the built-in SPI peripheral - as soon as I have time.
The conditionals #if SPI_BITBANG are already in place.
Hope this helps,
-e
PS this is compilable as-is, needed definitions are in the header file
Code:
/**
 ******************************************************************************
 * File Name          : tmmc.c
 * Date               : 25.10.2017 20:39:27
 * Description        : primitive access to sd/mmc cards
 * Platform           : Any STM32F0xx
 * IDE, Compiler      : Embitz 1.11 for STM32F0xx
 ******************************************************************************
 *
   DATE        WHO     WHAT

*  06.05.09    aek
*  25.10.2017  AEK     FIRST VERSION FOR STM32 (INHERITED)
*
*  TODO---------------------
*
****************************************************************************/

#include "TMMC.h"

// reference: SanDisk Secure Digital Card Product Manual Version 1.9 Document No. 80-13-00169 December 2003

#define SPI_BITBANG 1   // true if SPI is by bit-banging, false if by SPI integrated peripheral

// SD/MMC command codes. this list has only the ones actually used
#define CMD_GO_IDLE_STATE         0
#define CMD_SEND_OP_COND          1
#define CMD_SEND_CSD              9
#define CMD_SEND_CID              10
#define CMD_SEND_STATUS           13
#define CMD_SET_BLOCKLEN          16
#define CMD_READ_SINGLE_BLOCK     17
#define CMD_WRITE_SINGLE_BLOCK    24
#define CMD_ERASE                 38
#define CMD_APP_CMD               55
#define CMD_READ_OCR              58
#define ACMD_SEND_OP_COND         41

#define IDLE_STATE    0x01    // idle bit in R1 response

// defines for direct access to SPI bitbang pins
// note GPIO pins must have been configured beforehand
#define BITNSS (1<<4) // output bit in GPIOA
#define BITMOSI (1<<7) // output bit in GPIOA
#define BITSCK (1<<5) // output bit in GPIOA
#define BITMISO (1<<0) // input bit in GPIOB

static U16* Absrr = (U16*)(GPIOA_BASE+0x18);  // points to the GPIOA BSRR register
static U16* Abrr = (U16*) (GPIOA_BASE+0x28);  // points to the GPIOA BRR register
static U16* Bidr = (U16*) (GPIOB_BASE+0x10);  // points to the GPIOB IDR register

#define NSS1 {*Absrr = BITNSS;}   // to set NSS HIGH
#define NSS0 {*Abrr = BITNSS;}    // to set NSS LOW
#define MOSI1 {*Absrr = BITMOSI;} // etc
#define MOSI0 {*Abrr = BITMOSI;}
#define CLK1 {*Absrr = BITSCK;}
#define CLK0 {*Abrr = BITSCK;}
#define RMISO (*Bidr & BITMISO)   // to read MISO into BITMISO

#if SPI_BITBANG
#define UNSELECT(tt) { NSS1; Wait_ms(tt); }
#define SELECT(tt)   { NSS0; Wait_ms(tt); }
#else
#error missing code for non-bitbang
#endif

/************************************************************************
*
************************************************************************/

typedef union { // for endian reversal
  U8 b[4];
  U32 ul;
} b_ul;

static U8 CardType;    /* MMC = 0, SDCard v1 = 1, SDCard v2 = 2 */

/************************************************************************
*
************************************************************************/

#if SPI_BITBANG
U8 volatile CkDelayVal;  // delay clock on each edge by this many us

void Clk1 (void) {
 if (CkDelayVal) Wait_us(CkDelayVal);
 CLK1;
}

void Clk0 (void) {
 CLK0;
 if (CkDelayVal) Wait_us(CkDelayVal);
}
#endif

/************************************************************************
* set SPI transfer speed.
* for BITBANG: div specifies clock edge delay in us. from 0 to 10 or more
*
* for native: div is used as clock divider, from 1 to max (TBD)
************************************************************************/

void SpiClkSet (U8 div)
{
#if SPI_BITBANG
 CkDelayVal = div;
#else
#error missing code for non-bitbang
#endif
}

/************************************************************************
send one byte to spi and read ony byte from spi
************************************************************************/

#if SPI_BITBANG

//U8 WRSpiByte(U8) __attribute__((optimize(0)));
U8 WRSpiByte(U8 byte)
{
 U8 i,ret;
 for(i=0;i<8;i++) {
   if (byte & 0x80) MOSI1 else MOSI0
   Clk1();
   byte <<= 1;
   ret <<= 1;
   if (RMISO) ret |= 1;
   Clk0();
 }
 return ret;
}

//U8 RDSpiByte(void) __attribute__((optimize(0)));
U8 RDSpiByte(void)
{
 U8 i,ret;
 MOSI1;
 for(i=0;i<8;i++) {
   ret <<= 1;
   Clk1();
   if (RMISO) ret |= 1;
   Clk0();
 }
 return ret;
}

#else
#error missing code for non-bitbang
#endif

#define SPI_Byte(b) WRSpiByte(b)

/******************************************************************************
* Announce an error, return its code
*****************************************************************************/

U8 Abort (U8 code, char* msg)
{
 sstring(msg); sstring(" ("); spdec(code); sstring(")\n");
 UNSELECT(0);
 return code;
}

/******************************************************************************
*
*****************************************************************************/

void InitSpiLines (void)
{
#if SPI_BITBANG
//  cbit(BIURES); // turn card power on
 NSS1;
 MOSI1;
 CLK1;
 Wait_ms(50);
#else
/* (1) Master selection, BR: Fpclk/256 (due to C27 on the board, SPI_CLK is
set to the minimum) CPOL and CPHA at zero (rising first edge) */
/* (2) Slave select output enabled, RXNE IT, 8-bit Rx fifo */
/* (3) Enable SPI1 */
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR; /* (1) */
SPI1->CR2 = SPI_CR2_SSOE | SPI_CR2_RXNEIE | SPI_CR2_FRXTH
| SPI_CR2_DS_2 | SPI_CR2_DS_1 | SPI_CR2_DS_0; /* (2) */
SPI1->CR1 |= SPI_CR1_SPE; /* (3) */
#endif
 SpiClkSet(4);            // SPI clock initially slow
 return;
}

/************************************************************************
* select the card, send a command with arg and CRC
************************************************************************/

void SD_Command( U8 Cmd, U32 Arg )
{
  b_ul Temp; // for endian conversion
  U8 i;

  SELECT(0);           /* enable the card... */
  SPI_Byte(0xFF);           /* send buffer clocks to insure no operations are pending */
  SPI_Byte(0x40 | Cmd);           /* send command */
  Temp.ul = Arg;           /* send argument */
  for( i=0; i<4; i++ ) SPI_Byte(Temp.b[3-i]);        // TO BIG ENDIAN!
  WRSpiByte((Cmd == CMD_GO_IDLE_STATE)? 0x95:0xFF);           /* send CRC */
  WRSpiByte(0xFF);       /* send buffer clocks to insure card has finished all operations */
}

/************************************************************************
* expect a response code from card (R1, R2)
************************************************************************/

U8 SD_GetR1()
{
  U8 i, R1;

  for( i=0; i<10; i++ ) {               /* response will come after 1-10 0xffs */
     R1 = RDSpiByte();
     if (R1 != 0xff) return R1;         /* if it isn't 0xff, it is a response */
  }
  return R1;
}

U16 SD_GetR2()
{
  U16 R2;

  R2 = (U16)(SD_GetR1()) << 8;  // MSB first
  R2 |= RDSpiByte();          // then LSB
  return( R2 );
}

/******************************************************************************
* wait for SD card ready
*****************************************************************************/

U8 SD_WaitForReady()
{
  U8 i;
  U16 j;

  SPI_Byte(0xFF);
  j = 400; // timeout to 400ms
  do {
     i = RDSpiByte();
     Wait_ms(1);
  } while ((i != 0xFF) && --j);
  return i;
}

/************************************************************************
*
************************************************************************/

#define SD_TIME_OUT 200
#define SD_ERROR    201

U8 SD_Init(void)
{
  U16 CardStatus; // R2 value from status inquiry
  U16 Count;      // local counter

  // Global CardType - b0:MMC, b1:SDv1, b2:SDv2

  InitSpiLines();     /* also disable SPI chip select */

  /* initial speed is slow */
  SpiClkSet(0);
/*
for (;;) {
   WRSpiByte(0xaa);
}
*/
  /* send clocks with all ones data - 80 bits long to   */
  /* establish link with SD card. minimum 80 clocks.    */
  for(Count=0;Count<10;Count++) SPI_Byte(0xFF);

  /* enable the card with the CS pin */
  SELECT(0);

  /* ************************************************ */
  /* SET SD CARD TO SPI MODE - IDLE STATE          */
  /* ************************************************ */
  Count = 1000;     // one second timeout
  CardType = 0;

  /* wait for card to enter IDLE state */
  do {
     Wait_ms(1);
     SD_Command(CMD_GO_IDLE_STATE, 0);
  } while((SD_GetR1() != IDLE_STATE) && (--Count));

  /* timeout if we never made it to IDLE state */
  if(!Count) return SD_TIME_OUT;

  /* ************************************************ */
  /* COMPLETE SD CARD INITIALIZATION                  */
  /* FIGURE OUT WHAT TYPE OF CARD IS INSTALLED...     */
  /* ************************************************ */

  /* Is card SDSC or MMC? */
  SD_Command(CMD_APP_CMD, 0);
  SD_Command(ACMD_SEND_OP_COND, 0);
  if (SD_GetR1() <= 1) {
     CardType = 2;
  } else {
     CardType = 1;
  }

  /* wait for initialization to finish */
  Count = 2000;     // two seconds timeout
  do {
     Wait_ms(1);
     if (CardType == 2) {
        SD_Command(CMD_APP_CMD, 0);
        SD_Command(ACMD_SEND_OP_COND, 0);
     } else {
        SD_Command(CMD_SEND_OP_COND, 0);
     }
  } while(SD_GetR1() && --Count);

  if (!Count) return SD_TIME_OUT;

  /* ************************************************ */
  /* QUERY CARD STATUS...                             */
  /* ************************************************ */
  SD_Command(CMD_SEND_STATUS, 0);
  CardStatus = SD_GetR2();
  if (CardStatus) return SD_ERROR;

  /* ************************************************ */
  /* SET BLOCK SIZE...                                */
  /* ************************************************ */
  SD_Command( CMD_SET_BLOCKLEN, PHYSECSIZE);
  if (SD_GetR1()) {
     CardType = 0;
     return SD_ERROR;
  }

  /* ************************************************ */
  /* SWITCH TO HIGHEST SPI SPEED...                   */
  /* ************************************************ */
  SpiClkSet(0);            // SPI clock us delays
  UNSELECT(0);       /* deselect the card */
  return 0;           /* return OK */
}

/************************************************************************
*
************************************************************************/

U8 SD_ReadSector (U32 SectorNumber, U8 *Buffer)
{
  U8 c, i;
  U16 count;

  /* send block-read command... */
  SD_Command(CMD_READ_SINGLE_BLOCK, SectorNumber * PHYSECSIZE);
  c = SD_GetR1();
  i = SD_GetR1();
  count = 5000;
  while ( (i == 0xff) && --count) i = SD_GetR1();       /* wait for data token */
  if (c || i != 0xFE) return SD_TIME_OUT;       /* handle time out... */
  for (count=0; count<PHYSECSIZE; count++)  *Buffer++ = RDSpiByte();       /* read the sector... */

  SPI_Byte(0xFF);       /* ignore the checksum... */
  SPI_Byte(0xFF);
  UNSELECT(0);       /* release the CS line... */
  return 0;
}

/************************************************************************
*
************************************************************************/

U8 SD_WriteSector( U32 SectorNumber, U8 *Buffer )
{
  U8 res;
  U16 count;
//  LEDON;
  SD_Command(CMD_WRITE_SINGLE_BLOCK, SectorNumber * PHYSECSIZE);       /* send block-write command */
  res = SD_GetR1();

  SPI_Byte(0xFE );           /* send start block token */

  for( count= 0; count<PHYSECSIZE; count++ ) WRSpiByte(*Buffer++);       /* write the sector */

  SPI_Byte(0xFF);       /* ignore the checksum (dummy write) */
  SPI_Byte(0xFF);

  while (RDSpiByte() != 0xFF)       /* wait for response token */

  /* these 8 clock cycles are critical for the card */
  /* to finish up whatever it's working on at the */
  /* time (before CS is released!) */
  SPI_Byte(0xFF);

  /* release the CS line */
  UNSELECT(0);
  SPI_Byte(0xFF);
//   LEDOFF;
  return 0;
}

Code:
#pragma once
/**
 ******************************************************************************
 * File Name          : tmmc.h
 * Date               : 25.10.2017 20:39:27
 * Description        : primitive access to sd/mmc cards
 * Platform           : Any STM32F0xx
 * IDE, Compiler      : Embitz 1.11 for STM32F0xx
 ******************************************************************************
 *
   DATE        WHO     WHAT

*  25.10.2017  AEK     FIRST VERSION FOR STM32 (INHERITED)
*
*  TODO---------------------
*
****************************************************************************/

// imported items: only those strictly necessary to compile tmmc.c are listed.

// from #include "types.h"
typedef unsigned char       U8;
typedef   signed char       S8;
typedef unsigned short      U16;
typedef   signed short      S16;
typedef unsigned long       U32;
typedef   signed long       S32;
#define bool unsigned char

// from #include "TDEFS.h"
// Physical size in bytes of one MMC FLASH sector
#define PHYSECSIZE     512
typedef U8 TSec[PHYSECSIZE];

// from #include "BSP_SLOGR.h"
#define PERIPH_BASE           ((U32)0x40000000) // Peripheral base address in the alias region */
#define AHB2PERIPH_BASE       (PERIPH_BASE + 0x08000000)
#define GPIOA_BASE            (AHB2PERIPH_BASE + 0x00000000)
#define GPIOB_BASE            (AHB2PERIPH_BASE + 0x00000400)
void Wait_us(U16 tus);              /* waste tus microseconds */
void Wait_ms(U16 count);            /* waste count milliseconds */

// from #include "printf.h"
void sstring (const char *ss);      /* print a string */
void spdec (S32 v);                 /* fast decimal print */
void spsdecNL (char *ss, S32 v);    /* a string, a decimal, and a newline at end */


// exported SD / MMC FLASH Functions

bool SD_Init (void);            // Initializes card, configures for SPI commands
U8 SD_ReadSector(U32 secno, TSec pchar);  // read absolute 512-byte sector number into pchar
U8 SD_WriteSector(U32 secno,TSec wdata);  // write wdata into abs 512-byte sector
Reply
#3
(04-01-2018, 09:10 PM)escalator Wrote:
(18-12-2017, 02:35 PM)peterb Wrote: Hi,
I'm looking for a simple, low level SD card driver with SPL for STM32 devices without SDIO interface. Just simple initialize and block read/write/erase functions with SPI bus, no need of any filesystem, INT or DMA, etc. I found a very similar driver what I need, but in C++ with HAL:
https://ralimtek.com/Stm32_SPI_SD/
My C++ knowledge is very poor/old/dusty, so what do you think, how hard work would be to port this code to simple C with SPL?
Or can somebody suggest some similar SD card driver written for SPL?

Hi peterb,
here is a simple low-level SD/MMC driver, bit-banged.
Not a bugatti veyron, but works - on a discovery with F051 and a custom board with F030.
I am going to modify it to use the built-in SPI peripheral - as soon as I have time.
The conditionals #if SPI_BITBANG are already in place.
Hope this helps,
-e

Thank you very much! I think, that it can help much, but now I run into a strange problem with SPI port, what I write in another thread.
Peter
[UPDATE: problem solved, go on. :-) ]
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)