Lab 6

Overview

In this lab, you are going to write a driver and integrate an open source FAT file system implementation. This will allow the STM to read and write files on an SD card inserted on the back side of the LCD display. The embedded FAT file system (FATFS) implementation is hosted at http://elm-chan.org.

The FATFS is written to be used across a wide set of processors. The task of creating helper functions that can read and write the SD card (SPI device) is left to system developer. In addition to the read/write function, we will also need to create a function that can deliver the time/date in a specific format along with a precise delay function.

SPI Initialization

We will be using the SPI2 peripheral to connect to the SD card. Recall that we used the SPI1 peripheral in Lab 3 to connect the gyroscope. The initialization of SPI2 will follow this model closely. Here are the pin assignments.

Pin Function
PB13 SCK
PB14 MISO
PB15 MOSI
PC0 SD_CS

ds_sdcard.c: void ds_sdcard_interface_init(void)

Use the SPI initialization that you created in Lab 3 as a reference. For this lab, we are going to prescale the clock to the SD card. In later labs, we will improve the speed of this device. To change the prescalar, make the following adjustment to the SPI Initialization structure.

SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;

For the chip select pin, it is useful to have macros in the include file that mirror what was done for the gyro.

#define SD_CS_LOW()   GPIO_ResetBits(GPIOC, GPIO_Pin_0);
#define SD_CS_HIGH()  GPIO_SetBits(GPIOC, GPIO_Pin_0);

Wiring

The LCD on the top left of your board includes two SPI devices (LCD, SD Card). These devices have separate chip selects. Wire between the SCK, MISO, MOSI, and SD_CS to the corresponding pins on the STM32.

Helper Functions

To allow the FATFS code to operate on this system, we need to provide functions that communicate with the hardware unique to this system.

ds_sdcard.c: void ds_sdcard_readwrite(uint8_t *rxbuf, uint8_t *txbuf, int count)

Interactions with the SD card will routinely involve reading and writing buffers of data. This function passes pointers to buffers and the number of bytes. If a null pointer is passed for the rxbuf, then the data received can be discarded. If a null pointer is passed for the txbuf, then the data that is transmitter will be 0xFF which is considered an idle line.

void ds_sdcard_readwrite(uint8_t *rxbuf, uint8_t *txbuf, int count) {
for (index=0;index<count;index++) {
if (txbuf) {
   SPI_SendData8(SPI2, *txbuf++);
 }
 else {
   SPI_SendData8(SPI2, 0xFF);
 }
while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET);
if (rxbuf) {
   *rxbuf++ = (uint8_t) SPI_ReceiveData8(SPI2);
 }
 else {
   SPI_ReceiveData8(SPI2);      
 }
void ds_sdcard_readwrite(uint8_t *rxbuf, uint8_t *txbuf, int count) {
  int index;
  for (index=0;index<count;index++) {
    if (txbuf) {
      SPI_SendData8(SPI2, *txbuf++);
    }
    else {
      SPI_SendData8(SPI2, 0xFF);
    }
    while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE) == RESET);
    if (rxbuf) {
      *rxbuf++ = (uint8_t) SPI_ReceiveData8(SPI2);
    }
    else {
      SPI_ReceiveData8(SPI2);      
    }
  }
}

ds_sdcard.c: uint32_t get_fattime(void)

Because time/date can be handled in a variety of ways on embedded systems, the file system requires a helper function to provide this information. The information will be returned from this function in a packed 32-bit unsigned int. Use the data from the real-time clock to create this output int in the following format:

// Return a packed 32-bit word 
// bit31:25 Year from 1980 (0..127)
// bit24:21 Month (1..12)
// bit20:16 Day in month(1..31)
// bit15:11 Hour (0..23)
// bit10:5 Minute (0..59)
// bit4:0 Second / 2 (0..29)

ds_delay.c: void ds_delay_init(void)

The SD card requires some specific delays during initialization. To generate precise delays, we will dedicate TIMER 3 to this function. In this implementation, we will use an output compare that counts up from 0 to a value that represents the number of uS specified as an argument to the function. Because this function is blocking, we will poll for the completion of the compare rather than using an interrupt.

TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
TIM_OCInitTypeDef  TIM_OCInitStructure;
uint16_t PrescalerValue = 0;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
PrescalerValue = (uint16_t) ((SystemCoreClock) / 36000000) - 1;
TIM_TimeBaseStructure.TIM_Period = 65535;
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_PrescalerConfig(TIM3, PrescalerValue, TIM_PSCReloadMode_Immediate);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 36000; 
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Disable);
TIM_Cmd(TIM3, ENABLE);

ds_delay.c: void ds_delay_uS(uint16_t uS_count)

This function will generate a programmable delay from 2uS-1000uS. After confirming that the timer is off, the counter is reset to 0 and the compare register is loaded with the scaled version of the desired count. The counter is started and the status flag is polled for completion. Once the counter reaches the value, the flag can be cleared.

if (uS_count >= 1000) {
  uS_count = 1000;
}
uS_count *= 36; 
TIM_Cmd(TIM3, DISABLE);
TIM_SetCounter(TIM3,0);
TIM_SetCompare1(TIM3,uS_count);
TIM_Cmd(TIM3, ENABLE);  
while (TIM_GetFlagStatus(TIM3,TIM_FLAG_CC1)==RESET);
TIM_ClearFlag(TIM3,TIM_FLAG_CC1);
TIM_Cmd(TIM3, DISABLE);  

FATFS Files

The FATFS files are include in the ./ff9b/src directory. The two files that will be relevant for this lab are the following:

You will need to modify the following functions within the mmcbb file:

static void xmit_mmc (const BYTE* buff, UINT bc) {
  // buff: pointer to data buffer to send 
  // bc: byte count of data in the tranmit buffer
  ds_sdcard_readwrite(0,(uint8_t *)buff,bc);
}
static void rcvr_mmc (BYTE *buff, UINT bc) {
  // buff: pointer to the receive buffer
  // bc: byte count of data in the receive buffer
  ds_sdcard_readwrite(buff,0,bc);
}
DSTATUS disk_initialize (BYTE pdrv) {
  BYTE n, ty, cmd, buf[4];
  UINT tmr;
  DSTATUS s;
  if (pdrv) return RES_NOTRDY;
  ds_sdcard_interface_init();
  ...

Testing the system:

To test the file system, you will use a test routine include with the generic example code included with the ff9b package.

Open an existing file (message.txt).
Type the file content.
Data added to the file from the workstation !!!!
Close the file.
Create a new file (hello.txt).
Write a text data. (Hello world!)
14 bytes written.
Close the file.
Open root directory.
Directory listing...
      14  HELLO.TXT
      26  MESSAGE.TXT
Test completed.

Assignment

What to Turn in:

This assignment is due by midnight on 2/21. At midnight, your AI will pull the current state of your repository to review your code. In the following lab session, you will be asked to demonstrate your solution. Independently, you will also write a lab report describing in approximately 300 words or less the following:

You will turn in this lab report in the oncourse assignments section.