FPGA passthrough RAM loading, add graphics stack

This commit is contained in:
Renze Nicolai 2022-01-05 23:20:01 +01:00
parent 1b0acb6ecb
commit 1cdd3e9a4b
9 changed files with 1350 additions and 64 deletions

3
.gitmodules vendored
View file

@ -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

View file

@ -0,0 +1,5 @@
idf_component_register(
INCLUDE_DIRS "include"
SRCS "appfs.c"
REQUIRES spi_flash
)

View 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

View 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

View 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

@ -0,0 +1 @@
Subproject commit 034870cf736970ad12bcfecbb6af492509428035

View file

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

View file

@ -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

1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x6000, nvs, data, nvs, 0x9000, 0x4000,
4 otadata, data, ota, 0xd000, 0x2000
5 phy_init, data, phy, 0xf000, 0x1000,
6 factory, app, factory, 0x10000, 1M, ota_0, app, ota_0, 0x10000, 1536K
7 ota_1, app, ota_1, , 1536K
8 appfs, 0x43, 3, , 13248K

View file

@ -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