FPGA passthrough RAM loading, add graphics stack
This commit is contained in:
parent
1b0acb6ecb
commit
1cdd3e9a4b
9 changed files with 1350 additions and 64 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -25,3 +25,6 @@
|
|||
[submodule "factory_test/components/sdcard"]
|
||||
path = factory_test/components/sdcard
|
||||
url = git@github.com:Nicolai-Electronics/esp32-component-sdcard.git
|
||||
[submodule "factory_test/components/pax-graphics"]
|
||||
path = factory_test/components/pax-graphics
|
||||
url = https://github.com/robotman2412/pax-graphics.git
|
||||
|
|
5
factory_test/components/appfs/CMakeLists.txt
Normal file
5
factory_test/components/appfs/CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
idf_component_register(
|
||||
INCLUDE_DIRS "include"
|
||||
SRCS "appfs.c"
|
||||
REQUIRES spi_flash
|
||||
)
|
781
factory_test/components/appfs/appfs.c
Normal file
781
factory_test/components/appfs/appfs.c
Normal file
|
@ -0,0 +1,781 @@
|
|||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
Theory of operation:
|
||||
An appfs filesystem is meant to store executable applications (=ESP32 programs) alongside other
|
||||
data that is mmap()-able as a contiguous file.
|
||||
|
||||
Appfs does that by making sure the files rigidly adhere to the 64K-page-structure (called a 'sector'
|
||||
in this description) as predicated by the ESP32s MMU. This way, every file can be mmap()'ed into a
|
||||
contiguous region or ran as an ESP32 application. (For the future, maybe: Smaller files can be stored
|
||||
in parts of a 64K page, as long as all are contiguous and none cross any 64K boundaries.
|
||||
What about fragmentation tho?)
|
||||
|
||||
Because of these reasons, only a few operations are available:
|
||||
- Creating a file. This needs the filesize to be known beforehand; a file cannot change size afterwards.
|
||||
- Modifying a file. This follows the same rules as spi_flash_* because it maps directly to the underlying flash.
|
||||
- Deleting a file
|
||||
- Mmap()ping a file
|
||||
This makes the interface to appfs more akin to the partition interface than to a real filesystem.
|
||||
|
||||
At the moment, appfs is not yet tested with encrypted flash; compatibility is unknown.
|
||||
|
||||
Filesystem meta-info is stored using the first sector: there are 2 32K half-sectors there with management
|
||||
info. Each has a serial and a checksum. The sector with the highest serial and a matching checksum is
|
||||
taken as current; the data will ping-pong between the sectors. (And yes, this means the pages in these
|
||||
sectors will be rewritten every time a file is added/removed. Appfs is built with the assumption that
|
||||
it's a mostly store-only filesystem and apps will only change every now and then. The flash chips
|
||||
connected to the ESP32 chips usually can do up to 100.000 erases, so for most purposes the lifetime of
|
||||
the flash with appfs on it exceeds the lifetime of the product.)
|
||||
|
||||
Appfs assumes a partition of 16MiB or less, allowing for 256 128-byte sector descriptors to be stored in
|
||||
the management half-sectors. The first descriptor is a header used for filesystem meta-info.
|
||||
|
||||
Metainfo is stored per sector; each sector descriptor contains a zero-terminated filename (no
|
||||
directories are supported, but '/' is an usable character), the size of the file and a pointer to the
|
||||
next entry for the file if needed. The filename is only set for the first sector; it is all zeroes
|
||||
(actually: ignored) for other entries.
|
||||
|
||||
Integrity of the meta-info is guaranteed: the file system will never be in a state where sectors are
|
||||
lost or anything. Integrity of data is *NOT* guaranteed: on power loss, data may be half-written,
|
||||
contain sectors with only 0xff, and so on. It's up to the user to take care of this. However, files that
|
||||
are not written to do not run the risk of getting corrupted.
|
||||
|
||||
With regards to this code: it is assumed that an ESP32 will only have one appfs on flash, so everything
|
||||
is implemented as a singleton.
|
||||
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <alloca.h>
|
||||
#include "esp32/rom/crc.h"
|
||||
#include "esp32/rom/cache.h"
|
||||
#include "esp_spi_flash.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "appfs.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
|
||||
#if !CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED
|
||||
#error "Appfs will not work with SPI flash dangerous regions checking. Please use 'make menuconfig' to enable writing to dangerous regions."
|
||||
#endif
|
||||
|
||||
static const char *TAG = "appfs";
|
||||
|
||||
|
||||
#define APPFS_SECTOR_SZ SPI_FLASH_MMU_PAGE_SIZE
|
||||
#define APPFS_META_SZ (APPFS_SECTOR_SZ/2)
|
||||
#define APPFS_META_CNT 2
|
||||
#define APPFS_META_DESC_SZ 128
|
||||
#define APPFS_PAGES 255
|
||||
#define APPFS_MAGIC "AppFsDsc"
|
||||
|
||||
#define APPFS_USE_FREE 0xff //No file allocated here
|
||||
#define APPFS_ILLEGAL 0x55 //Sector cannot be used (usually because it's outside the partition)
|
||||
#define APPFS_USE_DATA 0 //Sector is in use for data
|
||||
|
||||
typedef struct __attribute__ ((__packed__)) {
|
||||
uint8_t magic[8]; //must be AppFsDsc
|
||||
uint32_t serial;
|
||||
uint32_t crc32;
|
||||
uint8_t reserved[128-16];
|
||||
} AppfsHeader;
|
||||
|
||||
typedef struct __attribute__ ((__packed__)) {
|
||||
char name[112]; //Only set for 1st sector of file. Rest has name set to 0xFF 0xFF ...
|
||||
uint32_t size; //in bytes
|
||||
uint8_t next; //next page containing the next 64K of the file; 0 if no next page (Because allocation always starts at 0 and pages can't refer to a lower page, 0 can never occur normally)
|
||||
uint8_t used; //one of APPFS_USE_*
|
||||
uint8_t reserved[10];
|
||||
} AppfsPageInfo;
|
||||
|
||||
typedef struct __attribute__ ((__packed__)) {
|
||||
AppfsHeader hdr;
|
||||
AppfsPageInfo page[APPFS_PAGES];
|
||||
} AppfsMeta;
|
||||
|
||||
static int appfsActiveMeta=0; //number of currently active metadata half-sector (0 or 1)
|
||||
static const AppfsMeta *appfsMeta=NULL; //mmap'ed flash
|
||||
#ifndef BOOTLOADER_BUILD
|
||||
static const esp_partition_t *appfsPart=NULL;
|
||||
static spi_flash_mmap_handle_t appfsMetaMmapHandle;
|
||||
#else
|
||||
static uint32_t appfsPartOffset=0;
|
||||
#endif
|
||||
|
||||
|
||||
static int page_in_part(int page) {
|
||||
#ifndef BOOTLOADER_BUILD
|
||||
return ((page+1)*APPFS_SECTOR_SZ < appfsPart->size);
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
//Find active meta half-sector. Updates appfsActiveMeta to the most current one and returns ESP_OK success.
|
||||
//Returns ESP_ERR_NOT_FOUND when no active metasector is found.
|
||||
static esp_err_t findActiveMeta() {
|
||||
int validSec=0; //bitmap of valid sectors
|
||||
uint32_t serial[APPFS_META_CNT]={0};
|
||||
AppfsHeader hdr;
|
||||
for (int sec=0; sec<APPFS_META_CNT; sec++) {
|
||||
//Read header
|
||||
memcpy(&hdr, &appfsMeta[sec].hdr, sizeof(AppfsHeader));
|
||||
if (memcmp(hdr.magic, APPFS_MAGIC, 8)==0) {
|
||||
//Save serial
|
||||
serial[sec]=hdr.serial;
|
||||
//Save and zero CRC
|
||||
uint32_t expectedCrc=hdr.crc32;
|
||||
hdr.crc32=0;
|
||||
uint32_t crc=0;
|
||||
crc=crc32_le(crc, (const uint8_t *)&hdr, APPFS_META_DESC_SZ);
|
||||
for (int j=0; j<APPFS_PAGES; j++) {
|
||||
crc=crc32_le(crc, (const uint8_t *)&appfsMeta[sec].page[j], APPFS_META_DESC_SZ);
|
||||
}
|
||||
if (crc==expectedCrc) {
|
||||
validSec|=(1<<sec);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Meta sector %d does not have a valid CRC (have %X expected %X.", sec, crc, expectedCrc);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Meta sector %d does not have a valid magic header.", sec);
|
||||
}
|
||||
}
|
||||
//Here, validSec should be a bitmap of sectors that are valid, while serials[] should contain their
|
||||
//serials.
|
||||
int best=-1;
|
||||
for (int sec=0; sec<APPFS_META_CNT; sec++) {
|
||||
if (validSec&(1<<sec)) {
|
||||
if (best==-1 || serial[sec]>serial[best]) best=sec;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Meta page 0: %svalid (serial %d)", (validSec&1)?"":"in", serial[0]);
|
||||
ESP_LOGI(TAG, "Meta page 1: %svalid (serial %d)", (validSec&2)?"":"in", serial[1]);
|
||||
|
||||
//'best' here is either still -1 (no valid sector found) or the sector with the highest valid serial.
|
||||
if (best==-1) {
|
||||
ESP_LOGI(TAG, "No valid page found.");
|
||||
//Eek! Nothing found!
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Using page %d as current.", best);
|
||||
}
|
||||
appfsActiveMeta=best;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
static int appfsGetFirstPageFor(const char *filename) {
|
||||
for (int j=0; j<APPFS_PAGES; j++) {
|
||||
if (appfsMeta[appfsActiveMeta].page[j].used==APPFS_USE_DATA && strcmp(appfsMeta[appfsActiveMeta].page[j].name, filename)==0) {
|
||||
return j;
|
||||
}
|
||||
}
|
||||
//Nothing found.
|
||||
return APPFS_INVALID_FD;
|
||||
}
|
||||
|
||||
bool appfsFdValid(int fd) {
|
||||
if (fd<0 || fd>=APPFS_PAGES) return false;
|
||||
if (appfsMeta[appfsActiveMeta].page[(int)fd].used!=APPFS_USE_DATA) return false;
|
||||
if (appfsMeta[appfsActiveMeta].page[(int)fd].name[0]==0xff) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int appfsExists(const char *filename) {
|
||||
return (appfsGetFirstPageFor(filename)==-1)?0:1;
|
||||
}
|
||||
|
||||
appfs_handle_t appfsOpen(const char *filename) {
|
||||
return appfsGetFirstPageFor(filename);
|
||||
}
|
||||
|
||||
void appfsClose(appfs_handle_t handle) {
|
||||
//Not needed in this implementation. Added for possible later use (concurrency?)
|
||||
}
|
||||
|
||||
void appfsEntryInfo(appfs_handle_t fd, const char **name, int *size) {
|
||||
if (name) *name=appfsMeta[appfsActiveMeta].page[fd].name;
|
||||
if (size) *size=appfsMeta[appfsActiveMeta].page[fd].size;
|
||||
}
|
||||
|
||||
appfs_handle_t appfsNextEntry(appfs_handle_t fd) {
|
||||
if (fd==APPFS_INVALID_FD) {
|
||||
fd=0;
|
||||
} else {
|
||||
fd++;
|
||||
}
|
||||
|
||||
if (fd>=APPFS_PAGES || fd<0) return APPFS_INVALID_FD;
|
||||
|
||||
while (appfsMeta[appfsActiveMeta].page[fd].used!=APPFS_USE_DATA || appfsMeta[appfsActiveMeta].page[fd].name[0]==0xff) {
|
||||
fd++;
|
||||
if (fd>=APPFS_PAGES) return APPFS_INVALID_FD;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
size_t appfsGetFreeMem() {
|
||||
size_t ret=0;
|
||||
for (int i=0; i<APPFS_PAGES; i++) {
|
||||
if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_USE_FREE && page_in_part(i)) {
|
||||
ret+=APPFS_SECTOR_SZ;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef BOOTLOADER_BUILD
|
||||
|
||||
#include "bootloader_flash.h"
|
||||
#include "soc/soc.h"
|
||||
#include "soc/cpu.h"
|
||||
#include "soc/rtc.h"
|
||||
#include "soc/dport_reg.h"
|
||||
|
||||
esp_err_t appfsBlInit(uint32_t offset, uint32_t len) {
|
||||
//Compile-time sanity check on size of structs
|
||||
_Static_assert(sizeof(AppfsHeader)==APPFS_META_DESC_SZ, "sizeof AppfsHeader != 128bytes");
|
||||
_Static_assert(sizeof(AppfsPageInfo)==APPFS_META_DESC_SZ, "sizeof AppfsPageInfo != 128bytes");
|
||||
_Static_assert(sizeof(AppfsMeta)==APPFS_META_SZ, "sizeof AppfsMeta != APPFS_META_SZ");
|
||||
//Map meta page
|
||||
appfsMeta=bootloader_mmap(offset, APPFS_SECTOR_SZ);
|
||||
if (!appfsMeta) return ESP_ERR_NOT_FOUND;
|
||||
if (findActiveMeta()!=ESP_OK) {
|
||||
//No valid metadata half-sector found. Initialize the first sector.
|
||||
ESP_LOGE(TAG, "No valid meta info found. Bailing out.");
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
appfsPartOffset=offset;
|
||||
ESP_LOGD(TAG, "Initialized.");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void appfsBlDeinit() {
|
||||
bootloader_munmap(appfsMeta);
|
||||
}
|
||||
|
||||
#define MMU_BLOCK0_VADDR 0x3f400000
|
||||
#define MMU_BLOCK50_VADDR 0x3f720000
|
||||
#define MMU_FLASH_MASK 0xffff0000
|
||||
#define MMU_BLOCK_SIZE 0x00010000
|
||||
|
||||
esp_err_t appfsBlMapRegions(int fd, AppfsBlRegionToMap *regions, int noRegions) {
|
||||
uint8_t pages[255];
|
||||
int pageCt=0;
|
||||
int page=fd;
|
||||
do {
|
||||
pages[pageCt++]=page;
|
||||
page=appfsMeta[appfsActiveMeta].page[page].next;
|
||||
} while (page!=0);
|
||||
//Okay, we have our info.
|
||||
bootloader_munmap(appfsMeta);
|
||||
|
||||
Cache_Read_Disable( 0 );
|
||||
Cache_Flush( 0 );
|
||||
for (int i = 0; i < DPORT_FLASH_MMU_TABLE_SIZE; i++) {
|
||||
DPORT_PRO_FLASH_MMU_TABLE[i] = DPORT_FLASH_MMU_TABLE_INVALID_VAL;
|
||||
}
|
||||
|
||||
for (int i=0; i<noRegions; i++) {
|
||||
uint32_t p=regions[i].fileAddr/APPFS_SECTOR_SZ;
|
||||
uint32_t d=regions[i].mapAddr&~(APPFS_SECTOR_SZ-1);
|
||||
for (uint32_t a=0; a<regions[i].length; a+=APPFS_SECTOR_SZ) {
|
||||
ESP_LOGI(TAG, "Flash mmap seg %d: %X from %X", i, d, appfsPartOffset+((pages[p]+1)*APPFS_SECTOR_SZ));
|
||||
for (int cpu=0; cpu<2; cpu++) {
|
||||
int e = cache_flash_mmu_set(cpu, 0, d, appfsPartOffset+((pages[p]+1)*APPFS_SECTOR_SZ), 64, 1);
|
||||
if (e != 0) {
|
||||
ESP_LOGE(TAG, "cache_flash_mmu_set failed for cpu %d: %d", cpu, e);
|
||||
Cache_Read_Enable(0);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
}
|
||||
d+=APPFS_SECTOR_SZ;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
DPORT_REG_CLR_BIT( DPORT_PRO_CACHE_CTRL1_REG, (DPORT_PRO_CACHE_MASK_IRAM0) | (DPORT_PRO_CACHE_MASK_IRAM1 & 0) | (DPORT_PRO_CACHE_MASK_IROM0 & 0) | DPORT_PRO_CACHE_MASK_DROM0 | DPORT_PRO_CACHE_MASK_DRAM1 );
|
||||
DPORT_REG_CLR_BIT( DPORT_APP_CACHE_CTRL1_REG, (DPORT_APP_CACHE_MASK_IRAM0) | (DPORT_APP_CACHE_MASK_IRAM1 & 0) | (DPORT_APP_CACHE_MASK_IROM0 & 0) | DPORT_APP_CACHE_MASK_DROM0 | DPORT_APP_CACHE_MASK_DRAM1 );
|
||||
Cache_Read_Enable( 0 );
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void* appfsBlMmap(int fd) {
|
||||
//We want to mmap() the pages of the file into memory. However, to do that we need to kill the mmap for the
|
||||
//meta info. To do this, we collect the pages before unmapping the meta info.
|
||||
uint8_t pages[255];
|
||||
int pageCt=0;
|
||||
int page=fd;
|
||||
do {
|
||||
pages[pageCt++]=page;
|
||||
page=appfsMeta[appfsActiveMeta].page[page].next;
|
||||
} while (page!=0);
|
||||
ESP_LOGI(TAG, "File %d has %d pages.", fd, pageCt);
|
||||
|
||||
if (pageCt>50) {
|
||||
ESP_LOGE(TAG, "appfsBlMmap: file too big to mmap");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//Okay, we have our info.
|
||||
bootloader_munmap(appfsMeta);
|
||||
//Bootloader_mmap only allows mapping of one consecutive memory range. We need more than that, so we essentially
|
||||
//replicate the function here.
|
||||
|
||||
Cache_Read_Disable(0);
|
||||
Cache_Flush(0);
|
||||
for (int i=0; i<pageCt; i++) {
|
||||
ESP_LOGI(TAG, "Mapping flash addr %X to mem addr %X for page %d", appfsPartOffset+((pages[i]+1)*APPFS_SECTOR_SZ), MMU_BLOCK0_VADDR+(i*APPFS_SECTOR_SZ), pages[i]);
|
||||
int e = cache_flash_mmu_set(0, 0, MMU_BLOCK0_VADDR+(i*APPFS_SECTOR_SZ),
|
||||
appfsPartOffset+((pages[i]+1)*APPFS_SECTOR_SZ), 64, 1);
|
||||
if (e != 0) {
|
||||
ESP_LOGE(TAG, "cache_flash_mmu_set failed: %d", e);
|
||||
Cache_Read_Enable(0);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
Cache_Read_Enable(0);
|
||||
return (void *)(MMU_BLOCK0_VADDR);
|
||||
}
|
||||
|
||||
void appfsBlMunmap() {
|
||||
/* Full MMU reset */
|
||||
Cache_Read_Disable(0);
|
||||
Cache_Flush(0);
|
||||
mmu_init(0);
|
||||
//Map meta page
|
||||
appfsMeta=bootloader_mmap(appfsPartOffset, APPFS_SECTOR_SZ);
|
||||
}
|
||||
|
||||
#else //so if !BOOTLOADER_BUILD
|
||||
|
||||
//Modifies the header in hdr to the correct crc and writes it to meta info no metano.
|
||||
//Assumes the serial etc is in order already, and the header section for metano has been erased.
|
||||
static esp_err_t writeHdr(AppfsHeader *hdr, int metaNo) {
|
||||
hdr->crc32=0;
|
||||
uint32_t crc=0;
|
||||
crc=crc32_le(crc, (const uint8_t *)hdr, APPFS_META_DESC_SZ);
|
||||
for (int j=0; j<APPFS_PAGES; j++) {
|
||||
crc=crc32_le(crc, (const uint8_t *)&appfsMeta[metaNo].page[j], APPFS_META_DESC_SZ);
|
||||
}
|
||||
hdr->crc32=crc;
|
||||
return esp_partition_write(appfsPart, metaNo*APPFS_META_SZ, hdr, sizeof(AppfsHeader));
|
||||
}
|
||||
|
||||
//Kill all existing filesystem metadata and re-initialize the fs.
|
||||
static esp_err_t initializeFs() {
|
||||
esp_err_t r;
|
||||
//Kill management sector
|
||||
r=esp_partition_erase_range(appfsPart, 0, APPFS_SECTOR_SZ);
|
||||
if (r!=ESP_OK) return r;
|
||||
//All the data pages are now set to 'free'. Add a header that makes the entire mess valid.
|
||||
AppfsHeader hdr;
|
||||
memset(&hdr, 0xff, sizeof(hdr));
|
||||
memcpy(hdr.magic, APPFS_MAGIC, 8);
|
||||
hdr.serial=0;
|
||||
//Mark pages outside of partition as invalid.
|
||||
int lastPage=(appfsPart->size/APPFS_SECTOR_SZ);
|
||||
for (int j=lastPage; j<APPFS_PAGES; j++) {
|
||||
AppfsPageInfo pi;
|
||||
memset(&pi, 0xff, sizeof(pi));
|
||||
pi.used=APPFS_ILLEGAL;
|
||||
r=esp_partition_write(appfsPart, 0*APPFS_META_SZ+(j+1)*APPFS_META_DESC_SZ, &pi, sizeof(pi));
|
||||
if (r!=ESP_OK) return r;
|
||||
}
|
||||
writeHdr(&hdr, 0);
|
||||
ESP_LOGI(TAG, "Re-initialized appfs: %d pages", lastPage);
|
||||
//Officially, we should also write the CRCs... we don't do this here because during the
|
||||
//runtime of this, the CRCs aren't checked and when the device reboots, it'll re-initialize
|
||||
//the fs anyway.
|
||||
appfsActiveMeta=0;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t appfsInit(int type, int subtype) {
|
||||
esp_err_t r;
|
||||
//Compile-time sanity check on size of structs
|
||||
_Static_assert(sizeof(AppfsHeader)==APPFS_META_DESC_SZ, "sizeof AppfsHeader != 128bytes");
|
||||
_Static_assert(sizeof(AppfsPageInfo)==APPFS_META_DESC_SZ, "sizeof AppfsPageInfo != 128bytes");
|
||||
_Static_assert(sizeof(AppfsMeta)==APPFS_META_SZ, "sizeof AppfsMeta != APPFS_META_SZ");
|
||||
//Find the indicated partition
|
||||
appfsPart=esp_partition_find_first(type, subtype, NULL);
|
||||
if (!appfsPart) return ESP_ERR_NOT_FOUND;
|
||||
//Memory map the appfs header so we can Do Stuff with it
|
||||
r=esp_partition_mmap(appfsPart, 0, APPFS_SECTOR_SZ, SPI_FLASH_MMAP_DATA, (const void**)&appfsMeta, &appfsMetaMmapHandle);
|
||||
if (r!=ESP_OK) return r;
|
||||
if (findActiveMeta()!=ESP_OK) {
|
||||
//No valid metadata half-sector found. Initialize the first sector.
|
||||
ESP_LOGE(TAG, "No valid meta info found. Re-initializing fs.");
|
||||
initializeFs();
|
||||
}
|
||||
ESP_LOGD(TAG, "Initialized.");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t writePageInfo(int newMeta, int page, AppfsPageInfo *pi) {
|
||||
return esp_partition_write(appfsPart, newMeta*APPFS_META_SZ+(page+1)*APPFS_META_DESC_SZ, pi, sizeof(AppfsPageInfo));
|
||||
}
|
||||
|
||||
|
||||
//This essentially writes a new meta page without any references to the file indicated.
|
||||
esp_err_t appfsDeleteFile(const char *filename) {
|
||||
esp_err_t r;
|
||||
int next=-1;
|
||||
int newMeta;
|
||||
AppfsHeader hdr;
|
||||
AppfsPageInfo pi;
|
||||
//See if we actually need to do something
|
||||
if (!appfsExists(filename)) return 0;
|
||||
//Create a new management sector
|
||||
newMeta=(appfsActiveMeta+1)%APPFS_META_CNT;
|
||||
r=esp_partition_erase_range(appfsPart, newMeta*APPFS_META_SZ, APPFS_META_SZ);
|
||||
if (r!=ESP_OK) return r;
|
||||
//Prepare header
|
||||
memcpy(&hdr, &appfsMeta[appfsActiveMeta].hdr, sizeof(hdr));
|
||||
hdr.serial++;
|
||||
hdr.crc32=0;
|
||||
for (int j=0; j<APPFS_PAGES; j++) {
|
||||
int needDelete=0;
|
||||
//Grab old page info from current meta sector
|
||||
memcpy(&pi, &appfsMeta[appfsActiveMeta].page[j], sizeof(pi));
|
||||
if (next==-1) {
|
||||
if (pi.used==APPFS_USE_DATA && strcmp(pi.name, filename)==0) {
|
||||
needDelete=1;
|
||||
next=pi.next;
|
||||
}
|
||||
} else if (next==0) {
|
||||
//File is killed entirely. No need to look for anything.
|
||||
} else {
|
||||
//Look for next sector of file
|
||||
if (j==next) {
|
||||
needDelete=1;
|
||||
next=pi.next;
|
||||
}
|
||||
}
|
||||
if (needDelete) {
|
||||
//Page info is 0xff anyway. No need to explicitly write that.
|
||||
} else {
|
||||
r=writePageInfo(newMeta, j, &pi);
|
||||
if (r!=ESP_OK) return r;
|
||||
}
|
||||
}
|
||||
r=writeHdr(&hdr, newMeta);
|
||||
appfsActiveMeta=newMeta;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
//This essentially writes a new meta page with the name changed.
|
||||
//If a file is found with the name, we also delete it.
|
||||
esp_err_t appfsRename(const char *from, const char *to) {
|
||||
esp_err_t r;
|
||||
int newMeta;
|
||||
AppfsHeader hdr;
|
||||
AppfsPageInfo pi;
|
||||
//See if we actually need to do something
|
||||
if (!appfsExists(from)) return ESP_FAIL;
|
||||
//Create a new management sector
|
||||
newMeta=(appfsActiveMeta+1)%APPFS_META_CNT;
|
||||
r=esp_partition_erase_range(appfsPart, newMeta*APPFS_META_SZ, APPFS_META_SZ);
|
||||
if (r!=ESP_OK) return r;
|
||||
//Prepare header
|
||||
memcpy(&hdr, &appfsMeta[appfsActiveMeta].hdr, sizeof(hdr));
|
||||
hdr.serial++;
|
||||
hdr.crc32=0;
|
||||
int nextDelete=-1;
|
||||
for (int j=0; j<APPFS_PAGES; j++) {
|
||||
//Grab old page info from current meta sector
|
||||
memcpy(&pi, &appfsMeta[appfsActiveMeta].page[j], sizeof(pi));
|
||||
int needDelete=0;
|
||||
if (nextDelete==-1 && pi.used==APPFS_USE_DATA && strcmp(pi.name, to)==0) {
|
||||
//First page of the dest file. We need to delete this!
|
||||
nextDelete=pi.next;
|
||||
needDelete=1;
|
||||
} else if (nextDelete==j) {
|
||||
//A page in the file to be deleted.
|
||||
nextDelete=pi.next;
|
||||
needDelete=1;
|
||||
} else if (pi.used==APPFS_USE_DATA && strcmp(pi.name, from)==0) {
|
||||
//Found old name. Rename to new.
|
||||
strncpy(pi.name, to, sizeof(pi.name));
|
||||
pi.name[sizeof(pi.name)-1]=0;
|
||||
}
|
||||
//If hdr needs deletion, leave it at 0xfffff...
|
||||
if (!needDelete) {
|
||||
r=writePageInfo(newMeta, j, &pi);
|
||||
if (r!=ESP_OK) return r;
|
||||
}
|
||||
}
|
||||
r=writeHdr(&hdr, newMeta);
|
||||
appfsActiveMeta=newMeta;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
//Allocate space for a new file. Will kill any existing files if needed.
|
||||
//Warning: may kill old file but not create new file if new file won't fit on fs, even with old file removed.
|
||||
//ToDo: in that case, fail before deleting file.
|
||||
esp_err_t appfsCreateFile(const char *filename, size_t size, appfs_handle_t *handle) {
|
||||
esp_err_t r;
|
||||
//If there are any references to this file, kill 'em.
|
||||
appfsDeleteFile(filename);
|
||||
ESP_LOGD(TAG, "Creating new file '%s'", filename);
|
||||
|
||||
//Figure out what pages to reserve for the file, and the next link structure.
|
||||
uint8_t nextForPage[APPFS_PAGES]; //Next page if used for file, APPFS_PAGES if not
|
||||
//Mark all pages as unused for file.
|
||||
for (int j=0; j<APPFS_PAGES; j++) nextForPage[j]=APPFS_PAGES;
|
||||
//Find pages where we can store data for the file.
|
||||
int first=-1, prev=-1;
|
||||
int sizeLeft=size;
|
||||
for (int j=0; j<APPFS_PAGES; j++) {
|
||||
if (appfsMeta[appfsActiveMeta].page[j].used==APPFS_USE_FREE && page_in_part(j)) {
|
||||
ESP_LOGD(TAG, "Using page %d...", j);
|
||||
if (prev==-1) {
|
||||
first=j; //first free page; save to store name here.
|
||||
} else {
|
||||
nextForPage[prev]=j; //mark prev page to go here
|
||||
}
|
||||
nextForPage[j]=0; //end of file... for now.
|
||||
prev=j;
|
||||
sizeLeft-=APPFS_SECTOR_SZ;
|
||||
if (sizeLeft<=0) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (sizeLeft>0) {
|
||||
//Eek! Can't allocate enough space!
|
||||
ESP_LOGD(TAG, "Not enough free space!");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
//Re-write a new meta page but with file allocated
|
||||
int newMeta=(appfsActiveMeta+1)%APPFS_META_CNT;
|
||||
ESP_LOGD(TAG, "Re-writing meta data to meta page %d...", newMeta);
|
||||
r=esp_partition_erase_range(appfsPart, newMeta*APPFS_META_SZ, APPFS_META_SZ);
|
||||
if (r!=ESP_OK) return r;
|
||||
//Prepare header
|
||||
AppfsHeader hdr;
|
||||
memcpy(&hdr, &appfsMeta[appfsActiveMeta].hdr, sizeof(hdr));
|
||||
hdr.serial++;
|
||||
hdr.crc32=0;
|
||||
for (int j=0; j<APPFS_PAGES; j++) {
|
||||
AppfsPageInfo pi;
|
||||
if (nextForPage[j]!=APPFS_PAGES) {
|
||||
//This is part of the file. Rewrite page data to indicate this.
|
||||
memset(&pi, 0xff, sizeof(pi));
|
||||
if (j==first) {
|
||||
//First page. Copy name and size.
|
||||
strcpy(pi.name, filename);
|
||||
pi.size=size;
|
||||
}
|
||||
pi.used=APPFS_USE_DATA;
|
||||
pi.next=nextForPage[j];
|
||||
} else {
|
||||
//Grab old page info from current meta sector
|
||||
memcpy(&pi, &appfsMeta[appfsActiveMeta].page[j], sizeof(pi));
|
||||
}
|
||||
if (pi.used!=APPFS_USE_FREE) {
|
||||
r=writePageInfo(newMeta, j, &pi);
|
||||
if (r!=ESP_OK) return r;
|
||||
}
|
||||
}
|
||||
//Write header and make active.
|
||||
r=writeHdr(&hdr, newMeta);
|
||||
appfsActiveMeta=newMeta;
|
||||
if (handle) *handle=first;
|
||||
ESP_LOGD(TAG, "Re-writing meta data done.");
|
||||
return r;
|
||||
}
|
||||
|
||||
esp_err_t appfsMmap(appfs_handle_t fd, size_t offset, size_t len, const void** out_ptr,
|
||||
spi_flash_mmap_memory_t memory, spi_flash_mmap_handle_t* out_handle) {
|
||||
esp_err_t r;
|
||||
int page=(int)fd;
|
||||
if (!appfsFdValid(page)) return ESP_ERR_NOT_FOUND;
|
||||
ESP_LOGD(TAG, "Mmapping file %s, offset %d, size %d", appfsMeta[appfsActiveMeta].page[page].name, offset, len);
|
||||
if (appfsMeta[appfsActiveMeta].page[page].size < (offset+len)) {
|
||||
ESP_LOGD(TAG, "Can't map file: trying to map byte %d in file of len %d\n", (offset+len), appfsMeta[appfsActiveMeta].page[page].size);
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
int dataStartPage=(appfsPart->address/SPI_FLASH_MMU_PAGE_SIZE)+1;
|
||||
while (offset >= APPFS_SECTOR_SZ) {
|
||||
page=appfsMeta[appfsActiveMeta].page[page].next;
|
||||
offset-=APPFS_SECTOR_SZ;
|
||||
ESP_LOGD(TAG, "Skipping a page (to page %d), remaining offset 0x%X", page, offset);
|
||||
}
|
||||
|
||||
int *pages=alloca(sizeof(int)*((len/APPFS_SECTOR_SZ)+1));
|
||||
int nopages=0;
|
||||
size_t mappedlen=0;
|
||||
while(len>mappedlen) {
|
||||
pages[nopages++]=page+dataStartPage;
|
||||
ESP_LOGD(TAG, "Mapping page %d (part offset %d).", page, dataStartPage);
|
||||
page=appfsMeta[appfsActiveMeta].page[page].next;
|
||||
mappedlen+=APPFS_SECTOR_SZ;
|
||||
}
|
||||
|
||||
r=spi_flash_mmap_pages(pages, nopages, memory, out_ptr, out_handle);
|
||||
if (r!=ESP_OK) {
|
||||
ESP_LOGD(TAG, "Can't map file: pi_flash_mmap_pages returned %d\n", r);
|
||||
return r;
|
||||
}
|
||||
*out_ptr=((uint8_t*)*out_ptr)+offset;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void appfsMunmap(spi_flash_mmap_handle_t handle) {
|
||||
spi_flash_munmap(handle);
|
||||
}
|
||||
|
||||
//Just mmaps and memcpys the data. Maybe not the fastest ever, but hey, if you want that you should mmap
|
||||
//and read from the flash cache memory area yourself.
|
||||
esp_err_t appfsRead(appfs_handle_t fd, size_t start, void *buf, size_t len) {
|
||||
const void *flash;
|
||||
spi_flash_mmap_handle_t handle;
|
||||
esp_err_t r=appfsMmap(fd, start, len, &flash, SPI_FLASH_MMAP_DATA, &handle);
|
||||
if (r!=ESP_OK) return r;
|
||||
memcpy(buf, flash, len);
|
||||
spi_flash_munmap(handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t appfsErase(appfs_handle_t fd, size_t start, size_t len) {
|
||||
esp_err_t r;
|
||||
int page=(int)fd;
|
||||
if (!appfsFdValid(page)) return ESP_ERR_NOT_FOUND;
|
||||
//Bail out if trying to erase past the file end.
|
||||
//Allow erases past the end of the file but still within the page reserved for the file.
|
||||
int roundedSize=(appfsMeta[appfsActiveMeta].page[page].size+(APPFS_SECTOR_SZ-1))&(~(APPFS_SECTOR_SZ-1));
|
||||
if (roundedSize < (start+len)) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
//Find initial page
|
||||
while (start >= APPFS_SECTOR_SZ) {
|
||||
page=appfsMeta[appfsActiveMeta].page[page].next;
|
||||
start-=APPFS_SECTOR_SZ;
|
||||
}
|
||||
//Page now is the initial page. Start is the offset into the page we need to start at.
|
||||
|
||||
while (len>0) {
|
||||
size_t size=len;
|
||||
//Make sure we do not go over a page boundary
|
||||
if ((size+start)>APPFS_SECTOR_SZ) size=APPFS_SECTOR_SZ-start;
|
||||
ESP_LOGD(TAG, "Erasing page %d offset 0x%X size 0x%X", page, start, size);
|
||||
r=esp_partition_erase_range(appfsPart, (page+1)*APPFS_SECTOR_SZ+start, size);
|
||||
if (r!=ESP_OK) return r;
|
||||
page=appfsMeta[appfsActiveMeta].page[page].next;
|
||||
len-=size;
|
||||
start=0; //offset is not needed anymore
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t appfsWrite(appfs_handle_t fd, size_t start, uint8_t *buf, size_t len) {
|
||||
esp_err_t r;
|
||||
int page=(int)fd;
|
||||
if (!appfsFdValid(page)) return ESP_ERR_NOT_FOUND;
|
||||
if (appfsMeta[appfsActiveMeta].page[page].size < (start+len)) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
while (start > APPFS_SECTOR_SZ) {
|
||||
page=appfsMeta[appfsActiveMeta].page[page].next;
|
||||
start-=APPFS_SECTOR_SZ;
|
||||
}
|
||||
while (len>0) {
|
||||
size_t size=len;
|
||||
if (size+start>APPFS_SECTOR_SZ) size=APPFS_SECTOR_SZ-start;
|
||||
ESP_LOGD(TAG, "Writing to page %d offset %d size %d", page, start, size);
|
||||
r=esp_partition_write(appfsPart, (page+1)*APPFS_SECTOR_SZ+start, buf, size);
|
||||
if (r!=ESP_OK) return r;
|
||||
page=appfsMeta[appfsActiveMeta].page[page].next;
|
||||
len-=size;
|
||||
buf+=size;
|
||||
start=0;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void appfsDump() {
|
||||
printf("AppFsDump: ..=free XX=illegal no=next page\n");
|
||||
for (int i=0; i<16; i++) printf("%02X-", i);
|
||||
printf("\n");
|
||||
for (int i=0; i<APPFS_PAGES; i++) {
|
||||
if (!page_in_part(i)) {
|
||||
printf(" ");
|
||||
} else if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_USE_FREE) {
|
||||
printf("..");
|
||||
} else if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_USE_DATA) {
|
||||
printf("%02X", appfsMeta[appfsActiveMeta].page[i].next);
|
||||
} else if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_ILLEGAL) {
|
||||
printf("XX");
|
||||
} else {
|
||||
printf("??");
|
||||
}
|
||||
if ((i&15)==15) {
|
||||
printf("\n");
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
for (int i=0; i<APPFS_PAGES; i++) {
|
||||
if (appfsMeta[appfsActiveMeta].page[i].used==APPFS_USE_DATA && appfsMeta[appfsActiveMeta].page[i].name[0]!=0xff) {
|
||||
printf("File %s starts at page %d\n", appfsMeta[appfsActiveMeta].page[i].name, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
esp_err_t appfsGetCurrentApp(appfs_handle_t *ret_app) {
|
||||
//Grab offset of this function in appfs
|
||||
size_t phys_offs = spi_flash_cache2phys(appfsGetCurrentApp);
|
||||
phys_offs -= appfsPart->address;
|
||||
|
||||
int page=(phys_offs/APPFS_SECTOR_SZ)-1;
|
||||
if (page<0 || page>=APPFS_PAGES) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
//Find first sector for this page.
|
||||
int tries=APPFS_PAGES; //make sure this loop always exits
|
||||
while (appfsMeta[appfsActiveMeta].page[page].name[0]==0xff) {
|
||||
int i;
|
||||
for (i=0; i<APPFS_PAGES; i++) {
|
||||
if (appfsMeta[appfsActiveMeta].page[i].next==page) {
|
||||
page=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//See if what we have still makes sense.
|
||||
if (tries==0 || i>=APPFS_PAGES) return ESP_ERR_NOT_FOUND;
|
||||
tries--;
|
||||
}
|
||||
|
||||
//Okay, found!
|
||||
*ret_app=page;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif
|
9
factory_test/components/appfs/component.mk
Normal file
9
factory_test/components/appfs/component.mk
Normal file
|
@ -0,0 +1,9 @@
|
|||
#
|
||||
# Component Makefile. By default,
|
||||
# this will take the sources in this directory, compile them and link them into
|
||||
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
|
||||
# please read the SDK documents if you need to do this.
|
||||
#
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
|
289
factory_test/components/appfs/include/appfs.h
Normal file
289
factory_test/components/appfs/include/appfs.h
Normal file
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* ----------------------------------------------------------------------------
|
||||
* "THE BEER-WARE LICENSE" (Revision 42):
|
||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
||||
* ----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include <stdint.h>
|
||||
#include "esp_spi_flash.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define APPFS_PART_TYPE 0x43 /*<! Default partition type of an appfs partition */
|
||||
#define APPFS_PART_SUBTYPE 0x3 /*<! Default partition subtype of an appfs partition */
|
||||
|
||||
typedef int appfs_handle_t;
|
||||
|
||||
#define APPFS_INVALID_FD -1 /*<! Some functions return this to indicate an error situation */
|
||||
|
||||
/**
|
||||
* @brief Initialize the appfs code and mount the appfs partition.
|
||||
*
|
||||
* Run this before using any of the other AppFs APIs
|
||||
*
|
||||
* @param type Partition type. Normally you'd pass APPFS_PART_TYPE here.
|
||||
* @param subtype Partition subtype. Normally you'd pass APPFS_PART_SUBTYPE here.
|
||||
* @return ESP_OK if all OK, an error from the underlying partition or flash code otherwise.
|
||||
*/
|
||||
esp_err_t appfsInit(int type, int subtype);
|
||||
|
||||
/**
|
||||
* @brief Check if a file with the given filename exists.
|
||||
*
|
||||
* @param filename Filename to check
|
||||
* @return 1 if a file with a name which exactly matches filename exists; 0 otherwise.
|
||||
*/
|
||||
int appfsExists(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Check if a file descriptor is valid
|
||||
*
|
||||
* Because file descriptors are integers which are more-or-less valid over multiple sessions, they can be stored
|
||||
* in non-volatile memory and re-used later. When doing this, a sanity check to see if the fd still
|
||||
* points at something valid may be useful. This function provides that sanity check.
|
||||
*
|
||||
* @param fd File descriptor to check
|
||||
* @return True if fd points to a valid file, false otherwise.
|
||||
*/
|
||||
bool appfsFdValid(int fd);
|
||||
|
||||
/**
|
||||
* @brief Open a file on a mounted appfs
|
||||
*
|
||||
* @param filename Filename of the file to open
|
||||
* @return The filedescriptor if succesful, APPFS_INVALID_FD if not. */
|
||||
appfs_handle_t appfsOpen(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Close a file on a mounted appfs
|
||||
*
|
||||
* @note In the current appfs implementation, this is a no-op. This may change in the future, however.
|
||||
* @param handle File descriptor to close
|
||||
*/
|
||||
void appfsClose(appfs_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Delete a file on the appfs
|
||||
*
|
||||
* @param filename Name of the file to delete
|
||||
* @return ESP_OK if file successfully deleted, an error otherwise.
|
||||
*/
|
||||
esp_err_t appfsDeleteFile(const char *filename);
|
||||
|
||||
/**
|
||||
* @brief Create a new file on the appfs
|
||||
*
|
||||
* Initially, the file will have random contents consisting of whatever used the sectors of
|
||||
* flash it occupies earlier. Note that this function also opens the file and returns a file
|
||||
* descriptor to it if succesful; no need for a separate appfsOpen call.
|
||||
*
|
||||
* @param filename Name of the file to be created
|
||||
* @param size Size of the file, in bytes
|
||||
* @param handle Pointer to an appfs_handle_t which will store the file descriptor of the created file
|
||||
* @return ESP_OK if file successfully deleted, an error otherwise.
|
||||
*/
|
||||
esp_err_t appfsCreateFile(const char *filename, size_t size, appfs_handle_t *handle);
|
||||
|
||||
/**
|
||||
* @brief Map a file into memory
|
||||
*
|
||||
* This maps a (portion of a) file into memory, where you can access it as if it was an array of bytes in RAM.
|
||||
* This uses the MMU and flash cache of the ESP32 to accomplish this effect. The memory is read-only; trying
|
||||
* to write to it will cause an exception.
|
||||
*
|
||||
* @param fd File descriptor of the file to map.
|
||||
* @param offset Offset into the file where the map starts
|
||||
* @param len Lenght of the map
|
||||
* @param out_ptr Pointer to a const void* variable where, if successful, a pointer to the memory is stored.
|
||||
* @param memory One of SPI_FLASH_MMAP_DATA or SPI_FLASH_MMAP_INST, where the former does a map to data memory
|
||||
* and the latter a map to instruction memory. You'd normally use the first option.
|
||||
* @param out_handle Pointer to a spi_flash_mmap_handle_t variable. This variable is needed to later free the
|
||||
* map again.
|
||||
* @return ESP_OK if file successfully deleted, an error otherwise.
|
||||
*/
|
||||
esp_err_t appfsMmap(appfs_handle_t fd, size_t offset, size_t len, const void** out_ptr,
|
||||
spi_flash_mmap_memory_t memory, spi_flash_mmap_handle_t* out_handle);
|
||||
|
||||
/**
|
||||
* @brief Unmap a previously mmap'ped file
|
||||
*
|
||||
* This unmaps a region previously mapped with appfsMmap
|
||||
*
|
||||
* @param handle Handle obtained in the previous appfsMmap call
|
||||
*/
|
||||
void appfsMunmap(spi_flash_mmap_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Erase a portion of an appfs file
|
||||
*
|
||||
* This sets all bits in the region to be erased to 1, so an appfsWrite can reset selected bits to 0 again.
|
||||
*
|
||||
* @param fd File descriptor of file to erase in.
|
||||
* @param start Start offset of file portion to be erased. Must be aligned to 4KiB.
|
||||
* @param len Length of file portion to be erased. Must be a multiple of 4KiB.
|
||||
* @return ESP_OK if file successfully deleted, an error otherwise.
|
||||
*/
|
||||
esp_err_t appfsErase(appfs_handle_t fd, size_t start, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Write to a file in appfs
|
||||
*
|
||||
* Note: Because this maps directly to a write of the underlying flash memory, this call is only able to
|
||||
* reset bits in the written area from 1 to 0. If you want to change bits from 0 to 1, call appfsErase on
|
||||
* the area to be written before calling this function. This function will return success even if the data
|
||||
* in flash is not the same as the data in the buffer due to bits being 1 in the buffer but 0 on flash.
|
||||
*
|
||||
* If the above paragraph is confusing, just remember to erase a region before you write to it.
|
||||
*
|
||||
* @param fd File descriptor of file to write to
|
||||
* @param start Offset into file to start writing
|
||||
* @param buf Buffer of bytes to write
|
||||
* @param len Length, in bytes, of data to be written
|
||||
* @return ESP_OK if write was successful, an error otherwise.
|
||||
*/
|
||||
esp_err_t appfsWrite(appfs_handle_t fd, size_t start, uint8_t *buf, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Read a portion of a file in appfs
|
||||
*
|
||||
* This function reads ``len`` bytes of file data, starting from offset ``start``, into the buffer
|
||||
* ``buf``. Note that if you do many reads, it usually is more efficient to map the file into memory and
|
||||
* read from it that way instead.
|
||||
*
|
||||
* @param fd File descriptor of the file
|
||||
* @param start Offset in the file to start reading from
|
||||
* @param buf Buffer to contain the read data
|
||||
* @param len Length, in bytes, of the data to read
|
||||
* @return ESP_OK if read was successful, an error otherwise.
|
||||
*/
|
||||
esp_err_t appfsRead(appfs_handle_t fd, size_t start, void *buf, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Atomically rename a file
|
||||
*
|
||||
* This atomically renames a file. If a file with the target name already exists, it will be deleted. This action
|
||||
* is done atomically, so at any point in time, either the original or the new file will fully exist under the target name.
|
||||
*
|
||||
* @param from Original name of file
|
||||
* @param to Target name of file
|
||||
* @return ESP_OK if rename was successful, an error otherwise.
|
||||
*/
|
||||
esp_err_t appfsRename(const char *from, const char *to);
|
||||
|
||||
/**
|
||||
* @brief Get file information
|
||||
*
|
||||
* Given a file descriptor, this returns the name and size of the file. The file descriptor needs
|
||||
* to be valid for this to work.
|
||||
*
|
||||
* @param fd File descriptor
|
||||
* @param name Pointer to a char pointer. This will be pointed at the filename in memory. There is no need
|
||||
* to free the pointer memory afterwards. Pointer memory is valid while the file exists / is not
|
||||
* deleted. Can be NULL if name information is not wanted.
|
||||
* @param size Pointer to an int where the size of the file will be written, or NULL if this information is
|
||||
* not wanted.
|
||||
*/
|
||||
void appfsEntryInfo(appfs_handle_t fd, const char **name, int *size);
|
||||
|
||||
/**
|
||||
* brief Get the next entry in the appfs.
|
||||
*
|
||||
* This function can be used to list all the files existing in the appfs. Pass it APPFS_INVALID_FD when
|
||||
* calling it for the first time to receive the first file descriptor. Pass it the result of the previous call
|
||||
* to get the next file descriptor. When this function returns APPFS_INVALID_FD, all files have been enumerated.
|
||||
* You can use ``appfsEntryInfo()`` to get the file name and size associated with the returned file descriptors
|
||||
*
|
||||
* @param fd File descriptor returned by previous call, or APPFS_INVALID_FD to get the first file descriptor
|
||||
* @return Next file descriptor, or APPFS_INVALID_FD if all files have been enumerated
|
||||
*/
|
||||
appfs_handle_t appfsNextEntry(appfs_handle_t fd);
|
||||
|
||||
/**
|
||||
* @brief Get file descriptor of currently running app.
|
||||
*
|
||||
* @param ret_app Pointer to variable to hold the file descriptor
|
||||
* @return ESP_OK on success, an error when e.g. the currently running code isn't located in appfs.
|
||||
*/
|
||||
esp_err_t appfsGetCurrentApp(appfs_handle_t *ret_app);
|
||||
|
||||
/**
|
||||
* @brief Get amount of free space in appfs partition
|
||||
*
|
||||
* @return amount of free space, in bytes
|
||||
*/
|
||||
size_t appfsGetFreeMem();
|
||||
|
||||
/**
|
||||
* @brief Debugging function: dump current appfs state
|
||||
*
|
||||
* Prints state of the appfs to stdout.
|
||||
*/
|
||||
void appfsDump();
|
||||
|
||||
|
||||
#ifdef BOOTLOADER_BUILD
|
||||
#include "bootloader_flash.h"
|
||||
|
||||
/**
|
||||
* @brief Appfs bootloader support: struct to hold a region of a file to map
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t fileAddr; /*<! Offset in file */
|
||||
uint32_t mapAddr; /*<! Address to map to */
|
||||
uint32_t length; /*<! Length of region */
|
||||
} AppfsBlRegionToMap;
|
||||
|
||||
/**
|
||||
* @brief Bootloader only: initialize appfs
|
||||
*
|
||||
* @param offset Offset, in bytes, in flash of the appfs partition
|
||||
* @param len Length, in bytes, of appfs partition
|
||||
*/
|
||||
esp_err_t appfsBlInit(uint32_t offset, uint32_t len);
|
||||
|
||||
/**
|
||||
* @brief Bootloader only: de-init appfs
|
||||
*/
|
||||
void appfsBlDeinit();
|
||||
/**
|
||||
* @brief Bootloader only: Map an entire appfs file into memory
|
||||
*
|
||||
* Note that only one file can be mapped at a time, and that between a
|
||||
* appfsBlMmap and appfsBlMunmap call, the appfs is in a state where the appfs meta information
|
||||
* is unmapped, meaning other appfs functions cannot be used.
|
||||
*
|
||||
* @param fd File descriptor to map
|
||||
* @return pointer to the mapped file
|
||||
*/
|
||||
void* appfsBlMmap(int fd);
|
||||
|
||||
/**
|
||||
* @brief Bootloader only: Un-mmap a file
|
||||
*/
|
||||
void appfsBlMunmap();
|
||||
|
||||
/*
|
||||
* @brief Bootloader only: map multiple regions within a file to various memory addressed.
|
||||
*
|
||||
* Used to load an app into memory for later execution.
|
||||
*
|
||||
* @param fd File descriptor to be mapped in
|
||||
* @param regions An array of region descriptors
|
||||
* @param noRegions Amount of regions to map
|
||||
* @return ESP_OK on success, an error when the map failed.
|
||||
*/
|
||||
esp_err_t appfsBlMapRegions(int fd, AppfsBlRegionToMap *regions, int noRegions);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
1
factory_test/components/pax-graphics
Submodule
1
factory_test/components/pax-graphics
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 034870cf736970ad12bcfecbb6af492509428035
|
|
@ -10,38 +10,46 @@
|
|||
#include "hardware.h"
|
||||
#include "bitstream2.h"
|
||||
#include "managed_i2c.h"
|
||||
#include "pax_gfx.h"
|
||||
#include "sdcard.h"
|
||||
#include "appfs.h"
|
||||
|
||||
static const char *TAG = "main";
|
||||
|
||||
bool calibrate = true;
|
||||
bool display_bno_value = true;
|
||||
bool display_bno_value = false;
|
||||
ILI9341* ili9341 = NULL;
|
||||
ICE40* ice40 = NULL;
|
||||
BNO055* bno055 = NULL;
|
||||
|
||||
bno055_vector_t rotation_offset = {.x = 0, .y = 0, .z = 0};
|
||||
|
||||
bno055_vector_t acceleration, magnetism, orientation, rotation, linear_acceleration, gravity;
|
||||
|
||||
void button_handler(uint8_t pin, bool value) {
|
||||
switch(pin) {
|
||||
case PCA9555_PIN_BTN_START: {
|
||||
printf("Start button %s\n", value ? "pressed" : "released");
|
||||
if (value) {
|
||||
/*if (value) {
|
||||
esp_err_t res = ice40_load_bitstream(ice40, proto2_bin, proto2_bin_len);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to program the FPGA (%d)", res);
|
||||
} else {
|
||||
printf("FPGA enabled\n");
|
||||
}
|
||||
}
|
||||
}*/
|
||||
break;
|
||||
}
|
||||
case PCA9555_PIN_BTN_SELECT: {
|
||||
printf("Select button %s\n", value ? "pressed" : "released");
|
||||
if (value) {
|
||||
/*if (value) {
|
||||
esp_err_t res = ice40_disable(ice40);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to disable the FPGA (%d)", res);
|
||||
} else {
|
||||
printf("FPGA disabled\n");
|
||||
}
|
||||
}
|
||||
}*/
|
||||
break;
|
||||
}
|
||||
case PCA9555_PIN_BTN_MENU:
|
||||
|
@ -117,16 +125,14 @@ void restart() {
|
|||
esp_restart();
|
||||
}
|
||||
|
||||
void bno055_task(BNO055* bno055, bno055_vector_t* rotation_offset) {
|
||||
void bno055_task(BNO055* bno055) {
|
||||
esp_err_t res;
|
||||
|
||||
bno055_vector_t acceleration, magnetism, orientation, rotation, linear_acceleration, gravity;
|
||||
|
||||
/*res = bno055_get_vector(bno055, BNO055_VECTOR_ACCELEROMETER, &acceleration);
|
||||
res = bno055_get_vector(bno055, BNO055_VECTOR_ACCELEROMETER, &acceleration);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Acceleration failed to read %d\n", res);
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
|
||||
res = bno055_get_vector(bno055, BNO055_VECTOR_MAGNETOMETER, &magnetism);
|
||||
if (res != ESP_OK) {
|
||||
|
@ -134,11 +140,11 @@ void bno055_task(BNO055* bno055, bno055_vector_t* rotation_offset) {
|
|||
return;
|
||||
}
|
||||
|
||||
/*res = bno055_get_vector(bno055, BNO055_VECTOR_GYROSCOPE, &orientation);
|
||||
res = bno055_get_vector(bno055, BNO055_VECTOR_GYROSCOPE, &orientation);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Orientation failed to read %d\n", res);
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
|
||||
res = bno055_get_vector(bno055, BNO055_VECTOR_EULER, &rotation);
|
||||
if (res != ESP_OK) {
|
||||
|
@ -146,7 +152,7 @@ void bno055_task(BNO055* bno055, bno055_vector_t* rotation_offset) {
|
|||
return;
|
||||
}
|
||||
|
||||
/*res = bno055_get_vector(bno055, BNO055_VECTOR_LINEARACCEL, &linear_acceleration);
|
||||
res = bno055_get_vector(bno055, BNO055_VECTOR_LINEARACCEL, &linear_acceleration);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Linear acceleration failed to read %d\n", res);
|
||||
return;
|
||||
|
@ -156,18 +162,22 @@ void bno055_task(BNO055* bno055, bno055_vector_t* rotation_offset) {
|
|||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Gravity failed to read %d\n", res);
|
||||
return;
|
||||
}*/
|
||||
}
|
||||
|
||||
if (calibrate) {
|
||||
rotation_offset->x = rotation.x;
|
||||
rotation_offset->y = rotation.y;
|
||||
rotation_offset->z = rotation.z;
|
||||
/*if (calibrate) {
|
||||
rotation_offset.x = rotation.x;
|
||||
rotation_offset.y = rotation.y;
|
||||
rotation_offset.z = rotation.z;
|
||||
calibrate = false;
|
||||
}
|
||||
|
||||
rotation.x -= rotation_offset->x;
|
||||
rotation.y -= rotation_offset->y;
|
||||
rotation.z -= rotation_offset->z;
|
||||
rotation.x -= rotation_offset.x;
|
||||
rotation.y -= rotation_offset.y;
|
||||
rotation.z -= rotation_offset.z;
|
||||
|
||||
if (rotation.x < 0) rotation.x = 360.0 - rotation.x;
|
||||
if (rotation.y < 0) rotation.y = 360.0 - rotation.y;
|
||||
if (rotation.z < 0) rotation.z = 360.0 - rotation.z;*/
|
||||
|
||||
/*printf("\n\n");
|
||||
printf("Acceleration (m/s²) x = %5.8f y = %5.8f z = %5.8f\n", acceleration.x, acceleration.y, acceleration.z);
|
||||
|
@ -182,36 +192,36 @@ void bno055_task(BNO055* bno055, bno055_vector_t* rotation_offset) {
|
|||
}
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
esp_err_t res;
|
||||
esp_err_t graphics_task(pax_buf_t* buffer, ILI9341* ili9341, uint8_t* framebuffer) {
|
||||
uint64_t millis = esp_timer_get_time() / 1000;
|
||||
pax_background(buffer, 0x000000);
|
||||
pax_col_t color0 = pax_col_hsv(millis * 255 / 8000, 255, 255);
|
||||
pax_col_t color1 = pax_col_hsv(millis * 255 / 8000 + 127, 255, 255);
|
||||
float a0 = 0;//millis / 3000.0 * M_PI;
|
||||
printf("a0 = %f (%f)\r\n", a0, rotation.y);
|
||||
float a1 = rotation.y * (M_PI / 180.0);//fmodf(a0, M_PI * 4) - M_PI * 2;
|
||||
pax_draw_arc(buffer, color0, 0, 0, 1, a0, a0 + a1);
|
||||
pax_push_2d(buffer);
|
||||
|
||||
res = hardware_init();
|
||||
if (res != ESP_OK) {
|
||||
printf("Failed to initialize hardware!\n");
|
||||
restart();
|
||||
}
|
||||
pax_apply_2d(buffer, matrix_2d_rotate(a0));
|
||||
pax_push_2d(buffer);
|
||||
pax_apply_2d(buffer, matrix_2d_translate(1, 0));
|
||||
pax_draw_rect(buffer, color1, -0.25, -0.25, 0.5, 0.5);
|
||||
pax_pop_2d(buffer);
|
||||
|
||||
ili9341 = get_ili9341();
|
||||
ice40 = get_ice40();
|
||||
|
||||
// LCD test
|
||||
/*uint8_t* framebuffer = heap_caps_malloc(ILI9341_BUFFER_SIZE, MALLOC_CAP_8BIT);
|
||||
if (framebuffer == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate framebuffer");
|
||||
restart();
|
||||
}
|
||||
|
||||
memset(framebuffer, 0, ILI9341_BUFFER_SIZE); // Clear framebuffer
|
||||
res = ili9341_write(ili9341, framebuffer);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write framebuffer to LCD");
|
||||
restart();
|
||||
}
|
||||
pax_apply_2d(buffer, matrix_2d_rotate(a1));
|
||||
pax_push_2d(buffer);
|
||||
pax_apply_2d(buffer, matrix_2d_translate(1, 0));
|
||||
pax_apply_2d(buffer, matrix_2d_rotate(-a0 - a1 + M_PI * 0.5));
|
||||
pax_draw_tri(buffer, color1, 0.25, 0, -0.125, 0.2165, -0.125, -0.2165);
|
||||
pax_pop_2d(buffer);
|
||||
|
||||
free(framebuffer);
|
||||
*/
|
||||
pax_pop_2d(buffer);
|
||||
|
||||
/* Print chip information */
|
||||
return ili9341_write(ili9341, framebuffer);
|
||||
}
|
||||
|
||||
void print_chip_info(void) {
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
printf("This is %s chip with %d CPU core(s), WiFi%s%s, ",
|
||||
|
@ -225,19 +235,203 @@ void app_main(void) {
|
|||
printf("%dMB %s flash\n", spi_flash_get_chip_size() / (1024 * 1024),
|
||||
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external");
|
||||
|
||||
printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());
|
||||
printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());
|
||||
}
|
||||
|
||||
// Initialize the buttons
|
||||
uint8_t* load_file_to_ram(FILE* fd, uint32_t* fsize) {
|
||||
fseek(fd, 0, SEEK_END);
|
||||
*fsize = ftell(fd);
|
||||
fseek(fd, 0, SEEK_SET);
|
||||
uint8_t* file = malloc(*fsize);
|
||||
fread(file, *fsize, 1, fd);
|
||||
return file;
|
||||
}
|
||||
|
||||
esp_err_t load_file_into_psram(FILE* fd) {
|
||||
fseek(fd, 0, SEEK_SET);
|
||||
const uint8_t write_cmd = 0x02;
|
||||
uint32_t amount_read;
|
||||
uint32_t position = 0;
|
||||
uint8_t* tx_buffer = malloc(SPI_MAX_TRANSFER_SIZE);
|
||||
if (tx_buffer == NULL) return ESP_FAIL;
|
||||
|
||||
while(1) {
|
||||
tx_buffer[0] = write_cmd;
|
||||
tx_buffer[1] = (position >> 16);
|
||||
tx_buffer[2] = (position >> 8) & 0xFF;
|
||||
tx_buffer[3] = position & 0xFF;
|
||||
amount_read = fread(&tx_buffer[4], 1, SPI_MAX_TRANSFER_SIZE - 4, fd);
|
||||
if (amount_read < 1) break;
|
||||
ESP_LOGI(TAG, "Writing PSRAM @ %u (%u bytes)", position, amount_read);
|
||||
esp_err_t res = ice40_transaction(ice40, tx_buffer, amount_read + 4, NULL, 0);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Write transaction failed @ %u", position);
|
||||
free(tx_buffer);
|
||||
return res;
|
||||
}
|
||||
position += amount_read;
|
||||
};
|
||||
free(tx_buffer);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t verify_file_in_psram(FILE* fd) {
|
||||
fseek(fd, 0, SEEK_SET);
|
||||
const uint8_t read_cmd = 0x03;
|
||||
uint32_t amount_read;
|
||||
uint32_t position = 0;
|
||||
uint8_t* tx_buffer = malloc(SPI_MAX_TRANSFER_SIZE);
|
||||
if (tx_buffer == NULL) return ESP_FAIL;
|
||||
memset(tx_buffer, 0, SPI_MAX_TRANSFER_SIZE);
|
||||
uint8_t* verify_buffer = malloc(SPI_MAX_TRANSFER_SIZE);
|
||||
if (verify_buffer == NULL) return ESP_FAIL;
|
||||
uint8_t* rx_buffer = malloc(SPI_MAX_TRANSFER_SIZE);
|
||||
if (rx_buffer == NULL) return ESP_FAIL;
|
||||
|
||||
while(1) {
|
||||
tx_buffer[0] = read_cmd;
|
||||
tx_buffer[1] = (position >> 16);
|
||||
tx_buffer[2] = (position >> 8) & 0xFF;
|
||||
tx_buffer[3] = position & 0xFF;
|
||||
amount_read = fread(&verify_buffer[4], 1, SPI_MAX_TRANSFER_SIZE - 4, fd);
|
||||
if (amount_read < 1) break;
|
||||
ESP_LOGI(TAG, "Reading PSRAM @ %u (%u bytes)", position, amount_read);
|
||||
esp_err_t res = ice40_transaction(ice40, tx_buffer, amount_read + 4, rx_buffer, amount_read + 4);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Read transaction failed @ %u", position);
|
||||
free(tx_buffer);
|
||||
return res;
|
||||
}
|
||||
position += amount_read;
|
||||
ESP_LOGI(TAG, "Verifying PSRAM @ %u (%u bytes)", position, amount_read);
|
||||
for (uint32_t i = 4; i < amount_read; i++) {
|
||||
if (rx_buffer[i] != verify_buffer[i]) {
|
||||
ESP_LOGE(TAG, "Verifying PSRAM @ %u failed: %02X != %02X", position + i, rx_buffer[i], verify_buffer[i]);
|
||||
free(tx_buffer);
|
||||
free(rx_buffer);
|
||||
free(verify_buffer);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
};
|
||||
free(tx_buffer);
|
||||
free(rx_buffer);
|
||||
free(verify_buffer);
|
||||
ESP_LOGI(TAG, "PSRAM contents verified!");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void fpga_test(void) {
|
||||
esp_err_t res = mount_sd(SD_CMD, SD_CLK, SD_D0, SD_PWR, "/sd", false, 5);
|
||||
if (res == ESP_OK) {
|
||||
ESP_LOGI(TAG, "SD card mounted");
|
||||
FILE* fpga_passthrough = fopen("/sd/passthrough.bin", "rb");
|
||||
if (fpga_passthrough == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open passthrough.bin (%d)", res);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Loading passthrough bitstream into RAM buffer...");
|
||||
uint32_t fpga_passthrough_bitstream_length;
|
||||
uint8_t* fpga_passthrough_bitstream = load_file_to_ram(fpga_passthrough, &fpga_passthrough_bitstream_length);
|
||||
fclose(fpga_passthrough);
|
||||
ESP_LOGI(TAG, "Loading passthrough bitstream into FPGA...");
|
||||
res = ice40_load_bitstream(ice40, fpga_passthrough_bitstream, fpga_passthrough_bitstream_length);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to load passthrough bitstream into FPGA (%d)", res);
|
||||
return;
|
||||
}
|
||||
free(fpga_passthrough_bitstream);
|
||||
|
||||
FILE* ram_contents = fopen("/sd/ram.bin", "rb");
|
||||
if (ram_contents == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open ram.bin");
|
||||
return;
|
||||
}
|
||||
|
||||
res = load_file_into_psram(ram_contents);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to load RAM contents into PSRAM (%d)", res);
|
||||
fclose(ram_contents);
|
||||
return;
|
||||
}
|
||||
|
||||
res = verify_file_in_psram(ram_contents);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to verify PSRAM contents (%d)", res);
|
||||
fclose(ram_contents);
|
||||
return;
|
||||
}
|
||||
|
||||
FILE* fpga_app = fopen("/sd/app.bin", "rb");
|
||||
if (fpga_app == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open app.bin");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Loading app bitstream into RAM buffer...");
|
||||
uint32_t fpga_app_bitstream_length;
|
||||
uint8_t* fpga_app_bitstream = load_file_to_ram(fpga_app, &fpga_app_bitstream_length);
|
||||
fclose(fpga_app);
|
||||
ESP_LOGI(TAG, "Loading app bitstream into FPGA...");
|
||||
res = ice40_load_bitstream(ice40, fpga_app_bitstream, fpga_app_bitstream_length);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to load app bitstream into FPGA (%d)", res);
|
||||
return;
|
||||
}
|
||||
free(fpga_app_bitstream);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "No SD card, skipping FPGA test");
|
||||
}
|
||||
}
|
||||
|
||||
void appfs_test(void) {
|
||||
esp_err_t res = appfsInit(APPFS_PART_TYPE, APPFS_PART_SUBTYPE);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "AppFS init failed: %d", res);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "AppFS initialized");
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
esp_err_t res;
|
||||
|
||||
res = hardware_init();
|
||||
if (res != ESP_OK) {
|
||||
printf("Failed to initialize hardware!\n");
|
||||
restart();
|
||||
}
|
||||
|
||||
ili9341 = get_ili9341();
|
||||
ice40 = get_ice40();
|
||||
bno055 = get_bno055();
|
||||
|
||||
print_chip_info();
|
||||
|
||||
uint8_t* framebuffer = heap_caps_malloc(ILI9341_BUFFER_SIZE, MALLOC_CAP_8BIT);
|
||||
if (framebuffer == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate framebuffer");
|
||||
restart();
|
||||
}
|
||||
memset(framebuffer, 0, ILI9341_BUFFER_SIZE);
|
||||
|
||||
pax_buf_t buffer;
|
||||
pax_buf_init(&buffer, framebuffer, ILI9341_WIDTH, ILI9341_HEIGHT, PAX_BUF_16_565RGB);
|
||||
pax_apply_2d(&buffer, matrix_2d_translate(buffer.width / 2.0, buffer.height / 2.0));
|
||||
pax_apply_2d(&buffer, matrix_2d_scale(50, 50));
|
||||
|
||||
button_init();
|
||||
|
||||
// Test for connection to RP2040 and to the BNO055 over I2C
|
||||
/*BNO055* bno055 = get_bno055();
|
||||
|
||||
bno055_vector_t rotation_offset;
|
||||
rotation_offset.x = 0;
|
||||
rotation_offset.y = 0;
|
||||
rotation_offset.z = 0;
|
||||
//appfs_test();
|
||||
|
||||
fpga_test();
|
||||
|
||||
/*while (1) {
|
||||
bno055_task(bno055);
|
||||
graphics_task(&buffer, ili9341, framebuffer);
|
||||
}*/
|
||||
/*
|
||||
uint8_t data_out, data_in;
|
||||
|
||||
enum {
|
||||
|
@ -274,9 +468,8 @@ void app_main(void) {
|
|||
} else {
|
||||
printf("GPIO status: %02x\n", data_in);
|
||||
}
|
||||
bno055_task(bno055, &rotation_offset);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}*/
|
||||
}
|
||||
|
||||
// FPGA RAM passthrough test
|
||||
|
||||
|
@ -355,5 +548,7 @@ void app_main(void) {
|
|||
int64_t rx_done_time = esp_timer_get_time();
|
||||
printf("Read took %lld microseconds\r\n", rx_done_time - rx_start_time);
|
||||
result = (((size_of_ram) / (rx_done_time - rx_start_time))*1000*1000)/1024;
|
||||
printf("%u bytes in %lld microseconds = %llu kB/s\r\n", size_of_ram, rx_done_time - rx_start_time, result);
|
||||
printf("%u bytes in %lld microseconds = %llu kB/s\r\n", size_of_ram, rx_done_time - rx_start_time, result);*/
|
||||
|
||||
free(framebuffer);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
# ESP-IDF Partition Table
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
nvs, data, nvs, 0x9000, 0x4000,
|
||||
otadata, data, ota, 0xd000, 0x2000
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
ota_0, app, ota_0, 0x10000, 1536K
|
||||
ota_1, app, ota_1, , 1536K
|
||||
appfs, 0x43, 3, , 13248K
|
||||
|
|
|
|
@ -231,10 +231,10 @@ CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
|||
CONFIG_ESPTOOLPY_FLASHFREQ="80m"
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
|
||||
# CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="4MB"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_DETECT=y
|
||||
CONFIG_ESPTOOLPY_BEFORE_RESET=y
|
||||
# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
|
||||
|
|
Loading…
Reference in a new issue