prettyprint

2023年8月31日 星期四

[Raspberry Pi Pico (c-sdk)] Storage: Ep 4. TinyUSB USB Mass Storage, SD/MMC Devices and W25Q Flash Devices

本實驗接續上一篇:

[Raspberry Pi Pico (c-sdk)] Storage: Ep 3. Using SD/MMC Devices and Flash Devices Together ,再加入USB Mass Storage。



實驗內容
  1. 分別對USB Mass Storage, SD/MMC and W25Q Flash進行100K data的write/read所需要的時間比較。
  2. 同時開啟三個檔案分別在USB MSC, SD 與W25Q Flash上,進行讀寫動作。

一、USB MSC host:

主要使用TinyUSB的程式庫,已移植到RP2040上。使用時在主程式的
  • CMakeLists.txt加入:
target_link_libraries(pico_storage 
        tinyusb_host 
        tinyusb_board
        )
  • 呼叫:
    board_init();
    tuh_init(BOARD_TUH_RHPORT);
  • 在main loop 程式中呼叫
    tuh_task(): 
    處理USB queue message
        基本結構如下
        #include "tusb.h"
        void main(void) {
            board_init();
            tuh_init(BOARD_TUH_RHPORT);
            while(1) {
                tuh_task(): 
            }    
        }
  • 至少重寫4個
    TU_ATTR_WEAK                  __attribute__ ((weak))
    的callback function:
    void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len);

    void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len);

    void tuh_msc_mount_cb(uint8_t dev_addr);

    void tuh_msc_umount_cb(uint8_t dev_addr);
二、USB Mass Storage driver說明:
將上述TinyUSB與FatFS檔案系統整合。
在FatFs有五個主要程式:
DSTATUS disk_initialize (BYTE drv);
DSTATUS disk_status (BYTE drv);
DRESULT disk_read (
BYTE drv, /* Physical drive number (0) */
BYTE *buff, /* Pointer to the data buffer to store read data */
LBA_t sector, /* Start sector number (LBA) */
UINT count /* Number of sectors to read (1..128) */
);
DRESULT disk_write (
BYTE drv, /* Physical drive number (0) */
const BYTE *buff, /* Ponter to the data to write */
LBA_t sector, /* Start sector number (LBA) */
UINT count /* Number of sectors to write (1..128) */
);
DRESULT disk_ioctl (
BYTE drv, /* Physical drive number (0) */
BYTE cmd, /* Control command code */
void *buff /* Pointer to the conrtol data */
);
DSTATUS disk_status (BYTE drv);


FRESULT f_mount(FATFS *fs, const TCHAR *path, BYTE opt);
會呼叫
disk_initialize();
因此在此函式中加入
if (!tuh_inited()) {
      board_init();
      tuh_init(BOARD_TUH_RHPORT);
}
使用時不需要在主程式呼叫borad_init() and tuh_init();

在init USB driver時主動呼叫callback function:
void tuh_msc_mount_cb(uint8_t dev_addr)
在此function 中呼叫
tuh_msc_inquiry(dev_addr, lun, &inquiry_resp, inquiry_complete_cb, 0);
取得device sector countsector size以便在disk_ioctl中傳回此參數。
在disk_read()中呼叫:
tuh_msc_read10(pusb_msc->dev_addr, pusb_msc->lun, buff, sector, (uint16_t) count, disk_io_complete, 0);
在disk_write呼叫
tuh_msc_write10(pusb_msc->dev_addr, pusb_msc->lun, buff, sector, (uint16_t) count, disk_io_complete, 0);

其他詳細程式內容附於文章末尾。

三、成果影片



四、程式碼

spi_sdmmc.c, spi_sdmmc.h, W25Q.c and W25Q.h 請參閱前篇內容。
  • usb_msc.c
 #include "pico/stdlib.h"
#include "usb_msc.h"
#include "bsp/board.h"

static usb_msc_t *_pUSBMSC;

static scsi_inquiry_resp_t inquiry_resp;


bool usb_msc_mounted() {
  return _pUSBMSC->usb_mounted;
}

bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_data)
{
  msc_cbw_t const* cbw = cb_data->cbw;
  msc_csw_t const* csw = cb_data->csw;

  if (csw->status != 0)
  {
    printf("Inquiry failed\r\n");
    return false;
  }

  // Print out Vendor ID, Product ID and Rev
  printf("%.8s %.16s rev %.4s\r\n", inquiry_resp.vendor_id, inquiry_resp.product_id, inquiry_resp.product_rev);

  // Get capacity of device
  _pUSBMSC->block_count = tuh_msc_get_block_count(dev_addr, cbw->lun);
  _pUSBMSC->block_size = tuh_msc_get_block_size(dev_addr, cbw->lun);
  
  printf("Disk Size: %lu MB\r\n", _pUSBMSC->block_count / ((1024*1024)/_pUSBMSC->block_size));

  _pUSBMSC->disk_busy=false;
  _pUSBMSC->usb_mounted=true;
  return true;
}

//------------- IMPLEMENTATION -------------//
void tuh_msc_mount_cb(uint8_t dev_addr)
{
    printf("A MassStorage device is mounted\r\n");
    _pUSBMSC->dev_addr = dev_addr;
    _pUSBMSC->lun = 0;
    tuh_msc_inquiry(_pUSBMSC->dev_addr, _pUSBMSC->lun, &inquiry_resp, inquiry_complete_cb, 0);
}

void tuh_msc_umount_cb(uint8_t dev_addr)
{
  printf("A MassStorage device is unmounted\r\n");
  _pUSBMSC->usb_mounted=false;
  f_unmount(USB_MSC_PATH);
}
/////////////

DRESULT usb_msc_initialize(usb_msc_t* pusb_msc) {  
    //printf("usb_msc disk initialize\r\n");
    if (!tuh_inited()) {
      board_init();
      tuh_init(BOARD_TUH_RHPORT);
      _pUSBMSC=pusb_msc;
      _pUSBMSC->disk_busy = true;
      _pUSBMSC->usb_mounted = false;
      absolute_time_t t1=get_absolute_time();
      absolute_time_t t2;
      while (1) {
        tuh_task();
        if (_pUSBMSC->usb_mounted) {
           _pUSBMSC->Stat &= ~STA_NOINIT;
          break;
        }
        t2 = get_absolute_time();
        if (absolute_time_diff_us(t1, t2) > 10000000) {
           _pUSBMSC->Stat = STA_NOINIT;
           break;
        }
      }
    }
    _pUSBMSC->disk_busy = false;
	return _pUSBMSC->Stat;
}

void wait_for_disk_io(usb_msc_t *pusb_msc)
{
  while(pusb_msc->disk_busy)
  {
    tuh_task();
    led_blinking();
  }
  led_blinking_off();
}

bool disk_io_complete(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_data)
{
  
  (void) dev_addr; (void) cb_data;
  _pUSBMSC->disk_busy = false; //////
  _pUSBMSC->Stat = RES_OK;
  return true;
}

DRESULT usb_msc_ioctl(
    BYTE cmd,		/* Control code */
	void *buff,		/* Buffer to send/receive control data */
    usb_msc_t* pusb_msc
) 
{
    DRESULT res = RES_ERROR;

    switch ( cmd )
    {
        case CTRL_SYNC:
        // nothing to do since we do blocking
        return RES_OK;

        case GET_SECTOR_COUNT:
        //*((DWORD*) buff) = (DWORD) tuh_msc_get_block_count(pusb_msc->dev_addr, pusb_msc->lun);
        *((DWORD*) buff) = pusb_msc->block_count;
        return RES_OK;

        case GET_SECTOR_SIZE:
        //*((WORD*) buff) = (WORD) tuh_msc_get_block_size(pusb_msc->dev_addr, pusb_msc->lun);
        *((WORD*) buff) = (WORD) pusb_msc->block_size;
        return RES_OK;

        case GET_BLOCK_SIZE:
        *((DWORD*) buff) = 1;    // erase block size in units of sector size
        return RES_OK;

        default:
        return RES_PARERR;
    }
}

DRESULT usb_msc_disk_write(
	const BYTE *buff,	/* Ponter to the data to write */
	LBA_t sector,		/* Start sector number (LBA) */
	UINT count, 			/* Number of sectors to write (1..128) */
  usb_msc_t *pusb_msc
) 
{
    pusb_msc->disk_busy = true;
    tuh_msc_write10(pusb_msc->dev_addr, pusb_msc->lun, buff, sector, (uint16_t) count, disk_io_complete, 0);
    wait_for_disk_io(pusb_msc);
    return pusb_msc->Stat;
}
DRESULT usb_msc_disk_read(
	BYTE *buff,	/* Ponter to the data to write */
	LBA_t sector,		/* Start sector number (LBA) */
	UINT count, 			/* Number of sectors to write (1..128) */
  usb_msc_t *pusb_msc
) 
{
    pusb_msc->disk_busy = true;
    tuh_msc_read10(pusb_msc->dev_addr, pusb_msc->lun, buff, sector, (uint16_t) count, disk_io_complete, 0);
    wait_for_disk_io(pusb_msc);
    return pusb_msc->Stat;
}
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
{
}
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
{
}
  • usb_msc.h
 #ifndef _USB_MSC_H_
#define _USB_MSC_H_
#include "tusb.h"

#include "ff.h"
#include "diskio.h"
#include "pico_storage.h"


typedef struct {
    uint8_t dev_addr;
    volatile bool disk_busy;
    uint8_t lun;
    uint32_t block_count;
    uint32_t block_size;
    bool     usb_mounted;
    DRESULT     Stat;

} usb_msc_t;

bool usb_msc_mounted();
DRESULT usb_msc_initialize(usb_msc_t* pusb_msc);
void wait_for_disk_io(usb_msc_t *pusb_msc);
bool disk_io_complete(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_data);
DRESULT usb_msc_ioctl(BYTE cmd,	void *buff,	  usb_msc_t* pusb_msc); 
DRESULT usb_msc_disk_write(const BYTE *buff, LBA_t sector,	UINT count,   usb_msc_t *pusb_msc); 
DRESULT usb_msc_disk_read(BYTE *buff, LBA_t sector,	UINT count,   usb_msc_t *pusb_msc); 
#endif
  • glue.c
 #include "stdio.h"
#include "stdlib.h"
#include "ff.h"
#include "diskio.h"
#include "spi_sdmmc.h"
#include "W25Q.h"
#include "usb_msc.h"
#include "hardware/rtc.h"
#include "inttypes.h"
#include "hardware/gpio.h"


#define SDMMC_DRV_0     0
#define W25Q_DRV_1      1
#define USB_MSC_DRV_2   2

sdmmc_data_t *pSDMMC=NULL;
w25q_data_t *pW25Q = NULL;
usb_msc_t    *pUSB_MSC=NULL;

//==================//
DSTATUS disk_initialize (BYTE drv){
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            if (pSDMMC == NULL) {
                pSDMMC = (sdmmc_data_t*)malloc(sizeof(sdmmc_data_t));
                pSDMMC->csPin = SDMMC_PIN_CS;
                pSDMMC->spiPort = SDMMC_SPI_PORT;
                pSDMMC->spiInit=false;
                pSDMMC->sectSize=512;
#ifdef __SPI_SDMMC_DMA
                pSDMMC->dmaInit=false;
#endif
    }
            stat = sdmmc_disk_initialize(pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
        if (pW25Q == NULL) {
            pW25Q = (w25q_data_t*)malloc(sizeof(w25q_data_t));
            pW25Q->spiInit=false;
            pW25Q->Stat=STA_NOINIT;
        }
		stat = w25q_disk_initialize(W25Q_SPI_PORT, W25Q_PIN_CS, pW25Q); 
		return stat;
		
	    break;
        case USB_MSC_DRV_2:
        if (pUSB_MSC == NULL) {
            pUSB_MSC = (usb_msc_t*)malloc(sizeof(usb_msc_t));
        }
        stat = usb_msc_initialize(pUSB_MSC);
        return stat;
        break;
    }
    return STA_NOINIT;
 }
/*-----------------------------------------------------------------------*/
/* Get disk status                                                       */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (BYTE drv) {
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat=  sdmmc_disk_status(pSDMMC); /* Return disk status */
            return stat;
        break;
        case W25Q_DRV_1:
            stat = pW25Q->Stat;
            return stat;
        break;
        case USB_MSC_DRV_2:
            //uint8_t dev_addr = pdrv + 1;
            return tuh_msc_mounted(pUSB_MSC->dev_addr) ? 0 : STA_NODISK;
        break;
    }
    return RES_PARERR;
	
}

/*-----------------------------------------------------------------------*/
/* Read sector(s)                                                        */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
	BYTE drv,		/* Physical drive number (0) */
	BYTE *buff,		/* Pointer to the data buffer to store read data */
	LBA_t sector,	/* Start sector number (LBA) */
	UINT count		/* Number of sectors to read (1..128) */
)
{
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_read(buff, sector, count, pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
            if (pW25Q->Stat & STA_NOINIT) return RES_NOTRDY;
		    w25q_read_sector((uint32_t)sector, 0, buff, count*pW25Q->sectorSize, pW25Q);
            return pW25Q->Stat;
        break;
        case USB_MSC_DRV_2:
            stat = usb_msc_disk_read(buff, sector, count, pUSB_MSC);
            return stat;
        break;
    }
	return RES_PARERR;
}

/*-----------------------------------------------------------------------*/
/* Write sector(s)                                                       */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write (
	BYTE drv,			/* Physical drive number (0) */
	const BYTE *buff,	/* Ponter to the data to write */
	LBA_t sector,		/* Start sector number (LBA) */
	UINT count			/* Number of sectors to write (1..128) */
)
{
    DSTATUS stat = STA_NODISK;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_write(buff, sector, count, pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
            stat = w25q_disk_write(buff, sector, count, pW25Q);
            return stat;
        break;
        case USB_MSC_DRV_2:
            stat = usb_msc_disk_write(buff, sector, count, pUSB_MSC);
            return stat;
        break;
    }
	return RES_PARERR;

}
#endif


/*-----------------------------------------------------------------------*/
/* Miscellaneous drive controls other than data read/write               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE drv,		/* Physical drive number (0) */
	BYTE cmd,		/* Control command code */
	void *buff		/* Pointer to the conrtol data */
)
{
    DSTATUS stat;
    switch (drv) {
        case SDMMC_DRV_0:
            stat = sdmmc_disk_ioctl(cmd, buff, pSDMMC);
            return stat;
        break;
        case W25Q_DRV_1:
            stat = w25q_disk_ioctl(cmd, buff, pW25Q);
            return stat;
        break;
        case USB_MSC_DRV_2:
            stat = usb_msc_ioctl(cmd, buff, pUSB_MSC);
            return stat;
        break;
    }
	return RES_PARERR;
}

DWORD get_fattime(void) {
    datetime_t t = {0, 0, 0, 0, 0, 0, 0};
    bool rc = rtc_get_datetime(&t);
    if (!rc) return 0;

    DWORD fattime = 0;
    // bit31:25
    // Year origin from the 1980 (0..127, e.g. 37 for 2017)
    uint8_t yr = t.year - 1980;
    fattime |= (0b01111111 & yr) << 25;
    // bit24:21
    // Month (1..12)
    uint8_t mo = t.month;
    fattime |= (0b00001111 & mo) << 21;
    // bit20:16
    // Day of the month (1..31)
    uint8_t da = t.day;
    fattime |= (0b00011111 & da) << 16;
    // bit15:11
    // Hour (0..23)
    uint8_t hr = t.hour;
    fattime |= (0b00011111 & hr) << 11;
    // bit10:5
    // Minute (0..59)
    uint8_t mi = t.min;
    fattime |= (0b00111111 & mi) << 5;
    // bit4:0
    // Second / 2 (0..29, e.g. 25 for 50)
    uint8_t sd = t.sec / 2;
    fattime |= (0b00011111 & sd);
    return fattime;
}

void led_blinking(void)
{
    static absolute_time_t  t1;
    static bool state=false;
        
    // Blink every interval ms
    if ( absolute_time_diff_us(t1, get_absolute_time()) < 100000) return; // not enough time
    t1 = get_absolute_time();
    gpio_put(LED_BLINKING_PIN, state);
    state = !state;
}

void led_blinking_off(void) {
    gpio_put(LED_BLINKING_PIN, false);
}
  • pic_storage.h
 #ifndef _PICO_STORAGE_H_
#define _PICO_STORAGE_H_
/* used by my project */
#define SDMMC_PATH      "0:"
#define W25Q_PATH       "1:"
#define USB_MSC_PATH	"2:"
#define SPI_BAUDRATE_LOW (1000*1000)
#define SPI_BAUDRATE_HIGH (50*1000*1000)
/* =================================  */
#define  LED_BLINKING_PIN     25
void led_blinking(void);
void led_blinking_off(void);
/* =================================  */
#endif
  • CMakeLists.txt
 add_library(pico_storage_drv INTERFACE)
target_sources(pico_storage_drv INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/glue.c
    ${CMAKE_CURRENT_LIST_DIR}/FatFs/ff.c
    ${CMAKE_CURRENT_LIST_DIR}/FatFs/ffunicode.c
    ${CMAKE_CURRENT_LIST_DIR}/FatFs/ffsystem.c
    ${CMAKE_CURRENT_LIST_DIR}/sdmmc/spi_sdmmc.c
    ${CMAKE_CURRENT_LIST_DIR}/flash/W25Q.c
    ${CMAKE_CURRENT_LIST_DIR}/usb_msc/usb_msc.c
)

target_include_directories(pico_storage_drv INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}
    ${CMAKE_CURRENT_LIST_DIR}/FatFs
    ${CMAKE_CURRENT_LIST_DIR}/flash
    ${CMAKE_CURRENT_LIST_DIR}/sdmmc
    ${CMAKE_CURRENT_LIST_DIR}/usb_msc
)

target_link_libraries(pico_storage_drv INTERFACE
        hardware_spi
        hardware_dma
        hardware_rtc
        pico_stdlib
        tinyusb_board
        tinyusb_host
)

沒有留言:

張貼留言