Lab Assignment: Device Interfacing w/ SPI + Data Structures

To learn how to create a single dynamic thread-safe driver for Synchronous Serial Port for two separate ports and to communicate with an external SPI Flash device.

This lab will utilize:

  • Mutexes
  • Lookup table structures
  • Enumerations
  • Bit field and structure mapping

Assignment

Part 0: Simple SPI driver

This part is just for you to get started.  After you get something functional, you need to move on to Part1 to elaborate your code.  So this code is not something you should turn in

void spi_init(void)
{
}

uint8_t spi_transfer(uint8_t out)
{
}

// WARNING: This is just a sample; you will have to fill in plenty of your own code per requirements
void read_sig(void)
{
  uint8_t d[2];
  
  // The simplest test is to try to read the signature of the Adesto flash and print it out
  adesto_cs();
  {
    d[0] = spi_transfer(0xAA); // TODO: Find what to send to read Adesto flash signature
    d[1] = spi_transfer(0xBB);
  }
  adesto_ds();
  
  printf("Returned data: %x %x\n", d[0], d[1]);
}

void main(void)
{
  spi_init();
  read_sig();
}

Code Block 1. Simple SPI test

Part 1: Elaborate SSP driver

Using the following class template

  1. Implement ALL class methods.
  2. All methods must function work as expected by their comment description.
#ifndef LABSPI_H
#define LABSPI_H

class LabSPI
{
private:
    // SSP register lookup table structure
    static const LPC_SSP_TypeDef * SSP[] = {LPC_SSP0, LPC_SSP1};
  
public:
    enum FrameModes
    {
        /* Fill this out based on the datasheet. */
    };

    enum Peripheral
    {
        SSP0 = 0,
        SSP1 = 1
    };

    /**
     * 1) Powers on SPPn peripheral
     * 2) Set peripheral clock
     * 3) Sets pins for specified peripheral to MOSI, MISO, and SCK
     *
     * @param peripheral which peripheral SSP0 or SSP1 you want to select.
     * @param data_size_select transfer size data width; To optimize the code, look for a pattern in the datasheet
     * @param format is the code format for which synchronous serial protocol you want to use.
     * @param divide is the how much to divide the clock for SSP; take care of error cases such as the value of 0, 1, and odd numbers
     *
     * @return true if initialization was successful
     */
    bool init(Peripheral peripheral, uint8_t data_size_select, FrameModes format, uint8_t divide);

    /**
     * Transfers a byte via SSP to an external device using the SSP data register.
     * This region must be protected by a mutex static to this class.
     *
     * @return received byte from external device via SSP data register.
     */
    uint8_t transfer(uint8_t send);

    LabSPI();
    ~LabSPI();
};

#endif

Code Block 2. SSP Driver Template Class

Part 2: SPI Flash Reader Application

Application is to retrieve the information from the SJOne board's SPI flash's and print the information about each bit (or field of bits) to STDOUT in a nicely formatted human understandable way. If the 7th bit of a status register has been set you cannot simply print that "bit 7 is set". If bit 7 is the busy bit, then print a sentence like, "SPI Flash is currently busy" or "SPI Flash is NOT busy."

WHEN setting the clock in the application, set the clock rate to something reasonable according to the specifications of the device.  Next lab we will go over clock speeds in more detail.

Part 3: Mutex

Since multiple tasks may access the SPI device in parallel, we need to create a Mutex to guard from simultaneous access from multiple tasks.  For this part, create two tasks that are deliberately designed to collide:

  • Task A always reads a page of the SPI flash with only a single vTaskDelay(1) in between
  • Task B always reads the chip signature and if the signature is ever unexpected, it prints fault information

Deliberately try to produce a problem scenario where you read unexpected data from the chip signature command with the assumption that the chip signature will never change (it won't).  Then, guard the SPI transaction with a mutex to prove that the problem is resolved.

void task_sig_reader(void *p)
{
  while (1)
  {
    cs();
    // Note that we may get interrupted here and the other task may corrupt our transfer
    {
      char s1 = spi.transfer(0xAA); // FIXME: This is not the real command to read signature
      char s2 = spi.transfer(0x55);
    }
    ds();
    
    if (s1 != expected1 || s2 != expected2) {
      puts("Ooops... race condition");
      vTaskSuspend(NULL); // Suspend this task
    }
    vTaskDelay(1);
  }
}

void task_page_reader(void *p)
{
  while (1)
  {
    cs();
    read_512_byte_page();
    ds();
    vTaskDelay(1);
  }
}

void main(void)
{
  spi.init_very_slow_speed_SPI(); // FIXME: Init SPI but with a large divider to deliberately run SPI very slow
  
  // Create tasks at the same priority level
  create_task_task_sig_reader(priority_1);
  create_task_task_page_reader(priority_1);
}

Requirements

  • Write your own SSP1 driver: Initialize the pins, clock, and the SSP driver.
  • Remember that the CS signal outside of the SSP driver, and it should just be a GPIO driver
  • Communicate to the Adesto flash and print out the following registers from the SPI FLASH (not the SSP peripheral) 
    • Manufacture ID (print 8-bit hex value)
    • 16-bit Device ID (print 16-bit hex value)
    • 16-bit status register
      • Detailed description of each bit's status
      • MUST create and use a bit field structure map for this register and use that to print the bit information
  • Prove the race condition can occur, and implement the mutex to solve the race condition

What to turn in:

  • Place everything inside of main file or include all relevant files.
  • Turn in the screenshots of terminal output.
  • Logic Analyzer Screenshots
    • Decoded Waveform of SPI retrieving manufacture ID.

For the logic analyzer, you must not forget to include the CS gpio pin otherwise, the waveform will not decode properly.

SCE (Engr294) has many logic analyzers that you can loan (or borrow).  Please align your work schedule to make sure you can go to SCE while it is open.

Extra Credit :

  • Read page zero (first 512 bytes), and print the following:
    • MUST create and use a structure mapping for the whole 512 byte page.
    • All meaningful contents of the 4 partition table entries.
      • MUST create and use a packed bit field structure for the partition table entries.
    • Boot signature
  • Use a terminal command to execute this application.

Here is the sample code block to read and parse MBR Sector from Flash memory Page0

/* 
 *  Create struct to map the MBR, PTE and FAT bootsector byte fields
 */ 
typedef union //Partition table entry
{
      uint8_t PTE[16];
      struct
      {
        //map all the 16 bytes of PTE
      }PTE_fields;
}__attribute__((__packed__))PTE_t;

//ToDo : Define struct FatBootSector

typedef union
{
   unsigned char mbr[512];
   struct{
      unsigned char boot_code[446];
      PTE_t  PTE1;
      PTE_t  PTE2;
      PTE_t  PTE3;
      PTE_t  PTE4;
      uint16_t boot_signature;
   }mbr_fields;
} __attribute__((__packed__)) MBR;

/* 
 *  Adesto flash has Page size of 512 Bytes. So to get MBR read Page0 from Flash
 *  To read flash page, use the spi_flash library functions.
 */
void read_flash_page(unsigned int page_num)
{

 // Create struct MBR to map the byte pattern of 512 bytes

 struct MBR            mbr_buffer;
 struct FatBootSector  fbs_buffer;

 // This reads Master Boot record.
 flash_read_sectors(mbr_buffer.mbr, page_num, count );  

 // To get the information about FAT file system such as sector size, total sectors etc, parse MBR
 // partition table to get the page number of FATFS Boot Sector

 uint32_t fat_boot_sector = mbr_buffer.PTE1.PTE_fileds.sector_start;

 flash_read_sectors(fbs_buffer.fbr, fat_boot_sector, count);

 // print the key data from MBR and FAT Boot sector 

  printf("Boot Signature: %hu \n", mbr_buffer.boot_signature);

 // Note: the data bytes in MBR and boot sector buffers are in little endian.
}

FAT Information

File System essentially consists of data structures used to manage all the files in a storage device. FAT File system uses a data structure called File Allocation Table(FAT) to organize and store information about the location of files in a partition. A partition with FATFS stores all metadata about the files and file systems in its first sector called Partition Boot Sector. Each partition in a storage disk has its own Boot Sector that also contains boot code if that partition is bootable. The information about all such partitions are located in table called Partition Table which is in first sector of disk named Master Boot Record.

Master Boot Record

The first 512 bytes (Page 0 of memory) of the flash contains a Master Boot Record (MBR) which has pointers to where the partitions are placed in the storage device.

MasterBootRecord.png

The first 446 bytes is the bootstrap code area. After that there is a partition table with four entries and then a boot signature (0x55AA). Each entry is 16 bytes long. To see the structure of the Master Boot Record: Sector layout and the structure of the entry can be found here Master Boot Record: Partition table entries.

In Summary, getting FAT File System information is a 2 step process where,

1. Read the Master Boot Record to obtain the location of the starting sector of FATFS partition which is its boot sector.
2. Read the Boot sector of the partition to obtain the FATFS information.

 Additional Information:

One of the fields in the partition entry is "partition type". This should tell us what type of filesystem is resident on the partition. In our case it should be FAT12 (0x01). The last 8 bytes in the partition entry will tell us the Logical Block Address (LBA) of the first absolute sector in the partition and the total number of sectors in the partition. Essentially, we want to read the sector pointed to by this LBA for the FAT boot sector. That sector will give us the required information (no of clusters, total sectors, etc.. ).


Revision #25
Created 7 months ago by Admin
Updated 5 months ago by Khalil Estell