prettyprint

2022年2月26日 星期六

ESP-IDF: ESP32 I2S介面語音錄音機(ESP32 I2S Audio Recorder using ESP-ADF)

 本實驗製作一個I2S介面錄音設備,使用元件如下:

  1. ESP32-WROOM-32(30pins) development board
  2. INMP441 microphone(I2S)
  3. MAX98357A 
  4. 3W4Ω Speaker
  5. 2.4" TFT (8-bit parallel port with touch)
  6. SPI SD Card
實驗內容:
  1. 使用touch screen控制錄放音樂存放在SD上。
  2. 錄音時動畫顯示
  3. 可調整錄音或撥放音量
  4. 可捲動選擇撥放的錄製清單
軟體開發環境: VSCode 使用ESP-IDF & ESP-ADF extension; 不使用ESP32 audio development board。
本實驗幾乎用完所有GPIO pins,在不使用ESP32 audio development board下,利用ESP-ADF環境開發audio application,實驗內容不另外define custom board,在menuconfig Audio board選用ESP32-Lyart-Mini,再自行重新另訂GPIO pins。



一、GPIO pins:

I2S:


SPI SD Card
TFT Display:

二、Record audio pipeline



INMP441使用left channel,接線如下:
相對應的參數設定,
I2S 參數:由I2S_STREAM_CFG_DEFAULT()修改:
i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
i2s_cfg.task_core = APP_CPU_NUM;
i2s_cfg.type = AUDIO_STREAM_READER;
i2s_cfg.i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
i2s_cfg.use_alc = true;
i2s_cfg.volume=-63;
i2s_stream_reader = i2s_stream_init(&i2s_cfg);

I2S pins:
i2s_pin_config_t rp = {
        .bck_io_num = 5,
        .ws_io_num = 25,
        .data_out_num = -1,
        .data_in_num = 39
    };
    i2s_set_pin(I2S_NUM_0, &rp);


三、Play audio pipeline

I2S參數使用default即可,因為audio_element_setinfo 函數填入

i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_cfg.use_alc = true;
    i2s_cfg.volume=0;
i2s_stream_writer = i2s_stream_init(&i2s_cfg);

audio_element_getinfo(audio_decoder, &music_info);
audio_element_setinfo(i2s_stream_writer, &music_info);

I2S pins:
i2s_pin_config_t wp = {
        .bck_io_num = 22,
        .ws_io_num = 25,
        .data_out_num = 26,
        .data_in_num = -1
    };
 i2s_set_pin(I2S_NUM_0, &wp);

SPI SD Card:
設定VFS base_path:
#define SDSPI_MOUNT "/sdcard"
SPI pin 設定
sdmmc_host_t sdspi_host = SDSPI_HOST_DEFAULT();
sdspi_device_config_t sdspi_device = SDSPI_DEVICE_CONFIG_DEFAULT();
spi_bus_config_t bus_config = {
    .miso_io_num = GPIO_NUM_19,
    .mosi_io_num = GPIO_NUM_23,
    .sclk_io_num = GPIO_NUM_18
};
sdspi_device.host_id = VSPI_HOST;
spi_dma_chan_t dma_chan = SPI_DMA_CH1;
esp_vfs_fat_mount_config_t mount_config = {
     .format_if_mount_failed=true,
     .max_files = 5,
     .allocation_unit_size=16*512   //8192
 };
 ret = spi_bus_initialize(sdspi_device.host_id, &bus_config, dma_chan);
 if (ret != ESP_OK) {
           return;
 }
 ret = esp_vfs_fat_sdspi_mount(SDSPI_MOUNT, &sdspi_host, &sdspi_device, &mount_config, &sdspi_card);
 if(ret != ESP_OK) {
      return;
}

相關SD FAT filesystem操作可參閱千一篇文章(

EPS32 ESP-IDF開發環境儲存設備與檔案系統實驗(一) -- SDMMC 與SDSPI )



四、8-bit parallel TFT(with touch)  
ILI3941 ESP32 Driver使用https://github.com/nopnop2002/esp-idf-parallel-tft, 相關的檔案為ili3941, lcd_com, lcd_lib, fontx,與font檔案,TFT程式碼放在components/tft_library下,font資料夾下放font檔案與相關的UI button JPEG檔案。font資料夾會先預載到flash 的spiffs partition下。Kconfig.projbuild是相關tft menuconfig設定,放在`main資料夾下。相關位置如下圖
Flash partition tables:

五、成果展示:


六、main程式碼:

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "audio_element.h"
#include "audio_pipeline.h"
#include "audio_event_iface.h"
#include "audio_common.h"
//#include "board.h"
#include "esp_peripherals.h"
#include "periph_sdcard.h"
#include "fatfs_stream.h"
#include "i2s_stream.h"
#include "wav_encoder.h"
#include "wav_decoder.h"
#include "lcd_com.h"
#include "lcd_lib.h"
#include "fontx.h"
#include "decode_jpeg.h"
#include "string.h"
#include "driver/i2s.h"

#if CONFIG_INTERFACE_I2S
#define INTERFACE INTERFACE_I2S
#elif CONFIG_INTERFACE_GPIO
#define INTERFACE INTERFACE_GPIO
#elif CONFIG_INTERFACE_REG
#define INTERFACE INTERFACE_REG
#endif

#include "ili9341.h"
#define DRIVER "ILI9340"
#define INIT_FUNCTION(a, b, c, d, e) ili9341_lcdInit(a, b, c, d, e)

#define RECORD_STATE	(1)
#define PLAY_STATE		(2)
#define PAUSE_STATE		(3)
#define STOP_STATE		(0)
#define VOLUME_MIN		(-50)
#define VOLUME_MAX		(50)
#define MAX_INDEX 		(1024)
int  volume=0;
static char **fltable=NULL;
static TFT_t dev;

static int16_t tindex=0, bindex=0, totalindex=0, lastnumber=0;
static int16_t selectindex=-1;
static char selectFile[11];

uint8_t gStatus=STOP_STATE; 
static TaskHandle_t hPlayWave, hRecordingTask;
static TaskHandle_t hPlayTask;
audio_element_handle_t fatfs_stream_reader, i2s_stream_writer, audio_decoder;
audio_element_handle_t fatfs_stream_writer, i2s_stream_reader, audio_encoder;

FontxFile fx16G[2];
FontxFile fx24G[2];
FontxFile fx32G[2];
FontxFile fx16M[2];
FontxFile fx24M[2];
FontxFile fx32M[2];


#define RECORD_TIME_SECONDS (20)

//static const char *TAG = "Audio Recoder";

#include "esp_vfs_fat.h"
#include "esp_vfs.h"
#include "driver/sdspi_host.h"
#include "esp_spiffs.h"

static sdmmc_card_t *sdspi_card;

#define SDSPI_MOUNT "/sdcard"

static audio_pipeline_handle_t rec_pipeline;
static audio_pipeline_handle_t play_pipeline;


void drawJPEG(TFT_t * dev, char * file, int px, int py, int width, int height) {
	lcdSetFontDirection(dev, 0);
	int _width = width;
	if (width > 240) _width = 240;
	int _height = height;
	if (height > 320) _height = 320;

	pixel_jpeg **pixels;
	uint16_t imageWidth;
	uint16_t imageHeight;
	esp_err_t err = decode_jpeg(&pixels, file, _width, _height, &imageWidth, &imageHeight);
	if (err == ESP_OK) {
		uint16_t jpegWidth = width;
		uint16_t offsetX = px;
		if (width > imageWidth) {
			jpegWidth = imageWidth;
		}
		uint16_t jpegHeight = height;
		uint16_t offsetY = py;
		if (height > imageHeight) {
			jpegHeight = imageHeight;
		}
		uint16_t *colors = (uint16_t*)malloc(sizeof(uint16_t) * jpegWidth);


		for(int y = 0; y < jpegHeight; y++){
			for(int x = 0;x < jpegWidth; x++){
				colors[x] = pixels[y][x];
				}
			lcdDrawMultiPixels(dev, offsetX, y+offsetY, jpegWidth, colors);
		}

		free(colors);
		release_image(&pixels, _width, _height);

	}
}


int button[7][2] = {{15,275},{60,275},{105,275},{150,275},{195,275},{202,10},{202,220}};

bool getTouchPos(TFT_t * dev, int *posx, int *posy) {
	
	float _xd = dev->_max_xp - dev->_min_xp;
	float _yd = dev->_max_yp - dev->_min_yp;
	
	float _xs = dev->_max_xc - dev->_min_xc;
	float _ys = dev->_max_yc - dev->_min_yc;
	//ESP_LOGD(TAG, "_xs=%f _ys=%f", _xs, _ys);

	int _xpos = 0;
	int _ypos = 0;

	int _xp;
	int _yp;
	if (touch_getxy(dev, &_xp, &_yp)) { 
		if (dev->_max_xp > dev->_min_xp) {
			if (_xp < dev->_min_xp && _xp > dev->_max_xp) return false;
		} else {
			if (_xp < dev->_max_xp && _xp > dev->_min_xp) return false;
		}
		if (dev->_max_yp > dev->_min_yp) {
			if (_yp < dev->_min_yp && _yp > dev->_max_yp) return false;
		} else {
			if (_yp < dev->_max_yp && _yp > dev->_min_yp) return false;
		}
		// Convert from position to coordinate
		_xpos = ( (float)(_xp - dev->_min_xp) / _xd * _xs ) + dev->_min_xc;
		_ypos = ( (float)(_yp - dev->_min_yp) / _yd * _ys ) + dev->_min_yc;
		*posx = _xpos;
		*posy = _ypos;
		return true;
	} else {
		return false;
	}
}

void playWave(void* param) {
	char *file[5] = {"/spiffs/w0.jpg", "/spiffs/w1.jpg", "/spiffs/w2.jpg", "/spiffs/w3.jpg", "/spiffs/w4.jpg"};
	int i = 0;
	lcdDrawFillRect(&dev,6,6,199,259,BLACK);
	while (1) {
		drawJPEG(&dev, file[i], 15, 80, 180, 100);
		vTaskDelay(500/portTICK_PERIOD_MS);
		i = (i+1) % 5;
	}
}

void drawBackground(TFT_t *dev) {
	lcdFillScreen(dev, BLACK);
	lcdDrawRect(dev, 5,5,200, 260,  CYAN);
	lcdDrawRect(dev, 201,5,239, 260,  CYAN);
	drawJPEG(dev, "/spiffs/mic.jpg", 15, 275, 36,36);
	drawJPEG(dev, "/spiffs/play.jpg", 60, 275, 36,36);
	drawJPEG(dev, "/spiffs/stop.jpg", 105, 275, 36,36);
	drawJPEG(dev, "/spiffs/volume_up.jpg", 150, 275, 36,36);
	drawJPEG(dev, "/spiffs/volume_down.jpg", 195, 275, 36,36);
	drawJPEG(dev, "/spiffs/up.jpg", 202, 10, 36,36);
	drawJPEG(dev, "/spiffs/down.jpg", 202, 220, 36,36);

	fltable = malloc(MAX_INDEX*sizeof(char*));
	char tf[12];
	lastnumber = 0;
	totalindex=-1;
	DIR* dir = opendir("/sdcard/");
	assert(dir != NULL);
	while (true) {
		struct dirent*pe = readdir(dir);
		if (!pe) break;
		if ((pe->d_name)[0] == 'v' && strlen(pe->d_name) >=6){
			totalindex++;
			fltable[totalindex] = malloc(strlen(pe->d_name)*sizeof(char));
			strcpy(fltable[totalindex], pe->d_name);
			strncpy(tf, pe->d_name+1,5);
			int i = atoi(tf);
			if (lastnumber < i) lastnumber = i;
		}
	}
	closedir(dir);	

	int items;
	if (totalindex < 9) items = totalindex+1; else items = 10;
	tindex=0;
	for (int i = 0; i < items; i++) {
		bindex=i;
		lcdDrawString(dev, fx24G, 15, 10+(i+1)*24, (uint8_t*)fltable[i], WHITE);
	}
}
void TFT_Touch_init(bool touchEnable) {
	esp_vfs_spiffs_conf_t conf = {
		.base_path = "/spiffs",
		.partition_label = NULL,
		.max_files = 10,
		.format_if_mount_failed =true
	};
	esp_err_t ret = esp_vfs_spiffs_register(&conf);

	if (ret != ESP_OK) {
		if (ret == ESP_FAIL) {
			//ESP_LOGE(TAG, "Failed to mount or format filesystem");
		} else if (ret == ESP_ERR_NOT_FOUND) {
			//ESP_LOGE(TAG, "Failed to find SPIFFS partition");
		} else {
			//ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)",esp_err_to_name(ret));
		}
		return;
	}

	size_t total = 0, used = 0;
	ret = esp_spiffs_info(NULL, &total,&used);
	if (ret != ESP_OK) {
		//ESP_LOGE(TAG,"Failed to get SPIFFS partition information (%s)",esp_err_to_name(ret));
	} else {
		//ESP_LOGI(TAG,"Partition size: total: %d, used: %d", total, used);
	}

	// set font file
	InitFontx(fx16G,"/spiffs/ILGH16XB.FNT",""); // 8x16Dot Gothic
	InitFontx(fx24G,"/spiffs/ILGH24XB.FNT",""); // 12x24Dot Gothic
	InitFontx(fx32G,"/spiffs/ILGH32XB.FNT",""); // 16x32Dot Gothic
	
	InitFontx(fx16M,"/spiffs/ILMH16XB.FNT",""); // 8x16Dot Mincyo
	InitFontx(fx24M,"/spiffs/ILMH24XB.FNT",""); // 12x24Dot Mincyo
	InitFontx(fx32M,"/spiffs/ILMH32XB.FNT",""); // 16x32Dot Mincyo
	//TFT_t dev;
	lcd_interface_cfg(&dev, INTERFACE);

	INIT_FUNCTION(&dev, CONFIG_WIDTH, CONFIG_HEIGHT, CONFIG_OFFSETX, CONFIG_OFFSETY);

	if (touchEnable) {
		int gpio_xp = dev._d6;
		int gpio_xm = dev._rs;
		int gpio_yp = dev._wr;
		int gpio_ym = dev._d7;
		touch_interface_cfg(&dev, CONFIG_ADC_CHANNEL_YP, CONFIG_ADC_CHANNEL_XM, gpio_xp, gpio_xm, gpio_yp, gpio_ym);
	}
}
void TouchCalibration(TFT_t * dev, int width, int height) {
	if (dev->_calibration == false) return;
	lcdFillScreen(dev, BLACK);
	// get font width & height
	uint8_t buffer[FontxGlyphBufSize];
	uint8_t fontWidth;
	uint8_t fontHeight;
	GetFontx(fx24G, 0, buffer, &fontWidth, &fontHeight);
	//ESP_LOGD(__FUNCTION__,"fontWidth=%d fontHeight=%d",fontWidth,fontHeight);

	uint8_t ascii[24];
	int xpos = 0;
	int ypos = 0;

	// Calibration
	lcdFillScreen(dev, BLACK);
	dev->_min_xc = 15;
	dev->_min_yc = 15;
	lcdDrawFillCircle(dev, dev->_min_xc, dev->_min_yc, 10, CYAN);
	strcpy((char *)ascii, "Calibration");
	ypos = ((height - fontHeight) / 2) - 1;
	xpos = (width - (strlen((char *)ascii) * fontWidth)) / 2;
	lcdSetFontDirection(dev, DIRECTION0);
	lcdDrawString(dev, fx24G, xpos, ypos, ascii, WHITE);
	ypos = ypos + fontHeight;
	int _xpos = xpos;
	
	for(int i=0;i<10;i++) {
		lcdDrawFillCircle(dev, _xpos, ypos, fontWidth/2, RED);
		_xpos = _xpos + fontWidth + 5;
	}

	int32_t xp = 0;
	int32_t yp = 0;
	int counter = 0;
	while(1) {
		vTaskDelay(1);
		int _xp;
		int _yp;
		if (touch_getxy(dev, &_xp, &_yp) == false) continue;
		xp += _xp;
		yp += _yp;
		//ESP_LOGI(TAG, "counter=%d _xp=%d _yp=%d xp=%d yp=%d", counter, _xp, _yp, xp, yp);
		counter++;
		if (counter == 100) break;
		if ((counter % 10) == 0) {
			lcdDrawFillCircle(dev, xpos, ypos, fontWidth/2, GREEN);
			xpos = xpos + fontWidth + 5;
		}
	} // end while
	dev->_min_xp = xp/100;
	dev->_min_yp = yp/100;
	//ESP_LOGI(TAG, "_min_xp=%d _min_yp=%d", dev->_min_xp, dev->_min_yp);

	lcdFillScreen(dev, BLACK);
	dev->_max_xc = width-10;
	dev->_max_yc = height-10;
	lcdDrawFillCircle(dev, dev->_max_xc, dev->_max_yc, 10, CYAN);
	ypos = ((height - fontHeight) / 2) - 1;
	xpos = (width - (strlen((char *)ascii) * fontWidth)) / 2;
	lcdDrawString(dev, fx24G, xpos, ypos, ascii, WHITE);
	ypos = ypos + fontHeight;
	_xpos = xpos;
	for(int i=0;i<10;i++) {
		lcdDrawFillCircle(dev, _xpos, ypos, fontWidth/2, RED);
		_xpos = _xpos + fontWidth + 5;
	}

	xp = 0;
	yp = 0;
	counter=0;
	while(1) {
		vTaskDelay(1);
		int _xp;
		int _yp;
		if (touch_getxy(dev, &_xp, &_yp) == false) continue;
		if (_xp < dev->_min_xp+100 || _yp < dev->_min_yp+100) continue;
		xp += _xp;
		yp += _yp;
		counter++;
		if (counter == 100) break;
		if ((counter % 10) == 0) {
			lcdDrawFillCircle(dev, xpos, ypos, fontWidth/2, GREEN);
			xpos = xpos + fontWidth + 5;
		}
	} // end while
	
	dev->_max_xp = xp/100;
	dev->_max_yp = yp/100;
	dev->_calibration = false;
}


void vTaskRecord(void *param) {
    esp_err_t ret;
    audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    rec_pipeline = audio_pipeline_init(&pipeline_cfg);
    mem_assert(rec_pipeline);

    fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
	fatfs_cfg.task_core=APP_CPU_NUM;
    fatfs_cfg.type = AUDIO_STREAM_WRITER;
    fatfs_stream_writer = fatfs_stream_init(&fatfs_cfg);

    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
	i2s_cfg.task_core = APP_CPU_NUM;
    i2s_cfg.type = AUDIO_STREAM_READER;
    i2s_cfg.i2s_config.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT;
    i2s_cfg.use_alc = true;
    i2s_cfg.volume=-63;
	i2s_stream_reader = i2s_stream_init(&i2s_cfg);	

    i2s_pin_config_t rp = {
        .bck_io_num = 5, 
        .ws_io_num = 25,
        .data_out_num = -1,
        .data_in_num = 39 //35
    };
    i2s_set_pin(I2S_NUM_0, &rp);

    wav_encoder_cfg_t wav_cfg = DEFAULT_WAV_ENCODER_CONFIG();
	wav_cfg.task_core = APP_CPU_NUM;
    audio_encoder = wav_encoder_init(&wav_cfg);
   
    audio_pipeline_register(rec_pipeline, i2s_stream_reader, "i2s");
    audio_pipeline_register(rec_pipeline, audio_encoder, "wav");
    audio_pipeline_register(rec_pipeline, fatfs_stream_writer, "file");

    const char *link_tag[3] = {"i2s", "wav", "file"};
    audio_pipeline_link(rec_pipeline, &link_tag[0], 3);

	audio_element_info_t music_info = {0};
    audio_element_getinfo(i2s_stream_reader, &music_info);
    audio_element_setinfo(fatfs_stream_writer, &music_info);

    char filename[100];
    sprintf(filename, "%s/%s", SDSPI_MOUNT, (char*) param);
    audio_element_set_uri(fatfs_stream_writer, filename);
    
    audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
    audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

    audio_pipeline_set_listener(rec_pipeline, evt);

    i2s_alc_volume_set(i2s_stream_reader, -63);
	
    audio_pipeline_run(rec_pipeline);
    
	vTaskDelay(310/portTICK_PERIOD_MS);
	i2s_alc_volume_set(i2s_stream_reader, 20);
	int second_recorded = 0;
    while (1) {
        audio_event_iface_msg_t msg;
		ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
		if (ret != ESP_OK) {
            continue;
        }
		/*
        if (audio_event_iface_listen(evt, &msg, 1000/portTICK_PERIOD_MS) != ESP_OK) {
            second_recorded ++;
            if (second_recorded >= RECORD_TIME_SECONDS) {
               audio_element_set_ringbuf_done(i2s_stream_reader);
            }
			continue;
        }
		*/

        /* Stop when the last pipeline element (fatfs_stream_writer in this case) receives stop event */
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT && msg.source == (void *) fatfs_stream_writer
            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS
            && (((int)msg.data == AEL_STATUS_STATE_STOPPED) || ((int)msg.data == AEL_STATUS_STATE_FINISHED)
                || ((int)msg.data == AEL_STATUS_ERROR_OPEN))) {
            break;
        }
    }
    
	audio_pipeline_stop(rec_pipeline);
    audio_pipeline_wait_for_stop(rec_pipeline);
    audio_pipeline_terminate(rec_pipeline);

    audio_pipeline_unregister(rec_pipeline, audio_encoder);
    audio_pipeline_unregister(rec_pipeline, i2s_stream_reader);
    audio_pipeline_unregister(rec_pipeline, fatfs_stream_writer);


    /* Terminal the pipeline before removing the listener */
    audio_pipeline_remove_listener(rec_pipeline);

    /* Make sure audio_pipeline_remove_listener & audio_event_iface_remove_listener are called before destroying event_iface */
    audio_event_iface_destroy(evt);

    /* Release all resources */
    audio_pipeline_deinit(rec_pipeline);
    audio_element_deinit(fatfs_stream_writer);
    audio_element_deinit(i2s_stream_reader);
    audio_element_deinit(audio_encoder);

    vTaskDelete(NULL);

}

void vTaskPlay(void *param) {
    
	audio_pipeline_cfg_t pipeline_cfg = DEFAULT_AUDIO_PIPELINE_CONFIG();
    play_pipeline = audio_pipeline_init(&pipeline_cfg);
    mem_assert(play_pipeline);

    //ESP_LOGI(TAG, "[Playing] Create fatfs stream to read data from sdcard");
    fatfs_stream_cfg_t fatfs_cfg = FATFS_STREAM_CFG_DEFAULT();
    fatfs_cfg.type = AUDIO_STREAM_READER;
    fatfs_stream_reader = fatfs_stream_init(&fatfs_cfg);

    //ESP_LOGI(TAG, "[Playing]  Create i2s stream to write audio data to MAX98357A");
    i2s_stream_cfg_t i2s_cfg = I2S_STREAM_CFG_DEFAULT();
    i2s_cfg.type = AUDIO_STREAM_WRITER;
    i2s_cfg.use_alc = true;
    i2s_cfg.volume=0;
	i2s_stream_writer = i2s_stream_init(&i2s_cfg);

    i2s_pin_config_t wp = {
        .bck_io_num = 22,
        .ws_io_num = 25,
        .data_out_num = 26, 
        .data_in_num = -1
    };
    i2s_set_pin(I2S_NUM_0, &wp);

    wav_decoder_cfg_t wav_cfg = DEFAULT_WAV_DECODER_CONFIG();
    audio_decoder = wav_decoder_init(&wav_cfg);
    
	audio_pipeline_register(play_pipeline, fatfs_stream_reader, "file");
    audio_pipeline_register(play_pipeline, audio_decoder, "wav");
    audio_pipeline_register(play_pipeline, i2s_stream_writer, "i2s");

    const char *link_tag[3] = {"file", "wav", "i2s"};
    audio_pipeline_link(play_pipeline, &link_tag[0], 3);
    
	char filename[100];
    sprintf(filename, "%s/%s", SDSPI_MOUNT, (char*) param);
    printf("%s\n", filename);
    audio_element_set_uri(fatfs_stream_reader, filename);
    
	audio_event_iface_cfg_t evt_cfg = AUDIO_EVENT_IFACE_DEFAULT_CFG();
    audio_event_iface_handle_t evt = audio_event_iface_init(&evt_cfg);

	audio_pipeline_set_listener(play_pipeline, evt);

    audio_pipeline_run(play_pipeline);

   
    while (1) {
        audio_event_iface_msg_t msg;
         esp_err_t ret = audio_event_iface_listen(evt, &msg, portMAX_DELAY);
        if (ret != ESP_OK) {
            continue;
        }
		
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
            && msg.source == (void *) audio_decoder
            && msg.cmd == AEL_MSG_CMD_REPORT_MUSIC_INFO) {
                audio_element_info_t music_info = {0};
            audio_element_getinfo(audio_decoder, &music_info);
            audio_element_setinfo(i2s_stream_writer, &music_info);
            i2s_stream_set_clk(i2s_stream_writer, music_info.sample_rates, music_info.bits, music_info.channels);
            continue;
        }
	
        if (msg.source_type == AUDIO_ELEMENT_TYPE_ELEMENT
            && (msg.source == (void *) i2s_stream_writer || msg.source == (void *) fatfs_stream_reader)
            && msg.cmd == AEL_MSG_CMD_REPORT_STATUS && 
			(((int)msg.data == AEL_STATUS_STATE_STOPPED) || ((int)msg.data == AEL_STATUS_STATE_FINISHED))) {            
            break;
        }
    }
    ////ESP_LOGI(TAG, "[Playing]  Stop audio_pipeline");
    audio_pipeline_stop(play_pipeline);
    audio_pipeline_wait_for_stop(play_pipeline);
    audio_pipeline_terminate(play_pipeline);

	audio_pipeline_unregister(play_pipeline, fatfs_stream_reader);
    audio_pipeline_unregister(play_pipeline, audio_decoder);
    audio_pipeline_unregister(play_pipeline, i2s_stream_writer);
   
    /* Terminal the pipeline before removing the listener */
    audio_pipeline_remove_listener(play_pipeline);

    /* Make sure audio_pipeline_remove_listener & audio_event_iface_remove_listener are called before destroying event_iface */
    audio_event_iface_destroy(evt);

    /* Release all resources */
    audio_pipeline_deinit(play_pipeline);
    audio_element_deinit(fatfs_stream_reader);
    audio_element_deinit(i2s_stream_writer);
    audio_element_deinit(audio_decoder);
	gStatus = PAUSE_STATE;
	drawJPEG(&dev, "/spiffs/play.jpg", button[1][0], button[1][1], 36,36);

	vTaskDelete(NULL);

}

void app_main(void)
{
    esp_err_t ret;
	audio_element_state_t m_el_state;
	TFT_Touch_init(true);
	TouchCalibration(&dev, CONFIG_WIDTH, CONFIG_HEIGHT);
	lcdDrawFillRect(&dev, 1,1,239,319, BLACK);

    
// mount sdspi sd card
    sdmmc_host_t sdspi_host = SDSPI_HOST_DEFAULT();
    sdspi_device_config_t sdspi_device = SDSPI_DEVICE_CONFIG_DEFAULT();
    spi_bus_config_t bus_config = {
        .miso_io_num = GPIO_NUM_19,
        .mosi_io_num = GPIO_NUM_23,
        .sclk_io_num = GPIO_NUM_18
    };
    sdspi_device.host_id = VSPI_HOST;
    spi_dma_chan_t dma_chan = SPI_DMA_CH1;
    esp_vfs_fat_mount_config_t mount_config = {
        .format_if_mount_failed=true,
        .max_files = 5,
        .allocation_unit_size=16*512   //8192 
    };
    ret = spi_bus_initialize(sdspi_device.host_id, &bus_config, dma_chan);
    if (ret != ESP_OK) {
		lcdDrawString(&dev, fx16M, 10,50, (uint8_t*)"SPI BUS initialize error", WHITE);
		vTaskDelay(2000/portTICK_PERIOD_MS);
        return;
    }
    ret = esp_vfs_fat_sdspi_mount(SDSPI_MOUNT, &sdspi_host, &sdspi_device, &mount_config, &sdspi_card);
    if(ret != ESP_OK) {
		lcdDrawString(&dev, fx16M, 10,50, (uint8_t*)"SDSPI sdcard mount error", WHITE);
		vTaskDelay(2000/portTICK_PERIOD_MS);
        return;
    }
	
	lcdDrawString(&dev, fx16M, 10,50, (uint8_t*)"SDSPI sdcard mounted", WHITE);
	vTaskDelay(1000/portTICK_PERIOD_MS);
	drawBackground(&dev);

	int posx, posy;
	int selItem=-1;
	char fileName[12];

	while(1) {
		vTaskDelay(100/portTICK_PERIOD_MS);
		if (getTouchPos(&dev, &posx, &posy)) {
			selItem = -1;
			for (int i = 0 ; i < 7; i++) {
				if (posx >= button[i][0]-5 && posx <= button[i][0]+41 && 
					posy >= button[i][1]-5 && posy <= button[i][1]+41) { //+=5
					selItem=i;
					break;
				}
			}
			switch(selItem) {
				case 0:
				if (gStatus != RECORD_STATE) {
					if (gStatus != STOP_STATE) {
						// stop play
						m_el_state = audio_element_get_state(i2s_stream_writer);
						if (m_el_state == AEL_STATE_RUNNING || m_el_state == AEL_STATE_PAUSED){
							audio_pipeline_stop(play_pipeline);
							audio_pipeline_wait_for_stop(play_pipeline);
							drawJPEG(&dev, "/spiffs/play.jpg", button[1][0], button[1][1], 36,36);
						}
					
					}
					gStatus = RECORD_STATE;
					lastnumber++;
					sprintf(fileName, "v%05d.wav", lastnumber);
					//lcdDrawFillRect(&dev, 6,6,199,259, BLACK);
					//lcdDrawString(&dev, fx32G, 15, 100, (uint8_t*)"Recording...",WHITE);
					xTaskCreatePinnedToCore(playWave, "play Wave", 1024*3, NULL, 3, &hPlayWave, PRO_CPU_NUM);
					xTaskCreatePinnedToCore(vTaskRecord, "record task", 1024*4, fileName, 3, &hRecordingTask, APP_CPU_NUM);
					selectindex=-1;
				}
				break;
				case 1:
				if (selectindex != -1 && gStatus == STOP_STATE) {
							xTaskCreatePinnedToCore(vTaskPlay, "play task", 1024*4, selectFile, 3, &hPlayTask, APP_CPU_NUM);
							drawJPEG(&dev, "/spiffs/pause.jpg", button[1][0], button[1][1], 36,36);
							gStatus = PLAY_STATE;
				} else {
					
					m_el_state = audio_element_get_state(i2s_stream_writer);
					if (m_el_state == AEL_STATE_PAUSED){
						audio_pipeline_resume(play_pipeline);
						gStatus = PLAY_STATE;
						drawJPEG(&dev, "/spiffs/pause.jpg", button[1][0], button[1][1], 36,36);
					} else if (m_el_state == AEL_STATE_RUNNING){
						audio_pipeline_pause(play_pipeline);
						gStatus = PAUSE_STATE;
						drawJPEG(&dev, "/spiffs/play.jpg", button[1][0], button[1][1], 36,36);
					} 
					
				}
				break;
				case 2:
					if (gStatus == PAUSE_STATE || gStatus == PLAY_STATE) {
						drawJPEG(&dev, "/spiffs/play.jpg", button[1][0], button[1][1], 36,36);
						//m_el_handle = audio_pipeline_get_el_by_tag(hPlayTask, "is2");
						//audio_element_set_ringbuf_done(fatfs_stream_reader);
						audio_pipeline_stop(play_pipeline);
						audio_pipeline_wait_for_stop(play_pipeline);
					} 
					if (gStatus == RECORD_STATE && totalindex < MAX_INDEX-1) {
						totalindex++;
						audio_pipeline_stop(rec_pipeline);
						audio_pipeline_wait_for_stop(rec_pipeline);
						vTaskDelete(hPlayWave);
						fltable[totalindex] = malloc(11*sizeof(char));
						sprintf(fileName,"v%05d.wav", lastnumber);
						strcpy(fltable[totalindex], fileName);
						bindex=totalindex;
						if (bindex > 9) tindex = bindex-9; else tindex=0;
						selectindex = -1;
						lcdDrawFillRect(&dev, 6,6,199,259, BLACK);
						for (int i = tindex; i <= bindex; i++) {
							lcdDrawString(&dev, fx24G, 15, 10+(i-tindex+1)*24, (uint8_t*)fltable[i], WHITE);
						}
					}
					gStatus = STOP_STATE;
				break;
				case 3:
					if (gStatus == PLAY_STATE) {
						ret = i2s_alc_volume_get(i2s_stream_writer, &volume);
						if (ret == ESP_OK) {
							if (volume < VOLUME_MAX) {
								volume = volume+2;
								i2s_alc_volume_set(i2s_stream_writer, volume);
							}
						}
					}
					if (gStatus == RECORD_STATE) {
						ret = i2s_alc_volume_get(i2s_stream_reader, &volume);
						if (ret == ESP_OK) {
							if (volume < VOLUME_MAX) {
								volume = volume+2;
								i2s_alc_volume_set(i2s_stream_reader, volume);
							}
						}
					}
				break;
				case 4:
				if (gStatus == PLAY_STATE) {
						ret = i2s_alc_volume_get(i2s_stream_writer, &volume);
						if (ret == ESP_OK) {
							if (volume > VOLUME_MIN) {
								volume = volume-2;
								i2s_alc_volume_set(i2s_stream_writer, volume);
							}
						}
					}
					if (gStatus == RECORD_STATE) {
						ret = i2s_alc_volume_get(i2s_stream_reader, &volume);
						if (ret == ESP_OK) {
							if (volume > VOLUME_MIN) {
								volume = volume-2;
								i2s_alc_volume_set(i2s_stream_reader, volume);
							}
						}
					}
				break;
				case 5:
					if (tindex == 0 || bindex - tindex < 9) break;
					tindex--;
					selectindex=-1;
					lcdDrawFillRect(&dev, 6,6,199,259, BLACK);
					for (int i = tindex; i < tindex+10; i++) {
						bindex=i;
						lcdDrawString(&dev, fx24G, 15, 10+(i-tindex+1)*24, (uint8_t*)fltable[i], WHITE);
					}
				break;
				case 6:
					if (bindex == totalindex || bindex - tindex < 9) break;
					tindex++;
					selectindex=-1;
					lcdDrawFillRect(&dev, 6,6,199,259, BLACK);
					for (int i = tindex; i < tindex+10; i++) {
						bindex=i;
						lcdDrawString(&dev, fx24G, 15, 10+(i-tindex+1)*24, (uint8_t*)fltable[i], WHITE);
					}
				break;
				default:
					if (posx > 5 && posx < 200 && posy > 5 && posy < 260) {
						if (selectindex != -1) {
							lcdDrawFillRect(&dev, 15, 10+(selectindex-tindex)*24, 199,10+(selectindex-tindex+1)*24, BLACK);
							lcdDrawString(&dev, fx24G, 15, 10+(selectindex-tindex+1)*24, (uint8_t*)fltable[selectindex], WHITE);
						}
						selectindex = (posy-5)/24+tindex;
						if (selectindex <= totalindex && selectindex-tindex < 10) {
							strcpy(selectFile, fltable[selectindex]);
							lcdDrawFillRect(&dev, 15, 10+(selectindex-tindex)*24, 199,10+(selectindex-tindex+1)*24, BLUE);
							lcdDrawString(&dev, fx24G, 15, 10+(selectindex-tindex+1)*24, (uint8_t*)fltable[selectindex], WHITE);
						} else {
							selectindex = -1;
						}
						
					}
				break;
			}
		}

	}
    esp_vfs_fat_sdcard_unmount(SDSPI_MOUNT, sdspi_card);   
}
	

2022年2月10日 星期四

EPS32 ESP-IDF開發環境儲存設備與檔案系統實驗(二) -- SPI FLASH (A Study on ESP32 Storage Devices and File System--SPI Flash using ESP-IDF)

本實驗是在ESP-IDF環境下,測試ESP32 spi flash storage。使用內建(main flash)與外接flash(external falsh) ,分別測試nvs, spiffs 與fat filesystem。

本實驗使用的外接flash為Winbond W25Q128FV外接模組,


共有VCC, GND, CS, CLK, DI 與DO pins,IO2與IO3未接出。根據W25Q128FV Datasheeet只能操作在standard spi 與dual spi operation。pin No.相對應表如下圖。

(取自Winbond W25Q128FV datasheet)

ESP32 ESP-IDF環境下直接支援Winbond flash,memuconfig預設是開啟支援的

一、加入 SPI Flash device 

  • 內建main flash在memuconfig 中指定mode, speed等,例如QIO, 80MHz


  • 外接SPI Flash依序以下列三個API就能加入系統中
  1. spi_bus_initialize(): 決定使用哪一個SPI HOST,MISO、MOSI、CLK pin和 DMA channel.
  2. spi_bus_add_flash_device():  附加 flash device 在SPI bus上.  指定CS pin, I/O Mode(dio、qio) 與spi bus speed等。
  3. esp_flash_init(): 開始與flash溝通. 偵測chip型號等。
三個API參數詳細說明如下:
esp_err_t spi_bus_initialize(spi_host_device_t host_id, const spi_bus_config_t * bus_config, spi_dma_chan_t dma_chan)
  1. host_id:SPI1_HOST, SPI2_HOST(HSPI_HOST), SPI3_HOST(VSPI_HOST)。例如
    VSPI_HOST。
  2. spi_bus_config_t: 指定miso_io_num, mosi_io_num and sclk_io_num,本實驗例子spi_bus_config_t bus_config = {
            .mosi_io_num = GPIO_NUM_23,
            .miso_io_num = GPIO_NUM_19,
            .sclk_io_num = GPIO_NUM_18 
        };
  3. spi_dma_chan_t: DMA channel,例如
    spi_dma_chan_t spi_dma_chan = SPI_DMA_CH2;
esp_err_t spi_bus_add_flash_device(esp_flash_t ** out_chip, const esp_flash_spi_device_config_t * config)
  1. esp_flash_t: 代表此flash device的pointer,例如
    esp_flash_t * flash_chip;
  2. esp_flash_spi_device_config_t: 指定此flash device的參數,io_mode, speed, cs pin。例如esp_flash_spi_device_config_t spi_device_config = {
            .host_id = VSPI_HOST,
            .cs_id = 0,
            .cs_io_num = 5,
            .io_mode = SPI_FLASH_DIO,
            .speed = ESP_FLASH_40MHZ
        };
esp_err_t IRAM_ATTR esp_flash_init(esp_flash_t * chip);

經過這三個API設定,順利加入spi flash device後,代表此flash device的pointer(上例為:flash_chip)即可以此flash_chip作為後續API的參數。測試系統順利附加spi flash device,如下圖。
(執行結果截圖)

二、分割SPI Flash Device Partition

flash 的partition內容分為幾個部分:
  1. label: 代表此partition名稱。
  2. type: app or data。
  3. subtype: app 或data的subtype,本實驗操作的三個檔案系統為type是data的subtype分別為nvs, spiffs, fat
  4. offset: 位移位址
  5. size:大小
  6. flags: 指定是否encrypted

  • 內建flash(main flash) partition建立方式:

內建flash(main flash) 的partition table可在menuconfig中指定使用Custom partition CSV,如下圖。
製作csv檔案,build project即可為內建flash建立所需的partitions,例如

  • 外接SPI flash partition table建立方式:

使用API esp_partition_register_external來將外接SPI Flash指定每個partition 的label, type, subtype, offset,size等參數,將partition table加入的flash_chip中。
此API詳細參數說明如下:
esp_err_t esp_partition_register_external(esp_flash_t * flash_chip, size_t offset, size_t size, const char * label, esp_partition_type_t type, esp_partition_subtype_t subtype, const esp_partition_t ** out_partition)
  1. esp_flash_t: 上述spi_bus_add_flash_device API, return 的 flshp_chip
  2. offset: 指定 offset
  3. size: 指定大小
  4. esp_partition_type_t: 例如ESP_PARTITION_TYPE_DATA為data type
  5. esp_partition_subtype_t:例如 ESP_PARTITION_SUBTYPE_DATA_FAT為fat subtype,
  6. esp_partiton_t: 為output partition pointer作為後續處理partition用,例如本實驗中fat_part, nvs_part等。
本實驗partition的lable, offset, size分別如下列設定:
const size_t fatOffset = 0, fatSize=8*1024*1024; // 8MB, use for FAT partition
const size_t nvsOffset = 8*1024*1024, nvsSize = 1024*1024; // 1MB, use for nvs partition
const size_t spiffsOffset = 9*1024*1024, spiffsSize = 2*1024*1024; // 2M, user for spiffs partition

const esp_partition_t *fat_part;
const esp_partition_t *nvs_part;
const esp_partition_t *spiffs_part;

// partition label
const char extern_flash_fat[] = "extFATData";
const char extern_flash_nvs[] = "extNVS";
const char extern_flash_spiffs[] = "extSPIFFS";

三、掛載SPI Flash Device Partition

本實驗使用nvs, spiffs, fat 三種 flash partitions,各別partition type說明如下:

NVS partition

    Non-volatile storage (NVS) 常用來儲存 key-value 資料在flash中。partition 的type為data, subtype 為nvs,當partition label 為 nvs 稱為default NVS partition。
    首先需初始化,nvs_flash_init(void)初始化default nvs partiton,使用nvs_flash_init_partiton(const char *partition_label)初始化其他nvs parttiton,例如nvs_flash_init_partiton("extNVS");
其他常用使令:
  1. nvs_flash_erase,nvs_flash_erase_partition:抹除整個partition。
  2. nvs_open,  nvs_open_from_partition: 開啟namespace, readonly or readwrite mode
  3. nvs_set_*: 設定key-value, value可為string, blob , integer or usigned integer。
  4. nvs_get_*: 取得key的value
  5. nvs_commit:寫入到flash中
  6. nvs_close:關閉並釋放資源

SPIFFS partition 

Flash的區塊有erase/write次數的限制,同一個區塊重複erase/write,將會造成讀取速度變慢,甚至損壞。使用Wear Leveling(耗損平均技術)能平均使用快閃記憶體中的每個儲存區塊,來避免某些儲存區塊因過度使用而形成壞區塊。

SPIFFS is a file system 常使用在內鑲系統 SPI NOR flash 設備上. 支援 Wear Leveling, file system 一致性檢查, 但不支援目錄架構。

使用 esp_vfs_spiffs_register掛載partition在vfs後,即可使用標準C file library處理spiffs檔案
esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf)
參數esp_vfs_spiffs_conf_t指定掛載base path, partition,最大可開啟檔案數,掛載失敗是否要格式化等,例如:
esp_vfs_spiffs_conf_t spiffsConf = {
        .base_path="/spiffs",
        .partition_label = "extSPIFFS", 
        .max_files=5,
        .format_if_mount_failed=true
    };
當掛在extSPIFFS partition的base path為"/spiffs"時,若要開啟"test.txt" 檔案,以指令fopen("/spiffs/test.txt","w");開啟謝入模式。

esp_vfs_spiffs_unregister:從VFS卸載SPIFFS。

FAT partition

使用FAT filesystem在Flash上與使用在SD Card上類似,但增加Wear Levelling設定。
常用指令:
esp_vfs_fat_spiflash_mount, esp_vfs_fat_spiflash_unmount, 

掛載 partition API:
esp_err_t esp_vfs_fat_spiflash_mount(const char *base_path, const char *partition_label, const esp_vfs_fat_mount_config_t *mount_config, wl_handle_t *wl_handle)

ESP-IDF 使用此單一API,完成下堆堆疊動作:把partition table中相對應的partition label做Wear Levelling library初始化,使用FATFS library掛載FAT partition在wear liveiling libarary上,最後註冊VFS的base path,之後即以standard C file libaray存取flash device上的檔案。
API參數說明如下:
  1. base_path: 為掛載partition label的base path,例如:"/spiflash"。
  2. partition label: 欲掛載的 partition label,例如:"extFATData":
  3. esp_vsf_fat_mount_config_t: 說明掛載失敗是否格式化,最大可開啟檔案數等,例如esp_vfs_fat_mount_config_t spiflash_mount_config = {
            .format_if_mount_failed = true,
            .max_files=3,
            .allocation_unit_size=CONFIG_WL_SECTOR_SIZE
        };
  4. wl_handle_t: wear levelling handle,作為後續其他API使用的參數,例如:
    static wl_handle_t wl_handle = WL_INVALID_HANDLE;

卸載partition API:
esp_err_t esp_vfs_fat_spiflash_unmount(const char * base_path, wl_handle_t wl_handle);


四、實驗結果

第一次使用flsah device,尚未分割partition與格式化, spiffs與fat filesystem根據esp_vfs_fat_mount_t指定.format_if_mount_failed = true,自動格式化該partition,如下圖所


分別在內建flash(main flash)與外加flash模組開啟nvs, spiffs 與 FAT partition,個別執行read/write,結果如下圖所示:


五、程式碼

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "esp_log.h"
#include "driver/sdspi_host.h"
#include "esp_partition.h"
#include "esp_spi_flash.h"
#include "esp_spiffs.h"
#include "esp_flash.h"
#include "esp_vfs_fat.h"
#include "esp_flash_spi_init.h"
#include "nvs_flash.h"
#include "esp_spiffs.h"

#define TAG "SPI FLASH TEST"

static esp_flash_t *flash_chip;
static wl_handle_t wl_handle = WL_INVALID_HANDLE;

const char extern_flash_fat[] = "extFATData";
const char extern_flash_nvs[] = "extNVS";
const char extern_flash_spiffs[] = "extSPIFFS";
const size_t fatOffset = 0, fatSize=8*1024*1024; // 8MB
const size_t nvsOffset = 8*1024*1024, nvsSize = 1024*1024; // 1MB
const size_t spiffsOffset = 9*1024*1024, spiffsSize = 2*1024*1024; // 2M


    
const esp_partition_t *fat_part;
const esp_partition_t *nvs_part;
const esp_partition_t *spiffs_part;

bool external_flash_init() {
    esp_err_t ret;
 
    spi_host_device_t vspi_host_id = VSPI_HOST;
    spi_bus_config_t bus_config = {
        .mosi_io_num = GPIO_NUM_23,
        .miso_io_num = GPIO_NUM_19,
        .sclk_io_num = GPIO_NUM_18  
    };
    spi_dma_chan_t spi_dma_chan = SPI_DMA_CH2;
    //step 1 spi_bus_initialize
    ret = spi_bus_initialize(vspi_host_id, &bus_config, spi_dma_chan);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "spi bus initialize failure: %s", esp_err_to_name(ret));
        return false;
    }
    // step 2 spi_bus_add_device
    
    esp_flash_spi_device_config_t spi_device_config = {
        .host_id = VSPI_HOST,
        //.cs_id = 0,
        .cs_io_num = 5,
        //.io_mode = SPI_FLASH_QIO,
        .io_mode = SPI_FLASH_DIO,
        .speed = ESP_FLASH_40MHZ
    };
    ret = spi_bus_add_flash_device(&flash_chip, &spi_device_config);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "spi bus add flash device failure: %s", esp_err_to_name(ret));
        return false;
    }
    //step 3 spi_flash_init
    ret = esp_flash_init(flash_chip);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "spi flash init: %s", esp_err_to_name(ret));
        return false;
    }
    uint32_t fsize;
    ret = esp_flash_get_size(flash_chip, &fsize);
    ESP_LOGI(TAG, "external SPI Flash(W25Q128FV) size:0x%0x, chip size:0x%0x", fsize, flash_chip->size);

    return true;
}

bool register_external_partition() {
    esp_err_t ret;
    
    ret = esp_partition_register_external(flash_chip, fatOffset, fatSize, extern_flash_fat, ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, &fat_part);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "register external fat partitional error:%s", esp_err_to_name(ret));
        return false;
    }
    
    ret = esp_partition_register_external(flash_chip, nvsOffset, nvsSize, extern_flash_nvs, ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, &nvs_part);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "register external nvs partitional error:%s", esp_err_to_name(ret));
        return false;
    }
    
    ret = esp_partition_register_external(flash_chip, spiffsOffset, spiffsSize, extern_flash_spiffs, ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, &spiffs_part);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "register external spiffs partitional error:%s", esp_err_to_name(ret));
        return false;
    }

    return true;
}

void nvs_read_write_test() {
    esp_err_t ret;

    //test main flash default nvs partition
    ESP_LOGI(TAG, "TEST main flash default nvs partition read/write");
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NEW_VERSION_FOUND || ret == ESP_ERR_NVS_NO_FREE_PAGES) {
        nvs_flash_erase();
        nvs_flash_init();
    }
    nvs_handle_t hNVS;
    ret = nvs_open("defaultnvs", NVS_READWRITE, &hNVS);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "open default nvs error:%s", esp_err_to_name(ret));
        return;
    }
    nvs_set_str(hNVS, "testK1", "default Key1 value");
    nvs_set_str(hNVS, "testK2", "default Key2 value");
    nvs_commit(hNVS);
    nvs_close(hNVS);

    ret = nvs_open("defaultnvs", NVS_READWRITE, &hNVS);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "open default nvs error:%s", esp_err_to_name(ret));
        return;
    }
    char tvalue[100];
    size_t tsize=100;
    nvs_get_str(hNVS, "testK1", tvalue, &tsize);
    printf("Read default nvs testK1, value=%s\n", tvalue);
    tsize=100;
    nvs_get_str(hNVS, "testK2", tvalue, &tsize);
    printf("Read default nvs testK1, value=%s\n\n", tvalue);
    nvs_close(hNVS);


    ESP_LOGI(TAG, "TEST external flash nvs_part partition read/write");
    ret = nvs_flash_init_partition(extern_flash_nvs);
    if (ret == ESP_ERR_NVS_NEW_VERSION_FOUND || ret == ESP_ERR_NVS_NO_FREE_PAGES) {
        nvs_flash_erase_partition(extern_flash_nvs);
        nvs_flash_init_partition(extern_flash_nvs);
    }
   
    ret = nvs_open_from_partition(extern_flash_nvs, "externalnvs", NVS_READWRITE, &hNVS);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "open external flash nvs error:%s", esp_err_to_name(ret));
        return;
    }
    nvs_set_str(hNVS, "extK1", "external flash nvs Key1 value");
    nvs_set_str(hNVS, "extK2", "external flash nvs Key2 value");
    nvs_commit(hNVS);
    nvs_close(hNVS);
    printf("\n");
    ret = nvs_open_from_partition(extern_flash_nvs, "externalnvs", NVS_READWRITE, &hNVS);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "open external flash nvs error:%s", esp_err_to_name(ret));
        return;
    }

    nvs_get_str(hNVS, "extK1", tvalue, &tsize);
    printf("Read external flash nvs extK1, value=%s\n", tvalue);
    tsize=100;
    nvs_get_str(hNVS, "extK2", tvalue, &tsize);
    printf("Read external flash nvs extK1, value=%s\n\n", tvalue);
    nvs_close(hNVS);
    printf("\n");
}

void spiffs_read_write_test() {
    esp_err_t ret;
    size_t t,u;
    FILE *fp;
    char buf[100];
   
    esp_vfs_spiffs_conf_t spiffsConf = {
        .base_path="/spiffs",
        .partition_label = "mainspiffs", 
        .max_files=5,
        .format_if_mount_failed=true
    };
    
    // test main flash spiffs 
    ESP_LOGI(TAG, "main flash spiffs read/write test");
    ret = esp_vfs_spiffs_register(&spiffsConf);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG,"mainflash spiffs register error:%s\n",esp_err_to_name(ret));
        return;
    }
    esp_spiffs_info("mainspiffs", &t, &u);
    ESP_LOGI(TAG, "main flash spiffs size -- Total:%d, used:%d\n", t,u);

    fp = fopen("/spiffs/mtest.txt", "w");
    if (fp) {
        fputs("main flash spiffs test line 1\n", fp);
        fputs("main flash spiffs test line 2\n", fp);
        fputs("main flash spiffs test line 3\n", fp);
        fclose(fp);
    } else {
        ESP_LOGI(TAG, "open main flash spiff error(write)");
        return;
    }

    
    fp = fopen("/spiffs/mtest.txt", "r");
    if (!fp) {
        ESP_LOGI(TAG, "open main flash spiff error(read)");
        return;
    }
    while(fgets(buf, 100, fp)) {
        printf("read from main flash spiffs: %s",buf);
    }
    fclose(fp);
    printf("\n");
    esp_vfs_spiffs_unregister("mainspiffs");

    // test external flash spiffs
    esp_vfs_spiffs_conf_t extspiffsConf = {
        .base_path="/spiffs",
        .partition_label = extern_flash_spiffs, 
        .max_files=5,
        .format_if_mount_failed=true
    };

    ESP_LOGI(TAG, "external flash spiffs read/write test");
    ret = esp_vfs_spiffs_register(&extspiffsConf);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG,"external flash spiffs register error: %s\n",esp_err_to_name(ret));
        return;
    }
    esp_spiffs_info(extern_flash_spiffs, &t, &u);
    ESP_LOGI(TAG, "external flash spiffs size -- Total:%d, used:%d\n", t,u);

    fp = fopen("/spiffs/mtest.txt", "w");
    if (fp) {
        fputs("external flash spiffs test line 1\n", fp);
        fputs("external flash spiffs test line 2\n", fp);
        fputs("external flash spiffs test line 3\n", fp);
        fclose(fp);
    }

    fp = fopen("/spiffs/mtest.txt", "r");
    while(fgets(buf, 100, fp)) {
        printf("read from external flash spiffs ----- %s",buf);
    }
    fclose(fp);
    printf("\n");
   esp_vfs_spiffs_unregister(extern_flash_spiffs);
    
}

void fat_read_write_test() {
    esp_err_t ret;
    FILE *fp;
    char buf[100];

     esp_vfs_fat_mount_config_t spiflash_mount_config = {
        .format_if_mount_failed = true,
        .max_files=3,
        .allocation_unit_size=CONFIG_WL_SECTOR_SIZE
    };
// test main spi flash fat filesysetm
    ESP_LOGI(TAG, "test main spi flash fat filesystem");
    ret = esp_vfs_fat_spiflash_mount("/spiflash", "mainfat", &spiflash_mount_config, &wl_handle);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "mount main flash fat filesystem error: %s", esp_err_to_name(ret));
        return;
    }
    fp = fopen("/spiflash/ftest.txt", "wb");
    if (fp) {
        fputs("main spi flash fat filesystem test, LINE 1\n", fp);
        fputs("main spi flash fat filesystem test, LINE 2\n", fp);
        fputs("main spi flash fat filesystem test, LINE 3\n", fp);
        fclose(fp);
    } else {
        ESP_LOGI(TAG, "open main flash write fat file error(write)\n");
        return;
    }
    ESP_LOGI(TAG, "main spi flash write finished");
    fp = fopen("/spiflash/ftest.txt", "rb");
    if(fp) {
        while(fgets(buf, 100, fp)) {
            printf(buf);
        }
        fclose(fp);
        printf("\n");
    }else  {
         ESP_LOGI(TAG, "open main flash write fat file error(read)\n");
         return;
    }

    esp_vfs_fat_spiflash_unmount("/spiflash", wl_handle);
// test external spi flash fat filesysetm
    ESP_LOGI(TAG, "test external spi flash fat filesystem");
    ret = esp_vfs_fat_spiflash_mount("/spiflash", extern_flash_fat, &spiflash_mount_config, &wl_handle);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG, "mount external flash fat filesystem error: %s", esp_err_to_name(ret));
        return;
    }
    fp = fopen("/spiflash/ftest.txt", "wb");
    if (fp) {
        fputs("external spi flash fat filesystem test, LINE 1\n", fp);
        fputs("external spi flash fat filesystem test, LINE 2\n", fp);
        fputs("external spi flash fat filesystem test, LINE 3\n", fp);
        fclose(fp);
    } else {
        ESP_LOGI(TAG, "open external flash write fat file error(write)\n");
        return;
    }
    ESP_LOGI(TAG, "external spi flash write finished");
    fp = fopen("/spiflash/ftest.txt", "rb");
    if(fp) {
        while(fgets(buf, 100, fp)) {
            printf(buf);
        }
        fclose(fp);
        printf("\n");
    }else  {
         ESP_LOGI(TAG, "open external flash write fat file error(read)\n");
         return;
    }
    
}
void unregister_external_partition() {
    if (nvs_part != NULL)
        esp_partition_deregister_external(nvs_part);
    if (fat_part != NULL)
        esp_partition_deregister_external(fat_part);
    if (spiffs_part != NULL)
        esp_partition_deregister_external(spiffs_part);
}
 
void external_spiffs_read_test() {
    esp_err_t ret;
    size_t t,u;
    FILE *fp;
    char buf[100];
   
    
    // test external flash spiffs
    esp_vfs_spiffs_conf_t extspiffsConf = {
        .base_path="/spiffs",
        .partition_label = extern_flash_spiffs, 
        .max_files=5,
        .format_if_mount_failed=true
    };

    ESP_LOGI(TAG, "external flash spiffs read test");
    ret = esp_vfs_spiffs_register(&extspiffsConf);
    if (ret != ESP_OK) {
        ESP_LOGI(TAG,"external flash spiffs register error: %s\n",esp_err_to_name(ret));
        return;
    }
    esp_spiffs_info(extern_flash_spiffs, &t, &u);
    ESP_LOGI(TAG, "external flash spiffs size -- Total:%d, used:%d\n", t,u);

    fp = fopen("/spiffs/mtest.txt", "r");
    while(fgets(buf, 100, fp)) {
        printf("read from external flash spiffs again ----- %s",buf);
    }
    fclose(fp);
    printf("\n");
   esp_vfs_spiffs_unregister(extern_flash_spiffs);
    
}
void app_main(void)
{
    flash_chip = malloc(sizeof(esp_flash_t));
    if (!external_flash_init()) {
        ESP_LOGI(TAG, "external flash init error");
        return;
    }
//esp_flash_erase_chip(flash_chip);
    if (!register_external_partition()) {
        ESP_LOGI(TAG, "register external flash partition error");
        return;
    }

    nvs_read_write_test();
    spiffs_read_write_test();
    fat_read_write_test();
    external_spiffs_read_test();

    unregister_external_partition();
    

}

2022年2月7日 星期一

EPS32 ESP-IDF開發環境儲存設備與檔案系統實驗(一) -- SDMMC 與SDSPI (A Study on ESP32 Storage Devices and File System--SDMMC and SDSPI using ESP-IDF)

    ESP32 MCU常用的儲存設備為SD(SDMMC & SDSPI) 或SPI Flash,本篇實驗記錄主樣針對SD ,而上層檔案系統使用FatFs,實驗使用ESP32-CAM開發版,因為本開發版已含有SD Card模組。有關SPI Flash探討將於下篇記錄中說明。

一、FatFS

    FatFs是一個為小型系統發展的FAT/exFAT檔案系統,軟體系統架構如下圖所示,

(本圖片取自FatFs官網,http://elm-chan.org/fsw/ff/00index_e.html)

MCU的Application使用統一的FatFs Module,透過中間層使用不同device提供的Low Level device controls去存取各自的devices(例如SD, Flash, RTC等)。

    FatFs Module 與 device controls是完全獨立的,每個設備提供自己的device controls,FatFs與storage device controls則透過Media Access Interface(MAI)中間層去銜接。如下同所示。

(本圖片取自FatFs官網,http://elm-chan.org/fsw/ff/00index_e.html)

FatFs MAI使用下列五個API銜接FatFs Module與Storage device controls:
  1. disk_status - 取得device status。
  2. disk_initialize - 初始化 device
  3. disk_read - Read data
  4. disk_write - Write data
  5. disk_ioctl - Control device dependent functions