prettyprint

2022年11月24日 星期四

[Raspberry Pi Pico (c-sdk)] Storage: Ep 2. Multi external flash memory devices with FatFs filesystem

本文章介紹在Raspberry Pi Pico環境使用兩個以上外加的Flash memory device,並使用FatFs 檔案系統格式化為Fat。

一、使用元件:

  1. Raspberry Pi Pico -- 1
  2. Winbond W25Q128FV 16MB -- 2


二、接線:

兩個Winbond W25Q128FV Flash memory 都接在SPI 0,第一個Flash 使用 CS Pin 17,第二個使用CS Pin 20。

三、流程說明:



  • 修改glue.c function針對每個Volume(Logical driver)呼叫個別的disk_status, disk_initialize, disk_read, disk_write, disk_ioctl。

  • 修改ffconf.h中的:

#define FF_VOLUMES 4
/* Number of volumes (logical drives) to be used. (1-10) */
設定要同時使用的Volumes數

#define FF_FS_LOCK 5
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/  and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/  is 1.
/
/  0:  Disable file lock function. To avoid volume corruption, application program
/      should avoid illegal open, remove and rename to the open objects.
/  >0: Enable file lock function. The value defines how many files/sub-directories
/      can be opened simultaneously under file lock control. Note that the file
/      lock control is independent of re-entrancy. */
設定要同時開啟的檔案數

  • 測試流程
  1. 個別執行f_mkfs將每個volume(flash device)格式化。
  2. 建立8KB亂數產生的測試資料。
  3. 在volume 0 建立test_p01.txt, test_p02.txt與test.txt三個檔案。在volume 1建立pat1_p11.txt, pat1_p12.txt與test.txt。
  4. 針對volume 0 與volume 1的test.txt寫入不同資料,再讀取以便驗證為確實在不同的volume上。
  5. 分別列出volume 0 與 volume 1的檔案名稱。
  • 測試結果


四、成果影片:




五、程式碼:
  • W25Q.c
#include "stdio.h"
#include "stdlib.h"
#include "W25Q.h"

#include "FatFs/ff.h"
#include "FatFs/ffconf.h"

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

static bool spi_lock=false;
static bool w25q_spi_init_flag=false;

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

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() {
    gpio_set_dir(PIN_CS_0, GPIO_OUT);
    gpio_set_dir(PIN_CS_1, GPIO_OUT);

    gpio_put(PIN_CS_0, 1);
    gpio_put(PIN_CS_1, 1);
    
    gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
    gpio_set_function(PIN_SCK,  GPIO_FUNC_SPI);
    gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
    gpio_set_function(PIN_CS_0,   GPIO_FUNC_SIO);
    gpio_set_function(PIN_CS_1,   GPIO_FUNC_SIO);

    spi_init(SPI_PORT, 5000*1000);
    w25q_spi_init_flag=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);
}

/*==================*/
bool w25q_spi_init(spi_inst_t *spi, uint cs_pin, w25q_data_t *w25q) {
    w25q->spi = spi;
    w25q->cs_pin = cs_pin;
    
    if (!w25q_spi_init_flag) w25q_spi_port_init();

    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;
	return true;
}


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;
	} while (bytesToWrite > 0);

}

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);
   
}

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;
}

void 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;
    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;
	} while (bytesToRead > 0);

}
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);
}
  • w25Q.h
#ifndef W25Q_H
#define W25Q_H
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/spi.h"


#define SPI_PORT spi0
#define PIN_MISO 16
#define PIN_SCK  18
#define PIN_MOSI 19

#define PIN_CS_0 17
#define PIN_CS_1 20   

typedef struct{
    spi_inst_t *spi;
    uint        cs_pin;
    uint8_t     uuid[8];
    uint32_t    jedec_id;
    uint32_t    blockCount;
    uint32_t    pageCount;
    uint32_t    sectorCount;
    uint16_t    pageSize;
    uint32_t    sectorSize;
    uint32_t    blockSize;
    uint8_t     statusRegister1;
    uint8_t     statusRegister2;
    uint8_t     statusRegister3;
    uint32_t    capacityKB;
    uint8_t     lock;
    uint8_t     Stat;
}w25q_data_t;


bool w25q_spi_init(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);
void 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
  • glue.c
#include "stdio.h"
#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */
#include "W25Q.h"
#include "hardware/rtc.h"
#include "pico/util/datetime.h"
#include "stdlib.h"

#define SPI_FLASH_0	0
#define SPI_FLASH_1 1

w25q_data_t *w25q_spi_cs0=NULL;
w25q_data_t *w25q_spi_cs1=NULL;

DSTATUS Stat = STA_NOINIT;

/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	stat = STA_NOINIT;
	if (pdrv == SPI_FLASH_0 && w25q_spi_cs0) {
		stat = w25q_spi_cs0->Stat;
	} 
	if (pdrv == SPI_FLASH_1 && w25q_spi_cs1) {
		stat = w25q_spi_cs0->Stat;
	}
	return stat;
}

/*-----------------------------------------------------------------------*/
/* Inidialize a Drive                                                    */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	stat=STA_NOINIT;
	switch (pdrv) {
		case SPI_FLASH_0:
		if (w25q_spi_cs0 == NULL) w25q_spi_cs0 = (w25q_data_t*)malloc(sizeof(w25q_data_t));
		if (w25q_spi_init(SPI_PORT, PIN_CS_0, w25q_spi_cs0)) {
			w25q_spi_cs0->Stat = RES_OK;
			stat = w25q_spi_cs0->Stat;
		}
		break;
		case SPI_FLASH_1:
		if (w25q_spi_cs1 == NULL) w25q_spi_cs1 = (w25q_data_t*)malloc(sizeof(w25q_data_t));
		if (w25q_spi_init(SPI_PORT, PIN_CS_1, w25q_spi_cs1)) {
			w25q_spi_cs1->Stat = RES_OK;
			stat = w25q_spi_cs1->Stat;
		}
		break;
	}
 	return stat;
}

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	LBA_t sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	DRESULT res;

	switch (pdrv) {
		case SPI_FLASH_0:
		if (w25q_spi_cs0->Stat & STA_NOINIT) return RES_NOTRDY;
		w25q_read_sector(sector, 0, buff, count*w25q_spi_cs0->sectorSize, w25q_spi_cs0);
		break;
		case SPI_FLASH_1:
		if (w25q_spi_cs1->Stat & STA_NOINIT) return RES_NOTRDY;
		w25q_read_sector(sector, 0, buff, count*w25q_spi_cs1->sectorSize, w25q_spi_cs1);
		break;
	}

	return RES_OK;
}

/*-----------------------------------------------------------------------*/
/* Write Sector(s)                                                       */
/*-----------------------------------------------------------------------*/

#if FF_FS_READONLY == 0

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	LBA_t sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	BYTE *tbuf=(BYTE*)buff;
	w25q_data_t * tmp_w25q=NULL;

	switch (pdrv) {
		case SPI_FLASH_0:
		if (w25q_spi_cs0 == NULL) return RES_NOTRDY;
		tmp_w25q = w25q_spi_cs0;
		break;
		case SPI_FLASH_1:
		if (w25q_spi_cs1 == NULL) return RES_NOTRDY;
		tmp_w25q = w25q_spi_cs1;
		break;
	}
	if (pdrv == SPI_FLASH_0 || pdrv == SPI_FLASH_1) {
		while(count > 1)
		{
			w25q_sector_erase(sector, tmp_w25q);
			w25q_write_sector(sector, 0, tbuf, tmp_w25q->sectorSize, tmp_w25q);
			count--;
			tbuf += tmp_w25q->sectorSize;
			sector++;
		}
		if (count == 1)
		{
			w25q_sector_erase(sector, tmp_w25q);
			w25q_write_sector(sector, 0, tbuf, tmp_w25q->sectorSize, tmp_w25q);
			count--;
		}
	}
	return count ? RES_ERROR : RES_OK;
}

#endif

/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions                                               */
/*-----------------------------------------------------------------------*/

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT res = RES_ERROR;
	
    switch(cmd) {
    case CTRL_SYNC:
    	res = RES_OK;
    	break;
    case GET_SECTOR_SIZE:
		if (pdrv == SPI_FLASH_0) {
    		*(WORD*)buff = (WORD)w25q_spi_cs0->sectorSize; // in f_mkfs() [WORD ss] 
    		res = RES_OK;
		}
		if (pdrv == SPI_FLASH_1) {
    		*(WORD*)buff = (WORD)w25q_spi_cs1->sectorSize; // in f_mkfs() [WORD ss] 
    		res = RES_OK;
		}
    	break;
    case GET_BLOCK_SIZE:
		if (pdrv == SPI_FLASH_0) {
			*(DWORD*)buff = w25q_spi_cs0->blockSize;
			res = RES_OK;
		}
		if (pdrv == SPI_FLASH_1) {
			*(DWORD*)buff = w25q_spi_cs1->blockSize;
			res = RES_OK;
		}
    	break;
    case GET_SECTOR_COUNT:
		if (pdrv == SPI_FLASH_0) {
			*(DWORD*)buff = w25q_spi_cs0->sectorCount;
			res = RES_OK;
		}
		if (pdrv == SPI_FLASH_1) {
			*(DWORD*)buff = w25q_spi_cs1->sectorCount;
			res = RES_OK;
		}
    	break;
    default:
    	res = RES_PARERR;
    	break;
    }
    return res;
}

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;
}

  • spi_multi_flash_fatfs.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "W25Q.h"
#include "ff.h"
#include "diskio.h"
#include "hardware/flash.h"
#include "string.h"

#define FLASH_PATH_0 "0:"
#define FLASH_PATH_1 "1:"

FRESULT scan_files (
    char* path        /* Start node to be scanned (***also used as work area***) */
)
{
    FRESULT res;
    DIR dir;
    UINT i;
    static FILINFO fno;


    res = f_opendir(&dir, path);                       /* Open the directory */
    if (res == FR_OK) {
        for (;;) {
            res = f_readdir(&dir, &fno);                   /* Read a directory item */
            if (res != FR_OK || fno.fname[0] == 0) break;  /* Break on error or end of dir */
            if (fno.fattrib & AM_DIR) {                    /* It is a directory */
                i = strlen(path);
                sprintf(&path[i], "/%s", fno.fname);
                res = scan_files(path);                    /* Enter the directory */
                if (res != FR_OK) break;
                path[i] = 0;
            } else {                                       /* It is a file. */
                printf("%s/%s\n", path, fno.fname);
            }
        }
        f_closedir(&dir);
    }

    return res;
}

int main()
{
    stdio_init_all();

    FRESULT res;
    FATFS fs0, fs1;
    FIL fil0, fil1;
    UINT br;
    uint8_t work[4096*2];
    uint8_t readbuff[4096*2];
   
    res = f_mkfs(FLASH_PATH_0, 0, work, 4096);
    if(res != FR_OK) {
        printf("mkfs 0 error\n");
        return 1;
    }
    printf("\n\nmkfs 0 successfully(run only once): remove all data\n");

    res = f_mkfs(FLASH_PATH_1, 0, work, 4096);
    if(res != FR_OK) {
        printf("mkfs 1 error\n");
        return 1;
    }
    printf("mkfs 1 successfully(run only once): remove all data\n");

    // generator test data
    for (uint32_t i=0; i < 4096*2; i++) {
        uint8_t c = rand() % 127;
        if (c < 31) c+=31;
        work[i] = c;
    }

    printf("\nwrite buffer:200-300\n");
    for (uint32_t i = 200; i <= 300; i++) {
        printf("%c",work[i]);
    }
    printf("\n");

    //mount fs0
    res = f_mount(&fs0, FLASH_PATH_0, 1);
    if (res != FR_OK) {
        printf("mount 0 error\n");
        return 1;
    }

    // mount fs1
    res = f_mount(&fs1, FLASH_PATH_1, 1);
    if (res != FR_OK) {
        printf("mount drv 1 error\n");
        return 1;
    }

    res = f_open(&fil0, FLASH_PATH_0"/test_p01.txt", FA_CREATE_ALWAYS|FA_WRITE);
    f_write(&fil0, "01abc", 5, &br);
    f_close(&fil0);

    res = f_open(&fil0, FLASH_PATH_0"/test_p02.txt", FA_CREATE_ALWAYS|FA_WRITE);
    f_write(&fil0, "02abc", 5, &br);
    f_close(&fil0);

    res = f_open(&fil0, FLASH_PATH_0"/test.txt", FA_CREATE_ALWAYS|FA_WRITE);
    if (res != FR_OK) {
        printf("open error\n");
        return 1;
    }
    res = f_write(&fil0, work, 4096*2, &br);
    f_close(&fil0);

    res = f_open(&fil1, FLASH_PATH_1"/pat1_p11.txt", FA_CREATE_ALWAYS|FA_WRITE);
    f_close(&fil1);
    res = f_open(&fil1, FLASH_PATH_1"/pat1_p12.txt", FA_CREATE_ALWAYS|FA_WRITE);
    f_close(&fil1);

    res = f_open(&fil1, FLASH_PATH_1"/test.txt", FA_CREATE_ALWAYS|FA_WRITE);
    if (res != FR_OK) {
        printf("open 1 error\n");
        return 1;
    }

    // change some data and write into FLASH_PATH_1
    memcpy(work+200, "FLASH_1", 7);
    f_write(&fil1, work, 4096*2, &br);
    f_close(&fil1);
    
    res = f_open(&fil0, FLASH_PATH_0"/test.txt", FA_READ);
    if (res != FR_OK) {
        printf("read open error\n");
        return 1;
    }
    f_read(&fil0, readbuff, 4096*2, &br);
    printf("\nread FLASH_PATH_0/test.txt buff:200-300\n");
    for (uint32_t i = 200; i <= 300; i++) {
        printf("%c",readbuff[i]);
    }
    printf("\n");
    
    res = f_open(&fil1, FLASH_PATH_1"/test.txt", FA_READ);
    if (res != FR_OK) {
        printf("read open 1 error\n");
        return 1;
    }
    
    f_read(&fil1, readbuff, 4096*2, &br);
    printf("\nread FLASH_PATH_1/test.txt buff:200-300\n");
    for (uint32_t i = 200; i <= 300; i++) {
        printf("%c",readbuff[i]);
    }
    printf("\n");
    f_close(&fil0);
    f_close(&fil1);

    printf("\nlist all files in  FLASH_PATH_0\n");
    scan_files(FLASH_PATH_0);

    printf("\nlist all files in  FLASH_PATH_1\n");
    scan_files(FLASH_PATH_1);

    f_unmount(FLASH_PATH_0);
    f_unmount(FLASH_PATH_1);

    
    return 0;
}


2022年11月22日 星期二

[Raspberry Pi Pico (c-sdk)] Storage: Ep 1. Builtin XIP flash & external flash device with FatFs Filesystem

 本文章介紹Raspberry Pi Pico 使用內建的flash 與外加的SPI flash devicet存取資料。

Raspberry Pi Pico 內建Flash為2MB Winbond W25Q16JV, QSPI interface。外加一個Winbond W25Q128FV 16MB的Flash device。測試再沒有檔案系統與有檔案系統下存取資料。


一、Raspberry Pi Pico開發板內建Flash讀寫:

開發板內建為Winbond W25Q16JV 2MB的Flash。除了存放程式碼外,多餘的空間可用來存放少量的資料,如系統設定檔等。
使用c-sdk內建指令,使用hardware_flash library。
CMakeLists.txt:
include file:
指令:
要寫入新資料或是要覆蓋就資料前,必須先erase後才能成功。
下列程式碼為在XIP_BASE(0x10000000) offset 1M位置寫入第二的page後再讀出。讀出資料直接取得XIP_BASE+offset address 的內容。


二、外接Flash模組:

使用Winbond W25Q128FV 16MB Flash測試。Datasheet指令集如下:

spi clock如下圖:
根據Datasheet指令,相關程式碼如文末所示。
  • 測試一:沒有使用檔案系統下,直接寫入page與讀取。
新的page或是要修改舊的page資料,需要先erase該page的sector後才能再寫入。
程式碼:

測試結果:

  • 測試二、使用FatFs檔案系統架構在device driver上。
  1. http://elm-chan.org/fsw/ff/00index_e.html下載FatFs modules。
  2. 複製檔案到專案子資料夾FatFs下


  3. 實做FatFs MAI(disk_status, disk_initialize, disk_read, disk_write)
  4. 依據文件說明,建立檔案flash_diskio.c實做上述五個function。
  5. 更改ffconf.h設定檔

程式碼:請參閱文末詳細程式碼。
測試結果

三、成果影片:



四、程式碼:

  • 修改過的ffconf.h

/*---------------------------------------------------------------------------/
/ Configurations of FatFs Module
/---------------------------------------------------------------------------*/
#define FFCONF_DEF 80286 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define FF_FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: Basic functions are fully enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define FF_USE_FIND 1 // 0 --> 1
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define FF_USE_MKFS 1 // 0 --> 1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define FF_USE_FASTSEEK 1 // 0 --> 1
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define FF_USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define FF_USE_CHMOD 0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
#define FF_USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
#define FF_USE_STRFUNC 1 // 0 --> 1
#define FF_PRINT_LLI 1
#define FF_PRINT_FLOAT 1
#define FF_STRF_ENCODE 3
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
/ f_printf().
/
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion.
/
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
/ makes f_printf() support floating point argument. These features want C99 or later.
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
/ to be read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define FF_CODE_PAGE 950
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/
#define FF_USE_LFN 3 // 0->3
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
#define FF_LFN_UNICODE 0
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
/ the file names to read. The maximum possible length of the read file name depends
/ on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_FS_RPATH 0
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define FF_VOLUMES 1
/* Number of volumes (logical drives) to be used. (1-10) */
#define FF_STR_VOLUME_ID 0
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table is needed as:
/
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/
#define FF_MULTI_PARTITION 0
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ function will be available. */
#define FF_MIN_SS 512
#define FF_MAX_SS 4096 // 512 --> 4096
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk, but a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */
#define FF_LBA64 0
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
#define FF_MIN_GPT 0x10000000
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */
#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
#define FF_FS_EXFAT 0
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define FF_FS_NORTC 0
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2022
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
#define FF_FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at the first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
#define FF_FS_LOCK 0
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this featuer.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
/ function, must be added to the project. Samples are available in ffsystem.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
*/
/*--- End of configuration options ---*/

  • CMakeLists.txt
# 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.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}")
endif()
project(spi_flash_fatfs 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(spi_flash_fatfs spi_flash_fatfs.c W25Q.c)
pico_set_program_name(spi_flash_fatfs "spi_flash_fatfs")
pico_set_program_version(spi_flash_fatfs "0.1")
pico_enable_stdio_uart(spi_flash_fatfs 1)
pico_enable_stdio_usb(spi_flash_fatfs 0)
# Add the standard library to the build
target_link_libraries(spi_flash_fatfs
pico_stdlib
hardware_flash
FatFs)
# Add the standard include files to the build
target_include_directories(spi_flash_fatfs PRIVATE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/FatFs
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)
# Add any user requested libraries
target_link_libraries(spi_flash_fatfs
hardware_spi
hardware_rtc
)
# Tell CMake where to find other source code
add_subdirectory(FatFs build)
pico_add_extra_outputs(spi_flash_fatfs)
  • W25Q.c
#include "stdio.h"
#include "stdlib.h"
#include "W25Q.h"
#include "FatFs/ff.h"
#include "FatFs/ffconf.h"
uint8_t rxbuf[10];
uint8_t txbuf[10];
w25q_data_t w25q_data;
/*=================*/
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_cs_low() {
gpio_put(w25q_data.cs_pin,0);
}
void w25q_spi_cs_high(){
gpio_put(w25q_data.cs_pin,1);
}
void w25q_send_cmd_read(uint8_t cmd, uint32_t address, uint8_t *buf, uint32_t len, bool is_fast) {
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();
spi_write_blocking(w25q_data.spi, &cmd, 1);
spi_write_blocking(w25q_data.spi, addr, addr_len);
spi_read_blocking(w25q_data.spi, 0x00, buf, len);
w25q_spi_cs_high();
}
void w25q_send_cmd_write(uint8_t cmd, uint32_t address, uint8_t *buf, uint32_t len) {
uint8_t addr[3];
addr[0] = (address & 0x00ff0000) >> 16;
addr[1] = (address & 0x0000ff00) >> 8;
addr[2] = (address & 0x000000ff);
w25q_write_enable();
w25q_spi_cs_low();
spi_write_blocking(w25q_data.spi, &cmd, 1);
spi_write_blocking(w25q_data.spi, addr, 3);
spi_write_blocking(w25q_data.spi, buf, len);
w25q_spi_cs_high();
}
void w25q_send_cmd_addr(uint8_t cmd, uint32_t address) {
uint8_t addr[3];
addr[0] = (address & 0x00ff0000) >> 16;
addr[1] = (address & 0x0000ff00) >> 8;
addr[2] = (address & 0x000000ff);
w25q_spi_cs_low();
spi_write_blocking(w25q_data.spi, &cmd, 1);
spi_write_blocking(w25q_data.spi, addr, 3);
w25q_spi_cs_high();
}
void w25q_send_cmd(uint8_t cmd, uint8_t *buf, uint32_t len) {
w25q_spi_cs_low();
spi_write_blocking(w25q_data.spi, &cmd, 1);
spi_read_blocking(w25q_data.spi, 0x00, buf, len);
w25q_spi_cs_high();
}
void w25q_send_simple_cmd(uint8_t cmd) {
w25q_spi_cs_low();
spi_write_blocking(w25q_data.spi, &cmd, 1);
w25q_spi_cs_high();
}
void w25q_write_enable() {
w25q_send_simple_cmd(i_write_enable);
sleep_ms(1);
}
void w25q_write_disable() {
w25q_send_simple_cmd(i_write_disable);
sleep_ms(1);
}
/*==================*/
bool w25q_spi_init(spi_inst_t *spi, uint cs_pin) {
w25q_data.spi = spi;
w25q_data.cs_pin = cs_pin;
gpio_set_dir(PIN_CS, GPIO_OUT);
spi_init(SPI_PORT, 5000*1000);
gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);
gpio_set_function(PIN_CS, GPIO_FUNC_SIO);
gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
w25q_get_JEDEC_ID();
w25q_data.lock = 1;
sleep_ms(100);
switch (w25q_data.jedec_id & 0x000000FF)
{
case 0x20: // w25q512
w25q_data.blockCount = 1024;
break;
case 0x19: // w25q256
w25q_data.blockCount = 512;
break;
case 0x18: // w25q128
w25q_data.blockCount = 256;
break;
case 0x17: // w25q64
w25q_data.blockCount = 128;
break;
case 0x16: // w25q32
w25q_data.blockCount = 64;
break;
case 0x15: // w25q16
w25q_data.blockCount = 32;
break;
case 0x14: // w25q80
w25q_data.blockCount = 16;
break;
case 0x13: // w25q40
w25q_data.blockCount = 8;
case 0x12: // w25q20
w25q_data.blockCount = 4;
break;
case 0x11: // w25q10
w25q_data.blockCount = 2;
break;
default:
w25q_data.lock = 0;
return false;
}
w25q_data.pageSize = 256;
w25q_data.sectorSize = 0x1000;
w25q_data.sectorCount = w25q_data.blockCount * 16;
w25q_data.pageCount = (w25q_data.sectorCount * w25q_data.sectorSize) / w25q_data.pageSize;
w25q_data.blockSize = w25q_data.sectorSize * 16;
w25q_data.capacityKB = (w25q_data.sectorCount * w25q_data.sectorSize) / 1024;
w25q_get_uid();
w25q_read_status_register_1();
w25q_read_status_register_2();
w25q_read_status_register_3();
w25q_data.lock = 0;
return true;
}
void w25q_read_status_register_1(){
w25q_send_cmd(i_read_status_r1, &w25q_data.statusRegister1, 1);
}
void w25q_read_status_register_2(){
w25q_send_cmd(i_read_status_r2, &w25q_data.statusRegister2, 1);
}
void w25q_read_status_register_3(){
w25q_send_cmd(i_read_status_r3, &w25q_data.statusRegister3, 1);
}
void w25q_write_status_register_1(){
w25q_send_cmd(i_write_status_r1, &w25q_data.statusRegister1, 1);
}
void w25q_write_status_register_2(){
w25q_send_cmd(i_write_status_r2, &w25q_data.statusRegister2, 1);
}
void w25q_write_status_register_3(){
w25q_send_cmd(i_write_status_r3, &w25q_data.statusRegister3, 1);
}
void w25q_wait_for_write_end(void)
{
sleep_ms(1);
w25q_spi_cs_low();
spi_write_blocking(w25q_data.spi, &i_read_status_r1,1);
do
{
spi_read_blocking(w25q_data.spi, 0x00, &w25q_data.statusRegister1,1);
sleep_ms(1);
} while ((w25q_data.statusRegister1 & 0x01) == 0x01);
w25q_spi_cs_high();
}
void w25q_erase_chip() {
while (w25q_data.lock) sleep_ms(1);
w25q_data.lock=1;
w25q_write_enable();
w25q_send_simple_cmd(i_erase_chip);
w25q_wait_for_write_end();
sleep_ms(10);
w25q_data.lock=0;
}
void w25q_page_program(uint32_t page_addr, uint16_t offset, uint8_t *buf, uint32_t len) {
while (w25q_data.lock) sleep_ms(1);
w25q_data.lock=1;
if (offset + len > w25q_data.pageSize) {
len = w25q_data.pageSize - offset;
}
page_addr = (page_addr * w25q_data.pageSize) + offset;
w25q_wait_for_write_end();
w25q_write_enable();
w25q_send_cmd_write(i_page_program, page_addr, buf, len);
w25q_wait_for_write_end();
sleep_ms(1);
w25q_data.lock=0;
}
/*===========================*/
uint32_t w25_page_to_sector_address(uint32_t pageAddress)
{
return ((pageAddress * w25q_data.pageSize) / w25q_data.sectorSize);
}
uint32_t w25q_page_to_block_address(uint32_t pageAddress)
{
return ((pageAddress * w25q_data.pageSize) / w25q_data.blockSize);
}
uint32_t w25q_data_sector_to_block_address(uint32_t sectorAddress)
{
return ((sectorAddress * w25q_data.sectorSize) / w25q_data.blockSize);
}
uint32_t w25q_sector_to_page_address(uint32_t sectorAddress)
{
return (sectorAddress * w25q_data.sectorSize) / w25q_data.pageSize;
}
uint32_t w25q_block_to_page_address(uint32_t blockAddress)
{
return (blockAddress * w25q_data.blockSize) / w25q_data.pageSize;
}
/*============================*/
void w25q_write_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf, uint32_t len) {
if (offset >= w25q_data.sectorSize) return;
if (offset + len > w25q_data.sectorSize)
len = w25q_data.sectorSize - offset;
uint32_t startPage;
int32_t bytesToWrite;
uint32_t localOffset;
startPage = w25q_sector_to_page_address(sect_addr) + (offset / w25q_data.pageSize);
localOffset = offset % w25q_data.pageSize;
bytesToWrite = len;
do
{
w25q_page_program(startPage, localOffset, buf, bytesToWrite);
startPage++;
bytesToWrite -= w25q_data.pageSize - localOffset;
buf += w25q_data.pageSize - localOffset;
localOffset = 0;
} while (bytesToWrite > 0);
}
void w25q_write_block_64k(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len) {
if ((len > w25q_data.blockSize) || (len == 0))
len = w25q_data.blockSize;
if (offset >= w25q_data.blockSize)
return;
uint32_t startPage;
int32_t bytesToWrite;
uint32_t localOffset;
if ((offset + len) > w25q_data.blockSize)
bytesToWrite = w25q_data.blockSize - offset;
else
bytesToWrite = len;
startPage = w25q_block_to_page_address(blk_addr) + (offset / w25q_data.pageSize);
localOffset = offset % w25q_data.pageSize;
do
{
w25q_page_program(startPage, localOffset, buf, len);
startPage++;
bytesToWrite -= w25q_data.pageSize - localOffset;
buf += w25q_data.pageSize - localOffset;
localOffset = 0;
} while (bytesToWrite > 0);
}
void w25q_read_bytes(uint32_t address, uint8_t *buf, uint32_t len) {
while (w25q_data.lock == 1) sleep_ms(1);
w25q_data.lock = 1;
w25q_send_cmd_read(i_fast_read_data, address, buf, len, true);
sleep_ms(1);
w25q_data.lock = 0;
}
void w25q_read_page(uint32_t page_addr, uint32_t offset, uint8_t *buf, uint32_t len) {
while (w25q_data.lock == 1) sleep_ms(1);
w25q_data.lock = 1;
if (offset >= w25q_data.pageSize) return;
if ((offset + len) >= w25q_data.pageSize)
len = w25q_data.pageSize - offset;
page_addr = page_addr * w25q_data.pageSize + offset;
w25q_send_cmd_read(i_fast_read_data, page_addr, buf, len, true);
sleep_ms(1);
w25q_data.lock = 0;
}
void w25q_read_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf, uint32_t len) {
if (offset >= w25q_data.sectorSize) return;
if (offset + len > w25q_data.sectorSize)
len = w25q_data.sectorSize - offset;
uint32_t startPage;
int32_t bytesToRead;
uint32_t localOffset;
bytesToRead = len;
startPage = w25q_sector_to_page_address(sect_addr) + (offset / w25q_data.pageSize);
localOffset = offset % w25q_data.pageSize;
do
{
w25q_read_page(startPage, localOffset, buf, bytesToRead);
startPage++;
bytesToRead -= w25q_data.pageSize - localOffset;
buf += w25q_data.pageSize - localOffset;
localOffset = 0;
} while (bytesToRead > 0);
}
void w25q_read_block(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len) {
if (offset+len > w25q_data.blockSize)
len = w25q_data.blockSize-offset;
uint32_t startPage;
int32_t bytesToRead;
uint32_t localOffset;
bytesToRead = len;
startPage = w25q_block_to_page_address(blk_addr) + (offset / w25q_data.pageSize);
localOffset = offset % w25q_data.pageSize;
do
{
w25q_read_page(startPage, localOffset, buf, bytesToRead);
startPage++;
bytesToRead -= w25q_data.pageSize - localOffset;
buf += w25q_data.pageSize - localOffset;
localOffset = 0;
} while (bytesToRead > 0);
}
void w25q_sector_erase(uint32_t sect_addr) {
while(w25q_data.lock) sleep_ms(1);
w25q_data.lock=1;
sect_addr = sect_addr * w25q_data.sectorSize;
w25q_wait_for_write_end();
w25q_write_enable();
w25q_send_cmd_addr(i_sector_erase, sect_addr);
w25q_wait_for_write_end();
sleep_ms(1);
w25q_data.lock=0;
}
void w25q_block_erase_32k(uint32_t blk_addr) {
while(w25q_data.lock) sleep_ms(1);
w25q_data.lock=1;
blk_addr = blk_addr * w25q_data.sectorSize * 8;
w25q_wait_for_write_end();
w25q_write_enable();
w25q_send_cmd_addr(i_block_erase_32k, blk_addr);
w25q_wait_for_write_end();
sleep_ms(1);
w25q_data.lock=0;
}
void w25q_block_erase_64k(uint32_t blk_addr) {
while(w25q_data.lock) sleep_ms(1);
w25q_data.lock=1;
blk_addr = blk_addr * w25q_data.sectorSize * 16;
w25q_wait_for_write_end();
w25q_write_enable();
w25q_send_cmd_addr(i_block_erase_64k, blk_addr);
w25q_wait_for_write_end();
sleep_ms(1);
w25q_data.lock=0;
}
void w25q_get_manufacter_device_id(uint8_t *mid){
assert(w25q_data.spi);
w25q_send_cmd_read(i_device_id, 0x000000, mid, 2, false);
}
void w25q_get_JEDEC_ID() {
uint8_t temp[3];
w25q_send_cmd(i_JEDEC_ID, temp, 3);
w25q_data.jedec_id = ((uint32_t)temp[0] << 16) | ((uint32_t)temp[1] << 8) | (uint32_t)temp[2];
}
void w25q_get_uid() {
assert(w25q_data.spi);
txbuf[0]= 0x4b;
txbuf[1] = 0x00; txbuf[2] = 0x00; txbuf[3] = 0x00;txbuf[4]=0x00;
w25q_spi_cs_low();
spi_write_blocking(w25q_data.spi, txbuf, 5);
spi_read_blocking(w25q_data.spi, 0x00, w25q_data.uuid, 8);
w25q_spi_cs_high();
}
  • W25Q.h
#ifndef W25Q_H
#define W25Q_H
#include "stdio.h"
#include "stdlib.h"
#include "pico/stdlib.h"
#include "hardware/spi.h"
#define SPI_PORT spi0
#define PIN_MISO 16
#define PIN_CS 17
#define PIN_SCK 18
#define PIN_MOSI 19
typedef struct {
spi_inst_t *spi;
uint cs_pin;
uint8_t uuid[8];
uint32_t jedec_id;
uint32_t blockCount;
uint32_t pageCount;
uint32_t sectorCount;
uint16_t pageSize;
uint32_t sectorSize;
uint32_t blockSize;
uint8_t statusRegister1;
uint8_t statusRegister2;
uint8_t statusRegister3;
uint32_t capacityKB;
uint8_t lock;
} w25q_data_t;
bool w25q_spi_init(spi_inst_t *spi, uint cs_pin);
void w25q_get_manufacter_device_id(uint8_t *mid);
void w25q_get_JEDEC_ID();
void w25q_erase_chip();
void w25q_page_program(uint32_t page_addr, uint16_t offset, uint8_t *buf, uint32_t len);
void w25q_write_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf, uint32_t len);
void w25q_write_block_64k(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len);
void w25q_read_bytes(uint32_t address, uint8_t *buf, uint32_t len);
void w25q_read_page(uint32_t page_addr, uint32_t offset, uint8_t *buf, uint32_t len);
void w25q_read_sector(uint32_t sect_addr, uint32_t offset, uint8_t *buf, uint32_t len);
void w25q_read_block(uint32_t blk_addr, uint32_t offset, uint8_t *buf, uint32_t len);
//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();
void w25q_read_status_register_2();
void w25q_read_status_register_3();
void w25q_write_status_register_1();
void w25q_write_status_register_2();
void w25q_write_status_register_3();
void w25q_sector_erase(uint32_t sect_addr);
void w25q_block_erase_32k(uint32_t blk_addr);
void w25q_block_erase_64k(uint32_t blk_addr);
void w25q_get_uid();
void w25q_write_enable();
void w25q_write_diable();
#endif
  • spi_flash_fatfs.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/spi.h"
#include "W25Q.h"
#include "ff.h"
#include "diskio.h"
#include "hardware/flash.h"
int main()
{
stdio_init_all();
/*
uint8_t testbuf[256];
uint32_t offset=0x100000; // 1M
for (uint32_t i=0; i < 256; i++) {
uint8_t c = rand() % 127;
if (c < 33) c='A';
testbuf[i] = c;
}
flash_range_erase(offset, 4096); // one sector
printf("write into flash page:");
for (uint32_t i=0; i < 256; i++) {
if (i % 100 == 0) printf("\n");
printf("%c", testbuf[i]);
}
printf("\n\n");
flash_range_program(offset+256, testbuf, 256); // second page
printf("read out flash page:");
for (uint32_t i=0; i < 256; i++) {
if (i %100 == 0) printf("\n");
printf("%c", *(uint8_t*)(XIP_BASE+offset+256+i)); //XIP_BASE + (byte offset) read out
}
printf("\nread finished\n\n");
//===========//
uint8_t testbuf[4096*2];
w25q_spi_init(SPI_PORT, PIN_CS);
for (uint32_t i=0; i < 4096*2; i++) {
uint8_t c = rand() % 127;
if (c < 33) c+=33;
testbuf[i] = c;
}
w25q_sector_erase(0);
w25q_sector_erase(1);
w25q_write_sector(0, 0, testbuf, 4096);
w25q_write_sector(1, 0, testbuf+4096, 4096);
printf("write data:sector 0, page 0");
for (int i =0; i< 256; i++) {
if (i % 100 == 0) printf("\n");
printf("%c",testbuf[i]);
}
printf("\n\nwrite data:sector 1, page 0");
for (int i =4096; i< 4096+256; i++) {
if ((i-4096) % 100 == 0) printf("\n");
printf("%c",testbuf[i]);
}
printf("\n\nclear buffer\n");
for (uint32_t i=0; i < 4096*2; i++) {
testbuf[i] = 0;
}
w25q_read_page(0, 0, testbuf, 256);
printf("\n\nread data:sector 0, page 0");
for (int i =0; i< 256; i++) {
if (i % 100 == 0) printf("\n");
printf("%c",testbuf[i]);
}
w25q_read_page(16, 0, testbuf, 256);
printf("\n\nread data:sector 1, page 0");
for (int i =0; i< 256; i++) {
if (i % 100 == 0) printf("\n");
printf("%c",testbuf[i]);
}
printf("\ntest finished\n");
*/
FRESULT res;
FATFS fs;
FIL fil;
UINT br;
uint8_t work[4096*2];
uint8_t readbuff[4096*2];
res = f_mkfs("0:", 0, work, 4096);
if(res != FR_OK) {
printf("mkfs error\n");
return 1;
}
printf("mkfs successfully(only run once)\n");
for (uint32_t i=0; i < 4096*2; i++) {
uint8_t c = rand() % 127;
if (c < 31) c+=31;
work[i] = c;
}
printf("write buffer:200-300\n");
for (uint32_t i = 200; i <= 300; i++) {
printf("%c",work[i]);
}
printf("\n");
res = f_mount(&fs, "0:", 1);
if (res != FR_OK) {
printf("mount error\n");
return 1;
}
res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS|FA_WRITE);
if (res != FR_OK) {
printf("open error\n");
return 1;
}
f_write(&fil, work, 4096*2, &br);
f_close(&fil);
res = f_open(&fil, "test.txt", FA_READ);
if (res != FR_OK) {
printf("read open error\n");
return 1;
}
f_read(&fil, readbuff, 4096*2, &br);
printf("read buff:200-300\n");
for (uint32_t i = 200; i <= 300; i++) {
printf("%c",readbuff[i]);
}
printf("\n\n\n");
f_close(&fil);
return 0;
}
  • FatFs/CMakeLists.txt
add_library(FatFs INTERFACE)
target_sources(FatFs INTERFACE
${CMAKE_CURRENT_LIST_DIR}/flash_diskio.c
${CMAKE_CURRENT_LIST_DIR}/ff.c
${CMAKE_CURRENT_LIST_DIR}/ffsystem.c
${CMAKE_CURRENT_LIST_DIR}/ffunicode.c
)
target_include_directories(FatFs INTERFACE
${CMAKE_CURRENT_LIST_DIR}/FatFs
)
target_link_libraries(FatFs INTERFACE
hardware_spi
hardware_dma
pico_stdlib
)
  • FatFs/flash_diskio.c
#include "stdio.h"
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
#include "W25Q.h"
#include "hardware/rtc.h"
#include "pico/util/datetime.h"
DSTATUS Stat = STA_NOINIT;
extern w25q_data_t w25q_data;
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
return Stat;
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
Stat=STA_NOINIT;
if (w25q_spi_init(SPI_PORT, PIN_CS))
Stat = RES_OK;
return Stat;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
DRESULT res;
/* pdrv should be 0 */
if (pdrv || !count) return RES_PARERR;
/* no disk */
if (Stat & STA_NOINIT) return RES_NOTRDY;
w25q_read_sector(sector, 0, buff, count*w25q_data.sectorSize);
return RES_OK;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
BYTE *tbuf=(BYTE*)buff;
while(count > 1)
{
w25q_sector_erase(sector);
w25q_write_sector(sector, 0, tbuf, w25q_data.sectorSize);
count--;
tbuf += w25q_data.sectorSize;
sector++;
}
if (count == 1)
{
w25q_sector_erase(sector);
w25q_write_sector(sector, 0, tbuf, w25q_data.sectorSize);
count--;
}
return count ? RES_ERROR : RES_OK;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res = RES_ERROR;
switch(cmd) {
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = (WORD)w25q_data.sectorSize; // in f_mkfs() [WORD ss]
res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(DWORD*)buff = w25q_data.blockSize;
res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = w25q_data.sectorCount;
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
return res;
}
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;
}

2022年11月15日 星期二

Raspberry Pi Pico PIO(Programmable IO) Episode 5: IR-Receiver(VS1838B)

 本篇文章介紹使用Raspberry Pi Pico PIO功能,製作一個IR 遙控器接收器,並用來控制伺服馬達。


一、使用材料:

  1. Raspberry Pi Pico
  2. VS1838B IR 接收器
  3. SG90 MicroServo
二、紅外線協定使用Samsung IR protocol:
Start bits: 4.5ms burst pulse + 4.5ms space
32 bits data(Address + command): logical "0": 590us burst pulse + 590us space; 
                                                        logical "1": 590us burst pulse + 1690us space
stop bits: 590us burst pulse + 590us space

三、VS1838B Datasheet:

burst pulse: output level LOW
space : output leve HIGH

四、Raspberry Pi Pico PIO說明:
1.設定StateMachine clock cycle週期為10us。

2. StateMachine程式碼:
  • 確定start bits: 4.5ms burst pulse + 4.5ms space
  • 每個bit 取樣點:650us > 560us 
  • IR發送器發送data bits為LSB first,所以設定shift right,且autopush=false

五、成果影片:


六、程式碼
  • PIO
; IR protocol:
; start bits: 4.5ms burst pulse + 4.5 ms space
; 32 bits data: logical "0": 590us burst pulse+590us space, logical "1": 590us burst pulse+1690us space
; stop bits: 590us burst pulse + 590us space
; StateMachine clock cycle period=10us 
.program samsung_ir_receiver
.wrap_target
start:
set x, 31                   ; 32*14=4480(us)   
start_burst:
jmp pin start
jmp x--, start_burst [12]

wait 1 pin 0                ; 10us
set x, 31                   ; 32*14=4480us
start_space:
in pins, 1
mov y, isr
jmp !y  start
jmp pin start_space [10]

mov isr, NULL

set x, 31
bit_loop:
wait 1 pin 0
nop [31]
nop [31] 
in pins, 1 
wait 0 pin 0
jmp x--, bit_loop
push
irq 0

wait 1 pin 0
wait_stop:
jmp pin, wait_stop

.wrap
  • ir-receiver.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "IR-receiver.h"
#include "hardware/clocks.h"
#include "hardware/pwm.h"

#define IR_PIN  15
#define PWM_PIN 16

int angle=0;
int slice, pwm_ch;

void setServoAngle(int ang) {
    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 ir_irq_handle() {
    if (pio_interrupt_get(pio0, 0)) {
        pio_interrupt_clear(pio0, 0);
        uint32_t code = pio_sm_get(pio0, 0);
        
        switch (code) {
            case 0xf8070707:    
                angle += 2;
                if (angle > 90) angle = 90;
                break;
            case 0xf40b0707:
                angle -= 2;
                if (angle < -90) angle = -90;
                break;
            case 0xed120707:
                angle = 90;
                break;
            case 0xef100707:
                angle = 0;
                break;
        }
        setServoAngle(angle);
    }

}

void ir_receiver_pio_init(PIO pio, uint sm, uint pin, uint32_t freq) {
    uint offset = pio_add_program(pio, &samsung_ir_receiver_program);
    pio_sm_config c = samsung_ir_receiver_program_get_default_config(offset);
    pio_gpio_init(pio, pin);
    pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
    float div = clock_get_hz(clk_sys)/freq;
    sm_config_set_clkdiv(&c, div);
    sm_config_set_in_pins(&c, pin);
    sm_config_set_in_shift(&c, true, false,32);
    sm_config_set_jmp_pin(&c, pin);
    // set interrupt 
    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);
    irq_add_shared_handler(PIO0_IRQ_0, ir_irq_handle, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
    irq_set_enabled(PIO0_IRQ_0, true);
    
    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);

}

int main()
{
    stdio_init_all();
    gpio_init(IR_PIN);
    gpio_set_dir(IR_PIN, false);
    gpio_disable_pulls(IR_PIN);
    ir_receiver_pio_init(pio0, 0, IR_PIN, 100000);  // set StateMachine clock sycle period=10us

    // set Servo pwm
    gpio_set_function(PWM_PIN, GPIO_FUNC_PWM);
    slice = pwm_gpio_to_slice_num(PWM_PIN);
    pwm_ch = pwm_gpio_to_channel(PWM_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);    
    
    setServoAngle(0);
    
    while(true) {
        tight_loop_contents();
    }
    return 0;
}