prettyprint

2023年9月5日 星期二

[Raspberry Pi Pico (c-sdk)] Storage: Ep 5. TinyUSB USB Mass Storage Device(USB Stick) with 2 LUNs

 本文章介紹Raspberry Pi Pico (RP2040)使用TinyUSB函式庫,建制一個含有兩個LUN(SD and W25Q flash)的USB Mass Storage device(USB Stick)。

有關USB界面與SCSI指令部份由TinyUSB函式庫處理,SD與W25Q Flash 的驅動程式則是修改自前一篇文章的程式碼。

一、TinyUSB

使用usb_device_dual範例程式。
  • usb_descriptiors.c: 為根據USB specification所定義,因此直接引用,未修改。
  • msc_disk_dual.c: 保留所有callback架構,修改後的檔名msc_device_disk.c,有關
    tud_msc_capacity_cb(),則呼叫SD與W25Q driver取得sector count and secotr size。
    tud_msc_read10_cb與tud_msc_write10_cb則個別呼叫SD與W25Q driver的sdmmc_read_sector(), sdmmc_write_sector(), w25q_read_serctor() and w25q_write_sector()。
  • mian.c:呼叫4個function即可。
    storage_driver_init();
    board_init();
    tud_init(BOARD_TUD_RHPORT);
    while (1)
    {
          tud_task(); // tinyusb device task
     }

  • 詳細程式碼附於文末,
    實際在Debian(Linux), Windows and FreeBSD系統下測試I/O效能、磁碟分割與檔案操作,請參閱下列影片。

二、成果展示

Note:
  • 若只使用SD card,則在 msc_device_disk.c 將
// Invoked to determine max LUN
uint8_t tud_msc_get_maxlun_cb(void)
{
  return 2; // LUN 0: SDMMC, LUN 1: W25Q Flash
  }
改為return 1;如下圖所示:
  • 在storage_driver.h檔案中定義SPI速度
#define SPI_BAUDRATE_LOW (1000*1000)
#define SPI_BAUDRATE_HIGH (40*1000*1000)
某些SD card的模組與Pico的相容性問題,可嘗試將
SPI_BAUDRATE_HIGH降低,例如:
#define SPI_BAUDRATE_HIGH (10*1000*1000)

三、程式碼


  • msc_device_disk.c

/* this file was modified from tinyUSB example: msc_disk_dual.c*/

/* 
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ha Thach (tinyusb.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */
#include "bsp/board.h"
#include "tusb.h"

#include "W25Q.h"
#include "spi_sdmmc.h"
#include "hardware/gpio.h"
#include "storage_driver.h"

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

void storage_driver_init() {
  // W25Q driver initialize
  pW25Q = (w25q_data_t*)malloc(sizeof(w25q_data_t));
  pW25Q->spiInit=false;
  w25q_disk_initialize(W25Q_SPI_PORT, W25Q_PIN_CS, pW25Q);

  // SDMMC driver initialize
  pSDMMC = (sdmmc_data_t*)malloc(sizeof(sdmmc_data_t));
  pSDMMC->spiInit=false;
#ifdef __SPI_SDMMC_DMA
  pSDMMC->dmaInit=false;
#endif
  sdmmc_disk_initialize(SDMMC_SPI_PORT, SDMMC_PIN_CS, pSDMMC);

  // LED blinking when reading/writing
  gpio_init(LED_BLINKING_PIN);
  gpio_set_dir(LED_BLINKING_PIN, true);
}

#if CFG_TUD_MSC

// Invoked to determine max LUN
uint8_t tud_msc_get_maxlun_cb(void)
{
  return 2; // LUN 0: SDMMC, LUN 1: W25Q Flash
}

// Invoked when received SCSI_CMD_INQUIRY
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
{
  switch (lun) {
    case SDMMC_LUN:
      sprintf(vendor_id  , "SDMMC");
      sprintf(product_id , "Mass Storage");
      sprintf(product_rev, "1.0");
    break;
    case W25Q_LUN:
      sprintf(vendor_id  , "Winbond");
      sprintf(product_id , "Mass Storage");
      sprintf(product_rev, "1.0");
    break;
  }
 
}

// Invoked when received Test Unit Ready command.
// return true allowing host to read/write this LUN e.g SD card inserted
bool tud_msc_test_unit_ready_cb(uint8_t lun)
{
  //if ( lun == 1 && board_button_read() ) return false;

  return true; // RAM disk is always ready
}

// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
// Application update block count and block size
void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size)
{
  switch(lun) {
    case SDMMC_LUN:
        *block_count = pSDMMC->sectCount;
        *block_size  = pSDMMC->sectSize;
    break;
    case W25Q_LUN:
        *block_count = pW25Q->sectorCount;
        *block_size  = pW25Q->sectorSize;
    break;
  }
}

// Invoked when received Start Stop Unit command
// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
// - Start = 1 : active mode, if load_eject = 1 : load disk storage
bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
{
  (void) lun;
  (void) power_condition;

  if ( load_eject )
  {
    if (start)
    {
      // load disk storage
    }else
    {
      // unload disk storage
    }
  }

  return true;
}

// Callback invoked when received READ10 command.
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
{
  switch(lun) {
    case SDMMC_LUN:
      if (!sdmmc_read_sector(lba, buffer, bufsize, pSDMMC)) return -1;
    break;
    case W25Q_LUN:
      if (!w25q_read_sector((uint32_t)lba, offset, buffer, bufsize, pW25Q)) return -1;
    break;
  }
  return (int32_t) bufsize;
}

bool tud_msc_is_writable_cb (uint8_t lun)
{
  (void) lun;

#ifdef CFG_EXAMPLE_MSC_READONLY
  return false;
#else
  return true;
#endif
}

// Callback invoked when received WRITE10 command.
// Process data in buffer to disk's storage and return number of written bytes
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize)
{

  switch (lun) {
    case SDMMC_LUN:
        if (!sdmmc_write_sector(lba, buffer, bufsize, pSDMMC)) return -1;
    break;
    case W25Q_LUN:
      if (offset >= pW25Q->sectorSize) return -1;
#ifndef CFG_EXAMPLE_MSC_READONLY
        w25q_sector_erase(lba, pW25Q);
        w25q_write_sector(lba, offset, buffer, bufsize, pW25Q);
#else
        (void) lun; (void) lba; (void) offset; (void) buffer;
#endif
    break;
  }
  

  return (int32_t) bufsize;
}

// Callback invoked when received an SCSI command not in built-in list below
// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
// - READ10 and WRITE10 has their own callbacks
int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize)
{
  // read10 & write10 has their own callback and MUST not be handled here

  void const* response = NULL;
  int32_t resplen = 0;

  // most scsi handled is input
  bool in_xfer = true;

  switch (scsi_cmd[0])
  {
    default:
      // Set Sense = Invalid Command Operation
      tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);

      // negative means error -> tinyusb could stall and/or response with failed status
      resplen = -1;
    break;
  }

  // return resplen must not larger than bufsize
  if ( resplen > bufsize ) resplen = bufsize;

  if ( response && (resplen > 0) )
  {
    if(in_xfer)
    {
      memcpy(buffer, response, (size_t) resplen);
    }else
    {
      // SCSI output
    }
  }

  return resplen;
}

#endif

void led_blinking_task(void)
{
  static uint32_t start_ms = 0;
  static bool led_state = false;

  // Blink every interval ms
  if ( board_millis() - start_ms < 50) return; // not enough time
  start_ms += 50;

  gpio_put(LED_BLINKING_PIN,led_state);
  led_state = 1 - led_state; // toggle
}

void led_blinking_task_off(void) {
  gpio_put(LED_BLINKING_PIN,false);
}
  • CMakeLists.txt(driver)
add_library(storage_driver INTERFACE)
target_sources(storage_driver INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/sdmmc/spi_sdmmc.c
    ${CMAKE_CURRENT_LIST_DIR}/flash/W25Q.c
    ${CMAKE_CURRENT_LIST_DIR}/msc_device_disk.c

)

target_include_directories(storage_driver INTERFACE
    ${CMAKE_CURRENT_LIST_DIR}/sdmmc
    ${CMAKE_CURRENT_LIST_DIR}/flash
    ${CMAKE_CURRENT_LIST_DIR}

)

target_link_libraries(storage_driver INTERFACE
        hardware_spi
        hardware_dma
        hardware_rtc
        pico_stdlib

)
  • storage_driver.h
#ifndef _STORAGE_DRIVER_H_
#define _STORAGE_DRIVER_H_
/* used by my project */

#define SPI_BAUDRATE_LOW (1000*1000)
#define SPI_BAUDRATE_HIGH (40*1000*1000)

enum {
    SDMMC_LUN=0,
    W25Q_LUN=1,
};

/* =================================  */
#define  LED_BLINKING_PIN     25
void led_blinking_task(void);
void led_blinking_task_off(void);
/* =================================  */

void storage_driver_init(void);

#endif
  • spi_sdmmc.h
/*
This library is derived from ChaN's FatFs - Generic FAT Filesystem Module.
*/
#ifndef SPI_SDMMC_H
#define SPI_SDMMC_H
#include "hardware/spi.h"
#include "hardware/dma.h"

//#define __SPI_SDMMC_DMA

/* SDMMC SPI pins*/
#define SDMMC_SPI_PORT spi1
#define SDMMC_PIN_MISO 12
#define SDMMC_PIN_CS   13
#define SDMMC_PIN_SCK  14
#define SDMMC_PIN_MOSI 15
/* ====================== */

/* MMC/SD command */
#define CMD0 (0)		   /* GO_IDLE_STATE */
#define CMD1 (1)		   /* SEND_OP_COND (MMC) */
#define ACMD41 (0x80 + 41) /* SEND_OP_COND (SDC) */
#define CMD8 (8)		   /* SEND_IF_COND */
#define CMD9 (9)		   /* SEND_CSD */
#define CMD10 (10)		   /* SEND_CID */
#define CMD12 (12)		   /* STOP_TRANSMISSION */
#define ACMD13 (0x80 + 13) /* SD_STATUS (SDC) */
#define CMD16 (16)		   /* SET_BLOCKLEN */
#define CMD17 (17)		   /* READ_SINGLE_BLOCK */
#define CMD18 (18)		   /* READ_MULTIPLE_BLOCK */
#define CMD23 (23)		   /* SET_BLOCK_COUNT (MMC) */
#define ACMD23 (0x80 + 23) /* SET_WR_BLK_ERASE_COUNT (SDC) */
#define CMD24 (24)		   /* WRITE_BLOCK */
#define CMD25 (25)		   /* WRITE_MULTIPLE_BLOCK */
#define CMD32 (32)		   /* ERASE_ER_BLK_START */
#define CMD33 (33)		   /* ERASE_ER_BLK_END */
#define CMD38 (38)		   /* ERASE */
#define CMD55 (55)		   /* APP_CMD */
#define CMD58 (58)		   /* READ_OCR */

#define SDMMC_SECT_SIZE 512

typedef struct {
    spi_inst_t *spiPort;
    bool spiInit;
    uint csPin;
    uint8_t cardType;
    uint16_t sectSize;
    uint32_t sectCount;
#ifdef __SPI_SDMMC_DMA
    uint read_dma_ch;
    uint write_dma_ch;
    dma_channel_config dma_rc;
    dma_channel_config dma_wc;
    bool dmaInit;
#endif
    uint8_t Stat;
}sdmmc_data_t;


uint8_t sdmmc_disk_initialize(spi_inst_t *spi, uint cs_pin, sdmmc_data_t *sdmmc);
//static 
int sdmmc_read_datablock (uint8_t *buff, uint btr, sdmmc_data_t *sdmmc);
//static 
int sdmmc_write_datablock (const uint8_t *buff, uint8_t token, sdmmc_data_t *sdmmc);
//static 
uint8_t sdmmc_send_cmd(uint8_t cmd,  uint32_t arg, sdmmc_data_t *sdmmc);
uint8_t sdmmc_write_sector(uint32_t sector, uint8_t *buff, uint32_t len, sdmmc_data_t *sdmmc);
uint8_t  sdmmc_read_sector(uint32_t sector, uint8_t* buff, uint32_t len, sdmmc_data_t *sdmmc);





/* MMC card type flags (MMC_GET_TYPE) */
#define CT_MMC3		0x01		/* MMC ver 3 */
#define CT_MMC4		0x02		/* MMC ver 4+ */
#define CT_MMC		0x03		/* MMC */
#define CT_SDC1		0x02		/* SDC ver 1 */
#define CT_SDC2		0x04		/* SDC ver 2+ */
#define CT_SDC		0x0C		/* SDC */
#define CT_BLOCK	0x10		/* Block addressing */




#endif 
  • spi_sdmmc.c
/*
This library is derived from ChaN's FatFs - Generic FAT Filesystem Module.
*/
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "spi_sdmmc.h"
#include "storage_driver.h"


#define SDMMC_CD 0 // card detect
#define SDMMC_WP 0 // write protected

static uint8_t dummy_block[SDMMC_SECT_SIZE];

void sdmmc_spi_cs_high(sdmmc_data_t *sdmmc);
void sdmmc_spi_cs_low(sdmmc_data_t *sdmmc);
static int sdmmc_wait_ready(uint timeout, sdmmc_data_t *sdmmc);
static void sdmmc_init_spi(sdmmc_data_t *sdmmc);

static void sdmmc_deselect(sdmmc_data_t *sdmmc)
{
	uint8_t src = 0xFF;
	sdmmc_spi_cs_high(sdmmc);
	spi_write_blocking(sdmmc->spiPort, &src, 1);
}

/*-----------------------------------------------------------------------*/
/* Select card and wait for ready                                        */
/*-----------------------------------------------------------------------*/
static int sdmmc_select(sdmmc_data_t *sdmmc) /* 1:OK, 0:Timeout */
{
	uint8_t src = 0xFF;
	sdmmc_spi_cs_low(sdmmc);
	spi_write_blocking(sdmmc->spiPort, &src, 1);
	if (sdmmc_wait_ready(500, sdmmc))
		return 1; /* Wait for card ready */
	sdmmc_deselect(sdmmc);
	return 0; /* Timeout */
}

uint64_t sdmmc_get_sector_count(sdmmc_data_t *sdmmc) {
	uint8_t n, csd[16];
	uint32_t st, ed, csize;

	uint64_t sectorCounter;

	uint8_t src = 0xFF;
	
	if ((sdmmc_send_cmd(CMD9, 0, sdmmc) == 0) && sdmmc_read_datablock(csd, 16, sdmmc))
	{
		if ((csd[0] >> 6) == 1)
		{ /* SDC CSD ver 2 */
			csize = csd[9] + ((uint16_t)csd[8] << 8) + ((uint32_t)(csd[7] & 63) << 16) + 1;
			sectorCounter = csize << 10;
		}
		else
		{ /* SDC CSD ver 1 or MMC */
			n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
			csize = (csd[8] >> 6) + ((uint16_t)csd[7] << 2) + ((uint16_t)(csd[6] & 3) << 10) + 1;
			sectorCounter = csize << (n - 9);
		}
	
	} else {
		sectorCounter=0;
	}
	sdmmc_deselect(sdmmc);
	return sectorCounter;
}

uint32_t sdmmc_get_block_count(sdmmc_data_t *sdmmc) {
	uint8_t n, csd[16];
	uint32_t st, ed, csize;

	uint32_t sectorCounter=0;

	uint8_t src = 0xFF;

	if (sdmmc->cardType & CT_SDC2)
	{ /* SDC ver 2+ */
		if (sdmmc_send_cmd(ACMD13, 0, sdmmc) == 0)
		{ /* Read SD status */
			spi_write_blocking(sdmmc->spiPort, &src, 1);
			if (sdmmc_read_datablock(csd, 16, sdmmc))
			{ /* Read partial block */
				for (n = 64 - 16; n; n--)
					spi_write_blocking(sdmmc->spiPort, &src, 1); // xchg_spi(0xFF);	/* Purge trailing data */
				sectorCounter = 16UL << (csd[10] >> 4);
			}
		}
	}
	else
	{ /* SDC ver 1 or MMC */
		if ((sdmmc_send_cmd(CMD9, 0, sdmmc) == 0) && sdmmc_read_datablock(csd, 16, sdmmc))
		{ /* Read CSD */
			if (sdmmc->cardType & CT_SDC1)
			{ /* SDC ver 1.XX */
				sectorCounter = (((csd[10] & 63) << 1) + ((uint16_t)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
			}
			else
			{ /* MMC */
				sectorCounter = ((uint16_t)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
			}

		}
	}
	sdmmc_deselect(sdmmc);
	return sectorCounter;
}

uint8_t  sdmmc_read_sector(uint32_t sector, uint8_t* buff, uint32_t len, sdmmc_data_t *sdmmc) {
	uint8_t ret=0;
	uint count;
	count = (len % sdmmc->sectSize)  ? ((len / sdmmc->sectSize) + 1) : (len / sdmmc->sectSize);
	
	if (!count)
		return ret; /* Check parameter */

	if (!(sdmmc->cardType & CT_BLOCK))
		sector *= sdmmc->sectSize; /* LBA ot BA conversion (byte addressing cards) */
	if (count == 1)
	{												  /* Single sector read */
		if ((sdmmc_send_cmd(CMD17, sector, sdmmc) == 0) /* READ_SINGLE_BLOCK */
			&& sdmmc_read_datablock(buff, sdmmc->sectSize, sdmmc))
		{
			ret = 1;
		}
led_blinking_task(); //// LED blinking
	}
	else
	{ /* Multiple sector read */
		if (sdmmc_send_cmd(CMD18, sector, sdmmc) == 0)
		{ /* READ_MULTIPLE_BLOCK */
			do
			{
				if (!sdmmc_read_datablock(buff, sdmmc->sectSize, sdmmc))
					break;
				buff += sdmmc->sectSize;
led_blinking_task(); //// LED blinking
			} while (--count);
			
			sdmmc_send_cmd(CMD12, 0, sdmmc); /* STOP_TRANSMISSION */
			ret = 1;
		}
	}
led_blinking_task_off();  //// LED blinking off
	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd()

	return ret;
}

uint8_t sdmmc_write_sector(uint32_t sector, uint8_t *buff, uint32_t len, sdmmc_data_t *sdmmc) {

	uint8_t ret=0;
	uint count;
	count = (len % sdmmc->sectSize)  ? ((len / sdmmc->sectSize)+1) : (len / sdmmc->sectSize);

	if (!count)
		return ret; /* Check parameter */
	//if (sdmmc->Stat & STA_NOINIT)
	//	return RES_NOTRDY; /* Check drive status */
	//if (sdmmc->Stat & STA_PROTECT)
	//	return RES_WRPRT; /* Check write protect */

	if (!(sdmmc->cardType & CT_BLOCK))
		sector *= sdmmc->sectSize; /* LBA ==> BA conversion (byte addressing cards) */

	if (count == 1)
	{												  /* Single sector write */
		if ((sdmmc_send_cmd(CMD24, sector, sdmmc) == 0) /* WRITE_BLOCK */
			&& sdmmc_write_datablock(buff, 0xFE, sdmmc))
		{
			ret = 1;
		}
led_blinking_task();  //// LED_blinking
	}
	else
	{ /* Multiple sector write */
		if (sdmmc->cardType & CT_SDC)
			sdmmc_send_cmd(ACMD23, count, sdmmc); /* Predefine number of sectors */
		if (sdmmc_send_cmd(CMD25, sector, sdmmc) == 0)
		{ /* WRITE_MULTIPLE_BLOCK */
			do
			{
				if (!sdmmc_write_datablock(buff, 0xFC, sdmmc))
					break;
				buff += sdmmc->sectSize;

led_blinking_task();  //// LED_blinking
			} while (--count);
			  // LED blinking off
			if (!sdmmc_write_datablock(0, 0xFD, sdmmc))
				count = 1; /* STOP_TRAN token */
			ret =1;
		}
	}
led_blinking_task_off();
	sdmmc_deselect(sdmmc); // sdmmc_select() is called in function sdmmc_send_cmd

}

/* sdmmc spi port initialize*/
void sdmmc_spi_port_init(sdmmc_data_t *sdmmc)
{
	spi_init(sdmmc->spiPort, SPI_BAUDRATE_LOW);
	gpio_set_function(SDMMC_PIN_MISO, GPIO_FUNC_SPI);
	gpio_set_function(sdmmc->csPin, GPIO_FUNC_SIO);
	gpio_set_function(SDMMC_PIN_SCK, GPIO_FUNC_SPI);
	gpio_set_function(SDMMC_PIN_MOSI, GPIO_FUNC_SPI);
	gpio_set_dir(sdmmc->csPin, GPIO_OUT);
	gpio_put(sdmmc->csPin, 1); // deselect

	sdmmc->spiInit = true; // alreadily initialized
}

/* config spi dma*/
#ifdef __SPI_SDMMC_DMA
void config_spi_dma(sdmmc_data_t *sdmmc)
{
	sdmmc->read_dma_ch = dma_claim_unused_channel(true);
	sdmmc->write_dma_ch = dma_claim_unused_channel(true);
	sdmmc->dma_rc = dma_channel_get_default_config(sdmmc->read_dma_ch);
	sdmmc->dma_wc = dma_channel_get_default_config(sdmmc->write_dma_ch);
	channel_config_set_transfer_data_size(&(sdmmc->dma_rc), DMA_SIZE_8);
	channel_config_set_transfer_data_size(&(sdmmc->dma_wc), DMA_SIZE_8);
	channel_config_set_read_increment(&(sdmmc->dma_rc), false);
	channel_config_set_write_increment(&(sdmmc->dma_rc), true);
	channel_config_set_read_increment(&(sdmmc->dma_wc), true);
	channel_config_set_write_increment(&(sdmmc->dma_wc), false);
	channel_config_set_dreq(&(sdmmc->dma_rc), spi_get_dreq(sdmmc->spiPort, false));
	channel_config_set_dreq(&(sdmmc->dma_wc), spi_get_dreq(sdmmc->spiPort, true));

	for (int i = 0; i < SDMMC_SECT_SIZE; i++)
		dummy_block[i] = 0xFF;

	dma_channel_configure(sdmmc->read_dma_ch,
						  &(sdmmc->dma_rc),
						  NULL,
						  &spi_get_hw(sdmmc->spiPort)->dr,
						  sdmmc->sectSize, false);
	dma_channel_configure(sdmmc->write_dma_ch,
						  &(sdmmc->dma_wc),
						  &spi_get_hw(sdmmc->spiPort)->dr,
						  NULL,
						  sdmmc->sectSize, false);
	sdmmc->dmaInit = true;
}
#endif

/* set spi cs low (select)*/
void sdmmc_spi_cs_low(sdmmc_data_t *sdmmc)
{
	gpio_put(sdmmc->csPin, 0);
}
/* set spi cs high (deselect)*/
void sdmmc_spi_cs_high(sdmmc_data_t *sdmmc)
{
	gpio_put(sdmmc->csPin, 1);
}
/* Initialize SDMMC SPI interface */
static void sdmmc_init_spi(sdmmc_data_t *sdmmc)
{
	sdmmc_spi_port_init(sdmmc); // if not initialized, init it
#ifdef __SPI_SDMMC_DMA
	if (!sdmmc->dmaInit)
		config_spi_dma(sdmmc);
#endif

	sleep_ms(10);
}

/* Receive a sector data (512 uint8_ts) */
static void sdmmc_read_spi_dma(
	uint8_t *buff, /* Pointer to data buffer */
	uint btr,	/* Number of uint8_ts to receive (even number) */
	sdmmc_data_t *sdmmc)
{
#ifdef __SPI_SDMMC_DMA
	dma_channel_set_read_addr(sdmmc->write_dma_ch, dummy_block, false);
	dma_channel_set_trans_count(sdmmc->write_dma_ch, btr, false);

	dma_channel_set_write_addr(sdmmc->read_dma_ch, buff, false);
	dma_channel_set_trans_count(sdmmc->read_dma_ch, btr, false);

	dma_start_channel_mask((1u << (sdmmc->read_dma_ch)) | (1u << (sdmmc->write_dma_ch)));
	dma_channel_wait_for_finish_blocking(sdmmc->read_dma_ch);
#else
	spi_read_blocking(sdmmc->spiPort, 0xFF, buff, btr);
#endif
}


/* Send a sector data (512 uint8_ts) */
static void sdmmc_write_spi_dma(
	const uint8_t *buff, /* Pointer to the data */
	uint btx,		  /* Number of uint8_ts to send (even number) */
	sdmmc_data_t *sdmmc)
{
#ifdef __SPI_SDMMC_DMA
	dma_channel_set_read_addr(sdmmc->write_dma_ch, buff, false);
	dma_channel_set_trans_count(sdmmc->write_dma_ch, btx, false);
	dma_channel_start(sdmmc->write_dma_ch);
	dma_channel_wait_for_finish_blocking(sdmmc->write_dma_ch);
#else
	spi_write_blocking(sdmmc->spiPort, buff, btx);
#endif
}


/*-----------------------------------------------------------------------*/
/* Wait for card ready                                                   */
/*-----------------------------------------------------------------------*/
static int sdmmc_wait_ready(uint timeout, sdmmc_data_t *sdmmc)
{
	uint8_t dst;
	absolute_time_t timeout_time = make_timeout_time_ms(timeout);
	do
	{
		spi_read_blocking(sdmmc->spiPort, 0xFF, &dst, 1);
	} while (dst != 0xFF && 0 < absolute_time_diff_us(get_absolute_time(), timeout_time)); /* Wait for card goes ready or timeout */

	return (dst == 0xFF) ? 1 : 0;
}

/*-----------------------------------------------------------------------*/
/* Deselect card and release SPI                                         */
/*-----------------------------------------------------------------------*/


/*-----------------------------------------------------------------------*/
/* Receive a data packet from the MMC                                    */
/*-----------------------------------------------------------------------*/
// static
int sdmmc_read_datablock(			 /* 1:OK, 0:Error */
						 uint8_t *buff, /* Data buffer */
						 uint btr,	 /* Data block length (uint8_t) */
						 sdmmc_data_t *sdmmc)
{
	uint8_t token;
	absolute_time_t timeout_time = make_timeout_time_ms(200);
	do
	{ /* Wait for DataStart token in timeout of 200ms */
		spi_read_blocking(sdmmc->spiPort, 0xFF, &token, 1);
		
	} while ((token == 0xFF) && 0 < absolute_time_diff_us(get_absolute_time(), timeout_time));
	if (token != 0xFE)
		return 0; /* Function fails if invalid DataStart token or timeout */

	sdmmc_read_spi_dma(buff, btr, sdmmc);
	// Discard CRC
	spi_read_blocking(sdmmc->spiPort, 0xFF, &token, 1);
	spi_read_blocking(sdmmc->spiPort, 0xFF, &token, 1);
	return 1; // Function succeeded
}

/*-----------------------------------------------------------------------*/
/* Send a data packet to the MMC                                         */
/*-----------------------------------------------------------------------*/
//#if FF_FS_READONLY == 0
// static
int sdmmc_write_datablock(					/* 1:OK, 0:Failed */
						  const uint8_t *buff, /* Ponter to 512 uint8_t data to be sent */
						  uint8_t token,		/* Token */
						  sdmmc_data_t *sdmmc)
{
	uint8_t resp;
	if (!sdmmc_wait_ready(500, sdmmc))
		return 0; /* Wait for card ready */
	// Send token : 0xFE--single block, 0xFC -- multiple block write start, 0xFD -- StopTrans
	spi_write_blocking(sdmmc->spiPort, &token, 1);
	if (token != 0xFD)
	{										   /* Send data if token is other than StopTran */
		sdmmc_write_spi_dma(buff, sdmmc->sectSize, sdmmc); /* Data */

		token = 0xFF;
		spi_write_blocking(sdmmc->spiPort, &token, 1); // Dummy CRC
		spi_write_blocking(sdmmc->spiPort, &token, 1);

		spi_read_blocking(sdmmc->spiPort, 0xFF, &resp, 1);
		// receive response token: 0x05 -- accepted, 0x0B -- CRC error, 0x0C -- Write Error
		if ((resp & 0x1F) != 0x05)
			return 0; /* Function fails if the data packet was not accepted */
	}
	return 1;
}
//#endif

/*-----------------------------------------------------------------------*/
/* Send a command packet to the MMC                                      */
/*-----------------------------------------------------------------------*/
//static
uint8_t sdmmc_send_cmd(			  /* Return value: R1 resp (bit7==1:Failed to send) */
						   uint8_t cmd,  /* Command index */
						   uint32_t arg, /* Argument */
						   sdmmc_data_t *sdmmc)
{
	uint8_t n, res;
	uint8_t tcmd[5];

	if (cmd & 0x80)
	{ /* Send a CMD55 prior to ACMD<n> */
		cmd &= 0x7F;
		res = sdmmc_send_cmd(CMD55, 0, sdmmc);
		if (res > 1)
			return res;
	}

	/* Select the card and wait for ready except to stop multiple block read */
	if (cmd != CMD12)
	{
		sdmmc_deselect(sdmmc);
		if (!sdmmc_select(sdmmc))
			return 0xFF;
	}

	/* Send command packet */
	tcmd[0] = 0x40 | cmd;		 // 0 1 cmd-index(6) --> 01xxxxxx(b)
	tcmd[1] = (uint8_t)(arg >> 24); // 32 bits argument
	tcmd[2] = (uint8_t)(arg >> 16);
	tcmd[3] = (uint8_t)(arg >> 8);
	tcmd[4] = (uint8_t)arg;
	spi_write_blocking(sdmmc->spiPort, tcmd, 5);
	n = 0x01; /* Dummy CRC + Stop */
	if (cmd == CMD0)
		n = 0x95; /* Valid CRC for CMD0(0) */
	if (cmd == CMD8)
		n = 0x87; /* Valid CRC for CMD8(0x1AA) */

	spi_write_blocking(sdmmc->spiPort, &n, 1);

	/* Receive command resp */
	if (cmd == CMD12)
		spi_read_blocking(sdmmc->spiPort, 0xFF, &res, 1); /* Diacard following one uint8_t when CMD12 */
	n = 10;												  /* Wait for response (10 uint8_ts max) */
	do
	{
		spi_read_blocking(sdmmc->spiPort, 0xFF, &res, 1);
	} while ((res & 0x80) && --n);

	return res; /* Return received response */
}

/*-----------------------------------------------------------------------*/
/* Initialize disk drive                                                 */
/*-----------------------------------------------------------------------*/
uint8_t sdmmc_init(sdmmc_data_t *sdmmc)
{
	uint8_t n, cmd, ty, src, ocr[4];

	sdmmc->Stat = 0;
	// low baudrate
	spi_set_baudrate(sdmmc->spiPort, SPI_BAUDRATE_LOW);
	src = 0xFF;
	sdmmc_spi_cs_low(sdmmc);
	for (n = 10; n; n--)
		spi_write_blocking(sdmmc->spiPort, &src, 1); // Send 80 dummy clocks
	sdmmc_spi_cs_high(sdmmc);

	ty = 0;
	if (sdmmc_send_cmd(CMD0, 0, sdmmc) == 1)
	{ /* Put the card SPI/Idle state, R1 bit0=1*/
		absolute_time_t timeout_time = make_timeout_time_ms(1000);
		if (sdmmc_send_cmd(CMD8, 0x1AA, sdmmc) == 1)
		{													 /* SDv2? */
			spi_read_blocking(sdmmc->spiPort, 0xFF, ocr, 4); // R7(5 uint8_ts): R1 read by sdmmc_send_cmd, Get the other 32 bit return value of R7 resp
			if (ocr[2] == 0x01 && ocr[3] == 0xAA)
			{ /* Is the card supports vcc of 2.7-3.6V? */
				while ((0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) && sdmmc_send_cmd(ACMD41, 1UL << 30, sdmmc))
					; /* Wait for end of initialization with ACMD41(HCS) */
				if ((0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) && sdmmc_send_cmd(CMD58, 0, sdmmc) == 0)
				{ /* Check CCS bit in the OCR */
					spi_read_blocking(sdmmc->spiPort, 0xFF, ocr, 4);
					ty = (ocr[0] & 0x40) ? CT_SDC2 | CT_BLOCK : CT_SDC2; /* Card id SDv2 */
				}
			}
		}
		else
		{ /* Not SDv2 card */
			if (sdmmc_send_cmd(ACMD41, 0, sdmmc) <= 1)
			{ /* SDv1 or MMC? */
				ty = CT_SDC1;
				cmd = ACMD41; /* SDv1 (ACMD41(0)) */
			}
			else
			{
				ty = CT_MMC3;
				cmd = CMD1; /* MMCv3 (CMD1(0)) */
			}
			while ((0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) && sdmmc_send_cmd(cmd, 0, sdmmc))
				;																										   /* Wait for end of initialization */
			if (!(0 < absolute_time_diff_us(get_absolute_time(), timeout_time)) || sdmmc_send_cmd(CMD16, SDMMC_SECT_SIZE, sdmmc) != 0) /* Set block length: 512 */
				ty = 0;
		}
	}
	sdmmc->cardType = ty; /* Card type */
	sdmmc_deselect(sdmmc);
	if (ty)
	{ /* OK */
		// high baudrate
		printf("\nThe actual baudrate(SD/MMC):%d\n",spi_set_baudrate(sdmmc->spiPort, SPI_BAUDRATE_HIGH)); // speed high
		sdmmc->sectSize = SDMMC_SECT_SIZE;
		sdmmc->Stat = 1; /* Clear STA_NOINIT flag */
	}
	else
	{ /* Failed */
		sdmmc->Stat = 0;
	}
	sdmmc->sectCount = sdmmc_get_sector_count(sdmmc);
	return sdmmc->Stat;
}
/////////////////////////////////////////////
uint8_t sdmmc_disk_initialize(spi_inst_t *spi, uint cs_pin, sdmmc_data_t *sdmmc)
{
	sdmmc->spiPort = spi;
    sdmmc->csPin = cs_pin;
	if (!sdmmc->spiInit) {
		sdmmc_init_spi(sdmmc); /* Initialize SPI */
	}
	uint8_t stat = sdmmc_init(sdmmc);

	return stat;
}
  • W25Q.h
#ifndef W25Q_H
#define W25Q_H
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/spi.h"


/* W25Q SPI pins*/
#define W25Q_SPI_PORT spi0
#define W25Q_PIN_MISO 16
#define W25Q_PIN_SCK  18
#define W25Q_PIN_MOSI 19
#define W25Q_PIN_CS 17
/* ====================== */

typedef struct{
    spi_inst_t *spi;
    uint        cs_pin;
    uint8_t     uuid[8];
    uint32_t    jedec_id;

    uint32_t    blockCount;
    uint32_t    blockSize;
    
    uint32_t    sectorCount;
    uint32_t    sectorSize;

    uint32_t    pageCount;
    uint16_t    pageSize;
    
    uint8_t     statusRegister1;
    uint8_t     statusRegister2;
    uint8_t     statusRegister3;
    uint32_t    capacityKB;
    uint8_t     lock;
    bool        spiInit;
    uint8_t     Stat;
}w25q_data_t;


uint8_t w25q_disk_initialize(spi_inst_t *spi, uint cs_pin, w25q_data_t *w25q);
void w25q_get_manufacter_device_id(uint8_t *mid, w25q_data_t *w25q);
void w25q_get_JEDEC_ID(w25q_data_t *w25q);
void w25q_erase_chip(w25q_data_t *w25q);
void w25q_page_program(uint32_t page_addr, uint16_t offset, uint8_t *buf, uint32_t len, w25q_data_t *w25q);
void w25q_write_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q);
void w25q_write_block_64k(uint32_t blk_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q);
void w25q_read_bytes(uint32_t address, uint8_t *buf, uint32_t len, w25q_data_t *w25q);
void w25q_read_page(uint32_t page_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q);
uint8_t w25q_read_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q);
void w25q_read_block(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len, w25q_data_t *w25q);
//void w25q_read_data(uint32_t address, uint8_t *buf, uint32_t len);
//void w25q_fast_read_data(uint32_t address, uint8_t *buf, uint32_t len);
void w25q_read_status_register_1(w25q_data_t *w25q);
void w25q_read_status_register_2(w25q_data_t *w25q);
void w25q_read_status_register_3(w25q_data_t *w25q);
void w25q_write_status_register_1(w25q_data_t *w25q);
void w25q_write_status_register_2(w25q_data_t *w25q);
void w25q_write_status_register_3(w25q_data_t *w25q);
void w25q_sector_erase(uint32_t sect_addr, w25q_data_t *w25q);
void w25q_block_erase_32k(uint32_t blk_addr,w25q_data_t *w25q);
void w25q_block_erase_64k(uint32_t blk_addr, w25q_data_t *w25q);
void w25q_get_uid(w25q_data_t *w25q);
void w25q_write_enable(w25q_data_t *w25q);
void w25q_write_diable(w25q_data_t *w25q);

#endif
  • W25Q.c
#include "stdio.h"
#include "stdlib.h"
#include "W25Q.h"
#include "storage_driver.h"

uint8_t rxbuf[10];
uint8_t txbuf[10];

/*=================*/

const uint8_t i_uniqueid=0x4b;
const uint8_t i_page_program=0x02;
const uint8_t i_read_data=0x03;
const uint8_t i_fast_read_data=0x0b;
const uint8_t i_write_disable=0x04;
const uint8_t i_read_status_r1=0x05;
const uint8_t i_read_status_r2=0x35;
const uint8_t i_read_status_r3=0x15;
const uint8_t i_write_status_r1=0x01;
const uint8_t i_write_status_r2=0x31;
const uint8_t i_write_status_r3=0x11;
const uint8_t i_sector_erase=0x20;
const uint8_t i_block_erase_32k=0x52;
const uint8_t i_block_erase_64k=0xd8;
const uint8_t i_write_enable=0x06;
const uint8_t i_erase_chip=0xc7;

const uint8_t i_device_id=0x90;
const uint8_t i_JEDEC_ID=0x9f;

void w25q_spi_port_init(w25q_data_t *w25q) {
    gpio_set_dir(w25q->cs_pin, GPIO_OUT);
    gpio_put(w25q->cs_pin, 1);
    gpio_set_function(w25q->cs_pin,   GPIO_FUNC_SIO);
    gpio_set_function(W25Q_PIN_MISO, GPIO_FUNC_SPI);
    gpio_set_function(W25Q_PIN_SCK,  GPIO_FUNC_SPI);
    gpio_set_function(W25Q_PIN_MOSI, GPIO_FUNC_SPI);
        
    printf("\nThe actual baudrate(W25Q):%d\n",spi_init(w25q->spi, SPI_BAUDRATE_HIGH));

    w25q->spiInit=true;
}

void w25q_spi_cs_low(w25q_data_t *w25q) {
    gpio_put(w25q->cs_pin,0);
}
void w25q_spi_cs_high(w25q_data_t *w25q){
    gpio_put(w25q->cs_pin,1);
}
void w25q_send_cmd_read(uint8_t cmd, uint32_t address, uint8_t *buf, uint32_t len, bool is_fast, w25q_data_t *w25q) {
    uint8_t addr[4];
    int addr_len=3;
    addr[3] = 0x00;
    if (is_fast) addr_len=4;
    addr[0] = (address & 0x00ff0000) >> 16;
    addr[1] = (address & 0x0000ff00) >> 8;
    addr[2] = (address & 0x000000ff);
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    spi_write_blocking(w25q->spi, addr, addr_len);
    spi_read_blocking(w25q->spi, 0x00, buf, len);
    w25q_spi_cs_high(w25q);
}

void w25q_send_cmd_write(uint8_t cmd, uint32_t address, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
    uint8_t addr[3];
    
    addr[0] = (address & 0x00ff0000) >> 16;
    addr[1] = (address & 0x0000ff00) >> 8;
    addr[2] = (address & 0x000000ff);
    w25q_write_enable(w25q);
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    spi_write_blocking(w25q->spi, addr, 3);
    spi_write_blocking(w25q->spi, buf, len);
    w25q_spi_cs_high(w25q);

}

void w25q_send_cmd_addr(uint8_t cmd, uint32_t address, w25q_data_t *w25q) {
    uint8_t addr[3];
    addr[0] = (address & 0x00ff0000) >> 16;
    addr[1] = (address & 0x0000ff00) >> 8;
    addr[2] = (address & 0x000000ff);
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    spi_write_blocking(w25q->spi, addr, 3);
    w25q_spi_cs_high(w25q);
}

void w25q_send_cmd(uint8_t cmd, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    spi_read_blocking(w25q->spi, 0x00, buf, len);
    w25q_spi_cs_high(w25q);
}

void w25q_send_simple_cmd(uint8_t cmd, w25q_data_t *w25q) {
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, &cmd, 1);
    w25q_spi_cs_high(w25q);
}

void w25q_write_enable(w25q_data_t *w25q) {
    w25q_send_simple_cmd(i_write_enable, w25q);
    sleep_ms(1);
}
void w25q_write_disable(w25q_data_t *w25q) {
    w25q_send_simple_cmd(i_write_disable, w25q);
    sleep_ms(1);
}

/*==================*/
uint8_t w25q_disk_initialize(spi_inst_t *spi, uint cs_pin, w25q_data_t *w25q) {
    w25q->spi = spi;
    w25q->cs_pin = cs_pin;

    if (!w25q->spiInit) w25q_spi_port_init(w25q);

    w25q_get_JEDEC_ID(w25q);
    w25q->lock = 1;
	sleep_ms(100);
	switch (w25q->jedec_id & 0x000000FF)
	{
	    case 0x20: // 	w25q512
		    w25q->blockCount = 1024;
		break;
	    case 0x19: // 	w25q256
		    w25q->blockCount = 512;
		break;
	    case 0x18: // 	w25q128
		    w25q->blockCount = 256;
		break;
	    case 0x17: //	w25q64
		    w25q->blockCount = 128;
		break;
	    case 0x16: //	w25q32
		    w25q->blockCount = 64;
		break;
        case 0x15: //	w25q16
            w25q->blockCount = 32;
            break;
        case 0x14: //	w25q80
            w25q->blockCount = 16;
            break;
        case 0x13: //	w25q40
            w25q->blockCount = 8;
        case 0x12: //	w25q20
            w25q->blockCount = 4;
            break;
        case 0x11: //	w25q10
            w25q->blockCount = 2;
            break;
        default:
            w25q->lock = 0;
            return false;
    }
	w25q->pageSize = 256;
	w25q->sectorSize = 0x1000;
	w25q->sectorCount = w25q->blockCount * 16;
	w25q->pageCount = (w25q->sectorCount * w25q->sectorSize) / w25q->pageSize;
	w25q->blockSize = w25q->sectorSize * 16;
	w25q->capacityKB = (w25q->sectorCount * w25q->sectorSize) / 1024;
	w25q_get_uid(w25q);
    w25q_read_status_register_1(w25q);
    w25q_read_status_register_2(w25q);
    w25q_read_status_register_3(w25q);
	w25q->lock = 0;
    w25q->Stat = 0;
	return w25q->Stat;
}


void w25q_read_status_register_1(w25q_data_t *w25q){
    w25q_send_cmd(i_read_status_r1, &w25q->statusRegister1, 1, w25q);
}
void w25q_read_status_register_2(w25q_data_t *w25q){
    w25q_send_cmd(i_read_status_r2, &w25q->statusRegister2, 1, w25q);
}
void w25q_read_status_register_3(w25q_data_t *w25q){
    w25q_send_cmd(i_read_status_r3, &w25q->statusRegister3, 1, w25q);
}

void w25q_write_status_register_1(w25q_data_t *w25q){
    w25q_send_cmd(i_write_status_r1, &w25q->statusRegister1, 1, w25q);
}
void w25q_write_status_register_2(w25q_data_t *w25q){
    w25q_send_cmd(i_write_status_r2, &w25q->statusRegister2, 1, w25q);
}
void w25q_write_status_register_3(w25q_data_t *w25q){
    w25q_send_cmd(i_write_status_r3, &w25q->statusRegister3, 1, w25q);
}

void w25q_wait_for_write_end(w25q_data_t *w25q)
{
	sleep_ms(1);
	w25q_spi_cs_low(w25q);
	spi_write_blocking(w25q->spi, &i_read_status_r1,1);
	do
	{
		spi_read_blocking(w25q->spi, 0x00, &w25q->statusRegister1,1);
		sleep_ms(1);
	} while ((w25q->statusRegister1 & 0x01) == 0x01);
	w25q_spi_cs_high(w25q);
}

void w25q_erase_chip(w25q_data_t *w25q) {
    while (w25q->lock) sleep_ms(1);
    w25q->lock=1;
    w25q_write_enable(w25q);
    w25q_send_simple_cmd(i_erase_chip, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(10);
    w25q->lock=0;
}

void w25q_page_program(uint32_t page_addr, uint16_t offset, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
    while (w25q->lock) sleep_ms(1);
    w25q->lock=1;
    if (offset + len > w25q->pageSize) {
        len = w25q->pageSize - offset;
    }
    page_addr = (page_addr * w25q->pageSize) + offset;
    w25q_wait_for_write_end(w25q);
    w25q_write_enable(w25q);
    w25q_send_cmd_write(i_page_program, page_addr, buf, len, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(1);
    w25q->lock=0;
}
/*===========================*/
uint32_t w25_page_to_sector_address(uint32_t pageAddress, w25q_data_t *w25q)
{
	return ((pageAddress * w25q->pageSize) / w25q->sectorSize);
}
uint32_t w25q_page_to_block_address(uint32_t pageAddress, w25q_data_t *w25q)
{
	return ((pageAddress * w25q->pageSize) / w25q->blockSize);
}
uint32_t w25q_data_sector_to_block_address(uint32_t sectorAddress, w25q_data_t *w25q)
{
	return ((sectorAddress * w25q->sectorSize) / w25q->blockSize);
}
uint32_t w25q_sector_to_page_address(uint32_t sectorAddress, w25q_data_t *w25q)
{
	return (sectorAddress * w25q->sectorSize) / w25q->pageSize;
}
uint32_t w25q_block_to_page_address(uint32_t blockAddress, w25q_data_t *w25q)
{
	return (blockAddress * w25q->blockSize) / w25q->pageSize;
}
/*============================*/

void w25q_write_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q) {
	if (offset >= w25q->sectorSize) return;
    if (offset + len  > w25q->sectorSize) 
		len = w25q->sectorSize - offset;
	uint32_t startPage;
	int32_t bytesToWrite;
	uint32_t localOffset;


    startPage = w25q_sector_to_page_address(sect_addr, w25q) + (offset / w25q->pageSize);
	localOffset = offset % w25q->pageSize;
    bytesToWrite = len;

	do
	{
        w25q_page_program(startPage, localOffset, buf, bytesToWrite, w25q);
		startPage++;
		bytesToWrite -= w25q->pageSize - localOffset;
		buf += w25q->pageSize - localOffset;
		localOffset = 0;
led_blinking_task();

	} while (bytesToWrite > 0);
led_blinking_task_off();

}

void w25q_write_block_64k(uint32_t blk_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q) {
	if ((len > w25q->blockSize) || (len == 0))
		len = w25q->blockSize;
	if (offset >= w25q->blockSize)
		return;
	uint32_t startPage;
	int32_t bytesToWrite;
	uint32_t localOffset;
	if ((offset + len) > w25q->blockSize)
		bytesToWrite = w25q->blockSize - offset;
	else
		bytesToWrite = len;
	startPage = w25q_block_to_page_address(blk_addr, w25q) + (offset / w25q->pageSize);
	localOffset = offset % w25q->pageSize;
	do
	{
		w25q_page_program(startPage, localOffset, buf, len, w25q);
		startPage++;
		bytesToWrite -= w25q->pageSize - localOffset;
		buf += w25q->pageSize - localOffset;
		localOffset = 0;
	} while (bytesToWrite > 0);
   
}

uint8_t w25q_disk_write(
	const uint8_t *buff,	/* Ponter to the data to write */
	uint64_t sector,		/* Start sector number (LBA) */
	uint count, 			/* Number of sectors to write (1..128) */
    w25q_data_t *w25q
) 
{
    uint8_t *tbuf=(uint8_t*)buff;
    while(count > 1)
    {
        w25q_sector_erase(sector, w25q);
        w25q_write_sector(sector, 0, tbuf, w25q->sectorSize, w25q);
        count--;
        tbuf += w25q->sectorSize;
        sector++;
    }
    if (count == 1)
    {
        w25q_sector_erase(sector, w25q);
        w25q_write_sector(sector, 0, tbuf, w25q->sectorSize, w25q);
        count--;
    }
	
	return count? 1: 0;
}

void w25q_read_bytes(uint32_t address, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
	while (w25q->lock == 1) sleep_ms(1);
	w25q->lock = 1;
    w25q_send_cmd_read(i_fast_read_data, address, buf, len, true, w25q);
	sleep_ms(1);
	w25q->lock = 0;
}

void w25q_read_page(uint32_t page_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q) {
	while (w25q->lock == 1) sleep_ms(1);
	w25q->lock = 1;
    if (offset >= w25q->pageSize) return;
	if ((offset + len) >= w25q->pageSize)
		len = w25q->pageSize - offset;
	page_addr = page_addr * w25q->pageSize + offset;
    w25q_send_cmd_read(i_fast_read_data, page_addr, buf, len, true, w25q);
	
	sleep_ms(1);
	w25q->lock = 0;
}

uint8_t w25q_read_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf,  uint32_t len, w25q_data_t *w25q) {
	
    if (offset >= w25q->sectorSize) return 0;
    if (offset + len > w25q->sectorSize)
		len = w25q->sectorSize - offset;
	uint32_t startPage;
	int32_t bytesToRead;
	uint32_t localOffset;
    bytesToRead = len;
	
    startPage = w25q_sector_to_page_address(sect_addr, w25q) + (offset / w25q->pageSize);
	localOffset = offset % w25q->pageSize;
	do
	{
		w25q_read_page(startPage, localOffset, buf, bytesToRead, w25q);
    
		startPage++;
		bytesToRead -= w25q->pageSize - localOffset;
		buf += w25q->pageSize - localOffset;
		localOffset = 0;
led_blinking_task();

	} while (bytesToRead > 0);
led_blinking_task_off();

    return 1;

}
void w25q_read_block(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len, w25q_data_t *w25q) {
	if (offset+len > w25q->blockSize)
		len = w25q->blockSize-offset;

	uint32_t startPage;
	int32_t bytesToRead;
	uint32_t localOffset;
    bytesToRead = len;

	startPage = w25q_block_to_page_address(blk_addr, w25q) + (offset / w25q->pageSize);
	localOffset = offset % w25q->pageSize;
	do
	{
		w25q_read_page(startPage, localOffset, buf, bytesToRead, w25q);
		startPage++;
		bytesToRead -= w25q->pageSize - localOffset;
		buf += w25q->pageSize - localOffset;
		localOffset = 0;
	} while (bytesToRead > 0);

}


void w25q_sector_erase(uint32_t sect_addr, w25q_data_t *w25q) {
    while(w25q->lock) sleep_ms(1);
    w25q->lock=1;
    sect_addr = sect_addr * w25q->sectorSize;
    w25q_wait_for_write_end(w25q);
    w25q_write_enable(w25q);
    w25q_send_cmd_addr(i_sector_erase, sect_addr, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(1);
    w25q->lock=0;
}
void w25q_block_erase_32k(uint32_t blk_addr, w25q_data_t *w25q) {
    while(w25q->lock) sleep_ms(1);
    w25q->lock=1;
    blk_addr = blk_addr * w25q->sectorSize * 8;
    w25q_wait_for_write_end(w25q);
    w25q_write_enable(w25q);
    w25q_send_cmd_addr(i_block_erase_32k, blk_addr, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(1);
    w25q->lock=0;
}
void w25q_block_erase_64k(uint32_t blk_addr, w25q_data_t *w25q) {
    while(w25q->lock) sleep_ms(1);
    w25q->lock=1;
    blk_addr = blk_addr * w25q->sectorSize * 16;
    w25q_wait_for_write_end(w25q);
    w25q_write_enable(w25q);
    w25q_send_cmd_addr(i_block_erase_64k, blk_addr, w25q);
    w25q_wait_for_write_end(w25q);
    sleep_ms(1);
    w25q->lock=0;
}
void w25q_get_manufacter_device_id(uint8_t *mid, w25q_data_t *w25q){
    assert(w25q->spi);
    w25q_send_cmd_read(i_device_id, 0x000000, mid, 2, false, w25q);
}

void w25q_get_JEDEC_ID(w25q_data_t *w25q) {
    uint8_t temp[3];
    w25q_send_cmd(i_JEDEC_ID, temp, 3, w25q);
    w25q->jedec_id = ((uint32_t)temp[0] << 16) | ((uint32_t)temp[1] << 8) | (uint32_t)temp[2];
}
void w25q_get_uid(w25q_data_t *w25q) {
    assert(w25q->spi);
    txbuf[0]= 0x4b;
    txbuf[1] = 0x00; txbuf[2] = 0x00; txbuf[3] = 0x00;txbuf[4]=0x00;
    w25q_spi_cs_low(w25q);
    spi_write_blocking(w25q->spi, txbuf, 5);
    spi_read_blocking(w25q->spi, 0x00, w25q->uuid, 8);
    w25q_spi_cs_high(w25q);
}
  • main.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"

#include "bsp/board.h"
#include "tusb.h"

#include "storage_driver.h"


/*------------- MAIN -------------*/
int main(void)
{
  stdio_init_all();

  storage_driver_init();

  board_init();

  // init device stack on configured roothub port
  tud_init(BOARD_TUD_RHPORT);
 

  while (1)
  {
    tud_task(); // tinyusb device task
  }

  return 0;
}
  • CMakeLists.txt(root)
# Generated Cmake Pico project file

cmake_minimum_required(VERSION 3.13)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

# Initialise pico_sdk from installed location
# (note this can come from environment, CMake cache etc)
set(PICO_SDK_PATH "/home/duser/pico/pico-sdk")

set(PICO_BOARD pico CACHE STRING "Board type")

# Pull in Raspberry Pi Pico SDK (must be before project)
include(pico_sdk_import.cmake)

if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0")
  message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()

project(pico_usb_msc_device C CXX ASM)

# Initialise the Raspberry Pi Pico SDK
pico_sdk_init()

# Add executable. Default name is the project name, version 0.1

add_executable(pico_usb_msc_device 
      main.c
      usb_descriptors.c )

pico_set_program_name(pico_usb_msc_device "pico_usb_msc_device")
pico_set_program_version(pico_usb_msc_device "0.1")

pico_enable_stdio_uart(pico_usb_msc_device 1)
pico_enable_stdio_usb(pico_usb_msc_device 0)

# Add the standard library to the build
target_link_libraries(pico_usb_msc_device
        pico_stdlib)

# Add the standard include files to the build
target_include_directories(pico_usb_msc_device PRIVATE
  ${CMAKE_CURRENT_LIST_DIR}
  ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)

add_subdirectory(storage_driver)
# Add any user requested libraries
target_link_libraries(pico_usb_msc_device 
        tinyusb_device 
        tinyusb_board
        storage_driver
        )

pico_add_extra_outputs(pico_usb_msc_device)

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
)

2023年8月24日 星期四

PWM Ep.2: Raspberry Pi Pico Remotely Controls Servo Motors Using ADC and nRF24L01+

本篇文章說明以PWM控制Servo Motor。以Raspberry Pi Pico整合ADC, nRF24L01+遠端遙控ESP32-CAM鏡頭拍攝的角度。

一、Servo motor and PWM:

上圖說明Servo的PWM Period為20ms(50Hz),Pico-SDK以下列指令來設定:

    gpio_set_function(pin, GPIO_FUNC_PWM);
    slice = pwm_gpio_to_slice_num(pin);
    pwm_ch = pwm_gpio_to_channel(pin);
    pwm_config c = pwm_get_default_config();
    pwm_config_set_clkdiv(&c, 125);  // 20ms period, steps 20000, clkdiv = 125 
    pwm_config_set_wrap(&c, 20000);
    pwm_config_set_phase_correct(&c, false);
    pwm_init(*slice, &c, true);  

系統clock為125M Hz
125M Hz/125/20000 = 50Hz。

以下列指令設定PWM duty Cycle:

pwm_set_chan_level(slice, pwm_ch, top_count);

top_count:angle*1000/180+15000, 0度時1.5ms duty cycle。-90~90為1ms~2ms。

實際使用時可能因為所使用元件誤差,需要調整,在本篇文章的實驗時-90~90為0.5ms~2.5ms。

二、ADC:

Raspberry Pi Pico 共有4個ADC ports,如上圖所示。解析度為12 bit因此取樣值為0~4095。下列指令

adc_init();
adc_gpio_init(ADC_PIN); // init and assign ADC pin

adc_select_input(ADC_NUM);  // gpio 26: adc_num 0, gpio 27:1,  gpio 28 : 2
adv_value = adc_read();

三、nRF24L01+:

驅動程式請參閱[Raspberry Pi Pico] nRF24L01+ Ep. 1 : Pico-SDK C-code driver using IRQ,相關網路拓撲請參閱[Raspberry Pi Pico] nRF24L01+ Ep. 2 : Various types of network topologies 

  • 發送端設定

nRF24_spi_default_init(20, 21, nRF24_irq_callback);
nRF24_config_mode(TRANSMITTER);
nRF24_enable_feature(FEATURE_EN_DPL, true);
nRF24_set_TX_addr(nRF24_addr0, 5);
nRF24_set_RX_addr(0, nRF24_addr0, 5);
nRF24_enable_data_pipe_dynamic_payload_length(0, true);

  • 接收端設定

nRF24_spi_default_init(20, 21, nRF24_irq_callback);
nRF24_config_mode(RECEIVER);
nRF24_enable_feature(FEATURE_EN_DPL, true);
nRF24_set_RX_addr(0, nRF24_addr0, 5);
nRF24_enable_data_pipe_dynamic_payload_length(0, true);

  • nRF24_irq_callback:

void nRF24_irq_callback(uint8_t event_type, uint8_t datapipe, uint8_t* data, uint8_t width) {
    static uint8_t XY_AXIS;
    static uint8_t ang[4];
    uint8_t data_buffer[33];
    uint8_t data_len; 
      switch(event_type) {
        case EVENT_RX_DR:
#ifdef TRANS_SIDE
#else   
            data_len = width;
            memcpy(data_buffer, data, width);
            deencrypt_data(data_buffer, &data_len);
            XY_AXIS=data_buffer[0];
            memset(ang,0,sizeof(ang));
            memcpy(ang, data_buffer+2, width-2);
            if (XY_AXIS == 'X') {
                setServoAngle(atoi(ang), pwm_ch_x, pwm_slice_x);
            }
            if (XY_AXIS == 'Y') {
                setServoAngle(atoi(ang), pwm_ch_y, pwm_slice_y);
            }
#endif
                
        break;
        case EVENT_TX_DS:
        break;
        case EVENT_MAX_RT:
        break;
    }
}

四、成果影片:



五、程式碼:
main.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/clocks.h"
#include "stdlib.h"
#include "hardware/pwm.h"
#include "hardware/adc.h"
#include "nRF24L01.h"
#include "string.h"

#define PWM_X_PIN   14
#define PWM_Y_PIN   15
uint pwm_ch_x, pwm_slice_x;
uint pwm_ch_y, pwm_slice_y;
int32_t servo_x_ang, servo_y_ang;
#define ADC_X_PIN   26
#define ADC_X_NUM   0
#define ADC_Y_PIN   27
#define ADC_Y_NUM   1
#define ADC_RANGE (1<<12)    // pi pico 12 bits ADC
int32_t adc_x_center, adc_y_center;

#define TRANS_SIDE

uint8_t nRF24_addr0[] = "0node";

void encrypt_data(uint8_t *data, uint8_t *len) {
    // do data encryption
    return;
}

void deencrypt_data(uint8_t *data, uint8_t *len) {
    // do data deencryption
    return;
}
void setServoAngle(int ang, uint pwm_ch, uint slice) {
    //uint16_t top_count = (uint16_t)(1000/180*ang + 1500);  // angle 0: 1.5 ms duty
    uint16_t top_count = (uint16_t)(ang/0.09 + 1500);  // angle 0: 1.5 ms duty
    pwm_set_chan_level(slice, pwm_ch, top_count);
}

void servo_init(uint pin, uint *pwm_ch, uint* slice) {
    // set Servo pwm
    gpio_set_function(pin, GPIO_FUNC_PWM);
    *slice = pwm_gpio_to_slice_num(pin);
    *pwm_ch = pwm_gpio_to_channel(pin);
    pwm_config c = pwm_get_default_config();
    pwm_config_set_clkdiv(&c, 125);  // 20ms period, steps 20000, clkdiv = 125 
    pwm_config_set_wrap(&c, 20000);
    pwm_config_set_phase_correct(&c, false);
    pwm_init(*slice, &c, true);    
}

void nRF24_irq_callback(uint8_t event_type, uint8_t datapipe, uint8_t* data, uint8_t width) {
    static uint8_t XY_AXIS;
    static uint8_t ang[4];
    uint8_t data_buffer[33];
    uint8_t data_len; 
      switch(event_type) {
        case EVENT_RX_DR:
#ifdef TRANS_SIDE
#else   
            data_len = width;
            memcpy(data_buffer, data, width);
            deencrypt_data(data_buffer, &data_len);
            XY_AXIS=data_buffer[0];
            memset(ang,0,sizeof(ang));
            memcpy(ang, data_buffer+2, width-2);
            if (XY_AXIS == 'X') {
                setServoAngle(atoi(ang), pwm_ch_x, pwm_slice_x);
            }
            if (XY_AXIS == 'Y') {
                setServoAngle(atoi(ang), pwm_ch_y, pwm_slice_y);
            }
#endif
        break;
        case EVENT_TX_DS:


        break;
        case EVENT_MAX_RT:

        break;
    }
}

int main()
{
    uint8_t payload[33];
    uint8_t payload_len;
    stdio_init_all();
    nRF24_spi_default_init(20, 21, nRF24_irq_callback);

#ifdef TRANS_SIDE
    nRF24_config_mode(TRANSMITTER);
    nRF24_enable_feature(FEATURE_EN_DPL, true);
    nRF24_set_TX_addr(nRF24_addr0, 5);
    nRF24_set_RX_addr(0, nRF24_addr0, 5);
    nRF24_enable_data_pipe_dynamic_payload_length(0, true);

    adc_init();                     // init and assign ADC pin
    adc_gpio_init(ADC_X_PIN);
    adc_gpio_init(ADC_Y_PIN);
    for (int i=0; i < 100; i++) {     // average 100 samples as mouse static position
        adc_select_input(ADC_X_NUM);
        adc_x_center += adc_read();
        adc_select_input(ADC_Y_NUM);
        adc_y_center += adc_read();
    }
    adc_x_center /=100;
    adc_y_center /=100;
#else 
    nRF24_config_mode(RECEIVER);
    nRF24_enable_feature(FEATURE_EN_DPL, true);
    nRF24_set_RX_addr(0, nRF24_addr0, 5);
    nRF24_enable_data_pipe_dynamic_payload_length(0, true);

    servo_y_ang = 0;
    servo_x_ang = 0;
    servo_init(PWM_X_PIN, &pwm_ch_x, &pwm_slice_x);
    servo_init(PWM_Y_PIN, &pwm_ch_y, &pwm_slice_y);

    setServoAngle(servo_x_ang, pwm_ch_x, pwm_slice_x);
    setServoAngle(servo_y_ang, pwm_ch_y, pwm_slice_y);
#endif 
    int32_t adc_raw;
    while(1) {
#ifdef TRANS_SIDE
        adc_select_input(ADC_X_NUM);
        adc_raw = adc_read();
        if(abs(adc_raw-adc_x_center) > 60) {
            //servo_x_ang=(adc_raw-adc_x_center)*90/adc_x_center;
            if (adc_raw < adc_x_center) {
                servo_x_ang++;
                if (servo_x_ang > 90) servo_x_ang = 90;
            }
            else { 
                servo_x_ang--;
                if (servo_x_ang < -90) servo_x_ang = -90;
           }
            //setServoAngle(servo_x_ang, pwm_ch_x, pwm_slice_x);
            sprintf(payload,"X:%d", servo_x_ang);
            payload_len = strlen(payload); 
            encrypt_data(payload, &payload_len);
            nRF24_write_payload(payload, payload_len);
            printf("payload:%s\n", payload);
        }

        adc_select_input(ADC_Y_NUM);
        adc_raw = adc_read();
        if(abs(adc_raw-adc_y_center) > 60) {
            if (adc_raw < adc_y_center) {
                servo_y_ang++;
                if (servo_y_ang > 90) servo_y_ang = 90;
            }
            else { 
                servo_y_ang--;
                if (servo_y_ang < -90) servo_y_ang = -90;
            }
            //setServoAngle(servo_y_ang, pwm_ch_y, pwm_slice_y);
            sprintf(payload,"Y:%d", servo_y_ang);
            payload_len = strlen(payload); 
            encrypt_data(payload, &payload_len);
            nRF24_write_payload(payload, payload_len);
            printf("payload:%s\n", payload);
        }
        //printf("payload:%s\n", payload);
#endif
       sleep_ms(20);
       
    }

    return 0;
}

2023年8月22日 星期二

PWM Ep. 1 : Square Wave Frequency and Duty Cycle Measurement -- Raspberry Pi Pico and STM32F103C8T6

本文章探討利用Raspberry Pi Pico來量測輸入PWM方波的頻率與佔空比。以STM32F103C8T6來產生PWM方波讓Pico來量測。

一、RP2040 PWM:

(source: RP2040 datasheet)
RP2040 共有8 PWM slices,每個slice有channel A與B,與GPIO對應表如自如所示。每個slice channel A and B均能輸出PWM方波。但只有channel B能作為輸入PWM用。

RP2040輸出方波的波型主要由下列三個主要參數決定。
  1. 8.4除瀕(8 bits整數,4 bits小數) :
    0: max(256)。整數:1~255,小數:0~(15/16)。
    pwm_set_clkdiv(slice_num, div);
  2. 16 bit 計數值(TOP register):
    pwm_set_wrap(slice_num, top_wap);
    top_wap: 0~65535
  3. 16 bit 計數比較值(CC register):
    pwm_set_chan_level(slice_num, PWM_CHAN_A, cc);
    cc: 0~65536
例如:頻率為500k佔空比為40%的方波
系統clock為125M Hz,
pwm_set_clkdiv(slice_num 12.5) --> 125M/12.5=10M
pwm_set_warp(slice_num, 19) --> 10M/(19+1) = 500k
pwm_set_chan_level(slice_num, PWM_CHAN_A, 8) --> 8/(19+1) = 40%

(Source RP2040 datasheet)

  • PWM輸入:
僅有每個slice的Channel B能接受輸入。
(source RP2040 datasheet)

將Channel B設為輸入有三種模式,分別為偵測High Leve, rising edge與falling edge。
pwm_config_set_clkdiv_mode(&cfg, mode);
pwm_set_clkdiv_mode(slice_num, mode);
mode:PWM_DIV_B_HIGH, PWM_DIV_B_RISING, PWM_DIV_B_FALLING

  1. 設定PWM_DIV_B_HIGH時可用來偵測Duty Cycle:
    當channel偵測到input PWM pulse level為high時,CC count 加 1,register為16 bits,因此最大值為65535。例如:
    slice_num : 3, click div: 100, 偵測時間為50ms。則channel B最大clock數為:
    125M Hz/100*(50/1000) =  62500 Hz。
    pwm_set_clkdiv_mode(slice_num, PWM_DIV_B_HIGH);    pwm_set_clkdiv(slice_num, 100);
    pwm_set_enabled(slice_num, true);
    sleep_ms(50);
    pwm_set_enabled(slice_num, false);

    上述為channel B 50ms總clock數。
    count=pwm_get_counter(slice_num);
    當channel B偵測到input PWM pulse為high時, count+1,因此
    count / 625000 * 100%即為Duty cycle。
  2. 設定PWM_DIV_B_RISING or PWM_DIV_B_FALLING時可用來偵測Frequency:
    當channel B偵測到input PWM pulse為RISING or FALLING時count+1。pwm_config cfg = pwm_get_default_config();    pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING);    pwm_config_set_clkdiv(&cfg, 1);   
    pwm_init(slice_num, &cfg, false);    gpio_set_function(gpio, GPIO_FUNC_PWM);
    以125M Hz偵測。
    pwm_set_enabled(slice_num, true);       
    sleep_ms(delay_ms);       
    pwm_set_enabled(slice_num, false);       
    rising_count = pwm_get_counter(slice_num);

    經過delay_ms後,偵測到
    rising_count個RISING EDGE,因此
    Freq = rising_count / delay_ms*1000 Hz
    但是rising_count最多為65535(16 bit register),因此必須根據input pulse frequence調整delay_ms數。
其他詳細程式碼附於文末。

二、STM32 PWM:

STM32 PWM由timer產生。

如上圖timer 1 clock為72 M Hz。
clock source設為internal clock(72M Hz),prescaler, counter period(ARR)分別為1, 299,因此PWM的Frequency為72M Hz/(1+1)/(299+1) = 120K Hz

Duty cycle=150/(299+1)*100% = 50% 

htim1.Instance->ARR = fs_presc;
htim1.Instance->CCR1 = fs_presc/duty;
改變ARR與CCR可以改變frequency 與duty cycle

三、展示影片:



四、程式碼:

 
#include <stdio.h>

#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "hardware/clocks.h"
#include "string.h"
#include "pico_tft/pico_tft.h"
#include "pico_tft/tft_string/tft_string.h"
#include "pico_tft/fonts/font_ubuntu_mono_24.h"

const uint MEASURE_PIN_1 = 3;
const uint MEASURE_PIN_2 = 5;
bool pwm_slice_warp_1=false;
bool pwm_slice_warp_2=false;

void on_pwm_wrap() {

   uint slice_num_1 = pwm_gpio_to_slice_num(MEASURE_PIN_1);
    if (pwm_get_irq_status_mask() & (1 << slice_num_1)) {
        pwm_clear_irq(slice_num_1);
        pwm_slice_warp_1=true;
        
    }

    uint slice_num_2 = pwm_gpio_to_slice_num(MEASURE_PIN_2);
    if (pwm_get_irq_status_mask() & (1 << slice_num_2)) {
        pwm_clear_irq(slice_num_2);
        pwm_slice_warp_2=true;
        
    }
}


float measure_frequency(uint gpio) {
    uint16_t measure_ms[] = {1,5,10,100,1000};
    bool *pwm_slice_warp=false;

    // Only the PWM B pins can be used as inputs.
    assert(pwm_gpio_to_channel(gpio) == PWM_CHAN_B);
    uint slice_num = pwm_gpio_to_slice_num(gpio);

    if (gpio == MEASURE_PIN_1) pwm_slice_warp = &pwm_slice_warp_1;
    if (gpio == MEASURE_PIN_2) pwm_slice_warp = &pwm_slice_warp_2;

    // Count once for every 1 cycles the PWM B input is high
    pwm_config cfg = pwm_get_default_config();
    pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_RISING);
    pwm_config_set_clkdiv(&cfg, 1);
    pwm_init(slice_num, &cfg, false);
    gpio_set_function(gpio, GPIO_FUNC_PWM);
    
    pwm_set_irq_enabled(slice_num, true);
    irq_set_exclusive_handler(PWM_IRQ_WRAP, on_pwm_wrap);
    irq_set_enabled(PWM_IRQ_WRAP, true);

    uint32_t ms=0;
    uint16_t count=0;
    uint8_t meauser_count = sizeof(measure_ms)/sizeof(measure_ms[0]);
    *pwm_slice_warp=false;
    for (int i=0; i < meauser_count;i++) {
        ms = measure_ms[i];
        pwm_set_enabled(slice_num, true);
        sleep_ms(ms);
        pwm_set_enabled(slice_num, false);
        count = pwm_get_counter(slice_num);
        pwm_set_counter(slice_num,0);

        if (*pwm_slice_warp) {
            *pwm_slice_warp=false;
            if (i > 0) {
                 ms = measure_ms[i-1];
                pwm_set_enabled(slice_num, true);
                sleep_ms(ms);
                pwm_set_enabled(slice_num, false);
                count = pwm_get_counter(slice_num);
                pwm_set_counter(slice_num,0);
            }
            break;
            
        } 
 
    }

    return ((float)count*1000/(ms));
    
}

float measure_duty_cycle(uint gpio) {
    // Only the PWM B pins can be used as inputs.
    assert(pwm_gpio_to_channel(gpio) == PWM_CHAN_B);
    uint slice_num = pwm_gpio_to_slice_num(gpio);

    // Count once for every 100 cycles the PWM B input is high
    pwm_config cfg = pwm_get_default_config();
    pwm_config_set_clkdiv_mode(&cfg, PWM_DIV_B_HIGH);
    pwm_config_set_clkdiv(&cfg, 100);
    pwm_init(slice_num, &cfg, false);
    gpio_set_function(gpio, GPIO_FUNC_PWM);
    uint16_t count=0;
    pwm_set_counter(slice_num,0);

    pwm_set_enabled(slice_num, true);
    sleep_ms(50);
    pwm_set_enabled(slice_num, false);
    count=pwm_get_counter(slice_num);

    float counting_rate = clock_get_hz(clk_sys) / 100;
    float max_possible_count = counting_rate *0.05;   // 125M/100*0.05 = 62500 < 65535

    return pwm_get_counter(slice_num)/max_possible_count;
}

void test_500k_50() {
    gpio_set_function(6, GPIO_FUNC_PWM);
    uint slice_num = pwm_gpio_to_slice_num(6);
    pwm_config cfg = pwm_get_default_config();
    pwm_config_set_clkdiv(&cfg, 12.5);
    pwm_config_set_wrap(&cfg, 19);
    pwm_init(slice_num, &cfg, true);

    pwm_set_chan_level(slice_num, PWM_CHAN_A, 10);
    
}

int main() {
    stdio_init_all();
    tft_init();
    tft_fill_rect(0,0, TFT_WIDTH-1, TFT_HEIGHT-1, 0xffff);
    printf("\nPWM frequency and duty cycle measurement example\n");
    uint8_t buffer[120];
    float duty, frequency;
    while (1) {
        //sleep_ms(1000);
        frequency = measure_frequency(MEASURE_PIN_1);
        duty = measure_duty_cycle(MEASURE_PIN_1)*100;
        sprintf(buffer, "F1:%.0f", frequency);
        tft_draw_string_withbg(10,10, "            ", 0x001f, 0xffff, &font_ubuntu_mono_24);
        tft_draw_string_withbg(10,10, buffer, 0x001f, 0xffff, &font_ubuntu_mono_24);
        sprintf(buffer, "D1:%02.2f%%", duty);
        tft_draw_string_withbg(10,35, buffer, 0xf800, 0xffff, &font_ubuntu_mono_24);

        printf("\nMEASURE 1 == Freq:%07.0f, Duty:%02.2f%%\n", frequency, duty);
       
        frequency = measure_frequency(MEASURE_PIN_2);
        duty = measure_duty_cycle(MEASURE_PIN_2)*100;
        sprintf(buffer, "F2:%.0f", frequency);
        tft_draw_string_withbg(10,65, "            ", 0x001f, 0xffff, &font_ubuntu_mono_24);
        tft_draw_string_withbg(10,65, buffer, 0x001f, 0xffff, &font_ubuntu_mono_24);
        sprintf(buffer, "D2:%02.2f%%", duty);
        tft_draw_string_withbg(10,90, buffer, 0xf800, 0xffff, &font_ubuntu_mono_24);
       
        printf("\nMEASURE 2 == Freq:%07.0f, Duty:%02.2f%%\n", frequency, duty);
        
    }
}