問題描述
因為項目是一個小電視項目需要播放動態圖,但是由於內部編程地址只有<1M,想要額能夠存放更多的動態圖。了解到esp-12s不止1M的flash,所以想要利用起來其余的空間。
本方法適用於: 低頻率寫數據,高頻率讀取數據,想以此完全替代RAM是不可能的。
解決辦法原理
項目的是基於arduino
庫來開發的,而icache
自動映射在前面1M(0x1010-0x100000)
左右flash中,所以這部分地址可以直接使用,而不需要spi來讀取。那么可以將后面非映射的區域(3M
左右)拷貝到該區域,代碼中直接使用該拷貝的位置即可。
過程
前章知道arduino
可將flash的非映射區做成文件系統。在里面由兩種FS:SpiFs
與LittleFs
。不過SpiFs
已經不再推薦了。這里使用LittleFs
來構建。
LittleFs
是Cpp
文件,且它引用的都是Cpp
文件,我這里使用了C
文件來include
導致報錯,大家需要注意,盡量使用Cpp
文件。
Arduino IDE
下構建文件系統,在網上偶許多帖子說明了。我這里使用的是vscode + platformio
開發模式。
文件夾目錄是data
該文件需要建立在項目根目錄下,不然無法識別,然后在platform.ini
中加入:
board_build.ldscript = eagle.flash.4m3m.ld
board_build.filesystem = littlefs
這兩句分別是定義鏈接腳本和文件系統類型,鏈接腳本可將flash分配成3M文件系統,1M的程序系統
。
接着可以在data
目錄下放入正常的文件:
build Filesystem image
表示構建文件系統,會生成bin
文件在.pio/build/esp8266/littlsfs.bin
Upload Filesystem Image
表示從接口(一般串口)
燒錄文件系統,不會清除程序。
代碼實現過程
將某個Buffer
區域定義為指定section
,然后在linker
腳本的irom0
區域加入指定鏈接位置,指定的鏈接位置請與扇區size
對齊,因為寫入數據前往往需要先清空扇區,buffer
大小頁盡量是sector
的整數倍,這樣不會錯誤寫入到程序區域,導致不可意料的錯誤。
注:attribute((at( 某個地址)))也可以實現,但在arduino中似乎被放棄,故使用指定section方式。
關鍵代碼如下:
1.代碼申請一塊區域
#define ROM_BUFFER __attribute__((section( "\".rom_buffer." __FILE__ "." __STRINGIZE(__LINE__) "." __STRINGIZE(__COUNTER__) "\"")))
const uint8_t Ani_A_cache_buffer[100][2*1024] ROM_BUFFER = {0x0ff,0x02,0x03,0xff}; //圖片緩沖區域。//請勿cpoy我的邪惡法師文章,這個為原創,轉載聲明出處
賦予初值只是為了方便調試。
2.修改ld
文件,ld
文件在.pio\build\esp12e\ld\local.eagle.app.v6.common.ld
但是該文件不可直接改,因為它是被生成的,修改文件C:\Users\用戶名\.platformio\packages\framework-arduinoespressif8266\tools\sdk\ld\eagle.app.v6.common.ld.h
:
/* IRAM is split into .text and .text1 to allow for moving specific */
/* functions into IRAM that would be matched by the irom0.text matcher */
.text : ALIGN(4)
{
......................
. = ALIGN(4);
__eh_frame = ABSOLUTE(.);
KEEP(*(.eh_frame))
. = ALIGN(4096); //添加。//請勿cpoy我的邪惡法師文章,這個為原創,轉載聲明出處
*(.rom_buffer.*) //添加。//請勿cpoy我的邪惡法師文章,這個為原創,轉載聲明出處
. = (. + 7) & ~ 3; /* Add a 0 entry to terminate the list */
//請勿cpoy我的邪惡法師文章,這個為原創,轉載聲明出處
_irom0_text_end = ABSOLUTE(.);
_flash_code_end = ABSOLUTE(.);
} >irom0_0_seg :irom0_0_phdr
. = ALIGN(4096);*(.rom_buffer.*)
為添加的語句。意為將linker點移到4096對齊位置,然后下面的含有rom_buffer
的段符號都被寫入irom0_0_seg
位置
3.使用改區域
//寫
//擦除緩沖區
//擦除從起始位置(flash位置或者0x40200000)開始的sec_no個扇區
#include <LittleFS.h>
#include "spi_flash.h"
#define ROM_MAP_START 0x40200000
SpiFlashOpResult spi_erase_anim_cache_buffer( uint32_t sec_no)
{
SpiFlashOpResult res = SPI_FLASH_RESULT_OK;
//注意要減1,扇區從0開始。//請勿cpoy我的邪惡法師文章,這個為原創,轉載聲明出處
uint32_t sec_strat = ((uint32_t)Ani_A_cache_buffer-ROM_MAP_START)/SPI_FLASH_SEC_SIZE - 1;
uint32_t sec_end = sec_strat + sec_no;
if( sec_end > (sec_strat+(Animate_Max_Fps/2)))
sec_end = sec_strat+(Animate_Max_Fps/2);
Serial.printf("Erase file system: %d - %d,sec_no:%d,MAX:%d\r\n", sec_strat, sec_end, sec_no, Animate_Max_Fps/2);
for( uint32_t i = sec_strat; i < sec_end; i++)
{
ESP.wdtFeed(); //喂狗,注意時間過程會導致狗復位。//請勿cpoy我的邪惡法師文章,這個為原創,轉載聲明出處
res = spi_flash_erase_sector( i);
//Serial.printf("%d[%x-%x] ", i, i*SPI_FLASH_SEC_SIZE, (i+1)*SPI_FLASH_SEC_SIZE-1);
if( res)
return res;
}
//AniAcSysPrintf("\r\n");
return res;
}
void setup()
{
int sec_no = sizeof(Ani_A_cache_buffer)/4096;
int addr = (unsigned int)Ani_A_cache_buffer;
unsigned char src_ptr[100] = "Hello, this is my test Code";
spi_erase_anim_cache_buffer( sec_no);
res = spi_flash_write( addr-ROM_MAP_START, (unsigned int)src_ptr, 40);//寫入size需要4字節對齊。//請勿cpoy我的邪惡法師文章,這個為原創,轉載聲明出處
}
//讀
unsigned char c1 = Ani_A_cache_buffer[0][0];
Serial.prinf( "c1=%d\r\n", c1);
寫入請不要直接使用改變量名,因為這個本就是const
變量。
請勿cpoy我的邪惡法師文章,這個為原創,轉載聲明出處
調試過程
在platform.ini
加入
build_flags =
...............
-Wl,-Map=filemap.map
可在項目根目錄下有filemap.map
搜索rom_buffer
可看到:
*fill* 0x00000000402b60a4 0xf5c
*(.rom_buffer.*)
.rom_buffer.AnimateAccSys.cpp.65.0
0x00000000402b7000 0x32000 .pio\build\esp12e\src\AnimateAccSys\AnimateAccSys.cpp.o
0x00000000402e9004 . = ((. + 0x7) & 0xfffffffffffffffc)
*fill* 0x00000000402e9000 0x4
0x00000000402e9004 _irom0_text_end = ABSOLUTE (.)
0x00000000402e9004 _flash_code_end = ABSOLUTE (.)
.rom_buffer.AnimateAccSys.cpp.65.0
在0x00000000402b7000
剛好對齊4096,並且大小為0x32000
恰是申請的Ani_A_cache_buffer
的大小。
也可在代碼中打印該地址:
Serial.printf("Ani_A_cache_buffer address:%p\r\n" , Ani_A_cache_buffer);
測試
相對來說速度正常,理論比spi讀取更快。